@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.
- package/AGENTS.md +213 -0
- package/CHANGELOG.md +54 -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 +11 -12
- package/prebuilds/darwin-arm64/@photostructure+fs-metadata.glibc.node +0 -0
- package/prebuilds/darwin-x64/@photostructure+fs-metadata.glibc.node +0 -0
- package/prebuilds/linux-arm64/@photostructure+fs-metadata.glibc.node +0 -0
- package/prebuilds/linux-arm64/@photostructure+fs-metadata.musl.node +0 -0
- package/prebuilds/linux-x64/@photostructure+fs-metadata.glibc.node +0 -0
- package/prebuilds/linux-x64/@photostructure+fs-metadata.musl.node +0 -0
- package/prebuilds/win32-arm64/@photostructure+fs-metadata.glibc.node +0 -0
- package/prebuilds/win32-x64/@photostructure+fs-metadata.glibc.node +0 -0
- package/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 +5 -15
- package/src/common/metadata_worker.h +7 -6
- package/src/common/shutdown.h +162 -0
- package/src/darwin/get_mount_point.cpp +6 -4
- package/src/darwin/hidden.cpp +9 -8
- package/src/darwin/hidden.h +4 -3
- package/src/darwin/volume_metadata.cpp +15 -1
- package/src/darwin/volume_mount_points.cpp +26 -4
- package/src/linux/mount_points.ts +8 -42
- package/src/linux/volume_metadata.cpp +5 -17
- 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/src/windows/hidden.cpp +10 -9
- package/src/windows/volume_metadata.cpp +5 -1
- package/src/windows/volume_mount_points.cpp +22 -4
- 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.1",
|
|
4
4
|
"description": "Cross-platform native filesystem metadata retrieval for Node.js",
|
|
5
5
|
"homepage": "https://photostructure.github.io/fs-metadata/",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -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
|
-
"node-gyp": "^12.
|
|
111
|
-
"npm-check-updates": "^
|
|
109
|
+
"node-gyp": "^12.3.0",
|
|
110
|
+
"npm-check-updates": "^22.0.1",
|
|
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
|
-
"terser": "^5.46.
|
|
117
|
-
"ts-jest": "^29.4.
|
|
115
|
+
"terser": "^5.46.2",
|
|
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
|
|
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
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
#include <string>
|
|
4
4
|
|
|
5
5
|
#include "common/debug_log.h"
|
|
6
|
+
#include "common/shutdown.h"
|
|
6
7
|
#if defined(_WIN32)
|
|
7
8
|
#include "windows/fs_meta.h"
|
|
8
9
|
#include "windows/hidden.h"
|
|
@@ -12,10 +13,6 @@
|
|
|
12
13
|
#include "darwin/hidden.h"
|
|
13
14
|
#elif defined(__linux__)
|
|
14
15
|
#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
16
|
#endif
|
|
20
17
|
|
|
21
18
|
namespace {
|
|
@@ -43,13 +40,6 @@ Napi::Value SetDebugPrefix(const Napi::CallbackInfo &info) {
|
|
|
43
40
|
return env.Undefined();
|
|
44
41
|
}
|
|
45
42
|
|
|
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
43
|
#if defined(_WIN32) || defined(__APPLE__)
|
|
54
44
|
// Fix: Remove extra parameter and use correct signature
|
|
55
45
|
Napi::Value GetVolumeMountPoints(const Napi::CallbackInfo &info) {
|
|
@@ -78,6 +68,10 @@ Napi::Value SetHiddenAttribute(const Napi::CallbackInfo &info) {
|
|
|
78
68
|
#endif
|
|
79
69
|
|
|
80
70
|
Napi::Object Init(Napi::Env env, Napi::Object exports) {
|
|
71
|
+
// Register a cleanup hook so in-flight native workers can short-circuit
|
|
72
|
+
// during env teardown instead of racing FreeEnvironment.
|
|
73
|
+
FSMeta::EnsureShutdownHook(env);
|
|
74
|
+
|
|
81
75
|
exports.Set("setDebugLogging", Napi::Function::New(env, SetDebugLogging));
|
|
82
76
|
exports.Set("setDebugPrefix", Napi::Function::New(env, SetDebugPrefix));
|
|
83
77
|
|
|
@@ -92,10 +86,6 @@ Napi::Object Init(Napi::Env env, Napi::Object exports) {
|
|
|
92
86
|
exports.Set("getMountPoint", Napi::Function::New(env, GetMountPointForPath));
|
|
93
87
|
#endif
|
|
94
88
|
|
|
95
|
-
#ifdef ENABLE_GIO
|
|
96
|
-
exports.Set("getGioMountPoints", Napi::Function::New(env, GetGioMountPoints));
|
|
97
|
-
#endif
|
|
98
|
-
|
|
99
89
|
#if defined(_WIN32) || defined(__APPLE__)
|
|
100
90
|
exports.Set("isHidden", Napi::Function::New(env, GetHiddenAttribute));
|
|
101
91
|
exports.Set("setHidden", Napi::Function::New(env, SetHiddenAttribute));
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
// src/common/metadata_worker.h
|
|
2
2
|
#pragma once
|
|
3
|
+
#include "./shutdown.h"
|
|
3
4
|
#include "./volume_metadata.h"
|
|
4
5
|
#include <napi.h>
|
|
5
6
|
|
|
6
7
|
namespace FSMeta {
|
|
7
8
|
|
|
8
|
-
class MetadataWorkerBase : public
|
|
9
|
+
class MetadataWorkerBase : public SafeAsyncWorker {
|
|
9
10
|
protected:
|
|
10
11
|
std::string mountPoint;
|
|
11
12
|
VolumeMetadata metadata;
|
|
@@ -13,17 +14,17 @@ protected:
|
|
|
13
14
|
|
|
14
15
|
MetadataWorkerBase(const std::string &path,
|
|
15
16
|
const Napi::Promise::Deferred &deferred)
|
|
16
|
-
:
|
|
17
|
-
deferred_(deferred) {}
|
|
17
|
+
: SafeAsyncWorker(deferred.Env()), mountPoint(path), deferred_(deferred) {}
|
|
18
18
|
|
|
19
19
|
void OnError(const Napi::Error &error) override {
|
|
20
|
-
|
|
20
|
+
Napi::HandleScope scope(Env());
|
|
21
|
+
SafeReject(deferred_, error.Value());
|
|
21
22
|
}
|
|
22
23
|
|
|
23
24
|
void OnOK() override {
|
|
24
25
|
Napi::HandleScope scope(Env());
|
|
25
|
-
deferred_
|
|
26
|
+
SafeResolve(deferred_, metadata.ToObject(Env()));
|
|
26
27
|
}
|
|
27
28
|
}; // class MetadataWorkerBase
|
|
28
29
|
|
|
29
|
-
} // namespace FSMeta
|
|
30
|
+
} // namespace FSMeta
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
// src/common/shutdown.h
|
|
2
|
+
// Shutdown-safety helpers: a per-env flag set during Node env teardown,
|
|
3
|
+
// plus deferred Resolve/Reject wrappers that swallow C++ Napi errors so they
|
|
4
|
+
// can never escape AsyncWorker callbacks.
|
|
5
|
+
//
|
|
6
|
+
// Why this exists: AsyncWorker::OnWorkComplete can run during
|
|
7
|
+
// node::FreeEnvironment cleanup. If napi_resolve_deferred / napi_reject_deferred
|
|
8
|
+
// fail at that point (env tearing down), node-addon-api throws a C++
|
|
9
|
+
// Napi::Error. With NAPI_CPP_EXCEPTIONS the rethrow path inside
|
|
10
|
+
// WrapVoidCallback then calls ThrowAsJavaScriptException, which can also fail,
|
|
11
|
+
// letting the C++ exception escape into a libuv cleanup hook frame that has
|
|
12
|
+
// no catch - terminate() / SIGABRT.
|
|
13
|
+
//
|
|
14
|
+
// The flag is stored as napi instance data so worker threads (each their own
|
|
15
|
+
// env) don't poison the main env's flag when they tear down.
|
|
16
|
+
//
|
|
17
|
+
|
|
18
|
+
#pragma once
|
|
19
|
+
|
|
20
|
+
#include <atomic>
|
|
21
|
+
#include <memory>
|
|
22
|
+
#include <napi.h>
|
|
23
|
+
#include <string>
|
|
24
|
+
|
|
25
|
+
namespace FSMeta {
|
|
26
|
+
|
|
27
|
+
struct ShutdownState {
|
|
28
|
+
std::atomic<bool> shuttingDown{false};
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
struct ModuleInstanceData {
|
|
32
|
+
std::shared_ptr<ShutdownState> shutdownState =
|
|
33
|
+
std::make_shared<ShutdownState>();
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
inline ModuleInstanceData *GetInstanceData(napi_env env) {
|
|
37
|
+
void *raw = nullptr;
|
|
38
|
+
if (napi_get_instance_data(env, &raw) != napi_ok) {
|
|
39
|
+
return nullptr;
|
|
40
|
+
}
|
|
41
|
+
return static_cast<ModuleInstanceData *>(raw);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
inline bool IsShuttingDown(napi_env env) {
|
|
45
|
+
if (auto *d = GetInstanceData(env)) {
|
|
46
|
+
return d->shutdownState->shuttingDown.load(std::memory_order_acquire);
|
|
47
|
+
}
|
|
48
|
+
// No instance data registered (test harness, etc.) - fail safe: not shutting.
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
inline std::shared_ptr<ShutdownState> GetShutdownState(napi_env env) {
|
|
53
|
+
if (auto *d = GetInstanceData(env)) {
|
|
54
|
+
return d->shutdownState;
|
|
55
|
+
}
|
|
56
|
+
return nullptr;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
inline bool IsShuttingDown(const std::shared_ptr<ShutdownState> &state) {
|
|
60
|
+
return state != nullptr &&
|
|
61
|
+
state->shuttingDown.load(std::memory_order_acquire);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Registers per-env shutdown state and a cleanup hook that flips the flag.
|
|
65
|
+
// Idempotent per env: binding.cpp Init runs once per env load.
|
|
66
|
+
inline void EnsureShutdownHook(napi_env env) {
|
|
67
|
+
if (GetInstanceData(env) != nullptr) {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
auto *data = new ModuleInstanceData();
|
|
71
|
+
auto *cleanupState = new std::shared_ptr<ShutdownState>(data->shutdownState);
|
|
72
|
+
napi_status status = napi_set_instance_data(
|
|
73
|
+
env, data,
|
|
74
|
+
[](napi_env /*env*/, void *raw, void * /*hint*/) {
|
|
75
|
+
delete static_cast<ModuleInstanceData *>(raw);
|
|
76
|
+
},
|
|
77
|
+
nullptr);
|
|
78
|
+
if (status != napi_ok) {
|
|
79
|
+
delete cleanupState;
|
|
80
|
+
delete data;
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
status = napi_add_env_cleanup_hook(
|
|
84
|
+
env,
|
|
85
|
+
[](void *arg) {
|
|
86
|
+
auto *state = static_cast<std::shared_ptr<ShutdownState> *>(arg);
|
|
87
|
+
(*state)->shuttingDown.store(true, std::memory_order_release);
|
|
88
|
+
delete state;
|
|
89
|
+
},
|
|
90
|
+
cleanupState);
|
|
91
|
+
if (status != napi_ok) {
|
|
92
|
+
delete cleanupState;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Wrap deferred.Resolve so a teardown-time napi failure cannot escape the
|
|
97
|
+
// AsyncWorker callback as an uncaught C++ exception.
|
|
98
|
+
inline void SafeResolve(const Napi::Promise::Deferred &deferred,
|
|
99
|
+
napi_value value) {
|
|
100
|
+
try {
|
|
101
|
+
deferred.Resolve(value);
|
|
102
|
+
} catch (...) {
|
|
103
|
+
// Env is tearing down; the JS-side promise is unobservable anyway.
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
inline void SafeReject(const Napi::Promise::Deferred &deferred,
|
|
108
|
+
napi_value value) {
|
|
109
|
+
try {
|
|
110
|
+
deferred.Reject(value);
|
|
111
|
+
} catch (...) {
|
|
112
|
+
// Env is tearing down; the JS-side promise is unobservable anyway.
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
class SafeAsyncWorker : public Napi::AsyncWorker {
|
|
117
|
+
public:
|
|
118
|
+
void OnExecute(Napi::Env /*env*/) override {
|
|
119
|
+
try {
|
|
120
|
+
Execute();
|
|
121
|
+
} catch (const std::exception &e) {
|
|
122
|
+
SetError(e.what());
|
|
123
|
+
} catch (...) {
|
|
124
|
+
SetError("Unknown native error");
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
void OnWorkComplete(Napi::Env env, napi_status status) override {
|
|
129
|
+
if (status != napi_cancelled && !IsShuttingDown()) {
|
|
130
|
+
try {
|
|
131
|
+
Napi::HandleScope scope(env);
|
|
132
|
+
if (status != napi_ok) {
|
|
133
|
+
OnError(Napi::Error::New(env));
|
|
134
|
+
} else if (error_.empty()) {
|
|
135
|
+
OnOK();
|
|
136
|
+
} else {
|
|
137
|
+
OnError(Napi::Error::New(env, error_));
|
|
138
|
+
}
|
|
139
|
+
} catch (...) {
|
|
140
|
+
// Env teardown can make value construction, handle scopes, or deferred
|
|
141
|
+
// resolution fail. The JS promise is no longer observable then.
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
Destroy();
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
protected:
|
|
148
|
+
explicit SafeAsyncWorker(Napi::Env env)
|
|
149
|
+
: Napi::AsyncWorker(env), shutdownState_(GetShutdownState(env)) {}
|
|
150
|
+
|
|
151
|
+
void SetError(const std::string &error) { error_ = error; }
|
|
152
|
+
|
|
153
|
+
bool IsShuttingDown() const {
|
|
154
|
+
return FSMeta::IsShuttingDown(shutdownState_);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
private:
|
|
158
|
+
std::shared_ptr<ShutdownState> shutdownState_;
|
|
159
|
+
std::string error_;
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
} // namespace FSMeta
|