/*
 * $Id: lcrashd.c,v 1.1 2002/01/18 12:35:49 felfert Exp $
 *
 * This file is part of lcrashd, a daemon for remote access of lcrash
 * via TCP.
 *
 * Copyright 2001 by Fritz Elfert <felfert@millenux.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version. See the file COPYING for more
 * information.
 */
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <signal.h>
#include <ctype.h>
#include <fcntl.h>
#include <errno.h>
#include <syslog.h>
#include <stdarg.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#if HAVE_SYS_WAIT_H
# include <sys/wait.h>
#endif
#ifndef WEXITSTATUS
# define WEXITSTATUS(stat_val) ((unsigned)(stat_val) >> 8)
#endif
#ifndef WIFEXITED
# define WIFEXITED(stat_val) (((stat_val) & 255) == 0)
#endif
#include <netdb.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <getopt.h>

#undef DEBUG

#define BUFFSIZE 0x8000

int  sv_socket;
int  lc_stdin;
int  lc_stdout;
int  lc_stderr;
int  lc_running;
int  lc_pid;
long lc_status;
int  tmp_stdin[2];
int  tmp_stdout[2];
int  tmp_stderr[2];
int  sock_eof;

char *lc_path;
char *lc_argv;

int raw_buflen;
int cmd_buflen;
int ctrl_buflen;
char *raw_buf;
char *cmd_buf;
char *ctrl_buf;
char *child_buf;

typedef struct {
    char c;
    unsigned short l;
} __attribute__((packed)) msgHead;

int log_dst;
#define LOGDST_STDIO  0
#define LOGDST_SYSLOG 1

int eprint(char *fmt, ...) {
    va_list ap;
    int ret = 0;

    va_start(ap, fmt);
    if (log_dst == LOGDST_STDIO)
	ret = vfprintf(stderr, fmt, ap);
    else {
	char *buf;
	int len = vsnprintf(NULL, 0, fmt, ap) + 1;

	buf = (char *)malloc(len);
	if (buf) {
		va_start(ap, fmt);
		ret = vsnprintf(buf, len, fmt, ap);
		syslog(LOG_ERR, "%s\n", buf);
		free(buf);
	}
    }
    va_end(ap);
    return ret;
}

int nprint(char *fmt, ...) {
    va_list ap;
    int ret = 0;

    va_start(ap, fmt);
    if (log_dst == LOGDST_STDIO)
	ret = vfprintf(stdout, fmt, ap);
    else {
	char *buf;
	int len = vsnprintf(NULL, 0, fmt, ap) + 1;

	buf = (char *)malloc(len);
	if (buf) {
		va_start(ap, fmt);
		ret = vsnprintf(buf, len, fmt, ap);
		syslog(LOG_INFO, "%s\n", buf);
		free(buf);
	}
    }
    va_end(ap);
    return ret;
}

void PERROR(const char *s) {
    if (log_dst == LOGDST_STDIO)
	perror(s);
    else
	eprint("%s: %m", s);
}

#ifdef DEBUG
void dump_h(msgHead *h, char *title) {
    fprintf(stderr, "Dump of %smsgHead (%d bytes):\n", title, sizeof(msgHead));
    fprintf(stderr, "  muxCode = %c (0x%02x)\n", isprint(h->c) ? h->c : '.', h->c);
    fprintf(stderr, "  len = %d\n", ntohs(h->l));
}
#endif

void fillbuffers(int fd) {
    msgHead h;
    int len;
    char *p;
    char *dst;
    int *dstl;
    int space = BUFFSIZE - raw_buflen;
    if (space <= 0)
	return;
    len = read(fd, raw_buf + raw_buflen, space);
    if (len == 0)
	sock_eof = 1;
    if (len < 0) {
	switch (errno) {
	    case EINTR:
		break;
	    case EAGAIN:
		break;
	    default:
		PERROR("read");
		exit(-1);
	}
	return;
    }
    raw_buflen += len;

    p = raw_buf;
    while (raw_buflen >= sizeof(h)) {
    	dst = 0;
	memcpy(&h, p, sizeof(h));
	p += sizeof(h);
	raw_buflen -= sizeof(h);
#ifdef DEBUG
	dump_h(&h, "RX ");
#endif
	switch (h.c) {
	    case 'C':
		dst = ctrl_buf;
		dstl = &ctrl_buflen;
		break;
	    case 'I':
		dst = cmd_buf;
		dstl = &cmd_buflen;
		break;
	    default:
		eprint("FATAL: got multiplexer code %02x\n", h.c);
		exit(-1);
		break;
	}
	h.l = ntohs(h.l);
	if ((dst) && (h.l > 0)) {
#ifdef DEBUG
	    char *dbuf = malloc(h.l + 1);
	    strncpy(dbuf, p, h.l);
	    fprintf(stderr, "RX buf: '%s'\n", dbuf);
	    free(dbuf);
#endif
	    memcpy(dst + *dstl, p, h.l);
	    *dstl += h.l;
	    p += h.l;
	    raw_buflen -= h.l;
	}
    }
    if (raw_buflen > 0)
	memcpy(raw_buf, p, raw_buflen);
}

char *getLINE(char *buf, int *buflen, int striplf) {
    char *lf;
    char *ret = NULL;
    if (*buflen <= 0)
    	return ret;
    lf = memchr(buf, '\n', *buflen);
    if (lf) {
	if (striplf)
	    *lf = 0;
	lf++;
	ret = malloc(lf - buf + 1);
	if (ret) {
	    memset(ret, 0, lf - buf + 1);
	    strncpy(ret, buf, lf - buf);
	    *buflen -= (lf - buf);
	    if (*buflen > 0)
		memcpy(buf, lf, *buflen);
	}
    }
    return ret;
}

#define getCTRL() getLINE(ctrl_buf, &ctrl_buflen, 1)
#define getCMD() getLINE(cmd_buf, &cmd_buflen, 0)

void start_lcrash() {
    int fd[2]; // startup-pipe
    pid_t _pid;
    int acount = 1;
    int in_ws = 1;
    int in_quote = 0;
    char *p;
    char *q;
    char **arglist;

#ifdef DEBUG
    fprintf(stderr, "Starting lcrash ...\n"); fflush(stderr);
#endif

    arglist = (char **)malloc(2 * sizeof(char *));
    arglist[0] = lc_path;
    q = lc_argv;
    for (p = lc_argv; p && *p; p++) {
	switch (*p) {
	    case '"':
		in_quote = !in_quote;
		if (in_quote)
			break;
		else
			in_ws = 0;
		// fall thru
	    case ' ':
	    case '\t':
	    case '\n':
		if (!(in_quote || in_ws)) {
	    		*p = 0;
	    		arglist[acount++] = strdup(q);
			arglist = (char **)realloc(arglist,
				(acount + 1) * sizeof(char *));
	    		in_ws = 1;
		}
		break;
	    default:
		if (in_ws)
		    q = p;
		in_ws = 0;
		break;
	}
    }
    if (strlen(q)) {
	arglist[acount++] = strdup(q);
	arglist = (char **)realloc(arglist, (acount + 1) * sizeof(char *));
    }
    arglist[acount] = 0;
    if (socketpair(AF_UNIX, SOCK_STREAM, 0, tmp_stdin) < 0) {
	PERROR("socketpair stdin");
	return;
    }
    if (socketpair(AF_UNIX, SOCK_STREAM, 0, tmp_stdout) < 0) {
	PERROR("socketpair stdout");
	close(tmp_stdin[0]);
	close(tmp_stdin[1]);
	return;
    }
    if (socketpair(AF_UNIX, SOCK_STREAM, 0, tmp_stderr) < 0) {
	PERROR("socketpair stderr");
	close(tmp_stdin[0]);
	close(tmp_stdin[1]);
	close(tmp_stderr[0]);
	close(tmp_stderr[1]);
	return;
    }
    if (pipe(fd) < 0) {
	// startup pipe failed, don't care .. will work anyway
	fd[0] = fd[1] = 0;
    }
    _pid = fork();
    if (_pid == 0) {
	// child
	int one = 1;
	struct linger so;
	struct sigaction act;

	if (fd[0])
	    close(fd[0]);

	close(tmp_stdin[1]);
	close(tmp_stdout[0]);
	close(tmp_stderr[0]);
	if (dup2(tmp_stdin[0], STDIN_FILENO) < 0) {
	    PERROR("dup2 stdin");
	    exit(-1);
	}
	if (dup2(tmp_stdout[1], STDOUT_FILENO) < 0) {
	    PERROR("dup2 stdout");
	    exit(-1);
	}
	if (setsockopt(tmp_stdout[1], SOL_SOCKET, SO_LINGER, &so, sizeof(so))) {
	    PERROR("setsockopt stdout");
	    exit(-1);
	}
	if (dup2(tmp_stderr[1], STDERR_FILENO) < 0) {
	    PERROR("dup stderr");
	    exit(-1);
	}
	if (setsockopt(tmp_stderr[1], SOL_SOCKET, SO_LINGER, &so, sizeof(so)))
	    exit(-1); // PERROR makes no sense, already duped ...

	// restore default SIGPIPE handler
	sigemptyset(&(act.sa_mask));
	sigaddset(&(act.sa_mask), SIGPIPE);
	act.sa_handler = SIG_DFL;
	act.sa_flags = 0;
	sigaction(SIGPIPE, &act, 0L);
	// set the close on exec flag.
	// Closing of fd[1] indicates that the execvp succeeded!
	if (fd[1])
	    fcntl(fd[1], F_SETFD, FD_CLOEXEC);
	execvp(arglist[0], arglist);
	// when we reach here, something went wrong.
	// Notify parent by writing 1 to pipe.
	if (fd[1])
	    write(fd[1], &one, 1);
	exit(-1);
    } else if (_pid == -1) {
	// fork failed
	free(arglist);
    } else {
	// parent
	if (fd[1])
	    close(fd[1]);
	// wait for child setup
	if (fd[0]) {
	    for (;;) {
		char result;
		int n = read(fd[0], &result, 1);
		if (n == 1) {
		    // Got error notification
		    close(fd[0]);
		    free(arglist);
		    _pid = 0;
		    return;
		}
		if (n == -1) {
		    if ((errno == ECHILD) || (errno == EINTR))
			continue;
		}
		break; // success
	    }
	    close(fd[0]);
	}

	// cleanup unneeded files
	close(tmp_stdin[0]);
	close(tmp_stdout[1]);
	close(tmp_stderr[1]);
	lc_stdin = tmp_stdin[1];
	lc_stdout = tmp_stdout[0];
	lc_stderr = tmp_stderr[0];
	lc_pid = _pid;
    }
}

int writemsg(int fd, char dst, char *buf, int l) {
    int r = 0;
    msgHead h;
    char *ibuf = malloc(((l > 0) ? l : strlen(buf)) + 2);
    char *p = ibuf;
    char *q;

    if (l < 0) {
	// control channel: strip whitespace, append LF
    	strncpy(ibuf, buf, strlen(buf));
    	ibuf[strlen(buf)] = 0;
    	while (*p && isspace(*p))
		p++;
    	q = p + strlen(p);
    	if (q == p)
		goto done;
    	q--;
    	while ((q > p) && isspace(*q))
		q--;
    	if (q == p)
		goto done;
    	*(++q) = 0;
    	strcat(p, "\n");
    } else {
	// data channel: keep as is
    	strncpy(ibuf, buf, l);
    	ibuf[l] = 0;
	p = ibuf;
    }
    h.c = dst;
    h.l = htons(strlen(p));
#ifdef DEBUG
    dump_h(&h, "TX ");
    fprintf(stderr, "TX buf: '%s'\n", p);
#endif
    r = write(fd, &h, sizeof(h));
    if (r == sizeof(h))
    	r = write(fd, p, strlen(p));
done:
    free(ibuf);
    return r;
}

#define writeCTRL(b) writemsg(sock,'C',b,-1)
#define writeSTDOUT(b,l) writemsg(sock,'O',b,l)
#define writeSTDERR(b,l) writemsg(sock,'E',b,l)

void setfd(fd_set *s, int fd, int *n) {
	if (fd > *n)
	    *n = fd;
	FD_SET(fd, s);
}

void child_handler(int dummy) {
    int status;
    pid_t this_pid;
    int saved_errno;

    // since waitpid and write change errno, we have to save it and restore it
    // (Richard Stevens, Advanced programming in the Unix Environment)
    saved_errno = errno;
    do {
	this_pid = waitpid(-1, &status, WNOHANG);
	if (this_pid > 0) {
	    if (this_pid == lc_pid) {
	    	lc_status = status;
		lc_running = 0;
	    }
	}
    } while (this_pid > 0);
    errno = saved_errno;
}

void cmdloop(int sock) {
    struct sigaction act;
    struct sockaddr_in peer;
    int n;

    sock_eof = 0;
    lc_running = 0;

    n = sizeof(peer);
    if (getpeername(sock, (struct sockaddr *)&peer, &n) < 0) {
	PERROR("getpeername");
	exit(-1);
    }
    nprint("Connection from %s\n", inet_ntoa(peer.sin_addr));

    // install SIGCHLD handler
    act.sa_handler=child_handler;
    sigemptyset(&(act.sa_mask));
    sigaddset(&(act.sa_mask), SIGCHLD);
    // Make sure we don't block this signal. gdb tends to do that :-(
    sigprocmask(SIG_UNBLOCK, &(act.sa_mask), 0); 

    act.sa_flags = SA_NOCLDSTOP;

    // take care of SunOS which automatically restarts interrupted system
    // calls (and thus does not have SA_RESTART)
#ifdef SA_RESTART
    act.sa_flags |= SA_RESTART;
#endif
    sigaction( SIGCHLD, &act, 0L); 

    // Ignore SIGPIPE
    act.sa_handler=SIG_IGN;
    sigemptyset(&(act.sa_mask));
    sigaddset(&(act.sa_mask), SIGPIPE);
    act.sa_flags = 0;
    sigaction( SIGPIPE, &act, 0L);

    lc_pid = 0;
    if (!(cmd_buf = (char *)malloc(BUFFSIZE))) {
	eprint("Could not allocate %d bytes for cmd_buf\n", BUFFSIZE);
	exit(-1);
    }
    if (!(raw_buf = (char *)malloc(BUFFSIZE))) {
	eprint("Could not allocate %d bytes for raw_buf\n", BUFFSIZE);
	exit(-1);
    }
    if (!(ctrl_buf = (char *)malloc(BUFFSIZE))) {
	eprint("Could not allocate %d bytes for ctrl_buf\n", BUFFSIZE);
	exit(-1);
    }
    if (!(child_buf = (char *)malloc(BUFFSIZE))) {
	eprint("Could not allocate %d bytes for child_buf\n", BUFFSIZE);
	exit(-1);
    }
    raw_buflen = cmd_buflen = ctrl_buflen = 0;

    writeCTRL("A");
    while (!sock_eof) {
    	int rc;
	int n;
	fd_set rfd;
	FD_ZERO(&rfd);
	setfd(&rfd, sock, &n);
	if (lc_running && (lc_pid > 0)) {
	    setfd(&rfd, lc_stdout, &n);
	    setfd(&rfd, lc_stderr, &n);
	}
	rc = select(n + 1, &rfd, NULL, NULL, NULL);
	if (lc_pid && (!lc_running)) {
	    char buf[20];
	    lc_pid = 0;
	    nprint("lcrash died with status %ld,  notifying client...\n",
		    lc_status);
	    sprintf(buf, "D%08x", htonl(lc_status));
	    writeCTRL(buf);
	}
	switch (rc) {
	    case -1:
		if (errno != EINTR) {
		    PERROR("select");
		    exit(-1);
		}
		break;
	    case 0:
		break;
	    default:
		if (lc_pid && lc_running) {
		    int r;
		    if (FD_ISSET(lc_stdout, &rfd)) {
			r = read(lc_stdout, child_buf, BUFFSIZE);
			if (r > 0)
			    writeSTDOUT(child_buf, r);
		    }
		    if (FD_ISSET(lc_stderr, &rfd)) {
			r = read(lc_stderr, child_buf, BUFFSIZE);
			if (r > 0)
			    writeSTDERR(child_buf, r);
		    }
		}
		if (FD_ISSET(sock, &rfd)) {
		    char *cmd;
		    fillbuffers(sock);
		    while ((cmd = getCTRL())) {
#ifdef DEBUG
			fprintf(stderr, "got CTRL: '%s'\n", cmd);
#endif
		    	if (!strncmp(cmd, "valid? ", 7)) {
			    char buf[5];
			    int valid = 1;
			    char *path = cmd + 7;
			    if (strlen(path) == 0)
				valid = 0;
			    else {
				struct stat stbuf;
				if (stat(path, &stbuf) < 0)
				    valid = 0;
				else {
				    if ((S_ISDIR(stbuf.st_mode))  ||
					(S_ISCHR(stbuf.st_mode))  ||
					(S_ISBLK(stbuf.st_mode))  ||
#ifdef S_ISSOCK
// CC: SYSVR4 systems don't have that macro
					(S_ISSOCK(stbuf.st_mode)) ||
#endif
					(S_ISFIFO(stbuf.st_mode)) ||
					(S_ISDIR(stbuf.st_mode)) )
					valid = 0;
				}
			    }
			    sprintf(buf,"V%d", valid);
			    writeCTRL(buf);
		    	}
		    	if (!strcmp(cmd, "running?")) {
			    char buf[5];
			    sprintf(buf,"R%d",(lc_pid > 0));
			    writeCTRL(buf);
		    	}
		    	if (!strncmp(cmd, "kill! ", 6)) {
			    char *buf = "K0";
			    char *ssig = cmd + 6;
			    int isig;
			    if (sscanf(ssig, "%i", &isig) == 1)
				if (lc_pid > 0) {
				    kill(lc_pid, isig);
				    buf = "K1";
				}
			    writeCTRL(buf);
		    	}
		    	if (!strncmp(cmd, "start! ", 7)) {
			    int plen;
			    int ppos;
			    char *p = cmd + 7;
			    char *buf = "S0";
			    if (sscanf(p, "%i %n", &plen, &ppos) >= 1) {
				p = cmd + 7 + ppos;
				if (lc_path)
				    free(lc_path);
				lc_path = malloc(plen + 2);
				if (!lc_path) {
				    eprint("Could not allocate lc_path\n");
				    exit(-1);
				}
				memset(lc_path, 0, plen + 2);
				strncpy(lc_path, p, plen);
				lc_argv = strdup(p + plen);
				start_lcrash();
			    }
			    if (lc_pid > 0) {
				lc_running = 1;
				buf = "S1";
			    }
			    writeCTRL(buf);
		    	}
			free(cmd);
		    }
		    while ((cmd = getCMD())) {
			if (lc_pid > 0)
			    write(lc_stdin, cmd, strlen(cmd));
			free(cmd);
		    }
		}
		break;
	}
    }
}

void svrexit_handler(int dummy) {
    int status;
    pid_t this_pid;
    int saved_errno;

    // since waitpid and write change errno, we have to save it and restore it
    // (Richard Stevens, Advanced programming in the Unix Environment)
    saved_errno = errno;
    do {
	this_pid = waitpid(-1, &status, WNOHANG);
    } while (this_pid > 0);
    errno = saved_errno;
}

static struct option opts[] = {
    {"inetd",   no_argument,       0, 'i'},
    {"port",    required_argument, 0, 'p'},
    {"bind",    required_argument, 0, 'b'},
    {"help",    no_argument,       0, 'h'},
    {"debug",   no_argument,       0, 'd'},
    {"version", no_argument,       0, 'V'},
    {NULL,      0,                 0, 0  }
};

void help() {
    printf(
	"Usage: " PACKAGE " [OPTION]...\n"
	"\n"
	"Valid options are:\n"
	"\n"
	"  -h, --help        Display this text and exit.\n"
	"  -V, --version     Display version information and exit.\n"
	"  -i, --inetd       Run from inetd.\n"
	"  -d, --debug       Dont't fork, use stdout/stderr instead of syslog.\n"
        "  -p, --port=<P>    Listen on port <P>. (default = 8192/lcrashd)\n"
	"  -b, --bindto=<A>  Bind to internet adress <A>. (default = ANY)\n"
	"\n"
	"If -i/--inetd is specified, port and listen-adress are ignored.\n"
	"<P> can be specified as service name from /etc/services.\n"
	"<A> can be specified as name or dotquad.\n"
	);
    exit(0);
}

void daemonize() {
    int i;

    signal(SIGHUP, SIG_IGN);
    signal(SIGALRM, SIG_IGN);
    if (fork())
	exit(0);
    setsid();
    for (i = 0; i < 255; i++)
	close(i);
}

int main(int argc, char **argv) {
    int opt;
    int port = 8192;
    int one  = 1;
    int do_fork = 1;
    int from_inetd = 0;
    char *bindaddr = NULL;
    char *portname = NULL;
    in_addr_t any = INADDR_ANY;
    struct servent *se = getservbyname("lcrashd", "tcp");
    struct protoent *pe = getprotobyname("tcp");
    struct sockaddr myaddr;
    struct sigaction act;

    log_dst = LOGDST_STDIO;
    if (se)
	port = ntohs(se->s_port);

    do {
	opt = getopt_long(argc, argv, "ihdVp:b:", opts, NULL);
	switch (opt) {
	    case 'i':
		from_inetd = 1;
	    	break;
	    case 'd':
		do_fork = 0;
	    	break;
	    case 'V':
		printf(PACKAGE " version " VERSION ", build at " __DATE__
			" " __TIME__ "\n");
		exit(0);
	    	break;
	    case 'h':
		help();
		break;
	    case 'p':
		portname = strdup(optarg);
		break;
	    case 'b':
		bindaddr = strdup(optarg);
		break;
	    case '?':
		opt = -1;
		optind = -1;
		break;
	}
    } while (opt != -1);
    if (optind < argc) {
	fprintf(stderr, "Try --help for a short description of options.\n");
	exit(1);
    }

    if (do_fork) {
	if (!from_inetd)
	    daemonize();
	openlog(PACKAGE, LOG_CONS|LOG_NDELAY|LOG_PID, LOG_DAEMON);
	log_dst = LOGDST_SYSLOG;
    }

    if (from_inetd) {
	cmdloop(0);
	nprint("Client disconnected\n");
	exit(0);
    }

    sv_socket = socket(PF_INET, SOCK_STREAM, pe->p_proto);
    if (sv_socket == -1) {
	PERROR("socket");
	exit(-1);
    }
    if (setsockopt(sv_socket, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(int)) < 0) {
	PERROR("setsockopt");
	exit(-1);
    }

    // set defaults
    memcpy(&((struct sockaddr_in *)&myaddr)->sin_addr, &any, sizeof(any));
    ((struct sockaddr_in *)&myaddr)->sin_port = htons(port);

    if (bindaddr) {
	struct hostent *he = gethostbyname(bindaddr);
	if (!he) {
	    PERROR(bindaddr);
	    exit(-1);
	}
    	memcpy(&((struct sockaddr_in *)&myaddr)->sin_addr,
		he->h_addr, sizeof(he->h_addr));
    }

    if (portname) {
	char *e;
	port = (unsigned short)strtoul(portname, &e, 0);

	if (*e == 0)
	    port = htons(port);
	else {
	    se = getservbyname(portname, "tcp");
	    if (!se) {
	    	PERROR(portname);
		exit(-1);
	    }
	    port = se->s_port;
	}
    	((struct sockaddr_in *)&myaddr)->sin_port = port;
    }

    if (bind(sv_socket, &myaddr, sizeof(myaddr)) != 0) {
	PERROR("bind");
	exit(-1);
    }
    if (listen(sv_socket, 2) < 0) {
	PERROR("listen");
	exit(-1);
    }
    // install SIGCHLD handler
    act.sa_handler=svrexit_handler;
    sigemptyset(&(act.sa_mask));
    sigaddset(&(act.sa_mask), SIGCHLD);
    // Make sure we don't block this signal. gdb tends to do that :-(
    sigprocmask(SIG_UNBLOCK, &(act.sa_mask), 0); 

    act.sa_flags = SA_NOCLDSTOP;

    // take care of SunOS which automatically restarts interrupted system
    // calls (and thus does not have SA_RESTART)
#ifdef SA_RESTART
    act.sa_flags |= SA_RESTART;
#endif
    sigaction( SIGCHLD, &act, 0L); 

    nprint(PACKAGE " " VERSION " started, waiting for requests\n");
    while (1) {
	int link = accept(sv_socket, NULL, NULL);
	if (link >= 0) {
	    pid_t pid = fork();
	    if (pid < 0) {
		PERROR("fork");
		exit(-1);
	    }
	    if (pid > 0)
		// parent
		close(link);
	    else {
		// child
		cmdloop(link);
		nprint("Client disconnected\n");
		exit(0);
	    }
	}
    }
}
