@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.
- package/CHANGELOG.md +31 -0
- package/CONTRIBUTING.md +3 -3
- package/binding.gyp +0 -22
- package/dist/index.cjs +10 -39
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.mts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.mjs +10 -39
- package/dist/index.mjs.map +1 -1
- package/doc/C++_REVIEW_TODO.md +3 -36
- package/doc/LINUX_API_REFERENCE.md +4 -147
- package/doc/SECURITY_AUDIT_2026.md +4 -0
- package/doc/gotchas.md +27 -0
- package/package.json +9 -10
- 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-x64/@photostructure+fs-metadata.glibc.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/scripts/clang-tidy.ts +1 -3
- package/scripts/prebuild-linux-glibc.sh +1 -5
- package/scripts/sanitizers-test.sh +0 -1
- package/src/binding.cpp +0 -15
- package/src/linux/mount_points.ts +8 -42
- package/src/linux/volume_metadata.cpp +0 -16
- package/src/mount_point_for_path.ts +1 -1
- package/src/types/mount_point.ts +1 -1
- package/src/types/native_bindings.ts +0 -5
- package/src/volume_metadata.ts +16 -7
- package/src/volume_mount_points.ts +1 -1
- package/scripts/setup-native.mjs +0 -39
- package/src/linux/gio_mount_points.cpp +0 -80
- package/src/linux/gio_mount_points.h +0 -37
- package/src/linux/gio_utils.cpp +0 -115
- package/src/linux/gio_utils.h +0 -69
- package/src/linux/gio_volume_metadata.cpp +0 -81
- package/src/linux/gio_volume_metadata.h +0 -20
package/doc/C++_REVIEW_TODO.md
CHANGED
|
@@ -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
|
-
|
|
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~~ ✅
|
|
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. [
|
|
8
|
-
2. [
|
|
9
|
-
3. [
|
|
10
|
-
4. [
|
|
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
|
+
"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
|
-
"
|
|
35
|
-
"build": "
|
|
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.
|
|
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.
|
|
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": "^
|
|
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.
|
|
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.
|
|
116
|
+
"ts-jest": "^29.4.9",
|
|
118
117
|
"tsup": "^8.5.1",
|
|
119
118
|
"tsx": "^4.21.0",
|
|
120
|
-
"typedoc": "^0.28.
|
|
119
|
+
"typedoc": "^0.28.19",
|
|
121
120
|
"typescript": "^5.9.3",
|
|
122
121
|
"typescript-eslint": "^8.57.2"
|
|
123
122
|
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/scripts/clang-tidy.ts
CHANGED
|
@@ -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("
|
|
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
|
|
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
|
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
|
|
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,
|
|
37
|
-
if (
|
|
38
|
-
|
|
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
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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
|
|
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
|
}
|
package/src/types/mount_point.ts
CHANGED
|
@@ -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
|
|
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
|
package/src/volume_metadata.ts
CHANGED
|
@@ -95,7 +95,8 @@ async function _getVolumeMetadata(
|
|
|
95
95
|
}
|
|
96
96
|
} catch (err) {
|
|
97
97
|
debug("[getVolumeMetadata] failed to get mtab info: " + err);
|
|
98
|
-
//
|
|
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
|
|
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
|
|
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
|
|
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
|
-
*
|
|
228
|
-
*
|
|
229
|
-
*
|
|
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(
|