diff --git a/internal/fs/meta_darwin.go b/internal/fs/meta_darwin.go index d7b28db2c..0b74064b3 100644 --- a/internal/fs/meta_darwin.go +++ b/internal/fs/meta_darwin.go @@ -23,3 +23,9 @@ func openMetadataHandle(path string, flag int) (*os.File, error) { _ = setFlags(f) return f, nil } + +// reopenMetadataHandle reopens a handle created by openMetadataHandle for reading. +// The caller must no longer use the original file. +func reopenMetadataHandle(f *os.File) (*os.File, error) { + return f, nil +} diff --git a/internal/fs/meta_linux.go b/internal/fs/meta_linux.go index a8165fa0a..3bfafe429 100644 --- a/internal/fs/meta_linux.go +++ b/internal/fs/meta_linux.go @@ -2,6 +2,7 @@ package fs import ( "os" + "syscall" "golang.org/x/sys/unix" ) @@ -23,3 +24,30 @@ func openMetadataHandle(path string, flag int) (*os.File, error) { _ = setFlags(f) return f, nil } + +// reopenMetadataHandle reopens a handle created by openMetadataHandle for reading. +// The caller must no longer use the original file. +func reopenMetadataHandle(f *os.File) (*os.File, error) { + defer func() { + _ = f.Close() + }() + + f2, err := os.OpenFile(linuxFdPath(f.Fd()), O_RDONLY, 0) + if err != nil { + return nil, err + } + defer func() { + _ = f2.Close() + }() + + // Duplicate the filehandle and use that to create a file object with the correct name. + // f2 will automatically close its file handle on garbage collection. + fd3, err := syscall.Dup(int(f2.Fd())) + if err != nil { + return nil, err + } + + f3 := os.NewFile(uintptr(fd3), f.Name()) + _ = setFlags(f3) + return f3, nil +} diff --git a/internal/fs/meta_windows.go b/internal/fs/meta_windows.go index 62d859439..570cca334 100644 --- a/internal/fs/meta_windows.go +++ b/internal/fs/meta_windows.go @@ -95,3 +95,37 @@ func openMetadataHandle(path string, flag int) (*os.File, error) { } return os.NewFile(uintptr(handle), path), nil } + +// reopenMetadataHandle reopens a handle created by openMetadataHandle for reading. +// The caller must no longer use the original file. +func reopenMetadataHandle(f *os.File) (*os.File, error) { + defer func() { + _ = f.Close() + }() + + fileAccess := windows.FILE_GENERIC_READ + shareMode := windows.FILE_SHARE_READ | windows.FILE_SHARE_WRITE | windows.FILE_SHARE_DELETE + attrs := windows.FILE_ATTRIBUTE_NORMAL | windows.FILE_FLAG_BACKUP_SEMANTICS + // FIXME correct FILE_FLAG_OPEN_REPARSE_POINT handling? + + handle, err := reOpenFile(windows.Handle(f.Fd()), uint32(fileAccess), uint32(shareMode), uint32(attrs)) + if err != nil { + return nil, err + } + + return os.NewFile(uintptr(handle), f.Name()), nil +} + +var ( + modkernel32 = windows.NewLazySystemDLL("kernel32.dll") + procReOpenFile = modkernel32.NewProc("ReOpenFile") +) + +func reOpenFile(original windows.Handle, access uint32, mode uint32, attrs uint32) (handle windows.Handle, err error) { + r0, _, e1 := syscall.SyscallN(procReOpenFile.Addr(), uintptr(original), uintptr(access), uintptr(mode), uintptr(attrs)) + handle = windows.Handle(r0) + if handle == windows.InvalidHandle { + err = errnoErr(e1) + } + return +}