[rp-download] SetFileOriginInfo_win32.cpp: Use NTDLL to write the Alternate Data Stream with just the file handle.
Some checks are pending
Codecov / run (push) Waiting to run
CodeQL / Analyze (cpp) (push) Waiting to run

Note that we have to use NtWriteFile(). Using WriteFile() results in
ERROR_BAD_COMMAND.

If the ADS write fails, it will be deleted with NtDeleteFile().

NOTE: Unicode only! ANSI builds won't write the ADS anymore.
ANSI builds (if they even work at all) should only be used on
Win9x, which doesn't support ADS anyway.

TODO: Convert NTSTATUS to POSIX error codes?
This commit is contained in:
David Korth 2025-06-12 23:33:56 -04:00
parent a19815f744
commit f157be4a97
4 changed files with 91 additions and 55 deletions

View File

@ -110,6 +110,11 @@ TARGET_INCLUDE_DIRECTORIES(${PROJECT_NAME}
IF(WIN32)
TARGET_LINK_LIBRARIES(${PROJECT_NAME} PRIVATE win32common rptext-noi18n)
TARGET_LINK_LIBRARIES(${PROJECT_NAME} PRIVATE wininet advapi32)
# NTDLL is used to open Alternate Data Streams without requiring the original filename.
# NOTE: Only works in Unicode builds. Non-Unicode builds would be for Win9x, which
# doesn't support Alternate Data Streams, anyway.
TARGET_LINK_LIBRARIES(${PROJECT_NAME} PRIVATE ntdll)
# Delay-load shell32.dll and ole32.dll to prevent a performance penalty due to gdi32.dll.
# Reference: https://randomascii.wordpress.com/2018/12/03/a-not-called-function-can-cause-a-5x-slowdown/
# This is also needed when disabling direct Win32k syscalls,

View File

@ -2,7 +2,7 @@
* ROM Properties Page shell extension. (rp-download) *
* SetFileOriginInfo.hpp: setFileOriginInfo() function. *
* *
* Copyright (c) 2016-2023 by David Korth. *
* Copyright (c) 2016-2025 by David Korth. *
* SPDX-License-Identifier: GPL-2.0-or-later *
***************************************************************************/
@ -19,29 +19,14 @@
namespace RpDownload {
#ifndef _WIN32
/**
* Set the file origin info.
* This uses xattrs on Linux and ADS on Windows.
* @param file Open file. (Must be writable.)
* @param url Origin URL.
* @param file Open file (Must be writable)
* @param url Origin URL
* @param mtime If >= 0, this value is set as the mtime.
* @return 0 on success; negative POSIX error code on error.
*/
int setFileOriginInfo(FILE *file, const TCHAR *url, time_t mtime);
#endif /* !_WIN32 */
#ifdef _WIN32
/**
* Set the file origin info.
* This uses xattrs on Linux and ADS on Windows.
* @param file Open file. (Must be writable.)
* @param filename Filename. [FIXME: Make it so we don't need this on Windows.]
* @param url Origin URL.
* @param mtime If >= 0, this value is set as the mtime.
* @return 0 on success; negative POSIX error code on error.
*/
int setFileOriginInfo(FILE *file, const TCHAR *filename, const TCHAR *url, time_t mtime);
#endif /* _WIN32 */
} //namespace RpDownload

View File

@ -2,7 +2,7 @@
* ROM Properties Page shell extension. (rp-download) *
* SetFileOriginInfo_win32.cpp: setFileOriginInfo() function. (Win32 version) *
* *
* Copyright (c) 2016-2024 by David Korth. *
* Copyright (c) 2016-2025 by David Korth. *
* SPDX-License-Identifier: GPL-2.0-or-later *
******************************************************************************/
@ -29,6 +29,39 @@
using std::string;
using std::tstring;
#ifdef UNICODE
// NTDLL function calls
#include <io.h>
#include <winternl.h>
// FIXME: RTL_CONSTANT_STRING macro is in ntdef.h,
// but #include'ing it breaks things.
// This version is from MinGW-w64 12.0.0.
#define RTL_CONSTANT_STRING(s) { sizeof(s)-sizeof((s)[0]), sizeof(s), const_cast<PWSTR>(s) }
// NTDLL functions not declared in winternl.h.
extern "C" {
__kernel_entry NTSYSCALLAPI NTSTATUS NtWriteFile(
HANDLE FileHandle,
HANDLE Event,
PIO_APC_ROUTINE ApcRoutine,
PVOID ApcContext,
PIO_STATUS_BLOCK IoStatusBlock,
PVOID Buffer,
ULONG Length,
PLARGE_INTEGER ByteOffset,
PULONG Key
);
typedef const OBJECT_ATTRIBUTES *PCOBJECT_ATTRIBUTES;
__kernel_entry NTSYSCALLAPI NTSTATUS NtDeleteFile(
PCOBJECT_ATTRIBUTES ObjectAttributes
);
}
#endif /* UNICODE */
namespace RpDownload {
/**
@ -75,13 +108,12 @@ static bool getStoreFileOriginInfo(void)
/**
* Set the file origin info.
* This uses xattrs on Linux and ADS on Windows.
* @param file Open file. (Must be writable.)
* @param filename Filename. [FIXME: Make it so we don't need this on Windows.]
* @param url Origin URL.
* @param file Open file (Must be writable)
* @param url Origin URL
* @param mtime If >= 0, this value is set as the mtime.
* @return 0 on success; negative POSIX error code on error.
*/
int setFileOriginInfo(FILE *file, const TCHAR *filename, const TCHAR *url, time_t mtime)
int setFileOriginInfo(FILE *file, const TCHAR *url, time_t mtime)
{
// NOTE: Even if one of the xattr functions fails, we'll
// continue with others and setting mtime. The first error
@ -94,6 +126,7 @@ int setFileOriginInfo(FILE *file, const TCHAR *filename, const TCHAR *url, time_
# error 32-bit time_t is not supported. Get a newer compiler.
#endif
#ifdef UNICODE
// Check if storeFileOriginInfo is enabled.
const bool storeFileOriginInfo = getStoreFileOriginInfo();
if (storeFileOriginInfo) do {
@ -102,25 +135,34 @@ int setFileOriginInfo(FILE *file, const TCHAR *filename, const TCHAR *url, time_
// - https://cqureacademy.com/blog/alternate-data-streams
// - https://stackoverflow.com/questions/46141321/open-alternate-data-stream-ads-from-file-handle-or-file-id
// - https://stackoverflow.com/a/46141949
// FIXME: NtCreateFile() seems to have issues, and we end up
// getting STATUS_INVALID_PARAMETER (0xC000000D).
// We'll use a regular CreateFile() call for now.
tstring tfilename = filename;
tfilename += _T(":Zone.Identifier");
HANDLE hAds = CreateFile(
tfilename.c_str(), // lpFileName
GENERIC_WRITE, // dwDesiredAccess
FILE_SHARE_READ, // dwShareMode
nullptr, // lpSecurityAttributes
CREATE_ALWAYS, // dwCreationDisposition
FILE_ATTRIBUTE_NORMAL, // dwFlagsAndAttributes
nullptr); // hTemplateFile
if (!hAds || hAds == INVALID_HANDLE_VALUE) {
HANDLE hFile = reinterpret_cast<HANDLE>(_get_osfhandle(fileno(file)));
IO_STATUS_BLOCK iosb;
UNICODE_STRING ObjectName = RTL_CONSTANT_STRING(L":Zone.Identifier");
OBJECT_ATTRIBUTES oa;
InitializeObjectAttributes(&oa, &ObjectName, 0, hFile, nullptr);
HANDLE hAds = nullptr;
NTSTATUS status = NtCreateFile(
&hAds, // FileHandle
FILE_GENERIC_WRITE, // DesiredAccess
&oa, // ObjectAttributes
&iosb, // IoStatusBlock
nullptr, // AllocationSize
FILE_ATTRIBUTE_NORMAL, // FileAttributes
FILE_SHARE_READ, // ShareAccess
FILE_OVERWRITE_IF, // CreateDisposition
FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT, // CreateOptions
nullptr, // EaBuffer
0); // EaLength
if (status < 0) {
// Error opening the ADS.
err = w32err_to_posix(GetLastError());
// TODO: Convert NTSTATUS to POSIX?
/*err = w32err_to_posix(GetLastError());
if (err == 0) {
err = EIO;
}
}*/
err = EIO;
break;
}
@ -135,19 +177,28 @@ int setFileOriginInfo(FILE *file, const TCHAR *filename, const TCHAR *url, time_
s_zoneID += T2U8(url);
s_zoneID += "\r\n";
DWORD dwBytesWritten = 0;
BOOL bRet = WriteFile(hAds, s_zoneID.data(), static_cast<DWORD>(s_zoneID.size()),
&dwBytesWritten, nullptr);
if ((!bRet || dwBytesWritten != static_cast<DWORD>(s_zoneID.size())) && err == 0) {
// Some error occurred...
err = w32err_to_posix(GetLastError());
if (err == 0) {
err = EIO;
}
}
status = NtWriteFile(
hAds, // FileHandle
nullptr, // Event
nullptr, // ApcRoutine
nullptr, // ApcContext
&iosb, // IoStatusBlock
s_zoneID.data(), // Buffer
static_cast<DWORD>(s_zoneID.size()), // Length
nullptr, // ByteOffset
nullptr); // Key
NtClose(hAds);
CloseHandle(hAds);
if (status < 0) {
// Error writing the stream data.
// Delete the stream.
NtDeleteFile(&oa);
// TODO: Convert NTSTATUS to Win32?
err = -EIO;
}
} while (0);
#endif /* UNICODE */
if (mtime >= 0) {
// TODO: 100ns timestamp precision for access time?

View File

@ -610,12 +610,7 @@ int RP_C_API _tmain(int argc, TCHAR *argv[])
fflush(f_out);
// Save the file origin information.
#ifdef _WIN32
// TODO: Figure out how to setFileOriginInfo() on Windows using an open file handle.
setFileOriginInfo(f_out, cache_filename.c_str(), full_url.c_str(), downloader.mtime());
#else /* !_WIN32 */
setFileOriginInfo(f_out, full_url.c_str(), downloader.mtime());
#endif /* _WIN32 */
fclose(f_out);
// Success.