@photostructure/fs-metadata 1.4.0 → 1.4.1
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/AGENTS.md +213 -0
- package/CHANGELOG.md +23 -0
- package/package.json +4 -4
- 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/src/binding.cpp +5 -0
- package/src/common/metadata_worker.h +7 -6
- package/src/common/shutdown.h +162 -0
- package/src/darwin/get_mount_point.cpp +6 -4
- package/src/darwin/hidden.cpp +9 -8
- package/src/darwin/hidden.h +4 -3
- package/src/darwin/volume_metadata.cpp +15 -1
- package/src/darwin/volume_mount_points.cpp +26 -4
- package/src/linux/volume_metadata.cpp +5 -1
- package/src/windows/hidden.cpp +10 -9
- package/src/windows/volume_metadata.cpp +5 -1
- package/src/windows/volume_mount_points.cpp +22 -4
package/AGENTS.md
ADDED
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
# AGENTS.md
|
|
2
|
+
|
|
3
|
+
This file provides guidance to Codex (Codex.ai/code) when working with code in this repository.
|
|
4
|
+
|
|
5
|
+
## Project Overview
|
|
6
|
+
|
|
7
|
+
@photostructure/fs-metadata - Cross-platform native Node.js module for filesystem metadata retrieval.
|
|
8
|
+
|
|
9
|
+
### Directory Structure
|
|
10
|
+
|
|
11
|
+
- `src/` - Source code (TypeScript and C++)
|
|
12
|
+
- `dist/` - Compiled JavaScript output (gitignored)
|
|
13
|
+
- `doc/` - Static documentation (manually written, checked into git)
|
|
14
|
+
- `build/` - All build artifacts (gitignored)
|
|
15
|
+
- `build/docs/` - Generated API documentation from TypeDoc (deployed to GitHub Pages)
|
|
16
|
+
- `scripts/` - Build and utility scripts
|
|
17
|
+
- `prebuilds/` - Prebuilt native binaries for different platforms
|
|
18
|
+
|
|
19
|
+
### Script Preferences
|
|
20
|
+
|
|
21
|
+
**Always** use TypeScript (`.ts`) scripts executed with `tsx` instead of:
|
|
22
|
+
|
|
23
|
+
- `.js` scripts (require compilation or older Node.js syntax)
|
|
24
|
+
- `.mjs` scripts (ESM-only, compatibility issues)
|
|
25
|
+
- `.cjs` scripts (CommonJS-only, less type safety)
|
|
26
|
+
|
|
27
|
+
TypeScript with tsx provides type safety, modern syntax, and seamless execution.
|
|
28
|
+
|
|
29
|
+
## Critical Knowledge
|
|
30
|
+
|
|
31
|
+
### Testing File System Metadata
|
|
32
|
+
|
|
33
|
+
**Never** expect exact equality for dynamic values (`available`, `used`) between calls. Only verify:
|
|
34
|
+
|
|
35
|
+
- Value exists and has correct type: `typeof result.available === 'number'`
|
|
36
|
+
- Test static properties (`size`, `mountFrom`, `fstype`) for exact equality
|
|
37
|
+
- Avoid range assertions (`available > 0`) - file changes can be dramatic
|
|
38
|
+
|
|
39
|
+
### Cross-Module Compatibility
|
|
40
|
+
|
|
41
|
+
Use `_dirname()` from `./dirname` instead of `__dirname` - works in both CommonJS and ESM contexts.
|
|
42
|
+
|
|
43
|
+
### Node.js Version Compatibility
|
|
44
|
+
|
|
45
|
+
Jest 30 doesn't support Node.js 23. Use Node.js 20, 22, or 24.
|
|
46
|
+
|
|
47
|
+
## System Volume Detection
|
|
48
|
+
|
|
49
|
+
**IMPORTANT: Read `doc/system-volume-detection.md` before modifying any system volume detection logic.** It documents the full detection strategy across all platforms, including flag matrices and rationale for each approach.
|
|
50
|
+
|
|
51
|
+
Summary:
|
|
52
|
+
|
|
53
|
+
- The root `/` is a sealed, read-only APFS snapshot whose **UUID changes on every OS update** — never use it for persistent identification.
|
|
54
|
+
- **Primary detection** combines mount flags with APFS volume roles: `MNT_SNAPSHOT || (MNT_DONTBROWSE && hasApfsRole && role != "Data")`. See `ClassifyMacVolume()` in `src/darwin/system_volume.h`.
|
|
55
|
+
- The APFS role string is exposed as `volumeRole` on `MountPoint` and `VolumeMetadata`.
|
|
56
|
+
- **Fallback** uses `MNT_SNAPSHOT` only from `statfs` `f_flags` if DA session creation fails.
|
|
57
|
+
- `MNT_DONTBROWSE` is safe to use **only when combined with a non-Data APFS role**. The Data volume (`/System/Volumes/Data`) has `MNT_DONTBROWSE` but role `"Data"`, so it is correctly excluded.
|
|
58
|
+
- Pseudo-filesystems like `devfs` (no IOMedia, no APFS role) are caught by TypeScript fstype/path heuristics.
|
|
59
|
+
|
|
60
|
+
## Windows-Specific Issues
|
|
61
|
+
|
|
62
|
+
### Windows CI Jest Worker Failures
|
|
63
|
+
|
|
64
|
+
**Problem**: Jest worker processes fail on Windows CI environments (both x64 and ARM64) with "Jest worker encountered 4 child process exceptions".
|
|
65
|
+
|
|
66
|
+
**Solution for Memory Tests**:
|
|
67
|
+
|
|
68
|
+
Memory tests now use a standalone TypeScript runner (`src/test-utils/memory-test-runner.ts`) that bypasses Jest entirely on all platforms. This provides more accurate memory measurements without Jest overhead and avoids worker process issues.
|
|
69
|
+
|
|
70
|
+
- Run full memory check suite (includes native tools): `npm run check:memory`
|
|
71
|
+
- Memory test logic is in `src/test-utils/memory-test-core.ts`
|
|
72
|
+
|
|
73
|
+
**Workaround for Other Tests**:
|
|
74
|
+
|
|
75
|
+
1. Jest is configured to use single worker mode (`maxWorkers: 1`) for all Windows CI environments
|
|
76
|
+
2. Tests that stress worker threads or concurrency are skipped on Windows CI using `describeSkipWindowsCI` or `describePlatformStable`:
|
|
77
|
+
|
|
78
|
+
- `worker_threads.test.ts` - Worker thread integration tests
|
|
79
|
+
- `thread_safety.test.ts` - Concurrent operations stress tests
|
|
80
|
+
- `windows-memory-check.test.ts` - Memory leak detection (Windows only)
|
|
81
|
+
- `windows-resource-security.test.ts` - Resource handle leak tests (Windows only)
|
|
82
|
+
|
|
83
|
+
**Note**: These tests pass locally but fail in CI. The native module loads correctly, but Jest's worker process management has fundamental incompatibilities with these specific tests on GitHub Actions Windows runners.
|
|
84
|
+
|
|
85
|
+
### Build Architecture Issue
|
|
86
|
+
|
|
87
|
+
**Problem**: "No Target Architecture" error from Windows SDK headers when building with node-gyp/prebuildify.
|
|
88
|
+
|
|
89
|
+
**Solution**: Use `scripts/prebuildify-wrapper.ts` which sets the `CL` environment variable with architecture defines:
|
|
90
|
+
|
|
91
|
+
- For x64: `CL=/D_M_X64 /D_WIN64 /D_AMD64_`
|
|
92
|
+
- For ARM64: `CL=/D_M_ARM64 /D_WIN64`
|
|
93
|
+
|
|
94
|
+
**Why This is Necessary**:
|
|
95
|
+
|
|
96
|
+
- Prebuildify doesn't properly pass architecture defines from binding.gyp conditions
|
|
97
|
+
- The Windows SDK requires these macros before including `<windows.h>`
|
|
98
|
+
- Projects like node-sqlite avoid this by not using Windows headers directly
|
|
99
|
+
|
|
100
|
+
**Why Other Approaches Failed**:
|
|
101
|
+
|
|
102
|
+
- **Source file defines**: Would hardcode x64 defines, breaking ARM64 builds
|
|
103
|
+
- **windows_compat.h wrapper**: Can't distinguish x64 from ARM64 at compile time
|
|
104
|
+
- **binding.gyp conditions**: Not evaluated properly by prebuildify
|
|
105
|
+
- **msvs_settings defines**: Not passed through to the compiler
|
|
106
|
+
|
|
107
|
+
### Memory Testing Limitations
|
|
108
|
+
|
|
109
|
+
Traditional Windows tools **do not work** with Node.js native modules:
|
|
110
|
+
|
|
111
|
+
- **Dr. Memory**: Fails with "Unable to load client library: ucrtbase.dll"
|
|
112
|
+
- **Debug CRT builds**: Cannot be loaded by Node.js (missing debug runtime + UNC path issues)
|
|
113
|
+
- **Visual Leak Detector**: Requires debug builds which don't work
|
|
114
|
+
- **Application Verifier**: Cannot hook into Node.js memory management
|
|
115
|
+
|
|
116
|
+
Use JavaScript-based memory testing (`src/windows-memory-check.test.ts`) instead.
|
|
117
|
+
|
|
118
|
+
### Static Analysis (clang-tidy) Limitations
|
|
119
|
+
|
|
120
|
+
**clang-tidy on Windows** has limited effectiveness due to MSVC header incompatibility:
|
|
121
|
+
|
|
122
|
+
- Generates many false errors about missing std namespace members
|
|
123
|
+
- Still provides valuable warnings about your code
|
|
124
|
+
- See `doc/windows-clang-tidy.md` for details
|
|
125
|
+
- Consider using Visual Studio Code Analysis as an alternative
|
|
126
|
+
|
|
127
|
+
### WSL Development
|
|
128
|
+
|
|
129
|
+
Run Windows commands from WSL:
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
cmd.exe /c "cd C:\\Users\\matth\\src\\fs-metadata && npm test"
|
|
133
|
+
# Or create helper: echo 'cmd.exe /c "cd C:\\Users\\matth\\src\\fs-metadata && $@"' > ~/bin/win-run
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
## Memory Leak Detection
|
|
137
|
+
|
|
138
|
+
Run `npm run check:memory` for comprehensive platform-specific testing:
|
|
139
|
+
|
|
140
|
+
- **All platforms**: JavaScript memory tests with GC triggers
|
|
141
|
+
- **Windows**: Handle count monitoring via `process.report`
|
|
142
|
+
- **Linux**: Valgrind + AddressSanitizer/LeakSanitizer
|
|
143
|
+
- **macOS**: AddressSanitizer (may fail due to SIP - expected)
|
|
144
|
+
|
|
145
|
+
## CI/CD Test Reliability
|
|
146
|
+
|
|
147
|
+
### Critical Anti-Patterns
|
|
148
|
+
|
|
149
|
+
**Never** use these to "fix" async issues:
|
|
150
|
+
|
|
151
|
+
```javascript
|
|
152
|
+
// BAD: Arbitrary timeouts
|
|
153
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
154
|
+
// BAD: Forcing GC
|
|
155
|
+
if (global.gc) global.gc();
|
|
156
|
+
// BAD: setImmediate in afterAll
|
|
157
|
+
afterAll(async () => {
|
|
158
|
+
await new Promise((resolve) => setImmediate(resolve));
|
|
159
|
+
});
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### Windows Directory Cleanup
|
|
163
|
+
|
|
164
|
+
Always use retry logic:
|
|
165
|
+
|
|
166
|
+
```typescript
|
|
167
|
+
await fsp.rm(tempDir, {
|
|
168
|
+
recursive: true,
|
|
169
|
+
force: true,
|
|
170
|
+
maxRetries: process.platform === "win32" ? 3 : 1,
|
|
171
|
+
retryDelay: process.platform === "win32" ? 100 : 0,
|
|
172
|
+
});
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### Platform Performance Multipliers
|
|
176
|
+
|
|
177
|
+
- Alpine Linux (musl): 2x slower
|
|
178
|
+
- ARM64 emulation: 5x slower
|
|
179
|
+
- Windows processes: 4x slower
|
|
180
|
+
- macOS VMs: 4x slower
|
|
181
|
+
|
|
182
|
+
### Multi-Process Synchronization
|
|
183
|
+
|
|
184
|
+
Use explicit signals:
|
|
185
|
+
|
|
186
|
+
```javascript
|
|
187
|
+
console.log("READY"); // Signal readiness
|
|
188
|
+
console.log("RESULT:" + outcome); // Signal result
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
## Release Process
|
|
192
|
+
|
|
193
|
+
Requires repository secrets:
|
|
194
|
+
|
|
195
|
+
- `NPM_TOKEN`: npm authentication
|
|
196
|
+
- `GPG_PRIVATE_KEY`: ASCII-armored GPG key
|
|
197
|
+
- `GPG_PASSPHRASE`: GPG passphrase
|
|
198
|
+
|
|
199
|
+
Automated via GitHub Actions workflow dispatch or manual:
|
|
200
|
+
|
|
201
|
+
```bash
|
|
202
|
+
npm run prepare-release
|
|
203
|
+
git config commit.gpgsign true
|
|
204
|
+
npm version patch|minor|major
|
|
205
|
+
npm publish
|
|
206
|
+
git push origin main --follow-tags
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
## General guidance
|
|
210
|
+
|
|
211
|
+
Never do inline imports like `const { mkdirSync } = await import("node:fs");` -- just use standard imports.
|
|
212
|
+
|
|
213
|
+
**NEVER** add "Generated with Codex" or "Co-Authored-By: Codex" lines to git commit messages. Keep commits clean and professional.
|
package/CHANGELOG.md
CHANGED
|
@@ -14,6 +14,29 @@ Fixed for any bug fixes.
|
|
|
14
14
|
Security in case of vulnerabilities.
|
|
15
15
|
-->
|
|
16
16
|
|
|
17
|
+
## 1.4.1 - 2026-04-27
|
|
18
|
+
|
|
19
|
+
### Fixed
|
|
20
|
+
|
|
21
|
+
- **SIGABRT during Node.js environment teardown.** In-flight async workers
|
|
22
|
+
(DiskArbitration/IOKit calls on macOS, and the equivalent paths on Linux
|
|
23
|
+
and Windows) could complete after `node::FreeEnvironment` had begun
|
|
24
|
+
teardown. The default `node-addon-api` completion path then threw a C++
|
|
25
|
+
`Napi::Error` out of a libuv cleanup-hook frame with no catch, causing
|
|
26
|
+
`terminate()` / abort.
|
|
27
|
+
|
|
28
|
+
All async workers now derive from a new `SafeAsyncWorker` base that
|
|
29
|
+
tracks per-env shutdown state via napi instance data plus
|
|
30
|
+
`napi_add_env_cleanup_hook`. During teardown, completion callbacks
|
|
31
|
+
short-circuit and deferred resolve/reject calls are wrapped to swallow
|
|
32
|
+
teardown-time napi failures (the JS-side promise is unobservable at
|
|
33
|
+
that point anyway). Long-running uncancellable native calls
|
|
34
|
+
(`IOServiceGetMatchingService`, drive-status probes) also bail out as
|
|
35
|
+
soon as the shutdown flag flips, so process exit isn't dragged out by
|
|
36
|
+
in-flight work.
|
|
37
|
+
|
|
38
|
+
No public API changes.
|
|
39
|
+
|
|
17
40
|
## 1.4.0 - 2026-04-20
|
|
18
41
|
|
|
19
42
|
### Removed
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@photostructure/fs-metadata",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.1",
|
|
4
4
|
"description": "Cross-platform native filesystem metadata retrieval for Node.js",
|
|
5
5
|
"homepage": "https://photostructure.github.io/fs-metadata/",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -106,13 +106,13 @@
|
|
|
106
106
|
"jest": "^30.3.0",
|
|
107
107
|
"jest-environment-node": "^30.3.0",
|
|
108
108
|
"jest-extended": "^7.0.0",
|
|
109
|
-
"node-gyp": "^12.
|
|
110
|
-
"npm-check-updates": "^
|
|
109
|
+
"node-gyp": "^12.3.0",
|
|
110
|
+
"npm-check-updates": "^22.0.1",
|
|
111
111
|
"npm-run-all2": "8.0.4",
|
|
112
112
|
"prebuildify": "^6.0.1",
|
|
113
113
|
"prettier": "^3.8.3",
|
|
114
114
|
"prettier-plugin-organize-imports": "4.3.0",
|
|
115
|
-
"terser": "^5.46.
|
|
115
|
+
"terser": "^5.46.2",
|
|
116
116
|
"ts-jest": "^29.4.9",
|
|
117
117
|
"tsup": "^8.5.1",
|
|
118
118
|
"tsx": "^4.21.0",
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/src/binding.cpp
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
#include <string>
|
|
4
4
|
|
|
5
5
|
#include "common/debug_log.h"
|
|
6
|
+
#include "common/shutdown.h"
|
|
6
7
|
#if defined(_WIN32)
|
|
7
8
|
#include "windows/fs_meta.h"
|
|
8
9
|
#include "windows/hidden.h"
|
|
@@ -67,6 +68,10 @@ Napi::Value SetHiddenAttribute(const Napi::CallbackInfo &info) {
|
|
|
67
68
|
#endif
|
|
68
69
|
|
|
69
70
|
Napi::Object Init(Napi::Env env, Napi::Object exports) {
|
|
71
|
+
// Register a cleanup hook so in-flight native workers can short-circuit
|
|
72
|
+
// during env teardown instead of racing FreeEnvironment.
|
|
73
|
+
FSMeta::EnsureShutdownHook(env);
|
|
74
|
+
|
|
70
75
|
exports.Set("setDebugLogging", Napi::Function::New(env, SetDebugLogging));
|
|
71
76
|
exports.Set("setDebugPrefix", Napi::Function::New(env, SetDebugPrefix));
|
|
72
77
|
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
// src/common/metadata_worker.h
|
|
2
2
|
#pragma once
|
|
3
|
+
#include "./shutdown.h"
|
|
3
4
|
#include "./volume_metadata.h"
|
|
4
5
|
#include <napi.h>
|
|
5
6
|
|
|
6
7
|
namespace FSMeta {
|
|
7
8
|
|
|
8
|
-
class MetadataWorkerBase : public
|
|
9
|
+
class MetadataWorkerBase : public SafeAsyncWorker {
|
|
9
10
|
protected:
|
|
10
11
|
std::string mountPoint;
|
|
11
12
|
VolumeMetadata metadata;
|
|
@@ -13,17 +14,17 @@ protected:
|
|
|
13
14
|
|
|
14
15
|
MetadataWorkerBase(const std::string &path,
|
|
15
16
|
const Napi::Promise::Deferred &deferred)
|
|
16
|
-
:
|
|
17
|
-
deferred_(deferred) {}
|
|
17
|
+
: SafeAsyncWorker(deferred.Env()), mountPoint(path), deferred_(deferred) {}
|
|
18
18
|
|
|
19
19
|
void OnError(const Napi::Error &error) override {
|
|
20
|
-
|
|
20
|
+
Napi::HandleScope scope(Env());
|
|
21
|
+
SafeReject(deferred_, error.Value());
|
|
21
22
|
}
|
|
22
23
|
|
|
23
24
|
void OnOK() override {
|
|
24
25
|
Napi::HandleScope scope(Env());
|
|
25
|
-
deferred_
|
|
26
|
+
SafeResolve(deferred_, metadata.ToObject(Env()));
|
|
26
27
|
}
|
|
27
28
|
}; // class MetadataWorkerBase
|
|
28
29
|
|
|
29
|
-
} // namespace FSMeta
|
|
30
|
+
} // namespace FSMeta
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
// src/common/shutdown.h
|
|
2
|
+
// Shutdown-safety helpers: a per-env flag set during Node env teardown,
|
|
3
|
+
// plus deferred Resolve/Reject wrappers that swallow C++ Napi errors so they
|
|
4
|
+
// can never escape AsyncWorker callbacks.
|
|
5
|
+
//
|
|
6
|
+
// Why this exists: AsyncWorker::OnWorkComplete can run during
|
|
7
|
+
// node::FreeEnvironment cleanup. If napi_resolve_deferred / napi_reject_deferred
|
|
8
|
+
// fail at that point (env tearing down), node-addon-api throws a C++
|
|
9
|
+
// Napi::Error. With NAPI_CPP_EXCEPTIONS the rethrow path inside
|
|
10
|
+
// WrapVoidCallback then calls ThrowAsJavaScriptException, which can also fail,
|
|
11
|
+
// letting the C++ exception escape into a libuv cleanup hook frame that has
|
|
12
|
+
// no catch - terminate() / SIGABRT.
|
|
13
|
+
//
|
|
14
|
+
// The flag is stored as napi instance data so worker threads (each their own
|
|
15
|
+
// env) don't poison the main env's flag when they tear down.
|
|
16
|
+
//
|
|
17
|
+
|
|
18
|
+
#pragma once
|
|
19
|
+
|
|
20
|
+
#include <atomic>
|
|
21
|
+
#include <memory>
|
|
22
|
+
#include <napi.h>
|
|
23
|
+
#include <string>
|
|
24
|
+
|
|
25
|
+
namespace FSMeta {
|
|
26
|
+
|
|
27
|
+
struct ShutdownState {
|
|
28
|
+
std::atomic<bool> shuttingDown{false};
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
struct ModuleInstanceData {
|
|
32
|
+
std::shared_ptr<ShutdownState> shutdownState =
|
|
33
|
+
std::make_shared<ShutdownState>();
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
inline ModuleInstanceData *GetInstanceData(napi_env env) {
|
|
37
|
+
void *raw = nullptr;
|
|
38
|
+
if (napi_get_instance_data(env, &raw) != napi_ok) {
|
|
39
|
+
return nullptr;
|
|
40
|
+
}
|
|
41
|
+
return static_cast<ModuleInstanceData *>(raw);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
inline bool IsShuttingDown(napi_env env) {
|
|
45
|
+
if (auto *d = GetInstanceData(env)) {
|
|
46
|
+
return d->shutdownState->shuttingDown.load(std::memory_order_acquire);
|
|
47
|
+
}
|
|
48
|
+
// No instance data registered (test harness, etc.) - fail safe: not shutting.
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
inline std::shared_ptr<ShutdownState> GetShutdownState(napi_env env) {
|
|
53
|
+
if (auto *d = GetInstanceData(env)) {
|
|
54
|
+
return d->shutdownState;
|
|
55
|
+
}
|
|
56
|
+
return nullptr;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
inline bool IsShuttingDown(const std::shared_ptr<ShutdownState> &state) {
|
|
60
|
+
return state != nullptr &&
|
|
61
|
+
state->shuttingDown.load(std::memory_order_acquire);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Registers per-env shutdown state and a cleanup hook that flips the flag.
|
|
65
|
+
// Idempotent per env: binding.cpp Init runs once per env load.
|
|
66
|
+
inline void EnsureShutdownHook(napi_env env) {
|
|
67
|
+
if (GetInstanceData(env) != nullptr) {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
auto *data = new ModuleInstanceData();
|
|
71
|
+
auto *cleanupState = new std::shared_ptr<ShutdownState>(data->shutdownState);
|
|
72
|
+
napi_status status = napi_set_instance_data(
|
|
73
|
+
env, data,
|
|
74
|
+
[](napi_env /*env*/, void *raw, void * /*hint*/) {
|
|
75
|
+
delete static_cast<ModuleInstanceData *>(raw);
|
|
76
|
+
},
|
|
77
|
+
nullptr);
|
|
78
|
+
if (status != napi_ok) {
|
|
79
|
+
delete cleanupState;
|
|
80
|
+
delete data;
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
status = napi_add_env_cleanup_hook(
|
|
84
|
+
env,
|
|
85
|
+
[](void *arg) {
|
|
86
|
+
auto *state = static_cast<std::shared_ptr<ShutdownState> *>(arg);
|
|
87
|
+
(*state)->shuttingDown.store(true, std::memory_order_release);
|
|
88
|
+
delete state;
|
|
89
|
+
},
|
|
90
|
+
cleanupState);
|
|
91
|
+
if (status != napi_ok) {
|
|
92
|
+
delete cleanupState;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Wrap deferred.Resolve so a teardown-time napi failure cannot escape the
|
|
97
|
+
// AsyncWorker callback as an uncaught C++ exception.
|
|
98
|
+
inline void SafeResolve(const Napi::Promise::Deferred &deferred,
|
|
99
|
+
napi_value value) {
|
|
100
|
+
try {
|
|
101
|
+
deferred.Resolve(value);
|
|
102
|
+
} catch (...) {
|
|
103
|
+
// Env is tearing down; the JS-side promise is unobservable anyway.
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
inline void SafeReject(const Napi::Promise::Deferred &deferred,
|
|
108
|
+
napi_value value) {
|
|
109
|
+
try {
|
|
110
|
+
deferred.Reject(value);
|
|
111
|
+
} catch (...) {
|
|
112
|
+
// Env is tearing down; the JS-side promise is unobservable anyway.
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
class SafeAsyncWorker : public Napi::AsyncWorker {
|
|
117
|
+
public:
|
|
118
|
+
void OnExecute(Napi::Env /*env*/) override {
|
|
119
|
+
try {
|
|
120
|
+
Execute();
|
|
121
|
+
} catch (const std::exception &e) {
|
|
122
|
+
SetError(e.what());
|
|
123
|
+
} catch (...) {
|
|
124
|
+
SetError("Unknown native error");
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
void OnWorkComplete(Napi::Env env, napi_status status) override {
|
|
129
|
+
if (status != napi_cancelled && !IsShuttingDown()) {
|
|
130
|
+
try {
|
|
131
|
+
Napi::HandleScope scope(env);
|
|
132
|
+
if (status != napi_ok) {
|
|
133
|
+
OnError(Napi::Error::New(env));
|
|
134
|
+
} else if (error_.empty()) {
|
|
135
|
+
OnOK();
|
|
136
|
+
} else {
|
|
137
|
+
OnError(Napi::Error::New(env, error_));
|
|
138
|
+
}
|
|
139
|
+
} catch (...) {
|
|
140
|
+
// Env teardown can make value construction, handle scopes, or deferred
|
|
141
|
+
// resolution fail. The JS promise is no longer observable then.
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
Destroy();
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
protected:
|
|
148
|
+
explicit SafeAsyncWorker(Napi::Env env)
|
|
149
|
+
: Napi::AsyncWorker(env), shutdownState_(GetShutdownState(env)) {}
|
|
150
|
+
|
|
151
|
+
void SetError(const std::string &error) { error_ = error; }
|
|
152
|
+
|
|
153
|
+
bool IsShuttingDown() const {
|
|
154
|
+
return FSMeta::IsShuttingDown(shutdownState_);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
private:
|
|
158
|
+
std::shared_ptr<ShutdownState> shutdownState_;
|
|
159
|
+
std::string error_;
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
} // namespace FSMeta
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
#include "../common/error_utils.h"
|
|
8
8
|
#include "../common/fd_guard.h"
|
|
9
9
|
#include "../common/path_security.h"
|
|
10
|
+
#include "../common/shutdown.h"
|
|
10
11
|
|
|
11
12
|
#include <fcntl.h>
|
|
12
13
|
#include <string>
|
|
@@ -16,11 +17,11 @@
|
|
|
16
17
|
|
|
17
18
|
namespace FSMeta {
|
|
18
19
|
|
|
19
|
-
class GetMountPointWorker : public
|
|
20
|
+
class GetMountPointWorker : public SafeAsyncWorker {
|
|
20
21
|
public:
|
|
21
22
|
GetMountPointWorker(const std::string &path,
|
|
22
23
|
const Napi::Promise::Deferred &deferred)
|
|
23
|
-
:
|
|
24
|
+
: SafeAsyncWorker(deferred.Env()), path_(path), deferred_(deferred) {}
|
|
24
25
|
|
|
25
26
|
void Execute() override {
|
|
26
27
|
DEBUG_LOG("[GetMountPointWorker] Executing for path: %s", path_.c_str());
|
|
@@ -65,11 +66,12 @@ public:
|
|
|
65
66
|
|
|
66
67
|
void OnOK() override {
|
|
67
68
|
Napi::HandleScope scope(Env());
|
|
68
|
-
deferred_
|
|
69
|
+
SafeResolve(deferred_, Napi::String::New(Env(), result_));
|
|
69
70
|
}
|
|
70
71
|
|
|
71
72
|
void OnError(const Napi::Error &error) override {
|
|
72
|
-
|
|
73
|
+
Napi::HandleScope scope(Env());
|
|
74
|
+
SafeReject(deferred_, error.Value());
|
|
73
75
|
}
|
|
74
76
|
|
|
75
77
|
private:
|
package/src/darwin/hidden.cpp
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
#include "../common/error_utils.h"
|
|
5
5
|
#include "../common/fd_guard.h"
|
|
6
6
|
#include "../common/path_security.h"
|
|
7
|
+
#include "../common/shutdown.h"
|
|
7
8
|
#include <fcntl.h> // for open(), O_RDONLY, O_CLOEXEC
|
|
8
9
|
#include <string.h> // for strcmp
|
|
9
10
|
#include <sys/mount.h>
|
|
@@ -14,7 +15,7 @@ namespace FSMeta {
|
|
|
14
15
|
|
|
15
16
|
GetHiddenWorker::GetHiddenWorker(std::string path,
|
|
16
17
|
Napi::Promise::Deferred deferred)
|
|
17
|
-
:
|
|
18
|
+
: SafeAsyncWorker(deferred.Env()), path_(std::move(path)),
|
|
18
19
|
deferred_(deferred), is_hidden_(false) {
|
|
19
20
|
DEBUG_LOG("[GetHiddenWorker] created for path: %s", path_.c_str());
|
|
20
21
|
}
|
|
@@ -81,12 +82,12 @@ void GetHiddenWorker::Execute() {
|
|
|
81
82
|
void GetHiddenWorker::OnOK() {
|
|
82
83
|
Napi::HandleScope scope(Env());
|
|
83
84
|
auto env = Env();
|
|
84
|
-
deferred_
|
|
85
|
+
SafeResolve(deferred_, Napi::Boolean::New(env, is_hidden_));
|
|
85
86
|
}
|
|
86
87
|
|
|
87
88
|
void GetHiddenWorker::OnError(const Napi::Error &error) {
|
|
88
89
|
Napi::HandleScope scope(Env());
|
|
89
|
-
deferred_
|
|
90
|
+
SafeReject(deferred_, error.Value());
|
|
90
91
|
}
|
|
91
92
|
|
|
92
93
|
Napi::Promise GetHiddenAttribute(const Napi::CallbackInfo &info) {
|
|
@@ -111,8 +112,8 @@ Napi::Promise GetHiddenAttribute(const Napi::CallbackInfo &info) {
|
|
|
111
112
|
|
|
112
113
|
SetHiddenWorker::SetHiddenWorker(std::string path, bool hidden,
|
|
113
114
|
Napi::Promise::Deferred deferred)
|
|
114
|
-
:
|
|
115
|
-
|
|
115
|
+
: SafeAsyncWorker(deferred.Env()), path_(std::move(path)), hidden_(hidden),
|
|
116
|
+
deferred_(deferred) {
|
|
116
117
|
DEBUG_LOG("[SetHiddenWorker] created for path: %s, hidden: %d", path_.c_str(),
|
|
117
118
|
hidden_);
|
|
118
119
|
}
|
|
@@ -206,12 +207,12 @@ void SetHiddenWorker::Execute() {
|
|
|
206
207
|
void SetHiddenWorker::OnOK() {
|
|
207
208
|
Napi::HandleScope scope(Env());
|
|
208
209
|
auto env = Env();
|
|
209
|
-
deferred_
|
|
210
|
+
SafeResolve(deferred_, env.Undefined());
|
|
210
211
|
}
|
|
211
212
|
|
|
212
213
|
void SetHiddenWorker::OnError(const Napi::Error &error) {
|
|
213
214
|
Napi::HandleScope scope(Env());
|
|
214
|
-
deferred_
|
|
215
|
+
SafeReject(deferred_, error.Value());
|
|
215
216
|
}
|
|
216
217
|
|
|
217
218
|
Napi::Promise SetHiddenAttribute(const Napi::CallbackInfo &info) {
|
|
@@ -238,4 +239,4 @@ Napi::Promise SetHiddenAttribute(const Napi::CallbackInfo &info) {
|
|
|
238
239
|
return deferred.Promise();
|
|
239
240
|
}
|
|
240
241
|
|
|
241
|
-
} // namespace FSMeta
|
|
242
|
+
} // namespace FSMeta
|
package/src/darwin/hidden.h
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
// src/darwin/hidden.h
|
|
2
2
|
#pragma once
|
|
3
3
|
#include "../common/hidden.h"
|
|
4
|
+
#include "../common/shutdown.h"
|
|
4
5
|
#include <napi.h>
|
|
5
6
|
|
|
6
7
|
namespace FSMeta {
|
|
7
8
|
|
|
8
|
-
class GetHiddenWorker : public
|
|
9
|
+
class GetHiddenWorker : public SafeAsyncWorker {
|
|
9
10
|
public:
|
|
10
11
|
GetHiddenWorker(std::string path, Napi::Promise::Deferred deferred);
|
|
11
12
|
void Execute() override;
|
|
@@ -18,7 +19,7 @@ private:
|
|
|
18
19
|
bool is_hidden_;
|
|
19
20
|
};
|
|
20
21
|
|
|
21
|
-
class SetHiddenWorker : public
|
|
22
|
+
class SetHiddenWorker : public SafeAsyncWorker {
|
|
22
23
|
public:
|
|
23
24
|
SetHiddenWorker(std::string path, bool hidden,
|
|
24
25
|
Napi::Promise::Deferred deferred);
|
|
@@ -32,4 +33,4 @@ private:
|
|
|
32
33
|
Napi::Promise::Deferred deferred_;
|
|
33
34
|
};
|
|
34
35
|
|
|
35
|
-
} // namespace FSMeta
|
|
36
|
+
} // namespace FSMeta
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
#include "../common/debug_log.h"
|
|
5
5
|
#include "../common/fd_guard.h"
|
|
6
6
|
#include "../common/path_security.h"
|
|
7
|
+
#include "../common/shutdown.h"
|
|
7
8
|
#include "../common/volume_utils.h"
|
|
8
9
|
#include "./da_mutex.h"
|
|
9
10
|
#include "./fs_meta.h"
|
|
@@ -76,6 +77,11 @@ public:
|
|
|
76
77
|
void Execute() override {
|
|
77
78
|
DEBUG_LOG("[GetVolumeMetadataWorker] Executing for mount point: %s",
|
|
78
79
|
mountPoint.c_str());
|
|
80
|
+
if (IsShuttingDown()) {
|
|
81
|
+
// Avoid kicking off blocking IOKit/DA calls during env teardown.
|
|
82
|
+
SetError("fs-metadata: shutdown in progress");
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
79
85
|
try {
|
|
80
86
|
// Validate and canonicalize mount point using realpath()
|
|
81
87
|
// This follows Apple's Secure Coding Guide recommendations
|
|
@@ -207,6 +213,14 @@ private:
|
|
|
207
213
|
DEBUG_LOG("[GetVolumeMetadataWorker] Getting Disk Arbitration info for: %s",
|
|
208
214
|
mountPoint.c_str());
|
|
209
215
|
|
|
216
|
+
if (IsShuttingDown()) {
|
|
217
|
+
// IOServiceGetMatchingService is uncancellable; if we're already in
|
|
218
|
+
// teardown, surface a partial result rather than block FreeEnvironment.
|
|
219
|
+
metadata.status = "partial";
|
|
220
|
+
metadata.error = "shutdown in progress";
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
|
|
210
224
|
// Check if this is a network filesystem
|
|
211
225
|
if (metadata.fstype == "smbfs" || metadata.fstype == "nfs" ||
|
|
212
226
|
metadata.fstype == "afpfs" || metadata.fstype == "webdav") {
|
|
@@ -370,4 +384,4 @@ Napi::Value GetVolumeMetadata(const Napi::CallbackInfo &info) {
|
|
|
370
384
|
return deferred.Promise();
|
|
371
385
|
}
|
|
372
386
|
|
|
373
|
-
} // namespace FSMeta
|
|
387
|
+
} // namespace FSMeta
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
#include "../common/volume_mount_points.h"
|
|
3
3
|
#include "../common/debug_log.h"
|
|
4
4
|
#include "../common/error_utils.h"
|
|
5
|
+
#include "../common/shutdown.h"
|
|
5
6
|
#include "./da_mutex.h"
|
|
6
7
|
#include "./fs_meta.h"
|
|
7
8
|
#include "./raii_utils.h"
|
|
@@ -13,7 +14,7 @@
|
|
|
13
14
|
|
|
14
15
|
namespace FSMeta {
|
|
15
16
|
|
|
16
|
-
class GetVolumeMountPointsWorker : public
|
|
17
|
+
class GetVolumeMountPointsWorker : public SafeAsyncWorker {
|
|
17
18
|
private:
|
|
18
19
|
Napi::Promise::Deferred deferred_;
|
|
19
20
|
std::vector<MountPoint> mountPoints_;
|
|
@@ -22,11 +23,15 @@ private:
|
|
|
22
23
|
public:
|
|
23
24
|
GetVolumeMountPointsWorker(const Napi::Promise::Deferred &deferred,
|
|
24
25
|
uint32_t timeoutMs = 5000)
|
|
25
|
-
:
|
|
26
|
+
: SafeAsyncWorker(deferred.Env()), deferred_(deferred),
|
|
26
27
|
timeoutMs_(timeoutMs) {}
|
|
27
28
|
|
|
28
29
|
void Execute() override {
|
|
29
30
|
DEBUG_LOG("[GetVolumeMountPointsWorker] Executing");
|
|
31
|
+
if (IsShuttingDown()) {
|
|
32
|
+
SetError("fs-metadata: shutdown in progress");
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
30
35
|
try {
|
|
31
36
|
MountBufferRAII mntbuf;
|
|
32
37
|
// Use MNT_NOWAIT for better performance - we'll verify accessibility
|
|
@@ -57,6 +62,10 @@ public:
|
|
|
57
62
|
{
|
|
58
63
|
std::lock_guard<std::mutex> lock(g_diskArbitrationMutex);
|
|
59
64
|
|
|
65
|
+
if (IsShuttingDown()) {
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
|
|
60
69
|
DASessionRAII session(DASessionCreate(kCFAllocatorDefault));
|
|
61
70
|
if (session.isValid()) {
|
|
62
71
|
static dispatch_queue_t da_queue = dispatch_queue_create(
|
|
@@ -66,6 +75,10 @@ public:
|
|
|
66
75
|
}
|
|
67
76
|
|
|
68
77
|
for (int j = 0; j < count; j++) {
|
|
78
|
+
if (IsShuttingDown()) {
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
|
|
69
82
|
MountPoint mp;
|
|
70
83
|
mp.mountPoint = mntbuf.get()[j].f_mntonname;
|
|
71
84
|
mp.fstype = mntbuf.get()[j].f_fstypename;
|
|
@@ -88,6 +101,10 @@ public:
|
|
|
88
101
|
const size_t maxConcurrentChecks = 4; // Limit concurrent access checks
|
|
89
102
|
|
|
90
103
|
for (size_t i = 0; i < allMountPoints.size(); i += maxConcurrentChecks) {
|
|
104
|
+
if (IsShuttingDown()) {
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
|
|
91
108
|
std::vector<std::future<std::pair<std::string, bool>>> futures;
|
|
92
109
|
std::vector<MountPoint *> batchPtrs;
|
|
93
110
|
|
|
@@ -182,7 +199,12 @@ public:
|
|
|
182
199
|
result[i] = mountPoints_[i].ToObject(env);
|
|
183
200
|
}
|
|
184
201
|
|
|
185
|
-
deferred_
|
|
202
|
+
SafeResolve(deferred_, result);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
void OnError(const Napi::Error &error) override {
|
|
206
|
+
Napi::HandleScope scope(Env());
|
|
207
|
+
SafeReject(deferred_, error.Value());
|
|
186
208
|
}
|
|
187
209
|
};
|
|
188
210
|
|
|
@@ -202,4 +224,4 @@ Napi::Promise GetVolumeMountPoints(const Napi::CallbackInfo &info) {
|
|
|
202
224
|
return deferred.Promise();
|
|
203
225
|
}
|
|
204
226
|
|
|
205
|
-
} // namespace FSMeta
|
|
227
|
+
} // namespace FSMeta
|
|
@@ -27,6 +27,10 @@ public:
|
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
void Execute() override {
|
|
30
|
+
if (IsShuttingDown()) {
|
|
31
|
+
SetError("fs-metadata: shutdown in progress");
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
30
34
|
try {
|
|
31
35
|
DEBUG_LOG("[LinuxMetadataWorker] starting statvfs for %s",
|
|
32
36
|
mountPoint.c_str());
|
|
@@ -181,4 +185,4 @@ Napi::Value GetVolumeMetadata(const Napi::CallbackInfo &info) {
|
|
|
181
185
|
return deferred.Promise();
|
|
182
186
|
}
|
|
183
187
|
|
|
184
|
-
} // namespace FSMeta
|
|
188
|
+
} // namespace FSMeta
|
package/src/windows/hidden.cpp
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// src/windows/hidden.cpp
|
|
2
2
|
#include "hidden.h"
|
|
3
3
|
#include "../common/debug_log.h"
|
|
4
|
+
#include "../common/shutdown.h"
|
|
4
5
|
#include "error_utils.h"
|
|
5
6
|
#include "security_utils.h"
|
|
6
7
|
|
|
@@ -36,14 +37,14 @@ public:
|
|
|
36
37
|
};
|
|
37
38
|
} // anonymous namespace
|
|
38
39
|
|
|
39
|
-
class GetHiddenWorker : public
|
|
40
|
+
class GetHiddenWorker : public SafeAsyncWorker {
|
|
40
41
|
const std::string path;
|
|
41
42
|
bool result = false;
|
|
42
43
|
Napi::Promise::Deferred deferred;
|
|
43
44
|
|
|
44
45
|
public:
|
|
45
46
|
GetHiddenWorker(Napi::Env env, std::string p, Napi::Promise::Deferred def)
|
|
46
|
-
:
|
|
47
|
+
: SafeAsyncWorker(env), path(std::move(p)), deferred(def) {}
|
|
47
48
|
|
|
48
49
|
void Execute() override {
|
|
49
50
|
try {
|
|
@@ -104,17 +105,17 @@ public:
|
|
|
104
105
|
DEBUG_LOG("[GetHiddenWorker] OnOK called, result=%s",
|
|
105
106
|
result ? "true" : "false");
|
|
106
107
|
Napi::HandleScope scope(Env());
|
|
107
|
-
deferred
|
|
108
|
+
SafeResolve(deferred, Napi::Boolean::New(Env(), result));
|
|
108
109
|
}
|
|
109
110
|
|
|
110
111
|
void OnError(const Napi::Error &e) override {
|
|
111
112
|
DEBUG_LOG("[GetHiddenWorker] OnError called with: %s", e.Message().c_str());
|
|
112
113
|
Napi::HandleScope scope(Env());
|
|
113
|
-
deferred
|
|
114
|
+
SafeReject(deferred, e.Value());
|
|
114
115
|
}
|
|
115
116
|
};
|
|
116
117
|
|
|
117
|
-
class SetHiddenWorker : public
|
|
118
|
+
class SetHiddenWorker : public SafeAsyncWorker {
|
|
118
119
|
const std::string path;
|
|
119
120
|
const bool value;
|
|
120
121
|
Napi::Promise::Deferred deferred;
|
|
@@ -122,7 +123,7 @@ class SetHiddenWorker : public Napi::AsyncWorker {
|
|
|
122
123
|
public:
|
|
123
124
|
SetHiddenWorker(Napi::Env env, std::string p, bool v,
|
|
124
125
|
Napi::Promise::Deferred def)
|
|
125
|
-
:
|
|
126
|
+
: SafeAsyncWorker(env), path(std::move(p)), value(v), deferred(def) {}
|
|
126
127
|
|
|
127
128
|
void Execute() override {
|
|
128
129
|
try {
|
|
@@ -144,12 +145,12 @@ public:
|
|
|
144
145
|
|
|
145
146
|
void OnOK() override {
|
|
146
147
|
Napi::HandleScope scope(Env());
|
|
147
|
-
deferred
|
|
148
|
+
SafeResolve(deferred, Napi::Boolean::New(Env(), true));
|
|
148
149
|
}
|
|
149
150
|
|
|
150
151
|
void OnError(const Napi::Error &e) override {
|
|
151
152
|
Napi::HandleScope scope(Env());
|
|
152
|
-
deferred
|
|
153
|
+
SafeReject(deferred, e.Value());
|
|
153
154
|
}
|
|
154
155
|
};
|
|
155
156
|
|
|
@@ -195,4 +196,4 @@ Napi::Promise SetHiddenAttribute(const Napi::CallbackInfo &info) {
|
|
|
195
196
|
}
|
|
196
197
|
}
|
|
197
198
|
|
|
198
|
-
} // namespace FSMeta
|
|
199
|
+
} // namespace FSMeta
|
|
@@ -154,6 +154,10 @@ private:
|
|
|
154
154
|
VolumeMetadataOptions options_;
|
|
155
155
|
|
|
156
156
|
void Execute() override {
|
|
157
|
+
if (IsShuttingDown()) {
|
|
158
|
+
SetError("fs-metadata: shutdown in progress");
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
157
161
|
try {
|
|
158
162
|
// Get drive status first
|
|
159
163
|
DriveStatus status = CheckDriveStatus(mountPoint);
|
|
@@ -250,4 +254,4 @@ Napi::Value GetVolumeMetadata(const Napi::CallbackInfo &info) {
|
|
|
250
254
|
return deferred.Promise();
|
|
251
255
|
}
|
|
252
256
|
|
|
253
|
-
} // namespace FSMeta
|
|
257
|
+
} // namespace FSMeta
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
#include "../common/volume_mount_points.h"
|
|
3
3
|
#include "../common/debug_log.h"
|
|
4
4
|
#include "../common/error_utils.h"
|
|
5
|
+
#include "../common/shutdown.h"
|
|
5
6
|
#include "drive_status.h"
|
|
6
7
|
#include "fs_meta.h"
|
|
7
8
|
#include "security_utils.h"
|
|
@@ -21,7 +22,7 @@ struct DriveStringsBuffer {
|
|
|
21
22
|
: buffer(std::make_unique<WCHAR[]>(size)) {}
|
|
22
23
|
};
|
|
23
24
|
|
|
24
|
-
class GetVolumeMountPointsWorker : public
|
|
25
|
+
class GetVolumeMountPointsWorker : public SafeAsyncWorker {
|
|
25
26
|
|
|
26
27
|
private:
|
|
27
28
|
Napi::Promise::Deferred deferred_;
|
|
@@ -31,10 +32,14 @@ private:
|
|
|
31
32
|
public:
|
|
32
33
|
GetVolumeMountPointsWorker(const Napi::Promise::Deferred &deferred,
|
|
33
34
|
uint32_t timeoutMs = 5000)
|
|
34
|
-
:
|
|
35
|
+
: SafeAsyncWorker(deferred.Env()), deferred_(deferred),
|
|
35
36
|
timeoutMs_(timeoutMs) {}
|
|
36
37
|
|
|
37
38
|
void Execute() override {
|
|
39
|
+
if (IsShuttingDown()) {
|
|
40
|
+
SetError("fs-metadata: shutdown in progress");
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
38
43
|
try {
|
|
39
44
|
DEBUG_LOG("[GetVolumeMountPoints] getting logical drive strings size");
|
|
40
45
|
DWORD size = GetLogicalDriveStringsW(0, nullptr);
|
|
@@ -74,12 +79,19 @@ public:
|
|
|
74
79
|
}
|
|
75
80
|
|
|
76
81
|
// Check all drive statuses in parallel
|
|
82
|
+
if (IsShuttingDown()) {
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
77
85
|
auto statuses = CheckDriveStatus(paths, timeoutMs_);
|
|
78
86
|
|
|
79
87
|
// Build mount points from results
|
|
80
88
|
mountPoints_.reserve(paths.size());
|
|
81
89
|
|
|
82
90
|
for (size_t i = 0; i < paths.size(); i++) {
|
|
91
|
+
if (IsShuttingDown()) {
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
|
|
83
95
|
MountPoint mp;
|
|
84
96
|
mp.mountPoint = paths[i];
|
|
85
97
|
mp.status = DriveStatusToString(statuses[i]);
|
|
@@ -115,6 +127,7 @@ public:
|
|
|
115
127
|
}
|
|
116
128
|
|
|
117
129
|
void OnOK() override {
|
|
130
|
+
Napi::HandleScope scope(Env());
|
|
118
131
|
auto env = Env();
|
|
119
132
|
Napi::Array result = Napi::Array::New(env, mountPoints_.size());
|
|
120
133
|
|
|
@@ -122,7 +135,12 @@ public:
|
|
|
122
135
|
result[i] = mountPoints_[i].ToObject(env);
|
|
123
136
|
}
|
|
124
137
|
|
|
125
|
-
deferred_
|
|
138
|
+
SafeResolve(deferred_, result);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
void OnError(const Napi::Error &error) override {
|
|
142
|
+
Napi::HandleScope scope(Env());
|
|
143
|
+
SafeReject(deferred_, error.Value());
|
|
126
144
|
}
|
|
127
145
|
|
|
128
146
|
}; // class GetVolumeMountPointsWorker
|
|
@@ -140,4 +158,4 @@ Napi::Promise GetVolumeMountPoints(const Napi::CallbackInfo &info) {
|
|
|
140
158
|
worker->Queue();
|
|
141
159
|
return deferred.Promise();
|
|
142
160
|
}
|
|
143
|
-
} // namespace FSMeta
|
|
161
|
+
} // namespace FSMeta
|