Skip site navigation (1)Skip section navigation (2)
Date:      Sat, 21 Feb 2015 10:14:37 +0000 (UTC)
From:      Jimmy Olgeni <olgeni@FreeBSD.org>
To:        ports-committers@freebsd.org, svn-ports-all@freebsd.org, svn-ports-head@freebsd.org
Subject:   svn commit: r379495 - in head/lang/erlang: . files
Message-ID:  <201502211014.t1LAEbeP061585@svn.freebsd.org>

next in thread | raw e-mail | index | archive | help
Author: olgeni
Date: Sat Feb 21 10:14:37 2015
New Revision: 379495
URL: https://svnweb.freebsd.org/changeset/ports/379495
QAT: https://qat.redports.org/buildarchive/r379495/

Log:
  Upgrade to version 17.4.1.
  
  - Sort OPTIONS_DEFINE and OPTIONS_DEFAULT.
  - Create SKIP files for applications disabled by port options.
  - Remove disabled applications from ${STAGEDIR}.
  - Check if dtraceall is actually loaded when DTRACE is enabled.

Added:
  head/lang/erlang/files/patch-otp-17.4.1   (contents, props changed)
Deleted:
  head/lang/erlang/files/patch-lib_ssl_src_ssl__handshake.erl
  head/lang/erlang/files/patch-lib_ssl_test_ssl__handshake__SUITE.erl
Modified:
  head/lang/erlang/Makefile
  head/lang/erlang/distinfo

Modified: head/lang/erlang/Makefile
==============================================================================
--- head/lang/erlang/Makefile	Sat Feb 21 10:02:31 2015	(r379494)
+++ head/lang/erlang/Makefile	Sat Feb 21 10:14:37 2015	(r379495)
@@ -2,8 +2,7 @@
 # $FreeBSD$
 
 PORTNAME=	erlang
-PORTVERSION=	17.3
-PORTREVISION=	3
+PORTVERSION=	17.4.1
 PORTEPOCH=	3
 CATEGORIES=	lang parallel java
 MASTER_SITES=	http://www.erlang.org/download/:erlangorg		\
@@ -34,7 +33,7 @@ PLIST_SUB=	ERLANG_LIB=${ERLANG_LIB}
 
 MAKE_JOBS_UNSAFE=yes
 
-OPTIONS_DEFINE=	GCC DOCS GS HIPE JAVA KQUEUE ODBC OPENSSL SCTP SMP THREADS WX DTRACE
+OPTIONS_DEFINE=	DOCS DTRACE GCC GS HIPE JAVA KQUEUE ODBC OPENSSL SCTP SMP THREADS WX
 
 GCC_DESC=	Use current GCC
 HIPE_DESC=	Build native HiPE compiler
@@ -45,9 +44,9 @@ WX_DESC=	Enable WX application
 GS_DESC=	Enable GS application (deprecated)
 DTRACE_DESC=	Enable DTrace support
 
-OPTIONS_DEFAULT=SMP OPENSSL THREADS SCTP KQUEUE
+OPTIONS_DEFAULT=KQUEUE OPENSSL SCTP SMP THREADS
 
-ERL_RELEASE=	${PORTVERSION}
+ERL_RELEASE=	17.4
 
 USES=		gmake perl5
 USE_AUTOTOOLS=	autoconf:env
@@ -56,8 +55,8 @@ USE_RC_SUBR=	epmd
 GNU_CONFIGURE=	yes
 LDFLAGS+=	-L${LOCALBASE}/lib
 
-EI_VSN=		3.7.18
-TOOLS_VSN=	2.7
+EI_VSN=		3.7.20
+TOOLS_VSN=	2.7.1
 
 .include <bsd.port.options.mk>
 
@@ -182,6 +181,19 @@ MAKE_ARGS+=	ARCH=x86
 post-patch:
 	@${ECHO_CMD} "MANPATH ${PREFIX}/lib/erlang/man" > ${WRKDIR}/erlang.conf
 
+pre-configure:
+.if ${PORT_OPTIONS:MDTRACE}
+	@if ! /sbin/kldstat | ${GREP} dtraceall >/dev/null 2>&1; then \
+		${ECHO_CMD}; \
+		${ECHO_CMD} "DTRACE option requires kernel module \"dtraceall\" to be loaded."; \
+		${ECHO_CMD}; \
+		exit 1; \
+	fi
+.endif
+.if ! ${PORT_OPTIONS:MWX}
+	echo "disabled by port options" > ${WRKSRC}/lib/wx/SKIP
+.endif
+
 post-install:
 	${INSTALL_DATA} ${WRKDIR}/erlang.conf ${STAGEDIR}${PREFIX}/etc/man.d/erlang.conf
 	${LN} -sf ../lib/${ERLANG_LIB}/lib/erl_interface-${EI_VSN}/bin/erl_call ${STAGEDIR}${PREFIX}/bin/erl_call
@@ -195,6 +207,15 @@ post-install:
 .if ${PORT_OPTIONS:MDOCS}
 	${TAR} --unlink -xzpf ${DISTDIR}/${DIST_SUBDIR}/${ERLANG_DOCS} \
 		-C ${STAGEDIR}${PREFIX}/lib/${ERLANG_LIB}
+
+	${MV} ${STAGEDIR}${PREFIX}/lib/${ERLANG_LIB}/lib/inets-5.10.4/* \
+		${STAGEDIR}${PREFIX}/lib/${ERLANG_LIB}/lib/inets-5.10.5
+	${RMDIR} ${STAGEDIR}${PREFIX}/lib/${ERLANG_LIB}/lib/inets-5.10.4
+
+	${MV} ${STAGEDIR}${PREFIX}/lib/${ERLANG_LIB}/erts-6.3/* \
+		${STAGEDIR}${PREFIX}/lib/${ERLANG_LIB}/erts-6.3.1
+	${RMDIR} ${STAGEDIR}${PREFIX}/lib/${ERLANG_LIB}/erts-6.3
+
 	${INSTALL_DATA} ${WRKSRC}/lib/dialyzer/doc/*.txt \
 		${STAGEDIR}${PREFIX}/lib/${ERLANG_LIB}/lib/dialyzer-*/doc/
 	@${MKDIR} ${STAGEDIR}${DOCSDIR}
@@ -203,6 +224,24 @@ post-install:
 .endfor
 .endif
 
+.if ! ${PORT_OPTIONS:MGS}
+	${RM} -r ${STAGEDIR}${PREFIX}/lib/erlang/lib/gs-*
+.endif
+.if ! ${PORT_OPTIONS:MJAVA}
+	${RM} -r ${STAGEDIR}${PREFIX}/lib/erlang/lib/jinterface-*
+.endif
+.if ! ${PORT_OPTIONS:MODBC}
+	${RM} -r ${STAGEDIR}${PREFIX}/lib/erlang/lib/odbc-*
+.endif
+.if ! ${PORT_OPTIONS:MOPENSSL}
+	${RM} -r ${STAGEDIR}${PREFIX}/lib/erlang/lib/crypto-*
+	${RM} -r ${STAGEDIR}${PREFIX}/lib/erlang/lib/ssh-*
+	${RM} -r ${STAGEDIR}${PREFIX}/lib/erlang/lib/ssl-*
+.endif
+.if ! ${PORT_OPTIONS:MWX}
+	${RM} -r ${STAGEDIR}${PREFIX}/lib/erlang/lib/wx-*
+.endif
+
 	@cd ${STAGEDIR}${PREFIX}; ${FIND} lib/${ERLANG_LIB}/* -type d -empty \
 		| ${SORT} \
 		| ${SED} -e 's#^#@dir #g' \

Modified: head/lang/erlang/distinfo
==============================================================================
--- head/lang/erlang/distinfo	Sat Feb 21 10:02:31 2015	(r379494)
+++ head/lang/erlang/distinfo	Sat Feb 21 10:14:37 2015	(r379495)
@@ -1,9 +1,9 @@
-SHA256 (erlang/otp_src_17.3.tar.gz) = d4be03eb346016f4ca95d53eb9437ffaa6106762f153620dd98cd7f7733b76e4
-SIZE (erlang/otp_src_17.3.tar.gz) = 67050599
-SHA256 (erlang/otp_doc_man_17.3.tar.gz) = 3fabdac0b0594432fdd8186fa2c74ff49a629a9bcc4174e3bd605a3d4002dab7
-SIZE (erlang/otp_doc_man_17.3.tar.gz) = 1357195
-SHA256 (erlang/otp_doc_html_17.3.tar.gz) = bd8278b34f610d6025549640d4e03d79d2a69d7129ea230c0d71d7e6b231f5b3
-SIZE (erlang/otp_doc_html_17.3.tar.gz) = 33444170
+SHA256 (erlang/otp_src_17.4.tar.gz) = 0d82eda6ae7ac6f0f860093324e540fa514497068ec3b4177800284e8c761f56
+SIZE (erlang/otp_src_17.4.tar.gz) = 67171182
+SHA256 (erlang/otp_doc_man_17.4.tar.gz) = 6c1cdb8e9d367c7b6dc6b20706de9fd0a0f0b7dffd66532663b2a24ed7679a58
+SIZE (erlang/otp_doc_man_17.4.tar.gz) = 1360952
+SHA256 (erlang/otp_doc_html_17.4.tar.gz) = dd42b0104418de18e2247608a337bcd3bb24c59bbc36294deb5fae73ab6c90d6
+SIZE (erlang/otp_doc_html_17.4.tar.gz) = 33483554
 SHA256 (erlang/armstrong_thesis_2003.pdf) = da585b914eb07350e2d6f727db5eb0fb1551f49fc1270f7d4dc079f2c8c1ab7a
 SIZE (erlang/armstrong_thesis_2003.pdf) = 859249
 SHA256 (erlang/bjarnelic.pdf) = 9544d4af5cea324e5c9cf77e670d9561ad61ddc240cdb5c2afecebc0a3d7429b

Added: head/lang/erlang/files/patch-otp-17.4.1
==============================================================================
--- /dev/null	00:00:00 1970	(empty, because file is newly added)
+++ head/lang/erlang/files/patch-otp-17.4.1	Sat Feb 21 10:14:37 2015	(r379495)
@@ -0,0 +1,1358 @@
+diff --git OTP_VERSION OTP_VERSION
+index 5f9cbaa..ae704d6 100644
+--- OTP_VERSION
++++ OTP_VERSION
+@@ -1 +1 @@
+-17.4
++17.4.1
+diff --git erts/doc/src/notes.xml erts/doc/src/notes.xml
+index c896ee0..af0d4d7 100644
+--- erts/doc/src/notes.xml
++++ erts/doc/src/notes.xml
+@@ -30,6 +30,65 @@
+   </header>
+   <p>This document describes the changes made to the ERTS application.</p>
+ 
++<section><title>Erts 6.3.1</title>
++
++    <section><title>Fixed Bugs and Malfunctions</title>
++      <list>
++        <item>
++          <p>
++	    Fix getifaddrs realloc pointer error</p>
++          <p>
++	    When a buffer was exhausted and subsequently reallocated,
++	    we could get an unsafe pointer pointing to faulty memory.</p>
++          <p>
++	    For this to occur we would need to have a large number of
++	    interfaces and a reallocation of memory to a lower
++	    addresses.</p>
++          <p>
++	    The symptom would be garbage returned from
++	    erlang:port_control(Port, 25, [])
++	    (prim_inet:getifaddrs(Port) resulting in a badarg) or a
++	    segmentation fault.</p>
++          <p>
++	    Own Id: OTP-12445</p>
++        </item>
++        <item>
++          <p>
++	    Don't close all file descriptors twice in child_setup</p>
++          <p>
++	    The commit c2b4eab25c907f453a394d382c04cd04e6c06b49
++	    introduced an error in which child_setup erroneously
++	    tried to close all file descriptors twice.</p>
++          <p>
++	    Use closefrom() if available when closing all file
++	    descriptors.</p>
++          <p>
++	    The function closefrom() was only used in the vfork()
++	    case before but is now also used in the fork() case if
++	    available.</p>
++          <p>
++	    Own Id: OTP-12446</p>
++        </item>
++        <item>
++          <p>
++	    During a crashdump all file descriptors are closed to
++	    ensure the closing of the epmd port and to reserve a file
++	    descriptor for the crashdump file.</p>
++          <p>
++	    If a driver (third party library) cannot handle closing
++	    of sockets this could result in a segmentation fault in
++	    which case a crashdump would not be produced. This is now
++	    fixed by only closing inets sockets via an emergency
++	    close callback to the driver and thus closing the epmd
++	    socket.</p>
++          <p>
++	    Own Id: OTP-12447</p>
++        </item>
++      </list>
++    </section>
++
++</section>
++
+ <section><title>Erts 6.3</title>
+ 
+     <section><title>Fixed Bugs and Malfunctions</title>
+diff --git erts/emulator/beam/erl_driver.h erts/emulator/beam/erl_driver.h
+index f9938fc..e498ac7 100644
+--- erts/emulator/beam/erl_driver.h
++++ erts/emulator/beam/erl_driver.h
+@@ -133,7 +133,7 @@ typedef struct {
+ 
+ #define ERL_DRV_EXTENDED_MARKER		(0xfeeeeeed)
+ #define ERL_DRV_EXTENDED_MAJOR_VERSION	3
+-#define ERL_DRV_EXTENDED_MINOR_VERSION	1
++#define ERL_DRV_EXTENDED_MINOR_VERSION	2
+ 
+ /*
+  * The emulator will refuse to load a driver with a major version
+@@ -361,6 +361,9 @@ typedef struct erl_drv_entry {
+     	                        /* Called on behalf of driver_select when
+ 				   it is safe to release 'event'. A typical
+ 				   unix driver would call close(event) */
++    void (*emergency_close)(ErlDrvData drv_data);
++                                /* called when the port is closed abruptly.
++				   specifically when erl_crash_dump is called. */
+     /* When adding entries here, dont forget to pad in obsolete/driver.h */
+ } ErlDrvEntry;
+ 
+diff --git erts/emulator/beam/global.h erts/emulator/beam/global.h
+index 891046a..32a2dc4 100644
+--- erts/emulator/beam/global.h
++++ erts/emulator/beam/global.h
+@@ -160,6 +160,7 @@ struct erts_driver_t_ {
+     void (*ready_async)(ErlDrvData drv_data, ErlDrvThreadData thread_data); /* Might be NULL */ 
+     void (*process_exit)(ErlDrvData drv_data, ErlDrvMonitor *monitor);
+     void (*stop_select)(ErlDrvEvent event, void*); /* Might be NULL */
++    void (*emergency_close)(ErlDrvData drv_data);  /* Might be NULL */
+ };
+ 
+ extern erts_driver_t *driver_list;
+@@ -852,6 +853,7 @@ Uint erts_port_ioq_size(Port *pp);
+ void erts_stale_drv_select(Eterm, ErlDrvPort, ErlDrvEvent, int, int);
+ 
+ Port *erts_get_heart_port(void);
++void erts_emergency_close_ports(void);
+ 
+ #if defined(ERTS_SMP) && defined(ERTS_ENABLE_LOCK_COUNT)
+ void erts_lcnt_enable_io_lock_count(int enable);
+diff --git erts/emulator/beam/io.c erts/emulator/beam/io.c
+index 9ae973e..012a7d1 100644
+--- erts/emulator/beam/io.c
++++ erts/emulator/beam/io.c
+@@ -7349,6 +7349,8 @@ no_stop_select_callback(ErlDrvEvent event, void* private)
+     erts_send_error_to_logger_nogl(dsbufp);
+ }
+ 
++#define IS_DRIVER_VERSION_GE(DE,MAJOR,MINOR) \
++    ((DE)->major_version >= (MAJOR) && (DE)->minor_version >= (MINOR))
+ 
+ static int
+ init_driver(erts_driver_t *drv, ErlDrvEntry *de, DE_Handle *handle)
+@@ -7396,6 +7398,7 @@ init_driver(erts_driver_t *drv, ErlDrvEntry *de, DE_Handle *handle)
+     drv->timeout = de->timeout ? de->timeout : no_timeout_callback;
+     drv->ready_async = de->ready_async;
+     drv->process_exit = de->process_exit;
++    drv->emergency_close = IS_DRIVER_VERSION_GE(de,3,2) ? de->emergency_close : NULL;
+     if (de->stop_select)
+ 	drv->stop_select = de->stop_select;
+     else
+@@ -7414,6 +7417,8 @@ init_driver(erts_driver_t *drv, ErlDrvEntry *de, DE_Handle *handle)
+     }
+ }
+ 
++#undef IS_DRIVER_VERSION_GE
++
+ void
+ erts_destroy_driver(erts_driver_t *drv)
+ {
+@@ -7557,7 +7562,7 @@ Port *erts_get_heart_port(void)
+ 	if (!port)
+ 	    continue;
+ 	/* only examine undead or alive ports */
+-	if (erts_atomic32_read_nob(&port->state) & ERTS_PORT_SFLGS_DEAD)
++	if (erts_atomic32_read_nob(&port->state) & ERTS_PORT_SFLGS_INVALID_DRIVER_LOOKUP)
+ 	    continue;
+ 	/* immediate atom compare */
+ 	reg = port->common.u.alive.reg;
+@@ -7568,3 +7573,23 @@ Port *erts_get_heart_port(void)
+ 
+     return NULL;
+ }
++
++void erts_emergency_close_ports(void)
++{
++    int ix, max = erts_ptab_max(&erts_port);
++
++    for (ix = 0; ix < max; ix++) {
++	Port *port = erts_pix2port(ix);
++
++	if (!port)
++	    continue;
++	/* only examine undead or alive ports */
++	if (erts_atomic32_read_nob(&port->state) & ERTS_PORT_SFLGS_INVALID_DRIVER_LOOKUP)
++	    continue;
++
++	/* emergency close socket */
++	if (port->drv_ptr->emergency_close) {
++	    port->drv_ptr->emergency_close((ErlDrvData) port->drv_data);
++	}
++    }
++}
+diff --git erts/emulator/drivers/common/inet_drv.c erts/emulator/drivers/common/inet_drv.c
+index db8a251..04721d2 100644
+--- erts/emulator/drivers/common/inet_drv.c
++++ erts/emulator/drivers/common/inet_drv.c
+@@ -268,14 +268,13 @@ static BOOL (WINAPI *fpSetHandleInformation)(HANDLE,DWORD,DWORD);
+ #define sock_htonl(x)              htonl((x))
+ #define sock_send(s,buf,len,flag)  send((s),(buf),(len),(flag))
+ #define sock_sendv(s, vec, size, np, flag) \
+-                WSASend((s),(WSABUF*)(vec),\
+-				   (size),(np),(flag),NULL,NULL)
++            WSASend((s),(WSABUF*)(vec),(size),(np),(flag),NULL,NULL)
+ #define sock_recv(s,buf,len,flag)  recv((s),(buf),(len),(flag))
+ 
+ #define sock_recvfrom(s,buf,blen,flag,addr,alen) \
+-                recvfrom((s),(buf),(blen),(flag),(addr),(alen))
++	    recvfrom((s),(buf),(blen),(flag),(addr),(alen))
+ #define sock_sendto(s,buf,blen,flag,addr,alen) \
+-                sendto((s),(buf),(blen),(flag),(addr),(alen))
++	    sendto((s),(buf),(blen),(flag),(addr),(alen))
+ #define sock_hostname(buf, len)    gethostname((buf), (len))
+ 
+ #define sock_getservbyname(name,proto) getservbyname((name),(proto))
+@@ -360,9 +359,9 @@ static ssize_t writev_fallback(int fd, const struct iovec *iov, int iovcnt, int
+ #define sock_accept(s, addr, len)   accept((s), (addr), (len))
+ #define sock_send(s,buf,len,flag)   inet_send((s),(buf),(len),(flag))
+ #define sock_sendto(s,buf,blen,flag,addr,alen) \
+-                sendto((s),(buf),(blen),(flag),(addr),(alen))
++	    sendto((s),(buf),(blen),(flag),(addr),(alen))
+ #define sock_sendv(s, vec, size, np, flag) \
+-		(*(np) = writev_fallback((s), (struct iovec*)(vec), (size), (*(np))))
++	    (*(np) = writev_fallback((s), (struct iovec*)(vec), (size), (*(np))))
+ #define sock_sendmsg(s,msghdr,flag) sendmsg((s),(msghdr),(flag))
+ 
+ #define sock_open(af, type, proto)  socket((af), (type), (proto))
+@@ -1178,6 +1177,7 @@ static ErlDrvSSizeT tcp_inet_ctl(ErlDrvData, unsigned int,
+ static void tcp_inet_timeout(ErlDrvData);
+ static void tcp_inet_process_exit(ErlDrvData, ErlDrvMonitor *); 
+ static void inet_stop_select(ErlDrvEvent, void*); 
++static void inet_emergency_close(ErlDrvData);
+ #ifdef __WIN32__
+ static void tcp_inet_event(ErlDrvData, ErlDrvEvent);
+ static void find_dynamic_functions(void);
+@@ -1288,7 +1288,8 @@ static struct erl_drv_entry tcp_inet_driver_entry =
+     ERL_DRV_FLAG_USE_PORT_LOCKING|ERL_DRV_FLAG_SOFT_BUSY,
+     NULL,
+     tcp_inet_process_exit,
+-    inet_stop_select
++    inet_stop_select,
++    inet_emergency_close
+ };
+ 
+ 
+@@ -1341,7 +1342,8 @@ static struct erl_drv_entry udp_inet_driver_entry =
+     ERL_DRV_FLAG_USE_PORT_LOCKING,
+     NULL,
+     NULL,
+-    inet_stop_select
++    inet_stop_select,
++    inet_emergency_close
+ };
+ #endif
+ 
+@@ -1375,7 +1377,8 @@ static struct erl_drv_entry sctp_inet_driver_entry =
+     ERL_DRV_FLAG_USE_PORT_LOCKING,
+     NULL,
+     NULL, /* process_exit */
+-    inet_stop_select
++    inet_stop_select,
++    inet_emergency_close
+ };
+ #endif
+ 
+@@ -1421,7 +1424,7 @@ static int packet_inet_input(udp_descriptor* udesc, HANDLE event);
+ static int packet_inet_output(udp_descriptor* udesc, HANDLE event);
+ #endif
+ 
+-/* convert descriptor poiner to inet_descriptor pointer */
++/* convert descriptor pointer to inet_descriptor pointer */
+ #define INETP(d) (&(d)->inet)
+ 
+ #ifdef __OSE__
+@@ -4721,6 +4724,36 @@ static char* sockaddr_to_buf(struct sockaddr* addr, char* ptr, char* end)
+     return NULL;
+ }
+ 
++/* sockaddr_bufsz_need
++ * Returns the number of bytes needed to store the information
++ * through sockaddr_to_buf
++ */
++
++static size_t sockaddr_bufsz_need(struct sockaddr* addr)
++{
++    if (addr->sa_family == AF_INET || addr->sa_family == 0) {
++	return 1 + sizeof(struct in_addr);
++    }
++#if defined(HAVE_IN6) && defined(AF_INET6)
++    else if (addr->sa_family == AF_INET6) {
++	return 1 + sizeof(struct in6_addr);
++    }
++#endif
++#if defined(AF_LINK)
++    if (addr->sa_family == AF_LINK) {
++	struct sockaddr_dl *sdl_p = (struct sockaddr_dl*) addr;
++	return 2 + sdl_p->sdl_alen;
++    }
++#endif
++#if defined(AF_PACKET) && defined(HAVE_NETPACKET_PACKET_H)
++    else if(addr->sa_family == AF_PACKET) {
++	struct sockaddr_ll *sll_p = (struct sockaddr_ll*) addr;
++	return 2 + sll_p->sll_halen;
++    }
++#endif
++    return 0;
++}
++
+ static char* buf_to_sockaddr(char* ptr, char* end, struct sockaddr* addr)
+ {
+     buf_check(ptr,end,1);
+@@ -5799,6 +5832,11 @@ done:
+ }
+ 
+ #elif defined(HAVE_GETIFADDRS)
++#ifdef  DEBUG
++#define GETIFADDRS_BUFSZ (1)
++#else
++#define GETIFADDRS_BUFSZ (512)
++#endif
+ 
+ static ErlDrvSSizeT inet_ctl_getifaddrs(inet_descriptor* desc_p,
+ 					char **rbuf_pp, ErlDrvSizeT rsize)
+@@ -5809,15 +5847,15 @@ static ErlDrvSSizeT inet_ctl_getifaddrs(inet_descriptor* desc_p,
+     char *buf_p;
+     char *buf_alloc_p;
+ 
+-    buf_size = 512;
+-    buf_alloc_p = ALLOC(buf_size);
++    buf_size = GETIFADDRS_BUFSZ;
++    buf_alloc_p = ALLOC(GETIFADDRS_BUFSZ);
+     buf_p = buf_alloc_p;
+ #   define BUF_ENSURE(Size)						\
+     do {								\
+ 	int NEED_, GOT_ = buf_p - buf_alloc_p;				\
+ 	NEED_ = GOT_ + (Size);						\
+ 	if (NEED_ > buf_size) {						\
+-	    buf_size = NEED_ + 512;					\
++	    buf_size = NEED_ + GETIFADDRS_BUFSZ;			\
+ 	    buf_alloc_p = REALLOC(buf_alloc_p, buf_size);		\
+ 	    buf_p = buf_alloc_p + GOT_;					\
+ 	}								\
+@@ -5830,7 +5868,7 @@ static ErlDrvSSizeT inet_ctl_getifaddrs(inet_descriptor* desc_p,
+ 	    while (! (P_ = sockaddr_to_buf((sa), buf_p,			\
+ 					   buf_alloc_p+buf_size))) {	\
+ 		int GOT_ = buf_p - buf_alloc_p;				\
+-		buf_size += 512;					\
++		buf_size += GETIFADDRS_BUFSZ;				\
+ 		buf_alloc_p = REALLOC(buf_alloc_p, buf_size);		\
+ 		buf_p = buf_alloc_p + GOT_;				\
+ 	    }								\
+@@ -5887,10 +5925,11 @@ static ErlDrvSSizeT inet_ctl_getifaddrs(inet_descriptor* desc_p,
+ 		     || ifa_p->ifa_addr->sa_family == AF_PACKET
+ #endif
+ 		     ) {
+-		char *bp = buf_p;
+-		BUF_ENSURE(1);
+-		SOCKADDR_TO_BUF(INET_IFOPT_HWADDR, ifa_p->ifa_addr);
+-		if (buf_p - bp < 4) buf_p = bp; /* Empty hwaddr */
++		size_t need = sockaddr_bufsz_need(ifa_p->ifa_addr);
++		if (need > 3) {
++		    BUF_ENSURE(1 + need);
++		    SOCKADDR_TO_BUF(INET_IFOPT_HWADDR, ifa_p->ifa_addr);
++		}
+ 	    }
+ #endif
+ 	}
+@@ -5905,6 +5944,7 @@ static ErlDrvSSizeT inet_ctl_getifaddrs(inet_descriptor* desc_p,
+     return buf_size;
+ #   undef BUF_ENSURE
+ }
++#undef GETIFADDRS_BUFSZ
+ 
+ #else
+ 
+@@ -8204,6 +8244,19 @@ static void inet_stop(inet_descriptor* desc)
+     FREE(desc);
+ }
+ 
++static void inet_emergency_close(ErlDrvData data)
++{
++    /* valid for any (UDP, TCP or SCTP) descriptor */
++    tcp_descriptor* tcp_desc = (tcp_descriptor*)data;
++    inet_descriptor* desc = INETP(tcp_desc);
++    DEBUGF(("inet_emergency_close(%ld) {s=%d\r\n",
++	    (long)desc->port, desc->s));
++    if (desc->s != INVALID_SOCKET) {
++	sock_close(desc->s);
++    }
++}
++
++
+ static void set_default_msgq_limits(ErlDrvPort port)
+ {
+     ErlDrvSizeT q_high = INET_HIGH_MSGQ_WATERMARK;
+diff --git erts/emulator/sys/unix/erl_child_setup.c erts/emulator/sys/unix/erl_child_setup.c
+index 94eb6b1..5ad92da 100644
+--- erts/emulator/sys/unix/erl_child_setup.c
++++ erts/emulator/sys/unix/erl_child_setup.c
+@@ -101,7 +101,9 @@ main(int argc, char *argv[])
+     if (sscanf(argv[CS_ARGV_FD_CR_IX], "%d:%d", &from, &to) != 2)
+ 	return 1;
+ 
+-#if defined(__ANDROID__)
++#if defined(HAVE_CLOSEFROM)
++    closefrom(from);
++#elif defined(__ANDROID__)
+     for (i = from; i <= to; i++) {
+ 	if (i!=__system_properties_fd)
+ 	    (void) close(i);
+@@ -109,13 +111,6 @@ main(int argc, char *argv[])
+ #else
+     for (i = from; i <= to; i++)
+ 	(void) close(i);
+-#endif /* __ANDROID__ */
+-
+-#if defined(HAVE_CLOSEFROM)
+-    closefrom(from);
+-#else
+-    for (i = from; i <= to; i++)
+-	(void) close(i);
+ #endif
+ 
+     if (!(argv[CS_ARGV_WD_IX][0] == '.' && argv[CS_ARGV_WD_IX][1] == '\0')
+@@ -147,8 +142,6 @@ main(int argc, char *argv[])
+     return 1;
+ }
+ 
+-
+-
+ #if defined(__ANDROID__)
+ int __system_properties_fd(void)
+ {
+diff --git erts/emulator/sys/unix/sys.c erts/emulator/sys/unix/sys.c
+index 0d677d5..cd87b32 100644
+--- erts/emulator/sys/unix/sys.c
++++ erts/emulator/sys/unix/sys.c
+@@ -202,8 +202,6 @@ static erts_smp_atomic_t sys_misc_mem_sz;
+ #if defined(ERTS_SMP)
+ static void smp_sig_notify(char c);
+ static int sig_notify_fds[2] = {-1, -1};
+-#elif defined(USE_THREADS)
+-static int async_fd[2];
+ #endif
+ 
+ #if CHLDWTHR || defined(ERTS_SMP)
+@@ -246,6 +244,8 @@ static void note_child_death(int, int);
+ static void* child_waiter(void *);
+ #endif
+ 
++static int crashdump_companion_cube_fd = -1;
++
+ /********************* General functions ****************************/
+ 
+ /* This is used by both the drivers and general I/O, must be set early */
+@@ -575,6 +575,14 @@ erts_sys_pre_init(void)
+       close(fd);
+     }
+ 
++    /* We need a file descriptor to close in the crashdump creation.
++     * We close this one to be sure we can get a fd for our real file ...
++     * so, we create one here ... a stone to carry all the way home.
++     */
++
++    crashdump_companion_cube_fd = open("/dev/null", O_RDONLY);
++
++    /* don't lose it, there will be cake */
+ }
+ 
+ void
+@@ -719,14 +727,13 @@ static ERTS_INLINE int
+ prepare_crash_dump(int secs)
+ {
+ #define NUFBUF (3)
+-    int i, max;
++    int i;
+     char env[21]; /* enough to hold any 64-bit integer */
+     size_t envsz;
+     DeclareTmpHeapNoproc(heap,NUFBUF);
+     Port *heart_port;
+     Eterm *hp = heap;
+     Eterm list = NIL;
+-    int heart_fd[2] = {-1,-1};
+     int has_heart = 0;
+ 
+     UseTmpHeapNoproc(NUFBUF);
+@@ -749,43 +756,22 @@ prepare_crash_dump(int secs)
+ 	alarm((unsigned int)secs);
+     }
+ 
++    /* close all viable sockets via emergency close callbacks.
++     * Specifically we want to close epmd sockets.
++     */
++
++    erts_emergency_close_ports();
++
+     if (heart_port) {
+-	/* hearts input fd
+-	 * We "know" drv_data is the in_fd since the port is started with read|write
+-	 */
+-	heart_fd[0] = (int)heart_port->drv_data;
+-	heart_fd[1] = (int)driver_data[heart_fd[0]].ofd;
+-	has_heart   = 1;
+-
++	has_heart = 1;
+ 	list = CONS(hp, make_small(8), list); hp += 2;
+-
+ 	/* send to heart port, CMD = 8, i.e. prepare crash dump =o */
+ 	erts_port_output(NULL, ERTS_PORT_SIG_FLG_FORCE_IMM_CALL, heart_port,
+ 			 heart_port->common.id, list, NULL);
+     }
+ 
+-    /* Make sure we unregister at epmd (unknown fd) and get at least
+-       one free filedescriptor (for erl_crash.dump) */
+-
+-    max = max_files;
+-    if (max < 1024)
+-	max = 1024;
+-    for (i = 3; i < max; i++) {
+-#if defined(ERTS_SMP)
+-	/* We don't want to close the signal notification pipe... */
+-	if (i == sig_notify_fds[0] || i == sig_notify_fds[1])
+-	    continue;
+-#elif defined(USE_THREADS)
+-	/* We don't want to close the async notification pipe... */
+-	if (i == async_fd[0] || i == async_fd[1])
+-	    continue;
+-#endif
+-	/* We don't want to close our heart yet ... */
+-	if (i == heart_fd[0] || i == heart_fd[1])
+-	    continue;
+-
+-	close(i);
+-    }
++    /* Make sure we have a fd for our crashdump file. */
++    close(crashdump_companion_cube_fd);
+ 
+     envsz = sizeof(env);
+     i = erts_sys_getenv__("ERL_CRASH_DUMP_NICE", env, &envsz);
+@@ -1574,9 +1560,13 @@ static ErlDrvData spawn_start(ErlDrvPort port_num, char* name, SysDriverOpts* op
+ 			goto child_error;
+ 	    }
+ 
++#if defined(HAVE_CLOSEFROM)
++	    closefrom(opts->use_stdio ? 3 : 5);
++#else
+ 	    for (i = opts->use_stdio ? 3 : 5; i < max_files; i++)
+ 		(void) close(i);
+-	    
++#endif
++
+ 	    if (opts->wd && chdir(opts->wd) < 0)
+ 		goto child_error;
+ 
+diff --git erts/vsn.mk erts/vsn.mk
+index d0dc8f7..e4b071b 100644
+--- erts/vsn.mk
++++ erts/vsn.mk
+@@ -17,7 +17,7 @@
+ # %CopyrightEnd%
+ # 
+ 
+-VSN = 6.3
++VSN = 6.3.1
+ 
+ # Port number 4365 in 4.2
+ # Port number 4366 in 4.3
+diff --git lib/inets/doc/src/httpd.xml lib/inets/doc/src/httpd.xml
+index 4ca038c..20c8a6b 100644
+--- lib/inets/doc/src/httpd.xml
++++ lib/inets/doc/src/httpd.xml
+@@ -4,7 +4,7 @@
+ <erlref>
+   <header>
+     <copyright>
+-      <year>1997</year><year>2013</year>
++      <year>1997</year><year>2015</year>
+       <holder>Ericsson AB. All Rights Reserved.</holder>
+     </copyright>
+     <legalnotice>
+@@ -249,7 +249,16 @@
+ 	<p>Limits the size of the message header of HTTP request.
+ 	Defaults to 10240. </p>
+       </item>
+-      
++
++      <marker id="prop_max_content_length"></marker>
++      <tag>{max_content_length, integer()}</tag>
++      <item>
++	<p>Maximum Content-Length in an incoming request, in bytes. Requests
++	with content larger than this are answered with Status 413.
++	Defaults to 100000000 (100 MB).
++	</p>
++      </item>
++
+       <marker id="prop_max_uri"></marker>
+       <tag>{max_uri_size, integer()}</tag>
+       <item>
+diff --git lib/inets/doc/src/notes.xml lib/inets/doc/src/notes.xml
+index fb70344..7f73aa5 100644
+--- lib/inets/doc/src/notes.xml
++++ lib/inets/doc/src/notes.xml
+@@ -32,7 +32,40 @@
+     <file>notes.xml</file>
+   </header>
+   
+-  <section><title>Inets 5.10.4</title>
++  <section><title>Inets 5.10.5</title>
++
++    <section><title>Fixed Bugs and Malfunctions</title>
++      <list>
++        <item>
++          <p>
++	    mod_alias now handles https-URIs properly</p>
++          <p>
++	    Consistent view of configuration parameter
++	    keep_alive_timeout, should be presented in the
++	    httpd:info/[1,2] function in the same unit as it is
++	    inputted.</p>
++          <p>
++	    Own Id: OTP-12436 Aux Id: seq12786 </p>
++        </item>
++      </list>
++    </section>
++
++
++    <section><title>Improvements and New Features</title>
++      <list>
++        <item>
++          <p>
++	    Gracefully handle invalid content-lenght headers instead
++	    of crashing in list_to_integer.</p>
++          <p>
++	    Own Id: OTP-12429</p>
++        </item>
++      </list>
++    </section>
++
++</section>
++
++<section><title>Inets 5.10.4</title>
+ 
+     <section><title>Fixed Bugs and Malfunctions</title>
+       <list>
+diff --git lib/inets/src/http_lib/http_internal.hrl lib/inets/src/http_lib/http_internal.hrl
+index 53b776c..5442574 100644
+--- lib/inets/src/http_lib/http_internal.hrl
++++ lib/inets/src/http_lib/http_internal.hrl
+@@ -1,7 +1,7 @@
+ %%
+ %% %CopyrightBegin%
+ %%
+-%% Copyright Ericsson AB 2002-2014. All Rights Reserved.
++%% Copyright Ericsson AB 2002-2015. All Rights Reserved.
+ %%
+ %% The contents of this file are subject to the Erlang Public License,
+ %% Version 1.1, (the "License"); you may not use this file except in
+@@ -28,6 +28,7 @@
+ -define(HTTP_MAX_URI_SIZE,    nolimit).
+ -define(HTTP_MAX_VERSION_STRING, 8).
+ -define(HTTP_MAX_METHOD_STRING, 20).
++-define(HTTP_MAX_CONTENT_LENGTH, 100000000).
+ 
+ -ifndef(HTTP_DEFAULT_SSL_KIND).
+ -define(HTTP_DEFAULT_SSL_KIND, essl).
+diff --git lib/inets/src/http_lib/http_request.erl lib/inets/src/http_lib/http_request.erl
+index f295453..a0833dd 100644
+--- lib/inets/src/http_lib/http_request.erl
++++ lib/inets/src/http_lib/http_request.erl
+@@ -1,7 +1,7 @@
+ %%
+ %% %CopyrightBegin%
+ %% 
+-%% Copyright Ericsson AB 2005-2014. All Rights Reserved.
++%% Copyright Ericsson AB 2005-2015. All Rights Reserved.
+ %% 
+ %% The contents of this file are subject to the Erlang Public License,
+ %% Version 1.1, (the "License"); you may not use this file except in
+@@ -21,8 +21,16 @@
+ 
+ -include("http_internal.hrl").
+ 
+--export([headers/2, http_headers/1, is_absolut_uri/1]).
++-export([headers/2, http_headers/1, is_absolut_uri/1, key_value/1]).
+ 
++
++key_value(KeyValueStr) ->
++    case lists:splitwith(fun($:) -> false; (_) -> true end, KeyValueStr) of
++	{Key, [$: | Value]} ->
++	    {http_util:to_lower(string:strip(Key)),  string:strip(Value)};
++	{_, []} -> 
++	    undefined
++    end.
+ %%-------------------------------------------------------------------------
+ %% headers(HeaderList, #http_request_h{}) -> #http_request_h{}
+ %%   HeaderList - ["HeaderField:Value"]     	
+@@ -34,14 +42,12 @@
+ %%-------------------------------------------------------------------------
+ headers([], Headers) ->
+     Headers;
+-headers([Header | Tail], Headers) ->  
+-    case lists:splitwith(fun($:) -> false; (_) -> true end, Header) of
+-	{Key, [$: | Value]}  ->
+-	    headers(Tail, headers(http_util:to_lower(string:strip(Key)), 
+-				  string:strip(Value), Headers));
+-	{_, []} -> 
+-	    headers(Tail, Headers)
+-    end.
++headers([{Key, Value} | Tail], Headers) ->  
++    headers(Tail, headers(Key, Value, Headers));
++headers([undefined], Headers) -> 
++    Headers;
++headers(KeyValues, Headers) -> 
++    headers([key_value(KeyValue) || KeyValue <-  KeyValues], Headers).
+ 
+ %%-------------------------------------------------------------------------
+ %% headers(#http_request_h{}) -> HeaderList
+diff --git lib/inets/src/http_server/httpd_conf.erl lib/inets/src/http_server/httpd_conf.erl
+index 27446ca..55698d5 100644
+--- lib/inets/src/http_server/httpd_conf.erl
++++ lib/inets/src/http_server/httpd_conf.erl
+@@ -1,7 +1,7 @@
+ %%
+ %% %CopyrightBegin%
+ %%
+-%% Copyright Ericsson AB 1997-2013. All Rights Reserved.
++%% Copyright Ericsson AB 1997-2015. All Rights Reserved.
+ %%
+ %% The contents of this file are subject to the Erlang Public License,
+ %% Version 1.1, (the "License"); you may not use this file except in
+@@ -205,13 +205,13 @@ load("MaxURISize " ++ MaxHeaderSize, []) ->
+                           " is an invalid number of MaxHeaderSize")}
+     end;
+ 
+-load("MaxBodySize " ++ MaxBodySize, []) ->
+-    case make_integer(MaxBodySize) of
++load("MaxContentLength " ++ Max, []) ->
++    case make_integer(Max) of
+         {ok, Integer} ->
+-            {ok, [], {max_body_size,Integer}};
++            {ok, [], {max_content_length, Integer}};
+         {error, _} ->
+-            {error, ?NICE(clean(MaxBodySize) ++
+-                          " is an invalid number of MaxBodySize")}
++            {error, ?NICE(clean(Max) ++
++			      " is an invalid number of MaxContentLength")}
+     end;
+ 
+ load("ServerName " ++ ServerName, []) ->
+@@ -337,7 +337,7 @@ load("MaxKeepAliveRequest " ++  MaxRequests, []) ->
+ load("KeepAliveTimeout " ++ Timeout, []) ->
+     case make_integer(Timeout) of
+ 	{ok, Integer} ->
+-	    {ok, [], {keep_alive_timeout, Integer*1000}};
++	    {ok, [], {keep_alive_timeout, Integer}};
+ 	{error, _} ->
+ 	    {error, ?NICE(clean(Timeout)++" is an invalid KeepAliveTimeout")}
+     end;
+@@ -569,6 +569,12 @@ validate_config_params([{max_body_size, Value} | Rest])
+ validate_config_params([{max_body_size, Value} | _]) -> 
+     throw({max_body_size, Value});
+ 
++validate_config_params([{max_content_length, Value} | Rest]) 
++  when is_integer(Value) andalso (Value > 0) ->
++    validate_config_params(Rest);
++validate_config_params([{max_content_length, Value} | _]) -> 
++    throw({max_content_length, Value});
++
+ validate_config_params([{server_name, Value} | Rest])  
+   when is_list(Value) ->
+     validate_config_params(Rest);
+@@ -635,7 +641,7 @@ validate_config_params([{max_keep_alive_request, Value} | Rest])
+   when is_integer(Value) andalso (Value > 0) ->
+     validate_config_params(Rest);
+ validate_config_params([{max_keep_alive_request, Value} | _]) ->
+-    throw({max_header_size, Value});
++    throw({max_keep_alive_request, Value});
+ 
+ validate_config_params([{keep_alive_timeout, Value} | Rest]) 
+   when is_integer(Value) andalso (Value >= 0) ->
+@@ -799,7 +805,7 @@ store({server_tokens, ServerTokens} = Entry, _ConfigList) ->
+     Server = server(ServerTokens), 
+     {ok, [Entry, {server, Server}]};
+ store({keep_alive_timeout, KeepAliveTimeout}, _ConfigList) ->
+-    {ok, {keep_alive_timeout, KeepAliveTimeout * 1000}};
++    {ok, {keep_alive_timeout, KeepAliveTimeout}};
+ store(ConfigListEntry, _ConfigList) ->
+     {ok, ConfigListEntry}.
+ 
+diff --git lib/inets/src/http_server/httpd_request.erl lib/inets/src/http_server/httpd_request.erl
+index 712c735..6985065 100644
+--- lib/inets/src/http_server/httpd_request.erl
++++ lib/inets/src/http_server/httpd_request.erl
+@@ -1,7 +1,7 @@
+ %%
+ %% %CopyrightBegin%
+ %%
+-%% Copyright Ericsson AB 2005-2014. All Rights Reserved.
++%% Copyright Ericsson AB 2005-2015. All Rights Reserved.
+ %%
+ %% The contents of this file are subject to the Erlang Public License,
+ %% Version 1.1, (the "License"); you may not use this file except in
+@@ -118,18 +118,17 @@ validate(Method, Uri, Version) ->
+ %% create it.
+ %% ----------------------------------------------------------------------
+ update_mod_data(ModData, Method, RequestURI, HTTPVersion, Headers)-> 
+-    ParsedHeaders =  tagup_header(Headers),
+-    PersistentConn = get_persistens(HTTPVersion, ParsedHeaders, 
++    PersistentConn = get_persistens(HTTPVersion, Headers, 
+ 				    ModData#mod.config_db),
+     {ok, ModData#mod{data = [],
+ 		     method = Method,
+ 		     absolute_uri = format_absolute_uri(RequestURI, 
+-							ParsedHeaders),
++							Headers),
+ 		     request_uri = format_request_uri(RequestURI),
+ 		     http_version = HTTPVersion,
+ 		     request_line = Method ++ " " ++ RequestURI ++ 
+ 		     " " ++ HTTPVersion,
+-		     parsed_header = ParsedHeaders,
++		     parsed_header = Headers,
+ 		     connection = PersistentConn}}.
+ 
+ %%%========================================================================
+@@ -146,14 +145,14 @@ parse_method(_, _, _, Max, _, _) ->
+     %% We do not know the version of the client as it comes after the
+     %% method send the lowest version in the response so that the client
+     %% will be able to handle it.
+-    {error, {too_long, Max, 413, "Method unreasonably long"}, lowest_version()}.
++    {error, {size_error, Max, 413, "Method unreasonably long"}, lowest_version()}.
+ 
+ parse_uri(_, _, Current, MaxURI, _, _) 
+   when (Current > MaxURI) andalso (MaxURI =/= nolimit) -> 
+     %% We do not know the version of the client as it comes after the
+     %% uri send the lowest version in the response so that the client
+     %% will be able to handle it.
+-    {error, {too_long, MaxURI, 414, "URI unreasonably long"},lowest_version()};
++    {error, {size_error, MaxURI, 414, "URI unreasonably long"},lowest_version()};
+ parse_uri(<<>>, URI, Current, Max, MaxSizes, Result) ->
+     {?MODULE, parse_uri, [URI, Current, Max, MaxSizes, Result]};
+ parse_uri(<<?SP, Rest/binary>>, URI, _, _, MaxSizes, Result) -> 
+@@ -179,12 +178,12 @@ parse_version(<<?CR>> = Data, Version, Current, Max, MaxSizes, Result) ->
+ parse_version(<<Octet, Rest/binary>>, Version, Current, Max, MaxSizes, Result)  when Current =< Max ->
+     parse_version(Rest, [Octet | Version], Current + 1, Max, MaxSizes, Result);
+ parse_version(_, _, _, Max,_,_) ->
+-    {error, {too_long, Max, 413, "Version string unreasonably long"}, lowest_version()}.
++    {error, {size_error, Max, 413, "Version string unreasonably long"}, lowest_version()}.
+ 
+ parse_headers(_, _, _, Current, Max, _, Result) 
+   when Max =/= nolimit andalso Current > Max -> 
+     HttpVersion = lists:nth(3, lists:reverse(Result)),
+-    {error, {too_long, Max, 413, "Headers unreasonably long"}, HttpVersion};
++    {error, {size_error, Max, 413, "Headers unreasonably long"}, HttpVersion};
+ 
+ parse_headers(<<>>, Header, Headers, Current, Max, MaxSizes, Result) ->
+     {?MODULE, parse_headers, [<<>>, Header, Headers, Current, Max, 
+@@ -204,14 +203,22 @@ parse_headers(<<?CR,?LF,?CR,?LF,Body/binary>>, [], [], _, _,  _, Result) ->
+ 					     Result])),
+     {ok, NewResult};
+ parse_headers(<<?CR,?LF,?CR,?LF,Body/binary>>, Header, Headers, _, _,
+-	      _, Result) ->
+-    HTTPHeaders = [lists:reverse(Header) | Headers],
+-    RequestHeaderRcord = 
+-	http_request:headers(HTTPHeaders, #http_request_h{}),
+-    NewResult = 
+-	list_to_tuple(lists:reverse([Body, {RequestHeaderRcord, 
+-						    HTTPHeaders} | Result])),
+-    {ok, NewResult};
++	      MaxSizes, Result) ->
++    case http_request:key_value(lists:reverse(Header)) of
++	undefined -> %% Skip headers with missing :
++	    {ok, list_to_tuple(lists:reverse([Body, {http_request:headers(Headers, #http_request_h{}), Headers} | Result]))};
++	NewHeader ->
++	    case check_header(NewHeader, MaxSizes) of 
++		ok ->
++		    {ok, list_to_tuple(lists:reverse([Body, {http_request:headers([NewHeader | Headers], 
++										  #http_request_h{}),  
++							     [NewHeader | Headers]} | Result]))};
++		
++		{error, Reason} ->
++		    HttpVersion = lists:nth(3, lists:reverse(Result)),
++		    {error, Reason, HttpVersion}
++	    end
++    end;

*** DIFF OUTPUT TRUNCATED AT 1000 LINES ***



Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?201502211014.t1LAEbeP061585>