Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 16 additions & 2 deletions pkg/os/os.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,9 +98,12 @@ func CopyFile(src, dst string) (err error) {
if info.IsDir() {
return errors.New("cannot read from directories")
}
if dstInfo, statErr := os.Stat(dst); statErr == nil && os.SameFile(info, dstInfo) {
return errors.New("source and destination are the same file")
}
Comment thread
zjubiology marked this conversation as resolved.
Resolved

// create new file, truncate if exists and apply same permissions as the original one
dstfile, err := os.OpenFile(dst, os.O_RDWR|os.O_CREATE|os.O_TRUNC, info.Mode().Perm()) //nolint:gosec
// Open without truncating so we can check the opened file's identity first.
dstfile, err := os.OpenFile(dst, os.O_RDWR|os.O_CREATE, info.Mode().Perm()) //nolint:gosec
if err != nil {
return err
}
Expand All @@ -114,6 +117,17 @@ func CopyFile(src, dst string) (err error) {
}
}()

dstInfo, err := dstfile.Stat()
if err != nil {
return err
}
if os.SameFile(info, dstInfo) {
return errors.New("source and destination are the same file")
}
if err := dstfile.Truncate(0); err != nil {
return err
}

_, err = io.Copy(dstfile, srcfile)
return err
}
33 changes: 33 additions & 0 deletions pkg/os/os_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,39 @@ func TestMustReadFile(t *testing.T) {
}
}

func TestCopyFileRejectsSameFile(t *testing.T) {
for _, name := range []string{"same path", "hard link", "symbolic link"} {
t.Run(name, func(t *testing.T) {
src := filepath.Join(t.TempDir(), "source")
content := []byte("must survive a failed copy")
if err := os.WriteFile(src, content, 0600); err != nil {
t.Fatal(err)
}
dst := src
if name != "same path" {
dst = src + ".link"
link := os.Link
if name == "symbolic link" {
link = os.Symlink
}
if err := link(src, dst); err != nil {
t.Skipf("link is unavailable: %v", err)
}
}
if err := CopyFile(src, dst); err == nil {
t.Fatal("expected copying a file onto itself to fail")
}
got, err := os.ReadFile(src)
if err != nil {
t.Fatal(err)
}
if string(got) != string(content) {
t.Fatalf("source content changed: got %q, want %q", got, content)
}
})
}
}

func TestMustWriteFile(t *testing.T) {
tempDir := t.TempDir()
testFile := filepath.Join(tempDir, "test.txt")
Expand Down
Loading