Files
moby/daemon/archive_tarcopyoptions_unix.go
Derek McGowan f74e5d48b3 Create github.com/moby/moby/v2 module
Signed-off-by: Derek McGowan <derek@mcg.dev>
2025-07-31 10:13:29 -07:00

132 lines
4.0 KiB
Go

//go:build !windows
package daemon
import (
"fmt"
"math"
"strconv"
"strings"
"github.com/moby/go-archive"
"github.com/moby/moby/v2/daemon/container"
"github.com/moby/moby/v2/errdefs"
"github.com/moby/sys/user"
)
func (daemon *Daemon) tarCopyOptions(ctr *container.Container, allowOverwriteDirWithFile bool) (*archive.TarOptions, error) {
if ctr.Config.User == "" {
return daemon.defaultTarCopyOptions(allowOverwriteDirWithFile), nil
}
uid, gid, err := getUIDGID(ctr.Config.User)
if err != nil {
return nil, errdefs.InvalidParameter(err)
}
return &archive.TarOptions{
NoOverwriteDirNonDir: !allowOverwriteDirWithFile,
ChownOpts: &archive.ChownOpts{UID: uid, GID: gid},
}, nil
}
// getUIDGID resolves the UID and GID of a given container's Config.User,
// which can contain a user name (or ID) and, optionally, group (or ID).
//
// usergrp is a username or uid, and optional group, in the format `user[:group]`.
// Both `user` and `group` can be provided as an `uid` / `gid`, so the following
// formats are supported:
//
// - username - valid username from /etc/passwd
// - username:groupname - valid username; valid groupname from /etc/passwd, /etc/group
// - uid - 32-bit unsigned int valid Linux UID value
// - uid:gid - uid value; 32-bit unsigned int Linux GID value
// - username:gid - valid username from getent(1), gid value; 32-bit unsigned int Linux GID value
// - uid:groupname - 32-bit unsigned int valid Linux UID value, valid groupname from /etc/group
//
// If only a username (or uid) is provided, an attempt is made to look up the gid
// for that username using /etc/passwd
func getUIDGID(ctrUser string) (uid int, gid int, _ error) {
userNameOrID, groupNameOrID, _ := strings.Cut(ctrUser, ":")
// Align with behavior of docker run, which treats an empty username
// or groupname as default (0 (root)).
//
// docker run --rm --user ":33" alpine id
// uid=0(root) gid=33 groups=33
//
// docker run --rm --user "33:" alpine id
// uid=33 gid=0(root) groups=0(root)
if userNameOrID != "" {
var err error
uid, gid, err = lookupUser(userNameOrID)
if err != nil {
return 0, 0, err
}
}
if groupNameOrID != "" {
var err error
gid, err = lookupGID(groupNameOrID)
if err != nil {
return 0, 0, err
}
}
return uid, gid, nil
}
// getIDOrName checks whether nameOrID is a ID (integer) or a Name.
// It assumes nameOrID is a name when failing to parse as integer,
// in which case a non-empty name is returned.
func getIDOrName(nameOrID string) (id int, name string) {
if uid, err := strconv.ParseUint(nameOrID, 10, 32); err == nil && uid <= math.MaxInt32 {
// uid provided
return int(uid), ""
}
// not an id, assume name
return 0, nameOrID
}
func lookupUser(nameOrID string) (uid, gid int, _ error) {
userID, userName := getIDOrName(nameOrID)
if userName != "" {
u, err := user.LookupUser(userName)
if err != nil {
return 0, 0, fmt.Errorf("failed to look up user %q in container: %w", userName, err)
}
return u.Uid, u.Gid, nil
}
u, err := user.LookupUid(userID)
if err != nil {
// Match behavior of "docker run": when using a UID for the
// user, resolving the user and its primary group is best-effort.
// If a user with the given UID is found, we use its primary
// group, otherwise use it as-is and use the default (0) as
// GID.
//
// docker run --rm --user 12345 ubuntu id
// uid=12345 gid=0(root) groups=0(root)
//
// docker run --rm ubuntu cat /etc/passwd | grep www-data
// www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
//
// docker run --rm --user 33 ubuntu id
// uid=33(www-data) gid=33(www-data) groups=33(www-data)
return userID, 0, nil
}
return u.Uid, u.Gid, nil
}
func lookupGID(nameOrID string) (int, error) {
groupID, groupName := getIDOrName(nameOrID)
if groupName == "" {
// GID is passed, no need to look up
return groupID, nil
}
group, err := user.LookupGroup(groupName)
if err != nil {
return 0, fmt.Errorf("failed to look up group %q in container: %w", nameOrID, err)
}
return group.Gid, nil
}