📜 ⬆️ ⬇️

Work with alternative data streams through WinAPI

In the last article I described what alternative threads are and how they can be used. There were examples of working with them via the command line, but you can do the same thing using software standard WinAPI tools.
Below is a free translation of the part of the article relating to this issue.

FS type check


First of all, you need to check if the file system supports alternate streams, otherwise there is no point in trying to work with them.
char szVolName[MAX_PATH], szFSName[MAX_PATH];
DWORD dwSN, dwMaxLen, dwVolFlags;
::GetVolumeInformation("C:\\", szVolName, MAX_PATH, &dwSN,
&dwMaxLen, &dwVolFlags, szFSName, MAX_PATH);

if (dwVolFlags & FILE_NAMED_STREAMS)
{
// File system supports named streams
}
else
{
// Named streams are not supported
}


* This source code was highlighted with Source Code Highlighter .

Creating an alternate stream


Like a regular file, an alternate stream can be created using the CreateFile function:
HANDLE hFile = ::CreateFile( "file.dat:alt" , ...

If the file did not exist, a zero-length file will be created with the specified name of the main stream.

Deleting an alternate stream


Deleting an alternate stream is as simple as deleting a regular file. The DeleteFile function, which fully supports streams, is suitable for this:
::DeleteFile( "file.dat:alt" );

When a standard stream is deleted, all its alternatives will also be deleted.

Copy alternate stream


You can use the CopyFile / CopyFileEx Win32 API functions to copy alternate streams. However, these functions may give unexpected results. There may be two options for copying.
')
Standard stream to standard stream: a regular file operation, with all named streams being copied along with the file. If the file existed then it is replaced.

Named stream to standard stream: in this case only the selected stream is copied. If the destination file exists, it will be deleted with all streams (if any), and a new file will be created with only the standard stream, to which copying will take place.

Copying in a simple read / write cycle gives the expected result:
HANDLE hInFile = ::CreateFile(szFromStream, GENERIC_READ, FILE_SHARE_READ, NULL,
OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, NULL);
HANDLE hOutFile = ::CreateFile(szToStream, GENERIC_WRITE, FILE_SHARE_READ, NULL,
CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, NULL);

BYTE buf[64*1024];
DWORD dwBytesRead, dwBytesWritten;

do
{
::ReadFile(hInFile, buf, sizeof (buf), &dwBytesRead, NULL);
if (dwBytesRead) ::WriteFile(hOutFile, buf, dwBytesRead, &dwBytesWritten, NULL);
}
while (dwBytesRead == sizeof (buf));

::CloseHandle(hInFile);
::CloseHandle(hOutFile);


* This source code was highlighted with Source Code Highlighter .

Thread list


You can get a list of threads using some of the Native API functions. Below is the code for getting a list of streams from a file:
// Open a file and obtain stream information

BYTE InfoBlock[64 * 1024]; // Buffer must be large enough
PFILE_STREAM_INFORMATION pStreamInfo = (PFILE_STREAM_INFORMATION)InfoBlock;
IO_STATUS_BLOCK ioStatus;

HANDLE hFile = ::CreateFile(szPath, 0, FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL, OPEN_EXISTING, 0, NULL);
NtQueryInformationFile(hFile, &ioStatus, InfoBlock,
sizeof (InfoBlock), FileStreamInformation);
::CloseHandle(hFile);


* This source code was highlighted with Source Code Highlighter .

The situation is more complicated with getting the list of directory streams:
// Open a directory and obtain stream information

// Obtain backup privilege in case we don't have it
HANDLE hToken;
TOKEN_PRIVILEGES tp;
::OpenProcessToken(::GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &hToken);
::LookupPrivilegeValue(NULL, SE_BACKUP_NAME, &tp.Privileges[0].Luid);
tp.PrivilegeCount = 1;
tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
::AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof (TOKEN_PRIVILEGES), NULL, NULL);
::CloseHandle(hToken);

HANDLE hFile = ::CreateFile(szPath, 0, FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);

BYTE InfoBlock[64 * 1024]; // Buffer must be large enough
PFILE_STREAM_INFORMATION pStreamInfo = (PFILE_STREAM_INFORMATION)InfoBlock;
IO_STATUS_BLOCK ioStatus;

pStreamInfo->StreamNameLength = 0; // Zero in this field means empty info block
NtQueryInformationFile(hFile, &ioStatus, InfoBlock,
sizeof (InfoBlock), FileStreamInformation);
::CloseHandle(hFile);


* This source code was highlighted with Source Code Highlighter .

Now, after receiving information about the file / directory, we can display the actual list itself:
WCHAR wszStreamName[MAX_PATH];

for (;;)
{
// Check if stream info block is empty (directory may have no stream)
if (pStreamInfo->StreamNameLength == 0) break ; // No stream found

// Get null-terminated stream name
memcpy(wszStreamName, pStreamInfo->StreamName, pStreamInfo->StreamNameLength);
wszStreamName[pStreamInfo->StreamNameLength / sizeof (WCHAR)] = L '\0' ;

print( "%S" , wszStreamName);

if (pStreamInfo->NextEntryOffset == 0) break ; // No more stream records
pStreamInfo = (PFILE_STREAM_INFORMATION)
((LPBYTE)pStreamInfo + pStreamInfo->NextEntryOffset); // Next stream record
}


* This source code was highlighted with Source Code Highlighter .


Examples on VC ++ console programs

Source: https://habr.com/ru/post/46990/


All Articles