1

I am coding a simple replacement for std::filesystem::exists() function using Windows API. Surprisingly, it turned out to be pretty hard. I want to keep my code simple, so I am using minimum functions. My function of choice is GetFileAttributesW(). Code is tested with fs::recursive_directory_iterator() function. My function thinks that all files in “C:\Windows\servicing\LCU*” don’t exist (ERROR_PATH_NOT_FOUND). This directory is responsible for storing Windows Update Caches and is famous for having extremely long file names. I couldn’t find anything else about this directory. Example of filenames and my code are included below. Hope this helps!

Edited: The solution to this problem is to prepend absolute file path with “\\?\” char sequence. It makes Windows handle short files correctly!

C:\Windows\servicing\LCU\Package_for_RollupFix~31bf3856ad364e35~amd64~~19041.2006.1.7\amd64_microsoft-windows-a..g-whatsnew.appxmain_31bf3856ad364e35_10.0.19041.1741_none_ee5d4a8d060d7653\f\new360videossquare44x44logo.targetsize-16_altform-unplated_contrast-black.png
C:\Windows\servicing\LCU\Package_for_RollupFix~31bf3856ad364e35~amd64~~19041.2006.1.7\amd64_microsoft-windows-a..g-whatsnew.appxmain_31bf3856ad364e35_10.0.19041.1741_none_ee5d4a8d060d7653\f\new360videossquare44x44logo.targetsize-16_altform-unplated_contrast-white.png
C:\Windows\servicing\LCU\Package_for_RollupFix~31bf3856ad364e35~amd64~~19041.2006.1.7\amd64_microsoft-windows-a..g-whatsnew.appxmain_31bf3856ad364e35_10.0.19041.1741_none_ee5d4a8d060d7653\f\new360videossquare44x44logo.targetsize-20_altform-unplated_contrast-black.png
C:\Windows\servicing\LCU\Package_for_RollupFix~31bf3856ad364e35~amd64~~19041.2006.1.7\amd64_microsoft-windows-a..g-whatsnew.appxmain_31bf3856ad364e35_10.0.19041.1741_none_ee5d4a8d060d7653\f\new360videossquare44x44logo.targetsize-20_altform-unplated_contrast-white.png
#include <windows.h>

#include <filesystem>
#include <iostream>
#include <string>

using namespace std;
namespace fs = std::filesystem;

int FileExists(wstring file_path) {
    /* TODO:
         1. Doesn't work with "C:\\Windows\\servicing\\LCU\\*".
         2. Improve error system.
    */

    DWORD attributes = GetFileAttributesW(file_path.c_str());
        
    // Valid attributes => File exists
    if (attributes != INVALID_FILE_ATTRIBUTES) {
        return true;
    }

    DWORD error_code = GetLastError();
    wcout << error_code << ' ' << file_path << '\n';

    // Path related error => File doesn't exist
    if (error_code == ERROR_PATH_NOT_FOUND || error_code == ERROR_INVALID_NAME ||
        error_code == ERROR_FILE_NOT_FOUND || error_code == ERROR_BAD_NETPATH)
    {
        return false;
    }

    // Other errors are logged before if statement
    // File is busy with IO operations, etc.
    return error_code;
}

int main() {

    for (fs::path path : fs::recursive_directory_iterator("C:\\", fs::directory_options::skip_permission_denied)) {
        FileExists(path);
    }

    return 0;
}
Julius
  • 31
  • 6
  • Welcome to Stack Overflow. Please read [the help pages](http://stackoverflow.com/help), take the SO [tour], read [ask], as well as [this question checklist](https://codeblog.jonskeet.uk/2012/11/24/stack-overflow-question-checklist/). And please don't tag multiple languages, only the language you're actually using. – Some programmer dude Sep 25 '22 at 15:48
  • The function may return 32 error code, which is responsible for “File is busy with IO operations”. It was not mentioned because I consider it an unhandled case for now. – Julius Sep 25 '22 at 15:50
  • What is the question? – Evg Sep 25 '22 at 15:51
  • @Evg how to solve this problem? – Julius Sep 25 '22 at 15:52
  • 3
    For file names longer than `MAX_PATH` (260 if I recall correctly), prepend the path with `"\\?\"`. For more details, see [Maximum Path Length Limitation](https://learn.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-limitation) – Igor Tandetnik Sep 25 '22 at 15:52
  • What does `std::filesystem::exists()` say about those files? – Evg Sep 25 '22 at 15:53
  • @IgorTandetnik will this work in Windows XP and Windows 7 also? – Julius Sep 25 '22 at 15:55
  • @Evg that they exist, Windows explorer shows me these file correctly. – Julius Sep 25 '22 at 15:56
  • I believe this convention existed since WinNT3 times (circa 1994), but my memory is foggy going that far back. Should definitely work on Win 7, and very likely on WinXP – Igor Tandetnik Sep 25 '22 at 15:56
  • Related [How can we check if a file Exists or not using Win32 program?](https://stackoverflow.com/questions/3828835/how-can-we-check-if-a-file-exists-or-not-using-win32-program) – Weather Vane Sep 25 '22 at 16:01
  • @IgorTandetnik oki, I will check if it just works. Hope it will be compatible with older versions of Windows. – Julius Sep 25 '22 at 16:02
  • @WeatherVane It checks for files, excluding directories. – Julius Sep 25 '22 at 16:04
  • @WeatherVane And there is zero error handling done! – Julius Sep 25 '22 at 16:04
  • @IgorTandetnik Yes, this method works on Windows 7. Thank you for your time and help, it helped a lot!!! – Julius Sep 25 '22 at 16:26
  • 2
    `FileExists` is just another instance of a [TOCTOU](https://en.wikipedia.org/wiki/Time-of-check_to_time-of-use) race. It's best not to introduce TOCTOU races. – IInspectable Sep 25 '22 at 17:39
  • @IInspectable: That's a rather pointless comment when re-implementing `std::filesystem::exists()`. It the original code was TOCTOU safe, the replacement is probably also safe, _unless_ it introduces a specific new TOCTOU vulnerability in its internal implementation. But I don't see that here. – MSalters Sep 26 '22 at 11:30
  • @IgorTandetnik: I think you mix up file names and paths in "file names longer than MAX_PATH". Windows (NTFS) has a file and directory name length limit of 256, which is a hard limit. `MAX_PATH` is a soft limit for the whole path length, and can be bypassed using the `\\?\ ` prefix as noted. Obviously, 255 character filenames in `c:\Windows\servicing\LCU` will cause paths in excess of 260 chars. – MSalters Sep 26 '22 at 11:38
  • @MSalters The initial code **wasn't** TOCTOU-safe. Like anything `std::filesystem`, there's no lifetime guarantees, anywhere. It's straight injection of POSIX-copium, keeping fingers crossed that your `fd` is still valid *and means the same thing* by the time you use it. Good luck to you and your lawyers if that went by you. Now, if ISO C++ matters to you, name a **single** instance where `std::filesystem::exists()` makes sense. Like, literally, just a single instance where this isn't the precursor to a bug. – IInspectable Sep 26 '22 at 11:49
  • @IInspectable: Well, the `fd` part is easy in this case: it's the native Windows API here, which doesn't use file descriptors. You still have the fundamental issue: the file _name_ has similar TOCTOU concerns. In defense of the function, it is an essential function from UI perspective. UI is often forgotten in security analysis, often to the detriment of security. E.g. plenty of VPN software can't tell the difference between "missing certificate" and "expired certificate", which would be trivial with `std::filesystem::exists` – MSalters Sep 26 '22 at 13:29
  • @MSalters A file descriptor has the same guarantees as `std::filesystem::exists()`: None. It's pure POSIX nonsense. If you want to get a tractable handle onto a system resource, request a `HANDLE`. The API call to get one is [`CreateFileW`](https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilew). – IInspectable Sep 26 '22 at 14:34
  • @IInspectable: Valid point, and then you'd use `GetFileInformationByHandle` instead of `GetFileAttributesW` from the question. Or use the nuclear option and use Transactional NTFS. But Microsoft seems to agree with POSIX, and considers dropping that API – MSalters Sep 26 '22 at 15:08

1 Answers1

1

The solution that worked for me is to prepend absolute file path with “\\?\” char sequence. Somehow, it makes Windows handle shortened file paths correctly!

Check out MSDN Article "Maximum File Path Limitation" for more info.

Julius
  • 31
  • 6
  • It has nothing to do with the _shortened_ path name. It simply signals the Windows file handling functions that the true file path is extremely long and/or has special character values in it. – Dúthomhas Sep 25 '22 at 16:47
  • This is a pretty weak answer. As well as the explanation being incorrect, there is no referenced source to the information. If you want to answer the question properly you should include a reference to the documentation. Although personally I'm not sure the topic can be salvaged into something useful for future visitors. – David Heffernan Sep 26 '22 at 07:42