#include "first.h" #include "fdevent.h" #include #include "sys-socket.h" #include "sys-unistd.h" /* */ #include #include #include #include #include "ck.h" #define force_assert(x) ck_assert(x) #ifndef _WIN32 #ifdef SOCK_CLOEXEC static int use_sock_cloexec; #endif #ifdef SOCK_NONBLOCK static int use_sock_nonblock; #endif void fdevent_socket_nb_cloexec_init (void) { #ifdef SOCK_CLOEXEC /* Test if SOCK_CLOEXEC is supported by kernel. * Linux kernels < 2.6.27 might return EINVAL if SOCK_CLOEXEC used * https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=529929 * http://www.linksysinfo.org/index.php?threads/lighttpd-no-longer-starts-toastman-1-28-0510-7.73132/ * Test if SOCK_NONBLOCK is ignored by kernel on sockets. * (reported on Android running a custom ROM) * https://redmine.lighttpd.net/issues/2883 */ #ifdef SOCK_NONBLOCK int fd = socket(AF_INET, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0); #else int fd = socket(AF_INET, SOCK_STREAM | SOCK_CLOEXEC, 0); #endif if (fd >= 0) { int flags = fcntl(fd, F_GETFL, 0); #ifdef SOCK_NONBLOCK use_sock_nonblock = (-1 != flags && (flags & O_NONBLOCK)); #endif use_sock_cloexec = 1; close(fd); } #endif } void fdevent_setfd_cloexec(int fd) { #ifdef FD_CLOEXEC if (fd < 0) return; force_assert(-1 != fcntl(fd, F_SETFD, FD_CLOEXEC)); #else UNUSED(fd); #endif } void fdevent_clrfd_cloexec(int fd) { #ifdef FD_CLOEXEC if (fd >= 0) force_assert(-1 != fcntl(fd, F_SETFD, 0)); #else UNUSED(fd); #endif } int fdevent_fcntl_set_nb(int fd) { #ifdef O_NONBLOCK return fcntl(fd, F_SETFL, O_NONBLOCK | O_RDWR); #else UNUSED(fd); return 0; #endif } int fdevent_fcntl_set_nb_cloexec(int fd) { fdevent_setfd_cloexec(fd); return fdevent_fcntl_set_nb(fd); } int fdevent_fcntl_set_nb_cloexec_sock(int fd) { #if defined(SOCK_CLOEXEC) && defined(SOCK_NONBLOCK) if (use_sock_cloexec && use_sock_nonblock) return 0; #endif return fdevent_fcntl_set_nb_cloexec(fd); } int fdevent_socket_cloexec(int domain, int type, int protocol) { int fd; #ifdef SOCK_CLOEXEC if (use_sock_cloexec) return socket(domain, type | SOCK_CLOEXEC, protocol); #endif if (-1 != (fd = socket(domain, type, protocol))) { #ifdef FD_CLOEXEC force_assert(-1 != fcntl(fd, F_SETFD, FD_CLOEXEC)); #endif } return fd; } int fdevent_socket_nb_cloexec(int domain, int type, int protocol) { int fd; #ifdef SOCK_CLOEXEC #ifdef SOCK_NONBLOCK if (use_sock_cloexec && use_sock_nonblock) return socket(domain, type | SOCK_CLOEXEC | SOCK_NONBLOCK, protocol); #else if (use_sock_cloexec) { fd = socket(domain, type | SOCK_CLOEXEC, protocol); #ifdef O_NONBLOCK if (-1 != fd) force_assert(-1 != fcntl(fd,F_SETFL,O_NONBLOCK|O_RDWR)); #endif return fd; } #endif #endif if (-1 != (fd = socket(domain, type, protocol))) { #ifdef FD_CLOEXEC force_assert(-1 != fcntl(fd, F_SETFD, FD_CLOEXEC)); #endif #ifdef O_NONBLOCK force_assert(-1 != fcntl(fd, F_SETFL, O_NONBLOCK | O_RDWR)); #endif } return fd; } #if 0 /* not used */ int fdevent_socketpair_cloexec (int domain, int typ, int protocol, int sv[2]) { sv[0] = sv[1] = -1; #if defined(SOCK_CLOEXEC) return socketpair(domain, typ | SOCK_CLOEXEC, protocol, sv); #else if (0 == socketpair(domain, typ, protocol, sv)) { if (0 == fdevent_socket_set_cloexec(sv[0]) && 0 == fdevent_socket_set_cloexec(sv[1])) return 0; close(sv[0]); close(sv[1]); sv[0] = sv[1] = -1; } return -1; #endif } int fdevent_socketpair_nb_cloexec (int domain, int typ, int protocol, int sv[2]) { sv[0] = sv[1] = -1; #if defined(SOCK_CLOEXEC) && defined(SOCK_NONBLOCK) return socketpair(domain, typ | SOCK_NONBLOCK | SOCK_CLOEXEC, protocol, sv); #else if (0 == socketpair(domain, typ, protocol, sv)) { if (0 == fdevent_socket_set_nb_cloexec(sv[0]) && 0 == fdevent_socket_set_nb_cloexec(sv[1])) return 0; close(sv[0]); close(sv[1]); sv[0] = sv[1] = -1; } return -1; #endif } #endif /* not used */ int fdevent_dup_cloexec (int fd) { #ifdef F_DUPFD_CLOEXEC return fcntl(fd, F_DUPFD_CLOEXEC, 3); #else const int newfd = fcntl(fd, F_DUPFD, 3); if (newfd >= 0) fdevent_setfd_cloexec(newfd); return newfd; #endif } #ifndef O_BINARY #define O_BINARY 0 #endif #ifndef O_LARGEFILE #define O_LARGEFILE 0 #endif #ifndef O_NOCTTY #define O_NOCTTY 0 #endif #ifndef O_NOFOLLOW #define O_NOFOLLOW 0 #endif /*(O_NOFOLLOW is not handled here)*/ /*(Note: O_NOFOLLOW affects only the final path segment, the target file, * not any intermediate symlinks along the path)*/ /* O_CLOEXEC handled further below, if defined) */ #ifdef O_NONBLOCK #define FDEVENT_O_FLAGS \ (O_BINARY | O_LARGEFILE | O_NOCTTY | O_NONBLOCK) #else #define FDEVENT_O_FLAGS \ (O_BINARY | O_LARGEFILE | O_NOCTTY ) #endif int fdevent_open_cloexec(const char *pathname, int symlinks, int flags, mode_t mode) { if (!symlinks) flags |= O_NOFOLLOW; #ifdef O_CLOEXEC return open(pathname, flags | O_CLOEXEC | FDEVENT_O_FLAGS, mode); #else int fd = open(pathname, flags | FDEVENT_O_FLAGS, mode); #ifdef FD_CLOEXEC if (fd != -1) force_assert(-1 != fcntl(fd, F_SETFD, FD_CLOEXEC)); #endif return fd; #endif } int fdevent_open_devnull(void) { #if defined(__sun) /* /dev/null is a symlink on Illumos */ return fdevent_open_cloexec("/dev/null", 1, O_RDWR, 0); #else return fdevent_open_cloexec("/dev/null", 0, O_RDWR, 0); #endif } int fdevent_open_dirname(char *path, int symlinks) { /*(handle special cases of no dirname or dirname is root directory)*/ char * const c = strrchr(path, '/'); const char * const dname = (NULL != c ? c == path ? "/" : path : "."); int dfd; int flags = O_RDONLY; #ifdef O_DIRECTORY flags |= O_DIRECTORY; #endif if (NULL != c) *c = '\0'; dfd = fdevent_open_cloexec(dname, symlinks, flags, 0); if (NULL != c) *c = '/'; return dfd; } int fdevent_pipe_cloexec (int * const fds, const unsigned int bufsz_hint) { #ifdef HAVE_PIPE2 if (0 != pipe2(fds, O_CLOEXEC)) #endif { if (0 != pipe(fds) #ifdef FD_CLOEXEC || 0 != fcntl(fds[0], F_SETFD, FD_CLOEXEC) || 0 != fcntl(fds[1], F_SETFD, FD_CLOEXEC) #endif ) return -1; } #ifdef F_SETPIPE_SZ if (bufsz_hint > 65536) if (0 != fcntl(fds[1], F_SETPIPE_SZ, bufsz_hint)) { } /*(ignore error)*/ #else UNUSED(bufsz_hint); #endif return 0; } int fdevent_socket_close(int fd) { return close(fd); } int fdevent_mkostemp(char *path, int flags) { #if defined(HAVE_MKOSTEMP) return mkostemp(path, O_CLOEXEC | flags); #else #ifdef __COVERITY__ /* POSIX-2008 requires mkstemp create file with 0600 perms */ umask(0600); #endif /* coverity[secure_temp : FALSE] */ const int fd = mkstemp(path); if (fd < 0) return fd; if (flags && 0 != fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) | flags)) { /* (should not happen; fd is regular file) */ int errnum = errno; close(fd); errno = errnum; return -1; } fdevent_setfd_cloexec(fd); return fd; #endif } /* accept4() added in Linux x86 in kernel 2.6.28, but not in arm until 2.6.36 * https://lwn.net/Articles/789961/ */ #if defined(__linux__) \ && (defined(__arm__) || defined(__thumb__) || defined(__arm64__)) #ifdef __has_include #if __has_include() #include #endif #endif #ifndef SYS_accept4 #define accept4(a,b,c,d) ((errno = ENOTSUP), -1) #endif #endif int fdevent_accept_listenfd(int listenfd, struct sockaddr *addr, size_t *addrlen) { int fd; socklen_t len = (socklen_t) *addrlen; #if defined(SOCK_CLOEXEC) && defined(SOCK_NONBLOCK) #if defined(__NetBSD__) const int sock_cloexec = 1; fd = paccept(listenfd, addr, &len, NULL, SOCK_CLOEXEC | SOCK_NONBLOCK); #else int sock_cloexec = use_sock_cloexec; if (sock_cloexec) { fd = accept4(listenfd, addr, &len, SOCK_CLOEXEC | SOCK_NONBLOCK); if (fd >= 0) { if (!use_sock_nonblock) { if (0 != fdevent_fcntl_set_nb(fd)) { close(fd); fd = -1; } } } else { switch (errno) { case ENOSYS: case ENOTSUP: case EPERM: fd = accept(listenfd, addr, &len); sock_cloexec = 0; break; default: break; } } } else { fd = accept(listenfd, addr, &len); } #endif #else const int sock_cloexec = 0; fd = accept(listenfd, addr, &len); #endif if (fd >= 0) { *addrlen = (size_t)len; if (!sock_cloexec && 0 != fdevent_fcntl_set_nb_cloexec(fd)) { close(fd); fd = -1; } } return fd; } #ifdef __APPLE__ #include #define environ (* _NSGetEnviron()) #else extern char **environ; #endif char ** fdevent_environ (void) { return environ; } #ifdef FD_CLOEXEC static int fdevent_dup2_close_clrfd_cloexec(int oldfd, int newfd) { if (oldfd >= 0) { if (oldfd != newfd) { force_assert(oldfd > STDERR_FILENO); if (newfd != dup2(oldfd, newfd)) return -1; } else { fdevent_clrfd_cloexec(newfd); } } return newfd; } #else static int fdevent_dup2_close_clrfd_cloexec(int oldfd, int newfd, int reuse) { if (oldfd >= 0) { if (oldfd != newfd) { force_assert(oldfd > STDERR_FILENO); if (newfd != dup2(oldfd, newfd)) return -1; if (!reuse) close(oldfd); } } return newfd; } #endif int fdevent_set_stdin_stdout_stderr(int fdin, int fdout, int fderr) { #ifdef FD_CLOEXEC if (STDIN_FILENO != fdevent_dup2_close_clrfd_cloexec(fdin, STDIN_FILENO)) return -1; if (STDOUT_FILENO != fdevent_dup2_close_clrfd_cloexec(fdout, STDOUT_FILENO)) return -1; if (STDERR_FILENO != fdevent_dup2_close_clrfd_cloexec(fderr, STDERR_FILENO)) return -1; #else if (STDIN_FILENO != fdevent_dup2_close_clrfd_cloexec(fdin, STDIN_FILENO, fdin == fdout || fdin == fderr)) return -1; if (STDOUT_FILENO != fdevent_dup2_close_clrfd_cloexec(fdout, STDOUT_FILENO, fdout == fderr)) return -1; if (STDERR_FILENO != fdevent_dup2_close_clrfd_cloexec(fderr, STDERR_FILENO, 0)) return -1; #endif return 0; } #include /* perror() rename() */ #include /* signal() kill() */ #ifdef HAVE_POSIX_SPAWN #include /* posix_spawn*() */ #endif int fdevent_rename(const char *oldpath, const char *newpath) { return rename(oldpath, newpath); } #if !defined(HAVE_POSIX_SPAWN_FILE_ACTIONS_ADDCLOSEFROM_NP) \ && defined(POSIX_SPAWN_CLOEXEC_DEFAULT) /* Mac OS */ __attribute_noinline__ static int fdevent_cloexec_default_prep (posix_spawn_file_actions_t *file_actions, int fd, int stdfd) { /* (other file actions already prepped in caller, * so ok to dup2() and overwrite 3+stdfd) */ int rc; if (fd < 0) fd = stdfd; rc = posix_spawn_file_actions_adddup2(file_actions, fd, 3+stdfd); if (0 != rc) return rc; rc = posix_spawn_file_actions_adddup2(file_actions, 3+stdfd, stdfd); if (0 != rc) return rc; rc = posix_spawn_file_actions_addclose(file_actions, 3+stdfd); return rc; } #endif pid_t fdevent_fork_execve(const char *name, char *argv[], char *envp[], int fdin, int fdout, int fderr, int dfd) { #ifdef HAVE_POSIX_SPAWN /* Caller must ensure that all fd* args are >= 3, i.e. > STDERR_FILENO (2), * unless fd* arg is -1, in which case we preserve existing target fd, or * unless fd does not have FD_CLOEXEC set *and* is not being replaced, * e.g. if fd 1 is open to /dev/null and fdout is -1 and fderr is 1 so * that fd 1 (STDOUT_FILENO) to /dev/null is dup2() to fd 2 (STDERR_FILENO). * Caller must handle so that if any dup() is required to make fd* >= 3, * then the caller has access to the new fds. The reason fd* args >= 3 * is required is that we set FD_CLOEXEC on all fds (thread-safety) and * a dup2() in child is used for dup2() side effect of removing FD_CLOEXEC. * (posix_spawn() provides posix_spawn_file_actions_adddup2() whereas * it does not provide a means to use fcntl() to remove FD_CLOEXEC) */ sigset_t sigs; posix_spawn_file_actions_t file_actions; posix_spawnattr_t attr; int rc; pid_t pid = -1; if (0 != (rc = posix_spawn_file_actions_init(&file_actions))) return pid; if (0 != (rc = posix_spawnattr_init(&attr))) { posix_spawn_file_actions_destroy(&file_actions); return pid; } if ( 0 == (rc = (fdin >= 0) ? posix_spawn_file_actions_adddup2( &file_actions, fdin, STDIN_FILENO) : 0) && 0 == (rc = (fdout >= 0) ? posix_spawn_file_actions_adddup2( &file_actions, fdout, STDOUT_FILENO) : 0) && 0 == (rc = (fderr >= 0) ? posix_spawn_file_actions_adddup2( &file_actions, fderr, STDERR_FILENO) : 0) #ifdef HAVE_POSIX_SPAWN_FILE_ACTIONS_ADDFCHDIR_NP && 0 == (rc = (-1 != dfd) ? posix_spawn_file_actions_addfchdir_np( &file_actions, dfd) : 0) #endif #ifdef HAVE_POSIX_SPAWNATTR_SETCWD_NP /* (QNX Neutrino 7.1 or later) */ && 0 == (rc = posix_spawnattr_setcwd_np(&attr, dfd)) && 0 == (rc = posix_spawnattr_setxflags(&spawnattr, POSIX_SPAWN_SETCWD | POSIX_SPAWN_SETSIGDEF | POSIX_SPAWN_SETSIGMASK)) #else && 0 == (rc = posix_spawnattr_setflags( &attr, POSIX_SPAWN_SETSIGDEF | POSIX_SPAWN_SETSIGMASK)) #endif && 0 == (rc = sigemptyset(&sigs)) && 0 == (rc = posix_spawnattr_setsigmask(&attr, &sigs)) #if defined(__GLIBC__) \ && (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 24 && __GLIBC_MINOR__ <= 37) /* glibc appears to walk all signals and to query and preserve some * sigaction flags even if setting to SIG_DFL, though if specified * in posix_spawnattr_setsigdefault(), resets to SIG_DFL without query. * Therefore, resetting all signals results in about 1/2 the syscalls.*/ && 0 == (rc = sigfillset(&sigs)) #else /*(force reset signals to SIG_DFL if server.c set to SIG_IGN)*/ #ifdef SIGTTOU && 0 == (rc = sigaddset(&sigs, SIGTTOU)) #endif #ifdef SIGTTIN && 0 == (rc = sigaddset(&sigs, SIGTTIN)) #endif #ifdef SIGTSTP && 0 == (rc = sigaddset(&sigs, SIGTSTP)) #endif && 0 == (rc = sigaddset(&sigs, SIGPIPE)) && 0 == (rc = sigaddset(&sigs, SIGUSR1)) #endif && 0 == (rc = posix_spawnattr_setsigdefault(&attr, &sigs))) { #if defined(HAVE_POSIX_SPAWN_FILE_ACTIONS_ADDCLOSEFROM_NP) \ || defined(POSIX_SPAWN_CLOEXEC_DEFAULT) /* Mac OS */ /* optional: potentially improve performance when many fds open * (might create new file descriptor table containing only 0,1,2 * instead of close() on all other fds with O_CLOEXEC flag set) * optional: disable manually and externally via gdb or other * debugger by setting trace_children to non-zero value */ static volatile sig_atomic_t trace_children; if (!trace_children) { #ifdef HAVE_POSIX_SPAWN_FILE_ACTIONS_ADDCLOSEFROM_NP posix_spawn_file_actions_addclosefrom_np(&file_actions, 3); #elif defined(POSIX_SPAWN_CLOEXEC_DEFAULT) /* Mac OS */ /* Apple: this workaround should not be necessary. * Please implement posix_spawn_file_actions_addclosefrom_np()*/ if (fdin < 3) /* <= STDERR_FILENO */ rc |= fdevent_cloexec_default_prep(&file_actions, fdin, STDIN_FILENO); if (fdout < 3) /* <= STDERR_FILENO */ rc |= fdevent_cloexec_default_prep(&file_actions, fdout, STDOUT_FILENO); if (fderr < 3) /* <= STDERR_FILENO */ rc |= fdevent_cloexec_default_prep(&file_actions, fderr, STDERR_FILENO); /* (not expecting any failures above and these are on fds that * are inherited, so not cleaning up excess fds from previous * adddup2 if any error occurs in this block) */ if (0 == rc) posix_spawnattr_setflags(&attr, POSIX_SPAWN_CLOEXEC_DEFAULT | POSIX_SPAWN_SETSIGDEF | POSIX_SPAWN_SETSIGMASK); rc = 0; #endif } #endif #if !defined(HAVE_POSIX_SPAWN_FILE_ACTIONS_ADDFCHDIR_NP) \ && !defined(HAVE_POSIX_SPAWNATTR_SETCWD_NP) /* not thread-safe, but ok since lighttpd not (currently) threaded * (alternatively, check HAVE_POSIX_SPAWN_FILE_ACTIONS_ADDFCHDIR_NP * along with HAVE_POSIX_SPAWN at top of block and use HAVE_FORK * below if HAVE_POSIX_SPAWN_FILE_ACTIONS_ADDFCHDIR_NP not avail)*/ if (-1 != dfd) { int ndfd = dfd; dfd = fdevent_open_dirname(".", 1); /* reuse dfd for cwd fd */ if (-1 == dfd || 0 != fchdir(ndfd)) rc = -1; /*(or could set to errno for posix consistency)*/ } if (0 == rc) #endif rc = posix_spawn(&pid, name, &file_actions, &attr, argv, envp ? envp : environ); if (0 != rc) pid = -1; #if !defined(HAVE_POSIX_SPAWN_FILE_ACTIONS_ADDFCHDIR_NP) \ && !defined(HAVE_POSIX_SPAWNATTR_SETCWD_NP) if (-1 != dfd) { if (0 != fchdir(dfd)) { /* ignore error; best effort */ /*rc = errno;*/ } close(dfd); } #endif } posix_spawn_file_actions_destroy(&file_actions); posix_spawnattr_destroy(&attr); return pid; #elif defined(HAVE_FORK) pid_t pid = fork(); if (0 != pid) return pid; /* parent (pid > 0) or fork() error (-1 == pid) */ /* child (0 == pid) */ if (-1 != dfd) { if (0 != fchdir(dfd)) _exit(errno); close(dfd); } if (0 != fdevent_set_stdin_stdout_stderr(fdin, fdout, fderr)) _exit(errno); #ifndef FD_CLOEXEC /*(might not be sufficient for open fds, but modern OS have FD_CLOEXEC)*/ for (int i = 3; i < 256; ++i) close(i); #endif /* reset_signals which may have been ignored (SIG_IGN) */ #ifdef SIGTTOU signal(SIGTTOU, SIG_DFL); #endif #ifdef SIGTTIN signal(SIGTTIN, SIG_DFL); #endif #ifdef SIGTSTP signal(SIGTSTP, SIG_DFL); #endif signal(SIGPIPE, SIG_DFL); signal(SIGUSR1, SIG_DFL); execve(name, argv, envp ? envp : environ); int errnum = errno; int argnum = (0 == strcmp(argv[0], "/bin/sh") && argv[1] && 0 == strcmp(argv[1], "-c")) ? 2 : 0; perror(argv[argnum]); _exit(errnum); #else UNUSED(name); UNUSED(argv); UNUSED(envp); UNUSED(fdin); UNUSED(fdout); UNUSED(fderr); UNUSED(dfd); return (pid_t)-1; #endif } int fdevent_kill (pid_t pid, int sig) { return kill(pid, sig); } #include "sys-wait.h" pid_t fdevent_waitpid(pid_t pid, int * const status, int nb) { const int flags = nb ? WNOHANG : 0; pid_t rv; do { rv = waitpid(pid, status, flags); } while (-1 == rv && errno == EINTR); return rv; } pid_t fdevent_waitpid_intr(pid_t pid, int * const status) { return waitpid(pid, status, 0); } #ifndef MSG_DONTWAIT #define MSG_DONTWAIT 0 #endif #ifndef MSG_NOSIGNAL #define MSG_NOSIGNAL 0 #endif ssize_t fdevent_socket_read_discard (int fd, char *buf, size_t sz, int family, int so_type) { #if defined(MSG_TRUNC) && defined(__linux__) if ((family == AF_INET || family == AF_INET6) && so_type == SOCK_STREAM) { ssize_t len = recv(fd, buf, sz, MSG_TRUNC|MSG_DONTWAIT|MSG_NOSIGNAL); if (len >= 0 || errno != EINVAL) return len; } #else UNUSED(family); UNUSED(so_type); #endif return read(fd, buf, sz); } #include #ifdef HAVE_SYS_FILIO_H #include /* FIONREAD (for illumos (OpenIndiana)) */ #endif int fdevent_ioctl_fionread (int fd, int fdfmt, int *toread) { #ifdef __CYGWIN__ /*(cygwin supports FIONREAD on pipes, not sockets)*/ if (fdfmt != S_IFIFO) { errno = EOPNOTSUPP; return -1; } #else UNUSED(fdfmt); #endif return ioctl(fd, FIONREAD, toread); } int fdevent_connect_status(int fd) { /* try to finish the connect() */ /*(should be called after connect() only when fd is writable (POLLOUT))*/ int opt; socklen_t len = sizeof(opt); return (0 == getsockopt(fd,SOL_SOCKET,SO_ERROR,&opt,&len)) ? opt : errno; } #endif /* !_WIN32 */ #ifndef _WIN32 #include #endif #if defined(__FreeBSD__) || defined(__NetBSD__) \ || defined(__OpenBSD__) || defined(__DragonFly__) #include #endif #if defined(__APPLE__) && defined(__MACH__) #include /* TARGET_OS_IPHONE, TARGET_OS_MAC */ #if TARGET_OS_IPHONE /* iOS, tvOS, or watchOS device */ /*#define TCPS_CLOSE_WAIT 5*/ /* ??? which header contains this, if any ??? */ #elif TARGET_OS_MAC /* MacOS */ #include #endif #endif /* fd must be TCP socket (AF_INET, AF_INET6), end-of-stream recv() 0 bytes */ int fdevent_is_tcp_half_closed(int fd) { #ifdef TCP_CONNECTION_INFO /* Darwin */ struct tcp_connection_info tcpi; socklen_t tlen = sizeof(tcpi); return (0 == getsockopt(fd, IPPROTO_TCP, TCP_CONNECTION_INFO, &tcpi, &tlen) && tcpi.tcpi_state == TCPS_CLOSE_WAIT); #elif defined(TCP_INFO) && defined(TCPS_CLOSE_WAIT) /* FreeBSD, NetBSD, OpenBSD (not present in DragonFlyBSD) */ struct tcp_info tcpi; socklen_t tlen = sizeof(tcpi); return (0 == getsockopt(fd, IPPROTO_TCP, TCP_INFO, &tcpi, &tlen) && tcpi.tcpi_state == TCPS_CLOSE_WAIT); #elif defined(TCP_INFO) && defined(__linux__) /* Linux (TCP_CLOSE_WAIT is enum, so can not #ifdef TCP_CLOSE_WAIT) */ struct tcp_info tcpi; socklen_t tlen = sizeof(tcpi);/*SOL_TCP == IPPROTO_TCP*/ return (0 == getsockopt(fd, SOL_TCP, TCP_INFO, &tcpi, &tlen) && tcpi.tcpi_state == TCP_CLOSE_WAIT); #else UNUSED(fd); /*(0 != getpeername() error might indicate TCP RST, but success * would not differentiate between half-close and full-close)*/ return 0; /* false (not half-closed) or TCP state unknown */ #endif } int fdevent_set_tcp_nodelay (const int fd, const int opt) { return setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &opt, sizeof(opt)); } int fdevent_set_so_reuseaddr (const int fd, const int opt) { return setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); } #include "sys-stat.h" #include "ck.h" #include "log.h" __attribute_cold__ /*(convenience routine for use at config at startup)*/ char * fdevent_load_file (const char * const fn, off_t *lim, log_error_st *errh, void *(malloc_fn)(size_t), void(free_fn)(void *)) { int fd; off_t sz = 0; char *buf = NULL; do { #if 0 /* /dev/fd/ and /proc/self/fd/ might be special-cased, but then would * be close()d at end of func before returning. Then again, that might * be desirable since otherwise those fds do not have FD_CLOEXEC set. */ fd = 0 == memcmp(fn, "/dev/fd/", sizeof("/dev/fd/")-1) ? atoi(fn + sizeof("/dev/fd/")-1) : 0 == memcmp(fn, "/proc/self/fd/", sizeof("/proc/self/fd/")-1) ? atoi(fn + sizeof("/proc/self/fd/")-1) : fdevent_open_cloexec(fn, 1, O_RDONLY, 0); /*(1: follows symlinks)*/ #else fd = fdevent_open_cloexec(fn, 1, O_RDONLY, 0); /*(1: follows symlinks)*/ #endif if (fd < 0) break; struct stat st; if (0 != fstat(fd, &st)) break; if (S_ISREG(st.st_mode)) { sz = st.st_size; if ((sizeof(off_t) > sizeof(size_t) && sz >= (off_t)~(size_t)0u) || (*lim != 0 && sz >= *lim)) { errno = EOVERFLOW; break; } buf = malloc_fn((size_t)sz+1); /* +1 trailing '\0' for str funcs */ if (NULL == buf) break; if (sz) { ssize_t rd = 0; off_t off = 0; do { rd = read(fd, buf+off, (size_t)(sz-off)); } while (rd > 0 ? (off += rd) != sz : rd < 0 && errno == EINTR); if (off != sz) { /*(file truncated?)*/ if (rd >= 0) errno = EIO; break; } } } else { /* attempt to read from non-regular file * e.g. FIFO/pipe from shell HERE doc (turns into e.g. "/dev/fd/63") * Note: read() might block! */ #ifndef _WIN32 #ifdef O_NONBLOCK /*(else read() might err EAGAIN Resource temporarily unavailable)*/ if (fcntl(fd, F_SETFL, (O_RDONLY|FDEVENT_O_FLAGS) & ~O_NONBLOCK)) {} /*(ignore fcntl() error; not expected and detected later if err)*/ #endif #endif ssize_t rd; off_t bsz = 0; if (*lim == 0) *lim = 32*1024*1024; /* set arbitrary limit, if not specified */ do { if (bsz <= sz+2) { if (bsz >= *lim) { rd = -1; errno = EOVERFLOW; break; } bsz = bsz ? (bsz << 1) : 65536; if (bsz > *lim) bsz = *lim; char *nbuf = malloc_fn((size_t)bsz); if (NULL == nbuf) { rd = -1; break; } if (buf) { memcpy(nbuf, buf, sz); ck_memzero(buf, (size_t)sz); free_fn(buf); } buf = nbuf; } rd = read(fd, buf+sz, (size_t)(bsz-sz-1)); } while (rd > 0 ? (sz += rd) : rd < 0 && errno == EINTR); if (rd != 0) break; } buf[sz] = '\0'; *lim = sz; close(fd); return buf; } while (0); int errnum = errno; if (errh) log_perror(errh, __FILE__, __LINE__, "%s() %s", __func__, fn); if (fd >= 0) close(fd); if (buf) { ck_memzero(buf, (size_t)sz); free_fn(buf); } *lim = 0; errno = errnum; return NULL; } int fdevent_load_file_bytes (char * const buf, const off_t sz, off_t off, const char * const fn, log_error_st *errh) { int fd; do { fd = fdevent_open_cloexec(fn, 1, O_RDONLY, 0); /*(1: follows symlinks)*/ if (fd < 0) break; if (0 != off && (off_t)-1 == lseek(fd, off, SEEK_SET)) break; off = 0; ssize_t rd = 0; do { rd = read(fd, buf+off, (size_t)(sz-off)); } while (rd > 0 ? (off += rd) != sz : rd < 0 && errno == EINTR); if (off != sz) { /*(file truncated? or incorrect sz requested)*/ if (rd >= 0) errno = EIO; break; } close(fd); return 0; } while (0); int errnum = errno; if (errh) log_perror(errh, __FILE__, __LINE__, "%s() %s", __func__, fn); if (fd >= 0) close(fd); ck_memzero(buf, (size_t)sz); errno = errnum; return -1; }