diff --git a/ggated/Makefile b/ggated/Makefile new file mode 100644 index 0000000..feae550 --- /dev/null +++ b/ggated/Makefile @@ -0,0 +1,12 @@ +# $FreeBSD$ + +.PATH: ../shared + +BINDIR= /sbin +PROG= ggated +SRCS= ggated.c ggate.c +MAN= ggated.8 +WARNS= 6 +CFLAGS+=-I../shared + +.include diff --git a/ggated/ggated.8 b/ggated/ggated.8 new file mode 100644 index 0000000..6b1bab4 --- /dev/null +++ b/ggated/ggated.8 @@ -0,0 +1,113 @@ +.\" Copyright (c) 2004 Pawel Jakub Dawidek +.\" All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" $FreeBSD$ +.\" +.Dd April 29, 2004 +.Dt GGATED 8 +.Os +.Sh NAME +.Nm ggated +.Nd "GEOM Gate network daemon" +.Sh SYNOPSIS +.Nm +.Op Fl h +.Op Fl n +.Op Fl v +.Op Fl a Ar address +.Op Fl p Ar port +.Op Fl R Ar rcvbuf +.Op Fl S Ar sndbuf +.Op Ar "exports file" +.Sh DESCRIPTION +The +.Nm +utility is a network server for GEOM Gate class. +It runs on a server machine to service GEOM Gate requests from workers +placed on a client machine. +Keep in mind, that connection between +.Nm ggatec +and +.Nm ggated +is not encrypted. +.Pp +Available options: +.Bl -tag -width ".Ar exports file" +.It Fl a Ar address +Specifies an IP address to bind to. +.It Fl h +Print available options. +.It Fl n +Do not use TCP_NODELAY option on TCP sockets. +.It Fl p Ar port +Port on which +.Nm +listens for connection. Default is 3080. +.It Fl R Ar rcvbuf +Size of receive buffer to use. +Default is 131072 (128kB). +.It Fl S Ar sndbuf +Size of send buffer to use. +Default is 131072 (128kB). +.It Fl v +Do not fork, run in foreground and print debug informations on standard +output. +.It Ar "exports file" +An alternate location for the exports file. +.El +.Pp +The format of an exports file is as follows: +.Bd -literal -offset indent +1.2.3.4 RO /dev/acd0 +1.2.3.0/24 RW /tmp/test.img +hostname WO /tmp/image +.Ed +.Pp +.Sh EXAMPLES +Export CD\-ROM device and a file: +.Pp +.Bd -literal -offset indent +# echo "1.2.3.0/24 RO /dev/acd0" > /etc/gg.exports +# echo "client RW /image" >> /etc/gg.exports +# ggated +.Ed +.Pp +.Sh DIAGNOSTICS +Exit status is 0 on success, or 1 if the command fails. +To get details about the failure, +.Nm +should be called with the +.Fl v +option. +.Sh SEE ALSO +.Xr geom 4 , +.Xr ggatec 8 , +.Xr ggatel 8 +.Sh AUTHORS +The +.Nm +utility as well as this manual page was written by +.An -split +.An Pawel Jakub Dawidek Aq pjd@FreeBSD.org . +.An -nosplit diff --git a/ggated/ggated.c b/ggated/ggated.c new file mode 100644 index 0000000..09a06d0 --- /dev/null +++ b/ggated/ggated.c @@ -0,0 +1,649 @@ +/*- + * Copyright (c) 2004 Pawel Jakub Dawidek + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include "ggate.h" + + +#define G_GATED_EXPORT_FILE "/etc/gg.exports" +#define G_GATED_DEBUG(...) \ + if (g_gate_verbose) { \ + printf(__VA_ARGS__); \ + printf("\n"); \ + } + +static const char *exports = G_GATED_EXPORT_FILE; +static int got_sighup = 0; +static int nagle = 1; +static unsigned rcvbuf = G_GATE_RCVBUF; +static unsigned sndbuf = G_GATE_SNDBUF; + +struct export { + char *e_path; /* path to device/file */ + in_addr_t e_ip; /* remote IP address */ + in_addr_t e_mask; /* IP mask */ + unsigned e_flags; /* flags (RO/RW) */ + SLIST_ENTRY(export) e_next; +}; +static SLIST_HEAD(, export) exports_list = + SLIST_HEAD_INITIALIZER(&exports_list); + +static void +usage(void) +{ + + fprintf(stderr, "usage: %s [-nv] [-a address] [-p port] [-R rcvbuf] " + "[-S sndbuf] [exports file]\n", getprogname()); + exit(EXIT_FAILURE); +} + +static char * +ip2str(in_addr_t ip) +{ + static char sip[16]; + + snprintf(sip, sizeof(sip), "%u.%u.%u.%u", + ((ip >> 24) & 0xff), + ((ip >> 16) & 0xff), + ((ip >> 8) & 0xff), + (ip & 0xff)); + return (sip); +} + +static in_addr_t +countmask(unsigned m) +{ + in_addr_t mask; + + if (m == 0) { + mask = 0x0; + } else { + mask = 1 << (32 - m); + mask--; + mask = ~mask; + } + return (mask); +} + +static void +line_parse(char *line, unsigned lineno) +{ + struct export *ex; + char *word, *path, *sflags; + unsigned flags, i, vmask; + in_addr_t ip, mask; + + ip = mask = flags = vmask = 0; + path = NULL; + sflags = NULL; + + for (i = 0, word = strtok(line, " \t"); word != NULL; + i++, word = strtok(NULL, " \t")) { + switch (i) { + case 0: /* IP address or host name */ + ip = g_gate_str2ip(strsep(&word, "/")); + if (ip == INADDR_NONE) { + g_gate_xlog("Invalid IP/host name at line %u.", + lineno); + } + ip = ntohl(ip); + if (word == NULL) + vmask = 32; + else { + errno = 0; + vmask = strtoul(word, NULL, 10); + if (vmask == 0 && errno != 0) { + g_gate_xlog("Invalid IP mask value at " + "line %u.", lineno); + } + if ((unsigned)vmask > 32) { + g_gate_xlog("Invalid IP mask value at line %u.", + lineno); + } + } + mask = countmask(vmask); + break; + case 1: /* flags */ + if (strcasecmp("rd", word) == 0 || + strcasecmp("ro", word) == 0) { + flags = O_RDONLY; + } else if (strcasecmp("wo", word) == 0) { + flags = O_WRONLY; + } else if (strcasecmp("rw", word) == 0) { + flags = O_RDWR; + } else { + g_gate_xlog("Invalid value in flags field at " + "line %u.", lineno); + } + sflags = word; + break; + case 2: /* path */ + if (strlen(word) >= MAXPATHLEN) { + g_gate_xlog("Path too long at line %u. ", + lineno); + } + path = word; + break; + default: + g_gate_xlog("Too many arguments at line %u. ", lineno); + } + } + if (i != 3) + g_gate_xlog("Too few arguments at line %u.", lineno); + + ex = malloc(sizeof(*ex)); + if (ex == NULL) + g_gate_xlog("No enough memory."); + ex->e_path = strdup(path); + if (ex->e_path == NULL) + g_gate_xlog("No enough memory."); + + /* Made 'and' here. */ + ex->e_ip = (ip & mask); + ex->e_mask = mask; + ex->e_flags = flags; + + SLIST_INSERT_HEAD(&exports_list, ex, e_next); + + g_gate_log(LOG_DEBUG, "Added %s/%u %s %s to exports list.", + ip2str(ex->e_ip), vmask, path, sflags); +} + +static void +exports_clear(void) +{ + struct export *ex; + + while (!SLIST_EMPTY(&exports_list)) { + ex = SLIST_FIRST(&exports_list); + SLIST_REMOVE_HEAD(&exports_list, e_next); + free(ex); + } +} + +#define EXPORTS_LINE_SIZE 2048 +static void +exports_get(void) +{ + char buf[EXPORTS_LINE_SIZE], *line; + unsigned lineno = 0, objs = 0, len; + FILE *fd; + + exports_clear(); + + fd = fopen(exports, "r"); + if (fd == NULL) { + g_gate_xlog("Cannot open exports file (%s): %s.", exports, + strerror(errno)); + } + + g_gate_log(LOG_INFO, "Reading exports file (%s).", exports); + + for (;;) { + if (fgets(buf, sizeof(buf), fd) == NULL) { + if (feof(fd)) + break; + + g_gate_xlog("Error while reading exports file: %s.", + strerror(errno)); + } + + /* Increase line count. */ + lineno++; + + /* Skip spaces and tabs. */ + for (line = buf; *line == ' ' || *line == '\t'; ++line) + ; + + /* Empty line, comment or empty line at the end of file. */ + if (*line == '\n' || *line == '#' || *line == '\0') + continue; + + len = strlen(line); + if (line[len - 1] == '\n') { + /* Remove new line char. */ + line[len - 1] = '\0'; + } else { + if (!feof(fd)) + g_gate_xlog("Line %u too long.", lineno); + } + + line_parse(line, lineno); + objs++; + } + + fclose(fd); + + if (objs == 0) + g_gate_xlog("There are no objects to export."); + + g_gate_log(LOG_INFO, "Exporting %u object(s).", objs); +} + +static struct export * +exports_find(struct sockaddr *s, const char *path) +{ + struct export *ex; + in_addr_t ip; + + ip = htonl(((struct sockaddr_in *)s)->sin_addr.s_addr); + SLIST_FOREACH(ex, &exports_list, e_next) { + if ((ip & ex->e_mask) != ex->e_ip) + continue; + if (path != NULL && strcmp(path, ex->e_path) != 0) + continue; + + g_gate_log(LOG_INFO, "Connection from: %s.", ip2str(ip)); + return (ex); + } + g_gate_log(LOG_INFO, "Unauthorized connection from: %s.", ip2str(ip)); + + return (NULL); +} + +static void +sendfail(int sfd, int error, const char *fmt, ...) +{ + struct g_gate_sinit sinit; + va_list ap; + int data; + + sinit.gs_error = error; + g_gate_swap2n_sinit(&sinit); + data = send(sfd, &sinit, sizeof(sinit), 0); + g_gate_swap2h_sinit(&sinit); + if (data == -1) { + g_gate_xlog("Error while sending initial packet: %s.", + strerror(errno)); + } + if (fmt != NULL) { + va_start(ap, fmt); + g_gate_xvlog(fmt, ap); + /* NOTREACHED */ + va_end(ap); + } + exit(EXIT_FAILURE); +} + +static void +serve(int sfd, struct sockaddr *s) +{ + struct g_gate_cinit cinit; + struct g_gate_sinit sinit; + struct g_gate_hdr hdr; + struct export *ex; + char ipmask[32]; /* 32 == strlen("xxx.xxx.xxx.xxx/xxx.xxx.xxx.xxx")+1 */ + size_t bufsize; + int32_t error; + int fd, flags; + ssize_t data; + char *buf; + + g_gate_log(LOG_DEBUG, "Receiving initial packet."); + data = recv(sfd, &cinit, sizeof(cinit), MSG_WAITALL); + g_gate_swap2h_cinit(&cinit); + if (data == -1) { + g_gate_xlog("Error while receiving initial packet: %s.", + strerror(errno)); + } + + ex = exports_find(s, cinit.gc_path); + if (ex == NULL) { + sendfail(sfd, EINVAL, "Requested path isn't exported: %s.", + strerror(errno)); + } + + error = 0; + strlcpy(ipmask, ip2str(ex->e_ip), sizeof(ipmask)); + strlcat(ipmask, "/", sizeof(ipmask)); + strlcat(ipmask, ip2str(ex->e_mask), sizeof(ipmask)); + if ((cinit.gc_flags & G_GATE_FLAG_READONLY) != 0) { + if (ex->e_flags == O_WRONLY) { + g_gate_log(LOG_ERR, "Read-only access requested, but " + "%s (%s) is exported write-only.", ex->e_path, + ipmask); + error = EPERM; + } else { + sinit.gs_flags = G_GATE_FLAG_READONLY; + } + } else if ((cinit.gc_flags & G_GATE_FLAG_WRITEONLY) != 0) { + if (ex->e_flags == O_RDONLY) { + g_gate_log(LOG_ERR, "Write-only access requested, but " + "%s (%s) is exported read-only.", ex->e_path, + ipmask); + error = EPERM; + } else { + sinit.gs_flags = G_GATE_FLAG_WRITEONLY; + } + } else { + if (ex->e_flags == O_RDONLY) { + g_gate_log(LOG_ERR, "Read-write access requested, but " + "%s (%s) is exported read-only.", ex->e_path, + ipmask); + error = EPERM; + } else if (ex->e_flags == O_WRONLY) { + g_gate_log(LOG_ERR, "Read-write access requested, but " + "%s (%s) is exported write-only.", ex->e_path, + ipmask); + error = EPERM; + } else { + sinit.gs_flags = 0; + } + } + if (error != 0) + sendfail(sfd, error, NULL); + flags = g_gate_openflags(sinit.gs_flags);; + fd = open(ex->e_path, flags); + if (fd < 0) { + sendfail(sfd, errno, "Error while opening %s: %s.", ex->e_path, + strerror(errno)); + } + + g_gate_log(LOG_DEBUG, "Sending initial packet."); + /* + * This field isn't used by ggc(8) for now. + * It should be used in future when user don't give device size. + */ + sinit.gs_mediasize = g_gate_mediasize(fd); + sinit.gs_sectorsize = g_gate_sectorsize(fd); + sinit.gs_error = 0; + g_gate_swap2n_sinit(&sinit); + data = send(sfd, &sinit, sizeof(sinit), 0); + g_gate_swap2h_sinit(&sinit); + if (data == -1) { + sendfail(sfd, errno, "Error while sending initial packet: %s.", + strerror(errno)); + } + + bufsize = G_GATE_BUFSIZE_START; + buf = malloc(bufsize); + if (buf == NULL) + g_gate_xlog("No enough memory."); + + g_gate_log(LOG_DEBUG, "New process: %u.", getpid()); + + for (;;) { + /* + * Receive request. + */ + data = recv(sfd, &hdr, sizeof(hdr), MSG_WAITALL); + if (data == 0) { + g_gate_log(LOG_DEBUG, "Process %u exiting.", getpid()); + exit(EXIT_SUCCESS); + } else if (data == -1) { + g_gate_xlog("Error while receiving hdr packet: %s.", + strerror(errno)); + } else if (data != sizeof(hdr)) { + g_gate_xlog("Malformed hdr packet received."); + } + g_gate_log(LOG_DEBUG, "Received hdr packet."); + g_gate_swap2h_hdr(&hdr); + + /* + * Increase buffer if there is need to. + */ + if (hdr.gh_length > bufsize) { + bufsize = hdr.gh_length; + g_gate_log(LOG_DEBUG, "Increasing buffer to %u.", + bufsize); + buf = realloc(buf, bufsize); + if (buf == NULL) + g_gate_xlog("No enough memory."); + } + + if (hdr.gh_cmd == BIO_READ) { + if (pread(fd, buf, hdr.gh_length, + hdr.gh_offset) == -1) { + error = errno; + g_gate_log(LOG_ERR, "Error while reading data " + "(offset=%ju, size=%zu): %s.", + (uintmax_t)hdr.gh_offset, + (size_t)hdr.gh_length, strerror(error)); + } else { + error = 0; + } + hdr.gh_error = error; + g_gate_swap2n_hdr(&hdr); + if (send(sfd, &hdr, sizeof(hdr), 0) == -1) { + g_gate_xlog("Error while sending status: %s.", + strerror(errno)); + } + g_gate_swap2h_hdr(&hdr); + /* Send data only if there was no error while pread(). */ + if (error == 0) { + data = send(sfd, buf, hdr.gh_length, 0); + if (data == -1) { + g_gate_xlog("Error while sending data: " + "%s.", strerror(errno)); + } + g_gate_log(LOG_DEBUG, "Sent %d bytes " + "(offset=%ju, size=%zu).", data, + (uintmax_t)hdr.gh_offset, + (size_t)hdr.gh_length); + } + } else /* if (hdr.gh_cmd == BIO_WRITE) */ { + g_gate_log(LOG_DEBUG, "Waiting for %u bytes of data...", + hdr.gh_length); + data = recv(sfd, buf, hdr.gh_length, MSG_WAITALL); + if (data == -1) { + g_gate_xlog("Error while receiving data: %s.", + strerror(errno)); + } + if (pwrite(fd, buf, hdr.gh_length, hdr.gh_offset) == -1) { + error = errno; + g_gate_log(LOG_ERR, "Error while writing data " + "(offset=%llu, size=%u): %s.", + hdr.gh_offset, hdr.gh_length, + strerror(error)); + } else { + error = 0; + } + hdr.gh_error = error; + g_gate_swap2n_hdr(&hdr); + if (send(sfd, &hdr, sizeof(hdr), 0) == -1) { + g_gate_xlog("Error while sending status: %s.", + strerror(errno)); + } + g_gate_swap2h_hdr(&hdr); + g_gate_log(LOG_DEBUG, "Received %d bytes (offset=%llu, " + "size=%u).", data, hdr.gh_offset, hdr.gh_length); + } + g_gate_log(LOG_DEBUG, "Tick."); + } +} + +static void +huphandler(int sig __unused) +{ + + got_sighup = 1; +} + +int +main(int argc, char *argv[]) +{ + struct sockaddr_in serv; + struct sockaddr from; + in_addr_t bindaddr; + socklen_t fromlen; + struct timeval tv; + int on, sfd, tmpsfd; + pid_t childpid; + unsigned bsize, port; + + bindaddr = htonl(INADDR_ANY); + port = G_GATE_PORT; + for (;;) { + int ch; + + ch = getopt(argc, argv, "a:hnp:R:S:v"); + if (ch == -1) + break; + switch (ch) { + case 'a': + bindaddr = g_gate_str2ip(optarg); + if (bindaddr == INADDR_NONE) { + errx(EXIT_FAILURE, + "Invalid IP/host name to bind to."); + } + break; + case 'n': + nagle = 0; + break; + case 'p': + errno = 0; + port = strtoul(optarg, NULL, 10); + if (port == 0 && errno != 0) + errx(EXIT_FAILURE, "Invalid port."); + break; + case 'R': + errno = 0; + rcvbuf = strtoul(optarg, NULL, 10); + if (rcvbuf == 0 && errno != 0) + errx(EXIT_FAILURE, "Invalid rcvbuf."); + break; + case 'S': + errno = 0; + sndbuf = strtoul(optarg, NULL, 10); + if (sndbuf == 0 && errno != 0) + errx(EXIT_FAILURE, "Invalid sndbuf."); + break; + case 'v': + g_gate_verbose++; + break; + case 'h': + default: + usage(); + } + } + argc -= optind; + argv += optind; + + if (argv[0] != NULL) + exports = argv[0]; + exports_get(); + + if (!g_gate_verbose) { + /* Run in daemon mode. */ + if (daemon(0, 0) < 0) + g_gate_xlog("Can't daemonize: %s", strerror(errno)); + } + + signal(SIGCHLD, SIG_IGN); + + sfd = socket(AF_INET, SOCK_STREAM, 0); + if (sfd < 0) + g_gate_xlog("Can't open stream socket: %s.", strerror(errno)); + bzero(&serv, sizeof(serv)); + serv.sin_family = AF_INET; + serv.sin_addr.s_addr = bindaddr; + serv.sin_port = htons(port); + on = 1; + if (nagle) { + if (setsockopt(sfd, IPPROTO_TCP, TCP_NODELAY, &on, + sizeof(on)) < 0) { + g_gate_xlog("setsockopt() error: %s.", strerror(errno)); + } + } + if (setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0) + g_gate_xlog("setsockopt(): %s.", strerror(errno)); + bsize = rcvbuf; + if (setsockopt(sfd, SOL_SOCKET, SO_RCVBUF, &bsize, sizeof(bsize)) < 0) + g_gate_xlog("setsockopt(): %s.", strerror(errno)); + bsize = sndbuf; + if (setsockopt(sfd, SOL_SOCKET, SO_SNDBUF, &bsize, sizeof(bsize)) < 0) + g_gate_xlog("setsockopt(): %s.", strerror(errno)); + tv.tv_sec = 10; + tv.tv_usec = 0; + if (setsockopt(sfd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)) || + setsockopt(sfd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) < 0) { + g_gate_xlog("setsockopt() error: %s.", strerror(errno)); + } + if (bind(sfd, (struct sockaddr *)&serv, sizeof(serv)) < 0) + g_gate_xlog("bind(): %s.", strerror(errno)); + if (listen(sfd, 5) < 0) + g_gate_xlog("listen(): %s.", strerror(errno)); + + g_gate_log(LOG_INFO, "Listen on port: %d.", port); + + signal(SIGHUP, huphandler); + + for (;;) { + fromlen = sizeof(from); + tmpsfd = accept(sfd, &from, &fromlen); + if (tmpsfd < 0) + g_gate_xlog("accept(): %s.", strerror(errno)); + + if (got_sighup) { + got_sighup = 0; + exports_get(); + } + + if (exports_find(&from, NULL) == NULL) { + close(tmpsfd); + continue; + } + + childpid = fork(); + if (childpid < 0) { + g_gate_xlog("Cannot create child process: %s.", + strerror(errno)); + } else if (childpid == 0) { + close(sfd); + serve(tmpsfd, &from); + /* NOTREACHED */ + } + close(tmpsfd); + } + close(sfd); + exit(EXIT_SUCCESS); +}