diff --git a/exec.go b/exec.go index 3649bdc1bbd..66ee3653c58 100644 --- a/exec.go +++ b/exec.go @@ -132,11 +132,6 @@ func execProcess(context *cli.Context) (int, error) { return -1, err } - logLevel := "info" - if context.GlobalBool("debug") { - logLevel = "debug" - } - r := &runner{ enableSubreaper: false, shouldDestroy: false, @@ -147,7 +142,6 @@ func execProcess(context *cli.Context) (int, error) { action: CT_ACT_RUN, init: false, preserveFDs: context.Int("preserve-fds"), - logLevel: logLevel, } return r.run(p) } diff --git a/init.go b/init.go index 61026e220e4..bddc237f6e5 100644 --- a/init.go +++ b/init.go @@ -17,7 +17,7 @@ func init() { runtime.GOMAXPROCS(1) runtime.LockOSThread() - logLevel, err := logrus.ParseLevel(os.Getenv("_LIBCONTAINER_LOGLEVEL")) + level, err := strconv.Atoi(os.Getenv("_LIBCONTAINER_LOGLEVEL")) if err != nil { panic(err) } @@ -27,7 +27,7 @@ func init() { panic(err) } - logrus.SetLevel(logLevel) + logrus.SetLevel(logrus.Level(level)) logrus.SetOutput(os.NewFile(uintptr(logPipeFd), "logpipe")) logrus.SetFormatter(new(logrus.JSONFormatter)) logrus.Debug("child process in init()") diff --git a/libcontainer/nsenter/nsexec.c b/libcontainer/nsenter/nsexec.c index 30b6d5e4ad3..7e79ca0eb24 100644 --- a/libcontainer/nsenter/nsexec.c +++ b/libcontainer/nsenter/nsexec.c @@ -89,14 +89,21 @@ struct nlconfig_t { size_t gidmappath_len; }; -#define PANIC "panic" -#define FATAL "fatal" -#define ERROR "error" -#define WARNING "warning" -#define INFO "info" -#define DEBUG "debug" +/* + * Log levels are the same as in logrus. + */ +#define PANIC 0 +#define FATAL 1 +#define ERROR 2 +#define WARNING 3 +#define INFO 4 +#define DEBUG 5 +#define TRACE 6 + +static const char *level_str[] = { "panic", "fatal", "error", "warning", "info", "debug", "trace" }; static int logfd = -1; +static int loglevel = DEBUG; /* * List of netlink message types sent to us as part of bootstrapping the init. @@ -134,13 +141,13 @@ int setns(int fd, int nstype) } #endif -static void write_log(const char *level, const char *format, ...) +static void write_log(int level, const char *format, ...) { char *message = NULL, *stage = NULL, *json = NULL; va_list args; int ret; - if (logfd < 0 || level == NULL) + if (logfd < 0 || level > loglevel) goto out; va_start(args, format); @@ -162,7 +169,8 @@ static void write_log(const char *level, const char *format, ...) goto out; } - ret = asprintf(&json, "{\"level\":\"%s\", \"msg\": \"%s[%d]: %s\"}\n", level, stage, getpid(), message); + ret = asprintf(&json, "{\"level\":\"%s\", \"msg\": \"%s[%d]: %s\"}\n", + level_str[level], stage, getpid(), message); if (ret < 0) { json = NULL; goto out; @@ -182,10 +190,14 @@ static void write_log(const char *level, const char *format, ...) /* XXX: This is ugly. */ static int syncfd = -1; -#define bail(fmt, ...) \ - do { \ - write_log(FATAL, fmt ": %m", ##__VA_ARGS__); \ - exit(1); \ +#define bail(fmt, ...) \ + do { \ + if (logfd < 0) \ + fprintf(stderr, "FATAL: " fmt ": %m\n", \ + ##__VA_ARGS__); \ + else \ + write_log(FATAL, fmt ": %m", ##__VA_ARGS__); \ + exit(1); \ } while(0) static int write_file(char *data, size_t data_len, char *pathfmt, ...) @@ -376,41 +388,55 @@ static int clone_parent(jmp_buf *env, int jmpval) } /* - * Gets the init pipe fd from the environment, which is used to read the - * bootstrap data and tell the parent what the new pid is after we finish - * setting up the environment. + * Returns an environment variable value as a non-negative integer, or -ENOENT + * if the variable was not found or has an empty value. + * + * If the value can not be converted to an integer, or the result is out of + * range, the function bails out. */ -static int initpipe(void) +static int getenv_int(const char *name) { - int pipenum; - char *initpipe, *endptr; + char *val, *endptr; + int ret; - initpipe = getenv("_LIBCONTAINER_INITPIPE"); - if (initpipe == NULL || *initpipe == '\0') - return -1; + val = getenv(name); + /* Treat empty value as unset variable. */ + if (val == NULL || *val == '\0') + return -ENOENT; - pipenum = strtol(initpipe, &endptr, 10); - if (*endptr != '\0') - bail("unable to parse _LIBCONTAINER_INITPIPE"); + ret = strtol(val, &endptr, 10); + if (val == endptr || *endptr != '\0') + bail("unable to parse %s=%s", name, val); + /* + * Sanity check: this must be a small non-negative number. + * Practically, we pass two fds (3 and 4) and a log level, + * for which the maximum is 6 (TRACE). + * */ + if (ret < 0 || ret > TRACE) + bail("bad value for %s=%s (%d)", name, val, ret); - return pipenum; + return ret; } +/* + * Sets up logging by getting log fd and log level from the environment, + * if available. + */ static void setup_logpipe(void) { - char *logpipe, *endptr; + int i; - logpipe = getenv("_LIBCONTAINER_LOGPIPE"); - if (logpipe == NULL || *logpipe == '\0') { + i = getenv_int("_LIBCONTAINER_LOGPIPE"); + if (i < 0) { + /* We are not runc init, or log pipe was not provided. */ return; } + logfd = i; - logfd = strtol(logpipe, &endptr, 10); - if (logpipe == endptr || *endptr != '\0') { - fprintf(stderr, "unable to parse _LIBCONTAINER_LOGPIPE, value: %s\n", logpipe); - /* It is too early to use bail */ - exit(1); - } + i = getenv_int("_LIBCONTAINER_LOGLEVEL"); + if (i < 0) + return; + loglevel = i; } /* Returns the clone(2) flag for a namespace, given the name of a namespace. */ @@ -621,12 +647,15 @@ void nsexec(void) setup_logpipe(); /* - * If we don't have an init pipe, just return to the go routine. - * We'll only get an init pipe for start or exec. + * Get the init pipe fd from the environment. The init pipe is used to + * read the bootstrap data and tell the parent what the new pids are + * after the setup is done. */ - pipenum = initpipe(); - if (pipenum == -1) + pipenum = getenv_int("_LIBCONTAINER_INITPIPE"); + if (pipenum < 0) { + /* We are not a runc init. Just return to go runtime. */ return; + } /* * We need to re-exec if we are not in a cloned binary. This is necessary diff --git a/utils_linux.go b/utils_linux.go index 707bf5d3bdc..5404f2eb98e 100644 --- a/utils_linux.go +++ b/utils_linux.go @@ -97,7 +97,7 @@ func getDefaultImagePath(context *cli.Context) string { // newProcess returns a new libcontainer Process with the arguments from the // spec and stdio from the current process. -func newProcess(p specs.Process, init bool, logLevel string) (*libcontainer.Process, error) { +func newProcess(p specs.Process) (*libcontainer.Process, error) { lp := &libcontainer.Process{ Args: p.Args, Env: p.Env, @@ -107,8 +107,6 @@ func newProcess(p specs.Process, init bool, logLevel string) (*libcontainer.Proc Label: p.SelinuxLabel, NoNewPrivileges: &p.NoNewPrivileges, AppArmorProfile: p.ApparmorProfile, - Init: init, - LogLevel: logLevel, } if p.ConsoleSize != nil { @@ -257,7 +255,6 @@ type runner struct { action CtAct notifySocket *notifySocket criuOpts *libcontainer.CriuOpts - logLevel string } func (r *runner) run(config *specs.Process) (int, error) { @@ -270,10 +267,13 @@ func (r *runner) run(config *specs.Process) (int, error) { if err = r.checkTerminal(config); err != nil { return -1, err } - process, err := newProcess(*config, r.init, r.logLevel) + process, err := newProcess(*config) if err != nil { return -1, err } + process.LogLevel = strconv.Itoa(int(logrus.GetLevel())) + // Populate the fields that come from runner. + process.Init = r.init if len(r.listenFDs) > 0 { process.Env = append(process.Env, "LISTEN_FDS="+strconv.Itoa(len(r.listenFDs)), "LISTEN_PID=1") process.ExtraFiles = append(process.ExtraFiles, r.listenFDs...) @@ -430,11 +430,6 @@ func startContainer(context *cli.Context, spec *specs.Spec, action CtAct, criuOp listenFDs = activation.Files(false) } - logLevel := "info" - if context.GlobalBool("debug") { - logLevel = "debug" - } - r := &runner{ enableSubreaper: !context.Bool("no-subreaper"), shouldDestroy: !context.Bool("keep"), @@ -448,7 +443,6 @@ func startContainer(context *cli.Context, spec *specs.Spec, action CtAct, criuOp action: action, criuOpts: criuOpts, init: true, - logLevel: logLevel, } return r.run(spec.Process) }