diff --git a/internal/third_party/dep/fs/fs.go b/internal/third_party/dep/fs/fs.go index 6e2720f3b..e6f5b98ad 100644 --- a/internal/third_party/dep/fs/fs.go +++ b/internal/third_party/dep/fs/fs.go @@ -36,6 +36,7 @@ import ( "fmt" "io" "io/fs" + "log/slog" "os" "path/filepath" "runtime" @@ -173,6 +174,14 @@ func CopyFile(src, dst string) (err error) { } else { return nil } + } else { + if fi, err := os.Lstat(src); err != nil { + return fmt.Errorf("stat failed: %w", err) + } else if !fi.Mode().IsRegular() { + fileType := fileTypeString(fi.Mode()) + slog.Debug("skip copying non-regular file", "src", src, "dst", dst, "type", fileType, "mode", fi.Mode()) + return nil + } } in, err := os.Open(src) @@ -246,6 +255,31 @@ func IsSymlink(path string) (bool, error) { return l.Mode()&os.ModeSymlink == os.ModeSymlink, nil } +// fileTypeString returns a human-readable description of the file type based on the mode. +func fileTypeString(mode os.FileMode) string { + switch { + case mode&os.ModeDir != 0: + return "directory" + case mode&os.ModeSymlink != 0: + return "symlink" + case mode&os.ModeNamedPipe != 0: + return "named pipe (FIFO)" + case mode&os.ModeSocket != 0: + return "socket" + case mode&os.ModeDevice != 0: + if mode&os.ModeCharDevice != 0 { + return "character device" + } + return "block device" + case mode&os.ModeIrregular != 0: + return "irregular file" + case mode.IsRegular(): + return "regular file" + default: + return "unknown file type" + } +} + // fixLongPath returns the extended-length (\\?\-prefixed) form of // path when needed, in order to avoid the default 260 character file // path limit imposed by Windows. If path is not easily converted to diff --git a/internal/third_party/dep/fs/fs_test.go b/internal/third_party/dep/fs/fs_test.go index 610771bc3..3f68db081 100644 --- a/internal/third_party/dep/fs/fs_test.go +++ b/internal/third_party/dep/fs/fs_test.go @@ -35,6 +35,7 @@ import ( "os" "path/filepath" "runtime" + "syscall" "testing" ) @@ -542,6 +543,48 @@ func TestIsDir(t *testing.T) { } } +func TestCopyFileSkipIrregularFiles(t *testing.T) { + dir := t.TempDir() + + // Create a regular file first + regularFile := filepath.Join(dir, "regular.txt") + if err := os.WriteFile(regularFile, []byte("regular file content"), 0644); err != nil { + t.Fatal(err) + } + + // Test copying a regular file - should succeed + dstRegular := filepath.Join(dir, "regular_copy.txt") + if err := CopyFile(regularFile, dstRegular); err != nil { + t.Fatalf("expected CopyFile to succeed for regular file, got error: %v", err) + } + + // Verify the regular file was copied + if _, err := os.Stat(dstRegular); err != nil { + t.Fatalf("expected regular file to be copied, but destination doesn't exist: %v", err) + } + + // Create a named pipe (FIFO) - this is an irregular file + // Note: syscall.Mkfifo is not available on Windows, so we skip this test there + pipeFile := filepath.Join(dir, "pipe") + if err := syscall.Mkfifo(pipeFile, 0644); err != nil { + if runtime.GOOS == "windows" { + t.Skip("skipping on windows - syscall.Mkfifo not available") + } + t.Fatal(err) + } + + // Test copying the named pipe - should skip without error + dstPipe := filepath.Join(dir, "pipe_copy") + if err := CopyFile(pipeFile, dstPipe); err != nil { + t.Fatalf("expected CopyFile to skip irregular file without error, got error: %v", err) + } + + // Verify the irregular file was NOT copied (destination should not exist) + if _, err := os.Stat(dstPipe); err == nil { + t.Fatalf("expected irregular file to be skipped, but destination exists") + } +} + func TestIsSymlink(t *testing.T) { var currentUID = os.Getuid()