Skip to content

Commit

Permalink
copier: retain symlink target w/ follow-link
Browse files Browse the repository at this point in the history
* add unit test

[NO NEW TESTS NEEDED]

Signed-off-by: danishprakash <[email protected]>
  • Loading branch information
danishprakash committed Sep 11, 2023
1 parent 0cbe852 commit c0e32d7
Show file tree
Hide file tree
Showing 2 changed files with 113 additions and 1 deletion.
12 changes: 11 additions & 1 deletion copier/copier.go
Original file line number Diff line number Diff line change
Expand Up @@ -1307,7 +1307,17 @@ func copierHandlerGet(bulkWriter io.Writer, req request, pm *fileutils.PatternMa
// cases where this was a symlink that we
// dereferenced, be sure to use the name of the
// link.
if err := copierHandlerGetOne(info, "", filepath.Base(queue[i]), item, req.GetOptions, tw, hardlinkChecker, idMappings); err != nil {

// If following link, pass symlink target for
// the link to be generated on the destination
var symlinkTarget string
if req.GetOptions.NoDerefSymlinks && info.Mode()&os.ModeType == os.ModeSymlink {
symlinkTarget, err = os.Readlink(item)
if err != nil {
return err
}
}
if err := copierHandlerGetOne(info, symlinkTarget, filepath.Base(queue[i]), item, req.GetOptions, tw, hardlinkChecker, idMappings); err != nil {
if req.GetOptions.IgnoreUnreadable && errorIsPermission(err) {
continue
}
Expand Down
102 changes: 102 additions & 0 deletions copier/copier_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1871,3 +1871,105 @@ func testRemove(t *testing.T) {
})
}
}

func TestGetNoDerefSymlink(t *testing.T) {
couldChroot := canChroot
canChroot = false
testGetNoDerefSymlink(t)
canChroot = couldChroot
}

func testGetNoDerefSymlink(t *testing.T) {
var testArchives = []struct {
name string
rootOnly bool
headers []tar.Header
contents map[string][]byte
expectedGetErrors []expectedError
}{
{
name: "regular",
rootOnly: false,
headers: []tar.Header{
{Name: "link-b", Typeflag: tar.TypeSymlink, Linkname: "../file-doesnt-exist", Size: 23, Mode: 0777, ModTime: testDate},
},
contents: map[string][]byte{
"archive-a": testArchiveSlice,
},
expectedGetErrors: []expectedError{
{inSubdir: false, name: "file-a", err: syscall.ENOENT},
{inSubdir: false, name: "link-b", err: syscall.ENOENT},
{inSubdir: false, name: "subdir-a/file-b", err: syscall.ENOENT},
{inSubdir: true, name: "link-0", err: syscall.ENOENT},
{inSubdir: true, name: "link-b", err: syscall.ENOENT},
{inSubdir: true, name: "subdir-a/file-b", err: syscall.ENOENT},
{inSubdir: true, name: "subdir-a/file-c", err: syscall.ENOENT},
},
},
}

topdir := "."
for _, testArchive := range testArchives {
if uid != 0 && testArchive.rootOnly {
t.Logf("test archive %q can only be tested with root privileges, skipping", testArchive.name)
continue
}

dir, err := makeContextFromArchive(t, makeArchive(testArchive.headers, testArchive.contents), topdir)
require.NoErrorf(t, err, "error creating context from archive %q", testArchive.name)

root := dir

for _, noDerefSymlinks := range []bool{true, false} {
for _, testItem := range testArchive.headers {
name := filepath.FromSlash(testItem.Name)
name = filepath.Join(root, topdir, name)
if !t.Failed() && testItem.Typeflag == tar.TypeSymlink {
t.Run(fmt.Sprintf("testSymlink"), func(t *testing.T) {
var getErr error
var wg sync.WaitGroup
getOptions := GetOptions{}
getOptions.NoDerefSymlinks = noDerefSymlinks
pipeReader, pipeWriter := io.Pipe()
wg.Add(1)
go func() {
getErr = Get(root, topdir, getOptions, []string{name}, pipeWriter)
pipeWriter.Close()
wg.Done()
}()
tr := tar.NewReader(pipeReader)
hdr, err := tr.Next()
actualContents := []string{}
for err == nil {
actualContents = append(actualContents, filepath.FromSlash(hdr.Linkname))
hdr, err = tr.Next()
}
sort.Strings(actualContents)
wg.Wait()

assert.Equal(t, io.EOF.Error(), err.Error(), "expected EOF at end of archive, got %q", err.Error())

// We stat the target(name) and then assert the err
// If we're following links (noDerefSymlinks: false)
// we expect an error because copier would create
// a link with a target which is non-existent (../file-b).
// https://github.com/containers/podman/issues/16585
//
// But if we're not following links, copier would
// create the link by following the target and stat
// should therefore return success.
_, err = os.Stat(name)
if !noDerefSymlinks {
fmt.Println(">>>>>>>> NAME: ", name)
assert.ErrorContains(t, getErr, fmt.Sprintf("copier: get: lstat %q", name))
} else {
assert.NoErrorf(t, getErr, "unexpected error from Get(%q): %v", name, getErr)
}

pipeReader.Close()
})
}
}
}
}
}

0 comments on commit c0e32d7

Please sign in to comment.