diff options
Diffstat (limited to 'pkg')
28 files changed, 413 insertions, 148 deletions
diff --git a/pkg/adapter/checkpoint_restore.go b/pkg/adapter/checkpoint_restore.go index 97ba5ecf7..ec1464fb1 100644 --- a/pkg/adapter/checkpoint_restore.go +++ b/pkg/adapter/checkpoint_restore.go @@ -4,16 +4,19 @@ package adapter import ( "context" + "io" + "io/ioutil" + "os" + "path/filepath" + "github.com/containers/libpod/libpod" "github.com/containers/libpod/libpod/image" + "github.com/containers/libpod/pkg/errorhandling" "github.com/containers/storage/pkg/archive" jsoniter "github.com/json-iterator/go" spec "github.com/opencontainers/runtime-spec/specs-go" "github.com/pkg/errors" - "io" - "io/ioutil" - "os" - "path/filepath" + "github.com/sirupsen/logrus" ) // Prefixing the checkpoint/restore related functions with 'cr' @@ -25,7 +28,7 @@ func crImportFromJSON(filePath string, v interface{}) error { if err != nil { return errors.Wrapf(err, "Failed to open container definition %s for restore", filePath) } - defer jsonFile.Close() + defer errorhandling.CloseQuiet(jsonFile) content, err := ioutil.ReadAll(jsonFile) if err != nil { @@ -48,7 +51,7 @@ func crImportCheckpoint(ctx context.Context, runtime *libpod.Runtime, input stri if err != nil { return nil, errors.Wrapf(err, "Failed to open checkpoint archive %s for import", input) } - defer archiveFile.Close() + defer errorhandling.CloseQuiet(archiveFile) options := &archive.TarOptions{ // Here we only need the files config.dump and spec.dump ExcludePatterns: []string{ @@ -62,15 +65,19 @@ func crImportCheckpoint(ctx context.Context, runtime *libpod.Runtime, input stri if err != nil { return nil, err } - defer os.RemoveAll(dir) + defer func() { + if err := os.RemoveAll(dir); err != nil { + logrus.Errorf("could not recursively remove %s: %q", dir, err) + } + }() err = archive.Untar(archiveFile, dir, options) if err != nil { return nil, errors.Wrapf(err, "Unpacking of checkpoint archive %s failed", input) } // Load spec.dump from temporary directory - spec := new(spec.Spec) - if err := crImportFromJSON(filepath.Join(dir, "spec.dump"), spec); err != nil { + dumpSpec := new(spec.Spec) + if err := crImportFromJSON(filepath.Join(dir, "spec.dump"), dumpSpec); err != nil { return nil, err } @@ -112,7 +119,7 @@ func crImportCheckpoint(ctx context.Context, runtime *libpod.Runtime, input stri } // Now create a new container from the just loaded information - container, err := runtime.RestoreContainer(ctx, spec, config) + container, err := runtime.RestoreContainer(ctx, dumpSpec, config) if err != nil { return nil, err } @@ -127,7 +134,7 @@ func crImportCheckpoint(ctx context.Context, runtime *libpod.Runtime, input stri return nil, errors.Errorf("Name of restored container (%s) does not match requested name (%s)", containerConfig.Name, ctrName) } - if newName == false { + if !newName { // Only check ID for a restore with the same name. // Using -n to request a new name for the restored container, will also create a new ID if containerConfig.ID != ctrID { diff --git a/pkg/adapter/containers.go b/pkg/adapter/containers.go index 0ea89a72c..86e9c0266 100644 --- a/pkg/adapter/containers.go +++ b/pkg/adapter/containers.go @@ -95,8 +95,8 @@ func (r *LocalRuntime) StopContainers(ctx context.Context, cli *cliconfig.StopVa } pool.Add(shared.Job{ - c.ID(), - func() error { + ID: c.ID(), + Fn: func() error { err := c.StopWithTimeout(*timeout) if err != nil { if errors.Cause(err) == define.ErrCtrStopped { @@ -134,8 +134,8 @@ func (r *LocalRuntime) KillContainers(ctx context.Context, cli *cliconfig.KillVa c := c pool.Add(shared.Job{ - c.ID(), - func() error { + ID: c.ID(), + Fn: func() error { return c.Kill(uint(signal)) }, }) @@ -163,8 +163,8 @@ func (r *LocalRuntime) InitContainers(ctx context.Context, cli *cliconfig.InitVa ctr := c pool.Add(shared.Job{ - ctr.ID(), - func() error { + ID: ctr.ID(), + Fn: func() error { err := ctr.Init(ctx) if err != nil { // If we're initializing all containers, ignore invalid state errors @@ -213,8 +213,8 @@ func (r *LocalRuntime) RemoveContainers(ctx context.Context, cli *cliconfig.RmVa c := c pool.Add(shared.Job{ - c.ID(), - func() error { + ID: c.ID(), + Fn: func() error { err := r.RemoveContainer(ctx, c, cli.Force, cli.Volumes) if err != nil { logrus.Debugf("Failed to remove container %s: %s", c.ID(), err.Error()) diff --git a/pkg/adapter/pods.go b/pkg/adapter/pods.go index a28e1ab4b..b45b02d09 100644 --- a/pkg/adapter/pods.go +++ b/pkg/adapter/pods.go @@ -70,8 +70,9 @@ func (r *LocalRuntime) PrunePods(ctx context.Context, cli *cliconfig.PodPruneVal for _, p := range pods { p := p - pool.Add(shared.Job{p.ID(), - func() error { + pool.Add(shared.Job{ + ID: p.ID(), + Fn: func() error { err := r.Runtime.RemovePod(ctx, p, cli.Force, cli.Force) if err != nil { logrus.Debugf("Failed to remove pod %s: %s", p.ID(), err.Error()) diff --git a/pkg/adapter/runtime.go b/pkg/adapter/runtime.go index 8ef88f36b..e65f07898 100644 --- a/pkg/adapter/runtime.go +++ b/pkg/adapter/runtime.go @@ -359,9 +359,6 @@ func (r *LocalRuntime) Events(c *cliconfig.EventValues) error { if eventsError != nil { return eventsError } - if err != nil { - return errors.Wrapf(err, "unable to tail the events log") - } w := bufio.NewWriter(os.Stdout) for event := range eventChannel { if len(c.Format) > 0 { diff --git a/pkg/adapter/runtime_remote.go b/pkg/adapter/runtime_remote.go index 800ed7569..db3f23629 100644 --- a/pkg/adapter/runtime_remote.go +++ b/pkg/adapter/runtime_remote.go @@ -97,6 +97,14 @@ func GetRuntime(ctx context.Context, c *cliconfig.PodmanCommand) (*LocalRuntime, }, nil } +// DeferredShutdown is a bogus wrapper for compaat with the libpod +// runtime and should only be run when a defer is being used +func (r RemoteRuntime) DeferredShutdown(force bool) { + if err := r.Shutdown(force); err != nil { + logrus.Error("unable to shutdown runtime") + } +} + // Shutdown is a bogus wrapper for compat with the libpod runtime func (r RemoteRuntime) Shutdown(force bool) error { return nil diff --git a/pkg/adapter/sigproxy_linux.go b/pkg/adapter/sigproxy_linux.go index af968cb89..efa6afa7b 100644 --- a/pkg/adapter/sigproxy_linux.go +++ b/pkg/adapter/sigproxy_linux.go @@ -27,7 +27,9 @@ func ProxySignals(ctr *libpod.Container) { if err := ctr.Kill(uint(s.(syscall.Signal))); err != nil { logrus.Errorf("Error forwarding signal %d to container %s: %v", s, ctr.ID(), err) signal.StopCatch(sigBuffer) - syscall.Kill(syscall.Getpid(), s.(syscall.Signal)) + if err := syscall.Kill(syscall.Getpid(), s.(syscall.Signal)); err != nil { + logrus.Errorf("failed to kill pid %d", syscall.Getpid()) + } } } }() diff --git a/pkg/adapter/terminal_linux.go b/pkg/adapter/terminal_linux.go index 3c4c3bd38..e3255ecb6 100644 --- a/pkg/adapter/terminal_linux.go +++ b/pkg/adapter/terminal_linux.go @@ -35,9 +35,15 @@ func StartAttachCtr(ctx context.Context, ctr *libpod.Container, stdout, stderr, } logrus.SetFormatter(&RawTtyFormatter{}) - term.SetRawTerminal(os.Stdin.Fd()) + if _, err := term.SetRawTerminal(os.Stdin.Fd()); err != nil { + return err + } - defer restoreTerminal(oldTermState) + defer func() { + if err := restoreTerminal(oldTermState); err != nil { + logrus.Errorf("unable to restore terminal: %q", err) + } + }() } streams := new(libpod.AttachStreams) diff --git a/pkg/cgroups/blkio.go b/pkg/cgroups/blkio.go index 9c2a811d9..bacd4eb93 100644 --- a/pkg/cgroups/blkio.go +++ b/pkg/cgroups/blkio.go @@ -37,7 +37,7 @@ func (c *blkioHandler) Create(ctr *CgroupControl) (bool, error) { // Destroy the cgroup func (c *blkioHandler) Destroy(ctr *CgroupControl) error { - return os.Remove(ctr.getCgroupv1Path(Blkio)) + return rmDirRecursively(ctr.getCgroupv1Path(Blkio)) } // Stat fills a metrics structure with usage stats for the controller diff --git a/pkg/cgroups/cgroups.go b/pkg/cgroups/cgroups.go index 1dad45d7f..081db772f 100644 --- a/pkg/cgroups/cgroups.go +++ b/pkg/cgroups/cgroups.go @@ -328,6 +328,13 @@ func Load(path string) (*CgroupControl, error) { systemd: false, } if !cgroup2 { + controllers, err := getAvailableControllers(handlers, false) + if err != nil { + return nil, err + } + control.additionalControllers = controllers + } + if !cgroup2 { for name := range handlers { p := control.getCgroupv1Path(name) if _, err := os.Stat(p); err != nil { @@ -355,11 +362,40 @@ func (c *CgroupControl) Delete() error { return c.DeleteByPath(c.path) } +// rmDirRecursively delete recursively a cgroup directory. +// It differs from os.RemoveAll as it doesn't attempt to unlink files. +// On cgroupfs we are allowed only to rmdir empty directories. +func rmDirRecursively(path string) error { + if err := os.Remove(path); err == nil || os.IsNotExist(err) { + return nil + } + entries, err := ioutil.ReadDir(path) + if err != nil { + return errors.Wrapf(err, "read %s", path) + } + for _, i := range entries { + if i.IsDir() { + if err := rmDirRecursively(filepath.Join(path, i.Name())); err != nil { + return err + } + } + } + if os.Remove(path); err != nil { + if !os.IsNotExist(err) { + return errors.Wrapf(err, "remove %s", path) + } + } + return nil +} + // DeleteByPath deletes the specified cgroup path func (c *CgroupControl) DeleteByPath(path string) error { if c.systemd { return systemdDestroy(path) } + if c.cgroup2 { + return rmDirRecursively(filepath.Join(cgroupRoot, c.path)) + } var lastError error for _, h := range handlers { if err := h.Destroy(c); err != nil { @@ -368,8 +404,11 @@ func (c *CgroupControl) DeleteByPath(path string) error { } for _, ctr := range c.additionalControllers { + if ctr.symlink { + continue + } p := c.getCgroupv1Path(ctr.name) - if err := os.Remove(p); err != nil { + if err := rmDirRecursively(p); err != nil { lastError = errors.Wrapf(err, "remove %s", p) } } diff --git a/pkg/cgroups/cpu.go b/pkg/cgroups/cpu.go index 1c8610cc4..03677f1ef 100644 --- a/pkg/cgroups/cpu.go +++ b/pkg/cgroups/cpu.go @@ -68,7 +68,7 @@ func (c *cpuHandler) Create(ctr *CgroupControl) (bool, error) { // Destroy the cgroup func (c *cpuHandler) Destroy(ctr *CgroupControl) error { - return os.Remove(ctr.getCgroupv1Path(CPU)) + return rmDirRecursively(ctr.getCgroupv1Path(CPU)) } // Stat fills a metrics structure with usage stats for the controller diff --git a/pkg/cgroups/cpuset.go b/pkg/cgroups/cpuset.go index 25d2f7f76..46d0484f2 100644 --- a/pkg/cgroups/cpuset.go +++ b/pkg/cgroups/cpuset.go @@ -3,7 +3,6 @@ package cgroups import ( "fmt" "io/ioutil" - "os" "path/filepath" "strings" @@ -77,7 +76,7 @@ func (c *cpusetHandler) Create(ctr *CgroupControl) (bool, error) { // Destroy the cgroup func (c *cpusetHandler) Destroy(ctr *CgroupControl) error { - return os.Remove(ctr.getCgroupv1Path(CPUset)) + return rmDirRecursively(ctr.getCgroupv1Path(CPUset)) } // Stat fills a metrics structure with usage stats for the controller diff --git a/pkg/cgroups/memory.go b/pkg/cgroups/memory.go index 80e88d17c..b3991f7e3 100644 --- a/pkg/cgroups/memory.go +++ b/pkg/cgroups/memory.go @@ -2,7 +2,6 @@ package cgroups import ( "fmt" - "os" "path/filepath" spec "github.com/opencontainers/runtime-spec/specs-go" @@ -33,7 +32,7 @@ func (c *memHandler) Create(ctr *CgroupControl) (bool, error) { // Destroy the cgroup func (c *memHandler) Destroy(ctr *CgroupControl) error { - return os.Remove(ctr.getCgroupv1Path(Memory)) + return rmDirRecursively(ctr.getCgroupv1Path(Memory)) } // Stat fills a metrics structure with usage stats for the controller diff --git a/pkg/cgroups/pids.go b/pkg/cgroups/pids.go index ffbde100d..65b9b5b34 100644 --- a/pkg/cgroups/pids.go +++ b/pkg/cgroups/pids.go @@ -3,7 +3,6 @@ package cgroups import ( "fmt" "io/ioutil" - "os" "path/filepath" spec "github.com/opencontainers/runtime-spec/specs-go" @@ -40,7 +39,7 @@ func (c *pidHandler) Create(ctr *CgroupControl) (bool, error) { // Destroy the cgroup func (c *pidHandler) Destroy(ctr *CgroupControl) error { - return os.Remove(ctr.getCgroupv1Path(Pids)) + return rmDirRecursively(ctr.getCgroupv1Path(Pids)) } // Stat fills a metrics structure with usage stats for the controller diff --git a/pkg/channelwriter/channelwriter.go b/pkg/channelwriter/channelwriter.go new file mode 100644 index 000000000..d51400eb3 --- /dev/null +++ b/pkg/channelwriter/channelwriter.go @@ -0,0 +1,34 @@ +package channelwriter + +import "github.com/pkg/errors" + +// Writer is an io.writer-like object that "writes" to a channel +// instead of a buffer or file, etc. It is handy for varlink endpoints when +// needing to handle endpoints that do logging "real-time" +type Writer struct { + ByteChannel chan []byte +} + +// NewChannelWriter creates a new channel writer and adds a +// byte slice channel into it. +func NewChannelWriter() *Writer { + byteChannel := make(chan []byte) + return &Writer{ + ByteChannel: byteChannel, + } +} + +// Write method for Writer +func (c *Writer) Write(w []byte) (int, error) { + if c.ByteChannel == nil { + return 0, errors.New("channel writer channel cannot be nil") + } + c.ByteChannel <- w + return len(w), nil +} + +// Close method for Writer +func (c *Writer) Close() error { + close(c.ByteChannel) + return nil +} diff --git a/pkg/errorhandling/errorhandling.go b/pkg/errorhandling/errorhandling.go new file mode 100644 index 000000000..970d47636 --- /dev/null +++ b/pkg/errorhandling/errorhandling.go @@ -0,0 +1,23 @@ +package errorhandling + +import ( + "os" + + "github.com/sirupsen/logrus" +) + +// SyncQuiet syncs a file and logs any error. Should only be used within +// a defer. +func SyncQuiet(f *os.File) { + if err := f.Sync(); err != nil { + logrus.Errorf("unable to sync file %s: %q", f.Name(), err) + } +} + +// CloseQuiet closes a file and logs any error. Should only be used within +// a defer. +func CloseQuiet(f *os.File) { + if err := f.Close(); err != nil { + logrus.Errorf("unable to close file %s: %q", f.Name(), err) + } +} diff --git a/pkg/firewall/firewalld.go b/pkg/firewall/firewalld.go index 32c2337a0..15e845cb7 100644 --- a/pkg/firewall/firewalld.go +++ b/pkg/firewall/firewalld.go @@ -18,6 +18,7 @@ package firewall import ( "fmt" + "github.com/sirupsen/logrus" "strings" "github.com/godbus/dbus" @@ -113,7 +114,9 @@ func (fb *fwdBackend) Del(conf *FirewallNetConf) error { // Remove firewalld rules which assigned the given source IP to the given zone firewalldObj := fb.conn.Object(firewalldName, firewalldPath) var res string - firewalldObj.Call(firewalldZoneInterface+"."+firewalldRemoveSourceMethod, 0, getFirewalldZone(conf), ipStr).Store(&res) + if err := firewalldObj.Call(firewalldZoneInterface+"."+firewalldRemoveSourceMethod, 0, getFirewalldZone(conf), ipStr).Store(&res); err != nil { + logrus.Errorf("unable to store firewallobj") + } } return nil } diff --git a/pkg/firewall/iptables.go b/pkg/firewall/iptables.go index 59d81b287..92d249f7b 100644 --- a/pkg/firewall/iptables.go +++ b/pkg/firewall/iptables.go @@ -21,6 +21,7 @@ package firewall import ( "fmt" + "github.com/sirupsen/logrus" "net" "github.com/coreos/go-iptables/iptables" @@ -53,7 +54,9 @@ func generateFilterRule(privChainName string) []string { func cleanupRules(ipt *iptables.IPTables, privChainName string, rules [][]string) { for _, rule := range rules { - ipt.Delete("filter", privChainName, rule...) + if err := ipt.Delete("filter", privChainName, rule...); err != nil { + logrus.Errorf("failed to delete iptables rule %s", privChainName) + } } } @@ -185,7 +188,9 @@ func (ib *iptablesBackend) Add(conf *FirewallNetConf) error { func (ib *iptablesBackend) Del(conf *FirewallNetConf) error { for proto, ipt := range ib.protos { - ib.delRules(conf, ipt, proto) + if err := ib.delRules(conf, ipt, proto); err != nil { + logrus.Errorf("failed to delete iptables backend rule %s", conf.IptablesAdminChainName) + } } return nil } diff --git a/pkg/hooks/exec/exec.go b/pkg/hooks/exec/exec.go index 0dd091561..4038e3d94 100644 --- a/pkg/hooks/exec/exec.go +++ b/pkg/hooks/exec/exec.go @@ -5,6 +5,7 @@ import ( "bytes" "context" "fmt" + "github.com/sirupsen/logrus" "io" osexec "os/exec" "time" @@ -54,7 +55,9 @@ func Run(ctx context.Context, hook *rspec.Hook, state []byte, stdout io.Writer, case err = <-exit: return err, err case <-ctx.Done(): - cmd.Process.Kill() + if err := cmd.Process.Kill(); err != nil { + logrus.Errorf("failed to kill pid %v", cmd.Process) + } timer := time.NewTimer(postKillTimeout) defer timer.Stop() select { diff --git a/pkg/logs/logs.go b/pkg/logs/logs.go index 0f684750e..89e4e5686 100644 --- a/pkg/logs/logs.go +++ b/pkg/logs/logs.go @@ -30,6 +30,7 @@ import ( "github.com/containers/libpod/libpod" "github.com/containers/libpod/libpod/define" + "github.com/containers/libpod/pkg/errorhandling" "github.com/pkg/errors" "github.com/sirupsen/logrus" ) @@ -153,7 +154,7 @@ func ReadLogs(logPath string, ctr *libpod.Container, opts *LogOptions) error { if err != nil { return errors.Wrapf(err, "failed to open log file %q", logPath) } - defer file.Close() + defer errorhandling.CloseQuiet(file) msg := &logMessage{} opts.bytes = -1 @@ -161,9 +162,9 @@ func ReadLogs(logPath string, ctr *libpod.Container, opts *LogOptions) error { reader := bufio.NewReader(file) if opts.Follow { - followLog(reader, writer, opts, ctr, msg, logPath) + err = followLog(reader, writer, opts, ctr, msg, logPath) } else { - dumpLog(reader, writer, opts, msg, logPath) + err = dumpLog(reader, writer, opts, msg, logPath) } return err } diff --git a/pkg/netns/netns_linux.go b/pkg/netns/netns_linux.go index a72a2d098..1d6fb873c 100644 --- a/pkg/netns/netns_linux.go +++ b/pkg/netns/netns_linux.go @@ -28,6 +28,7 @@ import ( "sync" "github.com/containernetworking/plugins/pkg/ns" + "github.com/sirupsen/logrus" "golang.org/x/sys/unix" ) @@ -83,12 +84,16 @@ func NewNS() (ns.NetNS, error) { if err != nil { return nil, err } - mountPointFd.Close() + if err := mountPointFd.Close(); err != nil { + return nil, err + } // Ensure the mount point is cleaned up on errors; if the namespace // was successfully mounted this will have no effect because the file // is in-use - defer os.RemoveAll(nsPath) + defer func() { + _ = os.RemoveAll(nsPath) + }() var wg sync.WaitGroup wg.Add(1) @@ -107,7 +112,11 @@ func NewNS() (ns.NetNS, error) { if err != nil { return } - defer origNS.Close() + defer func() { + if err := origNS.Close(); err != nil { + logrus.Errorf("unable to close namespace: %q", err) + } + }() // create a new netns on the current thread err = unix.Unshare(unix.CLONE_NEWNET) @@ -116,7 +125,11 @@ func NewNS() (ns.NetNS, error) { } // Put this thread back to the orig ns, since it might get reused (pre go1.10) - defer origNS.Set() + defer func() { + if err := origNS.Set(); err != nil { + logrus.Errorf("unable to set namespace: %q", err) + } + }() // bind mount the netns from the current thread (from /proc) onto the // mount point. This causes the namespace to persist, even when there diff --git a/pkg/rootless/rootless_linux.go b/pkg/rootless/rootless_linux.go index 8028a359c..99a0eb729 100644 --- a/pkg/rootless/rootless_linux.go +++ b/pkg/rootless/rootless_linux.go @@ -17,6 +17,7 @@ import ( "syscall" "unsafe" + "github.com/containers/libpod/pkg/errorhandling" "github.com/containers/storage/pkg/idtools" "github.com/docker/docker/pkg/signal" "github.com/godbus/dbus" @@ -41,8 +42,7 @@ const ( ) func runInUser() error { - os.Setenv("_CONTAINERS_USERNS_CONFIGURED", "done") - return nil + return os.Setenv("_CONTAINERS_USERNS_CONFIGURED", "done") } var ( @@ -57,9 +57,15 @@ func IsRootless() bool { rootlessGIDInit := int(C.rootless_gid()) if rootlessUIDInit != 0 { // This happens if we joined the user+mount namespace as part of - os.Setenv("_CONTAINERS_USERNS_CONFIGURED", "done") - os.Setenv("_CONTAINERS_ROOTLESS_UID", fmt.Sprintf("%d", rootlessUIDInit)) - os.Setenv("_CONTAINERS_ROOTLESS_GID", fmt.Sprintf("%d", rootlessGIDInit)) + if err := os.Setenv("_CONTAINERS_USERNS_CONFIGURED", "done"); err != nil { + logrus.Errorf("failed to set environment variable %s as %s", "_CONTAINERS_USERNS_CONFIGURED", "done") + } + if err := os.Setenv("_CONTAINERS_ROOTLESS_UID", fmt.Sprintf("%d", rootlessUIDInit)); err != nil { + logrus.Errorf("failed to set environment variable %s as %d", "_CONTAINERS_ROOTLESS_UID", rootlessUIDInit) + } + if err := os.Setenv("_CONTAINERS_ROOTLESS_GID", fmt.Sprintf("%d", rootlessGIDInit)); err != nil { + logrus.Errorf("failed to set environment variable %s as %d", "_CONTAINERS_ROOTLESS_GID", rootlessGIDInit) + } } isRootless = os.Geteuid() != 0 || os.Getenv("_CONTAINERS_USERNS_CONFIGURED") != "" }) @@ -185,18 +191,24 @@ func getUserNSFirstChild(fd uintptr) (*os.File, error) { } if ns == currentNS { - syscall.Close(int(nextFd)) + if err := syscall.Close(int(nextFd)); err != nil { + return nil, err + } // Drop O_CLOEXEC for the fd. _, _, errno := syscall.Syscall(syscall.SYS_FCNTL, fd, syscall.F_SETFD, 0) if errno != 0 { - syscall.Close(int(fd)) + if err := syscall.Close(int(fd)); err != nil { + logrus.Errorf("failed to close file descriptor %d", fd) + } return nil, errno } return os.NewFile(fd, "userns child"), nil } - syscall.Close(int(fd)) + if err := syscall.Close(int(fd)); err != nil { + return nil, err + } fd = nextFd } } @@ -208,7 +220,11 @@ func EnableLinger() (string, error) { conn, err := dbus.SystemBus() if err == nil { - defer conn.Close() + defer func() { + if err := conn.Close(); err != nil { + logrus.Errorf("unable to close dbus connection: %q", err) + } + }() } lingerEnabled := false @@ -252,7 +268,9 @@ func EnableLinger() (string, error) { if lingerEnabled && lingerFile != "" { f, err := os.Create(lingerFile) if err == nil { - f.Close() + if err := f.Close(); err != nil { + logrus.Errorf("failed to close %s", f.Name()) + } } else { logrus.Debugf("could not create linger file: %v", err) } @@ -296,13 +314,21 @@ func joinUserAndMountNS(pid uint, pausePid string) (bool, int, error) { if err != nil { return false, -1, err } - defer userNS.Close() + defer func() { + if err := userNS.Close(); err != nil { + logrus.Errorf("unable to close namespace: %q", err) + } + }() mountNS, err := os.Open(fmt.Sprintf("/proc/%d/ns/mnt", pid)) if err != nil { return false, -1, err } - defer userNS.Close() + defer func() { + if err := mountNS.Close(); err != nil { + logrus.Errorf("unable to close namespace: %q", err) + } + }() fd, err := getUserNSFirstChild(userNS.Fd()) if err != nil { @@ -348,9 +374,13 @@ func becomeRootInUserNS(pausePid, fileToRead string, fileOutput *os.File) (bool, } r, w := os.NewFile(uintptr(fds[0]), "sync host"), os.NewFile(uintptr(fds[1]), "sync child") - defer r.Close() - defer w.Close() - defer w.Write([]byte("0")) + defer errorhandling.CloseQuiet(r) + defer errorhandling.CloseQuiet(w) + defer func() { + if _, err := w.Write([]byte("0")); err != nil { + logrus.Errorf("failed to write byte 0: %q", err) + } + }() pidC := C.reexec_in_user_namespace(C.int(r.Fd()), cPausePid, cFileToRead, fileOutputFD) pid := int(pidC) @@ -361,9 +391,9 @@ func becomeRootInUserNS(pausePid, fileToRead string, fileOutput *os.File) (bool, var uids, gids []idtools.IDMap username := os.Getenv("USER") if username == "" { - user, err := user.LookupId(fmt.Sprintf("%d", os.Getuid())) + userID, err := user.LookupId(fmt.Sprintf("%d", os.Getuid())) if err == nil { - username = user.Username + username = userID.Username } } mappings, err := idtools.NewIDMappings(username, username) @@ -458,7 +488,9 @@ func becomeRootInUserNS(pausePid, fileToRead string, fileOutput *os.File) (bool, continue } - syscall.Kill(int(pidC), s.(syscall.Signal)) + if err := syscall.Kill(int(pidC), s.(syscall.Signal)); err != nil { + logrus.Errorf("failed to kill %d", int(pidC)) + } } }() @@ -519,17 +551,19 @@ func TryJoinFromFilePaths(pausePidPath string, needNewNamespace bool, paths []st r, w := os.NewFile(uintptr(fds[0]), "read file"), os.NewFile(uintptr(fds[1]), "write file") - defer w.Close() - defer r.Close() + defer errorhandling.CloseQuiet(w) + defer errorhandling.CloseQuiet(r) if _, _, err := becomeRootInUserNS("", path, w); err != nil { lastErr = err continue } - w.Close() + if err := w.Close(); err != nil { + return false, 0, err + } defer func() { - r.Close() + errorhandling.CloseQuiet(r) C.reexec_in_user_namespace_wait(-1, 0) }() diff --git a/pkg/spec/spec.go b/pkg/spec/spec.go index 06d1ac12d..d44beb3e4 100644 --- a/pkg/spec/spec.go +++ b/pkg/spec/spec.go @@ -20,6 +20,12 @@ import ( const cpuPeriod = 100000 +type systemUlimit struct { + name string + max uint64 + cur uint64 +} + func getAvailableGids() (int64, error) { idMap, err := user.ParseIDMapFile("/proc/self/gid_map") if err != nil { @@ -475,7 +481,9 @@ func addPidNS(config *CreateConfig, g *generate.Generator) error { func addUserNS(config *CreateConfig, g *generate.Generator) error { if IsNS(string(config.UsernsMode)) { - g.AddOrReplaceLinuxNamespace(spec.UserNamespace, NS(string(config.UsernsMode))) + if err := g.AddOrReplaceLinuxNamespace(spec.UserNamespace, NS(string(config.UsernsMode))); err != nil { + return err + } // runc complains if no mapping is specified, even if we join another ns. So provide a dummy mapping g.AddLinuxUIDMapping(uint32(0), uint32(0), uint32(1)) @@ -483,7 +491,9 @@ func addUserNS(config *CreateConfig, g *generate.Generator) error { } if (len(config.IDMappings.UIDMap) > 0 || len(config.IDMappings.GIDMap) > 0) && !config.UsernsMode.IsHost() { - g.AddOrReplaceLinuxNamespace(spec.UserNamespace, "") + if err := g.AddOrReplaceLinuxNamespace(spec.UserNamespace, ""); err != nil { + return err + } } return nil } @@ -553,6 +563,20 @@ func addRlimits(config *CreateConfig, g *generate.Generator) error { ) for _, u := range config.Resources.Ulimit { + if u == "host" { + if len(config.Resources.Ulimit) != 1 { + return errors.New("ulimit can use host only once") + } + hostLimits, err := getHostRlimits() + if err != nil { + return err + } + for _, i := range hostLimits { + g.AddProcessRlimits(i.name, i.max, i.cur) + } + break + } + ul, err := units.ParseUlimit(u) if err != nil { return errors.Wrapf(err, "ulimit option %q requires name=SOFT:HARD, failed to be parsed", u) diff --git a/pkg/spec/spec_linux.go b/pkg/spec/spec_linux.go new file mode 100644 index 000000000..fcdfc5c4e --- /dev/null +++ b/pkg/spec/spec_linux.go @@ -0,0 +1,42 @@ +//+build linux + +package createconfig + +import ( + "syscall" + + "github.com/pkg/errors" +) + +type systemRlimit struct { + name string + value int +} + +var systemLimits = []systemRlimit{ + {"RLIMIT_AS", syscall.RLIMIT_AS}, + {"RLIMIT_CORE", syscall.RLIMIT_CORE}, + {"RLIMIT_CPU", syscall.RLIMIT_CPU}, + {"RLIMIT_DATA", syscall.RLIMIT_DATA}, + {"RLIMIT_FSIZE", syscall.RLIMIT_FSIZE}, + {"RLIMIT_NOFILE", syscall.RLIMIT_NOFILE}, + {"RLIMIT_STACK", syscall.RLIMIT_STACK}, +} + +func getHostRlimits() ([]systemUlimit, error) { + ret := []systemUlimit{} + for _, i := range systemLimits { + var l syscall.Rlimit + if err := syscall.Getrlimit(i.value, &l); err != nil { + return nil, errors.Wrapf(err, "cannot read limits for %s", i.name) + } + s := systemUlimit{ + name: i.name, + max: l.Max, + cur: l.Cur, + } + ret = append(ret, s) + } + return ret, nil + +} diff --git a/pkg/spec/spec_unsupported.go b/pkg/spec/spec_unsupported.go new file mode 100644 index 000000000..0f6a9acdc --- /dev/null +++ b/pkg/spec/spec_unsupported.go @@ -0,0 +1,7 @@ +//+build !linux + +package createconfig + +func getHostRlimits() ([]systemUlimit, error) { + return nil, nil +} diff --git a/pkg/util/utils.go b/pkg/util/utils.go index 9e49f08a0..fba34a337 100644 --- a/pkg/util/utils.go +++ b/pkg/util/utils.go @@ -12,12 +12,14 @@ import ( "github.com/BurntSushi/toml" "github.com/containers/image/types" "github.com/containers/libpod/cmd/podman/cliconfig" + "github.com/containers/libpod/pkg/errorhandling" "github.com/containers/libpod/pkg/namespaces" "github.com/containers/libpod/pkg/rootless" "github.com/containers/storage" "github.com/containers/storage/pkg/idtools" "github.com/opencontainers/image-spec/specs-go/v1" "github.com/pkg/errors" + "github.com/sirupsen/logrus" "github.com/spf13/pflag" "golang.org/x/crypto/ssh/terminal" ) @@ -272,16 +274,20 @@ func getTomlStorage(storeOptions *storage.StoreOptions) *tomlConfig { // WriteStorageConfigFile writes the configuration to a file func WriteStorageConfigFile(storageOpts *storage.StoreOptions, storageConf string) error { - os.MkdirAll(filepath.Dir(storageConf), 0755) - file, err := os.OpenFile(storageConf, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0666) + if err := os.MkdirAll(filepath.Dir(storageConf), 0755); err != nil { + return err + } + storageFile, err := os.OpenFile(storageConf, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0666) if err != nil { return errors.Wrapf(err, "cannot open %s", storageConf) } tomlConfiguration := getTomlStorage(storageOpts) - defer file.Close() - enc := toml.NewEncoder(file) + defer errorhandling.CloseQuiet(storageFile) + enc := toml.NewEncoder(storageFile) if err := enc.Encode(tomlConfiguration); err != nil { - os.Remove(storageConf) + if err := os.Remove(storageConf); err != nil { + logrus.Errorf("unable to remove file %s", storageConf) + } return err } return nil diff --git a/pkg/util/utils_supported.go b/pkg/util/utils_supported.go index 99c9e4f1e..af55689a6 100644 --- a/pkg/util/utils_supported.go +++ b/pkg/util/utils_supported.go @@ -13,6 +13,7 @@ import ( "github.com/containers/libpod/pkg/rootless" "github.com/pkg/errors" + "github.com/sirupsen/logrus" ) // GetRootlessRuntimeDir returns the runtime directory when running as non root @@ -24,7 +25,9 @@ func GetRootlessRuntimeDir() (string, error) { uid := fmt.Sprintf("%d", rootless.GetRootlessUID()) if runtimeDir == "" { tmpDir := filepath.Join("/run", "user", uid) - os.MkdirAll(tmpDir, 0700) + if err := os.MkdirAll(tmpDir, 0700); err != nil { + logrus.Errorf("unable to make temp dir %s", tmpDir) + } st, err := os.Stat(tmpDir) if err == nil && int(st.Sys().(*syscall.Stat_t).Uid) == os.Geteuid() && st.Mode().Perm() == 0700 { runtimeDir = tmpDir @@ -32,7 +35,9 @@ func GetRootlessRuntimeDir() (string, error) { } if runtimeDir == "" { tmpDir := filepath.Join(os.TempDir(), fmt.Sprintf("run-%s", uid)) - os.MkdirAll(tmpDir, 0700) + if err := os.MkdirAll(tmpDir, 0700); err != nil { + logrus.Errorf("unable to make temp dir %s", tmpDir) + } st, err := os.Stat(tmpDir) if err == nil && int(st.Sys().(*syscall.Stat_t).Uid) == os.Geteuid() && st.Mode().Perm() == 0700 { runtimeDir = tmpDir diff --git a/pkg/varlinkapi/images.go b/pkg/varlinkapi/images.go index 0e2ad6bbf..2bebfd406 100644 --- a/pkg/varlinkapi/images.go +++ b/pkg/varlinkapi/images.go @@ -25,6 +25,7 @@ import ( "github.com/containers/libpod/libpod" "github.com/containers/libpod/libpod/define" "github.com/containers/libpod/libpod/image" + "github.com/containers/libpod/pkg/channelwriter" "github.com/containers/libpod/pkg/util" "github.com/containers/libpod/utils" "github.com/containers/storage/pkg/archive" @@ -495,9 +496,19 @@ func (i *LibpodAPI) DeleteUnusedImages(call iopodman.VarlinkCall) error { // Commit ... func (i *LibpodAPI) Commit(call iopodman.VarlinkCall, name, imageName string, changes []string, author, message string, pause bool, manifestType string) error { - var newImage *image.Image + var ( + newImage *image.Image + log []string + mimeType string + ) + output := channelwriter.NewChannelWriter() + channelClose := func() { + if err := output.Close(); err != nil { + logrus.Errorf("failed to close channel writer: %q", err) + } + } + defer channelClose() - output := bytes.NewBuffer([]byte{}) ctr, err := i.Runtime.LookupContainer(name) if err != nil { return call.ReplyContainerNotFound(name, err.Error()) @@ -507,7 +518,6 @@ func (i *LibpodAPI) Commit(call iopodman.VarlinkCall, name, imageName string, ch return call.ReplyErrorOccurred(err.Error()) } sc := image.GetSystemContext(rtc.SignaturePolicyPath, "", false) - var mimeType string switch manifestType { case "oci", "": //nolint mimeType = buildah.OCIv1ImageManifest @@ -535,6 +545,7 @@ func (i *LibpodAPI) Commit(call iopodman.VarlinkCall, name, imageName string, ch } c := make(chan error) + defer close(c) go func() { newImage, err = ctr.Commit(getContext(), imageName, options) @@ -542,48 +553,22 @@ func (i *LibpodAPI) Commit(call iopodman.VarlinkCall, name, imageName string, ch c <- err } c <- nil - close(c) }() - var log []string - done := false - for { - line, err := output.ReadString('\n') - if err == nil { - log = append(log, line) - continue - } else if err == io.EOF { - select { - case err := <-c: - if err != nil { - logrus.Errorf("reading of output during commit failed for %s", name) - return call.ReplyErrorOccurred(err.Error()) - } - done = true - default: - if !call.WantsMore() { - break - } - br := iopodman.MoreResponse{ - Logs: log, - } - call.ReplyCommit(br) - log = []string{} - } - } else { - return call.ReplyErrorOccurred(err.Error()) - } - if done { - break - } + // reply is the func being sent to the output forwarder. in this case it is replying + // with a more response struct + reply := func(br iopodman.MoreResponse) error { + return call.ReplyCommit(br) + } + log, err = forwardOutput(log, c, call.WantsMore(), output, reply) + if err != nil { + return call.ReplyErrorOccurred(err.Error()) } call.Continues = false - br := iopodman.MoreResponse{ Logs: log, Id: newImage.ID(), } - return call.ReplyCommit(br) } @@ -636,6 +621,7 @@ func (i *LibpodAPI) ExportImage(call iopodman.VarlinkCall, name, destination str func (i *LibpodAPI) PullImage(call iopodman.VarlinkCall, name string) error { var ( imageID string + err error ) dockerRegistryOptions := image.DockerRegistryOptions{} so := image.SigningOptions{} @@ -643,8 +629,16 @@ func (i *LibpodAPI) PullImage(call iopodman.VarlinkCall, name string) error { if call.WantsMore() { call.Continues = true } - output := bytes.NewBuffer([]byte{}) + output := channelwriter.NewChannelWriter() + channelClose := func() { + if err := output.Close(); err != nil { + logrus.Errorf("failed to close channel writer: %q", err) + } + } + defer channelClose() c := make(chan error) + defer close(c) + go func() { if strings.HasPrefix(name, dockerarchive.Transport.Name()+":") { srcRef, err := alltransports.ParseImageName(name) @@ -666,43 +660,17 @@ func (i *LibpodAPI) PullImage(call iopodman.VarlinkCall, name string) error { } } c <- nil - close(c) }() var log []string - done := false - for { - line, err := output.ReadString('\n') - if err == nil { - log = append(log, line) - continue - } else if err == io.EOF { - select { - case err := <-c: - if err != nil { - logrus.Errorf("reading of output during pull failed for %s", name) - return call.ReplyErrorOccurred(err.Error()) - } - done = true - default: - if !call.WantsMore() { - break - } - br := iopodman.MoreResponse{ - Logs: log, - } - call.ReplyPullImage(br) - log = []string{} - } - } else { - return call.ReplyErrorOccurred(err.Error()) - } - if done { - break - } + reply := func(br iopodman.MoreResponse) error { + return call.ReplyPullImage(br) + } + log, err = forwardOutput(log, c, call.WantsMore(), output, reply) + if err != nil { + return call.ReplyErrorOccurred(err.Error()) } call.Continues = false - br := iopodman.MoreResponse{ Logs: log, Id: imageID, diff --git a/pkg/varlinkapi/util.go b/pkg/varlinkapi/util.go index a74105795..e8f74e6aa 100644 --- a/pkg/varlinkapi/util.go +++ b/pkg/varlinkapi/util.go @@ -13,6 +13,7 @@ import ( "github.com/containers/libpod/cmd/podman/varlink" "github.com/containers/libpod/libpod" "github.com/containers/libpod/libpod/define" + "github.com/containers/libpod/pkg/channelwriter" "github.com/containers/storage/pkg/archive" ) @@ -196,3 +197,42 @@ func makePsOpts(inOpts iopodman.PsOpts) shared.PsOptions { Sync: derefBool(inOpts.Sync), } } + +// forwardOutput is a helper method for varlink endpoints that employ both more and without +// more. it is capable of sending updates as the output writer gets them or append them +// all to a log. the chan error is the error from the libpod call so we can honor +// and error event in that case. +func forwardOutput(log []string, c chan error, wantsMore bool, output *channelwriter.Writer, reply func(br iopodman.MoreResponse) error) ([]string, error) { + done := false + for { + select { + // We need to check if the libpod func being called has returned an + // error yet + case err := <-c: + if err != nil { + return nil, err + } + done = true + // if no error is found, we pull what we can from the log writer and + // append it to log string slice + case line := <-output.ByteChannel: + log = append(log, string(line)) + // If the end point is being used in more mode, send what we have + if wantsMore { + br := iopodman.MoreResponse{ + Logs: log, + } + if err := reply(br); err != nil { + return nil, err + } + // "reset" the log to empty because we are sending what we + // get as we get it + log = []string{} + } + } + if done { + break + } + } + return log, nil +} |