remote copy

Implement `podman-remote cp` and break out the logic from the previously
added `pkg/copy` into it's basic building blocks and move them up into
the `ContainerEngine` interface and `cmd/podman`.

The `--pause` and `--extract` flags are now deprecated and turned into
nops.

Note that this commit is vendoring a non-release version of Buildah to
pull in updates to the copier package.

Signed-off-by: Valentin Rothberg <rothberg@redhat.com>
This commit is contained in:
Valentin Rothberg
2020-12-11 11:00:25 +01:00
parent f56865879c
commit adcb3a7a60
32 changed files with 1247 additions and 1160 deletions

View File

@@ -10,6 +10,7 @@ import (
"net"
"os"
"os/user"
"path"
"path/filepath"
"strconv"
"strings"
@@ -202,11 +203,11 @@ type StatOptions struct {
// If root and directory are both not specified, the current root directory is
// used, and relative names in the globs list are treated as being relative to
// the current working directory.
// If root is specified and the current OS supports it, the stat() is performed
// in a chrooted context. If the directory is specified as an absolute path,
// it should either be the root directory or a subdirectory of the root
// directory. Otherwise, the directory is treated as a path relative to the
// root directory.
// If root is specified and the current OS supports it, and the calling process
// has the necessary privileges, the stat() is performed in a chrooted context.
// If the directory is specified as an absolute path, it should either be the
// root directory or a subdirectory of the root directory. Otherwise, the
// directory is treated as a path relative to the root directory.
// Relative names in the glob list are treated as being relative to the
// directory.
func Stat(root string, directory string, options StatOptions, globs []string) ([]*StatsForGlob, error) {
@@ -229,18 +230,19 @@ func Stat(root string, directory string, options StatOptions, globs []string) ([
// GetOptions controls parts of Get()'s behavior.
type GetOptions struct {
UIDMap, GIDMap []idtools.IDMap // map from hostIDs to containerIDs in the output archive
Excludes []string // contents to pretend don't exist, using the OS-specific path separator
ExpandArchives bool // extract the contents of named items that are archives
ChownDirs *idtools.IDPair // set ownership on directories. no effect on archives being extracted
ChmodDirs *os.FileMode // set permissions on directories. no effect on archives being extracted
ChownFiles *idtools.IDPair // set ownership of files. no effect on archives being extracted
ChmodFiles *os.FileMode // set permissions on files. no effect on archives being extracted
StripSetuidBit bool // strip the setuid bit off of items being copied. no effect on archives being extracted
StripSetgidBit bool // strip the setgid bit off of items being copied. no effect on archives being extracted
StripStickyBit bool // strip the sticky bit off of items being copied. no effect on archives being extracted
StripXattrs bool // don't record extended attributes of items being copied. no effect on archives being extracted
KeepDirectoryNames bool // don't strip the top directory's basename from the paths of items in subdirectories
UIDMap, GIDMap []idtools.IDMap // map from hostIDs to containerIDs in the output archive
Excludes []string // contents to pretend don't exist, using the OS-specific path separator
ExpandArchives bool // extract the contents of named items that are archives
ChownDirs *idtools.IDPair // set ownership on directories. no effect on archives being extracted
ChmodDirs *os.FileMode // set permissions on directories. no effect on archives being extracted
ChownFiles *idtools.IDPair // set ownership of files. no effect on archives being extracted
ChmodFiles *os.FileMode // set permissions on files. no effect on archives being extracted
StripSetuidBit bool // strip the setuid bit off of items being copied. no effect on archives being extracted
StripSetgidBit bool // strip the setgid bit off of items being copied. no effect on archives being extracted
StripStickyBit bool // strip the sticky bit off of items being copied. no effect on archives being extracted
StripXattrs bool // don't record extended attributes of items being copied. no effect on archives being extracted
KeepDirectoryNames bool // don't strip the top directory's basename from the paths of items in subdirectories
Rename map[string]string // rename items with the specified names, or under the specified names
}
// Get produces an archive containing items that match the specified glob
@@ -248,11 +250,11 @@ type GetOptions struct {
// If root and directory are both not specified, the current root directory is
// used, and relative names in the globs list are treated as being relative to
// the current working directory.
// If root is specified and the current OS supports it, the contents are read
// in a chrooted context. If the directory is specified as an absolute path,
// it should either be the root directory or a subdirectory of the root
// directory. Otherwise, the directory is treated as a path relative to the
// root directory.
// If root is specified and the current OS supports it, and the calling process
// has the necessary privileges, the contents are read in a chrooted context.
// If the directory is specified as an absolute path, it should either be the
// root directory or a subdirectory of the root directory. Otherwise, the
// directory is treated as a path relative to the root directory.
// Relative names in the glob list are treated as being relative to the
// directory.
func Get(root string, directory string, options GetOptions, globs []string, bulkWriter io.Writer) error {
@@ -278,25 +280,28 @@ func Get(root string, directory string, options GetOptions, globs []string, bulk
// PutOptions controls parts of Put()'s behavior.
type PutOptions struct {
UIDMap, GIDMap []idtools.IDMap // map from containerIDs to hostIDs when writing contents to disk
DefaultDirOwner *idtools.IDPair // set ownership of implicitly-created directories, default is ChownDirs, or 0:0 if ChownDirs not set
DefaultDirMode *os.FileMode // set permissions on implicitly-created directories, default is ChmodDirs, or 0755 if ChmodDirs not set
ChownDirs *idtools.IDPair // set ownership of newly-created directories
ChmodDirs *os.FileMode // set permissions on newly-created directories
ChownFiles *idtools.IDPair // set ownership of newly-created files
ChmodFiles *os.FileMode // set permissions on newly-created files
StripXattrs bool // don't bother trying to set extended attributes of items being copied
IgnoreXattrErrors bool // ignore any errors encountered when attempting to set extended attributes
UIDMap, GIDMap []idtools.IDMap // map from containerIDs to hostIDs when writing contents to disk
DefaultDirOwner *idtools.IDPair // set ownership of implicitly-created directories, default is ChownDirs, or 0:0 if ChownDirs not set
DefaultDirMode *os.FileMode // set permissions on implicitly-created directories, default is ChmodDirs, or 0755 if ChmodDirs not set
ChownDirs *idtools.IDPair // set ownership of newly-created directories
ChmodDirs *os.FileMode // set permissions on newly-created directories
ChownFiles *idtools.IDPair // set ownership of newly-created files
ChmodFiles *os.FileMode // set permissions on newly-created files
StripXattrs bool // don't bother trying to set extended attributes of items being copied
IgnoreXattrErrors bool // ignore any errors encountered when attempting to set extended attributes
NoOverwriteDirNonDir bool // instead of quietly overwriting directories with non-directories, return an error
Rename map[string]string // rename items with the specified names, or under the specified names
}
// Put extracts an archive from the bulkReader at the specified directory.
// If root and directory are both not specified, the current root directory is
// used.
// If root is specified and the current OS supports it, the contents are written
// in a chrooted context. If the directory is specified as an absolute path,
// it should either be the root directory or a subdirectory of the root
// directory. Otherwise, the directory is treated as a path relative to the
// root directory.
// If root is specified and the current OS supports it, and the calling process
// has the necessary privileges, the contents are written in a chrooted
// context. If the directory is specified as an absolute path, it should
// either be the root directory or a subdirectory of the root directory.
// Otherwise, the directory is treated as a path relative to the root
// directory.
func Put(root string, directory string, options PutOptions, bulkReader io.Reader) error {
req := request{
Request: requestPut,
@@ -325,11 +330,12 @@ type MkdirOptions struct {
// need to be created will be given the specified ownership and permissions.
// If root and directory are both not specified, the current root directory is
// used.
// If root is specified and the current OS supports it, the directory is
// created in a chrooted context. If the directory is specified as an absolute
// path, it should either be the root directory or a subdirectory of the root
// directory. Otherwise, the directory is treated as a path relative to the
// root directory.
// If root is specified and the current OS supports it, and the calling process
// has the necessary privileges, the directory is created in a chrooted
// context. If the directory is specified as an absolute path, it should
// either be the root directory or a subdirectory of the root directory.
// Otherwise, the directory is treated as a path relative to the root
// directory.
func Mkdir(root string, directory string, options MkdirOptions) error {
req := request{
Request: requestMkdir,
@@ -547,13 +553,13 @@ func copierWithSubprocess(bulkReader io.Reader, bulkWriter io.Writer, req reques
return nil, errors.Wrap(err, step)
}
if err = encoder.Encode(req); err != nil {
return killAndReturn(err, "error encoding request")
return killAndReturn(err, "error encoding request for copier subprocess")
}
if err = decoder.Decode(&resp); err != nil {
return killAndReturn(err, "error decoding response")
return killAndReturn(err, "error decoding response from copier subprocess")
}
if err = encoder.Encode(&request{Request: requestQuit}); err != nil {
return killAndReturn(err, "error encoding request")
return killAndReturn(err, "error encoding request for copier subprocess")
}
stdinWrite.Close()
stdinWrite = nil
@@ -626,7 +632,7 @@ func copierMain() {
// Read a request.
req := new(request)
if err := decoder.Decode(req); err != nil {
fmt.Fprintf(os.Stderr, "error decoding request: %v", err)
fmt.Fprintf(os.Stderr, "error decoding request from copier parent process: %v", err)
os.Exit(1)
}
if req.Request == requestQuit {
@@ -717,12 +723,12 @@ func copierMain() {
}
resp, cb, err := copierHandler(bulkReader, bulkWriter, *req)
if err != nil {
fmt.Fprintf(os.Stderr, "error handling request %#v: %v", *req, err)
fmt.Fprintf(os.Stderr, "error handling request %#v from copier parent process: %v", *req, err)
os.Exit(1)
}
// Encode the response.
if err := encoder.Encode(resp); err != nil {
fmt.Fprintf(os.Stderr, "error encoding response %#v: %v", *req, err)
fmt.Fprintf(os.Stderr, "error encoding response %#v for copier parent process: %v", *req, err)
os.Exit(1)
}
// If there's bulk data to transfer, run the callback to either
@@ -1118,6 +1124,34 @@ func copierHandlerGet(bulkWriter io.Writer, req request, pm *fileutils.PatternMa
return &response{Stat: statResponse.Stat, Get: getResponse{}}, cb, nil
}
func handleRename(rename map[string]string, name string) string {
if rename == nil {
return name
}
// header names always use '/', so use path instead of filepath to manipulate it
if directMapping, ok := rename[name]; ok {
return directMapping
}
prefix, remainder := path.Split(name)
for prefix != "" {
if mappedPrefix, ok := rename[prefix]; ok {
return path.Join(mappedPrefix, remainder)
}
if prefix[len(prefix)-1] == '/' {
if mappedPrefix, ok := rename[prefix[:len(prefix)-1]]; ok {
return path.Join(mappedPrefix, remainder)
}
}
newPrefix, middlePart := path.Split(prefix)
if newPrefix == prefix {
return name
}
prefix = newPrefix
remainder = path.Join(middlePart, remainder)
}
return name
}
func copierHandlerGetOne(srcfi os.FileInfo, symlinkTarget, name, contentPath string, options GetOptions, tw *tar.Writer, hardlinkChecker *util.HardlinkChecker, idMappings *idtools.IDMappings) error {
// build the header using the name provided
hdr, err := tar.FileInfoHeader(srcfi, symlinkTarget)
@@ -1127,6 +1161,9 @@ func copierHandlerGetOne(srcfi os.FileInfo, symlinkTarget, name, contentPath str
if name != "" {
hdr.Name = filepath.ToSlash(name)
}
if options.Rename != nil {
hdr.Name = handleRename(options.Rename, hdr.Name)
}
if options.StripSetuidBit {
hdr.Mode &^= cISUID
}
@@ -1164,6 +1201,9 @@ func copierHandlerGetOne(srcfi os.FileInfo, symlinkTarget, name, contentPath str
tr := tar.NewReader(rc)
hdr, err := tr.Next()
for err == nil {
if options.Rename != nil {
hdr.Name = handleRename(options.Rename, hdr.Name)
}
if err = tw.WriteHeader(hdr); err != nil {
return errors.Wrapf(err, "error writing tar header from %q to pipe", contentPath)
}
@@ -1311,8 +1351,13 @@ func copierHandlerPut(bulkReader io.Reader, req request, idMappings *idtools.IDM
createFile := func(path string, tr *tar.Reader) (int64, error) {
f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_TRUNC|os.O_EXCL, 0600)
if err != nil && os.IsExist(err) {
if err = os.Remove(path); err != nil {
return 0, errors.Wrapf(err, "copier: put: error removing file to be overwritten %q", path)
if req.PutOptions.NoOverwriteDirNonDir {
if st, err2 := os.Lstat(path); err2 == nil && st.IsDir() {
return 0, errors.Wrapf(err, "copier: put: error creating file at %q", path)
}
}
if err = os.RemoveAll(path); err != nil {
return 0, errors.Wrapf(err, "copier: put: error removing item to be overwritten %q", path)
}
f, err = os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_TRUNC|os.O_EXCL, 0600)
}
@@ -1360,6 +1405,14 @@ func copierHandlerPut(bulkReader io.Reader, req request, idMappings *idtools.IDM
tr := tar.NewReader(bulkReader)
hdr, err := tr.Next()
for err == nil {
if len(hdr.Name) == 0 {
// no name -> ignore the entry
hdr, err = tr.Next()
continue
}
if req.PutOptions.Rename != nil {
hdr.Name = handleRename(req.PutOptions.Rename, hdr.Name)
}
// figure out who should own this new item
if idMappings != nil && !idMappings.Empty() {
containerPair := idtools.IDPair{UID: hdr.Uid, GID: hdr.Gid}
@@ -1412,35 +1465,70 @@ func copierHandlerPut(bulkReader io.Reader, req request, idMappings *idtools.IDM
}
case tar.TypeLink:
var linkTarget string
if req.PutOptions.Rename != nil {
hdr.Linkname = handleRename(req.PutOptions.Rename, hdr.Linkname)
}
if linkTarget, err = resolvePath(targetDirectory, filepath.Join(req.Root, filepath.FromSlash(hdr.Linkname)), nil); err != nil {
return errors.Errorf("error resolving hardlink target path %q under root %q", hdr.Linkname, req.Root)
}
if err = os.Link(linkTarget, path); err != nil && os.IsExist(err) {
if req.PutOptions.NoOverwriteDirNonDir {
if st, err := os.Lstat(path); err == nil && st.IsDir() {
break
}
}
if err = os.Remove(path); err == nil {
err = os.Link(linkTarget, path)
}
}
case tar.TypeSymlink:
// if req.PutOptions.Rename != nil {
// todo: the general solution requires resolving to an absolute path, handling
// renaming, and then possibly converting back to a relative symlink
// }
if err = os.Symlink(filepath.FromSlash(hdr.Linkname), filepath.FromSlash(path)); err != nil && os.IsExist(err) {
if req.PutOptions.NoOverwriteDirNonDir {
if st, err := os.Lstat(path); err == nil && st.IsDir() {
break
}
}
if err = os.Remove(path); err == nil {
err = os.Symlink(filepath.FromSlash(hdr.Linkname), filepath.FromSlash(path))
}
}
case tar.TypeChar:
if err = mknod(path, chrMode(0600), int(mkdev(devMajor, devMinor))); err != nil && os.IsExist(err) {
if req.PutOptions.NoOverwriteDirNonDir {
if st, err := os.Lstat(path); err == nil && st.IsDir() {
break
}
}
if err = os.Remove(path); err == nil {
err = mknod(path, chrMode(0600), int(mkdev(devMajor, devMinor)))
}
}
case tar.TypeBlock:
if err = mknod(path, blkMode(0600), int(mkdev(devMajor, devMinor))); err != nil && os.IsExist(err) {
if req.PutOptions.NoOverwriteDirNonDir {
if st, err := os.Lstat(path); err == nil && st.IsDir() {
break
}
}
if err = os.Remove(path); err == nil {
err = mknod(path, blkMode(0600), int(mkdev(devMajor, devMinor)))
}
}
case tar.TypeDir:
if err = os.Mkdir(path, 0700); err != nil && os.IsExist(err) {
err = nil
var st os.FileInfo
if st, err = os.Stat(path); err == nil && !st.IsDir() {
// it's not a directory, so remove it and mkdir
if err = os.Remove(path); err == nil {
err = os.Mkdir(path, 0700)
}
}
// either we removed it and retried, or it was a directory,
// in which case we want to just add the new stuff under it
}
// make a note of the directory's times. we
// might create items under it, which will
@@ -1453,6 +1541,11 @@ func copierHandlerPut(bulkReader io.Reader, req request, idMappings *idtools.IDM
})
case tar.TypeFifo:
if err = mkfifo(path, 0600); err != nil && os.IsExist(err) {
if req.PutOptions.NoOverwriteDirNonDir {
if st, err := os.Lstat(path); err == nil && st.IsDir() {
break
}
}
if err = os.Remove(path); err == nil {
err = mkfifo(path, 0600)
}

View File

@@ -10,7 +10,7 @@ import (
"golang.org/x/sys/unix"
)
var canChroot = true
var canChroot = os.Getuid() == 0
func chroot(root string) (bool, error) {
if canChroot {