Add '-L' option for cp
Fixes #16555 Original docker `cp` always copy symbol link itself instead of target, now we provide '-L' option to allow docker to follow symbol link to real target. Signed-off-by: Zhang Wei <zhangwei555@huawei.com>
This commit is contained in:
parent
5edb94d724
commit
b4340e2b6c
3 changed files with 432 additions and 29 deletions
|
@ -63,6 +63,9 @@ func createSampleDir(t *testing.T, root string) {
|
||||||
{Regular, "dir4/file3-2", "file4-2\n", 0666},
|
{Regular, "dir4/file3-2", "file4-2\n", 0666},
|
||||||
{Symlink, "symlink1", "target1", 0666},
|
{Symlink, "symlink1", "target1", 0666},
|
||||||
{Symlink, "symlink2", "target2", 0666},
|
{Symlink, "symlink2", "target2", 0666},
|
||||||
|
{Symlink, "symlink3", root + "/file1", 0666},
|
||||||
|
{Symlink, "symlink4", root + "/symlink3", 0666},
|
||||||
|
{Symlink, "dirSymlink", root + "/dir1", 0740},
|
||||||
}
|
}
|
||||||
|
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
|
|
101
archive/copy.go
101
archive/copy.go
|
@ -135,30 +135,17 @@ type CopyInfo struct {
|
||||||
// operation. The given path should be an absolute local path. A source path
|
// operation. The given path should be an absolute local path. A source path
|
||||||
// has all symlinks evaluated that appear before the last path separator ("/"
|
// has all symlinks evaluated that appear before the last path separator ("/"
|
||||||
// on Unix). As it is to be a copy source, the path must exist.
|
// on Unix). As it is to be a copy source, the path must exist.
|
||||||
func CopyInfoSourcePath(path string) (CopyInfo, error) {
|
func CopyInfoSourcePath(path string, followLink bool) (CopyInfo, error) {
|
||||||
// Split the given path into its Directory and Base components. We will
|
// normalize the file path and then evaluate the symbol link
|
||||||
// evaluate symlinks in the directory component then append the base.
|
// we will use the target file instead of the symbol link if
|
||||||
|
// followLink is set
|
||||||
path = normalizePath(path)
|
path = normalizePath(path)
|
||||||
dirPath, basePath := filepath.Split(path)
|
|
||||||
|
|
||||||
resolvedDirPath, err := filepath.EvalSymlinks(dirPath)
|
resolvedPath, rebaseName, err := ResolveHostSourcePath(path, followLink)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return CopyInfo{}, err
|
return CopyInfo{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// resolvedDirPath will have been cleaned (no trailing path separators) so
|
|
||||||
// we can manually join it with the base path element.
|
|
||||||
resolvedPath := resolvedDirPath + string(filepath.Separator) + basePath
|
|
||||||
|
|
||||||
var rebaseName string
|
|
||||||
if hasTrailingPathSeparator(path) && filepath.Base(path) != filepath.Base(resolvedPath) {
|
|
||||||
// In the case where the path had a trailing separator and a symlink
|
|
||||||
// evaluation has changed the last path component, we will need to
|
|
||||||
// rebase the name in the archive that is being copied to match the
|
|
||||||
// originally requested name.
|
|
||||||
rebaseName = filepath.Base(path)
|
|
||||||
}
|
|
||||||
|
|
||||||
stat, err := os.Lstat(resolvedPath)
|
stat, err := os.Lstat(resolvedPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return CopyInfo{}, err
|
return CopyInfo{}, err
|
||||||
|
@ -279,7 +266,10 @@ func PrepareArchiveCopy(srcContent Reader, srcInfo, dstInfo CopyInfo) (dstDir st
|
||||||
// The destination exists as some type of file and the source content
|
// The destination exists as some type of file and the source content
|
||||||
// is also a file. The source content entry will have to be renamed to
|
// is also a file. The source content entry will have to be renamed to
|
||||||
// have a basename which matches the destination path's basename.
|
// have a basename which matches the destination path's basename.
|
||||||
return dstDir, rebaseArchiveEntries(srcContent, srcBase, dstBase), nil
|
if len(srcInfo.RebaseName) != 0 {
|
||||||
|
srcBase = srcInfo.RebaseName
|
||||||
|
}
|
||||||
|
return dstDir, RebaseArchiveEntries(srcContent, srcBase, dstBase), nil
|
||||||
case srcInfo.IsDir:
|
case srcInfo.IsDir:
|
||||||
// The destination does not exist and the source content is an archive
|
// The destination does not exist and the source content is an archive
|
||||||
// of a directory. The archive should be extracted to the parent of
|
// of a directory. The archive should be extracted to the parent of
|
||||||
|
@ -287,7 +277,10 @@ func PrepareArchiveCopy(srcContent Reader, srcInfo, dstInfo CopyInfo) (dstDir st
|
||||||
// created as a result should take the name of the destination path.
|
// created as a result should take the name of the destination path.
|
||||||
// The source content entries will have to be renamed to have a
|
// The source content entries will have to be renamed to have a
|
||||||
// basename which matches the destination path's basename.
|
// basename which matches the destination path's basename.
|
||||||
return dstDir, rebaseArchiveEntries(srcContent, srcBase, dstBase), nil
|
if len(srcInfo.RebaseName) != 0 {
|
||||||
|
srcBase = srcInfo.RebaseName
|
||||||
|
}
|
||||||
|
return dstDir, RebaseArchiveEntries(srcContent, srcBase, dstBase), nil
|
||||||
case assertsDirectory(dstInfo.Path):
|
case assertsDirectory(dstInfo.Path):
|
||||||
// The destination does not exist and is asserted to be created as a
|
// The destination does not exist and is asserted to be created as a
|
||||||
// directory, but the source content is not a directory. This is an
|
// directory, but the source content is not a directory. This is an
|
||||||
|
@ -301,14 +294,17 @@ func PrepareArchiveCopy(srcContent Reader, srcInfo, dstInfo CopyInfo) (dstDir st
|
||||||
// to be created when the archive is extracted and the source content
|
// to be created when the archive is extracted and the source content
|
||||||
// entry will have to be renamed to have a basename which matches the
|
// entry will have to be renamed to have a basename which matches the
|
||||||
// destination path's basename.
|
// destination path's basename.
|
||||||
return dstDir, rebaseArchiveEntries(srcContent, srcBase, dstBase), nil
|
if len(srcInfo.RebaseName) != 0 {
|
||||||
|
srcBase = srcInfo.RebaseName
|
||||||
|
}
|
||||||
|
return dstDir, RebaseArchiveEntries(srcContent, srcBase, dstBase), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// rebaseArchiveEntries rewrites the given srcContent archive replacing
|
// RebaseArchiveEntries rewrites the given srcContent archive replacing
|
||||||
// an occurrence of oldBase with newBase at the beginning of entry names.
|
// an occurrence of oldBase with newBase at the beginning of entry names.
|
||||||
func rebaseArchiveEntries(srcContent Reader, oldBase, newBase string) Archive {
|
func RebaseArchiveEntries(srcContent Reader, oldBase, newBase string) Archive {
|
||||||
if oldBase == string(os.PathSeparator) {
|
if oldBase == string(os.PathSeparator) {
|
||||||
// If oldBase specifies the root directory, use an empty string as
|
// If oldBase specifies the root directory, use an empty string as
|
||||||
// oldBase instead so that newBase doesn't replace the path separator
|
// oldBase instead so that newBase doesn't replace the path separator
|
||||||
|
@ -355,7 +351,7 @@ func rebaseArchiveEntries(srcContent Reader, oldBase, newBase string) Archive {
|
||||||
// CopyResource performs an archive copy from the given source path to the
|
// CopyResource performs an archive copy from the given source path to the
|
||||||
// given destination path. The source path MUST exist and the destination
|
// given destination path. The source path MUST exist and the destination
|
||||||
// path's parent directory must exist.
|
// path's parent directory must exist.
|
||||||
func CopyResource(srcPath, dstPath string) error {
|
func CopyResource(srcPath, dstPath string, followLink bool) error {
|
||||||
var (
|
var (
|
||||||
srcInfo CopyInfo
|
srcInfo CopyInfo
|
||||||
err error
|
err error
|
||||||
|
@ -369,7 +365,7 @@ func CopyResource(srcPath, dstPath string) error {
|
||||||
srcPath = PreserveTrailingDotOrSeparator(filepath.Clean(srcPath), srcPath)
|
srcPath = PreserveTrailingDotOrSeparator(filepath.Clean(srcPath), srcPath)
|
||||||
dstPath = PreserveTrailingDotOrSeparator(filepath.Clean(dstPath), dstPath)
|
dstPath = PreserveTrailingDotOrSeparator(filepath.Clean(dstPath), dstPath)
|
||||||
|
|
||||||
if srcInfo, err = CopyInfoSourcePath(srcPath); err != nil {
|
if srcInfo, err = CopyInfoSourcePath(srcPath, followLink); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -405,3 +401,58 @@ func CopyTo(content Reader, srcInfo CopyInfo, dstPath string) error {
|
||||||
|
|
||||||
return Untar(copyArchive, dstDir, options)
|
return Untar(copyArchive, dstDir, options)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ResolveHostSourcePath decides real path need to be copied with parameters such as
|
||||||
|
// whether to follow symbol link or not, if followLink is true, resolvedPath will return
|
||||||
|
// link target of any symbol link file, else it will only resolve symlink of directory
|
||||||
|
// but return symbol link file itself without resolving.
|
||||||
|
func ResolveHostSourcePath(path string, followLink bool) (resolvedPath, rebaseName string, err error) {
|
||||||
|
if followLink {
|
||||||
|
resolvedPath, err = filepath.EvalSymlinks(path)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
resolvedPath, rebaseName = GetRebaseName(path, resolvedPath)
|
||||||
|
} else {
|
||||||
|
dirPath, basePath := filepath.Split(path)
|
||||||
|
|
||||||
|
// if not follow symbol link, then resolve symbol link of parent dir
|
||||||
|
var resolvedDirPath string
|
||||||
|
resolvedDirPath, err = filepath.EvalSymlinks(dirPath)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// resolvedDirPath will have been cleaned (no trailing path separators) so
|
||||||
|
// we can manually join it with the base path element.
|
||||||
|
resolvedPath = resolvedDirPath + string(filepath.Separator) + basePath
|
||||||
|
if hasTrailingPathSeparator(path) && filepath.Base(path) != filepath.Base(resolvedPath) {
|
||||||
|
rebaseName = filepath.Base(path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return resolvedPath, rebaseName, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRebaseName normalizes and compares path and resolvedPath,
|
||||||
|
// return completed resolved path and rebased file name
|
||||||
|
func GetRebaseName(path, resolvedPath string) (string, string) {
|
||||||
|
// linkTarget will have been cleaned (no trailing path separators and dot) so
|
||||||
|
// we can manually join it with them
|
||||||
|
var rebaseName string
|
||||||
|
if specifiesCurrentDir(path) && !specifiesCurrentDir(resolvedPath) {
|
||||||
|
resolvedPath += string(filepath.Separator) + "."
|
||||||
|
}
|
||||||
|
|
||||||
|
if hasTrailingPathSeparator(path) && !hasTrailingPathSeparator(resolvedPath) {
|
||||||
|
resolvedPath += string(filepath.Separator)
|
||||||
|
}
|
||||||
|
|
||||||
|
if filepath.Base(path) != filepath.Base(resolvedPath) {
|
||||||
|
// In the case where the path had a trailing separator and a symlink
|
||||||
|
// evaluation has changed the last path component, we will need to
|
||||||
|
// rebase the name in the archive that is being copied to match the
|
||||||
|
// originally requested name.
|
||||||
|
rebaseName = filepath.Base(path)
|
||||||
|
}
|
||||||
|
return resolvedPath, rebaseName
|
||||||
|
}
|
||||||
|
|
|
@ -120,9 +120,15 @@ func logDirContents(t *testing.T, dirPath string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func testCopyHelper(t *testing.T, srcPath, dstPath string) (err error) {
|
func testCopyHelper(t *testing.T, srcPath, dstPath string) (err error) {
|
||||||
t.Logf("copying from %q to %q", srcPath, dstPath)
|
t.Logf("copying from %q to %q (not follow symbol link)", srcPath, dstPath)
|
||||||
|
|
||||||
return CopyResource(srcPath, dstPath)
|
return CopyResource(srcPath, dstPath, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testCopyHelperFSym(t *testing.T, srcPath, dstPath string) (err error) {
|
||||||
|
t.Logf("copying from %q to %q (follow symbol link)", srcPath, dstPath)
|
||||||
|
|
||||||
|
return CopyResource(srcPath, dstPath, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Basic assumptions about SRC and DST:
|
// Basic assumptions about SRC and DST:
|
||||||
|
@ -138,7 +144,7 @@ func TestCopyErrSrcNotExists(t *testing.T) {
|
||||||
tmpDirA, tmpDirB := getTestTempDirs(t)
|
tmpDirA, tmpDirB := getTestTempDirs(t)
|
||||||
defer removeAllPaths(tmpDirA, tmpDirB)
|
defer removeAllPaths(tmpDirA, tmpDirB)
|
||||||
|
|
||||||
if _, err := CopyInfoSourcePath(filepath.Join(tmpDirA, "file1")); !os.IsNotExist(err) {
|
if _, err := CopyInfoSourcePath(filepath.Join(tmpDirA, "file1"), false); !os.IsNotExist(err) {
|
||||||
t.Fatalf("expected IsNotExist error, but got %T: %s", err, err)
|
t.Fatalf("expected IsNotExist error, but got %T: %s", err, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -152,7 +158,7 @@ func TestCopyErrSrcNotDir(t *testing.T) {
|
||||||
// Load A with some sample files and directories.
|
// Load A with some sample files and directories.
|
||||||
createSampleDir(t, tmpDirA)
|
createSampleDir(t, tmpDirA)
|
||||||
|
|
||||||
if _, err := CopyInfoSourcePath(joinTrailingSep(tmpDirA, "file1")); !isNotDir(err) {
|
if _, err := CopyInfoSourcePath(joinTrailingSep(tmpDirA, "file1"), false); !isNotDir(err) {
|
||||||
t.Fatalf("expected IsNotDir error, but got %T: %s", err, err)
|
t.Fatalf("expected IsNotDir error, but got %T: %s", err, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -286,6 +292,27 @@ func TestCopyCaseA(t *testing.T) {
|
||||||
if err = fileContentsEqual(t, srcPath, dstPath); err != nil {
|
if err = fileContentsEqual(t, srcPath, dstPath); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
os.Remove(dstPath)
|
||||||
|
|
||||||
|
symlinkPath := filepath.Join(tmpDirA, "symlink3")
|
||||||
|
symlinkPath1 := filepath.Join(tmpDirA, "symlink4")
|
||||||
|
linkTarget := filepath.Join(tmpDirA, "file1")
|
||||||
|
|
||||||
|
if err = testCopyHelperFSym(t, symlinkPath, dstPath); err != nil {
|
||||||
|
t.Fatalf("unexpected error %T: %s", err, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = fileContentsEqual(t, linkTarget, dstPath); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
os.Remove(dstPath)
|
||||||
|
if err = testCopyHelperFSym(t, symlinkPath1, dstPath); err != nil {
|
||||||
|
t.Fatalf("unexpected error %T: %s", err, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = fileContentsEqual(t, linkTarget, dstPath); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// B. SRC specifies a file and DST (with trailing path separator) doesn't
|
// B. SRC specifies a file and DST (with trailing path separator) doesn't
|
||||||
|
@ -310,6 +337,16 @@ func TestCopyCaseB(t *testing.T) {
|
||||||
if err != ErrDirNotExists {
|
if err != ErrDirNotExists {
|
||||||
t.Fatalf("expected ErrDirNotExists error, but got %T: %s", err, err)
|
t.Fatalf("expected ErrDirNotExists error, but got %T: %s", err, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
symlinkPath := filepath.Join(tmpDirA, "symlink3")
|
||||||
|
|
||||||
|
if err = testCopyHelperFSym(t, symlinkPath, dstDir); err == nil {
|
||||||
|
t.Fatal("expected ErrDirNotExists error, but got nil instead")
|
||||||
|
}
|
||||||
|
if err != ErrDirNotExists {
|
||||||
|
t.Fatalf("expected ErrDirNotExists error, but got %T: %s", err, err)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// C. SRC specifies a file and DST exists as a file. This should overwrite
|
// C. SRC specifies a file and DST exists as a file. This should overwrite
|
||||||
|
@ -341,6 +378,44 @@ func TestCopyCaseC(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// C. Symbol link following version:
|
||||||
|
// SRC specifies a file and DST exists as a file. This should overwrite
|
||||||
|
// the file at DST with the contents of the source file.
|
||||||
|
func TestCopyCaseCFSym(t *testing.T) {
|
||||||
|
tmpDirA, tmpDirB := getTestTempDirs(t)
|
||||||
|
defer removeAllPaths(tmpDirA, tmpDirB)
|
||||||
|
|
||||||
|
// Load A and B with some sample files and directories.
|
||||||
|
createSampleDir(t, tmpDirA)
|
||||||
|
createSampleDir(t, tmpDirB)
|
||||||
|
|
||||||
|
symlinkPathBad := filepath.Join(tmpDirA, "symlink1")
|
||||||
|
symlinkPath := filepath.Join(tmpDirA, "symlink3")
|
||||||
|
linkTarget := filepath.Join(tmpDirA, "file1")
|
||||||
|
dstPath := filepath.Join(tmpDirB, "file2")
|
||||||
|
|
||||||
|
var err error
|
||||||
|
|
||||||
|
// first to test broken link
|
||||||
|
if err = testCopyHelperFSym(t, symlinkPathBad, dstPath); err == nil {
|
||||||
|
t.Fatalf("unexpected error %T: %s", err, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// test symbol link -> symbol link -> target
|
||||||
|
// Ensure they start out different.
|
||||||
|
if err = fileContentsEqual(t, linkTarget, dstPath); err == nil {
|
||||||
|
t.Fatal("expected different file contents")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = testCopyHelperFSym(t, symlinkPath, dstPath); err != nil {
|
||||||
|
t.Fatalf("unexpected error %T: %s", err, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = fileContentsEqual(t, linkTarget, dstPath); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// D. SRC specifies a file and DST exists as a directory. This should place
|
// D. SRC specifies a file and DST exists as a directory. This should place
|
||||||
// a copy of the source file inside it using the basename from SRC. Ensure
|
// a copy of the source file inside it using the basename from SRC. Ensure
|
||||||
// this works whether DST has a trailing path separator or not.
|
// this works whether DST has a trailing path separator or not.
|
||||||
|
@ -392,6 +467,59 @@ func TestCopyCaseD(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// D. Symbol link following version:
|
||||||
|
// SRC specifies a file and DST exists as a directory. This should place
|
||||||
|
// a copy of the source file inside it using the basename from SRC. Ensure
|
||||||
|
// this works whether DST has a trailing path separator or not.
|
||||||
|
func TestCopyCaseDFSym(t *testing.T) {
|
||||||
|
tmpDirA, tmpDirB := getTestTempDirs(t)
|
||||||
|
defer removeAllPaths(tmpDirA, tmpDirB)
|
||||||
|
|
||||||
|
// Load A and B with some sample files and directories.
|
||||||
|
createSampleDir(t, tmpDirA)
|
||||||
|
createSampleDir(t, tmpDirB)
|
||||||
|
|
||||||
|
srcPath := filepath.Join(tmpDirA, "symlink4")
|
||||||
|
linkTarget := filepath.Join(tmpDirA, "file1")
|
||||||
|
dstDir := filepath.Join(tmpDirB, "dir1")
|
||||||
|
dstPath := filepath.Join(dstDir, "symlink4")
|
||||||
|
|
||||||
|
var err error
|
||||||
|
|
||||||
|
// Ensure that dstPath doesn't exist.
|
||||||
|
if _, err = os.Stat(dstPath); !os.IsNotExist(err) {
|
||||||
|
t.Fatalf("did not expect dstPath %q to exist", dstPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = testCopyHelperFSym(t, srcPath, dstDir); err != nil {
|
||||||
|
t.Fatalf("unexpected error %T: %s", err, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = fileContentsEqual(t, linkTarget, dstPath); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now try again but using a trailing path separator for dstDir.
|
||||||
|
|
||||||
|
if err = os.RemoveAll(dstDir); err != nil {
|
||||||
|
t.Fatalf("unable to remove dstDir: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = os.MkdirAll(dstDir, os.FileMode(0755)); err != nil {
|
||||||
|
t.Fatalf("unable to make dstDir: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
dstDir = joinTrailingSep(tmpDirB, "dir1")
|
||||||
|
|
||||||
|
if err = testCopyHelperFSym(t, srcPath, dstDir); err != nil {
|
||||||
|
t.Fatalf("unexpected error %T: %s", err, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = fileContentsEqual(t, linkTarget, dstPath); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// E. SRC specifies a directory and DST does not exist. This should create a
|
// E. SRC specifies a directory and DST does not exist. This should create a
|
||||||
// directory at DST and copy the contents of the SRC directory into the DST
|
// directory at DST and copy the contents of the SRC directory into the DST
|
||||||
// directory. Ensure this works whether DST has a trailing path separator or
|
// directory. Ensure this works whether DST has a trailing path separator or
|
||||||
|
@ -436,6 +564,52 @@ func TestCopyCaseE(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// E. Symbol link following version:
|
||||||
|
// SRC specifies a directory and DST does not exist. This should create a
|
||||||
|
// directory at DST and copy the contents of the SRC directory into the DST
|
||||||
|
// directory. Ensure this works whether DST has a trailing path separator or
|
||||||
|
// not.
|
||||||
|
func TestCopyCaseEFSym(t *testing.T) {
|
||||||
|
tmpDirA, tmpDirB := getTestTempDirs(t)
|
||||||
|
defer removeAllPaths(tmpDirA, tmpDirB)
|
||||||
|
|
||||||
|
// Load A with some sample files and directories.
|
||||||
|
createSampleDir(t, tmpDirA)
|
||||||
|
|
||||||
|
srcDir := filepath.Join(tmpDirA, "dirSymlink")
|
||||||
|
linkTarget := filepath.Join(tmpDirA, "dir1")
|
||||||
|
dstDir := filepath.Join(tmpDirB, "testDir")
|
||||||
|
|
||||||
|
var err error
|
||||||
|
|
||||||
|
if err = testCopyHelperFSym(t, srcDir, dstDir); err != nil {
|
||||||
|
t.Fatalf("unexpected error %T: %s", err, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = dirContentsEqual(t, dstDir, linkTarget); err != nil {
|
||||||
|
t.Log("dir contents not equal")
|
||||||
|
logDirContents(t, tmpDirA)
|
||||||
|
logDirContents(t, tmpDirB)
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now try again but using a trailing path separator for dstDir.
|
||||||
|
|
||||||
|
if err = os.RemoveAll(dstDir); err != nil {
|
||||||
|
t.Fatalf("unable to remove dstDir: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
dstDir = joinTrailingSep(tmpDirB, "testDir")
|
||||||
|
|
||||||
|
if err = testCopyHelperFSym(t, srcDir, dstDir); err != nil {
|
||||||
|
t.Fatalf("unexpected error %T: %s", err, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = dirContentsEqual(t, dstDir, linkTarget); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// F. SRC specifies a directory and DST exists as a file. This should cause an
|
// F. SRC specifies a directory and DST exists as a file. This should cause an
|
||||||
// error as it is not possible to overwrite a file with a directory.
|
// error as it is not possible to overwrite a file with a directory.
|
||||||
func TestCopyCaseF(t *testing.T) {
|
func TestCopyCaseF(t *testing.T) {
|
||||||
|
@ -447,6 +621,7 @@ func TestCopyCaseF(t *testing.T) {
|
||||||
createSampleDir(t, tmpDirB)
|
createSampleDir(t, tmpDirB)
|
||||||
|
|
||||||
srcDir := filepath.Join(tmpDirA, "dir1")
|
srcDir := filepath.Join(tmpDirA, "dir1")
|
||||||
|
symSrcDir := filepath.Join(tmpDirA, "dirSymlink")
|
||||||
dstFile := filepath.Join(tmpDirB, "file1")
|
dstFile := filepath.Join(tmpDirB, "file1")
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
|
@ -458,6 +633,15 @@ func TestCopyCaseF(t *testing.T) {
|
||||||
if err != ErrCannotCopyDir {
|
if err != ErrCannotCopyDir {
|
||||||
t.Fatalf("expected ErrCannotCopyDir error, but got %T: %s", err, err)
|
t.Fatalf("expected ErrCannotCopyDir error, but got %T: %s", err, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// now test with symbol link
|
||||||
|
if err = testCopyHelperFSym(t, symSrcDir, dstFile); err == nil {
|
||||||
|
t.Fatal("expected ErrCannotCopyDir error, but got nil instead")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != ErrCannotCopyDir {
|
||||||
|
t.Fatalf("expected ErrCannotCopyDir error, but got %T: %s", err, err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// G. SRC specifies a directory and DST exists as a directory. This should copy
|
// G. SRC specifies a directory and DST exists as a directory. This should copy
|
||||||
|
@ -506,6 +690,54 @@ func TestCopyCaseG(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// G. Symbol link version:
|
||||||
|
// SRC specifies a directory and DST exists as a directory. This should copy
|
||||||
|
// the SRC directory and all its contents to the DST directory. Ensure this
|
||||||
|
// works whether DST has a trailing path separator or not.
|
||||||
|
func TestCopyCaseGFSym(t *testing.T) {
|
||||||
|
tmpDirA, tmpDirB := getTestTempDirs(t)
|
||||||
|
defer removeAllPaths(tmpDirA, tmpDirB)
|
||||||
|
|
||||||
|
// Load A and B with some sample files and directories.
|
||||||
|
createSampleDir(t, tmpDirA)
|
||||||
|
createSampleDir(t, tmpDirB)
|
||||||
|
|
||||||
|
srcDir := filepath.Join(tmpDirA, "dirSymlink")
|
||||||
|
linkTarget := filepath.Join(tmpDirA, "dir1")
|
||||||
|
dstDir := filepath.Join(tmpDirB, "dir2")
|
||||||
|
resultDir := filepath.Join(dstDir, "dirSymlink")
|
||||||
|
|
||||||
|
var err error
|
||||||
|
|
||||||
|
if err = testCopyHelperFSym(t, srcDir, dstDir); err != nil {
|
||||||
|
t.Fatalf("unexpected error %T: %s", err, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = dirContentsEqual(t, resultDir, linkTarget); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now try again but using a trailing path separator for dstDir.
|
||||||
|
|
||||||
|
if err = os.RemoveAll(dstDir); err != nil {
|
||||||
|
t.Fatalf("unable to remove dstDir: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = os.MkdirAll(dstDir, os.FileMode(0755)); err != nil {
|
||||||
|
t.Fatalf("unable to make dstDir: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
dstDir = joinTrailingSep(tmpDirB, "dir2")
|
||||||
|
|
||||||
|
if err = testCopyHelperFSym(t, srcDir, dstDir); err != nil {
|
||||||
|
t.Fatalf("unexpected error %T: %s", err, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = dirContentsEqual(t, resultDir, linkTarget); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// H. SRC specifies a directory's contents only and DST does not exist. This
|
// H. SRC specifies a directory's contents only and DST does not exist. This
|
||||||
// should create a directory at DST and copy the contents of the SRC
|
// should create a directory at DST and copy the contents of the SRC
|
||||||
// directory (but not the directory itself) into the DST directory. Ensure
|
// directory (but not the directory itself) into the DST directory. Ensure
|
||||||
|
@ -553,6 +785,55 @@ func TestCopyCaseH(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// H. Symbol link following version:
|
||||||
|
// SRC specifies a directory's contents only and DST does not exist. This
|
||||||
|
// should create a directory at DST and copy the contents of the SRC
|
||||||
|
// directory (but not the directory itself) into the DST directory. Ensure
|
||||||
|
// this works whether DST has a trailing path separator or not.
|
||||||
|
func TestCopyCaseHFSym(t *testing.T) {
|
||||||
|
tmpDirA, tmpDirB := getTestTempDirs(t)
|
||||||
|
defer removeAllPaths(tmpDirA, tmpDirB)
|
||||||
|
|
||||||
|
// Load A with some sample files and directories.
|
||||||
|
createSampleDir(t, tmpDirA)
|
||||||
|
|
||||||
|
srcDir := joinTrailingSep(tmpDirA, "dirSymlink") + "."
|
||||||
|
linkTarget := filepath.Join(tmpDirA, "dir1")
|
||||||
|
dstDir := filepath.Join(tmpDirB, "testDir")
|
||||||
|
|
||||||
|
var err error
|
||||||
|
|
||||||
|
if err = testCopyHelperFSym(t, srcDir, dstDir); err != nil {
|
||||||
|
t.Fatalf("unexpected error %T: %s", err, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = dirContentsEqual(t, dstDir, linkTarget); err != nil {
|
||||||
|
t.Log("dir contents not equal")
|
||||||
|
logDirContents(t, tmpDirA)
|
||||||
|
logDirContents(t, tmpDirB)
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now try again but using a trailing path separator for dstDir.
|
||||||
|
|
||||||
|
if err = os.RemoveAll(dstDir); err != nil {
|
||||||
|
t.Fatalf("unable to remove dstDir: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
dstDir = joinTrailingSep(tmpDirB, "testDir")
|
||||||
|
|
||||||
|
if err = testCopyHelperFSym(t, srcDir, dstDir); err != nil {
|
||||||
|
t.Fatalf("unexpected error %T: %s", err, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = dirContentsEqual(t, dstDir, linkTarget); err != nil {
|
||||||
|
t.Log("dir contents not equal")
|
||||||
|
logDirContents(t, tmpDirA)
|
||||||
|
logDirContents(t, tmpDirB)
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// I. SRC specifies a directory's contents only and DST exists as a file. This
|
// I. SRC specifies a directory's contents only and DST exists as a file. This
|
||||||
// should cause an error as it is not possible to overwrite a file with a
|
// should cause an error as it is not possible to overwrite a file with a
|
||||||
// directory.
|
// directory.
|
||||||
|
@ -565,6 +846,7 @@ func TestCopyCaseI(t *testing.T) {
|
||||||
createSampleDir(t, tmpDirB)
|
createSampleDir(t, tmpDirB)
|
||||||
|
|
||||||
srcDir := joinTrailingSep(tmpDirA, "dir1") + "."
|
srcDir := joinTrailingSep(tmpDirA, "dir1") + "."
|
||||||
|
symSrcDir := filepath.Join(tmpDirB, "dirSymlink")
|
||||||
dstFile := filepath.Join(tmpDirB, "file1")
|
dstFile := filepath.Join(tmpDirB, "file1")
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
|
@ -576,6 +858,15 @@ func TestCopyCaseI(t *testing.T) {
|
||||||
if err != ErrCannotCopyDir {
|
if err != ErrCannotCopyDir {
|
||||||
t.Fatalf("expected ErrCannotCopyDir error, but got %T: %s", err, err)
|
t.Fatalf("expected ErrCannotCopyDir error, but got %T: %s", err, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// now try with symbol link of dir
|
||||||
|
if err = testCopyHelperFSym(t, symSrcDir, dstFile); err == nil {
|
||||||
|
t.Fatal("expected ErrCannotCopyDir error, but got nil instead")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != ErrCannotCopyDir {
|
||||||
|
t.Fatalf("expected ErrCannotCopyDir error, but got %T: %s", err, err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// J. SRC specifies a directory's contents only and DST exists as a directory.
|
// J. SRC specifies a directory's contents only and DST exists as a directory.
|
||||||
|
@ -595,6 +886,11 @@ func TestCopyCaseJ(t *testing.T) {
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
|
// first to create an empty dir
|
||||||
|
if err = os.MkdirAll(dstDir, os.FileMode(0755)); err != nil {
|
||||||
|
t.Fatalf("unable to make dstDir: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
if err = testCopyHelper(t, srcDir, dstDir); err != nil {
|
if err = testCopyHelper(t, srcDir, dstDir); err != nil {
|
||||||
t.Fatalf("unexpected error %T: %s", err, err)
|
t.Fatalf("unexpected error %T: %s", err, err)
|
||||||
}
|
}
|
||||||
|
@ -623,3 +919,56 @@ func TestCopyCaseJ(t *testing.T) {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// J. Symbol link following version:
|
||||||
|
// SRC specifies a directory's contents only and DST exists as a directory.
|
||||||
|
// This should copy the contents of the SRC directory (but not the directory
|
||||||
|
// itself) into the DST directory. Ensure this works whether DST has a
|
||||||
|
// trailing path separator or not.
|
||||||
|
func TestCopyCaseJFSym(t *testing.T) {
|
||||||
|
tmpDirA, tmpDirB := getTestTempDirs(t)
|
||||||
|
defer removeAllPaths(tmpDirA, tmpDirB)
|
||||||
|
|
||||||
|
// Load A and B with some sample files and directories.
|
||||||
|
createSampleDir(t, tmpDirA)
|
||||||
|
createSampleDir(t, tmpDirB)
|
||||||
|
|
||||||
|
srcDir := joinTrailingSep(tmpDirA, "dirSymlink") + "."
|
||||||
|
linkTarget := filepath.Join(tmpDirA, "dir1")
|
||||||
|
dstDir := filepath.Join(tmpDirB, "dir5")
|
||||||
|
|
||||||
|
var err error
|
||||||
|
|
||||||
|
// first to create an empty dir
|
||||||
|
if err = os.MkdirAll(dstDir, os.FileMode(0755)); err != nil {
|
||||||
|
t.Fatalf("unable to make dstDir: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = testCopyHelperFSym(t, srcDir, dstDir); err != nil {
|
||||||
|
t.Fatalf("unexpected error %T: %s", err, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = dirContentsEqual(t, dstDir, linkTarget); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now try again but using a trailing path separator for dstDir.
|
||||||
|
|
||||||
|
if err = os.RemoveAll(dstDir); err != nil {
|
||||||
|
t.Fatalf("unable to remove dstDir: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = os.MkdirAll(dstDir, os.FileMode(0755)); err != nil {
|
||||||
|
t.Fatalf("unable to make dstDir: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
dstDir = joinTrailingSep(tmpDirB, "dir5")
|
||||||
|
|
||||||
|
if err = testCopyHelperFSym(t, srcDir, dstDir); err != nil {
|
||||||
|
t.Fatalf("unexpected error %T: %s", err, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = dirContentsEqual(t, dstDir, linkTarget); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue