[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