package fs import ( "fmt" "os" "reflect" "sync" "sync/atomic" "unsafe" "github.com/VictoriaMetrics/VictoriaMetrics/lib/logger" "golang.org/x/sys/windows" ) var ( kernelDLL = windows.MustLoadDLL("kernel32.dll") procLock = kernelDLL.MustFindProc("LockFileEx") procEvent = kernelDLL.MustFindProc("CreateEventW") procDisk = kernelDLL.MustFindProc("GetDiskFreeSpaceExW") ) // at windows only files could be synced // Sync for directories is not supported. func mustSyncPath(path string) { } func mustRemoveDirAtomic(dir string) { if !IsPathExist(dir) { return } n := atomic.AddUint64(&atomicDirRemoveCounter, 1) tmpDir := fmt.Sprintf("%s.must-remove.%d", dir, n) if err := os.Rename(dir, tmpDir); err != nil { logger.Panicf("FATAL: cannot move %s to %s: %s", dir, tmpDir, err) } err := os.RemoveAll(tmpDir) if err != nil { logger.Warnf("cannot remove dir: %q: %s, restart VictoriaMetrics process to complete file deletion", tmpDir, err) } } const ( lockfileExclusiveLock = 2 fileFlagNormal = 0x00000080 ) // https://github.com/juju/fslock/blob/master/fslock_windows.go func createFlockFile(flockFile string) (*os.File, error) { name, err := windows.UTF16PtrFromString(flockFile) if err != nil { return nil, err } handle, err := windows.CreateFile( name, windows.GENERIC_READ|windows.DELETE, windows.FILE_SHARE_READ|windows.FILE_SHARE_DELETE, nil, windows.OPEN_ALWAYS, windows.FILE_FLAG_OVERLAPPED|fileFlagNormal, 0) if err != nil { return nil, fmt.Errorf("cannot create lock file %q: %w", flockFile, err) } ol, err := newOverlapped() if err != nil { return nil, fmt.Errorf("cannot create Overlapped handler: %w", err) } // https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-lockfileex r1, _, err := procLock.Call(uintptr(handle), uintptr(lockfileExclusiveLock), uintptr(0), uintptr(1), uintptr(0), uintptr(unsafe.Pointer(ol))) if r1 == 0 { return nil, err } return os.NewFile(uintptr(handle), flockFile), nil } var ( mmapByAddrLock sync.Mutex mmapByAddr = map[uintptr]windows.Handle{} ) func mmap(fd int, length int) ([]byte, error) { flProtect := uint32(windows.PAGE_READONLY) dwDesiredAccess := uint32(windows.FILE_MAP_READ) // https://learn.microsoft.com/en-us/windows/win32/memory/creating-a-file-mapping-object#file-mapping-size // do not specify any length params, windows will set it according to the file size. // If length > file size, truncate is required according to api definition, we don't want it. h, errno := windows.CreateFileMapping(windows.Handle(fd), nil, flProtect, 0, 0, nil) if h == 0 { return nil, os.NewSyscallError("CreateFileMapping", errno) } addr, errno := windows.MapViewOfFile(h, dwDesiredAccess, 0, 0, 0) if addr == 0 { windows.CloseHandle(h) return nil, os.NewSyscallError("MapViewOfFile", errno) } data := make([]byte, 0) hdr := (*reflect.SliceHeader)(unsafe.Pointer(&data)) hdr.Data = addr hdr.Len = length hdr.Cap = hdr.Len mmapByAddrLock.Lock() mmapByAddr[addr] = h mmapByAddrLock.Unlock() return data, nil } func mUnmap(data []byte) error { // flush is not needed, since we perform only reading operation. // In case of write, additional call FlushViewOfFile must be performed. header := (*reflect.SliceHeader)(unsafe.Pointer(&data)) addr := header.Data mmapByAddrLock.Lock() h, ok := mmapByAddr[addr] if !ok { logger.Fatalf("BUG: unmapping for non exist addr: %d", addr) } delete(mmapByAddr, addr) mmapByAddrLock.Unlock() if err := windows.UnmapViewOfFile(addr); err != nil { return fmt.Errorf("cannot unmap memory mapped file: %w", err) } errno := windows.CloseHandle(h) return os.NewSyscallError("CloseHandle", errno) } func mustGetFreeSpace(path string) uint64 { var freeBytes int64 r, _, err := procDisk.Call(uintptr(unsafe.Pointer(windows.StringToUTF16Ptr(path))), uintptr(unsafe.Pointer(&freeBytes))) if r == 0 { logger.Panicf("FATAL: cannot get free space for %q : %s", path, err) } return uint64(freeBytes) } // stub func fadviseSequentialRead(f *os.File, prefetch bool) error { return nil } // copied from https://github.com/juju/fslock/blob/master/fslock_windows.go // https://docs.microsoft.com/en-us/windows/win32/api/minwinbase/ns-minwinbase-overlapped func newOverlapped() (*windows.Overlapped, error) { event, err := createEvent(nil, nil) if err != nil { return nil, err } return &windows.Overlapped{HEvent: event}, nil } // copied from https://github.com/juju/fslock/blob/master/fslock_windows.go // https://docs.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-createeventa func createEvent(sa *windows.SecurityAttributes, name *uint16) (windows.Handle, error) { r0, _, err := procEvent.Call(uintptr(unsafe.Pointer(sa)), uintptr(1), uintptr(1), uintptr(unsafe.Pointer(name))) handle := windows.Handle(r0) if handle == windows.InvalidHandle { return 0, err } return handle, nil }