Bump runc to e87436998478d222be209707503c27f6f91be
Fixes for cgroup memory updates and process labeling. Signed-off-by: Michael Crosby <crosbymichael@gmail.com>
This commit is contained in:
		
							parent
							
								
									c0295fac04
								
							
						
					
					
						commit
						d7fb9f0538
					
				
					 22 changed files with 260 additions and 118 deletions
				
			
		|  | @ -14,7 +14,7 @@ clone git github.com/docker/go-units 5d2041e26a699eaca682e2ea41c8f891e1060444 | |||
| clone git github.com/godbus/dbus e2cf28118e66a6a63db46cf6088a35d2054d3bb0 | ||||
| clone git github.com/golang/glog 23def4e6c14b4da8ac2ed8007337bc5eb5007998 | ||||
| clone git github.com/golang/protobuf 8d92cf5fc15a4382f8964b08e1f42a75c0591aa3 | ||||
| clone git github.com/opencontainers/runc 5f182ce7380f41b8c60a2ecaec14996d7e9cfd4a | ||||
| clone git github.com/opencontainers/runc e87436998478d222be209707503c27f6f91be0c5 | ||||
| clone git github.com/opencontainers/specs 3ce138b1934bf227a418e241ead496c383eaba1c | ||||
| clone git github.com/rcrowley/go-metrics eeba7bd0dd01ace6e690fa833b3f22aaec29af43 | ||||
| clone git github.com/satori/go.uuid f9ab0dce87d815821e221626b772e3475a0d2749 | ||||
|  |  | |||
|  | @ -133,15 +133,15 @@ config := &configs.Config{ | |||
| 	UidMappings: []configs.IDMap{ | ||||
| 		{ | ||||
| 			ContainerID: 0, | ||||
| 			Host: 1000, | ||||
| 			size: 65536, | ||||
| 			HostID: 1000, | ||||
| 			Size: 65536, | ||||
| 		}, | ||||
| 	}, | ||||
| 	GidMappings: []configs.IDMap{ | ||||
| 		{ | ||||
| 			ContainerID: 0, | ||||
| 			Host: 1000, | ||||
| 			size: 65536, | ||||
| 			HostID: 1000, | ||||
| 			Size: 65536, | ||||
| 		}, | ||||
| 	}, | ||||
| 	Networks: []*configs.Network{ | ||||
|  | @ -216,6 +216,9 @@ container.Pause() | |||
| 
 | ||||
| // resume all paused processes. | ||||
| container.Resume() | ||||
| 
 | ||||
| // send signal to container's init process. | ||||
| container.Signal(signal) | ||||
| ``` | ||||
| 
 | ||||
| 
 | ||||
|  |  | |||
|  | @ -9,7 +9,7 @@ import ( | |||
| ) | ||||
| 
 | ||||
| type Manager interface { | ||||
| 	// Apply cgroup configuration to the process with the specified pid | ||||
| 	// Applies cgroup configuration to the process with the specified pid | ||||
| 	Apply(pid int) error | ||||
| 
 | ||||
| 	// Returns the PIDs inside the cgroup set | ||||
|  |  | |||
|  | @ -65,22 +65,59 @@ func (s *MemoryGroup) SetKernelMemory(path string, cgroup *configs.Cgroup) error | |||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| func (s *MemoryGroup) Set(path string, cgroup *configs.Cgroup) error { | ||||
| 	if cgroup.Resources.Memory != 0 { | ||||
| 		if err := writeFile(path, "memory.limit_in_bytes", strconv.FormatInt(cgroup.Resources.Memory, 10)); err != nil { | ||||
| func setMemoryAndSwap(path string, cgroup *configs.Cgroup) error { | ||||
| 	// When memory and swap memory are both set, we need to handle the cases | ||||
| 	// for updating container. | ||||
| 	if cgroup.Resources.Memory != 0 && cgroup.Resources.MemorySwap > 0 { | ||||
| 		memoryUsage, err := getMemoryData(path, "") | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 
 | ||||
| 		// When update memory limit, we should adapt the write sequence | ||||
| 		// for memory and swap memory, so it won't fail because the new | ||||
| 		// value and the old value don't fit kernel's validation. | ||||
| 		if memoryUsage.Limit < uint64(cgroup.Resources.MemorySwap) { | ||||
| 			if err := writeFile(path, "memory.memsw.limit_in_bytes", strconv.FormatInt(cgroup.Resources.MemorySwap, 10)); err != nil { | ||||
| 				return err | ||||
| 			} | ||||
| 			if err := writeFile(path, "memory.limit_in_bytes", strconv.FormatInt(cgroup.Resources.Memory, 10)); err != nil { | ||||
| 				return err | ||||
| 			} | ||||
| 		} else { | ||||
| 			if err := writeFile(path, "memory.limit_in_bytes", strconv.FormatInt(cgroup.Resources.Memory, 10)); err != nil { | ||||
| 				return err | ||||
| 			} | ||||
| 			if err := writeFile(path, "memory.memsw.limit_in_bytes", strconv.FormatInt(cgroup.Resources.MemorySwap, 10)); err != nil { | ||||
| 				return err | ||||
| 			} | ||||
| 		} | ||||
| 	} else { | ||||
| 		if cgroup.Resources.Memory != 0 { | ||||
| 			if err := writeFile(path, "memory.limit_in_bytes", strconv.FormatInt(cgroup.Resources.Memory, 10)); err != nil { | ||||
| 				return err | ||||
| 			} | ||||
| 		} | ||||
| 		if cgroup.Resources.MemorySwap > 0 { | ||||
| 			if err := writeFile(path, "memory.memsw.limit_in_bytes", strconv.FormatInt(cgroup.Resources.MemorySwap, 10)); err != nil { | ||||
| 				return err | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| func (s *MemoryGroup) Set(path string, cgroup *configs.Cgroup) error { | ||||
| 	if err := setMemoryAndSwap(path, cgroup); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 
 | ||||
| 	if cgroup.Resources.MemoryReservation != 0 { | ||||
| 		if err := writeFile(path, "memory.soft_limit_in_bytes", strconv.FormatInt(cgroup.Resources.MemoryReservation, 10)); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 	} | ||||
| 	if cgroup.Resources.MemorySwap > 0 { | ||||
| 		if err := writeFile(path, "memory.memsw.limit_in_bytes", strconv.FormatInt(cgroup.Resources.MemorySwap, 10)); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 	} | ||||
| 	if cgroup.Resources.KernelMemoryTCP != 0 { | ||||
| 		if err := writeFile(path, "memory.kmem.tcp.limit_in_bytes", strconv.FormatInt(cgroup.Resources.KernelMemoryTCP, 10)); err != nil { | ||||
| 			return err | ||||
|  |  | |||
|  | @ -326,7 +326,7 @@ func RemovePaths(paths map[string]string) (err error) { | |||
| 			return nil | ||||
| 		} | ||||
| 	} | ||||
| 	return fmt.Errorf("Failed to remove paths: %s", paths) | ||||
| 	return fmt.Errorf("Failed to remove paths: %v", paths) | ||||
| } | ||||
| 
 | ||||
| func GetHugePageSize() ([]string, error) { | ||||
|  |  | |||
|  | @ -3,7 +3,9 @@ package configs | |||
| import ( | ||||
| 	"bytes" | ||||
| 	"encoding/json" | ||||
| 	"fmt" | ||||
| 	"os/exec" | ||||
| 	"time" | ||||
| 
 | ||||
| 	"github.com/Sirupsen/logrus" | ||||
| ) | ||||
|  | @ -247,10 +249,11 @@ func (hooks Hooks) MarshalJSON() ([]byte, error) { | |||
| 
 | ||||
| // HookState is the payload provided to a hook on execution. | ||||
| type HookState struct { | ||||
| 	Version string `json:"version"` | ||||
| 	ID      string `json:"id"` | ||||
| 	Pid     int    `json:"pid"` | ||||
| 	Root    string `json:"root"` | ||||
| 	Version    string `json:"ociVersion"` | ||||
| 	ID         string `json:"id"` | ||||
| 	Pid        int    `json:"pid"` | ||||
| 	Root       string `json:"root"` | ||||
| 	BundlePath string `json:"bundlePath"` | ||||
| } | ||||
| 
 | ||||
| type Hook interface { | ||||
|  | @ -274,10 +277,11 @@ func (f FuncHook) Run(s HookState) error { | |||
| } | ||||
| 
 | ||||
| type Command struct { | ||||
| 	Path string   `json:"path"` | ||||
| 	Args []string `json:"args"` | ||||
| 	Env  []string `json:"env"` | ||||
| 	Dir  string   `json:"dir"` | ||||
| 	Path    string         `json:"path"` | ||||
| 	Args    []string       `json:"args"` | ||||
| 	Env     []string       `json:"env"` | ||||
| 	Dir     string         `json:"dir"` | ||||
| 	Timeout *time.Duration `json:"timeout"` | ||||
| } | ||||
| 
 | ||||
| // NewCommandHooks will execute the provided command when the hook is run. | ||||
|  | @ -302,5 +306,23 @@ func (c Command) Run(s HookState) error { | |||
| 		Env:   c.Env, | ||||
| 		Stdin: bytes.NewReader(b), | ||||
| 	} | ||||
| 	return cmd.Run() | ||||
| 	errC := make(chan error, 1) | ||||
| 	go func() { | ||||
| 		out, err := cmd.CombinedOutput() | ||||
| 		if err != nil { | ||||
| 			err = fmt.Errorf("%s: %s", err, out) | ||||
| 		} | ||||
| 		errC <- err | ||||
| 	}() | ||||
| 	if c.Timeout != nil { | ||||
| 		select { | ||||
| 		case err := <-errC: | ||||
| 			return err | ||||
| 		case <-time.After(*c.Timeout): | ||||
| 			cmd.Process.Kill() | ||||
| 			cmd.Wait() | ||||
| 			return fmt.Errorf("hook ran past specified timeout of %.1fs", c.Timeout.Seconds()) | ||||
| 		} | ||||
| 	} | ||||
| 	return <-errC | ||||
| } | ||||
|  |  | |||
|  | @ -18,7 +18,7 @@ var namespaceInfo = map[NamespaceType]int{ | |||
| } | ||||
| 
 | ||||
| // CloneFlags parses the container's Namespaces options to set the correct | ||||
| // flags on clone, unshare. This functions returns flags only for new namespaces. | ||||
| // flags on clone, unshare. This function returns flags only for new namespaces. | ||||
| func (n *Namespaces) CloneFlags() uintptr { | ||||
| 	var flag int | ||||
| 	for _, v := range *n { | ||||
|  |  | |||
|  | @ -8,7 +8,7 @@ func (n *Namespace) Syscall() int { | |||
| } | ||||
| 
 | ||||
| // CloneFlags parses the container's Namespaces options to set the correct | ||||
| // flags on clone, unshare. This functions returns flags only for new namespaces. | ||||
| // flags on clone, unshare. This function returns flags only for new namespaces. | ||||
| func (n *Namespaces) CloneFlags() uintptr { | ||||
| 	panic("No namespace syscall support") | ||||
| 	return uintptr(0) | ||||
|  |  | |||
|  | @ -78,7 +78,7 @@ type Container interface { | |||
| 	// Systemerror - System error. | ||||
| 	Checkpoint(criuOpts *CriuOpts) error | ||||
| 
 | ||||
| 	// Restore restores the checkpointed container to a running state using the criu(8) utiity. | ||||
| 	// Restore restores the checkpointed container to a running state using the criu(8) utility. | ||||
| 	// | ||||
| 	// errors: | ||||
| 	// Systemerror - System error. | ||||
|  | @ -205,10 +205,11 @@ func (c *linuxContainer) Start(process *Process) error { | |||
| 		} | ||||
| 		if c.config.Hooks != nil { | ||||
| 			s := configs.HookState{ | ||||
| 				Version: c.config.Version, | ||||
| 				ID:      c.id, | ||||
| 				Pid:     parent.pid(), | ||||
| 				Root:    c.config.Rootfs, | ||||
| 				Version:    c.config.Version, | ||||
| 				ID:         c.id, | ||||
| 				Pid:        parent.pid(), | ||||
| 				Root:       c.config.Rootfs, | ||||
| 				BundlePath: utils.SearchLabels(c.config.Labels, "bundle"), | ||||
| 			} | ||||
| 			for _, hook := range c.config.Hooks.Poststart { | ||||
| 				if err := hook.Run(s); err != nil { | ||||
|  |  | |||
|  | @ -9,6 +9,7 @@ import ( | |||
| 	"os/exec" | ||||
| 	"path/filepath" | ||||
| 	"regexp" | ||||
| 	"runtime/debug" | ||||
| 	"strconv" | ||||
| 	"syscall" | ||||
| 
 | ||||
|  | @ -248,6 +249,13 @@ func (l *LinuxFactory) StartInitialization() (err error) { | |||
| 		// ensure that this pipe is always closed | ||||
| 		pipe.Close() | ||||
| 	}() | ||||
| 
 | ||||
| 	defer func() { | ||||
| 		if e := recover(); e != nil { | ||||
| 			err = fmt.Errorf("panic from initialization: %v, %v", e, string(debug.Stack())) | ||||
| 		} | ||||
| 	}() | ||||
| 
 | ||||
| 	i, err = newContainerInit(it, pipe) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
|  |  | |||
|  | @ -316,10 +316,9 @@ func setupRoute(config *configs.Config) error { | |||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| func setupRlimits(limits []configs.Rlimit) error { | ||||
| func setupRlimits(limits []configs.Rlimit, pid int) error { | ||||
| 	for _, rlimit := range limits { | ||||
| 		l := &syscall.Rlimit{Max: rlimit.Hard, Cur: rlimit.Soft} | ||||
| 		if err := syscall.Setrlimit(rlimit.Type, l); err != nil { | ||||
| 		if err := system.Prlimit(pid, rlimit.Type, syscall.Rlimit{Max: rlimit.Hard, Cur: rlimit.Soft}); err != nil { | ||||
| 			return fmt.Errorf("error setting rlimit type %v: %v", rlimit.Type, err) | ||||
| 		} | ||||
| 	} | ||||
|  |  | |||
|  | @ -21,6 +21,10 @@ func SetProcessLabel(processLabel string) error { | |||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| func GetFileLabel(path string) (string, error) { | ||||
| 	return "", nil | ||||
| } | ||||
| 
 | ||||
| func SetFileLabel(path string, fileLabel string) error { | ||||
| 	return nil | ||||
| } | ||||
|  | @ -48,7 +52,7 @@ func UnreserveLabel(label string) error { | |||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // DupSecOpt takes an process label and returns security options that | ||||
| // DupSecOpt takes a process label and returns security options that | ||||
| // can be used to set duplicate labels on future container processes | ||||
| func DupSecOpt(src string) []string { | ||||
| 	return nil | ||||
|  |  | |||
|  | @ -94,6 +94,11 @@ func GetProcessLabel() (string, error) { | |||
| 	return selinux.Getexeccon() | ||||
| } | ||||
| 
 | ||||
| // GetFileLabel returns the label for specified path | ||||
| func GetFileLabel(path string) (string, error) { | ||||
| 	return selinux.Getfilecon(path) | ||||
| } | ||||
| 
 | ||||
| // SetFileLabel modifies the "path" label to the specified file label | ||||
| func SetFileLabel(path string, fileLabel string) error { | ||||
| 	if selinux.SelinuxEnabled() && fileLabel != "" { | ||||
|  |  | |||
|  | @ -89,6 +89,11 @@ func (p *setnsProcess) start() (err error) { | |||
| 	if err := setOomScoreAdj(p.config.Config.OomScoreAdj, p.pid()); err != nil { | ||||
| 		return newSystemError(err) | ||||
| 	} | ||||
| 	// set rlimits, this has to be done here because we lose permissions | ||||
| 	// to raise the limits once we enter a user-namespace | ||||
| 	if err := setupRlimits(p.config.Rlimits, p.pid()); err != nil { | ||||
| 		return newSystemError(err) | ||||
| 	} | ||||
| 	if err := utils.WriteJSON(p.parentPipe, p.config); err != nil { | ||||
| 		return newSystemError(err) | ||||
| 	} | ||||
|  | @ -284,6 +289,11 @@ loop: | |||
| 			if err := setOomScoreAdj(p.config.Config.OomScoreAdj, p.pid()); err != nil { | ||||
| 				return newSystemError(err) | ||||
| 			} | ||||
| 			// set rlimits, this has to be done here because we lose permissions | ||||
| 			// to raise the limits once we enter a user-namespace | ||||
| 			if err := setupRlimits(p.config.Rlimits, p.pid()); err != nil { | ||||
| 				return newSystemError(err) | ||||
| 			} | ||||
| 			// call prestart hooks | ||||
| 			if !p.config.Config.Namespaces.Contains(configs.NEWNS) { | ||||
| 				if p.config.Config.Hooks != nil { | ||||
|  | @ -308,10 +318,11 @@ loop: | |||
| 		case procHooks: | ||||
| 			if p.config.Config.Hooks != nil { | ||||
| 				s := configs.HookState{ | ||||
| 					Version: p.container.config.Version, | ||||
| 					ID:      p.container.id, | ||||
| 					Pid:     p.pid(), | ||||
| 					Root:    p.config.Config.Rootfs, | ||||
| 					Version:    p.container.config.Version, | ||||
| 					ID:         p.container.id, | ||||
| 					Pid:        p.pid(), | ||||
| 					Root:       p.config.Config.Rootfs, | ||||
| 					BundlePath: utils.SearchLabels(p.config.Config.Labels, "bundle"), | ||||
| 				} | ||||
| 				for _, hook := range p.config.Config.Hooks.Prestart { | ||||
| 					if err := hook.Run(s); err != nil { | ||||
|  | @ -340,7 +351,7 @@ loop: | |||
| 		} | ||||
| 	} | ||||
| 	if !sentRun { | ||||
| 		return newSystemError(fmt.Errorf("could not synchronise with container process")) | ||||
| 		return newSystemError(fmt.Errorf("could not synchronise with container process: %v", ierr)) | ||||
| 	} | ||||
| 	if p.config.Config.Namespaces.Contains(configs.NEWNS) && !sentResume { | ||||
| 		return newSystemError(fmt.Errorf("could not synchronise after executing prestart hooks with container process")) | ||||
|  |  | |||
|  | @ -25,6 +25,16 @@ import ( | |||
| 
 | ||||
| const defaultMountFlags = syscall.MS_NOEXEC | syscall.MS_NOSUID | syscall.MS_NODEV | ||||
| 
 | ||||
| // setupDev returns true if /dev needs to be set up. | ||||
| func needsSetupDev(config *configs.Config) bool { | ||||
| 	for _, m := range config.Mounts { | ||||
| 		if m.Device == "bind" && (m.Destination == "/dev" || m.Destination == "/dev/") { | ||||
| 			return false | ||||
| 		} | ||||
| 	} | ||||
| 	return true | ||||
| } | ||||
| 
 | ||||
| // setupRootfs sets up the devices, mount points, and filesystems for use inside a | ||||
| // new mount namespace. | ||||
| func setupRootfs(config *configs.Config, console *linuxConsole, pipe io.ReadWriter) (err error) { | ||||
|  | @ -32,7 +42,7 @@ func setupRootfs(config *configs.Config, console *linuxConsole, pipe io.ReadWrit | |||
| 		return newSystemError(err) | ||||
| 	} | ||||
| 
 | ||||
| 	setupDev := len(config.Devices) != 0 | ||||
| 	setupDev := needsSetupDev(config) | ||||
| 	for _, m := range config.Mounts { | ||||
| 		for _, precmd := range m.PremountCmds { | ||||
| 			if err := mountCmd(precmd); err != nil { | ||||
|  | @ -140,8 +150,9 @@ func mountToRootfs(m *configs.Mount, rootfs, mountLabel string) error { | |||
| 			if err := mountPropagate(m, rootfs, ""); err != nil { | ||||
| 				return err | ||||
| 			} | ||||
| 			return label.SetFileLabel(dest, mountLabel) | ||||
| 		} | ||||
| 		return label.SetFileLabel(dest, mountLabel) | ||||
| 		return nil | ||||
| 	case "tmpfs": | ||||
| 		stat, err := os.Stat(dest) | ||||
| 		if err != nil { | ||||
|  |  | |||
|  | @ -5,7 +5,6 @@ package seccomp | |||
| import ( | ||||
| 	"bufio" | ||||
| 	"fmt" | ||||
| 	"log" | ||||
| 	"os" | ||||
| 	"strings" | ||||
| 	"syscall" | ||||
|  | @ -167,7 +166,6 @@ func matchCall(filter *libseccomp.ScmpFilter, call *configs.Syscall) error { | |||
| 	// Ignore it, don't error out | ||||
| 	callNum, err := libseccomp.GetSyscallFromName(call.Name) | ||||
| 	if err != nil { | ||||
| 		log.Printf("Error resolving syscall name %s: %s - ignoring syscall.", call.Name, err) | ||||
| 		return nil | ||||
| 	} | ||||
| 
 | ||||
|  |  | |||
|  | @ -28,9 +28,6 @@ func (l *linuxSetnsInit) Init() error { | |||
| 	if _, err := keyctl.JoinSessionKeyring(l.getSessionRingName()); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	if err := setupRlimits(l.config.Rlimits); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	if l.config.NoNewPrivileges { | ||||
| 		if err := system.Prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0); err != nil { | ||||
| 			return err | ||||
|  |  | |||
|  | @ -73,9 +73,6 @@ func (l *linuxStandardInit) Init() error { | |||
| 	if err := setupRoute(l.config.Config); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	if err := setupRlimits(l.config.Rlimits); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 
 | ||||
| 	label.Init() | ||||
| 	// InitializeMountNamespace() can be executed only for a new mount namespace | ||||
|  |  | |||
|  | @ -9,6 +9,7 @@ import ( | |||
| 
 | ||||
| 	"github.com/Sirupsen/logrus" | ||||
| 	"github.com/opencontainers/runc/libcontainer/configs" | ||||
| 	"github.com/opencontainers/runc/libcontainer/utils" | ||||
| ) | ||||
| 
 | ||||
| func newStateTransitionError(from, to containerState) error { | ||||
|  | @ -56,9 +57,10 @@ func destroy(c *linuxContainer) error { | |||
| func runPoststopHooks(c *linuxContainer) error { | ||||
| 	if c.config.Hooks != nil { | ||||
| 		s := configs.HookState{ | ||||
| 			Version: c.config.Version, | ||||
| 			ID:      c.id, | ||||
| 			Root:    c.config.Rootfs, | ||||
| 			Version:    c.config.Version, | ||||
| 			ID:         c.id, | ||||
| 			Root:       c.config.Rootfs, | ||||
| 			BundlePath: utils.SearchLabels(c.config.Labels, "bundle"), | ||||
| 		} | ||||
| 		for _, hook := range c.config.Hooks.Poststop { | ||||
| 			if err := hook.Run(s); err != nil { | ||||
|  |  | |||
|  | @ -53,6 +53,14 @@ func Execv(cmd string, args []string, env []string) error { | |||
| 	return syscall.Exec(name, args, env) | ||||
| } | ||||
| 
 | ||||
| func Prlimit(pid, resource int, limit syscall.Rlimit) error { | ||||
| 	_, _, err := syscall.RawSyscall6(syscall.SYS_PRLIMIT64, uintptr(pid), uintptr(resource), uintptr(unsafe.Pointer(&limit)), uintptr(unsafe.Pointer(&limit)), 0, 0) | ||||
| 	if err != 0 { | ||||
| 		return err | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| func SetParentDeathSignal(sig uintptr) error { | ||||
| 	if _, _, err := syscall.RawSyscall(syscall.SYS_PRCTL, syscall.PR_SET_PDEATHSIG, sig, 0); err != 0 { | ||||
| 		return err | ||||
|  |  | |||
|  | @ -15,7 +15,7 @@ const ( | |||
| ) | ||||
| 
 | ||||
| var ( | ||||
| 	ErrRange = fmt.Errorf("Uids and gids must be in range %d-%d", minId, maxId) | ||||
| 	ErrRange = fmt.Errorf("uids and gids must be in range %d-%d", minId, maxId) | ||||
| ) | ||||
| 
 | ||||
| type User struct { | ||||
|  | @ -42,29 +42,30 @@ func parseLine(line string, v ...interface{}) { | |||
| 
 | ||||
| 	parts := strings.Split(line, ":") | ||||
| 	for i, p := range parts { | ||||
| 		// Ignore cases where we don't have enough fields to populate the arguments. | ||||
| 		// Some configuration files like to misbehave. | ||||
| 		if len(v) <= i { | ||||
| 			// if we have more "parts" than we have places to put them, bail for great "tolerance" of naughty configuration files | ||||
| 			break | ||||
| 		} | ||||
| 
 | ||||
| 		// Use the type of the argument to figure out how to parse it, scanf() style. | ||||
| 		// This is legit. | ||||
| 		switch e := v[i].(type) { | ||||
| 		case *string: | ||||
| 			// "root", "adm", "/bin/bash" | ||||
| 			*e = p | ||||
| 		case *int: | ||||
| 			// "0", "4", "1000" | ||||
| 			// ignore string to int conversion errors, for great "tolerance" of naughty configuration files | ||||
| 			// "numbers", with conversion errors ignored because of some misbehaving configuration files. | ||||
| 			*e, _ = strconv.Atoi(p) | ||||
| 		case *[]string: | ||||
| 			// "", "root", "root,adm,daemon" | ||||
| 			// Comma-separated lists. | ||||
| 			if p != "" { | ||||
| 				*e = strings.Split(p, ",") | ||||
| 			} else { | ||||
| 				*e = []string{} | ||||
| 			} | ||||
| 		default: | ||||
| 			// panic, because this is a programming/logic error, not a runtime one | ||||
| 			panic("parseLine expects only pointers!  argument " + strconv.Itoa(i) + " is not a pointer!") | ||||
| 			// Someone goof'd when writing code using this function. Scream so they can hear us. | ||||
| 			panic(fmt.Sprintf("parseLine only accepts {*string, *int, *[]string} as arguments! %#v is not a pointer!", e)) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | @ -106,8 +107,8 @@ func ParsePasswdFilter(r io.Reader, filter func(User) bool) ([]User, error) { | |||
| 			return nil, err | ||||
| 		} | ||||
| 
 | ||||
| 		text := strings.TrimSpace(s.Text()) | ||||
| 		if text == "" { | ||||
| 		line := strings.TrimSpace(s.Text()) | ||||
| 		if line == "" { | ||||
| 			continue | ||||
| 		} | ||||
| 
 | ||||
|  | @ -117,10 +118,7 @@ func ParsePasswdFilter(r io.Reader, filter func(User) bool) ([]User, error) { | |||
| 		//  root:x:0:0:root:/root:/bin/bash | ||||
| 		//  adm:x:3:4:adm:/var/adm:/bin/false | ||||
| 		p := User{} | ||||
| 		parseLine( | ||||
| 			text, | ||||
| 			&p.Name, &p.Pass, &p.Uid, &p.Gid, &p.Gecos, &p.Home, &p.Shell, | ||||
| 		) | ||||
| 		parseLine(line, &p.Name, &p.Pass, &p.Uid, &p.Gid, &p.Gecos, &p.Home, &p.Shell) | ||||
| 
 | ||||
| 		if filter == nil || filter(p) { | ||||
| 			out = append(out, p) | ||||
|  | @ -135,6 +133,7 @@ func ParseGroupFile(path string) ([]Group, error) { | |||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	defer group.Close() | ||||
| 	return ParseGroup(group) | ||||
| } | ||||
|  | @ -178,10 +177,7 @@ func ParseGroupFilter(r io.Reader, filter func(Group) bool) ([]Group, error) { | |||
| 		//  root:x:0:root | ||||
| 		//  adm:x:4:root,adm,daemon | ||||
| 		p := Group{} | ||||
| 		parseLine( | ||||
| 			text, | ||||
| 			&p.Name, &p.Pass, &p.Gid, &p.List, | ||||
| 		) | ||||
| 		parseLine(text, &p.Name, &p.Pass, &p.Gid, &p.List) | ||||
| 
 | ||||
| 		if filter == nil || filter(p) { | ||||
| 			out = append(out, p) | ||||
|  | @ -192,9 +188,10 @@ func ParseGroupFilter(r io.Reader, filter func(Group) bool) ([]Group, error) { | |||
| } | ||||
| 
 | ||||
| type ExecUser struct { | ||||
| 	Uid, Gid int | ||||
| 	Sgids    []int | ||||
| 	Home     string | ||||
| 	Uid   int | ||||
| 	Gid   int | ||||
| 	Sgids []int | ||||
| 	Home  string | ||||
| } | ||||
| 
 | ||||
| // GetExecUserPath is a wrapper for GetExecUser. It reads data from each of the | ||||
|  | @ -235,12 +232,12 @@ func GetExecUserPath(userSpec string, defaults *ExecUser, passwdPath, groupPath | |||
| //     * "uid:gid | ||||
| //     * "user:gid" | ||||
| //     * "uid:group" | ||||
| // | ||||
| // It should be noted that if you specify a numeric user or group id, they will | ||||
| // not be evaluated as usernames (only the metadata will be filled). So attempting | ||||
| // to parse a user with user.Name = "1337" will produce the user with a UID of | ||||
| // 1337. | ||||
| func GetExecUser(userSpec string, defaults *ExecUser, passwd, group io.Reader) (*ExecUser, error) { | ||||
| 	var ( | ||||
| 		userArg, groupArg string | ||||
| 		name              string | ||||
| 	) | ||||
| 
 | ||||
| 	if defaults == nil { | ||||
| 		defaults = new(ExecUser) | ||||
| 	} | ||||
|  | @ -258,87 +255,113 @@ func GetExecUser(userSpec string, defaults *ExecUser, passwd, group io.Reader) ( | |||
| 		user.Sgids = []int{} | ||||
| 	} | ||||
| 
 | ||||
| 	// allow for userArg to have either "user" syntax, or optionally "user:group" syntax | ||||
| 	// Allow for userArg to have either "user" syntax, or optionally "user:group" syntax | ||||
| 	var userArg, groupArg string | ||||
| 	parseLine(userSpec, &userArg, &groupArg) | ||||
| 
 | ||||
| 	// Convert userArg and groupArg to be numeric, so we don't have to execute | ||||
| 	// Atoi *twice* for each iteration over lines. | ||||
| 	uidArg, uidErr := strconv.Atoi(userArg) | ||||
| 	gidArg, gidErr := strconv.Atoi(groupArg) | ||||
| 
 | ||||
| 	// Find the matching user. | ||||
| 	users, err := ParsePasswdFilter(passwd, func(u User) bool { | ||||
| 		if userArg == "" { | ||||
| 			// Default to current state of the user. | ||||
| 			return u.Uid == user.Uid | ||||
| 		} | ||||
| 		return u.Name == userArg || strconv.Itoa(u.Uid) == userArg | ||||
| 
 | ||||
| 		if uidErr == nil { | ||||
| 			// If the userArg is numeric, always treat it as a UID. | ||||
| 			return uidArg == u.Uid | ||||
| 		} | ||||
| 
 | ||||
| 		return u.Name == userArg | ||||
| 	}) | ||||
| 
 | ||||
| 	// If we can't find the user, we have to bail. | ||||
| 	if err != nil && passwd != nil { | ||||
| 		if userArg == "" { | ||||
| 			userArg = strconv.Itoa(user.Uid) | ||||
| 		} | ||||
| 		return nil, fmt.Errorf("Unable to find user %v: %v", userArg, err) | ||||
| 		return nil, fmt.Errorf("unable to find user %s: %v", userArg, err) | ||||
| 	} | ||||
| 
 | ||||
| 	haveUser := users != nil && len(users) > 0 | ||||
| 	if haveUser { | ||||
| 		// if we found any user entries that matched our filter, let's take the first one as "correct" | ||||
| 		name = users[0].Name | ||||
| 	var matchedUserName string | ||||
| 	if len(users) > 0 { | ||||
| 		// First match wins, even if there's more than one matching entry. | ||||
| 		matchedUserName = users[0].Name | ||||
| 		user.Uid = users[0].Uid | ||||
| 		user.Gid = users[0].Gid | ||||
| 		user.Home = users[0].Home | ||||
| 	} else if userArg != "" { | ||||
| 		// we asked for a user but didn't find them...  let's check to see if we wanted a numeric user | ||||
| 		user.Uid, err = strconv.Atoi(userArg) | ||||
| 		if err != nil { | ||||
| 			// not numeric - we have to bail | ||||
| 			return nil, fmt.Errorf("Unable to find user %v", userArg) | ||||
| 		// If we can't find a user with the given username, the only other valid | ||||
| 		// option is if it's a numeric username with no associated entry in passwd. | ||||
| 
 | ||||
| 		if uidErr != nil { | ||||
| 			// Not numeric. | ||||
| 			return nil, fmt.Errorf("unable to find user %s: %v", userArg, ErrNoPasswdEntries) | ||||
| 		} | ||||
| 		user.Uid = uidArg | ||||
| 
 | ||||
| 		// Must be inside valid uid range. | ||||
| 		if user.Uid < minId || user.Uid > maxId { | ||||
| 			return nil, ErrRange | ||||
| 		} | ||||
| 
 | ||||
| 		// if userArg couldn't be found in /etc/passwd but is numeric, just roll with it - this is legit | ||||
| 		// Okay, so it's numeric. We can just roll with this. | ||||
| 	} | ||||
| 
 | ||||
| 	if groupArg != "" || name != "" { | ||||
| 	// On to the groups. If we matched a username, we need to do this because of | ||||
| 	// the supplementary group IDs. | ||||
| 	if groupArg != "" || matchedUserName != "" { | ||||
| 		groups, err := ParseGroupFilter(group, func(g Group) bool { | ||||
| 			// Explicit group format takes precedence. | ||||
| 			if groupArg != "" { | ||||
| 				return g.Name == groupArg || strconv.Itoa(g.Gid) == groupArg | ||||
| 			} | ||||
| 
 | ||||
| 			// Check if user is a member. | ||||
| 			for _, u := range g.List { | ||||
| 				if u == name { | ||||
| 					return true | ||||
| 			// If the group argument isn't explicit, we'll just search for it. | ||||
| 			if groupArg == "" { | ||||
| 				// Check if user is a member of this group. | ||||
| 				for _, u := range g.List { | ||||
| 					if u == matchedUserName { | ||||
| 						return true | ||||
| 					} | ||||
| 				} | ||||
| 				return false | ||||
| 			} | ||||
| 
 | ||||
| 			return false | ||||
| 			if gidErr == nil { | ||||
| 				// If the groupArg is numeric, always treat it as a GID. | ||||
| 				return gidArg == g.Gid | ||||
| 			} | ||||
| 
 | ||||
| 			return g.Name == groupArg | ||||
| 		}) | ||||
| 		if err != nil && group != nil { | ||||
| 			return nil, fmt.Errorf("Unable to find groups for user %v: %v", users[0].Name, err) | ||||
| 			return nil, fmt.Errorf("unable to find groups for spec %v: %v", matchedUserName, err) | ||||
| 		} | ||||
| 
 | ||||
| 		haveGroup := groups != nil && len(groups) > 0 | ||||
| 		// Only start modifying user.Gid if it is in explicit form. | ||||
| 		if groupArg != "" { | ||||
| 			if haveGroup { | ||||
| 				// if we found any group entries that matched our filter, let's take the first one as "correct" | ||||
| 			if len(groups) > 0 { | ||||
| 				// First match wins, even if there's more than one matching entry. | ||||
| 				user.Gid = groups[0].Gid | ||||
| 			} else { | ||||
| 				// we asked for a group but didn't find id...  let's check to see if we wanted a numeric group | ||||
| 				user.Gid, err = strconv.Atoi(groupArg) | ||||
| 				if err != nil { | ||||
| 					// not numeric - we have to bail | ||||
| 					return nil, fmt.Errorf("Unable to find group %v", groupArg) | ||||
| 				} | ||||
| 			} else if groupArg != "" { | ||||
| 				// If we can't find a group with the given name, the only other valid | ||||
| 				// option is if it's a numeric group name with no associated entry in group. | ||||
| 
 | ||||
| 				// Ensure gid is inside gid range. | ||||
| 				if gidErr != nil { | ||||
| 					// Not numeric. | ||||
| 					return nil, fmt.Errorf("unable to find group %s: %v", groupArg, ErrNoGroupEntries) | ||||
| 				} | ||||
| 				user.Gid = gidArg | ||||
| 
 | ||||
| 				// Must be inside valid gid range. | ||||
| 				if user.Gid < minId || user.Gid > maxId { | ||||
| 					return nil, ErrRange | ||||
| 				} | ||||
| 
 | ||||
| 				// if groupArg couldn't be found in /etc/group but is numeric, just roll with it - this is legit | ||||
| 				// Okay, so it's numeric. We can just roll with this. | ||||
| 			} | ||||
| 		} else if haveGroup { | ||||
| 			// If implicit group format, fill supplementary gids. | ||||
| 		} else if len(groups) > 0 { | ||||
| 			// Supplementary group ids only make sense if in the implicit form. | ||||
| 			user.Sgids = make([]int, len(groups)) | ||||
| 			for i, group := range groups { | ||||
| 				user.Sgids[i] = group.Gid | ||||
|  |  | |||
|  | @ -7,6 +7,7 @@ import ( | |||
| 	"io" | ||||
| 	"os" | ||||
| 	"path/filepath" | ||||
| 	"strings" | ||||
| 	"syscall" | ||||
| ) | ||||
| 
 | ||||
|  | @ -84,3 +85,18 @@ func CleanPath(path string) string { | |||
| 	// Clean the path again for good measure. | ||||
| 	return filepath.Clean(path) | ||||
| } | ||||
| 
 | ||||
| // SearchLabels searches a list of key-value pairs for the provided key and | ||||
| // returns the corresponding value. The pairs must be separated with '='. | ||||
| func SearchLabels(labels []string, query string) string { | ||||
| 	for _, l := range labels { | ||||
| 		parts := strings.SplitN(l, "=", 2) | ||||
| 		if len(parts) < 2 { | ||||
| 			continue | ||||
| 		} | ||||
| 		if parts[0] == query { | ||||
| 			return parts[1] | ||||
| 		} | ||||
| 	} | ||||
| 	return "" | ||||
| } | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue