2013-09-13 13:44:09 +04:00
|
|
|
#ifndef _GNU_SOURCE
|
|
|
|
#define _GNU_SOURCE
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#include <unistd.h>
|
|
|
|
#include <stdio.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <errno.h>
|
|
|
|
#include <string.h>
|
|
|
|
#include <sys/types.h>
|
|
|
|
#include <sys/socket.h>
|
|
|
|
#include <sys/un.h>
|
|
|
|
#include <sys/wait.h>
|
|
|
|
#include <sys/stat.h>
|
|
|
|
|
|
|
|
#include "crtools.h"
|
2013-11-06 17:21:11 +04:00
|
|
|
#include "cr_options.h"
|
2013-11-06 14:34:32 +04:00
|
|
|
#include "util.h"
|
2013-09-13 13:44:09 +04:00
|
|
|
#include "log.h"
|
2013-10-02 23:44:42 +04:00
|
|
|
#include "pstree.h"
|
2013-09-13 13:43:56 +04:00
|
|
|
#include "cr-service.h"
|
2013-12-12 02:54:00 +04:00
|
|
|
#include "sd-daemon.h"
|
2013-09-13 13:43:56 +04:00
|
|
|
|
2013-09-28 09:44:29 +04:00
|
|
|
unsigned int service_sk_ino = -1;
|
2013-09-13 13:44:09 +04:00
|
|
|
|
2013-09-16 15:36:12 +04:00
|
|
|
static int recv_criu_msg(int socket_fd, CriuReq **msg)
|
2013-09-13 13:44:09 +04:00
|
|
|
{
|
|
|
|
unsigned char buf[MAX_MSG_SIZE];
|
|
|
|
int len;
|
|
|
|
|
|
|
|
len = read(socket_fd, buf, MAX_MSG_SIZE);
|
|
|
|
if (len == -1) {
|
2013-09-23 14:31:29 +04:00
|
|
|
pr_perror("Can't read request");
|
2013-09-13 13:44:09 +04:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2013-09-16 15:36:12 +04:00
|
|
|
*msg = criu_req__unpack(NULL, len, buf);
|
2013-09-13 13:44:09 +04:00
|
|
|
if (!*msg) {
|
2013-09-23 14:31:29 +04:00
|
|
|
pr_perror("Failed unpacking request");
|
2013-09-13 13:44:09 +04:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2013-09-16 15:36:12 +04:00
|
|
|
static int send_criu_msg(int socket_fd, CriuResp *msg)
|
2013-09-13 13:43:56 +04:00
|
|
|
{
|
2013-09-13 13:44:09 +04:00
|
|
|
unsigned char buf[MAX_MSG_SIZE];
|
|
|
|
int len;
|
|
|
|
|
2013-09-16 15:36:12 +04:00
|
|
|
len = criu_resp__get_packed_size(msg);
|
2013-09-13 13:44:09 +04:00
|
|
|
|
2013-09-16 15:36:12 +04:00
|
|
|
if (criu_resp__pack(msg, buf) != len) {
|
2013-09-13 13:44:09 +04:00
|
|
|
pr_perror("Failed packing response");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (write(socket_fd, buf, len) == -1) {
|
|
|
|
pr_perror("Can't send response");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2013-09-16 15:40:04 +04:00
|
|
|
int send_criu_dump_resp(int socket_fd, bool success, bool restored)
|
2013-09-13 13:44:09 +04:00
|
|
|
{
|
2013-09-16 15:36:12 +04:00
|
|
|
CriuResp msg = CRIU_RESP__INIT;
|
2013-09-16 15:40:04 +04:00
|
|
|
CriuDumpResp resp = CRIU_DUMP_RESP__INIT;
|
2013-09-13 13:44:09 +04:00
|
|
|
|
2013-09-16 15:36:12 +04:00
|
|
|
msg.type = CRIU_REQ_TYPE__DUMP;
|
|
|
|
msg.success = success;
|
2013-09-16 15:40:04 +04:00
|
|
|
msg.dump = &resp;
|
|
|
|
|
2013-09-17 00:50:03 +04:00
|
|
|
resp.has_restored = true;
|
2013-09-16 15:40:04 +04:00
|
|
|
resp.restored = restored;
|
2013-09-13 13:44:09 +04:00
|
|
|
|
|
|
|
return send_criu_msg(socket_fd, &msg);
|
|
|
|
}
|
|
|
|
|
2013-10-02 23:44:42 +04:00
|
|
|
int send_criu_restore_resp(int socket_fd, bool success, int pid)
|
|
|
|
{
|
|
|
|
CriuResp msg = CRIU_RESP__INIT;
|
|
|
|
CriuRestoreResp resp = CRIU_RESTORE_RESP__INIT;
|
|
|
|
|
|
|
|
msg.type = CRIU_REQ_TYPE__RESTORE;
|
|
|
|
msg.success = success;
|
|
|
|
msg.restore = &resp;
|
|
|
|
|
|
|
|
resp.pid = pid;
|
|
|
|
|
|
|
|
return send_criu_msg(socket_fd, &msg);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int setup_opts_from_req(int sk, CriuOpts *req)
|
2013-09-13 13:44:09 +04:00
|
|
|
{
|
|
|
|
struct ucred ids;
|
|
|
|
struct stat st;
|
|
|
|
socklen_t ids_len = sizeof(struct ucred);
|
|
|
|
char images_dir_path[PATH_MAX];
|
|
|
|
|
2013-09-28 05:54:50 +04:00
|
|
|
if (getsockopt(sk, SOL_SOCKET, SO_PEERCRED, &ids, &ids_len)) {
|
2013-09-13 13:44:09 +04:00
|
|
|
pr_perror("Can't get socket options.");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2013-09-28 15:51:09 +04:00
|
|
|
restrict_uid(ids.uid, ids.gid);
|
2013-09-13 13:44:09 +04:00
|
|
|
|
2013-09-28 05:54:50 +04:00
|
|
|
if (fstat(sk, &st)) {
|
2013-09-13 13:44:09 +04:00
|
|
|
pr_perror("Can't get socket stat");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2013-09-28 09:44:29 +04:00
|
|
|
BUG_ON(st.st_ino == -1);
|
2013-09-28 06:06:53 +04:00
|
|
|
service_sk_ino = st.st_ino;
|
2013-09-13 13:44:09 +04:00
|
|
|
|
2013-10-02 23:44:42 +04:00
|
|
|
/* going to dir, where to place/get images*/
|
2013-09-28 05:52:18 +04:00
|
|
|
sprintf(images_dir_path, "/proc/%d/fd/%d", ids.pid, req->images_dir_fd);
|
2013-09-13 13:44:09 +04:00
|
|
|
|
|
|
|
if (chdir(images_dir_path)) {
|
|
|
|
pr_perror("Can't chdir to images directory");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2013-11-15 23:04:27 +04:00
|
|
|
if (open_image_dir(".") < 0)
|
2013-09-13 13:44:09 +04:00
|
|
|
return -1;
|
|
|
|
|
|
|
|
/* initiate log file in imgs dir */
|
2013-10-02 16:01:44 +04:00
|
|
|
if (req->log_file)
|
|
|
|
opts.output = req->log_file;
|
|
|
|
else
|
2013-10-02 23:43:51 +04:00
|
|
|
opts.output = DEFAULT_LOG_FILENAME;
|
2013-09-13 13:44:09 +04:00
|
|
|
|
|
|
|
log_set_loglevel(req->log_level);
|
|
|
|
if (log_init(opts.output) == -1) {
|
|
|
|
pr_perror("Can't initiate log.");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2013-10-02 23:44:42 +04:00
|
|
|
/* checking flags from client */
|
2013-09-16 15:21:13 +04:00
|
|
|
if (req->has_leave_running && req->leave_running)
|
2013-09-13 13:44:09 +04:00
|
|
|
opts.final_state = TASK_ALIVE;
|
|
|
|
|
2013-09-16 15:21:13 +04:00
|
|
|
if (!req->has_pid) {
|
|
|
|
req->has_pid = true;
|
|
|
|
req->pid = ids.pid;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (req->has_ext_unix_sk)
|
|
|
|
opts.ext_unix_sk = req->ext_unix_sk;
|
|
|
|
|
|
|
|
if (req->has_tcp_established)
|
|
|
|
opts.tcp_established_ok = req->tcp_established;
|
|
|
|
|
|
|
|
if (req->has_evasive_devices)
|
|
|
|
opts.evasive_devices = req->evasive_devices;
|
|
|
|
|
|
|
|
if (req->has_shell_job)
|
|
|
|
opts.shell_job = req->shell_job;
|
|
|
|
|
|
|
|
if (req->has_file_locks)
|
|
|
|
opts.handle_file_locks = req->file_locks;
|
2013-09-13 13:44:09 +04:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2013-10-02 16:04:11 +04:00
|
|
|
static int dump_using_req(int sk, CriuOpts *req)
|
2013-09-13 13:44:09 +04:00
|
|
|
{
|
2013-09-16 15:36:12 +04:00
|
|
|
bool success = false;
|
2013-12-02 19:50:44 +04:00
|
|
|
bool self_dump = !req->pid;
|
2013-09-13 13:44:09 +04:00
|
|
|
|
2013-10-02 23:44:42 +04:00
|
|
|
if (setup_opts_from_req(sk, req) == -1) {
|
2013-09-13 13:44:09 +04:00
|
|
|
pr_perror("Arguments treating fail");
|
|
|
|
goto exit;
|
|
|
|
}
|
|
|
|
|
2013-10-02 23:44:42 +04:00
|
|
|
if (cr_dump_tasks(req->pid) == -1)
|
2013-09-13 13:44:09 +04:00
|
|
|
goto exit;
|
|
|
|
|
2013-12-05 12:05:46 +04:00
|
|
|
success = true;
|
2013-09-16 15:41:37 +04:00
|
|
|
exit:
|
2013-12-05 12:05:46 +04:00
|
|
|
if (req->leave_running || !self_dump) {
|
2013-09-28 05:54:50 +04:00
|
|
|
if (send_criu_dump_resp(sk, success, false) == -1) {
|
2013-09-13 13:44:09 +04:00
|
|
|
pr_perror("Can't send response");
|
2013-09-16 15:36:12 +04:00
|
|
|
success = false;
|
2013-09-13 13:44:09 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-09-16 15:36:12 +04:00
|
|
|
return success ? 0 : 1;
|
2013-09-13 13:44:09 +04:00
|
|
|
}
|
|
|
|
|
2013-10-02 23:44:42 +04:00
|
|
|
static int restore_using_req(int sk, CriuOpts *req)
|
|
|
|
{
|
|
|
|
bool success = false;
|
|
|
|
|
2013-10-02 23:20:33 +04:00
|
|
|
/*
|
|
|
|
* We can't restore processes under arbitrary task yet.
|
|
|
|
* Thus for now we force the detached restore under the
|
|
|
|
* cr service task.
|
|
|
|
*/
|
|
|
|
|
2013-10-02 23:44:42 +04:00
|
|
|
opts.restore_detach = true;
|
|
|
|
|
|
|
|
if (setup_opts_from_req(sk, req) == -1) {
|
|
|
|
pr_perror("Arguments treating fail");
|
|
|
|
goto exit;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (cr_restore_tasks())
|
|
|
|
goto exit;
|
|
|
|
|
|
|
|
success = true;
|
|
|
|
exit:
|
|
|
|
if (send_criu_restore_resp(sk, success, root_item->pid.real) == -1) {
|
|
|
|
pr_perror("Can't send response");
|
|
|
|
success = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return success ? 0 : 1;
|
|
|
|
}
|
|
|
|
|
2013-11-20 03:53:24 +04:00
|
|
|
static int check(int sk)
|
|
|
|
{
|
|
|
|
CriuResp resp = CRIU_RESP__INIT;
|
|
|
|
|
2013-11-22 09:29:12 +04:00
|
|
|
resp.type = CRIU_REQ_TYPE__CHECK;
|
|
|
|
|
2013-11-20 03:53:24 +04:00
|
|
|
if (!cr_check())
|
|
|
|
resp.success = true;
|
|
|
|
|
|
|
|
return send_criu_msg(sk, &resp);
|
|
|
|
}
|
|
|
|
|
2013-09-28 05:54:50 +04:00
|
|
|
static int cr_service_work(int sk)
|
2013-09-23 14:37:56 +04:00
|
|
|
{
|
|
|
|
CriuReq *msg = 0;
|
|
|
|
|
2013-10-30 02:48:39 +04:00
|
|
|
init_opts();
|
|
|
|
|
2013-09-28 05:54:50 +04:00
|
|
|
if (recv_criu_msg(sk, &msg) == -1) {
|
2013-09-23 14:37:56 +04:00
|
|
|
pr_perror("Can't recv request");
|
|
|
|
goto err;
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (msg->type) {
|
|
|
|
case CRIU_REQ_TYPE__DUMP:
|
2013-10-02 16:04:11 +04:00
|
|
|
return dump_using_req(sk, msg->opts);
|
2013-10-02 23:44:42 +04:00
|
|
|
case CRIU_REQ_TYPE__RESTORE:
|
|
|
|
return restore_using_req(sk, msg->opts);
|
2013-11-20 03:53:24 +04:00
|
|
|
case CRIU_REQ_TYPE__CHECK:
|
|
|
|
return check(sk);
|
2013-09-23 14:37:56 +04:00
|
|
|
|
2013-09-28 05:49:31 +04:00
|
|
|
default: {
|
|
|
|
CriuResp resp = CRIU_RESP__INIT;
|
|
|
|
|
|
|
|
resp.type = CRIU_REQ_TYPE__EMPTY;
|
|
|
|
resp.success = false;
|
|
|
|
/* XXX -- add optional error code to CriuResp */
|
|
|
|
|
2013-09-23 14:37:56 +04:00
|
|
|
pr_perror("Invalid request");
|
2013-09-28 05:54:50 +04:00
|
|
|
send_criu_msg(sk, &resp);
|
2013-09-28 05:49:31 +04:00
|
|
|
|
2013-09-23 14:37:56 +04:00
|
|
|
goto err;
|
|
|
|
}
|
2013-09-28 05:49:31 +04:00
|
|
|
}
|
2013-09-23 14:37:56 +04:00
|
|
|
|
|
|
|
err:
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2013-11-01 14:32:03 +04:00
|
|
|
static void reap_worker(int signo)
|
|
|
|
{
|
|
|
|
int saved_errno;
|
|
|
|
int status;
|
|
|
|
pid_t pid;
|
|
|
|
|
|
|
|
saved_errno = errno;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* As we block SIGCHLD, lets wait for every child that has
|
|
|
|
* already changed state.
|
|
|
|
*/
|
|
|
|
while (1) {
|
|
|
|
pid = waitpid(-1, &status, WNOHANG);
|
|
|
|
|
|
|
|
if (pid <= 0) {
|
|
|
|
errno = saved_errno;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (WIFEXITED(status))
|
|
|
|
pr_info("Worker(pid %d) exited with %d\n",
|
|
|
|
pid, WEXITSTATUS(status));
|
|
|
|
else if (WIFSIGNALED(status))
|
|
|
|
pr_info("Worker(pid %d) was killed by %d\n",
|
|
|
|
pid, WTERMSIG(status));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static int setup_sigchld_handler()
|
|
|
|
{
|
|
|
|
struct sigaction action;
|
|
|
|
|
|
|
|
sigemptyset(&action.sa_mask);
|
|
|
|
sigaddset(&action.sa_mask, SIGCHLD);
|
|
|
|
action.sa_handler = reap_worker;
|
|
|
|
action.sa_flags = SA_RESTART;
|
|
|
|
|
|
|
|
if (sigaction(SIGCHLD, &action, NULL)) {
|
|
|
|
pr_perror("Can't setup SIGCHLD handler");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int restore_sigchld_handler()
|
|
|
|
{
|
|
|
|
struct sigaction action;
|
|
|
|
|
|
|
|
sigemptyset(&action.sa_mask);
|
|
|
|
sigaddset(&action.sa_mask, SIGCHLD);
|
|
|
|
action.sa_handler = SIG_DFL;
|
|
|
|
action.sa_flags = SA_RESTART;
|
|
|
|
|
|
|
|
if (sigaction(SIGCHLD, &action, NULL)) {
|
|
|
|
pr_perror("Can't restore SIGCHLD handler");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2013-09-13 13:44:09 +04:00
|
|
|
int cr_service(bool daemon_mode)
|
|
|
|
{
|
2013-12-12 02:54:00 +04:00
|
|
|
int server_fd = -1, n;
|
2013-09-13 13:44:09 +04:00
|
|
|
int child_pid;
|
|
|
|
|
|
|
|
struct sockaddr_un client_addr;
|
|
|
|
socklen_t client_addr_len;
|
|
|
|
|
2013-12-12 02:54:00 +04:00
|
|
|
n = sd_listen_fds(0);
|
|
|
|
if (n > 1) {
|
|
|
|
pr_perror("Too many file descriptors (%d) recieved.", n);
|
2013-09-24 11:11:56 +04:00
|
|
|
goto err;
|
2013-12-12 02:54:00 +04:00
|
|
|
} else if (n == 1)
|
|
|
|
server_fd = SD_LISTEN_FDS_START + 0;
|
|
|
|
else {
|
|
|
|
struct sockaddr_un server_addr;
|
|
|
|
socklen_t server_addr_len;
|
|
|
|
|
|
|
|
server_fd = socket(AF_LOCAL, SOCK_SEQPACKET, 0);
|
|
|
|
if (server_fd == -1) {
|
|
|
|
pr_perror("Can't initialize service socket.");
|
|
|
|
goto err;
|
|
|
|
}
|
2013-09-13 13:44:09 +04:00
|
|
|
|
2013-12-12 02:54:00 +04:00
|
|
|
memset(&server_addr, 0, sizeof(server_addr));
|
|
|
|
memset(&client_addr, 0, sizeof(client_addr));
|
|
|
|
server_addr.sun_family = AF_LOCAL;
|
2013-09-13 13:44:09 +04:00
|
|
|
|
2013-12-12 02:54:00 +04:00
|
|
|
if (opts.addr == NULL)
|
|
|
|
opts.addr = CR_DEFAULT_SERVICE_ADDRESS;
|
2013-09-13 13:44:09 +04:00
|
|
|
|
2013-12-12 02:54:00 +04:00
|
|
|
strcpy(server_addr.sun_path, opts.addr);
|
2013-09-13 13:44:09 +04:00
|
|
|
|
2013-12-12 02:54:00 +04:00
|
|
|
server_addr_len = strlen(server_addr.sun_path)
|
|
|
|
+ sizeof(server_addr.sun_family);
|
|
|
|
client_addr_len = sizeof(client_addr);
|
2013-09-13 13:44:09 +04:00
|
|
|
|
2013-12-12 02:54:00 +04:00
|
|
|
unlink(server_addr.sun_path);
|
2013-09-13 13:44:09 +04:00
|
|
|
|
2013-12-12 02:54:00 +04:00
|
|
|
if (bind(server_fd, (struct sockaddr *) &server_addr,
|
|
|
|
server_addr_len) == -1) {
|
|
|
|
pr_perror("Can't bind.");
|
|
|
|
goto err;
|
|
|
|
}
|
2013-09-13 13:44:09 +04:00
|
|
|
|
2013-12-12 02:54:00 +04:00
|
|
|
pr_info("The service socket is bound to %s\n", server_addr.sun_path);
|
2013-09-13 13:44:09 +04:00
|
|
|
|
2013-12-12 02:54:00 +04:00
|
|
|
/* change service socket permissions, so anyone can connect to it */
|
|
|
|
if (chmod(server_addr.sun_path, 0666)) {
|
|
|
|
pr_perror("Can't change permissions of the service socket.");
|
|
|
|
goto err;
|
|
|
|
}
|
2013-09-13 13:44:09 +04:00
|
|
|
|
2013-12-12 02:54:00 +04:00
|
|
|
if (listen(server_fd, 16) == -1) {
|
|
|
|
pr_perror("Can't listen for socket connections.");
|
|
|
|
goto err;
|
|
|
|
}
|
2013-09-13 13:44:09 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
if (daemon_mode) {
|
2013-11-15 23:04:30 +04:00
|
|
|
if (daemon(1, 0) == -1) {
|
2013-09-13 13:44:09 +04:00
|
|
|
pr_perror("Can't run service server in the background");
|
2013-09-24 11:11:56 +04:00
|
|
|
goto err;
|
2013-09-13 13:44:09 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-09-16 15:45:01 +04:00
|
|
|
if (opts.pidfile) {
|
2013-11-20 14:26:41 +04:00
|
|
|
if (write_pidfile(getpid()) == -1) {
|
2013-09-16 15:45:01 +04:00
|
|
|
pr_perror("Can't write pidfile");
|
2013-12-12 10:02:25 -08:00
|
|
|
goto err;
|
2013-09-16 15:45:01 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-11-01 14:32:03 +04:00
|
|
|
if (setup_sigchld_handler())
|
|
|
|
goto err;
|
2013-09-13 13:44:09 +04:00
|
|
|
|
|
|
|
while (1) {
|
2013-09-28 05:54:50 +04:00
|
|
|
int sk;
|
|
|
|
|
2013-09-13 13:44:09 +04:00
|
|
|
pr_info("Waiting for connection...\n");
|
|
|
|
|
2013-09-28 05:54:50 +04:00
|
|
|
sk = accept(server_fd, &client_addr, &client_addr_len);
|
|
|
|
if (sk == -1) {
|
2013-09-13 13:44:09 +04:00
|
|
|
pr_perror("Can't accept connection.");
|
2013-09-24 11:12:55 +04:00
|
|
|
goto err;
|
2013-09-13 13:44:09 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
pr_info("Connected.\n");
|
2013-09-28 05:59:33 +04:00
|
|
|
child_pid = fork();
|
|
|
|
if (child_pid == 0) {
|
|
|
|
int ret;
|
2013-09-13 13:44:09 +04:00
|
|
|
|
2013-11-01 14:32:03 +04:00
|
|
|
if (restore_sigchld_handler())
|
|
|
|
exit(1);
|
|
|
|
|
2013-09-28 05:59:33 +04:00
|
|
|
close(server_fd);
|
|
|
|
ret = cr_service_work(sk);
|
2013-09-28 05:54:50 +04:00
|
|
|
close(sk);
|
2013-09-28 05:59:33 +04:00
|
|
|
exit(ret);
|
2013-09-13 13:44:09 +04:00
|
|
|
}
|
2013-09-28 05:59:33 +04:00
|
|
|
|
|
|
|
if (child_pid < 0)
|
|
|
|
pr_perror("Can't fork a child");
|
|
|
|
|
|
|
|
close(sk);
|
2013-09-13 13:44:09 +04:00
|
|
|
}
|
|
|
|
|
2013-09-24 11:11:56 +04:00
|
|
|
err:
|
|
|
|
close_safe(&server_fd);
|
|
|
|
|
|
|
|
return 1;
|
2013-09-13 13:43:56 +04:00
|
|
|
}
|