@photostructure/fs-metadata 0.0.1 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/README.md +68 -200
  2. package/binding.gyp +6 -3
  3. package/package.json +27 -24
  4. package/prebuilds/darwin-arm64/@photostructure+fs-metadata.glibc.node +0 -0
  5. package/prebuilds/linux-arm64/@photostructure+fs-metadata.musl.node +0 -0
  6. package/prebuilds/linux-x64/@photostructure+fs-metadata.glibc.node +0 -0
  7. package/prebuilds/linux-x64/@photostructure+fs-metadata.musl.node +0 -0
  8. package/prebuilds/win32-x64/@photostructure+fs-metadata.glibc.node +0 -0
  9. package/src/binding.cpp +59 -29
  10. package/src/common/debug_log.h +64 -0
  11. package/src/common/hidden.h +12 -0
  12. package/src/common/metadata_worker.h +1 -44
  13. package/src/common/volume_metadata.h +98 -8
  14. package/src/common/volume_mount_points.h +42 -2
  15. package/src/darwin/fs_meta.h +0 -11
  16. package/src/darwin/hidden.cpp +139 -0
  17. package/src/darwin/hidden.h +35 -0
  18. package/src/darwin/volume_metadata.cpp +105 -40
  19. package/src/darwin/volume_mount_points.cpp +88 -29
  20. package/src/linux/blkid_cache.cpp +9 -0
  21. package/src/linux/blkid_cache.h +1 -0
  22. package/src/linux/gio_mount_points.cpp +81 -0
  23. package/src/linux/{gio_worker.h → gio_mount_points.h} +7 -2
  24. package/src/linux/gio_utils.cpp +29 -115
  25. package/src/linux/gio_utils.h +69 -9
  26. package/src/linux/gio_volume_metadata.cpp +90 -0
  27. package/src/linux/gio_volume_metadata.h +20 -0
  28. package/src/linux/volume_metadata.cpp +31 -16
  29. package/src/windows/drive_status.h +227 -0
  30. package/src/windows/error_utils.h +27 -6
  31. package/src/windows/fs_meta.h +3 -33
  32. package/src/windows/hidden.cpp +160 -0
  33. package/src/windows/hidden.h +3 -0
  34. package/src/windows/string.h +48 -0
  35. package/src/windows/system_volume.h +63 -0
  36. package/src/windows/volume_metadata.cpp +171 -138
  37. package/src/windows/volume_mount_points.cpp +102 -26
  38. package/src/common/mount_point.h +0 -13
  39. package/src/linux/gio_worker.cpp +0 -87
@@ -0,0 +1,227 @@
1
+ // src/windows/drive_status.h
2
+
3
+ #pragma once
4
+ #include "../common/debug_log.h"
5
+ #include <memory>
6
+ #include <string>
7
+ #include <vector>
8
+ #include <windows.h>
9
+
10
+ namespace FSMeta {
11
+
12
+ enum class DriveStatus {
13
+ Healthy,
14
+ Timeout,
15
+ Inaccessible,
16
+ Disconnected,
17
+ Unknown
18
+ };
19
+
20
+ inline std::string DriveStatusToString(DriveStatus status) {
21
+ switch (status) {
22
+ case DriveStatus::Healthy:
23
+ return "healthy";
24
+ case DriveStatus::Timeout:
25
+ return "timeout";
26
+ case DriveStatus::Inaccessible:
27
+ return "inaccessible";
28
+ case DriveStatus::Disconnected:
29
+ return "disconnected";
30
+ default:
31
+ return "unknown";
32
+ }
33
+ }
34
+
35
+ class IOOperation {
36
+ private:
37
+ HANDLE completionEvent;
38
+ DriveStatus result;
39
+ std::string path;
40
+ DWORD threadId;
41
+ volatile bool shouldTerminate;
42
+ HANDLE threadHandle; // Store thread handle as member
43
+
44
+ public:
45
+ IOOperation()
46
+ : result(DriveStatus::Unknown), threadId(0), shouldTerminate(false),
47
+ threadHandle(NULL) {
48
+ completionEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
49
+ }
50
+
51
+ ~IOOperation() {
52
+ if (completionEvent) {
53
+ CloseHandle(completionEvent);
54
+ }
55
+ CleanupThread();
56
+ }
57
+
58
+ void CleanupThread() {
59
+ if (threadHandle) {
60
+ DEBUG_LOG("[IOOperation] Cleaning up thread %lu", threadId);
61
+ // Signal termination
62
+ shouldTerminate = true;
63
+
64
+ // Give thread chance to exit gracefully
65
+ if (WaitForSingleObject(threadHandle, 100) != WAIT_OBJECT_0) {
66
+ DEBUG_LOG("[IOOperation] Force terminating thread %lu", threadId);
67
+ TerminateThread(threadHandle, 1);
68
+ }
69
+
70
+ CloseHandle(threadHandle);
71
+ threadHandle = NULL;
72
+ DEBUG_LOG("[IOOperation] Thread cleanup complete");
73
+ }
74
+ }
75
+
76
+ static DWORD WINAPI WorkerThread(LPVOID param) {
77
+ IOOperation *self = static_cast<IOOperation *>(param);
78
+ std::string searchPath = self->path + "*";
79
+ HANDLE findHandle = INVALID_HANDLE_VALUE;
80
+ WIN32_FIND_DATAA findData;
81
+
82
+ DEBUG_LOG("[WorkerThread] Starting search on path: %s", searchPath.c_str());
83
+
84
+ findHandle = FindFirstFileExA(
85
+ searchPath.c_str(), FindExInfoBasic, &findData, FindExSearchNameMatch,
86
+ NULL, FIND_FIRST_EX_LARGE_FETCH | FIND_FIRST_EX_ON_DISK_ENTRIES_ONLY);
87
+
88
+ if (findHandle == INVALID_HANDLE_VALUE) {
89
+ self->result = self->MapErrorToDriveStatus(GetLastError());
90
+ DEBUG_LOG("[WorkerThread] Search failed with error: %lu", GetLastError());
91
+ } else {
92
+ self->result = DriveStatus::Healthy;
93
+ FindClose(findHandle);
94
+ DEBUG_LOG("[WorkerThread] Search completed successfully");
95
+ }
96
+
97
+ SetEvent(self->completionEvent);
98
+ DEBUG_LOG("[WorkerThread] Thread %lu exiting", self->threadId);
99
+ return 0;
100
+ }
101
+
102
+ DriveStatus CheckDriveWithTimeout(const std::string &checkPath,
103
+ DWORD timeoutMs) {
104
+ DEBUG_LOG("[CheckDriveStatus] Starting check for: %s timeout: %lu ms",
105
+ checkPath.c_str(), timeoutMs);
106
+
107
+ // Cleanup any previous thread
108
+ CleanupThread();
109
+
110
+ path = checkPath;
111
+ ResetEvent(completionEvent);
112
+ shouldTerminate = false;
113
+
114
+ threadHandle = CreateThread(NULL, 0, WorkerThread, this, 0, &threadId);
115
+ if (!threadHandle) {
116
+ DEBUG_LOG("[CheckDriveStatus] Failed to create thread");
117
+ return DriveStatus::Unknown;
118
+ }
119
+
120
+ DEBUG_LOG("[CheckDriveStatus] Created thread %lu", threadId);
121
+ DWORD waitResult = WaitForSingleObject(threadHandle, timeoutMs);
122
+
123
+ if (waitResult == WAIT_TIMEOUT) {
124
+ DEBUG_LOG("[CheckDriveStatus] Thread %lu timed out after %lu ms",
125
+ threadId, timeoutMs);
126
+ CleanupThread();
127
+ return DriveStatus::Timeout;
128
+ }
129
+
130
+ DEBUG_LOG("[CheckDriveStatus] Thread %lu completed normally", threadId);
131
+ CloseHandle(threadHandle);
132
+ DEBUG_LOG("[CheckDriveStatus] CloseHandle %lu completed", threadId);
133
+ threadHandle = NULL;
134
+ return result;
135
+ }
136
+
137
+ private:
138
+ DriveStatus MapErrorToDriveStatus(DWORD error) {
139
+ switch (error) {
140
+ case ERROR_FILE_NOT_FOUND:
141
+ case ERROR_PATH_NOT_FOUND:
142
+ case ERROR_ACCESS_DENIED:
143
+ case ERROR_LOGON_FAILURE:
144
+ return DriveStatus::Inaccessible;
145
+
146
+ case ERROR_BAD_NET_NAME:
147
+ case ERROR_NETWORK_UNREACHABLE:
148
+ case ERROR_NOT_CONNECTED:
149
+ case ERROR_NETWORK_ACCESS_DENIED:
150
+ case ERROR_BAD_NETPATH:
151
+ return DriveStatus::Disconnected;
152
+
153
+ default:
154
+ return DriveStatus::Unknown;
155
+ }
156
+ }
157
+ };
158
+
159
+ class ParallelDriveStatus {
160
+ private:
161
+ struct PendingCheck {
162
+ std::string path;
163
+ std::unique_ptr<IOOperation> op;
164
+ DWORD timeoutMs;
165
+ DriveStatus status;
166
+ };
167
+
168
+ std::vector<std::unique_ptr<PendingCheck>> pending_;
169
+
170
+ public:
171
+ void Submit(const std::string &path, DWORD timeoutMs = 1000) {
172
+ auto check = std::make_unique<PendingCheck>();
173
+ check->path = path;
174
+ check->op = std::make_unique<IOOperation>();
175
+ check->timeoutMs = timeoutMs;
176
+ check->status = DriveStatus::Unknown;
177
+
178
+ DEBUG_LOG("[ParallelDriveStatus] Submitting check for: %s", path.c_str());
179
+ pending_.push_back(std::move(check));
180
+ }
181
+
182
+ std::vector<DriveStatus> WaitForResults() {
183
+ std::vector<DriveStatus> results;
184
+ results.reserve(pending_.size());
185
+
186
+ // Start all operations
187
+ for (auto &check : pending_) {
188
+ check->status =
189
+ check->op->CheckDriveWithTimeout(check->path, check->timeoutMs);
190
+ }
191
+
192
+ // Collect results in original order
193
+ for (auto &check : pending_) {
194
+ DEBUG_LOG("[ParallelDriveStatus] Result for %s: %s", check->path.c_str(),
195
+ DriveStatusToString(check->status).c_str());
196
+ results.push_back(check->status);
197
+ }
198
+
199
+ pending_.clear();
200
+ return results;
201
+ }
202
+ };
203
+
204
+ // Update CheckDriveStatus to support checking multiple paths
205
+ inline std::vector<DriveStatus>
206
+ CheckDriveStatus(const std::vector<std::string> &paths,
207
+ DWORD timeoutMs = 1000) {
208
+ try {
209
+ ParallelDriveStatus checker;
210
+ for (const auto &path : paths) {
211
+ checker.Submit(path, timeoutMs);
212
+ }
213
+ return checker.WaitForResults();
214
+ } catch (...) {
215
+ DEBUG_LOG("[CheckDriveStatus] caught unexpected exception");
216
+ return std::vector<DriveStatus>(paths.size(), DriveStatus::Unknown);
217
+ }
218
+ }
219
+
220
+ // Keep single path version for backwards compatibility
221
+ inline DriveStatus CheckDriveStatus(const std::string &path,
222
+ DWORD timeoutMs = 1000) {
223
+ std::vector<std::string> paths{path};
224
+ return CheckDriveStatus(paths, timeoutMs)[0];
225
+ }
226
+
227
+ } // namespace FSMeta
@@ -11,12 +11,33 @@ class FSException : public std::runtime_error {
11
11
  public:
12
12
  explicit FSException(const std::string &message)
13
13
  : std::runtime_error(message) {}
14
- };
15
14
 
16
- inline std::string CreateErrorMessage(const char *operation, DWORD error) {
17
- std::ostringstream oss;
18
- oss << operation << " failed with error: " << error;
19
- return oss.str();
20
- }
15
+ FSException(const std::string &operation, DWORD errorCode)
16
+ : std::runtime_error(FormatWindowsError(operation, errorCode)) {}
17
+
18
+ private:
19
+ static std::string FormatWindowsError(const std::string &operation,
20
+ DWORD error) {
21
+ if (error == 0) {
22
+ return operation + " failed with an unknown error";
23
+ }
24
+
25
+ LPVOID messageBuffer;
26
+ size_t size = FormatMessageA(
27
+ FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |
28
+ FORMAT_MESSAGE_IGNORE_INSERTS,
29
+ NULL, error, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
30
+ (LPSTR)&messageBuffer, 0, NULL);
31
+
32
+ if (size == 0 || !messageBuffer) {
33
+ return operation + " failed with error code: " + std::to_string(error);
34
+ }
35
+
36
+ std::string errorMessage((LPSTR)messageBuffer, size);
37
+ LocalFree(messageBuffer);
38
+
39
+ return operation + " failed: " + errorMessage;
40
+ }
41
+ };
21
42
 
22
43
  } // namespace FSMeta
@@ -1,43 +1,13 @@
1
1
  // src/windows/fs_meta.h
2
+
2
3
  #pragma once
3
4
  #include "../common/volume_metadata.h"
4
5
  #include "../common/volume_mount_points.h"
5
- #include "./error_utils.h"
6
- #include <napi.h>
7
-
8
- #include <string>
6
+ #include <windows.h> // for MAX_PATH
9
7
 
10
8
  namespace FSMeta {
11
9
 
12
10
  constexpr size_t ERROR_BUFFER_SIZE = 256;
13
- constexpr DWORD BUFFER_SIZE = MAX_PATH + 1;
14
-
15
- enum DriveStatus {
16
- Unknown,
17
- Unavailable,
18
- Healthy,
19
- Disconnected,
20
- Error,
21
- NoMedia
22
- };
23
-
24
- inline const char *DriveStatusToString(DriveStatus status) {
25
- switch (status) {
26
- case Unknown:
27
- return "unknown";
28
- case Unavailable:
29
- return "unavailable";
30
- case Healthy:
31
- return "healthy";
32
- case Disconnected:
33
- return "disconnected";
34
- case Error:
35
- return "error";
36
- case NoMedia:
37
- return "no_media";
38
- default:
39
- return "unknown";
40
- }
41
- }
11
+ constexpr size_t BUFFER_SIZE = MAX_PATH + 1;
42
12
 
43
13
  } // namespace FSMeta
@@ -0,0 +1,160 @@
1
+ // src/windows/hidden.cpp
2
+ #include "hidden.h"
3
+ #include "error_utils.h"
4
+ #include <windows.h>
5
+
6
+ namespace FSMeta {
7
+
8
+ namespace {
9
+ // Utility class for path conversion
10
+ class PathConverter {
11
+ public:
12
+ static std::wstring ToWString(const std::string &path) {
13
+ if (path.empty()) {
14
+ return std::wstring();
15
+ }
16
+
17
+ // Pre-calculate required buffer size
18
+ int wlen = MultiByteToWideChar(CP_UTF8, 0, path.c_str(),
19
+ static_cast<int>(path.length()), nullptr, 0);
20
+ if (wlen == 0) {
21
+ throw FSException("Path conversion", GetLastError());
22
+ }
23
+
24
+ // Reserve exact size needed
25
+ std::wstring wpath(wlen, 0);
26
+ if (!MultiByteToWideChar(CP_UTF8, 0, path.c_str(),
27
+ static_cast<int>(path.length()), &wpath[0],
28
+ wlen)) {
29
+ throw FSException("Path conversion", GetLastError());
30
+ }
31
+ return wpath;
32
+ }
33
+ };
34
+
35
+ // RAII wrapper for file attributes
36
+ class FileAttributeHandler {
37
+ const std::wstring &path;
38
+ DWORD attributes;
39
+
40
+ public:
41
+ explicit FileAttributeHandler(const std::wstring &p)
42
+ : path(p), attributes(GetFileAttributesW(p.c_str())) {
43
+ if (attributes == INVALID_FILE_ATTRIBUTES) {
44
+ throw FSException("GetFileAttributes", GetLastError());
45
+ }
46
+ }
47
+
48
+ bool isHidden() const { return (attributes & FILE_ATTRIBUTE_HIDDEN) != 0; }
49
+
50
+ void setHidden(bool value) {
51
+ DWORD newAttrs = value ? (attributes | FILE_ATTRIBUTE_HIDDEN)
52
+ : (attributes & ~FILE_ATTRIBUTE_HIDDEN);
53
+
54
+ if (!SetFileAttributesW(path.c_str(), newAttrs)) {
55
+ throw FSException("SetFileAttributes", GetLastError());
56
+ }
57
+ attributes = newAttrs;
58
+ }
59
+ };
60
+ } // anonymous namespace
61
+
62
+ class GetHiddenWorker : public Napi::AsyncWorker {
63
+ const std::string path;
64
+ bool result;
65
+ Napi::Promise::Deferred deferred;
66
+
67
+ public:
68
+ GetHiddenWorker(Napi::Env env, std::string p, Napi::Promise::Deferred def)
69
+ : Napi::AsyncWorker(env), path(std::move(p)), deferred(std::move(def)) {}
70
+
71
+ void Execute() override {
72
+ try {
73
+ auto wpath = PathConverter::ToWString(path);
74
+ FileAttributeHandler handler(wpath);
75
+ result = handler.isHidden();
76
+ } catch (const FSException &e) {
77
+ SetError(e.what());
78
+ } catch (const std::exception &e) {
79
+ SetError(std::string("Unexpected error: ") + e.what());
80
+ }
81
+ }
82
+
83
+ void OnOK() override { deferred.Resolve(Napi::Boolean::New(Env(), result)); }
84
+
85
+ void OnError(const Napi::Error &e) override { deferred.Reject(e.Value()); }
86
+ };
87
+
88
+ class SetHiddenWorker : public Napi::AsyncWorker {
89
+ const std::string path;
90
+ const bool value;
91
+ Napi::Promise::Deferred deferred;
92
+
93
+ public:
94
+ SetHiddenWorker(Napi::Env env, std::string p, bool v,
95
+ Napi::Promise::Deferred def)
96
+ : Napi::AsyncWorker(env), path(std::move(p)), value(v),
97
+ deferred(std::move(def)) {}
98
+
99
+ void Execute() override {
100
+ try {
101
+ auto wpath = PathConverter::ToWString(path);
102
+ FileAttributeHandler handler(wpath);
103
+ handler.setHidden(value);
104
+ } catch (const FSException &e) {
105
+ SetError(e.what());
106
+ } catch (const std::exception &e) {
107
+ SetError(std::string("Unexpected error: ") + e.what());
108
+ }
109
+ }
110
+
111
+ void OnOK() override { deferred.Resolve(Napi::Boolean::New(Env(), true)); }
112
+
113
+ void OnError(const Napi::Error &e) override { deferred.Reject(e.Value()); }
114
+ };
115
+
116
+ Napi::Promise GetHiddenAttribute(const Napi::CallbackInfo &info) {
117
+ Napi::Env env = info.Env();
118
+ auto deferred = Napi::Promise::Deferred::New(env);
119
+
120
+ try {
121
+ if (info.Length() < 1 || !info[0].IsString()) {
122
+ throw Napi::TypeError::New(env, "String path expected");
123
+ }
124
+
125
+ std::string path = info[0].As<Napi::String>();
126
+ auto *worker =
127
+ new GetHiddenWorker(env, std::move(path), std::move(deferred));
128
+ worker->Queue();
129
+
130
+ return deferred.Promise();
131
+ } catch (const Napi::Error &e) {
132
+ deferred.Reject(e.Value());
133
+ return deferred.Promise();
134
+ }
135
+ }
136
+
137
+ Napi::Promise SetHiddenAttribute(const Napi::CallbackInfo &info) {
138
+ Napi::Env env = info.Env();
139
+ auto deferred = Napi::Promise::Deferred::New(env);
140
+
141
+ try {
142
+ if (info.Length() < 2 || !info[0].IsString() || !info[1].IsBoolean()) {
143
+ throw Napi::TypeError::New(env, "String path and boolean value expected");
144
+ }
145
+
146
+ std::string path = info[0].As<Napi::String>();
147
+ bool value = info[1].As<Napi::Boolean>();
148
+
149
+ auto *worker =
150
+ new SetHiddenWorker(env, std::move(path), value, std::move(deferred));
151
+ worker->Queue();
152
+
153
+ return deferred.Promise();
154
+ } catch (const Napi::Error &e) {
155
+ deferred.Reject(e.Value());
156
+ return deferred.Promise();
157
+ }
158
+ }
159
+
160
+ } // namespace FSMeta
@@ -0,0 +1,3 @@
1
+ // src/windows/hidden.h
2
+ #pragma once
3
+ #include "../common/hidden.h"
@@ -0,0 +1,48 @@
1
+ // src/windows/string.h
2
+
3
+ #pragma once
4
+ #include <string>
5
+ #include <windows.h>
6
+
7
+ namespace FSMeta {
8
+
9
+ inline std::string WideToUtf8(const WCHAR *wide) {
10
+ if (!wide || wide[0] == 0)
11
+ return "";
12
+
13
+ int size =
14
+ WideCharToMultiByte(CP_UTF8, 0, wide, -1, nullptr, 0, nullptr, nullptr);
15
+ if (size <= 0)
16
+ return "";
17
+
18
+ std::string result(size - 1, 0);
19
+ WideCharToMultiByte(CP_UTF8, 0, wide, -1, &result[0], size, nullptr, nullptr);
20
+ return result;
21
+ }
22
+
23
+ class PathConverter {
24
+ public:
25
+ static std::wstring ToWString(const std::string &path) {
26
+ if (path.empty()) {
27
+ return L"";
28
+ }
29
+
30
+ int wlen = MultiByteToWideChar(CP_UTF8, 0, path.c_str(),
31
+ static_cast<int>(path.length()), nullptr, 0);
32
+
33
+ if (wlen == 0) {
34
+ return L"";
35
+ }
36
+
37
+ std::wstring wpath(wlen, 0);
38
+ if (!MultiByteToWideChar(CP_UTF8, 0, path.c_str(),
39
+ static_cast<int>(path.length()), &wpath[0],
40
+ wlen)) {
41
+ return L"";
42
+ }
43
+
44
+ return wpath;
45
+ }
46
+ };
47
+
48
+ } // namespace FSMeta
@@ -0,0 +1,63 @@
1
+ // src/windows/system_volume.h
2
+ #pragma once
3
+ #include "../common/debug_log.h"
4
+ #include "string.h"
5
+ #include <PathCch.h>
6
+ #include <shlobj.h> // For SHGetFolderPathW and CSIDL constants
7
+ #include <string>
8
+ #include <windows.h>
9
+
10
+ // If FILE_SUPPORTS_SYSTEM_PATHS is not defined (older SDK)
11
+ #ifndef FILE_SUPPORTS_SYSTEM_PATHS
12
+ #define FILE_SUPPORTS_SYSTEM_PATHS 0x00100000
13
+ #endif
14
+
15
+ #ifndef FILE_SUPPORTS_SYSTEM_FILES
16
+ #define FILE_SUPPORTS_SYSTEM_FILES 0x00200000
17
+ #endif
18
+
19
+ namespace FSMeta {
20
+
21
+ inline bool IsSystemVolume(const std::wstring &drive) {
22
+ WCHAR systemRoot[MAX_PATH];
23
+ if (SUCCEEDED(
24
+ SHGetFolderPathW(nullptr, CSIDL_WINDOWS, nullptr, 0, systemRoot))) {
25
+ WCHAR rootPath[4];
26
+ wcsncpy_s(rootPath, systemRoot, 3);
27
+ rootPath[3] = '\0';
28
+
29
+ if (_wcsnicmp(drive.c_str(), rootPath, 2) == 0) {
30
+ DEBUG_LOG("[IsSystemVolume] %ls is a system volume", drive.c_str());
31
+ return true;
32
+ }
33
+ }
34
+
35
+ // Modern volume properties check
36
+ DWORD volumeFlags = 0;
37
+ wchar_t fileSystemName[MAX_PATH + 1] = {0};
38
+
39
+ if (GetVolumeInformationW(drive.c_str(), nullptr, 0, nullptr, nullptr,
40
+ &volumeFlags, fileSystemName, MAX_PATH)) {
41
+
42
+ // Check for modern system volume indicators
43
+ if ((volumeFlags & FILE_SUPPORTS_SYSTEM_PATHS) ||
44
+ (volumeFlags & FILE_SUPPORTS_SYSTEM_FILES)) {
45
+ DEBUG_LOG("[IsSystemVolume] %ls has system volume flags (0x%08X)",
46
+ drive.c_str(), volumeFlags);
47
+ return true;
48
+ }
49
+ } else {
50
+ DEBUG_LOG("[IsSystemVolume] %ls GetVolumeInformationW failed: %lu",
51
+ drive.c_str(), GetLastError());
52
+ }
53
+
54
+ DEBUG_LOG("[IsSystemVolume] %ls is not a system volume", drive.c_str());
55
+ return false;
56
+ }
57
+
58
+ // Legacy compatibility overload
59
+ inline bool IsSystemVolume(const WCHAR *drive) {
60
+ return IsSystemVolume(std::wstring(drive));
61
+ }
62
+
63
+ } // namespace FSMeta