pkg/lockedfile/lockedfile_windows.go (134 lines of code) (raw):
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
package lockedfile
import (
"golang.org/x/sys/windows"
"syscall"
"time"
)
const (
reserved = 0
allBytes = ^uint32(0)
fileIOTimeoutInMilliseconds = 10000
)
type lockedFile struct {
fileHandle windows.Handle
metadata *Metadata
}
func newInner(filePath string, timeout time.Duration, metadata *Metadata) (*lockedFile, error) {
name, err := windows.UTF16PtrFromString(filePath)
if err != nil {
return nil, err
}
// Open for asynchronous I/O so that we can timeout waiting for the lock.
// Also open shared so that other processes can open the file (but will
// still need to lock it).
handle, err := windows.CreateFile(
name,
windows.GENERIC_READ|windows.GENERIC_WRITE,
uint32(windows.FILE_SHARE_READ|windows.FILE_SHARE_WRITE),
nil,
windows.OPEN_ALWAYS,
windows.FILE_FLAG_OVERLAPPED|windows.FILE_ATTRIBUTE_NORMAL,
0)
if err != nil {
return nil, err
}
ol, err := getOverlapped()
if err != nil {
return nil, err
}
defer windows.CloseHandle(ol.HEvent)
err = windows.LockFileEx(handle, windows.LOCKFILE_EXCLUSIVE_LOCK, reserved, allBytes, allBytes, ol)
if err == nil {
return &lockedFile{handle, metadata}, nil
}
// ERROR_IO_PENDING is expected when we're waiting on an asynchronous event
// to occur.
if err != syscall.ERROR_IO_PENDING {
return nil, err
}
timeoutInMilliseconds := uint32(timeout / time.Millisecond)
s, err := windows.WaitForSingleObject(ol.HEvent, timeoutInMilliseconds)
switch s {
case syscall.WAIT_OBJECT_0:
// success!
return &lockedFile{handle, metadata}, nil
case syscall.WAIT_TIMEOUT:
windows.CancelIo(handle)
return nil, &FileLockTimeoutError{"file lock could not be acquired in the specified time"}
default:
return nil, err
}
}
func (self *lockedFile) ReadLockedFile() ([]byte, error) {
// make an empty byte slice with 4KB default size
fileBytes := make([]byte, 0, 4096)
buffer := make([]byte, 4096, 4096)
ol, err := getOverlapped()
if err != nil {
return nil, err
}
defer windows.Close(ol.HEvent)
for {
err := windows.ReadFile(self.fileHandle, buffer, nil, ol)
if err != nil && err != syscall.ERROR_IO_PENDING {
return nil, err
}
var readBytes uint32
err = windows.GetOverlappedResult(self.fileHandle, ol, &readBytes, true)
if err != nil {
if err == windows.ERROR_HANDLE_EOF {
break
}
return nil, err
}
fileBytes = append(fileBytes, buffer[:readBytes]...)
// modify ol to read next bytes
longOffset := combineTwoUint32ToUlong(ol.OffsetHigh, ol.Offset)
longOffset += uint64(readBytes)
ol.OffsetHigh, ol.Offset = splitUlongToTwoUint32(longOffset)
err = windows.ResetEvent(ol.HEvent)
if err != nil {
return nil, err
}
}
return fileBytes, nil
}
// warning this function does not resize the file when overwriting to a smaller size
func (self *lockedFile) WriteLockedFile(bytes []byte) error {
ol, err := getOverlapped()
if err != nil {
return err
}
defer windows.Close(ol.HEvent)
err = windows.WriteFile(self.fileHandle, bytes, nil, ol)
if err != syscall.ERROR_IO_PENDING {
return err
}
s, err := windows.WaitForSingleObject(ol.HEvent, fileIOTimeoutInMilliseconds)
switch s {
case syscall.WAIT_OBJECT_0:
// success writing file
return err
case syscall.WAIT_TIMEOUT:
windows.CancelIo(self.fileHandle)
return &FileIoTimeout{"fileIO timed out"}
default:
return err
}
return nil
}
func (self *lockedFile) closeInner() error {
err := windows.UnlockFileEx(self.fileHandle, reserved, allBytes, allBytes, &windows.Overlapped{HEvent: 0})
if err != nil {
return err
}
return windows.Close(self.fileHandle)
}
func getOverlapped() (*windows.Overlapped, error) {
event, err := windows.CreateEvent(nil, 1, 0, nil)
if err != nil {
return nil, err
}
return &windows.Overlapped{HEvent: event}, nil
}
func splitUlongToTwoUint32(ulong uint64) (high uint32, low uint32) {
low = uint32(ulong)
high = uint32(ulong >> 32)
return
}
func combineTwoUint32ToUlong(high uint32, low uint32) (long uint64) {
long = uint64(high)
long = long << 32
long += uint64(low)
return long
}