[lxc-devel] [go-lxc/v2] Fix the locking logic
caglar10ur on Github
lxc-bot at linuxcontainers.org
Sun Oct 1 22:22:55 UTC 2017
A non-text attachment was scrubbed...
Name: not available
Type: text/x-mailbox
Size: 677 bytes
Desc: not available
URL: <http://lists.linuxcontainers.org/pipermail/lxc-devel/attachments/20171001/8d3a0552/attachment.bin>
-------------- next part --------------
From 51992fac799d3bca497e4cb7bfcb7591a215a29b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?S=2E=C3=87a=C4=9Flar=20Onur?= <caglar at 10ur.org>
Date: Sun, 1 Oct 2017 15:20:51 -0700
Subject: [PATCH] Fix the locking logic
The original code where we were doing following
func X() {
call makesure
makesure locks
...work
makesure unlocks
lock
...work
unlock
}
was potentially racy as someone else could obtain the lock in between
makesure and mutex.Lock call.
Now all exported functions are holding the lock throughout their scope.
---
container.go | 451 +++++++++++++++++++++++++++++++++++++----------------------
1 file changed, 282 insertions(+), 169 deletions(-)
diff --git a/container.go b/container.go
index bbcfa1d..c1d219e 100644
--- a/container.go
+++ b/container.go
@@ -16,6 +16,7 @@ import (
"io/ioutil"
"os"
"os/exec"
+ "path"
"path/filepath"
"reflect"
"strconv"
@@ -53,20 +54,20 @@ const (
)
func (c *Container) makeSure(flags int) error {
- if flags&isDefined != 0 && !c.Defined() {
- return ErrNotDefined
+ if flags&isDefined != 0 && !c.defined() {
+ return fmt.Errorf("%s: %q", ErrNotDefined, c.name())
}
- if flags&isNotDefined != 0 && c.Defined() {
- return ErrAlreadyDefined
+ if flags&isNotDefined != 0 && c.defined() {
+ return fmt.Errorf("%s: %q", ErrAlreadyDefined, c.name())
}
- if flags&isRunning != 0 && !c.Running() {
- return ErrNotRunning
+ if flags&isRunning != 0 && !c.running() {
+ return fmt.Errorf("%s: %q", ErrNotRunning, c.name())
}
- if flags&isNotRunning != 0 && c.Running() {
- return ErrAlreadyRunning
+ if flags&isNotRunning != 0 && c.running() {
+ return fmt.Errorf("%s: %q", ErrAlreadyRunning, c.name())
}
if flags&isPrivileged != 0 && os.Geteuid() != 0 {
@@ -85,7 +86,7 @@ func (c *Container) makeSure(flags int) error {
}
func (c *Container) cgroupItemAsByteSize(filename string, missing error) (ByteSize, error) {
- size, err := strconv.ParseFloat(c.CgroupItem(filename)[0], 64)
+ size, err := strconv.ParseFloat(c.cgroupItem(filename)[0], 64)
if err != nil {
return -1, missing
}
@@ -93,18 +94,35 @@ func (c *Container) cgroupItemAsByteSize(filename string, missing error) (ByteSi
}
func (c *Container) setCgroupItemWithByteSize(filename string, limit ByteSize, missing error) error {
- if err := c.SetCgroupItem(filename, fmt.Sprintf("%.f", limit)); err != nil {
+ if err := c.setCgroupItem(filename, fmt.Sprintf("%.f", limit)); err != nil {
return missing
}
return nil
}
+func (c *Container) name() string {
+ return C.GoString(c.container.name)
+}
+
// Name returns the name of the container.
func (c *Container) Name() string {
c.mu.RLock()
defer c.mu.RUnlock()
- return C.GoString(c.container.name)
+ return c.name()
+}
+
+// String returns the string represantation of container.
+func (c *Container) String() string {
+ c.mu.RLock()
+ defer c.mu.RUnlock()
+
+ return path.Join(c.configPath(), c.name())
+}
+
+// Caller needs to hold the lock
+func (c *Container) defined() bool {
+ return bool(C.go_lxc_defined(c.container))
}
// Defined returns true if the container is already defined.
@@ -112,7 +130,12 @@ func (c *Container) Defined() bool {
c.mu.RLock()
defer c.mu.RUnlock()
- return bool(C.go_lxc_defined(c.container))
+ return c.defined()
+}
+
+// Caller needs to hold the lock
+func (c *Container) running() bool {
+ return bool(C.go_lxc_running(c.container))
}
// Running returns true if the container is already running.
@@ -120,7 +143,7 @@ func (c *Container) Running() bool {
c.mu.RLock()
defer c.mu.RUnlock()
- return bool(C.go_lxc_running(c.container))
+ return c.running()
}
// Controllable returns true if the caller can control the container.
@@ -133,13 +156,13 @@ func (c *Container) Controllable() bool {
// CreateSnapshot creates a new snapshot.
func (c *Container) CreateSnapshot() (*Snapshot, error) {
+ c.mu.Lock()
+ defer c.mu.Unlock()
+
if err := c.makeSure(isDefined | isNotRunning); err != nil {
return nil, err
}
- c.mu.Lock()
- defer c.mu.Unlock()
-
ret := int(C.go_lxc_snapshot(c.container))
if ret < 0 {
return nil, ErrCreateSnapshotFailed
@@ -149,13 +172,13 @@ func (c *Container) CreateSnapshot() (*Snapshot, error) {
// RestoreSnapshot creates a new container based on a snapshot.
func (c *Container) RestoreSnapshot(snapshot Snapshot, name string) error {
+ c.mu.Lock()
+ defer c.mu.Unlock()
+
if err := c.makeSure(isDefined); err != nil {
return err
}
- c.mu.Lock()
- defer c.mu.Unlock()
-
cname := C.CString(name)
defer C.free(unsafe.Pointer(cname))
@@ -170,13 +193,13 @@ func (c *Container) RestoreSnapshot(snapshot Snapshot, name string) error {
// DestroySnapshot destroys the specified snapshot.
func (c *Container) DestroySnapshot(snapshot Snapshot) error {
+ c.mu.Lock()
+ defer c.mu.Unlock()
+
if err := c.makeSure(isDefined); err != nil {
return err
}
- c.mu.Lock()
- defer c.mu.Unlock()
-
csnapname := C.CString(snapshot.Name)
defer C.free(unsafe.Pointer(csnapname))
@@ -188,13 +211,13 @@ func (c *Container) DestroySnapshot(snapshot Snapshot) error {
// DestroyAllSnapshots destroys all the snapshot.
func (c *Container) DestroyAllSnapshots() error {
+ c.mu.Lock()
+ defer c.mu.Unlock()
+
if err := c.makeSure(isDefined | isGreaterEqualThanLXC11); err != nil {
return err
}
- c.mu.Lock()
- defer c.mu.Unlock()
-
if !bool(C.go_lxc_snapshot_destroy_all(c.container)) {
return ErrDestroyAllSnapshotsFailed
}
@@ -203,13 +226,13 @@ func (c *Container) DestroyAllSnapshots() error {
// Snapshots returns the list of container snapshots.
func (c *Container) Snapshots() ([]Snapshot, error) {
+ c.mu.RLock()
+ defer c.mu.RUnlock()
+
if err := c.makeSure(isDefined); err != nil {
return nil, err
}
- c.mu.Lock()
- defer c.mu.Unlock()
-
var csnapshots *C.struct_lxc_snapshot
size := int(C.go_lxc_snapshot_list(c.container, &csnapshots))
@@ -239,12 +262,17 @@ func (c *Container) Snapshots() ([]Snapshot, error) {
return snapshots, nil
}
+// Caller needs to hold the lock
+func (c *Container) state() State {
+ return StateMap[C.GoString(C.go_lxc_state(c.container))]
+}
+
// State returns the state of the container.
func (c *Container) State() State {
c.mu.RLock()
defer c.mu.RUnlock()
- return StateMap[C.GoString(C.go_lxc_state(c.container))]
+ return c.state()
}
// InitPid returns the process ID of the container's init process
@@ -297,17 +325,14 @@ func (c *Container) SetVerbosity(verbosity Verbosity) {
// Freeze freezes the running container.
func (c *Container) Freeze() error {
- if err := c.makeSure(isRunning); err != nil {
- return err
- }
+ c.mu.Lock()
+ defer c.mu.Unlock()
- if c.State() == FROZEN {
+ // check the state using lockless version
+ if c.state() == FROZEN {
return ErrAlreadyFrozen
}
- c.mu.Lock()
- defer c.mu.Unlock()
-
if !bool(C.go_lxc_freeze(c.container)) {
return ErrFreezeFailed
}
@@ -317,17 +342,18 @@ func (c *Container) Freeze() error {
// Unfreeze thaws the frozen container.
func (c *Container) Unfreeze() error {
+ c.mu.Lock()
+ defer c.mu.Unlock()
+
if err := c.makeSure(isRunning); err != nil {
return err
}
- if c.State() != FROZEN {
+ // check the state using lockless version
+ if c.state() != FROZEN {
return ErrNotFrozen
}
- c.mu.Lock()
- defer c.mu.Unlock()
-
if !bool(C.go_lxc_unfreeze(c.container)) {
return ErrUnfreezeFailed
}
@@ -337,6 +363,9 @@ func (c *Container) Unfreeze() error {
// Create creates the container using given TemplateOptions
func (c *Container) Create(options TemplateOptions) error {
+ c.mu.Lock()
+ defer c.mu.Unlock()
+
// FIXME: Support bdev_specs
//
// bdev_specs:
@@ -348,9 +377,6 @@ func (c *Container) Create(options TemplateOptions) error {
return err
}
- c.mu.Lock()
- defer c.mu.Unlock()
-
// use download template if not set
if options.Template == "" {
options.Template = "download"
@@ -435,13 +461,13 @@ func (c *Container) Create(options TemplateOptions) error {
// Start starts the container.
func (c *Container) Start() error {
+ c.mu.Lock()
+ defer c.mu.Unlock()
+
if err := c.makeSure(isNotRunning); err != nil {
return err
}
- c.mu.Lock()
- defer c.mu.Unlock()
-
if !bool(C.go_lxc_start(c.container, 0, nil)) {
return ErrStartFailed
}
@@ -450,13 +476,13 @@ func (c *Container) Start() error {
// StartWithArgs starts the container using given arguments.
func (c *Container) StartWithArgs(args []string) error {
+ c.mu.Lock()
+ defer c.mu.Unlock()
+
if err := c.makeSure(isNotRunning); err != nil {
return err
}
- c.mu.Lock()
- defer c.mu.Unlock()
-
if !bool(C.go_lxc_start(c.container, 0, makeNullTerminatedArgs(args))) {
return ErrStartFailed
}
@@ -465,23 +491,23 @@ func (c *Container) StartWithArgs(args []string) error {
// Execute executes the given command in a temporary container.
func (c *Container) Execute(args ...string) ([]byte, error) {
+ c.mu.Lock()
+ defer c.mu.Unlock()
+
if err := c.makeSure(isNotDefined); err != nil {
return nil, err
}
// Deal with LXC 2.1's need of a defined container
if VersionAtLeast(2, 1, 0) {
- os.MkdirAll(filepath.Join(c.ConfigPath(), c.Name()), 0700)
- c.SaveConfigFile(filepath.Join(c.ConfigPath(), c.Name(), "config"))
- defer os.RemoveAll(filepath.Join(c.ConfigPath(), c.Name()))
+ os.MkdirAll(filepath.Join(c.configPath(), c.name()), 0700)
+ c.SaveConfigFile(filepath.Join(c.configPath(), c.name(), "config"))
+ defer os.RemoveAll(filepath.Join(c.configPath(), c.name()))
}
- cargs := []string{"lxc-execute", "-n", c.Name(), "-P", c.ConfigPath(), "--"}
+ cargs := []string{"lxc-execute", "-n", c.name(), "-P", c.configPath(), "--"}
cargs = append(cargs, args...)
- c.mu.Lock()
- defer c.mu.Unlock()
-
// FIXME: Go runtime and src/lxc/start.c signal_handler are not playing nice together so use lxc-execute for now
// go-nuts thread: https://groups.google.com/forum/#!msg/golang-nuts/h9GbvfYv83w/5Ly_jvOr86wJ
output, err := exec.Command(cargs[0], cargs[1:]...).CombinedOutput()
@@ -506,13 +532,13 @@ func (c *Container) Execute(args ...string) ([]byte, error) {
// Stop stops the container.
func (c *Container) Stop() error {
+ c.mu.Lock()
+ defer c.mu.Unlock()
+
if err := c.makeSure(isRunning); err != nil {
return err
}
- c.mu.Lock()
- defer c.mu.Unlock()
-
if !bool(C.go_lxc_stop(c.container)) {
return ErrStopFailed
}
@@ -521,13 +547,13 @@ func (c *Container) Stop() error {
// Reboot reboots the container.
func (c *Container) Reboot() error {
+ c.mu.Lock()
+ defer c.mu.Unlock()
+
if err := c.makeSure(isRunning); err != nil {
return err
}
- c.mu.Lock()
- defer c.mu.Unlock()
-
if !bool(C.go_lxc_reboot(c.container)) {
return ErrRebootFailed
}
@@ -536,11 +562,12 @@ func (c *Container) Reboot() error {
// Shutdown shuts down the container.
func (c *Container) Shutdown(timeout time.Duration) error {
+ c.mu.Lock()
+ defer c.mu.Unlock()
+
if err := c.makeSure(isRunning); err != nil {
return err
}
- c.mu.Lock()
- defer c.mu.Unlock()
if !bool(C.go_lxc_shutdown(c.container, C.int(timeout.Seconds()))) {
return ErrShutdownFailed
@@ -550,13 +577,13 @@ func (c *Container) Shutdown(timeout time.Duration) error {
// Destroy destroys the container.
func (c *Container) Destroy() error {
+ c.mu.Lock()
+ defer c.mu.Unlock()
+
if err := c.makeSure(isDefined | isNotRunning); err != nil {
return err
}
- c.mu.Lock()
- defer c.mu.Unlock()
-
if !bool(C.go_lxc_destroy(c.container)) {
return ErrDestroyFailed
}
@@ -565,13 +592,13 @@ func (c *Container) Destroy() error {
// DestroyWithAllSnapshots destroys the container and its snapshots
func (c *Container) DestroyWithAllSnapshots() error {
+ c.mu.Lock()
+ defer c.mu.Unlock()
+
if err := c.makeSure(isDefined | isNotRunning | isGreaterEqualThanLXC11); err != nil {
return err
}
- c.mu.Lock()
- defer c.mu.Unlock()
-
if !bool(C.go_lxc_destroy_with_snapshots(c.container)) {
return ErrDestroyWithAllSnapshotsFailed
}
@@ -580,6 +607,9 @@ func (c *Container) DestroyWithAllSnapshots() error {
// Clone clones the container using given arguments with specified backend.
func (c *Container) Clone(name string, options CloneOptions) error {
+ c.mu.Lock()
+ defer c.mu.Unlock()
+
// FIXME: bdevdata, newsize and hookargs
//
// bdevdata:
@@ -595,9 +625,6 @@ func (c *Container) Clone(name string, options CloneOptions) error {
return err
}
- c.mu.Lock()
- defer c.mu.Unlock()
-
// use Directory backend if not set
if options.Backend == 0 {
options.Backend = Directory
@@ -637,13 +664,13 @@ func (c *Container) Clone(name string, options CloneOptions) error {
// Rename renames the container.
func (c *Container) Rename(name string) error {
+ c.mu.Lock()
+ defer c.mu.Unlock()
+
if err := c.makeSure(isDefined | isNotRunning); err != nil {
return err
}
- c.mu.Lock()
- defer c.mu.Unlock()
-
cname := C.CString(name)
defer C.free(unsafe.Pointer(cname))
@@ -692,11 +719,7 @@ func (c *Container) ConfigItem(key string) []string {
return strings.Split(ret, "\n")
}
-// SetConfigItem sets the value of the given config item.
-func (c *Container) SetConfigItem(key string, value string) error {
- c.mu.Lock()
- defer c.mu.Unlock()
-
+func (c *Container) setConfigItem(key string, value string) error {
ckey := C.CString(key)
defer C.free(unsafe.Pointer(ckey))
@@ -709,6 +732,14 @@ func (c *Container) SetConfigItem(key string, value string) error {
return nil
}
+// SetConfigItem sets the value of the given config item.
+func (c *Container) SetConfigItem(key string, value string) error {
+ c.mu.Lock()
+ defer c.mu.Unlock()
+
+ return c.setConfigItem(key, value)
+}
+
// RunningConfigItem returns the value of the given config item.
func (c *Container) RunningConfigItem(key string) []string {
c.mu.RLock()
@@ -725,11 +756,7 @@ func (c *Container) RunningConfigItem(key string) []string {
return strings.Split(ret, "\n")
}
-// CgroupItem returns the value of the given cgroup subsystem value.
-func (c *Container) CgroupItem(key string) []string {
- c.mu.RLock()
- defer c.mu.RUnlock()
-
+func (c *Container) cgroupItem(key string) []string {
ckey := C.CString(key)
defer C.free(unsafe.Pointer(ckey))
@@ -741,11 +768,7 @@ func (c *Container) CgroupItem(key string) []string {
return strings.Split(ret, "\n")
}
-// SetCgroupItem sets the value of given cgroup subsystem value.
-func (c *Container) SetCgroupItem(key string, value string) error {
- c.mu.Lock()
- defer c.mu.Unlock()
-
+func (c *Container) setCgroupItem(key string, value string) error {
ckey := C.CString(key)
defer C.free(unsafe.Pointer(ckey))
@@ -758,6 +781,22 @@ func (c *Container) SetCgroupItem(key string, value string) error {
return nil
}
+// CgroupItem returns the value of the given cgroup subsystem value.
+func (c *Container) CgroupItem(key string) []string {
+ c.mu.RLock()
+ defer c.mu.RUnlock()
+
+ return c.cgroupItem(key)
+}
+
+// SetCgroupItem sets the value of given cgroup subsystem value.
+func (c *Container) SetCgroupItem(key string, value string) error {
+ c.mu.Lock()
+ defer c.mu.Unlock()
+
+ return c.setCgroupItem(key, value)
+}
+
// ClearConfig completely clears the containers in-memory configuration.
func (c *Container) ClearConfig() {
c.mu.Lock()
@@ -831,12 +870,16 @@ func (c *Container) SaveConfigFile(path string) error {
return nil
}
+func (c *Container) configPath() string {
+ return C.GoString(C.go_lxc_get_config_path(c.container))
+}
+
// ConfigPath returns the configuration file's path.
func (c *Container) ConfigPath() string {
c.mu.RLock()
defer c.mu.RUnlock()
- return C.GoString(C.go_lxc_get_config_path(c.container))
+ return c.configPath()
}
// SetConfigPath sets the configuration file's path.
@@ -855,6 +898,9 @@ func (c *Container) SetConfigPath(path string) error {
// MemoryUsage returns memory usage of the container in bytes.
func (c *Container) MemoryUsage() (ByteSize, error) {
+ c.mu.RLock()
+ defer c.mu.RUnlock()
+
if err := c.makeSure(isRunning); err != nil {
return -1, err
}
@@ -864,6 +910,9 @@ func (c *Container) MemoryUsage() (ByteSize, error) {
// MemoryLimit returns memory limit of the container in bytes.
func (c *Container) MemoryLimit() (ByteSize, error) {
+ c.mu.RLock()
+ defer c.mu.RUnlock()
+
if err := c.makeSure(isRunning); err != nil {
return -1, err
}
@@ -873,6 +922,9 @@ func (c *Container) MemoryLimit() (ByteSize, error) {
// SetMemoryLimit sets memory limit of the container in bytes.
func (c *Container) SetMemoryLimit(limit ByteSize) error {
+ c.mu.Lock()
+ defer c.mu.Unlock()
+
if err := c.makeSure(isRunning); err != nil {
return err
}
@@ -882,6 +934,9 @@ func (c *Container) SetMemoryLimit(limit ByteSize) error {
// SoftMemoryLimit returns soft memory limit of the container in bytes.
func (c *Container) SoftMemoryLimit() (ByteSize, error) {
+ c.mu.RLock()
+ defer c.mu.RUnlock()
+
if err := c.makeSure(isRunning); err != nil {
return -1, err
}
@@ -891,6 +946,9 @@ func (c *Container) SoftMemoryLimit() (ByteSize, error) {
// SetSoftMemoryLimit sets soft memory limit of the container in bytes.
func (c *Container) SetSoftMemoryLimit(limit ByteSize) error {
+ c.mu.Lock()
+ defer c.mu.Unlock()
+
if err := c.makeSure(isRunning); err != nil {
return err
}
@@ -900,6 +958,9 @@ func (c *Container) SetSoftMemoryLimit(limit ByteSize) error {
// KernelMemoryUsage returns current kernel memory allocation of the container in bytes.
func (c *Container) KernelMemoryUsage() (ByteSize, error) {
+ c.mu.RLock()
+ defer c.mu.RUnlock()
+
if err := c.makeSure(isRunning); err != nil {
return -1, err
}
@@ -909,6 +970,9 @@ func (c *Container) KernelMemoryUsage() (ByteSize, error) {
// KernelMemoryLimit returns kernel memory limit of the container in bytes.
func (c *Container) KernelMemoryLimit() (ByteSize, error) {
+ c.mu.RLock()
+ defer c.mu.RUnlock()
+
if err := c.makeSure(isRunning); err != nil {
return -1, err
}
@@ -918,6 +982,9 @@ func (c *Container) KernelMemoryLimit() (ByteSize, error) {
// SetKernelMemoryLimit sets kernel memory limit of the container in bytes.
func (c *Container) SetKernelMemoryLimit(limit ByteSize) error {
+ c.mu.Lock()
+ defer c.mu.Unlock()
+
if err := c.makeSure(isRunning); err != nil {
return err
}
@@ -927,6 +994,9 @@ func (c *Container) SetKernelMemoryLimit(limit ByteSize) error {
// MemorySwapUsage returns memory+swap usage of the container in bytes.
func (c *Container) MemorySwapUsage() (ByteSize, error) {
+ c.mu.RLock()
+ defer c.mu.RUnlock()
+
if err := c.makeSure(isRunning); err != nil {
return -1, err
}
@@ -936,6 +1006,9 @@ func (c *Container) MemorySwapUsage() (ByteSize, error) {
// MemorySwapLimit returns the memory+swap limit of the container in bytes.
func (c *Container) MemorySwapLimit() (ByteSize, error) {
+ c.mu.RLock()
+ defer c.mu.RUnlock()
+
if err := c.makeSure(isRunning); err != nil {
return -1, err
}
@@ -945,6 +1018,9 @@ func (c *Container) MemorySwapLimit() (ByteSize, error) {
// SetMemorySwapLimit sets memory+swap limit of the container in bytes.
func (c *Container) SetMemorySwapLimit(limit ByteSize) error {
+ c.mu.Lock()
+ defer c.mu.Unlock()
+
if err := c.makeSure(isRunning); err != nil {
return err
}
@@ -954,13 +1030,13 @@ func (c *Container) SetMemorySwapLimit(limit ByteSize) error {
// BlkioUsage returns number of bytes transferred to/from the disk by the container.
func (c *Container) BlkioUsage() (ByteSize, error) {
+ c.mu.RLock()
+ defer c.mu.RUnlock()
+
if err := c.makeSure(isRunning); err != nil {
return -1, err
}
- c.mu.RLock()
- defer c.mu.RUnlock()
-
for _, v := range c.CgroupItem("blkio.throttle.io_service_bytes") {
b := strings.Split(v, " ")
if b[0] == "Total" {
@@ -977,13 +1053,13 @@ func (c *Container) BlkioUsage() (ByteSize, error) {
// CPUTime returns the total CPU time (in nanoseconds) consumed by all tasks
// in this cgroup (including tasks lower in the hierarchy).
func (c *Container) CPUTime() (time.Duration, error) {
+ c.mu.RLock()
+ defer c.mu.RUnlock()
+
if err := c.makeSure(isRunning); err != nil {
return -1, err
}
- c.mu.RLock()
- defer c.mu.RUnlock()
-
cpuUsage, err := strconv.ParseInt(c.CgroupItem("cpuacct.usage")[0], 10, 64)
if err != nil {
return -1, err
@@ -994,13 +1070,13 @@ func (c *Container) CPUTime() (time.Duration, error) {
// CPUTimePerCPU returns the CPU time (in nanoseconds) consumed on each CPU by
// all tasks in this cgroup (including tasks lower in the hierarchy).
func (c *Container) CPUTimePerCPU() (map[int]time.Duration, error) {
+ c.mu.RLock()
+ defer c.mu.RUnlock()
+
if err := c.makeSure(isRunning); err != nil {
return nil, err
}
- c.mu.RLock()
- defer c.mu.RUnlock()
-
cpuTimes := make(map[int]time.Duration)
for i, v := range strings.Split(c.CgroupItem("cpuacct.usage_percpu")[0], " ") {
cpuUsage, err := strconv.ParseInt(v, 10, 64)
@@ -1015,13 +1091,13 @@ func (c *Container) CPUTimePerCPU() (map[int]time.Duration, error) {
// CPUStats returns the number of CPU cycles (in the units defined by USER_HZ on the system)
// consumed by tasks in this cgroup and its children in both user mode and system (kernel) mode.
func (c *Container) CPUStats() (map[string]int64, error) {
+ c.mu.RLock()
+ defer c.mu.RUnlock()
+
if err := c.makeSure(isRunning); err != nil {
return nil, err
}
- c.mu.RLock()
- defer c.mu.RUnlock()
-
cpuStat := c.CgroupItem("cpuacct.stat")
user, err := strconv.ParseInt(strings.Split(cpuStat[0], "user ")[1], 10, 64)
if err != nil {
@@ -1043,14 +1119,14 @@ func (c *Container) CPUStats() (map[string]int64, error) {
// indicate that it is done with the allocated console so that it can
// be allocated by another caller.
func (c *Container) ConsoleFd(ttynum int) (int, error) {
+ c.mu.Lock()
+ defer c.mu.Unlock()
+
// FIXME: Make idiomatic
if err := c.makeSure(isRunning); err != nil {
return -1, err
}
- c.mu.Lock()
- defer c.mu.Unlock()
-
ret := int(C.go_lxc_console_getfd(c.container, C.int(ttynum)))
if ret < 0 {
return -1, ErrAttachFailed
@@ -1062,13 +1138,13 @@ func (c *Container) ConsoleFd(ttynum int) (int, error) {
//
// This function will not return until the console has been exited by the user.
func (c *Container) Console(options ConsoleOptions) error {
+ c.mu.Lock()
+ defer c.mu.Unlock()
+
if err := c.makeSure(isRunning); err != nil {
return err
}
- c.mu.Lock()
- defer c.mu.Unlock()
-
ret := bool(C.go_lxc_console(c.container,
C.int(options.Tty),
C.int(options.StdinFd),
@@ -1085,13 +1161,13 @@ func (c *Container) Console(options ConsoleOptions) error {
// AttachShell attaches a shell to the container.
// It clears all environment variables before attaching.
func (c *Container) AttachShell(options AttachOptions) error {
+ c.mu.Lock()
+ defer c.mu.Unlock()
+
if err := c.makeSure(isRunning); err != nil {
return err
}
- c.mu.Lock()
- defer c.mu.Unlock()
-
cenv := makeNullTerminatedArgs(options.Env)
if cenv == nil {
return ErrAllocationFailed
@@ -1126,11 +1202,7 @@ func (c *Container) AttachShell(options AttachOptions) error {
return nil
}
-// RunCommandStatus attachs a shell and runs the command within the container.
-// The process will wait for the command to finish and return the result of
-// waitpid(), i.e. the process' exit status. An error is returned only when
-// invocation of the command completely fails.
-func (c *Container) RunCommandStatus(args []string, options AttachOptions) (int, error) {
+func (c *Container) runCommandStatus(args []string, options AttachOptions) (int, error) {
if len(args) == 0 {
return -1, ErrInsufficientNumberOfArguments
}
@@ -1139,9 +1211,6 @@ func (c *Container) RunCommandStatus(args []string, options AttachOptions) (int,
return -1, err
}
- c.mu.Lock()
- defer c.mu.Unlock()
-
cargs := makeNullTerminatedArgs(args)
if cargs == nil {
return -1, ErrAllocationFailed
@@ -1182,8 +1251,22 @@ func (c *Container) RunCommandStatus(args []string, options AttachOptions) (int,
return ret, nil
}
+// RunCommandStatus attachs a shell and runs the command within the container.
+// The process will wait for the command to finish and return the result of
+// waitpid(), i.e. the process' exit status. An error is returned only when
+// invocation of the command completely fails.
+func (c *Container) RunCommandStatus(args []string, options AttachOptions) (int, error) {
+ c.mu.Lock()
+ defer c.mu.Unlock()
+
+ return c.runCommandStatus(args, options)
+}
+
// RunCommandNoWait runs the given command and returns without waiting it to finish.
func (c *Container) RunCommandNoWait(args []string, options AttachOptions) (int, error) {
+ c.mu.Lock()
+ defer c.mu.Unlock()
+
if len(args) == 0 {
return -1, ErrInsufficientNumberOfArguments
}
@@ -1192,9 +1275,6 @@ func (c *Container) RunCommandNoWait(args []string, options AttachOptions) (int,
return -1, err
}
- c.mu.Lock()
- defer c.mu.Unlock()
-
cargs := makeNullTerminatedArgs(args)
if cargs == nil {
return -1, ErrAllocationFailed
@@ -1245,7 +1325,10 @@ func (c *Container) RunCommandNoWait(args []string, options AttachOptions) (int,
// The process will wait for the command to finish and return a success status. An error
// is returned only when invocation of the command completely fails.
func (c *Container) RunCommand(args []string, options AttachOptions) (bool, error) {
- ret, err := c.RunCommandStatus(args, options)
+ c.mu.Lock()
+ defer c.mu.Unlock()
+
+ ret, err := c.runCommandStatus(args, options)
if err != nil {
return false, err
}
@@ -1257,13 +1340,13 @@ func (c *Container) RunCommand(args []string, options AttachOptions) (bool, erro
// Interfaces returns the names of the network interfaces.
func (c *Container) Interfaces() ([]string, error) {
+ c.mu.RLock()
+ defer c.mu.RUnlock()
+
if err := c.makeSure(isRunning); err != nil {
return nil, err
}
- c.mu.RLock()
- defer c.mu.RUnlock()
-
result := C.go_lxc_get_interfaces(c.container)
if result == nil {
return nil, ErrInterfaces
@@ -1273,13 +1356,13 @@ func (c *Container) Interfaces() ([]string, error) {
// InterfaceStats returns the stats about container's network interfaces
func (c *Container) InterfaceStats() (map[string]map[string]ByteSize, error) {
+ c.mu.RLock()
+ defer c.mu.RUnlock()
+
if err := c.makeSure(isRunning); err != nil {
return nil, err
}
- c.mu.RLock()
- defer c.mu.RUnlock()
-
var interfaceName string
statistics := make(map[string]map[string]ByteSize)
@@ -1325,13 +1408,13 @@ func (c *Container) InterfaceStats() (map[string]map[string]ByteSize, error) {
// IPAddress returns the IP address of the given network interface.
func (c *Container) IPAddress(interfaceName string) ([]string, error) {
+ c.mu.RLock()
+ defer c.mu.RUnlock()
+
if err := c.makeSure(isRunning); err != nil {
return nil, err
}
- c.mu.RLock()
- defer c.mu.RUnlock()
-
cinterface := C.CString(interfaceName)
defer C.free(unsafe.Pointer(cinterface))
@@ -1344,13 +1427,13 @@ func (c *Container) IPAddress(interfaceName string) ([]string, error) {
// IPv4Address returns the IPv4 address of the given network interface.
func (c *Container) IPv4Address(interfaceName string) ([]string, error) {
+ c.mu.RLock()
+ defer c.mu.RUnlock()
+
if err := c.makeSure(isRunning); err != nil {
return nil, err
}
- c.mu.RLock()
- defer c.mu.RUnlock()
-
cinterface := C.CString(interfaceName)
defer C.free(unsafe.Pointer(cinterface))
@@ -1366,13 +1449,13 @@ func (c *Container) IPv4Address(interfaceName string) ([]string, error) {
// IPv6Address returns the IPv6 address of the given network interface.
func (c *Container) IPv6Address(interfaceName string) ([]string, error) {
+ c.mu.RLock()
+ defer c.mu.RUnlock()
+
if err := c.makeSure(isRunning); err != nil {
return nil, err
}
- c.mu.RLock()
- defer c.mu.RUnlock()
-
cinterface := C.CString(interfaceName)
defer C.free(unsafe.Pointer(cinterface))
@@ -1388,9 +1471,12 @@ func (c *Container) IPv6Address(interfaceName string) ([]string, error) {
// WaitIPAddresses waits until IPAddresses call returns something or time outs
func (c *Container) WaitIPAddresses(timeout time.Duration) ([]string, error) {
+ c.mu.RLock()
+ defer c.mu.RUnlock()
+
now := time.Now()
for {
- if result, err := c.IPAddresses(); err == nil && len(result) > 0 {
+ if result, err := c.ipAddresses(); err == nil && len(result) > 0 {
return result, nil
}
// Python API sleeps 1 second as well
@@ -1402,15 +1488,11 @@ func (c *Container) WaitIPAddresses(timeout time.Duration) ([]string, error) {
}
}
-// IPAddresses returns all IP addresses.
-func (c *Container) IPAddresses() ([]string, error) {
+func (c *Container) ipAddresses() ([]string, error) {
if err := c.makeSure(isRunning); err != nil {
return nil, err
}
- c.mu.RLock()
- defer c.mu.RUnlock()
-
result := C.go_lxc_get_ips(c.container, nil, nil, 0)
if result == nil {
return nil, ErrIPAddresses
@@ -1419,15 +1501,23 @@ func (c *Container) IPAddresses() ([]string, error) {
}
+// IPAddresses returns all IP addresses.
+func (c *Container) IPAddresses() ([]string, error) {
+ c.mu.RLock()
+ defer c.mu.RUnlock()
+
+ return c.ipAddresses()
+}
+
// IPv4Addresses returns all IPv4 addresses.
func (c *Container) IPv4Addresses() ([]string, error) {
+ c.mu.RLock()
+ defer c.mu.RUnlock()
+
if err := c.makeSure(isRunning); err != nil {
return nil, err
}
- c.mu.RLock()
- defer c.mu.RUnlock()
-
cfamily := C.CString("inet")
defer C.free(unsafe.Pointer(cfamily))
@@ -1440,13 +1530,13 @@ func (c *Container) IPv4Addresses() ([]string, error) {
// IPv6Addresses returns all IPv6 addresses.
func (c *Container) IPv6Addresses() ([]string, error) {
+ c.mu.RLock()
+ defer c.mu.RUnlock()
+
if err := c.makeSure(isRunning); err != nil {
return nil, err
}
- c.mu.RLock()
- defer c.mu.RUnlock()
-
cfamily := C.CString("inet6")
defer C.free(unsafe.Pointer(cfamily))
@@ -1459,6 +1549,9 @@ func (c *Container) IPv6Addresses() ([]string, error) {
// LogFile returns the name of the logfile.
func (c *Container) LogFile() string {
+ c.mu.RLock()
+ defer c.mu.RUnlock()
+
if VersionAtLeast(2, 1, 0) {
return c.ConfigItem("lxc.log.file")[0]
}
@@ -1468,11 +1561,14 @@ func (c *Container) LogFile() string {
// SetLogFile sets the name of the logfile.
func (c *Container) SetLogFile(filename string) error {
+ c.mu.Lock()
+ defer c.mu.Unlock()
+
var err error
if VersionAtLeast(2, 1, 0) {
- err = c.SetConfigItem("lxc.log.file", filename)
+ err = c.setConfigItem("lxc.log.file", filename)
} else {
- err = c.SetConfigItem("lxc.logfile", filename)
+ err = c.setConfigItem("lxc.logfile", filename)
}
if err != nil {
return err
@@ -1483,6 +1579,9 @@ func (c *Container) SetLogFile(filename string) error {
// LogLevel returns the level of the logfile.
func (c *Container) LogLevel() LogLevel {
+ c.mu.RLock()
+ defer c.mu.RUnlock()
+
if VersionAtLeast(2, 1, 0) {
return logLevelMap[c.ConfigItem("lxc.log.level")[0]]
}
@@ -1492,11 +1591,14 @@ func (c *Container) LogLevel() LogLevel {
// SetLogLevel sets the level of the logfile.
func (c *Container) SetLogLevel(level LogLevel) error {
+ c.mu.Lock()
+ defer c.mu.Unlock()
+
var err error
if VersionAtLeast(2, 1, 0) {
- err = c.SetConfigItem("lxc.log.level", level.String())
+ err = c.setConfigItem("lxc.log.level", level.String())
} else {
- err = c.SetConfigItem("lxc.loglevel", level.String())
+ err = c.setConfigItem("lxc.loglevel", level.String())
}
if err != nil {
return err
@@ -1506,13 +1608,13 @@ func (c *Container) SetLogLevel(level LogLevel) error {
// AddDeviceNode adds specified device to the container.
func (c *Container) AddDeviceNode(source string, destination ...string) error {
+ c.mu.Lock()
+ defer c.mu.Unlock()
+
if err := c.makeSure(isRunning | isPrivileged); err != nil {
return err
}
- c.mu.Lock()
- defer c.mu.Unlock()
-
csource := C.CString(source)
defer C.free(unsafe.Pointer(csource))
@@ -1530,18 +1632,17 @@ func (c *Container) AddDeviceNode(source string, destination ...string) error {
return ErrAddDeviceNodeFailed
}
return nil
-
}
// RemoveDeviceNode removes the specified device from the container.
func (c *Container) RemoveDeviceNode(source string, destination ...string) error {
+ c.mu.Lock()
+ defer c.mu.Unlock()
+
if err := c.makeSure(isRunning | isPrivileged); err != nil {
return err
}
- c.mu.Lock()
- defer c.mu.Unlock()
-
csource := C.CString(source)
defer C.free(unsafe.Pointer(csource))
@@ -1563,6 +1664,9 @@ func (c *Container) RemoveDeviceNode(source string, destination ...string) error
// Checkpoint checkpoints the container.
func (c *Container) Checkpoint(opts CheckpointOptions) error {
+ c.mu.Lock()
+ defer c.mu.Unlock()
+
if err := c.makeSure(isRunning | isGreaterEqualThanLXC11); err != nil {
return err
}
@@ -1581,6 +1685,9 @@ func (c *Container) Checkpoint(opts CheckpointOptions) error {
// Restore restores the container from a checkpoint.
func (c *Container) Restore(opts RestoreOptions) error {
+ c.mu.Lock()
+ defer c.mu.Unlock()
+
if err := c.makeSure(isGreaterEqualThanLXC11); err != nil {
return err
}
@@ -1598,6 +1705,9 @@ func (c *Container) Restore(opts RestoreOptions) error {
// Migrate migrates the container.
func (c *Container) Migrate(cmd uint, opts MigrateOptions) error {
+ c.mu.Lock()
+ defer c.mu.Unlock()
+
if err := c.makeSure(isNotDefined | isGreaterEqualThanLXC20); err != nil {
return err
}
@@ -1650,6 +1760,9 @@ func (c *Container) Migrate(cmd uint, opts MigrateOptions) error {
// AttachInterface attaches specifed netdev to the container.
func (c *Container) AttachInterface(source, destination string) error {
+ c.mu.Lock()
+ defer c.mu.Unlock()
+
if err := c.makeSure(isRunning | isPrivileged | isGreaterEqualThanLXC11); err != nil {
return err
}
@@ -1671,13 +1784,13 @@ func (c *Container) AttachInterface(source, destination string) error {
// DetachInterface detaches specifed netdev from the container.
func (c *Container) DetachInterface(source string) error {
+ c.mu.Lock()
+ defer c.mu.Unlock()
+
if err := c.makeSure(isRunning | isPrivileged | isGreaterEqualThanLXC11); err != nil {
return err
}
- c.mu.Lock()
- defer c.mu.Unlock()
-
csource := C.CString(source)
defer C.free(unsafe.Pointer(csource))
@@ -1689,13 +1802,13 @@ func (c *Container) DetachInterface(source string) error {
// DetachInterfaceRename detaches specifed netdev from the container and renames it.
func (c *Container) DetachInterfaceRename(source, target string) error {
+ c.mu.Lock()
+ defer c.mu.Unlock()
+
if err := c.makeSure(isRunning | isPrivileged | isGreaterEqualThanLXC11); err != nil {
return err
}
- c.mu.Lock()
- defer c.mu.Unlock()
-
csource := C.CString(source)
defer C.free(unsafe.Pointer(csource))
More information about the lxc-devel
mailing list