[lxc-devel] [lxd/master] Refactoring: Move all LXD commands to separate files

stgraber on Github lxc-bot at linuxcontainers.org
Wed Dec 14 20:23:33 UTC 2016


A non-text attachment was scrubbed...
Name: not available
Type: text/x-mailbox
Size: 395 bytes
Desc: not available
URL: <http://lists.linuxcontainers.org/pipermail/lxc-devel/attachments/20161214/d0f2c9ca/attachment.bin>
-------------- next part --------------
From 62b264b83f0383418f458d68f43a27af6a81f092 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Wed, 14 Dec 2016 14:58:08 -0500
Subject: [PATCH 01/15] main: Move activateifneeded to own file
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/main.go                  | 70 ---------------------------------------
 lxd/main_activateifneeded.go | 78 ++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 78 insertions(+), 70 deletions(-)
 create mode 100644 lxd/main_activateifneeded.go

diff --git a/lxd/main.go b/lxd/main.go
index 02204b5..c77a351 100644
--- a/lxd/main.go
+++ b/lxd/main.go
@@ -476,76 +476,6 @@ func cmdShutdown() error {
 	return nil
 }
 
-func cmdActivateIfNeeded() error {
-	// Don't start a full daemon, we just need DB access
-	d := &Daemon{
-		imagesDownloading:     map[string]chan bool{},
-		imagesDownloadingLock: sync.RWMutex{},
-		lxcpath:               shared.VarPath("containers"),
-	}
-
-	if !shared.PathExists(shared.VarPath("lxd.db")) {
-		shared.LogDebugf("No DB, so no need to start the daemon now.")
-		return nil
-	}
-
-	err := initializeDbObject(d, shared.VarPath("lxd.db"))
-	if err != nil {
-		return err
-	}
-
-	/* Load all config values from the database */
-	err = daemonConfigInit(d.db)
-	if err != nil {
-		return err
-	}
-
-	// Look for network socket
-	value := daemonConfig["core.https_address"].Get()
-	if value != "" {
-		shared.LogDebugf("Daemon has core.https_address set, activating...")
-		_, err := lxd.NewClient(&lxd.DefaultConfig, "local")
-		return err
-	}
-
-	// Look for auto-started or previously started containers
-	d.IdmapSet, err = shared.DefaultIdmapSet()
-	if err != nil {
-		return err
-	}
-
-	result, err := dbContainersList(d.db, cTypeRegular)
-	if err != nil {
-		return err
-	}
-
-	for _, name := range result {
-		c, err := containerLoadByName(d, name)
-		if err != nil {
-			return err
-		}
-
-		config := c.ExpandedConfig()
-		lastState := config["volatile.last_state.power"]
-		autoStart := config["boot.autostart"]
-
-		if c.IsRunning() {
-			shared.LogDebugf("Daemon has running containers, activating...")
-			_, err := lxd.NewClient(&lxd.DefaultConfig, "local")
-			return err
-		}
-
-		if lastState == "RUNNING" || lastState == "Running" || shared.IsTrue(autoStart) {
-			shared.LogDebugf("Daemon has auto-started containers, activating...")
-			_, err := lxd.NewClient(&lxd.DefaultConfig, "local")
-			return err
-		}
-	}
-
-	shared.LogDebugf("No need to start the daemon now.")
-	return nil
-}
-
 func cmdWaitReady() error {
 	var timeout int
 
diff --git a/lxd/main_activateifneeded.go b/lxd/main_activateifneeded.go
new file mode 100644
index 0000000..50cce63
--- /dev/null
+++ b/lxd/main_activateifneeded.go
@@ -0,0 +1,78 @@
+package main
+
+import (
+	"sync"
+
+	"github.com/lxc/lxd"
+	"github.com/lxc/lxd/shared"
+)
+
+func cmdActivateIfNeeded() error {
+	// Don't start a full daemon, we just need DB access
+	d := &Daemon{
+		imagesDownloading:     map[string]chan bool{},
+		imagesDownloadingLock: sync.RWMutex{},
+		lxcpath:               shared.VarPath("containers"),
+	}
+
+	if !shared.PathExists(shared.VarPath("lxd.db")) {
+		shared.LogDebugf("No DB, so no need to start the daemon now.")
+		return nil
+	}
+
+	err := initializeDbObject(d, shared.VarPath("lxd.db"))
+	if err != nil {
+		return err
+	}
+
+	/* Load all config values from the database */
+	err = daemonConfigInit(d.db)
+	if err != nil {
+		return err
+	}
+
+	// Look for network socket
+	value := daemonConfig["core.https_address"].Get()
+	if value != "" {
+		shared.LogDebugf("Daemon has core.https_address set, activating...")
+		_, err := lxd.NewClient(&lxd.DefaultConfig, "local")
+		return err
+	}
+
+	// Look for auto-started or previously started containers
+	d.IdmapSet, err = shared.DefaultIdmapSet()
+	if err != nil {
+		return err
+	}
+
+	result, err := dbContainersList(d.db, cTypeRegular)
+	if err != nil {
+		return err
+	}
+
+	for _, name := range result {
+		c, err := containerLoadByName(d, name)
+		if err != nil {
+			return err
+		}
+
+		config := c.ExpandedConfig()
+		lastState := config["volatile.last_state.power"]
+		autoStart := config["boot.autostart"]
+
+		if c.IsRunning() {
+			shared.LogDebugf("Daemon has running containers, activating...")
+			_, err := lxd.NewClient(&lxd.DefaultConfig, "local")
+			return err
+		}
+
+		if lastState == "RUNNING" || lastState == "Running" || shared.IsTrue(autoStart) {
+			shared.LogDebugf("Daemon has auto-started containers, activating...")
+			_, err := lxd.NewClient(&lxd.DefaultConfig, "local")
+			return err
+		}
+	}
+
+	shared.LogDebugf("No need to start the daemon now.")
+	return nil
+}

From e79c496f30cff1db70b6009e0877987fa9851d98 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Wed, 14 Dec 2016 14:59:18 -0500
Subject: [PATCH 02/15] main: Move callhook to own file
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/main.go          | 69 ---------------------------------------------
 lxd/main_callhook.go | 79 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 79 insertions(+), 69 deletions(-)
 create mode 100644 lxd/main_callhook.go

diff --git a/lxd/main.go b/lxd/main.go
index c77a351..d76d6fe 100644
--- a/lxd/main.go
+++ b/lxd/main.go
@@ -257,75 +257,6 @@ func run() error {
 	return cmdDaemon()
 }
 
-func cmdCallHook(args []string) error {
-	if len(args) < 4 {
-		return fmt.Errorf("Invalid arguments")
-	}
-
-	path := args[1]
-	id := args[2]
-	state := args[3]
-	target := ""
-
-	err := os.Setenv("LXD_DIR", path)
-	if err != nil {
-		return err
-	}
-
-	c, err := lxd.NewClient(&lxd.DefaultConfig, "local")
-	if err != nil {
-		return err
-	}
-
-	url := fmt.Sprintf("%s/internal/containers/%s/on%s", c.BaseURL, id, state)
-
-	if state == "stop" {
-		target = os.Getenv("LXC_TARGET")
-		if target == "" {
-			target = "unknown"
-		}
-		url = fmt.Sprintf("%s?target=%s", url, target)
-	}
-
-	req, err := http.NewRequest("GET", url, nil)
-	if err != nil {
-		return err
-	}
-
-	hook := make(chan error, 1)
-	go func() {
-		raw, err := c.Http.Do(req)
-		if err != nil {
-			hook <- err
-			return
-		}
-
-		_, err = lxd.HoistResponse(raw, lxd.Sync)
-		if err != nil {
-			hook <- err
-			return
-		}
-
-		hook <- nil
-	}()
-
-	select {
-	case err := <-hook:
-		if err != nil {
-			return err
-		}
-		break
-	case <-time.After(30 * time.Second):
-		return fmt.Errorf("Hook didn't finish within 30s")
-	}
-
-	if target == "reboot" {
-		return fmt.Errorf("Reboot must be handled by LXD.")
-	}
-
-	return nil
-}
-
 func cmdDaemon() error {
 	if *argCPUProfile != "" {
 		f, err := os.Create(*argCPUProfile)
diff --git a/lxd/main_callhook.go b/lxd/main_callhook.go
new file mode 100644
index 0000000..b8e2737
--- /dev/null
+++ b/lxd/main_callhook.go
@@ -0,0 +1,79 @@
+package main
+
+import (
+	"fmt"
+	"net/http"
+	"os"
+	"time"
+
+	"github.com/lxc/lxd"
+)
+
+func cmdCallHook(args []string) error {
+	if len(args) < 4 {
+		return fmt.Errorf("Invalid arguments")
+	}
+
+	path := args[1]
+	id := args[2]
+	state := args[3]
+	target := ""
+
+	err := os.Setenv("LXD_DIR", path)
+	if err != nil {
+		return err
+	}
+
+	c, err := lxd.NewClient(&lxd.DefaultConfig, "local")
+	if err != nil {
+		return err
+	}
+
+	url := fmt.Sprintf("%s/internal/containers/%s/on%s", c.BaseURL, id, state)
+
+	if state == "stop" {
+		target = os.Getenv("LXC_TARGET")
+		if target == "" {
+			target = "unknown"
+		}
+		url = fmt.Sprintf("%s?target=%s", url, target)
+	}
+
+	req, err := http.NewRequest("GET", url, nil)
+	if err != nil {
+		return err
+	}
+
+	hook := make(chan error, 1)
+	go func() {
+		raw, err := c.Http.Do(req)
+		if err != nil {
+			hook <- err
+			return
+		}
+
+		_, err = lxd.HoistResponse(raw, lxd.Sync)
+		if err != nil {
+			hook <- err
+			return
+		}
+
+		hook <- nil
+	}()
+
+	select {
+	case err := <-hook:
+		if err != nil {
+			return err
+		}
+		break
+	case <-time.After(30 * time.Second):
+		return fmt.Errorf("Hook didn't finish within 30s")
+	}
+
+	if target == "reboot" {
+		return fmt.Errorf("Reboot must be handled by LXD.")
+	}
+
+	return nil
+}

From 292d2190d6d21f26b07dc4ed178e3fc6937d83a1 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Wed, 14 Dec 2016 15:00:21 -0500
Subject: [PATCH 03/15] main: Move daemon to own file
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/main.go        |  90 -----------------------------------------------
 lxd/main_daemon.go | 101 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 101 insertions(+), 90 deletions(-)
 create mode 100644 lxd/main_daemon.go

diff --git a/lxd/main.go b/lxd/main.go
index d76d6fe..ae40a2d 100644
--- a/lxd/main.go
+++ b/lxd/main.go
@@ -10,11 +10,8 @@ import (
 	"net/http"
 	"os"
 	"os/exec"
-	"os/signal"
-	"runtime/pprof"
 	"strconv"
 	"strings"
-	"sync"
 	"syscall"
 	"time"
 
@@ -257,93 +254,6 @@ func run() error {
 	return cmdDaemon()
 }
 
-func cmdDaemon() error {
-	if *argCPUProfile != "" {
-		f, err := os.Create(*argCPUProfile)
-		if err != nil {
-			fmt.Printf("Error opening cpu profile file: %s\n", err)
-			return nil
-		}
-		pprof.StartCPUProfile(f)
-		defer pprof.StopCPUProfile()
-	}
-
-	if *argMemProfile != "" {
-		go memProfiler(*argMemProfile)
-	}
-
-	neededPrograms := []string{"dnsmasq", "setfacl", "rsync", "tar", "unsquashfs", "xz"}
-	for _, p := range neededPrograms {
-		_, err := exec.LookPath(p)
-		if err != nil {
-			return err
-		}
-	}
-
-	if *argPrintGoroutinesEvery > 0 {
-		go func() {
-			for {
-				time.Sleep(time.Duration(*argPrintGoroutinesEvery) * time.Second)
-				shared.PrintStack()
-			}
-		}()
-	}
-
-	d := &Daemon{
-		group:     *argGroup,
-		SetupMode: shared.PathExists(shared.VarPath(".setup_mode"))}
-	err := d.Init()
-	if err != nil {
-		if d != nil && d.db != nil {
-			d.db.Close()
-		}
-		return err
-	}
-
-	var ret error
-	var wg sync.WaitGroup
-	wg.Add(1)
-
-	go func() {
-		ch := make(chan os.Signal)
-		signal.Notify(ch, syscall.SIGPWR)
-		sig := <-ch
-
-		shared.LogInfof("Received '%s signal', shutting down containers.", sig)
-
-		containersShutdown(d)
-
-		ret = d.Stop()
-		wg.Done()
-	}()
-
-	go func() {
-		<-d.shutdownChan
-
-		shared.LogInfof("Asked to shutdown by API, shutting down containers.")
-
-		containersShutdown(d)
-
-		ret = d.Stop()
-		wg.Done()
-	}()
-
-	go func() {
-		ch := make(chan os.Signal)
-		signal.Notify(ch, syscall.SIGINT)
-		signal.Notify(ch, syscall.SIGQUIT)
-		signal.Notify(ch, syscall.SIGTERM)
-		sig := <-ch
-
-		shared.LogInfof("Received '%s signal', exiting.", sig)
-		ret = d.Stop()
-		wg.Done()
-	}()
-
-	wg.Wait()
-	return ret
-}
-
 func cmdReady() error {
 	c, err := lxd.NewClient(&lxd.DefaultConfig, "local")
 	if err != nil {
diff --git a/lxd/main_daemon.go b/lxd/main_daemon.go
new file mode 100644
index 0000000..acc60b7
--- /dev/null
+++ b/lxd/main_daemon.go
@@ -0,0 +1,101 @@
+package main
+
+import (
+	"fmt"
+	"os"
+	"os/exec"
+	"os/signal"
+	"runtime/pprof"
+	"sync"
+	"syscall"
+	"time"
+
+	"github.com/lxc/lxd/shared"
+)
+
+func cmdDaemon() error {
+	if *argCPUProfile != "" {
+		f, err := os.Create(*argCPUProfile)
+		if err != nil {
+			fmt.Printf("Error opening cpu profile file: %s\n", err)
+			return nil
+		}
+		pprof.StartCPUProfile(f)
+		defer pprof.StopCPUProfile()
+	}
+
+	if *argMemProfile != "" {
+		go memProfiler(*argMemProfile)
+	}
+
+	neededPrograms := []string{"dnsmasq", "setfacl", "rsync", "tar", "unsquashfs", "xz"}
+	for _, p := range neededPrograms {
+		_, err := exec.LookPath(p)
+		if err != nil {
+			return err
+		}
+	}
+
+	if *argPrintGoroutinesEvery > 0 {
+		go func() {
+			for {
+				time.Sleep(time.Duration(*argPrintGoroutinesEvery) * time.Second)
+				shared.PrintStack()
+			}
+		}()
+	}
+
+	d := &Daemon{
+		group:     *argGroup,
+		SetupMode: shared.PathExists(shared.VarPath(".setup_mode"))}
+	err := d.Init()
+	if err != nil {
+		if d != nil && d.db != nil {
+			d.db.Close()
+		}
+		return err
+	}
+
+	var ret error
+	var wg sync.WaitGroup
+	wg.Add(1)
+
+	go func() {
+		ch := make(chan os.Signal)
+		signal.Notify(ch, syscall.SIGPWR)
+		sig := <-ch
+
+		shared.LogInfof("Received '%s signal', shutting down containers.", sig)
+
+		containersShutdown(d)
+
+		ret = d.Stop()
+		wg.Done()
+	}()
+
+	go func() {
+		<-d.shutdownChan
+
+		shared.LogInfof("Asked to shutdown by API, shutting down containers.")
+
+		containersShutdown(d)
+
+		ret = d.Stop()
+		wg.Done()
+	}()
+
+	go func() {
+		ch := make(chan os.Signal)
+		signal.Notify(ch, syscall.SIGINT)
+		signal.Notify(ch, syscall.SIGQUIT)
+		signal.Notify(ch, syscall.SIGTERM)
+		sig := <-ch
+
+		shared.LogInfof("Received '%s signal', exiting.", sig)
+		ret = d.Stop()
+		wg.Done()
+	}()
+
+	wg.Wait()
+	return ret
+}

From 43c1035ae02d295a4ec8c90bf04fbabefef0c6a3 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Wed, 14 Dec 2016 15:01:29 -0500
Subject: [PATCH 04/15] main: Move forkexec to own file
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/containers.go    | 124 ----------------------------------------------
 lxd/main.go          |   2 +-
 lxd/main_forkexec.go | 135 +++++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 136 insertions(+), 125 deletions(-)
 create mode 100644 lxd/main_forkexec.go

diff --git a/lxd/containers.go b/lxd/containers.go
index 46b74b1..4d10289 100644
--- a/lxd/containers.go
+++ b/lxd/containers.go
@@ -1,12 +1,10 @@
 package main
 
 import (
-	"encoding/json"
 	"fmt"
 	"os"
 	"sort"
 	"strconv"
-	"strings"
 	"sync"
 	"syscall"
 	"time"
@@ -261,125 +259,3 @@ func startContainer(args []string) error {
 
 	return c.Start()
 }
-
-/*
- * This is called by lxd when called as "lxd forkexec <container>"
- */
-func execContainer(args []string) (int, error) {
-	if len(args) < 6 {
-		return -1, fmt.Errorf("Bad arguments: %q", args)
-	}
-
-	name := args[1]
-	lxcpath := args[2]
-	configPath := args[3]
-
-	c, err := lxc.NewContainer(name, lxcpath)
-	if err != nil {
-		return -1, fmt.Errorf("Error initializing container for start: %q", err)
-	}
-
-	err = c.LoadConfigFile(configPath)
-	if err != nil {
-		return -1, fmt.Errorf("Error opening startup config file: %q", err)
-	}
-
-	syscall.Dup3(int(os.Stdin.Fd()), 200, 0)
-	syscall.Dup3(int(os.Stdout.Fd()), 201, 0)
-	syscall.Dup3(int(os.Stderr.Fd()), 202, 0)
-
-	syscall.Close(int(os.Stdin.Fd()))
-	syscall.Close(int(os.Stdout.Fd()))
-	syscall.Close(int(os.Stderr.Fd()))
-
-	opts := lxc.DefaultAttachOptions
-	opts.ClearEnv = true
-	opts.StdinFd = 200
-	opts.StdoutFd = 201
-	opts.StderrFd = 202
-
-	logPath := shared.LogPath(name, "forkexec.log")
-	if shared.PathExists(logPath) {
-		os.Remove(logPath)
-	}
-
-	logFile, err := os.OpenFile(logPath, os.O_WRONLY|os.O_CREATE|os.O_SYNC, 0644)
-	if err == nil {
-		syscall.Dup3(int(logFile.Fd()), 1, 0)
-		syscall.Dup3(int(logFile.Fd()), 2, 0)
-	}
-
-	env := []string{}
-	cmd := []string{}
-
-	section := ""
-	for _, arg := range args[5:len(args)] {
-		// The "cmd" section must come last as it may contain a --
-		if arg == "--" && section != "cmd" {
-			section = ""
-			continue
-		}
-
-		if section == "" {
-			section = arg
-			continue
-		}
-
-		if section == "env" {
-			fields := strings.SplitN(arg, "=", 2)
-			if len(fields) == 2 && fields[0] == "HOME" {
-				opts.Cwd = fields[1]
-			}
-			env = append(env, arg)
-		} else if section == "cmd" {
-			cmd = append(cmd, arg)
-		} else {
-			return -1, fmt.Errorf("Invalid exec section: %s", section)
-		}
-	}
-
-	opts.Env = env
-
-	status, err := c.RunCommandNoWait(cmd, opts)
-	if err != nil {
-		return -1, fmt.Errorf("Failed running command: %q", err)
-	}
-	// Send the PID of the executing process.
-	w := os.NewFile(uintptr(3), "attachedPid")
-	defer w.Close()
-
-	err = json.NewEncoder(w).Encode(status)
-	if err != nil {
-		return -1, fmt.Errorf("Failed sending PID of executing command: %q", err)
-	}
-
-	proc, err := os.FindProcess(status)
-	if err != nil {
-		return -1, fmt.Errorf("Failed finding process: %q", err)
-	}
-
-	procState, err := proc.Wait()
-	if err != nil {
-		return -1, fmt.Errorf("Failed waiting on process %d: %q", status, err)
-	}
-
-	if procState.Success() {
-		return 0, nil
-	}
-
-	exCode, ok := procState.Sys().(syscall.WaitStatus)
-	if ok {
-		if exCode.Exited() {
-			return exCode.ExitStatus(), nil
-		}
-		// Backwards compatible behavior. Report success when we exited
-		// due to a signal. Otherwise this may break Jenkins, e.g. when
-		// lxc exec foo reboot receives SIGTERM and exCode.Exitstats()
-		// would report -1.
-		if exCode.Signaled() {
-			return 0, nil
-		}
-	}
-
-	return -1, fmt.Errorf("Command failed")
-}
diff --git a/lxd/main.go b/lxd/main.go
index ae40a2d..31bdcff 100644
--- a/lxd/main.go
+++ b/lxd/main.go
@@ -233,7 +233,7 @@ func run() error {
 		case "forkstart":
 			return startContainer(os.Args[1:])
 		case "forkexec":
-			ret, err := execContainer(os.Args[1:])
+			ret, err := cmdForkExec(os.Args[1:])
 			if err != nil {
 				fmt.Fprintf(os.Stderr, "error: %v\n", err)
 			}
diff --git a/lxd/main_forkexec.go b/lxd/main_forkexec.go
new file mode 100644
index 0000000..8ebb0a6
--- /dev/null
+++ b/lxd/main_forkexec.go
@@ -0,0 +1,135 @@
+package main
+
+import (
+	"encoding/json"
+	"fmt"
+	"os"
+	"strings"
+	"syscall"
+
+	"gopkg.in/lxc/go-lxc.v2"
+
+	"github.com/lxc/lxd/shared"
+)
+
+/*
+ * This is called by lxd when called as "lxd forkexec <container>"
+ */
+func cmdForkExec(args []string) (int, error) {
+	if len(args) < 6 {
+		return -1, fmt.Errorf("Bad arguments: %q", args)
+	}
+
+	name := args[1]
+	lxcpath := args[2]
+	configPath := args[3]
+
+	c, err := lxc.NewContainer(name, lxcpath)
+	if err != nil {
+		return -1, fmt.Errorf("Error initializing container for start: %q", err)
+	}
+
+	err = c.LoadConfigFile(configPath)
+	if err != nil {
+		return -1, fmt.Errorf("Error opening startup config file: %q", err)
+	}
+
+	syscall.Dup3(int(os.Stdin.Fd()), 200, 0)
+	syscall.Dup3(int(os.Stdout.Fd()), 201, 0)
+	syscall.Dup3(int(os.Stderr.Fd()), 202, 0)
+
+	syscall.Close(int(os.Stdin.Fd()))
+	syscall.Close(int(os.Stdout.Fd()))
+	syscall.Close(int(os.Stderr.Fd()))
+
+	opts := lxc.DefaultAttachOptions
+	opts.ClearEnv = true
+	opts.StdinFd = 200
+	opts.StdoutFd = 201
+	opts.StderrFd = 202
+
+	logPath := shared.LogPath(name, "forkexec.log")
+	if shared.PathExists(logPath) {
+		os.Remove(logPath)
+	}
+
+	logFile, err := os.OpenFile(logPath, os.O_WRONLY|os.O_CREATE|os.O_SYNC, 0644)
+	if err == nil {
+		syscall.Dup3(int(logFile.Fd()), 1, 0)
+		syscall.Dup3(int(logFile.Fd()), 2, 0)
+	}
+
+	env := []string{}
+	cmd := []string{}
+
+	section := ""
+	for _, arg := range args[5:len(args)] {
+		// The "cmd" section must come last as it may contain a --
+		if arg == "--" && section != "cmd" {
+			section = ""
+			continue
+		}
+
+		if section == "" {
+			section = arg
+			continue
+		}
+
+		if section == "env" {
+			fields := strings.SplitN(arg, "=", 2)
+			if len(fields) == 2 && fields[0] == "HOME" {
+				opts.Cwd = fields[1]
+			}
+			env = append(env, arg)
+		} else if section == "cmd" {
+			cmd = append(cmd, arg)
+		} else {
+			return -1, fmt.Errorf("Invalid exec section: %s", section)
+		}
+	}
+
+	opts.Env = env
+
+	status, err := c.RunCommandNoWait(cmd, opts)
+	if err != nil {
+		return -1, fmt.Errorf("Failed running command: %q", err)
+	}
+	// Send the PID of the executing process.
+	w := os.NewFile(uintptr(3), "attachedPid")
+	defer w.Close()
+
+	err = json.NewEncoder(w).Encode(status)
+	if err != nil {
+		return -1, fmt.Errorf("Failed sending PID of executing command: %q", err)
+	}
+
+	proc, err := os.FindProcess(status)
+	if err != nil {
+		return -1, fmt.Errorf("Failed finding process: %q", err)
+	}
+
+	procState, err := proc.Wait()
+	if err != nil {
+		return -1, fmt.Errorf("Failed waiting on process %d: %q", status, err)
+	}
+
+	if procState.Success() {
+		return 0, nil
+	}
+
+	exCode, ok := procState.Sys().(syscall.WaitStatus)
+	if ok {
+		if exCode.Exited() {
+			return exCode.ExitStatus(), nil
+		}
+		// Backwards compatible behavior. Report success when we exited
+		// due to a signal. Otherwise this may break Jenkins, e.g. when
+		// lxc exec foo reboot receives SIGTERM and exCode.Exitstats()
+		// would report -1.
+		if exCode.Signaled() {
+			return 0, nil
+		}
+	}
+
+	return -1, fmt.Errorf("Command failed")
+}

From 03d19427f5f6d933eb78f840d13024e42c6e7224 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Wed, 14 Dec 2016 15:08:10 -0500
Subject: [PATCH 05/15] main: Move forkgetnet to own file
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/main.go            | 138 +---------------------------------------------
 lxd/main_forkgetnet.go | 146 +++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 147 insertions(+), 137 deletions(-)
 create mode 100644 lxd/main_forkgetnet.go

diff --git a/lxd/main.go b/lxd/main.go
index 31bdcff..144d7ec 100644
--- a/lxd/main.go
+++ b/lxd/main.go
@@ -2,9 +2,7 @@ package main
 
 import (
 	"bufio"
-	"encoding/json"
 	"fmt"
-	"io/ioutil"
 	"math/rand"
 	"net"
 	"net/http"
@@ -227,7 +225,7 @@ func run() error {
 
 		// Internal commands
 		case "forkgetnet":
-			return printnet()
+			return cmdForkGetNet()
 		case "forkmigrate":
 			return MigrateContainer(os.Args[1:])
 		case "forkstart":
@@ -841,140 +839,6 @@ they otherwise would.
 	return nil
 }
 
-func printnet() error {
-	networks := map[string]shared.ContainerStateNetwork{}
-
-	interfaces, err := net.Interfaces()
-	if err != nil {
-		return err
-	}
-
-	stats := map[string][]int64{}
-
-	content, err := ioutil.ReadFile("/proc/net/dev")
-	if err == nil {
-		for _, line := range strings.Split(string(content), "\n") {
-			fields := strings.Fields(line)
-
-			if len(fields) != 17 {
-				continue
-			}
-
-			rxBytes, err := strconv.ParseInt(fields[1], 10, 64)
-			if err != nil {
-				continue
-			}
-
-			rxPackets, err := strconv.ParseInt(fields[2], 10, 64)
-			if err != nil {
-				continue
-			}
-
-			txBytes, err := strconv.ParseInt(fields[9], 10, 64)
-			if err != nil {
-				continue
-			}
-
-			txPackets, err := strconv.ParseInt(fields[10], 10, 64)
-			if err != nil {
-				continue
-			}
-
-			intName := strings.TrimSuffix(fields[0], ":")
-			stats[intName] = []int64{rxBytes, rxPackets, txBytes, txPackets}
-		}
-	}
-
-	for _, netIf := range interfaces {
-		netState := "down"
-		netType := "unknown"
-
-		if netIf.Flags&net.FlagBroadcast > 0 {
-			netType = "broadcast"
-		}
-
-		if netIf.Flags&net.FlagPointToPoint > 0 {
-			netType = "point-to-point"
-		}
-
-		if netIf.Flags&net.FlagLoopback > 0 {
-			netType = "loopback"
-		}
-
-		if netIf.Flags&net.FlagUp > 0 {
-			netState = "up"
-		}
-
-		network := shared.ContainerStateNetwork{
-			Addresses: []shared.ContainerStateNetworkAddress{},
-			Counters:  shared.ContainerStateNetworkCounters{},
-			Hwaddr:    netIf.HardwareAddr.String(),
-			Mtu:       netIf.MTU,
-			State:     netState,
-			Type:      netType,
-		}
-
-		addrs, err := netIf.Addrs()
-		if err == nil {
-			for _, addr := range addrs {
-				fields := strings.SplitN(addr.String(), "/", 2)
-				if len(fields) != 2 {
-					continue
-				}
-
-				family := "inet"
-				if strings.Contains(fields[0], ":") {
-					family = "inet6"
-				}
-
-				scope := "global"
-				if strings.HasPrefix(fields[0], "127") {
-					scope = "local"
-				}
-
-				if fields[0] == "::1" {
-					scope = "local"
-				}
-
-				if strings.HasPrefix(fields[0], "169.254") {
-					scope = "link"
-				}
-
-				if strings.HasPrefix(fields[0], "fe80:") {
-					scope = "link"
-				}
-
-				address := shared.ContainerStateNetworkAddress{}
-				address.Family = family
-				address.Address = fields[0]
-				address.Netmask = fields[1]
-				address.Scope = scope
-
-				network.Addresses = append(network.Addresses, address)
-			}
-		}
-
-		counters, ok := stats[netIf.Name]
-		if ok {
-			network.Counters.BytesReceived = counters[0]
-			network.Counters.PacketsReceived = counters[1]
-			network.Counters.BytesSent = counters[2]
-			network.Counters.PacketsSent = counters[3]
-		}
-
-		networks[netIf.Name] = network
-	}
-
-	buf, err := json.Marshal(networks)
-	if err != nil {
-		return err
-	}
-
-	fmt.Printf("%s\n", buf)
-
-	return nil
-}
-
 func cmdMigrateDumpSuccess(args []string) error {
 	if len(args) != 3 {
 		return fmt.Errorf("bad migrate dump success args %s", args)
diff --git a/lxd/main_forkgetnet.go b/lxd/main_forkgetnet.go
new file mode 100644
index 0000000..7e016f3
--- /dev/null
+++ b/lxd/main_forkgetnet.go
@@ -0,0 +1,146 @@
+package main
+
+import (
+	"encoding/json"
+	"fmt"
+	"io/ioutil"
+	"net"
+	"strconv"
+	"strings"
+
+	"github.com/lxc/lxd/shared"
+)
+
+func cmdForkGetNet() error {
+	networks := map[string]shared.ContainerStateNetwork{}
+
+	interfaces, err := net.Interfaces()
+	if err != nil {
+		return err
+	}
+
+	stats := map[string][]int64{}
+
+	content, err := ioutil.ReadFile("/proc/net/dev")
+	if err == nil {
+		for _, line := range strings.Split(string(content), "\n") {
+			fields := strings.Fields(line)
+
+			if len(fields) != 17 {
+				continue
+			}
+
+			rxBytes, err := strconv.ParseInt(fields[1], 10, 64)
+			if err != nil {
+				continue
+			}
+
+			rxPackets, err := strconv.ParseInt(fields[2], 10, 64)
+			if err != nil {
+				continue
+			}
+
+			txBytes, err := strconv.ParseInt(fields[9], 10, 64)
+			if err != nil {
+				continue
+			}
+
+			txPackets, err := strconv.ParseInt(fields[10], 10, 64)
+			if err != nil {
+				continue
+			}
+
+			intName := strings.TrimSuffix(fields[0], ":")
+			stats[intName] = []int64{rxBytes, rxPackets, txBytes, txPackets}
+		}
+	}
+
+	for _, netIf := range interfaces {
+		netState := "down"
+		netType := "unknown"
+
+		if netIf.Flags&net.FlagBroadcast > 0 {
+			netType = "broadcast"
+		}
+
+		if netIf.Flags&net.FlagPointToPoint > 0 {
+			netType = "point-to-point"
+		}
+
+		if netIf.Flags&net.FlagLoopback > 0 {
+			netType = "loopback"
+		}
+
+		if netIf.Flags&net.FlagUp > 0 {
+			netState = "up"
+		}
+
+		network := shared.ContainerStateNetwork{
+			Addresses: []shared.ContainerStateNetworkAddress{},
+			Counters:  shared.ContainerStateNetworkCounters{},
+			Hwaddr:    netIf.HardwareAddr.String(),
+			Mtu:       netIf.MTU,
+			State:     netState,
+			Type:      netType,
+		}
+
+		addrs, err := netIf.Addrs()
+		if err == nil {
+			for _, addr := range addrs {
+				fields := strings.SplitN(addr.String(), "/", 2)
+				if len(fields) != 2 {
+					continue
+				}
+
+				family := "inet"
+				if strings.Contains(fields[0], ":") {
+					family = "inet6"
+				}
+
+				scope := "global"
+				if strings.HasPrefix(fields[0], "127") {
+					scope = "local"
+				}
+
+				if fields[0] == "::1" {
+					scope = "local"
+				}
+
+				if strings.HasPrefix(fields[0], "169.254") {
+					scope = "link"
+				}
+
+				if strings.HasPrefix(fields[0], "fe80:") {
+					scope = "link"
+				}
+
+				address := shared.ContainerStateNetworkAddress{}
+				address.Family = family
+				address.Address = fields[0]
+				address.Netmask = fields[1]
+				address.Scope = scope
+
+				network.Addresses = append(network.Addresses, address)
+			}
+		}
+
+		counters, ok := stats[netIf.Name]
+		if ok {
+			network.Counters.BytesReceived = counters[0]
+			network.Counters.PacketsReceived = counters[1]
+			network.Counters.BytesSent = counters[2]
+			network.Counters.PacketsSent = counters[3]
+		}
+
+		networks[netIf.Name] = network
+	}
+
+	buf, err := json.Marshal(networks)
+	if err != nil {
+		return err
+	}
+
+	fmt.Printf("%s\n", buf)
+
+	return nil
+}

From 2a9132bf1ac83936cda9a90398a2ab6a86fadb1d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Wed, 14 Dec 2016 15:09:21 -0500
Subject: [PATCH 06/15] main: Move forkmigrate to own file
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/main.go             |  2 +-
 lxd/main_forkmigrate.go | 52 +++++++++++++++++++++++++++++++++++++++++++++++++
 lxd/migrate.go          | 44 -----------------------------------------
 3 files changed, 53 insertions(+), 45 deletions(-)
 create mode 100644 lxd/main_forkmigrate.go

diff --git a/lxd/main.go b/lxd/main.go
index 144d7ec..9540870 100644
--- a/lxd/main.go
+++ b/lxd/main.go
@@ -227,7 +227,7 @@ func run() error {
 		case "forkgetnet":
 			return cmdForkGetNet()
 		case "forkmigrate":
-			return MigrateContainer(os.Args[1:])
+			return cmdForkMigrate(os.Args[1:])
 		case "forkstart":
 			return startContainer(os.Args[1:])
 		case "forkexec":
diff --git a/lxd/main_forkmigrate.go b/lxd/main_forkmigrate.go
new file mode 100644
index 0000000..45e437a
--- /dev/null
+++ b/lxd/main_forkmigrate.go
@@ -0,0 +1,52 @@
+package main
+
+import (
+	"fmt"
+	"os"
+	"strconv"
+
+	"gopkg.in/lxc/go-lxc.v2"
+)
+
+/*
+ * Similar to forkstart, this is called when lxd is invoked as:
+ *
+ *    lxd forkmigrate <container> <lxcpath> <path_to_config> <path_to_criu_images> <preserves_inodes>
+ *
+ * liblxc's restore() sets up the processes in such a way that the monitor ends
+ * up being a child of the process that calls it, in our case lxd. However, we
+ * really want the monitor to be daemonized, so we fork again. Additionally, we
+ * want to fork for the same reasons we do forkstart (i.e. reduced memory
+ * footprint when we fork tasks that will never free golang's memory, etc.)
+ */
+func cmdForkMigrate(args []string) error {
+	if len(args) != 6 {
+		return fmt.Errorf("Bad arguments %q", args)
+	}
+
+	name := args[1]
+	lxcpath := args[2]
+	configPath := args[3]
+	imagesDir := args[4]
+	preservesInodes, err := strconv.ParseBool(args[5])
+
+	c, err := lxc.NewContainer(name, lxcpath)
+	if err != nil {
+		return err
+	}
+
+	if err := c.LoadConfigFile(configPath); err != nil {
+		return err
+	}
+
+	/* see https://github.com/golang/go/issues/13155, startContainer, and dc3a229 */
+	os.Stdin.Close()
+	os.Stdout.Close()
+	os.Stderr.Close()
+
+	return c.Migrate(lxc.MIGRATE_RESTORE, lxc.MigrateOptions{
+		Directory:       imagesDir,
+		Verbose:         true,
+		PreservesInodes: preservesInodes,
+	})
+}
diff --git a/lxd/migrate.go b/lxd/migrate.go
index 6b125cf..e45fb15 100644
--- a/lxd/migrate.go
+++ b/lxd/migrate.go
@@ -12,7 +12,6 @@ import (
 	"net/url"
 	"os"
 	"path/filepath"
-	"strconv"
 	"strings"
 	"sync"
 
@@ -876,46 +875,3 @@ func (c *migrationSink) Do(migrateOp *operation) error {
 		}
 	}
 }
-
-/*
- * Similar to forkstart, this is called when lxd is invoked as:
- *
- *    lxd forkmigrate <container> <lxcpath> <path_to_config> <path_to_criu_images> <preserves_inodes>
- *
- * liblxc's restore() sets up the processes in such a way that the monitor ends
- * up being a child of the process that calls it, in our case lxd. However, we
- * really want the monitor to be daemonized, so we fork again. Additionally, we
- * want to fork for the same reasons we do forkstart (i.e. reduced memory
- * footprint when we fork tasks that will never free golang's memory, etc.)
- */
-func MigrateContainer(args []string) error {
-	if len(args) != 6 {
-		return fmt.Errorf("Bad arguments %q", args)
-	}
-
-	name := args[1]
-	lxcpath := args[2]
-	configPath := args[3]
-	imagesDir := args[4]
-	preservesInodes, err := strconv.ParseBool(args[5])
-
-	c, err := lxc.NewContainer(name, lxcpath)
-	if err != nil {
-		return err
-	}
-
-	if err := c.LoadConfigFile(configPath); err != nil {
-		return err
-	}
-
-	/* see https://github.com/golang/go/issues/13155, startContainer, and dc3a229 */
-	os.Stdin.Close()
-	os.Stdout.Close()
-	os.Stderr.Close()
-
-	return c.Migrate(lxc.MIGRATE_RESTORE, lxc.MigrateOptions{
-		Directory:       imagesDir,
-		Verbose:         true,
-		PreservesInodes: preservesInodes,
-	})
-}

From 346f9afbe5e203d14ec32ba61deb1b9701c18ea5 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Wed, 14 Dec 2016 15:11:54 -0500
Subject: [PATCH 07/15] main: Move forkstart to own file
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/containers.go     | 53 ---------------------------------------------
 lxd/main.go           |  2 +-
 lxd/main_forkstart.go | 59 +++++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 60 insertions(+), 54 deletions(-)
 create mode 100644 lxd/main_forkstart.go

diff --git a/lxd/containers.go b/lxd/containers.go
index 4d10289..d5305d3 100644
--- a/lxd/containers.go
+++ b/lxd/containers.go
@@ -1,16 +1,11 @@
 package main
 
 import (
-	"fmt"
-	"os"
 	"sort"
 	"strconv"
 	"sync"
-	"syscall"
 	"time"
 
-	"gopkg.in/lxc/go-lxc.v2"
-
 	"github.com/lxc/lxd/shared"
 
 	log "gopkg.in/inconshreveable/log15.v2"
@@ -211,51 +206,3 @@ func containerDeleteSnapshots(d *Daemon, cname string) error {
 
 	return nil
 }
-
-/*
- * This is called by lxd when called as "lxd forkstart <container>"
- * 'forkstart' is used instead of just 'start' in the hopes that people
- * do not accidentally type 'lxd start' instead of 'lxc start'
- */
-func startContainer(args []string) error {
-	if len(args) != 4 {
-		return fmt.Errorf("Bad arguments: %q", args)
-	}
-
-	name := args[1]
-	lxcpath := args[2]
-	configPath := args[3]
-
-	c, err := lxc.NewContainer(name, lxcpath)
-	if err != nil {
-		return fmt.Errorf("Error initializing container for start: %q", err)
-	}
-
-	err = c.LoadConfigFile(configPath)
-	if err != nil {
-		return fmt.Errorf("Error opening startup config file: %q", err)
-	}
-
-	/* due to https://github.com/golang/go/issues/13155 and the
-	 * CollectOutput call we make for the forkstart process, we need to
-	 * close our stdin/stdout/stderr here. Collecting some of the logs is
-	 * better than collecting no logs, though.
-	 */
-	os.Stdin.Close()
-	os.Stderr.Close()
-	os.Stdout.Close()
-
-	// Redirect stdout and stderr to a log file
-	logPath := shared.LogPath(name, "forkstart.log")
-	if shared.PathExists(logPath) {
-		os.Remove(logPath)
-	}
-
-	logFile, err := os.OpenFile(logPath, os.O_WRONLY|os.O_CREATE|os.O_SYNC, 0644)
-	if err == nil {
-		syscall.Dup3(int(logFile.Fd()), 1, 0)
-		syscall.Dup3(int(logFile.Fd()), 2, 0)
-	}
-
-	return c.Start()
-}
diff --git a/lxd/main.go b/lxd/main.go
index 9540870..5d7dfb9 100644
--- a/lxd/main.go
+++ b/lxd/main.go
@@ -229,7 +229,7 @@ func run() error {
 		case "forkmigrate":
 			return cmdForkMigrate(os.Args[1:])
 		case "forkstart":
-			return startContainer(os.Args[1:])
+			return cmdForkStart(os.Args[1:])
 		case "forkexec":
 			ret, err := cmdForkExec(os.Args[1:])
 			if err != nil {
diff --git a/lxd/main_forkstart.go b/lxd/main_forkstart.go
new file mode 100644
index 0000000..cc3d2ed
--- /dev/null
+++ b/lxd/main_forkstart.go
@@ -0,0 +1,59 @@
+package main
+
+import (
+	"fmt"
+	"os"
+	"syscall"
+
+	"gopkg.in/lxc/go-lxc.v2"
+
+	"github.com/lxc/lxd/shared"
+)
+
+/*
+ * This is called by lxd when called as "lxd forkstart <container>"
+ * 'forkstart' is used instead of just 'start' in the hopes that people
+ * do not accidentally type 'lxd start' instead of 'lxc start'
+ */
+func cmdForkStart(args []string) error {
+	if len(args) != 4 {
+		return fmt.Errorf("Bad arguments: %q", args)
+	}
+
+	name := args[1]
+	lxcpath := args[2]
+	configPath := args[3]
+
+	c, err := lxc.NewContainer(name, lxcpath)
+	if err != nil {
+		return fmt.Errorf("Error initializing container for start: %q", err)
+	}
+
+	err = c.LoadConfigFile(configPath)
+	if err != nil {
+		return fmt.Errorf("Error opening startup config file: %q", err)
+	}
+
+	/* due to https://github.com/golang/go/issues/13155 and the
+	 * CollectOutput call we make for the forkstart process, we need to
+	 * close our stdin/stdout/stderr here. Collecting some of the logs is
+	 * better than collecting no logs, though.
+	 */
+	os.Stdin.Close()
+	os.Stderr.Close()
+	os.Stdout.Close()
+
+	// Redirect stdout and stderr to a log file
+	logPath := shared.LogPath(name, "forkstart.log")
+	if shared.PathExists(logPath) {
+		os.Remove(logPath)
+	}
+
+	logFile, err := os.OpenFile(logPath, os.O_WRONLY|os.O_CREATE|os.O_SYNC, 0644)
+	if err == nil {
+		syscall.Dup3(int(logFile.Fd()), 1, 0)
+		syscall.Dup3(int(logFile.Fd()), 2, 0)
+	}
+
+	return c.Start()
+}

From 0f9e1f1d43a7fd43025ea5e5a04f410ada0e697a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Wed, 14 Dec 2016 15:13:30 -0500
Subject: [PATCH 08/15] main: Move import to own file
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/main.go        | 24 ------------------------
 lxd/main_import.go | 32 ++++++++++++++++++++++++++++++++
 2 files changed, 32 insertions(+), 24 deletions(-)
 create mode 100644 lxd/main_import.go

diff --git a/lxd/main.go b/lxd/main.go
index 5d7dfb9..023a14f 100644
--- a/lxd/main.go
+++ b/lxd/main.go
@@ -857,27 +857,3 @@ func cmdMigrateDumpSuccess(args []string) error {
 
 	return c.WaitForSuccess(args[1])
 }
-
-func cmdImport(args []string) error {
-	name := args[1]
-
-	c, err := lxd.NewClient(&lxd.DefaultConfig, "local")
-	if err != nil {
-		return err
-	}
-
-	url := fmt.Sprintf("%s/internal/containers?target=%s", c.BaseURL, name)
-
-	req, err := http.NewRequest("POST", url, nil)
-	if err != nil {
-		return err
-	}
-
-	raw, err := c.Http.Do(req)
-	_, err = lxd.HoistResponse(raw, lxd.Sync)
-	if err != nil {
-		return err
-	}
-
-	return nil
-}
diff --git a/lxd/main_import.go b/lxd/main_import.go
new file mode 100644
index 0000000..d7c6b7e
--- /dev/null
+++ b/lxd/main_import.go
@@ -0,0 +1,32 @@
+package main
+
+import (
+	"fmt"
+	"net/http"
+
+	"github.com/lxc/lxd"
+)
+
+func cmdImport(args []string) error {
+	name := args[1]
+
+	c, err := lxd.NewClient(&lxd.DefaultConfig, "local")
+	if err != nil {
+		return err
+	}
+
+	url := fmt.Sprintf("%s/internal/containers?target=%s", c.BaseURL, name)
+
+	req, err := http.NewRequest("POST", url, nil)
+	if err != nil {
+		return err
+	}
+
+	raw, err := c.Http.Do(req)
+	_, err = lxd.HoistResponse(raw, lxd.Sync)
+	if err != nil {
+		return err
+	}
+
+	return nil
+}

From 26c8281ec7c91b218bb39fe3d930fe034705e6ad Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Wed, 14 Dec 2016 15:14:52 -0500
Subject: [PATCH 09/15] main: Move init to own file
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/main.go      | 481 ------------------------------------------------------
 lxd/main_init.go | 490 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 490 insertions(+), 481 deletions(-)
 create mode 100644 lxd/main_init.go

diff --git a/lxd/main.go b/lxd/main.go
index 023a14f..002f520 100644
--- a/lxd/main.go
+++ b/lxd/main.go
@@ -1,20 +1,12 @@
 package main
 
 import (
-	"bufio"
 	"fmt"
 	"math/rand"
-	"net"
 	"net/http"
 	"os"
-	"os/exec"
-	"strconv"
-	"strings"
-	"syscall"
 	"time"
 
-	"golang.org/x/crypto/ssh/terminal"
-
 	"github.com/lxc/lxd"
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/gnuflag"
@@ -366,479 +358,6 @@ func cmdWaitReady() error {
 	return nil
 }
 
-func cmdInit() error {
-	var defaultPrivileged int // controls whether we set security.privileged=true
-	var storageBackend string // dir or zfs
-	var storageMode string    // existing, loop or device
-	var storageLoopSize int64 // Size in GB
-	var storageDevice string  // Path
-	var storagePool string    // pool name
-	var networkAddress string // Address
-	var networkPort int64     // Port
-	var trustPassword string  // Trust password
-	var imagesAutoUpdate bool // controls whether we set images.auto_update_interval to 0
-	var bridgeName string     // Bridge name
-	var bridgeIPv4 string     // IPv4 address
-	var bridgeIPv4Nat bool    // IPv4 address
-	var bridgeIPv6 string     // IPv6 address
-	var bridgeIPv6Nat bool    // IPv6 address
-
-	// Detect userns
-	defaultPrivileged = -1
-	runningInUserns = shared.RunningInUserNS()
-	imagesAutoUpdate = true
-
-	// Only root should run this
-	if os.Geteuid() != 0 {
-		return fmt.Errorf("This must be run as root")
-	}
-
-	backendsAvailable := []string{"dir"}
-	backendsSupported := []string{"dir", "zfs"}
-
-	// Detect zfs
-	out, err := exec.LookPath("zfs")
-	if err == nil && len(out) != 0 && !runningInUserns {
-		_ = loadModule("zfs")
-
-		err := shared.RunCommand("zpool", "list")
-		if err == nil {
-			backendsAvailable = append(backendsAvailable, "zfs")
-		}
-	}
-
-	reader := bufio.NewReader(os.Stdin)
-
-	askBool := func(question string, default_ string) bool {
-		for {
-			fmt.Printf(question)
-			input, _ := reader.ReadString('\n')
-			input = strings.TrimSuffix(input, "\n")
-			if input == "" {
-				input = default_
-			}
-			if shared.StringInSlice(strings.ToLower(input), []string{"yes", "y"}) {
-				return true
-			} else if shared.StringInSlice(strings.ToLower(input), []string{"no", "n"}) {
-				return false
-			}
-
-			fmt.Printf("Invalid input, try again.\n\n")
-		}
-	}
-
-	askChoice := func(question string, choices []string, default_ string) string {
-		for {
-			fmt.Printf(question)
-			input, _ := reader.ReadString('\n')
-			input = strings.TrimSuffix(input, "\n")
-			if input == "" {
-				input = default_
-			}
-			if shared.StringInSlice(input, choices) {
-				return input
-			}
-
-			fmt.Printf("Invalid input, try again.\n\n")
-		}
-	}
-
-	askInt := func(question string, min int64, max int64, default_ string) int64 {
-		for {
-			fmt.Printf(question)
-			input, _ := reader.ReadString('\n')
-			input = strings.TrimSuffix(input, "\n")
-			if input == "" {
-				input = default_
-			}
-			intInput, err := strconv.ParseInt(input, 10, 64)
-
-			if err == nil && (min == -1 || intInput >= min) && (max == -1 || intInput <= max) {
-				return intInput
-			}
-
-			fmt.Printf("Invalid input, try again.\n\n")
-		}
-	}
-
-	askString := func(question string, default_ string, validate func(string) error) string {
-		for {
-			fmt.Printf(question)
-			input, _ := reader.ReadString('\n')
-			input = strings.TrimSuffix(input, "\n")
-			if input == "" {
-				input = default_
-			}
-			if validate != nil {
-				result := validate(input)
-				if result != nil {
-					fmt.Printf("Invalid input: %s\n\n", result)
-					continue
-				}
-			}
-			if len(input) != 0 {
-				return input
-			}
-
-			fmt.Printf("Invalid input, try again.\n\n")
-		}
-	}
-
-	askPassword := func(question string) string {
-		for {
-			fmt.Printf(question)
-			pwd, _ := terminal.ReadPassword(0)
-			fmt.Printf("\n")
-			inFirst := string(pwd)
-			inFirst = strings.TrimSuffix(inFirst, "\n")
-
-			fmt.Printf("Again: ")
-			pwd, _ = terminal.ReadPassword(0)
-			fmt.Printf("\n")
-			inSecond := string(pwd)
-			inSecond = strings.TrimSuffix(inSecond, "\n")
-
-			if inFirst == inSecond {
-				return inFirst
-			}
-
-			fmt.Printf("Invalid input, try again.\n\n")
-		}
-	}
-
-	// Confirm that LXD is online
-	c, err := lxd.NewClient(&lxd.DefaultConfig, "local")
-	if err != nil {
-		return fmt.Errorf("Unable to talk to LXD: %s", err)
-	}
-
-	// Check that we have no containers or images in the store
-	containers, err := c.ListContainers()
-	if err != nil {
-		return fmt.Errorf("Unable to list the LXD containers: %s", err)
-	}
-
-	images, err := c.ListImages()
-	if err != nil {
-		return fmt.Errorf("Unable to list the LXD images: %s", err)
-	}
-
-	if len(containers) > 0 || len(images) > 0 {
-		return fmt.Errorf("You have existing containers or images. lxd init requires an empty LXD.")
-	}
-
-	if *argAuto {
-		if *argStorageBackend == "" {
-			*argStorageBackend = "dir"
-		}
-
-		// Do a bunch of sanity checks
-		if !shared.StringInSlice(*argStorageBackend, backendsSupported) {
-			return fmt.Errorf("The requested backend '%s' isn't supported by lxd init.", *argStorageBackend)
-		}
-
-		if !shared.StringInSlice(*argStorageBackend, backendsAvailable) {
-			return fmt.Errorf("The requested backend '%s' isn't available on your system (missing tools).", *argStorageBackend)
-		}
-
-		if *argStorageBackend == "dir" {
-			if *argStorageCreateLoop != -1 || *argStorageCreateDevice != "" || *argStoragePool != "" {
-				return fmt.Errorf("None of --storage-pool, --storage-create-device or --storage-create-loop may be used with the 'dir' backend.")
-			}
-		}
-
-		if *argStorageBackend == "zfs" {
-			if *argStorageCreateLoop != -1 && *argStorageCreateDevice != "" {
-				return fmt.Errorf("Only one of --storage-create-device or --storage-create-loop can be specified with the 'zfs' backend.")
-			}
-
-			if *argStoragePool == "" {
-				return fmt.Errorf("--storage-pool must be specified with the 'zfs' backend.")
-			}
-		}
-
-		if *argNetworkAddress == "" {
-			if *argNetworkPort != -1 {
-				return fmt.Errorf("--network-port cannot be used without --network-address.")
-			}
-			if *argTrustPassword != "" {
-				return fmt.Errorf("--trust-password cannot be used without --network-address.")
-			}
-		}
-
-		// Set the local variables
-		if *argStorageCreateDevice != "" {
-			storageMode = "device"
-		} else if *argStorageCreateLoop != -1 {
-			storageMode = "loop"
-		} else {
-			storageMode = "existing"
-		}
-
-		storageBackend = *argStorageBackend
-		storageLoopSize = *argStorageCreateLoop
-		storageDevice = *argStorageCreateDevice
-		storagePool = *argStoragePool
-		networkAddress = *argNetworkAddress
-		networkPort = *argNetworkPort
-		trustPassword = *argTrustPassword
-	} else {
-		if *argStorageBackend != "" || *argStorageCreateDevice != "" || *argStorageCreateLoop != -1 || *argStoragePool != "" || *argNetworkAddress != "" || *argNetworkPort != -1 || *argTrustPassword != "" {
-			return fmt.Errorf("Init configuration is only valid with --auto")
-		}
-
-		defaultStorage := "dir"
-		if shared.StringInSlice("zfs", backendsAvailable) {
-			defaultStorage = "zfs"
-		}
-
-		storageBackend = askChoice(fmt.Sprintf("Name of the storage backend to use (dir or zfs) [default=%s]: ", defaultStorage), backendsSupported, defaultStorage)
-
-		if !shared.StringInSlice(storageBackend, backendsSupported) {
-			return fmt.Errorf("The requested backend '%s' isn't supported by lxd init.", storageBackend)
-		}
-
-		if !shared.StringInSlice(storageBackend, backendsAvailable) {
-			return fmt.Errorf("The requested backend '%s' isn't available on your system (missing tools).", storageBackend)
-		}
-
-		if storageBackend == "zfs" {
-			if askBool("Create a new ZFS pool (yes/no) [default=yes]? ", "yes") {
-				storagePool = askString("Name of the new ZFS pool [default=lxd]: ", "lxd", nil)
-				if askBool("Would you like to use an existing block device (yes/no) [default=no]? ", "no") {
-					deviceExists := func(path string) error {
-						if !shared.IsBlockdevPath(path) {
-							return fmt.Errorf("'%s' is not a block device", path)
-						}
-						return nil
-					}
-					storageDevice = askString("Path to the existing block device: ", "", deviceExists)
-					storageMode = "device"
-				} else {
-					st := syscall.Statfs_t{}
-					err := syscall.Statfs(shared.VarPath(), &st)
-					if err != nil {
-						return fmt.Errorf("couldn't statfs %s: %s", shared.VarPath(), err)
-					}
-
-					/* choose 15 GB < x < 100GB, where x is 20% of the disk size */
-					def := uint64(st.Frsize) * st.Blocks / (1024 * 1024 * 1024) / 5
-					if def > 100 {
-						def = 100
-					}
-					if def < 15 {
-						def = 15
-					}
-
-					q := fmt.Sprintf("Size in GB of the new loop device (1GB minimum) [default=%d]: ", def)
-					storageLoopSize = askInt(q, 1, -1, fmt.Sprintf("%d", def))
-					storageMode = "loop"
-				}
-			} else {
-				storagePool = askString("Name of the existing ZFS pool or dataset: ", "", nil)
-				storageMode = "existing"
-			}
-		}
-
-		if runningInUserns {
-			fmt.Printf(`
-We detected that you are running inside an unprivileged container.
-This means that unless you manually configured your host otherwise,
-you will not have enough uid and gid to allocate to your containers.
-
-LXD can re-use your container's own allocation to avoid the problem.
-Doing so makes your nested containers slightly less safe as they could
-in theory attack their parent container and gain more privileges than
-they otherwise would.
-
-`)
-			if askBool("Would you like to have your containers share their parent's allocation (yes/no) [default=yes]? ", "yes") {
-				defaultPrivileged = 1
-			} else {
-				defaultPrivileged = 0
-			}
-		}
-
-		if askBool("Would you like LXD to be available over the network (yes/no) [default=no]? ", "no") {
-			isIPAddress := func(s string) error {
-				if s != "all" && net.ParseIP(s) == nil {
-					return fmt.Errorf("'%s' is not an IP address", s)
-				}
-				return nil
-			}
-
-			networkAddress = askString("Address to bind LXD to (not including port) [default=all]: ", "all", isIPAddress)
-			if networkAddress == "all" {
-				networkAddress = "::"
-			}
-
-			if net.ParseIP(networkAddress).To4() == nil {
-				networkAddress = fmt.Sprintf("[%s]", networkAddress)
-			}
-			networkPort = askInt("Port to bind LXD to [default=8443]: ", 1, 65535, "8443")
-			trustPassword = askPassword("Trust password for new clients: ")
-		}
-
-		if !askBool("Would you like stale cached images to be updated automatically (yes/no) [default=yes]? ", "yes") {
-			imagesAutoUpdate = false
-		}
-
-		if askBool("Would you like to create a new network bridge (yes/no) [default=yes]? ", "yes") {
-			bridgeName = askString("What should the new bridge be called [default=lxdbr0]? ", "lxdbr0", networkValidName)
-			bridgeIPv4 = askString("What IPv4 subnet should be used (CIDR notation, “auto” or “none”) [default=auto]? ", "auto", func(value string) error {
-				if shared.StringInSlice(value, []string{"auto", "none"}) {
-					return nil
-				}
-				return networkValidAddressCIDRV4(value)
-			})
-
-			if !shared.StringInSlice(bridgeIPv4, []string{"auto", "none"}) {
-				bridgeIPv4Nat = askBool("Would you like LXD to NAT IPv4 traffic on your bridge? [default=yes]? ", "yes")
-			}
-
-			bridgeIPv6 = askString("What IPv6 subnet should be used (CIDR notation, “auto” or “none”) [default=auto]? ", "auto", func(value string) error {
-				if shared.StringInSlice(value, []string{"auto", "none"}) {
-					return nil
-				}
-				return networkValidAddressCIDRV6(value)
-			})
-
-			if !shared.StringInSlice(bridgeIPv6, []string{"auto", "none"}) {
-				bridgeIPv6Nat = askBool("Would you like LXD to NAT IPv6 traffic on your bridge? [default=yes]? ", "yes")
-			}
-		}
-	}
-
-	if !shared.StringInSlice(storageBackend, []string{"dir", "zfs"}) {
-		return fmt.Errorf("Invalid storage backend: %s", storageBackend)
-	}
-
-	// Unset all storage keys, core.https_address and core.trust_password
-	for _, key := range []string{"storage.zfs_pool_name", "core.https_address", "core.trust_password"} {
-		_, err = c.SetServerConfig(key, "")
-		if err != nil {
-			return err
-		}
-	}
-
-	// Destroy any existing loop device
-	for _, file := range []string{"zfs.img"} {
-		os.Remove(shared.VarPath(file))
-	}
-
-	if storageBackend == "zfs" {
-		if storageMode == "loop" {
-			storageDevice = shared.VarPath("zfs.img")
-			f, err := os.Create(storageDevice)
-			if err != nil {
-				return fmt.Errorf("Failed to open %s: %s", storageDevice, err)
-			}
-
-			err = f.Chmod(0600)
-			if err != nil {
-				return fmt.Errorf("Failed to chmod %s: %s", storageDevice, err)
-			}
-
-			err = f.Truncate(int64(storageLoopSize * 1024 * 1024 * 1024))
-			if err != nil {
-				return fmt.Errorf("Failed to create sparse file %s: %s", storageDevice, err)
-			}
-
-			err = f.Close()
-			if err != nil {
-				return fmt.Errorf("Failed to close %s: %s", storageDevice, err)
-			}
-		}
-
-		if shared.StringInSlice(storageMode, []string{"loop", "device"}) {
-			output, err := exec.Command(
-				"zpool",
-				"create", storagePool, storageDevice,
-				"-f", "-m", "none", "-O", "compression=on").CombinedOutput()
-			if err != nil {
-				return fmt.Errorf("Failed to create the ZFS pool: %s", output)
-			}
-		}
-
-		// Configure LXD to use the pool
-		_, err = c.SetServerConfig("storage.zfs_pool_name", storagePool)
-		if err != nil {
-			return err
-		}
-	}
-
-	if defaultPrivileged == 0 {
-		err = c.SetProfileConfigItem("default", "security.privileged", "")
-		if err != nil {
-			return err
-		}
-	} else if defaultPrivileged == 1 {
-		err = c.SetProfileConfigItem("default", "security.privileged", "true")
-		if err != nil {
-		}
-	}
-
-	if imagesAutoUpdate {
-		ss, err := c.ServerStatus()
-		if err != nil {
-			return err
-		}
-		if val, ok := ss.Config["images.auto_update_interval"]; ok && val == "0" {
-			_, err = c.SetServerConfig("images.auto_update_interval", "")
-			if err != nil {
-				return err
-			}
-		}
-	} else {
-		_, err = c.SetServerConfig("images.auto_update_interval", "0")
-		if err != nil {
-			return err
-		}
-	}
-
-	if networkAddress != "" {
-		_, err = c.SetServerConfig("core.https_address", fmt.Sprintf("%s:%d", networkAddress, networkPort))
-		if err != nil {
-			return err
-		}
-
-		if trustPassword != "" {
-			_, err = c.SetServerConfig("core.trust_password", trustPassword)
-			if err != nil {
-				return err
-			}
-		}
-	}
-
-	if bridgeName != "" {
-		bridgeConfig := map[string]string{}
-		bridgeConfig["ipv4.address"] = bridgeIPv4
-		bridgeConfig["ipv6.address"] = bridgeIPv6
-
-		if bridgeIPv4Nat {
-			bridgeConfig["ipv4.nat"] = "true"
-		}
-
-		if bridgeIPv6Nat {
-			bridgeConfig["ipv6.nat"] = "true"
-		}
-
-		err = c.NetworkCreate(bridgeName, bridgeConfig)
-		if err != nil {
-			return err
-		}
-
-		props := []string{"nictype=bridged", fmt.Sprintf("parent=%s", bridgeName)}
-		_, err = c.ProfileDeviceAdd("default", "eth0", "nic", props)
-		if err != nil {
-			return err
-		}
-	}
-
-	fmt.Printf("LXD has been successfully configured.\n")
-	return nil
-}
-
 func cmdMigrateDumpSuccess(args []string) error {
 	if len(args) != 3 {
 		return fmt.Errorf("bad migrate dump success args %s", args)
diff --git a/lxd/main_init.go b/lxd/main_init.go
new file mode 100644
index 0000000..2d621d8
--- /dev/null
+++ b/lxd/main_init.go
@@ -0,0 +1,490 @@
+package main
+
+import (
+	"bufio"
+	"fmt"
+	"net"
+	"os"
+	"os/exec"
+	"strconv"
+	"strings"
+	"syscall"
+
+	"golang.org/x/crypto/ssh/terminal"
+
+	"github.com/lxc/lxd"
+	"github.com/lxc/lxd/shared"
+)
+
+func cmdInit() error {
+	var defaultPrivileged int // controls whether we set security.privileged=true
+	var storageBackend string // dir or zfs
+	var storageMode string    // existing, loop or device
+	var storageLoopSize int64 // Size in GB
+	var storageDevice string  // Path
+	var storagePool string    // pool name
+	var networkAddress string // Address
+	var networkPort int64     // Port
+	var trustPassword string  // Trust password
+	var imagesAutoUpdate bool // controls whether we set images.auto_update_interval to 0
+	var bridgeName string     // Bridge name
+	var bridgeIPv4 string     // IPv4 address
+	var bridgeIPv4Nat bool    // IPv4 address
+	var bridgeIPv6 string     // IPv6 address
+	var bridgeIPv6Nat bool    // IPv6 address
+
+	// Detect userns
+	defaultPrivileged = -1
+	runningInUserns = shared.RunningInUserNS()
+	imagesAutoUpdate = true
+
+	// Only root should run this
+	if os.Geteuid() != 0 {
+		return fmt.Errorf("This must be run as root")
+	}
+
+	backendsAvailable := []string{"dir"}
+	backendsSupported := []string{"dir", "zfs"}
+
+	// Detect zfs
+	out, err := exec.LookPath("zfs")
+	if err == nil && len(out) != 0 && !runningInUserns {
+		_ = loadModule("zfs")
+
+		err := shared.RunCommand("zpool", "list")
+		if err == nil {
+			backendsAvailable = append(backendsAvailable, "zfs")
+		}
+	}
+
+	reader := bufio.NewReader(os.Stdin)
+
+	askBool := func(question string, default_ string) bool {
+		for {
+			fmt.Printf(question)
+			input, _ := reader.ReadString('\n')
+			input = strings.TrimSuffix(input, "\n")
+			if input == "" {
+				input = default_
+			}
+			if shared.StringInSlice(strings.ToLower(input), []string{"yes", "y"}) {
+				return true
+			} else if shared.StringInSlice(strings.ToLower(input), []string{"no", "n"}) {
+				return false
+			}
+
+			fmt.Printf("Invalid input, try again.\n\n")
+		}
+	}
+
+	askChoice := func(question string, choices []string, default_ string) string {
+		for {
+			fmt.Printf(question)
+			input, _ := reader.ReadString('\n')
+			input = strings.TrimSuffix(input, "\n")
+			if input == "" {
+				input = default_
+			}
+			if shared.StringInSlice(input, choices) {
+				return input
+			}
+
+			fmt.Printf("Invalid input, try again.\n\n")
+		}
+	}
+
+	askInt := func(question string, min int64, max int64, default_ string) int64 {
+		for {
+			fmt.Printf(question)
+			input, _ := reader.ReadString('\n')
+			input = strings.TrimSuffix(input, "\n")
+			if input == "" {
+				input = default_
+			}
+			intInput, err := strconv.ParseInt(input, 10, 64)
+
+			if err == nil && (min == -1 || intInput >= min) && (max == -1 || intInput <= max) {
+				return intInput
+			}
+
+			fmt.Printf("Invalid input, try again.\n\n")
+		}
+	}
+
+	askString := func(question string, default_ string, validate func(string) error) string {
+		for {
+			fmt.Printf(question)
+			input, _ := reader.ReadString('\n')
+			input = strings.TrimSuffix(input, "\n")
+			if input == "" {
+				input = default_
+			}
+			if validate != nil {
+				result := validate(input)
+				if result != nil {
+					fmt.Printf("Invalid input: %s\n\n", result)
+					continue
+				}
+			}
+			if len(input) != 0 {
+				return input
+			}
+
+			fmt.Printf("Invalid input, try again.\n\n")
+		}
+	}
+
+	askPassword := func(question string) string {
+		for {
+			fmt.Printf(question)
+			pwd, _ := terminal.ReadPassword(0)
+			fmt.Printf("\n")
+			inFirst := string(pwd)
+			inFirst = strings.TrimSuffix(inFirst, "\n")
+
+			fmt.Printf("Again: ")
+			pwd, _ = terminal.ReadPassword(0)
+			fmt.Printf("\n")
+			inSecond := string(pwd)
+			inSecond = strings.TrimSuffix(inSecond, "\n")
+
+			if inFirst == inSecond {
+				return inFirst
+			}
+
+			fmt.Printf("Invalid input, try again.\n\n")
+		}
+	}
+
+	// Confirm that LXD is online
+	c, err := lxd.NewClient(&lxd.DefaultConfig, "local")
+	if err != nil {
+		return fmt.Errorf("Unable to talk to LXD: %s", err)
+	}
+
+	// Check that we have no containers or images in the store
+	containers, err := c.ListContainers()
+	if err != nil {
+		return fmt.Errorf("Unable to list the LXD containers: %s", err)
+	}
+
+	images, err := c.ListImages()
+	if err != nil {
+		return fmt.Errorf("Unable to list the LXD images: %s", err)
+	}
+
+	if len(containers) > 0 || len(images) > 0 {
+		return fmt.Errorf("You have existing containers or images. lxd init requires an empty LXD.")
+	}
+
+	if *argAuto {
+		if *argStorageBackend == "" {
+			*argStorageBackend = "dir"
+		}
+
+		// Do a bunch of sanity checks
+		if !shared.StringInSlice(*argStorageBackend, backendsSupported) {
+			return fmt.Errorf("The requested backend '%s' isn't supported by lxd init.", *argStorageBackend)
+		}
+
+		if !shared.StringInSlice(*argStorageBackend, backendsAvailable) {
+			return fmt.Errorf("The requested backend '%s' isn't available on your system (missing tools).", *argStorageBackend)
+		}
+
+		if *argStorageBackend == "dir" {
+			if *argStorageCreateLoop != -1 || *argStorageCreateDevice != "" || *argStoragePool != "" {
+				return fmt.Errorf("None of --storage-pool, --storage-create-device or --storage-create-loop may be used with the 'dir' backend.")
+			}
+		}
+
+		if *argStorageBackend == "zfs" {
+			if *argStorageCreateLoop != -1 && *argStorageCreateDevice != "" {
+				return fmt.Errorf("Only one of --storage-create-device or --storage-create-loop can be specified with the 'zfs' backend.")
+			}
+
+			if *argStoragePool == "" {
+				return fmt.Errorf("--storage-pool must be specified with the 'zfs' backend.")
+			}
+		}
+
+		if *argNetworkAddress == "" {
+			if *argNetworkPort != -1 {
+				return fmt.Errorf("--network-port cannot be used without --network-address.")
+			}
+			if *argTrustPassword != "" {
+				return fmt.Errorf("--trust-password cannot be used without --network-address.")
+			}
+		}
+
+		// Set the local variables
+		if *argStorageCreateDevice != "" {
+			storageMode = "device"
+		} else if *argStorageCreateLoop != -1 {
+			storageMode = "loop"
+		} else {
+			storageMode = "existing"
+		}
+
+		storageBackend = *argStorageBackend
+		storageLoopSize = *argStorageCreateLoop
+		storageDevice = *argStorageCreateDevice
+		storagePool = *argStoragePool
+		networkAddress = *argNetworkAddress
+		networkPort = *argNetworkPort
+		trustPassword = *argTrustPassword
+	} else {
+		if *argStorageBackend != "" || *argStorageCreateDevice != "" || *argStorageCreateLoop != -1 || *argStoragePool != "" || *argNetworkAddress != "" || *argNetworkPort != -1 || *argTrustPassword != "" {
+			return fmt.Errorf("Init configuration is only valid with --auto")
+		}
+
+		defaultStorage := "dir"
+		if shared.StringInSlice("zfs", backendsAvailable) {
+			defaultStorage = "zfs"
+		}
+
+		storageBackend = askChoice(fmt.Sprintf("Name of the storage backend to use (dir or zfs) [default=%s]: ", defaultStorage), backendsSupported, defaultStorage)
+
+		if !shared.StringInSlice(storageBackend, backendsSupported) {
+			return fmt.Errorf("The requested backend '%s' isn't supported by lxd init.", storageBackend)
+		}
+
+		if !shared.StringInSlice(storageBackend, backendsAvailable) {
+			return fmt.Errorf("The requested backend '%s' isn't available on your system (missing tools).", storageBackend)
+		}
+
+		if storageBackend == "zfs" {
+			if askBool("Create a new ZFS pool (yes/no) [default=yes]? ", "yes") {
+				storagePool = askString("Name of the new ZFS pool [default=lxd]: ", "lxd", nil)
+				if askBool("Would you like to use an existing block device (yes/no) [default=no]? ", "no") {
+					deviceExists := func(path string) error {
+						if !shared.IsBlockdevPath(path) {
+							return fmt.Errorf("'%s' is not a block device", path)
+						}
+						return nil
+					}
+					storageDevice = askString("Path to the existing block device: ", "", deviceExists)
+					storageMode = "device"
+				} else {
+					st := syscall.Statfs_t{}
+					err := syscall.Statfs(shared.VarPath(), &st)
+					if err != nil {
+						return fmt.Errorf("couldn't statfs %s: %s", shared.VarPath(), err)
+					}
+
+					/* choose 15 GB < x < 100GB, where x is 20% of the disk size */
+					def := uint64(st.Frsize) * st.Blocks / (1024 * 1024 * 1024) / 5
+					if def > 100 {
+						def = 100
+					}
+					if def < 15 {
+						def = 15
+					}
+
+					q := fmt.Sprintf("Size in GB of the new loop device (1GB minimum) [default=%d]: ", def)
+					storageLoopSize = askInt(q, 1, -1, fmt.Sprintf("%d", def))
+					storageMode = "loop"
+				}
+			} else {
+				storagePool = askString("Name of the existing ZFS pool or dataset: ", "", nil)
+				storageMode = "existing"
+			}
+		}
+
+		if runningInUserns {
+			fmt.Printf(`
+We detected that you are running inside an unprivileged container.
+This means that unless you manually configured your host otherwise,
+you will not have enough uid and gid to allocate to your containers.
+
+LXD can re-use your container's own allocation to avoid the problem.
+Doing so makes your nested containers slightly less safe as they could
+in theory attack their parent container and gain more privileges than
+they otherwise would.
+
+`)
+			if askBool("Would you like to have your containers share their parent's allocation (yes/no) [default=yes]? ", "yes") {
+				defaultPrivileged = 1
+			} else {
+				defaultPrivileged = 0
+			}
+		}
+
+		if askBool("Would you like LXD to be available over the network (yes/no) [default=no]? ", "no") {
+			isIPAddress := func(s string) error {
+				if s != "all" && net.ParseIP(s) == nil {
+					return fmt.Errorf("'%s' is not an IP address", s)
+				}
+				return nil
+			}
+
+			networkAddress = askString("Address to bind LXD to (not including port) [default=all]: ", "all", isIPAddress)
+			if networkAddress == "all" {
+				networkAddress = "::"
+			}
+
+			if net.ParseIP(networkAddress).To4() == nil {
+				networkAddress = fmt.Sprintf("[%s]", networkAddress)
+			}
+			networkPort = askInt("Port to bind LXD to [default=8443]: ", 1, 65535, "8443")
+			trustPassword = askPassword("Trust password for new clients: ")
+		}
+
+		if !askBool("Would you like stale cached images to be updated automatically (yes/no) [default=yes]? ", "yes") {
+			imagesAutoUpdate = false
+		}
+
+		if askBool("Would you like to create a new network bridge (yes/no) [default=yes]? ", "yes") {
+			bridgeName = askString("What should the new bridge be called [default=lxdbr0]? ", "lxdbr0", networkValidName)
+			bridgeIPv4 = askString("What IPv4 subnet should be used (CIDR notation, “auto” or “none”) [default=auto]? ", "auto", func(value string) error {
+				if shared.StringInSlice(value, []string{"auto", "none"}) {
+					return nil
+				}
+				return networkValidAddressCIDRV4(value)
+			})
+
+			if !shared.StringInSlice(bridgeIPv4, []string{"auto", "none"}) {
+				bridgeIPv4Nat = askBool("Would you like LXD to NAT IPv4 traffic on your bridge? [default=yes]? ", "yes")
+			}
+
+			bridgeIPv6 = askString("What IPv6 subnet should be used (CIDR notation, “auto” or “none”) [default=auto]? ", "auto", func(value string) error {
+				if shared.StringInSlice(value, []string{"auto", "none"}) {
+					return nil
+				}
+				return networkValidAddressCIDRV6(value)
+			})
+
+			if !shared.StringInSlice(bridgeIPv6, []string{"auto", "none"}) {
+				bridgeIPv6Nat = askBool("Would you like LXD to NAT IPv6 traffic on your bridge? [default=yes]? ", "yes")
+			}
+		}
+	}
+
+	if !shared.StringInSlice(storageBackend, []string{"dir", "zfs"}) {
+		return fmt.Errorf("Invalid storage backend: %s", storageBackend)
+	}
+
+	// Unset all storage keys, core.https_address and core.trust_password
+	for _, key := range []string{"storage.zfs_pool_name", "core.https_address", "core.trust_password"} {
+		_, err = c.SetServerConfig(key, "")
+		if err != nil {
+			return err
+		}
+	}
+
+	// Destroy any existing loop device
+	for _, file := range []string{"zfs.img"} {
+		os.Remove(shared.VarPath(file))
+	}
+
+	if storageBackend == "zfs" {
+		if storageMode == "loop" {
+			storageDevice = shared.VarPath("zfs.img")
+			f, err := os.Create(storageDevice)
+			if err != nil {
+				return fmt.Errorf("Failed to open %s: %s", storageDevice, err)
+			}
+
+			err = f.Chmod(0600)
+			if err != nil {
+				return fmt.Errorf("Failed to chmod %s: %s", storageDevice, err)
+			}
+
+			err = f.Truncate(int64(storageLoopSize * 1024 * 1024 * 1024))
+			if err != nil {
+				return fmt.Errorf("Failed to create sparse file %s: %s", storageDevice, err)
+			}
+
+			err = f.Close()
+			if err != nil {
+				return fmt.Errorf("Failed to close %s: %s", storageDevice, err)
+			}
+		}
+
+		if shared.StringInSlice(storageMode, []string{"loop", "device"}) {
+			output, err := exec.Command(
+				"zpool",
+				"create", storagePool, storageDevice,
+				"-f", "-m", "none", "-O", "compression=on").CombinedOutput()
+			if err != nil {
+				return fmt.Errorf("Failed to create the ZFS pool: %s", output)
+			}
+		}
+
+		// Configure LXD to use the pool
+		_, err = c.SetServerConfig("storage.zfs_pool_name", storagePool)
+		if err != nil {
+			return err
+		}
+	}
+
+	if defaultPrivileged == 0 {
+		err = c.SetProfileConfigItem("default", "security.privileged", "")
+		if err != nil {
+			return err
+		}
+	} else if defaultPrivileged == 1 {
+		err = c.SetProfileConfigItem("default", "security.privileged", "true")
+		if err != nil {
+		}
+	}
+
+	if imagesAutoUpdate {
+		ss, err := c.ServerStatus()
+		if err != nil {
+			return err
+		}
+		if val, ok := ss.Config["images.auto_update_interval"]; ok && val == "0" {
+			_, err = c.SetServerConfig("images.auto_update_interval", "")
+			if err != nil {
+				return err
+			}
+		}
+	} else {
+		_, err = c.SetServerConfig("images.auto_update_interval", "0")
+		if err != nil {
+			return err
+		}
+	}
+
+	if networkAddress != "" {
+		_, err = c.SetServerConfig("core.https_address", fmt.Sprintf("%s:%d", networkAddress, networkPort))
+		if err != nil {
+			return err
+		}
+
+		if trustPassword != "" {
+			_, err = c.SetServerConfig("core.trust_password", trustPassword)
+			if err != nil {
+				return err
+			}
+		}
+	}
+
+	if bridgeName != "" {
+		bridgeConfig := map[string]string{}
+		bridgeConfig["ipv4.address"] = bridgeIPv4
+		bridgeConfig["ipv6.address"] = bridgeIPv6
+
+		if bridgeIPv4Nat {
+			bridgeConfig["ipv4.nat"] = "true"
+		}
+
+		if bridgeIPv6Nat {
+			bridgeConfig["ipv6.nat"] = "true"
+		}
+
+		err = c.NetworkCreate(bridgeName, bridgeConfig)
+		if err != nil {
+			return err
+		}
+
+		props := []string{"nictype=bridged", fmt.Sprintf("parent=%s", bridgeName)}
+		_, err = c.ProfileDeviceAdd("default", "eth0", "nic", props)
+		if err != nil {
+			return err
+		}
+	}
+
+	fmt.Printf("LXD has been successfully configured.\n")
+	return nil
+}

From befdc2b40dfd576c260f382e9796b52e5a22da78 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Wed, 14 Dec 2016 15:15:27 -0500
Subject: [PATCH 10/15] main: Move migratedumpsuccess to own file
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/main.go                    | 19 -------------------
 lxd/main_migratedumpsuccess.go | 26 ++++++++++++++++++++++++++
 2 files changed, 26 insertions(+), 19 deletions(-)
 create mode 100644 lxd/main_migratedumpsuccess.go

diff --git a/lxd/main.go b/lxd/main.go
index 002f520..818cb5c 100644
--- a/lxd/main.go
+++ b/lxd/main.go
@@ -357,22 +357,3 @@ func cmdWaitReady() error {
 
 	return nil
 }
-
-func cmdMigrateDumpSuccess(args []string) error {
-	if len(args) != 3 {
-		return fmt.Errorf("bad migrate dump success args %s", args)
-	}
-
-	c, err := lxd.NewClient(&lxd.DefaultConfig, "local")
-	if err != nil {
-		return err
-	}
-
-	conn, err := c.Websocket(args[1], args[2])
-	if err != nil {
-		return err
-	}
-	conn.Close()
-
-	return c.WaitForSuccess(args[1])
-}
diff --git a/lxd/main_migratedumpsuccess.go b/lxd/main_migratedumpsuccess.go
new file mode 100644
index 0000000..c4c8fb1
--- /dev/null
+++ b/lxd/main_migratedumpsuccess.go
@@ -0,0 +1,26 @@
+package main
+
+import (
+	"fmt"
+
+	"github.com/lxc/lxd"
+)
+
+func cmdMigrateDumpSuccess(args []string) error {
+	if len(args) != 3 {
+		return fmt.Errorf("bad migrate dump success args %s", args)
+	}
+
+	c, err := lxd.NewClient(&lxd.DefaultConfig, "local")
+	if err != nil {
+		return err
+	}
+
+	conn, err := c.Websocket(args[1], args[2])
+	if err != nil {
+		return err
+	}
+	conn.Close()
+
+	return c.WaitForSuccess(args[1])
+}

From 942efea65edc39a2508d5b7a79d51026696fc0d0 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Wed, 14 Dec 2016 15:17:42 -0500
Subject: [PATCH 11/15] main: Move netcat to own file
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/main.go        |  2 +-
 lxd/main_netcat.go | 49 +++++++++++++++++++++++++++++++++++++++++++++++++
 lxd/rsync.go       | 41 -----------------------------------------
 3 files changed, 50 insertions(+), 42 deletions(-)
 create mode 100644 lxd/main_netcat.go

diff --git a/lxd/main.go b/lxd/main.go
index 818cb5c..478dbef 100644
--- a/lxd/main.go
+++ b/lxd/main.go
@@ -229,7 +229,7 @@ func run() error {
 			}
 			os.Exit(ret)
 		case "netcat":
-			return Netcat(os.Args[1:])
+			return cmdNetcat(os.Args[1:])
 		case "migratedumpsuccess":
 			return cmdMigrateDumpSuccess(os.Args[1:])
 		}
diff --git a/lxd/main_netcat.go b/lxd/main_netcat.go
new file mode 100644
index 0000000..f5be1b3
--- /dev/null
+++ b/lxd/main_netcat.go
@@ -0,0 +1,49 @@
+package main
+
+import (
+	"fmt"
+	"io"
+	"net"
+	"os"
+	"sync"
+)
+
+// Netcat is called with:
+//
+//    lxd netcat /path/to/unix/socket
+//
+// and does unbuffered netcatting of to socket to stdin/stdout. Any arguments
+// after the path to the unix socket are ignored, so that this can be passed
+// directly to rsync as the sync command.
+func cmdNetcat(args []string) error {
+	if len(args) < 2 {
+		return fmt.Errorf("Bad arguments %q", args)
+	}
+
+	uAddr, err := net.ResolveUnixAddr("unix", args[1])
+	if err != nil {
+		return err
+	}
+
+	conn, err := net.DialUnix("unix", nil, uAddr)
+	if err != nil {
+		return err
+	}
+
+	wg := sync.WaitGroup{}
+	wg.Add(1)
+
+	go func() {
+		io.Copy(os.Stdout, conn)
+		conn.Close()
+		wg.Done()
+	}()
+
+	go func() {
+		io.Copy(conn, os.Stdin)
+	}()
+
+	wg.Wait()
+
+	return nil
+}
diff --git a/lxd/rsync.go b/lxd/rsync.go
index 5be1354..f6a5ff5 100644
--- a/lxd/rsync.go
+++ b/lxd/rsync.go
@@ -7,7 +7,6 @@ import (
 	"net"
 	"os"
 	"os/exec"
-	"sync"
 
 	"github.com/gorilla/websocket"
 
@@ -174,43 +173,3 @@ func RsyncRecv(path string, conn *websocket.Conn, writeWrapper func(io.WriteClos
 
 	return err
 }
-
-// Netcat is called with:
-//
-//    lxd netcat /path/to/unix/socket
-//
-// and does unbuffered netcatting of to socket to stdin/stdout. Any arguments
-// after the path to the unix socket are ignored, so that this can be passed
-// directly to rsync as the sync command.
-func Netcat(args []string) error {
-	if len(args) < 2 {
-		return fmt.Errorf("Bad arguments %q", args)
-	}
-
-	uAddr, err := net.ResolveUnixAddr("unix", args[1])
-	if err != nil {
-		return err
-	}
-
-	conn, err := net.DialUnix("unix", nil, uAddr)
-	if err != nil {
-		return err
-	}
-
-	wg := sync.WaitGroup{}
-	wg.Add(1)
-
-	go func() {
-		io.Copy(os.Stdout, conn)
-		conn.Close()
-		wg.Done()
-	}()
-
-	go func() {
-		io.Copy(conn, os.Stdin)
-	}()
-
-	wg.Wait()
-
-	return nil
-}

From efab8b5e85f9e589f3ee9f3186b780b30102af41 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Wed, 14 Dec 2016 15:18:29 -0500
Subject: [PATCH 12/15] main: Move ready to own file
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/main.go       | 24 ------------------------
 lxd/main_ready.go | 31 +++++++++++++++++++++++++++++++
 2 files changed, 31 insertions(+), 24 deletions(-)
 create mode 100644 lxd/main_ready.go

diff --git a/lxd/main.go b/lxd/main.go
index 478dbef..f214c66 100644
--- a/lxd/main.go
+++ b/lxd/main.go
@@ -244,30 +244,6 @@ func run() error {
 	return cmdDaemon()
 }
 
-func cmdReady() error {
-	c, err := lxd.NewClient(&lxd.DefaultConfig, "local")
-	if err != nil {
-		return err
-	}
-
-	req, err := http.NewRequest("PUT", c.BaseURL+"/internal/ready", nil)
-	if err != nil {
-		return err
-	}
-
-	raw, err := c.Http.Do(req)
-	if err != nil {
-		return err
-	}
-
-	_, err = lxd.HoistResponse(raw, lxd.Sync)
-	if err != nil {
-		return err
-	}
-
-	return nil
-}
-
 func cmdShutdown() error {
 	var timeout int
 
diff --git a/lxd/main_ready.go b/lxd/main_ready.go
new file mode 100644
index 0000000..4e4ebfb
--- /dev/null
+++ b/lxd/main_ready.go
@@ -0,0 +1,31 @@
+package main
+
+import (
+	"net/http"
+
+	"github.com/lxc/lxd"
+)
+
+func cmdReady() error {
+	c, err := lxd.NewClient(&lxd.DefaultConfig, "local")
+	if err != nil {
+		return err
+	}
+
+	req, err := http.NewRequest("PUT", c.BaseURL+"/internal/ready", nil)
+	if err != nil {
+		return err
+	}
+
+	raw, err := c.Http.Do(req)
+	if err != nil {
+		return err
+	}
+
+	_, err = lxd.HoistResponse(raw, lxd.Sync)
+	if err != nil {
+		return err
+	}
+
+	return nil
+}

From 87172ab27a64faeb186d39ad699c0f9dc0cf01f7 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Wed, 14 Dec 2016 15:19:09 -0500
Subject: [PATCH 13/15] main: Move shutdown to own file
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/main.go          | 39 ---------------------------------------
 lxd/main_shutdown.go | 48 ++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 48 insertions(+), 39 deletions(-)
 create mode 100644 lxd/main_shutdown.go

diff --git a/lxd/main.go b/lxd/main.go
index f214c66..bfc0641 100644
--- a/lxd/main.go
+++ b/lxd/main.go
@@ -244,45 +244,6 @@ func run() error {
 	return cmdDaemon()
 }
 
-func cmdShutdown() error {
-	var timeout int
-
-	if *argTimeout == -1 {
-		timeout = 60
-	} else {
-		timeout = *argTimeout
-	}
-
-	c, err := lxd.NewClient(&lxd.DefaultConfig, "local")
-	if err != nil {
-		return err
-	}
-
-	req, err := http.NewRequest("PUT", c.BaseURL+"/internal/shutdown", nil)
-	if err != nil {
-		return err
-	}
-
-	_, err = c.Http.Do(req)
-	if err != nil {
-		return err
-	}
-
-	monitor := make(chan error, 1)
-	go func() {
-		monitor <- c.Monitor(nil, func(m interface{}) {})
-	}()
-
-	select {
-	case <-monitor:
-		break
-	case <-time.After(time.Second * time.Duration(timeout)):
-		return fmt.Errorf("LXD still running after %ds timeout.", timeout)
-	}
-
-	return nil
-}
-
 func cmdWaitReady() error {
 	var timeout int
 
diff --git a/lxd/main_shutdown.go b/lxd/main_shutdown.go
new file mode 100644
index 0000000..9110ed5
--- /dev/null
+++ b/lxd/main_shutdown.go
@@ -0,0 +1,48 @@
+package main
+
+import (
+	"fmt"
+	"net/http"
+	"time"
+
+	"github.com/lxc/lxd"
+)
+
+func cmdShutdown() error {
+	var timeout int
+
+	if *argTimeout == -1 {
+		timeout = 60
+	} else {
+		timeout = *argTimeout
+	}
+
+	c, err := lxd.NewClient(&lxd.DefaultConfig, "local")
+	if err != nil {
+		return err
+	}
+
+	req, err := http.NewRequest("PUT", c.BaseURL+"/internal/shutdown", nil)
+	if err != nil {
+		return err
+	}
+
+	_, err = c.Http.Do(req)
+	if err != nil {
+		return err
+	}
+
+	monitor := make(chan error, 1)
+	go func() {
+		monitor <- c.Monitor(nil, func(m interface{}) {})
+	}()
+
+	select {
+	case <-monitor:
+		break
+	case <-time.After(time.Second * time.Duration(timeout)):
+		return fmt.Errorf("LXD still running after %ds timeout.", timeout)
+	}
+
+	return nil
+}

From 088996571cfcc1f8bdc6d9c0ab3a593c25795046 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Wed, 14 Dec 2016 15:19:54 -0500
Subject: [PATCH 14/15] main: Move waitready to own file
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/main.go           | 53 ---------------------------------------------
 lxd/main_waitready.go | 60 +++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 60 insertions(+), 53 deletions(-)
 create mode 100644 lxd/main_waitready.go

diff --git a/lxd/main.go b/lxd/main.go
index bfc0641..327df27 100644
--- a/lxd/main.go
+++ b/lxd/main.go
@@ -3,11 +3,9 @@ package main
 import (
 	"fmt"
 	"math/rand"
-	"net/http"
 	"os"
 	"time"
 
-	"github.com/lxc/lxd"
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/gnuflag"
 	"github.com/lxc/lxd/shared/logging"
@@ -243,54 +241,3 @@ func run() error {
 
 	return cmdDaemon()
 }
-
-func cmdWaitReady() error {
-	var timeout int
-
-	if *argTimeout == -1 {
-		timeout = 15
-	} else {
-		timeout = *argTimeout
-	}
-
-	finger := make(chan error, 1)
-	go func() {
-		for {
-			c, err := lxd.NewClient(&lxd.DefaultConfig, "local")
-			if err != nil {
-				time.Sleep(500 * time.Millisecond)
-				continue
-			}
-
-			req, err := http.NewRequest("GET", c.BaseURL+"/internal/ready", nil)
-			if err != nil {
-				time.Sleep(500 * time.Millisecond)
-				continue
-			}
-
-			raw, err := c.Http.Do(req)
-			if err != nil {
-				time.Sleep(500 * time.Millisecond)
-				continue
-			}
-
-			_, err = lxd.HoistResponse(raw, lxd.Sync)
-			if err != nil {
-				time.Sleep(500 * time.Millisecond)
-				continue
-			}
-
-			finger <- nil
-			return
-		}
-	}()
-
-	select {
-	case <-finger:
-		break
-	case <-time.After(time.Second * time.Duration(timeout)):
-		return fmt.Errorf("LXD still not running after %ds timeout.", timeout)
-	}
-
-	return nil
-}
diff --git a/lxd/main_waitready.go b/lxd/main_waitready.go
new file mode 100644
index 0000000..7e2b4cf
--- /dev/null
+++ b/lxd/main_waitready.go
@@ -0,0 +1,60 @@
+package main
+
+import (
+	"fmt"
+	"net/http"
+	"time"
+
+	"github.com/lxc/lxd"
+)
+
+func cmdWaitReady() error {
+	var timeout int
+
+	if *argTimeout == -1 {
+		timeout = 15
+	} else {
+		timeout = *argTimeout
+	}
+
+	finger := make(chan error, 1)
+	go func() {
+		for {
+			c, err := lxd.NewClient(&lxd.DefaultConfig, "local")
+			if err != nil {
+				time.Sleep(500 * time.Millisecond)
+				continue
+			}
+
+			req, err := http.NewRequest("GET", c.BaseURL+"/internal/ready", nil)
+			if err != nil {
+				time.Sleep(500 * time.Millisecond)
+				continue
+			}
+
+			raw, err := c.Http.Do(req)
+			if err != nil {
+				time.Sleep(500 * time.Millisecond)
+				continue
+			}
+
+			_, err = lxd.HoistResponse(raw, lxd.Sync)
+			if err != nil {
+				time.Sleep(500 * time.Millisecond)
+				continue
+			}
+
+			finger <- nil
+			return
+		}
+	}()
+
+	select {
+	case <-finger:
+		break
+	case <-time.After(time.Second * time.Duration(timeout)):
+		return fmt.Errorf("LXD still not running after %ds timeout.", timeout)
+	}
+
+	return nil
+}

From 1d733c5b112ea0efe73ee16d485b4c8c49efd0de Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Wed, 14 Dec 2016 15:22:53 -0500
Subject: [PATCH 15/15] main: Rename nsexec.go to main_nsexec.go
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/main_nsexec.go | 676 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 lxd/nsexec.go      | 676 -----------------------------------------------------
 2 files changed, 676 insertions(+), 676 deletions(-)
 create mode 100644 lxd/main_nsexec.go
 delete mode 100644 lxd/nsexec.go

diff --git a/lxd/main_nsexec.go b/lxd/main_nsexec.go
new file mode 100644
index 0000000..8d5c038
--- /dev/null
+++ b/lxd/main_nsexec.go
@@ -0,0 +1,676 @@
+/**
+ * This file is a bit funny. The goal here is to use setns() to manipulate
+ * files inside the container, so we don't have to reason about the paths to
+ * make sure they don't escape (we can simply rely on the kernel for
+ * correctness). Unfortunately, you can't setns() to a mount namespace with a
+ * multi-threaded program, which every golang binary is. However, by declaring
+ * our init as an initializer, we can capture process control before it is
+ * transferred to the golang runtime, so we can then setns() as we'd like
+ * before golang has a chance to set up any threads. So, we implement two new
+ * lxd fork* commands which are captured here, and take a file on the host fs
+ * and copy it into the container ns.
+ *
+ * An alternative to this would be to move this code into a separate binary,
+ * which of course has problems of its own when it comes to packaging (how do
+ * we find the binary, what do we do if someone does file push and it is
+ * missing, etc.). After some discussion, even though the embedded method is
+ * somewhat convoluted, it was preferred.
+ */
+package main
+
+/*
+#define _GNU_SOURCE
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/mount.h>
+#include <sched.h>
+#include <linux/sched.h>
+#include <linux/limits.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <fcntl.h>
+#include <stdbool.h>
+#include <unistd.h>
+#include <errno.h>
+#include <alloca.h>
+#include <libgen.h>
+#include <ifaddrs.h>
+#include <dirent.h>
+
+// This expects:
+//  ./lxd forkputfile /source/path <pid> /target/path
+// or
+//  ./lxd forkgetfile /target/path <pid> /soruce/path <uid> <gid> <mode>
+// i.e. 8 arguments, each which have a max length of PATH_MAX.
+// Unfortunately, lseek() and fstat() both fail (EINVAL and 0 size) for
+// procfs. Also, we can't mmap, because procfs doesn't support that, either.
+//
+#define CMDLINE_SIZE (8 * PATH_MAX)
+
+void error(char *msg)
+{
+	int old_errno = errno;
+
+	if (old_errno == 0) {
+		fprintf(stderr, "%s\n", msg);
+		fprintf(stderr, "errno: 0\n");
+		return;
+	}
+
+	perror(msg);
+	fprintf(stderr, "errno: %d\n", old_errno);
+}
+
+int mkdir_p(const char *dir, mode_t mode)
+{
+	const char *tmp = dir;
+	const char *orig = dir;
+	char *makeme;
+
+	do {
+		dir = tmp + strspn(tmp, "/");
+		tmp = dir + strcspn(dir, "/");
+		makeme = strndup(orig, dir - orig);
+		if (*makeme) {
+			if (mkdir(makeme, mode) && errno != EEXIST) {
+				fprintf(stderr, "failed to create directory '%s': %s\n", makeme, strerror(errno));
+				free(makeme);
+				return -1;
+			}
+		}
+		free(makeme);
+	} while(tmp != dir);
+
+	return 0;
+}
+
+int copy(int target, int source)
+{
+	ssize_t n;
+	char buf[1024];
+
+	if (ftruncate(target, 0) < 0) {
+		error("error: truncate");
+		return -1;
+	}
+
+	while ((n = read(source, buf, 1024)) > 0) {
+		if (write(target, buf, n) != n) {
+			error("error: write");
+			return -1;
+		}
+	}
+
+	if (n < 0) {
+		error("error: read");
+		return -1;
+	}
+
+	return 0;
+}
+
+int dosetns(int pid, char *nstype) {
+	int mntns;
+	char buf[PATH_MAX];
+
+	sprintf(buf, "/proc/%d/ns/%s", pid, nstype);
+	mntns = open(buf, O_RDONLY);
+	if (mntns < 0) {
+		error("error: open mntns");
+		return -1;
+	}
+
+	if (setns(mntns, 0) < 0) {
+		error("error: setns");
+		close(mntns);
+		return -1;
+	}
+	close(mntns);
+
+	return 0;
+}
+
+void attach_userns(int pid) {
+	char nspath[PATH_MAX];
+	char userns_source[PATH_MAX];
+	char userns_target[PATH_MAX];
+
+	sprintf(nspath, "/proc/%d/ns/user", pid);
+	if (access(nspath, F_OK) == 0) {
+		if (readlink("/proc/self/ns/user", userns_source, 18) < 0) {
+			fprintf(stderr, "Failed readlink of source namespace: %s\n", strerror(errno));
+			_exit(1);
+		}
+
+		if (readlink(nspath, userns_target, PATH_MAX) < 0) {
+			fprintf(stderr, "Failed readlink of target namespace: %s\n", strerror(errno));
+			_exit(1);
+		}
+
+		if (strncmp(userns_source, userns_target, PATH_MAX) != 0) {
+			if (dosetns(pid, "user") < 0) {
+				fprintf(stderr, "Failed setns to container user namespace: %s\n", strerror(errno));
+				_exit(1);
+			}
+
+			if (setuid(0) < 0) {
+				fprintf(stderr, "Failed setuid to container root user: %s\n", strerror(errno));
+				_exit(1);
+			}
+
+			if (setgid(0) < 0) {
+				fprintf(stderr, "Failed setgid to container root group: %s\n", strerror(errno));
+				_exit(1);
+			}
+		}
+	}
+}
+
+int manip_file_in_ns(char *rootfs, int pid, char *host, char *container, bool is_put, uid_t uid, gid_t gid, mode_t mode, uid_t defaultUid, gid_t defaultGid, mode_t defaultMode) {
+	int host_fd = -1, container_fd = -1;
+	int ret = -1;
+	int container_open_flags;
+	struct stat st;
+	int exists = 1;
+	bool is_dir_manip = !strcmp(host, "");
+
+	if (!is_dir_manip) {
+		host_fd = open(host, O_RDWR);
+		if (host_fd < 0) {
+			error("error: open");
+			return -1;
+		}
+	}
+
+	if (pid > 0) {
+		attach_userns(pid);
+
+		if (dosetns(pid, "mnt") < 0) {
+			error("error: setns");
+			goto close_host;
+		}
+	} else {
+		if (chroot(rootfs) < 0) {
+			error("error: chroot");
+			goto close_host;
+		}
+
+		if (chdir("/") < 0) {
+			error("error: chdir");
+			goto close_host;
+		}
+	}
+
+	if (is_put && is_dir_manip) {
+		if (mode == -1) {
+			mode = defaultMode;
+		}
+
+		if (uid == -1) {
+			uid = defaultUid;
+		}
+
+		if (gid == -1) {
+			gid = defaultGid;
+		}
+
+		if (mkdir(container, mode) < 0 && errno != EEXIST) {
+			error("error: mkdir");
+			return -1;
+		}
+
+		if (chown(container, uid, gid) < 0) {
+			error("error: chown");
+			return -1;
+		}
+
+		return 0;
+	}
+
+	if (stat(container, &st) < 0)
+		exists = 0;
+
+	container_open_flags = O_RDWR;
+	if (is_put)
+		container_open_flags |= O_CREAT;
+
+	if (is_put && !is_dir_manip && exists && S_ISDIR(st.st_mode)) {
+		error("error: Path already exists as a directory");
+		goto close_host;
+	}
+
+	if (exists && S_ISDIR(st.st_mode))
+		container_open_flags = O_DIRECTORY;
+
+	umask(0);
+	container_fd = open(container, container_open_flags, 0);
+	if (container_fd < 0) {
+		error("error: open");
+		goto close_host;
+	}
+
+	if (is_put) {
+		if (!exists) {
+			if (mode == -1) {
+				mode = defaultMode;
+			}
+
+			if (uid == -1) {
+				uid = defaultUid;
+			}
+
+			if (gid == -1) {
+				gid = defaultGid;
+			}
+		}
+
+		if (copy(container_fd, host_fd) < 0) {
+			error("error: copy");
+			goto close_container;
+		}
+
+		if (mode != -1 && fchmod(container_fd, mode) < 0) {
+			error("error: chmod");
+			goto close_container;
+		}
+
+		if (fchown(container_fd, uid, gid) < 0) {
+			error("error: chown");
+			goto close_container;
+		}
+		ret = 0;
+	} else {
+
+		if (fstat(container_fd, &st) < 0) {
+			error("error: stat");
+			goto close_container;
+		}
+
+		fprintf(stderr, "uid: %ld\n", (long)st.st_uid);
+		fprintf(stderr, "gid: %ld\n", (long)st.st_gid);
+		fprintf(stderr, "mode: %ld\n", (unsigned long)st.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO));
+		if (S_ISDIR(st.st_mode)) {
+			DIR *fdir;
+			struct dirent *de;
+
+			fdir = fdopendir(container_fd);
+			if (!fdir) {
+				error("error: fdopendir");
+				goto close_container;
+			}
+
+			fprintf(stderr, "type: directory\n");
+
+			while((de = readdir(fdir))) {
+				int len, i;
+
+				if (!strcmp(de->d_name, ".") || !strcmp(de->d_name, ".."))
+					continue;
+
+				fprintf(stderr, "entry: ");
+
+				// swap \n to \0 since we split this output by line
+				for (i = 0, len = strlen(de->d_name); i < len; i++) {
+					if (*(de->d_name + i) == '\n')
+						putc(0, stderr);
+					else
+						putc(*(de->d_name + i), stderr);
+				}
+				fprintf(stderr, "\n");
+			}
+
+			// container_fd is dead now that we fopendir'd it
+			goto close_host;
+		} else {
+			fprintf(stderr, "type: file\n");
+			ret = copy(host_fd, container_fd);
+		}
+		fprintf(stderr, "type: %s", S_ISDIR(st.st_mode) ? "directory" : "file");
+	}
+
+close_container:
+	close(container_fd);
+close_host:
+	close(host_fd);
+	return ret;
+}
+
+#define ADVANCE_ARG_REQUIRED()					\
+	do {							\
+		while (*cur != 0)				\
+			cur++;					\
+		cur++;						\
+		if (size <= cur - buf) {			\
+			fprintf(stderr, "not enough arguments\n");	\
+			_exit(1);				\
+		}						\
+	} while(0)
+
+void ensure_dir(char *dest) {
+	struct stat sb;
+	if (stat(dest, &sb) == 0) {
+		if ((sb.st_mode & S_IFMT) == S_IFDIR)
+			return;
+		if (unlink(dest) < 0) {
+			fprintf(stderr, "Failed to remove old %s: %s\n", dest, strerror(errno));
+			_exit(1);
+		}
+	}
+	if (mkdir(dest, 0755) < 0) {
+		fprintf(stderr, "Failed to mkdir %s: %s\n", dest, strerror(errno));
+		_exit(1);
+	}
+}
+
+void ensure_file(char *dest) {
+	struct stat sb;
+	int fd;
+
+	if (stat(dest, &sb) == 0) {
+		if ((sb.st_mode & S_IFMT) != S_IFDIR)
+			return;
+		if (rmdir(dest) < 0) {
+			fprintf(stderr, "Failed to remove old %s: %s\n", dest, strerror(errno));
+			_exit(1);
+		}
+	}
+
+	fd = creat(dest, 0755);
+	if (fd < 0) {
+		fprintf(stderr, "Failed to mkdir %s: %s\n", dest, strerror(errno));
+		_exit(1);
+	}
+	close(fd);
+}
+
+void create(char *src, char *dest) {
+	char *destdirname;
+	struct stat sb;
+	if (stat(src, &sb) < 0) {
+		fprintf(stderr, "source %s does not exist\n", src);
+		_exit(1);
+	}
+
+	destdirname = strdup(dest);
+	destdirname = dirname(destdirname);
+
+	if (mkdir_p(destdirname, 0755) < 0) {
+		fprintf(stderr, "failed to create path: %s\n", destdirname);
+		free(destdirname);
+		_exit(1);
+	}
+
+	switch (sb.st_mode & S_IFMT) {
+	case S_IFDIR:
+		ensure_dir(dest);
+		return;
+	default:
+		ensure_file(dest);
+		return;
+	}
+
+	free(destdirname);
+}
+
+void forkmount(char *buf, char *cur, ssize_t size) {
+	char *src, *dest, *opts;
+
+	ADVANCE_ARG_REQUIRED();
+	int pid = atoi(cur);
+
+	attach_userns(pid);
+
+	if (dosetns(pid, "mnt") < 0) {
+		fprintf(stderr, "Failed setns to container mount namespace: %s\n", strerror(errno));
+		_exit(1);
+	}
+
+	ADVANCE_ARG_REQUIRED();
+	src = cur;
+
+	ADVANCE_ARG_REQUIRED();
+	dest = cur;
+
+	create(src, dest);
+
+	if (access(src, F_OK) < 0) {
+		fprintf(stderr, "Mount source doesn't exist: %s\n", strerror(errno));
+		_exit(1);
+	}
+
+	if (access(dest, F_OK) < 0) {
+		fprintf(stderr, "Mount destination doesn't exist: %s\n", strerror(errno));
+		_exit(1);
+	}
+
+	// Here, we always move recursively, because we sometimes allow
+	// recursive mounts. If the mount has no kids then it doesn't matter,
+	// but if it does, we want to move those too.
+	if (mount(src, dest, "none", MS_MOVE | MS_REC, NULL) < 0) {
+		fprintf(stderr, "Failed mounting %s onto %s: %s\n", src, dest, strerror(errno));
+		_exit(1);
+	}
+
+	_exit(0);
+}
+
+void forkumount(char *buf, char *cur, ssize_t size) {
+	ADVANCE_ARG_REQUIRED();
+	int pid = atoi(cur);
+
+	if (dosetns(pid, "mnt") < 0) {
+		fprintf(stderr, "Failed setns to container mount namespace: %s\n", strerror(errno));
+		_exit(1);
+	}
+
+	ADVANCE_ARG_REQUIRED();
+	if (access(cur, F_OK) < 0) {
+		fprintf(stderr, "Mount path doesn't exist: %s\n", strerror(errno));
+		_exit(1);
+	}
+
+	if (umount2(cur, MNT_DETACH) < 0) {
+		fprintf(stderr, "Error unmounting %s: %s\n", cur, strerror(errno));
+		_exit(1);
+	}
+	_exit(0);
+}
+
+void forkdofile(char *buf, char *cur, bool is_put, ssize_t size) {
+	uid_t uid = 0;
+	gid_t gid = 0;
+	mode_t mode = 0;
+	uid_t defaultUid = 0;
+	gid_t defaultGid = 0;
+	mode_t defaultMode = 0;
+	char *command = cur, *rootfs = NULL, *source = NULL, *target = NULL;
+	pid_t pid;
+
+	ADVANCE_ARG_REQUIRED();
+	rootfs = cur;
+
+	ADVANCE_ARG_REQUIRED();
+	pid = atoi(cur);
+
+	ADVANCE_ARG_REQUIRED();
+	source = cur;
+
+	ADVANCE_ARG_REQUIRED();
+	target = cur;
+
+	if (is_put) {
+		ADVANCE_ARG_REQUIRED();
+		uid = atoi(cur);
+
+		ADVANCE_ARG_REQUIRED();
+		gid = atoi(cur);
+
+		ADVANCE_ARG_REQUIRED();
+		mode = atoi(cur);
+
+		ADVANCE_ARG_REQUIRED();
+		defaultUid = atoi(cur);
+
+		ADVANCE_ARG_REQUIRED();
+		defaultGid = atoi(cur);
+
+		ADVANCE_ARG_REQUIRED();
+		defaultMode = atoi(cur);
+	}
+
+	_exit(manip_file_in_ns(rootfs, pid, source, target, is_put, uid, gid, mode, defaultUid, defaultGid, defaultMode));
+}
+
+void forkcheckfile(char *buf, char *cur, bool is_put, ssize_t size) {
+	char *command = cur, *rootfs = NULL, *path = NULL;
+	pid_t pid;
+
+	ADVANCE_ARG_REQUIRED();
+	rootfs = cur;
+
+	ADVANCE_ARG_REQUIRED();
+	pid = atoi(cur);
+
+	ADVANCE_ARG_REQUIRED();
+	path = cur;
+
+	if (pid > 0) {
+		attach_userns(pid);
+
+		if (dosetns(pid, "mnt") < 0) {
+			error("error: setns");
+			_exit(1);
+		}
+	} else {
+		if (chroot(rootfs) < 0) {
+			error("error: chroot");
+			_exit(1);
+		}
+
+		if (chdir("/") < 0) {
+			error("error: chdir");
+			_exit(1);
+		}
+	}
+
+	if (access(path, F_OK) < 0) {
+		fprintf(stderr, "Path doesn't exist: %s\n", strerror(errno));
+		_exit(1);
+	}
+
+	_exit(0);
+}
+
+void forkremovefile(char *buf, char *cur, bool is_put, ssize_t size) {
+	char *command = cur, *rootfs = NULL, *path = NULL;
+	pid_t pid;
+	struct stat sb;
+
+	ADVANCE_ARG_REQUIRED();
+	rootfs = cur;
+
+	ADVANCE_ARG_REQUIRED();
+	pid = atoi(cur);
+
+	ADVANCE_ARG_REQUIRED();
+	path = cur;
+
+	if (pid > 0) {
+		attach_userns(pid);
+
+		if (dosetns(pid, "mnt") < 0) {
+			error("error: setns");
+			_exit(1);
+		}
+	} else {
+		if (chroot(rootfs) < 0) {
+			error("error: chroot");
+			_exit(1);
+		}
+
+		if (chdir("/") < 0) {
+			error("error: chdir");
+			_exit(1);
+		}
+	}
+
+	if (stat(path, &sb) < 0) {
+		error("error: stat");
+		_exit(1);
+	}
+
+	if ((sb.st_mode & S_IFMT) == S_IFDIR) {
+		if (rmdir(path) < 0) {
+			fprintf(stderr, "Failed to remove %s: %s\n", path, strerror(errno));
+			_exit(1);
+		}
+	} else {
+		if (unlink(path) < 0) {
+			fprintf(stderr, "Failed to remove %s: %s\n", path, strerror(errno));
+			_exit(1);
+		}
+	}
+
+	_exit(0);
+}
+
+void forkgetnet(char *buf, char *cur, ssize_t size) {
+	ADVANCE_ARG_REQUIRED();
+	int pid = atoi(cur);
+
+	if (dosetns(pid, "net") < 0) {
+		fprintf(stderr, "Failed setns to container network namespace: %s\n", strerror(errno));
+		_exit(1);
+	}
+
+	// The rest happens in Go
+}
+
+__attribute__((constructor)) void init(void) {
+	int cmdline;
+	char buf[CMDLINE_SIZE];
+	ssize_t size;
+	char *cur;
+
+	cmdline = open("/proc/self/cmdline", O_RDONLY);
+	if (cmdline < 0) {
+		error("error: open");
+		_exit(232);
+	}
+
+	memset(buf, 0, sizeof(buf));
+	if ((size = read(cmdline, buf, sizeof(buf)-1)) < 0) {
+		close(cmdline);
+		error("error: read");
+		_exit(232);
+	}
+	close(cmdline);
+
+	cur = buf;
+	// skip argv[0]
+	while (*cur != 0)
+		cur++;
+	cur++;
+	if (size <= cur - buf)
+		return;
+
+	if (strcmp(cur, "forkputfile") == 0) {
+		forkdofile(buf, cur, true, size);
+	} else if (strcmp(cur, "forkgetfile") == 0) {
+		forkdofile(buf, cur, false, size);
+	} else if (strcmp(cur, "forkcheckfile") == 0) {
+		forkcheckfile(buf, cur, false, size);
+	} else if (strcmp(cur, "forkremovefile") == 0) {
+		forkremovefile(buf, cur, false, size);
+	} else if (strcmp(cur, "forkmount") == 0) {
+		forkmount(buf, cur, size);
+	} else if (strcmp(cur, "forkumount") == 0) {
+		forkumount(buf, cur, size);
+	} else if (strcmp(cur, "forkgetnet") == 0) {
+		forkgetnet(buf, cur, size);
+	}
+}
+*/
+import "C"
diff --git a/lxd/nsexec.go b/lxd/nsexec.go
deleted file mode 100644
index 8d5c038..0000000
--- a/lxd/nsexec.go
+++ /dev/null
@@ -1,676 +0,0 @@
-/**
- * This file is a bit funny. The goal here is to use setns() to manipulate
- * files inside the container, so we don't have to reason about the paths to
- * make sure they don't escape (we can simply rely on the kernel for
- * correctness). Unfortunately, you can't setns() to a mount namespace with a
- * multi-threaded program, which every golang binary is. However, by declaring
- * our init as an initializer, we can capture process control before it is
- * transferred to the golang runtime, so we can then setns() as we'd like
- * before golang has a chance to set up any threads. So, we implement two new
- * lxd fork* commands which are captured here, and take a file on the host fs
- * and copy it into the container ns.
- *
- * An alternative to this would be to move this code into a separate binary,
- * which of course has problems of its own when it comes to packaging (how do
- * we find the binary, what do we do if someone does file push and it is
- * missing, etc.). After some discussion, even though the embedded method is
- * somewhat convoluted, it was preferred.
- */
-package main
-
-/*
-#define _GNU_SOURCE
-#include <string.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <sys/mount.h>
-#include <sched.h>
-#include <linux/sched.h>
-#include <linux/limits.h>
-#include <sys/mman.h>
-#include <sys/stat.h>
-#include <sys/types.h>
-#include <fcntl.h>
-#include <stdbool.h>
-#include <unistd.h>
-#include <errno.h>
-#include <alloca.h>
-#include <libgen.h>
-#include <ifaddrs.h>
-#include <dirent.h>
-
-// This expects:
-//  ./lxd forkputfile /source/path <pid> /target/path
-// or
-//  ./lxd forkgetfile /target/path <pid> /soruce/path <uid> <gid> <mode>
-// i.e. 8 arguments, each which have a max length of PATH_MAX.
-// Unfortunately, lseek() and fstat() both fail (EINVAL and 0 size) for
-// procfs. Also, we can't mmap, because procfs doesn't support that, either.
-//
-#define CMDLINE_SIZE (8 * PATH_MAX)
-
-void error(char *msg)
-{
-	int old_errno = errno;
-
-	if (old_errno == 0) {
-		fprintf(stderr, "%s\n", msg);
-		fprintf(stderr, "errno: 0\n");
-		return;
-	}
-
-	perror(msg);
-	fprintf(stderr, "errno: %d\n", old_errno);
-}
-
-int mkdir_p(const char *dir, mode_t mode)
-{
-	const char *tmp = dir;
-	const char *orig = dir;
-	char *makeme;
-
-	do {
-		dir = tmp + strspn(tmp, "/");
-		tmp = dir + strcspn(dir, "/");
-		makeme = strndup(orig, dir - orig);
-		if (*makeme) {
-			if (mkdir(makeme, mode) && errno != EEXIST) {
-				fprintf(stderr, "failed to create directory '%s': %s\n", makeme, strerror(errno));
-				free(makeme);
-				return -1;
-			}
-		}
-		free(makeme);
-	} while(tmp != dir);
-
-	return 0;
-}
-
-int copy(int target, int source)
-{
-	ssize_t n;
-	char buf[1024];
-
-	if (ftruncate(target, 0) < 0) {
-		error("error: truncate");
-		return -1;
-	}
-
-	while ((n = read(source, buf, 1024)) > 0) {
-		if (write(target, buf, n) != n) {
-			error("error: write");
-			return -1;
-		}
-	}
-
-	if (n < 0) {
-		error("error: read");
-		return -1;
-	}
-
-	return 0;
-}
-
-int dosetns(int pid, char *nstype) {
-	int mntns;
-	char buf[PATH_MAX];
-
-	sprintf(buf, "/proc/%d/ns/%s", pid, nstype);
-	mntns = open(buf, O_RDONLY);
-	if (mntns < 0) {
-		error("error: open mntns");
-		return -1;
-	}
-
-	if (setns(mntns, 0) < 0) {
-		error("error: setns");
-		close(mntns);
-		return -1;
-	}
-	close(mntns);
-
-	return 0;
-}
-
-void attach_userns(int pid) {
-	char nspath[PATH_MAX];
-	char userns_source[PATH_MAX];
-	char userns_target[PATH_MAX];
-
-	sprintf(nspath, "/proc/%d/ns/user", pid);
-	if (access(nspath, F_OK) == 0) {
-		if (readlink("/proc/self/ns/user", userns_source, 18) < 0) {
-			fprintf(stderr, "Failed readlink of source namespace: %s\n", strerror(errno));
-			_exit(1);
-		}
-
-		if (readlink(nspath, userns_target, PATH_MAX) < 0) {
-			fprintf(stderr, "Failed readlink of target namespace: %s\n", strerror(errno));
-			_exit(1);
-		}
-
-		if (strncmp(userns_source, userns_target, PATH_MAX) != 0) {
-			if (dosetns(pid, "user") < 0) {
-				fprintf(stderr, "Failed setns to container user namespace: %s\n", strerror(errno));
-				_exit(1);
-			}
-
-			if (setuid(0) < 0) {
-				fprintf(stderr, "Failed setuid to container root user: %s\n", strerror(errno));
-				_exit(1);
-			}
-
-			if (setgid(0) < 0) {
-				fprintf(stderr, "Failed setgid to container root group: %s\n", strerror(errno));
-				_exit(1);
-			}
-		}
-	}
-}
-
-int manip_file_in_ns(char *rootfs, int pid, char *host, char *container, bool is_put, uid_t uid, gid_t gid, mode_t mode, uid_t defaultUid, gid_t defaultGid, mode_t defaultMode) {
-	int host_fd = -1, container_fd = -1;
-	int ret = -1;
-	int container_open_flags;
-	struct stat st;
-	int exists = 1;
-	bool is_dir_manip = !strcmp(host, "");
-
-	if (!is_dir_manip) {
-		host_fd = open(host, O_RDWR);
-		if (host_fd < 0) {
-			error("error: open");
-			return -1;
-		}
-	}
-
-	if (pid > 0) {
-		attach_userns(pid);
-
-		if (dosetns(pid, "mnt") < 0) {
-			error("error: setns");
-			goto close_host;
-		}
-	} else {
-		if (chroot(rootfs) < 0) {
-			error("error: chroot");
-			goto close_host;
-		}
-
-		if (chdir("/") < 0) {
-			error("error: chdir");
-			goto close_host;
-		}
-	}
-
-	if (is_put && is_dir_manip) {
-		if (mode == -1) {
-			mode = defaultMode;
-		}
-
-		if (uid == -1) {
-			uid = defaultUid;
-		}
-
-		if (gid == -1) {
-			gid = defaultGid;
-		}
-
-		if (mkdir(container, mode) < 0 && errno != EEXIST) {
-			error("error: mkdir");
-			return -1;
-		}
-
-		if (chown(container, uid, gid) < 0) {
-			error("error: chown");
-			return -1;
-		}
-
-		return 0;
-	}
-
-	if (stat(container, &st) < 0)
-		exists = 0;
-
-	container_open_flags = O_RDWR;
-	if (is_put)
-		container_open_flags |= O_CREAT;
-
-	if (is_put && !is_dir_manip && exists && S_ISDIR(st.st_mode)) {
-		error("error: Path already exists as a directory");
-		goto close_host;
-	}
-
-	if (exists && S_ISDIR(st.st_mode))
-		container_open_flags = O_DIRECTORY;
-
-	umask(0);
-	container_fd = open(container, container_open_flags, 0);
-	if (container_fd < 0) {
-		error("error: open");
-		goto close_host;
-	}
-
-	if (is_put) {
-		if (!exists) {
-			if (mode == -1) {
-				mode = defaultMode;
-			}
-
-			if (uid == -1) {
-				uid = defaultUid;
-			}
-
-			if (gid == -1) {
-				gid = defaultGid;
-			}
-		}
-
-		if (copy(container_fd, host_fd) < 0) {
-			error("error: copy");
-			goto close_container;
-		}
-
-		if (mode != -1 && fchmod(container_fd, mode) < 0) {
-			error("error: chmod");
-			goto close_container;
-		}
-
-		if (fchown(container_fd, uid, gid) < 0) {
-			error("error: chown");
-			goto close_container;
-		}
-		ret = 0;
-	} else {
-
-		if (fstat(container_fd, &st) < 0) {
-			error("error: stat");
-			goto close_container;
-		}
-
-		fprintf(stderr, "uid: %ld\n", (long)st.st_uid);
-		fprintf(stderr, "gid: %ld\n", (long)st.st_gid);
-		fprintf(stderr, "mode: %ld\n", (unsigned long)st.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO));
-		if (S_ISDIR(st.st_mode)) {
-			DIR *fdir;
-			struct dirent *de;
-
-			fdir = fdopendir(container_fd);
-			if (!fdir) {
-				error("error: fdopendir");
-				goto close_container;
-			}
-
-			fprintf(stderr, "type: directory\n");
-
-			while((de = readdir(fdir))) {
-				int len, i;
-
-				if (!strcmp(de->d_name, ".") || !strcmp(de->d_name, ".."))
-					continue;
-
-				fprintf(stderr, "entry: ");
-
-				// swap \n to \0 since we split this output by line
-				for (i = 0, len = strlen(de->d_name); i < len; i++) {
-					if (*(de->d_name + i) == '\n')
-						putc(0, stderr);
-					else
-						putc(*(de->d_name + i), stderr);
-				}
-				fprintf(stderr, "\n");
-			}
-
-			// container_fd is dead now that we fopendir'd it
-			goto close_host;
-		} else {
-			fprintf(stderr, "type: file\n");
-			ret = copy(host_fd, container_fd);
-		}
-		fprintf(stderr, "type: %s", S_ISDIR(st.st_mode) ? "directory" : "file");
-	}
-
-close_container:
-	close(container_fd);
-close_host:
-	close(host_fd);
-	return ret;
-}
-
-#define ADVANCE_ARG_REQUIRED()					\
-	do {							\
-		while (*cur != 0)				\
-			cur++;					\
-		cur++;						\
-		if (size <= cur - buf) {			\
-			fprintf(stderr, "not enough arguments\n");	\
-			_exit(1);				\
-		}						\
-	} while(0)
-
-void ensure_dir(char *dest) {
-	struct stat sb;
-	if (stat(dest, &sb) == 0) {
-		if ((sb.st_mode & S_IFMT) == S_IFDIR)
-			return;
-		if (unlink(dest) < 0) {
-			fprintf(stderr, "Failed to remove old %s: %s\n", dest, strerror(errno));
-			_exit(1);
-		}
-	}
-	if (mkdir(dest, 0755) < 0) {
-		fprintf(stderr, "Failed to mkdir %s: %s\n", dest, strerror(errno));
-		_exit(1);
-	}
-}
-
-void ensure_file(char *dest) {
-	struct stat sb;
-	int fd;
-
-	if (stat(dest, &sb) == 0) {
-		if ((sb.st_mode & S_IFMT) != S_IFDIR)
-			return;
-		if (rmdir(dest) < 0) {
-			fprintf(stderr, "Failed to remove old %s: %s\n", dest, strerror(errno));
-			_exit(1);
-		}
-	}
-
-	fd = creat(dest, 0755);
-	if (fd < 0) {
-		fprintf(stderr, "Failed to mkdir %s: %s\n", dest, strerror(errno));
-		_exit(1);
-	}
-	close(fd);
-}
-
-void create(char *src, char *dest) {
-	char *destdirname;
-	struct stat sb;
-	if (stat(src, &sb) < 0) {
-		fprintf(stderr, "source %s does not exist\n", src);
-		_exit(1);
-	}
-
-	destdirname = strdup(dest);
-	destdirname = dirname(destdirname);
-
-	if (mkdir_p(destdirname, 0755) < 0) {
-		fprintf(stderr, "failed to create path: %s\n", destdirname);
-		free(destdirname);
-		_exit(1);
-	}
-
-	switch (sb.st_mode & S_IFMT) {
-	case S_IFDIR:
-		ensure_dir(dest);
-		return;
-	default:
-		ensure_file(dest);
-		return;
-	}
-
-	free(destdirname);
-}
-
-void forkmount(char *buf, char *cur, ssize_t size) {
-	char *src, *dest, *opts;
-
-	ADVANCE_ARG_REQUIRED();
-	int pid = atoi(cur);
-
-	attach_userns(pid);
-
-	if (dosetns(pid, "mnt") < 0) {
-		fprintf(stderr, "Failed setns to container mount namespace: %s\n", strerror(errno));
-		_exit(1);
-	}
-
-	ADVANCE_ARG_REQUIRED();
-	src = cur;
-
-	ADVANCE_ARG_REQUIRED();
-	dest = cur;
-
-	create(src, dest);
-
-	if (access(src, F_OK) < 0) {
-		fprintf(stderr, "Mount source doesn't exist: %s\n", strerror(errno));
-		_exit(1);
-	}
-
-	if (access(dest, F_OK) < 0) {
-		fprintf(stderr, "Mount destination doesn't exist: %s\n", strerror(errno));
-		_exit(1);
-	}
-
-	// Here, we always move recursively, because we sometimes allow
-	// recursive mounts. If the mount has no kids then it doesn't matter,
-	// but if it does, we want to move those too.
-	if (mount(src, dest, "none", MS_MOVE | MS_REC, NULL) < 0) {
-		fprintf(stderr, "Failed mounting %s onto %s: %s\n", src, dest, strerror(errno));
-		_exit(1);
-	}
-
-	_exit(0);
-}
-
-void forkumount(char *buf, char *cur, ssize_t size) {
-	ADVANCE_ARG_REQUIRED();
-	int pid = atoi(cur);
-
-	if (dosetns(pid, "mnt") < 0) {
-		fprintf(stderr, "Failed setns to container mount namespace: %s\n", strerror(errno));
-		_exit(1);
-	}
-
-	ADVANCE_ARG_REQUIRED();
-	if (access(cur, F_OK) < 0) {
-		fprintf(stderr, "Mount path doesn't exist: %s\n", strerror(errno));
-		_exit(1);
-	}
-
-	if (umount2(cur, MNT_DETACH) < 0) {
-		fprintf(stderr, "Error unmounting %s: %s\n", cur, strerror(errno));
-		_exit(1);
-	}
-	_exit(0);
-}
-
-void forkdofile(char *buf, char *cur, bool is_put, ssize_t size) {
-	uid_t uid = 0;
-	gid_t gid = 0;
-	mode_t mode = 0;
-	uid_t defaultUid = 0;
-	gid_t defaultGid = 0;
-	mode_t defaultMode = 0;
-	char *command = cur, *rootfs = NULL, *source = NULL, *target = NULL;
-	pid_t pid;
-
-	ADVANCE_ARG_REQUIRED();
-	rootfs = cur;
-
-	ADVANCE_ARG_REQUIRED();
-	pid = atoi(cur);
-
-	ADVANCE_ARG_REQUIRED();
-	source = cur;
-
-	ADVANCE_ARG_REQUIRED();
-	target = cur;
-
-	if (is_put) {
-		ADVANCE_ARG_REQUIRED();
-		uid = atoi(cur);
-
-		ADVANCE_ARG_REQUIRED();
-		gid = atoi(cur);
-
-		ADVANCE_ARG_REQUIRED();
-		mode = atoi(cur);
-
-		ADVANCE_ARG_REQUIRED();
-		defaultUid = atoi(cur);
-
-		ADVANCE_ARG_REQUIRED();
-		defaultGid = atoi(cur);
-
-		ADVANCE_ARG_REQUIRED();
-		defaultMode = atoi(cur);
-	}
-
-	_exit(manip_file_in_ns(rootfs, pid, source, target, is_put, uid, gid, mode, defaultUid, defaultGid, defaultMode));
-}
-
-void forkcheckfile(char *buf, char *cur, bool is_put, ssize_t size) {
-	char *command = cur, *rootfs = NULL, *path = NULL;
-	pid_t pid;
-
-	ADVANCE_ARG_REQUIRED();
-	rootfs = cur;
-
-	ADVANCE_ARG_REQUIRED();
-	pid = atoi(cur);
-
-	ADVANCE_ARG_REQUIRED();
-	path = cur;
-
-	if (pid > 0) {
-		attach_userns(pid);
-
-		if (dosetns(pid, "mnt") < 0) {
-			error("error: setns");
-			_exit(1);
-		}
-	} else {
-		if (chroot(rootfs) < 0) {
-			error("error: chroot");
-			_exit(1);
-		}
-
-		if (chdir("/") < 0) {
-			error("error: chdir");
-			_exit(1);
-		}
-	}
-
-	if (access(path, F_OK) < 0) {
-		fprintf(stderr, "Path doesn't exist: %s\n", strerror(errno));
-		_exit(1);
-	}
-
-	_exit(0);
-}
-
-void forkremovefile(char *buf, char *cur, bool is_put, ssize_t size) {
-	char *command = cur, *rootfs = NULL, *path = NULL;
-	pid_t pid;
-	struct stat sb;
-
-	ADVANCE_ARG_REQUIRED();
-	rootfs = cur;
-
-	ADVANCE_ARG_REQUIRED();
-	pid = atoi(cur);
-
-	ADVANCE_ARG_REQUIRED();
-	path = cur;
-
-	if (pid > 0) {
-		attach_userns(pid);
-
-		if (dosetns(pid, "mnt") < 0) {
-			error("error: setns");
-			_exit(1);
-		}
-	} else {
-		if (chroot(rootfs) < 0) {
-			error("error: chroot");
-			_exit(1);
-		}
-
-		if (chdir("/") < 0) {
-			error("error: chdir");
-			_exit(1);
-		}
-	}
-
-	if (stat(path, &sb) < 0) {
-		error("error: stat");
-		_exit(1);
-	}
-
-	if ((sb.st_mode & S_IFMT) == S_IFDIR) {
-		if (rmdir(path) < 0) {
-			fprintf(stderr, "Failed to remove %s: %s\n", path, strerror(errno));
-			_exit(1);
-		}
-	} else {
-		if (unlink(path) < 0) {
-			fprintf(stderr, "Failed to remove %s: %s\n", path, strerror(errno));
-			_exit(1);
-		}
-	}
-
-	_exit(0);
-}
-
-void forkgetnet(char *buf, char *cur, ssize_t size) {
-	ADVANCE_ARG_REQUIRED();
-	int pid = atoi(cur);
-
-	if (dosetns(pid, "net") < 0) {
-		fprintf(stderr, "Failed setns to container network namespace: %s\n", strerror(errno));
-		_exit(1);
-	}
-
-	// The rest happens in Go
-}
-
-__attribute__((constructor)) void init(void) {
-	int cmdline;
-	char buf[CMDLINE_SIZE];
-	ssize_t size;
-	char *cur;
-
-	cmdline = open("/proc/self/cmdline", O_RDONLY);
-	if (cmdline < 0) {
-		error("error: open");
-		_exit(232);
-	}
-
-	memset(buf, 0, sizeof(buf));
-	if ((size = read(cmdline, buf, sizeof(buf)-1)) < 0) {
-		close(cmdline);
-		error("error: read");
-		_exit(232);
-	}
-	close(cmdline);
-
-	cur = buf;
-	// skip argv[0]
-	while (*cur != 0)
-		cur++;
-	cur++;
-	if (size <= cur - buf)
-		return;
-
-	if (strcmp(cur, "forkputfile") == 0) {
-		forkdofile(buf, cur, true, size);
-	} else if (strcmp(cur, "forkgetfile") == 0) {
-		forkdofile(buf, cur, false, size);
-	} else if (strcmp(cur, "forkcheckfile") == 0) {
-		forkcheckfile(buf, cur, false, size);
-	} else if (strcmp(cur, "forkremovefile") == 0) {
-		forkremovefile(buf, cur, false, size);
-	} else if (strcmp(cur, "forkmount") == 0) {
-		forkmount(buf, cur, size);
-	} else if (strcmp(cur, "forkumount") == 0) {
-		forkumount(buf, cur, size);
-	} else if (strcmp(cur, "forkgetnet") == 0) {
-		forkgetnet(buf, cur, size);
-	}
-}
-*/
-import "C"


More information about the lxc-devel mailing list