2015-05-15 05:44:48 +08:00
/*
nsjail - cmdline parsing
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
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 "cmdline.h"
# include <ctype.h>
# include <errno.h>
2016-02-29 06:40:34 +08:00
# include <fcntl.h>
2015-05-15 05:44:48 +08:00
# include <getopt.h>
# include <grp.h>
# include <limits.h>
# include <pwd.h>
# include <stdbool.h>
# include <stdio.h>
2017-05-11 22:17:54 +08:00
# include <stdlib.h>
2015-10-17 22:48:30 +08:00
# include <string.h>
2015-05-15 05:44:48 +08:00
# include <strings.h>
2015-10-17 22:48:30 +08:00
# include <sys/mount.h>
2017-05-11 22:17:54 +08:00
# include <sys/personality.h>
2016-02-29 06:40:34 +08:00
# include <sys/stat.h>
2015-05-15 05:44:48 +08:00
# include <sys/time.h>
2016-02-29 06:40:34 +08:00
# include <sys/types.h>
2015-05-15 05:44:48 +08:00
# include <unistd.h>
2017-05-26 07:44:16 +08:00
# include "config.h"
2015-05-15 05:44:48 +08:00
# include "log.h"
2017-05-21 23:37:18 +08:00
# include "mount.h"
2016-01-17 11:14:09 +08:00
# include "util.h"
2017-05-27 05:07:47 +08:00
# include "user.h"
2015-05-15 05:44:48 +08:00
struct custom_option {
struct option opt ;
const char * descr ;
} ;
2017-05-11 22:17:54 +08:00
/* *INDENT-OFF* */
struct custom_option custom_opts [ ] = {
{ { " help " , no_argument , NULL , ' h ' } , " Help plz.. " } ,
{ { " mode " , required_argument , NULL , ' M ' } ,
" Execution mode (default: o [MODE_STANDALONE_ONCE]): \n "
" \t l: Wait for connections on a TCP port (specified with --port) "
" [MODE_LISTEN_TCP] \n "
" \t o: Immediately launch a single process on the console using "
" clone/execve [MODE_STANDALONE_ONCE] \n "
" \t e: Immediately launch a single process on the console using execve "
" [MODE_STANDALONE_EXECVE] \n "
" \t r: Immediately launch a single process on the console, keep doing it "
" forever [MODE_STANDALONE_RERUN] " } ,
2017-05-26 07:44:16 +08:00
{ { " config " , required_argument , NULL , ' C ' } , " Configuration file in the config.proto ProtoBuf format " } ,
2017-05-11 22:17:54 +08:00
{ { " chroot " , required_argument , NULL , ' c ' } , " Directory containing / of the jail (default: none) " } ,
{ { " rw " , no_argument , NULL , 0x601 } , " Mount / and /proc as RW (default: RO) " } ,
2017-05-27 05:07:47 +08:00
{ { " user " , required_argument , NULL , ' u ' } , " Username/uid of processess inside the jail (default: your current uid). You can also use inside_ns_uid:outside_ns_uid:count convention here. Can be specified multiple times " } ,
{ { " group " , required_argument , NULL , ' g ' } , " Groupname/gid of processess inside the jail (default: your current gid). You can also use inside_ns_gid:global_ns_gid:count convention here. Can be specified multiple times " } ,
2017-05-11 22:17:54 +08:00
{ { " hostname " , required_argument , NULL , ' H ' } , " UTS name (hostname) of the jail (default: 'NSJAIL') " } ,
{ { " cwd " , required_argument , NULL , ' D ' } , " Directory in the namespace the process will run (default: '/') " } ,
{ { " port " , required_argument , NULL , ' p ' } , " TCP port to bind to (enables MODE_LISTEN_TCP) (default: 0) " } ,
{ { " bindhost " , required_argument , NULL , 0x604 } , " IP address port to bind to (only in [MODE_LISTEN_TCP]), '::ffff:127.0.0.1' for locahost (default: '::') " } ,
{ { " max_conns_per_ip " , required_argument , NULL , ' i ' } , " Maximum number of connections per one IP (only in [MODE_LISTEN_TCP]), (default: 0 (unlimited)) " } ,
{ { " log " , required_argument , NULL , ' l ' } , " Log file (default: /proc/self/fd/2) " } ,
{ { " time_limit " , required_argument , NULL , ' t ' } , " Maximum time that a jail can exist, in seconds (default: 600) " } ,
{ { " daemon " , no_argument , NULL , ' d ' } , " Daemonize after start " } ,
{ { " verbose " , no_argument , NULL , ' v ' } , " Verbose output " } ,
{ { " quiet " , no_argument , NULL , ' q ' } , " Only output warning and more important messages " } ,
{ { " keep_env " , no_argument , NULL , ' e ' } , " Should all environment variables be passed to the child? " } ,
{ { " env " , required_argument , NULL , ' E ' } , " Environment variable (can be used multiple times) " } ,
{ { " keep_caps " , no_argument , NULL , 0x0501 } , " Don't drop capabilities (DANGEROUS) " } ,
{ { " silent " , no_argument , NULL , 0x0502 } , " Redirect child's fd:0/1/2 to /dev/null " } ,
{ { " skip_setsid " , no_argument , NULL , 0x0504 } , " Don't call setsid(), allows for terminal signal handling in the sandboxed process " } ,
{ { " pass_fd " , required_argument , NULL , 0x0505 } , " Don't close this FD before executing child (can be specified multiple times), by default: 0/1/2 are kept open " } ,
{ { " pivot_root_only " , no_argument , NULL , 0x0506 } , " Only perform pivot_root, no chroot. This will enable nested namespaces " } ,
{ { " disable_no_new_privs " , no_argument , NULL , 0x0507 } , " Don't set the prctl(NO_NEW_PRIVS, 1) (DANGEROUS) " } ,
{ { " rlimit_as " , required_argument , NULL , 0x0201 } , " RLIMIT_AS in MB, 'max' for RLIM_INFINITY, 'def' for the current value (default: 512) " } ,
{ { " rlimit_core " , required_argument , NULL , 0x0202 } , " RLIMIT_CORE in MB, 'max' for RLIM_INFINITY, 'def' for the current value (default: 0) " } ,
{ { " rlimit_cpu " , required_argument , NULL , 0x0203 } , " RLIMIT_CPU, 'max' for RLIM_INFINITY, 'def' for the current value (default: 600) " } ,
{ { " rlimit_fsize " , required_argument , NULL , 0x0204 } , " RLIMIT_FSIZE in MB, 'max' for RLIM_INFINITY, 'def' for the current value (default: 1) " } ,
{ { " rlimit_nofile " , required_argument , NULL , 0x0205 } , " RLIMIT_NOFILE, 'max' for RLIM_INFINITY, 'def' for the current value (default: 32) " } ,
{ { " rlimit_nproc " , required_argument , NULL , 0x0206 } , " RLIMIT_NPROC, 'max' for RLIM_INFINITY, 'def' for the current value (default: 'def') " } ,
{ { " rlimit_stack " , required_argument , NULL , 0x0207 } , " RLIMIT_STACK in MB, 'max' for RLIM_INFINITY, 'def' for the current value (default: 'def') " } ,
{ { " persona_addr_compat_layout " , no_argument , NULL , 0x0301 } , " personality(ADDR_COMPAT_LAYOUT) " } ,
{ { " persona_mmap_page_zero " , no_argument , NULL , 0x0302 } , " personality(MMAP_PAGE_ZERO) " } ,
{ { " persona_read_implies_exec " , no_argument , NULL , 0x0303 } , " personality(READ_IMPLIES_EXEC) " } ,
{ { " persona_addr_limit_3gb " , no_argument , NULL , 0x0304 } , " personality(ADDR_LIMIT_3GB) " } ,
{ { " persona_addr_no_randomize " , no_argument , NULL , 0x0305 } , " personality(ADDR_NO_RANDOMIZE) " } ,
{ { " disable_clone_newnet " , no_argument , NULL , ' N ' } , " Don't use CLONE_NEWNET. Enable networking inside the jail " } ,
{ { " disable_clone_newuser " , no_argument , NULL , 0x0402 } , " Don't use CLONE_NEWUSER. Requires euid==0 " } ,
{ { " disable_clone_newns " , no_argument , NULL , 0x0403 } , " Don't use CLONE_NEWNS " } ,
{ { " disable_clone_newpid " , no_argument , NULL , 0x0404 } , " Don't use CLONE_NEWPID " } ,
{ { " disable_clone_newipc " , no_argument , NULL , 0x0405 } , " Don't use CLONE_NEWIPC " } ,
{ { " disable_clone_newuts " , no_argument , NULL , 0x0406 } , " Don't use CLONE_NEWUTS " } ,
{ { " enable_clone_newcgroup " , no_argument , NULL , 0x0407 } , " Use CLONE_NEWCGROUP " } ,
{ { " uid_mapping " , required_argument , NULL , ' U ' } , " Add a custom uid mapping of the form inside_uid:outside_uid:count. Setting this requires newuidmap to be present " } ,
{ { " gid_mapping " , required_argument , NULL , ' G ' } , " Add a custom gid mapping of the form inside_gid:outside_gid:count. Setting this requires newuidmap to be present " } ,
{ { " bindmount_ro " , required_argument , NULL , ' R ' } , " List of mountpoints to be mounted --bind (ro) inside the container. Can be specified multiple times. Supports 'source' syntax, or 'source:dest' " } ,
{ { " bindmount " , required_argument , NULL , ' B ' } , " List of mountpoints to be mounted --bind (rw) inside the container. Can be specified multiple times. Supports 'source' syntax, or 'source:dest' " } ,
{ { " tmpfsmount " , required_argument , NULL , ' T ' } , " List of mountpoints to be mounted as RW/tmpfs inside the container. Can be specified multiple times. Supports 'dest' syntax " } ,
{ { " tmpfs_size " , required_argument , NULL , 0x0602 } , " Number of bytes to allocate for tmpfsmounts (default: 4194304) " } ,
{ { " disable_proc " , no_argument , NULL , 0x0603 } , " Disable mounting /proc in the jail " } ,
{ { " seccomp_policy " , required_argument , NULL , ' P ' } , " Path to file containing seccomp-bpf policy (see kafel/) " } ,
{ { " seccomp_string " , required_argument , NULL , 0x0901 } , " String with kafel seccomp-bpf policy (see kafel/) " } ,
{ { " cgroup_mem_max " , required_argument , NULL , 0x0801 } , " Maximum number of bytes to use in the group (default: '0' - disabled) " } ,
{ { " cgroup_mem_mount " , required_argument , NULL , 0x0802 } , " Location of memory cgroup FS (default: '/sys/fs/cgroup/memory') " } ,
{ { " cgroup_mem_parent " , required_argument , NULL , 0x0803 } , " Which pre-existing memory cgroup to use as a parent (default: 'NSJAIL') " } ,
{ { " cgroup_pids_max " , required_argument , NULL , 0x0811 } , " Maximum number of pids in a cgroup (default: '0' - disabled) " } ,
{ { " cgroup_pids_mount " , required_argument , NULL , 0x0812 } , " Location of pids cgroup FS (default: '/sys/fs/cgroup/pids') " } ,
{ { " cgroup_pids_parent " , required_argument , NULL , 0x0813 } , " Which pre-existing pids cgroup to use as a parent (default: 'NSJAIL') " } ,
{ { " iface_no_lo " , no_argument , NULL , 0x700 } , " Don't bring up the 'lo' interface " } ,
{ { " macvlan_iface " , required_argument , NULL , ' I ' } , " Interface which will be cloned (MACVLAN) and put inside the subprocess' namespace as 'vs' " } ,
2017-05-11 23:18:07 +08:00
{ { " macvlan_vs_ip " , required_argument , NULL , 0x701 } , " IP of the 'vs' interface (e.g. \" 192.168.0.1 \" ) " } ,
{ { " macvlan_vs_nm " , required_argument , NULL , 0x702 } , " Netmask of the 'vs' interface (e.g. \" 255.255.255.0 \" ) " } ,
{ { " macvlan_vs_gw " , required_argument , NULL , 0x703 } , " Default GW for the 'vs' interface (e.g. \" 192.168.0.1 \" ) " } ,
2017-05-11 22:17:54 +08:00
} ;
struct custom_option deprecated_opts [ ] = {
// Compatibilty flags for MACVLAN.
// TODO(rswiecki): Remove this at some point.
{ { " iface " , required_argument , NULL , ' I ' } , " Interface which will be cloned (MACVLAN) and put inside the subprocess' namespace as 'vs' " } ,
2017-05-11 23:18:07 +08:00
{ { " iface_vs_ip " , required_argument , NULL , 0x701 } , " IP of the 'vs' interface (e.g. \" 192.168.0.1 \" ) " } ,
{ { " iface_vs_nm " , required_argument , NULL , 0x702 } , " Netmask of the 'vs' interface (e.g. \" 255.255.255.0 \" ) " } ,
{ { " iface_vs_gw " , required_argument , NULL , 0x703 } , " Default GW for the 'vs' interface (e.g. \" 192.168.0.1 \" ) " } ,
2017-05-11 22:17:54 +08:00
} ;
/* *INDENT-ON* */
2015-05-15 05:44:48 +08:00
static const char * logYesNo ( bool yes )
{
return ( yes ? " true " : " false " ) ;
}
2017-05-11 22:17:54 +08:00
static void cmdlineOptUsage ( struct custom_option * option )
{
if ( option - > opt . val < 0x80 ) {
LOG_HELP_BOLD ( " --%s%s%c %s " , option - > opt . name , " |- " , option - > opt . val ,
option - > opt . has_arg = = required_argument ? " VALUE " : " " ) ;
} else {
LOG_HELP_BOLD ( " --%s %s " , option - > opt . name ,
option - > opt . has_arg = = required_argument ? " VALUE " : " " ) ;
}
LOG_HELP ( " \t %s " , option - > descr ) ;
}
static void cmdlineUsage ( const char * pname )
2015-05-15 05:44:48 +08:00
{
LOG_HELP_BOLD ( " Usage: %s [options] -- path_to_command [args] " , pname ) ;
LOG_HELP_BOLD ( " Options: " ) ;
2017-05-11 22:17:54 +08:00
for ( size_t i = 0 ; i < ARRAYSIZE ( custom_opts ) ; i + + ) {
cmdlineOptUsage ( & custom_opts [ i ] ) ;
}
LOG_HELP_BOLD ( " \n Deprecated options: " ) ;
for ( size_t i = 0 ; i < ARRAYSIZE ( deprecated_opts ) ; i + + ) {
cmdlineOptUsage ( & deprecated_opts [ i ] ) ;
// Find replacement flag.
for ( size_t j = 0 ; j < ARRAYSIZE ( custom_opts ) ; j + + ) {
if ( custom_opts [ j ] . opt . val = = deprecated_opts [ i ] . opt . val ) {
LOG_HELP_BOLD ( " \t DEPRECATED: Use %s instead. " ,
custom_opts [ j ] . opt . name ) ;
break ;
}
2015-05-15 05:44:48 +08:00
}
}
2016-05-10 06:54:25 +08:00
LOG_HELP_BOLD ( " \n Examples: " ) ;
LOG_HELP ( " Wait on a port 31337 for connections, and run /bin/sh " ) ;
LOG_HELP_BOLD ( " nsjail -Ml --port 31337 --chroot / -- /bin/sh -i " ) ;
LOG_HELP ( " Re-run echo command as a sub-process " ) ;
LOG_HELP_BOLD ( " nsjail -Mr --chroot / -- /bin/echo \" ABC \" " ) ;
LOG_HELP ( " Run echo command once only, as a sub-process " ) ;
LOG_HELP_BOLD ( " nsjail -Mo --chroot / -- /bin/echo \" ABC \" " ) ;
2016-05-10 21:54:10 +08:00
LOG_HELP ( " Execute echo command directly, without a supervising process " ) ;
LOG_HELP_BOLD ( " nsjail -Me --chroot / --disable_proc -- /bin/echo \" ABC \" " ) ;
2015-05-15 05:44:48 +08:00
}
void cmdlineLogParams ( struct nsjconf_t * nsjconf )
{
switch ( nsjconf - > mode ) {
case MODE_LISTEN_TCP :
LOG_I ( " Mode: LISTEN_TCP " ) ;
break ;
case MODE_STANDALONE_ONCE :
LOG_I ( " Mode: STANDALONE_ONCE " ) ;
break ;
2015-08-15 22:02:38 +08:00
case MODE_STANDALONE_EXECVE :
LOG_I ( " Mode: STANDALONE_EXECVE " ) ;
break ;
2015-05-15 05:44:48 +08:00
case MODE_STANDALONE_RERUN :
LOG_I ( " Mode: STANDALONE_RERUN " ) ;
break ;
default :
LOG_F ( " Mode: UNKNOWN " ) ;
break ;
}
2017-05-11 22:17:54 +08:00
LOG_I ( " Jail parameters: hostname:'%s', chroot:'%s', process:'%s', "
" bind:[%s]:%d, "
" max_conns_per_ip:%u, time_limit:%ld, personality:%#lx, daemonize:%s, "
" clone_newnet:%s, clone_newuser:%s, clone_newns:%s, clone_newpid:%s, "
" clone_newipc:%s, clonew_newuts:%s, clone_newcgroup:%s, keep_caps:%s, "
" tmpfs_size:%zu, disable_no_new_privs:%s, pivot_root_only:%s " ,
nsjconf - > hostname , nsjconf - > chroot , nsjconf - > argv [ 0 ] , nsjconf - > bindhost ,
nsjconf - > port , nsjconf - > max_conns_per_ip , nsjconf - > tlimit ,
nsjconf - > personality , logYesNo ( nsjconf - > daemonize ) ,
logYesNo ( nsjconf - > clone_newnet ) , logYesNo ( nsjconf - > clone_newuser ) ,
logYesNo ( nsjconf - > clone_newns ) , logYesNo ( nsjconf - > clone_newpid ) ,
logYesNo ( nsjconf - > clone_newipc ) , logYesNo ( nsjconf - > clone_newuts ) ,
logYesNo ( nsjconf - > clone_newcgroup ) , logYesNo ( nsjconf - > keep_caps ) ,
nsjconf - > tmpfs_size , logYesNo ( nsjconf - > disable_no_new_privs ) ,
logYesNo ( nsjconf - > pivot_root_only ) ) ;
2015-06-18 09:00:39 +08:00
2016-09-25 20:30:19 +08:00
{
struct mounts_t * p ;
TAILQ_FOREACH ( p , & nsjconf - > mountpts , pointers ) {
2017-05-24 20:46:44 +08:00
LOG_I
( " Mount point: src:'%s' dst:'%s' type:'%s' flags:%s options:'%s' isDir:%s " ,
p - > src , p - > dst , p - > fs_type , mountFlagsToStr ( p - > flags ) , p - > options ,
p - > isDir ? " True " : " False " ) ;
2016-09-25 20:30:19 +08:00
}
}
2017-02-08 07:36:32 +08:00
{
struct idmap_t * p ;
TAILQ_FOREACH ( p , & nsjconf - > uids , pointers ) {
2017-05-27 05:07:47 +08:00
LOG_I ( " Uid map: inside_uid:%d outside_uid:%d count:%zu " , p - > inside_id ,
p - > outside_id , p - > count ) ;
2017-05-24 20:32:39 +08:00
if ( p - > outside_id = = 0 ) {
LOG_W ( " Process will be UID/EUID=0 in the global user namespace " ) ;
}
2017-02-08 07:36:32 +08:00
}
TAILQ_FOREACH ( p , & nsjconf - > gids , pointers ) {
2017-05-27 05:07:47 +08:00
LOG_I ( " Gid map: inside_gid:%d outside_gid:%d count:%zu " , p - > inside_id ,
p - > outside_id , p - > count ) ;
2017-05-24 20:32:39 +08:00
if ( p - > outside_id = = 0 ) {
LOG_W ( " Process will be GID/EGID=0 in the global user namespace " ) ;
}
2017-02-08 07:36:32 +08:00
}
}
2016-09-25 20:30:19 +08:00
{
2017-05-27 05:07:47 +08:00
struct idmap_t * p ;
2017-05-27 05:26:07 +08:00
TAILQ_FOREACH ( p , & nsjconf - > newuidmap , pointers ) {
2017-05-27 05:07:47 +08:00
LOG_I ( " Newuid mapping: inside_uid:%u outside_uid:%u count:%zu " ,
2016-10-12 06:59:10 +08:00
p - > inside_id , p - > outside_id , p - > count ) ;
2017-05-24 20:32:39 +08:00
if ( p - > outside_id = = 0 ) {
LOG_W ( " Process will be UID/EUID=0 in the global user namespace " ) ;
}
2016-09-25 20:30:19 +08:00
}
2017-05-27 05:26:07 +08:00
TAILQ_FOREACH ( p , & nsjconf - > newgidmap , pointers ) {
2017-05-27 05:07:47 +08:00
LOG_I ( " Newgid mapping: inside_uid:%u outside_uid:%u count:%zu " ,
2016-10-12 06:59:10 +08:00
p - > inside_id , p - > outside_id , p - > count ) ;
2017-05-24 20:32:39 +08:00
if ( p - > outside_id = = 0 ) {
LOG_W ( " Process will be GID/EGID=0 in the global user namespace " ) ;
}
2016-09-25 20:30:19 +08:00
}
2015-05-15 05:44:48 +08:00
}
}
2016-07-21 21:48:47 +08:00
__rlim64_t cmdlineParseRLimit ( int res , const char * optarg , unsigned long mul )
2015-05-15 05:44:48 +08:00
{
2015-10-14 01:06:49 +08:00
struct rlimit64 cur ;
if ( prlimit64 ( 0 , res , NULL , & cur ) = = - 1 ) {
2015-05-15 05:44:48 +08:00
PLOG_F ( " getrlimit(%d) " , res ) ;
}
if ( strcasecmp ( optarg , " max " ) = = 0 ) {
return cur . rlim_max ;
}
if ( strcasecmp ( optarg , " def " ) = = 0 ) {
return cur . rlim_cur ;
}
2017-05-27 05:07:47 +08:00
if ( utilIsANumber ( optarg ) = = false ) {
2015-08-16 02:10:07 +08:00
LOG_F ( " RLIMIT %d needs a numeric or 'max'/'def' value ('%s' provided) " , res ,
optarg ) ;
2015-05-15 05:44:48 +08:00
}
2016-07-21 21:48:47 +08:00
__rlim64_t val = strtoull ( optarg , NULL , 0 ) * mul ;
2015-10-14 01:06:49 +08:00
if ( val = = ULLONG_MAX & & errno ! = 0 ) {
2015-05-15 05:44:48 +08:00
PLOG_F ( " strtoul('%s', 0) " , optarg ) ;
}
return val ;
}
2015-10-17 22:48:30 +08:00
/* findSpecDestination mutates spec (source:dest) to have a null byte instead
* of ' : ' in between source and dest , then returns a pointer to the dest
* string . */
2016-01-23 14:05:24 +08:00
static char * cmdlineSplitStrByColon ( char * spec )
2015-10-17 22:48:30 +08:00
{
2017-05-27 05:07:47 +08:00
if ( spec = = NULL ) {
return NULL ;
}
2015-10-17 22:48:30 +08:00
char * dest = spec ;
while ( * dest ! = ' : ' & & * dest ! = ' \0 ' ) {
dest + + ;
}
switch ( * dest ) {
case ' : ' :
* dest = ' \0 ' ;
return dest + 1 ;
case ' \0 ' :
2017-05-27 05:07:47 +08:00
return NULL ;
2015-10-17 22:48:30 +08:00
default :
2017-05-27 05:07:47 +08:00
LOG_F ( " Impossible condition in cmdlineSplitStrByColon() " ) ;
return NULL ;
2016-01-23 14:05:24 +08:00
}
}
2015-05-15 05:44:48 +08:00
bool cmdlineParse ( int argc , char * argv [ ] , struct nsjconf_t * nsjconf )
{
2017-05-11 22:17:54 +08:00
/* *INDENT-OFF* */
( * nsjconf ) = ( const struct nsjconf_t ) {
. hostname = " NSJAIL " ,
. cwd = " / " ,
. chroot = NULL ,
. argv = NULL ,
. port = 0 ,
. bindhost = " :: " ,
2017-05-26 20:08:09 +08:00
. logfile = NULL ,
. loglevel = INFO ,
2017-05-11 22:17:54 +08:00
. daemonize = false ,
. tlimit = 0 ,
. pivot_root_only = false ,
. keep_caps = false ,
. disable_no_new_privs = false ,
. rl_as = 512 * ( 1024 * 1024 ) ,
. rl_core = 0 ,
. rl_cpu = 600 ,
. rl_fsize = 1 * ( 1024 * 1024 ) ,
. rl_nofile = 32 ,
. rl_nproc = cmdlineParseRLimit ( RLIMIT_NPROC , " def " , 1 ) ,
. rl_stack = cmdlineParseRLimit ( RLIMIT_STACK , " def " , 1 ) ,
. personality = 0 ,
. clone_newnet = true ,
. clone_newuser = true ,
. clone_newns = true ,
. clone_newpid = true ,
. clone_newipc = true ,
. clone_newuts = true ,
. clone_newcgroup = false ,
. mode = MODE_STANDALONE_ONCE ,
. is_root_rw = false ,
. is_silent = false ,
. skip_setsid = false ,
. max_conns_per_ip = 0 ,
. tmpfs_size = 4 * ( 1024 * 1024 ) ,
. mount_proc = true ,
. cgroup_mem_mount = " /sys/fs/cgroup/memory " ,
. cgroup_mem_parent = " NSJAIL " ,
. cgroup_mem_max = ( size_t ) 0 ,
. cgroup_pids_mount = " /sys/fs/cgroup/pids " ,
. cgroup_pids_parent = " NSJAIL " ,
. cgroup_pids_max = ( size_t ) 0 ,
. iface_no_lo = false ,
. iface = NULL ,
. iface_vs_ip = " 0.0.0.0 " ,
. iface_vs_nm = " 255.255.255.0 " ,
. iface_vs_gw = " 0.0.0.0 " ,
. kafel_file = NULL ,
. kafel_string = NULL ,
} ;
/* *INDENT-ON* */
2015-06-17 22:52:51 +08:00
2016-01-09 23:09:05 +08:00
TAILQ_INIT ( & nsjconf - > pids ) ;
TAILQ_INIT ( & nsjconf - > mountpts ) ;
2016-06-18 06:46:57 +08:00
TAILQ_INIT ( & nsjconf - > open_fds ) ;
2017-05-27 05:07:47 +08:00
TAILQ_INIT ( & nsjconf - > envs ) ;
TAILQ_INIT ( & nsjconf - > uids ) ;
TAILQ_INIT ( & nsjconf - > gids ) ;
2017-05-27 05:26:07 +08:00
TAILQ_INIT ( & nsjconf - > newuidmap ) ;
TAILQ_INIT ( & nsjconf - > newgidmap ) ;
2015-06-18 09:00:39 +08:00
2015-10-17 22:48:30 +08:00
static char cmdlineTmpfsSz [ PATH_MAX ] = " size=4194304 " ;
2015-05-15 05:44:48 +08:00
2016-06-18 06:46:57 +08:00
struct fds_t * f ;
f = utilMalloc ( sizeof ( struct fds_t ) ) ;
f - > fd = STDIN_FILENO ;
TAILQ_INSERT_HEAD ( & nsjconf - > open_fds , f , pointers ) ;
f = utilMalloc ( sizeof ( struct fds_t ) ) ;
f - > fd = STDOUT_FILENO ;
TAILQ_INSERT_HEAD ( & nsjconf - > open_fds , f , pointers ) ;
f = utilMalloc ( sizeof ( struct fds_t ) ) ;
f - > fd = STDERR_FILENO ;
TAILQ_INSERT_HEAD ( & nsjconf - > open_fds , f , pointers ) ;
2017-05-11 22:17:54 +08:00
// Generate options array for getopt_long.
size_t options_length = ARRAYSIZE ( custom_opts ) + ARRAYSIZE ( deprecated_opts ) + 1 ;
struct option opts [ options_length ] ;
2015-05-15 05:44:48 +08:00
for ( unsigned i = 0 ; i < ARRAYSIZE ( custom_opts ) ; i + + ) {
opts [ i ] = custom_opts [ i ] . opt ;
}
2017-05-11 22:17:54 +08:00
for ( unsigned i = 0 ; i < ARRAYSIZE ( deprecated_opts ) ; i + + ) {
opts [ ARRAYSIZE ( custom_opts ) + i ] = deprecated_opts [ i ] . opt ;
}
// Last, NULL option as a terminator.
struct option terminator = { NULL , 0 , NULL , 0 } ;
memcpy ( & opts [ options_length - 1 ] . name , & terminator , sizeof ( terminator ) ) ;
2015-05-15 05:44:48 +08:00
int opt_index = 0 ;
for ( ; ; ) {
2017-05-11 22:17:54 +08:00
int c = getopt_long ( argc , argv ,
2017-05-26 07:44:16 +08:00
" H:D:C:c:p:i:u:g:l:t:M:Ndvqeh?E:R:B:T:P:I:U:G: " , opts ,
2016-01-27 00:42:10 +08:00
& opt_index ) ;
2015-05-15 05:44:48 +08:00
if ( c = = - 1 ) {
break ;
}
switch ( c ) {
case ' H ' :
nsjconf - > hostname = optarg ;
break ;
2015-11-07 20:01:44 +08:00
case ' D ' :
nsjconf - > cwd = optarg ;
break ;
2017-05-26 07:44:16 +08:00
case ' C ' :
if ( configParse ( nsjconf , optarg ) = = false ) {
LOG_F ( " Couldn't parse configuration from '%s' file " , optarg ) ;
}
break ;
2015-05-15 05:44:48 +08:00
case ' c ' :
nsjconf - > chroot = optarg ;
break ;
case ' p ' :
nsjconf - > port = strtoul ( optarg , NULL , 0 ) ;
2016-08-19 03:31:07 +08:00
nsjconf - > mode = MODE_LISTEN_TCP ;
2015-05-15 05:44:48 +08:00
break ;
2016-02-26 01:27:48 +08:00
case 0x604 :
nsjconf - > bindhost = optarg ;
break ;
2015-05-15 05:44:48 +08:00
case ' i ' :
nsjconf - > max_conns_per_ip = strtoul ( optarg , NULL , 0 ) ;
break ;
case ' l ' :
2017-05-26 11:12:01 +08:00
nsjconf - > logfile = optarg ;
2017-05-26 21:22:59 +08:00
if ( logInitLogFile ( nsjconf ) = = false ) {
return false ;
}
2015-05-15 05:44:48 +08:00
break ;
case ' d ' :
nsjconf - > daemonize = true ;
break ;
case ' v ' :
2017-05-26 20:08:09 +08:00
nsjconf - > loglevel = DEBUG ;
2017-05-26 21:22:59 +08:00
if ( logInitLogFile ( nsjconf ) = = false ) {
return false ;
}
2017-02-15 04:54:02 +08:00
break ;
case ' q ' :
2017-05-26 20:08:09 +08:00
nsjconf - > loglevel = WARNING ;
2017-05-26 21:22:59 +08:00
if ( logInitLogFile ( nsjconf ) = = false ) {
return false ;
}
2015-05-15 05:44:48 +08:00
break ;
case ' e ' :
nsjconf - > keep_env = true ;
break ;
case ' t ' :
nsjconf - > tlimit = strtol ( optarg , NULL , 0 ) ;
break ;
case ' h ' : /* help */
2017-05-11 22:17:54 +08:00
cmdlineUsage ( argv [ 0 ] ) ;
2017-02-16 09:22:37 +08:00
exit ( 0 ) ;
2015-05-15 05:44:48 +08:00
break ;
case 0x0201 :
nsjconf - > rl_as = cmdlineParseRLimit ( RLIMIT_AS , optarg , ( 1024 * 1024 ) ) ;
break ;
case 0x0202 :
nsjconf - > rl_core = cmdlineParseRLimit ( RLIMIT_CORE , optarg , ( 1024 * 1024 ) ) ;
break ;
case 0x0203 :
nsjconf - > rl_cpu = cmdlineParseRLimit ( RLIMIT_CPU , optarg , 1 ) ;
break ;
case 0x0204 :
nsjconf - > rl_fsize = cmdlineParseRLimit ( RLIMIT_FSIZE , optarg , ( 1024 * 1024 ) ) ;
break ;
case 0x0205 :
nsjconf - > rl_nofile = cmdlineParseRLimit ( RLIMIT_NOFILE , optarg , 1 ) ;
break ;
case 0x0206 :
nsjconf - > rl_nproc = cmdlineParseRLimit ( RLIMIT_NPROC , optarg , 1 ) ;
break ;
case 0x0207 :
nsjconf - > rl_stack = cmdlineParseRLimit ( RLIMIT_STACK , optarg , ( 1024 * 1024 ) ) ;
break ;
case 0x0301 :
nsjconf - > personality | = ADDR_COMPAT_LAYOUT ;
break ;
case 0x0302 :
nsjconf - > personality | = MMAP_PAGE_ZERO ;
break ;
case 0x0303 :
nsjconf - > personality | = READ_IMPLIES_EXEC ;
break ;
case 0x0304 :
nsjconf - > personality | = ADDR_LIMIT_3GB ;
break ;
case 0x0305 :
nsjconf - > personality | = ADDR_NO_RANDOMIZE ;
break ;
case ' N ' :
nsjconf - > clone_newnet = false ;
break ;
case 0x0402 :
nsjconf - > clone_newuser = false ;
break ;
case 0x0403 :
nsjconf - > clone_newns = false ;
break ;
case 0x0404 :
nsjconf - > clone_newpid = false ;
break ;
case 0x0405 :
nsjconf - > clone_newipc = false ;
break ;
case 0x0406 :
nsjconf - > clone_newuts = false ;
break ;
2016-06-19 17:55:55 +08:00
case 0x0407 :
nsjconf - > clone_newcgroup = true ;
break ;
2015-05-15 05:44:48 +08:00
case 0x0501 :
2015-08-15 22:02:38 +08:00
nsjconf - > keep_caps = true ;
2015-05-15 05:44:48 +08:00
break ;
case 0x0502 :
2015-08-15 22:02:38 +08:00
nsjconf - > is_silent = true ;
2015-05-15 05:44:48 +08:00
break ;
2016-01-26 01:09:32 +08:00
case 0x0504 :
nsjconf - > skip_setsid = true ;
break ;
2017-05-11 22:17:54 +08:00
case 0x0505 : {
2016-06-18 06:46:57 +08:00
struct fds_t * f ;
f = utilMalloc ( sizeof ( struct fds_t ) ) ;
f - > fd = ( int ) strtol ( optarg , NULL , 0 ) ;
TAILQ_INSERT_HEAD ( & nsjconf - > open_fds , f , pointers ) ;
2017-05-11 22:17:54 +08:00
} break ;
2016-09-25 15:30:08 +08:00
case 0x0506 :
nsjconf - > pivot_root_only = true ;
break ;
2016-09-25 21:56:28 +08:00
case 0x0507 :
nsjconf - > disable_no_new_privs = true ;
break ;
2015-08-15 22:02:38 +08:00
case 0x0601 :
2015-05-15 05:44:48 +08:00
nsjconf - > is_root_rw = true ;
break ;
2015-08-15 22:02:38 +08:00
case 0x0602 :
nsjconf - > tmpfs_size = strtoull ( optarg , NULL , 0 ) ;
2015-10-17 22:48:30 +08:00
snprintf ( cmdlineTmpfsSz , sizeof ( cmdlineTmpfsSz ) , " size=%zu " ,
nsjconf - > tmpfs_size ) ;
2015-08-15 22:02:38 +08:00
break ;
case 0x0603 :
nsjconf - > mount_proc = false ;
2015-05-15 05:44:48 +08:00
break ;
2017-05-11 22:17:54 +08:00
case ' E ' : {
2016-02-28 09:34:43 +08:00
struct charptr_t * p = utilMalloc ( sizeof ( struct charptr_t ) ) ;
2016-01-27 00:42:10 +08:00
p - > val = optarg ;
TAILQ_INSERT_TAIL ( & nsjconf - > envs , p , pointers ) ;
2017-05-11 22:17:54 +08:00
} break ;
2017-05-27 05:07:47 +08:00
case ' u ' : {
char * i_id = optarg ;
char * o_id = cmdlineSplitStrByColon ( i_id ) ;
char * cnt = cmdlineSplitStrByColon ( o_id ) ;
size_t count = ( cnt = = NULL
| | strlen ( cnt ) = = 0 ) ? 1U : ( size_t ) strtoull ( cnt ,
NULL ,
0 ) ;
struct idmap_t * p =
userParseId ( i_id , o_id , count , false /* is_gid */ ) ;
if ( p ) {
TAILQ_INSERT_TAIL ( & nsjconf - > uids , p , pointers ) ;
} else {
return false ;
}
}
break ;
case ' g ' : {
char * i_id = optarg ;
char * o_id = cmdlineSplitStrByColon ( i_id ) ;
char * cnt = cmdlineSplitStrByColon ( o_id ) ;
size_t count = ( cnt = = NULL
| | strlen ( cnt ) = = 0 ) ? 1U : ( size_t ) strtoull ( cnt ,
NULL ,
0 ) ;
struct idmap_t * p =
userParseId ( i_id , o_id , count , true /* is_gid */ ) ;
if ( p ) {
TAILQ_INSERT_TAIL ( & nsjconf - > gids , p , pointers ) ;
} else {
return false ;
}
}
break ;
case ' U ' : {
char * i_id = optarg ;
char * o_id = cmdlineSplitStrByColon ( i_id ) ;
char * cnt = cmdlineSplitStrByColon ( o_id ) ;
size_t count = ( cnt = = NULL
| | strlen ( cnt ) = = 0 ) ? 1U : ( size_t ) strtoull ( cnt ,
NULL ,
0 ) ;
struct idmap_t * p =
userParseId ( i_id , o_id , count , false /* is_gid */ ) ;
if ( p ) {
2017-05-27 05:26:07 +08:00
TAILQ_INSERT_TAIL ( & nsjconf - > newuidmap , p , pointers ) ;
2017-05-27 05:07:47 +08:00
} else {
return false ;
}
}
break ;
2017-05-11 22:17:54 +08:00
case ' G ' : {
2017-05-27 05:07:47 +08:00
char * i_id = optarg ;
char * o_id = cmdlineSplitStrByColon ( i_id ) ;
char * cnt = cmdlineSplitStrByColon ( o_id ) ;
size_t count = ( cnt = = NULL
| | strlen ( cnt ) = = 0 ) ? 1U : ( size_t ) strtoull ( cnt ,
NULL ,
0 ) ;
struct idmap_t * p =
userParseId ( i_id , o_id , count , true /* is_gid */ ) ;
if ( p ) {
2017-05-27 05:26:07 +08:00
TAILQ_INSERT_TAIL ( & nsjconf - > newgidmap , p , pointers ) ;
2016-09-25 20:30:19 +08:00
} else {
2017-05-27 05:07:47 +08:00
return false ;
2016-09-25 20:30:19 +08:00
}
}
break ;
2017-05-11 22:17:54 +08:00
case ' R ' : {
2016-02-28 09:34:43 +08:00
struct mounts_t * p = utilMalloc ( sizeof ( struct mounts_t ) ) ;
2015-10-17 22:48:30 +08:00
p - > src = optarg ;
2016-01-23 14:05:24 +08:00
p - > dst = cmdlineSplitStrByColon ( optarg ) ;
2016-03-01 22:36:32 +08:00
p - > flags = MS_BIND | MS_REC | MS_RDONLY ;
2016-03-01 05:22:03 +08:00
p - > options = " " ;
p - > fs_type = " " ;
2017-05-24 20:46:44 +08:00
p - > isDir = mountIsDir ( optarg ) ;
2016-01-09 23:09:05 +08:00
TAILQ_INSERT_TAIL ( & nsjconf - > mountpts , p , pointers ) ;
2017-05-11 22:17:54 +08:00
} break ;
case ' B ' : {
2016-02-28 09:34:43 +08:00
struct mounts_t * p = utilMalloc ( sizeof ( struct mounts_t ) ) ;
2015-10-17 22:48:30 +08:00
p - > src = optarg ;
2016-01-23 14:05:24 +08:00
p - > dst = cmdlineSplitStrByColon ( optarg ) ;
2016-03-01 22:36:32 +08:00
p - > flags = MS_BIND | MS_REC ;
2016-03-01 05:22:03 +08:00
p - > options = " " ;
p - > fs_type = " " ;
2017-05-24 20:46:44 +08:00
p - > isDir = mountIsDir ( optarg ) ;
2016-01-09 23:09:05 +08:00
TAILQ_INSERT_TAIL ( & nsjconf - > mountpts , p , pointers ) ;
2017-05-11 22:17:54 +08:00
} break ;
case ' T ' : {
2016-02-28 09:34:43 +08:00
struct mounts_t * p = utilMalloc ( sizeof ( struct mounts_t ) ) ;
2017-05-24 23:09:24 +08:00
p - > src = " none " ;
2015-10-17 22:48:30 +08:00
p - > dst = optarg ;
p - > flags = 0 ;
p - > options = cmdlineTmpfsSz ;
p - > fs_type = " tmpfs " ;
2017-05-24 20:46:44 +08:00
p - > isDir = true ;
2016-01-09 23:09:05 +08:00
TAILQ_INSERT_TAIL ( & nsjconf - > mountpts , p , pointers ) ;
2017-05-11 22:17:54 +08:00
} break ;
2015-05-15 05:44:48 +08:00
case ' M ' :
switch ( optarg [ 0 ] ) {
case ' l ' :
nsjconf - > mode = MODE_LISTEN_TCP ;
break ;
case ' o ' :
nsjconf - > mode = MODE_STANDALONE_ONCE ;
break ;
2015-08-15 22:02:38 +08:00
case ' e ' :
nsjconf - > mode = MODE_STANDALONE_EXECVE ;
break ;
2015-05-15 05:44:48 +08:00
case ' r ' :
nsjconf - > mode = MODE_STANDALONE_RERUN ;
break ;
default :
LOG_E ( " Modes supported: -M l - MODE_LISTEN_TCP (default) " ) ;
LOG_E ( " -M o - MODE_STANDALONE_ONCE " ) ;
LOG_E ( " -M r - MODE_STANDALONE_RERUN " ) ;
2016-01-17 11:14:09 +08:00
LOG_E ( " -M e - MODE_STANDALONE_EXECVE " ) ;
2017-05-11 22:17:54 +08:00
cmdlineUsage ( argv [ 0 ] ) ;
2015-05-15 05:44:48 +08:00
return false ;
break ;
}
break ;
2016-02-29 07:14:36 +08:00
case 0x700 :
2016-02-29 09:51:55 +08:00
nsjconf - > iface_no_lo = true ;
break ;
2016-02-29 22:36:31 +08:00
case ' I ' :
nsjconf - > iface = optarg ;
break ;
2016-02-29 09:51:55 +08:00
case 0x701 :
nsjconf - > iface_vs_ip = optarg ;
break ;
case 0x702 :
nsjconf - > iface_vs_nm = optarg ;
break ;
case 0x703 :
nsjconf - > iface_vs_gw = optarg ;
2016-02-29 07:14:36 +08:00
break ;
2016-06-19 19:54:36 +08:00
case 0x801 :
2016-06-20 00:12:15 +08:00
nsjconf - > cgroup_mem_max = ( size_t ) strtoull ( optarg , NULL , 0 ) ;
2016-06-19 19:54:36 +08:00
break ;
case 0x802 :
2016-06-20 00:12:15 +08:00
nsjconf - > cgroup_mem_mount = optarg ;
2016-06-19 19:54:36 +08:00
break ;
case 0x803 :
2016-06-20 00:12:15 +08:00
nsjconf - > cgroup_mem_parent = optarg ;
2016-06-19 19:54:36 +08:00
break ;
2017-04-20 23:48:20 +08:00
case 0x811 :
nsjconf - > cgroup_pids_max = ( size_t ) strtoull ( optarg , NULL , 0 ) ;
break ;
case 0x812 :
nsjconf - > cgroup_pids_mount = optarg ;
break ;
case 0x813 :
nsjconf - > cgroup_pids_parent = optarg ;
break ;
2016-10-12 09:52:08 +08:00
case ' P ' :
2016-10-12 09:15:33 +08:00
if ( ( nsjconf - > kafel_file = fopen ( optarg , " r " ) ) = = NULL ) {
PLOG_F ( " Couldn't open '%s' " , optarg ) ;
2016-09-13 18:10:15 +08:00
}
break ;
2016-10-12 09:52:08 +08:00
case 0x0901 :
nsjconf - > kafel_string = optarg ;
break ;
2015-05-15 05:44:48 +08:00
default :
2017-05-11 22:17:54 +08:00
cmdlineUsage ( argv [ 0 ] ) ;
2015-05-15 05:44:48 +08:00
return false ;
break ;
}
}
2015-10-17 22:48:30 +08:00
if ( nsjconf - > mount_proc = = true ) {
2016-02-28 09:34:43 +08:00
struct mounts_t * p = utilMalloc ( sizeof ( struct mounts_t ) ) ;
2017-05-24 23:09:24 +08:00
p - > src = " none " ;
2015-10-17 22:48:30 +08:00
p - > dst = " /proc " ;
p - > flags = 0 ;
2017-04-23 05:54:33 +08:00
if ( nsjconf - > is_root_rw = = false ) {
p - > flags | = MS_RDONLY ;
}
2016-03-01 05:22:03 +08:00
p - > options = " " ;
2015-10-17 22:48:30 +08:00
p - > fs_type = " proc " ;
2017-05-24 20:46:44 +08:00
p - > isDir = true ;
2016-01-09 23:09:05 +08:00
TAILQ_INSERT_HEAD ( & nsjconf - > mountpts , p , pointers ) ;
2015-10-17 22:48:30 +08:00
}
2016-08-17 04:07:44 +08:00
if ( nsjconf - > chroot ! = NULL ) {
2016-02-28 09:34:43 +08:00
struct mounts_t * p = utilMalloc ( sizeof ( struct mounts_t ) ) ;
2015-10-17 22:48:30 +08:00
p - > src = nsjconf - > chroot ;
p - > dst = " / " ;
2016-03-01 22:36:32 +08:00
p - > flags = MS_BIND | MS_REC ;
2016-03-01 05:22:03 +08:00
p - > options = " " ;
p - > fs_type = " " ;
2017-05-24 20:46:44 +08:00
p - > isDir = true ;
2015-10-17 22:48:30 +08:00
if ( nsjconf - > is_root_rw = = false ) {
p - > flags | = MS_RDONLY ;
}
2016-01-09 23:09:05 +08:00
TAILQ_INSERT_HEAD ( & nsjconf - > mountpts , p , pointers ) ;
2016-06-19 07:35:06 +08:00
} else {
struct mounts_t * p = utilMalloc ( sizeof ( struct mounts_t ) ) ;
2017-05-24 23:09:24 +08:00
p - > src = " none " ;
2016-06-19 07:35:06 +08:00
p - > dst = " / " ;
p - > flags = 0 ;
p - > options = " " ;
p - > fs_type = " tmpfs " ;
2017-05-24 20:46:44 +08:00
p - > isDir = true ;
2016-06-19 07:35:06 +08:00
if ( nsjconf - > is_root_rw = = false ) {
p - > flags | = MS_RDONLY ;
}
TAILQ_INSERT_HEAD ( & nsjconf - > mountpts , p , pointers ) ;
2015-10-17 22:48:30 +08:00
}
2017-02-08 07:36:32 +08:00
if ( TAILQ_EMPTY ( & nsjconf - > uids ) ) {
struct idmap_t * p = utilMalloc ( sizeof ( struct idmap_t ) ) ;
p - > inside_id = getuid ( ) ;
p - > outside_id = getuid ( ) ;
2017-05-27 05:07:47 +08:00
p - > count = 1U ;
2017-02-08 07:36:32 +08:00
TAILQ_INSERT_HEAD ( & nsjconf - > uids , p , pointers ) ;
}
if ( TAILQ_EMPTY ( & nsjconf - > gids ) ) {
struct idmap_t * p = utilMalloc ( sizeof ( struct idmap_t ) ) ;
p - > inside_id = getgid ( ) ;
p - > outside_id = getgid ( ) ;
2017-05-27 05:07:47 +08:00
p - > count = 1U ;
2017-02-08 07:36:32 +08:00
TAILQ_INSERT_HEAD ( & nsjconf - > gids , p , pointers ) ;
}
2016-10-18 00:17:08 +08:00
2017-05-26 20:08:09 +08:00
if ( logInitLogFile ( nsjconf ) = = false ) {
2015-05-15 05:44:48 +08:00
return false ;
}
2017-05-27 08:24:41 +08:00
if ( argv [ optind ] ) {
nsjconf - > argv = & argv [ optind ] ;
}
if ( nsjconf - > argv = = NULL | | nsjconf - > argv [ 0 ] = = NULL ) {
2016-01-23 14:05:24 +08:00
LOG_E ( " No command provided " ) ;
2017-05-11 22:17:54 +08:00
cmdlineUsage ( argv [ 0 ] ) ;
2015-05-15 05:44:48 +08:00
return false ;
}
return true ;
}