X-Git-Url: http://git.onelab.eu/?a=blobdiff_plain;ds=sidebyside;f=lib%2Fsocket-util.c;h=f74e43b47cb8ce8449d6090fca5dd21e87e366ab;hb=b31bcf60cf4fbabca0182196c97b3004e42f53e5;hp=912c47e097f8e80c807cabf3cfddcab343594184;hpb=02dd3123a0e312f1d33403e744af52dd6096f12d;p=sliver-openvswitch.git diff --git a/lib/socket-util.c b/lib/socket-util.c index 912c47e09..f74e43b47 100644 --- a/lib/socket-util.c +++ b/lib/socket-util.c @@ -26,13 +26,28 @@ #include #include #include +#include +#include #include #include #include "fatal-signal.h" #include "util.h" - #include "vlog.h" -#define THIS_MODULE VLM_socket_util + +VLOG_DEFINE_THIS_MODULE(socket_util); + +/* #ifdefs make it a pain to maintain code: you have to try to build both ways. + * Thus, this file compiles all of the code regardless of the target, by + * writing "if (LINUX)" instead of "#ifdef __linux__". */ +#ifdef __linux__ +#define LINUX 1 +#else +#define LINUX 0 +#endif + +#ifndef O_DIRECTORY +#define O_DIRECTORY 0 +#endif /* Sets 'fd' to non-blocking mode. Returns 0 if successful, otherwise a * positive errno value. */ @@ -53,6 +68,28 @@ set_nonblocking(int fd) } } +static bool +rlim_is_finite(rlim_t limit) +{ + if (limit == RLIM_INFINITY) { + return false; + } + +#ifdef RLIM_SAVED_CUR /* FreeBSD 8.0 lacks RLIM_SAVED_CUR. */ + if (limit == RLIM_SAVED_CUR) { + return false; + } +#endif + +#ifdef RLIM_SAVED_MAX /* FreeBSD 8.0 lacks RLIM_SAVED_MAX. */ + if (limit == RLIM_SAVED_MAX) { + return false; + } +#endif + + return true; +} + /* Returns the maximum valid FD value, plus 1. */ int get_max_fds(void) @@ -60,10 +97,7 @@ get_max_fds(void) static int max_fds = -1; if (max_fds < 0) { struct rlimit r; - if (!getrlimit(RLIMIT_NOFILE, &r) - && r.rlim_cur != RLIM_INFINITY - && r.rlim_cur != RLIM_SAVED_MAX - && r.rlim_cur != RLIM_SAVED_CUR) { + if (!getrlimit(RLIMIT_NOFILE, &r) && rlim_is_finite(r.rlim_cur)) { max_fds = r.rlim_cur; } else { VLOG_WARN("failed to obtain fd limit, defaulting to 1024"); @@ -77,7 +111,7 @@ get_max_fds(void) * address, into a numeric IP address in '*addr'. Returns 0 if successful, * otherwise a positive errno value. */ int -lookup_ip(const char *host_name, struct in_addr *addr) +lookup_ip(const char *host_name, struct in_addr *addr) { if (!inet_aton(host_name, addr)) { struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5); @@ -90,7 +124,7 @@ lookup_ip(const char *host_name, struct in_addr *addr) /* Returns the error condition associated with socket 'fd' and resets the * socket's error status. */ int -get_socket_error(int fd) +get_socket_error(int fd) { int error; socklen_t len = sizeof(error); @@ -103,7 +137,7 @@ get_socket_error(int fd) } int -check_connection_completion(int fd) +check_connection_completion(int fd) { struct pollfd pfd; int retval; @@ -150,12 +184,7 @@ drain_rcvbuf(int fd) * * On other Unix-like OSes, MSG_TRUNC has no effect in the flags * argument. */ -#ifdef __linux__ -#define BUFFER_SIZE 1 -#else -#define BUFFER_SIZE 2048 -#endif - char buffer[BUFFER_SIZE]; + char buffer[LINUX ? 1 : 2048]; ssize_t n_bytes = recv(fd, buffer, sizeof buffer, MSG_TRUNC | MSG_DONTWAIT); if (n_bytes <= 0 || n_bytes >= rcvbuf) { @@ -187,7 +216,7 @@ drain_fd(int fd, size_t n_packets) /* Stores in '*un' a sockaddr_un that refers to file 'name'. Stores in * '*un_len' the size of the sockaddr_un. */ static void -make_sockaddr_un(const char *name, struct sockaddr_un* un, socklen_t *un_len) +make_sockaddr_un__(const char *name, struct sockaddr_un *un, socklen_t *un_len) { un->sun_family = AF_UNIX; strncpy(un->sun_path, name, sizeof un->sun_path); @@ -196,6 +225,68 @@ make_sockaddr_un(const char *name, struct sockaddr_un* un, socklen_t *un_len) + strlen (un->sun_path) + 1); } +/* Stores in '*un' a sockaddr_un that refers to file 'name'. Stores in + * '*un_len' the size of the sockaddr_un. + * + * Returns 0 on success, otherwise a positive errno value. On success, + * '*dirfdp' is either -1 or a nonnegative file descriptor that the caller + * should close after using '*un' to bind or connect. On failure, '*dirfdp' is + * -1. */ +static int +make_sockaddr_un(const char *name, struct sockaddr_un *un, socklen_t *un_len, + int *dirfdp) +{ + enum { MAX_UN_LEN = sizeof un->sun_path - 1 }; + + *dirfdp = -1; + if (strlen(name) > MAX_UN_LEN) { + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1); + + if (LINUX) { + /* 'name' is too long to fit in a sockaddr_un, but we have a + * workaround for that on Linux: shorten it by opening a file + * descriptor for the directory part of the name and indirecting + * through /proc/self/fd//. */ + char *dir, *base; + char *short_name; + int dirfd; + + dir = dir_name(name); + base = base_name(name); + + dirfd = open(dir, O_DIRECTORY | O_RDONLY); + if (dirfd < 0) { + return errno; + } + + short_name = xasprintf("/proc/self/fd/%d/%s", dirfd, base); + free(dir); + free(base); + + if (strlen(short_name) <= MAX_UN_LEN) { + make_sockaddr_un__(short_name, un, un_len); + free(short_name); + *dirfdp = dirfd; + return 0; + } + free(short_name); + close(dirfd); + + VLOG_WARN_RL(&rl, "Unix socket name %s is longer than maximum " + "%d bytes (even shortened)", name, MAX_UN_LEN); + } else { + /* 'name' is too long and we have no workaround. */ + VLOG_WARN_RL(&rl, "Unix socket name %s is longer than maximum " + "%d bytes", name, MAX_UN_LEN); + } + + return ENAMETOOLONG; + } else { + make_sockaddr_un__(name, un, un_len); + return 0; + } +} + /* Creates a Unix domain socket in the given 'style' (either SOCK_DGRAM or * SOCK_STREAM) that is bound to '*bind_path' (if 'bind_path' is non-null) and * connected to '*connect_path' (if 'connect_path' is non-null). If 'nonblock' @@ -222,9 +313,11 @@ make_unix_socket(int style, bool nonblock, bool passcred OVS_UNUSED, if (nonblock) { int flags = fcntl(fd, F_GETFL, 0); if (flags == -1) { + error = errno; goto error; } if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1) { + error = errno; goto error; } } @@ -232,13 +325,22 @@ make_unix_socket(int style, bool nonblock, bool passcred OVS_UNUSED, if (bind_path) { struct sockaddr_un un; socklen_t un_len; - make_sockaddr_un(bind_path, &un, &un_len); - if (unlink(un.sun_path) && errno != ENOENT) { - VLOG_WARN("unlinking \"%s\": %s\n", un.sun_path, strerror(errno)); + int dirfd; + + if (unlink(bind_path) && errno != ENOENT) { + VLOG_WARN("unlinking \"%s\": %s\n", bind_path, strerror(errno)); } fatal_signal_add_file_to_unlink(bind_path); - if (bind(fd, (struct sockaddr*) &un, un_len) - || fchmod(fd, S_IRWXU)) { + + error = make_sockaddr_un(bind_path, &un, &un_len, &dirfd); + if (!error && (bind(fd, (struct sockaddr*) &un, un_len) + || fchmod(fd, S_IRWXU))) { + error = errno; + } + if (dirfd >= 0) { + close(dirfd); + } + if (error) { goto error; } } @@ -246,10 +348,18 @@ make_unix_socket(int style, bool nonblock, bool passcred OVS_UNUSED, if (connect_path) { struct sockaddr_un un; socklen_t un_len; - make_sockaddr_un(connect_path, &un, &un_len); - if (connect(fd, (struct sockaddr*) &un, un_len) + int dirfd; + + error = make_sockaddr_un(connect_path, &un, &un_len, &dirfd); + if (!error + && connect(fd, (struct sockaddr*) &un, un_len) && errno != EINPROGRESS) { - printf("connect failed with %s\n", strerror(errno)); + error = errno; + } + if (dirfd >= 0) { + close(dirfd); + } + if (error) { goto error; } } @@ -258,6 +368,7 @@ make_unix_socket(int style, bool nonblock, bool passcred OVS_UNUSED, if (passcred) { int enable = 1; if (setsockopt(fd, SOL_SOCKET, SO_PASSCRED, &enable, sizeof(enable))) { + error = errno; goto error; } } @@ -266,7 +377,9 @@ make_unix_socket(int style, bool nonblock, bool passcred OVS_UNUSED, return fd; error: - error = errno == EAGAIN ? EPROTO : errno; + if (error == EAGAIN) { + error = EPROTO; + } if (bind_path) { fatal_signal_remove_file_to_unlink(bind_path); } @@ -292,55 +405,79 @@ guess_netmask(uint32_t ip) : htonl(0)); /* ??? */ } -/* Opens a non-blocking IPv4 socket of the specified 'style' and connects to - * 'target', which should be a string in the format "[:]". - * is required. If 'default_port' is nonzero then is optional and - * defaults to 'default_port'. - * - * 'style' should be SOCK_STREAM (for TCP) or SOCK_DGRAM (for UDP). - * - * On success, returns 0 (indicating connection complete) or EAGAIN (indicating - * connection in progress), in which case the new file descriptor is stored - * into '*fdp'. On failure, returns a positive errno value other than EAGAIN - * and stores -1 into '*fdp'. +/* Parses 'target', which should be a string in the format "[:]". + * is required. If 'default_port' is nonzero then is optional + * and defaults to 'default_port'. * - * If 'sinp' is non-null, then on success the target address is stored into - * '*sinp'. */ -int -inet_open_active(int style, const char *target_, uint16_t default_port, - struct sockaddr_in *sinp, int *fdp) + * On success, returns true and stores the parsed remote address into '*sinp'. + * On failure, logs an error, stores zeros into '*sinp', and returns false. */ +bool +inet_parse_active(const char *target_, uint16_t default_port, + struct sockaddr_in *sinp) { char *target = xstrdup(target_); char *save_ptr = NULL; const char *host_name; const char *port_string; - struct sockaddr_in sin; - int fd = -1; - int error; + bool ok = false; /* Defaults. */ - memset(&sin, 0, sizeof sin); - sin.sin_family = AF_INET; - sin.sin_port = htons(default_port); + sinp->sin_family = AF_INET; + sinp->sin_port = htons(default_port); /* Tokenize. */ host_name = strtok_r(target, ":", &save_ptr); port_string = strtok_r(NULL, ":", &save_ptr); if (!host_name) { - ovs_error(0, "%s: bad peer name format", target_); - error = EAFNOSUPPORT; + VLOG_ERR("%s: bad peer name format", target_); goto exit; } /* Look up IP, port. */ - error = lookup_ip(host_name, &sin.sin_addr); - if (error) { + if (lookup_ip(host_name, &sinp->sin_addr)) { goto exit; } if (port_string && atoi(port_string)) { - sin.sin_port = htons(atoi(port_string)); + sinp->sin_port = htons(atoi(port_string)); } else if (!default_port) { VLOG_ERR("%s: port number must be specified", target_); + goto exit; + } + + ok = true; + +exit: + if (!ok) { + memset(sinp, 0, sizeof *sinp); + } + free(target); + return ok; +} + +/* Opens a non-blocking IPv4 socket of the specified 'style' and connects to + * 'target', which should be a string in the format "[:]". + * is required. If 'default_port' is nonzero then is optional and + * defaults to 'default_port'. + * + * 'style' should be SOCK_STREAM (for TCP) or SOCK_DGRAM (for UDP). + * + * On success, returns 0 (indicating connection complete) or EAGAIN (indicating + * connection in progress), in which case the new file descriptor is stored + * into '*fdp'. On failure, returns a positive errno value other than EAGAIN + * and stores -1 into '*fdp'. + * + * If 'sinp' is non-null, then on success the target address is stored into + * '*sinp'. */ +int +inet_open_active(int style, const char *target, uint16_t default_port, + struct sockaddr_in *sinp, int *fdp) +{ + struct sockaddr_in sin; + int fd = -1; + int error; + + /* Parse. */ + if (!inet_parse_active(target, default_port, &sin)) { error = EAFNOSUPPORT; goto exit; } @@ -348,7 +485,7 @@ inet_open_active(int style, const char *target_, uint16_t default_port, /* Create non-blocking socket. */ fd = socket(AF_INET, style, 0); if (fd < 0) { - VLOG_ERR("%s: socket: %s", target_, strerror(errno)); + VLOG_ERR("%s: socket: %s", target, strerror(errno)); error = errno; goto exit; } @@ -379,7 +516,6 @@ exit: } else { *fdp = -1; } - free(target); return error; } @@ -413,7 +549,7 @@ inet_open_passive(int style, const char *target_, int default_port, struct sockaddr_in sin; const char *host_name; const char *port_string; - int fd, error, port; + int fd = 0, error, port; unsigned int yes = 1; /* Address defaults. */ @@ -588,3 +724,31 @@ fsync_parent_dir(const char *file_name) return error; } + +/* Obtains the modification time of the file named 'file_name' to the greatest + * supported precision. If successful, stores the mtime in '*mtime' and + * returns 0. On error, returns a positive errno value and stores zeros in + * '*mtime'. */ +int +get_mtime(const char *file_name, struct timespec *mtime) +{ + struct stat s; + + if (!stat(file_name, &s)) { + mtime->tv_sec = s.st_mtime; + +#if HAVE_STRUCT_STAT_ST_MTIM_TV_NSEC + mtime->tv_nsec = s.st_mtim.tv_nsec; +#elif HAVE_STRUCT_STAT_ST_MTIMENSEC + mtime->tv_nsec = s.st_mtimensec; +#else + mtime->tv_nsec = 0; +#endif + + return 0; + } else { + mtime->tv_sec = mtime->tv_nsec = 0; + return errno; + } +} +