[lxc-devel] [lxd/master] Bugfixes

stgraber on Github lxc-bot at linuxcontainers.org
Tue Mar 7 03:25:33 UTC 2017


A non-text attachment was scrubbed...
Name: not available
Type: text/x-mailbox
Size: 301 bytes
Desc: not available
URL: <http://lists.linuxcontainers.org/pipermail/lxc-devel/attachments/20170307/88fdda11/attachment.bin>
-------------- next part --------------
From 9065ae1c38794c1bb48e145aced582379af5f58e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Mon, 6 Mar 2017 21:46:48 -0500
Subject: [PATCH] global: Use RunCommand everywhere

---
 lxd/apparmor.go          |  10 ++--
 lxd/container_lxc.go     | 110 +++++++++++++++++++--------------------
 lxd/db_update.go         |   5 +-
 lxd/devices.go           |  12 ++---
 lxd/images.go            |  10 ++--
 lxd/main_init.go         |   2 +-
 lxd/networks.go          |  61 +++++++++++-----------
 lxd/networks_iptables.go |  12 ++---
 lxd/networks_utils.go    |  15 +++---
 lxd/patches.go           |  25 +++++----
 lxd/storage.go           |  23 ++-------
 lxd/storage_btrfs.go     |  69 +++++++++++++------------
 lxd/storage_dir.go       |   5 +-
 lxd/storage_lvm.go       |  88 ++++++++++++++++----------------
 lxd/storage_shared.go    |   5 +-
 lxd/storage_zfs.go       | 130 +++++++++++++++++++++++------------------------
 lxd/util.go              |   3 +-
 shared/util.go           |  28 +++++++---
 shared/util_linux.go     |   2 +-
 19 files changed, 303 insertions(+), 312 deletions(-)

diff --git a/lxd/apparmor.go b/lxd/apparmor.go
index cd4ac6c..89881a8 100644
--- a/lxd/apparmor.go
+++ b/lxd/apparmor.go
@@ -6,7 +6,6 @@ import (
 	"io"
 	"io/ioutil"
 	"os"
-	"os/exec"
 	"path"
 	"strings"
 
@@ -395,16 +394,15 @@ func runApparmor(command string, c container) error {
 		return nil
 	}
 
-	cmd := exec.Command("apparmor_parser", []string{
+	output, err := shared.RunCommand("apparmor_parser", []string{
 		fmt.Sprintf("-%sWL", command),
 		path.Join(aaPath, "cache"),
 		path.Join(aaPath, "profiles", AAProfileShort(c)),
 	}...)
 
-	output, err := cmd.CombinedOutput()
 	if err != nil {
 		shared.LogError("Running apparmor",
-			log.Ctx{"action": command, "output": string(output), "err": err})
+			log.Ctx{"action": command, "output": output, "err": err})
 	}
 
 	return err
@@ -519,7 +517,7 @@ func aaProfile() string {
 }
 
 func aaParserSupports(feature string) bool {
-	out, err := exec.Command("apparmor_parser", "--version").CombinedOutput()
+	out, err := shared.RunCommand("apparmor_parser", "--version")
 	if err != nil {
 		return false
 	}
@@ -528,7 +526,7 @@ func aaParserSupports(feature string) bool {
 	minor := 0
 	micro := 0
 
-	_, err = fmt.Sscanf(strings.Split(string(out), "\n")[0], "AppArmor parser version %d.%d.%d", &major, &minor, &micro)
+	_, err = fmt.Sscanf(strings.Split(out, "\n")[0], "AppArmor parser version %d.%d.%d", &major, &minor, &micro)
 	if err != nil {
 		return false
 	}
diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index efd7c72..9177f77 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -1753,7 +1753,7 @@ func (c *containerLXC) startCommon() (string, error) {
 			if m["nictype"] == "macvlan" && m["vlan"] != "" {
 				device := networkGetHostDevice(m["parent"], m["vlan"])
 				if !shared.PathExists(fmt.Sprintf("/sys/class/net/%s", device)) {
-					err := shared.RunCommand("ip", "link", "add", "link", m["parent"], "name", device, "up", "type", "vlan", "id", m["vlan"])
+					_, err := shared.RunCommand("ip", "link", "add", "link", m["parent"], "name", device, "up", "type", "vlan", "id", m["vlan"])
 					if err != nil {
 						return "", err
 					}
@@ -1950,16 +1950,16 @@ func (c *containerLXC) Start(stateful bool) error {
 	}
 
 	// Start the LXC container
-	out, err := exec.Command(
+	out, err := shared.RunCommand(
 		execPath,
 		"forkstart",
 		c.name,
 		c.daemon.lxcpath,
-		configPath).CombinedOutput()
+		configPath)
 
 	// Capture debug output
-	if string(out) != "" {
-		for _, line := range strings.Split(strings.TrimRight(string(out), "\n"), "\n") {
+	if out != "" {
+		for _, line := range strings.Split(strings.TrimRight(out, "\n"), "\n") {
 			shared.LogDebugf("forkstart: %s", line)
 		}
 	}
@@ -4170,18 +4170,18 @@ func (c *containerLXC) Migrate(cmd uint, stateDir string, function string, stop
 
 		configPath := filepath.Join(c.LogPath(), "lxc.conf")
 
-		var out []byte
-		out, migrateErr = exec.Command(
+		var out string
+		out, migrateErr = shared.RunCommand(
 			execPath,
 			"forkmigrate",
 			c.name,
 			c.daemon.lxcpath,
 			configPath,
 			stateDir,
-			fmt.Sprintf("%v", preservesInodes)).CombinedOutput()
+			fmt.Sprintf("%v", preservesInodes))
 
-		if string(out) != "" {
-			for _, line := range strings.Split(strings.TrimRight(string(out), "\n"), "\n") {
+		if out != "" {
+			for _, line := range strings.Split(strings.TrimRight(out, "\n"), "\n") {
 				shared.LogDebugf("forkmigrate: %s", line)
 			}
 		}
@@ -4194,7 +4194,7 @@ func (c *containerLXC) Migrate(cmd uint, stateDir string, function string, stop
 				filepath.Join(c.LogPath(), "lxc.conf"),
 				stateDir,
 				err,
-				string(out))
+				out)
 		}
 
 	} else {
@@ -4413,13 +4413,13 @@ func (c *containerLXC) FileExists(path string) error {
 	}
 
 	// Check if the file exists in the container
-	out, err := exec.Command(
+	out, err := shared.RunCommand(
 		execPath,
 		"forkcheckfile",
 		c.RootfsPath(),
 		fmt.Sprintf("%d", c.InitPID()),
 		path,
-	).CombinedOutput()
+	)
 
 	// Tear down container storage if needed
 	if !c.IsRunning() {
@@ -4430,12 +4430,12 @@ func (c *containerLXC) FileExists(path string) error {
 	}
 
 	// Process forkcheckfile response
-	if string(out) != "" {
-		if strings.HasPrefix(string(out), "error:") {
-			return fmt.Errorf(strings.TrimPrefix(strings.TrimSuffix(string(out), "\n"), "error: "))
+	if out != "" {
+		if strings.HasPrefix(out, "error:") {
+			return fmt.Errorf(strings.TrimPrefix(strings.TrimSuffix(out, "\n"), "error: "))
 		}
 
-		for _, line := range strings.Split(strings.TrimRight(string(out), "\n"), "\n") {
+		for _, line := range strings.Split(strings.TrimRight(out, "\n"), "\n") {
 			shared.LogDebugf("forkcheckfile: %s", line)
 		}
 	}
@@ -4462,14 +4462,14 @@ func (c *containerLXC) FilePull(srcpath string, dstpath string) (int64, int64, o
 	}
 
 	// Get the file from the container
-	out, err := exec.Command(
+	out, err := shared.RunCommand(
 		execPath,
 		"forkgetfile",
 		c.RootfsPath(),
 		fmt.Sprintf("%d", c.InitPID()),
 		dstpath,
 		srcpath,
-	).CombinedOutput()
+	)
 
 	// Tear down container storage if needed
 	if !c.IsRunning() {
@@ -4487,7 +4487,7 @@ func (c *containerLXC) FilePull(srcpath string, dstpath string) (int64, int64, o
 	var errStr string
 
 	// Process forkgetfile response
-	for _, line := range strings.Split(strings.TrimRight(string(out), "\n"), "\n") {
+	for _, line := range strings.Split(strings.TrimRight(out, "\n"), "\n") {
 		if line == "" {
 			continue
 		}
@@ -4604,7 +4604,7 @@ func (c *containerLXC) FilePush(srcpath string, dstpath string, uid int64, gid i
 	}
 
 	// Push the file to the container
-	out, err := exec.Command(
+	out, err := shared.RunCommand(
 		execPath,
 		"forkputfile",
 		c.RootfsPath(),
@@ -4618,7 +4618,7 @@ func (c *containerLXC) FilePush(srcpath string, dstpath string, uid int64, gid i
 		fmt.Sprintf("%d", rootGid),
 		fmt.Sprintf("%d", int(os.FileMode(0640)&os.ModePerm)),
 		write,
-	).CombinedOutput()
+	)
 
 	// Tear down container storage if needed
 	if !c.IsRunning() {
@@ -4629,7 +4629,7 @@ func (c *containerLXC) FilePush(srcpath string, dstpath string, uid int64, gid i
 	}
 
 	// Process forkgetfile response
-	for _, line := range strings.Split(strings.TrimRight(string(out), "\n"), "\n") {
+	for _, line := range strings.Split(strings.TrimRight(out, "\n"), "\n") {
 		if line == "" {
 			continue
 		}
@@ -4682,13 +4682,13 @@ func (c *containerLXC) FileRemove(path string) error {
 	}
 
 	// Remove the file from the container
-	out, err := exec.Command(
+	out, err := shared.RunCommand(
 		execPath,
 		"forkremovefile",
 		c.RootfsPath(),
 		fmt.Sprintf("%d", c.InitPID()),
 		path,
-	).CombinedOutput()
+	)
 
 	// Tear down container storage if needed
 	if !c.IsRunning() {
@@ -4699,7 +4699,7 @@ func (c *containerLXC) FileRemove(path string) error {
 	}
 
 	// Process forkremovefile response
-	for _, line := range strings.Split(strings.TrimRight(string(out), "\n"), "\n") {
+	for _, line := range strings.Split(strings.TrimRight(out, "\n"), "\n") {
 		if line == "" {
 			continue
 		}
@@ -4906,20 +4906,20 @@ func (c *containerLXC) networkState() map[string]api.ContainerStateNetwork {
 	}
 
 	// Get the network state from the container
-	out, err := exec.Command(
+	out, err := shared.RunCommand(
 		execPath,
 		"forkgetnet",
-		fmt.Sprintf("%d", pid)).CombinedOutput()
+		fmt.Sprintf("%d", pid))
 
 	// Process forkgetnet response
 	if err != nil {
-		shared.LogError("Error calling 'lxd forkgetnet", log.Ctx{"container": c.name, "output": string(out), "pid": pid})
+		shared.LogError("Error calling 'lxd forkgetnet", log.Ctx{"container": c.name, "output": out, "pid": pid})
 		return result
 	}
 
 	networks := map[string]api.ContainerStateNetwork{}
 
-	err = json.Unmarshal(out, &networks)
+	err = json.Unmarshal([]byte(out), &networks)
 	if err != nil {
 		shared.LogError("Failure to read forkgetnet json", log.Ctx{"container": c.name, "err": err})
 		return result
@@ -5143,10 +5143,10 @@ func (c *containerLXC) insertMount(source, target, fstype string, flags int) err
 	mntsrc := filepath.Join("/dev/.lxd-mounts", filepath.Base(tmpMount))
 	pidStr := fmt.Sprintf("%d", pid)
 
-	out, err := exec.Command(execPath, "forkmount", pidStr, mntsrc, target).CombinedOutput()
+	out, err := shared.RunCommand(execPath, "forkmount", pidStr, mntsrc, target)
 
-	if string(out) != "" {
-		for _, line := range strings.Split(strings.TrimRight(string(out), "\n"), "\n") {
+	if out != "" {
+		for _, line := range strings.Split(strings.TrimRight(out, "\n"), "\n") {
 			shared.LogDebugf("forkmount: %s", line)
 		}
 	}
@@ -5173,10 +5173,10 @@ func (c *containerLXC) removeMount(mount string) error {
 
 	// Remove the mount from the container
 	pidStr := fmt.Sprintf("%d", pid)
-	out, err := exec.Command(execPath, "forkumount", pidStr, mount).CombinedOutput()
+	out, err := shared.RunCommand(execPath, "forkumount", pidStr, mount)
 
-	if string(out) != "" {
-		for _, line := range strings.Split(strings.TrimRight(string(out), "\n"), "\n") {
+	if out != "" {
+		for _, line := range strings.Split(strings.TrimRight(out, "\n"), "\n") {
 			shared.LogDebugf("forkumount: %s", line)
 		}
 	}
@@ -5562,12 +5562,12 @@ func (c *containerLXC) createNetworkDevice(name string, m types.Device) (string,
 	if shared.StringInSlice(m["nictype"], []string{"bridged", "p2p"}) {
 		n2 := deviceNextVeth()
 
-		err := exec.Command("ip", "link", "add", n1, "type", "veth", "peer", "name", n2).Run()
+		_, err := shared.RunCommand("ip", "link", "add", n1, "type", "veth", "peer", "name", n2)
 		if err != nil {
 			return "", fmt.Errorf("Failed to create the veth interface: %s", err)
 		}
 
-		err = exec.Command("ip", "link", "set", n1, "up").Run()
+		_, err = shared.RunCommand("ip", "link", "set", n1, "up")
 		if err != nil {
 			return "", fmt.Errorf("Failed to bring up the veth interface %s: %s", n1, err)
 		}
@@ -5600,14 +5600,14 @@ func (c *containerLXC) createNetworkDevice(name string, m types.Device) (string,
 		if m["vlan"] != "" {
 			device = networkGetHostDevice(m["parent"], m["vlan"])
 			if !shared.PathExists(fmt.Sprintf("/sys/class/net/%s", device)) {
-				err := shared.RunCommand("ip", "link", "add", "link", m["parent"], "name", device, "up", "type", "vlan", "id", m["vlan"])
+				_, err := shared.RunCommand("ip", "link", "add", "link", m["parent"], "name", device, "up", "type", "vlan", "id", m["vlan"])
 				if err != nil {
 					return "", err
 				}
 			}
 		}
 
-		err := exec.Command("ip", "link", "add", n1, "link", device, "type", "macvlan", "mode", "bridge").Run()
+		_, err := shared.RunCommand("ip", "link", "add", n1, "link", device, "type", "macvlan", "mode", "bridge")
 		if err != nil {
 			return "", fmt.Errorf("Failed to create the new macvlan interface: %s", err)
 		}
@@ -5617,7 +5617,7 @@ func (c *containerLXC) createNetworkDevice(name string, m types.Device) (string,
 
 	// Set the MAC address
 	if m["hwaddr"] != "" {
-		err := exec.Command("ip", "link", "set", "dev", dev, "address", m["hwaddr"]).Run()
+		_, err := shared.RunCommand("ip", "link", "set", "dev", dev, "address", m["hwaddr"])
 		if err != nil {
 			deviceRemoveInterface(dev)
 			return "", fmt.Errorf("Failed to set the MAC address: %s", err)
@@ -5625,7 +5625,7 @@ func (c *containerLXC) createNetworkDevice(name string, m types.Device) (string,
 	}
 
 	// Bring the interface up
-	err := exec.Command("ip", "link", "set", "dev", dev, "up").Run()
+	_, err := shared.RunCommand("ip", "link", "set", "dev", dev, "up")
 	if err != nil {
 		deviceRemoveInterface(dev)
 		return "", fmt.Errorf("Failed to bring up the interface: %s", err)
@@ -5790,12 +5790,12 @@ func (c *containerLXC) fillNetworkDevice(name string, m types.Device) (types.Dev
 }
 
 func (c *containerLXC) createNetworkFilter(name string, bridge string, hwaddr string) error {
-	err := shared.RunCommand("ebtables", "-A", "FORWARD", "-s", "!", hwaddr, "-i", name, "-o", bridge, "-j", "DROP")
+	_, err := shared.RunCommand("ebtables", "-A", "FORWARD", "-s", "!", hwaddr, "-i", name, "-o", bridge, "-j", "DROP")
 	if err != nil {
 		return err
 	}
 
-	err = shared.RunCommand("ebtables", "-A", "INPUT", "-s", "!", hwaddr, "-i", name, "-j", "DROP")
+	_, err = shared.RunCommand("ebtables", "-A", "INPUT", "-s", "!", hwaddr, "-i", name, "-j", "DROP")
 	if err != nil {
 		return err
 	}
@@ -5804,8 +5804,8 @@ func (c *containerLXC) createNetworkFilter(name string, bridge string, hwaddr st
 }
 
 func (c *containerLXC) removeNetworkFilter(hwaddr string, bridge string) error {
-	out, err := exec.Command("ebtables", "-L", "--Lmac2", "--Lx").Output()
-	for _, line := range strings.Split(string(out), "\n") {
+	out, err := shared.RunCommand("ebtables", "-L", "--Lmac2", "--Lx")
+	for _, line := range strings.Split(out, "\n") {
 		line = strings.TrimSpace(line)
 		fields := strings.Fields(line)
 
@@ -5813,7 +5813,7 @@ func (c *containerLXC) removeNetworkFilter(hwaddr string, bridge string) error {
 			match := []string{"ebtables", "-t", "filter", "-A", "INPUT", "-s", "!", hwaddr, "-i", fields[9], "-j", "DROP"}
 			if reflect.DeepEqual(fields, match) {
 				fields[3] = "-D"
-				err = shared.RunCommand(fields[0], fields[1:]...)
+				_, err = shared.RunCommand(fields[0], fields[1:]...)
 				if err != nil {
 					return err
 				}
@@ -5822,7 +5822,7 @@ func (c *containerLXC) removeNetworkFilter(hwaddr string, bridge string) error {
 			match := []string{"ebtables", "-t", "filter", "-A", "FORWARD", "-s", "!", hwaddr, "-i", fields[9], "-o", bridge, "-j", "DROP"}
 			if reflect.DeepEqual(fields, match) {
 				fields[3] = "-D"
-				err = shared.RunCommand(fields[0], fields[1:]...)
+				_, err = shared.RunCommand(fields[0], fields[1:]...)
 				if err != nil {
 					return err
 				}
@@ -6488,34 +6488,34 @@ func (c *containerLXC) setNetworkLimits(name string, m types.Device) error {
 	}
 
 	// Clean any existing entry
-	_ = exec.Command("tc", "qdisc", "del", "dev", veth, "root").Run()
-	_ = exec.Command("tc", "qdisc", "del", "dev", veth, "ingress").Run()
+	shared.RunCommand("tc", "qdisc", "del", "dev", veth, "root")
+	shared.RunCommand("tc", "qdisc", "del", "dev", veth, "ingress")
 
 	// Apply new limits
 	if m["limits.ingress"] != "" {
-		out, err := exec.Command("tc", "qdisc", "add", "dev", veth, "root", "handle", "1:0", "htb", "default", "10").CombinedOutput()
+		out, err := shared.RunCommand("tc", "qdisc", "add", "dev", veth, "root", "handle", "1:0", "htb", "default", "10")
 		if err != nil {
 			return fmt.Errorf("Failed to create root tc qdisc: %s", out)
 		}
 
-		out, err = exec.Command("tc", "class", "add", "dev", veth, "parent", "1:0", "classid", "1:10", "htb", "rate", fmt.Sprintf("%dbit", ingressInt)).CombinedOutput()
+		out, err = shared.RunCommand("tc", "class", "add", "dev", veth, "parent", "1:0", "classid", "1:10", "htb", "rate", fmt.Sprintf("%dbit", ingressInt))
 		if err != nil {
 			return fmt.Errorf("Failed to create limit tc class: %s", out)
 		}
 
-		out, err = exec.Command("tc", "filter", "add", "dev", veth, "parent", "1:0", "protocol", "all", "u32", "match", "u32", "0", "0", "flowid", "1:1").CombinedOutput()
+		out, err = shared.RunCommand("tc", "filter", "add", "dev", veth, "parent", "1:0", "protocol", "all", "u32", "match", "u32", "0", "0", "flowid", "1:1")
 		if err != nil {
 			return fmt.Errorf("Failed to create tc filter: %s", out)
 		}
 	}
 
 	if m["limits.egress"] != "" {
-		out, err := exec.Command("tc", "qdisc", "add", "dev", veth, "handle", "ffff:0", "ingress").CombinedOutput()
+		out, err := shared.RunCommand("tc", "qdisc", "add", "dev", veth, "handle", "ffff:0", "ingress")
 		if err != nil {
 			return fmt.Errorf("Failed to create ingress tc qdisc: %s", out)
 		}
 
-		out, err = exec.Command("tc", "filter", "add", "dev", veth, "parent", "ffff:0", "protocol", "all", "u32", "match", "u32", "0", "0", "police", "rate", fmt.Sprintf("%dbit", egressInt), "burst", "1024k", "mtu", "64kb", "drop", "flowid", ":1").CombinedOutput()
+		out, err = shared.RunCommand("tc", "filter", "add", "dev", veth, "parent", "ffff:0", "protocol", "all", "u32", "match", "u32", "0", "0", "police", "rate", fmt.Sprintf("%dbit", egressInt), "burst", "1024k", "mtu", "64kb", "drop", "flowid", ":1")
 		if err != nil {
 			return fmt.Errorf("Failed to create ingress tc qdisc: %s", out)
 		}
diff --git a/lxd/db_update.go b/lxd/db_update.go
index 8109b39..9d274a0 100644
--- a/lxd/db_update.go
+++ b/lxd/db_update.go
@@ -5,7 +5,6 @@ import (
 	"fmt"
 	"io/ioutil"
 	"os"
-	"os/exec"
 	"path/filepath"
 	"strconv"
 	"strings"
@@ -481,9 +480,9 @@ func dbUpdateFromV15(currentVersion int, version int, d *Daemon) error {
 
 		shared.LogDebug("About to rename cName in lv upgrade", log.Ctx{"lvLinkPath": lvLinkPath, "cName": cName, "newLVName": newLVName})
 
-		output, err := exec.Command("lvrename", vgName, cName, newLVName).CombinedOutput()
+		output, err := shared.RunCommand("lvrename", vgName, cName, newLVName)
 		if err != nil {
-			return fmt.Errorf("Could not rename LV '%s' to '%s': %v\noutput:%s", cName, newLVName, err, string(output))
+			return fmt.Errorf("Could not rename LV '%s' to '%s': %v\noutput:%s", cName, newLVName, err, output)
 		}
 
 		if err := os.Remove(lvLinkPath); err != nil {
diff --git a/lxd/devices.go b/lxd/devices.go
index 238863f..dff4493 100644
--- a/lxd/devices.go
+++ b/lxd/devices.go
@@ -9,7 +9,6 @@ import (
 	"io/ioutil"
 	"math/big"
 	"os"
-	"os/exec"
 	"path"
 	"path/filepath"
 	"regexp"
@@ -877,7 +876,8 @@ func deviceNextVeth() string {
 }
 
 func deviceRemoveInterface(nic string) error {
-	return exec.Command("ip", "link", "del", nic).Run()
+	_, err := shared.RunCommand("ip", "link", "del", nic)
+	return err
 }
 
 func deviceMountDisk(srcPath string, dstPath string, readonly bool, recursive bool) error {
@@ -1071,13 +1071,13 @@ func deviceGetParentBlocks(path string) ([]string, error) {
 		// Accessible zfs filesystems
 		poolName := strings.Split(device[1], "/")[0]
 
-		output, err := exec.Command("zpool", "status", "-P", "-L", poolName).CombinedOutput()
+		output, err := shared.RunCommand("zpool", "status", "-P", "-L", poolName)
 		if err != nil {
 			return nil, fmt.Errorf("Failed to query zfs filesystem information for %s: %s", device[1], output)
 		}
 
 		header := true
-		for _, line := range strings.Split(string(output), "\n") {
+		for _, line := range strings.Split(output, "\n") {
 			fields := strings.Fields(line)
 			if len(fields) < 5 {
 				continue
@@ -1125,12 +1125,12 @@ func deviceGetParentBlocks(path string) ([]string, error) {
 		}
 	} else if fs == "btrfs" && shared.PathExists(device[1]) {
 		// Accessible btrfs filesystems
-		output, err := exec.Command("btrfs", "filesystem", "show", device[1]).CombinedOutput()
+		output, err := shared.RunCommand("btrfs", "filesystem", "show", device[1])
 		if err != nil {
 			return nil, fmt.Errorf("Failed to query btrfs filesystem information for %s: %s", device[1], output)
 		}
 
-		for _, line := range strings.Split(string(output), "\n") {
+		for _, line := range strings.Split(output, "\n") {
 			fields := strings.Fields(line)
 			if len(fields) == 0 || fields[0] != "devid" {
 				continue
diff --git a/lxd/images.go b/lxd/images.go
index 7b95ee3..c203289 100644
--- a/lxd/images.go
+++ b/lxd/images.go
@@ -117,7 +117,7 @@ func unpack(d *Daemon, file string, path string, sType storageType) error {
 		return fmt.Errorf("Unsupported image format: %s", extension)
 	}
 
-	output, err := exec.Command(command, args...).CombinedOutput()
+	output, err := shared.RunCommand(command, args...)
 	if err != nil {
 		// Check if we ran out of space
 		fs := syscall.Statfs_t{}
@@ -136,7 +136,7 @@ func unpack(d *Daemon, file string, path string, sType storageType) error {
 			}
 		}
 
-		co := string(output)
+		co := output
 		shared.LogDebugf("Unpacking failed")
 		shared.LogDebugf(co)
 
@@ -801,15 +801,15 @@ func getImageMetadata(fname string) (*imageMetadata, error) {
 	args = append(args, fname, metadataName)
 
 	// read the metadata.yaml
-	output, err := exec.Command("tar", args...).CombinedOutput()
+	output, err := shared.RunCommand("tar", args...)
 
 	if err != nil {
-		outputLines := strings.Split(string(output), "\n")
+		outputLines := strings.Split(output, "\n")
 		return nil, fmt.Errorf("Could not extract image %s from tar: %v (%s)", metadataName, err, outputLines[0])
 	}
 
 	metadata := imageMetadata{}
-	err = yaml.Unmarshal(output, &metadata)
+	err = yaml.Unmarshal([]byte(output), &metadata)
 
 	if err != nil {
 		return nil, fmt.Errorf("Could not parse %s: %v", metadataName, err)
diff --git a/lxd/main_init.go b/lxd/main_init.go
index bb8c3ee..3bc1c49 100644
--- a/lxd/main_init.go
+++ b/lxd/main_init.go
@@ -47,7 +47,7 @@ func cmdInit() error {
 	if err == nil && len(out) != 0 && !runningInUserns {
 		_ = loadModule("zfs")
 
-		err := shared.RunCommand("zpool", "list")
+		_, err := shared.RunCommand("zpool", "list")
 		if err == nil {
 			backendsAvailable = append(backendsAvailable, "zfs")
 		}
diff --git a/lxd/networks.go b/lxd/networks.go
index 8a03d56..3ea207b 100644
--- a/lxd/networks.go
+++ b/lxd/networks.go
@@ -8,7 +8,6 @@ import (
 	"net"
 	"net/http"
 	"os"
-	"os/exec"
 	"strconv"
 	"strings"
 
@@ -212,7 +211,7 @@ func doNetworkGet(d *Daemon, name string) (api.Network, error) {
 	} else if shared.PathExists(fmt.Sprintf("/sys/class/net/%s/bonding", n.Name)) {
 		n.Type = "bond"
 	} else {
-		_, err := exec.Command("ovs-vsctl", "br-exists", n.Name).CombinedOutput()
+		_, err := shared.RunCommand("ovs-vsctl", "br-exists", n.Name)
 		if err == nil {
 			n.Type = "bridge"
 		} else {
@@ -534,12 +533,12 @@ func (n *network) Start() error {
 	// Create the bridge interface
 	if !n.IsRunning() {
 		if n.config["bridge.driver"] == "openvswitch" {
-			err := shared.RunCommand("ovs-vsctl", "add-br", n.name)
+			_, err := shared.RunCommand("ovs-vsctl", "add-br", n.name)
 			if err != nil {
 				return err
 			}
 		} else {
-			err := shared.RunCommand("ip", "link", "add", n.name, "type", "bridge")
+			_, err := shared.RunCommand("ip", "link", "add", n.name, "type", "bridge")
 			if err != nil {
 				return err
 			}
@@ -571,7 +570,7 @@ func (n *network) Start() error {
 	// Cleanup any existing tunnel device
 	for _, iface := range ifaces {
 		if strings.HasPrefix(iface.Name, fmt.Sprintf("%s-", n.name)) {
-			err = shared.RunCommand("ip", "link", "del", iface.Name)
+			_, err = shared.RunCommand("ip", "link", "del", iface.Name)
 			if err != nil {
 				return err
 			}
@@ -594,7 +593,7 @@ func (n *network) Start() error {
 
 	// Attempt to add a dummy device to the bridge to force the MTU
 	if mtu != "" && n.config["bridge.driver"] != "openvswitch" {
-		err = shared.RunCommand("ip", "link", "add", fmt.Sprintf("%s-mtu", n.name), "mtu", mtu, "type", "dummy")
+		_, err = shared.RunCommand("ip", "link", "add", fmt.Sprintf("%s-mtu", n.name), "mtu", mtu, "type", "dummy")
 		if err == nil {
 			networkAttachInterface(n.name, fmt.Sprintf("%s-mtu", n.name))
 		}
@@ -605,13 +604,13 @@ func (n *network) Start() error {
 		mtu = "1500"
 	}
 
-	err = shared.RunCommand("ip", "link", "set", n.name, "mtu", mtu)
+	_, err = shared.RunCommand("ip", "link", "set", n.name, "mtu", mtu)
 	if err != nil {
 		return err
 	}
 
 	// Bring it up
-	err = shared.RunCommand("ip", "link", "set", n.name, "up")
+	_, err = shared.RunCommand("ip", "link", "set", n.name, "up")
 	if err != nil {
 		return err
 	}
@@ -654,12 +653,12 @@ func (n *network) Start() error {
 	}
 
 	// Flush all IPv4 addresses and routes
-	err = shared.RunCommand("ip", "-4", "addr", "flush", "dev", n.name, "scope", "global")
+	_, err = shared.RunCommand("ip", "-4", "addr", "flush", "dev", n.name, "scope", "global")
 	if err != nil {
 		return err
 	}
 
-	err = shared.RunCommand("ip", "-4", "route", "flush", "dev", n.name, "proto", "static")
+	_, err = shared.RunCommand("ip", "-4", "route", "flush", "dev", n.name, "proto", "static")
 	if err != nil {
 		return err
 	}
@@ -758,7 +757,7 @@ func (n *network) Start() error {
 		}
 
 		// Add the address
-		err = shared.RunCommand("ip", "-4", "addr", "add", "dev", n.name, n.config["ipv4.address"])
+		_, err = shared.RunCommand("ip", "-4", "addr", "add", "dev", n.name, n.config["ipv4.address"])
 		if err != nil {
 			return err
 		}
@@ -775,7 +774,7 @@ func (n *network) Start() error {
 		if n.config["ipv4.routes"] != "" {
 			for _, route := range strings.Split(n.config["ipv4.routes"], ",") {
 				route = strings.TrimSpace(route)
-				err = shared.RunCommand("ip", "-4", "route", "add", "dev", n.name, route, "proto", "static")
+				_, err = shared.RunCommand("ip", "-4", "route", "add", "dev", n.name, route, "proto", "static")
 				if err != nil {
 					return err
 				}
@@ -795,12 +794,12 @@ func (n *network) Start() error {
 	}
 
 	// Flush all IPv6 addresses and routes
-	err = shared.RunCommand("ip", "-6", "addr", "flush", "dev", n.name, "scope", "global")
+	_, err = shared.RunCommand("ip", "-6", "addr", "flush", "dev", n.name, "scope", "global")
 	if err != nil {
 		return err
 	}
 
-	err = shared.RunCommand("ip", "-6", "route", "flush", "dev", n.name, "proto", "static")
+	_, err = shared.RunCommand("ip", "-6", "route", "flush", "dev", n.name, "proto", "static")
 	if err != nil {
 		return err
 	}
@@ -918,7 +917,7 @@ func (n *network) Start() error {
 		}
 
 		// Add the address
-		err = shared.RunCommand("ip", "-6", "addr", "add", "dev", n.name, n.config["ipv6.address"])
+		_, err = shared.RunCommand("ip", "-6", "addr", "add", "dev", n.name, n.config["ipv6.address"])
 		if err != nil {
 			return err
 		}
@@ -935,7 +934,7 @@ func (n *network) Start() error {
 		if n.config["ipv6.routes"] != "" {
 			for _, route := range strings.Split(n.config["ipv6.routes"], ",") {
 				route = strings.TrimSpace(route)
-				err = shared.RunCommand("ip", "-6", "route", "add", "dev", n.name, route, "proto", "static")
+				_, err = shared.RunCommand("ip", "-6", "route", "add", "dev", n.name, route, "proto", "static")
 				if err != nil {
 					return err
 				}
@@ -983,7 +982,7 @@ func (n *network) Start() error {
 		}
 
 		// Add the address
-		err = shared.RunCommand("ip", "-4", "addr", "add", "dev", n.name, fanAddress)
+		_, err = shared.RunCommand("ip", "-4", "addr", "add", "dev", n.name, fanAddress)
 		if err != nil {
 			return err
 		}
@@ -998,12 +997,12 @@ func (n *network) Start() error {
 
 		// Setup the tunnel
 		if n.config["fan.type"] == "ipip" {
-			err = shared.RunCommand("ip", "-4", "route", "flush", "dev", "tunl0")
+			_, err = shared.RunCommand("ip", "-4", "route", "flush", "dev", "tunl0")
 			if err != nil {
 				return err
 			}
 
-			err = shared.RunCommand("ip", "link", "set", "tunl0", "up")
+			_, err = shared.RunCommand("ip", "link", "set", "tunl0", "up")
 			if err != nil {
 				return err
 			}
@@ -1011,14 +1010,14 @@ func (n *network) Start() error {
 			// Fails if the map is already set
 			shared.RunCommand("ip", "link", "change", "tunl0", "type", "ipip", "fan-map", fmt.Sprintf("%s:%s", overlay, underlay))
 
-			err = shared.RunCommand("ip", "route", "add", overlay, "dev", "tunl0", "src", addr[0])
+			_, err = shared.RunCommand("ip", "route", "add", overlay, "dev", "tunl0", "src", addr[0])
 			if err != nil {
 				return err
 			}
 		} else {
 			vxlanID := fmt.Sprintf("%d", binary.BigEndian.Uint32(overlaySubnet.IP.To4())>>8)
 
-			err = shared.RunCommand("ip", "link", "add", tunName, "type", "vxlan", "id", vxlanID, "dev", devName, "dstport", "0", "local", devAddr, "fan-map", fmt.Sprintf("%s:%s", overlay, underlay))
+			_, err = shared.RunCommand("ip", "link", "add", tunName, "type", "vxlan", "id", vxlanID, "dev", devName, "dstport", "0", "local", devAddr, "fan-map", fmt.Sprintf("%s:%s", overlay, underlay))
 			if err != nil {
 				return err
 			}
@@ -1028,12 +1027,12 @@ func (n *network) Start() error {
 				return err
 			}
 
-			err = shared.RunCommand("ip", "link", "set", tunName, "mtu", mtu, "up")
+			_, err = shared.RunCommand("ip", "link", "set", tunName, "mtu", mtu, "up")
 			if err != nil {
 				return err
 			}
 
-			err = shared.RunCommand("ip", "link", "set", n.name, "up")
+			_, err = shared.RunCommand("ip", "link", "set", n.name, "up")
 			if err != nil {
 				return err
 			}
@@ -1105,7 +1104,7 @@ func (n *network) Start() error {
 		}
 
 		// Create the interface
-		err = shared.RunCommand(cmd[0], cmd[1:]...)
+		_, err = shared.RunCommand(cmd[0], cmd[1:]...)
 		if err != nil {
 			return err
 		}
@@ -1116,12 +1115,12 @@ func (n *network) Start() error {
 			return err
 		}
 
-		err = shared.RunCommand("ip", "link", "set", tunName, "mtu", mtu, "up")
+		_, err = shared.RunCommand("ip", "link", "set", tunName, "mtu", mtu, "up")
 		if err != nil {
 			return err
 		}
 
-		err = shared.RunCommand("ip", "link", "set", n.name, "up")
+		_, err = shared.RunCommand("ip", "link", "set", n.name, "up")
 		if err != nil {
 			return err
 		}
@@ -1174,9 +1173,9 @@ func (n *network) Start() error {
 		}
 
 		// Start dnsmasq (occasionally races, try a few times)
-		output, err := tryExec(dnsmasqCmd[0], dnsmasqCmd[1:]...)
+		output, err := shared.TryRunCommand(dnsmasqCmd[0], dnsmasqCmd[1:]...)
 		if err != nil {
-			return fmt.Errorf("Failed to run: %s: %s", strings.Join(dnsmasqCmd, " "), strings.TrimSpace(string(output)))
+			return fmt.Errorf("Failed to run: %s: %s", strings.Join(dnsmasqCmd, " "), strings.TrimSpace(output))
 		}
 
 		// Update the static leases
@@ -1196,12 +1195,12 @@ func (n *network) Stop() error {
 
 	// Destroy the bridge interface
 	if n.config["bridge.driver"] == "openvswitch" {
-		err := shared.RunCommand("ovs-vsctl", "del-br", n.name)
+		_, err := shared.RunCommand("ovs-vsctl", "del-br", n.name)
 		if err != nil {
 			return err
 		}
 	} else {
-		err := shared.RunCommand("ip", "link", "del", n.name)
+		_, err := shared.RunCommand("ip", "link", "del", n.name)
 		if err != nil {
 			return err
 		}
@@ -1248,7 +1247,7 @@ func (n *network) Stop() error {
 	// Cleanup any existing tunnel device
 	for _, iface := range ifaces {
 		if strings.HasPrefix(iface.Name, fmt.Sprintf("%s-", n.name)) {
-			err = shared.RunCommand("ip", "link", "del", iface.Name)
+			_, err = shared.RunCommand("ip", "link", "del", iface.Name)
 			if err != nil {
 				return err
 			}
diff --git a/lxd/networks_iptables.go b/lxd/networks_iptables.go
index fb2585e..d493d72 100644
--- a/lxd/networks_iptables.go
+++ b/lxd/networks_iptables.go
@@ -2,7 +2,6 @@ package main
 
 import (
 	"fmt"
-	"os/exec"
 	"strings"
 
 	"github.com/lxc/lxd/shared"
@@ -23,7 +22,8 @@ func networkIptablesPrepend(protocol string, netName string, table string, chain
 	args := append(baseArgs, []string{"-C", chain}...)
 	args = append(args, rule...)
 	args = append(args, "-m", "comment", "--comment", fmt.Sprintf("generated for LXD network %s", netName))
-	if shared.RunCommand(cmd, args...) == nil {
+	_, err := shared.RunCommand(cmd, args...)
+	if err == nil {
 		return nil
 	}
 
@@ -32,7 +32,7 @@ func networkIptablesPrepend(protocol string, netName string, table string, chain
 	args = append(args, rule...)
 	args = append(args, "-m", "comment", "--comment", fmt.Sprintf("generated for LXD network %s", netName))
 
-	err := shared.RunCommand(cmd, args...)
+	_, err = shared.RunCommand(cmd, args...)
 	if err != nil {
 		return err
 	}
@@ -58,12 +58,12 @@ func networkIptablesClear(protocol string, netName string, table string) error {
 
 	// List the rules
 	args := append(baseArgs, "-S")
-	output, err := exec.Command(cmd, args...).Output()
+	output, err := shared.RunCommand(cmd, args...)
 	if err != nil {
 		return fmt.Errorf("Failed to list %s rules for %s (table %s)", protocol, netName, table)
 	}
 
-	for _, line := range strings.Split(string(output), "\n") {
+	for _, line := range strings.Split(output, "\n") {
 		if !strings.Contains(line, fmt.Sprintf("generated for LXD network %s", netName)) {
 			continue
 		}
@@ -73,7 +73,7 @@ func networkIptablesClear(protocol string, netName string, table string) error {
 		fields[0] = "-D"
 
 		args = append(baseArgs, fields...)
-		err = shared.RunCommand("sh", "-c", fmt.Sprintf("%s %s", cmd, strings.Join(args, " ")))
+		_, err = shared.RunCommand("sh", "-c", fmt.Sprintf("%s %s", cmd, strings.Join(args, " ")))
 		if err != nil {
 			return err
 		}
diff --git a/lxd/networks_utils.go b/lxd/networks_utils.go
index 70e4334..0009e99 100644
--- a/lxd/networks_utils.go
+++ b/lxd/networks_utils.go
@@ -11,7 +11,6 @@ import (
 	"math/rand"
 	"net"
 	"os"
-	"os/exec"
 	"path/filepath"
 	"regexp"
 	"strconv"
@@ -35,14 +34,14 @@ func networkAutoAttach(d *Daemon, devName string) error {
 
 func networkAttachInterface(netName string, devName string) error {
 	if shared.PathExists(fmt.Sprintf("/sys/class/net/%s/bridge", netName)) {
-		err := shared.RunCommand("ip", "link", "set", devName, "master", netName)
+		_, err := shared.RunCommand("ip", "link", "set", devName, "master", netName)
 		if err != nil {
 			return err
 		}
 	} else {
-		err := shared.RunCommand("ovs-vsctl", "port-to-br", devName)
+		_, err := shared.RunCommand("ovs-vsctl", "port-to-br", devName)
 		if err != nil {
-			err := shared.RunCommand("ovs-vsctl", "add-port", netName, devName)
+			_, err := shared.RunCommand("ovs-vsctl", "add-port", netName, devName)
 			if err != nil {
 				return err
 			}
@@ -54,14 +53,14 @@ func networkAttachInterface(netName string, devName string) error {
 
 func networkDetachInterface(netName string, devName string) error {
 	if shared.PathExists(fmt.Sprintf("/sys/class/net/%s/bridge", netName)) {
-		err := shared.RunCommand("ip", "link", "set", devName, "nomaster")
+		_, err := shared.RunCommand("ip", "link", "set", devName, "nomaster")
 		if err != nil {
 			return err
 		}
 	} else {
-		err := shared.RunCommand("ovs-vsctl", "port-to-br", devName)
+		_, err := shared.RunCommand("ovs-vsctl", "port-to-br", devName)
 		if err == nil {
-			err := shared.RunCommand("ovs-vsctl", "del-port", netName, devName)
+			_, err := shared.RunCommand("ovs-vsctl", "del-port", netName, devName)
 			if err != nil {
 				return err
 			}
@@ -218,7 +217,7 @@ func networkPingSubnet(subnet *net.IPNet) bool {
 			cmd = "ping6"
 		}
 
-		_, err := exec.Command(cmd, "-n", "-q", ip.String(), "-c", "1", "-W", "1").CombinedOutput()
+		_, err := shared.RunCommand(cmd, "-n", "-q", ip.String(), "-c", "1", "-W", "1")
 		if err != nil {
 			// Remote didn't answer
 			return
diff --git a/lxd/patches.go b/lxd/patches.go
index 369860b..4017343 100644
--- a/lxd/patches.go
+++ b/lxd/patches.go
@@ -3,7 +3,6 @@ package main
 import (
 	"fmt"
 	"os"
-	"os/exec"
 	"path/filepath"
 	"strings"
 	"syscall"
@@ -982,7 +981,7 @@ func upgradeFromStorageTypeLvm(name string, d *Daemon, defaultPoolName string, d
 		newContainerLvName := fmt.Sprintf("%s_%s", storagePoolVolumeApiEndpointContainers, ctLvName)
 		containerLvDevPath := getLvmDevPath(defaultPoolName, storagePoolVolumeApiEndpointContainers, ctLvName)
 		if !shared.PathExists(containerLvDevPath) {
-			_, err := tryExec("lvrename", defaultPoolName, ctLvName, newContainerLvName)
+			_, err := shared.TryRunCommand("lvrename", defaultPoolName, ctLvName, newContainerLvName)
 			if err != nil {
 				return err
 			}
@@ -1083,7 +1082,7 @@ func upgradeFromStorageTypeLvm(name string, d *Daemon, defaultPoolName string, d
 			newSnapshotLvName := fmt.Sprintf("%s_%s", storagePoolVolumeApiEndpointContainers, csLvName)
 			snapshotLvDevPath := getLvmDevPath(defaultPoolName, storagePoolVolumeApiEndpointContainers, csLvName)
 			if !shared.PathExists(snapshotLvDevPath) {
-				_, err := tryExec("lvrename", defaultPoolName, csLvName, newSnapshotLvName)
+				_, err := shared.TryRunCommand("lvrename", defaultPoolName, csLvName, newSnapshotLvName)
 				if err != nil {
 					return err
 				}
@@ -1180,7 +1179,7 @@ func upgradeFromStorageTypeLvm(name string, d *Daemon, defaultPoolName string, d
 		newImageLvName := fmt.Sprintf("%s_%s", storagePoolVolumeApiEndpointImages, img)
 		imageLvDevPath := getLvmDevPath(defaultPoolName, storagePoolVolumeApiEndpointImages, img)
 		if !shared.PathExists(imageLvDevPath) {
-			_, err := tryExec("lvrename", defaultPoolName, img, newImageLvName)
+			_, err := shared.TryRunCommand("lvrename", defaultPoolName, img, newImageLvName)
 			if err != nil {
 				return err
 			}
@@ -1262,11 +1261,11 @@ func upgradeFromStorageTypeZfs(name string, d *Daemon, defaultPoolName string, d
 		// Querying the size of a storage pool only makes sense when it
 		// is not a dataset.
 		if poolName == defaultPoolName {
-			output, err := exec.Command("zpool", "get", "size", "-p", "-H", defaultPoolName).CombinedOutput()
+			output, err := shared.RunCommand("zpool", "get", "size", "-p", "-H", defaultPoolName)
 			if err == nil {
-				lidx := strings.LastIndex(string(output), "\t")
-				fidx := strings.LastIndex(string(output)[:lidx-1], "\t")
-				poolConfig["size"] = string(output)[fidx+1 : lidx]
+				lidx := strings.LastIndex(output, "\t")
+				fidx := strings.LastIndex(output[:lidx-1], "\t")
+				poolConfig["size"] = output[fidx+1 : lidx]
 			}
 		}
 
@@ -1334,7 +1333,7 @@ func upgradeFromStorageTypeZfs(name string, d *Daemon, defaultPoolName string, d
 		ctDataset := fmt.Sprintf("%s/containers/%s", defaultPoolName, ct)
 		oldContainerMntPoint := shared.VarPath("containers", ct)
 		if shared.IsMountPoint(oldContainerMntPoint) {
-			_, err := tryExec("zfs", "unmount", "-f", ctDataset)
+			_, err := shared.TryRunCommand("zfs", "unmount", "-f", ctDataset)
 			if err != nil {
 				shared.LogWarnf("Failed to unmount ZFS filesystem via zfs unmount. Trying lazy umount (MNT_DETACH)...")
 				err := tryUnmount(oldContainerMntPoint, syscall.MNT_DETACH)
@@ -1362,11 +1361,11 @@ func upgradeFromStorageTypeZfs(name string, d *Daemon, defaultPoolName string, d
 
 		// Set new mountpoint for the container's dataset it will be
 		// automatically mounted.
-		output, err := exec.Command(
+		output, err := shared.RunCommand(
 			"zfs",
 			"set",
 			fmt.Sprintf("mountpoint=%s", newContainerMntPoint),
-			ctDataset).CombinedOutput()
+			ctDataset)
 		if err != nil {
 			shared.LogWarnf("Failed to set new ZFS mountpoint: %s.", output)
 			failedUpgradeEntities = append(failedUpgradeEntities, fmt.Sprintf("containers/%s: Failed to set new zfs mountpoint: %s", ct, err))
@@ -1482,7 +1481,7 @@ func upgradeFromStorageTypeZfs(name string, d *Daemon, defaultPoolName string, d
 		// around.
 		imageDataset := fmt.Sprintf("%s/images/%s", defaultPoolName, img)
 		if shared.PathExists(oldImageMntPoint) && shared.IsMountPoint(oldImageMntPoint) {
-			_, err := tryExec("zfs", "unmount", "-f", imageDataset)
+			_, err := shared.TryRunCommand("zfs", "unmount", "-f", imageDataset)
 			if err != nil {
 				shared.LogWarnf("Failed to unmount ZFS filesystem via zfs unmount. Trying lazy umount (MNT_DETACH)...")
 				err := tryUnmount(oldImageMntPoint, syscall.MNT_DETACH)
@@ -1496,7 +1495,7 @@ func upgradeFromStorageTypeZfs(name string, d *Daemon, defaultPoolName string, d
 
 		// Set new mountpoint for the container's dataset it will be
 		// automatically mounted.
-		output, err := exec.Command("zfs", "set", "mountpoint=none", imageDataset).CombinedOutput()
+		output, err := shared.RunCommand("zfs", "set", "mountpoint=none", imageDataset)
 		if err != nil {
 			shared.LogWarnf("Failed to set new ZFS mountpoint: %s.", output)
 		}
diff --git a/lxd/storage.go b/lxd/storage.go
index fb34784..d2b22ea 100644
--- a/lxd/storage.go
+++ b/lxd/storage.go
@@ -5,7 +5,6 @@ import (
 	"fmt"
 	"io"
 	"os"
-	"os/exec"
 	"reflect"
 	"sync"
 	"sync/atomic"
@@ -124,7 +123,7 @@ func storageRsyncCopy(source string, dest string) (string, error) {
 		rsyncVerbosity = "-vi"
 	}
 
-	output, err := exec.Command(
+	output, err := shared.RunCommand(
 		"rsync",
 		"-a",
 		"-HAX",
@@ -134,9 +133,9 @@ func storageRsyncCopy(source string, dest string) (string, error) {
 		"--numeric-ids",
 		rsyncVerbosity,
 		shared.AddSlash(source),
-		dest).CombinedOutput()
+		dest)
 
-	return string(output), err
+	return output, err
 }
 
 // storageType defines the type of a storage
@@ -629,22 +628,6 @@ func ShiftIfNecessary(container container, srcIdmap *shared.IdmapSet) error {
 }
 
 // Useful functions for unreliable backends
-func tryExec(name string, arg ...string) ([]byte, error) {
-	var err error
-	var output []byte
-
-	for i := 0; i < 20; i++ {
-		output, err = exec.Command(name, arg...).CombinedOutput()
-		if err == nil {
-			break
-		}
-
-		time.Sleep(500 * time.Millisecond)
-	}
-
-	return output, err
-}
-
 func tryMount(src string, dst string, fs string, flags uintptr, options string) error {
 	var err error
 
diff --git a/lxd/storage_btrfs.go b/lxd/storage_btrfs.go
index 5b08583..33abab6 100644
--- a/lxd/storage_btrfs.go
+++ b/lxd/storage_btrfs.go
@@ -59,12 +59,12 @@ func (s *storageBtrfs) StorageCoreInit() error {
 		return fmt.Errorf("The 'btrfs' tool isn't available")
 	}
 
-	output, err := exec.Command("btrfs", "version").CombinedOutput()
+	output, err := shared.RunCommand("btrfs", "version")
 	if err != nil {
 		return fmt.Errorf("The 'btrfs' tool isn't working properly")
 	}
 
-	count, err := fmt.Sscanf(strings.SplitN(string(output), " ", 2)[1], "v%s\n", &s.sTypeVersion)
+	count, err := fmt.Sscanf(strings.SplitN(output, " ", 2)[1], "v%s\n", &s.sTypeVersion)
 	if err != nil || count != 1 {
 		return fmt.Errorf("The 'btrfs' tool isn't working properly")
 	}
@@ -118,9 +118,9 @@ func (s *storageBtrfs) StoragePoolCreate() error {
 			return fmt.Errorf("Failed to create sparse file %s: %s", source, err)
 		}
 
-		output, err := exec.Command(
+		output, err := shared.RunCommand(
 			"mkfs.btrfs",
-			"-L", s.pool.Name, source).CombinedOutput()
+			"-L", s.pool.Name, source)
 		if err != nil {
 			return fmt.Errorf("Failed to create the BTRFS pool: %s", output)
 		}
@@ -131,9 +131,9 @@ func (s *storageBtrfs) StoragePoolCreate() error {
 		if filepath.IsAbs(source) {
 			isBlockDev = shared.IsBlockdevPath(source)
 			if isBlockDev {
-				output, err := exec.Command(
+				output, err := shared.RunCommand(
 					"mkfs.btrfs",
-					"-L", s.pool.Name, source).CombinedOutput()
+					"-L", s.pool.Name, source)
 				if err != nil {
 					return fmt.Errorf("Failed to create the BTRFS pool: %s", output)
 				}
@@ -210,8 +210,8 @@ func (s *storageBtrfs) StoragePoolCreate() error {
 	}
 
 	// Enable quotas
-	output, err := exec.Command(
-		"btrfs", "quota", "enable", poolMntPoint).CombinedOutput()
+	output, err := shared.RunCommand(
+		"btrfs", "quota", "enable", poolMntPoint)
 	if err != nil && !runningInUserns {
 		return fmt.Errorf("Failed to enable quotas on BTRFS pool: %s", output)
 	}
@@ -992,12 +992,12 @@ func (s *storageBtrfs) ContainerSetQuota(container container, size int64) error
 		return err
 	}
 
-	output, err := exec.Command(
+	output, err := shared.RunCommand(
 		"btrfs",
 		"qgroup",
 		"limit",
 		"-e", fmt.Sprintf("%d", size),
-		subvol).CombinedOutput()
+		subvol)
 
 	if err != nil {
 		return fmt.Errorf("Failed to set btrfs quota: %s", output)
@@ -1346,13 +1346,13 @@ func btrfsSubVolumeCreate(subvol string) error {
 		}
 	}
 
-	output, err := exec.Command(
+	output, err := shared.RunCommand(
 		"btrfs",
 		"subvolume",
 		"create",
-		subvol).CombinedOutput()
+		subvol)
 	if err != nil {
-		shared.LogErrorf("Failed to create BTRFS subvolume \"%s\": %s.", subvol, string(output))
+		shared.LogErrorf("Failed to create BTRFS subvolume \"%s\": %s.", subvol, output)
 		return err
 	}
 
@@ -1360,20 +1360,20 @@ func btrfsSubVolumeCreate(subvol string) error {
 }
 
 func btrfsSubVolumeQGroup(subvol string) (string, error) {
-	output, err := exec.Command(
+	output, err := shared.RunCommand(
 		"btrfs",
 		"qgroup",
 		"show",
 		subvol,
 		"-e",
-		"-f").CombinedOutput()
+		"-f")
 
 	if err != nil {
 		return "", fmt.Errorf("btrfs quotas not supported. Try enabling them with 'btrfs quota enable'.")
 	}
 
 	var qgroup string
-	for _, line := range strings.Split(string(output), "\n") {
+	for _, line := range strings.Split(output, "\n") {
 		if line == "" || strings.HasPrefix(line, "qgroupid") || strings.HasPrefix(line, "---") {
 			continue
 		}
@@ -1394,19 +1394,19 @@ func btrfsSubVolumeQGroup(subvol string) (string, error) {
 }
 
 func (s *storageBtrfs) btrfsPoolVolumeQGroupUsage(subvol string) (int64, error) {
-	output, err := exec.Command(
+	output, err := shared.RunCommand(
 		"btrfs",
 		"qgroup",
 		"show",
 		subvol,
 		"-e",
-		"-f").CombinedOutput()
+		"-f")
 
 	if err != nil {
 		return -1, fmt.Errorf("btrfs quotas not supported. Try enabling them with 'btrfs quota enable'.")
 	}
 
-	for _, line := range strings.Split(string(output), "\n") {
+	for _, line := range strings.Split(output, "\n") {
 		if line == "" || strings.HasPrefix(line, "qgroupid") || strings.HasPrefix(line, "---") {
 			continue
 		}
@@ -1431,26 +1431,25 @@ func btrfsSubVolumeDelete(subvol string) error {
 	// Attempt (but don't fail on) to delete any qgroup on the subvolume
 	qgroup, err := btrfsSubVolumeQGroup(subvol)
 	if err == nil {
-		exec.Command(
+		shared.RunCommand(
 			"btrfs",
 			"qgroup",
 			"destroy",
 			qgroup,
-			subvol).Run()
+			subvol)
 	}
 
 	// Attempt to make the subvolume writable
-	exec.Command("btrfs", "property", "set", subvol, "ro", "false").CombinedOutput()
+	shared.RunCommand("btrfs", "property", "set", subvol, "ro", "false")
 
 	// Delete the subvolume itself
-	err = exec.Command(
+	_, err = shared.RunCommand(
 		"btrfs",
 		"subvolume",
 		"delete",
-		subvol,
-	).Run()
+		subvol)
 
-	return nil
+	return err
 }
 
 // btrfsPoolVolumesDelete is the recursive variant on btrfsPoolVolumeDelete,
@@ -1483,30 +1482,30 @@ func btrfsSubVolumesDelete(subvol string) error {
  * the result will be readonly if "readonly" is True.
  */
 func btrfsSnapshot(source string, dest string, readonly bool) error {
-	var output []byte
+	var output string
 	var err error
 	if readonly {
-		output, err = exec.Command(
+		output, err = shared.RunCommand(
 			"btrfs",
 			"subvolume",
 			"snapshot",
 			"-r",
 			source,
-			dest).CombinedOutput()
+			dest)
 	} else {
-		output, err = exec.Command(
+		output, err = shared.RunCommand(
 			"btrfs",
 			"subvolume",
 			"snapshot",
 			source,
-			dest).CombinedOutput()
+			dest)
 	}
 	if err != nil {
 		return fmt.Errorf(
 			"subvolume snapshot failed, source=%s, dest=%s, output=%s",
 			source,
 			dest,
-			string(output),
+			output,
 		)
 	}
 
@@ -1967,17 +1966,17 @@ func (s *storageBtrfs) MigrationSink(live bool, container container, snapshots [
 }
 
 func (s *storageBtrfs) btrfsLookupFsUUID(fs string) (string, error) {
-	output, err := exec.Command(
+	output, err := shared.RunCommand(
 		"btrfs",
 		"filesystem",
 		"show",
 		"--raw",
-		fs).CombinedOutput()
+		fs)
 	if err != nil {
 		return "", fmt.Errorf("Failed to detect UUID.")
 	}
 
-	outputString := string(output)
+	outputString := output
 	idx := strings.Index(outputString, "uuid: ")
 	outputString = outputString[idx+6:]
 	outputString = strings.TrimSpace(outputString)
diff --git a/lxd/storage_dir.go b/lxd/storage_dir.go
index 1e083e7..074c661 100644
--- a/lxd/storage_dir.go
+++ b/lxd/storage_dir.go
@@ -3,7 +3,6 @@ package main
 import (
 	"fmt"
 	"os"
-	"os/exec"
 	"path/filepath"
 	"strings"
 
@@ -320,9 +319,9 @@ func (s *storageDir) ContainerDelete(container container) error {
 		err := os.RemoveAll(containerMntPoint)
 		if err != nil {
 			// RemovaAll fails on very long paths, so attempt an rm -Rf
-			output, err := exec.Command("rm", "-Rf", containerMntPoint).CombinedOutput()
+			output, err := shared.RunCommand("rm", "-Rf", containerMntPoint)
 			if err != nil {
-				return fmt.Errorf("Error removing %s: %s.", containerMntPoint, string(output))
+				return fmt.Errorf("Error removing %s: %s.", containerMntPoint, output)
 			}
 		}
 	}
diff --git a/lxd/storage_lvm.go b/lxd/storage_lvm.go
index 75b767c..2f3e364 100644
--- a/lxd/storage_lvm.go
+++ b/lxd/storage_lvm.go
@@ -16,25 +16,25 @@ import (
 )
 
 func storageVGActivate(lvmVolumePath string) error {
-	output, err := tryExec("vgchange", "-ay", lvmVolumePath)
+	output, err := shared.TryRunCommand("vgchange", "-ay", lvmVolumePath)
 	if err != nil {
-		return fmt.Errorf("Could not activate volume group \"%s\": %s.", lvmVolumePath, string(output))
+		return fmt.Errorf("Could not activate volume group \"%s\": %s.", lvmVolumePath, output)
 	}
 
 	return nil
 }
 
 func storageLVActivate(lvmVolumePath string, readonly bool) error {
-	var output []byte
+	var output string
 	var err error
 	if readonly {
-		output, err = tryExec("lvchange", "-ay", "-pr", lvmVolumePath)
+		output, err = shared.TryRunCommand("lvchange", "-ay", "-pr", lvmVolumePath)
 	} else {
-		output, err = tryExec("lvchange", "-ay", lvmVolumePath)
+		output, err = shared.TryRunCommand("lvchange", "-ay", lvmVolumePath)
 	}
 
 	if err != nil {
-		return fmt.Errorf("Could not activate logival volume \"%s\": %s.", lvmVolumePath, string(output))
+		return fmt.Errorf("Could not activate logival volume \"%s\": %s.", lvmVolumePath, output)
 	}
 
 	return nil
@@ -174,30 +174,30 @@ func storageLVMValidateThinPoolName(d *Daemon, vgName string, value string) erro
 }
 
 func lvmVGRename(oldName string, newName string) error {
-	output, err := tryExec("vgrename", oldName, newName)
+	output, err := shared.TryRunCommand("vgrename", oldName, newName)
 	if err != nil {
-		return fmt.Errorf("Could not rename volume group from \"%s\" to \"%s\": %s.", oldName, newName, string(output))
+		return fmt.Errorf("Could not rename volume group from \"%s\" to \"%s\": %s.", oldName, newName, output)
 	}
 
 	return nil
 }
 
 func lvmLVRename(vgName string, oldName string, newName string) error {
-	output, err := tryExec("lvrename", vgName, oldName, newName)
+	output, err := shared.TryRunCommand("lvrename", vgName, oldName, newName)
 	if err != nil {
-		return fmt.Errorf("Could not rename volume group from \"%s\" to \"%s\": %s.", oldName, newName, string(output))
+		return fmt.Errorf("Could not rename volume group from \"%s\" to \"%s\": %s.", oldName, newName, output)
 	}
 
 	return nil
 }
 
 func xfsGenerateNewUUID(lvpath string) error {
-	output, err := exec.Command(
+	output, err := shared.RunCommand(
 		"xfs_admin",
 		"-U", "generate",
-		lvpath).CombinedOutput()
+		lvpath)
 	if err != nil {
-		return fmt.Errorf("Error generating new UUID: %v\noutput:'%s'", err, string(output))
+		return fmt.Errorf("Error generating new UUID: %v\noutput:'%s'", err, output)
 	}
 
 	return nil
@@ -299,11 +299,11 @@ func (s *storageLvm) StorageCoreInit() error {
 	}
 	s.sTypeName = typeName
 
-	output, err := exec.Command("lvm", "version").CombinedOutput()
+	output, err := shared.RunCommand("lvm", "version")
 	if err != nil {
-		return fmt.Errorf("Error getting LVM version: %v\noutput:'%s'", err, string(output))
+		return fmt.Errorf("Error getting LVM version: %v\noutput:'%s'", err, output)
 	}
-	lines := strings.Split(string(output), "\n")
+	lines := strings.Split(output, "\n")
 
 	s.sTypeVersion = ""
 	for idx, line := range lines {
@@ -477,18 +477,18 @@ func (s *storageLvm) StoragePoolCreate() error {
 		ok, err := storagePVExists(loopDevicePath)
 		if err == nil && !ok {
 			// Create a new lvm physical volume.
-			output, err := exec.Command("pvcreate", loopDevicePath).CombinedOutput()
+			output, err := shared.RunCommand("pvcreate", loopDevicePath)
 			if err != nil {
 				return fmt.Errorf("Failed to create the physical volume for the lvm storage pool: %s.", output)
 			}
 			defer func() {
 				if tryUndo {
-					exec.Command("pvremove", loopDevicePath).Run()
+					shared.RunCommand("pvremove", loopDevicePath)
 				}
 			}()
 		}
 
-		msg, err := tryExec("pvscan")
+		msg, err := shared.TryRunCommand("pvscan")
 		if err != nil {
 			shared.LogWarnf("Failed to run pvscan: %s.", msg)
 		}
@@ -497,13 +497,13 @@ func (s *storageLvm) StoragePoolCreate() error {
 		ok, err = storageVGExists(poolName)
 		if err == nil && !ok {
 			// Create a volume group on the physical volume.
-			output, err := exec.Command("vgcreate", poolName, loopDevicePath).CombinedOutput()
+			output, err := shared.RunCommand("vgcreate", poolName, loopDevicePath)
 			if err != nil {
 				return fmt.Errorf("Failed to create the volume group for the lvm storage pool: %s.", output)
 			}
 		}
 
-		msg, err = tryExec("vgscan")
+		msg, err = shared.TryRunCommand("vgscan")
 		if err != nil {
 			shared.LogWarnf("Failed to run vgscan: %s.", msg)
 		}
@@ -525,18 +525,18 @@ func (s *storageLvm) StoragePoolCreate() error {
 			ok, err := storagePVExists(source)
 			if err == nil && !ok {
 				// Create a new lvm physical volume.
-				output, err := exec.Command("pvcreate", source).CombinedOutput()
+				output, err := shared.RunCommand("pvcreate", source)
 				if err != nil {
 					return fmt.Errorf("Failed to create the physical volume for the lvm storage pool: %s.", output)
 				}
 				defer func() {
 					if tryUndo {
-						exec.Command("pvremove", source).Run()
+						shared.RunCommand("pvremove", source)
 					}
 				}()
 			}
 
-			msg, err := tryExec("pvscan")
+			msg, err := shared.TryRunCommand("pvscan")
 			if err != nil {
 				shared.LogWarnf("Failed to run pvscan: %s.", msg)
 			}
@@ -545,13 +545,13 @@ func (s *storageLvm) StoragePoolCreate() error {
 			ok, err = storageVGExists(poolName)
 			if err == nil && !ok {
 				// Create a volume group on the physical volume.
-				output, err := exec.Command("vgcreate", poolName, source).CombinedOutput()
+				output, err := shared.RunCommand("vgcreate", poolName, source)
 				if err != nil {
 					return fmt.Errorf("Failed to create the volume group for the lvm storage pool: %s.", output)
 				}
 			}
 
-			msg, err = tryExec("vgscan")
+			msg, err = shared.TryRunCommand("vgscan")
 			if err != nil {
 				shared.LogWarnf("Failed to run vgscan: %s.", msg)
 			}
@@ -605,7 +605,7 @@ func (s *storageLvm) StoragePoolDelete() error {
 
 	poolName := s.getOnDiskPoolName()
 	// Remove the volume group.
-	output, err := exec.Command("vgremove", "-f", poolName).CombinedOutput()
+	output, err := shared.RunCommand("vgremove", "-f", poolName)
 	if err != nil {
 		return fmt.Errorf("Failed to destroy the volume group for the lvm storage pool: %s.", output)
 	}
@@ -1877,30 +1877,30 @@ func (s *storageLvm) createThinLV(vgName string, thinPoolName string, lvName str
 
 	lvmThinPoolPath := fmt.Sprintf("%s/%s", vgName, thinPoolName)
 	lvmPoolVolumeName := getPrefixedLvName(volumeType, lvName)
-	output, err := tryExec(
+	output, err := shared.TryRunCommand(
 		"lvcreate",
 		"--thin",
 		"-n", lvmPoolVolumeName,
 		"--virtualsize", lvSize+"B", lvmThinPoolPath)
 	if err != nil {
-		shared.LogErrorf("Could not create LV \"%s\": %s.", lvmPoolVolumeName, string(output))
+		shared.LogErrorf("Could not create LV \"%s\": %s.", lvmPoolVolumeName, output)
 		return fmt.Errorf("Could not create thin LV named %s", lvmPoolVolumeName)
 	}
 
 	fsPath := getLvmDevPath(vgName, volumeType, lvName)
 	switch lvFsType {
 	case "xfs":
-		output, err = tryExec("mkfs.xfs", fsPath)
+		output, err = shared.TryRunCommand("mkfs.xfs", fsPath)
 	default:
 		// default = ext4
-		output, err = tryExec(
+		output, err = shared.TryRunCommand(
 			"mkfs.ext4",
 			"-E", "nodiscard,lazy_itable_init=0,lazy_journal_init=0",
 			fsPath)
 	}
 
 	if err != nil {
-		shared.LogErrorf("Filesystem creation failed: %s.", string(output))
+		shared.LogErrorf("Filesystem creation failed: %s.", output)
 		return fmt.Errorf("Error making filesystem on image LV: %v", err)
 	}
 
@@ -1915,15 +1915,15 @@ func (s *storageLvm) createDefaultThinPool(vgName string, thinPoolName string, l
 
 	// Create the thin pool
 	lvmThinPool := fmt.Sprintf("%s/%s", vgName, thinPoolName)
-	var output []byte
+	var output string
 	if isRecent {
-		output, err = tryExec(
+		output, err = shared.TryRunCommand(
 			"lvcreate",
 			"--poolmetadatasize", "1G",
 			"-l", "100%FREE",
 			"--thinpool", lvmThinPool)
 	} else {
-		output, err = tryExec(
+		output, err = shared.TryRunCommand(
 			"lvcreate",
 			"--poolmetadatasize", "1G",
 			"-L", "1G",
@@ -1931,16 +1931,16 @@ func (s *storageLvm) createDefaultThinPool(vgName string, thinPoolName string, l
 	}
 
 	if err != nil {
-		shared.LogErrorf("Could not create thin pool \"%s\": %s.", thinPoolName, string(output))
+		shared.LogErrorf("Could not create thin pool \"%s\": %s.", thinPoolName, output)
 		return fmt.Errorf("Could not create LVM thin pool named %s", thinPoolName)
 	}
 
 	if !isRecent {
 		// Grow it to the maximum VG size (two step process required by old LVM)
-		output, err = tryExec("lvextend", "--alloc", "anywhere", "-l", "100%FREE", lvmThinPool)
+		output, err = shared.TryRunCommand("lvextend", "--alloc", "anywhere", "-l", "100%FREE", lvmThinPool)
 
 		if err != nil {
-			shared.LogErrorf("Could not grow thin pool: \"%s\": %s.", thinPoolName, string(output))
+			shared.LogErrorf("Could not grow thin pool: \"%s\": %s.", thinPoolName, output)
 			return fmt.Errorf("Could not grow LVM thin pool named %s", thinPoolName)
 		}
 	}
@@ -1950,10 +1950,10 @@ func (s *storageLvm) createDefaultThinPool(vgName string, thinPoolName string, l
 
 func (s *storageLvm) removeLV(vgName string, volumeType string, lvName string) error {
 	lvmVolumePath := getLvmDevPath(vgName, volumeType, lvName)
-	output, err := tryExec("lvremove", "-f", lvmVolumePath)
+	output, err := shared.TryRunCommand("lvremove", "-f", lvmVolumePath)
 
 	if err != nil {
-		shared.LogErrorf("Could not remove LV \"%s\": %s.", lvName, string(output))
+		shared.LogErrorf("Could not remove LV \"%s\": %s.", lvName, output)
 		return fmt.Errorf("Could not remove LV named %s", lvName)
 	}
 
@@ -1969,21 +1969,21 @@ func (s *storageLvm) createSnapshotLV(vgName string, origLvName string, origVolu
 	}
 
 	lvmPoolVolumeName := getPrefixedLvName(volumeType, lvName)
-	var output []byte
+	var output string
 	if isRecent {
-		output, err = tryExec(
+		output, err = shared.TryRunCommand(
 			"lvcreate",
 			"-kn",
 			"-n", lvmPoolVolumeName,
 			"-s", sourceLvmVolumePath)
 	} else {
-		output, err = tryExec(
+		output, err = shared.TryRunCommand(
 			"lvcreate",
 			"-n", lvmPoolVolumeName,
 			"-s", sourceLvmVolumePath)
 	}
 	if err != nil {
-		shared.LogErrorf("Could not create LV snapshot: %s -> %s: %s.", origLvName, lvName, string(output))
+		shared.LogErrorf("Could not create LV snapshot: %s -> %s: %s.", origLvName, lvName, output)
 		return "", fmt.Errorf("Could not create snapshot LV named %s", lvName)
 	}
 
diff --git a/lxd/storage_shared.go b/lxd/storage_shared.go
index c3730f9..64bb980 100644
--- a/lxd/storage_shared.go
+++ b/lxd/storage_shared.go
@@ -2,7 +2,6 @@ package main
 
 import (
 	"fmt"
-	"os/exec"
 
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/api"
@@ -82,13 +81,13 @@ func (s *storageShared) setUnprivUserAcl(c container, destPath string) error {
 
 	// Attempt to set a POSIX ACL first.
 	acl := fmt.Sprintf("%d:rx", uid)
-	err = exec.Command("setfacl", "-m", acl, destPath).Run()
+	_, err = shared.RunCommand("setfacl", "-m", acl, destPath)
 	if err == nil {
 		return nil
 	}
 
 	// Fallback to chmod if the fs doesn't support it.
-	err = exec.Command("chmod", "+x", destPath).Run()
+	_, err = shared.RunCommand("chmod", "+x", destPath)
 	if err != nil {
 		shared.LogDebugf("Failed to set executable bit on the container path: %s", err)
 		return err
diff --git a/lxd/storage_zfs.go b/lxd/storage_zfs.go
index 6079380..e521b5b 100644
--- a/lxd/storage_zfs.go
+++ b/lxd/storage_zfs.go
@@ -107,12 +107,12 @@ func (s *storageZfs) StoragePoolCheck() error {
 		shared.LogDebugf("ZFS storage pool \"%s\" does not exist. Trying to import it.", poolName)
 
 		disksPath := shared.VarPath("disks")
-		output, err := exec.Command(
+		output, err := shared.RunCommand(
 			"zpool",
 			"import",
-			"-d", disksPath, poolName).CombinedOutput()
+			"-d", disksPath, poolName)
 		if err != nil {
-			return fmt.Errorf("ZFS storage pool \"%s\" could not be imported: %s.", poolName, string(output))
+			return fmt.Errorf("ZFS storage pool \"%s\" could not be imported: %s.", poolName, output)
 		}
 
 		shared.LogDebugf("ZFS storage pool \"%s\" successfully imported.", poolName)
@@ -1446,13 +1446,13 @@ func (s *storageZfs) ImageUmount(fingerprint string) (bool, error) {
 
 // Helper functions
 func (s *storageZfs) zfsPoolCheck(pool string) error {
-	output, err := exec.Command(
-		"zfs", "get", "type", "-H", "-o", "value", pool).CombinedOutput()
+	output, err := shared.RunCommand(
+		"zfs", "get", "type", "-H", "-o", "value", pool)
 	if err != nil {
-		return fmt.Errorf(strings.Split(string(output), "\n")[0])
+		return fmt.Errorf(strings.Split(output, "\n")[0])
 	}
 
-	poolType := strings.Split(string(output), "\n")[0]
+	poolType := strings.Split(output, "\n")[0]
 	if poolType != "filesystem" {
 		return fmt.Errorf("Unsupported pool type: %s", poolType)
 	}
@@ -1491,10 +1491,10 @@ func (s *storageZfs) zfsPoolCreate() error {
 			return fmt.Errorf("Failed to create sparse file %s: %s", vdev, err)
 		}
 
-		output, err := exec.Command(
+		output, err := shared.RunCommand(
 			"zpool",
 			"create", zpoolName, vdev,
-			"-f", "-m", "none", "-O", "compression=on").CombinedOutput()
+			"-f", "-m", "none", "-O", "compression=on")
 		if err != nil {
 			return fmt.Errorf("Failed to create the ZFS pool: %s", output)
 		}
@@ -1519,10 +1519,10 @@ func (s *storageZfs) zfsPoolCreate() error {
 			// safest way is to just store the name of the zfs pool
 			// we create.
 			s.pool.Config["source"] = zpoolName
-			output, err := exec.Command(
+			output, err := shared.RunCommand(
 				"zpool",
 				"create", zpoolName, vdev,
-				"-f", "-m", "none", "-O", "compression=on").CombinedOutput()
+				"-f", "-m", "none", "-O", "compression=on")
 			if err != nil {
 				return fmt.Errorf("Failed to create the ZFS pool: %s", output)
 			}
@@ -1536,15 +1536,15 @@ func (s *storageZfs) zfsPoolCreate() error {
 			if strings.Contains(vdev, "/") {
 				ok := s.zfsFilesystemEntityExists(vdev, false)
 				if !ok {
-					output, err := exec.Command(
+					output, err := shared.RunCommand(
 						"zfs",
 						"create",
 						"-p",
 						"-o",
 						"mountpoint=none",
-						vdev).CombinedOutput()
+						vdev)
 					if err != nil {
-						shared.LogErrorf("zfs create failed: %s.", string(output))
+						shared.LogErrorf("zfs create failed: %s.", output)
 						return fmt.Errorf("Failed to create ZFS filesystem: %s", output)
 					}
 				}
@@ -1613,15 +1613,15 @@ func (s *storageZfs) zfsPoolCreate() error {
 
 func (s *storageZfs) zfsPoolVolumeClone(source string, name string, dest string, mountpoint string) error {
 	poolName := s.getOnDiskPoolName()
-	output, err := exec.Command(
+	output, err := shared.RunCommand(
 		"zfs",
 		"clone",
 		"-p",
 		"-o", fmt.Sprintf("mountpoint=%s", mountpoint),
 		fmt.Sprintf("%s/%s@%s", poolName, source, name),
-		fmt.Sprintf("%s/%s", poolName, dest)).CombinedOutput()
+		fmt.Sprintf("%s/%s", poolName, dest))
 	if err != nil {
-		shared.LogErrorf("zfs clone failed: %s.", string(output))
+		shared.LogErrorf("zfs clone failed: %s.", output)
 		return fmt.Errorf("Failed to clone the filesystem: %s", output)
 	}
 
@@ -1643,15 +1643,15 @@ func (s *storageZfs) zfsPoolVolumeClone(source string, name string, dest string,
 		destSubvol := dest + strings.TrimPrefix(sub, source)
 		snapshotMntPoint := getSnapshotMountPoint(s.pool.Name, destSubvol)
 
-		output, err := exec.Command(
+		output, err := shared.RunCommand(
 			"zfs",
 			"clone",
 			"-p",
 			"-o", fmt.Sprintf("mountpoint=%s", snapshotMntPoint),
 			fmt.Sprintf("%s/%s@%s", poolName, sub, name),
-			fmt.Sprintf("%s/%s", poolName, destSubvol)).CombinedOutput()
+			fmt.Sprintf("%s/%s", poolName, destSubvol))
 		if err != nil {
-			shared.LogErrorf("zfs clone failed: %s.", string(output))
+			shared.LogErrorf("zfs clone failed: %s.", output)
 			return fmt.Errorf("Failed to clone the sub-volume: %s", output)
 		}
 	}
@@ -1661,13 +1661,13 @@ func (s *storageZfs) zfsPoolVolumeClone(source string, name string, dest string,
 
 func (s *storageZfs) zfsPoolVolumeCreate(path string) error {
 	poolName := s.getOnDiskPoolName()
-	output, err := exec.Command(
+	output, err := shared.RunCommand(
 		"zfs",
 		"create",
 		"-p",
-		fmt.Sprintf("%s/%s", poolName, path)).CombinedOutput()
+		fmt.Sprintf("%s/%s", poolName, path))
 	if err != nil {
-		shared.LogErrorf("zfs create failed: %s.", string(output))
+		shared.LogErrorf("zfs create failed: %s.", output)
 		return fmt.Errorf("Failed to create ZFS filesystem: %s", output)
 	}
 
@@ -1675,15 +1675,15 @@ func (s *storageZfs) zfsPoolVolumeCreate(path string) error {
 }
 
 func (s *storageZfs) zfsFilesystemEntityDelete() error {
-	var output []byte
+	var output string
 	var err error
 	poolName := s.getOnDiskPoolName()
 	if strings.Contains(poolName, "/") {
 		// Command to destroy a zfs dataset.
-		output, err = exec.Command("zfs", "destroy", "-r", poolName).CombinedOutput()
+		output, err = shared.RunCommand("zfs", "destroy", "-r", poolName)
 	} else {
 		// Command to destroy a zfs pool.
-		output, err = exec.Command("zpool", "destroy", "-f", poolName).CombinedOutput()
+		output, err = shared.RunCommand("zpool", "destroy", "-f", poolName)
 	}
 	if err != nil {
 		return fmt.Errorf("Failed to delete the ZFS pool: %s", output)
@@ -1714,14 +1714,14 @@ func (s *storageZfs) zfsPoolVolumeDestroy(path string) error {
 
 	poolName := s.getOnDiskPoolName()
 	// Due to open fds or kernel refs, this may fail for a bit, give it 10s
-	output, err := tryExec(
+	output, err := shared.TryRunCommand(
 		"zfs",
 		"destroy",
 		"-r",
 		fmt.Sprintf("%s/%s", poolName, path))
 
 	if err != nil {
-		shared.LogErrorf("zfs destroy failed: %s.", string(output))
+		shared.LogErrorf("zfs destroy failed: %s.", output)
 		return fmt.Errorf("Failed to destroy ZFS filesystem: %s", output)
 	}
 
@@ -1818,33 +1818,33 @@ func (s *storageZfs) zfsFilesystemEntityPropertyGet(path string, key string, pre
 		fsToCheck = fmt.Sprintf("%s/%s", s.getOnDiskPoolName(), path)
 	}
 
-	output, err := exec.Command(
+	output, err := shared.RunCommand(
 		"zfs",
 		"get",
 		"-H",
 		"-p",
 		"-o", "value",
 		key,
-		fsToCheck).CombinedOutput()
+		fsToCheck)
 	if err != nil {
-		return string(output), fmt.Errorf("Failed to get ZFS config: %s", output)
+		return "", fmt.Errorf("Failed to get ZFS config: %s", output)
 	}
 
-	return strings.TrimRight(string(output), "\n"), nil
+	return strings.TrimRight(output, "\n"), nil
 }
 
 func (s *storageZfs) zfsPoolVolumeRename(source string, dest string) error {
 	var err error
-	var output []byte
+	var output string
 
 	poolName := s.getOnDiskPoolName()
 	for i := 0; i < 20; i++ {
-		output, err = exec.Command(
+		output, err = shared.RunCommand(
 			"zfs",
 			"rename",
 			"-p",
 			fmt.Sprintf("%s/%s", poolName, source),
-			fmt.Sprintf("%s/%s", poolName, dest)).CombinedOutput()
+			fmt.Sprintf("%s/%s", poolName, dest))
 
 		// Success
 		if err == nil {
@@ -1860,19 +1860,19 @@ func (s *storageZfs) zfsPoolVolumeRename(source string, dest string) error {
 	}
 
 	// Timeout
-	shared.LogErrorf("zfs rename failed: %s.", string(output))
+	shared.LogErrorf("zfs rename failed: %s.", output)
 	return fmt.Errorf("Failed to rename ZFS filesystem: %s", output)
 }
 
 func (s *storageZfs) zfsPoolVolumeSet(path string, key string, value string) error {
 	poolName := s.getOnDiskPoolName()
-	output, err := exec.Command(
+	output, err := shared.RunCommand(
 		"zfs",
 		"set",
 		fmt.Sprintf("%s=%s", key, value),
-		fmt.Sprintf("%s/%s", poolName, path)).CombinedOutput()
+		fmt.Sprintf("%s/%s", poolName, path))
 	if err != nil {
-		shared.LogErrorf("zfs set failed: %s.", string(output))
+		shared.LogErrorf("zfs set failed: %s.", output)
 		return fmt.Errorf("Failed to set ZFS config: %s", output)
 	}
 
@@ -1881,13 +1881,13 @@ func (s *storageZfs) zfsPoolVolumeSet(path string, key string, value string) err
 
 func (s *storageZfs) zfsPoolVolumeSnapshotCreate(path string, name string) error {
 	poolName := s.getOnDiskPoolName()
-	output, err := exec.Command(
+	output, err := shared.RunCommand(
 		"zfs",
 		"snapshot",
 		"-r",
-		fmt.Sprintf("%s/%s@%s", poolName, path, name)).CombinedOutput()
+		fmt.Sprintf("%s/%s@%s", poolName, path, name))
 	if err != nil {
-		shared.LogErrorf("zfs snapshot failed: %s.", string(output))
+		shared.LogErrorf("zfs snapshot failed: %s.", output)
 		return fmt.Errorf("Failed to create ZFS snapshot: %s", output)
 	}
 
@@ -1896,13 +1896,13 @@ func (s *storageZfs) zfsPoolVolumeSnapshotCreate(path string, name string) error
 
 func (s *storageZfs) zfsPoolVolumeSnapshotDestroy(path string, name string) error {
 	poolName := s.getOnDiskPoolName()
-	output, err := exec.Command(
+	output, err := shared.RunCommand(
 		"zfs",
 		"destroy",
 		"-r",
-		fmt.Sprintf("%s/%s@%s", poolName, path, name)).CombinedOutput()
+		fmt.Sprintf("%s/%s@%s", poolName, path, name))
 	if err != nil {
-		shared.LogErrorf("zfs destroy failed: %s.", string(output))
+		shared.LogErrorf("zfs destroy failed: %s.", output)
 		return fmt.Errorf("Failed to destroy ZFS snapshot: %s", output)
 	}
 
@@ -1911,12 +1911,12 @@ func (s *storageZfs) zfsPoolVolumeSnapshotDestroy(path string, name string) erro
 
 func (s *storageZfs) zfsPoolVolumeSnapshotRestore(path string, name string) error {
 	poolName := s.getOnDiskPoolName()
-	output, err := tryExec(
+	output, err := shared.TryRunCommand(
 		"zfs",
 		"rollback",
 		fmt.Sprintf("%s/%s@%s", poolName, path, name))
 	if err != nil {
-		shared.LogErrorf("zfs rollback failed: %s.", string(output))
+		shared.LogErrorf("zfs rollback failed: %s.", output)
 		return fmt.Errorf("Failed to restore ZFS snapshot: %s", output)
 	}
 
@@ -1935,12 +1935,12 @@ func (s *storageZfs) zfsPoolVolumeSnapshotRestore(path string, name string) erro
 			continue
 		}
 
-		output, err := tryExec(
+		output, err := shared.TryRunCommand(
 			"zfs",
 			"rollback",
 			fmt.Sprintf("%s/%s@%s", poolName, sub, name))
 		if err != nil {
-			shared.LogErrorf("zfs rollback failed: %s.", string(output))
+			shared.LogErrorf("zfs rollback failed: %s.", output)
 			return fmt.Errorf("Failed to restore ZFS sub-volume snapshot: %s", output)
 		}
 	}
@@ -1950,14 +1950,14 @@ func (s *storageZfs) zfsPoolVolumeSnapshotRestore(path string, name string) erro
 
 func (s *storageZfs) zfsPoolVolumeSnapshotRename(path string, oldName string, newName string) error {
 	poolName := s.getOnDiskPoolName()
-	output, err := exec.Command(
+	output, err := shared.RunCommand(
 		"zfs",
 		"rename",
 		"-r",
 		fmt.Sprintf("%s/%s@%s", poolName, path, oldName),
-		fmt.Sprintf("%s/%s@%s", poolName, path, newName)).CombinedOutput()
+		fmt.Sprintf("%s/%s@%s", poolName, path, newName))
 	if err != nil {
-		shared.LogErrorf("zfs snapshot rename failed: %s.", string(output))
+		shared.LogErrorf("zfs snapshot rename failed: %s.", output)
 		return fmt.Errorf("Failed to rename ZFS snapshot: %s", output)
 	}
 
@@ -1965,7 +1965,7 @@ func (s *storageZfs) zfsPoolVolumeSnapshotRename(path string, oldName string, ne
 }
 
 func zfsMount(poolName string, path string) error {
-	output, err := tryExec(
+	output, err := shared.TryRunCommand(
 		"zfs",
 		"mount",
 		fmt.Sprintf("%s/%s", poolName, path))
@@ -1981,7 +1981,7 @@ func (s *storageZfs) zfsPoolVolumeMount(path string) error {
 }
 
 func zfsUmount(poolName string, path string) error {
-	output, err := tryExec(
+	output, err := shared.TryRunCommand(
 		"zfs",
 		"unmount",
 		fmt.Sprintf("%s/%s", poolName, path))
@@ -1997,20 +1997,20 @@ func (s *storageZfs) zfsPoolVolumeUmount(path string) error {
 }
 
 func (s *storageZfs) zfsPoolListSubvolumes(path string) ([]string, error) {
-	output, err := exec.Command(
+	output, err := shared.RunCommand(
 		"zfs",
 		"list",
 		"-t", "filesystem",
 		"-o", "name",
 		"-H",
-		"-r", path).CombinedOutput()
+		"-r", path)
 	if err != nil {
-		shared.LogErrorf("zfs list failed: %s.", string(output))
+		shared.LogErrorf("zfs list failed: %s.", output)
 		return []string{}, fmt.Errorf("Failed to list ZFS filesystems: %s", output)
 	}
 
 	children := []string{}
-	for _, entry := range strings.Split(string(output), "\n") {
+	for _, entry := range strings.Split(output, "\n") {
 		if entry == "" {
 			continue
 		}
@@ -2034,7 +2034,7 @@ func (s *storageZfs) zfsPoolListSnapshots(path string) ([]string, error) {
 		fullPath = fmt.Sprintf("%s/%s", poolName, path)
 	}
 
-	output, err := exec.Command(
+	output, err := shared.RunCommand(
 		"zfs",
 		"list",
 		"-t", "snapshot",
@@ -2042,14 +2042,14 @@ func (s *storageZfs) zfsPoolListSnapshots(path string) ([]string, error) {
 		"-H",
 		"-d", "1",
 		"-s", "creation",
-		"-r", fullPath).CombinedOutput()
+		"-r", fullPath)
 	if err != nil {
-		shared.LogErrorf("zfs list failed: %s.", string(output))
+		shared.LogErrorf("zfs list failed: %s.", output)
 		return []string{}, fmt.Errorf("Failed to list ZFS snapshots: %s", output)
 	}
 
 	children := []string{}
-	for _, entry := range strings.Split(string(output), "\n") {
+	for _, entry := range strings.Split(output, "\n") {
 		if entry == "" {
 			continue
 		}
@@ -2120,19 +2120,19 @@ func (s *storageZfs) zfsPoolGetUsers() ([]string, error) {
 }
 
 func zfsFilesystemEntityExists(zfsEntity string) bool {
-	output, err := exec.Command(
+	output, err := shared.RunCommand(
 		"zfs",
 		"get",
 		"type",
 		"-H",
 		"-o",
 		"name",
-		zfsEntity).CombinedOutput()
+		zfsEntity)
 	if err != nil {
 		return false
 	}
 
-	detectedName := strings.TrimSpace(string(output))
+	detectedName := strings.TrimSpace(output)
 	if detectedName != zfsEntity {
 		return false
 	}
diff --git a/lxd/util.go b/lxd/util.go
index cce5ffb..e85a5a8 100644
--- a/lxd/util.go
+++ b/lxd/util.go
@@ -63,5 +63,6 @@ func loadModule(module string) error {
 		return nil
 	}
 
-	return shared.RunCommand("modprobe", module)
+	_, err := shared.RunCommand("modprobe", module)
+	return err
 }
diff --git a/shared/util.go b/shared/util.go
index a25848c..4e829ea 100644
--- a/shared/util.go
+++ b/shared/util.go
@@ -455,12 +455,12 @@ func IsBlockdevPath(pathName string) bool {
 }
 
 func BlockFsDetect(dev string) (string, error) {
-	out, err := exec.Command("blkid", "-s", "TYPE", "-o", "value", dev).Output()
+	out, err := RunCommand("blkid", "-s", "TYPE", "-o", "value", dev)
 	if err != nil {
-		return "", fmt.Errorf("Failed to run blkid on: %s", dev)
+		return "", err
 	}
 
-	return strings.TrimSpace(string(out)), nil
+	return strings.TrimSpace(out), nil
 }
 
 // DeepCopy copies src to dest by using encoding/gob so its not that fast.
@@ -756,13 +756,29 @@ func RemoveDuplicatesFromString(s string, sep string) string {
 	return s
 }
 
-func RunCommand(name string, arg ...string) error {
+func RunCommand(name string, arg ...string) (string, error) {
 	output, err := exec.Command(name, arg...).CombinedOutput()
 	if err != nil {
-		return fmt.Errorf("Failed to run: %s %s: %s", name, strings.Join(arg, " "), strings.TrimSpace(string(output)))
+		return "", fmt.Errorf("Failed to run: %s %s: %s", name, strings.Join(arg, " "), strings.TrimSpace(string(output)))
 	}
 
-	return nil
+	return string(output), nil
+}
+
+func TryRunCommand(name string, arg ...string) (string, error) {
+	var err error
+	var output string
+
+	for i := 0; i < 20; i++ {
+		output, err = RunCommand(name, arg...)
+		if err == nil {
+			break
+		}
+
+		time.Sleep(500 * time.Millisecond)
+	}
+
+	return output, err
 }
 
 func TimeIsSet(ts time.Time) bool {
diff --git a/shared/util_linux.go b/shared/util_linux.go
index a92e595..68310cb 100644
--- a/shared/util_linux.go
+++ b/shared/util_linux.go
@@ -369,7 +369,7 @@ func GetFileStat(p string) (uid int, gid int, major int, minor int,
 func IsMountPoint(name string) bool {
 	_, err := exec.LookPath("mountpoint")
 	if err == nil {
-		err = exec.Command("mountpoint", "-q", name).Run()
+		_, err = RunCommand("mountpoint", "-q", name)
 		if err != nil {
 			return false
 		}


More information about the lxc-devel mailing list