From f3a03c416408bf7d81dee438a10566428962c5ee Mon Sep 17 00:00:00 2001 From: Nikolay Date: Sat, 27 Feb 2021 01:37:07 +0300 Subject: [PATCH] Adds windows build (#1040) * fixes windows compilation, adds signal impl for windows, adds free space usage for windows, https://github.com/VictoriaMetrics/VictoriaMetrics/issues/70 https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1036 NOTE victoria metrics database still CANNOT work under windows system, only vmagent is supported. To completly port victoria metrics, you have to fix issues with separators, parsing and posix file removall * rollback separator * Adds windows setInformation api, it must behave like unix, need to test it. changes procutil * check for invlaid param * Fixes posix delete semantic * refactored a bit * fixes openbsd build * removed windows api call * Fixes code after windows add * Update lib/procutil/signal_windows.go Co-authored-by: Aliaksandr Valialkin --- app/vmagent/Makefile | 4 + lib/fs/fs.go | 42 +--------- lib/fs/fs_nix.go | 22 ++++- lib/fs/fs_openbsd.go | 18 ++++ lib/fs/fs_unix.go | 45 ++++++++++ lib/fs/fs_windows.go | 149 +++++++++++++++++++++++++++++++++ lib/fs/reader_at.go | 5 +- lib/procutil/signal.go | 2 + lib/procutil/signal_windows.go | 60 +++++++++++++ 9 files changed, 305 insertions(+), 42 deletions(-) create mode 100644 lib/fs/fs_unix.go create mode 100644 lib/fs/fs_windows.go create mode 100644 lib/procutil/signal_windows.go diff --git a/app/vmagent/Makefile b/app/vmagent/Makefile index 0ee29504d..e0be998ab 100644 --- a/app/vmagent/Makefile +++ b/app/vmagent/Makefile @@ -73,6 +73,10 @@ vmagent-ppc64le: vmagent-386: CGO_ENABLED=0 GOARCH=386 $(MAKE) vmagent-local-with-goarch +vmagent-windows: + GOOS=windows CGO_ENABLED=0 $(MAKE) vmagent-local-with-goarch + + vmagent-local-with-goarch: APP_NAME=vmagent $(MAKE) app-local-with-goarch diff --git a/lib/fs/fs.go b/lib/fs/fs.go index 229e0266b..8f8c65943 100644 --- a/lib/fs/fs.go +++ b/lib/fs/fs.go @@ -13,26 +13,15 @@ import ( "github.com/VictoriaMetrics/VictoriaMetrics/lib/fasttime" "github.com/VictoriaMetrics/VictoriaMetrics/lib/filestream" "github.com/VictoriaMetrics/VictoriaMetrics/lib/logger" - "golang.org/x/sys/unix" ) +var tmpFileNum uint64 + // MustSyncPath syncs contents of the given path. func MustSyncPath(path string) { - d, err := os.Open(path) - if err != nil { - logger.Panicf("FATAL: cannot open %q: %s", path, err) - } - if err := d.Sync(); err != nil { - _ = d.Close() - logger.Panicf("FATAL: cannot flush %q to storage: %s", path, err) - } - if err := d.Close(); err != nil { - logger.Panicf("FATAL: cannot close %q: %s", path, err) - } + mustSyncPath(path) } -var tmpFileNum uint64 - // WriteFileAtomically atomically writes data to the given file path. // // WriteFileAtomically returns only after the file is fully written and synced @@ -332,15 +321,7 @@ func MustWriteData(w io.Writer, data []byte) { // CreateFlockFile creates flock.lock file in the directory dir // and returns the handler to the file. func CreateFlockFile(dir string) (*os.File, error) { - flockFile := dir + "/flock.lock" - flockF, err := os.Create(flockFile) - if err != nil { - return nil, fmt.Errorf("cannot create lock file %q: %w", flockFile, err) - } - if err := unix.Flock(int(flockF.Fd()), unix.LOCK_EX|unix.LOCK_NB); err != nil { - return nil, fmt.Errorf("cannot acquire lock on file %q: %w", flockFile, err) - } - return flockF, nil + return createFlockFile(dir) } // MustGetFreeSpace returns free space for the given directory path. @@ -372,18 +353,3 @@ type freeSpaceEntry struct { updateTime uint64 freeSpace uint64 } - -func mustGetFreeSpace(path string) uint64 { - d, err := os.Open(path) - if err != nil { - logger.Panicf("FATAL: cannot determine free disk space on %q: %s", path, err) - } - defer MustClose(d) - - fd := d.Fd() - var stat unix.Statfs_t - if err := unix.Fstatfs(int(fd), &stat); err != nil { - logger.Panicf("FATAL: cannot determine free disk space on %q: %s", path, err) - } - return freeSpace(stat) -} diff --git a/lib/fs/fs_nix.go b/lib/fs/fs_nix.go index a1e126127..df7c51fef 100644 --- a/lib/fs/fs_nix.go +++ b/lib/fs/fs_nix.go @@ -2,7 +2,27 @@ package fs -import "golang.org/x/sys/unix" +import ( + "os" + + "github.com/VictoriaMetrics/VictoriaMetrics/lib/logger" + "golang.org/x/sys/unix" +) + +func mustGetFreeSpace(path string) uint64 { + d, err := os.Open(path) + if err != nil { + logger.Panicf("FATAL: cannot determine free disk space on %q: %s", path, err) + } + defer MustClose(d) + + fd := d.Fd() + var stat unix.Statfs_t + if err := unix.Fstatfs(int(fd), &stat); err != nil { + logger.Panicf("FATAL: cannot determine free disk space on %q: %s", path, err) + } + return freeSpace(stat) +} func freeSpace(stat unix.Statfs_t) uint64 { return uint64(stat.Bavail) * uint64(stat.Bsize) diff --git a/lib/fs/fs_openbsd.go b/lib/fs/fs_openbsd.go index 1ab4b9358..9d48a9ee1 100644 --- a/lib/fs/fs_openbsd.go +++ b/lib/fs/fs_openbsd.go @@ -1,9 +1,27 @@ package fs import ( + "os" + + "github.com/VictoriaMetrics/VictoriaMetrics/lib/logger" "golang.org/x/sys/unix" ) +func mustGetFreeSpace(path string) uint64 { + d, err := os.Open(path) + if err != nil { + logger.Panicf("FATAL: cannot determine free disk space on %q: %s", path, err) + } + defer MustClose(d) + + fd := d.Fd() + var stat unix.Statfs_t + if err := unix.Fstatfs(int(fd), &stat); err != nil { + logger.Panicf("FATAL: cannot determine free disk space on %q: %s", path, err) + } + return freeSpace(stat) +} + func freeSpace(stat unix.Statfs_t) uint64 { return uint64(stat.F_bavail) * uint64(stat.F_bsize) } diff --git a/lib/fs/fs_unix.go b/lib/fs/fs_unix.go new file mode 100644 index 000000000..ceb27d3a3 --- /dev/null +++ b/lib/fs/fs_unix.go @@ -0,0 +1,45 @@ +// +build linux darwin freebsd openbsd + +package fs + +import ( + "fmt" + "os" + + "github.com/VictoriaMetrics/VictoriaMetrics/lib/logger" + "golang.org/x/sys/unix" +) + +func mmap(fd int, offset int64, length int) (data []byte, err error) { + return unix.Mmap(fd, 0, length, unix.PROT_READ, unix.MAP_SHARED) + +} +func mUnmap(data []byte) error { + return unix.Munmap(data) +} + +func mustSyncPath(path string) { + d, err := os.Open(path) + if err != nil { + logger.Panicf("FATAL: cannot open %q: %s", path, err) + } + if err := d.Sync(); err != nil { + _ = d.Close() + logger.Panicf("FATAL: cannot flush %q to storage: %s", path, err) + } + if err := d.Close(); err != nil { + logger.Panicf("FATAL: cannot close %q: %s", path, err) + } +} + +func createFlockFile(dir string) (*os.File, error) { + flockFile := dir + "/flock.lock" + flockF, err := os.Create(flockFile) + if err != nil { + return nil, fmt.Errorf("cannot create lock file %q: %w", flockFile, err) + } + if err := unix.Flock(int(flockF.Fd()), unix.LOCK_EX|unix.LOCK_NB); err != nil { + return nil, fmt.Errorf("cannot acquire lock on file %q: %w", flockFile, err) + } + return flockF, nil +} diff --git a/lib/fs/fs_windows.go b/lib/fs/fs_windows.go new file mode 100644 index 000000000..e92e3391f --- /dev/null +++ b/lib/fs/fs_windows.go @@ -0,0 +1,149 @@ +package fs + +import ( + "fmt" + "os" + "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") + ntDLL = windows.MustLoadDLL("ntdll.dll") + ntSetInformationProc = ntDLL.MustFindProc("NtSetInformationFile") +) + +// panic at windows, if file already open by another process. +// one of possible solutions - change files opening process with correct flags. +// https://github.com/dgraph-io/badger/issues/699 +// https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-flushfilebuffers +func mustSyncPath(string) { +} + +const ( + lockfileExclusiveLock = 2 + fileFlagNormal = 0x00000080 + // https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/ntddk/ns-ntddk-_file_disposition_information_ex + fileDispositionPosixSemantics = 0x00000002 + fileDispositionIgnoreReadonlyAttribute = 0x00000010 +) + +// createFlockFile creates flock.lock file in the directory dir +// and returns the handler to the file. +// https://github.com/juju/fslock/blob/master/fslock_windows.go +func createFlockFile(dir string) (*os.File, error) { + flockFile := dir + "/flock.lock" + 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 + // overlapped is dropped? + 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 +} + +// stub +func mmap(fd int, offset int64, length int) ([]byte, error) { + return nil, nil +} + +// stub +func mUnmap([]byte) error { + return nil +} + +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.Errorf("cannot get free space: %v", err) + return 0 + } + 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 +} + +// https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/ntddk/ns-ntddk-_file_disposition_information_ex +type fileDispositionInformationEx struct { + Flags uint32 +} + +// https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/ns-wdm-_io_status_block +type ioStatusBlock struct { + Status, Information uintptr +} + +// UpdateFileHandle - changes file deletion semantic at windows to posix-like. +func UpdateFileHandle(path string) error { + handle, err := windows.Open(path, windows.GENERIC_READ|windows.DELETE, windows.FILE_SHARE_READ|windows.FILE_SHARE_DELETE) + if err != nil { + return err + } + return setPosixDelete(handle) +} + +// supported starting with Windows 10, version 1709. +// supported by NTFS only. +func setPosixDelete(handle windows.Handle) error { + var iosb ioStatusBlock + flags := fileDispositionInformationEx{ + Flags: fileDispositionPosixSemantics | fileDispositionIgnoreReadonlyAttribute, + } + // class FileDispositionInformationEx, // 64 + // https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/ne-wdm-_file_information_class + r0, _, err := ntSetInformationProc.Call(uintptr(handle), uintptr(unsafe.Pointer(&iosb)), uintptr(unsafe.Pointer(&flags)), unsafe.Sizeof(flags), uintptr(64)) + if r0 == 0 { + return nil + } + return fmt.Errorf("cannot set file disposition information: NT_STATUS: 0x%X, error: %w", r0, err) +} diff --git a/lib/fs/reader_at.go b/lib/fs/reader_at.go index 219bbcbb1..13850695c 100644 --- a/lib/fs/reader_at.go +++ b/lib/fs/reader_at.go @@ -7,7 +7,6 @@ import ( "github.com/VictoriaMetrics/VictoriaMetrics/lib/logger" "github.com/VictoriaMetrics/metrics" - "golang.org/x/sys/unix" ) var disableMmap = flag.Bool("fs.disableMmap", is32BitPtr, "Whether to use pread() instead of mmap() for reading data files. "+ @@ -65,7 +64,7 @@ func (r *ReaderAt) MustReadAt(p []byte, off int64) { func (r *ReaderAt) MustClose() { fname := r.f.Name() if len(r.mmapData) > 0 { - if err := unix.Munmap(r.mmapData[:cap(r.mmapData)]); err != nil { + if err := mUnmap(r.mmapData[:cap(r.mmapData)]); err != nil { logger.Panicf("FATAL: cannot unmap data for file %q: %s", fname, err) } r.mmapData = nil @@ -135,7 +134,7 @@ func mmapFile(f *os.File, size int64) ([]byte, error) { if size%4096 != 0 { size += 4096 - size%4096 } - data, err := unix.Mmap(int(f.Fd()), 0, int(size), unix.PROT_READ, unix.MAP_SHARED) + data, err := mmap(int(f.Fd()), 0, int(size)) if err != nil { return nil, fmt.Errorf("cannot mmap file with size %d: %w", size, err) } diff --git a/lib/procutil/signal.go b/lib/procutil/signal.go index ade5acd2b..7076b0b71 100644 --- a/lib/procutil/signal.go +++ b/lib/procutil/signal.go @@ -1,3 +1,5 @@ +// +build !windows + package procutil import ( diff --git a/lib/procutil/signal_windows.go b/lib/procutil/signal_windows.go new file mode 100644 index 000000000..6b16aee73 --- /dev/null +++ b/lib/procutil/signal_windows.go @@ -0,0 +1,60 @@ +// +build windows + +package procutil + +import ( + "os" + "os/signal" + "sync" + "syscall" +) + +// WaitForSigterm waits for either SIGTERM or SIGINT +// +// Returns the caught signal. +// +// Windows dont have SIGHUP syscall. +func WaitForSigterm() os.Signal { + ch := make(chan os.Signal, 1) + signal.Notify(ch, os.Interrupt, syscall.SIGTERM) + sig := <-ch + return sig +} + +type sigHUPNotifier struct { + lock sync.Mutex + subscribers []chan<- os.Signal +} + +var notifier sigHUPNotifier + +// https://golang.org/pkg/os/signal/#hdr-Windows +// https://github.com/golang/go/issues/6948 +// SelfSIGHUP sends SIGHUP signal to the subscribed listeners. +func SelfSIGHUP() { + notifier.notify(syscall.SIGHUP) +} + +// NewSighupChan returns a channel, which is triggered on every SelfSIGHUP. +func NewSighupChan() <-chan os.Signal { + ch := make(chan os.Signal, 1) + notifier.subscribe(ch) + return ch +} + +func (sn *sigHUPNotifier) subscribe(sub chan<- os.Signal) { + sn.lock.Lock() + defer sn.lock.Unlock() + sn.subscribers = append(sn.subscribers, sub) +} + +func (sn *sigHUPNotifier) notify(sig os.Signal) { + sn.lock.Lock() + defer sn.lock.Unlock() + for _, sub := range sn.subscribers { + select { + case sub <- sig: + default: + } + } +}