/* * Process Hacker - * file stream object * * Copyright (C) 2010-2011 wj32 * * This file is part of Process Hacker. * * Process Hacker is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Process Hacker is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Process Hacker. If not, see . */ #include #include #include PPH_OBJECT_TYPE PhFileStreamType; BOOLEAN PhFileStreamInitialization( VOID ) { PH_OBJECT_TYPE_PARAMETERS parameters; parameters.FreeListSize = sizeof(PH_FILE_STREAM); parameters.FreeListCount = 16; PhFileStreamType = PhCreateObjectTypeEx(L"FileStream", PH_OBJECT_TYPE_USE_FREE_LIST, PhpFileStreamDeleteProcedure, ¶meters); return TRUE; } NTSTATUS PhCreateFileStream( _Out_ PPH_FILE_STREAM *FileStream, _In_ PWSTR FileName, _In_ ACCESS_MASK DesiredAccess, _In_ ULONG ShareAccess, _In_ ULONG CreateDisposition, _In_ ULONG Flags ) { NTSTATUS status; PPH_FILE_STREAM fileStream; HANDLE fileHandle; ULONG createOptions; if (Flags & PH_FILE_STREAM_ASYNCHRONOUS) createOptions = FILE_NON_DIRECTORY_FILE; else createOptions = FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT; if (!NT_SUCCESS(status = PhCreateFileWin32( &fileHandle, FileName, DesiredAccess, 0, ShareAccess, CreateDisposition, createOptions ))) return status; if (!NT_SUCCESS(status = PhCreateFileStream2( &fileStream, fileHandle, Flags, PAGE_SIZE ))) { NtClose(fileHandle); return status; } if (Flags & PH_FILE_STREAM_APPEND) { LARGE_INTEGER zero; zero.QuadPart = 0; if (!NT_SUCCESS(PhSeekFileStream( fileStream, &zero, SeekEnd ))) { PhDereferenceObject(fileStream); return status; } } *FileStream = fileStream; return status; } NTSTATUS PhCreateFileStream2( _Out_ PPH_FILE_STREAM *FileStream, _In_ HANDLE FileHandle, _In_ ULONG Flags, _In_ ULONG BufferLength ) { PPH_FILE_STREAM fileStream; fileStream = PhCreateObject(sizeof(PH_FILE_STREAM), PhFileStreamType); fileStream->FileHandle = FileHandle; fileStream->Flags = Flags; fileStream->Position.QuadPart = 0; if (!(Flags & PH_FILE_STREAM_UNBUFFERED)) { fileStream->Buffer = NULL; fileStream->BufferLength = BufferLength; } else { fileStream->Buffer = NULL; fileStream->BufferLength = 0; } fileStream->ReadPosition = 0; fileStream->ReadLength = 0; fileStream->WritePosition = 0; *FileStream = fileStream; return STATUS_SUCCESS; } VOID NTAPI PhpFileStreamDeleteProcedure( _In_ PVOID Object, _In_ ULONG Flags ) { PPH_FILE_STREAM fileStream = (PPH_FILE_STREAM)Object; PhFlushFileStream(fileStream, FALSE); if (!(fileStream->Flags & PH_FILE_STREAM_HANDLE_UNOWNED)) NtClose(fileStream->FileHandle); if (fileStream->Buffer) PhFreePage(fileStream->Buffer); } /** * Verifies that a file stream's position matches the position held by the file object. */ VOID PhVerifyFileStream( _In_ PPH_FILE_STREAM FileStream ) { NTSTATUS status; // If the file object is asynchronous, the file object doesn't maintain its position. if (!(FileStream->Flags & ( PH_FILE_STREAM_OWN_POSITION | PH_FILE_STREAM_ASYNCHRONOUS ))) { FILE_POSITION_INFORMATION positionInfo; IO_STATUS_BLOCK isb; if (!NT_SUCCESS(status = NtQueryInformationFile( FileStream->FileHandle, &isb, &positionInfo, sizeof(FILE_POSITION_INFORMATION), FilePositionInformation ))) PhRaiseStatus(status); if (FileStream->Position.QuadPart != positionInfo.CurrentByteOffset.QuadPart) PhRaiseStatus(STATUS_INTERNAL_ERROR); } } NTSTATUS PhpAllocateBufferFileStream( _Inout_ PPH_FILE_STREAM FileStream ) { FileStream->Buffer = PhAllocatePage(FileStream->BufferLength, NULL); if (FileStream->Buffer) return STATUS_SUCCESS; else return STATUS_NO_MEMORY; } NTSTATUS PhpReadFileStream( _Inout_ PPH_FILE_STREAM FileStream, _Out_writes_bytes_(Length) PVOID Buffer, _In_ ULONG Length, _Out_opt_ PULONG ReadLength ) { NTSTATUS status; IO_STATUS_BLOCK isb; PLARGE_INTEGER position; position = NULL; if (FileStream->Flags & PH_FILE_STREAM_OWN_POSITION) position = &FileStream->Position; status = NtReadFile( FileStream->FileHandle, NULL, NULL, NULL, &isb, Buffer, Length, position, NULL ); if (status == STATUS_PENDING) { // Wait for the operation to finish. This probably means we got called on an asynchronous // file object. status = NtWaitForSingleObject(FileStream->FileHandle, FALSE, NULL); if (NT_SUCCESS(status)) status = isb.Status; } if (NT_SUCCESS(status)) { FileStream->Position.QuadPart += isb.Information; if (ReadLength) *ReadLength = (ULONG)isb.Information; } return status; } NTSTATUS PhReadFileStream( _Inout_ PPH_FILE_STREAM FileStream, _Out_writes_bytes_(Length) PVOID Buffer, _In_ ULONG Length, _Out_opt_ PULONG ReadLength ) { NTSTATUS status = STATUS_SUCCESS; ULONG availableLength; ULONG readLength; if (FileStream->Flags & PH_FILE_STREAM_UNBUFFERED) { return PhpReadFileStream( FileStream, Buffer, Length, ReadLength ); } // How much do we have available to copy out of the buffer? availableLength = FileStream->ReadLength - FileStream->ReadPosition; if (availableLength == 0) { // Make sure buffered writes are flushed. if (FileStream->WritePosition != 0) { if (!NT_SUCCESS(status = PhpFlushWriteFileStream(FileStream))) return status; } // If this read is too big, pass it through. if (Length >= FileStream->BufferLength) { // These are now invalid. FileStream->ReadPosition = 0; FileStream->ReadLength = 0; return PhpReadFileStream( FileStream, Buffer, Length, ReadLength ); } if (!FileStream->Buffer) { if (!NT_SUCCESS(status = PhpAllocateBufferFileStream(FileStream))) return status; } // Read as much as we can into our buffer. if (!NT_SUCCESS(status = PhpReadFileStream( FileStream, FileStream->Buffer, FileStream->BufferLength, &readLength ))) return status; if (readLength == 0) { // No data read. if (ReadLength) *ReadLength = readLength; return status; } FileStream->ReadPosition = 0; FileStream->ReadLength = readLength; } else { readLength = availableLength; } if (readLength > Length) readLength = Length; // Try to satisfy the request from the buffer. memcpy( Buffer, (PCHAR)FileStream->Buffer + FileStream->ReadPosition, readLength ); FileStream->ReadPosition += readLength; // If we didn't completely satisfy the request, read some more. if ( readLength < Length && // Don't try to read more if the buffer wasn't even filled up last time. (No more to read.) FileStream->ReadLength == FileStream->BufferLength ) { ULONG readLength2; if (NT_SUCCESS(status = PhpReadFileStream( FileStream, (PCHAR)Buffer + readLength, Length - readLength, &readLength2 ))) { readLength += readLength2; // These are now invalid. FileStream->ReadPosition = 0; FileStream->ReadLength = 0; } } if (NT_SUCCESS(status)) { if (ReadLength) *ReadLength = readLength; } return status; } NTSTATUS PhpWriteFileStream( _Inout_ PPH_FILE_STREAM FileStream, _In_reads_bytes_(Length) PVOID Buffer, _In_ ULONG Length ) { NTSTATUS status; IO_STATUS_BLOCK isb; PLARGE_INTEGER position; position = NULL; if (FileStream->Flags & PH_FILE_STREAM_OWN_POSITION) position = &FileStream->Position; status = NtWriteFile( FileStream->FileHandle, NULL, NULL, NULL, &isb, Buffer, Length, position, NULL ); if (status == STATUS_PENDING) { // Wait for the operation to finish. This probably means we got called on an asynchronous // file object. status = NtWaitForSingleObject(FileStream->FileHandle, FALSE, NULL); if (NT_SUCCESS(status)) status = isb.Status; } if (NT_SUCCESS(status)) { FileStream->Position.QuadPart += isb.Information; FileStream->Flags |= PH_FILE_STREAM_WRITTEN; } return status; } NTSTATUS PhWriteFileStream( _Inout_ PPH_FILE_STREAM FileStream, _In_reads_bytes_(Length) PVOID Buffer, _In_ ULONG Length ) { NTSTATUS status = STATUS_SUCCESS; ULONG availableLength; ULONG writtenLength; if (FileStream->Flags & PH_FILE_STREAM_UNBUFFERED) { return PhpWriteFileStream( FileStream, Buffer, Length ); } if (FileStream->WritePosition == 0) { // Make sure buffered reads are flushed. if (!NT_SUCCESS(status = PhpFlushReadFileStream(FileStream))) return status; } if (FileStream->WritePosition != 0) { availableLength = FileStream->BufferLength - FileStream->WritePosition; // Try to satisfy the request by copying the data to the buffer. if (availableLength != 0) { writtenLength = availableLength; if (writtenLength > Length) writtenLength = Length; memcpy( (PCHAR)FileStream->Buffer + FileStream->WritePosition, Buffer, writtenLength ); FileStream->WritePosition += writtenLength; if (writtenLength == Length) { // The request has been completely satisfied. return status; } Buffer = (PCHAR)Buffer + writtenLength; Length -= writtenLength; } // If we didn't completely satisfy the request, it's because the buffer is full. Flush it. if (!NT_SUCCESS(status = PhpWriteFileStream( FileStream, FileStream->Buffer, FileStream->WritePosition ))) return status; FileStream->WritePosition = 0; } // If the write is too big, pass it through. if (Length >= FileStream->BufferLength) { if (!NT_SUCCESS(status = PhpWriteFileStream( FileStream, Buffer, Length ))) return status; } else if (Length != 0) { if (!FileStream->Buffer) { if (!NT_SUCCESS(status = PhpAllocateBufferFileStream(FileStream))) return status; } // Completely satisfy the request by copying the data to the buffer. memcpy( FileStream->Buffer, Buffer, Length ); FileStream->WritePosition = Length; } return status; } NTSTATUS PhpFlushReadFileStream( _Inout_ PPH_FILE_STREAM FileStream ) { NTSTATUS status = STATUS_SUCCESS; if (FileStream->ReadLength - FileStream->ReadPosition != 0) { LARGE_INTEGER offset; // We have some buffered read data, so our position is too far ahead. We need to move it // back to the first unused byte. offset.QuadPart = -(LONG)(FileStream->ReadLength - FileStream->ReadPosition); if (!NT_SUCCESS(status = PhpSeekFileStream( FileStream, &offset, SeekCurrent ))) return status; } FileStream->ReadPosition = 0; FileStream->ReadLength = 0; return status; } NTSTATUS PhpFlushWriteFileStream( _Inout_ PPH_FILE_STREAM FileStream ) { NTSTATUS status = STATUS_SUCCESS; if (!NT_SUCCESS(status = PhpWriteFileStream( FileStream, FileStream->Buffer, FileStream->WritePosition ))) return status; FileStream->WritePosition = 0; return status; } /** * Flushes the file stream. * * \param FileStream A file stream object. * \param Full TRUE to flush the file object through the operating system, otherwise FALSE to only * ensure the buffer is flushed to the operating system. */ NTSTATUS PhFlushFileStream( _Inout_ PPH_FILE_STREAM FileStream, _In_ BOOLEAN Full ) { NTSTATUS status = STATUS_SUCCESS; if (FileStream->WritePosition != 0) { if (!NT_SUCCESS(status = PhpFlushWriteFileStream(FileStream))) return status; } if (FileStream->ReadPosition != 0) { if (!NT_SUCCESS(status = PhpFlushReadFileStream(FileStream))) return status; } if (Full && (FileStream->Flags & PH_FILE_STREAM_WRITTEN)) { IO_STATUS_BLOCK isb; if (!NT_SUCCESS(status = NtFlushBuffersFile( FileStream->FileHandle, &isb ))) return status; } return status; } VOID PhGetPositionFileStream( _In_ PPH_FILE_STREAM FileStream, _Out_ PLARGE_INTEGER Position ) { Position->QuadPart = FileStream->Position.QuadPart + (FileStream->ReadPosition - FileStream->ReadLength) + FileStream->WritePosition; } NTSTATUS PhpSeekFileStream( _Inout_ PPH_FILE_STREAM FileStream, _In_ PLARGE_INTEGER Offset, _In_ PH_SEEK_ORIGIN Origin ) { NTSTATUS status = STATUS_SUCCESS; switch (Origin) { case SeekStart: { FileStream->Position = *Offset; } break; case SeekCurrent: { FileStream->Position.QuadPart += Offset->QuadPart; } break; case SeekEnd: { if (!NT_SUCCESS(status = PhGetFileSize( FileStream->FileHandle, &FileStream->Position ))) return status; FileStream->Position.QuadPart += Offset->QuadPart; } break; } if (!(FileStream->Flags & PH_FILE_STREAM_OWN_POSITION)) { FILE_POSITION_INFORMATION positionInfo; IO_STATUS_BLOCK isb; positionInfo.CurrentByteOffset = FileStream->Position; if (!NT_SUCCESS(status = NtSetInformationFile( FileStream->FileHandle, &isb, &positionInfo, sizeof(FILE_POSITION_INFORMATION), FilePositionInformation ))) return status; } return status; } NTSTATUS PhSeekFileStream( _Inout_ PPH_FILE_STREAM FileStream, _In_ PLARGE_INTEGER Offset, _In_ PH_SEEK_ORIGIN Origin ) { NTSTATUS status = STATUS_SUCCESS; LARGE_INTEGER offset; offset = *Offset; if (FileStream->WritePosition != 0) { if (!NT_SUCCESS(status = PhpFlushWriteFileStream(FileStream))) return status; } else if (FileStream->ReadPosition != 0) { if (Origin == SeekCurrent) { // We have buffered read data, which means our position is too far ahead. Subtract this // difference from the offset (which will affect the position accordingly). offset.QuadPart -= FileStream->ReadLength - FileStream->ReadPosition; } // TODO: Try to keep some of the read buffer. FileStream->ReadPosition = 0; FileStream->ReadLength = 0; } if (!NT_SUCCESS(status = PhpSeekFileStream( FileStream, &offset, Origin ))) return status; return status; } NTSTATUS PhLockFileStream( _Inout_ PPH_FILE_STREAM FileStream, _In_ PLARGE_INTEGER Position, _In_ PLARGE_INTEGER Length, _In_ BOOLEAN Wait, _In_ BOOLEAN Shared ) { NTSTATUS status; IO_STATUS_BLOCK isb; status = NtLockFile( FileStream->FileHandle, NULL, NULL, NULL, &isb, Position, Length, 0, !Wait, !Shared ); if (status == STATUS_PENDING) { // Wait for the operation to finish. This probably means we got called on an asynchronous // file object. NtWaitForSingleObject(FileStream->FileHandle, FALSE, NULL); status = isb.Status; } return status; } NTSTATUS PhUnlockFileStream( _Inout_ PPH_FILE_STREAM FileStream, _In_ PLARGE_INTEGER Position, _In_ PLARGE_INTEGER Length ) { IO_STATUS_BLOCK isb; return NtUnlockFile( FileStream->FileHandle, &isb, Position, Length, 0 ); } NTSTATUS PhWriteStringAsUtf8FileStream( _Inout_ PPH_FILE_STREAM FileStream, _In_ PPH_STRINGREF String ) { return PhWriteStringAsUtf8FileStreamEx(FileStream, String->Buffer, String->Length); } NTSTATUS PhWriteStringAsUtf8FileStream2( _Inout_ PPH_FILE_STREAM FileStream, _In_ PWSTR String ) { PH_STRINGREF string; PhInitializeStringRef(&string, String); return PhWriteStringAsUtf8FileStream(FileStream, &string); } NTSTATUS PhWriteStringAsUtf8FileStreamEx( _Inout_ PPH_FILE_STREAM FileStream, _In_ PWSTR Buffer, _In_ SIZE_T Length ) { NTSTATUS status = STATUS_SUCCESS; PH_STRINGREF block; SIZE_T inPlaceUtf8Size; PCHAR inPlaceUtf8 = NULL; PPH_BYTES utf8 = NULL; if (Length > PAGE_SIZE) { // In UTF-8, the maximum number of bytes per code point is 4. inPlaceUtf8Size = PAGE_SIZE / sizeof(WCHAR) * 4; inPlaceUtf8 = PhAllocatePage(inPlaceUtf8Size, NULL); } while (Length != 0) { block.Buffer = Buffer; block.Length = PAGE_SIZE; if (block.Length > Length) block.Length = Length; if (inPlaceUtf8) { SIZE_T bytesInUtf8String; if (!PhConvertUtf16ToUtf8Buffer( inPlaceUtf8, inPlaceUtf8Size, &bytesInUtf8String, block.Buffer, block.Length )) { status = STATUS_INVALID_PARAMETER; goto CleanupExit; } status = PhWriteFileStream(FileStream, inPlaceUtf8, (ULONG)bytesInUtf8String); } else { utf8 = PhConvertUtf16ToUtf8Ex(block.Buffer, block.Length); if (!utf8) { status = STATUS_INVALID_PARAMETER; goto CleanupExit; } status = PhWriteFileStream(FileStream, utf8->Buffer, (ULONG)utf8->Length); PhDereferenceObject(utf8); } if (!NT_SUCCESS(status)) goto CleanupExit; Buffer += block.Length / sizeof(WCHAR); Length -= block.Length; } CleanupExit: if (inPlaceUtf8) PhFreePage(inPlaceUtf8); return status; } NTSTATUS PhWriteStringFormatAsUtf8FileStream_V( _Inout_ PPH_FILE_STREAM FileStream, _In_ _Printf_format_string_ PWSTR Format, _In_ va_list ArgPtr ) { NTSTATUS status; PPH_STRING string; string = PhFormatString_V(Format, ArgPtr); status = PhWriteStringAsUtf8FileStream(FileStream, &string->sr); PhDereferenceObject(string); return status; } NTSTATUS PhWriteStringFormatAsUtf8FileStream( _Inout_ PPH_FILE_STREAM FileStream, _In_ _Printf_format_string_ PWSTR Format, ... ) { va_list argptr; va_start(argptr, Format); return PhWriteStringFormatAsUtf8FileStream_V(FileStream, Format, argptr); }