@photostructure/fs-metadata 0.5.0 → 0.6.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/C++_REVIEW_TODO.md +1 -1
- package/CHANGELOG.md +11 -1
- package/CLAUDE.md +30 -23
- package/CONTRIBUTING.md +41 -0
- package/binding.gyp +2 -1
- package/dist/index.cjs +1439 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +360 -0
- package/dist/index.d.mts +360 -0
- package/dist/index.d.ts +360 -0
- package/dist/index.mjs +1397 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +19 -22
- package/prebuilds/darwin-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-x64/@photostructure+fs-metadata.glibc.node +0 -0
- package/scripts/check-memory.mjs +53 -173
- package/scripts/clang-tidy.mjs +1 -1
- package/scripts/sanitizers-test.sh +151 -0
- package/scripts/valgrind-test.mjs +1 -1
- package/scripts/{valgrind.sh → valgrind-test.sh} +15 -0
- package/src/common/error_utils.h +23 -0
- package/src/common/metadata_worker.h +4 -1
- package/src/darwin/hidden.cpp +13 -6
- package/src/darwin/volume_metadata.cpp +2 -2
- package/src/darwin/volume_mount_points.cpp +8 -1
- package/src/linux/blkid_cache.cpp +8 -2
- package/src/linux/volume_metadata.cpp +1 -1
- package/src/test-utils/worker-thread-helper.cjs +88 -0
- package/src/windows/hidden.cpp +20 -11
- package/coverage/base.css +0 -224
- package/coverage/block-navigation.js +0 -87
- package/coverage/favicon.png +0 -0
- package/coverage/index.html +0 -131
- package/coverage/lcov-report/base.css +0 -224
- package/coverage/lcov-report/block-navigation.js +0 -87
- package/coverage/lcov-report/favicon.png +0 -0
- package/coverage/lcov-report/index.html +0 -131
- package/coverage/lcov-report/prettify.css +0 -1
- package/coverage/lcov-report/prettify.js +0 -2
- package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
- package/coverage/lcov-report/sorter.js +0 -196
- package/coverage/lcov-report/src/array.ts.html +0 -217
- package/coverage/lcov-report/src/async.ts.html +0 -547
- package/coverage/lcov-report/src/debuglog.ts.html +0 -187
- package/coverage/lcov-report/src/defer.ts.html +0 -175
- package/coverage/lcov-report/src/dirname.ts.html +0 -124
- package/coverage/lcov-report/src/error.ts.html +0 -322
- package/coverage/lcov-report/src/fs.ts.html +0 -316
- package/coverage/lcov-report/src/glob.ts.html +0 -472
- package/coverage/lcov-report/src/hidden.ts.html +0 -724
- package/coverage/lcov-report/src/index.html +0 -521
- package/coverage/lcov-report/src/index.ts.html +0 -676
- package/coverage/lcov-report/src/linux/dev_disk.ts.html +0 -316
- package/coverage/lcov-report/src/linux/index.html +0 -146
- package/coverage/lcov-report/src/linux/mount_points.ts.html +0 -364
- package/coverage/lcov-report/src/linux/mtab.ts.html +0 -493
- package/coverage/lcov-report/src/mount_point.ts.html +0 -106
- package/coverage/lcov-report/src/number.ts.html +0 -148
- package/coverage/lcov-report/src/object.ts.html +0 -265
- package/coverage/lcov-report/src/options.ts.html +0 -475
- package/coverage/lcov-report/src/path.ts.html +0 -268
- package/coverage/lcov-report/src/platform.ts.html +0 -112
- package/coverage/lcov-report/src/random.ts.html +0 -205
- package/coverage/lcov-report/src/remote_info.ts.html +0 -553
- package/coverage/lcov-report/src/stack_path.ts.html +0 -298
- package/coverage/lcov-report/src/string.ts.html +0 -382
- package/coverage/lcov-report/src/string_enum.ts.html +0 -208
- package/coverage/lcov-report/src/system_volume.ts.html +0 -301
- package/coverage/lcov-report/src/unc.ts.html +0 -274
- package/coverage/lcov-report/src/units.ts.html +0 -274
- package/coverage/lcov-report/src/uuid.ts.html +0 -157
- package/coverage/lcov-report/src/volume_health_status.ts.html +0 -259
- package/coverage/lcov-report/src/volume_metadata.ts.html +0 -787
- package/coverage/lcov-report/src/volume_mount_points.ts.html +0 -388
- package/coverage/lcov.info +0 -3581
- package/coverage/prettify.css +0 -1
- package/coverage/prettify.js +0 -2
- package/coverage/sort-arrow-sprite.png +0 -0
- package/coverage/sorter.js +0 -196
- package/coverage/src/array.ts.html +0 -217
- package/coverage/src/async.ts.html +0 -547
- package/coverage/src/debuglog.ts.html +0 -187
- package/coverage/src/defer.ts.html +0 -175
- package/coverage/src/dirname.ts.html +0 -124
- package/coverage/src/error.ts.html +0 -322
- package/coverage/src/fs.ts.html +0 -316
- package/coverage/src/glob.ts.html +0 -472
- package/coverage/src/hidden.ts.html +0 -724
- package/coverage/src/index.html +0 -521
- package/coverage/src/index.ts.html +0 -676
- package/coverage/src/linux/dev_disk.ts.html +0 -316
- package/coverage/src/linux/index.html +0 -146
- package/coverage/src/linux/mount_points.ts.html +0 -364
- package/coverage/src/linux/mtab.ts.html +0 -493
- package/coverage/src/mount_point.ts.html +0 -106
- package/coverage/src/number.ts.html +0 -148
- package/coverage/src/object.ts.html +0 -265
- package/coverage/src/options.ts.html +0 -475
- package/coverage/src/path.ts.html +0 -268
- package/coverage/src/platform.ts.html +0 -112
- package/coverage/src/random.ts.html +0 -205
- package/coverage/src/remote_info.ts.html +0 -553
- package/coverage/src/stack_path.ts.html +0 -298
- package/coverage/src/string.ts.html +0 -382
- package/coverage/src/string_enum.ts.html +0 -208
- package/coverage/src/system_volume.ts.html +0 -301
- package/coverage/src/unc.ts.html +0 -274
- package/coverage/src/units.ts.html +0 -274
- package/coverage/src/uuid.ts.html +0 -157
- package/coverage/src/volume_health_status.ts.html +0 -259
- package/coverage/src/volume_metadata.ts.html +0 -787
- package/coverage/src/volume_mount_points.ts.html +0 -388
- package/scripts/run-asan.sh +0 -92
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# AddressSanitizer and LeakSanitizer test runner for @photostructure/fs-metadata
|
|
3
|
+
# Runs comprehensive memory safety checks on native code
|
|
4
|
+
|
|
5
|
+
set -euo pipefail
|
|
6
|
+
|
|
7
|
+
# Check if we're on Linux
|
|
8
|
+
if [[ "$OSTYPE" != "linux-gnu"* ]]; then
|
|
9
|
+
echo "AddressSanitizer tests are only supported on Linux"
|
|
10
|
+
exit 0
|
|
11
|
+
fi
|
|
12
|
+
|
|
13
|
+
# Check for clang
|
|
14
|
+
if ! command -v clang &> /dev/null; then
|
|
15
|
+
echo "Error: clang is required for AddressSanitizer tests"
|
|
16
|
+
echo "Install with: sudo apt-get install clang"
|
|
17
|
+
exit 1
|
|
18
|
+
fi
|
|
19
|
+
|
|
20
|
+
# Colors for output
|
|
21
|
+
RED='\033[0;31m'
|
|
22
|
+
GREEN='\033[0;32m'
|
|
23
|
+
YELLOW='\033[1;33m'
|
|
24
|
+
BLUE='\033[0;34m'
|
|
25
|
+
NC='\033[0m' # No Color
|
|
26
|
+
|
|
27
|
+
# Configuration
|
|
28
|
+
CLEAN_BUILD=${CLEAN_BUILD:-1}
|
|
29
|
+
VERBOSE=${VERBOSE:-0}
|
|
30
|
+
OUTPUT_FILE="asan-output.log"
|
|
31
|
+
|
|
32
|
+
echo -e "${YELLOW}Running AddressSanitizer and LeakSanitizer tests...${NC}"
|
|
33
|
+
|
|
34
|
+
# Clean previous builds if requested
|
|
35
|
+
if [[ "$CLEAN_BUILD" == "1" ]]; then
|
|
36
|
+
echo "Cleaning previous builds..."
|
|
37
|
+
rm -rf build/
|
|
38
|
+
fi
|
|
39
|
+
rm -f "$OUTPUT_FILE"
|
|
40
|
+
|
|
41
|
+
# Set up build environment
|
|
42
|
+
export CC=clang
|
|
43
|
+
export CXX=clang++
|
|
44
|
+
export CFLAGS="-fsanitize=address -fno-omit-frame-pointer -g -O1"
|
|
45
|
+
export CXXFLAGS="-fsanitize=address -fno-omit-frame-pointer -g -O1"
|
|
46
|
+
export LDFLAGS="-fsanitize=address"
|
|
47
|
+
|
|
48
|
+
# Comprehensive ASAN options combining both implementations
|
|
49
|
+
export ASAN_OPTIONS="detect_leaks=1:halt_on_error=0:print_stats=1:check_initialization_order=1:strict_init_order=1:print_module_map=1"
|
|
50
|
+
export LSAN_OPTIONS="suppressions=$(pwd)/.lsan-suppressions.txt:print_suppressions=0"
|
|
51
|
+
|
|
52
|
+
# Increase Node.js heap size for ASan overhead
|
|
53
|
+
export NODE_OPTIONS="--max-old-space-size=8192"
|
|
54
|
+
|
|
55
|
+
# Find and set ASan runtime library
|
|
56
|
+
echo "Detecting ASan runtime library..."
|
|
57
|
+
ASAN_LIB=$(clang -print-file-name=libclang_rt.asan-x86_64.so 2>/dev/null || echo "")
|
|
58
|
+
|
|
59
|
+
if [[ -n "$ASAN_LIB" && "$ASAN_LIB" != *"not found"* && -f "$ASAN_LIB" ]]; then
|
|
60
|
+
export LD_PRELOAD="$ASAN_LIB"
|
|
61
|
+
echo -e "${BLUE}Using ASan library: $ASAN_LIB${NC}"
|
|
62
|
+
else
|
|
63
|
+
# Try common paths as fallback
|
|
64
|
+
for lib in /usr/lib/x86_64-linux-gnu/libasan.so.{8,6} /usr/lib64/libasan.so.{8,6}; do
|
|
65
|
+
if [[ -f "$lib" ]]; then
|
|
66
|
+
export LD_PRELOAD="$lib"
|
|
67
|
+
echo -e "${BLUE}Using ASan library: $lib${NC}"
|
|
68
|
+
break
|
|
69
|
+
fi
|
|
70
|
+
done
|
|
71
|
+
fi
|
|
72
|
+
|
|
73
|
+
if [[ -z "${LD_PRELOAD:-}" ]]; then
|
|
74
|
+
echo -e "${YELLOW}Warning: Could not find ASan runtime library${NC}"
|
|
75
|
+
fi
|
|
76
|
+
|
|
77
|
+
# Build the native module
|
|
78
|
+
echo "Building with AddressSanitizer..."
|
|
79
|
+
npm run configure:native
|
|
80
|
+
npm run node-gyp-rebuild
|
|
81
|
+
|
|
82
|
+
# Run tests and capture output
|
|
83
|
+
echo -e "${YELLOW}Running tests with AddressSanitizer...${NC}"
|
|
84
|
+
set +e # Don't exit on test failure
|
|
85
|
+
npm test -- --no-coverage 2>&1 | tee "$OUTPUT_FILE"
|
|
86
|
+
TEST_EXIT_CODE=${PIPESTATUS[0]}
|
|
87
|
+
set -e
|
|
88
|
+
|
|
89
|
+
echo -e "${BLUE}\nFull ASAN output saved to: $OUTPUT_FILE${NC}"
|
|
90
|
+
|
|
91
|
+
# Analyze output for errors specific to our code
|
|
92
|
+
echo -e "\n${YELLOW}Analyzing ASAN output...${NC}"
|
|
93
|
+
|
|
94
|
+
# Count different types of issues
|
|
95
|
+
OUR_ERRORS=0
|
|
96
|
+
OUR_LEAKS=0
|
|
97
|
+
INTERNAL_LEAKS=0
|
|
98
|
+
|
|
99
|
+
# Check for ASAN errors in our code (not V8/Node internals)
|
|
100
|
+
if grep -E "(ERROR: AddressSanitizer|ERROR: LeakSanitizer)" "$OUTPUT_FILE" | grep -E "(fs_metadata\.node|/src/)" > /dev/null; then
|
|
101
|
+
OUR_ERRORS=1
|
|
102
|
+
fi
|
|
103
|
+
|
|
104
|
+
# Check for direct/indirect leaks in our code with context
|
|
105
|
+
while IFS= read -r line_num; do
|
|
106
|
+
# Get 5 lines before and 10 lines after for context
|
|
107
|
+
start=$((line_num - 5))
|
|
108
|
+
end=$((line_num + 10))
|
|
109
|
+
if sed -n "${start},${end}p" "$OUTPUT_FILE" | grep -E "(fs_metadata\.node|/src/|photostructure)" > /dev/null; then
|
|
110
|
+
OUR_LEAKS=1
|
|
111
|
+
break
|
|
112
|
+
fi
|
|
113
|
+
done < <(grep -n "Direct leak\|Indirect leak" "$OUTPUT_FILE" | cut -d: -f1)
|
|
114
|
+
|
|
115
|
+
# Count V8/Node internal leaks for information
|
|
116
|
+
INTERNAL_LEAKS=$(grep -c "leak.*\/usr\/bin\/node" "$OUTPUT_FILE" 2>/dev/null || echo "0")
|
|
117
|
+
INTERNAL_LEAKS="${INTERNAL_LEAKS//[[:space:]]/}" # Remove any whitespace
|
|
118
|
+
|
|
119
|
+
# Report results
|
|
120
|
+
EXIT_CODE=0
|
|
121
|
+
|
|
122
|
+
if [[ "$OUR_ERRORS" -eq 1 ]]; then
|
|
123
|
+
echo -e "${RED}\n✗ AddressSanitizer found errors in fs-metadata code:${NC}"
|
|
124
|
+
grep -E "(ERROR: AddressSanitizer|ERROR: LeakSanitizer)" "$OUTPUT_FILE" | grep -E "(fs_metadata\.node|/src/)" | head -20
|
|
125
|
+
EXIT_CODE=1
|
|
126
|
+
fi
|
|
127
|
+
|
|
128
|
+
if [[ "$OUR_LEAKS" -eq 1 ]]; then
|
|
129
|
+
echo -e "${RED}\n✗ LeakSanitizer found memory leaks in fs-metadata code:${NC}"
|
|
130
|
+
# Show leak summary
|
|
131
|
+
grep -A 5 "SUMMARY: AddressSanitizer" "$OUTPUT_FILE" || true
|
|
132
|
+
EXIT_CODE=1
|
|
133
|
+
fi
|
|
134
|
+
|
|
135
|
+
if [[ "$EXIT_CODE" -eq 0 ]]; then
|
|
136
|
+
echo -e "${GREEN}\n✓ AddressSanitizer and LeakSanitizer tests passed (no issues in fs-metadata code)${NC}"
|
|
137
|
+
if [[ "$INTERNAL_LEAKS" -gt 0 ]]; then
|
|
138
|
+
echo -e "${YELLOW} Note: $INTERNAL_LEAKS V8/Node.js internal leaks detected (suppressed)${NC}"
|
|
139
|
+
fi
|
|
140
|
+
else
|
|
141
|
+
echo -e "${RED}\n✗ Memory safety issues detected!${NC}"
|
|
142
|
+
echo -e "${YELLOW}See $OUTPUT_FILE for full details${NC}"
|
|
143
|
+
fi
|
|
144
|
+
|
|
145
|
+
# Show ASAN statistics if verbose
|
|
146
|
+
if [[ "$VERBOSE" -eq 1 ]] && grep -q "Stats:" "$OUTPUT_FILE"; then
|
|
147
|
+
echo -e "\n${BLUE}ASAN Statistics:${NC}"
|
|
148
|
+
grep -A 20 "Stats:" "$OUTPUT_FILE" | head -20
|
|
149
|
+
fi
|
|
150
|
+
|
|
151
|
+
exit $EXIT_CODE
|
|
@@ -35,6 +35,21 @@ if [ ! -f "$VALGRIND_TEST" ]; then
|
|
|
35
35
|
exit 1
|
|
36
36
|
fi
|
|
37
37
|
|
|
38
|
+
# Check if dist directory exists (indicates build completed)
|
|
39
|
+
if [ ! -d "$ROOT_DIR/dist" ]; then
|
|
40
|
+
echo -e "${RED}Error: dist/ directory not found. Run 'npm run build:dist' first.${NC}"
|
|
41
|
+
exit 1
|
|
42
|
+
fi
|
|
43
|
+
|
|
44
|
+
# Pre-flight check: run the test script without valgrind first
|
|
45
|
+
echo "Running pre-flight check..."
|
|
46
|
+
if ! node "$VALGRIND_TEST" > /dev/null 2>&1; then
|
|
47
|
+
echo -e "${RED}Error: Test script failed to run. Running again to show error:${NC}"
|
|
48
|
+
node "$VALGRIND_TEST"
|
|
49
|
+
exit 1
|
|
50
|
+
fi
|
|
51
|
+
echo -e "${GREEN}✓ Pre-flight check passed${NC}"
|
|
52
|
+
|
|
38
53
|
# Use the committed suppressions file
|
|
39
54
|
SUPP_FILE="$ROOT_DIR/.valgrind.supp"
|
|
40
55
|
|
package/src/common/error_utils.h
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// src/common/error_utils.h
|
|
2
2
|
#pragma once
|
|
3
|
+
#include <cstring>
|
|
3
4
|
#include <stdexcept>
|
|
4
5
|
#include <string>
|
|
5
6
|
|
|
@@ -11,9 +12,31 @@ public:
|
|
|
11
12
|
: std::runtime_error(message) {}
|
|
12
13
|
};
|
|
13
14
|
|
|
15
|
+
// Simple error code only version
|
|
14
16
|
inline std::string CreateErrorMessage(const char *operation, int error) {
|
|
15
17
|
return std::string(operation) +
|
|
16
18
|
" failed with error: " + std::to_string(error);
|
|
17
19
|
}
|
|
18
20
|
|
|
21
|
+
// Human-readable error with code (used by most callers)
|
|
22
|
+
inline std::string CreateErrorMessageWithStrerror(const char *operation,
|
|
23
|
+
int error) {
|
|
24
|
+
return std::string(strerror(error)) + " (" + std::to_string(error) + ")";
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Convenience function for common pattern: "operation failed for 'path': error"
|
|
28
|
+
inline std::string CreatePathErrorMessage(const char *operation,
|
|
29
|
+
const std::string &path, int error) {
|
|
30
|
+
return std::string(operation) + " failed for '" + path +
|
|
31
|
+
"': " + std::string(strerror(error)) + " (" + std::to_string(error) +
|
|
32
|
+
")";
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// For operations without a path context
|
|
36
|
+
inline std::string CreateDetailedErrorMessage(const char *operation,
|
|
37
|
+
int error) {
|
|
38
|
+
return std::string(operation) + " failed: " + std::string(strerror(error)) +
|
|
39
|
+
" (" + std::to_string(error) + ")";
|
|
40
|
+
}
|
|
41
|
+
|
|
19
42
|
} // namespace FSMeta
|
|
@@ -20,7 +20,10 @@ protected:
|
|
|
20
20
|
deferred_.Reject(error.Value());
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
void OnOK() override {
|
|
23
|
+
void OnOK() override {
|
|
24
|
+
Napi::HandleScope scope(Env());
|
|
25
|
+
deferred_.Resolve(metadata.ToObject(Env()));
|
|
26
|
+
}
|
|
24
27
|
}; // class MetadataWorkerBase
|
|
25
28
|
|
|
26
29
|
} // namespace FSMeta
|
package/src/darwin/hidden.cpp
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// src/darwin/hidden.cpp
|
|
2
2
|
#include "hidden.h"
|
|
3
3
|
#include "../common/debug_log.h"
|
|
4
|
+
#include "../common/error_utils.h"
|
|
4
5
|
#include <sys/stat.h>
|
|
5
6
|
#include <unistd.h>
|
|
6
7
|
|
|
@@ -27,11 +28,11 @@ void GetHiddenWorker::Execute() {
|
|
|
27
28
|
int error = errno;
|
|
28
29
|
if (error == ENOENT) {
|
|
29
30
|
DEBUG_LOG("[GetHiddenWorker] path not found: %s", path_.c_str());
|
|
30
|
-
SetError("Path not found");
|
|
31
|
+
SetError("Path not found: '" + path_ + "'");
|
|
31
32
|
} else {
|
|
32
33
|
DEBUG_LOG("[GetHiddenWorker] failed to stat path %s: %s (%d)",
|
|
33
34
|
path_.c_str(), strerror(error), error);
|
|
34
|
-
SetError(
|
|
35
|
+
SetError(CreatePathErrorMessage("stat", path_, error));
|
|
35
36
|
}
|
|
36
37
|
return;
|
|
37
38
|
}
|
|
@@ -41,11 +42,13 @@ void GetHiddenWorker::Execute() {
|
|
|
41
42
|
}
|
|
42
43
|
|
|
43
44
|
void GetHiddenWorker::OnOK() {
|
|
45
|
+
Napi::HandleScope scope(Env());
|
|
44
46
|
auto env = Env();
|
|
45
47
|
deferred_.Resolve(Napi::Boolean::New(env, is_hidden_));
|
|
46
48
|
}
|
|
47
49
|
|
|
48
50
|
void GetHiddenWorker::OnError(const Napi::Error &error) {
|
|
51
|
+
Napi::HandleScope scope(Env());
|
|
49
52
|
deferred_.Reject(error.Value());
|
|
50
53
|
}
|
|
51
54
|
|
|
@@ -92,11 +95,11 @@ void SetHiddenWorker::Execute() {
|
|
|
92
95
|
int error = errno;
|
|
93
96
|
if (error == ENOENT) {
|
|
94
97
|
DEBUG_LOG("[SetHiddenWorker] path not found: %s", path_.c_str());
|
|
95
|
-
SetError("Path not found");
|
|
98
|
+
SetError("Path not found: '" + path_ + "'");
|
|
96
99
|
} else {
|
|
97
100
|
DEBUG_LOG("[SetHiddenWorker] failed to stat path %s: %s (%d)",
|
|
98
101
|
path_.c_str(), strerror(error), error);
|
|
99
|
-
SetError(
|
|
102
|
+
SetError(CreatePathErrorMessage("stat", path_, error));
|
|
100
103
|
}
|
|
101
104
|
return;
|
|
102
105
|
}
|
|
@@ -109,8 +112,10 @@ void SetHiddenWorker::Execute() {
|
|
|
109
112
|
}
|
|
110
113
|
|
|
111
114
|
if (chflags(path_.c_str(), new_flags) != 0) {
|
|
112
|
-
|
|
113
|
-
|
|
115
|
+
int error = errno;
|
|
116
|
+
DEBUG_LOG("[SetHiddenWorker] failed to set flags for %s: %s (%d)",
|
|
117
|
+
path_.c_str(), strerror(error), error);
|
|
118
|
+
SetError(CreatePathErrorMessage("chflags", path_, error));
|
|
114
119
|
return;
|
|
115
120
|
}
|
|
116
121
|
DEBUG_LOG("[SetHiddenWorker] successfully set hidden=%d for: %s", hidden_,
|
|
@@ -118,11 +123,13 @@ void SetHiddenWorker::Execute() {
|
|
|
118
123
|
}
|
|
119
124
|
|
|
120
125
|
void SetHiddenWorker::OnOK() {
|
|
126
|
+
Napi::HandleScope scope(Env());
|
|
121
127
|
auto env = Env();
|
|
122
128
|
deferred_.Resolve(env.Undefined());
|
|
123
129
|
}
|
|
124
130
|
|
|
125
131
|
void SetHiddenWorker::OnError(const Napi::Error &error) {
|
|
132
|
+
Napi::HandleScope scope(Env());
|
|
126
133
|
deferred_.Reject(error.Value());
|
|
127
134
|
}
|
|
128
135
|
|
|
@@ -70,14 +70,14 @@ private:
|
|
|
70
70
|
if (statvfs(mountPoint.c_str(), &vfs) != 0) {
|
|
71
71
|
DEBUG_LOG("[GetVolumeMetadataWorker] statvfs failed: %s (%d)",
|
|
72
72
|
strerror(errno), errno);
|
|
73
|
-
SetError(
|
|
73
|
+
SetError(CreatePathErrorMessage("statvfs", mountPoint, errno));
|
|
74
74
|
return false;
|
|
75
75
|
}
|
|
76
76
|
|
|
77
77
|
if (statfs(mountPoint.c_str(), &fs) != 0) {
|
|
78
78
|
DEBUG_LOG("[GetVolumeMetadataWorker] statfs failed: %s (%d)",
|
|
79
79
|
strerror(errno), errno);
|
|
80
|
-
SetError(
|
|
80
|
+
SetError(CreatePathErrorMessage("statfs", mountPoint, errno));
|
|
81
81
|
return false;
|
|
82
82
|
}
|
|
83
83
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// src/darwin/volume_mount_points.cpp
|
|
2
2
|
#include "../common/volume_mount_points.h"
|
|
3
3
|
#include "../common/debug_log.h"
|
|
4
|
+
#include "../common/error_utils.h"
|
|
4
5
|
#include "./fs_meta.h"
|
|
5
6
|
#include "./raii_utils.h"
|
|
6
7
|
#include <chrono>
|
|
@@ -33,7 +34,13 @@ public:
|
|
|
33
34
|
int count = getmntinfo_r_np(mntbuf.ptr(), MNT_NOWAIT);
|
|
34
35
|
|
|
35
36
|
if (count <= 0) {
|
|
36
|
-
|
|
37
|
+
if (count == 0) {
|
|
38
|
+
throw std::runtime_error("No mount points found");
|
|
39
|
+
} else {
|
|
40
|
+
// getmntinfo_r_np returns -1 on error and sets errno
|
|
41
|
+
throw FSException(
|
|
42
|
+
CreateDetailedErrorMessage("getmntinfo_r_np", errno));
|
|
43
|
+
}
|
|
37
44
|
}
|
|
38
45
|
|
|
39
46
|
for (int i = 0; i < count; i++) {
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
#include "blkid_cache.h"
|
|
4
4
|
#include "../common/debug_log.h"
|
|
5
|
+
#include "../common/error_utils.h"
|
|
5
6
|
#include <stdexcept>
|
|
6
7
|
|
|
7
8
|
namespace FSMeta {
|
|
@@ -14,8 +15,13 @@ BlkidCache::BlkidCache() : cache_(nullptr) {
|
|
|
14
15
|
const std::lock_guard<std::mutex> lock(mutex_);
|
|
15
16
|
DEBUG_LOG("[BlkidCache] initializing cache");
|
|
16
17
|
if (blkid_get_cache(&cache_, nullptr) != 0) {
|
|
17
|
-
|
|
18
|
-
|
|
18
|
+
int error = errno;
|
|
19
|
+
DEBUG_LOG("[BlkidCache] failed to initialize cache: errno=%d", error);
|
|
20
|
+
if (error != 0) {
|
|
21
|
+
throw FSException(CreateDetailedErrorMessage("blkid_get_cache", error));
|
|
22
|
+
} else {
|
|
23
|
+
throw FSException("Failed to initialize blkid cache (no errno set)");
|
|
24
|
+
}
|
|
19
25
|
}
|
|
20
26
|
DEBUG_LOG("[BlkidCache] cache initialized successfully");
|
|
21
27
|
}
|
|
@@ -31,7 +31,7 @@ public:
|
|
|
31
31
|
mountPoint.c_str());
|
|
32
32
|
struct statvfs vfs;
|
|
33
33
|
if (statvfs(mountPoint.c_str(), &vfs) != 0) {
|
|
34
|
-
throw FSException(
|
|
34
|
+
throw FSException(CreatePathErrorMessage("statvfs", mountPoint, errno));
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
const uint64_t blockSize = vfs.f_frsize ? vfs.f_frsize : vfs.f_bsize;
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
// Worker thread helper for tests
|
|
2
|
+
// This file is CommonJS format for compatibility with worker threads
|
|
3
|
+
/* eslint-disable @typescript-eslint/no-require-imports */
|
|
4
|
+
/* eslint-disable no-undef */
|
|
5
|
+
|
|
6
|
+
const { parentPort, workerData } = require('node:worker_threads');
|
|
7
|
+
const path = require('node:path');
|
|
8
|
+
|
|
9
|
+
// Use eval with the worker data to create the module functions
|
|
10
|
+
const createModule = () => {
|
|
11
|
+
const nodeGypBuild = require('node-gyp-build');
|
|
12
|
+
const binding = nodeGypBuild(path.join(__dirname, '../..'));
|
|
13
|
+
|
|
14
|
+
// Platform detection
|
|
15
|
+
const platform = process.platform;
|
|
16
|
+
const isLinux = platform === 'linux';
|
|
17
|
+
|
|
18
|
+
// For Linux, we need a simplified implementation since the main one is complex
|
|
19
|
+
if (isLinux) {
|
|
20
|
+
return {
|
|
21
|
+
getVolumeMountPoints: async () => {
|
|
22
|
+
// Return the same mount points as the main thread would
|
|
23
|
+
// This is a simplified version for testing
|
|
24
|
+
return [
|
|
25
|
+
{ mountPoint: '/', fstype: 'ext4', isSystemVolume: true },
|
|
26
|
+
{ mountPoint: '/boot', fstype: 'ext4', isSystemVolume: true },
|
|
27
|
+
{ mountPoint: '/boot/efi', fstype: 'vfat', isSystemVolume: true },
|
|
28
|
+
{ mountPoint: '/home', fstype: 'ext4', isSystemVolume: false }
|
|
29
|
+
];
|
|
30
|
+
},
|
|
31
|
+
getVolumeMetadata: async (mountPoint, options) => {
|
|
32
|
+
return binding.getVolumeMetadata({ mountPoint, ...options });
|
|
33
|
+
},
|
|
34
|
+
isHidden: async (filePath) => {
|
|
35
|
+
const basename = path.basename(filePath);
|
|
36
|
+
return basename.startsWith('.');
|
|
37
|
+
},
|
|
38
|
+
setHidden: async (/* filePath, hidden */) => {
|
|
39
|
+
// Linux doesn't support hidden attribute
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// For Windows and macOS, use the native binding directly
|
|
46
|
+
return {
|
|
47
|
+
getVolumeMountPoints: async () => {
|
|
48
|
+
return binding.getVolumeMountPoints();
|
|
49
|
+
},
|
|
50
|
+
getVolumeMetadata: async (mountPoint, options) => {
|
|
51
|
+
return binding.getVolumeMetadata({ mountPoint, ...options });
|
|
52
|
+
},
|
|
53
|
+
isHidden: binding.isHidden,
|
|
54
|
+
setHidden: binding.setHidden
|
|
55
|
+
};
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const fsMetadata = createModule();
|
|
59
|
+
|
|
60
|
+
async function runWorkerTask() {
|
|
61
|
+
try {
|
|
62
|
+
const { task, ...params } = workerData;
|
|
63
|
+
let result;
|
|
64
|
+
|
|
65
|
+
switch (task) {
|
|
66
|
+
case 'getVolumeMountPoints':
|
|
67
|
+
result = await fsMetadata.getVolumeMountPoints();
|
|
68
|
+
break;
|
|
69
|
+
case 'getVolumeMetadata':
|
|
70
|
+
result = await fsMetadata.getVolumeMetadata(params.mountPoint, params.options);
|
|
71
|
+
break;
|
|
72
|
+
case 'isHidden':
|
|
73
|
+
result = await fsMetadata.isHidden(params.path);
|
|
74
|
+
break;
|
|
75
|
+
case 'setHidden':
|
|
76
|
+
result = await fsMetadata.setHidden(params.path, params.hidden);
|
|
77
|
+
break;
|
|
78
|
+
default:
|
|
79
|
+
throw new Error('Unknown task: ' + task);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
parentPort.postMessage({ success: true, result });
|
|
83
|
+
} catch (error) {
|
|
84
|
+
parentPort.postMessage({ success: false, error: error.message });
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
runWorkerTask();
|
package/src/windows/hidden.cpp
CHANGED
|
@@ -66,7 +66,7 @@ class GetHiddenWorker : public Napi::AsyncWorker {
|
|
|
66
66
|
|
|
67
67
|
public:
|
|
68
68
|
GetHiddenWorker(Napi::Env env, std::string p, Napi::Promise::Deferred def)
|
|
69
|
-
: Napi::AsyncWorker(env), path(std::move(p)), deferred(
|
|
69
|
+
: Napi::AsyncWorker(env), path(std::move(p)), deferred(def) {}
|
|
70
70
|
|
|
71
71
|
void Execute() override {
|
|
72
72
|
try {
|
|
@@ -86,9 +86,15 @@ public:
|
|
|
86
86
|
}
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
-
void OnOK() override {
|
|
89
|
+
void OnOK() override {
|
|
90
|
+
Napi::HandleScope scope(Env());
|
|
91
|
+
deferred.Resolve(Napi::Boolean::New(Env(), result));
|
|
92
|
+
}
|
|
90
93
|
|
|
91
|
-
void OnError(const Napi::Error &e) override {
|
|
94
|
+
void OnError(const Napi::Error &e) override {
|
|
95
|
+
Napi::HandleScope scope(Env());
|
|
96
|
+
deferred.Reject(e.Value());
|
|
97
|
+
}
|
|
92
98
|
};
|
|
93
99
|
|
|
94
100
|
class SetHiddenWorker : public Napi::AsyncWorker {
|
|
@@ -99,8 +105,7 @@ class SetHiddenWorker : public Napi::AsyncWorker {
|
|
|
99
105
|
public:
|
|
100
106
|
SetHiddenWorker(Napi::Env env, std::string p, bool v,
|
|
101
107
|
Napi::Promise::Deferred def)
|
|
102
|
-
: Napi::AsyncWorker(env), path(std::move(p)), value(v),
|
|
103
|
-
deferred(std::move(def)) {}
|
|
108
|
+
: Napi::AsyncWorker(env), path(std::move(p)), value(v), deferred(def) {}
|
|
104
109
|
|
|
105
110
|
void Execute() override {
|
|
106
111
|
try {
|
|
@@ -120,9 +125,15 @@ public:
|
|
|
120
125
|
}
|
|
121
126
|
}
|
|
122
127
|
|
|
123
|
-
void OnOK() override {
|
|
128
|
+
void OnOK() override {
|
|
129
|
+
Napi::HandleScope scope(Env());
|
|
130
|
+
deferred.Resolve(Napi::Boolean::New(Env(), true));
|
|
131
|
+
}
|
|
124
132
|
|
|
125
|
-
void OnError(const Napi::Error &e) override {
|
|
133
|
+
void OnError(const Napi::Error &e) override {
|
|
134
|
+
Napi::HandleScope scope(Env());
|
|
135
|
+
deferred.Reject(e.Value());
|
|
136
|
+
}
|
|
126
137
|
};
|
|
127
138
|
|
|
128
139
|
Napi::Promise GetHiddenAttribute(const Napi::CallbackInfo &info) {
|
|
@@ -135,8 +146,7 @@ Napi::Promise GetHiddenAttribute(const Napi::CallbackInfo &info) {
|
|
|
135
146
|
}
|
|
136
147
|
|
|
137
148
|
std::string path = info[0].As<Napi::String>();
|
|
138
|
-
auto *worker =
|
|
139
|
-
new GetHiddenWorker(env, std::move(path), std::move(deferred));
|
|
149
|
+
auto *worker = new GetHiddenWorker(env, std::move(path), deferred);
|
|
140
150
|
worker->Queue();
|
|
141
151
|
|
|
142
152
|
return deferred.Promise();
|
|
@@ -158,8 +168,7 @@ Napi::Promise SetHiddenAttribute(const Napi::CallbackInfo &info) {
|
|
|
158
168
|
std::string path = info[0].As<Napi::String>();
|
|
159
169
|
bool value = info[1].As<Napi::Boolean>();
|
|
160
170
|
|
|
161
|
-
auto *worker =
|
|
162
|
-
new SetHiddenWorker(env, std::move(path), value, std::move(deferred));
|
|
171
|
+
auto *worker = new SetHiddenWorker(env, std::move(path), value, deferred);
|
|
163
172
|
worker->Queue();
|
|
164
173
|
|
|
165
174
|
return deferred.Promise();
|