2015-05-15 05:44:48 +08:00
|
|
|
/*
|
|
|
|
|
|
|
|
nsjail - subprocess management
|
|
|
|
-----------------------------------------
|
|
|
|
|
|
|
|
Copyright 2014 Google Inc. All Rights Reserved.
|
|
|
|
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
you may not use this file except in compliance with the License.
|
|
|
|
You may obtain a copy of the License at
|
|
|
|
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
See the License for the specific language governing permissions and
|
|
|
|
limitations under the License.
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include "subproc.h"
|
|
|
|
|
|
|
|
#include <arpa/inet.h>
|
|
|
|
#include <errno.h>
|
2015-05-15 22:02:15 +08:00
|
|
|
#include <fcntl.h>
|
2016-07-21 21:48:47 +08:00
|
|
|
#include <linux/sched.h>
|
2015-05-15 05:44:48 +08:00
|
|
|
#include <netinet/in.h>
|
|
|
|
#include <sched.h>
|
|
|
|
#include <signal.h>
|
|
|
|
#include <stdio.h>
|
|
|
|
#include <stdint.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <string.h>
|
|
|
|
#include <sys/prctl.h>
|
|
|
|
#include <sys/queue.h>
|
|
|
|
#include <sys/syscall.h>
|
|
|
|
#include <sys/types.h>
|
|
|
|
#include <sys/wait.h>
|
|
|
|
#include <time.h>
|
|
|
|
#include <unistd.h>
|
|
|
|
|
|
|
|
#include "common.h"
|
2016-06-19 21:50:25 +08:00
|
|
|
#include "cgroup.h"
|
2015-05-15 05:44:48 +08:00
|
|
|
#include "contain.h"
|
|
|
|
#include "log.h"
|
|
|
|
#include "net.h"
|
|
|
|
#include "sandbox.h"
|
2016-03-03 22:54:15 +08:00
|
|
|
#include "user.h"
|
2016-01-17 11:14:09 +08:00
|
|
|
#include "util.h"
|
2015-05-15 05:44:48 +08:00
|
|
|
|
2016-05-09 21:16:26 +08:00
|
|
|
static const char subprocDoneChar = 'D';
|
2016-02-28 09:34:43 +08:00
|
|
|
|
2015-05-15 22:02:15 +08:00
|
|
|
static int subprocNewProc(struct nsjconf_t *nsjconf, int fd_in, int fd_out, int fd_err, int pipefd)
|
2015-05-15 05:44:48 +08:00
|
|
|
{
|
2016-03-16 03:42:03 +08:00
|
|
|
if (containSetupFD(nsjconf, fd_in, fd_out, fd_err) == false) {
|
2015-08-16 02:48:48 +08:00
|
|
|
exit(1);
|
|
|
|
}
|
2016-05-05 11:44:12 +08:00
|
|
|
|
|
|
|
if (pipefd == -1) {
|
|
|
|
if (userInitNsFromParent(nsjconf, syscall(__NR_getpid)) == false) {
|
2016-06-19 21:50:25 +08:00
|
|
|
LOG_E("Couldn't initialize net user namespace");
|
2016-05-05 11:44:12 +08:00
|
|
|
exit(1);
|
|
|
|
}
|
2016-06-20 01:36:56 +08:00
|
|
|
if (cgroupInitNsFromParent(nsjconf, syscall(__NR_getpid)) == false) {
|
|
|
|
LOG_E("Couldn't initialize net user namespace");
|
|
|
|
exit(1);
|
|
|
|
}
|
2016-05-05 11:44:12 +08:00
|
|
|
} else {
|
|
|
|
char doneChar;
|
|
|
|
if (utilReadFromFd(pipefd, &doneChar, sizeof(doneChar)) != sizeof(doneChar)) {
|
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
if (doneChar != subprocDoneChar) {
|
|
|
|
exit(1);
|
|
|
|
}
|
2016-02-28 09:34:43 +08:00
|
|
|
}
|
2016-03-08 22:57:09 +08:00
|
|
|
if (containContain(nsjconf) == false) {
|
2015-05-15 05:44:48 +08:00
|
|
|
exit(1);
|
|
|
|
}
|
2016-01-27 00:42:10 +08:00
|
|
|
if (nsjconf->keep_env == false) {
|
|
|
|
clearenv();
|
|
|
|
}
|
|
|
|
struct charptr_t *p;
|
|
|
|
TAILQ_FOREACH(p, &nsjconf->envs, pointers) {
|
|
|
|
putenv(p->val);
|
2015-05-15 05:44:48 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
LOG_D("Trying to execve('%s')", nsjconf->argv[0]);
|
2016-03-04 08:39:21 +08:00
|
|
|
for (size_t i = 0; nsjconf->argv[i]; i++) {
|
|
|
|
LOG_D(" Arg[%zu]: '%s'", i, nsjconf->argv[i]);
|
2015-05-15 05:44:48 +08:00
|
|
|
}
|
2016-03-08 22:57:09 +08:00
|
|
|
|
|
|
|
/* Should be the last one in the sequence */
|
|
|
|
if (sandboxApply(nsjconf) == false) {
|
|
|
|
exit(1);
|
|
|
|
}
|
2016-01-27 00:42:10 +08:00
|
|
|
execv(nsjconf->argv[0], &nsjconf->argv[0]);
|
2015-05-15 22:02:15 +08:00
|
|
|
|
|
|
|
PLOG_E("execve('%s') failed", nsjconf->argv[0]);
|
|
|
|
|
|
|
|
_exit(1);
|
2015-05-15 05:44:48 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static void subprocAdd(struct nsjconf_t *nsjconf, pid_t pid, int sock)
|
|
|
|
{
|
2016-02-28 09:34:43 +08:00
|
|
|
struct pids_t *p = utilMalloc(sizeof(struct pids_t));
|
2015-05-15 05:44:48 +08:00
|
|
|
p->pid = pid;
|
|
|
|
p->start = time(NULL);
|
|
|
|
netConnToText(sock, true /* remote */ , p->remote_txt, sizeof(p->remote_txt),
|
|
|
|
&p->remote_addr);
|
2016-05-08 09:09:43 +08:00
|
|
|
|
|
|
|
char fname[PATH_MAX];
|
|
|
|
snprintf(fname, sizeof(fname), "/proc/%d/syscall", (int)pid);
|
2016-05-09 21:16:26 +08:00
|
|
|
p->pid_syscall_fd = TEMP_FAILURE_RETRY(open(fname, O_RDONLY));
|
2016-05-08 09:09:43 +08:00
|
|
|
|
2016-01-09 23:09:05 +08:00
|
|
|
TAILQ_INSERT_HEAD(&nsjconf->pids, p, pointers);
|
2015-05-15 05:44:48 +08:00
|
|
|
|
|
|
|
LOG_D("Added pid '%d' with start time '%u' to the queue for IP: '%s'", pid,
|
|
|
|
(unsigned int)p->start, p->remote_txt);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void subprocRemove(struct nsjconf_t *nsjconf, pid_t pid)
|
|
|
|
{
|
|
|
|
struct pids_t *p;
|
2016-01-09 23:09:05 +08:00
|
|
|
TAILQ_FOREACH(p, &nsjconf->pids, pointers) {
|
2015-05-15 05:44:48 +08:00
|
|
|
if (p->pid == pid) {
|
|
|
|
LOG_D("Removing pid '%d' from the queue (IP:'%s', start time:'%u')", p->pid,
|
|
|
|
p->remote_txt, (unsigned int)p->start);
|
2016-05-10 05:11:18 +08:00
|
|
|
TEMP_FAILURE_RETRY(close(p->pid_syscall_fd));
|
2016-01-09 23:09:05 +08:00
|
|
|
TAILQ_REMOVE(&nsjconf->pids, p, pointers);
|
2015-05-15 05:44:48 +08:00
|
|
|
free(p);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
LOG_W("PID: %d not found (?)", pid);
|
|
|
|
}
|
|
|
|
|
|
|
|
int subprocCount(struct nsjconf_t *nsjconf)
|
|
|
|
{
|
|
|
|
int cnt = 0;
|
|
|
|
struct pids_t *p;
|
2016-01-09 23:09:05 +08:00
|
|
|
TAILQ_FOREACH(p, &nsjconf->pids, pointers) {
|
2015-05-15 05:44:48 +08:00
|
|
|
cnt++;
|
|
|
|
}
|
|
|
|
return cnt;
|
|
|
|
}
|
|
|
|
|
|
|
|
void subprocDisplay(struct nsjconf_t *nsjconf)
|
|
|
|
{
|
|
|
|
LOG_I("Total number of spawned namespaces: %d", subprocCount(nsjconf));
|
|
|
|
time_t now = time(NULL);
|
|
|
|
struct pids_t *p;
|
2016-01-09 23:09:05 +08:00
|
|
|
TAILQ_FOREACH(p, &nsjconf->pids, pointers) {
|
2015-05-15 05:44:48 +08:00
|
|
|
time_t diff = now - p->start;
|
2015-05-15 22:02:15 +08:00
|
|
|
time_t left = nsjconf->tlimit ? nsjconf->tlimit - diff : 0;
|
2015-08-16 02:10:07 +08:00
|
|
|
LOG_I("PID: %d, Remote host: %s, Run time: %ld sec. (time left: %ld sec.)", p->pid,
|
|
|
|
p->remote_txt, (long)diff, (long)left);
|
2015-05-15 05:44:48 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-05-08 09:09:43 +08:00
|
|
|
static struct pids_t *subprocGetPidElem(struct nsjconf_t *nsjconf, pid_t pid)
|
|
|
|
{
|
|
|
|
struct pids_t *p;
|
|
|
|
TAILQ_FOREACH(p, &nsjconf->pids, pointers) {
|
|
|
|
if (p->pid == pid) {
|
|
|
|
return p;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void subprocSeccompViolation(struct nsjconf_t *nsjconf, siginfo_t * si)
|
2016-05-05 07:58:26 +08:00
|
|
|
{
|
2016-05-10 21:45:48 +08:00
|
|
|
LOG_W("PID: %d commited syscall/seccomp violation and exited with SIGSYS", si->si_pid);
|
2016-05-08 09:09:43 +08:00
|
|
|
|
|
|
|
struct pids_t *p = subprocGetPidElem(nsjconf, si->si_pid);
|
|
|
|
if (p == NULL) {
|
|
|
|
LOG_E("Couldn't find pid element in the subproc list for PID: %d", (int)si->si_pid);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
char buf[4096];
|
|
|
|
ssize_t rdsize = utilReadFromFd(p->pid_syscall_fd, buf, sizeof(buf) - 1);
|
|
|
|
if (rdsize < 1) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
buf[rdsize - 1] = '\0';
|
|
|
|
|
2016-05-10 21:45:48 +08:00
|
|
|
uintptr_t sc, arg1, arg2, arg3, arg4, arg5, arg6, sp, pc;
|
2016-05-08 09:36:31 +08:00
|
|
|
if (sscanf
|
2016-05-10 21:47:13 +08:00
|
|
|
(buf, "%td %tx %tx %tx %tx %tx %tx %tx %tx", &sc, &arg1, &arg2, &arg3, &arg4, &arg5,
|
2016-05-08 09:36:31 +08:00
|
|
|
&arg6, &sp, &pc) != 9) {
|
2016-05-08 09:36:16 +08:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2016-05-08 09:09:43 +08:00
|
|
|
LOG_W
|
2016-05-10 21:47:13 +08:00
|
|
|
("PID: %d, Syscall number: %td, Arguments: %#tx, %#tx, %#tx, %#tx, %#tx, %#tx, SP: %#tx, PC: %#tx",
|
2016-05-09 21:16:26 +08:00
|
|
|
(int)si->si_pid, sc, arg1, arg2, arg3, arg4, arg5, arg6, sp, pc);
|
2016-05-05 07:58:26 +08:00
|
|
|
}
|
|
|
|
|
2015-07-08 00:33:10 +08:00
|
|
|
int subprocReap(struct nsjconf_t *nsjconf)
|
2015-05-15 05:44:48 +08:00
|
|
|
{
|
|
|
|
int status;
|
2015-07-08 00:33:10 +08:00
|
|
|
int rv = 0;
|
2016-05-05 07:58:26 +08:00
|
|
|
siginfo_t si;
|
|
|
|
|
|
|
|
for (;;) {
|
|
|
|
si.si_pid = 0;
|
|
|
|
if (waitid(P_ALL, 0, &si, WNOHANG | WNOWAIT | WEXITED) == -1) {
|
|
|
|
break;
|
2015-05-15 05:44:48 +08:00
|
|
|
}
|
2016-05-05 07:58:26 +08:00
|
|
|
if (si.si_pid == 0) {
|
|
|
|
break;
|
|
|
|
}
|
2016-05-05 11:04:01 +08:00
|
|
|
if (si.si_code == CLD_KILLED && si.si_status == SIGSYS) {
|
2016-05-08 09:09:43 +08:00
|
|
|
subprocSeccompViolation(nsjconf, &si);
|
2016-05-05 07:58:26 +08:00
|
|
|
}
|
|
|
|
|
2016-05-05 11:07:21 +08:00
|
|
|
if (wait4(si.si_pid, &status, WNOHANG, NULL) == si.si_pid) {
|
2016-06-19 22:02:00 +08:00
|
|
|
cgroupFinishFromParent(nsjconf, si.si_pid);
|
2016-05-05 07:58:26 +08:00
|
|
|
if (WIFEXITED(status)) {
|
|
|
|
subprocRemove(nsjconf, si.si_pid);
|
|
|
|
LOG_I("PID: %d exited with status: %d, (PIDs left: %d)", si.si_pid,
|
|
|
|
WEXITSTATUS(status), subprocCount(nsjconf));
|
2016-05-05 11:12:06 +08:00
|
|
|
rv = WEXITSTATUS(status) % 100;
|
|
|
|
if (rv == 0 && WEXITSTATUS(status) != 0) {
|
|
|
|
rv = 1;
|
2016-05-05 07:58:26 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
if (WIFSIGNALED(status)) {
|
|
|
|
subprocRemove(nsjconf, si.si_pid);
|
|
|
|
LOG_I("PID: %d terminated with signal: %d, (PIDs left: %d)",
|
|
|
|
si.si_pid, WTERMSIG(status), subprocCount(nsjconf));
|
|
|
|
rv = 100 + WTERMSIG(status);
|
|
|
|
}
|
2015-05-15 05:44:48 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
time_t now = time(NULL);
|
|
|
|
struct pids_t *p;
|
2016-01-09 23:09:05 +08:00
|
|
|
TAILQ_FOREACH(p, &nsjconf->pids, pointers) {
|
2015-05-15 05:44:48 +08:00
|
|
|
if (nsjconf->tlimit == 0) {
|
|
|
|
continue;
|
|
|
|
}
|
2016-05-05 07:58:26 +08:00
|
|
|
pid_t pid = p->pid;
|
2015-05-15 05:44:48 +08:00
|
|
|
time_t diff = now - p->start;
|
|
|
|
if (diff >= nsjconf->tlimit) {
|
|
|
|
LOG_I("PID: %d run time >= time limit (%ld >= %ld) (%s). Killing it", pid,
|
|
|
|
(long)diff, (long)nsjconf->tlimit, p->remote_txt);
|
|
|
|
/* Probably a kernel bug - some processes cannot be killed with KILL if
|
|
|
|
* they're namespaced, and in a stopped state */
|
|
|
|
kill(pid, SIGCONT);
|
|
|
|
PLOG_D("Sent SIGCONT to PID: %d", pid);
|
|
|
|
kill(pid, SIGKILL);
|
|
|
|
PLOG_D("Sent SIGKILL to PID: %d", pid);
|
|
|
|
}
|
|
|
|
}
|
2015-07-08 00:33:10 +08:00
|
|
|
return rv;
|
2015-05-15 05:44:48 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
void subprocKillAll(struct nsjconf_t *nsjconf)
|
|
|
|
{
|
|
|
|
struct pids_t *p;
|
2016-01-09 23:09:05 +08:00
|
|
|
TAILQ_FOREACH(p, &nsjconf->pids, pointers) {
|
2015-05-15 05:44:48 +08:00
|
|
|
kill(p->pid, SIGKILL);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-02-29 06:23:24 +08:00
|
|
|
static bool subprocInitParent(struct nsjconf_t *nsjconf, pid_t pid, int pipefd)
|
|
|
|
{
|
2016-03-03 22:43:40 +08:00
|
|
|
if (netInitNsFromParent(nsjconf, pid) == false) {
|
2016-02-29 06:23:24 +08:00
|
|
|
LOG_E("Couldn't create and put MACVTAP interface into NS of PID '%d'", pid);
|
|
|
|
return false;
|
|
|
|
}
|
2016-06-19 21:50:25 +08:00
|
|
|
if (cgroupInitNsFromParent(nsjconf, pid) == false) {
|
|
|
|
LOG_E("Couldn't initialize cgroup user namespace");
|
|
|
|
exit(1);
|
|
|
|
}
|
2016-03-03 22:54:15 +08:00
|
|
|
if (userInitNsFromParent(nsjconf, pid) == false) {
|
2016-02-29 06:23:24 +08:00
|
|
|
LOG_E("Couldn't initialize user namespaces for pid %d", pid);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
if (utilWriteToFd(pipefd, &subprocDoneChar, sizeof(subprocDoneChar)) !=
|
|
|
|
sizeof(subprocDoneChar)) {
|
|
|
|
LOG_E("Couldn't signal the new process via a socketpair");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2015-05-15 05:44:48 +08:00
|
|
|
void subprocRunChild(struct nsjconf_t *nsjconf, int fd_in, int fd_out, int fd_err)
|
|
|
|
{
|
|
|
|
if (netLimitConns(nsjconf, fd_in) == false) {
|
|
|
|
return;
|
|
|
|
}
|
2016-06-19 17:55:55 +08:00
|
|
|
#ifndef CLONE_NEWCGROUP
|
|
|
|
#define CLONE_NEWCGROUP 0x02000000
|
|
|
|
#endif
|
2016-03-04 08:39:21 +08:00
|
|
|
unsigned long flags = 0UL;
|
2015-05-15 05:44:48 +08:00
|
|
|
flags |= (nsjconf->clone_newnet ? CLONE_NEWNET : 0);
|
|
|
|
flags |= (nsjconf->clone_newuser ? CLONE_NEWUSER : 0);
|
|
|
|
flags |= (nsjconf->clone_newns ? CLONE_NEWNS : 0);
|
|
|
|
flags |= (nsjconf->clone_newpid ? CLONE_NEWPID : 0);
|
|
|
|
flags |= (nsjconf->clone_newipc ? CLONE_NEWIPC : 0);
|
|
|
|
flags |= (nsjconf->clone_newuts ? CLONE_NEWUTS : 0);
|
2016-06-19 17:55:55 +08:00
|
|
|
flags |= (nsjconf->clone_newcgroup ? CLONE_NEWCGROUP : 0);
|
2015-05-15 05:44:48 +08:00
|
|
|
|
2015-08-15 22:02:38 +08:00
|
|
|
if (nsjconf->mode == MODE_STANDALONE_EXECVE) {
|
2016-03-04 08:39:21 +08:00
|
|
|
LOG_D("Entering namespace with flags: %#lx", flags);
|
2015-08-15 22:02:38 +08:00
|
|
|
if (unshare(flags) == -1) {
|
2016-03-04 08:39:21 +08:00
|
|
|
PLOG_E("unshare(%#lx)", flags);
|
2015-08-15 22:02:38 +08:00
|
|
|
_exit(EXIT_FAILURE);
|
|
|
|
}
|
|
|
|
subprocNewProc(nsjconf, fd_in, fd_out, fd_err, -1);
|
|
|
|
}
|
|
|
|
|
|
|
|
flags |= SIGCHLD;
|
2016-03-04 08:39:21 +08:00
|
|
|
LOG_D("Creating new process with clone flags: %#lx", flags);
|
2015-05-15 05:44:48 +08:00
|
|
|
|
2016-02-28 09:34:43 +08:00
|
|
|
int sv[2];
|
|
|
|
if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, sv) == -1) {
|
|
|
|
PLOG_E("socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC) failed");
|
2015-05-15 22:02:15 +08:00
|
|
|
return;
|
|
|
|
}
|
2016-05-10 05:16:26 +08:00
|
|
|
int child_fd = sv[0];
|
|
|
|
int parent_fd = sv[1];
|
2015-05-15 22:02:15 +08:00
|
|
|
|
2015-11-25 01:34:05 +08:00
|
|
|
pid_t pid = syscall(__NR_clone, (uintptr_t) flags, NULL, NULL, NULL, (uintptr_t) 0);
|
2015-05-15 05:44:48 +08:00
|
|
|
if (pid == 0) {
|
2016-05-10 05:16:26 +08:00
|
|
|
TEMP_FAILURE_RETRY(close(parent_fd));
|
|
|
|
subprocNewProc(nsjconf, fd_in, fd_out, fd_err, child_fd);
|
2015-05-15 05:44:48 +08:00
|
|
|
}
|
2016-04-25 22:06:19 +08:00
|
|
|
defer {
|
2016-05-10 05:16:26 +08:00
|
|
|
TEMP_FAILURE_RETRY(close(parent_fd));
|
2016-06-12 19:07:40 +08:00
|
|
|
};
|
2016-05-10 05:16:26 +08:00
|
|
|
TEMP_FAILURE_RETRY(close(child_fd));
|
2015-05-15 05:44:48 +08:00
|
|
|
if (pid == -1) {
|
2016-03-04 08:39:21 +08:00
|
|
|
PLOG_E("clone(flags=%#lx) failed. You probably need root privileges if your system "
|
2015-05-15 05:44:48 +08:00
|
|
|
"doesn't support CLONE_NEWUSER. Alternatively, you might want to recompile your "
|
2015-08-12 10:32:34 +08:00
|
|
|
"kernel with support for namespaces or check the setting of the "
|
|
|
|
"kernel.unprivileged_userns_clone sysctl", flags);
|
2015-05-15 05:44:48 +08:00
|
|
|
return;
|
|
|
|
}
|
2016-02-29 23:09:08 +08:00
|
|
|
subprocAdd(nsjconf, pid, fd_in);
|
2015-05-15 05:44:48 +08:00
|
|
|
|
2016-05-10 05:16:26 +08:00
|
|
|
if (subprocInitParent(nsjconf, pid, parent_fd) == false) {
|
2016-02-28 23:43:35 +08:00
|
|
|
return;
|
2016-02-28 09:34:43 +08:00
|
|
|
}
|
2015-05-28 09:37:08 +08:00
|
|
|
|
2015-10-18 01:11:48 +08:00
|
|
|
char cs_addr[64];
|
|
|
|
netConnToText(fd_in, true /* remote */ , cs_addr, sizeof(cs_addr), NULL);
|
|
|
|
LOG_I("PID: %d about to execute '%s' for %s", pid, nsjconf->argv[0], cs_addr);
|
2015-05-15 05:44:48 +08:00
|
|
|
}
|