// Copyright © 2013, 2014, The Go-LXC Authors. All rights reserved. // Use of this source code is governed by a LGPLv2.1 // license that can be found in the LICENSE file. //go:build linux && cgo // +build linux,cgo package lxc // #include // #include // #include "lxc-binding.h" import "C" import ( "fmt" "io/ioutil" "os" "os/exec" "path" "path/filepath" "strconv" "strings" "sync" "syscall" "time" "unsafe" "golang.org/x/sys/unix" ) // Container struct type Container struct { mu sync.RWMutex container *C.struct_lxc_container verbosity Verbosity } // Snapshot struct type Snapshot struct { Name string CommentPath string Timestamp string Path string } const ( isDefined = 1 << iota isNotDefined isRunning isNotRunning isPrivileged isUnprivileged isGreaterEqualThanLXC11 isGreaterEqualThanLXC20 ) func (c *Container) makeSure(flags int) error { if flags&isDefined != 0 && !c.defined() { return fmt.Errorf("%s: %q", ErrNotDefined, c.name()) } if flags&isNotDefined != 0 && c.defined() { return fmt.Errorf("%s: %q", ErrAlreadyDefined, c.name()) } if flags&isRunning != 0 && !c.running() { return fmt.Errorf("%s: %q", ErrNotRunning, c.name()) } if flags&isNotRunning != 0 && c.running() { return fmt.Errorf("%s: %q", ErrAlreadyRunning, c.name()) } if flags&isPrivileged != 0 && os.Geteuid() != 0 { return ErrMethodNotAllowed } if flags&isGreaterEqualThanLXC11 != 0 && !VersionAtLeast(1, 1, 0) { return ErrNotSupported } if flags&isGreaterEqualThanLXC20 != 0 && !VersionAtLeast(2, 0, 0) { return ErrNotSupported } return nil } func (c *Container) cgroupItemAsByteSize(filename string, missing error) (ByteSize, error) { size, err := strconv.ParseFloat(c.cgroupItem(filename)[0], 64) if err != nil { return -1, missing } return ByteSize(size), nil } func (c *Container) setCgroupItemWithByteSize(filename string, limit ByteSize, missing error) error { if err := c.setCgroupItem(filename, fmt.Sprintf("%.f", limit)); err != nil { return missing } return nil } // Release decrements the reference counter of the container object. // nil on success or if reference was successfully dropped and container has been freed, and ErrReleaseFailed on error. func (c *Container) Release() error { c.mu.Lock() defer c.mu.Unlock() if C.lxc_container_put(c.container) == -1 { return ErrReleaseFailed } c.container = nil return nil } func (c *Container) name() string { if c.container == nil { return "" } 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.name() } // String returns the string representation 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 { if c.container == nil { return false } return bool(C.go_lxc_defined(c.container)) } // Defined returns true if the container is already defined. func (c *Container) Defined() bool { c.mu.RLock() defer c.mu.RUnlock() return c.defined() } // Caller needs to hold the lock func (c *Container) running() bool { if c.container == nil { return false } return bool(C.go_lxc_running(c.container)) } // Running returns true if the container is already running. func (c *Container) Running() bool { c.mu.RLock() defer c.mu.RUnlock() return c.running() } // Controllable returns true if the caller can control the container. func (c *Container) Controllable() bool { c.mu.RLock() defer c.mu.RUnlock() if c.container == nil { return false } return bool(C.go_lxc_may_control(c.container)) } // CreateSnapshot creates a new snapshot. func (c *Container) CreateSnapshot() (*Snapshot, error) { c.mu.Lock() defer c.mu.Unlock() if c.container == nil { return nil, ErrNotDefined } if err := c.makeSure(isDefined | isNotRunning); err != nil { return nil, err } ret := int(C.go_lxc_snapshot(c.container)) if ret < 0 { return nil, ErrCreateSnapshotFailed } return &Snapshot{Name: fmt.Sprintf("snap%d", ret)}, nil } // 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 c.container == nil { return ErrNotDefined } if err := c.makeSure(isDefined); err != nil { return err } cname := C.CString(name) defer C.free(unsafe.Pointer(cname)) csnapname := C.CString(snapshot.Name) defer C.free(unsafe.Pointer(csnapname)) if !bool(C.go_lxc_snapshot_restore(c.container, csnapname, cname)) { return ErrRestoreSnapshotFailed } return nil } // DestroySnapshot destroys the specified snapshot. func (c *Container) DestroySnapshot(snapshot Snapshot) error { c.mu.Lock() defer c.mu.Unlock() if c.container == nil { return ErrNotDefined } if err := c.makeSure(isDefined); err != nil { return err } csnapname := C.CString(snapshot.Name) defer C.free(unsafe.Pointer(csnapname)) if !bool(C.go_lxc_snapshot_destroy(c.container, csnapname)) { return ErrDestroySnapshotFailed } return nil } // DestroyAllSnapshots destroys all the snapshot. func (c *Container) DestroyAllSnapshots() error { c.mu.Lock() defer c.mu.Unlock() if c.container == nil { return ErrNotDefined } if err := c.makeSure(isDefined | isGreaterEqualThanLXC11); err != nil { return err } if !bool(C.go_lxc_snapshot_destroy_all(c.container)) { return ErrDestroyAllSnapshotsFailed } return nil } // Snapshots returns the list of container snapshots. func (c *Container) Snapshots() ([]Snapshot, error) { c.mu.RLock() defer c.mu.RUnlock() if c.container == nil { return nil, ErrNotDefined } if err := c.makeSure(isDefined); err != nil { return nil, err } var csnapshots *C.struct_lxc_snapshot size := int(C.go_lxc_snapshot_list(c.container, &csnapshots)) defer freeSnapshots(csnapshots, size) if size < 1 { return nil, ErrNoSnapshot } gosnapshots := (*[(1 << 26) - 1]C.struct_lxc_snapshot)(unsafe.Pointer(csnapshots))[:size:size] snapshots := make([]Snapshot, size, size) for i := 0; i < size; i++ { snapshots[i] = Snapshot{ Name: C.GoString(gosnapshots[i].name), Timestamp: C.GoString(gosnapshots[i].timestamp), CommentPath: C.GoString(gosnapshots[i].comment_pathname), Path: C.GoString(gosnapshots[i].lxcpath), } } return snapshots, nil } // Caller needs to hold the lock func (c *Container) state() State { if c.container == nil { return StateMap["STOPPED"] } 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 c.state() } // InitPid returns the process ID of the container's init process // seen from outside the container. func (c *Container) InitPid() int { c.mu.RLock() defer c.mu.RUnlock() if c.container == nil { return -1 } return int(C.go_lxc_init_pid(c.container)) } // InitPidFd returns the pidfd of the container's init process. func (c *Container) InitPidFd() (*os.File, error) { c.mu.RLock() defer c.mu.RUnlock() if c.container == nil { return nil, ErrNotDefined } pidfd := int(C.go_lxc_init_pidfd(c.container)) if pidfd < 0 { return nil, unix.Errno(unix.EBADF) } return os.NewFile(uintptr(pidfd), "[pidfd]"), nil } // DevptsFd returns the pidfd of the container's init process. func (c *Container) DevptsFd() (*os.File, error) { c.mu.RLock() defer c.mu.RUnlock() if c.container == nil { return nil, ErrNotDefined } devptsFd := int(C.go_lxc_devpts_fd(c.container)) if devptsFd < 0 { return nil, unix.Errno(unix.EBADF) } return os.NewFile(uintptr(devptsFd), "/dev/pts/ptmx"), nil } // SeccompNotifyFd returns the seccomp notify fd of the container. func (c *Container) SeccompNotifyFd() (*os.File, error) { c.mu.RLock() defer c.mu.RUnlock() if c.container == nil { return nil, ErrNotDefined } notifyFd := int(C.go_lxc_seccomp_notify_fd(c.container)) if notifyFd < 0 { return nil, unix.Errno(unix.EBADF) } return os.NewFile(uintptr(notifyFd), "seccomp notify"), nil } // SeccompNotifyFdActive returns the seccomp notify fd of the running container. func (c *Container) SeccompNotifyFdActive() (*os.File, error) { c.mu.RLock() defer c.mu.RUnlock() if c.container == nil { return nil, ErrNotDefined } notifyFd := int(C.go_lxc_seccomp_notify_fd_active(c.container)) if notifyFd < 0 { return nil, unix.Errno(unix.EBADF) } return os.NewFile(uintptr(notifyFd), "seccomp notify"), nil } // SetTimeout sets the response receive timeout for commands func (c *Container) SetTimeout(timeout time.Duration) error { c.mu.Lock() defer c.mu.Unlock() if c.container == nil { return ErrNotDefined } ret := int(C.go_lxc_set_timeout(c.container, C.int(timeout.Seconds()))) if ret < 0 { return unix.Errno(-ret) } return nil } // Daemonize returns true if the container wished to be daemonized. func (c *Container) Daemonize() bool { c.mu.RLock() defer c.mu.RUnlock() if c.container == nil { return false } return bool(c.container.daemonize) } // WantDaemonize determines if the container wants to run daemonized. func (c *Container) WantDaemonize(state bool) error { c.mu.Lock() defer c.mu.Unlock() if c.container == nil { return ErrNotDefined } if !bool(C.go_lxc_want_daemonize(c.container, C.bool(state))) { return ErrDaemonizeFailed } return nil } // WantCloseAllFds determines whether container wishes all file descriptors // to be closed on startup. func (c *Container) WantCloseAllFds(state bool) error { c.mu.Lock() defer c.mu.Unlock() if c.container == nil { return ErrNotDefined } if !bool(C.go_lxc_want_close_all_fds(c.container, C.bool(state))) { return ErrCloseAllFdsFailed } return nil } // SetVerbosity sets the verbosity level of some API calls func (c *Container) SetVerbosity(verbosity Verbosity) { c.mu.Lock() defer c.mu.Unlock() c.verbosity = verbosity } // Freeze freezes the running container. func (c *Container) Freeze() error { c.mu.Lock() defer c.mu.Unlock() if c.container == nil { return ErrNotDefined } // check the state using lockless version if c.state() == FROZEN { return ErrAlreadyFrozen } if !bool(C.go_lxc_freeze(c.container)) { return ErrFreezeFailed } return nil } // Unfreeze thaws the frozen container. func (c *Container) Unfreeze() error { c.mu.Lock() defer c.mu.Unlock() if c.container == nil { return ErrNotDefined } if err := c.makeSure(isRunning); err != nil { return err } // check the state using lockless version if c.state() != FROZEN { return ErrNotFrozen } if !bool(C.go_lxc_unfreeze(c.container)) { return ErrUnfreezeFailed } return nil } // Create creates the container using given TemplateOptions func (c *Container) Create(options TemplateOptions) error { c.mu.Lock() defer c.mu.Unlock() if err := c.makeSure(isNotDefined); err != nil { return err } bdevspecs := buildBdevSpecs(options.BackendSpecs) // use download template if not set if options.Template == "" { options.Template = "download" } // use Directory backend if not set if options.Backend == 0 { options.Backend = Directory } var args []string if options.Template == "download" { // required parameters if options.Distro == "" || options.Release == "" || options.Arch == "" { return ErrInsufficientNumberOfArguments } args = append(args, "--dist", options.Distro, "--release", options.Release, "--arch", options.Arch) // optional arguments if options.Variant != "" { args = append(args, "--variant", options.Variant) } if options.Server != "" { args = append(args, "--server", options.Server) } if options.KeyID != "" { args = append(args, "--keyid", options.KeyID) } if options.KeyServer != "" { args = append(args, "--keyserver", options.KeyServer) } if options.DisableGPGValidation { args = append(args, "--no-validate") } if options.FlushCache { args = append(args, "--flush-cache") } if options.ForceCache { args = append(args, "--force-cache") } } else { // optional arguments if options.Release != "" { args = append(args, "--release", options.Release) } if options.Arch != "" { args = append(args, "--arch", options.Arch) } if options.FlushCache { args = append(args, "--flush-cache") } } if options.ExtraArgs != nil { args = append(args, options.ExtraArgs...) } ctemplate := C.CString(options.Template) defer C.free(unsafe.Pointer(ctemplate)) cbackend := C.CString(options.Backend.String()) defer C.free(unsafe.Pointer(cbackend)) ret := false if args != nil { cargs := makeNullTerminatedArgs(args) if cargs == nil { return ErrAllocationFailed } defer freeNullTerminatedArgs(cargs, len(args)) ret = bool(C.go_lxc_create(c.container, ctemplate, cbackend, bdevspecs, C.int(c.verbosity), cargs)) } else { ret = bool(C.go_lxc_create(c.container, ctemplate, cbackend, bdevspecs, C.int(c.verbosity), nil)) } if !ret { return ErrCreateFailed } return nil } // Start starts the container. func (c *Container) Start() error { c.mu.Lock() defer c.mu.Unlock() if c.container == nil { return ErrNotDefined } if err := c.makeSure(isNotRunning); err != nil { return err } if !bool(C.go_lxc_start(c.container, 0, nil)) { return ErrStartFailed } return nil } // StartWithArgs starts the container using given arguments. func (c *Container) StartWithArgs(args []string) error { c.mu.Lock() defer c.mu.Unlock() if c.container == nil { return ErrNotDefined } if err := c.makeSure(isNotRunning); err != nil { return err } if args != nil { if !bool(C.go_lxc_start(c.container, 0, makeNullTerminatedArgs(args))) { return ErrStartFailed } } else { if !bool(C.go_lxc_start(c.container, 0, nil)) { return ErrStartFailed } } return nil } // StartExecute starts a container. It runs a minimal init as PID 1 and the // requested program as the second process. func (c *Container) StartExecute(args []string) error { c.mu.Lock() defer c.mu.Unlock() if c.container == nil { return ErrNotDefined } if err := c.makeSure(isNotRunning); err != nil { return err } if args != nil { if !bool(C.go_lxc_start(c.container, 1, makeNullTerminatedArgs(args))) { return ErrStartFailed } } else { if !bool(C.go_lxc_start(c.container, 1, nil)) { return ErrStartFailed } } return nil } // 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 } 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 = append(cargs, args...) // 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() if err != nil { // Do not suppress stderr if the exit code != 0. Return with err. if len(output) > 1 { return output, ErrExecuteFailed } return nil, ErrExecuteFailed } return output, nil } // Stop stops the container. func (c *Container) Stop() error { c.mu.Lock() defer c.mu.Unlock() if c.container == nil { return ErrNotDefined } if err := c.makeSure(isRunning); err != nil { return err } if !bool(C.go_lxc_stop(c.container)) { return ErrStopFailed } return nil } // Reboot reboots the container. func (c *Container) Reboot() error { c.mu.Lock() defer c.mu.Unlock() if c.container == nil { return ErrNotDefined } if err := c.makeSure(isRunning); err != nil { return err } if !bool(C.go_lxc_reboot(c.container)) { return ErrRebootFailed } return nil } // Shutdown shuts down the container. func (c *Container) Shutdown(timeout time.Duration) error { c.mu.Lock() defer c.mu.Unlock() if c.container == nil { return ErrNotDefined } if err := c.makeSure(isRunning); err != nil { return err } if !bool(C.go_lxc_shutdown(c.container, C.int(timeout.Seconds()))) { return ErrShutdownFailed } return nil } // Destroy destroys the container. func (c *Container) Destroy() error { c.mu.Lock() defer c.mu.Unlock() if c.container == nil { return ErrNotDefined } if err := c.makeSure(isDefined | isNotRunning); err != nil { return err } if !bool(C.go_lxc_destroy(c.container)) { return ErrDestroyFailed } return nil } // DestroyWithAllSnapshots destroys the container and its snapshots func (c *Container) DestroyWithAllSnapshots() error { c.mu.Lock() defer c.mu.Unlock() if c.container == nil { return ErrNotDefined } if err := c.makeSure(isDefined | isNotRunning | isGreaterEqualThanLXC11); err != nil { return err } if !bool(C.go_lxc_destroy_with_snapshots(c.container)) { return ErrDestroyWithAllSnapshotsFailed } return nil } // 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: // zfs requires zfsroot // lvm requires lvname/vgname/thinpool as well as fstype and fssize // btrfs requires nothing // dir requires nothing // // newsize: for blockdev-backed backingstores // // hookargs: additional arguments to pass to the clone hook script if err := c.makeSure(isDefined | isNotRunning); err != nil { return err } // use Directory backend if not set if options.Backend == 0 { options.Backend = Directory } var flags int if options.KeepName { flags |= C.LXC_CLONE_KEEPNAME } if options.KeepMAC { flags |= C.LXC_CLONE_KEEPMACADDR } if options.Snapshot { flags |= C.LXC_CLONE_SNAPSHOT } cname := C.CString(name) defer C.free(unsafe.Pointer(cname)) cbackend := C.CString(options.Backend.String()) defer C.free(unsafe.Pointer(cbackend)) if options.ConfigPath != "" { clxcpath := C.CString(options.ConfigPath) defer C.free(unsafe.Pointer(clxcpath)) if !bool(C.go_lxc_clone(c.container, cname, clxcpath, C.int(flags), cbackend)) { return ErrCloneFailed } } else { if !bool(C.go_lxc_clone(c.container, cname, nil, C.int(flags), cbackend)) { return ErrCloneFailed } } return nil } // Rename renames the container. func (c *Container) Rename(name string) error { c.mu.Lock() defer c.mu.Unlock() if c.container == nil { return ErrNotDefined } if err := c.makeSure(isDefined | isNotRunning); err != nil { return err } cname := C.CString(name) defer C.free(unsafe.Pointer(cname)) if !bool(C.go_lxc_rename(c.container, cname)) { return ErrRenameFailed } return nil } // Wait waits for container to reach a particular state. func (c *Container) Wait(state State, timeout time.Duration) bool { c.mu.Lock() defer c.mu.Unlock() if c.container == nil { return false } cstate := C.CString(state.String()) defer C.free(unsafe.Pointer(cstate)) return bool(C.go_lxc_wait(c.container, cstate, C.int(timeout.Seconds()))) } // ConfigFileName returns the container's configuration file's name. func (c *Container) ConfigFileName() string { c.mu.RLock() defer c.mu.RUnlock() if c.container == nil { return "" } // allocated in lxc.c configFileName := C.go_lxc_config_file_name(c.container) defer C.free(unsafe.Pointer(configFileName)) return C.GoString(configFileName) } func (c *Container) configItem(key string) []string { if c.container == nil { return nil } ckey := C.CString(key) defer C.free(unsafe.Pointer(ckey)) // allocated in lxc.c configItem := C.go_lxc_get_config_item(c.container, ckey) defer C.free(unsafe.Pointer(configItem)) ret := strings.TrimSpace(C.GoString(configItem)) return strings.Split(ret, "\n") } // ConfigItem returns the value of the given config item. func (c *Container) ConfigItem(key string) []string { c.mu.RLock() defer c.mu.RUnlock() return c.configItem(key) } func (c *Container) setConfigItem(key string, value string) error { if c.container == nil { return ErrNotDefined } ckey := C.CString(key) defer C.free(unsafe.Pointer(ckey)) cvalue := C.CString(value) defer C.free(unsafe.Pointer(cvalue)) if !bool(C.go_lxc_set_config_item(c.container, ckey, cvalue)) { return ErrSettingConfigItemFailed } 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) } func (c *Container) runningConfigItem(key string) []string { if c.container == nil { return nil } ckey := C.CString(key) defer C.free(unsafe.Pointer(ckey)) // allocated in lxc.c configItem := C.go_lxc_get_running_config_item(c.container, ckey) defer C.free(unsafe.Pointer(configItem)) ret := strings.TrimSpace(C.GoString(configItem)) return strings.Split(ret, "\n") } // RunningConfigItem returns the value of the given config item. func (c *Container) RunningConfigItem(key string) []string { c.mu.RLock() defer c.mu.RUnlock() return c.runningConfigItem(key) } func (c *Container) cgroupItem(key string) []string { if c.container == nil { return nil } ckey := C.CString(key) defer C.free(unsafe.Pointer(ckey)) // allocated in lxc.c cgroupItem := C.go_lxc_get_cgroup_item(c.container, ckey) defer C.free(unsafe.Pointer(cgroupItem)) ret := strings.TrimSpace(C.GoString(cgroupItem)) return strings.Split(ret, "\n") } func (c *Container) setCgroupItem(key string, value string) error { if c.container == nil { return ErrNotDefined } ckey := C.CString(key) defer C.free(unsafe.Pointer(ckey)) cvalue := C.CString(value) defer C.free(unsafe.Pointer(cvalue)) if !bool(C.go_lxc_set_cgroup_item(c.container, ckey, cvalue)) { return ErrSettingCgroupItemFailed } 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() defer c.mu.Unlock() if c.container == nil { return } C.go_lxc_clear_config(c.container) } // ClearConfigItem clears the value of given config item. func (c *Container) ClearConfigItem(key string) error { c.mu.Lock() defer c.mu.Unlock() if c.container == nil { return ErrNotDefined } ckey := C.CString(key) defer C.free(unsafe.Pointer(ckey)) if !bool(C.go_lxc_clear_config_item(c.container, ckey)) { return ErrClearingConfigItemFailed } return nil } // ConfigKeys returns the names of the config items. func (c *Container) ConfigKeys(key ...string) []string { c.mu.RLock() defer c.mu.RUnlock() if c.container == nil { return nil } var ret string if key != nil && len(key) == 1 { ckey := C.CString(key[0]) defer C.free(unsafe.Pointer(ckey)) // allocated in lxc.c keys := C.go_lxc_get_keys(c.container, ckey) defer C.free(unsafe.Pointer(keys)) ret = strings.TrimSpace(C.GoString(keys)) } else { // allocated in lxc.c keys := C.go_lxc_get_keys(c.container, nil) defer C.free(unsafe.Pointer(keys)) ret = strings.TrimSpace(C.GoString(keys)) } return strings.Split(ret, "\n") } // LoadConfigFile loads the configuration file from given path. func (c *Container) LoadConfigFile(path string) error { c.mu.Lock() defer c.mu.Unlock() if c.container == nil { return ErrNotDefined } cpath := C.CString(path) defer C.free(unsafe.Pointer(cpath)) if !bool(C.go_lxc_load_config(c.container, cpath)) { return ErrLoadConfigFailed } return nil } func (c *Container) saveConfigFile(path string) error { if c.container == nil { return ErrNotDefined } cpath := C.CString(path) defer C.free(unsafe.Pointer(cpath)) if !bool(C.go_lxc_save_config(c.container, cpath)) { return ErrSaveConfigFailed } return nil } // SaveConfigFile saves the configuration file to given path. func (c *Container) SaveConfigFile(path string) error { c.mu.Lock() defer c.mu.Unlock() return c.saveConfigFile(path) } func (c *Container) configPath() string { if c.container == nil { return "" } 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.configPath() } // SetConfigPath sets the configuration file's path. func (c *Container) SetConfigPath(path string) error { c.mu.Lock() defer c.mu.Unlock() if c.container == nil { return ErrNotDefined } cpath := C.CString(path) defer C.free(unsafe.Pointer(cpath)) if !bool(C.go_lxc_set_config_path(c.container, cpath)) { return ErrSettingConfigPathFailed } return nil } // 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 } return c.cgroupItemAsByteSize("memory.usage_in_bytes", ErrMemLimit) } // 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 } return c.cgroupItemAsByteSize("memory.limit_in_bytes", ErrMemLimit) } // 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 } return c.setCgroupItemWithByteSize("memory.limit_in_bytes", limit, ErrSettingMemoryLimitFailed) } // 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 } return c.cgroupItemAsByteSize("memory.soft_limit_in_bytes", ErrSoftMemLimit) } // 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 } return c.setCgroupItemWithByteSize("memory.soft_limit_in_bytes", limit, ErrSettingSoftMemoryLimitFailed) } // 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 } return c.cgroupItemAsByteSize("memory.kmem.usage_in_bytes", ErrKMemLimit) } // 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 } return c.cgroupItemAsByteSize("memory.kmem.limit_in_bytes", ErrKMemLimit) } // 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 } return c.setCgroupItemWithByteSize("memory.kmem.limit_in_bytes", limit, ErrSettingKMemoryLimitFailed) } // 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 } return c.cgroupItemAsByteSize("memory.memsw.usage_in_bytes", ErrMemorySwapLimit) } // 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 } return c.cgroupItemAsByteSize("memory.memsw.limit_in_bytes", ErrMemorySwapLimit) } // 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 } return c.setCgroupItemWithByteSize("memory.memsw.limit_in_bytes", limit, ErrSettingMemorySwapLimitFailed) } // 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 } ioServiceBytes := c.cgroupItem("blkio.throttle.io_service_bytes") if ioServiceBytes[0] == "" { return 0, nil } for _, v := range ioServiceBytes { b := strings.Split(v, " ") if b[0] == "Total" { blkioUsed, err := strconv.ParseFloat(b[1], 64) if err != nil { return -1, err } return ByteSize(blkioUsed), nil } } return -1, ErrBlkioUsage } // 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 } usage := c.cgroupItem("cpuacct.usage") if usage[0] == "" { return 0, nil } cpuUsage, err := strconv.ParseInt(usage[0], 10, 64) if err != nil { return -1, err } return time.Duration(cpuUsage), nil } // 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 } usagePerCPU := c.cgroupItem("cpuacct.usage_percpu") if usagePerCPU[0] == "" { return map[int]time.Duration{0: 0}, nil } cpuTimes := make(map[int]time.Duration) for i, v := range strings.Split(usagePerCPU[0], " ") { cpuUsage, err := strconv.ParseInt(v, 10, 64) if err != nil { return nil, err } cpuTimes[i] = time.Duration(cpuUsage) } return cpuTimes, nil } // 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 } stat := c.cgroupItem("cpuacct.stat") if stat[0] == "" { return map[string]int64{"user": 0, "system": 0}, nil } user, err := strconv.ParseInt(strings.Split(stat[0], "user ")[1], 10, 64) if err != nil { return nil, err } system, err := strconv.ParseInt(strings.Split(stat[1], "system ")[1], 10, 64) if err != nil { return nil, err } return map[string]int64{"user": user, "system": system}, nil } // ConsoleFd allocates a console tty from container // ttynum: tty number to attempt to allocate or -1 to allocate the first available tty // // Returns "ttyfd" on success, -1 on failure. The returned "ttyfd" is // used to keep the tty allocated. The caller should close "ttyfd" to // 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() if c.container == nil { return -1, ErrNotDefined } // FIXME: Make idiomatic if err := c.makeSure(isRunning); err != nil { return -1, err } ret := int(C.go_lxc_console_getfd(c.container, C.int(ttynum))) if ret < 0 { return ret, ErrAttachFailed } return ret, nil } // Console allocates and runs a console tty from container // // 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 c.container == nil { return ErrNotDefined } if err := c.makeSure(isRunning); err != nil { return err } ret := bool(C.go_lxc_console(c.container, C.int(options.Tty), C.int(options.StdinFd), C.int(options.StdoutFd), C.int(options.StderrFd), C.int(options.EscapeCharacter))) if !ret { return ErrAttachFailed } return nil } // 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 c.container == nil { return ErrNotDefined } if err := c.makeSure(isRunning); err != nil { return err } cenv := makeNullTerminatedArgs(options.Env) if cenv == nil { return ErrAllocationFailed } defer freeNullTerminatedArgs(cenv, len(options.Env)) cenvToKeep := makeNullTerminatedArgs(options.EnvToKeep) if cenvToKeep == nil { return ErrAllocationFailed } defer freeNullTerminatedArgs(cenvToKeep, len(options.EnvToKeep)) cwd := C.CString(options.Cwd) defer C.free(unsafe.Pointer(cwd)) groups := makeGroups(options.Groups) ret := int(C.go_lxc_attach(c.container, C.bool(options.ClearEnv), C.int(options.Namespaces), C.long(options.Arch), C.uid_t(options.UID), C.gid_t(options.GID), groups, C.int(options.StdinFd), C.int(options.StdoutFd), C.int(options.StderrFd), cwd, cenv, cenvToKeep, C.int(attachFlags(options)), )) if ret < 0 { return ErrAttachFailed } return nil } func attachFlags(opts AttachOptions) int { flags := C.LXC_ATTACH_DEFAULT if opts.RemountSysProc { flags |= C.LXC_ATTACH_REMOUNT_PROC_SYS } if opts.ElevatedPrivileges { flags &^= (C.LXC_ATTACH_MOVE_TO_CGROUP | C.LXC_ATTACH_DROP_CAPABILITIES | C.LXC_ATTACH_LSM_EXEC) } return flags } func makeGroups(groups []int) C.struct_lxc_groups_t { if len(groups) == 0 { return C.struct_lxc_groups_t{size: 0, list: nil} } l := make([]C.gid_t, len(groups)) for i, g := range groups { l[i] = C.gid_t(g) } return C.struct_lxc_groups_t{size: C.size_t(len(groups)), list: &l[0]} } func (c *Container) runCommandStatus(args []string, options AttachOptions) (int, error) { if c.container == nil { return -1, ErrNotDefined } if len(args) == 0 { return -1, ErrInsufficientNumberOfArguments } if err := c.makeSure(isRunning); err != nil { return -1, err } cargs := makeNullTerminatedArgs(args) if cargs == nil { return -1, ErrAllocationFailed } defer freeNullTerminatedArgs(cargs, len(args)) cenv := makeNullTerminatedArgs(options.Env) if cenv == nil { return -1, ErrAllocationFailed } defer freeNullTerminatedArgs(cenv, len(options.Env)) cenvToKeep := makeNullTerminatedArgs(options.EnvToKeep) if cenvToKeep == nil { return -1, ErrAllocationFailed } defer freeNullTerminatedArgs(cenvToKeep, len(options.EnvToKeep)) cwd := C.CString(options.Cwd) defer C.free(unsafe.Pointer(cwd)) groups := makeGroups(options.Groups) ret := int(C.go_lxc_attach_run_wait( c.container, C.bool(options.ClearEnv), C.int(options.Namespaces), C.long(options.Arch), C.uid_t(options.UID), C.gid_t(options.GID), groups, C.int(options.StdinFd), C.int(options.StdoutFd), C.int(options.StderrFd), cwd, cenv, cenvToKeep, cargs, C.int(attachFlags(options)), )) if ret < 0 { return ret, nil } // Mirror the behavior of WEXITSTATUS(). return int((ret & 0xFF00) >> 8), 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 } if err := c.makeSure(isRunning); err != nil { return -1, err } cargs := makeNullTerminatedArgs(args) if cargs == nil { return -1, ErrAllocationFailed } defer freeNullTerminatedArgs(cargs, len(args)) cenv := makeNullTerminatedArgs(options.Env) if cenv == nil { return -1, ErrAllocationFailed } defer freeNullTerminatedArgs(cenv, len(options.Env)) cenvToKeep := makeNullTerminatedArgs(options.EnvToKeep) if cenvToKeep == nil { return -1, ErrAllocationFailed } defer freeNullTerminatedArgs(cenvToKeep, len(options.EnvToKeep)) cwd := C.CString(options.Cwd) defer C.free(unsafe.Pointer(cwd)) groups := makeGroups(options.Groups) var attachedPid C.pid_t ret := int(C.go_lxc_attach_no_wait( c.container, C.bool(options.ClearEnv), C.int(options.Namespaces), C.long(options.Arch), C.uid_t(options.UID), C.gid_t(options.GID), groups, C.int(options.StdinFd), C.int(options.StdoutFd), C.int(options.StderrFd), cwd, cenv, cenvToKeep, cargs, &attachedPid, C.int(attachFlags(options)), )) if ret < 0 { return ret, ErrAttachFailed } return int(attachedPid), nil } // RunCommand attachs a shell and runs the command within the container. // 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) { c.mu.Lock() defer c.mu.Unlock() ret, err := c.runCommandStatus(args, options) if err != nil { return false, err } if ret < 0 { return false, ErrAttachFailed } return ret == 0, nil } // Interfaces returns the names of the network interfaces. func (c *Container) Interfaces() ([]string, error) { c.mu.RLock() defer c.mu.RUnlock() if c.container == nil { return nil, ErrNotDefined } if err := c.makeSure(isRunning); err != nil { return nil, err } result := C.go_lxc_get_interfaces(c.container) if result == nil { return nil, ErrInterfaces } return convertArgs(result), nil } // 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 } var interfaceName string statistics := make(map[string]map[string]ByteSize) netPrefix := "lxc.net" if !VersionAtLeast(2, 1, 0) { netPrefix = "lxc.network" } for i := 0; i < len(c.configItem(netPrefix)); i++ { interfaceType := c.runningConfigItem(fmt.Sprintf("%s.%d.type", netPrefix, i)) if interfaceType == nil { continue } if interfaceType[0] == "veth" { interfaceName = c.runningConfigItem(fmt.Sprintf("%s.%d.veth.pair", netPrefix, i))[0] } else { interfaceName = c.runningConfigItem(fmt.Sprintf("%s.%d.link", netPrefix, i))[0] } for _, v := range []string{"rx", "tx"} { /* tx and rx are reversed from the host vs container */ content, err := ioutil.ReadFile(fmt.Sprintf("/sys/class/net/%s/statistics/%s_bytes", interfaceName, v)) if err != nil { return nil, err } bytes, err := strconv.ParseInt(strings.Split(string(content), "\n")[0], 10, 64) if err != nil { return nil, err } if statistics[interfaceName] == nil { statistics[interfaceName] = make(map[string]ByteSize) } statistics[interfaceName][v] = ByteSize(bytes) } } return statistics, nil } // 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 c.container == nil { return nil, ErrNotDefined } if err := c.makeSure(isRunning); err != nil { return nil, err } cinterface := C.CString(interfaceName) defer C.free(unsafe.Pointer(cinterface)) result := C.go_lxc_get_ips(c.container, cinterface, nil, 0) if result == nil { return nil, ErrIPAddress } return convertArgs(result), nil } // 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 c.container == nil { return nil, ErrNotDefined } if err := c.makeSure(isRunning); err != nil { return nil, err } cinterface := C.CString(interfaceName) defer C.free(unsafe.Pointer(cinterface)) cfamily := C.CString("inet") defer C.free(unsafe.Pointer(cfamily)) result := C.go_lxc_get_ips(c.container, cinterface, cfamily, 0) if result == nil { return nil, ErrIPv4Addresses } return convertArgs(result), nil } // 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 c.container == nil { return nil, ErrNotDefined } if err := c.makeSure(isRunning); err != nil { return nil, err } cinterface := C.CString(interfaceName) defer C.free(unsafe.Pointer(cinterface)) cfamily := C.CString("inet6") defer C.free(unsafe.Pointer(cfamily)) result := C.go_lxc_get_ips(c.container, cinterface, cfamily, 0) if result == nil { return nil, ErrIPv6Addresses } return convertArgs(result), nil } // 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 { return result, nil } // Python API sleeps 1 second as well time.Sleep(1 * time.Second) if time.Since(now) >= timeout { return nil, ErrIPAddresses } } } func (c *Container) ipAddresses() ([]string, error) { if c.container == nil { return nil, ErrNotDefined } if err := c.makeSure(isRunning); err != nil { return nil, err } result := C.go_lxc_get_ips(c.container, nil, nil, 0) if result == nil { return nil, ErrIPAddresses } return convertArgs(result), nil } // 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 c.container == nil { return nil, ErrNotDefined } if err := c.makeSure(isRunning); err != nil { return nil, err } cfamily := C.CString("inet") defer C.free(unsafe.Pointer(cfamily)) result := C.go_lxc_get_ips(c.container, nil, cfamily, 0) if result == nil { return nil, ErrIPv4Addresses } return convertArgs(result), nil } // IPv6Addresses returns all IPv6 addresses. func (c *Container) IPv6Addresses() ([]string, error) { c.mu.RLock() defer c.mu.RUnlock() if c.container == nil { return nil, ErrNotDefined } if err := c.makeSure(isRunning); err != nil { return nil, err } cfamily := C.CString("inet6") defer C.free(unsafe.Pointer(cfamily)) result := C.go_lxc_get_ips(c.container, nil, cfamily, 0) if result == nil { return nil, ErrIPv6Addresses } return convertArgs(result), nil } // 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] } return c.configItem("lxc.logfile")[0] } // 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) } else { err = c.setConfigItem("lxc.logfile", filename) } if err != nil { return err } return nil } // 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]] } return logLevelMap[c.configItem("lxc.loglevel")[0]] } // 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()) } else { err = c.setConfigItem("lxc.loglevel", level.String()) } if err != nil { return err } return nil } // AddDeviceNode adds specified device to the container. func (c *Container) AddDeviceNode(source string, destination ...string) error { c.mu.Lock() defer c.mu.Unlock() if c.container == nil { return ErrNotDefined } if err := c.makeSure(isRunning | isPrivileged); err != nil { return err } csource := C.CString(source) defer C.free(unsafe.Pointer(csource)) if destination != nil && len(destination) == 1 { cdestination := C.CString(destination[0]) defer C.free(unsafe.Pointer(cdestination)) if !bool(C.go_lxc_add_device_node(c.container, csource, cdestination)) { return ErrAddDeviceNodeFailed } return nil } if !bool(C.go_lxc_add_device_node(c.container, csource, nil)) { 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 c.container == nil { return ErrNotDefined } if err := c.makeSure(isRunning | isPrivileged); err != nil { return err } csource := C.CString(source) defer C.free(unsafe.Pointer(csource)) if destination != nil && len(destination) == 1 { cdestination := C.CString(destination[0]) defer C.free(unsafe.Pointer(cdestination)) if !bool(C.go_lxc_remove_device_node(c.container, csource, cdestination)) { return ErrRemoveDeviceNodeFailed } return nil } if !bool(C.go_lxc_remove_device_node(c.container, csource, nil)) { return ErrRemoveDeviceNodeFailed } return nil } // Checkpoint checkpoints the container. func (c *Container) Checkpoint(opts CheckpointOptions) error { c.mu.Lock() defer c.mu.Unlock() if c.container == nil { return ErrNotDefined } if err := c.makeSure(isRunning | isGreaterEqualThanLXC11); err != nil { return err } cdirectory := C.CString(opts.Directory) defer C.free(unsafe.Pointer(cdirectory)) cstop := C.bool(opts.Stop) cverbose := C.bool(opts.Verbose) if !C.go_lxc_checkpoint(c.container, cdirectory, cstop, cverbose) { return ErrCheckpointFailed } return nil } // Restore restores the container from a checkpoint. func (c *Container) Restore(opts RestoreOptions) error { c.mu.Lock() defer c.mu.Unlock() if c.container == nil { return ErrNotDefined } if err := c.makeSure(isGreaterEqualThanLXC11); err != nil { return err } cdirectory := C.CString(opts.Directory) defer C.free(unsafe.Pointer(cdirectory)) cverbose := C.bool(opts.Verbose) if !C.bool(C.go_lxc_restore(c.container, cdirectory, cverbose)) { return ErrRestoreFailed } return nil } // Migrate migrates the container. func (c *Container) Migrate(cmd uint, opts MigrateOptions) error { c.mu.Lock() defer c.mu.Unlock() if c.container == nil { return ErrNotDefined } if err := c.makeSure(isNotDefined | isGreaterEqualThanLXC20); err != nil { return err } if cmd != MIGRATE_RESTORE { if err := c.makeSure(isRunning); err != nil { return err } } cdirectory := C.CString(opts.Directory) defer C.free(unsafe.Pointer(cdirectory)) var cpredumpdir *C.char if opts.PredumpDir != "" { cpredumpdir = C.CString(opts.PredumpDir) defer C.free(unsafe.Pointer(cpredumpdir)) } /* Since we can't do conditional compilation here, we allocate the * "extras" struct and then merge them in the C code. */ copts := C.struct_migrate_opts{ directory: cdirectory, verbose: C.bool(opts.Verbose), stop: C.bool(opts.Stop), predump_dir: cpredumpdir, } var cActionScript *C.char if opts.ActionScript != "" { cActionScript = C.CString(opts.ActionScript) defer C.free(unsafe.Pointer(cActionScript)) } extras := C.struct_extra_migrate_opts{ preserves_inodes: C.bool(opts.PreservesInodes), action_script: cActionScript, ghost_limit: C.uint64_t(opts.GhostLimit), features_to_check: C.uint64_t(opts.FeaturesToCheck), } ret := C.int(C.go_lxc_migrate(c.container, C.uint(cmd), &copts, &extras)) if ret != 0 { return fmt.Errorf("migration failed %d", ret) } return nil } // AttachInterface attaches specified netdev to the container. func (c *Container) AttachInterface(source, destination string) error { c.mu.Lock() defer c.mu.Unlock() if c.container == nil { return ErrNotDefined } if err := c.makeSure(isRunning | isPrivileged | isGreaterEqualThanLXC11); err != nil { return err } csource := C.CString(source) defer C.free(unsafe.Pointer(csource)) cdestination := C.CString(destination) defer C.free(unsafe.Pointer(cdestination)) if !bool(C.go_lxc_attach_interface(c.container, csource, cdestination)) { return ErrAttachInterfaceFailed } return nil } // DetachInterface detaches specified netdev from the container. func (c *Container) DetachInterface(source string) error { c.mu.Lock() defer c.mu.Unlock() if c.container == nil { return ErrNotDefined } if err := c.makeSure(isRunning | isPrivileged | isGreaterEqualThanLXC11); err != nil { return err } csource := C.CString(source) defer C.free(unsafe.Pointer(csource)) if !bool(C.go_lxc_detach_interface(c.container, csource, nil)) { return ErrDetachInterfaceFailed } return nil } // DetachInterfaceRename detaches specified netdev from the container and renames it. func (c *Container) DetachInterfaceRename(source, target string) error { c.mu.Lock() defer c.mu.Unlock() if c.container == nil { return ErrNotDefined } if err := c.makeSure(isRunning | isPrivileged | isGreaterEqualThanLXC11); err != nil { return err } csource := C.CString(source) defer C.free(unsafe.Pointer(csource)) ctarget := C.CString(target) defer C.free(unsafe.Pointer(ctarget)) if !bool(C.go_lxc_detach_interface(c.container, csource, ctarget)) { return ErrDetachInterfaceFailed } return nil } // ConsoleLog allows to perform operations on the container's in-memory console // buffer. func (c *Container) ConsoleLog(opt ConsoleLogOptions) ([]byte, error) { c.mu.Lock() defer c.mu.Unlock() if c.container == nil { return nil, ErrNotDefined } cl := C.struct_lxc_console_log{ clear: C.bool(opt.ClearLog), read: C.bool(opt.ReadLog), data: nil, } // CGO is a fickle little beast: // We need to manually allocate memory here that we pass to C. If we // were to pass a GO pointer by passing a C.uint64_t pointer we'd end in // the situation where we have a GO pointer that points to a GO pointer. // Go will freak out when this happens. So give C its own memory. var buf unsafe.Pointer buf = C.malloc(C.sizeof_uint64_t) if buf == nil { return nil, syscall.ENOMEM } defer C.free(buf) cl.read_max = (*C.uint64_t)(buf) *cl.read_max = C.uint64_t(opt.ReadMax) ret := C.go_lxc_console_log(c.container, &cl) if ret < 0 { return nil, syscall.Errno(-ret) } numBytes := C.int(*cl.read_max) if C.uint64_t(numBytes) != *cl.read_max { return nil, syscall.ERANGE } return C.GoBytes(unsafe.Pointer(cl.data), numBytes), nil } // ErrorNum returns the error_num field of the container. func (c *Container) ErrorNum() int { if c.container == nil { return -1 } cError := C.go_lxc_error_num(c.container) return int(cError) } func buildBdevSpecs(o *BackendStoreSpecs) *C.struct_bdev_specs { if o == nil { return nil } // bdev_specs: // zfs requires zfsroot // lvm requires lvname/vgname/thinpool as well as fstype and fssize // btrfs requires nothing // dir requires nothing specs := C.struct_bdev_specs{} if o.FSType != "" { fstype := C.CString(o.FSType) specs.fstype = fstype defer C.free(unsafe.Pointer(fstype)) } if o.FSSize > 0 { specs.fssize = C.uint64_t(o.FSSize) } if o.ZFS.Root != "" { zfsroot := C.CString(o.ZFS.Root) specs.zfs.zfsroot = zfsroot defer C.free(unsafe.Pointer(zfsroot)) } if o.LVM.VG != "" { vg := C.CString(o.LVM.VG) specs.lvm.vg = vg defer C.free(unsafe.Pointer(vg)) } if o.LVM.Thinpool != "" { lv := C.CString(o.LVM.Thinpool) specs.lvm.thinpool = lv defer C.free(unsafe.Pointer(lv)) } if o.RBD.Name != "" { lv := C.CString(o.RBD.Name) specs.rbd.rbdname = lv defer C.free(unsafe.Pointer(lv)) } if o.RBD.Pool != "" { lv := C.CString(o.RBD.Pool) specs.rbd.rbdpool = lv defer C.free(unsafe.Pointer(lv)) } if o.Dir != nil { dir := C.CString(*o.Dir) specs.dir = dir defer C.free(unsafe.Pointer(dir)) } return &specs }