mirror of
https://github.com/VictoriaMetrics/VictoriaMetrics.git
synced 2024-12-15 16:30:55 +01:00
5d0299ac19
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
165 lines
4.8 KiB
Go
165 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
|
|
}
|