@photostructure/fs-metadata 0.6.1 → 0.7.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 +7 -1
- package/CLAUDE.md +141 -315
- package/CODE_OF_CONDUCT.md +11 -11
- package/CONTRIBUTING.md +1 -1
- package/README.md +34 -103
- package/binding.gyp +97 -22
- package/claude.sh +23 -0
- package/dist/index.cjs +51 -21
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +5 -0
- package/dist/index.d.mts +5 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.mjs +51 -21
- package/dist/index.mjs.map +1 -1
- package/doc/C++_REVIEW_TODO.md +97 -25
- package/doc/GPG_RELEASE_HOWTO.md +44 -13
- package/doc/MACOS_API_REFERENCE.md +469 -0
- package/doc/SECURITY_AUDIT_2025.md +809 -0
- package/doc/SSH_RELEASE_HOWTO.md +28 -24
- package/doc/WINDOWS_API_REFERENCE.md +422 -0
- package/doc/WINDOWS_ARM64_SECURITY.md +161 -0
- package/doc/WINDOWS_DEBUG_GUIDE.md +9 -2
- package/doc/examples.md +267 -0
- package/doc/gotchas.md +297 -0
- package/doc/logo.png +0 -0
- package/doc/logo.svg +85 -0
- package/doc/macos-asan-sip-issue.md +71 -0
- package/doc/social.png +0 -0
- package/doc/social.svg +125 -0
- package/doc/windows-build.md +226 -0
- package/doc/windows-clang-tidy.md +72 -0
- package/doc/windows-memory-testing.md +108 -0
- package/doc/windows-prebuildify-arm64.md +232 -0
- package/jest.config.cjs +23 -0
- package/package.json +61 -36
- 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/check-memory.ts +186 -0
- package/scripts/clang-tidy.ts +690 -99
- package/scripts/install.cjs +42 -0
- package/scripts/is-platform.mjs +1 -1
- package/scripts/macos-asan.sh +155 -0
- package/scripts/post-build.mjs +3 -3
- package/scripts/prebuild-linux-glibc.sh +12 -1
- package/scripts/prebuildify-wrapper.ts +77 -0
- package/scripts/precommit.ts +45 -20
- package/scripts/sanitizers-test.sh +1 -1
- package/src/common/volume_metadata.h +6 -0
- package/src/darwin/hidden.cpp +73 -25
- package/src/darwin/path_security.h +149 -0
- package/src/darwin/raii_utils.h +104 -4
- package/src/darwin/volume_metadata.cpp +132 -58
- package/src/darwin/volume_mount_points.cpp +80 -47
- package/src/hidden.ts +36 -13
- package/src/linux/gio_mount_points.cpp +17 -18
- package/src/linux/gio_utils.cpp +92 -37
- package/src/linux/gio_utils.h +11 -5
- package/src/linux/gio_volume_metadata.cpp +111 -48
- package/src/linux/volume_metadata.cpp +67 -4
- package/src/object.ts +1 -0
- package/src/options.ts +6 -0
- package/src/path.ts +11 -0
- package/src/remote_info.ts +5 -3
- package/src/stack_path.ts +8 -6
- package/src/string_enum.ts +1 -0
- package/src/test-utils/memory-test-core.ts +336 -0
- package/src/test-utils/memory-test-runner.ts +108 -0
- package/src/test-utils/platform.ts +46 -1
- package/src/test-utils/worker-thread-helper.cjs +154 -27
- package/src/types/native_bindings.ts +1 -1
- package/src/types/options.ts +6 -0
- package/src/windows/drive_status.h +133 -163
- package/src/windows/error_utils.h +54 -3
- package/src/windows/fs_meta.h +1 -1
- package/src/windows/hidden.cpp +60 -43
- package/src/windows/security_utils.h +250 -0
- package/src/windows/string.h +68 -11
- package/src/windows/system_volume.h +1 -1
- package/src/windows/thread_pool.h +206 -0
- package/src/windows/volume_metadata.cpp +11 -6
- package/src/windows/volume_mount_points.cpp +8 -7
- package/src/windows/windows_arch.h +39 -0
- package/scripts/check-memory.mjs +0 -123
package/doc/gotchas.md
ADDED
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
# Gotchas and Platform-Specific Issues
|
|
2
|
+
|
|
3
|
+
This guide covers common issues, platform quirks, and important considerations when using `@photostructure/fs-metadata`.
|
|
4
|
+
|
|
5
|
+
## Timeout Issues
|
|
6
|
+
|
|
7
|
+
### Network Volumes Can Hang
|
|
8
|
+
|
|
9
|
+
**Problem**: Linux, macOS, and Windows can block system calls indefinitely when network filesystems are unhealthy.
|
|
10
|
+
|
|
11
|
+
**Solution**: Always use timeouts for network volumes:
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
// Default timeout may be too short for network drives
|
|
15
|
+
const metadata = await getVolumeMetadata("\\\\nas\\share", {
|
|
16
|
+
timeoutMs: 30000, // 30 seconds
|
|
17
|
+
});
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
**Why it happens**:
|
|
21
|
+
|
|
22
|
+
- Windows: SMB shares can block when the remote host is down
|
|
23
|
+
- Linux: NFS mounts without `soft` option will retry forever
|
|
24
|
+
- macOS: AFP/SMB shares may hang during network interruptions
|
|
25
|
+
|
|
26
|
+
### Optical Drives
|
|
27
|
+
|
|
28
|
+
Optical drives (CD/DVD) can take 30+ seconds to spin up:
|
|
29
|
+
|
|
30
|
+
```typescript
|
|
31
|
+
const metadata = await getVolumeMetadata("D:\\", {
|
|
32
|
+
timeoutMs: 45000, // 45 seconds for optical drives
|
|
33
|
+
});
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Platform-Specific Gotchas
|
|
37
|
+
|
|
38
|
+
### Windows
|
|
39
|
+
|
|
40
|
+
#### UNC Paths and Mapped Drives
|
|
41
|
+
|
|
42
|
+
Mapped network drives may not appear in volume listings:
|
|
43
|
+
|
|
44
|
+
```typescript
|
|
45
|
+
// This might not show mapped drives
|
|
46
|
+
const volumes = await getVolumeMountPoints();
|
|
47
|
+
|
|
48
|
+
// Use UNC path directly instead
|
|
49
|
+
const metadata = await getVolumeMetadata("\\\\server\\share");
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
#### System Volume Detection
|
|
53
|
+
|
|
54
|
+
`C:\` is both a system volume and user storage. The library returns it in all queries:
|
|
55
|
+
|
|
56
|
+
```typescript
|
|
57
|
+
const volumes = await getAllVolumeMetadata({ includeSystemVolumes: false });
|
|
58
|
+
// C:\ will still be included on Windows
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
#### Long Path Support
|
|
62
|
+
|
|
63
|
+
Windows 10+ supports paths longer than 260 characters (MAX_PATH) when long path support is enabled:
|
|
64
|
+
|
|
65
|
+
```typescript
|
|
66
|
+
// Paths up to 32,768 characters are now supported
|
|
67
|
+
const longPath = "C:\\" + "verylongdirectoryname\\".repeat(20) + "file.txt";
|
|
68
|
+
const metadata = await getVolumeMetadata(longPath);
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
**Requirements**:
|
|
72
|
+
|
|
73
|
+
- Windows 10 version 1607 or later
|
|
74
|
+
- Long path support enabled in registry or manifest
|
|
75
|
+
- Path must not exceed PATHCCH_MAX_CCH (32,768 wide characters)
|
|
76
|
+
|
|
77
|
+
**Enabling long paths** (administrator required):
|
|
78
|
+
|
|
79
|
+
```powershell
|
|
80
|
+
# Set registry key
|
|
81
|
+
New-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Control\FileSystem" `
|
|
82
|
+
-Name "LongPathsEnabled" -Value 1 -PropertyType DWORD -Force
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
**Note**: Even without long path support enabled system-wide, the library handles paths up to 32,768 characters internally and fails gracefully on older systems.
|
|
86
|
+
|
|
87
|
+
#### Build Issues
|
|
88
|
+
|
|
89
|
+
If you see "No Target Architecture" errors when building from source, ensure Visual Studio build tools are properly installed. See [Windows Build Guide](./windows-build.md).
|
|
90
|
+
|
|
91
|
+
### Linux
|
|
92
|
+
|
|
93
|
+
#### Docker Containers
|
|
94
|
+
|
|
95
|
+
The `node:20` Docker image is **not supported** due to GLIBC version requirements:
|
|
96
|
+
|
|
97
|
+
```dockerfile
|
|
98
|
+
# ❌ Won't work
|
|
99
|
+
FROM node:20
|
|
100
|
+
|
|
101
|
+
# ✅ Use this instead
|
|
102
|
+
FROM node:20-bullseye
|
|
103
|
+
# or
|
|
104
|
+
FROM debian:bullseye
|
|
105
|
+
RUN apt-get update && apt-get install -y nodejs npm
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
#### System Volume Filtering
|
|
109
|
+
|
|
110
|
+
Many mount points on Linux are system-only:
|
|
111
|
+
|
|
112
|
+
```typescript
|
|
113
|
+
// This filters out /proc, /sys, /dev, snap mounts, etc.
|
|
114
|
+
const userVolumes = await getAllVolumeMetadata({ includeSystemVolumes: false });
|
|
115
|
+
|
|
116
|
+
// To see everything:
|
|
117
|
+
const allVolumes = await getAllVolumeMetadata({ includeSystemVolumes: true });
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
#### GVfs/FUSE Mounts
|
|
121
|
+
|
|
122
|
+
User-mounted volumes (like Google Drive, SMB shares via Nautilus) appear under `/run/user/*/gvfs`:
|
|
123
|
+
|
|
124
|
+
```typescript
|
|
125
|
+
const volumes = await getVolumeMountPoints();
|
|
126
|
+
// May include entries like:
|
|
127
|
+
// /run/user/1000/gvfs/smb-share:server=nas,share=documents
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### macOS
|
|
131
|
+
|
|
132
|
+
#### APFS Containers
|
|
133
|
+
|
|
134
|
+
APFS volumes in the same container share space:
|
|
135
|
+
|
|
136
|
+
```typescript
|
|
137
|
+
const volumes = await getAllVolumeMetadata();
|
|
138
|
+
// Multiple volumes might report identical 'available' space
|
|
139
|
+
// because they share the same APFS container
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
#### System Integrity Protection (SIP)
|
|
143
|
+
|
|
144
|
+
Memory debugging tools like AddressSanitizer may fail due to SIP:
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
# This might not work on macOS with SIP enabled
|
|
148
|
+
ASAN_OPTIONS=detect_leaks=1 npm test
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
## Hidden Files Gotchas
|
|
152
|
+
|
|
153
|
+
### POSIX vs Native Behavior
|
|
154
|
+
|
|
155
|
+
Hidden file operations behave differently per platform:
|
|
156
|
+
|
|
157
|
+
```typescript
|
|
158
|
+
// On Windows: Sets hidden attribute
|
|
159
|
+
await setHidden("C:\\file.txt", true);
|
|
160
|
+
// File remains at: C:\file.txt (hidden)
|
|
161
|
+
|
|
162
|
+
// On Linux/macOS: Renames file
|
|
163
|
+
await setHidden("/home/user/file.txt", true);
|
|
164
|
+
// File moved to: /home/user/.file.txt
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
### Already Hidden Files
|
|
168
|
+
|
|
169
|
+
Setting an already-hidden file to hidden is a no-op:
|
|
170
|
+
|
|
171
|
+
```typescript
|
|
172
|
+
// No error, no change
|
|
173
|
+
await setHidden("/path/to/.hidden", true);
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
### Invalid Paths
|
|
177
|
+
|
|
178
|
+
Dot-prefixing can create invalid paths:
|
|
179
|
+
|
|
180
|
+
```typescript
|
|
181
|
+
// This will fail - can't hide root directory
|
|
182
|
+
await setHidden("/", true); // Error!
|
|
183
|
+
|
|
184
|
+
// This will fail - parent directory in path
|
|
185
|
+
await setHidden("/path/../file", true); // Error!
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
## Memory and Resource Management
|
|
189
|
+
|
|
190
|
+
### Handle Leaks on Windows
|
|
191
|
+
|
|
192
|
+
Each volume check uses a separate thread on Windows. With many volumes:
|
|
193
|
+
|
|
194
|
+
```typescript
|
|
195
|
+
// This creates one thread per volume
|
|
196
|
+
const volumes = await getVolumeMountPoints();
|
|
197
|
+
|
|
198
|
+
// For systems with many volumes, use smaller batches
|
|
199
|
+
const BATCH_SIZE = 10;
|
|
200
|
+
for (let i = 0; i < mountPoints.length; i += BATCH_SIZE) {
|
|
201
|
+
const batch = mountPoints.slice(i, i + BATCH_SIZE);
|
|
202
|
+
await Promise.all(batch.map((mp) => getVolumeMetadata(mp.mountPoint)));
|
|
203
|
+
}
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
## Testing Gotchas
|
|
207
|
+
|
|
208
|
+
### File System State
|
|
209
|
+
|
|
210
|
+
Tests can fail due to file system state changes:
|
|
211
|
+
|
|
212
|
+
```typescript
|
|
213
|
+
// ❌ Bad: Assumes exact values
|
|
214
|
+
expect(metadata.available).toBe(1000000);
|
|
215
|
+
|
|
216
|
+
// ✅ Good: Checks types and ranges
|
|
217
|
+
expect(typeof metadata.available).toBe("number");
|
|
218
|
+
expect(metadata.available).toBeGreaterThanOrEqual(0);
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
### Timing Issues
|
|
222
|
+
|
|
223
|
+
File operations may not be immediately visible:
|
|
224
|
+
|
|
225
|
+
```typescript
|
|
226
|
+
// After creating a file
|
|
227
|
+
await fs.writeFile(path, data);
|
|
228
|
+
// May need a small delay on some systems
|
|
229
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
230
|
+
const hidden = await isHidden(path);
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
### Cross-Platform Testing
|
|
234
|
+
|
|
235
|
+
What works on one platform may fail on another:
|
|
236
|
+
|
|
237
|
+
```typescript
|
|
238
|
+
// Windows: This is valid
|
|
239
|
+
const metadata = await getVolumeMetadata("C:"); // No trailing slash
|
|
240
|
+
|
|
241
|
+
// Linux/macOS: Must have trailing slash
|
|
242
|
+
const metadata2 = await getVolumeMetadata("/"); // Trailing slash required
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
## Error Messages
|
|
246
|
+
|
|
247
|
+
### Common Errors and Solutions
|
|
248
|
+
|
|
249
|
+
| Error | Cause | Solution |
|
|
250
|
+
| ----------------------------- | ---------------------- | -------------------------------- |
|
|
251
|
+
| `ENOENT` | Path doesn't exist | Check path exists before calling |
|
|
252
|
+
| `EACCES` | Permission denied | Run with appropriate permissions |
|
|
253
|
+
| `ETIMEDOUT` | Operation timed out | Increase `timeoutMs` option |
|
|
254
|
+
| `Invalid mountPoint` | Empty or invalid path | Validate input before calling |
|
|
255
|
+
| `statvfs failed` | Linux filesystem issue | Check mount is accessible |
|
|
256
|
+
| `GetVolumeInformation failed` | Windows API error | Verify drive letter is correct |
|
|
257
|
+
|
|
258
|
+
### Platform-Specific Error Patterns
|
|
259
|
+
|
|
260
|
+
```typescript
|
|
261
|
+
try {
|
|
262
|
+
await getVolumeMetadata(mountPoint);
|
|
263
|
+
} catch (error) {
|
|
264
|
+
if (
|
|
265
|
+
process.platform === "win32" &&
|
|
266
|
+
error.message.includes("cannot find the path")
|
|
267
|
+
) {
|
|
268
|
+
// Windows-specific path error
|
|
269
|
+
} else if (
|
|
270
|
+
process.platform === "linux" &&
|
|
271
|
+
error.message.includes("statvfs")
|
|
272
|
+
) {
|
|
273
|
+
// Linux-specific filesystem error
|
|
274
|
+
} else if (error.code === "ETIMEDOUT") {
|
|
275
|
+
// Cross-platform timeout
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
## Debug Mode
|
|
281
|
+
|
|
282
|
+
Enable debug logging to troubleshoot issues:
|
|
283
|
+
|
|
284
|
+
```bash
|
|
285
|
+
# Linux/macOS
|
|
286
|
+
NODE_DEBUG=fs-meta npm test
|
|
287
|
+
|
|
288
|
+
# Windows
|
|
289
|
+
set NODE_DEBUG=fs-meta && npm test
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
Debug output includes:
|
|
293
|
+
|
|
294
|
+
- Native API calls and their results
|
|
295
|
+
- Timeout occurrences
|
|
296
|
+
- Thread creation/destruction (Windows)
|
|
297
|
+
- Memory allocation tracking (debug builds)
|
package/doc/logo.png
ADDED
|
Binary file
|
package/doc/logo.svg
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
|
2
|
+
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
|
3
|
+
|
|
4
|
+
<svg
|
|
5
|
+
width="150"
|
|
6
|
+
height="165"
|
|
7
|
+
viewBox="0 0 37.5 41.25"
|
|
8
|
+
version="1.1"
|
|
9
|
+
id="svg1"
|
|
10
|
+
inkscape:version="1.4.2 (7335b4f457, 2025-06-11)"
|
|
11
|
+
sodipodi:docname="logo.svg"
|
|
12
|
+
xml:space="preserve"
|
|
13
|
+
inkscape:export-filename="logo.png"
|
|
14
|
+
inkscape:export-xdpi="734.07092"
|
|
15
|
+
inkscape:export-ydpi="734.07092"
|
|
16
|
+
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
|
17
|
+
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
|
18
|
+
xmlns:xlink="http://www.w3.org/1999/xlink"
|
|
19
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
20
|
+
xmlns:svg="http://www.w3.org/2000/svg"><sodipodi:namedview
|
|
21
|
+
id="namedview1"
|
|
22
|
+
pagecolor="#505050"
|
|
23
|
+
bordercolor="#ffffff"
|
|
24
|
+
borderopacity="1"
|
|
25
|
+
inkscape:showpageshadow="0"
|
|
26
|
+
inkscape:pageopacity="0"
|
|
27
|
+
inkscape:pagecheckerboard="1"
|
|
28
|
+
inkscape:deskcolor="#505050"
|
|
29
|
+
inkscape:document-units="mm"
|
|
30
|
+
inkscape:zoom="7.6368504"
|
|
31
|
+
inkscape:cx="39.61057"
|
|
32
|
+
inkscape:cy="62.65672"
|
|
33
|
+
inkscape:window-width="3774"
|
|
34
|
+
inkscape:window-height="2075"
|
|
35
|
+
inkscape:window-x="66"
|
|
36
|
+
inkscape:window-y="46"
|
|
37
|
+
inkscape:window-maximized="1"
|
|
38
|
+
inkscape:current-layer="g1"
|
|
39
|
+
showgrid="false" /><defs
|
|
40
|
+
id="defs1"><inkscape:path-effect
|
|
41
|
+
effect="offset"
|
|
42
|
+
id="path-effect14"
|
|
43
|
+
is_visible="true"
|
|
44
|
+
lpeversion="1.3"
|
|
45
|
+
linejoin_type="bevel"
|
|
46
|
+
unit="mm"
|
|
47
|
+
offset="0.65"
|
|
48
|
+
miter_limit="4"
|
|
49
|
+
attempt_force_join="false"
|
|
50
|
+
update_on_knot_move="true" /><linearGradient
|
|
51
|
+
inkscape:collect="always"
|
|
52
|
+
xlink:href="#a"
|
|
53
|
+
id="linearGradient503"
|
|
54
|
+
gradientUnits="userSpaceOnUse"
|
|
55
|
+
gradientTransform="matrix(0.15514043,0.00118797,-6.1719265e-4,0.08060071,59.744983,-84.81034)"
|
|
56
|
+
x1="352.15787"
|
|
57
|
+
y1="1902.979"
|
|
58
|
+
x2="562.99939"
|
|
59
|
+
y2="2219.3118" /><linearGradient
|
|
60
|
+
id="a"><stop
|
|
61
|
+
offset="0"
|
|
62
|
+
stop-color="#4ed8ff"
|
|
63
|
+
id="stop869" /><stop
|
|
64
|
+
offset="1"
|
|
65
|
+
stop-color="#ff14b3"
|
|
66
|
+
id="stop871" /></linearGradient></defs><g
|
|
67
|
+
inkscape:label="Layer 1"
|
|
68
|
+
inkscape:groupmode="layer"
|
|
69
|
+
id="layer1"
|
|
70
|
+
transform="translate(-110.28523,-61.796263)"><g
|
|
71
|
+
id="g1"><path
|
|
72
|
+
style="fill:#ffffff;stroke-width:0.0735"
|
|
73
|
+
d="m 129.89285,100.11556 c 0.16568,-0.0848 3.50065,-2.033848 13.46043,-7.866705 1.2326,-0.721858 1.38946,-0.828757 1.54506,-1.052961 0.25688,-0.370117 0.24282,0.101444 0.2593,-8.69347 l 0.0152,-8.10339 -0.11102,-0.243072 c -0.22445,-0.491384 -0.35893,-0.585758 -3.20734,-2.250908 -0.74956,-0.438185 -2.10682,-1.233241 -3.01614,-1.766785 -9.39657,-5.513485 -9.08628,-5.341062 -9.55645,-5.310353 -0.39795,0.02599 -0.1182,-0.122569 -3.56274,1.89189 -1.58711,0.928187 -4.38553,2.563026 -6.2187,3.632972 -1.83318,1.069955 -3.79609,2.21582 -4.36204,2.546364 -1.10806,0.647167 -1.38937,0.870732 -1.54253,1.225892 l -0.0933,0.216288 -0.007,8.134267 c -0.008,9.14498 -0.0359,8.398632 0.3324,8.808711 0.12048,0.134159 0.37587,0.319901 0.69551,0.505863 0.27782,0.161619 1.06814,0.623956 1.75627,1.027413 9.67576,5.673002 12.46814,7.292594 12.71234,7.373184 0.17446,0.0576 0.7321,0.011 0.90056,-0.0752 z"
|
|
74
|
+
id="path507" /><g
|
|
75
|
+
id="g3"
|
|
76
|
+
transform="translate(-0.18434968,0.2733591)"><g
|
|
77
|
+
id="g2" /></g><path
|
|
78
|
+
style="font-variation-settings:normal;baseline-shift:baseline;display:inline;overflow:visible;vector-effect:none;fill:url(#linearGradient503);fill-opacity:1;stroke:none;stroke-width:0.40255;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;-inkscape-stroke:none;paint-order:markers fill stroke;enable-background:accumulate;stop-color:#000000"
|
|
79
|
+
d="m 129.33172,62.474478 c -0.71044,1.1e-5 -1.42066,0.183282 -2.05579,0.549994 l -13.79801,7.966458 c -1.27025,0.7334 -2.05589,2.094602 -2.05587,3.561363 l 2.2e-4,15.932551 c 3e-5,1.466762 0.78586,2.827676 2.05613,3.561036 l 13.79792,7.9661 c 1.27027,0.73336 2.84204,0.73339 4.11228,-10e-6 l 13.79771,-7.966467 c 1.27024,-0.733401 2.05621,-2.094599 2.05619,-3.561361 l -2.2e-4,-15.932551 c -3e-5,-1.466762 -0.78586,-2.827677 -2.05613,-3.561037 L 131.3879,63.024459 c -0.63513,-0.36668 -1.34573,-0.549992 -2.05618,-0.549981 z m 1.5e-4,2.901126 c 0.20823,-1.7e-5 0.41672,0.05417 0.60481,0.162766 l 13.79793,7.966096 c 0.37616,0.217181 0.60465,0.612905 0.60465,1.047267 l 5.2e-4,15.932556 c 3e-5,0.434362 -0.22845,0.829959 -0.60461,1.047145 l -13.79803,7.966771 c -0.37618,0.217183 -0.8331,0.217053 -1.20927,-1.31e-4 L 114.92962,91.53198 c -0.37618,-0.217181 -0.60463,-0.612908 -0.60463,-1.04727 l -2.2e-4,-15.932553 c -3e-5,-0.434362 0.22845,-0.829956 0.60461,-1.047145 l 13.79803,-7.966771 c 0.18804,-0.108591 0.39619,-0.162635 0.60446,-0.162637 z"
|
|
80
|
+
id="path491" /><g
|
|
81
|
+
id="g55"
|
|
82
|
+
transform="translate(379.31824,236.94698)" /><path
|
|
83
|
+
style="fill:#4d4d4d;stroke-width:0.999997"
|
|
84
|
+
d="m 120.85241,90.551072 c -0.45299,-0.09328 -0.79404,-0.422443 -0.89813,-0.866853 -0.0621,-0.26508 -0.0763,-13.953019 -0.0148,-14.285543 0.091,-0.492226 0.41156,-0.824672 0.88082,-0.913448 0.29206,-0.05525 10.78459,-0.03219 10.94812,0.02407 0.63278,0.217685 0.76542,0.561655 0.76542,1.984801 v 1.000462 l -0.29725,0.07246 c -0.51586,0.125741 -1.09108,0.373025 -1.53774,0.661066 l -0.21213,0.136798 -0.19868,-0.281488 c -2.19097,-3.104235 -6.71525,-2.835562 -8.38363,0.49786 -1.00594,2.009853 -0.41679,4.554218 1.38799,5.994355 l 0.1737,0.138609 -0.24672,0.179248 c -1.20959,0.878785 -1.53259,1.219731 -1.64021,1.731391 -0.284,1.350272 1.16088,2.501333 2.37043,1.888405 0.36477,-0.184843 0.53472,-0.388294 1.6257,-1.946225 l 0.73137,-1.044418 0.30973,-0.02175 c 0.77593,-0.05448 1.61755,-0.342932 2.26247,-0.775413 l 0.2174,-0.145789 0.0521,0.101912 c 0.56284,1.100421 1.93935,2.12349 3.18749,2.369046 l 0.19796,0.03895 v 1.226304 c 0,1.693212 -0.0716,1.905324 -0.73912,2.190342 -0.15532,0.06631 -10.62955,0.109256 -10.94229,0.04486 z m 17.99613,-1.323393 c -0.053,-0.01853 -0.14485,-0.06985 -0.20406,-0.114034 -0.0817,-0.06096 -1.98866,-2.182614 -2.75837,-3.068928 l -0.10232,-0.117834 -0.22667,0.124593 c -4.29848,2.362779 -8.65328,-3.174125 -5.28242,-6.71632 3.68598,-3.873346 9.83783,1.322125 6.59037,5.565821 l -0.0899,0.117491 1.22951,1.184015 c 2.02565,1.950702 1.92194,1.831883 1.94623,2.229731 0.0357,0.584343 -0.5328,0.994551 -1.10236,0.795465 z m -4.6792,-3.880733 c 1.65687,-0.408997 2.73353,-2.187831 2.30537,-3.808918 -0.74639,-2.826001 -4.57042,-3.239705 -5.88053,-0.63619 -1.18315,2.351227 1.01018,5.078269 3.57516,4.445108 z m -11.30125,2.827817 c -0.56259,-0.202446 -0.85879,-0.618408 -0.85879,-1.206033 0,-0.606531 0.0238,-0.632744 1.77898,-1.95568 2.55695,-1.92731 2.60031,-1.955752 2.87046,-1.883007 0.4561,0.122816 0.456,0.446978 -3.1e-4,1.102836 -0.17382,0.24981 -0.45815,0.663886 -0.63185,0.92017 -0.98722,1.456598 -1.72916,2.4843 -1.96674,2.724232 -0.33413,0.337426 -0.77492,0.447463 -1.19171,0.297482 z m 2.9654,-5.638293 c -1.49942,-0.347288 -1.98448,-2.183253 -0.84845,-3.211308 1.18818,-1.075228 3.12684,-0.211478 3.12684,1.393126 0,1.168493 -1.14352,2.081037 -2.27839,1.818182 z m 0.68416,-1.236936 c 0.44447,-0.229159 0.52309,-0.764738 0.15917,-1.084261 -0.42752,-0.375368 -1.09629,-0.07738 -1.09123,0.48623 0.005,0.486031 0.51449,0.81333 0.93206,0.598031 z"
|
|
85
|
+
id="path74" /></g></g></svg>
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# macOS AddressSanitizer and System Integrity Protection (SIP) Issue
|
|
2
|
+
|
|
3
|
+
## Problem
|
|
4
|
+
|
|
5
|
+
When running tests with AddressSanitizer (ASAN) on macOS, Jest worker processes fail with:
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
ERROR: Interceptors are not working. This may be because AddressSanitizer is loaded too late (e.g. via dlopen).
|
|
9
|
+
A jest worker process was terminated by another process: signal=SIGABRT, exitCode=null.
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## Root Cause
|
|
13
|
+
|
|
14
|
+
macOS System Integrity Protection (SIP) strips `DYLD_*` environment variables (including `DYLD_INSERT_LIBRARIES`) from child processes for security reasons. Since Jest spawns worker processes to run tests in parallel, these workers don't inherit the ASAN library injection, causing them to abort.
|
|
15
|
+
|
|
16
|
+
## Verification
|
|
17
|
+
|
|
18
|
+
The native module works correctly with ASAN when called directly:
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
export DYLD_INSERT_LIBRARIES="/Library/Developer/CommandLineTools/usr/lib/clang/17/lib/darwin/libclang_rt.asan_osx_dynamic.dylib"
|
|
22
|
+
node -e "require('./build/Release/fs_metadata.node').getVolumeMountPoints().then(console.log)"
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Solutions
|
|
26
|
+
|
|
27
|
+
### 1. Run Tests in Single Process Mode (Recommended)
|
|
28
|
+
|
|
29
|
+
The easiest workaround is to run Jest in single-process mode:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
npm test -- --runInBand --maxWorkers=1
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
This has been implemented in `scripts/macos-asan.sh`.
|
|
36
|
+
|
|
37
|
+
### 2. Static ASAN Linking (Alternative)
|
|
38
|
+
|
|
39
|
+
Instead of relying on dynamic library injection, link ASAN statically. However, this requires careful coordination with Node.js's build configuration.
|
|
40
|
+
|
|
41
|
+
### 3. Disable SIP (Not Recommended)
|
|
42
|
+
|
|
43
|
+
Temporarily disabling SIP allows DYLD_INSERT_LIBRARIES to work but compromises system security.
|
|
44
|
+
|
|
45
|
+
### 4. Use Linux for ASAN Testing
|
|
46
|
+
|
|
47
|
+
The Linux ASAN tests in CI don't have this limitation and can catch most memory issues.
|
|
48
|
+
|
|
49
|
+
## Current Implementation
|
|
50
|
+
|
|
51
|
+
The `scripts/macos-asan.sh` script now:
|
|
52
|
+
|
|
53
|
+
1. Builds with ASAN flags
|
|
54
|
+
2. Runs tests with `--runInBand` to avoid worker processes
|
|
55
|
+
3. Detects the "interceptors not installed" error and treats it as expected behavior
|
|
56
|
+
4. Falls back to the macOS `leaks` tool for additional memory checking
|
|
57
|
+
5. Provides clear messaging about the SIP limitation
|
|
58
|
+
|
|
59
|
+
The `scripts/check-memory.mjs` script:
|
|
60
|
+
|
|
61
|
+
1. Ensures a clean build before running JavaScript memory tests to avoid ASAN contamination
|
|
62
|
+
2. Clears ASAN environment variables when running tests
|
|
63
|
+
3. Treats macOS ASAN failures due to SIP as warnings, not errors
|
|
64
|
+
4. Still runs the `leaks` tool for native memory checking
|
|
65
|
+
|
|
66
|
+
## CI Considerations
|
|
67
|
+
|
|
68
|
+
- The GitHub Actions workflow runs ASAN tests on both Linux and macOS
|
|
69
|
+
- Linux ASAN tests are more reliable due to no SIP restrictions
|
|
70
|
+
- macOS ASAN failures due to SIP are treated as warnings, not CI failures
|
|
71
|
+
- macOS tests still provide value through the `leaks` tool and JavaScript memory tests
|
package/doc/social.png
ADDED
|
Binary file
|
package/doc/social.svg
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
|
2
|
+
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
|
3
|
+
|
|
4
|
+
<svg
|
|
5
|
+
width="1280"
|
|
6
|
+
height="640"
|
|
7
|
+
viewBox="0 0 338.66666 169.33333"
|
|
8
|
+
version="1.1"
|
|
9
|
+
id="svg1"
|
|
10
|
+
inkscape:version="1.4.2 (7335b4f457, 2025-06-11)"
|
|
11
|
+
sodipodi:docname="social.svg"
|
|
12
|
+
inkscape:export-filename="social.png"
|
|
13
|
+
inkscape:export-xdpi="96"
|
|
14
|
+
inkscape:export-ydpi="96"
|
|
15
|
+
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
|
16
|
+
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
|
17
|
+
xmlns:xlink="http://www.w3.org/1999/xlink"
|
|
18
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
19
|
+
xmlns:svg="http://www.w3.org/2000/svg">
|
|
20
|
+
<sodipodi:namedview
|
|
21
|
+
id="namedview1"
|
|
22
|
+
pagecolor="#505050"
|
|
23
|
+
bordercolor="#ffffff"
|
|
24
|
+
borderopacity="1"
|
|
25
|
+
inkscape:showpageshadow="0"
|
|
26
|
+
inkscape:pageopacity="0"
|
|
27
|
+
inkscape:pagecheckerboard="1"
|
|
28
|
+
inkscape:deskcolor="#505050"
|
|
29
|
+
inkscape:document-units="mm"
|
|
30
|
+
inkscape:zoom="1.72549"
|
|
31
|
+
inkscape:cx="497.53983"
|
|
32
|
+
inkscape:cy="397.858"
|
|
33
|
+
inkscape:window-width="3774"
|
|
34
|
+
inkscape:window-height="2075"
|
|
35
|
+
inkscape:window-x="66"
|
|
36
|
+
inkscape:window-y="46"
|
|
37
|
+
inkscape:window-maximized="1"
|
|
38
|
+
inkscape:current-layer="layer1" />
|
|
39
|
+
<defs
|
|
40
|
+
id="defs1">
|
|
41
|
+
<linearGradient
|
|
42
|
+
id="a">
|
|
43
|
+
<stop
|
|
44
|
+
offset="0"
|
|
45
|
+
stop-color="#4ed8ff"
|
|
46
|
+
id="stop869" />
|
|
47
|
+
<stop
|
|
48
|
+
offset="1"
|
|
49
|
+
stop-color="#ff14b3"
|
|
50
|
+
id="stop871" />
|
|
51
|
+
</linearGradient>
|
|
52
|
+
<linearGradient
|
|
53
|
+
inkscape:collect="always"
|
|
54
|
+
xlink:href="#a"
|
|
55
|
+
id="linearGradient503"
|
|
56
|
+
gradientUnits="userSpaceOnUse"
|
|
57
|
+
gradientTransform="matrix(0.39403452,0.00301727,-0.00156758,0.20471429,-6.7202983,-353.74855)"
|
|
58
|
+
x1="352.15787"
|
|
59
|
+
y1="1902.979"
|
|
60
|
+
x2="562.99939"
|
|
61
|
+
y2="2219.3118" />
|
|
62
|
+
</defs>
|
|
63
|
+
<g
|
|
64
|
+
inkscape:label="Layer 1"
|
|
65
|
+
inkscape:groupmode="layer"
|
|
66
|
+
id="layer1">
|
|
67
|
+
<rect
|
|
68
|
+
style="fill:#b3b3b3;stroke-width:0.25;paint-order:markers fill stroke;stroke-dasharray:none"
|
|
69
|
+
id="rect3"
|
|
70
|
+
width="338.73895"
|
|
71
|
+
height="169.20047"
|
|
72
|
+
x="-0.49628502"
|
|
73
|
+
y="0.040662654"
|
|
74
|
+
inkscape:export-filename="social.png"
|
|
75
|
+
inkscape:export-xdpi="251.23711"
|
|
76
|
+
inkscape:export-ydpi="251.23711" />
|
|
77
|
+
<g
|
|
78
|
+
id="g3"
|
|
79
|
+
transform="matrix(2.5048234,0,0,2.5048234,-159.04805,-139.45947)"
|
|
80
|
+
style="stroke-width:0.186306">
|
|
81
|
+
<g
|
|
82
|
+
id="g2"
|
|
83
|
+
style="stroke-width:0.186306" />
|
|
84
|
+
</g>
|
|
85
|
+
<g
|
|
86
|
+
id="g55"
|
|
87
|
+
transform="matrix(2.5048234,0,0,2.5048234,791.53892,453.36615)"
|
|
88
|
+
style="stroke-width:0.186306" />
|
|
89
|
+
<text
|
|
90
|
+
xml:space="preserve"
|
|
91
|
+
style="font-size:14.2942px;text-align:start;writing-mode:lr-tb;direction:ltr;text-anchor:start;fill:#4d4d4d;stroke-width:0.183461;-inkscape-stroke:none;paint-order:markers fill stroke"
|
|
92
|
+
x="163.786"
|
|
93
|
+
y="141.75613"
|
|
94
|
+
id="text3"><tspan
|
|
95
|
+
sodipodi:role="line"
|
|
96
|
+
id="tspan3"
|
|
97
|
+
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-family:'Roboto Mono';-inkscape-font-specification:'Roboto Mono Bold';text-align:center;text-anchor:middle;fill:#000000;stroke-width:0.183461"
|
|
98
|
+
x="163.786"
|
|
99
|
+
y="141.75613">@photostructure/fs-metadata</tspan></text>
|
|
100
|
+
<path
|
|
101
|
+
style="fill:#ffffff;stroke-width:0.0342997"
|
|
102
|
+
d="m 171.44524,115.93678 c 0.42071,-0.21553 8.89118,-5.16568 34.18759,-19.980303 3.13064,-1.833392 3.52904,-2.104923 3.92422,-2.674382 0.65246,-0.940045 0.6167,0.257435 0.65856,-22.080167 l 0.0381,-20.58144 -0.28193,-0.617353 c -0.57006,-1.248041 -0.91164,-1.487733 -8.1462,-5.717009 -1.90377,-1.112902 -5.351,-3.132213 -7.66056,-4.487364 -23.86594,-14.003467 -23.07785,-13.565556 -24.27201,-13.487563 -1.01074,0.06586 -0.30043,-0.311317 -9.04886,4.805157 -4.031,2.357459 -11.13859,6.509722 -15.79462,9.227209 -4.65598,2.717542 -9.6415,5.62786 -11.07894,6.467434 -2.81432,1.643718 -3.52882,2.211544 -3.9178,3.113599 l -0.23675,0.549321 -0.0163,20.659868 c -0.0218,23.226921 -0.0914,21.33132 0.84425,22.372869 0.30588,0.340707 0.95469,0.812525 1.76651,1.284834 0.70563,0.410371 2.71291,1.58472 4.46069,2.609451 24.57505,14.408609 31.6673,18.522179 32.28753,18.726819 0.44303,0.1464 1.85941,0.0272 2.28725,-0.19104 z"
|
|
103
|
+
id="path507" />
|
|
104
|
+
<g
|
|
105
|
+
id="g3-0"
|
|
106
|
+
transform="matrix(2.5398569,0,0,2.5398569,-158.93223,-137.64812)"
|
|
107
|
+
style="stroke-width:0.183736">
|
|
108
|
+
<g
|
|
109
|
+
id="g2-6"
|
|
110
|
+
style="stroke-width:0.183736" />
|
|
111
|
+
</g>
|
|
112
|
+
<path
|
|
113
|
+
style="font-variation-settings:normal;baseline-shift:baseline;display:inline;overflow:visible;vector-effect:none;fill:url(#linearGradient503);fill-opacity:1;stroke:none;stroke-width:0.187855;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;-inkscape-stroke:none;paint-order:markers fill stroke;enable-background:accumulate;stop-color:#000000"
|
|
114
|
+
d="m 170.02004,20.333816 c -1.80438,5.4e-5 -3.60828,0.465341 -5.22141,1.396896 l -35.04497,20.233712 c -3.22626,1.862728 -5.22167,5.319972 -5.22162,9.045319 l 6e-4,40.466391 c 5e-5,3.725401 1.99596,7.181883 5.22228,9.044556 l 35.04469,20.23273 c 3.22632,1.86262 7.21841,1.86273 10.44461,0 l 35.04421,-20.23371 c 3.22626,-1.862727 5.22249,-5.319972 5.22244,-9.045318 l -5.4e-4,-40.466445 c -1.1e-4,-3.725347 -1.99597,-7.181884 -5.22228,-9.044502 L 175.24248,21.730712 c -1.61318,-0.931337 -3.418,-1.396896 -5.22244,-1.396896 z m 3.8e-4,7.368455 c 0.52902,-5.4e-5 1.05842,0.137698 1.53617,0.413637 l 35.04475,20.232733 c 0.9554,0.551607 1.53574,1.556691 1.53574,2.659904 l 0.001,40.466391 c 5e-5,1.103214 -0.58024,2.108026 -1.53563,2.659632 l -35.04502,20.234472 c -0.95545,0.55161 -2.11598,0.55128 -3.07137,-3.8e-4 L 133.4408,94.135929 c -0.95544,-0.551607 -1.53568,-1.55669 -1.53568,-2.659904 l -5.4e-4,-40.466391 c -1.1e-4,-1.103214 0.58018,-2.107971 1.53557,-2.659632 l 35.04502,-20.23442 c 0.47786,-0.27594 1.00629,-0.413093 1.53525,-0.413093 z"
|
|
115
|
+
id="path491" />
|
|
116
|
+
<g
|
|
117
|
+
id="g55-3"
|
|
118
|
+
transform="matrix(2.5398569,0,0,2.5398569,804.95006,463.469)"
|
|
119
|
+
style="stroke-width:0.183736" />
|
|
120
|
+
<path
|
|
121
|
+
style="fill:#4d4d4d;stroke-width:0.466662"
|
|
122
|
+
d="m 148.48381,91.644364 c -1.15051,-0.236753 -2.01676,-1.072953 -2.2811,-2.201692 -0.15784,-0.673249 -0.19376,-35.438686 -0.0381,-36.283214 0.23131,-1.250218 1.0453,-2.094582 2.23718,-2.320068 0.74177,-0.140419 27.39132,-0.08164 27.80664,0.06096 1.6072,0.552858 1.94404,1.426503 1.94404,5.041093 v 2.541038 l -0.75494,0.18396 c -1.3102,0.31948 -2.77121,0.947446 -3.90566,1.67904 l -0.53882,0.347237 -0.50453,-0.714939 c -5.56472,-7.884304 -17.05578,-7.201912 -21.29322,1.264478 -2.55491,5.104717 -1.05858,11.567035 3.52533,15.224785 l 0.4414,0.352136 -0.62666,0.455 c -3.07218,2.231954 -3.89254,3.097925 -4.16587,4.397453 -0.72136,3.429487 2.94847,6.35303 6.02054,4.796285 0.92644,-0.469695 1.35809,-0.986198 4.12902,-4.943126 l 1.85756,-2.652666 0.78667,-0.05497 c 1.97077,-0.138242 4.10835,-0.871033 5.7464,-1.969457 l 0.55215,-0.370097 0.13226,0.259068 c 1.42955,2.794881 4.92566,5.393338 8.09575,6.017005 l 0.50289,0.09906 v 3.114633 c 0,4.30052 -0.18178,4.839228 -1.8772,5.563147 -0.39459,0.168176 -26.99755,0.277572 -27.7919,0.11375 z m 45.7076,-3.361237 c -0.13443,-0.04898 -0.36792,-0.177428 -0.51813,-0.289546 -0.20736,-0.155114 -5.05089,-5.543553 -7.00587,-7.794665 l -0.25961,-0.299342 -0.57572,0.316214 c -10.91752,6.001167 -21.97812,-8.061787 -13.41659,-17.058449 9.36186,-9.83776 24.98667,3.357972 16.73859,14.136375 l -0.22859,0.298255 3.12274,3.007196 c 5.14488,4.954502 4.88146,4.652764 4.94318,5.663236 0.0909,1.484141 -1.35325,2.526017 -2.79983,2.020345 z m -11.8845,-9.856482 c 4.20822,-1.038828 6.94279,-5.55678 5.85531,-9.674102 -1.89571,-7.177638 -11.60818,-8.228439 -14.93568,-1.615851 -3.00507,5.971777 2.5657,12.898076 9.08037,11.289953 z m -28.70353,7.182209 c -1.4289,-0.514325 -2.18123,-1.570623 -2.18123,-3.063146 0,-1.540471 0.0604,-1.607089 4.51839,-4.967128 6.49427,-4.895069 6.60437,-4.967347 7.29052,-4.782571 1.15845,0.311861 1.15818,1.135271 -7.6e-4,2.801031 -0.4414,0.634498 -1.16363,1.68617 -1.60481,2.337104 -2.5074,3.699549 -4.39184,6.309762 -4.99526,6.91917 -0.84861,0.85699 -1.96816,1.136468 -3.02674,0.75554 z m 7.53168,-14.320443 c -3.80829,-0.882081 -5.04028,-5.545132 -2.15494,-8.156271 3.01781,-2.73093 7.94172,-0.537184 7.94172,3.538339 0,2.967847 -2.90438,5.285575 -5.78678,4.617932 z m 1.73766,-3.141628 c 1.1289,-0.582031 1.32859,-1.942353 0.40438,-2.753898 -1.08585,-0.953379 -2.78443,-0.196478 -2.77158,1.234979 0.0109,1.234434 1.30676,2.065736 2.36731,1.518919 z"
|
|
123
|
+
id="path74" />
|
|
124
|
+
</g>
|
|
125
|
+
</svg>
|