@photostructure/fs-metadata 1.3.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.
Files changed (52) hide show
  1. package/AGENTS.md +213 -0
  2. package/CHANGELOG.md +54 -0
  3. package/CONTRIBUTING.md +3 -3
  4. package/binding.gyp +0 -22
  5. package/dist/index.cjs +10 -39
  6. package/dist/index.cjs.map +1 -1
  7. package/dist/index.d.cts +1 -1
  8. package/dist/index.d.mts +1 -1
  9. package/dist/index.d.ts +1 -1
  10. package/dist/index.mjs +10 -39
  11. package/dist/index.mjs.map +1 -1
  12. package/doc/C++_REVIEW_TODO.md +3 -36
  13. package/doc/LINUX_API_REFERENCE.md +4 -147
  14. package/doc/SECURITY_AUDIT_2026.md +4 -0
  15. package/doc/gotchas.md +27 -0
  16. package/package.json +11 -12
  17. package/prebuilds/darwin-arm64/@photostructure+fs-metadata.glibc.node +0 -0
  18. package/prebuilds/darwin-x64/@photostructure+fs-metadata.glibc.node +0 -0
  19. package/prebuilds/linux-arm64/@photostructure+fs-metadata.glibc.node +0 -0
  20. package/prebuilds/linux-arm64/@photostructure+fs-metadata.musl.node +0 -0
  21. package/prebuilds/linux-x64/@photostructure+fs-metadata.glibc.node +0 -0
  22. package/prebuilds/linux-x64/@photostructure+fs-metadata.musl.node +0 -0
  23. package/prebuilds/win32-arm64/@photostructure+fs-metadata.glibc.node +0 -0
  24. package/prebuilds/win32-x64/@photostructure+fs-metadata.glibc.node +0 -0
  25. package/scripts/clang-tidy.ts +1 -3
  26. package/scripts/prebuild-linux-glibc.sh +1 -5
  27. package/scripts/sanitizers-test.sh +0 -1
  28. package/src/binding.cpp +5 -15
  29. package/src/common/metadata_worker.h +7 -6
  30. package/src/common/shutdown.h +162 -0
  31. package/src/darwin/get_mount_point.cpp +6 -4
  32. package/src/darwin/hidden.cpp +9 -8
  33. package/src/darwin/hidden.h +4 -3
  34. package/src/darwin/volume_metadata.cpp +15 -1
  35. package/src/darwin/volume_mount_points.cpp +26 -4
  36. package/src/linux/mount_points.ts +8 -42
  37. package/src/linux/volume_metadata.cpp +5 -17
  38. package/src/mount_point_for_path.ts +1 -1
  39. package/src/types/mount_point.ts +1 -1
  40. package/src/types/native_bindings.ts +0 -5
  41. package/src/volume_metadata.ts +16 -7
  42. package/src/volume_mount_points.ts +1 -1
  43. package/src/windows/hidden.cpp +10 -9
  44. package/src/windows/volume_metadata.cpp +5 -1
  45. package/src/windows/volume_mount_points.cpp +22 -4
  46. package/scripts/setup-native.mjs +0 -39
  47. package/src/linux/gio_mount_points.cpp +0 -80
  48. package/src/linux/gio_mount_points.h +0 -37
  49. package/src/linux/gio_utils.cpp +0 -115
  50. package/src/linux/gio_utils.h +0 -69
  51. package/src/linux/gio_volume_metadata.cpp +0 -81
  52. package/src/linux/gio_volume_metadata.h +0 -20
@@ -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 Napi::AsyncWorker {
20
+ class GetMountPointWorker : public SafeAsyncWorker {
20
21
  public:
21
22
  GetMountPointWorker(const std::string &path,
22
23
  const Napi::Promise::Deferred &deferred)
23
- : Napi::AsyncWorker(deferred.Env()), path_(path), deferred_(deferred) {}
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_.Resolve(Napi::String::New(Env(), result_));
69
+ SafeResolve(deferred_, Napi::String::New(Env(), result_));
69
70
  }
70
71
 
71
72
  void OnError(const Napi::Error &error) override {
72
- deferred_.Reject(error.Value());
73
+ Napi::HandleScope scope(Env());
74
+ SafeReject(deferred_, error.Value());
73
75
  }
74
76
 
75
77
  private:
@@ -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
- : Napi::AsyncWorker(deferred.Env()), path_(std::move(path)),
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_.Resolve(Napi::Boolean::New(env, is_hidden_));
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_.Reject(error.Value());
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
- : Napi::AsyncWorker(deferred.Env()), path_(std::move(path)),
115
- hidden_(hidden), deferred_(deferred) {
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_.Resolve(env.Undefined());
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_.Reject(error.Value());
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
@@ -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 Napi::AsyncWorker {
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 Napi::AsyncWorker {
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 Napi::AsyncWorker {
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
- : Napi::AsyncWorker(deferred.Env()), deferred_(deferred),
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_.Resolve(result);
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
@@ -2,69 +2,35 @@
2
2
  import { readFile } from "node:fs/promises";
3
3
  import { debug } from "../debuglog";
4
4
  import { toError, WrappedError } from "../error";
5
- import { isMountPoint } from "../mount_point";
6
- import { compactValues } from "../object";
7
5
  import { optionsWithDefaults } from "../options";
8
6
  import { type MountPoint } from "../types/mount_point";
9
- import type { NativeBindingsFn } from "../types/native_bindings";
10
7
  import type { Options } from "../types/options";
11
8
  import { MountEntry, mountEntryToMountPoint, parseMtab } from "./mtab";
12
9
 
13
10
  export async function getLinuxMountPoints(
14
- native: NativeBindingsFn,
15
11
  opts?: Pick<Options, "linuxMountTablePaths">,
16
12
  ): Promise<MountPoint[]> {
17
13
  const o = optionsWithDefaults(opts);
18
- const raw: MountPoint[] = [];
19
- try {
20
- // Get GIO mounts if available from native module
21
- const arr = await (await native()).getGioMountPoints?.();
22
- debug("[getLinuxMountPoints] GIO mount points: %o", arr);
23
- if (arr != null) raw.push(...arr);
24
- } catch (error) {
25
- debug("Failed to get GIO mount points: %s", error);
26
- // GIO support not compiled in or failed, continue with just mtab mounts
27
- }
28
-
29
14
  let cause: Error | undefined;
30
15
  for (const input of o.linuxMountTablePaths) {
31
16
  try {
32
17
  const mtabContent = await readFile(input, "utf8");
33
- const arr = parseMtab(mtabContent)
18
+ const results = parseMtab(mtabContent)
34
19
  .map((ea) => mountEntryToMountPoint(ea))
35
20
  .filter((ea) => ea != null);
36
- debug("[getLinuxMountPoints] %s mount points: %o", input, arr);
37
- if (arr.length > 0) {
38
- raw.push(...arr);
39
- break;
21
+ debug("[getLinuxMountPoints] %s mount points: %o", input, results);
22
+ if (results.length > 0) {
23
+ return results;
40
24
  }
41
25
  } catch (error) {
42
26
  cause ??= toError(error);
43
27
  }
44
28
  }
45
29
 
46
- const byMountPoint = new Map<string, MountPoint>();
47
- for (const ea of raw) {
48
- const prior = byMountPoint.get(ea.mountPoint);
49
- const merged = { ...compactValues(prior), ...compactValues(ea) };
50
- if (isMountPoint(merged)) {
51
- byMountPoint.set(merged.mountPoint, merged);
52
- }
53
- }
54
-
55
- if (byMountPoint.size === 0) {
56
- throw new WrappedError(
57
- `Failed to find any mount points (tried: ${JSON.stringify(o.linuxMountTablePaths)})`,
58
- { cause },
59
- );
60
- }
61
-
62
- const results = [...byMountPoint.values()];
63
- debug("[getLinuxMountPoints] %o", {
64
- results: results.map((ea) => ea.mountPoint),
65
- });
66
-
67
- return results;
30
+ throw new WrappedError(
31
+ `Failed to find any mount points (tried: ${JSON.stringify(o.linuxMountTablePaths)})`,
32
+ { cause },
33
+ );
68
34
  }
69
35
 
70
36
  export async function getLinuxMtabMetadata(
@@ -12,10 +12,6 @@
12
12
  #include <sys/statvfs.h>
13
13
  #include <unistd.h>
14
14
 
15
- #ifdef ENABLE_GIO
16
- #include "gio_volume_metadata.h"
17
- #endif
18
-
19
15
  namespace FSMeta {
20
16
 
21
17
  class LinuxMetadataWorker : public MetadataWorkerBase {
@@ -31,6 +27,10 @@ public:
31
27
  }
32
28
 
33
29
  void Execute() override {
30
+ if (IsShuttingDown()) {
31
+ SetError("fs-metadata: shutdown in progress");
32
+ return;
33
+ }
34
34
  try {
35
35
  DEBUG_LOG("[LinuxMetadataWorker] starting statvfs for %s",
36
36
  mountPoint.c_str());
@@ -113,18 +113,6 @@ public:
113
113
  mountPoint.c_str(), metadata.size / 1e9,
114
114
  metadata.available / 1e9);
115
115
 
116
- #ifdef ENABLE_GIO
117
- try {
118
- DEBUG_LOG("[LinuxMetadataWorker] collecting GIO metadata for %s",
119
- validated_mount_point.c_str());
120
- gio::addMountMetadata(validated_mount_point, metadata);
121
- } catch (const std::exception &e) {
122
- DEBUG_LOG("[LinuxMetadataWorker] GIO error for %s: %s",
123
- validated_mount_point.c_str(), e.what());
124
- metadata.status = std::string("GIO warning: ") + e.what();
125
- }
126
- #endif
127
-
128
116
  if (!options_.device.empty()) {
129
117
  DEBUG_LOG("[LinuxMetadataWorker] getting blkid info for device %s",
130
118
  options_.device.c_str());
@@ -197,4 +185,4 @@ Napi::Value GetVolumeMetadata(const Napi::CallbackInfo &info) {
197
185
  return deferred.Promise();
198
186
  }
199
187
 
200
- } // namespace FSMeta
188
+ } // namespace FSMeta
@@ -48,7 +48,7 @@ export async function getMountPointForPathImpl(
48
48
  throw new Error("getMountPoint native function unavailable");
49
49
  }
50
50
 
51
- // Linux/Windows: device ID matching + path prefix tiebreaker
51
+ // Linux/Windows: device ID filtering + longest ancestor path matching
52
52
  debug("[getMountPointForPath] using device matching for %s", resolved);
53
53
  return findMountPointByDeviceId(resolved, resolvedStat, opts, nativeFn);
54
54
  }
@@ -29,7 +29,7 @@ export interface MountPoint {
29
29
  * verifyVolume`.
30
30
  *
31
31
  * If there are non-critical errors while extracting metadata, those error
32
- * messages may be added to this field (say, from blkid or gio).
32
+ * messages may be added to this field (say, from blkid).
33
33
  *
34
34
  * @see {@link VolumeHealthStatuses} for values returned by Windows.
35
35
  */
@@ -43,11 +43,6 @@ export interface NativeBindings {
43
43
  options?: Pick<Options, "timeoutMs">,
44
44
  ): Promise<MountPoint[]>;
45
45
 
46
- /**
47
- * This is only available on Linux, and only if libglib-2.0 is installed.
48
- */
49
- getGioMountPoints?(): Promise<MountPoint[]>;
50
-
51
46
  /**
52
47
  * This is only a partial implementation for most platforms, to minimize
53
48
  * native code when possible. The javascript side handles a bunch of
@@ -95,7 +95,8 @@ async function _getVolumeMetadata(
95
95
  }
96
96
  } catch (err) {
97
97
  debug("[getVolumeMetadata] failed to get mtab info: " + err);
98
- // this may be a GIO mount. Ignore the error and continue.
98
+ // Mtab lookup can fail for transient mounts or race conditions.
99
+ // Ignore and continue with whatever the native call returns.
99
100
  }
100
101
  }
101
102
 
@@ -140,7 +141,7 @@ async function _getVolumeMetadata(
140
141
  remote,
141
142
  }) as VolumeMetadata;
142
143
 
143
- // Backfill if blkid or gio failed us:
144
+ // Backfill if blkid failed us:
144
145
  if (isLinux && isNotBlank(device)) {
145
146
  // Sometimes blkid doesn't have the UUID in cache. Try to get it from
146
147
  // /dev/disk/by-uuid:
@@ -209,7 +210,7 @@ export async function getVolumeMetadataForPathImpl(
209
210
 
210
211
  // Linux/Windows: stat().dev is reliable (no firmlinks). Find the mount point
211
212
  // by comparing device IDs, using path prefix as a tiebreaker for bind mounts
212
- // or GIO mounts that share the same device id.
213
+ // or GVfs/FUSE mounts that share the same device id.
213
214
  const mountPoint = await findMountPointByDeviceId(
214
215
  resolved,
215
216
  resolvedStat,
@@ -221,12 +222,18 @@ export async function getVolumeMetadataForPathImpl(
221
222
  }
222
223
 
223
224
  /**
224
- * Find the mount point for a resolved path using device ID matching.
225
+ * Find the mount point for a resolved path using device ID + path ancestry.
225
226
  * Used on Linux and Windows where stat().dev is reliable (no firmlinks).
226
227
  *
227
- * Compares device IDs of mount points against the target path's device ID,
228
- * using path prefix as a tiebreaker for bind mounts or GIO mounts that share
229
- * the same device id. The longest prefix match wins.
228
+ * Device ID filters out unrelated filesystems. Among same-device mount points,
229
+ * ancestor-path matches (mount point is a parent of `resolved`) are strongly
230
+ * preferred over device-only matches GVfs/FUSE mounts on Linux can share
231
+ * the same device ID across unrelated volumes (e.g. multiple SMB shares
232
+ * under /run/user/.../gvfs/), so device ID alone is ambiguous. The longest
233
+ * ancestor wins.
234
+ *
235
+ * The device-only fallback (`deviceMatches`) exists for bind mounts where the
236
+ * canonical mount point may not be a path ancestor of the target.
230
237
  */
231
238
  export async function findMountPointByDeviceId(
232
239
  resolved: string,
@@ -261,6 +268,8 @@ export async function findMountPointByDeviceId(
261
268
  }),
262
269
  );
263
270
 
271
+ // Prefer ancestor matches — they're unambiguous. Fall back to device-only
272
+ // matches only when the mount point isn't an ancestor (e.g. bind mounts).
264
273
  const candidates = prefixMatches.length > 0 ? prefixMatches : deviceMatches;
265
274
  if (candidates.length === 0) {
266
275
  throw new Error(
@@ -51,7 +51,7 @@ async function _getVolumeMountPoints(
51
51
  );
52
52
  return points;
53
53
  })()
54
- : getLinuxMountPoints(nativeFn, o));
54
+ : getLinuxMountPoints(o));
55
55
 
56
56
  debug("[getVolumeMountPoints] raw mount points: %o", raw);
57
57
 
@@ -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 Napi::AsyncWorker {
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
- : Napi::AsyncWorker(env), path(std::move(p)), deferred(def) {}
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.Resolve(Napi::Boolean::New(Env(), result));
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.Reject(e.Value());
114
+ SafeReject(deferred, e.Value());
114
115
  }
115
116
  };
116
117
 
117
- class SetHiddenWorker : public Napi::AsyncWorker {
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
- : Napi::AsyncWorker(env), path(std::move(p)), value(v), deferred(def) {}
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.Resolve(Napi::Boolean::New(Env(), true));
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.Reject(e.Value());
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 Napi::AsyncWorker {
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
- : Napi::AsyncWorker(deferred.Env()), deferred_(deferred),
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_.Resolve(result);
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