Merge pull request #220 from crosbymichael/build-tags

Add seccomp build tag
diff --git a/libcontainer/configs/config.go b/libcontainer/configs/config.go
index 1179f63..f262876 100644
--- a/libcontainer/configs/config.go
+++ b/libcontainer/configs/config.go
@@ -1,5 +1,11 @@
 package configs
 
+import (
+	"bytes"
+	"encoding/json"
+	"os/exec"
+)
+
 type Rlimit struct {
 	Type int    `json:"type"`
 	Hard uint64 `json:"hard"`
@@ -159,4 +165,76 @@
 	// A number of rules are given, each having an action to be taken if a syscall matches it.
 	// A default action to be taken if no rules match is also given.
 	Seccomp *Seccomp `json:"seccomp"`
+
+	// Hooks are a collection of actions to perform at various container lifecycle events.
+	// Hooks are not able to be marshaled to json but they are also not needed to.
+	Hooks *Hooks `json:"-"`
+}
+
+type Hooks struct {
+	// Prestart commands are executed after the container namespaces are created,
+	// but before the user supplied command is executed from init.
+	Prestart []Hook
+
+	// Poststop commands are executed after the container init process exits.
+	Poststop []Hook
+}
+
+// HookState is the payload provided to a hook on execution.
+type HookState struct {
+	ID   string `json:"id"`
+	Pid  int    `json:"pid"`
+	Root string `json:"root"`
+}
+
+type Hook interface {
+	// Run executes the hook with the provided state.
+	Run(HookState) error
+}
+
+// NewFunctionHooks will call the provided function when the hook is run.
+func NewFunctionHook(f func(HookState) error) FuncHook {
+	return FuncHook{
+		run: f,
+	}
+}
+
+type FuncHook struct {
+	run func(HookState) error
+}
+
+func (f FuncHook) Run(s HookState) error {
+	return f.run(s)
+}
+
+type Command struct {
+	Path string   `json:"path"`
+	Args []string `json:"args"`
+	Env  []string `json:"env"`
+	Dir  string   `json:"dir"`
+}
+
+// NewCommandHooks will execute the provided command when the hook is run.
+func NewCommandHook(cmd Command) CommandHook {
+	return CommandHook{
+		Command: cmd,
+	}
+}
+
+type CommandHook struct {
+	Command
+}
+
+func (c Command) Run(s HookState) error {
+	b, err := json.Marshal(s)
+	if err != nil {
+		return err
+	}
+	cmd := exec.Cmd{
+		Path:  c.Path,
+		Args:  c.Args,
+		Env:   c.Env,
+		Stdin: bytes.NewReader(b),
+	}
+	return cmd.Run()
 }
diff --git a/libcontainer/configs/mount.go b/libcontainer/configs/mount.go
index 5a69f81..50668f0 100644
--- a/libcontainer/configs/mount.go
+++ b/libcontainer/configs/mount.go
@@ -25,10 +25,3 @@
 	// Optional Command to be run after Source is mounted.
 	PostmountCmds []Command `json:"postmount_cmds"`
 }
-
-type Command struct {
-	Path string   `json:"path"`
-	Args []string `json:"args"`
-	Env  []string `json:"env"`
-	Dir  string   `json:"dir"`
-}
diff --git a/libcontainer/container_linux.go b/libcontainer/container_linux.go
index 9210ec6..574773b 100644
--- a/libcontainer/container_linux.go
+++ b/libcontainer/container_linux.go
@@ -185,6 +185,7 @@
 		parentPipe: parentPipe,
 		manager:    c.cgroupManager,
 		config:     c.newInitConfig(p),
+		container:  c,
 	}, nil
 }
 
@@ -247,6 +248,17 @@
 		err = rerr
 	}
 	c.initProcess = nil
+	if c.config.Hooks != nil {
+		s := configs.HookState{
+			ID:   c.id,
+			Root: c.config.Rootfs,
+		}
+		for _, hook := range c.config.Hooks.Poststop {
+			if err := hook.Run(s); err != nil {
+				return err
+			}
+		}
+	}
 	return err
 }
 
diff --git a/libcontainer/integration/exec_test.go b/libcontainer/integration/exec_test.go
index 8e47e08..bbd1037 100644
--- a/libcontainer/integration/exec_test.go
+++ b/libcontainer/integration/exec_test.go
@@ -932,3 +932,65 @@
 		t.Fatalf("Expected oom_score_adj %d; got %q", config.OomScoreAdj, outputOomScoreAdj)
 	}
 }
+
+func TestHook(t *testing.T) {
+	if testing.Short() {
+		return
+	}
+	root, err := newTestRoot()
+	ok(t, err)
+	defer os.RemoveAll(root)
+
+	rootfs, err := newRootfs()
+	ok(t, err)
+	defer remove(rootfs)
+
+	config := newTemplateConfig(rootfs)
+	config.Hooks = &configs.Hooks{
+		Prestart: []configs.Hook{
+			configs.NewFunctionHook(func(s configs.HookState) error {
+				f, err := os.Create(filepath.Join(s.Root, "test"))
+				if err != nil {
+					return err
+				}
+				return f.Close()
+			}),
+		},
+		Poststop: []configs.Hook{
+			configs.NewFunctionHook(func(s configs.HookState) error {
+				return os.RemoveAll(filepath.Join(s.Root, "test"))
+			}),
+		},
+	}
+	container, err := factory.Create("test", config)
+	ok(t, err)
+
+	var stdout bytes.Buffer
+	pconfig := libcontainer.Process{
+		Args:   []string{"sh", "-c", "ls /test"},
+		Env:    standardEnvironment,
+		Stdin:  nil,
+		Stdout: &stdout,
+	}
+	err = container.Start(&pconfig)
+	ok(t, err)
+
+	// Wait for process
+	waitProcess(&pconfig, t)
+
+	outputLs := string(stdout.Bytes())
+
+	// Check that the ls output has the expected file touched by the prestart hook
+	if !strings.Contains(outputLs, "/test") {
+		container.Destroy()
+		t.Fatalf("ls output doesn't have the expected file: %s", outputLs)
+	}
+
+	if err := container.Destroy(); err != nil {
+		t.Fatalf("container destory %s", err)
+	}
+	fi, err := os.Stat(filepath.Join(rootfs, "test"))
+	if err == nil || !os.IsNotExist(err) {
+		t.Fatalf("expected file to not exist, got %s", fi.Name())
+	}
+}
diff --git a/libcontainer/process_linux.go b/libcontainer/process_linux.go
index 0fe06e8..f191c16 100644
--- a/libcontainer/process_linux.go
+++ b/libcontainer/process_linux.go
@@ -13,6 +13,7 @@
 	"syscall"
 
 	"github.com/opencontainers/runc/libcontainer/cgroups"
+	"github.com/opencontainers/runc/libcontainer/configs"
 	"github.com/opencontainers/runc/libcontainer/system"
 )
 
@@ -200,6 +201,18 @@
 			p.manager.Destroy()
 		}
 	}()
+	if p.config.Config.Hooks != nil {
+		s := configs.HookState{
+			ID:   p.container.id,
+			Pid:  p.pid(),
+			Root: p.config.Config.Rootfs,
+		}
+		for _, hook := range p.config.Config.Hooks.Prestart {
+			if err := hook.Run(s); err != nil {
+				return newSystemError(err)
+			}
+		}
+	}
 	if err := p.createNetworkInterfaces(); err != nil {
 		return newSystemError(err)
 	}