@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
@@ -1,39 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- // scripts/setup-native.mjs
4
-
5
- // This script sets up the config.gypi file to include GIO support when available.
6
- // It should be run before building native modules with node-gyp.
7
-
8
- import { execSync } from "node:child_process";
9
- import { writeFileSync } from "node:fs";
10
- import { platform } from "node:os";
11
- import { argv } from "node:process";
12
- import { pathToFileURL } from "node:url";
13
-
14
- function hasGio() {
15
- if (platform() !== "linux") return false;
16
- try {
17
- execSync("pkg-config --exists gio-2.0", { stdio: "ignore" });
18
- return true;
19
- } catch {
20
- return false;
21
- }
22
- }
23
-
24
- export function configure() {
25
- // Create a gyp config file that node-gyp will read
26
- const config = {
27
- variables: {
28
- "enable_gio%": hasGio() ? "true" : "false",
29
- },
30
- };
31
-
32
- const payload = JSON.stringify(config, null, 2);
33
- writeFileSync("config.gypi", payload);
34
- }
35
-
36
- // If the script is run directly, call the configure function
37
- if (import.meta.url === pathToFileURL(argv[1]).href) {
38
- configure();
39
- }
@@ -1,80 +0,0 @@
1
- // src/linux/gio_mount_points.cpp
2
- #ifdef ENABLE_GIO
3
-
4
- #include "gio_mount_points.h"
5
- #include "../common/debug_log.h"
6
- #include "gio_utils.h"
7
- #include <gio/gio.h>
8
- #include <memory>
9
- #include <stdexcept>
10
-
11
- namespace FSMeta {
12
- namespace gio {
13
-
14
- GioMountPointsWorker::GioMountPointsWorker(
15
- const Napi::Promise::Deferred &deferred)
16
- : Napi::AsyncWorker(deferred.Env()), deferred_(deferred) {}
17
-
18
- GioMountPointsWorker::~GioMountPointsWorker() { mountPoints.clear(); }
19
-
20
- void GioMountPointsWorker::Execute() {
21
- try {
22
- DEBUG_LOG("[GioMountPoints] processing mounts");
23
-
24
- // Use thread-safe g_unix_mounts_get() API
25
- MountIterator::forEachMount([this](GUnixMountEntry *entry) {
26
- // Get mount path and filesystem type from thread-safe Unix mount API
27
- const char *mount_path = g_unix_mount_get_mount_path(entry);
28
- const char *fs_type = g_unix_mount_get_fs_type(entry);
29
-
30
- if (mount_path && fs_type) {
31
- DEBUG_LOG("[GioMountPoints] found {mountPoint: %s, fsType: %s}",
32
- mount_path, fs_type);
33
-
34
- MountPoint point{};
35
- point.mountPoint = mount_path;
36
- point.fstype = fs_type;
37
- mountPoints.push_back(point);
38
- } else {
39
- DEBUG_LOG("[GioMountPoints] skipping mount with null path or fstype");
40
- }
41
-
42
- return true; // Continue iteration
43
- });
44
-
45
- DEBUG_LOG("[GioMountPoints] found %zu mount points", mountPoints.size());
46
- } catch (const std::exception &e) {
47
- DEBUG_LOG("[GioMountPoints] error: %s", e.what());
48
- SetError(e.what());
49
- }
50
- }
51
-
52
- void GioMountPointsWorker::OnOK() {
53
- const Napi::HandleScope scope(Env());
54
- const Napi::Array result = Napi::Array::New(Env());
55
-
56
- for (size_t i = 0; i < mountPoints.size(); i++) {
57
- const Napi::Object point = Napi::Object::New(Env());
58
- point.Set("mountPoint", mountPoints[i].mountPoint);
59
- point.Set("fstype", mountPoints[i].fstype);
60
- result.Set(i, point);
61
- }
62
-
63
- deferred_.Resolve(result);
64
- }
65
-
66
- void GioMountPointsWorker::OnError(const Napi::Error &error) {
67
- deferred_.Reject(error.Value());
68
- }
69
-
70
- Napi::Value GetMountPoints(Napi::Env env) {
71
- auto deferred = Napi::Promise::Deferred::New(env);
72
- auto *worker = new GioMountPointsWorker(deferred);
73
- worker->Queue();
74
- return deferred.Promise();
75
- }
76
-
77
- } // namespace gio
78
- } // namespace FSMeta
79
-
80
- #endif // ENABLE_GIO
@@ -1,37 +0,0 @@
1
- // src/linux/gio_mount_points.h
2
-
3
- #pragma once
4
-
5
- #ifdef ENABLE_GIO
6
-
7
- #include "../common/volume_mount_points.h"
8
- #include <napi.h>
9
- #include <string>
10
- #include <vector>
11
-
12
- namespace FSMeta {
13
- namespace gio {
14
-
15
- /**
16
- * Get mount points asynchronously using GIO
17
- */
18
- Napi::Value GetMountPoints(Napi::Env env);
19
-
20
- class GioMountPointsWorker : public Napi::AsyncWorker {
21
- public:
22
- explicit GioMountPointsWorker(const Napi::Promise::Deferred &deferred);
23
- ~GioMountPointsWorker() override; // Add override specifier
24
-
25
- void Execute() override;
26
- void OnOK() override;
27
- void OnError(const Napi::Error &error) override;
28
-
29
- private:
30
- std::vector<MountPoint> mountPoints;
31
- Napi::Promise::Deferred deferred_;
32
- };
33
-
34
- } // namespace gio
35
- } // namespace FSMeta
36
-
37
- #endif // ENABLE_GIO
@@ -1,115 +0,0 @@
1
- // src/linux/gio_utils.cpp
2
- //
3
- // Thread-Safe Mount Enumeration for Linux
4
- //
5
- // This implementation uses g_unix_mounts_get() as the primary, thread-safe path
6
- // for enumerating mounts. GVolumeMonitor is optionally used for enrichment but
7
- // is NOT required for correct operation.
8
- //
9
- // IMPORTANT THREAD SAFETY NOTES:
10
- //
11
- // According to GIO documentation
12
- // (https://docs.gtk.org/gio/class.VolumeMonitor.html): "GVolumeMonitor is not
13
- // thread-default-context aware and so should not be used other than from the
14
- // main thread, with no thread-default-context active."
15
- //
16
- // However, g_unix_mounts_get() is explicitly thread-safe:
17
- // - Uses getmntent_r() when available (reentrant)
18
- // - Falls back to getmntent() with G_LOCK protection
19
- // See: https://gitlab.gnome.org/GNOME/glib/-/blob/main/gio/gunixmounts.c
20
- //
21
- // This design:
22
- // ✅ Primary path uses thread-safe g_unix_mounts_get()
23
- // ✅ Optional GVolumeMonitor enhancement (best-effort, may be skipped)
24
- // ✅ Fixes Finding #6 (thread safety violation)
25
- // ✅ Fixes Finding #7 (double-free risk with g_list_free_full)
26
-
27
- #ifdef ENABLE_GIO
28
-
29
- #include "gio_utils.h"
30
- #include "../common/debug_log.h"
31
- #include <gio/gio.h>
32
- #include <gio/gunixmounts.h>
33
- #include <memory>
34
- #include <stdexcept>
35
-
36
- namespace FSMeta {
37
- namespace gio {
38
-
39
- void MountIterator::forEachMount(const MountCallback &callback) {
40
- // PRIMARY PATH: Thread-safe Unix mount enumeration
41
- // g_unix_mounts_get() is documented as thread-safe and can be called
42
- // from worker threads without violating GIO threading requirements.
43
- GList *unix_mounts = g_unix_mounts_get(nullptr);
44
-
45
- if (!unix_mounts) {
46
- DEBUG_LOG("[gio::MountIterator::forEachMount] no mounts found");
47
- return;
48
- }
49
-
50
- DEBUG_LOG("[gio::MountIterator::forEachMount] processing Unix mounts");
51
-
52
- // Iterate over all Unix mounts
53
- GList *current = unix_mounts;
54
- bool should_continue = true;
55
-
56
- while (current && should_continue) {
57
- GUnixMountEntry *entry = static_cast<GUnixMountEntry *>(current->data);
58
-
59
- if (!entry) {
60
- DEBUG_LOG("[gio::MountIterator::forEachMount] Skipping null entry");
61
- current = current->next;
62
- continue;
63
- }
64
-
65
- try {
66
- // Get mount path from thread-safe Unix mount API
67
- const char *mount_path = g_unix_mount_get_mount_path(entry);
68
- if (!mount_path) {
69
- DEBUG_LOG(
70
- "[gio::MountIterator::forEachMount] Skipping mount with null path");
71
- current = current->next;
72
- continue;
73
- }
74
-
75
- DEBUG_LOG("[gio::MountIterator::forEachMount] processing mount: %s",
76
- mount_path);
77
-
78
- // Invoke callback with Unix mount entry
79
- // The callback receives the entry and can extract data using
80
- // g_unix_mount_get_* functions
81
- should_continue = callback(entry);
82
-
83
- } catch (const std::exception &e) {
84
- DEBUG_LOG("[gio::MountIterator::forEachMount] Exception during mount "
85
- "processing: %s",
86
- e.what());
87
- // Clean up and re-throw
88
- g_list_free_full(unix_mounts,
89
- reinterpret_cast<GDestroyNotify>(g_unix_mount_free));
90
- throw;
91
- }
92
-
93
- current = current->next;
94
- }
95
-
96
- // Free list and all mount entries
97
- // Each entry is freed with g_unix_mount_free() - no double-free risk
98
- g_list_free_full(unix_mounts,
99
- reinterpret_cast<GDestroyNotify>(g_unix_mount_free));
100
-
101
- DEBUG_LOG("[gio::MountIterator::forEachMount] completed");
102
- }
103
-
104
- // NOTE: tryGetMonitor() has been removed.
105
- //
106
- // GVolumeMonitor is NOT thread-safe and must only be used from the main thread.
107
- // See: https://docs.gtk.org/gio/class.VolumeMonitor.html
108
- //
109
- // Since all our code runs on Napi::AsyncWorker threads, using GVolumeMonitor
110
- // causes race conditions leading to GLib-GObject-CRITICAL errors.
111
-
112
- } // namespace gio
113
- } // namespace FSMeta
114
-
115
- #endif // ENABLE_GIO
@@ -1,69 +0,0 @@
1
- // src/linux/gio_utils.h
2
- #pragma once
3
-
4
- #ifdef ENABLE_GIO
5
-
6
- #include <gio/gio.h>
7
- #include <gio/gunixmounts.h>
8
- #include <napi.h>
9
- #include <string>
10
- #include <vector>
11
-
12
- // Custom deleter for GObject types using g_object_unref
13
- template <typename T> struct GObjectDeleter {
14
- void operator()(T *ptr) const {
15
- if (ptr) {
16
- g_object_unref(ptr);
17
- }
18
- }
19
- };
20
-
21
- // Custom deleter for g_free (used for strings from GIO APIs like
22
- // g_file_get_path)
23
- struct GFreeDeleter {
24
- void operator()(void *ptr) const {
25
- if (ptr) {
26
- g_free(ptr);
27
- }
28
- }
29
- };
30
-
31
- // Smart pointer aliases for RAII management of GIO resources
32
- // These ensure proper cleanup even when exceptions occur
33
- template <typename T> using GObjectPtr = std::unique_ptr<T, GObjectDeleter<T>>;
34
-
35
- // Common GIO object types
36
- using GFilePtr = GObjectPtr<GFile>;
37
- using GMountPtr = GObjectPtr<GMount>;
38
- using GVolumePtr = GObjectPtr<GVolume>;
39
- using GVolumeMonitorPtr = GObjectPtr<GVolumeMonitor>;
40
- using GFileInfoPtr = GObjectPtr<GFileInfo>;
41
-
42
- // For strings allocated by GIO (g_file_get_path, g_file_get_uri, etc.)
43
- using GCharPtr = std::unique_ptr<char, GFreeDeleter>;
44
-
45
- namespace FSMeta {
46
- namespace gio {
47
-
48
- class MountIterator {
49
- public:
50
- // Callback type for mount processing
51
- // Receives GUnixMountEntry which provides thread-safe access to mount data
52
- // Return true to continue iteration, false to stop
53
- using MountCallback = std::function<bool(GUnixMountEntry *)>;
54
-
55
- // Static method to iterate over mounts using thread-safe g_unix_mounts_get()
56
- // This is safe to call from worker threads
57
- static void forEachMount(const MountCallback &callback);
58
-
59
- // NOTE: tryGetMonitor() has been removed because GVolumeMonitor is NOT
60
- // thread-safe. See: https://docs.gtk.org/gio/class.VolumeMonitor.html
61
- };
62
-
63
- // Note: GioResource<T> has been removed in favor of GObjectPtr<T> above,
64
- // which provides equivalent RAII semantics with std::unique_ptr.
65
-
66
- } // namespace gio
67
- } // namespace FSMeta
68
-
69
- #endif // ENABLE_GIO
@@ -1,81 +0,0 @@
1
- // src/linux/gio_volume_metadata.cpp
2
-
3
- #ifdef ENABLE_GIO
4
-
5
- #include "gio_volume_metadata.h"
6
- #include "../common/debug_log.h"
7
- #include "gio_utils.h"
8
- #include <gio/gio.h>
9
- #include <memory>
10
- #include <stdexcept>
11
-
12
- namespace FSMeta {
13
- namespace gio {
14
-
15
- void addMountMetadata(const std::string &mountPoint, VolumeMetadata &metadata) {
16
- DEBUG_LOG("[gio::addMountMetadata] getting mount metadata for %s",
17
- mountPoint.c_str());
18
-
19
- bool found = false;
20
-
21
- // PRIMARY PATH: Thread-safe Unix mount API
22
- MountIterator::forEachMount([&](GUnixMountEntry *entry) {
23
- const char *mount_path = g_unix_mount_get_mount_path(entry);
24
- if (!mount_path || mountPoint != mount_path) {
25
- return true; // Continue iteration
26
- }
27
-
28
- // Found matching mount point
29
- DEBUG_LOG("[gio::addMountMetadata] found matching mount point: %s",
30
- mount_path);
31
- found = true;
32
-
33
- // Get basic metadata from thread-safe Unix mount API
34
- if (metadata.fstype.empty()) {
35
- const char *fs_type = g_unix_mount_get_fs_type(entry);
36
- if (fs_type) {
37
- DEBUG_LOG("[gio::addMountMetadata] {mountPoint: %s, fsType: %s}",
38
- mount_path, fs_type);
39
- metadata.fstype = fs_type;
40
- }
41
- }
42
-
43
- if (metadata.mountFrom.empty()) {
44
- const char *device_path = g_unix_mount_get_device_path(entry);
45
- if (device_path) {
46
- DEBUG_LOG("[gio::addMountMetadata] {mountPoint: %s, mountFrom: %s}",
47
- mount_path, device_path);
48
- metadata.mountFrom = device_path;
49
- }
50
- }
51
-
52
- // NOTE: GVolumeMonitor enrichment has been removed.
53
- //
54
- // According to GIO documentation:
55
- // https://docs.gtk.org/gio/class.VolumeMonitor.html
56
- // "GVolumeMonitor is not thread-default-context aware and so should not
57
- // be used other than from the main thread, with no thread-default-context
58
- // active."
59
- //
60
- // This function is called from Napi::AsyncWorker::Execute() which runs
61
- // on a worker thread. Using GVolumeMonitor here causes race conditions
62
- // leading to GLib-GObject-CRITICAL errors like:
63
- // "g_object_ref: assertion '!object_already_finalized' failed"
64
- //
65
- // The basic metadata (fstype, mountFrom) from g_unix_mounts_get() is
66
- // sufficient and thread-safe. Rich metadata (label, mountName, uri) can
67
- // be obtained from blkid or other thread-safe sources.
68
-
69
- return false; // Stop iteration, we found our mount
70
- });
71
-
72
- if (!found) {
73
- DEBUG_LOG("[gio::addMountMetadata] mount point %s not found",
74
- mountPoint.c_str());
75
- }
76
- }
77
-
78
- } // namespace gio
79
- } // namespace FSMeta
80
-
81
- #endif // ENABLE_GIO
@@ -1,20 +0,0 @@
1
- // src/linux/gio_volume_metadata.h
2
-
3
- #pragma once
4
-
5
- #ifdef ENABLE_GIO
6
-
7
- #include "../common/volume_metadata.h"
8
- #include <string>
9
-
10
- namespace FSMeta {
11
- namespace gio {
12
- /**
13
- * Add metadata from GIO to the volume metadata
14
- */
15
- void addMountMetadata(const std::string &mountPoint, VolumeMetadata &metadata);
16
-
17
- } // namespace gio
18
- } // namespace FSMeta
19
-
20
- #endif // ENABLE_GIO