Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 79 additions & 5 deletions pkg/blockdevice/new_block_device_from_file_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,15 @@ func NewBlockDeviceFromFile(path string, minimumSizeBytes int, zeroInitialize bo
return nil, 0, 0, util.StatusWrapf(err, "Failed to open file %#v", path)
}

bd, sectorSizeBytes, sectorCount, err := createBlockDevice(path, minimumSizeBytes, zeroInitialize, handle)
if err != nil {
windows.CloseHandle(handle)
return nil, 0, 0, err
}
return bd, sectorSizeBytes, sectorCount, nil
}

func createBlockDevice(path string, minimumSizeBytes int, zeroInitialize bool, handle windows.Handle) (BlockDevice, int, int64, error) {
// Get the sector size from the disk where the file is located.

// Extract the root path (drive letter) from the full path.
Expand All @@ -59,7 +68,6 @@ func NewBlockDeviceFromFile(path string, minimumSizeBytes int, zeroInitialize bo
}
rootPathPtr, err := syscall.UTF16PtrFromString(rootPath)
if err != nil {
windows.CloseHandle(handle)
return nil, 0, 0, util.StatusWrapf(err, "Failed to convert root path %#v to UTF-16", rootPath)
}

Expand All @@ -72,7 +80,6 @@ func NewBlockDeviceFromFile(path string, minimumSizeBytes int, zeroInitialize bo
/*numberOfFreeClusters=*/ 0,
/*totalNumberOfClusters=*/ 0)
if r == 0 {
windows.CloseHandle(handle)
return nil, 0, 0, util.StatusWrapf(err, "Failed to get disk sector size for %#v", rootPath)
}

Expand All @@ -82,25 +89,92 @@ func NewBlockDeviceFromFile(path string, minimumSizeBytes int, zeroInitialize bo
sectorCount := int64((uint64(minimumSizeBytes) + uint64(sectorSizeBytes) - 1) / uint64(sectorSizeBytes))
sizeBytes := int64(sectorSizeBytes) * sectorCount

// Determine if we need to make the file sparse.
// If we want to zero initialise the file, then we always make the
// file sparse as this is the most efficient way of doing so.
// We also mark the file as sparse if we need to resize the file.
// If we do not mark the file as sparse, then when this process
// terminates, Windows will write out the entire file's contents
// to disk, even if only 1 byte has been written to the file. See
// https://devblogs.microsoft.com/oldnewthing/20110922-00/?p=9573
// for further details.
var makeFileSparse bool
if zeroInitialize {
makeFileSparse = true
} else {
// Check if we need to resize the file.
var fileInfo windows.ByHandleFileInformation
if err := windows.GetFileInformationByHandle(handle, &fileInfo); err != nil {
return nil, 0, 0, util.StatusWrapf(err, "Failed to get file information for %#v", path)
}
existingSize := int64(fileInfo.FileSizeHigh)<<32 | int64(fileInfo.FileSizeLow)
makeFileSparse = existingSize < int64(minimumSizeBytes)
}

var fileSizeHigh int32 = int32(sizeBytes >> 32)
if _, err = windows.SetFilePointer(handle, int32(sizeBytes), (*int32)(unsafe.Pointer(&fileSizeHigh)), windows.FILE_BEGIN); err != nil {
windows.CloseHandle(handle)
return nil, 0, 0, util.StatusWrapf(err, "Failed to set file pointer for %#v", path)
}

if err := windows.SetEndOfFile(handle); err != nil {
windows.CloseHandle(handle)
return nil, 0, 0, util.StatusWrapf(err, "Failed to set end of file for %#v to %d bytes", path, sizeBytes)
}

if makeFileSparse {
if err := makeSparseFile(handle, sizeBytes); err != nil {
return nil, 0, 0, util.StatusWrapf(err, "Failed to make file sparse for %#v", path)
}
}

bd, err := newMemoryMappedBlockDevice(handle, int(sizeBytes))
if err != nil {
windows.CloseHandle(handle)
return nil, 0, 0, err
}
return bd, sectorSizeBytes, sectorCount, nil
}

func makeSparseFile(handle windows.Handle, sizeBytes int64) error {
// FILE_ZERO_DATA_INFORMATION
type fileZeroDataInformation struct {
FileOffset int64
BeyondFinalZero int64
}

// Mark the file as sparse.
var bytesReturned uint32
if err := windows.DeviceIoControl(
handle,
windows.FSCTL_SET_SPARSE,
/*inBuffer=*/ nil,
/*inBufferSize=*/ 0,
/*outBuffer=*/ nil,
/*outBufferSize=*/ 0,
&bytesReturned,
/*overlapped=*/ nil,
); err != nil {
return util.StatusWrapf(err, "Failed to mark file as sparse")
}
// Zero the file's data.
zeroData := fileZeroDataInformation{
FileOffset: 0,
BeyondFinalZero: sizeBytes,
}
if err := windows.DeviceIoControl(
handle,
windows.FSCTL_SET_ZERO_DATA,
(*byte)(unsafe.Pointer(&zeroData)),
uint32(unsafe.Sizeof(zeroData)),
/*outBuffer=*/ nil,
/*outBufferSize=*/ 0,
&bytesReturned,
/*overlapped=*/ nil,
); err != nil {
return util.StatusWrapf(err, "Failed to zero sparse file data")
}

return nil
}

// A ScopeWalker that extracts the root path for GetDiskFreeSpaceW.
type rootPathScopeWalker struct {
rootPath string
Expand Down
Loading