From 81e5a3f7a745c1ad93f17967ad46e50950deed66 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Tue, 24 Jun 2014 16:54:50 -0700 Subject: [PATCH 1/4] Replace pid and started file with State type Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- namespaces/exec.go | 10 +++++++-- namespaces/execin.go | 4 ++-- namespaces/pid.go | 28 -------------------------- nsinit/exec.go | 11 +++++----- nsinit/utils.go | 16 --------------- state.go | 48 ++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 64 insertions(+), 53 deletions(-) delete mode 100644 namespaces/pid.go create mode 100644 state.go diff --git a/namespaces/exec.go b/namespaces/exec.go index 10238536e..44efb938d 100644 --- a/namespaces/exec.go +++ b/namespaces/exec.go @@ -56,12 +56,18 @@ func Exec(container *libcontainer.Config, term Terminal, rootfs, dataPath string if err != nil { return -1, err } - if err := WritePid(dataPath, command.Process.Pid, started); err != nil { + + state := &libcontainer.State{ + Pid1: command.Process.Pid, + Pid1StartTime: started, + } + + if err := libcontainer.WriteState(dataPath, state); err != nil { command.Process.Kill() command.Wait() return -1, err } - defer DeletePid(dataPath) + defer libcontainer.DeleteState(dataPath) // Do this before syncing with child so that no children // can escape the cgroup diff --git a/namespaces/execin.go b/namespaces/execin.go index 5dc8071ca..bbb83e67a 100644 --- a/namespaces/execin.go +++ b/namespaces/execin.go @@ -13,7 +13,7 @@ import ( ) // ExecIn uses an existing pid and joins the pid's namespaces with the new command. -func ExecIn(container *libcontainer.Config, nspid int, args []string) error { +func ExecIn(container *libcontainer.Config, state *libcontainer.State, args []string) error { // TODO(vmarmol): If this gets too long, send it over a pipe to the child. // Marshall the container into JSON since it won't be available in the namespace. containerJson, err := json.Marshal(container) @@ -22,7 +22,7 @@ func ExecIn(container *libcontainer.Config, nspid int, args []string) error { } // Enter the namespace and then finish setup - finalArgs := []string{os.Args[0], "nsenter", "--nspid", strconv.Itoa(nspid), "--containerjson", string(containerJson), "--"} + finalArgs := []string{os.Args[0], "nsenter", "--nspid", strconv.Itoa(state.Pid1), "--containerjson", string(containerJson), "--"} finalArgs = append(finalArgs, args...) if err := system.Execv(finalArgs[0], finalArgs[0:], os.Environ()); err != nil { return err diff --git a/namespaces/pid.go b/namespaces/pid.go deleted file mode 100644 index 8d97ec146..000000000 --- a/namespaces/pid.go +++ /dev/null @@ -1,28 +0,0 @@ -package namespaces - -import ( - "fmt" - "io/ioutil" - "os" - "path/filepath" -) - -// WritePid writes the namespaced processes pid to pid and it's start time -// to the path specified -func WritePid(path string, pid int, startTime string) error { - err := ioutil.WriteFile(filepath.Join(path, "pid"), []byte(fmt.Sprint(pid)), 0655) - if err != nil { - return err - } - return ioutil.WriteFile(filepath.Join(path, "start"), []byte(startTime), 0655) -} - -// DeletePid removes the pid and started file from disk when the container's process -// dies and the container is cleanly removed -func DeletePid(path string) error { - err := os.Remove(filepath.Join(path, "pid")) - if serr := os.Remove(filepath.Join(path, "start")); err == nil { - err = serr - } - return err -} diff --git a/nsinit/exec.go b/nsinit/exec.go index deaf963fd..b1b36c135 100644 --- a/nsinit/exec.go +++ b/nsinit/exec.go @@ -19,19 +19,20 @@ var execCommand = cli.Command{ } func execAction(context *cli.Context) { - var nspid, exitCode int + var exitCode int container, err := loadContainer() if err != nil { log.Fatal(err) } - if nspid, err = readPid(); err != nil && !os.IsNotExist(err) { - log.Fatalf("unable to read pid: %s", err) + state, err := libcontainer.LoadState(dataPath) + if err != nil && !os.IsNotExist(err) { + log.Fatalf("unable to read state.json: %s", err) } - if nspid > 0 { - err = namespaces.ExecIn(container, nspid, []string(context.Args())) + if state != nil { + err = namespaces.ExecIn(container, state, []string(context.Args())) } else { term := namespaces.NewTerminal(os.Stdin, os.Stdout, os.Stderr, container.Tty) exitCode, err = startContainer(container, term, dataPath, []string(context.Args())) diff --git a/nsinit/utils.go b/nsinit/utils.go index fb672521f..44194d885 100644 --- a/nsinit/utils.go +++ b/nsinit/utils.go @@ -2,11 +2,9 @@ package main import ( "encoding/json" - "io/ioutil" "log" "os" "path/filepath" - "strconv" "github.com/docker/libcontainer" ) @@ -26,20 +24,6 @@ func loadContainer() (*libcontainer.Config, error) { return container, nil } -func readPid() (int, error) { - data, err := ioutil.ReadFile(filepath.Join(dataPath, "pid")) - if err != nil { - return -1, err - } - - pid, err := strconv.Atoi(string(data)) - if err != nil { - return -1, err - } - - return pid, nil -} - func openLog(name string) error { f, err := os.OpenFile(name, os.O_CREATE|os.O_RDWR|os.O_APPEND, 0755) if err != nil { diff --git a/state.go b/state.go new file mode 100644 index 000000000..330f8195b --- /dev/null +++ b/state.go @@ -0,0 +1,48 @@ +package libcontainer + +import ( + "encoding/json" + "os" + "path/filepath" +) + +// State represents a running container's state +type State struct { + // Pid1 is the process id for the container's pid 1 in it's parent namespace + Pid1 int `json:"pid1,omitempty"` + // Pid1StartTime is the process start time for the container's pid 1 + Pid1StartTime string `json:"pid1_start_time,omitempty"` +} + +// WriteState writes the container's runtime state to a state.json file +// in the specified path +func WriteState(basePath string, state *State) error { + f, err := os.Create(filepath.Join(basePath, "state.json")) + if err != nil { + return err + } + defer f.Close() + + return json.NewEncoder(f).Encode(state) +} + +// LoadState reads the state.json file for a running container +func LoadState(basePath string) (*State, error) { + f, err := os.Open(filepath.Join(basePath, "state.json")) + if err != nil { + return nil, err + } + defer f.Close() + + var state *State + if err := json.NewDecoder(f).Decode(&state); err != nil { + return nil, err + } + + return state, nil +} + +// DeleteState deletes the state.json file +func DeleteState(basePath string) error { + return os.Remove(filepath.Join(basePath, "state.json")) +} From 854bcc0f7e6c1aec43c9ea6ba17243fce58dc611 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Tue, 24 Jun 2014 16:57:29 -0700 Subject: [PATCH 2/4] Update readme with state.json changes Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- README.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index e4cf36f37..46c97e757 100644 --- a/README.md +++ b/README.md @@ -19,10 +19,10 @@ process are specified in this file. The configuration is used for each process See the `sample_configs` folder for examples of what the container configuration should look like. -Using this configuration and the current directory holding the rootfs for a process, one can use libcontainer to exec the container. Running the life of the namespace, a `pid` file -is written to the current directory with the pid of the namespaced process to the external world. A client can use this pid to wait, kill, or perform other operation with the container. If a user tries to run a new process inside an existing container with a live namespace, the namespace will be joined by the new process. +Using this configuration and the current directory holding the rootfs for a process, one can use libcontainer to exec the container. During the life of the container, a `state.json` file +is written to the current directory with the pid and start time of the container's PID1. A client can use this pid to wait, kill, or perform other operation with the container. If a user tries to run a new process inside an existing container with a live namespace, the namespace will be joined by the new process. -You may also specify an alternate root place where the `container.json` file is read and where the `pid` file will be saved. +You may also specify an alternate root place where the `container.json` file is read and where the `state.json` file will be saved. #### nsinit @@ -38,7 +38,6 @@ nsinit exec /bin/bash If you wish to spawn another process inside the container while your current bash session is running just run the exact same command again to get another bash shell or change the command. If the original process dies, PID 1, all other processes spawned inside the container will also be killed and the namespace will be removed. -You can identify if a process is running in a container by looking to see if `pid` is in the root of the directory. #### Future See the [roadmap](ROADMAP.md). From 77dcaac12973086f3a6f74f8bca0043ea940702f Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Tue, 24 Jun 2014 17:17:41 -0700 Subject: [PATCH 3/4] Update code based on review comments Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- namespaces/exec.go | 2 +- nsinit/exec.go | 2 +- state.go | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/namespaces/exec.go b/namespaces/exec.go index 44efb938d..d62b295fe 100644 --- a/namespaces/exec.go +++ b/namespaces/exec.go @@ -62,7 +62,7 @@ func Exec(container *libcontainer.Config, term Terminal, rootfs, dataPath string Pid1StartTime: started, } - if err := libcontainer.WriteState(dataPath, state); err != nil { + if err := libcontainer.SaveState(dataPath, state); err != nil { command.Process.Kill() command.Wait() return -1, err diff --git a/nsinit/exec.go b/nsinit/exec.go index b1b36c135..c58c30664 100644 --- a/nsinit/exec.go +++ b/nsinit/exec.go @@ -26,7 +26,7 @@ func execAction(context *cli.Context) { log.Fatal(err) } - state, err := libcontainer.LoadState(dataPath) + state, err := libcontainer.GetState(dataPath) if err != nil && !os.IsNotExist(err) { log.Fatalf("unable to read state.json: %s", err) } diff --git a/state.go b/state.go index 330f8195b..1f05d4c07 100644 --- a/state.go +++ b/state.go @@ -14,9 +14,9 @@ type State struct { Pid1StartTime string `json:"pid1_start_time,omitempty"` } -// WriteState writes the container's runtime state to a state.json file +// SaveState writes the container's runtime state to a state.json file // in the specified path -func WriteState(basePath string, state *State) error { +func SaveState(basePath string, state *State) error { f, err := os.Create(filepath.Join(basePath, "state.json")) if err != nil { return err @@ -26,8 +26,8 @@ func WriteState(basePath string, state *State) error { return json.NewEncoder(f).Encode(state) } -// LoadState reads the state.json file for a running container -func LoadState(basePath string) (*State, error) { +// GetState reads the state.json file for a running container +func GetState(basePath string) (*State, error) { f, err := os.Open(filepath.Join(basePath, "state.json")) if err != nil { return nil, err From 5bb250143ead14eed2485589544543aec9f2cc58 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Wed, 25 Jun 2014 11:42:08 -0700 Subject: [PATCH 4/4] Rename to InitPid Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- namespaces/exec.go | 4 ++-- namespaces/execin.go | 2 +- state.go | 8 ++++---- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/namespaces/exec.go b/namespaces/exec.go index d62b295fe..4783e66f7 100644 --- a/namespaces/exec.go +++ b/namespaces/exec.go @@ -58,8 +58,8 @@ func Exec(container *libcontainer.Config, term Terminal, rootfs, dataPath string } state := &libcontainer.State{ - Pid1: command.Process.Pid, - Pid1StartTime: started, + InitPid: command.Process.Pid, + InitStartTime: started, } if err := libcontainer.SaveState(dataPath, state); err != nil { diff --git a/namespaces/execin.go b/namespaces/execin.go index bbb83e67a..d349282e8 100644 --- a/namespaces/execin.go +++ b/namespaces/execin.go @@ -22,7 +22,7 @@ func ExecIn(container *libcontainer.Config, state *libcontainer.State, args []st } // Enter the namespace and then finish setup - finalArgs := []string{os.Args[0], "nsenter", "--nspid", strconv.Itoa(state.Pid1), "--containerjson", string(containerJson), "--"} + finalArgs := []string{os.Args[0], "nsenter", "--nspid", strconv.Itoa(state.InitPid), "--containerjson", string(containerJson), "--"} finalArgs = append(finalArgs, args...) if err := system.Execv(finalArgs[0], finalArgs[0:], os.Environ()); err != nil { return err diff --git a/state.go b/state.go index 1f05d4c07..b60b901cc 100644 --- a/state.go +++ b/state.go @@ -8,10 +8,10 @@ import ( // State represents a running container's state type State struct { - // Pid1 is the process id for the container's pid 1 in it's parent namespace - Pid1 int `json:"pid1,omitempty"` - // Pid1StartTime is the process start time for the container's pid 1 - Pid1StartTime string `json:"pid1_start_time,omitempty"` + // InitPid is the init process id in the parent namespace + InitPid int `json:"init_pid,omitempty"` + // InitStartTime is the init process start time + InitStartTime string `json:"init_start_time,omitempty"` } // SaveState writes the container's runtime state to a state.json file