Merge pull request #9795 from LK4D4/update_libcontainer
Update libcontainer to 1597c68f7b941fd97881155d7f077852e2914e7b
This commit is contained in:
commit
305c898cd7
43 changed files with 517 additions and 362 deletions
|
@ -1399,7 +1399,7 @@ func serveFd(addr string, job *engine.Job) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func lookupGidByName(nameOrGid string) (int, error) {
|
func lookupGidByName(nameOrGid string) (int, error) {
|
||||||
groupFile, err := user.GetGroupFile()
|
groupFile, err := user.GetGroupPath()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return -1, err
|
return -1, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -82,7 +82,7 @@ func (d *driver) createContainer(c *execdriver.Command) (*libcontainer.Config, e
|
||||||
|
|
||||||
func (d *driver) createNetwork(container *libcontainer.Config, c *execdriver.Command) error {
|
func (d *driver) createNetwork(container *libcontainer.Config, c *execdriver.Command) error {
|
||||||
if c.Network.HostNetworking {
|
if c.Network.HostNetworking {
|
||||||
container.Namespaces["NEWNET"] = false
|
container.Namespaces.Remove(libcontainer.NEWNET)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -119,10 +119,7 @@ func (d *driver) createNetwork(container *libcontainer.Config, c *execdriver.Com
|
||||||
cmd := active.cmd
|
cmd := active.cmd
|
||||||
|
|
||||||
nspath := filepath.Join("/proc", fmt.Sprint(cmd.Process.Pid), "ns", "net")
|
nspath := filepath.Join("/proc", fmt.Sprint(cmd.Process.Pid), "ns", "net")
|
||||||
container.Networks = append(container.Networks, &libcontainer.Network{
|
container.Namespaces.Add(libcontainer.NEWNET, nspath)
|
||||||
Type: "netns",
|
|
||||||
NsPath: nspath,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -130,7 +127,7 @@ func (d *driver) createNetwork(container *libcontainer.Config, c *execdriver.Com
|
||||||
|
|
||||||
func (d *driver) createIpc(container *libcontainer.Config, c *execdriver.Command) error {
|
func (d *driver) createIpc(container *libcontainer.Config, c *execdriver.Command) error {
|
||||||
if c.Ipc.HostIpc {
|
if c.Ipc.HostIpc {
|
||||||
container.Namespaces["NEWIPC"] = false
|
container.Namespaces.Remove(libcontainer.NEWIPC)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -144,7 +141,7 @@ func (d *driver) createIpc(container *libcontainer.Config, c *execdriver.Command
|
||||||
}
|
}
|
||||||
cmd := active.cmd
|
cmd := active.cmd
|
||||||
|
|
||||||
container.IpcNsPath = filepath.Join("/proc", fmt.Sprint(cmd.Process.Pid), "ns", "ipc")
|
container.Namespaces.Add(libcontainer.NEWIPC, filepath.Join("/proc", fmt.Sprint(cmd.Process.Pid), "ns", "ipc"))
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -61,10 +61,6 @@ func NewDriver(root, initPath string) (*driver, error) {
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *driver) notifyOnOOM(config *libcontainer.Config) (<-chan struct{}, error) {
|
|
||||||
return fs.NotifyOnOOM(config.Cgroups)
|
|
||||||
}
|
|
||||||
|
|
||||||
type execOutput struct {
|
type execOutput struct {
|
||||||
exitCode int
|
exitCode int
|
||||||
err error
|
err error
|
||||||
|
@ -152,11 +148,16 @@ func (d *driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, startCallba
|
||||||
}
|
}
|
||||||
|
|
||||||
oomKill := false
|
oomKill := false
|
||||||
oomKillNotification, err := d.notifyOnOOM(container)
|
state, err := libcontainer.GetState(filepath.Join(d.root, c.ID))
|
||||||
if err == nil {
|
if err == nil {
|
||||||
_, oomKill = <-oomKillNotification
|
oomKillNotification, err := libcontainer.NotifyOnOOM(state)
|
||||||
|
if err == nil {
|
||||||
|
_, oomKill = <-oomKillNotification
|
||||||
|
} else {
|
||||||
|
log.Warnf("WARNING: Your kernel does not support OOM notifications: %s", err)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
log.Warnf("WARNING: Your kernel does not support OOM notifications: %s", err)
|
log.Warnf("Failed to get container state, oom notify will not work: %s", err)
|
||||||
}
|
}
|
||||||
// wait for the container to exit.
|
// wait for the container to exit.
|
||||||
execOutput := <-execOutputChan
|
execOutput := <-execOutputChan
|
||||||
|
|
|
@ -25,12 +25,12 @@ func New() *libcontainer.Config {
|
||||||
"KILL",
|
"KILL",
|
||||||
"AUDIT_WRITE",
|
"AUDIT_WRITE",
|
||||||
},
|
},
|
||||||
Namespaces: map[string]bool{
|
Namespaces: libcontainer.Namespaces{
|
||||||
"NEWNS": true,
|
{Type: "NEWNS"},
|
||||||
"NEWUTS": true,
|
{Type: "NEWUTS"},
|
||||||
"NEWIPC": true,
|
{Type: "NEWIPC"},
|
||||||
"NEWPID": true,
|
{Type: "NEWPID"},
|
||||||
"NEWNET": true,
|
{Type: "NEWNET"},
|
||||||
},
|
},
|
||||||
Cgroups: &cgroups.Cgroup{
|
Cgroups: &cgroups.Cgroup{
|
||||||
Parent: "docker",
|
Parent: "docker",
|
||||||
|
|
|
@ -66,7 +66,7 @@ if [ "$1" = '--go' ]; then
|
||||||
mv tmp-tar src/code.google.com/p/go/src/pkg/archive/tar
|
mv tmp-tar src/code.google.com/p/go/src/pkg/archive/tar
|
||||||
fi
|
fi
|
||||||
|
|
||||||
clone git github.com/docker/libcontainer 53eca435e63db58b06cf796d3a9326db5fd42253
|
clone git github.com/docker/libcontainer 1597c68f7b941fd97881155d7f077852e2914e7b
|
||||||
# see src/github.com/docker/libcontainer/update-vendor.sh which is the "source of truth" for libcontainer deps (just like this file)
|
# see src/github.com/docker/libcontainer/update-vendor.sh which is the "source of truth" for libcontainer deps (just like this file)
|
||||||
rm -rf src/github.com/docker/libcontainer/vendor
|
rm -rf src/github.com/docker/libcontainer/vendor
|
||||||
eval "$(grep '^clone ' src/github.com/docker/libcontainer/update-vendor.sh | grep -v 'github.com/codegangsta/cli')"
|
eval "$(grep '^clone ' src/github.com/docker/libcontainer/update-vendor.sh | grep -v 'github.com/codegangsta/cli')"
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
|
|
||||||
all:
|
all:
|
||||||
docker build -t docker/libcontainer .
|
docker build -t dockercore/libcontainer .
|
||||||
|
|
||||||
test:
|
test:
|
||||||
# we need NET_ADMIN for the netlink tests and SYS_ADMIN for mounting
|
# we need NET_ADMIN for the netlink tests and SYS_ADMIN for mounting
|
||||||
docker run --rm -it --privileged docker/libcontainer
|
docker run --rm -it --privileged dockercore/libcontainer
|
||||||
|
|
||||||
sh:
|
sh:
|
||||||
docker run --rm -it --privileged -w /busybox docker/libcontainer nsinit exec sh
|
docker run --rm -it --privileged -w /busybox dockercore/libcontainer nsinit exec sh
|
||||||
|
|
||||||
GO_PACKAGES = $(shell find . -not \( -wholename ./vendor -prune -o -wholename ./.git -prune \) -name '*.go' -print0 | xargs -0n1 dirname | sort -u)
|
GO_PACKAGES = $(shell find . -not \( -wholename ./vendor -prune -o -wholename ./.git -prune \) -name '*.go' -print0 | xargs -0n1 dirname | sort -u)
|
||||||
|
|
||||||
|
|
|
@ -318,4 +318,29 @@ a container.
|
||||||
| Resume | Resume all processes inside the container if paused |
|
| Resume | Resume all processes inside the container if paused |
|
||||||
| Exec | Execute a new process inside of the container ( requires setns ) |
|
| Exec | Execute a new process inside of the container ( requires setns ) |
|
||||||
|
|
||||||
|
### Execute a new process inside of a running container.
|
||||||
|
|
||||||
|
User can execute a new process inside of a running container. Any binaries to be
|
||||||
|
executed must be accessible within the container's rootfs.
|
||||||
|
|
||||||
|
The started process will run inside the container's rootfs. Any changes
|
||||||
|
made by the process to the container's filesystem will persist after the
|
||||||
|
process finished executing.
|
||||||
|
|
||||||
|
The started process will join all the container's existing namespaces. When the
|
||||||
|
container is paused, the process will also be paused and will resume when
|
||||||
|
the container is unpaused. The started process will only run when the container's
|
||||||
|
primary process (PID 1) is running, and will not be restarted when the container
|
||||||
|
is restarted.
|
||||||
|
|
||||||
|
#### Planned additions
|
||||||
|
|
||||||
|
The started process will have its own cgroups nested inside the container's
|
||||||
|
cgroups. This is used for process tracking and optionally resource allocation
|
||||||
|
handling for the new process. Freezer cgroup is required, the rest of the cgroups
|
||||||
|
are optional. The process executor must place its pid inside the correct
|
||||||
|
cgroups before starting the process. This is done so that no child processes or
|
||||||
|
threads can escape the cgroups.
|
||||||
|
|
||||||
|
When the process is stopped, the process executor will try (in a best-effort way)
|
||||||
|
to stop all its children and remove the sub-cgroups.
|
||||||
|
|
|
@ -50,6 +50,7 @@ type Cgroup struct {
|
||||||
CpuQuota int64 `json:"cpu_quota,omitempty"` // CPU hardcap limit (in usecs). Allowed cpu time in a given period.
|
CpuQuota int64 `json:"cpu_quota,omitempty"` // CPU hardcap limit (in usecs). Allowed cpu time in a given period.
|
||||||
CpuPeriod int64 `json:"cpu_period,omitempty"` // CPU period to be used for hardcapping (in usecs). 0 to use system default.
|
CpuPeriod int64 `json:"cpu_period,omitempty"` // CPU period to be used for hardcapping (in usecs). 0 to use system default.
|
||||||
CpusetCpus string `json:"cpuset_cpus,omitempty"` // CPU to use
|
CpusetCpus string `json:"cpuset_cpus,omitempty"` // CPU to use
|
||||||
|
CpusetMems string `json:"cpuset_mems,omitempty"` // MEM to use
|
||||||
Freezer FreezerState `json:"freezer,omitempty"` // set the freeze value for the process
|
Freezer FreezerState `json:"freezer,omitempty"` // set the freeze value for the process
|
||||||
Slice string `json:"slice,omitempty"` // Parent slice to use for systemd
|
Slice string `json:"slice,omitempty"` // Parent slice to use for systemd
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,7 @@ func (s *CpusetGroup) Set(d *data) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return s.SetDir(dir, d.c.CpusetCpus, d.pid)
|
return s.SetDir(dir, d.c.CpusetCpus, d.c.CpusetMems, d.pid)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *CpusetGroup) Remove(d *data) error {
|
func (s *CpusetGroup) Remove(d *data) error {
|
||||||
|
@ -29,7 +29,7 @@ func (s *CpusetGroup) GetStats(path string, stats *cgroups.Stats) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *CpusetGroup) SetDir(dir, value string, pid int) error {
|
func (s *CpusetGroup) SetDir(dir, cpus string, mems string, pid int) error {
|
||||||
if err := s.ensureParent(dir); err != nil {
|
if err := s.ensureParent(dir); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -40,10 +40,15 @@ func (s *CpusetGroup) SetDir(dir, value string, pid int) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we don't use --cpuset, the default cpuset.cpus is set in
|
// If we don't use --cpuset-xxx, the default value inherit from parent cgroup
|
||||||
// s.ensureParent, otherwise, use the value we set
|
// is set in s.ensureParent, otherwise, use the value we set
|
||||||
if value != "" {
|
if cpus != "" {
|
||||||
if err := writeFile(dir, "cpuset.cpus", value); err != nil {
|
if err := writeFile(dir, "cpuset.cpus", cpus); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if mems != "" {
|
||||||
|
if err := writeFile(dir, "cpuset.mems", mems); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,12 +38,17 @@ func (s *MemoryGroup) Set(d *data) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// By default, MemorySwap is set to twice the size of RAM.
|
// By default, MemorySwap is set to twice the size of RAM.
|
||||||
// If you want to omit MemorySwap, set it to `-1'.
|
// If you want to omit MemorySwap, set it to '-1'.
|
||||||
if d.c.MemorySwap != -1 {
|
if d.c.MemorySwap == 0 {
|
||||||
if err := writeFile(dir, "memory.memsw.limit_in_bytes", strconv.FormatInt(d.c.Memory*2, 10)); err != nil {
|
if err := writeFile(dir, "memory.memsw.limit_in_bytes", strconv.FormatInt(d.c.Memory*2, 10)); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if d.c.MemorySwap > 0 {
|
||||||
|
if err := writeFile(dir, "memory.memsw.limit_in_bytes", strconv.FormatInt(d.c.MemorySwap, 10)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -90,4 +90,8 @@ func expectMemoryStatEquals(t *testing.T, expected, actual cgroups.MemoryStats)
|
||||||
t.Fail()
|
t.Fail()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if expected.Failcnt != actual.Failcnt {
|
||||||
|
log.Printf("Expected memory failcnt %d but found %d\n", expected.Failcnt, actual.Failcnt)
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -313,5 +313,5 @@ func joinCpuset(c *cgroups.Cgroup, pid int) error {
|
||||||
|
|
||||||
s := &fs.CpusetGroup{}
|
s := &fs.CpusetGroup{}
|
||||||
|
|
||||||
return s.SetDir(path, c.CpusetCpus, pid)
|
return s.SetDir(path, c.CpusetCpus, c.CpusetMems, pid)
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@ import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/docker/docker/pkg/mount"
|
"github.com/docker/docker/pkg/mount"
|
||||||
)
|
)
|
||||||
|
@ -193,13 +194,30 @@ func EnterPid(cgroupPaths map[string]string, pid int) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// RemovePaths iterates over the provided paths removing them.
|
// RemovePaths iterates over the provided paths removing them.
|
||||||
// If an error is encountered the removal proceeds and the first error is
|
// We trying to remove all paths five times with increasing delay between tries.
|
||||||
// returned to ensure a partial removal is not possible.
|
// If after all there are not removed cgroups - appropriate error will be
|
||||||
|
// returned.
|
||||||
func RemovePaths(paths map[string]string) (err error) {
|
func RemovePaths(paths map[string]string) (err error) {
|
||||||
for _, path := range paths {
|
delay := 10 * time.Millisecond
|
||||||
if rerr := os.RemoveAll(path); err == nil {
|
for i := 0; i < 5; i++ {
|
||||||
err = rerr
|
if i != 0 {
|
||||||
|
time.Sleep(delay)
|
||||||
|
delay *= 2
|
||||||
|
}
|
||||||
|
for s, p := range paths {
|
||||||
|
os.RemoveAll(p)
|
||||||
|
// TODO: here probably should be logging
|
||||||
|
_, err := os.Stat(p)
|
||||||
|
// We need this strange way of checking cgroups existence because
|
||||||
|
// RemoveAll almost always returns error, even on already removed
|
||||||
|
// cgroups
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
delete(paths, s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(paths) == 0 {
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return err
|
return fmt.Errorf("Failed to remove paths: %s", paths)
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,57 @@ type MountConfig mount.MountConfig
|
||||||
|
|
||||||
type Network network.Network
|
type Network network.Network
|
||||||
|
|
||||||
|
type NamespaceType string
|
||||||
|
|
||||||
|
const (
|
||||||
|
NEWNET NamespaceType = "NEWNET"
|
||||||
|
NEWPID NamespaceType = "NEWPID"
|
||||||
|
NEWNS NamespaceType = "NEWNS"
|
||||||
|
NEWUTS NamespaceType = "NEWUTS"
|
||||||
|
NEWIPC NamespaceType = "NEWIPC"
|
||||||
|
NEWUSER NamespaceType = "NEWUSER"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Namespace defines configuration for each namespace. It specifies an
|
||||||
|
// alternate path that is able to be joined via setns.
|
||||||
|
type Namespace struct {
|
||||||
|
Type NamespaceType `json:"type"`
|
||||||
|
Path string `json:"path,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Namespaces []Namespace
|
||||||
|
|
||||||
|
func (n Namespaces) Remove(t NamespaceType) bool {
|
||||||
|
i := n.index(t)
|
||||||
|
if i == -1 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
n = append(n[:i], n[i+1:]...)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n Namespaces) Add(t NamespaceType, path string) {
|
||||||
|
i := n.index(t)
|
||||||
|
if i == -1 {
|
||||||
|
n = append(n, Namespace{Type: t, Path: path})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
n[i].Path = path
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n Namespaces) index(t NamespaceType) int {
|
||||||
|
for i, ns := range n {
|
||||||
|
if ns.Type == t {
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n Namespaces) Contains(t NamespaceType) bool {
|
||||||
|
return n.index(t) != -1
|
||||||
|
}
|
||||||
|
|
||||||
// Config defines configuration options for executing a process inside a contained environment.
|
// Config defines configuration options for executing a process inside a contained environment.
|
||||||
type Config struct {
|
type Config struct {
|
||||||
// Mount specific options.
|
// Mount specific options.
|
||||||
|
@ -38,7 +89,7 @@ type Config struct {
|
||||||
|
|
||||||
// Namespaces specifies the container's namespaces that it should setup when cloning the init process
|
// Namespaces specifies the container's namespaces that it should setup when cloning the init process
|
||||||
// If a namespace is not provided that namespace is shared from the container's parent process
|
// If a namespace is not provided that namespace is shared from the container's parent process
|
||||||
Namespaces map[string]bool `json:"namespaces,omitempty"`
|
Namespaces Namespaces `json:"namespaces,omitempty"`
|
||||||
|
|
||||||
// Capabilities specify the capabilities to keep when executing the process inside the container
|
// Capabilities specify the capabilities to keep when executing the process inside the container
|
||||||
// All capbilities not specified will be dropped from the processes capability mask
|
// All capbilities not specified will be dropped from the processes capability mask
|
||||||
|
@ -47,9 +98,6 @@ type Config struct {
|
||||||
// Networks specifies the container's network setup to be created
|
// Networks specifies the container's network setup to be created
|
||||||
Networks []*Network `json:"networks,omitempty"`
|
Networks []*Network `json:"networks,omitempty"`
|
||||||
|
|
||||||
// Ipc specifies the container's ipc setup to be created
|
|
||||||
IpcNsPath string `json:"ipc,omitempty"`
|
|
||||||
|
|
||||||
// Routes can be specified to create entries in the route table as the container is started
|
// Routes can be specified to create entries in the route table as the container is started
|
||||||
Routes []*Route `json:"routes,omitempty"`
|
Routes []*Route `json:"routes,omitempty"`
|
||||||
|
|
||||||
|
|
|
@ -64,12 +64,12 @@ func TestConfigJsonFormat(t *testing.T) {
|
||||||
t.Fail()
|
t.Fail()
|
||||||
}
|
}
|
||||||
|
|
||||||
if !container.Namespaces["NEWNET"] {
|
if !container.Namespaces.Contains(NEWNET) {
|
||||||
t.Log("namespaces should contain NEWNET")
|
t.Log("namespaces should contain NEWNET")
|
||||||
t.Fail()
|
t.Fail()
|
||||||
}
|
}
|
||||||
|
|
||||||
if container.Namespaces["NEWUSER"] {
|
if container.Namespaces.Contains(NEWUSER) {
|
||||||
t.Log("namespaces should not contain NEWUSER")
|
t.Log("namespaces should not contain NEWUSER")
|
||||||
t.Fail()
|
t.Fail()
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,8 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/docker/libcontainer"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestExecPS(t *testing.T) {
|
func TestExecPS(t *testing.T) {
|
||||||
|
@ -55,7 +57,6 @@ func TestIPCPrivate(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
config := newTemplateConfig(rootfs)
|
config := newTemplateConfig(rootfs)
|
||||||
config.Namespaces["NEWIPC"] = true
|
|
||||||
buffers, exitCode, err := runContainer(config, "", "readlink", "/proc/self/ns/ipc")
|
buffers, exitCode, err := runContainer(config, "", "readlink", "/proc/self/ns/ipc")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
|
@ -87,7 +88,7 @@ func TestIPCHost(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
config := newTemplateConfig(rootfs)
|
config := newTemplateConfig(rootfs)
|
||||||
config.Namespaces["NEWIPC"] = false
|
config.Namespaces.Remove(libcontainer.NEWIPC)
|
||||||
buffers, exitCode, err := runContainer(config, "", "readlink", "/proc/self/ns/ipc")
|
buffers, exitCode, err := runContainer(config, "", "readlink", "/proc/self/ns/ipc")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
|
@ -119,8 +120,7 @@ func TestIPCJoinPath(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
config := newTemplateConfig(rootfs)
|
config := newTemplateConfig(rootfs)
|
||||||
config.Namespaces["NEWIPC"] = false
|
config.Namespaces.Add(libcontainer.NEWIPC, "/proc/1/ns/ipc")
|
||||||
config.IpcNsPath = "/proc/1/ns/ipc"
|
|
||||||
|
|
||||||
buffers, exitCode, err := runContainer(config, "", "readlink", "/proc/self/ns/ipc")
|
buffers, exitCode, err := runContainer(config, "", "readlink", "/proc/self/ns/ipc")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -148,8 +148,7 @@ func TestIPCBadPath(t *testing.T) {
|
||||||
defer remove(rootfs)
|
defer remove(rootfs)
|
||||||
|
|
||||||
config := newTemplateConfig(rootfs)
|
config := newTemplateConfig(rootfs)
|
||||||
config.Namespaces["NEWIPC"] = false
|
config.Namespaces.Add(libcontainer.NEWIPC, "/proc/1/ns/ipcc")
|
||||||
config.IpcNsPath = "/proc/1/ns/ipcc"
|
|
||||||
|
|
||||||
_, _, err = runContainer(config, "", "true")
|
_, _, err = runContainer(config, "", "true")
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
|
140
vendor/src/github.com/docker/libcontainer/integration/execin_test.go
vendored
Normal file
140
vendor/src/github.com/docker/libcontainer/integration/execin_test.go
vendored
Normal file
|
@ -0,0 +1,140 @@
|
||||||
|
package integration
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/docker/libcontainer"
|
||||||
|
"github.com/docker/libcontainer/namespaces"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestExecIn(t *testing.T) {
|
||||||
|
if testing.Short() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
rootfs, err := newRootFs()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer remove(rootfs)
|
||||||
|
|
||||||
|
config := newTemplateConfig(rootfs)
|
||||||
|
if err := writeConfig(config); err != nil {
|
||||||
|
t.Fatalf("failed to write config %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
containerCmd, statePath, containerErr := startLongRunningContainer(config)
|
||||||
|
defer func() {
|
||||||
|
// kill the container
|
||||||
|
if containerCmd.Process != nil {
|
||||||
|
containerCmd.Process.Kill()
|
||||||
|
}
|
||||||
|
if err := <-containerErr; err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// start the exec process
|
||||||
|
state, err := libcontainer.GetState(statePath)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to get state %s", err)
|
||||||
|
}
|
||||||
|
buffers := newStdBuffers()
|
||||||
|
execErr := make(chan error, 1)
|
||||||
|
go func() {
|
||||||
|
_, err := namespaces.ExecIn(config, state, []string{"ps"},
|
||||||
|
os.Args[0], "exec", buffers.Stdin, buffers.Stdout, buffers.Stderr,
|
||||||
|
"", nil)
|
||||||
|
execErr <- err
|
||||||
|
}()
|
||||||
|
if err := <-execErr; err != nil {
|
||||||
|
t.Fatalf("exec finished with error %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
out := buffers.Stdout.String()
|
||||||
|
if !strings.Contains(out, "sleep 10") || !strings.Contains(out, "ps") {
|
||||||
|
t.Fatalf("unexpected running process, output %q", out)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestExecInRlimit(t *testing.T) {
|
||||||
|
if testing.Short() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
rootfs, err := newRootFs()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer remove(rootfs)
|
||||||
|
|
||||||
|
config := newTemplateConfig(rootfs)
|
||||||
|
if err := writeConfig(config); err != nil {
|
||||||
|
t.Fatalf("failed to write config %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
containerCmd, statePath, containerErr := startLongRunningContainer(config)
|
||||||
|
defer func() {
|
||||||
|
// kill the container
|
||||||
|
if containerCmd.Process != nil {
|
||||||
|
containerCmd.Process.Kill()
|
||||||
|
}
|
||||||
|
if err := <-containerErr; err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// start the exec process
|
||||||
|
state, err := libcontainer.GetState(statePath)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to get state %s", err)
|
||||||
|
}
|
||||||
|
buffers := newStdBuffers()
|
||||||
|
execErr := make(chan error, 1)
|
||||||
|
go func() {
|
||||||
|
_, err := namespaces.ExecIn(config, state, []string{"/bin/sh", "-c", "ulimit -n"},
|
||||||
|
os.Args[0], "exec", buffers.Stdin, buffers.Stdout, buffers.Stderr,
|
||||||
|
"", nil)
|
||||||
|
execErr <- err
|
||||||
|
}()
|
||||||
|
if err := <-execErr; err != nil {
|
||||||
|
t.Fatalf("exec finished with error %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
out := buffers.Stdout.String()
|
||||||
|
if limit := strings.TrimSpace(out); limit != "1024" {
|
||||||
|
t.Fatalf("expected rlimit to be 1024, got %s", limit)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// start a long-running container so we have time to inspect execin processes
|
||||||
|
func startLongRunningContainer(config *libcontainer.Config) (*exec.Cmd, string, chan error) {
|
||||||
|
containerErr := make(chan error, 1)
|
||||||
|
containerCmd := &exec.Cmd{}
|
||||||
|
var statePath string
|
||||||
|
|
||||||
|
createCmd := func(container *libcontainer.Config, console, dataPath, init string,
|
||||||
|
pipe *os.File, args []string) *exec.Cmd {
|
||||||
|
containerCmd = namespaces.DefaultCreateCommand(container, console, dataPath, init, pipe, args)
|
||||||
|
statePath = dataPath
|
||||||
|
return containerCmd
|
||||||
|
}
|
||||||
|
|
||||||
|
var containerStart sync.WaitGroup
|
||||||
|
containerStart.Add(1)
|
||||||
|
go func() {
|
||||||
|
buffers := newStdBuffers()
|
||||||
|
_, err := namespaces.Exec(config,
|
||||||
|
buffers.Stdin, buffers.Stdout, buffers.Stderr,
|
||||||
|
"", config.RootFs, []string{"sleep", "10"},
|
||||||
|
createCmd, containerStart.Done)
|
||||||
|
containerErr <- err
|
||||||
|
}()
|
||||||
|
containerStart.Wait()
|
||||||
|
|
||||||
|
return containerCmd, statePath, containerErr
|
||||||
|
}
|
|
@ -1,33 +1,76 @@
|
||||||
package integration
|
package integration
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
|
||||||
|
"github.com/docker/libcontainer"
|
||||||
"github.com/docker/libcontainer/namespaces"
|
"github.com/docker/libcontainer/namespaces"
|
||||||
|
_ "github.com/docker/libcontainer/namespaces/nsenter"
|
||||||
)
|
)
|
||||||
|
|
||||||
// init runs the libcontainer initialization code because of the busybox style needs
|
// init runs the libcontainer initialization code because of the busybox style needs
|
||||||
// to work around the go runtime and the issues with forking
|
// to work around the go runtime and the issues with forking
|
||||||
func init() {
|
func init() {
|
||||||
if len(os.Args) < 2 || os.Args[1] != "init" {
|
if len(os.Args) < 2 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
runtime.LockOSThread()
|
// handle init
|
||||||
|
if len(os.Args) >= 2 && os.Args[1] == "init" {
|
||||||
|
runtime.LockOSThread()
|
||||||
|
|
||||||
container, err := loadConfig()
|
container, err := loadConfig()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
rootfs, err := os.Getwd()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := namespaces.Init(container, rootfs, "", os.NewFile(3, "pipe"), os.Args[3:]); err != nil {
|
||||||
|
log.Fatalf("unable to initialize for container: %s", err)
|
||||||
|
}
|
||||||
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
rootfs, err := os.Getwd()
|
// handle execin
|
||||||
if err != nil {
|
if len(os.Args) >= 2 && os.Args[0] == "nsenter-exec" {
|
||||||
log.Fatal(err)
|
runtime.LockOSThread()
|
||||||
}
|
|
||||||
|
|
||||||
if err := namespaces.Init(container, rootfs, "", os.NewFile(3, "pipe"), os.Args[3:]); err != nil {
|
// User args are passed after '--' in the command line.
|
||||||
log.Fatalf("unable to initialize for container: %s", err)
|
userArgs := findUserArgs()
|
||||||
|
|
||||||
|
config, err := loadConfigFromFd()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("docker-exec: unable to receive config from sync pipe: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := namespaces.FinalizeSetns(config, userArgs); err != nil {
|
||||||
|
log.Fatalf("docker-exec: failed to exec: %s", err)
|
||||||
|
}
|
||||||
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
os.Exit(1)
|
}
|
||||||
|
|
||||||
|
func findUserArgs() []string {
|
||||||
|
for i, a := range os.Args {
|
||||||
|
if a == "--" {
|
||||||
|
return os.Args[i+1:]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return []string{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// loadConfigFromFd loads a container's config from the sync pipe that is provided by
|
||||||
|
// fd 3 when running a process
|
||||||
|
func loadConfigFromFd() (*libcontainer.Config, error) {
|
||||||
|
var config *libcontainer.Config
|
||||||
|
if err := json.NewDecoder(os.NewFile(3, "child")).Decode(&config); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return config, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,12 +32,12 @@ func newTemplateConfig(rootfs string) *libcontainer.Config {
|
||||||
"KILL",
|
"KILL",
|
||||||
"AUDIT_WRITE",
|
"AUDIT_WRITE",
|
||||||
},
|
},
|
||||||
Namespaces: map[string]bool{
|
Namespaces: libcontainer.Namespaces{
|
||||||
"NEWNS": true,
|
{Type: libcontainer.NEWNS},
|
||||||
"NEWUTS": true,
|
{Type: libcontainer.NEWUTS},
|
||||||
"NEWIPC": true,
|
{Type: libcontainer.NEWIPC},
|
||||||
"NEWPID": true,
|
{Type: libcontainer.NEWPID},
|
||||||
"NEWNET": true,
|
{Type: libcontainer.NEWNET},
|
||||||
},
|
},
|
||||||
Cgroups: &cgroups.Cgroup{
|
Cgroups: &cgroups.Cgroup{
|
||||||
Parent: "integration",
|
Parent: "integration",
|
||||||
|
|
|
@ -1,29 +0,0 @@
|
||||||
package ipc
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"syscall"
|
|
||||||
|
|
||||||
"github.com/docker/libcontainer/system"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Join the IPC Namespace of specified ipc path if it exists.
|
|
||||||
// If the path does not exist then you are not joining a container.
|
|
||||||
func Initialize(nsPath string) error {
|
|
||||||
if nsPath == "" {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
f, err := os.OpenFile(nsPath, os.O_RDONLY, 0)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed get IPC namespace fd: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = system.Setns(f.Fd(), syscall.CLONE_NEWIPC)
|
|
||||||
f.Close()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to setns current IPC namespace: %v", err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
|
@ -97,6 +97,10 @@ func FinalizeSetns(container *libcontainer.Config, args []string) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := setupRlimits(container); err != nil {
|
||||||
|
return fmt.Errorf("setup rlimits %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
if err := FinalizeNamespace(container); err != nil {
|
if err := FinalizeNamespace(container); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,6 @@ import (
|
||||||
"github.com/docker/libcontainer"
|
"github.com/docker/libcontainer"
|
||||||
"github.com/docker/libcontainer/apparmor"
|
"github.com/docker/libcontainer/apparmor"
|
||||||
"github.com/docker/libcontainer/console"
|
"github.com/docker/libcontainer/console"
|
||||||
"github.com/docker/libcontainer/ipc"
|
|
||||||
"github.com/docker/libcontainer/label"
|
"github.com/docker/libcontainer/label"
|
||||||
"github.com/docker/libcontainer/mount"
|
"github.com/docker/libcontainer/mount"
|
||||||
"github.com/docker/libcontainer/netlink"
|
"github.com/docker/libcontainer/netlink"
|
||||||
|
@ -65,7 +64,10 @@ func Init(container *libcontainer.Config, uncleanRootfs, consolePath string, pip
|
||||||
if err := json.NewDecoder(pipe).Decode(&networkState); err != nil {
|
if err := json.NewDecoder(pipe).Decode(&networkState); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
// join any namespaces via a path to the namespace fd if provided
|
||||||
|
if err := joinExistingNamespaces(container.Namespaces); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
if consolePath != "" {
|
if consolePath != "" {
|
||||||
if err := console.OpenAndDup(consolePath); err != nil {
|
if err := console.OpenAndDup(consolePath); err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -79,9 +81,7 @@ func Init(container *libcontainer.Config, uncleanRootfs, consolePath string, pip
|
||||||
return fmt.Errorf("setctty %s", err)
|
return fmt.Errorf("setctty %s", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err := ipc.Initialize(container.IpcNsPath); err != nil {
|
|
||||||
return fmt.Errorf("setup IPC %s", err)
|
|
||||||
}
|
|
||||||
if err := setupNetwork(container, networkState); err != nil {
|
if err := setupNetwork(container, networkState); err != nil {
|
||||||
return fmt.Errorf("setup networking %s", err)
|
return fmt.Errorf("setup networking %s", err)
|
||||||
}
|
}
|
||||||
|
@ -178,17 +178,17 @@ func SetupUser(u string) error {
|
||||||
Home: "/",
|
Home: "/",
|
||||||
}
|
}
|
||||||
|
|
||||||
passwdFile, err := user.GetPasswdFile()
|
passwdPath, err := user.GetPasswdPath()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
groupFile, err := user.GetGroupFile()
|
groupPath, err := user.GetGroupPath()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
execUser, err := user.GetExecUserFile(u, &defaultExecUser, passwdFile, groupFile)
|
execUser, err := user.GetExecUserPath(u, &defaultExecUser, passwdPath, groupPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("get supplementary groups %s", err)
|
return fmt.Errorf("get supplementary groups %s", err)
|
||||||
}
|
}
|
||||||
|
@ -308,3 +308,22 @@ func LoadContainerEnvironment(container *libcontainer.Config) error {
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// joinExistingNamespaces gets all the namespace paths specified for the container and
|
||||||
|
// does a setns on the namespace fd so that the current process joins the namespace.
|
||||||
|
func joinExistingNamespaces(namespaces []libcontainer.Namespace) error {
|
||||||
|
for _, ns := range namespaces {
|
||||||
|
if ns.Path != "" {
|
||||||
|
f, err := os.OpenFile(ns.Path, os.O_RDONLY, 0)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = system.Setns(f.Fd(), uintptr(namespaceInfo[ns.Type]))
|
||||||
|
f.Close()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
@ -15,6 +15,10 @@
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <getopt.h>
|
#include <getopt.h>
|
||||||
|
|
||||||
|
#ifndef PR_SET_CHILD_SUBREAPER
|
||||||
|
#define PR_SET_CHILD_SUBREAPER 36
|
||||||
|
#endif
|
||||||
|
|
||||||
static const kBufSize = 256;
|
static const kBufSize = 256;
|
||||||
static const char *kNsEnter = "nsenter";
|
static const char *kNsEnter = "nsenter";
|
||||||
|
|
||||||
|
@ -32,8 +36,8 @@ void get_args(int *argc, char ***argv)
|
||||||
contents_size += kBufSize;
|
contents_size += kBufSize;
|
||||||
contents = (char *)realloc(contents, contents_size);
|
contents = (char *)realloc(contents, contents_size);
|
||||||
bytes_read =
|
bytes_read =
|
||||||
read(fd, contents + contents_offset,
|
read(fd, contents + contents_offset,
|
||||||
contents_size - contents_offset);
|
contents_size - contents_offset);
|
||||||
contents_offset += bytes_read;
|
contents_offset += bytes_read;
|
||||||
}
|
}
|
||||||
while (bytes_read > 0);
|
while (bytes_read > 0);
|
||||||
|
@ -90,9 +94,10 @@ void nsenter()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (prctl(PR_SET_CHILD_SUBREAPER, 1, 0, 0, 0) == -1) {
|
if (prctl(PR_SET_CHILD_SUBREAPER, 1, 0, 0, 0) == -1) {
|
||||||
fprintf(stderr, "nsenter: failed to set child subreaper: %s", strerror(errno));
|
fprintf(stderr, "nsenter: failed to set child subreaper: %s",
|
||||||
exit(1);
|
strerror(errno));
|
||||||
}
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
static const struct option longopts[] = {
|
static const struct option longopts[] = {
|
||||||
{"nspid", required_argument, NULL, 'n'},
|
{"nspid", required_argument, NULL, 'n'},
|
||||||
|
|
|
@ -1,50 +0,0 @@
|
||||||
package namespaces
|
|
||||||
|
|
||||||
import "errors"
|
|
||||||
|
|
||||||
type (
|
|
||||||
Namespace struct {
|
|
||||||
Key string `json:"key,omitempty"`
|
|
||||||
Value int `json:"value,omitempty"`
|
|
||||||
File string `json:"file,omitempty"`
|
|
||||||
}
|
|
||||||
Namespaces []*Namespace
|
|
||||||
)
|
|
||||||
|
|
||||||
// namespaceList is used to convert the libcontainer types
|
|
||||||
// into the names of the files located in /proc/<pid>/ns/* for
|
|
||||||
// each namespace
|
|
||||||
var (
|
|
||||||
namespaceList = Namespaces{}
|
|
||||||
ErrUnkownNamespace = errors.New("Unknown namespace")
|
|
||||||
ErrUnsupported = errors.New("Unsupported method")
|
|
||||||
)
|
|
||||||
|
|
||||||
func (ns *Namespace) String() string {
|
|
||||||
return ns.Key
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetNamespace(key string) *Namespace {
|
|
||||||
for _, ns := range namespaceList {
|
|
||||||
if ns.Key == key {
|
|
||||||
cpy := *ns
|
|
||||||
return &cpy
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Contains returns true if the specified Namespace is
|
|
||||||
// in the slice
|
|
||||||
func (n Namespaces) Contains(ns string) bool {
|
|
||||||
return n.Get(ns) != nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n Namespaces) Get(ns string) *Namespace {
|
|
||||||
for _, nsp := range n {
|
|
||||||
if nsp != nil && nsp.Key == ns {
|
|
||||||
return nsp
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
|
@ -1,16 +0,0 @@
|
||||||
package namespaces
|
|
||||||
|
|
||||||
import (
|
|
||||||
"syscall"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
namespaceList = Namespaces{
|
|
||||||
{Key: "NEWNS", Value: syscall.CLONE_NEWNS, File: "mnt"},
|
|
||||||
{Key: "NEWUTS", Value: syscall.CLONE_NEWUTS, File: "uts"},
|
|
||||||
{Key: "NEWIPC", Value: syscall.CLONE_NEWIPC, File: "ipc"},
|
|
||||||
{Key: "NEWUSER", Value: syscall.CLONE_NEWUSER, File: "user"},
|
|
||||||
{Key: "NEWPID", Value: syscall.CLONE_NEWPID, File: "pid"},
|
|
||||||
{Key: "NEWNET", Value: syscall.CLONE_NEWNET, File: "net"},
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,30 +0,0 @@
|
||||||
package namespaces
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestNamespacesContains(t *testing.T) {
|
|
||||||
ns := Namespaces{
|
|
||||||
GetNamespace("NEWPID"),
|
|
||||||
GetNamespace("NEWNS"),
|
|
||||||
GetNamespace("NEWUTS"),
|
|
||||||
}
|
|
||||||
|
|
||||||
if ns.Contains("NEWNET") {
|
|
||||||
t.Fatal("namespaces should not contain NEWNET")
|
|
||||||
}
|
|
||||||
|
|
||||||
if !ns.Contains("NEWPID") {
|
|
||||||
t.Fatal("namespaces should contain NEWPID but does not")
|
|
||||||
}
|
|
||||||
|
|
||||||
withNil := Namespaces{
|
|
||||||
GetNamespace("UNDEFINED"), // this element will be nil
|
|
||||||
GetNamespace("NEWPID"),
|
|
||||||
}
|
|
||||||
|
|
||||||
if !withNil.Contains("NEWPID") {
|
|
||||||
t.Fatal("namespaces should contain NEWPID but does not")
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -5,6 +5,8 @@ package namespaces
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
|
"github.com/docker/libcontainer"
|
||||||
)
|
)
|
||||||
|
|
||||||
type initError struct {
|
type initError struct {
|
||||||
|
@ -15,6 +17,15 @@ func (i initError) Error() string {
|
||||||
return i.Message
|
return i.Message
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var namespaceInfo = map[libcontainer.NamespaceType]int{
|
||||||
|
libcontainer.NEWNET: syscall.CLONE_NEWNET,
|
||||||
|
libcontainer.NEWNS: syscall.CLONE_NEWNS,
|
||||||
|
libcontainer.NEWUSER: syscall.CLONE_NEWUSER,
|
||||||
|
libcontainer.NEWIPC: syscall.CLONE_NEWIPC,
|
||||||
|
libcontainer.NEWUTS: syscall.CLONE_NEWUTS,
|
||||||
|
libcontainer.NEWPID: syscall.CLONE_NEWPID,
|
||||||
|
}
|
||||||
|
|
||||||
// New returns a newly initialized Pipe for communication between processes
|
// New returns a newly initialized Pipe for communication between processes
|
||||||
func newInitPipe() (parent *os.File, child *os.File, err error) {
|
func newInitPipe() (parent *os.File, child *os.File, err error) {
|
||||||
fds, err := syscall.Socketpair(syscall.AF_LOCAL, syscall.SOCK_STREAM|syscall.SOCK_CLOEXEC, 0)
|
fds, err := syscall.Socketpair(syscall.AF_LOCAL, syscall.SOCK_STREAM|syscall.SOCK_CLOEXEC, 0)
|
||||||
|
@ -26,13 +37,9 @@ func newInitPipe() (parent *os.File, child *os.File, err error) {
|
||||||
|
|
||||||
// GetNamespaceFlags parses the container's Namespaces options to set the correct
|
// GetNamespaceFlags parses the container's Namespaces options to set the correct
|
||||||
// flags on clone, unshare, and setns
|
// flags on clone, unshare, and setns
|
||||||
func GetNamespaceFlags(namespaces map[string]bool) (flag int) {
|
func GetNamespaceFlags(namespaces libcontainer.Namespaces) (flag int) {
|
||||||
for key, enabled := range namespaces {
|
for _, v := range namespaces {
|
||||||
if enabled {
|
flag |= namespaceInfo[v.Type]
|
||||||
if ns := GetNamespace(key); ns != nil {
|
|
||||||
flag |= ns.Value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return flag
|
return flag
|
||||||
}
|
}
|
||||||
|
|
|
@ -522,11 +522,10 @@ func NetworkSetMacAddress(iface *net.Interface, macaddr string) error {
|
||||||
|
|
||||||
var (
|
var (
|
||||||
MULTICAST byte = 0x1
|
MULTICAST byte = 0x1
|
||||||
LOCALOUI byte = 0x2
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if hwaddr[0]&0x1 == MULTICAST || hwaddr[0]&0x2 != LOCALOUI {
|
if hwaddr[0]&0x1 == MULTICAST {
|
||||||
return fmt.Errorf("Incorrect Local MAC Address specified: %s", macaddr)
|
return fmt.Errorf("Multicast MAC Address is not supported: %s", macaddr)
|
||||||
}
|
}
|
||||||
|
|
||||||
wb := newNetlinkRequest(syscall.RTM_SETLINK, syscall.NLM_F_ACK)
|
wb := newNetlinkRequest(syscall.RTM_SETLINK, syscall.NLM_F_ACK)
|
||||||
|
|
|
@ -1,39 +0,0 @@
|
||||||
// +build linux
|
|
||||||
|
|
||||||
package network
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"syscall"
|
|
||||||
|
|
||||||
"github.com/docker/libcontainer/system"
|
|
||||||
)
|
|
||||||
|
|
||||||
// crosbymichael: could make a network strategy that instead of returning veth pair names it returns a pid to an existing network namespace
|
|
||||||
type NetNS struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *NetNS) Create(n *Network, nspid int, networkState *NetworkState) error {
|
|
||||||
networkState.NsPath = n.NsPath
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *NetNS) Initialize(config *Network, networkState *NetworkState) error {
|
|
||||||
if networkState.NsPath == "" {
|
|
||||||
return fmt.Errorf("nspath does is not specified in NetworkState")
|
|
||||||
}
|
|
||||||
|
|
||||||
f, err := os.OpenFile(networkState.NsPath, os.O_RDONLY, 0)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed get network namespace fd: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := system.Setns(f.Fd(), syscall.CLONE_NEWNET); err != nil {
|
|
||||||
f.Close()
|
|
||||||
return fmt.Errorf("failed to setns current network namespace: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
f.Close()
|
|
||||||
return nil
|
|
||||||
}
|
|
|
@ -88,6 +88,18 @@ func SetInterfaceIp(name string, rawIp string) error {
|
||||||
return netlink.NetworkLinkAddIp(iface, ip, ipNet)
|
return netlink.NetworkLinkAddIp(iface, ip, ipNet)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func DeleteInterfaceIp(name string, rawIp string) error {
|
||||||
|
iface, err := net.InterfaceByName(name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
ip, ipNet, err := net.ParseCIDR(rawIp)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return netlink.NetworkLinkDelIp(iface, ip, ipNet)
|
||||||
|
}
|
||||||
|
|
||||||
func SetMtu(name string, mtu int) error {
|
func SetMtu(name string, mtu int) error {
|
||||||
iface, err := net.InterfaceByName(name)
|
iface, err := net.InterfaceByName(name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -13,7 +13,6 @@ var (
|
||||||
var strategies = map[string]NetworkStrategy{
|
var strategies = map[string]NetworkStrategy{
|
||||||
"veth": &Veth{},
|
"veth": &Veth{},
|
||||||
"loopback": &Loopback{},
|
"loopback": &Loopback{},
|
||||||
"netns": &NetNS{},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NetworkStrategy represents a specific network configuration for
|
// NetworkStrategy represents a specific network configuration for
|
||||||
|
|
|
@ -8,9 +8,6 @@ type Network struct {
|
||||||
// Type sets the networks type, commonly veth and loopback
|
// Type sets the networks type, commonly veth and loopback
|
||||||
Type string `json:"type,omitempty"`
|
Type string `json:"type,omitempty"`
|
||||||
|
|
||||||
// Path to network namespace
|
|
||||||
NsPath string `json:"ns_path,omitempty"`
|
|
||||||
|
|
||||||
// The bridge to use.
|
// The bridge to use.
|
||||||
Bridge string `json:"bridge,omitempty"`
|
Bridge string `json:"bridge,omitempty"`
|
||||||
|
|
||||||
|
@ -50,6 +47,4 @@ type NetworkState struct {
|
||||||
VethHost string `json:"veth_host,omitempty"`
|
VethHost string `json:"veth_host,omitempty"`
|
||||||
// The name of the veth interface created inside the container for the child.
|
// The name of the veth interface created inside the container for the child.
|
||||||
VethChild string `json:"veth_child,omitempty"`
|
VethChild string `json:"veth_child,omitempty"`
|
||||||
// Net namespace path.
|
|
||||||
NsPath string `json:"ns_path,omitempty"`
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,33 +1,29 @@
|
||||||
// +build linux
|
// +build linux
|
||||||
|
|
||||||
package fs
|
package libcontainer
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"github.com/docker/libcontainer/cgroups"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// NotifyOnOOM sends signals on the returned channel when the cgroup reaches
|
const oomCgroupName = "memory"
|
||||||
// its memory limit. The channel is closed when the cgroup is removed.
|
|
||||||
func NotifyOnOOM(c *cgroups.Cgroup) (<-chan struct{}, error) {
|
// NotifyOnOOM returns channel on which you can expect event about OOM,
|
||||||
d, err := getCgroupData(c, 0)
|
// if process died without OOM this channel will be closed.
|
||||||
|
// s is current *libcontainer.State for container.
|
||||||
|
func NotifyOnOOM(s *State) (<-chan struct{}, error) {
|
||||||
|
dir := s.CgroupPaths[oomCgroupName]
|
||||||
|
if dir == "" {
|
||||||
|
return nil, fmt.Errorf("There is no path for %q in state", oomCgroupName)
|
||||||
|
}
|
||||||
|
oomControl, err := os.Open(filepath.Join(dir, "memory.oom_control"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return notifyOnOOM(d)
|
|
||||||
}
|
|
||||||
|
|
||||||
func notifyOnOOM(d *data) (<-chan struct{}, error) {
|
|
||||||
dir, err := d.path("memory")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
fd, _, syserr := syscall.RawSyscall(syscall.SYS_EVENTFD2, 0, syscall.FD_CLOEXEC, 0)
|
fd, _, syserr := syscall.RawSyscall(syscall.SYS_EVENTFD2, 0, syscall.FD_CLOEXEC, 0)
|
||||||
if syserr != 0 {
|
if syserr != 0 {
|
||||||
return nil, syserr
|
return nil, syserr
|
||||||
|
@ -35,48 +31,32 @@ func notifyOnOOM(d *data) (<-chan struct{}, error) {
|
||||||
|
|
||||||
eventfd := os.NewFile(fd, "eventfd")
|
eventfd := os.NewFile(fd, "eventfd")
|
||||||
|
|
||||||
oomControl, err := os.Open(filepath.Join(dir, "memory.oom_control"))
|
eventControlPath := filepath.Join(dir, "cgroup.event_control")
|
||||||
if err != nil {
|
data := fmt.Sprintf("%d %d", eventfd.Fd(), oomControl.Fd())
|
||||||
eventfd.Close()
|
if err := ioutil.WriteFile(eventControlPath, []byte(data), 0700); err != nil {
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
eventControlPath = filepath.Join(dir, "cgroup.event_control")
|
|
||||||
data = fmt.Sprintf("%d %d", eventfd.Fd(), oomControl.Fd())
|
|
||||||
)
|
|
||||||
|
|
||||||
if err := writeFile(dir, "cgroup.event_control", data); err != nil {
|
|
||||||
eventfd.Close()
|
eventfd.Close()
|
||||||
oomControl.Close()
|
oomControl.Close()
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
ch := make(chan struct{})
|
ch := make(chan struct{})
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
defer func() {
|
defer func() {
|
||||||
close(ch)
|
close(ch)
|
||||||
eventfd.Close()
|
eventfd.Close()
|
||||||
oomControl.Close()
|
oomControl.Close()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
buf := make([]byte, 8)
|
buf := make([]byte, 8)
|
||||||
|
|
||||||
for {
|
for {
|
||||||
if _, err := eventfd.Read(buf); err != nil {
|
if _, err := eventfd.Read(buf); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// When a cgroup is destroyed, an event is sent to eventfd.
|
// When a cgroup is destroyed, an event is sent to eventfd.
|
||||||
// So if the control path is gone, return instead of notifying.
|
// So if the control path is gone, return instead of notifying.
|
||||||
if _, err := os.Lstat(eventControlPath); os.IsNotExist(err) {
|
if _, err := os.Lstat(eventControlPath); os.IsNotExist(err) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
ch <- struct{}{}
|
ch <- struct{}{}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
return ch, nil
|
return ch, nil
|
||||||
}
|
}
|
|
@ -1,38 +1,48 @@
|
||||||
// +build linux
|
// +build linux
|
||||||
|
|
||||||
package fs
|
package libcontainer
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"syscall"
|
"syscall"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestNotifyOnOOM(t *testing.T) {
|
func TestNotifyOnOOM(t *testing.T) {
|
||||||
helper := NewCgroupTestUtil("memory", t)
|
memoryPath, err := ioutil.TempDir("", "testnotifyoom-")
|
||||||
defer helper.cleanup()
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
helper.writeFileContents(map[string]string{
|
}
|
||||||
"memory.oom_control": "",
|
oomPath := filepath.Join(memoryPath, "memory.oom_control")
|
||||||
"cgroup.event_control": "",
|
eventPath := filepath.Join(memoryPath, "cgroup.event_control")
|
||||||
})
|
if err := ioutil.WriteFile(oomPath, []byte{}, 0700); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := ioutil.WriteFile(eventPath, []byte{}, 0700); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
var eventFd, oomControlFd int
|
var eventFd, oomControlFd int
|
||||||
|
st := &State{
|
||||||
ooms, err := notifyOnOOM(helper.CgroupData)
|
CgroupPaths: map[string]string{
|
||||||
|
"memory": memoryPath,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
ooms, err := NotifyOnOOM(st)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("expected no error, got:", err)
|
t.Fatal("expected no error, got:", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
memoryPath, _ := helper.CgroupData.path("memory")
|
data, err := ioutil.ReadFile(eventPath)
|
||||||
data, err := readFile(memoryPath, "cgroup.event_control")
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("couldn't read event control file:", err)
|
t.Fatal("couldn't read event control file:", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := fmt.Sscanf(data, "%d %d", &eventFd, &oomControlFd); err != nil {
|
if _, err := fmt.Sscanf(string(data), "%d %d", &eventFd, &oomControlFd); err != nil {
|
||||||
t.Fatalf("invalid control data %q: %s", data, err)
|
t.Fatalf("invalid control data %q: %s", data, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -62,7 +72,9 @@ func TestNotifyOnOOM(t *testing.T) {
|
||||||
|
|
||||||
// simulate what happens when a cgroup is destroyed by cleaning up and then
|
// simulate what happens when a cgroup is destroyed by cleaning up and then
|
||||||
// writing to the eventfd.
|
// writing to the eventfd.
|
||||||
helper.cleanup()
|
if err := os.RemoveAll(memoryPath); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
if _, err := syscall.Write(efd, buf); err != nil {
|
if _, err := syscall.Write(efd, buf); err != nil {
|
||||||
t.Fatal("unable to write to eventfd:", err)
|
t.Fatal("unable to write to eventfd:", err)
|
||||||
}
|
}
|
|
@ -176,13 +176,13 @@
|
||||||
"TERM=xterm"
|
"TERM=xterm"
|
||||||
],
|
],
|
||||||
"hostname": "koye",
|
"hostname": "koye",
|
||||||
"namespaces": {
|
"namespaces": [
|
||||||
"NEWIPC": true,
|
{"type":"NEWIPC"},
|
||||||
"NEWNET": true,
|
{"type": "NEWNET"},
|
||||||
"NEWNS": true,
|
{"type": "NEWNS"},
|
||||||
"NEWPID": true,
|
{"type": "NEWPID"},
|
||||||
"NEWUTS": true
|
{"type": "NEWUTS"}
|
||||||
},
|
],
|
||||||
"networks": [
|
"networks": [
|
||||||
{
|
{
|
||||||
"address": "127.0.0.1/0",
|
"address": "127.0.0.1/0",
|
||||||
|
|
|
@ -175,13 +175,13 @@
|
||||||
"TERM=xterm"
|
"TERM=xterm"
|
||||||
],
|
],
|
||||||
"hostname": "koye",
|
"hostname": "koye",
|
||||||
"namespaces": {
|
"namespaces": [
|
||||||
"NEWIPC": true,
|
{"type": "NEWIPC"},
|
||||||
"NEWNET": true,
|
{"type": "NEWNET"},
|
||||||
"NEWNS": true,
|
{"type": "NEWNS"},
|
||||||
"NEWPID": true,
|
{"type": "NEWPID"},
|
||||||
"NEWUTS": true
|
{"type": "NEWUTS"}
|
||||||
},
|
],
|
||||||
"networks": [
|
"networks": [
|
||||||
{
|
{
|
||||||
"address": "127.0.0.1/0",
|
"address": "127.0.0.1/0",
|
||||||
|
|
|
@ -181,13 +181,13 @@
|
||||||
"TERM=xterm"
|
"TERM=xterm"
|
||||||
],
|
],
|
||||||
"hostname": "koye",
|
"hostname": "koye",
|
||||||
"namespaces": {
|
"namespaces": [
|
||||||
"NEWIPC": true,
|
{"type": "NEWIPC"},
|
||||||
"NEWNET": true,
|
{"type": "NEWNET"},
|
||||||
"NEWNS": true,
|
{"type": "NEWNS"},
|
||||||
"NEWPID": true,
|
{"type": "NEWPID"},
|
||||||
"NEWUTS": true
|
{"type": "NEWUTS"}
|
||||||
},
|
],
|
||||||
"networks": [
|
"networks": [
|
||||||
{
|
{
|
||||||
"address": "127.0.0.1/0",
|
"address": "127.0.0.1/0",
|
||||||
|
|
|
@ -175,13 +175,13 @@
|
||||||
"TERM=xterm"
|
"TERM=xterm"
|
||||||
],
|
],
|
||||||
"hostname": "koye",
|
"hostname": "koye",
|
||||||
"namespaces": {
|
"namespaces": [
|
||||||
"NEWIPC": true,
|
{"type": "NEWIPC"},
|
||||||
"NEWNET": true,
|
{"type": "NEWNET"},
|
||||||
"NEWNS": true,
|
{"type": "NEWNS"},
|
||||||
"NEWPID": true,
|
{"type": "NEWPID"},
|
||||||
"NEWUTS": true
|
{"type": "NEWUTS"}
|
||||||
},
|
],
|
||||||
"networks": [
|
"networks": [
|
||||||
{
|
{
|
||||||
"address": "127.0.0.1/0",
|
"address": "127.0.0.1/0",
|
||||||
|
|
|
@ -177,13 +177,13 @@
|
||||||
"TERM=xterm"
|
"TERM=xterm"
|
||||||
],
|
],
|
||||||
"hostname": "koye",
|
"hostname": "koye",
|
||||||
"namespaces": {
|
"namespaces": [
|
||||||
"NEWIPC": true,
|
{"type": "NEWIPC"},
|
||||||
"NEWNET": true,
|
{"type": "NEWNET"},
|
||||||
"NEWNS": true,
|
{"type": "NEWNS"},
|
||||||
"NEWPID": true,
|
{"type": "NEWPID"},
|
||||||
"NEWUTS": true
|
{"type": "NEWUTS"}
|
||||||
},
|
],
|
||||||
"networks": [
|
"networks": [
|
||||||
{
|
{
|
||||||
"address": "127.0.0.1/0",
|
"address": "127.0.0.1/0",
|
||||||
|
|
|
@ -1 +1,2 @@
|
||||||
Tianon Gravi <admwiggin@gmail.com> (@tianon)
|
Tianon Gravi <admwiggin@gmail.com> (@tianon)
|
||||||
|
Aleksa Sarai <cyphar@cyphar.com> (@cyphar)
|
||||||
|
|
|
@ -9,22 +9,22 @@ import (
|
||||||
|
|
||||||
// Unix-specific path to the passwd and group formatted files.
|
// Unix-specific path to the passwd and group formatted files.
|
||||||
const (
|
const (
|
||||||
unixPasswdFile = "/etc/passwd"
|
unixPasswdPath = "/etc/passwd"
|
||||||
unixGroupFile = "/etc/group"
|
unixGroupPath = "/etc/group"
|
||||||
)
|
)
|
||||||
|
|
||||||
func GetPasswdFile() (string, error) {
|
func GetPasswdPath() (string, error) {
|
||||||
return unixPasswdFile, nil
|
return unixPasswdPath, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetPasswd() (io.ReadCloser, error) {
|
func GetPasswd() (io.ReadCloser, error) {
|
||||||
return os.Open(unixPasswdFile)
|
return os.Open(unixPasswdPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetGroupFile() (string, error) {
|
func GetGroupPath() (string, error) {
|
||||||
return unixGroupFile, nil
|
return unixGroupPath, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetGroup() (io.ReadCloser, error) {
|
func GetGroup() (io.ReadCloser, error) {
|
||||||
return os.Open(unixGroupFile)
|
return os.Open(unixGroupPath)
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@ package user
|
||||||
|
|
||||||
import "io"
|
import "io"
|
||||||
|
|
||||||
func GetPasswdFile() (string, error) {
|
func GetPasswdPath() (string, error) {
|
||||||
return "", ErrUnsupported
|
return "", ErrUnsupported
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -12,7 +12,7 @@ func GetPasswd() (io.ReadCloser, error) {
|
||||||
return nil, ErrUnsupported
|
return nil, ErrUnsupported
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetGroupFile() (string, error) {
|
func GetGroupPath() (string, error) {
|
||||||
return "", ErrUnsupported
|
return "", ErrUnsupported
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -197,11 +197,11 @@ type ExecUser struct {
|
||||||
Home string
|
Home string
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetExecUserFile is a wrapper for GetExecUser. It reads data from each of the
|
// GetExecUserPath is a wrapper for GetExecUser. It reads data from each of the
|
||||||
// given file paths and uses that data as the arguments to GetExecUser. If the
|
// given file paths and uses that data as the arguments to GetExecUser. If the
|
||||||
// files cannot be opened for any reason, the error is ignored and a nil
|
// files cannot be opened for any reason, the error is ignored and a nil
|
||||||
// io.Reader is passed instead.
|
// io.Reader is passed instead.
|
||||||
func GetExecUserFile(userSpec string, defaults *ExecUser, passwdPath, groupPath string) (*ExecUser, error) {
|
func GetExecUserPath(userSpec string, defaults *ExecUser, passwdPath, groupPath string) (*ExecUser, error) {
|
||||||
passwd, err := os.Open(passwdPath)
|
passwd, err := os.Open(passwdPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
passwd = nil
|
passwd = nil
|
||||||
|
|
Loading…
Add table
Reference in a new issue