@photostructure/fs-metadata 1.2.0 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/CONTRIBUTING.md +3 -3
  3. package/binding.gyp +0 -22
  4. package/dist/index.cjs +11 -40
  5. package/dist/index.cjs.map +1 -1
  6. package/dist/index.d.cts +15 -3
  7. package/dist/index.d.mts +15 -3
  8. package/dist/index.d.ts +15 -3
  9. package/dist/index.mjs +11 -40
  10. package/dist/index.mjs.map +1 -1
  11. package/doc/C++_REVIEW_TODO.md +3 -36
  12. package/doc/LINUX_API_REFERENCE.md +4 -147
  13. package/doc/SECURITY_AUDIT_2026.md +5 -0
  14. package/doc/gotchas.md +27 -0
  15. package/package.json +9 -10
  16. package/prebuilds/darwin-arm64/@photostructure+fs-metadata.glibc.node +0 -0
  17. package/prebuilds/darwin-x64/@photostructure+fs-metadata.glibc.node +0 -0
  18. package/prebuilds/linux-arm64/@photostructure+fs-metadata.glibc.node +0 -0
  19. package/prebuilds/linux-x64/@photostructure+fs-metadata.glibc.node +0 -0
  20. package/prebuilds/win32-arm64/@photostructure+fs-metadata.glibc.node +0 -0
  21. package/prebuilds/win32-x64/@photostructure+fs-metadata.glibc.node +0 -0
  22. package/scripts/clang-tidy.ts +1 -3
  23. package/scripts/prebuild-linux-glibc.sh +1 -5
  24. package/scripts/sanitizers-test.sh +0 -1
  25. package/src/binding.cpp +0 -15
  26. package/src/index.ts +6 -2
  27. package/src/linux/mount_points.ts +8 -42
  28. package/src/linux/volume_metadata.cpp +0 -16
  29. package/src/mount_point_for_path.ts +1 -1
  30. package/src/types/mount_point.ts +1 -1
  31. package/src/types/native_bindings.ts +0 -5
  32. package/src/types/options.ts +14 -0
  33. package/src/volume_metadata.ts +22 -11
  34. package/src/volume_mount_points.ts +1 -1
  35. package/scripts/setup-native.mjs +0 -39
  36. package/src/linux/gio_mount_points.cpp +0 -80
  37. package/src/linux/gio_mount_points.h +0 -37
  38. package/src/linux/gio_utils.cpp +0 -115
  39. package/src/linux/gio_utils.h +0 -69
  40. package/src/linux/gio_volume_metadata.cpp +0 -81
  41. package/src/linux/gio_volume_metadata.h +0 -20
@@ -142,12 +142,10 @@ This document outlines a comprehensive review of all C++ files in the fs-metadat
142
142
 
143
143
  - [x] Review BlkidCache RAII wrapper - Already excellent
144
144
  - [x] Check blkid_get_tag_value free() calls - Correctly using free()
145
- - [x] Verify GIO integration memory management - Proper smart pointers
146
145
  - [x] Validate statvfs error handling - Already comprehensive
147
146
  - [x] **Added**: Input validation for empty mount points
148
147
  - **Platform APIs verified**:
149
148
  - libblkid API availability
150
- - GIO optional dependency handling
151
149
  - statvfs behavior
152
150
 
153
151
  #### `src/linux/blkid_cache.cpp` ✅
@@ -159,38 +157,8 @@ This document outlines a comprehensive review of all C++ files in the fs-metadat
159
157
  - **Platform APIs verified**:
160
158
  - libblkid cache APIs - Proper use of blkid_get_cache/blkid_put_cache
161
159
 
162
- #### `src/linux/gio_utils.cpp` (if GIO enabled)
163
-
164
- - [x] Review g_object_unref patterns - Already using GioResource RAII
165
- - [x] Check GError cleanup - Not used in this file (GError-free patterns)
166
- - [x] Verify GMount/GVolume reference counting - Proper ref/unref pairs
167
- - [x] **Added**: Exception handling in forEachMount callback
168
- - [x] **Added**: Null checks for root.get() before G_IS_FILE
169
- - [x] **Added**: const-correctness for GioResource and callback results
170
- - **Platform APIs verified**:
171
- - GLib/GIO APIs (GNOME) - Proper memory management patterns
172
- - GVfs mount integration
173
-
174
- #### `src/linux/gio_mount_points.cpp` (if GIO enabled) ✅
175
-
176
- - [x] Review g_volume_monitor lifecycle - Correct usage (singleton pattern)
177
- - [x] Check GList cleanup patterns - Using g_list_free_full correctly
178
- - [x] Verify string ownership - Proper GCharPtr smart pointers
179
- - [x] **Added**: const-correctness for local variables (GCharPtr, GFileInfoPtr, etc.)
180
- - **Platform APIs verified**:
181
- - GVolumeMonitor APIs - Reference counting rules
182
- - Mount enumeration
183
-
184
- #### `src/linux/gio_volume_metadata.cpp` (if GIO enabled) ✅
185
-
186
- - [x] Review g_drive object management - Using GObjectPtr smart pointers
187
- - [x] Check g_icon handling - No GIcon usage in this file
188
- - [x] Verify string ownership - Proper GCharPtr usage
189
- - [x] **Added**: Defensive null checks for GObjectPtr::get()
190
- - [x] **Added**: Null checks for string getters
191
- - [x] **Added**: const-correctness for all GCharPtr and GObjectPtr locals
192
- - **Platform APIs verified**:
193
- - GDrive/GVolume metadata APIs - Ownership transfer rules
160
+ > **Note**: GIO/GLib integration was removed in favor of `/proc/self/mounts`
161
+ > parsing in TypeScript. See CHANGELOG.md for details.
194
162
 
195
163
  ## Testing Strategy
196
164
 
@@ -237,7 +205,7 @@ This document outlines a comprehensive review of all C++ files in the fs-metadat
237
205
  - ✅ WNetConnection: Handle ERROR_MORE_DATA with dynamic buffer resize
238
206
  - ✅ GetVolumeInformation: Use MAX_PATH+1 instead of BUFFER_SIZE
239
207
  3. ~~**High**: CoreFoundation reference counting in macOS code~~ ✅ Completed - CFReleaser RAII wrapper
240
- 4. ~~**High**: GIO object lifecycle management~~ ✅ Completed - Using smart pointers throughout
208
+ 4. ~~**High**: GIO object lifecycle management~~ ✅ N/A - GIO integration removed entirely (see CHANGELOG)
241
209
  5. ~~**Medium**: Security - Add comprehensive path validation~~ ✅ Completed
242
210
  - ✅ Check for ".." in all platforms (Windows/Darwin have it, Linux doesn't have hidden file support)
243
211
  - Validate against null bytes and special characters (remaining)
@@ -358,6 +326,5 @@ The Darwin/macOS implementation demonstrates excellent resource management with
358
326
  ### Linux
359
327
 
360
328
  - libblkid: Use free() for blkid_get_tag_value results, blkid_put_cache() for cache
361
- - GLib/GIO: Use g_object_unref() or g_clear_object(), match allocation functions
362
329
  - Key Documentation: libblkid man pages, GNOME Developer Documentation
363
330
  - **Review Status**: ✅ All files properly reviewed and use smart pointers
@@ -4,115 +4,10 @@ Reference for Linux APIs used in fs-metadata, with links to official documentati
4
4
 
5
5
  ## Table of Contents
6
6
 
7
- 1. [GIO/GLib APIs](#gioglib-apis)
8
- 2. [libblkid APIs](#libblkid-apis)
9
- 3. [POSIX File System APIs](#posix-file-system-apis)
10
- 4. [RAII Patterns](#raii-patterns)
11
- 5. [References](#references)
12
-
13
- ## GIO/GLib APIs
14
-
15
- ### Thread Safety Overview
16
-
17
- **Critical**: GIO has different thread safety guarantees for different APIs:
18
-
19
- | API | Thread Safe? | Used? | Notes |
20
- | ------------------------ | ------------ | ----- | ----------------------------------------- |
21
- | `g_unix_mounts_get()` | Yes | ✅ | Uses `getmntent_r()` or internal `G_LOCK` |
22
- | `g_unix_mount_get_*()` | Yes | ✅ | Safe on `GUnixMountEntry` |
23
- | `g_volume_monitor_get()` | **No** | ❌ | Main thread only - **NOT USED** |
24
-
25
- **GIO Threading Docs**: https://docs.gtk.org/gio/class.VolumeMonitor.html
26
-
27
- > "GVolumeMonitor is not thread-default-context aware and should not be used other than from the main thread."
28
-
29
- This codebase only uses thread-safe APIs (`g_unix_mounts_get()` and related functions)
30
- because all native code runs on `Napi::AsyncWorker` threads.
31
-
32
- ### g_unix_mounts_get
33
-
34
- - **Docs**: https://docs.gtk.org/gio/func.unix_mounts_get.html
35
- - **Purpose**: Thread-safe enumeration of mounted filesystems
36
- - **Returns**: `GList*` of `GUnixMountEntry*` (caller owns both)
37
- - **Cleanup**: `g_list_free_full(list, (GDestroyNotify)g_unix_mount_free)`
38
-
39
- ```cpp
40
- GList *mounts = g_unix_mounts_get(nullptr);
41
- for (GList *l = mounts; l != nullptr; l = l->next) {
42
- GUnixMountEntry *entry = static_cast<GUnixMountEntry*>(l->data);
43
- const char *path = g_unix_mount_get_mount_path(entry);
44
- const char *fstype = g_unix_mount_get_fs_type(entry);
45
- }
46
- g_list_free_full(mounts, reinterpret_cast<GDestroyNotify>(g_unix_mount_free));
47
- ```
48
-
49
- **Source**: https://gitlab.gnome.org/GNOME/glib/-/blob/main/gio/gunixmounts.c
50
-
51
- ### GUnixMountEntry Functions
52
-
53
- - **Docs**: https://docs.gtk.org/gio-unix/struct.MountEntry.html
54
- - `g_unix_mount_get_mount_path()` - Returns `const char*` (borrowed)
55
- - `g_unix_mount_get_fs_type()` - Returns `const char*` (borrowed)
56
- - `g_unix_mount_get_device_path()` - Returns `const char*` (borrowed)
57
- - `g_unix_mount_free()` - Free a single entry
58
-
59
- **Note**: String returns are borrowed - do NOT `g_free()` them.
60
-
61
- ### g_volume_monitor_get (NOT USED)
62
-
63
- > ⚠️ **NOT USED IN THIS CODEBASE**: GVolumeMonitor is NOT thread-safe and has been
64
- > removed from fs-metadata. This section is retained for reference only.
65
-
66
- - **Docs**: https://docs.gtk.org/gio/type_func.VolumeMonitor.get.html
67
- - **Purpose**: Get system volume monitor for rich metadata
68
- - **Returns**: Owned `GVolumeMonitor*` reference
69
- - **Cleanup**: **Must** call `g_object_unref()` when done
70
-
71
- **Why Not Used**: GVolumeMonitor can only be safely used from the main thread.
72
- Node.js native addons use `Napi::AsyncWorker` which runs on libuv thread pool
73
- worker threads. Using GVolumeMonitor from these threads causes race conditions:
74
-
75
- ```
76
- GLib-GObject-CRITICAL: g_object_ref: assertion '!object_already_finalized' failed
77
- ```
78
-
79
- The thread-safe `g_unix_mounts_get()` API provides sufficient metadata (fstype,
80
- mountFrom). Rich metadata (label, uri) is obtained from blkid instead.
81
-
82
- ### g_volume_monitor_get_mounts (NOT USED)
83
-
84
- > ⚠️ **NOT USED**: See above - GVolumeMonitor is not thread-safe.
85
-
86
- - **Docs**: https://docs.gtk.org/gio/method.VolumeMonitor.get_mounts.html
87
- - **Returns**: `GList*` of `GMount*` (caller owns list and references)
88
- - **Cleanup**: `g_list_free_full(mounts, g_object_unref)`
89
-
90
- ### GMount / GVolume / GFile Functions (NOT USED)
91
-
92
- > ⚠️ **NOT USED**: These require GVolumeMonitor which is not thread-safe.
93
-
94
- | Function | Returns | Ownership |
95
- | -------------------------------- | ---------- | ------------------- |
96
- | `g_mount_get_root()` | `GFile*` | Caller owns, unref |
97
- | `g_mount_get_volume()` | `GVolume*` | Caller owns, unref |
98
- | `g_mount_get_name()` | `char*` | Caller owns, g_free |
99
- | `g_mount_get_default_location()` | `GFile*` | Caller owns, unref |
100
- | `g_file_get_path()` | `char*` | Caller owns, g_free |
101
- | `g_file_get_uri()` | `char*` | Caller owns, g_free |
102
- | `g_volume_get_name()` | `char*` | Caller owns, g_free |
103
-
104
- **Memory Management Docs**: https://docs.gtk.org/gobject/concepts.html#reference-counting
105
-
106
- ### GLib Memory Functions
107
-
108
- | Function | Purpose |
109
- | -------------------- | ------------------------------------- |
110
- | `g_object_unref()` | Decrement GObject reference count |
111
- | `g_free()` | Free GLib-allocated memory |
112
- | `g_list_free()` | Free GList container only |
113
- | `g_list_free_full()` | Free GList and elements with callback |
114
-
115
- **Docs**: https://docs.gtk.org/glib/func.free.html
7
+ 1. [libblkid APIs](#libblkid-apis)
8
+ 2. [POSIX File System APIs](#posix-file-system-apis)
9
+ 3. [RAII Patterns](#raii-patterns)
10
+ 4. [References](#references)
116
11
 
117
12
  ## libblkid APIs
118
13
 
@@ -221,32 +116,6 @@ if (fd < 0) {
221
116
 
222
117
  ## RAII Patterns
223
118
 
224
- ### GIO Smart Pointers (gio_utils.h)
225
-
226
- ```cpp
227
- // Custom deleters
228
- template <typename T> struct GObjectDeleter {
229
- void operator()(T *ptr) const { if (ptr) g_object_unref(ptr); }
230
- };
231
-
232
- struct GFreeDeleter {
233
- void operator()(void *ptr) const { if (ptr) g_free(ptr); }
234
- };
235
-
236
- // Smart pointer aliases
237
- template <typename T> using GObjectPtr = std::unique_ptr<T, GObjectDeleter<T>>;
238
-
239
- using GFilePtr = GObjectPtr<GFile>;
240
- using GMountPtr = GObjectPtr<GMount>;
241
- using GVolumePtr = GObjectPtr<GVolume>;
242
- using GVolumeMonitorPtr = GObjectPtr<GVolumeMonitor>; // Not currently used
243
- using GCharPtr = std::unique_ptr<char, GFreeDeleter>;
244
- ```
245
-
246
- > **Note**: `GVolumeMonitorPtr`, `GMountPtr`, `GVolumePtr`, and `GFilePtr` are
247
- > defined but not currently used because GVolumeMonitor is not thread-safe.
248
- > They are retained for potential future use if a main-thread-only API is added.
249
-
250
119
  ### BlkidCache RAII
251
120
 
252
121
  ```cpp
@@ -285,18 +154,6 @@ FdGuard guard{fd};
285
154
 
286
155
  ## References
287
156
 
288
- ### GIO/GLib Official Documentation
289
-
290
- - [GIO Reference](https://docs.gtk.org/gio/)
291
- - [GLib Reference](https://docs.gtk.org/glib/)
292
- - [GObject Memory Management](https://docs.gtk.org/gobject/concepts.html#reference-counting)
293
- - [GVolumeMonitor](https://docs.gtk.org/gio/class.VolumeMonitor.html)
294
- - [Unix Mounts](https://docs.gtk.org/gio-unix/struct.MountEntry.html)
295
-
296
- ### GLib Source Code
297
-
298
- - [gunixmounts.c](https://gitlab.gnome.org/GNOME/glib/-/blob/main/gio/gunixmounts.c) - Thread-safe mount enumeration implementation
299
-
300
157
  ### libblkid / util-linux
301
158
 
302
159
  - [libblkid(3) man page](https://man7.org/linux/man-pages/man3/libblkid.3.html)
@@ -5,6 +5,10 @@
5
5
  **Scope**: Complete codebase review including API verification against official documentation
6
6
  **Previous Audit**: October-December 2025 (see `doc/SECURITY_AUDIT_2025.md`)
7
7
 
8
+ > **Post-audit note (April 2026):** The Linux GIO/GLib integration discussed in
9
+ > re-verification findings #6 and #7 has since been removed entirely. See the
10
+ > "Removed" section of `CHANGELOG.md` for the rationale.
11
+
8
12
  ## Executive Summary
9
13
 
10
14
  This audit covers all changes since the December 2025 re-audit, focusing on the new
@@ -190,6 +194,7 @@ A caller could theoretically provide a malicious pattern causing ReDoS.
190
194
  **Files**: `src/windows/volume_mount_points.cpp`, `src/windows/volume_metadata.cpp`, `src/windows/system_volume.h`
191
195
 
192
196
  **Issue**: `GetVolumeInformationW` was called redundantly:
197
+
193
198
  - In `volume_mount_points.cpp`: once for `fstype`/`isReadOnly`, then again inside `IsSystemVolume()`
194
199
  - In `volume_metadata.cpp`: `IsSystemVolume()` queried the API, then `VolumeInfo` queried it again 5 lines later
195
200
 
package/doc/gotchas.md CHANGED
@@ -133,6 +133,33 @@ FROM debian:bullseye
133
133
  RUN apt-get update && apt-get install -y nodejs npm
134
134
  ```
135
135
 
136
+ #### Electron Consumers Need libblkid Headers
137
+
138
+ **Problem**: Electron apps that bundle `@photostructure/fs-metadata` fail to build on Linux with:
139
+
140
+ ```
141
+ fatal error: blkid/blkid.h: No such file or directory
142
+ ```
143
+
144
+ …even though `npm install` reports that prebuilds were found.
145
+
146
+ **Why it happens**: `@electron/rebuild` (invoked by `electron-forge`, `electron-builder`, and friends) **always recompiles native modules from source** against Electron's bundled Node ABI. The Node-ABI prebuilds shipped in `prebuilds/` are not Electron-compatible and are ignored. The fresh compile then needs the same system headers a from-source build needs.
147
+
148
+ **Solution**: Install `libblkid-dev` (and friends) on the build machine before running `electron-rebuild` / `electron-forge package`:
149
+
150
+ ```bash
151
+ # Debian/Ubuntu
152
+ sudo apt-get install -y libblkid-dev
153
+
154
+ # Fedora/RHEL
155
+ sudo dnf install -y libblkid-devel
156
+
157
+ # Alpine
158
+ apk add blkid-dev
159
+ ```
160
+
161
+ In CI, add this as a step before `npm install` / the Electron package step. The same applies to any consumer that compiles from source for an unsupported architecture or glibc version. See [CONTRIBUTING.md](../CONTRIBUTING.md#on-ubuntudebian) for the full development dependency list.
162
+
136
163
  #### System Volume Filtering
137
164
 
138
165
  Many mount points on Linux are system-only:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@photostructure/fs-metadata",
3
- "version": "1.2.0",
3
+ "version": "1.4.0",
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",
@@ -31,9 +31,8 @@
31
31
  "clean:gyp-cache": "del-cli --force build %USERPROFILE%/.node-gyp ~/.node-gyp",
32
32
  "clean:native": "node-gyp clean",
33
33
  "node-gyp-rebuild": "node-gyp rebuild",
34
- "setup:native": "node scripts/setup-native.mjs",
35
- "build": "run-s setup:native build:native build:dist",
36
- "build:native": "npm run setup:native && tsx scripts/prebuildify-wrapper.ts",
34
+ "build": "run-s build:native build:dist",
35
+ "build:native": "tsx scripts/prebuildify-wrapper.ts",
37
36
  "build:linux-glibc": "bash scripts/prebuild-linux-glibc.sh",
38
37
  "build:dist": "tsup && node scripts/post-build.mjs",
39
38
  "docs": "typedoc",
@@ -96,28 +95,28 @@
96
95
  },
97
96
  "devDependencies": {
98
97
  "@types/jest": "^30.0.0",
99
- "@types/node": "^25.5.0",
98
+ "@types/node": "^25.6.0",
100
99
  "@types/semver": "^7.7.1",
101
100
  "cross-env": "^10.1.0",
102
101
  "del-cli": "^7.0.0",
103
102
  "eslint": "9.39.1",
104
103
  "eslint-plugin-regexp": "^3.1.0",
105
104
  "eslint-plugin-security": "^4.0.0",
106
- "globals": "^17.4.0",
105
+ "globals": "^17.5.0",
107
106
  "jest": "^30.3.0",
108
107
  "jest-environment-node": "^30.3.0",
109
108
  "jest-extended": "^7.0.0",
110
109
  "node-gyp": "^12.2.0",
111
- "npm-check-updates": "^19.6.6",
110
+ "npm-check-updates": "^21.0.2",
112
111
  "npm-run-all2": "8.0.4",
113
112
  "prebuildify": "^6.0.1",
114
- "prettier": "^3.8.1",
113
+ "prettier": "^3.8.3",
115
114
  "prettier-plugin-organize-imports": "4.3.0",
116
115
  "terser": "^5.46.1",
117
- "ts-jest": "^29.4.6",
116
+ "ts-jest": "^29.4.9",
118
117
  "tsup": "^8.5.1",
119
118
  "tsx": "^4.21.0",
120
- "typedoc": "^0.28.18",
119
+ "typedoc": "^0.28.19",
121
120
  "typescript": "^5.9.3",
122
121
  "typescript-eslint": "^8.57.2"
123
122
  }
@@ -140,8 +140,6 @@ async function generateWindowsCompileCommands(): Promise<boolean> {
140
140
  console.log("Generating compile_commands.json for Windows...");
141
141
 
142
142
  try {
143
- execSync("npm run setup:native", { stdio: "inherit" });
144
-
145
143
  const nodeVersion = process.version.slice(1);
146
144
 
147
145
  // Try multiple possible locations for node-gyp headers
@@ -293,7 +291,7 @@ async function generateUnixCompileCommands(): Promise<void> {
293
291
  process.exit(1);
294
292
  }
295
293
 
296
- execSync("npm run setup:native && bear -- npm run node-gyp-rebuild", {
294
+ execSync("bear -- npm run node-gyp-rebuild", {
297
295
  stdio: "inherit",
298
296
  });
299
297
 
@@ -88,7 +88,7 @@ docker cp . "$CONTAINER_NAME:/tmp/project"
88
88
  docker exec "$CONTAINER_NAME" sh -c "
89
89
  cd /tmp/project && \
90
90
  apt-get update -qq && \
91
- apt-get install -y -qq build-essential python3 libglib2.0-dev libblkid-dev uuid-dev git && \
91
+ apt-get install -y -qq build-essential python3 libblkid-dev uuid-dev git && \
92
92
  # Verify versions
93
93
  echo 'Python version:' && python3 --version && \
94
94
  echo 'GCC version:' && gcc --version | head -1 && \
@@ -100,7 +100,6 @@ docker exec "$CONTAINER_NAME" sh -c "
100
100
  # Copy artifacts back
101
101
  docker cp "$CONTAINER_NAME:/tmp/project/prebuilds" . 2>/dev/null || true
102
102
  docker cp "$CONTAINER_NAME:/tmp/project/build" . 2>/dev/null || true
103
- docker cp "$CONTAINER_NAME:/tmp/project/config.gypi" . 2>/dev/null || true
104
103
 
105
104
  # Fix ownership (docker cp preserves container's root ownership)
106
105
  if [ -d prebuilds ]; then
@@ -109,9 +108,6 @@ fi
109
108
  if [ -d build ]; then
110
109
  chown -R "$(id -u):$(id -g)" build
111
110
  fi
112
- if [ -f config.gypi ]; then
113
- chown "$(id -u):$(id -g)" config.gypi
114
- fi
115
111
 
116
112
  # Clean up container
117
113
  docker rm -f "$CONTAINER_NAME" >/dev/null
@@ -76,7 +76,6 @@ fi
76
76
 
77
77
  # Build the native module
78
78
  echo "Building with AddressSanitizer..."
79
- npm run setup:native
80
79
  npm run clean:native
81
80
  npm run node-gyp-rebuild
82
81
 
package/src/binding.cpp CHANGED
@@ -12,10 +12,6 @@
12
12
  #include "darwin/hidden.h"
13
13
  #elif defined(__linux__)
14
14
  #include "common/volume_metadata.h"
15
- #ifdef ENABLE_GIO
16
- #include "linux/gio_mount_points.h"
17
- #include "linux/gio_volume_metadata.h"
18
- #endif
19
15
  #endif
20
16
 
21
17
  namespace {
@@ -43,13 +39,6 @@ Napi::Value SetDebugPrefix(const Napi::CallbackInfo &info) {
43
39
  return env.Undefined();
44
40
  }
45
41
 
46
- #ifdef ENABLE_GIO
47
- Napi::Value GetGioMountPoints(const Napi::CallbackInfo &info) {
48
- const Napi::Env env = info.Env();
49
- return FSMeta::gio::GetMountPoints(env);
50
- }
51
- #endif
52
-
53
42
  #if defined(_WIN32) || defined(__APPLE__)
54
43
  // Fix: Remove extra parameter and use correct signature
55
44
  Napi::Value GetVolumeMountPoints(const Napi::CallbackInfo &info) {
@@ -92,10 +81,6 @@ Napi::Object Init(Napi::Env env, Napi::Object exports) {
92
81
  exports.Set("getMountPoint", Napi::Function::New(env, GetMountPointForPath));
93
82
  #endif
94
83
 
95
- #ifdef ENABLE_GIO
96
- exports.Set("getGioMountPoints", Napi::Function::New(env, GetGioMountPoints));
97
- #endif
98
-
99
84
  #if defined(_WIN32) || defined(__APPLE__)
100
85
  exports.Set("isHidden", Napi::Function::New(env, GetHiddenAttribute));
101
86
  exports.Set("setHidden", Napi::Function::New(env, SetHiddenAttribute));
package/src/index.ts CHANGED
@@ -122,7 +122,9 @@ export function getVolumeMetadata(
122
122
  */
123
123
  export function getVolumeMetadataForPath(
124
124
  pathname: string,
125
- opts?: Partial<Pick<Options, "timeoutMs" | "linuxMountTablePaths">>,
125
+ opts?: Partial<
126
+ Pick<Options, "timeoutMs" | "linuxMountTablePaths" | "mountPoints">
127
+ >,
126
128
  ): Promise<VolumeMetadata> {
127
129
  return getVolumeMetadataForPathImpl(
128
130
  pathname,
@@ -148,7 +150,9 @@ export function getVolumeMetadataForPath(
148
150
  */
149
151
  export function getMountPointForPath(
150
152
  pathname: string,
151
- opts?: Partial<Pick<Options, "timeoutMs" | "linuxMountTablePaths">>,
153
+ opts?: Partial<
154
+ Pick<Options, "timeoutMs" | "linuxMountTablePaths" | "mountPoints">
155
+ >,
152
156
  ): Promise<string> {
153
157
  return getMountPointForPathImpl(
154
158
  pathname,
@@ -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 {
@@ -113,18 +109,6 @@ public:
113
109
  mountPoint.c_str(), metadata.size / 1e9,
114
110
  metadata.available / 1e9);
115
111
 
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
112
  if (!options_.device.empty()) {
129
113
  DEBUG_LOG("[LinuxMetadataWorker] getting blkid info for device %s",
130
114
  options_.device.c_str());
@@ -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
@@ -1,5 +1,7 @@
1
1
  // src/types/options.ts
2
2
 
3
+ import type { MountPoint } from "./mount_point";
4
+
3
5
  /**
4
6
  * Configuration options for filesystem operations.
5
7
  *
@@ -7,6 +9,18 @@
7
9
  * @see {@link OptionsDefault} for the default values
8
10
  */
9
11
  export interface Options {
12
+ /**
13
+ * Pre-fetched mount points to use instead of querying the system.
14
+ *
15
+ * When provided, functions like {@link getMountPointForPath} and
16
+ * {@link getVolumeMetadataForPath} will use these mount points for device ID
17
+ * matching instead of calling {@link getVolumeMountPoints} internally. This
18
+ * avoids redundant system queries when resolving multiple paths.
19
+ *
20
+ * Obtain via `getVolumeMountPoints({ includeSystemVolumes: true })` — system
21
+ * volumes must be included for device ID matching to work correctly.
22
+ */
23
+ mountPoints?: MountPoint[];
10
24
  /**
11
25
  * Timeout in milliseconds for filesystem operations.
12
26
  *