GEOM Gate network daemon.

This commit is contained in:
Pawel Jakub Dawidek 2004-04-30 16:19:50 +00:00
parent 2041034c3d
commit d1d669bd76
3 changed files with 774 additions and 0 deletions

View File

@ -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 <bsd.prog.mk>

113
sbin/ggate/ggated/ggated.8 Normal file
View File

@ -0,0 +1,113 @@
.\" Copyright (c) 2004 Pawel Jakub Dawidek <pjd@FreeBSD.org>
.\" 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

649
sbin/ggate/ggated/ggated.c Normal file
View File

@ -0,0 +1,649 @@
/*-
* Copyright (c) 2004 Pawel Jakub Dawidek <pjd@FreeBSD.org>
* 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 <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/param.h>
#include <sys/queue.h>
#include <sys/endian.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/disk.h>
#include <sys/bio.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <signal.h>
#include <err.h>
#include <errno.h>
#include <string.h>
#include <libgen.h>
#include <syslog.h>
#include <stdarg.h>
#include <geom/gate/g_gate.h>
#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);
}