VictoriaMetrics/lib/fs/fs_windows.go
Nikolay 5d0299ac19
lib/fs: do not panic at windows at dir deletion (#4132)
Windows doesn't allow to remove dir with opened files. Usually it's a case for snapshots, hard cannot be removed if file is openned.
With this change, dir will be renamed and properly deleted at the next process start.
It's recommended to restart vmstorage/vmsingle for snapshots deletion completion periodically.
https://github.com/VictoriaMetrics/VictoriaMetrics/issues/70
2023-05-08 23:11:55 -07:00

164 lines
4.8 KiB
Go

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
}