Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

winapi: Implement CopyFileA #499

Merged
merged 1 commit into from
Oct 8, 2021
Merged
Show file tree
Hide file tree
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
2 changes: 2 additions & 0 deletions lib/winapi/fileapi.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ BOOL DeleteFileA (LPCTSTR lpFileName);
BOOL RemoveDirectoryA (LPCSTR lpPathName);
BOOL CreateDirectoryA (LPCSTR lpPathName, LPSECURITY_ATTRIBUTES lpSecurityAttributes);
BOOL MoveFileA (LPCTSTR lpExistingFileName, LPCTSTR lpNewFileName);
BOOL CopyFileA (LPCSTR lpExistingFileName, LPCSTR lpNewFileName, BOOL bFailIfExists);

BOOL GetDiskFreeSpaceExA (LPCSTR lpDirectoryName, PULARGE_INTEGER lpFreeBytesAvailableToCaller, PULARGE_INTEGER lpTotalNumberOfBytes, PULARGE_INTEGER lpTotalNumberOfFreeBytes);
BOOL GetDiskFreeSpaceA (LPCSTR lpRootPathName, LPDWORD lpSectorsPerCluster, LPDWORD lpBytesPerSector, LPDWORD lpNumberOfFreeClusters, LPDWORD lpTotalNumberOfClusters);
Expand All @@ -57,6 +58,7 @@ DWORD GetLogicalDriveStringsA (DWORD nBufferLength, LPSTR lpBuffer);
#define RemoveDirectory(...) RemoveDirectoryA(__VA_ARGS__)
#define CreateDirectory(...) CreateDirectoryA(__VA_ARGS__)
#define MoveFile(...) MoveFileA(__VA_ARGS__)
#define CopyFile(...) CopyFileA(__VA_ARGS__)
#define GetDiskFreeSpaceEx GetDiskFreeSpaceExA
#define GetDiskFreeSpace GetDiskFreeSpaceA
#define GetLogicalDriveStrings GetLogicalDriveStringsA
Expand Down
150 changes: 143 additions & 7 deletions lib/winapi/filemanip.c
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#include <fileapi.h>
#include <handleapi.h>
#include <winbase.h>
#include <winerror.h>
#include <assert.h>
Expand Down Expand Up @@ -162,14 +163,22 @@ BOOL SetFileTime (HANDLE hFile, const FILETIME *lpCreationTime, const FILETIME *
return TRUE;
}

static NTSTATUS DeleteHandle (HANDLE handle)
{
IO_STATUS_BLOCK ioStatusBlock;
FILE_DISPOSITION_INFORMATION dispositionInformation;
dispositionInformation.DeleteFile = TRUE;

return NtSetInformationFile(handle, &ioStatusBlock, &dispositionInformation, sizeof(dispositionInformation), FileDispositionInformation);
}

BOOL DeleteFileA (LPCTSTR lpFileName)
{
NTSTATUS status;
HANDLE handle;
ANSI_STRING path;
OBJECT_ATTRIBUTES objectAttributes;
IO_STATUS_BLOCK ioStatusBlock;
FILE_DISPOSITION_INFORMATION dispositionInformation;

assert(lpFileName != NULL);
RtlInitAnsiString(&path, lpFileName);
Expand All @@ -182,9 +191,7 @@ BOOL DeleteFileA (LPCTSTR lpFileName)
return FALSE;
}

dispositionInformation.DeleteFile = TRUE;

status = NtSetInformationFile(handle, &ioStatusBlock, &dispositionInformation, sizeof(dispositionInformation), FileDispositionInformation);
status = DeleteHandle(handle);

if (!NT_SUCCESS(status)) {
NtClose(handle);
Expand All @@ -208,7 +215,6 @@ BOOL RemoveDirectoryA (LPCSTR lpPathName)
ANSI_STRING path;
OBJECT_ATTRIBUTES objectAttributes;
IO_STATUS_BLOCK ioStatusBlock;
FILE_DISPOSITION_INFORMATION dispositionInformation;

assert(lpPathName != NULL);
RtlInitAnsiString(&path, lpPathName);
Expand All @@ -221,9 +227,8 @@ BOOL RemoveDirectoryA (LPCSTR lpPathName)
return FALSE;
}

dispositionInformation.DeleteFile = TRUE;
status = DeleteHandle(handle);

status = NtSetInformationFile(handle, &ioStatusBlock, &dispositionInformation, sizeof(dispositionInformation), FileDispositionInformation);
if (!NT_SUCCESS(status)) {
SetLastError(RtlNtStatusToDosError(status));
return FALSE;
Expand Down Expand Up @@ -303,6 +308,137 @@ BOOL MoveFileA (LPCTSTR lpExistingFileName, LPCTSTR lpNewFileName)
}
}

BOOL CopyFileA (LPCSTR lpExistingFileName, LPCSTR lpNewFileName, BOOL bFailIfExists)
{
NTSTATUS status;
HANDLE sourceHandle;
HANDLE targetHandle = INVALID_HANDLE_VALUE;
ANSI_STRING targetPath;
OBJECT_ATTRIBUTES objectAttributes;
IO_STATUS_BLOCK ioStatusBlock;
FILE_BASIC_INFORMATION fileBasicInformation;
FILE_NETWORK_OPEN_INFORMATION networkOpenInformation;
LPVOID readBuffer = NULL;
SIZE_T readBufferRegionSize = 64 * 1024;
DWORD bytesRead;

sourceHandle = CreateFile(
lpExistingFileName,
FILE_GENERIC_READ,
FILE_SHARE_READ,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN,
NULL);
if (sourceHandle == INVALID_HANDLE_VALUE) {
return FALSE;
}

status = NtQueryInformationFile(
sourceHandle,
&ioStatusBlock,
&networkOpenInformation,
sizeof(networkOpenInformation),
FileNetworkOpenInformation);
if (!NT_SUCCESS(status)) {
NtClose(sourceHandle);
SetLastError(RtlNtStatusToDosError(status));
return FALSE;
thrimbor marked this conversation as resolved.
Show resolved Hide resolved
}

RtlInitAnsiString(&targetPath, lpNewFileName);
InitializeObjectAttributes(&objectAttributes, &targetPath, OBJ_CASE_INSENSITIVE, ObDosDevicesDirectory(), NULL);

status = NtCreateFile(
&targetHandle,
FILE_GENERIC_WRITE,
&objectAttributes,
&ioStatusBlock,
&networkOpenInformation.AllocationSize,
networkOpenInformation.FileAttributes,
0,
bFailIfExists ? FILE_CREATE : FILE_SUPERSEDE,
FILE_SYNCHRONOUS_IO_NONALERT | FILE_NON_DIRECTORY_FILE | FILE_SEQUENTIAL_ONLY);
if (!NT_SUCCESS(status)) {
NtClose(sourceHandle);
SetLastError(RtlNtStatusToDosError(status));
return FALSE;
}

status = NtAllocateVirtualMemory(&readBuffer,
0,
&readBufferRegionSize,
MEM_RESERVE | MEM_COMMIT,
PAGE_READWRITE);
if (!NT_SUCCESS(status)) {
NtClose(sourceHandle);
NtClose(targetHandle);
SetLastError(RtlNtStatusToDosError(status));
return FALSE;
}

while (TRUE) {
const BYTE *bufferPos = readBuffer;
if (!ReadFile(sourceHandle, readBuffer, readBufferRegionSize, &bytesRead, NULL)) {
NtFreeVirtualMemory(&readBuffer, &readBufferRegionSize, MEM_RELEASE);
DeleteHandle(targetHandle);
NtClose(sourceHandle);
NtClose(targetHandle);
return FALSE;
thrimbor marked this conversation as resolved.
Show resolved Hide resolved
}

if (!bytesRead) {
break;
}

while (bytesRead > 0) {
DWORD bytesWritten = 0;
if (!WriteFile(targetHandle, bufferPos, bytesRead, &bytesWritten, NULL)) {
NtFreeVirtualMemory(&readBuffer, &readBufferRegionSize, MEM_RELEASE);
DeleteHandle(targetHandle);
NtClose(sourceHandle);
NtClose(targetHandle);
return FALSE;
}
bytesRead -= bytesWritten;
bufferPos += bytesWritten;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't the kernel offer some call or IOCTL to copy files?
This user-space copy loop feels a bit involved and there's probably a lot that can go wrong (stuff like not applying the proper flags to the file when creating it, not taking advantage of file-system optimizations etc.)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Totally agree; I didn't see a function that looked appropriate in xboxkrnl.h so I assumed there was no lower level method that could be utilized. Similarly I did not have luck searching the win32 docs.

Is there someplace else I could look?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Poked around a bit more, the only lower level copying I found was added as part of windows server after win2k/xp days.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Possibly something more like NtWriteFile() than using the user abstraction WriteFile() is what he's hinting at?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I assume he was looking for something more like https://docs.microsoft.com/en-us/windows/win32/api/winioctl/ni-winioctl-fsctl_duplicate_extents_to_file where the entire copy loop is handled at a lower level (but that's not available in the XBOX kernel).

I think a switch to NtWriteFile would end up duplicating much of the content of WriteFile and still operating in user-space. I don't know enough to say if the efficiency gain warrants the maintenance cost, but happy to make that change if it's preferred.

@JayFoxRox can you clarify?

}

status = NtFreeVirtualMemory(&readBuffer, &readBufferRegionSize, MEM_RELEASE);
assert(NT_SUCCESS(status));

thrimbor marked this conversation as resolved.
Show resolved Hide resolved
RtlZeroMemory(&fileBasicInformation, sizeof(fileBasicInformation));
fileBasicInformation.LastWriteTime = networkOpenInformation.LastWriteTime;
fileBasicInformation.FileAttributes = networkOpenInformation.FileAttributes;
status = NtSetInformationFile(
targetHandle,
&ioStatusBlock,
&fileBasicInformation,
sizeof(fileBasicInformation),
FileBasicInformation);
if (!NT_SUCCESS(status)) {
SetLastError(RtlNtStatusToDosError(status));
NtClose(sourceHandle);
NtClose(targetHandle);
return FALSE;
}

status = NtClose(sourceHandle);
if (!NT_SUCCESS(status)) {
SetLastError(RtlNtStatusToDosError(status));
NtClose(targetHandle);
return FALSE;
}

status = NtClose(targetHandle);
if (!NT_SUCCESS(status)) {
SetLastError(RtlNtStatusToDosError(status));
return FALSE;
}
return TRUE;
}

BOOL GetDiskFreeSpaceExA (LPCSTR lpDirectoryName, PULARGE_INTEGER lpFreeBytesAvailableToCaller, PULARGE_INTEGER lpTotalNumberOfBytes, PULARGE_INTEGER lpTotalNumberOfFreeBytes)
{
NTSTATUS status;
Expand Down