/* Licensed under BSD-MIT - see LICENSE file for details */
#include <ccan/net/net.h>
+#include <ccan/noerr/noerr.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <poll.h>
#include <errno.h>
#include <stdbool.h>
#include <netinet/in.h>
+#include <assert.h>
struct addrinfo *net_client_lookup(const char *hostname,
const char *service,
return (fcntl(fd, F_SETFL, flags) == 0);
}
-/* We only handle IPv4 and IPv6 */
-#define MAX_PROTOS 2
-
-static void remove_fd(struct pollfd pfd[],
- const struct addrinfo *addr[],
- socklen_t slen[],
- unsigned int *num,
- unsigned int i)
+static int start_connect(const struct addrinfo *addr, bool *immediate)
{
- memmove(pfd + i, pfd + i + 1, (*num - i - 1) * sizeof(pfd[0]));
- memmove(addr + i, addr + i + 1, (*num - i - 1) * sizeof(addr[0]));
- memmove(slen + i, slen + i + 1, (*num - i - 1) * sizeof(slen[0]));
- (*num)--;
+ int fd;
+
+ *immediate = false;
+
+ fd = socket(addr->ai_family, addr->ai_socktype, addr->ai_protocol);
+ if (fd == -1)
+ return fd;
+
+ if (!set_nonblock(fd, true))
+ goto close;
+
+ if (connect(fd, addr->ai_addr, addr->ai_addrlen) == 0) {
+ /* Immediate connect. */
+ *immediate = true;
+ return fd;
+ }
+
+ if (errno == EINPROGRESS)
+ return fd;
+
+close:
+ close_noerr(fd);
+ return -1;
}
-int net_connect(const struct addrinfo *addrinfo)
+
+int net_connect_async(const struct addrinfo *addrinfo, struct pollfd pfds[2])
{
- int sockfd = -1, saved_errno;
- unsigned int i, num;
- const struct addrinfo *ipv4 = NULL, *ipv6 = NULL;
- const struct addrinfo *addr[MAX_PROTOS];
- socklen_t slen[MAX_PROTOS];
- struct pollfd pfd[MAX_PROTOS];
+ const struct addrinfo *addr[2] = { NULL, NULL };
+ unsigned int i;
+ pfds[0].fd = pfds[1].fd = -1;
+ pfds[0].events = pfds[1].events = POLLOUT;
+
+ /* Give IPv6 a slight advantage, by trying it first. */
for (; addrinfo; addrinfo = addrinfo->ai_next) {
switch (addrinfo->ai_family) {
case AF_INET:
- if (!ipv4)
- ipv4 = addrinfo;
+ addr[1] = addrinfo;
break;
case AF_INET6:
- if (!ipv6)
- ipv6 = addrinfo;
+ addr[0] = addrinfo;
break;
+ default:
+ continue;
}
}
- num = 0;
- /* We give IPv6 a slight edge by connecting it first. */
- if (ipv6) {
- addr[num] = ipv6;
- slen[num] = sizeof(struct sockaddr_in6);
- pfd[num].fd = socket(AF_INET6, ipv6->ai_socktype,
- ipv6->ai_protocol);
- if (pfd[num].fd != -1)
- num++;
+ /* In case we found nothing. */
+ errno = ENOENT;
+ for (i = 0; i < 2; i++) {
+ bool immediate;
+
+ if (!addr[i])
+ continue;
+
+ pfds[i].fd = start_connect(addr[i], &immediate);
+ if (immediate) {
+ if (pfds[!i].fd != -1)
+ close(pfds[!i].fd);
+ if (!set_nonblock(pfds[i].fd, false)) {
+ close_noerr(pfds[i].fd);
+ return -1;
+ }
+ return pfds[0].fd;
+ }
}
- if (ipv4) {
- addr[num] = ipv4;
- slen[num] = sizeof(struct sockaddr_in);
- pfd[num].fd = socket(AF_INET, ipv4->ai_socktype,
- ipv4->ai_protocol);
- if (pfd[num].fd != -1)
- num++;
+
+ if (pfds[0].fd != -1 || pfds[1].fd != -1)
+ errno = EINPROGRESS;
+ return -1;
+}
+
+void net_connect_abort(struct pollfd pfds[2])
+{
+ unsigned int i;
+
+ for (i = 0; i < 2; i++) {
+ if (pfds[i].fd != -1)
+ close_noerr(pfds[i].fd);
+ pfds[i].fd = -1;
}
+}
+
+int net_connect_complete(struct pollfd pfds[2])
+{
+ unsigned int i;
+
+ assert(pfds[0].fd != -1 || pfds[1].fd != -1);
+
+ for (i = 0; i < 2; i++) {
+ int err;
+ socklen_t errlen = sizeof(err);
- for (i = 0; i < num; i++) {
- if (!set_nonblock(pfd[i].fd, true)) {
- remove_fd(pfd, addr, slen, &num, i--);
+ if (pfds[i].fd == -1)
continue;
+ if (getsockopt(pfds[i].fd, SOL_SOCKET, SO_ERROR, &err,
+ &errlen) != 0) {
+ net_connect_abort(pfds);
+ return -1;
}
- /* Connect *can* be instant. */
- if (connect(pfd[i].fd, addr[i]->ai_addr, slen[i]) == 0)
- goto got_one;
- if (errno != EINPROGRESS) {
- /* Remove dead one. */
- remove_fd(pfd, addr, slen, &num, i--);
+ if (err == 0) {
+ /* Don't hand them non-blocking fd! */
+ if (!set_nonblock(pfds[i].fd, false)) {
+ net_connect_abort(pfds);
+ return -1;
+ }
+ /* Close other one. */
+ if (pfds[!i].fd != -1)
+ close(pfds[!i].fd);
+ return pfds[i].fd;
}
- pfd[i].events = POLLOUT;
}
- while (num && poll(pfd, num, -1) != -1) {
- for (i = 0; i < num; i++) {
- int err;
- socklen_t errlen = sizeof(err);
- if (!pfd[i].revents)
- continue;
- if (getsockopt(pfd[i].fd, SOL_SOCKET, SO_ERROR, &err,
- &errlen) != 0)
- goto out;
- if (err == 0)
- goto got_one;
-
- /* Remove dead one. */
- errno = err;
- remove_fd(pfd, addr, slen, &num, i--);
- }
- }
+ /* Still going... */
+ errno = EINPROGRESS;
+ return -1;
+}
-got_one:
- /* We don't want to hand them a non-blocking socket! */
- if (set_nonblock(pfd[i].fd, false))
- sockfd = pfd[i].fd;
+int net_connect(const struct addrinfo *addrinfo)
+{
+ struct pollfd pfds[2];
+ int sockfd;
+
+ sockfd = net_connect_async(addrinfo, pfds);
+ /* Immediate connect or error is easy. */
+ if (sockfd >= 0 || errno != EINPROGRESS)
+ return sockfd;
+
+ while (poll(pfds, 2, -1) != -1) {
+ sockfd = net_connect_complete(pfds);
+ if (sockfd >= 0 || errno != EINPROGRESS)
+ return sockfd;
+ }
-out:
- saved_errno = errno;
- for (i = 0; i < num; i++)
- if (pfd[i].fd != sockfd)
- close(pfd[i].fd);
- errno = saved_errno;
- return sockfd;
+ net_connect_abort(pfds);
+ return -1;
}
struct addrinfo *net_server_lookup(const char *service,
--- /dev/null
+#include <ccan/tap/tap.h>
+#include <stdlib.h>
+
+/* Make sure we override these! */
+static void *no_malloc(size_t size)
+{
+ abort();
+}
+static void *no_realloc(void *p, size_t size)
+{
+ abort();
+}
+static void no_free(void *p)
+{
+ abort();
+}
+#define malloc no_malloc
+#define realloc no_realloc
+#define free no_free
+
+#include <ccan/opt/opt.c>
+#include <ccan/opt/usage.c>
+#include <ccan/opt/helpers.c>
+#include <ccan/opt/parse.c>
+#include "utils.h"
+
+#undef malloc
+#undef realloc
+#undef free
+
+static unsigned int alloc_count, realloc_count, free_count;
+static void *ptrs[100];
+
+static void **find_ptr(void *p)
+{
+ unsigned int i;
+
+ for (i = 0; i < 100; i++)
+ if (ptrs[i] == p)
+ return ptrs + i;
+ return NULL;
+}
+
+static void *allocfn(size_t size)
+{
+ alloc_count++;
+ return *find_ptr(NULL) = malloc(size);
+}
+
+static void *reallocfn(void *ptr, size_t size)
+{
+ realloc_count++;
+ if (!ptr)
+ alloc_count++;
+
+ return *find_ptr(ptr) = realloc(ptr, size);
+}
+
+static void freefn(void *ptr)
+{
+ free_count++;
+ free(ptr);
+ *find_ptr(ptr) = NULL;
+}
+
+int main(int argc, char *argv[])
+{
+ const char *myname = argv[0];
+
+ plan_tests(220);
+
+ opt_set_alloc(allocfn, reallocfn, freefn);
+
+ /* Simple short arg.*/
+ opt_register_noarg("-a", test_noarg, NULL, "All");
+ ok1(parse_args(&argc, &argv, "-a", NULL));
+ ok1(argc == 1);
+ ok1(argv[0] == myname);
+ ok1(argv[1] == NULL);
+ ok1(test_cb_called == 1);
+
+ /* Simple long arg. */
+ opt_register_noarg("--aaa", test_noarg, NULL, "AAAAll");
+ ok1(parse_args(&argc, &argv, "--aaa", NULL));
+ ok1(argc == 1);
+ ok1(argv[0] == myname);
+ ok1(argv[1] == NULL);
+ ok1(test_cb_called == 2);
+
+ /* Both long and short args. */
+ opt_register_noarg("--aaa|-a", test_noarg, NULL, "AAAAAAll");
+ ok1(parse_args(&argc, &argv, "--aaa", "-a", NULL));
+ ok1(argc == 1);
+ ok1(argv[0] == myname);
+ ok1(argv[1] == NULL);
+ ok1(test_cb_called == 4);
+
+ /* Extra arguments preserved. */
+ ok1(parse_args(&argc, &argv, "--aaa", "-a", "extra", "args", NULL));
+ ok1(argc == 3);
+ ok1(argv[0] == myname);
+ ok1(strcmp(argv[1], "extra") == 0);
+ ok1(strcmp(argv[2], "args") == 0);
+ ok1(test_cb_called == 6);
+
+ /* Malformed versions. */
+ ok1(!parse_args(&argc, &argv, "--aaa=arg", NULL));
+ ok1(strstr(err_output, ": --aaa: doesn't allow an argument"));
+ ok1(!parse_args(&argc, &argv, "--aa", NULL));
+ ok1(strstr(err_output, ": --aa: unrecognized option"));
+ ok1(!parse_args(&argc, &argv, "--aaargh", NULL));
+ ok1(strstr(err_output, ": --aaargh: unrecognized option"));
+
+ /* Argument variants. */
+ reset_options();
+ test_cb_called = 0;
+ opt_register_arg("-a|--aaa", test_arg, NULL, "aaa", "AAAAAAll");
+ ok1(parse_args(&argc, &argv, "--aaa", "aaa", NULL));
+ ok1(argc == 1);
+ ok1(argv[0] == myname);
+ ok1(test_cb_called == 1);
+
+ ok1(parse_args(&argc, &argv, "--aaa=aaa", NULL));
+ ok1(argc == 1);
+ ok1(argv[0] == myname);
+ ok1(test_cb_called == 2);
+
+ ok1(parse_args(&argc, &argv, "-a", "aaa", NULL));
+ ok1(argc == 1);
+ ok1(argv[0] == myname);
+ ok1(test_cb_called == 3);
+
+ /* Malformed versions. */
+ ok1(!parse_args(&argc, &argv, "-a", NULL));
+ ok1(strstr(err_output, ": -a: requires an argument"));
+ ok1(!parse_args(&argc, &argv, "--aaa", NULL));
+ ok1(strstr(err_output, ": --aaa: requires an argument"));
+ ok1(!parse_args(&argc, &argv, "--aa", NULL));
+ ok1(strstr(err_output, ": --aa: unrecognized option"));
+ ok1(!parse_args(&argc, &argv, "--aaargh", NULL));
+ ok1(strstr(err_output, ": --aaargh: unrecognized option"));
+
+ /* Now, tables. */
+ /* Short table: */
+ reset_options();
+ test_cb_called = 0;
+ opt_register_table(short_table, NULL);
+ ok1(parse_args(&argc, &argv, "-a", NULL));
+ ok1(argc == 1);
+ ok1(argv[0] == myname);
+ ok1(argv[1] == NULL);
+ ok1(test_cb_called == 1);
+ /* This one needs an arg. */
+ ok1(parse_args(&argc, &argv, "-b", NULL) == false);
+ ok1(test_cb_called == 1);
+ ok1(parse_args(&argc, &argv, "-b", "b", NULL));
+ ok1(argc == 1);
+ ok1(argv[0] == myname);
+ ok1(argv[1] == NULL);
+ ok1(test_cb_called == 2);
+
+ /* Long table: */
+ reset_options();
+ test_cb_called = 0;
+ opt_register_table(long_table, NULL);
+ ok1(parse_args(&argc, &argv, "--ddd", NULL));
+ ok1(argc == 1);
+ ok1(argv[0] == myname);
+ ok1(argv[1] == NULL);
+ ok1(test_cb_called == 1);
+ /* This one needs an arg. */
+ ok1(parse_args(&argc, &argv, "--eee", NULL) == false);
+ ok1(test_cb_called == 1);
+ ok1(parse_args(&argc, &argv, "--eee", "eee", NULL));
+ ok1(argc == 1);
+ ok1(argv[0] == myname);
+ ok1(argv[1] == NULL);
+ ok1(test_cb_called == 2);
+
+ /* Short and long, both. */
+ reset_options();
+ test_cb_called = 0;
+ opt_register_table(long_and_short_table, NULL);
+ ok1(parse_args(&argc, &argv, "-g", NULL));
+ ok1(argc == 1);
+ ok1(argv[0] == myname);
+ ok1(argv[1] == NULL);
+ ok1(test_cb_called == 1);
+ ok1(parse_args(&argc, &argv, "--ggg", NULL));
+ ok1(argc == 1);
+ ok1(argv[0] == myname);
+ ok1(argv[1] == NULL);
+ ok1(test_cb_called == 2);
+ /* This one needs an arg. */
+ ok1(parse_args(&argc, &argv, "-h", NULL) == false);
+ ok1(test_cb_called == 2);
+ ok1(parse_args(&argc, &argv, "-h", "hhh", NULL));
+ ok1(argc == 1);
+ ok1(argv[0] == myname);
+ ok1(argv[1] == NULL);
+ ok1(test_cb_called == 3);
+ ok1(parse_args(&argc, &argv, "--hhh", NULL) == false);
+ ok1(test_cb_called == 3);
+ ok1(parse_args(&argc, &argv, "--hhh", "hhh", NULL));
+ ok1(argc == 1);
+ ok1(argv[0] == myname);
+ ok1(argv[1] == NULL);
+ ok1(test_cb_called == 4);
+
+ /* Those will all work as tables. */
+ test_cb_called = 0;
+ reset_options();
+ opt_register_table(subtables, NULL);
+ ok1(parse_args(&argc, &argv, "-a", NULL));
+ ok1(argc == 1);
+ ok1(argv[0] == myname);
+ ok1(argv[1] == NULL);
+ ok1(test_cb_called == 1);
+ /* This one needs an arg. */
+ ok1(parse_args(&argc, &argv, "-b", NULL) == false);
+ ok1(test_cb_called == 1);
+ ok1(parse_args(&argc, &argv, "-b", "b", NULL));
+ ok1(argc == 1);
+ ok1(argv[0] == myname);
+ ok1(argv[1] == NULL);
+ ok1(test_cb_called == 2);
+
+ ok1(parse_args(&argc, &argv, "--ddd", NULL));
+ ok1(argc == 1);
+ ok1(argv[0] == myname);
+ ok1(argv[1] == NULL);
+ ok1(test_cb_called == 3);
+ /* This one needs an arg. */
+ ok1(parse_args(&argc, &argv, "--eee", NULL) == false);
+ ok1(test_cb_called == 3);
+ ok1(parse_args(&argc, &argv, "--eee", "eee", NULL));
+ ok1(argc == 1);
+ ok1(argv[0] == myname);
+ ok1(argv[1] == NULL);
+ ok1(test_cb_called == 4);
+
+ /* Short and long, both. */
+ ok1(parse_args(&argc, &argv, "-g", NULL));
+ ok1(argc == 1);
+ ok1(argv[0] == myname);
+ ok1(argv[1] == NULL);
+ ok1(test_cb_called == 5);
+ ok1(parse_args(&argc, &argv, "--ggg", NULL));
+ ok1(argc == 1);
+ ok1(argv[0] == myname);
+ ok1(argv[1] == NULL);
+ ok1(test_cb_called == 6);
+ /* This one needs an arg. */
+ ok1(parse_args(&argc, &argv, "-h", NULL) == false);
+ ok1(test_cb_called == 6);
+ ok1(parse_args(&argc, &argv, "-h", "hhh", NULL));
+ ok1(argc == 1);
+ ok1(argv[0] == myname);
+ ok1(argv[1] == NULL);
+ ok1(test_cb_called == 7);
+ ok1(parse_args(&argc, &argv, "--hhh", NULL) == false);
+ ok1(test_cb_called == 7);
+ ok1(parse_args(&argc, &argv, "--hhh", "hhh", NULL));
+ ok1(argc == 1);
+ ok1(argv[0] == myname);
+ ok1(argv[1] == NULL);
+ ok1(test_cb_called == 8);
+
+ /* Now the tricky one: -? must not be confused with an unknown option */
+ test_cb_called = 0;
+ reset_options();
+
+ /* glibc's getopt does not handle ? with arguments. */
+ opt_register_noarg("-?", test_noarg, NULL, "Help");
+ ok1(parse_args(&argc, &argv, "-?", NULL));
+ ok1(test_cb_called == 1);
+ ok1(parse_args(&argc, &argv, "-a", NULL) == false);
+ ok1(test_cb_called == 1);
+ ok1(strstr(err_output, ": -a: unrecognized option"));
+ ok1(parse_args(&argc, &argv, "--aaaa", NULL) == false);
+ ok1(test_cb_called == 1);
+ ok1(strstr(err_output, ": --aaaa: unrecognized option"));
+
+ test_cb_called = 0;
+ reset_options();
+
+ /* Corner cases involving short arg parsing weirdness. */
+ opt_register_noarg("-a|--aaa", test_noarg, NULL, "a");
+ opt_register_arg("-b|--bbb", test_arg, NULL, "bbb", "b");
+ opt_register_arg("-c|--ccc", test_arg, NULL, "aaa", "c");
+ /* -aa == -a -a */
+ ok1(parse_args(&argc, &argv, "-aa", NULL));
+ ok1(test_cb_called == 2);
+ ok1(parse_args(&argc, &argv, "-aab", NULL) == false);
+ ok1(test_cb_called == 4);
+ ok1(strstr(err_output, ": -b: requires an argument"));
+ ok1(parse_args(&argc, &argv, "-bbbb", NULL));
+ ok1(test_cb_called == 5);
+ ok1(parse_args(&argc, &argv, "-aabbbb", NULL));
+ ok1(test_cb_called == 8);
+ ok1(parse_args(&argc, &argv, "-aabbbb", "-b", "bbb", NULL));
+ ok1(test_cb_called == 12);
+ ok1(parse_args(&argc, &argv, "-aabbbb", "--bbb", "bbb", NULL));
+ ok1(test_cb_called == 16);
+ ok1(parse_args(&argc, &argv, "-aabbbb", "--bbb=bbb", NULL));
+ ok1(test_cb_called == 20);
+ ok1(parse_args(&argc, &argv, "-aacaaa", NULL));
+ ok1(test_cb_called == 23);
+ ok1(parse_args(&argc, &argv, "-aacaaa", "-a", NULL));
+ ok1(test_cb_called == 27);
+ ok1(parse_args(&argc, &argv, "-aacaaa", "--bbb", "bbb", "-aacaaa",
+ NULL));
+ ok1(test_cb_called == 34);
+
+ test_cb_called = 0;
+ reset_options();
+
+ /* -- and POSIXLY_CORRECT */
+ opt_register_noarg("-a|--aaa", test_noarg, NULL, "a");
+ ok1(parse_args(&argc, &argv, "-a", "--", "-a", NULL));
+ ok1(test_cb_called == 1);
+ ok1(argc == 2);
+ ok1(strcmp(argv[1], "-a") == 0);
+ ok1(!argv[2]);
+
+ unsetenv("POSIXLY_CORRECT");
+ ok1(parse_args(&argc, &argv, "-a", "somearg", "-a", "--", "-a", NULL));
+ ok1(test_cb_called == 3);
+ ok1(argc == 3);
+ ok1(strcmp(argv[1], "somearg") == 0);
+ ok1(strcmp(argv[2], "-a") == 0);
+ ok1(!argv[3]);
+
+ setenv("POSIXLY_CORRECT", "1", 1);
+ ok1(parse_args(&argc, &argv, "-a", "somearg", "-a", "--", "-a", NULL));
+ ok1(test_cb_called == 4);
+ ok1(argc == 5);
+ ok1(strcmp(argv[1], "somearg") == 0);
+ ok1(strcmp(argv[2], "-a") == 0);
+ ok1(strcmp(argv[3], "--") == 0);
+ ok1(strcmp(argv[4], "-a") == 0);
+ ok1(!argv[5]);
+
+ /* We should have tested each one at least once! */
+ ok1(realloc_count);
+ ok1(alloc_count);
+ ok1(free_count);
+
+ ok1(free_count < alloc_count);
+ reset_options();
+ ok1(free_count == alloc_count);
+
+ /* parse_args allocates argv */
+ free(argv);
+ return exit_status();
+}