Skip to content

Commit c27e26f

Browse files
committed
docker cp: report both content size and transferred size
When copying files to a container, the `docker cp` command would print a message reporting the size of files copied. However, this size was based on the size of the TAR stream sent, which includes the size of the TAR headers. docker container create --name my-container busybox touch empty-file docker cp empty-file my-container:/empty-file Successfully copied 1.54kB to my-container:/empty-file This patch adds a `calcTARContentSize` utility, which uses the TAR headers to calculate the size of files included. With this patch applied, the content size is reported, instead of the size of the TAR stream used for transport; docker container create --name my-container busybox touch empty-file docker cp empty-file my-container:/empty-file Successfully copied 0B (transferred 1.54kB) to my-container:/empty-file mkdir empty-dir docker cp ./empty-dir my-container:/somewhere/ Successfully copied 0B (transferred 1.54kB) to my-container:/somewhere/ docker cp ./cli/command my-container:/files/ Successfully copied 2.01MB (transferred 2.53MB) to my-container:/files/ Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
1 parent 7b93d61 commit c27e26f

File tree

1 file changed

+55
-3
lines changed
  • cli/command/container

1 file changed

+55
-3
lines changed

cli/command/container/cp.go

Lines changed: 55 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package container
22

33
import (
4+
"archive/tar"
45
"bytes"
56
"context"
67
"errors"
@@ -354,6 +355,7 @@ func copyToContainer(ctx context.Context, dockerCLI command.Cli, copyConfig cpCo
354355
content io.ReadCloser
355356
resolvedDstPath string
356357
copiedSize int64
358+
contentSize int64
357359
)
358360

359361
if srcPath == "-" {
@@ -387,10 +389,11 @@ func copyToContainer(ctx context.Context, dockerCLI command.Cli, copyConfig cpCo
387389
// extracted. This function also infers from the source and destination
388390
// info which directory to extract to, which may be the parent of the
389391
// destination that the user specified.
390-
dstDir, preparedArchive, err := archive.PrepareArchiveCopy(srcArchive, srcInfo, dstInfo)
392+
dstDir, preparedArchive1, err := archive.PrepareArchiveCopy(srcArchive, srcInfo, dstInfo)
391393
if err != nil {
392394
return err
393395
}
396+
preparedArchive := calcTARContentSize(preparedArchive1, &contentSize)
394397
defer preparedArchive.Close()
395398

396399
resolvedDstPath = dstDir
@@ -421,8 +424,9 @@ func copyToContainer(ctx context.Context, dockerCLI command.Cli, copyConfig cpCo
421424
cancel()
422425
<-done
423426
restore()
424-
_, _ = fmt.Fprintln(dockerCLI.Err(), "Successfully copied", progressHumanSize(copiedSize), "to", copyConfig.container+":"+dstInfo.Path)
425-
427+
_, _ = fmt.Fprintf(dockerCLI.Err(), "Successfully copied %s (transferred %s) to %s:%s\n",
428+
progressHumanSize(contentSize), progressHumanSize(copiedSize), copyConfig.container, dstInfo.Path,
429+
)
426430
return err
427431
}
428432

@@ -469,3 +473,51 @@ func splitCpArg(arg string) (ctr, path string) {
469473
func isAbs(path string) bool {
470474
return filepath.IsAbs(path) || strings.HasPrefix(path, string(os.PathSeparator))
471475
}
476+
477+
// calcTARContentSize calculates the total size of files transferred in
478+
// the TAR archive based on information in the TAR header. This allows
479+
// presenting the data copied, excluding the TAR header.
480+
func calcTARContentSize(srcContent io.ReadCloser, size *int64) io.ReadCloser {
481+
if size == nil {
482+
return srcContent
483+
}
484+
485+
r, w := io.Pipe()
486+
487+
go func() {
488+
var total int64
489+
defer func() {
490+
_ = srcContent.Close()
491+
atomic.StoreInt64(size, total)
492+
}()
493+
494+
tee := io.TeeReader(srcContent, w)
495+
tr := tar.NewReader(tee)
496+
497+
for {
498+
hdr, err := tr.Next()
499+
if err != nil {
500+
if errors.Is(err, io.EOF) {
501+
_ = w.Close()
502+
return
503+
}
504+
_ = w.CloseWithError(err)
505+
return
506+
}
507+
508+
switch hdr.Typeflag {
509+
case tar.TypeReg, tar.TypeRegA:
510+
total += hdr.Size
511+
}
512+
513+
// Drain entry payload (tee forwards bytes to w).
514+
//nolint:gosec // G110: see RebaseArchiveEntries rationale
515+
if _, err := io.Copy(io.Discard, tr); err != nil {
516+
_ = w.CloseWithError(err)
517+
return
518+
}
519+
}
520+
}()
521+
522+
return r
523+
}

0 commit comments

Comments
 (0)