version 1.29, 2011/02/15 10:37:07 |
version 1.42, 2011/03/08 18:28:01 |
|
|
*/ |
*/ |
|
|
#include <sys/cdefs.h> |
#include <sys/cdefs.h> |
__RCSID("$NetBSD"); |
__RCSID("$NetBSD$"); |
|
|
#include <sys/param.h> |
#include <sys/param.h> |
#include <sys/event.h> |
#include <sys/event.h> |
Line 43 __RCSID("$NetBSD"); |
|
Line 43 __RCSID("$NetBSD"); |
|
|
|
#include <assert.h> |
#include <assert.h> |
#include <dlfcn.h> |
#include <dlfcn.h> |
|
#include <err.h> |
#include <errno.h> |
#include <errno.h> |
#include <fcntl.h> |
#include <fcntl.h> |
#include <link.h> |
#include <link.h> |
Line 65 int (*host_connect)(int, const struct so |
|
Line 66 int (*host_connect)(int, const struct so |
|
int (*host_fcntl)(int, int, ...); |
int (*host_fcntl)(int, int, ...); |
int (*host_poll)(struct pollfd *, nfds_t, int); |
int (*host_poll)(struct pollfd *, nfds_t, int); |
ssize_t (*host_read)(int, void *, size_t); |
ssize_t (*host_read)(int, void *, size_t); |
ssize_t (*host_sendto)(int, const void *, size_t, int, |
ssize_t (*host_sendmsg)(int, const struct msghdr *, int); |
const struct sockaddr *, socklen_t); |
|
int (*host_setsockopt)(int, int, int, const void *, socklen_t); |
int (*host_setsockopt)(int, int, int, const void *, socklen_t); |
int (*host_dup)(int); |
int (*host_dup)(int); |
|
|
Line 74 int (*host_kqueue)(void); |
|
Line 74 int (*host_kqueue)(void); |
|
int (*host_kevent)(int, const struct kevent *, size_t, |
int (*host_kevent)(int, const struct kevent *, size_t, |
struct kevent *, size_t, const struct timespec *); |
struct kevent *, size_t, const struct timespec *); |
|
|
|
int (*host_execve)(const char *, char *const[], char *const[]); |
|
|
#include "sp_common.c" |
#include "sp_common.c" |
|
|
static struct spclient clispc = { |
static struct spclient clispc = { |
Line 86 static sigset_t fullset; |
|
Line 88 static sigset_t fullset; |
|
static int doconnect(bool); |
static int doconnect(bool); |
static int handshake_req(struct spclient *, int, void *, int, bool); |
static int handshake_req(struct spclient *, int, void *, int, bool); |
|
|
time_t retrytimo = RUMPCLIENT_RETRYCONN_ONCE; |
/* |
|
* Default: don't retry. Most clients can't handle it |
|
* (consider e.g. fds suddenly going missing). |
|
*/ |
|
static time_t retrytimo = 0; |
|
|
static int |
static int |
send_with_recon(struct spclient *spc, const void *data, size_t dlen) |
send_with_recon(struct spclient *spc, struct iovec *iov, size_t iovlen) |
{ |
{ |
struct timeval starttime, curtime; |
struct timeval starttime, curtime; |
time_t prevreconmsg; |
time_t prevreconmsg; |
Line 97 send_with_recon(struct spclient *spc, co |
|
Line 103 send_with_recon(struct spclient *spc, co |
|
int rv; |
int rv; |
|
|
for (prevreconmsg = 0, reconretries = 0;;) { |
for (prevreconmsg = 0, reconretries = 0;;) { |
rv = dosend(spc, data, dlen); |
rv = dosend(spc, iov, iovlen); |
if (__predict_false(rv == ENOTCONN || rv == EBADF)) { |
if (__predict_false(rv == ENOTCONN || rv == EBADF)) { |
/* no persistent connections */ |
/* no persistent connections */ |
if (retrytimo == 0) |
if (retrytimo == 0) { |
|
rv = ENOTCONN; |
break; |
break; |
|
} |
if (retrytimo == RUMPCLIENT_RETRYCONN_DIE) |
if (retrytimo == RUMPCLIENT_RETRYCONN_DIE) |
exit(1); |
exit(1); |
|
|
Line 194 cliwaitresp(struct spclient *spc, struct |
|
Line 202 cliwaitresp(struct spclient *spc, struct |
|
|
|
dosig = 0; |
dosig = 0; |
for (gotresp = 0; !gotresp; ) { |
for (gotresp = 0; !gotresp; ) { |
switch (readframe(spc)) { |
/* |
case 0: |
* typically we don't have a frame waiting |
rv = host_kevent(kq, NULL, 0, |
* when we come in here, so call kevent now |
kev, __arraycount(kev), NULL); |
*/ |
|
rv = host_kevent(kq, NULL, 0, |
|
kev, __arraycount(kev), NULL); |
|
|
if (__predict_false(rv == -1)) { |
if (__predict_false(rv == -1)) { |
goto cleanup; |
goto activity; |
} |
} |
|
|
/* |
|
* XXX: don't know how this can |
|
* happen (timeout cannot expire |
|
* since there isn't one), but |
|
* it does happen |
|
*/ |
|
if (__predict_false(rv == 0)) |
|
continue; |
|
|
|
for (i = 0; i < rv; i++) { |
|
if (kev[i].filter |
|
== EVFILT_SIGNAL) |
|
dosig++; |
|
} |
|
if (dosig) |
|
goto cleanup; |
|
|
|
|
/* |
|
* XXX: don't know how this can happen |
|
* (timeout cannot expire since there |
|
* isn't one), but it does happen. |
|
* treat it as an expectional condition |
|
* and go through tryread to determine |
|
* alive status. |
|
*/ |
|
if (__predict_false(rv == 0)) |
|
goto activity; |
|
|
|
for (i = 0; i < rv; i++) { |
|
if (kev[i].filter == EVFILT_SIGNAL) |
|
dosig++; |
|
} |
|
if (dosig) |
|
goto cleanup; |
|
|
|
/* |
|
* ok, activity. try to read a frame to |
|
* determine what happens next. |
|
*/ |
|
activity: |
|
switch (readframe(spc)) { |
|
case 0: |
continue; |
continue; |
case -1: |
case -1: |
imalive = false; |
imalive = false; |
goto cleanup; |
goto cleanup; |
default: |
default: |
|
/* case 1 */ |
break; |
break; |
} |
} |
|
|
Line 278 syscall_req(struct spclient *spc, sigset |
|
Line 297 syscall_req(struct spclient *spc, sigset |
|
{ |
{ |
struct rsp_hdr rhdr; |
struct rsp_hdr rhdr; |
struct respwait rw; |
struct respwait rw; |
|
struct iovec iov[2]; |
int rv; |
int rv; |
|
|
rhdr.rsp_len = sizeof(rhdr) + dlen; |
rhdr.rsp_len = sizeof(rhdr) + dlen; |
Line 285 syscall_req(struct spclient *spc, sigset |
|
Line 305 syscall_req(struct spclient *spc, sigset |
|
rhdr.rsp_type = RUMPSP_SYSCALL; |
rhdr.rsp_type = RUMPSP_SYSCALL; |
rhdr.rsp_sysnum = sysnum; |
rhdr.rsp_sysnum = sysnum; |
|
|
|
IOVPUT(iov[0], rhdr); |
|
IOVPUT_WITHSIZE(iov[1], __UNCONST(data), dlen); |
|
|
do { |
do { |
putwait(spc, &rw, &rhdr); |
putwait(spc, &rw, &rhdr); |
if ((rv = send_with_recon(spc, &rhdr, sizeof(rhdr))) != 0) { |
if ((rv = send_with_recon(spc, iov, __arraycount(iov))) != 0) { |
unputwait(spc, &rw); |
|
continue; |
|
} |
|
if ((rv = send_with_recon(spc, data, dlen)) != 0) { |
|
unputwait(spc, &rw); |
unputwait(spc, &rw); |
continue; |
continue; |
} |
} |
Line 310 handshake_req(struct spclient *spc, int |
|
Line 329 handshake_req(struct spclient *spc, int |
|
int cancel, bool haslock) |
int cancel, bool haslock) |
{ |
{ |
struct handshake_fork rf; |
struct handshake_fork rf; |
|
const char *myprogname; |
struct rsp_hdr rhdr; |
struct rsp_hdr rhdr; |
struct respwait rw; |
struct respwait rw; |
sigset_t omask; |
sigset_t omask; |
size_t bonus; |
size_t bonus; |
|
struct iovec iov[2]; |
int rv; |
int rv; |
|
|
if (type == HANDSHAKE_FORK) { |
if (type == HANDSHAKE_FORK) { |
bonus = sizeof(rf); |
bonus = sizeof(rf); |
} else { |
} else { |
bonus = strlen(getprogname())+1; |
myprogname = getprogname(); |
|
bonus = strlen(myprogname)+1; |
} |
} |
|
|
/* performs server handshake */ |
/* performs server handshake */ |
Line 328 handshake_req(struct spclient *spc, int |
|
Line 350 handshake_req(struct spclient *spc, int |
|
rhdr.rsp_type = RUMPSP_HANDSHAKE; |
rhdr.rsp_type = RUMPSP_HANDSHAKE; |
rhdr.rsp_handshake = type; |
rhdr.rsp_handshake = type; |
|
|
|
IOVPUT(iov[0], rhdr); |
|
|
pthread_sigmask(SIG_SETMASK, &fullset, &omask); |
pthread_sigmask(SIG_SETMASK, &fullset, &omask); |
if (haslock) |
if (haslock) |
putwait_locked(spc, &rw, &rhdr); |
putwait_locked(spc, &rw, &rhdr); |
else |
else |
putwait(spc, &rw, &rhdr); |
putwait(spc, &rw, &rhdr); |
rv = dosend(spc, &rhdr, sizeof(rhdr)); |
|
if (type == HANDSHAKE_FORK) { |
if (type == HANDSHAKE_FORK) { |
memcpy(rf.rf_auth, data, sizeof(rf.rf_auth)); /* uh, why? */ |
memcpy(rf.rf_auth, data, sizeof(rf.rf_auth)); /* uh, why? */ |
rf.rf_cancel = cancel; |
rf.rf_cancel = cancel; |
rv = send_with_recon(spc, &rf, sizeof(rf)); |
IOVPUT(iov[1], rf); |
} else { |
} else { |
rv = dosend(spc, getprogname(), strlen(getprogname())+1); |
IOVPUT_WITHSIZE(iov[1], __UNCONST(getprogname()), bonus); |
} |
} |
|
rv = send_with_recon(spc, iov, __arraycount(iov)); |
if (rv || cancel) { |
if (rv || cancel) { |
if (haslock) |
if (haslock) |
unputwait_locked(spc, &rw); |
unputwait_locked(spc, &rw); |
Line 368 prefork_req(struct spclient *spc, sigset |
|
Line 392 prefork_req(struct spclient *spc, sigset |
|
{ |
{ |
struct rsp_hdr rhdr; |
struct rsp_hdr rhdr; |
struct respwait rw; |
struct respwait rw; |
|
struct iovec iov[1]; |
int rv; |
int rv; |
|
|
rhdr.rsp_len = sizeof(rhdr); |
rhdr.rsp_len = sizeof(rhdr); |
Line 375 prefork_req(struct spclient *spc, sigset |
|
Line 400 prefork_req(struct spclient *spc, sigset |
|
rhdr.rsp_type = RUMPSP_PREFORK; |
rhdr.rsp_type = RUMPSP_PREFORK; |
rhdr.rsp_error = 0; |
rhdr.rsp_error = 0; |
|
|
|
IOVPUT(iov[0], rhdr); |
|
|
do { |
do { |
putwait(spc, &rw, &rhdr); |
putwait(spc, &rw, &rhdr); |
rv = send_with_recon(spc, &rhdr, sizeof(rhdr)); |
rv = send_with_recon(spc, iov, __arraycount(iov)); |
if (rv != 0) { |
if (rv != 0) { |
unputwait(spc, &rw); |
unputwait(spc, &rw); |
continue; |
continue; |
Line 421 send_copyin_resp(struct spclient *spc, u |
|
Line 448 send_copyin_resp(struct spclient *spc, u |
|
int wantstr) |
int wantstr) |
{ |
{ |
struct rsp_hdr rhdr; |
struct rsp_hdr rhdr; |
|
struct iovec iov[2]; |
|
|
if (wantstr) |
if (wantstr) |
dlen = MIN(dlen, strlen(data)+1); |
dlen = MIN(dlen, strlen(data)+1); |
Line 431 send_copyin_resp(struct spclient *spc, u |
|
Line 459 send_copyin_resp(struct spclient *spc, u |
|
rhdr.rsp_type = RUMPSP_COPYIN; |
rhdr.rsp_type = RUMPSP_COPYIN; |
rhdr.rsp_sysnum = 0; |
rhdr.rsp_sysnum = 0; |
|
|
|
IOVPUT(iov[0], rhdr); |
|
IOVPUT_WITHSIZE(iov[1], data, dlen); |
|
|
if (resp_sendlock(spc) != 0) |
if (resp_sendlock(spc) != 0) |
return; |
return; |
(void)dosend(spc, &rhdr, sizeof(rhdr)); |
(void)SENDIOV(spc, iov); |
(void)dosend(spc, data, dlen); |
|
sendunlock(spc); |
sendunlock(spc); |
} |
} |
|
|
|
|
send_anonmmap_resp(struct spclient *spc, uint64_t reqno, void *addr) |
send_anonmmap_resp(struct spclient *spc, uint64_t reqno, void *addr) |
{ |
{ |
struct rsp_hdr rhdr; |
struct rsp_hdr rhdr; |
|
struct iovec iov[2]; |
|
|
rhdr.rsp_len = sizeof(rhdr) + sizeof(addr); |
rhdr.rsp_len = sizeof(rhdr) + sizeof(addr); |
rhdr.rsp_reqno = reqno; |
rhdr.rsp_reqno = reqno; |
Line 449 send_anonmmap_resp(struct spclient *spc, |
|
Line 480 send_anonmmap_resp(struct spclient *spc, |
|
rhdr.rsp_type = RUMPSP_ANONMMAP; |
rhdr.rsp_type = RUMPSP_ANONMMAP; |
rhdr.rsp_sysnum = 0; |
rhdr.rsp_sysnum = 0; |
|
|
|
IOVPUT(iov[0], rhdr); |
|
IOVPUT(iov[1], addr); |
|
|
if (resp_sendlock(spc) != 0) |
if (resp_sendlock(spc) != 0) |
return; |
return; |
(void)dosend(spc, &rhdr, sizeof(rhdr)); |
(void)SENDIOV(spc, iov); |
(void)dosend(spc, &addr, sizeof(addr)); |
|
sendunlock(spc); |
sendunlock(spc); |
} |
} |
|
|
|
|
return 0; |
return 0; |
} |
} |
|
|
void *(*rumpclient_dlsym)(void *, const char *); |
void *rumpclient__dlsym(void *, const char *); |
static int init_done = 0; |
void *rumphijack_dlsym(void *, const char *); |
|
void * |
|
rumpclient__dlsym(void *handle, const char *symbol) |
|
{ |
|
|
|
return dlsym(handle, symbol); |
|
} |
|
__weak_alias(rumphijack_dlsym,rumpclient__dlsym); |
|
|
|
static pid_t init_done = 0; |
|
|
int |
int |
rumpclient_init() |
rumpclient_init() |
Line 718 rumpclient_init() |
|
Line 760 rumpclient_init() |
|
int error; |
int error; |
int rv = -1; |
int rv = -1; |
int hstype; |
int hstype; |
|
pid_t mypid; |
|
|
if (init_done) |
/* |
|
* Make sure we're not riding the context of a previous |
|
* host fork. Note: it's *possible* that after n>1 forks |
|
* we have the same pid as one of our exited parents, but |
|
* I'm pretty sure there are 0 practical implications, since |
|
* it means generations would have to skip rumpclient init. |
|
*/ |
|
if (init_done == (mypid = getpid())) |
return 0; |
return 0; |
init_done = 1; |
|
|
|
sigfillset(&fullset); |
/* kq does not traverse fork() */ |
|
if (init_done != 0) |
|
kq = -1; |
|
init_done = mypid; |
|
|
/* dlsym overrided by rumphijack? */ |
sigfillset(&fullset); |
if (!rumpclient_dlsym) |
|
rumpclient_dlsym = dlsym; |
|
|
|
/* |
/* |
* sag mir, wo die symbol sind. zogen fort, der krieg beginnt. |
* sag mir, wo die symbol sind. zogen fort, der krieg beginnt. |
* wann wird man je verstehen? wann wird man je verstehen? |
* wann wird man je verstehen? wann wird man je verstehen? |
*/ |
*/ |
#define FINDSYM2(_name_,_syscall_) \ |
#define FINDSYM2(_name_,_syscall_) \ |
if ((host_##_name_ = rumpclient_dlsym(RTLD_NEXT, \ |
if ((host_##_name_ = rumphijack_dlsym(RTLD_NEXT, \ |
#_syscall_)) == NULL) \ |
#_syscall_)) == NULL) { \ |
/* host_##_name_ = _syscall_ */; |
if (rumphijack_dlsym == rumpclient__dlsym) \ |
|
host_##_name_ = _name_; /* static fallback */ \ |
|
if (host_##_name_ == NULL) \ |
|
errx(1, "cannot find %s: %s", #_syscall_, \ |
|
dlerror()); \ |
|
} |
#define FINDSYM(_name_) FINDSYM2(_name_,_name_) |
#define FINDSYM(_name_) FINDSYM2(_name_,_name_) |
FINDSYM2(socket,__socket30); |
FINDSYM2(socket,__socket30) |
FINDSYM(close); |
FINDSYM(close) |
FINDSYM(connect); |
FINDSYM(connect) |
FINDSYM(fcntl); |
FINDSYM(fcntl) |
FINDSYM(poll); |
FINDSYM(poll) |
FINDSYM(read); |
FINDSYM(read) |
FINDSYM(sendto); |
FINDSYM(sendmsg) |
FINDSYM(setsockopt); |
FINDSYM(setsockopt) |
FINDSYM(dup); |
FINDSYM(dup) |
FINDSYM(kqueue); |
FINDSYM(kqueue) |
|
FINDSYM(execve) |
#if !__NetBSD_Prereq__(5,99,7) |
#if !__NetBSD_Prereq__(5,99,7) |
FINDSYM(kevent); |
FINDSYM(kevent) |
#else |
#else |
FINDSYM2(kevent,_sys___kevent50); |
FINDSYM2(kevent,_sys___kevent50) |
#endif |
#endif |
#undef FINDSYM |
#undef FINDSYM |
#undef FINDSY2 |
#undef FINDSY2 |
Line 800 rumpclient_init() |
|
Line 856 rumpclient_init() |
|
|
|
struct rumpclient_fork { |
struct rumpclient_fork { |
uint32_t fork_auth[AUTHLEN]; |
uint32_t fork_auth[AUTHLEN]; |
|
struct spclient fork_spc; |
|
int fork_kq; |
}; |
}; |
|
|
struct rumpclient_fork * |
struct rumpclient_fork * |
Line 813 rumpclient_prefork(void) |
|
Line 871 rumpclient_prefork(void) |
|
pthread_sigmask(SIG_SETMASK, &fullset, &omask); |
pthread_sigmask(SIG_SETMASK, &fullset, &omask); |
rpf = malloc(sizeof(*rpf)); |
rpf = malloc(sizeof(*rpf)); |
if (rpf == NULL) |
if (rpf == NULL) |
return NULL; |
goto out; |
|
|
if ((rv = prefork_req(&clispc, &omask, &resp)) != 0) { |
if ((rv = prefork_req(&clispc, &omask, &resp)) != 0) { |
free(rpf); |
free(rpf); |
Line 825 rumpclient_prefork(void) |
|
Line 883 rumpclient_prefork(void) |
|
memcpy(rpf->fork_auth, resp, sizeof(rpf->fork_auth)); |
memcpy(rpf->fork_auth, resp, sizeof(rpf->fork_auth)); |
free(resp); |
free(resp); |
|
|
|
rpf->fork_spc = clispc; |
|
rpf->fork_kq = kq; |
|
|
out: |
out: |
pthread_sigmask(SIG_SETMASK, &omask, NULL); |
pthread_sigmask(SIG_SETMASK, &omask, NULL); |
return rpf; |
return rpf; |
Line 859 rumpclient_fork_init(struct rumpclient_f |
|
Line 920 rumpclient_fork_init(struct rumpclient_f |
|
return 0; |
return 0; |
} |
} |
|
|
|
/*ARGSUSED*/ |
|
void |
|
rumpclient_fork_cancel(struct rumpclient_fork *rpf) |
|
{ |
|
|
|
/* EUNIMPL */ |
|
} |
|
|
|
void |
|
rumpclient_fork_vparent(struct rumpclient_fork *rpf) |
|
{ |
|
|
|
clispc = rpf->fork_spc; |
|
kq = rpf->fork_kq; |
|
} |
|
|
void |
void |
rumpclient_setconnretry(time_t timeout) |
rumpclient_setconnretry(time_t timeout) |
{ |
{ |
Line 926 rumpclient__closenotify(int *fdp, enum r |
|
Line 1003 rumpclient__closenotify(int *fdp, enum r |
|
return 0; |
return 0; |
} |
} |
|
|
|
pid_t |
|
rumpclient_fork() |
|
{ |
|
|
|
return rumpclient__dofork(fork); |
|
} |
|
|
/* |
/* |
* Process is about to exec. Save info about our existing connection |
* Process is about to exec. Save info about our existing connection |
* in the env. rumpclient will check for this info in init(). |
* in the env. rumpclient will check for this info in init(). |
Line 933 rumpclient__closenotify(int *fdp, enum r |
|
Line 1017 rumpclient__closenotify(int *fdp, enum r |
|
* may use it as well. |
* may use it as well. |
*/ |
*/ |
int |
int |
rumpclient__exec_augmentenv(char *const oenv1[], char *const oenv2[], |
rumpclient_exec(const char *path, char *const argv[], char *const envp[]) |
char ***newenvp) |
|
{ |
{ |
char buf[4096]; |
char buf[4096]; |
char **newenv; |
char **newenv; |
char *envstr, *envstr2; |
char *envstr, *envstr2; |
size_t nelem1, nelem2; |
size_t nelem; |
|
int rv, sverrno; |
|
|
snprintf(buf, sizeof(buf), "RUMPCLIENT__EXECFD=%d,%d", |
snprintf(buf, sizeof(buf), "RUMPCLIENT__EXECFD=%d,%d", |
clispc.spc_fd, kq); |
clispc.spc_fd, kq); |
Line 963 rumpclient__exec_augmentenv(char *const |
|
Line 1047 rumpclient__exec_augmentenv(char *const |
|
envstr2 = NULL; |
envstr2 = NULL; |
} |
} |
|
|
nelem1 = 0; |
for (nelem = 0; envp && envp[nelem]; nelem++) |
if (oenv1) { |
continue; |
for (; oenv1[nelem1]; nelem1++) |
|
continue; |
|
} |
|
nelem2 = 0; |
|
if (oenv2) { |
|
for (; oenv2[nelem2]; nelem2++) |
|
continue; |
|
} |
|
|
|
newenv = malloc(sizeof(*newenv) * nelem1+nelem2+3); |
newenv = malloc(sizeof(*newenv) * (nelem+3)); |
if (newenv == NULL) { |
if (newenv == NULL) { |
free(envstr2); |
free(envstr2); |
free(envstr); |
free(envstr); |
return ENOMEM; |
return ENOMEM; |
} |
} |
memcpy(&newenv[0], oenv1, sizeof(*oenv1) * nelem1); |
memcpy(&newenv[0], envp, nelem*sizeof(*envp)); |
memcpy(&newenv[nelem1], oenv2, sizeof(*oenv2) * nelem2); |
|
|
|
newenv[nelem1+nelem2] = envstr; |
newenv[nelem] = envstr; |
newenv[nelem1+nelem2+1] = envstr2; |
newenv[nelem+1] = envstr2; |
newenv[nelem1+nelem2+2] = NULL; |
newenv[nelem+2] = NULL; |
|
|
|
rv = host_execve(path, argv, newenv); |
|
|
|
_DIAGASSERT(rv != 0); |
|
sverrno = errno; |
|
free(envstr2); |
|
free(envstr); |
|
free(newenv); |
|
errno = sverrno; |
|
return rv; |
|
} |
|
|
*newenvp = newenv; |
int |
|
rumpclient_daemon(int nochdir, int noclose) |
|
{ |
|
struct rumpclient_fork *rf; |
|
int sverrno; |
|
|
|
if ((rf = rumpclient_prefork()) == NULL) |
|
return -1; |
|
|
|
if (daemon(nochdir, noclose) == -1) { |
|
sverrno = errno; |
|
rumpclient_fork_cancel(rf); |
|
errno = sverrno; |
|
return -1; |
|
} |
|
|
|
if (rumpclient_fork_init(rf) == -1) |
|
return -1; |
|
|
return 0; |
return 0; |
} |
} |