dir_windows.go raw

   1  //go:build windows
   2  // +build windows
   3  
   4  /*
   5   * SPDX-FileCopyrightText: © Hypermode Inc. <hello@hypermode.com>
   6   * SPDX-License-Identifier: Apache-2.0
   7   */
   8  
   9  package badger
  10  
  11  // OpenDir opens a directory in windows with write access for syncing.
  12  import (
  13  	"os"
  14  	"path/filepath"
  15  	"syscall"
  16  
  17  	"github.com/dgraph-io/badger/v4/y"
  18  )
  19  
  20  // FILE_ATTRIBUTE_TEMPORARY - A file that is being used for temporary storage.
  21  // FILE_FLAG_DELETE_ON_CLOSE - The file is to be deleted immediately after all of its handles are
  22  // closed, which includes the specified handle and any other open or duplicated handles.
  23  // See: https://docs.microsoft.com/en-us/windows/desktop/FileIO/file-attribute-constants
  24  // NOTE: Added here to avoid importing golang.org/x/sys/windows
  25  const (
  26  	FILE_ATTRIBUTE_TEMPORARY  = 0x00000100
  27  	FILE_FLAG_DELETE_ON_CLOSE = 0x04000000
  28  )
  29  
  30  func openDir(path string) (*os.File, error) {
  31  	fd, err := openDirWin(path)
  32  	if err != nil {
  33  		return nil, err
  34  	}
  35  	return os.NewFile(uintptr(fd), path), nil
  36  }
  37  
  38  func openDirWin(path string) (fd syscall.Handle, err error) {
  39  	if len(path) == 0 {
  40  		return syscall.InvalidHandle, syscall.ERROR_FILE_NOT_FOUND
  41  	}
  42  	pathp, err := syscall.UTF16PtrFromString(path)
  43  	if err != nil {
  44  		return syscall.InvalidHandle, err
  45  	}
  46  	access := uint32(syscall.GENERIC_READ | syscall.GENERIC_WRITE)
  47  	sharemode := uint32(syscall.FILE_SHARE_READ | syscall.FILE_SHARE_WRITE)
  48  	createmode := uint32(syscall.OPEN_EXISTING)
  49  	fl := uint32(syscall.FILE_FLAG_BACKUP_SEMANTICS)
  50  	return syscall.CreateFile(pathp, access, sharemode, nil, createmode, fl, 0)
  51  }
  52  
  53  // DirectoryLockGuard holds a lock on the directory.
  54  type directoryLockGuard struct {
  55  	h    syscall.Handle
  56  	path string
  57  }
  58  
  59  // AcquireDirectoryLock acquires exclusive access to a directory.
  60  func acquireDirectoryLock(dirPath string, pidFileName string, readOnly bool) (*directoryLockGuard, error) {
  61  	if readOnly {
  62  		return nil, ErrWindowsNotSupported
  63  	}
  64  
  65  	// Convert to absolute path so that Release still works even if we do an unbalanced
  66  	// chdir in the meantime.
  67  	absLockFilePath, err := filepath.Abs(filepath.Join(dirPath, pidFileName))
  68  	if err != nil {
  69  		return nil, y.Wrap(err, "Cannot get absolute path for pid lock file")
  70  	}
  71  
  72  	// This call creates a file handler in memory that only one process can use at a time. When
  73  	// that process ends, the file is deleted by the system.
  74  	// FILE_ATTRIBUTE_TEMPORARY is used to tell Windows to try to create the handle in memory.
  75  	// FILE_FLAG_DELETE_ON_CLOSE is not specified in syscall_windows.go but tells Windows to delete
  76  	// the file when all processes holding the handler are closed.
  77  	// XXX: this works but it's a bit klunky. i'd prefer to use LockFileEx but it needs unsafe pkg.
  78  	h, err := syscall.CreateFile(
  79  		syscall.StringToUTF16Ptr(absLockFilePath), 0, 0, nil,
  80  		syscall.OPEN_ALWAYS,
  81  		uint32(FILE_ATTRIBUTE_TEMPORARY|FILE_FLAG_DELETE_ON_CLOSE),
  82  		0)
  83  	if err != nil {
  84  		return nil, y.Wrapf(err,
  85  			"Cannot create lock file %q.  Another process is using this Badger database",
  86  			absLockFilePath)
  87  	}
  88  
  89  	return &directoryLockGuard{h: h, path: absLockFilePath}, nil
  90  }
  91  
  92  // Release removes the directory lock.
  93  func (g *directoryLockGuard) release() error {
  94  	g.path = ""
  95  	return syscall.CloseHandle(g.h)
  96  }
  97  
  98  // Windows doesn't support syncing directories to the file system. See
  99  // https://github.com/hypermodeinc/badger/issues/699#issuecomment-504133587 for more details.
 100  func syncDir(dir string) error { return nil }
 101