diff --git a/error.go b/error.go index 31ebb320..85b0dcaf 100644 --- a/error.go +++ b/error.go @@ -15,6 +15,7 @@ const ( ContainerNotExists ContainerPaused ContainerNotStopped + ContainerNotRunning // Common errors ConfigInvalid @@ -36,7 +37,9 @@ func (c ErrorCode) String() string { case ContainerNotExists: return "Container does not exist" case ContainerNotStopped: - return "Container isn't stopped" + return "Container is not stopped" + case ContainerNotRunning: + return "Container is not running" default: return "Unknown error" } diff --git a/generic_error.go b/generic_error.go index 85010007..ff614ee6 100644 --- a/generic_error.go +++ b/generic_error.go @@ -11,12 +11,14 @@ import ( var errorTemplate = template.Must(template.New("error").Parse(`Timestamp: {{.Timestamp}} Code: {{.ECode}} +{{if .Err }} Message: {{.Err.Error}} +{{end}} Frames:{{range $i, $frame := .Stack.Frames}} --- {{$i}}: {{$frame.Function}} Package: {{$frame.Package}} -File: {{$frame.File}}{{end}} +File: {{$frame.File}}@{{$frame.Line}}{{end}} `)) func newGenericError(err error, c ErrorCode) Error { @@ -27,7 +29,7 @@ func newGenericError(err error, c ErrorCode) Error { Timestamp: time.Now(), Err: err, ECode: c, - Stack: stacktrace.Capture(2), + Stack: stacktrace.Capture(1), } } @@ -39,7 +41,7 @@ func newSystemError(err error) Error { Timestamp: time.Now(), Err: err, ECode: SystemError, - Stack: stacktrace.Capture(2), + Stack: stacktrace.Capture(1), } } diff --git a/linux_container.go b/linux_container.go index efe9375b..0cb6749f 100644 --- a/linux_container.go +++ b/linux_container.go @@ -184,7 +184,6 @@ func (c *linuxContainer) commandTemplate(p *Process, childPipe *os.File) (*exec. func (c *linuxContainer) newInitProcess(p *Process, cmd *exec.Cmd, parentPipe, childPipe *os.File) *initProcess { t := "_LIBCONTAINER_INITTYPE=standard" - cloneFlags := c.config.Namespaces.CloneFlags() if cloneFlags&syscall.CLONE_NEWUSER != 0 { c.addUidGidMappings(cmd.SysProcAttr) @@ -225,6 +224,8 @@ func (c *linuxContainer) newInitConfig(process *Process) *initConfig { Config: c.config, Args: process.Args, Env: process.Env, + User: process.User, + Cwd: process.Cwd, } } @@ -273,6 +274,7 @@ func (c *linuxContainer) Destroy() error { if rerr := os.RemoveAll(c.root); err == nil { err = rerr } + c.initProcess = nil return err } @@ -285,6 +287,9 @@ func (c *linuxContainer) Resume() error { } func (c *linuxContainer) Signal(signal os.Signal) error { + if c.initProcess == nil { + return newGenericError(nil, ContainerNotRunning) + } return c.initProcess.signal(signal) } diff --git a/linux_process.go b/linux_process.go index 955df882..ceef3c70 100644 --- a/linux_process.go +++ b/linux_process.go @@ -54,15 +54,15 @@ func (p *setnsProcess) signal(s os.Signal) error { func (p *setnsProcess) start() (err error) { defer p.parentPipe.Close() if p.forkedProcess, err = p.execSetns(); err != nil { - return err + return newSystemError(err) } if len(p.cgroupPaths) > 0 { if err := cgroups.EnterPid(p.cgroupPaths, p.forkedProcess.Pid); err != nil { - return err + return newSystemError(err) } } if err := json.NewEncoder(p.parentPipe).Encode(p.config); err != nil { - return err + return newSystemError(err) } return nil } @@ -75,18 +75,18 @@ func (p *setnsProcess) execSetns() (*os.Process, error) { err := p.cmd.Start() p.childPipe.Close() if err != nil { - return nil, err + return nil, newSystemError(err) } status, err := p.cmd.Process.Wait() if err != nil { - return nil, err + return nil, newSystemError(err) } if !status.Success() { - return nil, &exec.ExitError{status} + return nil, newSystemError(&exec.ExitError{status}) } var pid *pid if err := json.NewDecoder(p.parentPipe).Decode(&pid); err != nil { - return nil, err + return nil, newSystemError(err) } return os.FindProcess(pid.Pid) } @@ -129,12 +129,12 @@ func (p *initProcess) start() error { err := p.cmd.Start() p.childPipe.Close() if err != nil { - return err + return newSystemError(err) } // Do this before syncing with child so that no children // can escape the cgroup if err := p.manager.Apply(p.pid()); err != nil { - return err + return newSystemError(err) } defer func() { if err != nil { @@ -143,13 +143,13 @@ func (p *initProcess) start() error { } }() if err := p.createNetworkInterfaces(); err != nil { - return err + return newSystemError(err) } // Start the setup process to setup the init process if p.cmd.SysProcAttr.Cloneflags&syscall.CLONE_NEWUSER != 0 { parent, err := p.newUsernsSetupProcess() if err != nil { - return err + return newSystemError(err) } if err := parent.start(); err != nil { if err := parent.terminate(); err != nil { @@ -158,20 +158,20 @@ func (p *initProcess) start() error { return err } if _, err := parent.wait(); err != nil { - return err + return newSystemError(err) } } if err := p.sendConfig(); err != nil { - return err + return newSystemError(err) } // wait for the child process to fully complete and receive an error message // if one was encoutered var ierr *initError if err := json.NewDecoder(p.parentPipe).Decode(&ierr); err != nil && err != io.EOF { - return err + return newSystemError(err) } if ierr != nil { - return ierr + return newSystemError(ierr) } return nil } @@ -232,7 +232,7 @@ func (p *initProcess) createNetworkInterfaces() error { func (p *initProcess) newUsernsSetupProcess() (parentProcess, error) { parentPipe, childPipe, err := newPipe() if err != nil { - return nil, err + return nil, newSystemError(err) } cmd := exec.Command(p.cmd.Args[0], p.cmd.Args[1:]...) cmd.ExtraFiles = []*os.File{childPipe} diff --git a/linux_userns_init.go b/linux_userns_init.go index a898f2d2..a7da9ce4 100644 --- a/linux_userns_init.go +++ b/linux_userns_init.go @@ -21,7 +21,8 @@ func (l *linuxUsernsInit) Init() error { } consolePath := l.config.Config.Console if consolePath != "" { - console := newConsoleFromPath(consolePath) + // TODO: why is this hard coded? + console := newConsoleFromPath("/dev/console") if err := console.dupStdio(); err != nil { return err } diff --git a/nsinit/config.go b/nsinit/config.go index 3760f64c..f06804f2 100644 --- a/nsinit/config.go +++ b/nsinit/config.go @@ -12,14 +12,30 @@ import ( "github.com/docker/libcontainer/configs" ) +var createFlags = []cli.Flag{ + cli.IntFlag{Name: "parent-death-signal", Usage: "set the signal that will be delivered to the process incase the parent dies"}, + cli.BoolFlag{Name: "read-only", Usage: "set the container's rootfs as read-only"}, + cli.StringSliceFlag{Name: "bind", Value: &cli.StringSlice{}, Usage: "add bind mounts to the container"}, + cli.StringSliceFlag{Name: "tmpfs", Value: &cli.StringSlice{}, Usage: "add tmpfs mounts to the container"}, + cli.IntFlag{Name: "cpushares", Usage: "set the cpushares for the container"}, + cli.IntFlag{Name: "memory-limit", Usage: "set the memory limit for the container"}, + cli.IntFlag{Name: "memory-swap", Usage: "set the memory swap limit for the container"}, + cli.StringFlag{Name: "cpuset-cpus", Usage: "set the cpuset cpus"}, + cli.StringFlag{Name: "cpuset-mems", Usage: "set the cpuset mems"}, + cli.StringFlag{Name: "apparmor-profile", Usage: "set the apparmor profile"}, + cli.StringFlag{Name: "process-label", Usage: "set the process label"}, + cli.StringFlag{Name: "mount-label", Usage: "set the mount label"}, +} + var configCommand = cli.Command{ Name: "config", Usage: "generate a standard configuration file for a container", - Flags: []cli.Flag{ + Flags: append([]cli.Flag{ cli.StringFlag{Name: "file,f", Value: "stdout", Usage: "write the configuration to the specified file"}, - }, + }, createFlags...), Action: func(context *cli.Context) { template := getTemplate() + modify(template, context) data, err := json.MarshalIndent(template, "", "\t") if err != nil { fatal(err) @@ -41,6 +57,19 @@ var configCommand = cli.Command{ }, } +func modify(config *configs.Config, context *cli.Context) { + config.ParentDeathSignal = context.Int("parent-death-signal") + config.Readonlyfs = context.Bool("read-only") + config.Cgroups.CpusetCpus = context.String("cpuset-cpus") + config.Cgroups.CpusetMems = context.String("cpuset-mems") + config.Cgroups.CpuShares = int64(context.Int("cpushares")) + config.Cgroups.Memory = int64(context.Int("memory-limit")) + config.Cgroups.MemorySwap = int64(context.Int("memory-swap")) + config.AppArmorProfile = context.String("apparmor-profile") + config.ProcessLabel = context.String("process-label") + config.MountLabel = context.String("mount-label") +} + func getTemplate() *configs.Config { cwd, err := os.Getwd() if err != nil { @@ -91,14 +120,8 @@ func getTemplate() *configs.Config { Rlimits: []configs.Rlimit{ { Type: syscall.RLIMIT_NOFILE, - Hard: uint64(1024), - Soft: uint64(1024), - }, - }, - Mounts: []*configs.Mount{ - { - Type: "tmpfs", - Destination: "/tmp", + Hard: 1024, + Soft: 1024, }, }, } diff --git a/nsinit/exec.go b/nsinit/exec.go index 6b90ce28..5c6b830f 100644 --- a/nsinit/exec.go +++ b/nsinit/exec.go @@ -20,13 +20,14 @@ var execCommand = cli.Command{ Name: "exec", Usage: "execute a new command inside a container", Action: execAction, - Flags: []cli.Flag{ + Flags: append([]cli.Flag{ cli.BoolFlag{Name: "tty,t", Usage: "allocate a TTY to the container"}, cli.StringFlag{Name: "id", Value: "nsinit", Usage: "specify the ID for a container"}, cli.StringFlag{Name: "config", Value: "container.json", Usage: "path to the configuration file"}, + cli.BoolFlag{Name: "create", Usage: "create the container's configuration on the fly with arguments"}, cli.StringFlag{Name: "user,u", Value: "root", Usage: "set the user, uid, and/or gid for the process"}, cli.StringSliceFlag{Name: "env", Value: standardEnvironment, Usage: "set environment variables for the process"}, - }, + }, createFlags...), } func execAction(context *cli.Context) { @@ -38,6 +39,7 @@ func execAction(context *cli.Context) { if err != nil { fatal(err) } + created := false container, err := factory.Load(context.String("id")) if err != nil { if lerr, ok := err.(libcontainer.Error); !ok || lerr.Code() != libcontainer.ContainerNotExists { @@ -45,10 +47,15 @@ func execAction(context *cli.Context) { } config, err := loadConfig(context) if err != nil { + tty.Close() fatal(err) } - config.Console = tty.console.Path() + if tty.console != nil { + config.Console = tty.console.Path() + } + created = true if container, err = factory.Create(context.String("id"), config); err != nil { + tty.Close() fatal(err) } } @@ -64,19 +71,26 @@ func execAction(context *cli.Context) { tty.attach(process) pid, err := container.Start(process) if err != nil { + tty.Close() fatal(err) } proc, err := os.FindProcess(pid) if err != nil { + tty.Close() fatal(err) } status, err := proc.Wait() if err != nil { + tty.Close() fatal(err) } - if err := container.Destroy(); err != nil { - fatal(err) + if created { + if err := container.Destroy(); err != nil { + tty.Close() + fatal(err) + } } + tty.Close() os.Exit(utils.ExitStatus(status.Sys().(syscall.WaitStatus))) } diff --git a/nsinit/main.go b/nsinit/main.go index 64261117..a2afd00c 100644 --- a/nsinit/main.go +++ b/nsinit/main.go @@ -18,13 +18,14 @@ func main() { cli.StringFlag{Name: "root", Value: ".", Usage: "root directory for containers"}, } app.Commands = []cli.Command{ + configCommand, execCommand, initCommand, oomCommand, pauseCommand, statsCommand, unpauseCommand, - configCommand, + stateCommand, } if err := app.Run(os.Args); err != nil { log.Fatal(err) diff --git a/nsinit/state.go b/nsinit/state.go new file mode 100644 index 00000000..46981bb7 --- /dev/null +++ b/nsinit/state.go @@ -0,0 +1,31 @@ +package main + +import ( + "encoding/json" + "fmt" + + "github.com/codegangsta/cli" +) + +var stateCommand = cli.Command{ + Name: "state", + Usage: "get the container's current state", + Flags: []cli.Flag{ + cli.StringFlag{Name: "id", Value: "nsinit", Usage: "specify the ID for a container"}, + }, + Action: func(context *cli.Context) { + container, err := getContainer(context) + if err != nil { + fatal(err) + } + state, err := container.State() + if err != nil { + fatal(err) + } + data, err := json.MarshalIndent(state, "", "\t") + if err != nil { + fatal(err) + } + fmt.Printf("%s", data) + }, +} diff --git a/nsinit/stats.go b/nsinit/stats.go index 8320fed4..49087fa2 100644 --- a/nsinit/stats.go +++ b/nsinit/stats.go @@ -3,7 +3,6 @@ package main import ( "encoding/json" "fmt" - "log" "github.com/codegangsta/cli" ) @@ -17,15 +16,15 @@ var statsCommand = cli.Command{ Action: func(context *cli.Context) { container, err := getContainer(context) if err != nil { - log.Fatal(err) + fatal(err) } stats, err := container.Stats() if err != nil { - log.Fatal(err) + fatal(err) } - data, jerr := json.MarshalIndent(stats, "", "\t") + data, err := json.MarshalIndent(stats, "", "\t") if err != nil { - log.Fatal(jerr) + fatal(err) } fmt.Printf("%s", data) }, diff --git a/nsinit/utils.go b/nsinit/utils.go index 62d8e6fd..73c13b59 100644 --- a/nsinit/utils.go +++ b/nsinit/utils.go @@ -11,6 +11,11 @@ import ( ) func loadConfig(context *cli.Context) (*configs.Config, error) { + if context.Bool("create") { + config := getTemplate() + modify(config, context) + return config, nil + } f, err := os.Open(context.String("config")) if err != nil { return nil, err