// SPDX-License-Identifier: GPL-2.0 /* * Copyright (C) 2009, 2010 Red Hat Inc, Steven Rostedt * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef VSOCK #include #endif #include "trace-local.h" #include "trace-msg.h" #define dprint(fmt, ...) tracecmd_debug(fmt, ##__VA_ARGS__) #define MAX_OPTION_SIZE 4096 #define _VAR_DIR_Q(dir) #dir #define VAR_DIR_Q(dir) _VAR_DIR_Q(dir) #define VAR_RUN_DIR VAR_DIR_Q(VAR_DIR) "/run" #define LISTEN_PIDFILE "trace-cmd-net.pid" static char *default_output_dir = "."; static char *output_dir; static char *default_output_file = "trace"; static char *output_file; static bool use_vsock; static int backlog = 5; static int do_daemon; /* Used for signaling INT to finish */ static struct tracecmd_msg_handle *stop_msg_handle; static bool done; #define pdie(fmt, ...) \ do { \ tracecmd_plog_error(fmt, ##__VA_ARGS__);\ if (do_daemon) \ remove_pid_file(LISTEN_PIDFILE);\ exit(-1); \ } while (0) #define TEMP_FILE_STR "%s.%s:%s.cpu%d", output_file, host, port, cpu static char *get_temp_file(const char *host, const char *port, int cpu) { char *file = NULL; int size; size = snprintf(file, 0, TEMP_FILE_STR); file = malloc(size + 1); if (!file) return NULL; sprintf(file, TEMP_FILE_STR); return file; } static void put_temp_file(char *file) { free(file); } static void signal_setup(int sig, sighandler_t handle) { struct sigaction action; sigaction(sig, NULL, &action); /* Make accept return EINTR */ action.sa_flags &= ~SA_RESTART; action.sa_handler = handle; sigaction(sig, &action, NULL); } static void delete_temp_file(const char *host, const char *port, int cpu) { char file[PATH_MAX]; snprintf(file, PATH_MAX, TEMP_FILE_STR); unlink(file); } static int read_string(int fd, char *buf, size_t size) { size_t i; int n; for (i = 0; i < size; i++) { n = read(fd, buf+i, 1); if (!buf[i] || n <= 0) break; } return i; } static int process_option(struct tracecmd_msg_handle *msg_handle, char *option) { /* currently the only option we have is to us TCP */ if (strcmp(option, "TCP") == 0) { msg_handle->flags |= TRACECMD_MSG_FL_USE_TCP; return 1; } return 0; } static void finish(int sig) { if (stop_msg_handle) tracecmd_msg_set_done(stop_msg_handle); done = true; } void make_pid_name(char *buf, const char *pidfile_basename) { snprintf(buf, PATH_MAX, VAR_RUN_DIR "/%s", pidfile_basename); } void remove_pid_file(const char *pidfile_basename) { char buf[PATH_MAX]; make_pid_name(buf, pidfile_basename); unlink(buf); } static int process_child(int sfd, const char *host, const char *port, int cpu, int page_size, enum port_type type) { struct sockaddr_storage peer_addr; #ifdef VSOCK struct sockaddr_vm vm_addr; #endif struct sockaddr *addr; socklen_t addr_len; char buf[page_size]; char *tempfile; int left; int cfd; int fd; int r, w; int once = 0; signal_setup(SIGUSR1, finish); tempfile = get_temp_file(host, port, cpu); if (!tempfile) return -ENOMEM; fd = open(tempfile, O_WRONLY | O_TRUNC | O_CREAT, 0644); if (fd < 0) pdie("creating %s", tempfile); if (type == USE_TCP) { addr = (struct sockaddr *)&peer_addr; addr_len = sizeof(peer_addr); #ifdef VSOCK } else if (type == USE_VSOCK) { addr = (struct sockaddr *)&vm_addr; addr_len = sizeof(vm_addr); #endif } if (type == USE_TCP || type == USE_VSOCK) { if (listen(sfd, backlog) < 0) pdie("listen"); cfd = accept(sfd, addr, &addr_len); if (cfd < 0 && errno == EINTR) goto done; if (cfd < 0) pdie("accept"); close(sfd); sfd = cfd; } for (;;) { /* TODO, make this copyless! */ r = read(sfd, buf, page_size); if (r < 0) { if (errno == EINTR) break; pdie("reading pages from client"); } if (!r) break; /* UDP requires that we get the full size in one go */ if (type == USE_UDP && r < page_size && !once) { once = 1; warning("read %d bytes, expected %d", r, page_size); } left = r; do { w = write(fd, buf + (r - left), left); if (w > 0) left -= w; } while (w >= 0 && left); } done: put_temp_file(tempfile); exit(0); } static int setup_vsock_port(int start_port, int *sfd) { int sd; sd = trace_vsock_make(start_port); if (sd < 0) return -errno; *sfd = sd; return start_port; } int trace_net_make(int port, enum port_type type) { struct addrinfo hints; struct addrinfo *result, *rp; char buf[BUFSIZ]; int sd; int s; snprintf(buf, BUFSIZ, "%d", port); memset(&hints, 0, sizeof(hints)); hints.ai_family = AF_UNSPEC; hints.ai_flags = AI_PASSIVE; switch (type) { case USE_TCP: hints.ai_socktype = SOCK_STREAM; break; case USE_UDP: hints.ai_socktype = SOCK_DGRAM; break; default: return -1; } s = getaddrinfo(NULL, buf, &hints, &result); if (s != 0) pdie("getaddrinfo: error opening socket"); for (rp = result; rp != NULL; rp = rp->ai_next) { sd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); if (sd < 0) continue; set_tcp_no_delay(sd, rp->ai_socktype); if (bind(sd, rp->ai_addr, rp->ai_addrlen) == 0) break; close(sd); } freeaddrinfo(result); if (rp == NULL) return -1; dprint("Create listen port: %d fd:%d\n", port, sd); return sd; } int trace_net_search(int start_port, int *sfd, enum port_type type) { int num_port = start_port; if (type == USE_VSOCK) return setup_vsock_port(start_port, sfd); again: *sfd = trace_net_make(num_port, type); if (*sfd < 0) { if (++num_port > MAX_PORT_SEARCH) pdie("No available ports to bind"); goto again; } return num_port; } static void fork_reader(int sfd, const char *node, const char *port, int *pid, int cpu, int pagesize, enum port_type type) { int ret; *pid = fork(); if (*pid < 0) pdie("creating reader"); if (!*pid) { ret = process_child(sfd, node, port, cpu, pagesize, type); if (ret < 0) pdie("Problem with reader %d", ret); } close(sfd); } static int open_port(const char *node, const char *port, int *pid, int cpu, int pagesize, int start_port, enum port_type type) { int sfd; int num_port; /* * trace_net_search() currently does not return an error, but if that * changes in the future, we have a check for it now. */ num_port = trace_net_search(start_port, &sfd, type); if (num_port < 0) return num_port; fork_reader(sfd, node, port, pid, cpu, pagesize, type); return num_port; } static int communicate_with_client(struct tracecmd_msg_handle *msg_handle) { char *last_proto = NULL; char buf[BUFSIZ]; char *option; int pagesize = 0; int options; int size; int cpus; int n, s, t, i; int ret = -EINVAL; int fd = msg_handle->fd; /* Let the client know what we are */ write(fd, "tracecmd", 8); try_again: /* read back the CPU count */ n = read_string(fd, buf, BUFSIZ); if (n == BUFSIZ) /** ERROR **/ return -EINVAL; cpus = atoi(buf); /* Is the client using the new protocol? */ if (cpus == -1) { if (memcmp(buf, V3_CPU, n) != 0) { /* If it did not send a version, then bail */ if (memcmp(buf, "-1V", 3)) { tracecmd_plog("Unknown string %s\n", buf); goto out; } /* Skip "-1" */ tracecmd_plog("Cannot handle the protocol %s\n", buf+2); /* If it returned the same command as last time, bail! */ if (last_proto && strncmp(last_proto, buf, n) == 0) { tracecmd_plog("Repeat of version %s sent\n", last_proto); goto out; } free(last_proto); last_proto = malloc(n + 1); if (last_proto) { memcpy(last_proto, buf, n); last_proto[n] = 0; } /* Return the highest protocol we can use */ write(fd, "V3", 3); goto try_again; } /* Let the client know we use v3 protocol */ write(fd, "V3", 3); /* read the rest of dummy data */ n = read(fd, buf, sizeof(V3_MAGIC)); if (memcmp(buf, V3_MAGIC, n) != 0) goto out; /* We're off! */ write(fd, "OK", 2); msg_handle->version = V3_PROTOCOL; /* read the CPU count, the page size, and options */ if ((pagesize = tracecmd_msg_initial_setting(msg_handle)) < 0) goto out; } else { /* The client is using the v1 protocol */ tracecmd_plog("cpus=%d\n", cpus); if (cpus < 0) goto out; msg_handle->cpu_count = cpus; /* next read the page size */ n = read_string(fd, buf, BUFSIZ); if (n == BUFSIZ) /** ERROR **/ goto out; pagesize = atoi(buf); tracecmd_plog("pagesize=%d\n", pagesize); if (pagesize <= 0) goto out; /* Now the number of options */ n = read_string(fd, buf, BUFSIZ); if (n == BUFSIZ) /** ERROR **/ return -EINVAL; options = atoi(buf); for (i = 0; i < options; i++) { /* next is the size of the options */ n = read_string(fd, buf, BUFSIZ); if (n == BUFSIZ) /** ERROR **/ goto out; size = atoi(buf); /* prevent a client from killing us */ if (size > MAX_OPTION_SIZE) goto out; ret = -ENOMEM; option = malloc(size); if (!option) goto out; ret = -EIO; t = size; s = 0; do { s = read(fd, option+s, t); if (s <= 0) { free(option); goto out; } t -= s; s = size - t; } while (t); s = process_option(msg_handle, option); free(option); /* do we understand this option? */ ret = -EINVAL; if (!s) goto out; } } if (msg_handle->flags & TRACECMD_MSG_FL_USE_TCP) tracecmd_plog("Using TCP for live connection\n"); ret = pagesize; out: free(last_proto); return ret; } static int create_client_file(const char *node, const char *port) { char buf[BUFSIZ]; int ofd; snprintf(buf, BUFSIZ, "%s.%s:%s.dat", output_file, node, port); ofd = open(buf, O_RDWR | O_CREAT | O_TRUNC, 0644); if (ofd < 0) pdie("Can not create file %s", buf); return ofd; } static void destroy_all_readers(int cpus, int *pid_array, const char *node, const char *port) { int cpu; for (cpu = 0; cpu < cpus; cpu++) { if (pid_array[cpu] > 0) { kill(pid_array[cpu], SIGKILL); waitpid(pid_array[cpu], NULL, 0); delete_temp_file(node, port, cpu); pid_array[cpu] = 0; } } free(pid_array); } static int *create_all_readers(const char *node, const char *port, int pagesize, struct tracecmd_msg_handle *msg_handle) { enum port_type port_type = USE_UDP; char buf[BUFSIZ]; unsigned int *port_array; int *pid_array; unsigned int start_port; unsigned int connect_port; int cpus = msg_handle->cpu_count; int cpu; int pid; if (!pagesize) return NULL; if (msg_handle->flags & TRACECMD_MSG_FL_USE_TCP) port_type = USE_TCP; else if (msg_handle->flags & TRACECMD_MSG_FL_USE_VSOCK) port_type = USE_VSOCK; port_array = malloc(sizeof(*port_array) * cpus); if (!port_array) return NULL; pid_array = malloc(sizeof(*pid_array) * cpus); if (!pid_array) { free(port_array); return NULL; } memset(pid_array, 0, sizeof(int) * cpus); start_port = START_PORT_SEARCH; /* Now create a port for each CPU */ for (cpu = 0; cpu < cpus; cpu++) { connect_port = open_port(node, port, &pid, cpu, pagesize, start_port, port_type); if (connect_port < 0) goto out_free; port_array[cpu] = connect_port; pid_array[cpu] = pid; /* * Due to some bugging finding ports, * force search after last port */ start_port = connect_port + 1; } if (msg_handle->version == V3_PROTOCOL) { /* send set of port numbers to the client */ if (tracecmd_msg_send_port_array(msg_handle, port_array) < 0) { tracecmd_plog("Failed sending port array\n"); goto out_free; } } else { /* send the client a comma deliminated set of port numbers */ for (cpu = 0; cpu < cpus; cpu++) { snprintf(buf, BUFSIZ, "%s%d", cpu ? "," : "", port_array[cpu]); write(msg_handle->fd, buf, strlen(buf)); } /* end with null terminator */ write(msg_handle->fd, "\0", 1); } free(port_array); return pid_array; out_free: free(port_array); destroy_all_readers(cpus, pid_array, node, port); return NULL; } static int collect_metadata_from_client(struct tracecmd_msg_handle *msg_handle, int ofd) { char buf[BUFSIZ]; int n, s, t; int ifd = msg_handle->fd; int ret = 0; do { n = read(ifd, buf, BUFSIZ); if (n < 0) { if (errno == EINTR) continue; ret = -errno; break; } t = n; s = 0; do { s = write(ofd, buf+s, t); if (s < 0) { if (errno == EINTR) break; ret = -errno; goto out; } t -= s; s = n - t; } while (t); } while (n > 0 && !tracecmd_msg_done(msg_handle)); out: return ret; } static void stop_all_readers(int cpus, int *pid_array) { int cpu; for (cpu = 0; cpu < cpus; cpu++) { if (pid_array[cpu] > 0) kill(pid_array[cpu], SIGUSR1); } } static int put_together_file(int cpus, int ofd, const char *node, const char *port, bool write_options) { struct tracecmd_output *handle = NULL; char **temp_files; int cpu; int ret = -ENOMEM; /* Now put together the file */ temp_files = malloc(sizeof(*temp_files) * cpus); if (!temp_files) return -ENOMEM; for (cpu = 0; cpu < cpus; cpu++) { temp_files[cpu] = get_temp_file(node, port, cpu); if (!temp_files[cpu]) goto out; } handle = tracecmd_get_output_handle_fd(ofd); if (!handle) { ret = -1; goto out; } if (write_options) { ret = tracecmd_write_cpus(handle, cpus); if (ret) goto out; ret = tracecmd_write_buffer_info(handle); if (ret) goto out; ret = tracecmd_write_options(handle); if (ret) goto out; } ret = tracecmd_write_cpu_data(handle, cpus, temp_files, NULL); out: tracecmd_output_close(handle); for (cpu--; cpu >= 0; cpu--) { put_temp_file(temp_files[cpu]); } free(temp_files); return ret; } static int process_client(struct tracecmd_msg_handle *msg_handle, const char *node, const char *port) { int *pid_array; int pagesize; int cpus; int ofd; int ret; pagesize = communicate_with_client(msg_handle); if (pagesize < 0) return pagesize; ofd = create_client_file(node, port); pid_array = create_all_readers(node, port, pagesize, msg_handle); if (!pid_array) { close(ofd); return -ENOMEM; } /* on signal stop this msg */ stop_msg_handle = msg_handle; /* Now we are ready to start reading data from the client */ if (msg_handle->version == V3_PROTOCOL) ret = tracecmd_msg_collect_data(msg_handle, ofd); else ret = collect_metadata_from_client(msg_handle, ofd); stop_msg_handle = NULL; /* wait a little to let our readers finish reading */ sleep(1); cpus = msg_handle->cpu_count; /* stop our readers */ stop_all_readers(cpus, pid_array); /* wait a little to have the readers clean up */ sleep(1); if (!ret) ret = put_together_file(cpus, ofd, node, port, msg_handle->version < V3_PROTOCOL); destroy_all_readers(cpus, pid_array, node, port); close(ofd); return ret; } static int do_fork(int cfd) { pid_t pid; /* in debug mode, we do not fork off children */ if (tracecmd_get_debug()) return 0; pid = fork(); if (pid < 0) { warning("failed to create child"); return -1; } if (pid > 0) { close(cfd); return pid; } signal_setup(SIGINT, finish); return 0; } bool trace_net_cmp_connection(struct sockaddr_storage *addr, const char *name) { char host[NI_MAXHOST], nhost[NI_MAXHOST]; char service[NI_MAXSERV]; socklen_t addr_len = sizeof(*addr); struct addrinfo *result, *rp; struct addrinfo hints; bool found = false; int s; if (getnameinfo((struct sockaddr *)addr, addr_len, host, NI_MAXHOST, service, NI_MAXSERV, NI_NUMERICSERV)) return -1; if (strcmp(host, name) == 0) return true; /* Check other IPs that name could be for */ memset(&hints, 0, sizeof(hints)); hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM; /* Check other IPs that name could be for */ s = getaddrinfo(name, NULL, &hints, &result); if (s != 0) return false; for (rp = result; rp != NULL; rp = rp->ai_next) { if (getnameinfo(rp->ai_addr, rp->ai_addrlen, nhost, NI_MAXHOST, service, NI_MAXSERV, NI_NUMERICSERV)) continue; if (strcmp(host, nhost) == 0) { found = 1; break; } } freeaddrinfo(result); return found; } bool trace_net_cmp_connection_fd(int fd, const char *name) { struct sockaddr_storage addr; socklen_t addr_len = sizeof(addr); if (getpeername(fd, (struct sockaddr *)&addr, &addr_len)) return false; return trace_net_cmp_connection(&addr, name); }; int trace_net_print_connection(int fd) { char host[NI_MAXHOST], service[NI_MAXSERV]; struct sockaddr_storage net_addr; socklen_t addr_len; addr_len = sizeof(net_addr); if (getpeername(fd, (struct sockaddr *)&net_addr, &addr_len)) return -1; if (getnameinfo((struct sockaddr *)&net_addr, addr_len, host, NI_MAXHOST, service, NI_MAXSERV, NI_NUMERICSERV)) return -1; if (tracecmd_get_debug()) tracecmd_debug("Connected to %s:%s fd:%d\n", host, service, fd); else tracecmd_plog("Connected to %s:%s\n", host, service); return 0; } static int do_connection(int cfd, struct sockaddr *addr, socklen_t addr_len) { struct tracecmd_msg_handle *msg_handle; char host[NI_MAXHOST], service[NI_MAXSERV]; int s; int ret; ret = do_fork(cfd); if (ret) return ret; msg_handle = tracecmd_msg_handle_alloc(cfd, 0); if (use_vsock) { #ifdef VSOCK struct sockaddr_vm *vm_addr = (struct sockaddr_vm *)addr; snprintf(host, NI_MAXHOST, "V%d", vm_addr->svm_cid); snprintf(service, NI_MAXSERV, "%d", vm_addr->svm_port); #endif } else { s = getnameinfo((struct sockaddr *)addr, addr_len, host, NI_MAXHOST, service, NI_MAXSERV, NI_NUMERICSERV); if (s == 0) tracecmd_plog("Connected with %s:%s\n", host, service); else { tracecmd_plog("Error with getnameinfo: %s\n", gai_strerror(s)); close(cfd); tracecmd_msg_handle_close(msg_handle); return -1; } } process_client(msg_handle, host, service); tracecmd_msg_handle_close(msg_handle); if (!tracecmd_get_debug()) exit(0); return 0; } static int *client_pids; static int free_pids; static int saved_pids; static void add_process(int pid) { int *client = NULL; int i; if (free_pids) { for (i = 0; i < saved_pids; i++) { if (!client_pids[i]) { client = &client_pids[i]; break; } } free_pids--; if (!client) warning("Could not find free pid"); } if (!client) { client_pids = realloc(client_pids, sizeof(*client_pids) * (saved_pids + 1)); if (!client_pids) pdie("allocating pids"); client = &client_pids[saved_pids++]; } *client = pid; } static void remove_process(int pid) { int i; for (i = 0; i < saved_pids; i++) { if (client_pids[i] == pid) break; } if (i == saved_pids) return; client_pids[i] = 0; free_pids++; } static void kill_clients(void) { int status; int i; for (i = 0; i < saved_pids; i++) { if (!client_pids[i]) continue; /* Only kill the clients if we received SIGINT or SIGTERM */ if (done) kill(client_pids[i], SIGINT); waitpid(client_pids[i], &status, 0); } saved_pids = 0; } static void clean_up(void) { int status; int ret; /* Clean up any children that has started before */ do { ret = waitpid(0, &status, WNOHANG); if (ret > 0) remove_process(ret); } while (ret > 0); } static void do_accept_loop(int sfd) { struct sockaddr_storage peer_addr; #ifdef VSOCK struct sockaddr_vm vm_addr; #endif struct sockaddr *addr; socklen_t addr_len; int cfd, pid; if (use_vsock) { #ifdef VSOCK addr = (struct sockaddr *)&vm_addr; addr_len = sizeof(vm_addr); #endif } else { addr = (struct sockaddr *)&peer_addr; addr_len = sizeof(peer_addr); } do { cfd = accept(sfd, addr, &addr_len); if (cfd < 0 && errno == EINTR) { clean_up(); continue; } if (cfd < 0) pdie("connecting"); pid = do_connection(cfd, addr, addr_len); if (pid > 0) add_process(pid); } while (!done); /* Get any final stragglers */ clean_up(); } void make_pid_file(const char *pidfile_basename) { char buf[PATH_MAX]; int fd; make_pid_name(buf, pidfile_basename); fd = open(buf, O_WRONLY | O_CREAT | O_TRUNC, 0644); if (fd < 0) { perror(buf); return; } sprintf(buf, "%d\n", getpid()); write(fd, buf, strlen(buf)); close(fd); } static void sigstub(int sig) { } static int get_vsock(const char *port) { unsigned int cid; int sd; sd = trace_vsock_make(atoi(port)); if (sd < 0) return sd; cid = trace_vsock_local_cid(); if (cid >= 0) printf("listening on @%u:%s\n", cid, port); return sd; } static int get_network(char *port) { struct addrinfo hints; struct addrinfo *result, *rp; int sfd, s; memset(&hints, 0, sizeof(hints)); hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM; hints.ai_flags = AI_PASSIVE; s = getaddrinfo(NULL, port, &hints, &result); if (s != 0) pdie("getaddrinfo: error opening %s", port); for (rp = result; rp != NULL; rp = rp->ai_next) { sfd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); if (sfd < 0) continue; set_tcp_no_delay(sfd, rp->ai_socktype); if (bind(sfd, rp->ai_addr, rp->ai_addrlen) == 0) break; close(sfd); } if (rp == NULL) pdie("Could not bind"); freeaddrinfo(result); return sfd; } static void do_listen(char *port) { int sfd; if (!tracecmd_get_debug()) signal_setup(SIGCHLD, sigstub); if (do_daemon) make_pid_file(LISTEN_PIDFILE); if (use_vsock) sfd = get_vsock(port); else sfd = get_network(port); if (listen(sfd, backlog) < 0) pdie("listen"); do_accept_loop(sfd); kill_clients(); if (do_daemon) remove_pid_file(LISTEN_PIDFILE); } static void start_daemon(void) { do_daemon = 1; if (daemon(1, 0) < 0) die("starting daemon"); } enum { OPT_verbose = 254, OPT_debug = 255, }; void trace_listen(int argc, char **argv) { char *logfile = NULL; char *port = NULL; int daemon = 0; int c; if (argc < 2) usage(argv); if (strcmp(argv[1], "listen") != 0) usage(argv); for (;;) { int option_index = 0; static struct option long_options[] = { {"port", required_argument, NULL, 'p'}, {"help", no_argument, NULL, '?'}, {"debug", no_argument, NULL, OPT_debug}, {"verbose", optional_argument, NULL, OPT_verbose}, {NULL, 0, NULL, 0} }; c = getopt_long (argc-1, argv+1, "+hp:Vo:d:l:D", long_options, &option_index); if (c == -1) break; switch (c) { case 'h': usage(argv); break; case 'p': port = optarg; break; case 'd': output_dir = optarg; break; case 'V': use_vsock = true; break; case 'o': output_file = optarg; break; case 'l': logfile = optarg; break; case 'D': daemon = 1; break; case OPT_debug: tracecmd_set_debug(true); break; case OPT_verbose: if (trace_set_verbose(optarg) < 0) die("invalid verbose level %s", optarg); break; default: usage(argv); } } if (!port) usage(argv); if ((argc - optind) >= 2) usage(argv); if (!output_file) output_file = default_output_file; if (!output_dir) output_dir = default_output_dir; if (logfile) { /* set the writes to a logfile instead */ if (tracecmd_set_logfile(logfile) < 0) die("creating log file %s", logfile); } if (chdir(output_dir) < 0) die("Can't access directory %s", output_dir); if (daemon) start_daemon(); signal_setup(SIGINT, finish); signal_setup(SIGTERM, finish); do_listen(port); return; }