@photostructure/fs-metadata 0.6.0 → 0.7.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.
- package/CHANGELOG.md +11 -6
- package/CLAUDE.md +160 -136
- package/CODE_OF_CONDUCT.md +11 -11
- package/CONTRIBUTING.md +2 -2
- package/README.md +34 -84
- package/binding.gyp +98 -23
- package/claude.sh +23 -0
- package/dist/index.cjs +53 -22
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +5 -0
- package/dist/index.d.mts +5 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.mjs +52 -21
- package/dist/index.mjs.map +1 -1
- package/{C++_REVIEW_TODO.md → doc/C++_REVIEW_TODO.md} +97 -25
- package/doc/GPG_RELEASE_HOWTO.md +505 -0
- package/doc/MACOS_API_REFERENCE.md +469 -0
- package/doc/SECURITY_AUDIT_2025.md +809 -0
- package/doc/SSH_RELEASE_HOWTO.md +207 -0
- package/doc/WINDOWS_API_REFERENCE.md +422 -0
- package/doc/WINDOWS_ARM64_SECURITY.md +161 -0
- package/doc/WINDOWS_DEBUG_GUIDE.md +96 -0
- package/doc/examples.md +267 -0
- package/doc/gotchas.md +297 -0
- package/doc/logo.png +0 -0
- package/doc/logo.svg +85 -0
- package/doc/macos-asan-sip-issue.md +71 -0
- package/doc/social.png +0 -0
- package/doc/social.svg +125 -0
- package/doc/windows-build.md +226 -0
- package/doc/windows-clang-tidy.md +72 -0
- package/doc/windows-memory-testing.md +108 -0
- package/doc/windows-prebuildify-arm64.md +232 -0
- package/jest.config.cjs +24 -0
- package/package.json +68 -44
- package/prebuilds/darwin-arm64/@photostructure+fs-metadata.glibc.node +0 -0
- package/prebuilds/darwin-x64/@photostructure+fs-metadata.glibc.node +0 -0
- package/prebuilds/linux-arm64/@photostructure+fs-metadata.glibc.node +0 -0
- package/prebuilds/linux-arm64/@photostructure+fs-metadata.musl.node +0 -0
- package/prebuilds/linux-x64/@photostructure+fs-metadata.glibc.node +0 -0
- package/prebuilds/linux-x64/@photostructure+fs-metadata.musl.node +0 -0
- package/prebuilds/win32-arm64/@photostructure+fs-metadata.glibc.node +0 -0
- package/prebuilds/win32-x64/@photostructure+fs-metadata.glibc.node +0 -0
- package/scripts/check-memory.ts +186 -0
- package/scripts/clang-tidy.ts +832 -0
- package/scripts/install.cjs +42 -0
- package/scripts/is-platform.mjs +1 -1
- package/scripts/macos-asan.sh +155 -0
- package/scripts/post-build.mjs +3 -3
- package/scripts/prebuild-linux-glibc.sh +119 -0
- package/scripts/prebuildify-wrapper.ts +77 -0
- package/scripts/precommit.ts +70 -0
- package/scripts/sanitizers-test.sh +7 -1
- package/scripts/{configure.mjs → setup-native.mjs} +4 -1
- package/src/binding.cpp +1 -1
- package/src/common/error_utils.h +0 -6
- package/src/common/volume_metadata.h +6 -0
- package/src/darwin/hidden.cpp +73 -25
- package/src/darwin/path_security.h +149 -0
- package/src/darwin/raii_utils.h +104 -4
- package/src/darwin/volume_metadata.cpp +132 -58
- package/src/darwin/volume_mount_points.cpp +80 -47
- package/src/hidden.ts +36 -13
- package/src/linux/gio_mount_points.cpp +17 -18
- package/src/linux/gio_utils.cpp +92 -37
- package/src/linux/gio_utils.h +11 -5
- package/src/linux/gio_volume_metadata.cpp +111 -48
- package/src/linux/volume_metadata.cpp +67 -4
- package/src/object.ts +1 -0
- package/src/options.ts +6 -0
- package/src/path.ts +11 -0
- package/src/platform.ts +25 -0
- package/src/remote_info.ts +5 -3
- package/src/stack_path.ts +8 -6
- package/src/string_enum.ts +1 -0
- package/src/test-utils/benchmark-harness.ts +192 -0
- package/src/test-utils/debuglog-child.ts +30 -2
- package/src/test-utils/debuglog-enabled-child.ts +38 -8
- package/src/test-utils/jest-setup.ts +14 -0
- package/src/test-utils/memory-test-core.ts +336 -0
- package/src/test-utils/memory-test-runner.ts +108 -0
- package/src/test-utils/platform.ts +46 -1
- package/src/test-utils/worker-thread-helper.cjs +157 -26
- package/src/types/native_bindings.ts +1 -1
- package/src/types/options.ts +6 -0
- package/src/windows/drive_status.h +133 -163
- package/src/windows/error_utils.h +54 -3
- package/src/windows/fs_meta.h +1 -1
- package/src/windows/hidden.cpp +60 -43
- package/src/windows/security_utils.h +250 -0
- package/src/windows/string.h +68 -11
- package/src/windows/system_volume.h +1 -1
- package/src/windows/thread_pool.h +206 -0
- package/src/windows/volume_metadata.cpp +11 -6
- package/src/windows/volume_mount_points.cpp +8 -7
- package/src/windows/windows_arch.h +39 -0
- package/scripts/check-memory.mjs +0 -123
- package/scripts/clang-tidy.mjs +0 -73
package/src/darwin/hidden.cpp
CHANGED
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
#include "hidden.h"
|
|
3
3
|
#include "../common/debug_log.h"
|
|
4
4
|
#include "../common/error_utils.h"
|
|
5
|
+
#include "path_security.h"
|
|
6
|
+
#include <string.h> // for strcmp
|
|
7
|
+
#include <sys/mount.h>
|
|
5
8
|
#include <sys/stat.h>
|
|
6
9
|
#include <unistd.h>
|
|
7
10
|
|
|
@@ -9,35 +12,51 @@ namespace FSMeta {
|
|
|
9
12
|
|
|
10
13
|
GetHiddenWorker::GetHiddenWorker(std::string path,
|
|
11
14
|
Napi::Promise::Deferred deferred)
|
|
12
|
-
: Napi::AsyncWorker(deferred.Env()), path_(path)
|
|
13
|
-
is_hidden_(false) {
|
|
15
|
+
: Napi::AsyncWorker(deferred.Env()), path_(std::move(path)),
|
|
16
|
+
deferred_(deferred), is_hidden_(false) {
|
|
14
17
|
DEBUG_LOG("[GetHiddenWorker] created for path: %s", path_.c_str());
|
|
15
18
|
}
|
|
16
19
|
|
|
17
20
|
void GetHiddenWorker::Execute() {
|
|
18
21
|
DEBUG_LOG("[GetHiddenWorker] checking hidden status for: %s", path_.c_str());
|
|
19
22
|
|
|
20
|
-
//
|
|
21
|
-
|
|
22
|
-
|
|
23
|
+
// Validate and canonicalize path using realpath() to prevent directory
|
|
24
|
+
// traversal This follows Apple's Secure Coding Guide recommendations For
|
|
25
|
+
// isHidden(), we allow non-existent paths (they will fail stat() below)
|
|
26
|
+
std::string error;
|
|
27
|
+
std::string validated_path = ValidateAndCanonicalizePath(path_, error, true);
|
|
28
|
+
if (validated_path.empty()) {
|
|
29
|
+
// If validation failed, check if it's because the path doesn't exist
|
|
30
|
+
// In that case, return the expected "Path not found" error for TypeScript
|
|
31
|
+
// layer
|
|
32
|
+
if (error.find("realpath") != std::string::npos &&
|
|
33
|
+
error.find("No such file or directory") != std::string::npos) {
|
|
34
|
+
SetError("Path not found: '" + path_ + "'");
|
|
35
|
+
} else {
|
|
36
|
+
SetError(error);
|
|
37
|
+
}
|
|
23
38
|
return;
|
|
24
39
|
}
|
|
25
40
|
|
|
41
|
+
// Use the validated path for all subsequent operations
|
|
42
|
+
DEBUG_LOG("[GetHiddenWorker] Using validated path: %s",
|
|
43
|
+
validated_path.c_str());
|
|
44
|
+
|
|
26
45
|
struct stat statbuf;
|
|
27
|
-
if (stat(
|
|
46
|
+
if (stat(validated_path.c_str(), &statbuf) != 0) {
|
|
28
47
|
int error = errno;
|
|
29
48
|
if (error == ENOENT) {
|
|
30
|
-
DEBUG_LOG("[GetHiddenWorker] path not found: %s",
|
|
31
|
-
SetError("Path not found: '" +
|
|
49
|
+
DEBUG_LOG("[GetHiddenWorker] path not found: %s", validated_path.c_str());
|
|
50
|
+
SetError("Path not found: '" + validated_path + "'");
|
|
32
51
|
} else {
|
|
33
52
|
DEBUG_LOG("[GetHiddenWorker] failed to stat path %s: %s (%d)",
|
|
34
|
-
|
|
35
|
-
SetError(CreatePathErrorMessage("stat",
|
|
53
|
+
validated_path.c_str(), strerror(error), error);
|
|
54
|
+
SetError(CreatePathErrorMessage("stat", validated_path, error));
|
|
36
55
|
}
|
|
37
56
|
return;
|
|
38
57
|
}
|
|
39
58
|
is_hidden_ = (statbuf.st_flags & UF_HIDDEN) != 0;
|
|
40
|
-
DEBUG_LOG("[GetHiddenWorker] path %s is %s",
|
|
59
|
+
DEBUG_LOG("[GetHiddenWorker] path %s is %s", validated_path.c_str(),
|
|
41
60
|
is_hidden_ ? "hidden" : "not hidden");
|
|
42
61
|
}
|
|
43
62
|
|
|
@@ -74,8 +93,8 @@ Napi::Promise GetHiddenAttribute(const Napi::CallbackInfo &info) {
|
|
|
74
93
|
|
|
75
94
|
SetHiddenWorker::SetHiddenWorker(std::string path, bool hidden,
|
|
76
95
|
Napi::Promise::Deferred deferred)
|
|
77
|
-
: Napi::AsyncWorker(deferred.Env()), path_(path)
|
|
78
|
-
deferred_(deferred) {
|
|
96
|
+
: Napi::AsyncWorker(deferred.Env()), path_(std::move(path)),
|
|
97
|
+
hidden_(hidden), deferred_(deferred) {
|
|
79
98
|
DEBUG_LOG("[SetHiddenWorker] created for path: %s, hidden: %d", path_.c_str(),
|
|
80
99
|
hidden_);
|
|
81
100
|
}
|
|
@@ -84,22 +103,34 @@ void SetHiddenWorker::Execute() {
|
|
|
84
103
|
DEBUG_LOG("[SetHiddenWorker] setting hidden=%d for: %s", hidden_,
|
|
85
104
|
path_.c_str());
|
|
86
105
|
|
|
87
|
-
//
|
|
88
|
-
|
|
89
|
-
|
|
106
|
+
// macOS uses BSD file flags (UF_HIDDEN) to control file visibility.
|
|
107
|
+
// This is different from the dot-prefix convention used on Unix systems.
|
|
108
|
+
// The chflags() system call modifies these BSD-specific file flags.
|
|
109
|
+
|
|
110
|
+
// Validate and canonicalize path using realpath() to prevent directory
|
|
111
|
+
// traversal This follows Apple's Secure Coding Guide recommendations For
|
|
112
|
+
// setHidden, the file must exist, so we use ValidatePathForRead
|
|
113
|
+
std::string error;
|
|
114
|
+
std::string validated_path = ValidatePathForRead(path_, error);
|
|
115
|
+
if (validated_path.empty()) {
|
|
116
|
+
SetError(error);
|
|
90
117
|
return;
|
|
91
118
|
}
|
|
92
119
|
|
|
120
|
+
// Use the validated path for all subsequent operations
|
|
121
|
+
DEBUG_LOG("[SetHiddenWorker] Using validated path: %s",
|
|
122
|
+
validated_path.c_str());
|
|
123
|
+
|
|
93
124
|
struct stat statbuf;
|
|
94
|
-
if (stat(
|
|
125
|
+
if (stat(validated_path.c_str(), &statbuf) != 0) {
|
|
95
126
|
int error = errno;
|
|
96
127
|
if (error == ENOENT) {
|
|
97
|
-
DEBUG_LOG("[SetHiddenWorker] path not found: %s",
|
|
98
|
-
SetError("Path not found: '" +
|
|
128
|
+
DEBUG_LOG("[SetHiddenWorker] path not found: %s", validated_path.c_str());
|
|
129
|
+
SetError("Path not found: '" + validated_path + "'");
|
|
99
130
|
} else {
|
|
100
131
|
DEBUG_LOG("[SetHiddenWorker] failed to stat path %s: %s (%d)",
|
|
101
|
-
|
|
102
|
-
SetError(CreatePathErrorMessage("stat",
|
|
132
|
+
validated_path.c_str(), strerror(error), error);
|
|
133
|
+
SetError(CreatePathErrorMessage("stat", validated_path, error));
|
|
103
134
|
}
|
|
104
135
|
return;
|
|
105
136
|
}
|
|
@@ -111,15 +142,32 @@ void SetHiddenWorker::Execute() {
|
|
|
111
142
|
new_flags = statbuf.st_flags & ~UF_HIDDEN;
|
|
112
143
|
}
|
|
113
144
|
|
|
114
|
-
if (chflags(
|
|
145
|
+
if (chflags(validated_path.c_str(), new_flags) != 0) {
|
|
115
146
|
int error = errno;
|
|
116
147
|
DEBUG_LOG("[SetHiddenWorker] failed to set flags for %s: %s (%d)",
|
|
117
|
-
|
|
118
|
-
|
|
148
|
+
validated_path.c_str(), strerror(error), error);
|
|
149
|
+
|
|
150
|
+
// Check if this is an APFS filesystem issue
|
|
151
|
+
struct statfs fs;
|
|
152
|
+
bool is_apfs = false;
|
|
153
|
+
if (statfs(validated_path.c_str(), &fs) == 0) {
|
|
154
|
+
is_apfs = (strcmp(fs.f_fstypename, "apfs") == 0);
|
|
155
|
+
DEBUG_LOG("[SetHiddenWorker] filesystem type: %s", fs.f_fstypename);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Provide more detailed error message for APFS
|
|
159
|
+
if (is_apfs && (error == EPERM || error == ENOTSUP)) {
|
|
160
|
+
SetError("Setting hidden attribute failed on APFS filesystem. "
|
|
161
|
+
"This is a known issue with some APFS volumes. "
|
|
162
|
+
"Error: " +
|
|
163
|
+
CreatePathErrorMessage("chflags", validated_path, error));
|
|
164
|
+
} else {
|
|
165
|
+
SetError(CreatePathErrorMessage("chflags", validated_path, error));
|
|
166
|
+
}
|
|
119
167
|
return;
|
|
120
168
|
}
|
|
121
169
|
DEBUG_LOG("[SetHiddenWorker] successfully set hidden=%d for: %s", hidden_,
|
|
122
|
-
|
|
170
|
+
validated_path.c_str());
|
|
123
171
|
}
|
|
124
172
|
|
|
125
173
|
void SetHiddenWorker::OnOK() {
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
// src/darwin/path_security.h
|
|
2
|
+
// Secure path validation for macOS using realpath()
|
|
3
|
+
// Implements recommendations from Apple's Secure Coding Guide
|
|
4
|
+
// https://developer.apple.com/library/archive/documentation/Security/Conceptual/SecureCodingGuide/Articles/RaceConditions.html
|
|
5
|
+
|
|
6
|
+
#pragma once
|
|
7
|
+
|
|
8
|
+
#include "../common/debug_log.h"
|
|
9
|
+
#include "../common/error_utils.h"
|
|
10
|
+
#include <cerrno>
|
|
11
|
+
#include <cstring>
|
|
12
|
+
#include <string>
|
|
13
|
+
#include <sys/param.h> // For PATH_MAX
|
|
14
|
+
#include <unistd.h> // For realpath()
|
|
15
|
+
|
|
16
|
+
namespace FSMeta {
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Validates a path for security issues and canonicalizes it using realpath().
|
|
20
|
+
*
|
|
21
|
+
* This function prevents directory traversal attacks by:
|
|
22
|
+
* 1. Checking for null bytes (path injection)
|
|
23
|
+
* 2. Using realpath() to resolve symbolic links and path references (../, ./)
|
|
24
|
+
* 3. Handling non-existent paths by validating the parent directory
|
|
25
|
+
*
|
|
26
|
+
* @param path The path to validate
|
|
27
|
+
* @param error Output parameter for error message if validation fails
|
|
28
|
+
* @param allow_nonexistent If true, allows paths that don't exist by validating
|
|
29
|
+
* parent
|
|
30
|
+
* @return The canonicalized path, or empty string if validation fails
|
|
31
|
+
*/
|
|
32
|
+
inline std::string ValidateAndCanonicalizePath(const std::string &path,
|
|
33
|
+
std::string &error,
|
|
34
|
+
bool allow_nonexistent = false) {
|
|
35
|
+
DEBUG_LOG("[ValidateAndCanonicalizePath] Validating path: %s "
|
|
36
|
+
"(allow_nonexistent: %d)",
|
|
37
|
+
path.c_str(), allow_nonexistent);
|
|
38
|
+
|
|
39
|
+
// Check for empty path
|
|
40
|
+
if (path.empty()) {
|
|
41
|
+
error = "Empty path provided";
|
|
42
|
+
DEBUG_LOG("[ValidateAndCanonicalizePath] %s", error.c_str());
|
|
43
|
+
return "";
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Security check #1: Reject paths with null bytes (path injection attack)
|
|
47
|
+
if (path.find('\0') != std::string::npos) {
|
|
48
|
+
error = "Invalid path containing null byte";
|
|
49
|
+
DEBUG_LOG("[ValidateAndCanonicalizePath] %s", error.c_str());
|
|
50
|
+
return "";
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Security check #2: Use realpath() to canonicalize and validate
|
|
54
|
+
// realpath() resolves symbolic links and eliminates ../, ./, redundant
|
|
55
|
+
// slashes
|
|
56
|
+
char resolved_path[PATH_MAX];
|
|
57
|
+
if (realpath(path.c_str(), resolved_path) != nullptr) {
|
|
58
|
+
// Path exists and was successfully canonicalized
|
|
59
|
+
std::string canonical_path(resolved_path);
|
|
60
|
+
DEBUG_LOG("[ValidateAndCanonicalizePath] Canonicalized: %s -> %s",
|
|
61
|
+
path.c_str(), canonical_path.c_str());
|
|
62
|
+
return canonical_path;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// realpath() failed - check if it's because the path doesn't exist
|
|
66
|
+
int realpath_error = errno;
|
|
67
|
+
|
|
68
|
+
if (realpath_error == ENOENT && allow_nonexistent) {
|
|
69
|
+
// For operations that create files (like setHidden), validate parent
|
|
70
|
+
// directory
|
|
71
|
+
DEBUG_LOG(
|
|
72
|
+
"[ValidateAndCanonicalizePath] Path doesn't exist, validating parent");
|
|
73
|
+
|
|
74
|
+
// Find the parent directory
|
|
75
|
+
size_t last_slash = path.find_last_of('/');
|
|
76
|
+
std::string parent_dir;
|
|
77
|
+
|
|
78
|
+
if (last_slash == std::string::npos) {
|
|
79
|
+
// No slash found - relative path, use current directory
|
|
80
|
+
parent_dir = ".";
|
|
81
|
+
} else if (last_slash == 0) {
|
|
82
|
+
// Root directory
|
|
83
|
+
parent_dir = "/";
|
|
84
|
+
} else {
|
|
85
|
+
parent_dir = path.substr(0, last_slash);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Validate parent directory exists and is accessible
|
|
89
|
+
if (realpath(parent_dir.c_str(), resolved_path) == nullptr) {
|
|
90
|
+
int parent_error = errno;
|
|
91
|
+
error =
|
|
92
|
+
CreatePathErrorMessage("realpath (parent)", parent_dir, parent_error);
|
|
93
|
+
DEBUG_LOG("[ValidateAndCanonicalizePath] Parent validation failed: %s",
|
|
94
|
+
error.c_str());
|
|
95
|
+
return "";
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Parent is valid - construct the full path
|
|
99
|
+
std::string parent_canonical(resolved_path);
|
|
100
|
+
std::string filename =
|
|
101
|
+
(last_slash == std::string::npos) ? path : path.substr(last_slash + 1);
|
|
102
|
+
|
|
103
|
+
std::string result;
|
|
104
|
+
if (parent_canonical == "/") {
|
|
105
|
+
result = "/" + filename;
|
|
106
|
+
} else {
|
|
107
|
+
result = parent_canonical + "/" + filename;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
DEBUG_LOG(
|
|
111
|
+
"[ValidateAndCanonicalizePath] Validated non-existent path: %s -> %s",
|
|
112
|
+
path.c_str(), result.c_str());
|
|
113
|
+
return result;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// realpath() failed for a different reason, or path doesn't exist and we
|
|
117
|
+
// don't allow it
|
|
118
|
+
error = CreatePathErrorMessage("realpath", path, realpath_error);
|
|
119
|
+
DEBUG_LOG("[ValidateAndCanonicalizePath] Failed: %s", error.c_str());
|
|
120
|
+
return "";
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Validates that a path is secure for read operations.
|
|
125
|
+
* The path must exist and be accessible.
|
|
126
|
+
*
|
|
127
|
+
* @param path The path to validate
|
|
128
|
+
* @param error Output parameter for error message if validation fails
|
|
129
|
+
* @return The canonicalized path, or empty string if validation fails
|
|
130
|
+
*/
|
|
131
|
+
inline std::string ValidatePathForRead(const std::string &path,
|
|
132
|
+
std::string &error) {
|
|
133
|
+
return ValidateAndCanonicalizePath(path, error, false);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Validates that a path is secure for write operations.
|
|
138
|
+
* The path may not exist, but its parent directory must be valid.
|
|
139
|
+
*
|
|
140
|
+
* @param path The path to validate
|
|
141
|
+
* @param error Output parameter for error message if validation fails
|
|
142
|
+
* @return The canonicalized path, or empty string if validation fails
|
|
143
|
+
*/
|
|
144
|
+
inline std::string ValidatePathForWrite(const std::string &path,
|
|
145
|
+
std::string &error) {
|
|
146
|
+
return ValidateAndCanonicalizePath(path, error, true);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
} // namespace FSMeta
|
package/src/darwin/raii_utils.h
CHANGED
|
@@ -1,11 +1,18 @@
|
|
|
1
1
|
#pragma once
|
|
2
2
|
|
|
3
3
|
#include <CoreFoundation/CoreFoundation.h>
|
|
4
|
+
#include <DiskArbitration/DiskArbitration.h>
|
|
4
5
|
#include <sys/mount.h>
|
|
5
6
|
|
|
7
|
+
// RAII (Resource Acquisition Is Initialization) utilities for macOS APIs.
|
|
8
|
+
// These wrappers ensure proper cleanup of system resources even in the
|
|
9
|
+
// presence of exceptions, preventing memory leaks and resource exhaustion.
|
|
10
|
+
|
|
6
11
|
namespace FSMeta {
|
|
7
12
|
|
|
8
|
-
// Generic RAII wrapper for resources that need free()
|
|
13
|
+
// Generic RAII wrapper for resources that need free().
|
|
14
|
+
// This is used for C-style allocations that must be freed with free().
|
|
15
|
+
// Common usage: buffers returned by system APIs like getmntinfo_r_np().
|
|
9
16
|
template <typename T> class ResourceRAII {
|
|
10
17
|
private:
|
|
11
18
|
T *resource_;
|
|
@@ -41,10 +48,48 @@ public:
|
|
|
41
48
|
ResourceRAII &operator=(const ResourceRAII &) = delete;
|
|
42
49
|
};
|
|
43
50
|
|
|
44
|
-
// Specialized for mount
|
|
45
|
-
|
|
51
|
+
// Specialized RAII wrapper for mount buffer from getmntinfo_r_np().
|
|
52
|
+
// getmntinfo_r_np() allocates a buffer that the caller must free.
|
|
53
|
+
// This wrapper ensures the buffer is freed even if exceptions occur.
|
|
54
|
+
class MountBufferRAII {
|
|
55
|
+
private:
|
|
56
|
+
struct statfs *buffer_;
|
|
57
|
+
|
|
58
|
+
public:
|
|
59
|
+
MountBufferRAII() : buffer_(nullptr) {}
|
|
60
|
+
~MountBufferRAII() {
|
|
61
|
+
if (buffer_) {
|
|
62
|
+
free(buffer_);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
46
65
|
|
|
47
|
-
|
|
66
|
+
struct statfs **ptr() { return &buffer_; }
|
|
67
|
+
struct statfs *get() { return buffer_; }
|
|
68
|
+
|
|
69
|
+
// Add move operations for better resource management
|
|
70
|
+
MountBufferRAII(MountBufferRAII &&other) noexcept : buffer_(other.buffer_) {
|
|
71
|
+
other.buffer_ = nullptr;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
MountBufferRAII &operator=(MountBufferRAII &&other) noexcept {
|
|
75
|
+
if (this != &other) {
|
|
76
|
+
if (buffer_)
|
|
77
|
+
free(buffer_);
|
|
78
|
+
buffer_ = other.buffer_;
|
|
79
|
+
other.buffer_ = nullptr;
|
|
80
|
+
}
|
|
81
|
+
return *this;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Prevent copying
|
|
85
|
+
MountBufferRAII(const MountBufferRAII &) = delete;
|
|
86
|
+
MountBufferRAII &operator=(const MountBufferRAII &) = delete;
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
// CoreFoundation RAII wrapper following the Create/Copy/Get rule.
|
|
90
|
+
// Any CF object obtained via Create or Copy functions must be released.
|
|
91
|
+
// This wrapper automatically calls CFRelease() in the destructor,
|
|
92
|
+
// preventing memory leaks from Core Foundation objects.
|
|
48
93
|
template <typename T> class CFReleaser {
|
|
49
94
|
private:
|
|
50
95
|
T ref_;
|
|
@@ -82,4 +127,59 @@ public:
|
|
|
82
127
|
}
|
|
83
128
|
};
|
|
84
129
|
|
|
130
|
+
// Specialized RAII wrapper for DASession that handles dispatch queue lifecycle.
|
|
131
|
+
// DASessionSetDispatchQueue must be called with NULL before the session is
|
|
132
|
+
// released. This wrapper ensures proper cleanup order: unschedule then release.
|
|
133
|
+
class DASessionRAII {
|
|
134
|
+
private:
|
|
135
|
+
CFReleaser<DASessionRef> session_;
|
|
136
|
+
bool is_scheduled_;
|
|
137
|
+
|
|
138
|
+
public:
|
|
139
|
+
explicit DASessionRAII(DASessionRef session = nullptr) noexcept
|
|
140
|
+
: session_(session), is_scheduled_(false) {}
|
|
141
|
+
|
|
142
|
+
~DASessionRAII() { unschedule(); }
|
|
143
|
+
|
|
144
|
+
// Schedule the session on a dispatch queue
|
|
145
|
+
void scheduleOnQueue(dispatch_queue_t queue) {
|
|
146
|
+
if (session_.isValid() && queue != nullptr) {
|
|
147
|
+
DASessionSetDispatchQueue(session_.get(), queue);
|
|
148
|
+
is_scheduled_ = true;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Unschedule the session (must be called before session is released)
|
|
153
|
+
void unschedule() {
|
|
154
|
+
if (is_scheduled_ && session_.isValid()) {
|
|
155
|
+
DASessionSetDispatchQueue(session_.get(), nullptr);
|
|
156
|
+
is_scheduled_ = false;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
DASessionRef get() const noexcept { return session_.get(); }
|
|
161
|
+
bool isValid() const noexcept { return session_.isValid(); }
|
|
162
|
+
|
|
163
|
+
// Prevent copying
|
|
164
|
+
DASessionRAII(const DASessionRAII &) = delete;
|
|
165
|
+
DASessionRAII &operator=(const DASessionRAII &) = delete;
|
|
166
|
+
|
|
167
|
+
// Allow moving
|
|
168
|
+
DASessionRAII(DASessionRAII &&other) noexcept
|
|
169
|
+
: session_(std::move(other.session_)),
|
|
170
|
+
is_scheduled_(other.is_scheduled_) {
|
|
171
|
+
other.is_scheduled_ = false;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
DASessionRAII &operator=(DASessionRAII &&other) noexcept {
|
|
175
|
+
if (this != &other) {
|
|
176
|
+
unschedule();
|
|
177
|
+
session_ = std::move(other.session_);
|
|
178
|
+
is_scheduled_ = other.is_scheduled_;
|
|
179
|
+
other.is_scheduled_ = false;
|
|
180
|
+
}
|
|
181
|
+
return *this;
|
|
182
|
+
}
|
|
183
|
+
};
|
|
184
|
+
|
|
85
185
|
} // namespace FSMeta
|