mirror of
https://github.com/moby/moby.git
synced 2026-01-11 18:51:37 +00:00
This change changes the default for noOverwriteDirNonDir to be true internally, with the intent to change the default at the API to follow accordingly. The `AllowOverwriteDirWithFile` option in the Client was added when reimplementing the CLI using the API Client lib in [moby@1b2b91b]. Before that refactor, the `noOverwriteDirNonDir` query argument [would be set unconditionally][1] by the CLI, with no options to control the behavior. The `noOverwriteDirNonDir` query parameter was added in [moby@db9cc91] to set the `NoOverwriteDirNonDir` option that was implemented in pkg/archive in [moby@a74799b]. It was added in [PR13171-comment2], following a discussion on the risk of replacing a directory with a file and vice-versa in [PR13171-comment]. > In my latest changes from yesterday: > > - Removed the `GET stat-path` endpoint and added a `HEAD` handler to > the `archive-path` endpoint. Updated the api docs to reflect this. > Also moved api docs changes from `v1.19` to `v1.20`. > - Added a `NoOverwriteDirNonDir` flag to `archive.TarOptions` to indicate > that we do not want to overwrite a directory with a non-directory (and > vice versa) when unpacking an archive. > - Added a corresponding but optional `noOverwriteDirNonDir` parameter > to the `PUT extract-to-dir` endpoint to specify desired behavior. > > These changes combine to keep the behavior we want It's unclear why these were added as an *option* and why it was implemented as opt-in (not opt-out), as overwriting a file with a directory (or vice-versa) would generally be unexpected behavior. [1]:8c9ad7b818/api/client/cp.go (L345-L346)[moby@1b2b91b]:1b2b91ba43[moby@a74799b]:a74799b701[moby@db9cc91]:db9cc91a9e[PR13171-comment]: https://github.com/moby/moby/pull/13171#issuecomment-106559765 [PR13171-comment2]: https://github.com/moby/moby/pull/13171#issuecomment-108538643 Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
132 lines
4.0 KiB
Go
132 lines
4.0 KiB
Go
//go:build !windows
|
|
|
|
package daemon
|
|
|
|
import (
|
|
"fmt"
|
|
"math"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/docker/docker/daemon/container"
|
|
"github.com/docker/docker/errdefs"
|
|
"github.com/moby/go-archive"
|
|
"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
|
|
}
|