package sequential import ( "os" "path/filepath" "strconv" "sync" "time" "unsafe" "golang.org/x/sys/windows" ) // Create is a copy of [os.Create], modified to use sequential file access. // // It uses [windows.FILE_FLAG_SEQUENTIAL_SCAN] rather than [windows.FILE_ATTRIBUTE_NORMAL] // as implemented in golang. Refer to the [Win32 API documentation] for details // on sequential file access. // // [Win32 API documentation]: https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilea#FILE_FLAG_SEQUENTIAL_SCAN func Create(name string) (*os.File, error) { return openFileSequential(name, windows.O_RDWR|windows.O_CREAT|windows.O_TRUNC) } // Open is a copy of [os.Open], modified to use sequential file access. // // It uses [windows.FILE_FLAG_SEQUENTIAL_SCAN] rather than [windows.FILE_ATTRIBUTE_NORMAL] // as implemented in golang. Refer to the [Win32 API documentation] for details // on sequential file access. // // [Win32 API documentation]: https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilea#FILE_FLAG_SEQUENTIAL_SCAN func Open(name string) (*os.File, error) { return openFileSequential(name, windows.O_RDONLY) } // OpenFile is a copy of [os.OpenFile], modified to use sequential file access. // // It uses [windows.FILE_FLAG_SEQUENTIAL_SCAN] rather than [windows.FILE_ATTRIBUTE_NORMAL] // as implemented in golang. Refer to the [Win32 API documentation] for details // on sequential file access. // // [Win32 API documentation]: https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilea#FILE_FLAG_SEQUENTIAL_SCAN func OpenFile(name string, flag int, _ os.FileMode) (*os.File, error) { return openFileSequential(name, flag) } func openFileSequential(name string, flag int) (file *os.File, err error) { if name == "" { return nil, &os.PathError{Op: "open", Path: name, Err: windows.ERROR_FILE_NOT_FOUND} } r, e := openSequential(name, flag|windows.O_CLOEXEC) if e != nil { return nil, &os.PathError{Op: "open", Path: name, Err: e} } return os.NewFile(uintptr(r), name), nil } func makeInheritSa() *windows.SecurityAttributes { var sa windows.SecurityAttributes sa.Length = uint32(unsafe.Sizeof(sa)) sa.InheritHandle = 1 return &sa } func openSequential(path string, mode int) (fd windows.Handle, err error) { if len(path) == 0 { return windows.InvalidHandle, windows.ERROR_FILE_NOT_FOUND } pathp, err := windows.UTF16PtrFromString(path) if err != nil { return windows.InvalidHandle, err } var access uint32 switch mode & (windows.O_RDONLY | windows.O_WRONLY | windows.O_RDWR) { case windows.O_RDONLY: access = windows.GENERIC_READ case windows.O_WRONLY: access = windows.GENERIC_WRITE case windows.O_RDWR: access = windows.GENERIC_READ | windows.GENERIC_WRITE } if mode&windows.O_CREAT != 0 { access |= windows.GENERIC_WRITE } if mode&windows.O_APPEND != 0 { access &^= windows.GENERIC_WRITE access |= windows.FILE_APPEND_DATA } sharemode := uint32(windows.FILE_SHARE_READ | windows.FILE_SHARE_WRITE) var sa *windows.SecurityAttributes if mode&windows.O_CLOEXEC == 0 { sa = makeInheritSa() } var createmode uint32 switch { case mode&(windows.O_CREAT|windows.O_EXCL) == (windows.O_CREAT | windows.O_EXCL): createmode = windows.CREATE_NEW case mode&(windows.O_CREAT|windows.O_TRUNC) == (windows.O_CREAT | windows.O_TRUNC): createmode = windows.CREATE_ALWAYS case mode&windows.O_CREAT == windows.O_CREAT: createmode = windows.OPEN_ALWAYS case mode&windows.O_TRUNC == windows.O_TRUNC: createmode = windows.TRUNCATE_EXISTING default: createmode = windows.OPEN_EXISTING } // Use FILE_FLAG_SEQUENTIAL_SCAN rather than FILE_ATTRIBUTE_NORMAL as implemented in golang. // https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilea#FILE_FLAG_SEQUENTIAL_SCAN h, e := windows.CreateFile(pathp, access, sharemode, sa, createmode, windows.FILE_FLAG_SEQUENTIAL_SCAN, 0) return h, e } // Helpers for CreateTemp var ( rand uint32 randmu sync.Mutex ) func reseed() uint32 { return uint32(time.Now().UnixNano() + int64(os.Getpid())) } func nextSuffix() string { randmu.Lock() r := rand if r == 0 { r = reseed() } r = r*1664525 + 1013904223 // constants from Numerical Recipes rand = r randmu.Unlock() return strconv.Itoa(int(1e9 + r%1e9))[1:] } // CreateTemp is a copy of [os.CreateTemp], modified to use sequential file access. // // It uses [windows.FILE_FLAG_SEQUENTIAL_SCAN] rather than [windows.FILE_ATTRIBUTE_NORMAL] // as implemented in golang. Refer to the [Win32 API documentation] for details // on sequential file access. // // [Win32 API documentation]: https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilea#FILE_FLAG_SEQUENTIAL_SCAN func CreateTemp(dir, prefix string) (f *os.File, err error) { if dir == "" { dir = os.TempDir() } nconflict := 0 for i := 0; i < 10000; i++ { name := filepath.Join(dir, prefix+nextSuffix()) f, err = openFileSequential(name, windows.O_RDWR|windows.O_CREAT|windows.O_EXCL) if os.IsExist(err) { if nconflict++; nconflict > 10 { randmu.Lock() rand = reseed() randmu.Unlock() } continue } break } return }