git-svn-id: file:///svn/unbound/trunk@1212 be551aaa-1e26-0410-a405-d3ace91eadb9
This commit is contained in:
Wouter Wijngaards 2008-08-27 11:29:46 +00:00
parent f809bfa145
commit e474ca2619
7 changed files with 249 additions and 123 deletions

View file

@ -261,23 +261,21 @@ writepid (const char* pidfile, pid_t pid)
/** /**
* check old pid file. * check old pid file.
* @param cfg: the config settings * @param pidfile: the file name of the pid file.
* @param inchroot: if pidfile is inchroot and we can thus expect to
* be able to delete it.
*/ */
static void static void
checkoldpid(struct config_file* cfg) checkoldpid(char* pidfile, int inchroot)
{ {
pid_t old; pid_t old;
char* file = cfg->pidfile; if((old = readpid(pidfile)) != -1) {
if(cfg->chrootdir && cfg->chrootdir[0] &&
strncmp(file, cfg->chrootdir, strlen(cfg->chrootdir))==0) {
file += strlen(cfg->chrootdir);
}
if((old = readpid(file)) != -1) {
/* see if it is still alive */ /* see if it is still alive */
if(kill(old, 0) == 0 || errno == EPERM) if(kill(old, 0) == 0 || errno == EPERM)
log_warn("unbound is already running as pid %u.", log_warn("unbound is already running as pid %u.",
(unsigned)old); (unsigned)old);
else log_warn("did not exit gracefully last time (%u)", else if(inchroot)
log_warn("did not exit gracefully last time (%u)",
(unsigned)old); (unsigned)old);
} }
} }
@ -285,18 +283,16 @@ checkoldpid(struct config_file* cfg)
/** detach from command line */ /** detach from command line */
static void static void
detach(struct config_file* cfg) detach(void)
{ {
#ifdef HAVE_WORKING_FORK #ifdef HAVE_WORKING_FORK
int fd, err; int fd;
/* Take off... */ /* Take off... */
switch (fork()) { switch (fork()) {
case 0: case 0:
break; break;
case -1: case -1:
err=errno; fatal_exit("fork failed: %s", strerror(errno));
unlink(cfg->pidfile);
fatal_exit("fork failed: %s", strerror(err));
default: default:
/* exit interactive session */ /* exit interactive session */
exit(0); exit(0);
@ -313,14 +309,12 @@ detach(struct config_file* cfg)
if (fd > 2) if (fd > 2)
(void)close(fd); (void)close(fd);
} }
#else
(void)cfg;
#endif /* HAVE_WORKING_FORK */ #endif /* HAVE_WORKING_FORK */
} }
/** daemonize, drop user priviliges and chroot if needed */ /** daemonize, drop user priviliges and chroot if needed */
static void static void
do_chroot(struct daemon* daemon, struct config_file* cfg, int debug_mode, perform_setup(struct daemon* daemon, struct config_file* cfg, int debug_mode,
char** cfgfile) char** cfgfile)
{ {
#ifdef HAVE_GETPWNAM #ifdef HAVE_GETPWNAM
@ -331,7 +325,6 @@ do_chroot(struct daemon* daemon, struct config_file* cfg, int debug_mode,
memset(&gid, 112, sizeof(gid)); memset(&gid, 112, sizeof(gid));
log_assert(cfg); log_assert(cfg);
/* daemonize last to be able to print error to user */
if(cfg->username && cfg->username[0]) { if(cfg->username && cfg->username[0]) {
struct passwd *pwd; struct passwd *pwd;
if((pwd = getpwnam(cfg->username)) == NULL) if((pwd = getpwnam(cfg->username)) == NULL)
@ -341,6 +334,69 @@ do_chroot(struct daemon* daemon, struct config_file* cfg, int debug_mode,
endpwent(); endpwent();
} }
#endif #endif
/* init syslog (as root) if needed, before daemonize, otherwise
* a fork error could not be printed since daemonize closed stderr.*/
if(cfg->use_syslog)
log_init(cfg->logfile, cfg->use_syslog, cfg->chrootdir);
else if(cfg->logfile && cfg->logfile[0]) {
/* open logfile temporary with root permissions, to log
* errors happening after daemonizing, that closes stderr.
* After all that we reopen the logfile with less permits. */
char* lf = fname_after_chroot(cfg->logfile, cfg, 1);
if(!lf) fatal_exit("logfile malloc: out of memory");
log_init(lf, 0, NULL);
free(lf);
}
#ifdef HAVE_KILL
/* check old pid file before forking */
if(cfg->pidfile && cfg->pidfile[0]) {
/* calculate position of pidfile */
if(cfg->pidfile[0] == '/')
daemon->pidfile = strdup(cfg->pidfile);
else daemon->pidfile = fname_after_chroot(cfg->pidfile,
cfg, 1);
if(!daemon->pidfile)
fatal_exit("pidfile alloc: out of memory");
checkoldpid(daemon->pidfile,
/* true if pidfile is inside chrootdir, or nochroot */
!(cfg->chrootdir && cfg->chrootdir[0]) ||
(cfg->chrootdir && cfg->chrootdir[0] &&
strncmp(daemon->pidfile, cfg->chrootdir,
strlen(cfg->chrootdir))==0));
}
#endif
/* daemonize because pid is needed by the writepid func */
if(!debug_mode && cfg->do_daemonize) {
detach();
}
/* write new pidfile (while still root, so can be outside chroot) */
#ifdef HAVE_KILL
if(cfg->pidfile && cfg->pidfile[0]) {
writepid(daemon->pidfile, getpid());
if(!(cfg->chrootdir && cfg->chrootdir[0]) ||
(cfg->chrootdir && cfg->chrootdir[0] &&
strncmp(daemon->pidfile, cfg->chrootdir,
strlen(cfg->chrootdir))==0)) {
/* delete of pidfile could potentially work,
* chown to get permissions */
if(cfg->username && cfg->username[0]) {
if(chown(daemon->pidfile, uid, gid) == -1) {
fatal_exit("cannot chown %u.%u %s: %s",
(unsigned)uid, (unsigned)gid,
daemon->pidfile, strerror(errno));
}
}
}
}
#else
(void)daemon;
#endif
/* box into the chroot */
#ifdef HAVE_CHROOT #ifdef HAVE_CHROOT
if(cfg->chrootdir && cfg->chrootdir[0]) { if(cfg->chrootdir && cfg->chrootdir[0]) {
if(chdir(cfg->chrootdir)) { if(chdir(cfg->chrootdir)) {
@ -359,6 +415,7 @@ do_chroot(struct daemon* daemon, struct config_file* cfg, int debug_mode,
#else #else
(void)cfgfile; (void)cfgfile;
#endif #endif
/* change to working directory inside chroot */
if(cfg->directory && cfg->directory[0]) { if(cfg->directory && cfg->directory[0]) {
char* dir = cfg->directory; char* dir = cfg->directory;
if(cfg->chrootdir && cfg->chrootdir[0] && if(cfg->chrootdir && cfg->chrootdir[0] &&
@ -373,6 +430,8 @@ do_chroot(struct daemon* daemon, struct config_file* cfg, int debug_mode,
verbose(VERB_QUERY, "chdir to %s", dir); verbose(VERB_QUERY, "chdir to %s", dir);
} }
} }
/* drop permissions after chroot, getpwnam, pidfile, syslog done*/
#ifdef HAVE_GETPWNAM #ifdef HAVE_GETPWNAM
if(cfg->username && cfg->username[0]) { if(cfg->username && cfg->username[0]) {
#ifdef HAVE_SETRESGID #ifdef HAVE_SETRESGID
@ -397,31 +456,10 @@ do_chroot(struct daemon* daemon, struct config_file* cfg, int debug_mode,
cfg->username); cfg->username);
} }
#endif #endif
#ifdef HAVE_KILL /* file logging inited after chroot,chdir,setuid is done so that
/* check old pid file before forking */ * it would succeed on SIGHUP as well */
if(cfg->pidfile && cfg->pidfile[0]) { if(!cfg->use_syslog)
checkoldpid(cfg); log_init(cfg->logfile, cfg->use_syslog, cfg->chrootdir);
}
#endif
/* init logfile just before fork */
log_init(cfg->logfile, cfg->use_syslog, cfg->chrootdir);
if(!debug_mode && cfg->do_daemonize) {
detach(cfg);
}
#ifdef HAVE_KILL
if(cfg->pidfile && cfg->pidfile[0]) {
char* pf = cfg->pidfile;
if(cfg->chrootdir && cfg->chrootdir[0] &&
strncmp(pf, cfg->chrootdir, strlen(cfg->chrootdir))==0)
pf += strlen(cfg->chrootdir);
writepid(pf, getpid());
if(!(daemon->pidfile = strdup(pf)))
log_err("pidf: malloc failed");
}
#else
(void)daemon;
#endif
} }
/** /**
@ -436,12 +474,12 @@ run_daemon(char* cfgfile, int cmdline_verbose, int debug_mode)
{ {
struct config_file* cfg = NULL; struct config_file* cfg = NULL;
struct daemon* daemon = NULL; struct daemon* daemon = NULL;
int done_chroot = 0; int done_setup = 0;
if(!(daemon = daemon_init())) if(!(daemon = daemon_init()))
fatal_exit("alloc failure"); fatal_exit("alloc failure");
while(!daemon->need_to_exit) { while(!daemon->need_to_exit) {
if(done_chroot) if(done_setup)
verbose(VERB_OPS, "Restart of %s.", PACKAGE_STRING); verbose(VERB_OPS, "Restart of %s.", PACKAGE_STRING);
else verbose(VERB_OPS, "Start of %s.", PACKAGE_STRING); else verbose(VERB_OPS, "Start of %s.", PACKAGE_STRING);
@ -459,9 +497,9 @@ run_daemon(char* cfgfile, int cmdline_verbose, int debug_mode)
/* prepare */ /* prepare */
if(!daemon_open_shared_ports(daemon)) if(!daemon_open_shared_ports(daemon))
fatal_exit("could not open ports"); fatal_exit("could not open ports");
if(!done_chroot) { if(!done_setup) {
do_chroot(daemon, cfg, debug_mode, &cfgfile); perform_setup(daemon, cfg, debug_mode, &cfgfile);
done_chroot = 1; done_setup = 1;
} else log_init(cfg->logfile, cfg->use_syslog, cfg->chrootdir); } else log_init(cfg->logfile, cfg->use_syslog, cfg->chrootdir);
/* work */ /* work */
daemon_fork(daemon); daemon_fork(daemon);
@ -472,8 +510,21 @@ run_daemon(char* cfgfile, int cmdline_verbose, int debug_mode)
config_delete(cfg); config_delete(cfg);
} }
verbose(VERB_ALGO, "Exit cleanup."); verbose(VERB_ALGO, "Exit cleanup.");
if(daemon->pidfile) /* this unlink may not work if the pidfile is located outside
unlink(daemon->pidfile); * of the chroot/workdir or we no longer have permissions */
if(daemon->pidfile) {
int fd;
char* pf = daemon->pidfile;
if(cfg->chrootdir && cfg->chrootdir[0] &&
strncmp(pf, cfg->chrootdir, strlen(cfg->chrootdir)==0))
pf += strlen(cfg->chrootdir);
/* truncate pidfile */
fd = open(pf, O_WRONLY | O_TRUNC, 0644);
if(fd != -1)
close(fd);
/* delete pidfile */
unlink(pf);
}
daemon_delete(daemon); daemon_delete(daemon);
} }

View file

@ -1,6 +1,8 @@
27 August 2008: Wouter 27 August 2008: Wouter
- daemon(3) is causing problems for people. Reverting the patch. - daemon(3) is causing problems for people. Reverting the patch.
bug#200, and 199 and 203 contain sideline discussion on it. bug#200, and 199 and 203 contain sideline discussion on it.
- bug#199: pidfile can be outside chroot. openlog is done before
chroot and drop permissions.
26 August 2008: Wouter 26 August 2008: Wouter
- test for insecure zone when DLV is in use, also does negative cache. - test for insecure zone when DLV is in use, also does negative cache.

View file

@ -150,15 +150,17 @@ server:
# chroot has been performed the now defunct portion of the config # chroot has been performed the now defunct portion of the config
# file path is removed to be able to reread the config after a reload. # file path is removed to be able to reread the config after a reload.
# #
# All other file paths (working dir, pidfile, logfile, roothints, # All other file paths (working dir, logfile, roothints, and
# key files) can be specified in several ways: # key files) can be specified in several ways:
# o as an absolute path relative to the new root. # o as an absolute path relative to the new root.
# o as a relative path to the working directory. # o as a relative path to the working directory.
# o as an absolute path relative to the original root. # o as an absolute path relative to the original root.
# In the last case the path is adjusted to remove the unused portion. # In the last case the path is adjusted to remove the unused portion.
# #
# Additionally, unbound may need to access /dev/random (for entropy) # The pid file can be absolute and outside of the chroot, it is
# and to /dev/log (if you use syslog) from inside the chroot. # written just prior to performing the chroot and dropping permissions.
#
# Additionally, unbound may need to access /dev/random (for entropy).
# How to do this is specific to your OS. # How to do this is specific to your OS.
# #
# If you give "" no chroot is performed. The path must not end in a /. # If you give "" no chroot is performed. The path must not end in a /.
@ -182,7 +184,7 @@ server:
# log to, with identity "unbound". If yes, it overrides the logfile. # log to, with identity "unbound". If yes, it overrides the logfile.
# use-syslog: yes # use-syslog: yes
# the pid file. # the pid file. Can be an absolute path outside of chroot/work dir.
# pidfile: "@UNBOUND_PIDFILE@" # pidfile: "@UNBOUND_PIDFILE@"
# file to read root hints from. # file to read root hints from.

View file

@ -49,7 +49,7 @@ example.conf file with all the options.
# unbound.conf(5) config file for unbound(8). # unbound.conf(5) config file for unbound(8).
server: server:
directory: "/etc/unbound" directory: "/etc/unbound"
username: unbound # make sure it can write to pidfile. username: unbound
# make sure unbound can access entropy from inside the chroot. # make sure unbound can access entropy from inside the chroot.
# e.g. on linux the use these commands (on BSD, devfs(8) is used): # e.g. on linux the use these commands (on BSD, devfs(8) is used):
# mount --bind -n /dev/random /etc/unbound/dev/random # mount --bind -n /dev/random /etc/unbound/dev/random
@ -253,15 +253,20 @@ commandline) as a full path from the original root. After the
chroot has been performed the now defunct portion of the config chroot has been performed the now defunct portion of the config
file path is removed to be able to reread the config after a reload. file path is removed to be able to reread the config after a reload.
.IP .IP
All other file paths (working dir, pidfile, logfile, roothints, All other file paths (working dir, logfile, roothints, and
key files) can be specified in several ways: key files) can be specified in several ways:
as an absolute path relative to the new root, as an absolute path relative to the new root,
as a relative path to the working directory, or as a relative path to the working directory, or
as an absolute path relative to the original root. as an absolute path relative to the original root.
In the last case the path is adjusted to remove the unused portion. In the last case the path is adjusted to remove the unused portion.
.IP .IP
The pidfile can be either a relative path to the working directory, or
an absolute path relative to the original root. It is written just prior
to chroot and dropping permissions. This allows the pidfile to be
/var/run/unbound.pid and the chroot to be /var/unbound, for example.
.IP
Additionally, unbound may need to access /dev/random (for entropy) Additionally, unbound may need to access /dev/random (for entropy)
and to /dev/log (if you use syslog) from inside the chroot. from inside the chroot.
.IP .IP
If given a chroot is done to the given directory. The default is If given a chroot is done to the given directory. The default is
"@UNBOUND_CHROOT_DIR@". If you give "" no chroot is performed. "@UNBOUND_CHROOT_DIR@". If you give "" no chroot is performed.

View file

@ -216,64 +216,17 @@ is_dir(const char* fname)
return 1; return 1;
} }
/** convert a filename to full pathname in original filesys
* @param fname: the path name to convert.
* Must not be null or empty.
* @param cfg: config struct for chroot and chdir (if set).
* @param use_chdir: if false, only chroot is applied.
* @return pointer to static buffer which is: [chroot][chdir]fname
*/
static char*
fname_after_chroot(const char* fname, struct config_file* cfg, int use_chdir)
{
static char buf[1024];
int slashit = 0;
buf[0] = 0;
if(cfg->chrootdir && cfg->chrootdir[0] &&
strncmp(cfg->chrootdir, fname, strlen(cfg->chrootdir)) == 0) {
/* already full pathname, return it */
strncpy(buf, fname, sizeof(buf)-1);
buf[sizeof(buf)-1] = 0;
return buf;
}
/* chroot */
if(cfg->chrootdir && cfg->chrootdir[0]) {
/* start with chrootdir */
strncpy(buf, cfg->chrootdir, sizeof(buf)-1);
slashit = 1;
}
/* chdir */
if(fname[0] == '/' || !use_chdir) {
/* full path, no chdir */
} else if(cfg->directory && cfg->directory[0]) {
/* prepend chdir */
if(slashit && cfg->directory[0] != '/')
strncat(buf, "/", sizeof(buf)-strlen(buf)-1);
if(strncmp(cfg->chrootdir, cfg->directory,
strlen(cfg->chrootdir)) == 0)
strncat(buf, cfg->directory+strlen(cfg->chrootdir),
sizeof(buf)-strlen(buf)-1);
else strncat(buf, cfg->directory, sizeof(buf)-strlen(buf)-1);
slashit = 1;
}
/* fname */
if(slashit && fname[0] != '/')
strncat(buf, "/", sizeof(buf)-strlen(buf)-1);
strncat(buf, fname, sizeof(buf)-strlen(buf)-1);
buf[sizeof(buf)-1] = 0;
return buf;
}
/** get base dir of a fname */ /** get base dir of a fname */
static char* static char*
basedir(const char* fname, struct config_file* cfg) basedir(char* fname)
{ {
char* d = fname_after_chroot(fname, cfg, 1); char* rev;
char* rev = strrchr(d, '/'); if(!fname) fatal_exit("out of memory");
rev = strrchr(fname, '/');
if(!rev) return NULL; if(!rev) return NULL;
if(d == rev) return NULL; if(fname == rev) return NULL;
rev[0] = 0; rev[0] = 0;
return d; return fname;
} }
/** check chroot for a file string */ /** check chroot for a file string */
@ -283,12 +236,13 @@ check_chroot_string(const char* desc, char** ss,
{ {
char* str = *ss; char* str = *ss;
if(str && str[0]) { if(str && str[0]) {
if(!is_file(fname_after_chroot(str, cfg, 1))) { *ss = fname_after_chroot(str, cfg, 1);
if(!*ss) fatal_exit("out of memory");
if(!is_file(*ss)) {
fatal_exit("%s: \"%s\" does not exist in chrootdir %s", fatal_exit("%s: \"%s\" does not exist in chrootdir %s",
desc, str, chrootdir); desc, str, chrootdir);
} }
/* put in a new full path for continued checking */ /* put in a new full path for continued checking */
*ss = strdup(fname_after_chroot(str, cfg, 1));
free(str); free(str);
} }
} }
@ -343,21 +297,28 @@ morechecks(struct config_file* cfg, char* fname)
fatal_exit("config file %s is not inside chroot %s", fatal_exit("config file %s is not inside chroot %s",
buf, cfg->chrootdir); buf, cfg->chrootdir);
} }
if(cfg->directory && cfg->directory[0] && !is_dir( if(cfg->directory && cfg->directory[0]) {
fname_after_chroot(cfg->directory, cfg, 0))) { char* ad = fname_after_chroot(cfg->directory, cfg, 0);
fatal_exit("bad chdir directory"); if(!ad) fatal_exit("out of memory");
if(!is_dir(ad)) fatal_exit("bad chdir directory");
free(ad);
} }
if( (cfg->chrootdir && cfg->chrootdir[0]) || if( (cfg->chrootdir && cfg->chrootdir[0]) ||
(cfg->directory && cfg->directory[0])) { (cfg->directory && cfg->directory[0])) {
if(cfg->pidfile && cfg->pidfile[0] && if(cfg->pidfile && cfg->pidfile[0]) {
basedir(cfg->pidfile, cfg) && char* ad = (cfg->pidfile[0]=='/')?strdup(cfg->pidfile):
!is_dir(basedir(cfg->pidfile, cfg))) { fname_after_chroot(cfg->pidfile, cfg, 1);
fatal_exit("pidfile directory does not exist"); char* bd = basedir(ad);
if(bd && !is_dir(bd))
fatal_exit("pidfile directory does not exist");
free(ad);
} }
if(cfg->logfile && cfg->logfile[0] && if(cfg->logfile && cfg->logfile[0]) {
basedir(cfg->logfile, cfg) && char* ad = fname_after_chroot(cfg->logfile, cfg, 1);
!is_dir(basedir(cfg->logfile, cfg))) { char* bd = basedir(ad);
fatal_exit("logfile directory does not exist"); if(bd && !is_dir(bd))
fatal_exit("logfile directory does not exist");
free(ad);
} }
} }

View file

@ -739,3 +739,96 @@ config_apply(struct config_file* config)
MAX_TTL = (uint32_t)config->max_ttl; MAX_TTL = (uint32_t)config->max_ttl;
} }
/**
* Calculate string length of full pathname in original filesys
* @param fname: the path name to convert.
* Must not be null or empty.
* @param cfg: config struct for chroot and chdir (if set).
* @param use_chdir: if false, only chroot is applied.
* @return length of string.
* remember to allocate one more for 0 at end in mallocs.
*/
static size_t
strlen_after_chroot(const char* fname, struct config_file* cfg, int use_chdir)
{
size_t len = 0;
int slashit = 0;
if(cfg->chrootdir && cfg->chrootdir[0] &&
strncmp(cfg->chrootdir, fname, strlen(cfg->chrootdir)) == 0) {
/* already full pathname, return it */
return strlen(fname);
}
/* chroot */
if(cfg->chrootdir && cfg->chrootdir[0]) {
/* start with chrootdir */
len += strlen(cfg->chrootdir);
slashit = 1;
}
/* chdir */
if(fname[0] == '/' || !use_chdir) {
/* full path, no chdir */
} else if(cfg->directory && cfg->directory[0]) {
/* prepend chdir */
if(slashit && cfg->directory[0] != '/')
len++;
if(cfg->chrootdir && cfg->chrootdir[0] &&
strncmp(cfg->chrootdir, cfg->directory,
strlen(cfg->chrootdir)) == 0)
len += strlen(cfg->directory)-strlen(cfg->chrootdir);
else len += strlen(cfg->directory);
slashit = 1;
}
/* fname */
if(slashit && fname[0] != '/')
len++;
len += strlen(fname);
return len;
}
char*
fname_after_chroot(const char* fname, struct config_file* cfg, int use_chdir)
{
size_t len = strlen_after_chroot(fname, cfg, use_chdir);
int slashit = 0;
char* buf = (char*)malloc(len+1);
if(!buf)
return NULL;
buf[0] = 0;
/* is fname already in chroot ? */
if(cfg->chrootdir && cfg->chrootdir[0] &&
strncmp(cfg->chrootdir, fname, strlen(cfg->chrootdir)) == 0) {
/* already full pathname, return it */
strncpy(buf, fname, len);
buf[len] = 0;
return buf;
}
/* chroot */
if(cfg->chrootdir && cfg->chrootdir[0]) {
/* start with chrootdir */
strncpy(buf, cfg->chrootdir, len);
slashit = 1;
}
/* chdir */
if(fname[0] == '/' || !use_chdir) {
/* full path, no chdir */
} else if(cfg->directory && cfg->directory[0]) {
/* prepend chdir */
if(slashit && cfg->directory[0] != '/')
strncat(buf, "/", len-strlen(buf));
/* is the directory already in the chroot? */
if(cfg->chrootdir && cfg->chrootdir[0] &&
strncmp(cfg->chrootdir, cfg->directory,
strlen(cfg->chrootdir)) == 0)
strncat(buf, cfg->directory+strlen(cfg->chrootdir),
len-strlen(buf));
else strncat(buf, cfg->directory, len-strlen(buf));
slashit = 1;
}
/* fname */
if(slashit && fname[0] != '/')
strncat(buf, "/", len-strlen(buf));
strncat(buf, fname, len-strlen(buf));
buf[len] = 0;
return buf;
}

View file

@ -384,6 +384,18 @@ int cfg_condense_ports(struct config_file* cfg, int** avail);
*/ */
int cfg_scan_ports(int* avail, int num); int cfg_scan_ports(int* avail, int num);
/**
* Convert a filename to full pathname in original filesys
* @param fname: the path name to convert.
* Must not be null or empty.
* @param cfg: config struct for chroot and chdir (if set).
* @param use_chdir: if false, only chroot is applied.
* @return pointer to malloced buffer which is: [chroot][chdir]fname
* or NULL on malloc failure.
*/
char* fname_after_chroot(const char* fname, struct config_file* cfg,
int use_chdir);
/** /**
* Used during options parsing * Used during options parsing
*/ */