@photostructure/fs-metadata 1.3.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 (39) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/CONTRIBUTING.md +3 -3
  3. package/binding.gyp +0 -22
  4. package/dist/index.cjs +10 -39
  5. package/dist/index.cjs.map +1 -1
  6. package/dist/index.d.cts +1 -1
  7. package/dist/index.d.mts +1 -1
  8. package/dist/index.d.ts +1 -1
  9. package/dist/index.mjs +10 -39
  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 +4 -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/linux/mount_points.ts +8 -42
  27. package/src/linux/volume_metadata.cpp +0 -16
  28. package/src/mount_point_for_path.ts +1 -1
  29. package/src/types/mount_point.ts +1 -1
  30. package/src/types/native_bindings.ts +0 -5
  31. package/src/volume_metadata.ts +16 -7
  32. package/src/volume_mount_points.ts +1 -1
  33. package/scripts/setup-native.mjs +0 -39
  34. package/src/linux/gio_mount_points.cpp +0 -80
  35. package/src/linux/gio_mount_points.h +0 -37
  36. package/src/linux/gio_utils.cpp +0 -115
  37. package/src/linux/gio_utils.h +0 -69
  38. package/src/linux/gio_volume_metadata.cpp +0 -81
  39. 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
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.3.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));
@@ -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
@@ -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