2016-09-08 13:11:39 -04:00
package container
import (
2018-05-03 21:02:44 -04:00
"context"
2020-09-04 16:23:13 -04:00
"fmt"
2016-09-08 13:11:39 -04:00
"io"
"os"
"path/filepath"
"strings"
2017-04-17 18:07:56 -04:00
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
2016-09-08 13:11:39 -04:00
"github.com/docker/docker/api/types"
"github.com/docker/docker/pkg/archive"
"github.com/docker/docker/pkg/system"
2020-09-04 16:23:13 -04:00
units "github.com/docker/go-units"
"github.com/morikuni/aec"
2017-03-09 13:23:45 -05:00
"github.com/pkg/errors"
2016-09-08 13:11:39 -04:00
"github.com/spf13/cobra"
)
type copyOptions struct {
source string
destination string
followLink bool
2016-11-14 08:37:08 -05:00
copyUIDGID bool
2020-09-04 16:23:13 -04:00
quiet bool
2016-09-08 13:11:39 -04:00
}
type copyDirection int
const (
2017-10-30 17:52:08 -04:00
fromContainer copyDirection = 1 << iota
2016-09-08 13:11:39 -04:00
toContainer
acrossContainers = fromContainer | toContainer
)
type cpConfig struct {
followLink bool
2017-10-30 17:52:08 -04:00
copyUIDGID bool
2020-09-04 16:23:13 -04:00
quiet bool
2017-10-30 17:52:08 -04:00
sourcePath string
destPath string
container string
2016-09-08 13:11:39 -04:00
}
2020-09-04 16:23:13 -04:00
// copyProgressPrinter wraps io.ReadCloser to print progress information when
// copying files to/from a container.
type copyProgressPrinter struct {
io . ReadCloser
toContainer bool
total * float64
writer io . Writer
}
func ( pt * copyProgressPrinter ) Read ( p [ ] byte ) ( int , error ) {
n , err := pt . ReadCloser . Read ( p )
* pt . total += float64 ( n )
if err == nil {
fmt . Fprint ( pt . writer , aec . Restore )
fmt . Fprint ( pt . writer , aec . EraseLine ( aec . EraseModes . All ) )
if pt . toContainer {
fmt . Fprintln ( pt . writer , "Copying to container - " + units . HumanSize ( * pt . total ) )
} else {
fmt . Fprintln ( pt . writer , "Copying from container - " + units . HumanSize ( * pt . total ) )
}
}
return n , err
}
2016-09-08 13:11:39 -04:00
// NewCopyCommand creates a new `docker cp` command
2017-10-11 12:18:27 -04:00
func NewCopyCommand ( dockerCli command . Cli ) * cobra . Command {
2016-09-08 13:11:39 -04:00
var opts copyOptions
cmd := & cobra . Command {
Use : ` cp [ OPTIONS ] CONTAINER : SRC_PATH DEST_PATH | -
docker cp [ OPTIONS ] SRC_PATH | - CONTAINER : DEST_PATH ` ,
Short : "Copy files/folders between a container and the local filesystem" ,
Long : strings . Join ( [ ] string {
"Copy files/folders between a container and the local filesystem\n" ,
"\nUse '-' as the source to read a tar archive from stdin\n" ,
"and extract it to a directory destination in a container.\n" ,
"Use '-' as the destination to stream a tar archive of a\n" ,
"container source to stdout." ,
} , "" ) ,
Args : cli . ExactArgs ( 2 ) ,
RunE : func ( cmd * cobra . Command , args [ ] string ) error {
if args [ 0 ] == "" {
2016-12-24 19:05:37 -05:00
return errors . New ( "source can not be empty" )
2016-09-08 13:11:39 -04:00
}
if args [ 1 ] == "" {
2016-12-24 19:05:37 -05:00
return errors . New ( "destination can not be empty" )
2016-09-08 13:11:39 -04:00
}
opts . source = args [ 0 ]
opts . destination = args [ 1 ]
2020-09-04 16:23:13 -04:00
if ! cmd . Flag ( "quiet" ) . Changed {
// User did not specify "quiet" flag; suppress output if no terminal is attached
opts . quiet = ! dockerCli . Out ( ) . IsTerminal ( )
}
2016-09-08 13:11:39 -04:00
return runCopy ( dockerCli , opts )
} ,
}
flags := cmd . Flags ( )
flags . BoolVarP ( & opts . followLink , "follow-link" , "L" , false , "Always follow symbol link in SRC_PATH" )
2016-11-14 08:37:08 -05:00
flags . BoolVarP ( & opts . copyUIDGID , "archive" , "a" , false , "Archive mode (copy all uid/gid information)" )
2020-09-04 16:23:13 -04:00
flags . BoolVarP ( & opts . quiet , "quiet" , "q" , false , "Suppress progress output during copy. Progress output is automatically suppressed if no terminal is attached" )
2016-09-08 13:11:39 -04:00
return cmd
}
2017-10-11 12:18:27 -04:00
func runCopy ( dockerCli command . Cli , opts copyOptions ) error {
2016-09-08 13:11:39 -04:00
srcContainer , srcPath := splitCpArg ( opts . source )
2017-10-30 17:52:08 -04:00
destContainer , destPath := splitCpArg ( opts . destination )
copyConfig := cpConfig {
followLink : opts . followLink ,
copyUIDGID : opts . copyUIDGID ,
2020-09-04 16:23:13 -04:00
quiet : opts . quiet ,
2017-10-30 17:52:08 -04:00
sourcePath : srcPath ,
destPath : destPath ,
}
2016-09-08 13:11:39 -04:00
var direction copyDirection
if srcContainer != "" {
direction |= fromContainer
2017-10-30 17:52:08 -04:00
copyConfig . container = srcContainer
2016-09-08 13:11:39 -04:00
}
2017-10-30 17:52:08 -04:00
if destContainer != "" {
2016-09-08 13:11:39 -04:00
direction |= toContainer
2017-10-30 17:52:08 -04:00
copyConfig . container = destContainer
2016-09-08 13:11:39 -04:00
}
ctx := context . Background ( )
switch direction {
case fromContainer :
2017-10-30 17:52:08 -04:00
return copyFromContainer ( ctx , dockerCli , copyConfig )
2016-09-08 13:11:39 -04:00
case toContainer :
2017-10-30 17:52:08 -04:00
return copyToContainer ( ctx , dockerCli , copyConfig )
2016-09-08 13:11:39 -04:00
case acrossContainers :
2016-12-24 19:05:37 -05:00
return errors . New ( "copying between containers is not supported" )
2016-09-08 13:11:39 -04:00
default :
2016-12-24 19:05:37 -05:00
return errors . New ( "must specify at least one container source" )
2016-09-08 13:11:39 -04:00
}
}
func resolveLocalPath ( localPath string ) ( absPath string , err error ) {
if absPath , err = filepath . Abs ( localPath ) ; err != nil {
return
}
2017-09-29 08:18:19 -04:00
return archive . PreserveTrailingDotOrSeparator ( absPath , localPath , filepath . Separator ) , nil
2016-09-08 13:11:39 -04:00
}
2017-10-30 17:52:08 -04:00
func copyFromContainer ( ctx context . Context , dockerCli command . Cli , copyConfig cpConfig ) ( err error ) {
dstPath := copyConfig . destPath
srcPath := copyConfig . sourcePath
2016-09-08 13:11:39 -04:00
if dstPath != "-" {
// Get an absolute destination path.
dstPath , err = resolveLocalPath ( dstPath )
if err != nil {
return err
}
}
2019-02-07 03:17:35 -05:00
if err := command . ValidateOutputPath ( dstPath ) ; err != nil {
return err
}
2017-10-30 17:52:08 -04:00
client := dockerCli . Client ( )
2016-09-08 13:11:39 -04:00
// if client requests to follow symbol link, then must decide target file to be copied
var rebaseName string
2017-10-30 17:52:08 -04:00
if copyConfig . followLink {
srcStat , err := client . ContainerStatPath ( ctx , copyConfig . container , srcPath )
2016-09-08 13:11:39 -04:00
// If the destination is a symbolic link, we should follow it.
if err == nil && srcStat . Mode & os . ModeSymlink != 0 {
linkTarget := srcStat . LinkTarget
if ! system . IsAbs ( linkTarget ) {
// Join with the parent directory.
srcParent , _ := archive . SplitPathDirEntry ( srcPath )
linkTarget = filepath . Join ( srcParent , linkTarget )
}
linkTarget , rebaseName = archive . GetRebaseName ( srcPath , linkTarget )
srcPath = linkTarget
}
}
2017-10-30 17:52:08 -04:00
content , stat , err := client . CopyFromContainer ( ctx , copyConfig . container , srcPath )
2016-09-08 13:11:39 -04:00
if err != nil {
return err
}
defer content . Close ( )
if dstPath == "-" {
2017-10-30 17:52:08 -04:00
_ , err = io . Copy ( dockerCli . Out ( ) , content )
2016-09-08 13:11:39 -04:00
return err
}
srcInfo := archive . CopyInfo {
Path : srcPath ,
Exists : true ,
IsDir : stat . Mode . IsDir ( ) ,
RebaseName : rebaseName ,
}
2020-09-04 16:23:13 -04:00
var copiedSize float64
if ! copyConfig . quiet {
content = & copyProgressPrinter {
ReadCloser : content ,
toContainer : false ,
writer : dockerCli . Err ( ) ,
total : & copiedSize ,
}
}
2016-09-08 13:11:39 -04:00
preArchive := content
if len ( srcInfo . RebaseName ) != 0 {
_ , srcBase := archive . SplitPathDirEntry ( srcInfo . Path )
preArchive = archive . RebaseArchiveEntries ( content , srcBase , srcInfo . RebaseName )
}
2020-09-04 16:23:13 -04:00
if copyConfig . quiet {
return archive . CopyTo ( preArchive , srcInfo , dstPath )
}
fmt . Fprint ( dockerCli . Err ( ) , aec . Save )
fmt . Fprintln ( dockerCli . Err ( ) , "Preparing to copy..." )
res := archive . CopyTo ( preArchive , srcInfo , dstPath )
fmt . Fprint ( dockerCli . Err ( ) , aec . Restore )
fmt . Fprint ( dockerCli . Err ( ) , aec . EraseLine ( aec . EraseModes . All ) )
fmt . Fprintln ( dockerCli . Err ( ) , "Successfully copied" , units . HumanSize ( copiedSize ) , "to" , dstPath )
return res
2016-09-08 13:11:39 -04:00
}
2017-10-30 17:52:08 -04:00
// In order to get the copy behavior right, we need to know information
// about both the source and destination. The API is a simple tar
// archive/extract API but we can use the stat info header about the
// destination to be more informed about exactly what the destination is.
func copyToContainer ( ctx context . Context , dockerCli command . Cli , copyConfig cpConfig ) ( err error ) {
srcPath := copyConfig . sourcePath
dstPath := copyConfig . destPath
2016-09-08 13:11:39 -04:00
if srcPath != "-" {
// Get an absolute source path.
srcPath , err = resolveLocalPath ( srcPath )
if err != nil {
return err
}
}
2017-10-30 17:52:08 -04:00
client := dockerCli . Client ( )
2016-09-08 13:11:39 -04:00
// Prepare destination copy info by stat-ing the container path.
dstInfo := archive . CopyInfo { Path : dstPath }
2017-10-30 17:52:08 -04:00
dstStat , err := client . ContainerStatPath ( ctx , copyConfig . container , dstPath )
2016-09-08 13:11:39 -04:00
// If the destination is a symbolic link, we should evaluate it.
if err == nil && dstStat . Mode & os . ModeSymlink != 0 {
linkTarget := dstStat . LinkTarget
if ! system . IsAbs ( linkTarget ) {
// Join with the parent directory.
dstParent , _ := archive . SplitPathDirEntry ( dstPath )
linkTarget = filepath . Join ( dstParent , linkTarget )
}
dstInfo . Path = linkTarget
2017-10-30 17:52:08 -04:00
dstStat , err = client . ContainerStatPath ( ctx , copyConfig . container , linkTarget )
2016-09-08 13:11:39 -04:00
}
2019-02-07 03:17:35 -05:00
// Validate the destination path
if err := command . ValidateOutputPathFileMode ( dstStat . Mode ) ; err != nil {
return errors . Wrapf ( err , ` destination "%s:%s" must be a directory or a regular file ` , copyConfig . container , dstPath )
}
2016-09-08 13:11:39 -04:00
// Ignore any error and assume that the parent directory of the destination
// path exists, in which case the copy may still succeed. If there is any
// type of conflict (e.g., non-directory overwriting an existing directory
// or vice versa) the extraction will fail. If the destination simply did
// not exist, but the parent directory does, the extraction will still
// succeed.
if err == nil {
dstInfo . Exists , dstInfo . IsDir = true , dstStat . Mode . IsDir ( )
}
var (
2020-09-04 16:23:13 -04:00
content io . ReadCloser
2016-09-08 13:11:39 -04:00
resolvedDstPath string
2020-09-04 16:23:13 -04:00
copiedSize float64
2016-09-08 13:11:39 -04:00
)
if srcPath == "-" {
content = os . Stdin
resolvedDstPath = dstInfo . Path
if ! dstInfo . IsDir {
2017-10-30 17:52:08 -04:00
return errors . Errorf ( "destination \"%s:%s\" must be a directory" , copyConfig . container , dstPath )
2016-09-08 13:11:39 -04:00
}
} else {
// Prepare source copy info.
2017-10-30 17:52:08 -04:00
srcInfo , err := archive . CopyInfoSourcePath ( srcPath , copyConfig . followLink )
2016-09-08 13:11:39 -04:00
if err != nil {
return err
}
srcArchive , err := archive . TarResource ( srcInfo )
if err != nil {
return err
}
defer srcArchive . Close ( )
// With the stat info about the local source as well as the
// destination, we have enough information to know whether we need to
// alter the archive that we upload so that when the server extracts
// it to the specified directory in the container we get the desired
// copy behavior.
// See comments in the implementation of `archive.PrepareArchiveCopy`
// for exactly what goes into deciding how and whether the source
// archive needs to be altered for the correct copy behavior when it is
// extracted. This function also infers from the source and destination
// info which directory to extract to, which may be the parent of the
// destination that the user specified.
dstDir , preparedArchive , err := archive . PrepareArchiveCopy ( srcArchive , srcInfo , dstInfo )
if err != nil {
return err
}
defer preparedArchive . Close ( )
resolvedDstPath = dstDir
content = preparedArchive
2020-09-04 16:23:13 -04:00
if ! copyConfig . quiet {
content = & copyProgressPrinter {
ReadCloser : content ,
toContainer : true ,
writer : dockerCli . Err ( ) ,
total : & copiedSize ,
}
}
2016-09-08 13:11:39 -04:00
}
options := types . CopyToContainerOptions {
AllowOverwriteDirWithFile : false ,
2017-10-30 17:52:08 -04:00
CopyUIDGID : copyConfig . copyUIDGID ,
2016-09-08 13:11:39 -04:00
}
2020-09-04 16:23:13 -04:00
if copyConfig . quiet {
return client . CopyToContainer ( ctx , copyConfig . container , resolvedDstPath , content , options )
}
fmt . Fprint ( dockerCli . Err ( ) , aec . Save )
fmt . Fprintln ( dockerCli . Err ( ) , "Preparing to copy..." )
res := client . CopyToContainer ( ctx , copyConfig . container , resolvedDstPath , content , options )
fmt . Fprint ( dockerCli . Err ( ) , aec . Restore )
fmt . Fprint ( dockerCli . Err ( ) , aec . EraseLine ( aec . EraseModes . All ) )
fmt . Fprintln ( dockerCli . Err ( ) , "Successfully copied" , units . HumanSize ( copiedSize ) , "to" , copyConfig . container + ":" + dstInfo . Path )
return res
2016-09-08 13:11:39 -04:00
}
// We use `:` as a delimiter between CONTAINER and PATH, but `:` could also be
// in a valid LOCALPATH, like `file:name.txt`. We can resolve this ambiguity by
// requiring a LOCALPATH with a `:` to be made explicit with a relative or
// absolute path:
// `/path/to/file:name.txt` or `./file:name.txt`
//
// This is apparently how `scp` handles this as well:
// http://www.cyberciti.biz/faq/rsync-scp-file-name-with-colon-punctuation-in-it/
//
// We can't simply check for a filepath separator because container names may
// have a separator, e.g., "host0/cname1" if container is in a Docker cluster,
// so we have to check for a `/` or `.` prefix. Also, in the case of a Windows
// client, a `:` could be part of an absolute Windows path, in which case it
// is immediately proceeded by a backslash.
func splitCpArg ( arg string ) ( container , path string ) {
if system . IsAbs ( arg ) {
// Explicit local absolute path, e.g., `C:\foo` or `/foo`.
return "" , arg
}
parts := strings . SplitN ( arg , ":" , 2 )
if len ( parts ) == 1 || strings . HasPrefix ( parts [ 0 ] , "." ) {
// Either there's no `:` in the arg
// OR it's an explicit local relative path like `./file:name.txt`.
return "" , arg
}
return parts [ 0 ] , parts [ 1 ]
}