From e2b8933d213b283578babc1d86538950295e4fc7 Mon Sep 17 00:00:00 2001 From: Martijn Dwars Date: Fri, 27 Feb 2015 19:50:55 +0100 Subject: [PATCH] Move directory size calculation to pkg/ (fixes #10970) Signed-off-by: Martijn Dwars --- daemon/container.go | 3 +- daemon/graphdriver/aufs/aufs.go | 4 +- pkg/directory/directory.go | 39 +++++++++++ pkg/directory/directory_test.go | 120 ++++++++++++++++++++++++++++++++ utils/utils_daemon.go | 31 --------- 5 files changed, 163 insertions(+), 34 deletions(-) create mode 100644 pkg/directory/directory.go create mode 100644 pkg/directory/directory_test.go diff --git a/daemon/container.go b/daemon/container.go index 676674bc2d..964567cccc 100644 --- a/daemon/container.go +++ b/daemon/container.go @@ -26,6 +26,7 @@ import ( "github.com/docker/docker/pkg/archive" "github.com/docker/docker/pkg/broadcastwriter" "github.com/docker/docker/pkg/common" + "github.com/docker/docker/pkg/directory" "github.com/docker/docker/pkg/ioutils" "github.com/docker/docker/pkg/networkfs/etchosts" "github.com/docker/docker/pkg/networkfs/resolvconf" @@ -885,7 +886,7 @@ func (container *Container) GetSize() (int64, int64) { } if _, err = os.Stat(container.basefs); err != nil { - if sizeRootfs, err = utils.TreeSize(container.basefs); err != nil { + if sizeRootfs, err = directory.Size(container.basefs); err != nil { sizeRootfs = -1 } } diff --git a/daemon/graphdriver/aufs/aufs.go b/daemon/graphdriver/aufs/aufs.go index 103a568e21..22579b8b63 100644 --- a/daemon/graphdriver/aufs/aufs.go +++ b/daemon/graphdriver/aufs/aufs.go @@ -35,8 +35,8 @@ import ( "github.com/docker/docker/pkg/archive" "github.com/docker/docker/pkg/chrootarchive" "github.com/docker/docker/pkg/common" + "github.com/docker/docker/pkg/directory" mountpk "github.com/docker/docker/pkg/mount" - "github.com/docker/docker/utils" "github.com/docker/libcontainer/label" ) @@ -320,7 +320,7 @@ func (a *Driver) applyDiff(id string, diff archive.ArchiveReader) error { // relative to its base filesystem directory. func (a *Driver) DiffSize(id, parent string) (size int64, err error) { // AUFS doesn't need the parent layer to calculate the diff size. - return utils.TreeSize(path.Join(a.rootPath(), "diff", id)) + return directory.Size(path.Join(a.rootPath(), "diff", id)) } // ApplyDiff extracts the changeset from the given diff into the diff --git a/pkg/directory/directory.go b/pkg/directory/directory.go new file mode 100644 index 0000000000..80fb9a8332 --- /dev/null +++ b/pkg/directory/directory.go @@ -0,0 +1,39 @@ +// +build linux + +package directory + +import ( + "os" + "path/filepath" + "syscall" +) + +// Size walks a directory tree and returns its total size in bytes. +func Size(dir string) (size int64, err error) { + data := make(map[uint64]struct{}) + err = filepath.Walk(dir, func(d string, fileInfo os.FileInfo, e error) error { + // Ignore directory sizes + if fileInfo == nil { + return nil + } + + s := fileInfo.Size() + if fileInfo.IsDir() || s == 0 { + return nil + } + + // Check inode to handle hard links correctly + inode := fileInfo.Sys().(*syscall.Stat_t).Ino + // inode is not a uint64 on all platforms. Cast it to avoid issues. + if _, exists := data[uint64(inode)]; exists { + return nil + } + // inode is not a uint64 on all platforms. Cast it to avoid issues. + data[uint64(inode)] = struct{}{} + + size += s + + return nil + }) + return +} diff --git a/pkg/directory/directory_test.go b/pkg/directory/directory_test.go new file mode 100644 index 0000000000..a137c59098 --- /dev/null +++ b/pkg/directory/directory_test.go @@ -0,0 +1,120 @@ +package directory + +import ( + "os" + "testing" +) + +// Size of an empty directory should be 0 +func TestSizeEmpty(t *testing.T) { + var err error + if err = os.Mkdir("/tmp/testSizeEmptyDirectory", 0777); err != nil { + t.Fatalf("failed to create directory: %s", err) + } + + var size int64 + if size, _ = Size("/tmp/testSizeEmptyDirectory"); size != 0 { + t.Fatalf("empty directory has size: %d", size) + } +} + +// Size of a directory with one empty file should be 0 +func TestSizeEmptyFile(t *testing.T) { + var err error + if err = os.Mkdir("/tmp/testSizeEmptyFile", 0777); err != nil { + t.Fatalf("failed to create directory: %s", err) + } + + if _, err = os.Create("/tmp/testSizeEmptyFile/file"); err != nil { + t.Fatalf("failed to create file: %s", err) + } + + var size int64 + if size, _ = Size("/tmp/testSizeEmptyFile"); size != 0 { + t.Fatalf("directory with one file has size: %d", size) + } +} + +// Size of a directory with one 5-byte file should be 5 +func TestSizeNonemptyFile(t *testing.T) { + var err error + if err = os.Mkdir("/tmp/testSizeNonemptyFile", 0777); err != nil { + t.Fatalf("failed to create directory: %s", err) + } + + var file *os.File + if file, err = os.Create("/tmp/testSizeNonemptyFile/file"); err != nil { + t.Fatalf("failed to create file: %s", err) + } + + d := []byte{97, 98, 99, 100, 101} + file.Write(d) + + var size int64 + if size, _ = Size("/tmp/testSizeNonemptyFile"); size != 5 { + t.Fatalf("directory with one 5-byte file has size: %d", size) + } +} + +// Size of a directory with one empty directory should be 0 +func TestSizeNestedDirectoryEmpty(t *testing.T) { + var err error + if err = os.MkdirAll("/tmp/testSizeNestedDirectoryEmpty/nested", 0777); err != nil { + t.Fatalf("failed to create directory: %s", err) + } + + var size int64 + if size, _ = Size("/tmp/testSizeNestedDirectoryEmpty"); size != 0 { + t.Fatalf("directory with one empty directory has size: %d", size) + } +} + +// Test directory with 1 file and 1 empty directory +func TestSizeFileAndNestedDirectoryEmpty(t *testing.T) { + var err error + if err = os.MkdirAll("/tmp/testSizeFileAndNestedDirectoryEmpty/nested", 0777); err != nil { + t.Fatalf("failed to create directory: %s", err) + } + + var file *os.File + if file, err = os.Create("/tmp/testSizeFileAndNestedDirectoryEmpty/file"); err != nil { + t.Fatalf("failed to create file: %s", err) + } + + d := []byte{100, 111, 99, 107, 101, 114} + file.Write(d) + + var size int64 + if size, _ = Size("/tmp/testSizeFileAndNestedDirectoryEmpty"); size != 6 { + t.Fatalf("directory with 6-byte file and empty directory has size: %d", size) + } +} + +// Test directory with 1 file and 1 non-empty directory +func TestSizeFileAndNestedDirectoryNonempty(t *testing.T) { + var err error + if err = os.MkdirAll("/tmp/testSizeFileAndNestedDirectoryEmpty/nested", 0777); err != nil { + t.Fatalf("failed to create directory: %s", err) + } + + var file *os.File + if file, err = os.Create("/tmp/testSizeFileAndNestedDirectoryEmpty/file"); err != nil { + t.Fatalf("failed to create file: %s", err) + } + + data := []byte{100, 111, 99, 107, 101, 114} + file.Write(data) + + var nestedFile *os.File + if nestedFile, err = os.Create("/tmp/testSizeFileAndNestedDirectoryEmpty/nested/file"); err != nil { + t.Fatalf("failed to create file: %s", err) + } + + nestedData := []byte{100, 111, 99, 107, 101, 114} + nestedFile.Write(nestedData) + + var size int64 + if size, _ = Size("/tmp/testSizeFileAndNestedDirectoryEmpty"); size != 12 { + t.Fatalf("directory with 6-byte file and empty directory has size: %d", size) + } +} diff --git a/utils/utils_daemon.go b/utils/utils_daemon.go index 9989f05e31..9aee485fef 100644 --- a/utils/utils_daemon.go +++ b/utils/utils_daemon.go @@ -4,40 +4,9 @@ package utils import ( "os" - "path/filepath" "syscall" ) -// TreeSize walks a directory tree and returns its total size in bytes. -func TreeSize(dir string) (size int64, err error) { - data := make(map[uint64]struct{}) - err = filepath.Walk(dir, func(d string, fileInfo os.FileInfo, e error) error { - // Ignore directory sizes - if fileInfo == nil { - return nil - } - - s := fileInfo.Size() - if fileInfo.IsDir() || s == 0 { - return nil - } - - // Check inode to handle hard links correctly - inode := fileInfo.Sys().(*syscall.Stat_t).Ino - // inode is not a uint64 on all platforms. Cast it to avoid issues. - if _, exists := data[uint64(inode)]; exists { - return nil - } - // inode is not a uint64 on all platforms. Cast it to avoid issues. - data[uint64(inode)] = struct{}{} - - size += s - - return nil - }) - return -} - // IsFileOwner checks whether the current user is the owner of the given file. func IsFileOwner(f string) bool { if fileInfo, err := os.Stat(f); err == nil && fileInfo != nil {