github.com/pingcap/badger@v1.5.1-0.20230103063557-828f39b09b6d/dir_windows.go (about)

     1  // +build windows
     2  
     3  /*
     4   * Copyright 2017 Dgraph Labs, Inc. and Contributors
     5   *
     6   * Licensed under the Apache License, Version 2.0 (the "License");
     7   * you may not use this file except in compliance with the License.
     8   * You may obtain a copy of the License at
     9   *
    10   *     http://www.apache.org/licenses/LICENSE-2.0
    11   *
    12   * Unless required by applicable law or agreed to in writing, software
    13   * distributed under the License is distributed on an "AS IS" BASIS,
    14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    15   * See the License for the specific language governing permissions and
    16   * limitations under the License.
    17   */
    18  
    19  package badger
    20  
    21  // OpenDir opens a directory in windows with write access for syncing.
    22  import (
    23  	"fmt"
    24  	"os"
    25  	"path/filepath"
    26  	"syscall"
    27  	"unsafe"
    28  
    29  	"github.com/pingcap/errors"
    30  	"golang.org/x/sys/windows"
    31  )
    32  
    33  func openDir(path string) (*os.File, error) {
    34  	fd, err := openDirWin(path)
    35  	if err != nil {
    36  		return nil, err
    37  	}
    38  	return os.NewFile(uintptr(fd), path), nil
    39  }
    40  
    41  func openDirWin(path string) (fd syscall.Handle, err error) {
    42  	if len(path) == 0 {
    43  		return syscall.InvalidHandle, syscall.ERROR_FILE_NOT_FOUND
    44  	}
    45  	pathp, err := syscall.UTF16PtrFromString(path)
    46  	if err != nil {
    47  		return syscall.InvalidHandle, err
    48  	}
    49  	access := uint32(syscall.GENERIC_READ | syscall.GENERIC_WRITE)
    50  	sharemode := uint32(syscall.FILE_SHARE_READ | syscall.FILE_SHARE_WRITE)
    51  	createmode := uint32(syscall.OPEN_EXISTING)
    52  	fl := uint32(syscall.FILE_FLAG_BACKUP_SEMANTICS)
    53  	return syscall.CreateFile(pathp, access, sharemode, nil, createmode, fl, 0)
    54  }
    55  
    56  // DirectoryLockGuard holds a lock on the directory.
    57  type directoryLockGuard struct {
    58  	fd *os.File
    59  }
    60  
    61  // AcquireDirectoryLock acquires exclusive access to a directory.
    62  func acquireDirectoryLock(dirPath string, pidFileName string, readOnly bool) (*directoryLockGuard, error) {
    63  	// Convert to absolute path so that Release still works even if we do an unbalanced
    64  	// chdir in the meantime.
    65  	absLockFilePath, err := filepath.Abs(filepath.Join(dirPath, pidFileName))
    66  	if err != nil {
    67  		return nil, errors.Wrap(err, "Cannot get absolute path for pid lock file")
    68  	}
    69  
    70  	file, err := createFileAndLock(absLockFilePath, readOnly, false)
    71  	if err != nil {
    72  		return nil, err
    73  	}
    74  	if file == nil {
    75  		file, err = createFileAndLock(absLockFilePath, readOnly, true)
    76  	}
    77  	if err != nil {
    78  		return nil, err
    79  	}
    80  
    81  	if !readOnly {
    82  		_, err = file.WriteAt([]byte(fmt.Sprintf("%d\n", os.Getpid())), 0)
    83  		if err != nil {
    84  			_ = file.Close()
    85  			return nil, errors.Wrap(err, "Cannot write to pid lock file")
    86  		}
    87  	}
    88  	return &directoryLockGuard{fd: file}, nil
    89  }
    90  
    91  var (
    92  	mod  = windows.NewLazyDLL("kernel32.dll")
    93  	proc = mod.NewProc("CreateFileW")
    94  )
    95  
    96  func createFileAndLock(path string, share bool, create bool) (*os.File, error) {
    97  	dwShareMode := uint32(0) // Exclusive (no sharing)
    98  	if share {
    99  		dwShareMode = windows.FILE_SHARE_READ
   100  	}
   101  	op := windows.OPEN_EXISTING
   102  	if create {
   103  		op = windows.CREATE_NEW
   104  	}
   105  
   106  	a, _, err := proc.Call(
   107  		uintptr(unsafe.Pointer(windows.StringToUTF16Ptr(path))),
   108  		uintptr(windows.GENERIC_READ|windows.GENERIC_WRITE),
   109  		uintptr(dwShareMode),
   110  		uintptr(0), // No security attributes.
   111  		uintptr(op),
   112  		uintptr(windows.FILE_ATTRIBUTE_NORMAL),
   113  		0, // No template file.
   114  	)
   115  	switch err.(windows.Errno) {
   116  	case 0:
   117  		return os.NewFile(a, path), nil
   118  	case windows.ERROR_FILE_NOT_FOUND:
   119  		return nil, nil
   120  	case windows.ERROR_SHARING_VIOLATION, windows.ERROR_FILE_EXISTS:
   121  		return nil, errors.New(fmt.Sprintf("Cannot acquire directory lock on %q, Another process is using this Badger database.", path))
   122  	default:
   123  		_ = windows.Close(windows.Handle(a))
   124  		return nil, errors.WithStack(err)
   125  	}
   126  }
   127  
   128  // Release removes the directory lock.
   129  func (g *directoryLockGuard) release() error {
   130  	if g.fd == nil {
   131  		return nil
   132  	}
   133  	return g.fd.Close()
   134  }