Best way to get volume name of symbolic link target [NTFS] - c++

I want a reliable way to get the volume name of a symbolic link's target that isn't super complicated.
So it looks like the FILE_NAME_INFO structure does not contain any info about the volume the file resides on. I am able to obtain the path of symlink targets from this structure, but for now I just assume the target resides on the same volume. However, I know symlinks permit targets on other volumes.
#include <windows.h>
#include <stdio.h>
#include <tchar.h>
#include <ole2.h>
struct FILE_NAME_INFO_AND_BUF {
FILE_NAME_INFO fni;
WCHAR buf[260];
};
WCHAR* getReparseTarget(WCHAR* linkFileName) {
HANDLE hFile;
WCHAR *szGuid = (WCHAR *)malloc(sizeof(WCHAR) * MAX_PATH);
BOOL result;
FILE_NAME_INFO_AND_BUF fnib = { 0 };
hFile = ::CreateFile(linkFileName, FILE_READ_ATTRIBUTES,
FILE_SHARE_READ |
FILE_SHARE_WRITE,
0,
OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS, 0);
if (hFile == INVALID_HANDLE_VALUE) {
::CloseHandle(hFile);
return NULL;
}
result = ::GetFileInformationByHandleEx(hFile, FileNameInfo, &fnib, sizeof(fnib));
if (!result) {
fprintf(stderr, "GetFileInformationByHandleEx Error %d\n", ::GetLastError());
::CloseHandle(hFile);
return NULL;
}
WCHAR *targetFileName = (WCHAR *)malloc(sizeof(WCHAR) * MAX_PATH);
wmemset(targetFileName, 0, MAX_PATH);
wcsncpy(targetFileName, linkFileName, 2);
wcscat(targetFileName, fnib.fni.FileName);
return targetFileName;
}
As you can see I'm cheating and getting the volume name, in this case the drive letter, from the input string, but this wouldn't work if the target was on another volume. Also I'd prefer obtaining the volume name with the GUID e.g. \\?\Volume{f993747a-5d7a-4de1-a97a-c20c1af1ba02}\ in it than the drive letter e.g. C:\

The absolute simplest way, as long as you can target Vista or later, is to use the GetFinalPathNameByHandle function.
If you need to target XP as well then you can find a symlink's target by opening the link itself (not the file it points to) using the FILE_FLAG_OPEN_REPARSE_POINT flag, and then use the FSCTL_GET_REPARSE_POINT IO control code to find the target of the link.
Because a link's target can potentially contain other links (up to a maximum of 31 I believe), you have to do this on every element of the path to be sure you've found the final target.

Related

How to get attribute of a directory like last access and last modify

I wanted to get timestamps of a directory and then show it to the user. I have written the following function, but it doesn't work when I give a full path of a directory to present its timestamp like access time. What should I do to fix this issue? I didn't know how should I open a directory and get information about its timestamps correctly for regular files my code works fine but when I wanted to extract information about directory it doesn't work.
#include <windows.h>
#include <iostream>
#include <stdio.h>
#include <strsafe.h>
BOOL GetLastWriteTimeDirectory(HANDLE arg_h_file, LPSTR arg_lpsz_string, DWORD arg_dw_size)
{
FILETIME ft_CreateTime, ft_AccessTime, ft_WriteTime;
SYSTEMTIME st_UTC, st_Local;
DWORD dw_Return;
// Retrieve the file times for the file.
if (!GetFileTime(arg_h_file, &ft_CreateTime, &ft_AccessTime, &ft_WriteTime))
{
return FALSE;
}
// Convert the last-write time to local time.
FileTimeToSystemTime(&ft_WriteTime, &st_UTC);
SystemTimeToTzSpecificLocalTime(NULL, &st_UTC, &st_Local);
// Build a string showing the date and time.
dw_Return = StringCchPrintfA(arg_lpsz_string, arg_dw_size, "%02d/%02d/%d %02d:%02d", st_Local.wMonth, st_Local.wDay, st_Local.wYear, st_Local.wHour, st_Local.wMinute);
if (S_OK == dw_Return)
{
return TRUE;
}
else
{
return FALSE;
}
}
bool AttributeLastAccessDirectory(const char* arg_path)
{
HANDLE handleFile;
char bufferLastAccessTime[MAX_PATH];
char pathDirectory[MAX_PATH];
strcpy(pathDirectory, arg_path);
handleFile = CreateFileA(pathDirectory, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
if (handleFile == INVALID_HANDLE_VALUE)
{
return false;
}
if (GetLastWriteTimeDirectory(handleFile, bufferLastAccessTime, MAX_PATH))
{
printf("\n\t\t");
printf("%s", "Last Accessed: \t");
printf("%s\n", bufferLastAccessTime);
CloseHandle(handleFile);
return true;
}
CloseHandle(handleFile);
return false;
}
int main(int argc, char* argv[])
{
AttributeLastAccessDirectory("C:\\Users\\mkahs\\Desktop\\Sample\\");
return 0;
}
According to the documentation for CreateFileA:
To open a directory using CreateFile, specify the FILE_FLAG_BACKUP_SEMANTICS flag as part of dwFlagsAndAttributes. Appropriate security checks still apply when this flag is used without SE_BACKUP_NAME and SE_RESTORE_NAME privileges.
Your CreateFileA function call currently sets the dwFlagsAndAttributes parameter (the sixth parameter) to 0. Setting it to FILE_FLAG_BACKUP_SEMANTICS should fix the problem.
Also, there is no need for the pathDirectory array and the call to strcpy that sets it. The arg_path parameter can be passed to CreateFileA directly.

Enumerate all partitions and test if they are NTFS

I'm using:
DWORD d = GetLogicalDrives();
for (int i = 0; i < 26; i++)
{
if ((1 << i) & d) // drive letter 'A' + i present on computer
{
wstring s = std::wstring(L"\\\\.\\") + wchar_t('A' + i) + L":";
PARTITION_INFORMATION diskInfo;
DWORD dwResult;
HANDLE dev = CreateFile(LPWSTR(s.c_str()), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_READONLY, NULL);
DeviceIoControl(dev, IOCTL_DISK_GET_PARTITION_INFO, NULL, 0, &diskInfo, sizeof(diskInfo), &dwResult, NULL);
CloseHandle(dev);
if (diskInfo.PartitionType == PARTITION_IFS)
{
...
}
}
}
to enumerate all NTFS partitions of a computer.
It works on my Windows 7, on a Windows 8.1 I tried it on, and on a Windows 10 computer.
But it fails on another Windows 10 computer: on this one, the volume C: has a diskInfo.PartitionType value equal to 0x00, instead of 0x07 (PARTITION_IFS).
This value is (see the doc here):
PARTITION_ENTRY_UNUSED : 0x00 : An unused entry partition.
This is strange, since, I can confirm, the partition is really NTFS.
Questions:
Is it well-known that IOCTL_DISK_GET_PARTITION_INFO is not 100% reliable to get the partition type?
What would be a more reliable way to enumerate all NTFS volumes?
Note: I also looked at using IOCTL_DISK_GET_PARTITION_INFO_EX instead of IOCTL_DISK_GET_PARTITION_INFO but then the structure PARTITION_INFORMATION_EX does not seem to give informations about PartitionType, whereas the structure PARTITION_INFORMATION does give access to PartitionType.
As #RemyLebeau says, you are not checking the return value for each call.
PARTITION_ENTRY_UNUSED often means the DeviceIoControl() call failed. It depends on the permissions of your user. You should check your user's access rights to see if it has the FILE_READ_DATA permission (included in GENERIC_READ) on volume C:. In my test environment, if you have no access to open volume C: with GENERIC_READ, CreateFile() returns INVALID_HANDLE_VALUE, and then DeviceIoControl() fails as well.
EDIT:
I suggest using GetVolumeInformation(), for example:
wchar_t fs[MAX_PATH + 1] = { 0 };
GetVolumeInformationW(L"C:\\", NULL, 0, NULL, NULL, NULL, fs, MAX_PATH + 1);
And you will see the Type info in the fs buffer.
I did further investigation thanks to #RemyLebeau's comments with:
HANDLE dev = CreateFile(..., GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_READONLY, NULL);
if (dev == INVALID_HANDLE_VALUE)
{
DWORD err = GetLastError(); // then MessageBox
}
else
{
BOOL ret = DeviceIoControl(dev, IOCTL_DISK_GET_PARTITION_INFO, NULL, 0, &diskInfo, sizeof(diskInfo), &dwResult, NULL);
if (ret == FALSE)
{
DWORD err = GetLastError(); // then MessageBox
}
CloseHandle(dev);
}
on the computer where it failed (computer with Windows 10). I found that CreateFile succeeded but then DeviceIoControl failed with GetLastError being 1 i.e. ERROR_INVALID_FUNCTION (see System Error Codes (0-499)).
Conclusion (I quote Remy's comment):
That means IOCTL_DISK_GET_PARTITION_INFO is not supported by the device you passed to DeviceIoControl().
I then tried with IOCTL_DISK_GET_PARTITION_INFO_EX:
PARTITION_INFORMATION_EX diskInfo;
BOOL ret = DeviceIoControl(dev, IOCTL_DISK_GET_PARTITION_INFO_EX, NULL, 0, &diskInfo, sizeof(diskInfo), &lpBytesReturned, NULL);
and then it worked. I could see that diskInfo.PartitionStyle was PARTITION_STYLE_GPT (=1), and this was the reason why IOCTL_DISK_GET_PARTITION_INFO failed. I quote Remy's comment again:
IOCTL_DISK_GET_PARTITION_INFO is not supported on GPT partitioned drives.
So here's the conclusion:
use IOCTL_DISK_GET_PARTITION_INFO_EX instead of IOCTL_DISK_GET_PARTITION_INFO
if diskInfo.PartitionStyle is 0 (PARTITION_STYLE_MBR) then diskInfo.Mbr.PartitionType can be tested. If it's 0x07, it's NTFS.
if diskInfo.PartitionStyle is 1 (PARTITION_STYLE_GPT) then diskInfo.Gpt.PartitionType can be tested, see here: https://learn.microsoft.com/en-us/windows/desktop/api/winioctl/ns-winioctl-_partition_information_gpt. Even if the NTFS Wikipedia page mentions the GUID EBD0A0A2-B9E5-4433-87C0-68B6B72699C7 for NTFS in the GPT case, this GUID is in fact unrelated to file system (see comment below about this).
it's probably easier to use GetVolumeInformation() instead and just compare if the result is the "NTFS" string, as in the other answer
in my particular case, I initially wanted to test if a volume is NTFS or not before attempting an indexing with DeviceIoControl(hVol, FSCTL_ENUM_USN_DATA, ...) because I thought such MFT querying would be limited to NTFS volumes. In fact, an easier solution would be to NOT TEST if it's NTFS or not, and just do the FSCTL_ENUM_USN_DATA. The worst that can happen is that FSCTL_ENUM_USN_DATA fails with ERROR_INVALID_FUNCTION error, per the documentation:
"ERROR_INVALID_FUNCTION The file system on the specified volume does not support this control code."

Get Document Directory

I'm working on a little singleplayer cheat for a game and am planning on making it public, I have written the API and it all works well but I want to save the data that the user enters to a .ini file so that they will only have to log in one time unless their credentials change. I have done this and it works with a relative path C:\Users\Name\Documents\Cheat\Authorise.ini Although When check the result, it doesn't seem to save nor read the data in the file.
I'm wondering if there is a better way to get the Documents Directory.
Function:
std::string authFile = "C:\\Users\\%USERNAME%\\Documents\\Cheats\\Authorise.ini";
std::string username = GUI::Files::ReadStringFromIni(authFile, "Login", "Username");
std::string password = GUI::Files::ReadStringFromIni(authFile, "Login", "Password");`
Since you're on Windows, you should use the Windows API call available for this very purpose in <ShlObj.h> called SHGetKnownFolderPath. Note that you had best use a std::wstring instead for this purpose, since there is no variant of SHGetKnownFolderPath that accepts an MBCS or ANSI string. Also, this will get you the entire path to the user's profile directory, not just the username.
PWSTR path;
SHGetKnownFolderPath(FOLDERID_Documents, KF_FLAG_DEFAULT, NULL, &path);
std::wstring strpath(path);
CoTaskMemFree(path);
Also, once you're done with path, free it with CoTaskMemFree.
Different versions of Windows store user profiles in different locations, and even the default name of the Documents folder can differ. In fact, the name and location of the user's Documents folder is fully customizable by the user, and may not even be located under the user's profile at all. So, you should not assume the Documents folder is always located at C:\\Users\\%USERNAME%\\Documents.
The best way to get the correct path to a user's Documents folder on all versions of Windows is to simply ask Windows itself. Use SHGetFolderPath(CSIDL_MYDOCUMENTS) (pre-Vista) or SHGetKnownFolderPath(FOLDERID_Documents) (Vista+) for that, eg:
#include <shlobj.h>
#include <shlwapi.h>
std::string GetDocumentsFolder()
{
std::string path;
char szPath[MAX_PATH+1] = {};
if (SHGetFolderPathA(NULL, CSIDL_MYDOCUMENTS, NULL, SHGFP_TYPE_CURRENT, szPath) == S_OK)
path = PathAddBackslashA(szPath);
/*
PWSTR pPath = NULL;
if (SHGetKnownFolderPath(FOLDERID_Documents, KF_FLAG_DEFAULT, NULL, &pPath) == S_OK)
{
int wlen = lstrlenW(pPath);
int len = WideCharToMultiByte(CP_ACP, 0, pPath, wlen, NULL, 0, NULL, NULL);
if (len > 0)
{
path.resize(len+1);
WideCharToMultiByte(CP_ACP, 0, pPath, wlen, &path[0], len, NULL, NULL);
path[len] = '\\';
}
CoTaskMemFree(pPath);
}
*/
return path;
}
std::string GetAuthFilePath()
{
std::string path = GetDocumentsFolder();
if (!path.empty())
path += "Cheats\\Authorise.ini";
return path;
}
std::string authFile = GetAuthFilePath();
...

FindFirstFile returns INVALID_HANDLE_VALUE when lpFileName is volume

There is a short example:
WIN32_FIND_DATA fd;
HANDLE h = FindFirstFile(L"C:", &fd);
if (h == INVALID_HANDLE_VALUE)
{
wprintf(L"Err = %d\n", GetLastError());
return 1;
}
do {
std::wstring fullPath(fd.cFileName);
wprintf(L"FullPath = %s\n", fullPath.c_str());
} while (FindNextFile(h, &fd));
FindClose(h);
I'm confused since
HANDLE h = FindFirstFile(L"C:", &fd); // OK
HANDLE h = FindFirstFile(L"E:", &fd); // INVALID_HANDLE_VALUE
HANDLE h = FindFirstFile(L"F:", &fd); // INVALID_HANDLE_VALUE
But E and F are real, existing volumes.
I do so because I need all information from the WIN32_FIND_DATA structure which will be passed to the kernel mode.
This question seems to be based on a misunderstanding. You say:
I do not want to examine the files and directories in "E:*", I want to get information about this volume.
and
I need all information from the WIN32_FIND_DATA structure.
Well, the find data information is meaningful for file and directory objects, but not for volume objects.
You'll need to use some other means to obtain volume information. Perhaps GetVolumeInformation, WMI, or maybe something else depending on your needs.
I think you have to put "E:\\*" not only the volume. Or you want to find the volume entry? Also, it is good to clean fd structure memset( &fd, 0, sizeof fd ).
On my machine (Win7 x64) FindFirstFile(L"C:", &fd) returns -1 (C: is system disc), GetLastError() is 2 (i.e. "file not found").
I've checked how FindFirstFile is working. It converts winapi like path to nt path at some stage using RtlDosPathNameToRelativeNtPathName_U and then checks whether PartName is empty or not.
If it's empty it returns mentioned error code.
if it's a mask, it continues and enumerates entries that match: that's why FindFirstFile(L"C:\\*.*", &fd) returns valid handle. Thus if you need to enumerate all files in C: using form C:\\*.*.
In case if you need a volume information use special function GetVolumeInformation.

How Do you set MOTW on an Executable

How do you set MOTW (Mark of the Web) on an executable that is downloaded from the internet?
This data is stored in an NTFS alternative file stream alongside an executable. The stream is called Zone.Identifier:
Windows® Internet Explorer® uses the stream name Zone.Identifier for storage of URL security zones.
The fully qualified form is sample.txt:Zone.Identifier:$DATA
The stream is a simple text stream of the form:
[ZoneTransfer]
ZoneId=3
MSDN-SECZONES gives an explanation of security zones.
(N.B. The original has a space between the colon and "Zone" but I think this is erroneous.)
You can find the ZoneIds in UrlMon.h in the SDK; there's an enum which equates to
enum URLZONE {
URLZONE_LOCAL_MACHINE = 0,
URLZONE_INTRANET = 1,
URLZONE_TRUSTED = 2,
URLZONE_INTERNET = 3,
URLZONE_RESTRICTED = 4
};
(The original uses previous value + 1 rather than absolute values.)
As Hans says in the comments, these can be written with the standard Win32 file APIs CreateFile and WriteFile.
Firefox always writes Internet Zone, zone 3 - Firefox code here (MPL/LGPL/GPL tri-license):
bool SetInternetZoneIdentifier(const FilePath& full_path) {
const DWORD kShare = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE;
std::wstring path = full_path.value() + L":Zone.Identifier";
HANDLE file = CreateFile(path.c_str(), GENERIC_WRITE, kShare, NULL,
OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
if (INVALID_HANDLE_VALUE == file)
return false;
const char kIdentifier[] = "[ZoneTransfer]\nZoneId=3";
DWORD written = 0;
BOOL result = WriteFile(file, kIdentifier, arraysize(kIdentifier), &written,
NULL);
CloseHandle(file);
if (!result || written != arraysize(kIdentifier)) {
DCHECK(FALSE);
return false;
}
return true;
}
Alternatively there's an IE COM API CLSID_PersistentZoneIdentifier you can use to abstract this all for you.
It is not explicitly stated in RFC 3514, but today, due to increased security requirements, implementations should really retain the information of the presence or absence of the RFC3514 bit in a network transmission, when they write files out to disk, and vice-versa for reading from disk.