@gmickel/gno 1.34.6 → 1.35.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/README.md +12 -1
- package/browser-extension/artifacts/{gno-browser-clipper-v1.34.6.zip → gno-browser-clipper-v1.35.0.zip} +0 -0
- package/browser-extension/artifacts/gno-browser-clipper-v1.35.0.zip.sha256 +1 -0
- package/browser-extension/dist/manifest.json +1 -1
- package/package.json +1 -1
- package/spec/cli.md +37 -0
- package/spec/output-schemas/mcp-job-status.schema.json +6 -2
- package/src/config/index.ts +4 -0
- package/src/config/types.ts +14 -0
- package/src/core/path-rules.ts +34 -0
- package/src/ingestion/index.ts +21 -0
- package/src/ingestion/record-container.ts +23 -1
- package/src/ingestion/source-availability/darwin-io.ts +295 -0
- package/src/ingestion/source-availability/darwin-path.ts +58 -0
- package/src/ingestion/source-availability/directory.ts +402 -0
- package/src/ingestion/source-availability/index.ts +74 -0
- package/src/ingestion/source-availability/readers.ts +360 -0
- package/src/ingestion/source-availability/resolve.ts +28 -0
- package/src/ingestion/source-availability/types.ts +170 -0
- package/src/ingestion/sync.ts +197 -24
- package/src/ingestion/types.ts +45 -3
- package/src/ingestion/walker.ts +263 -5
- package/src/serve/public/globals.built.css +1 -1
- package/src/serve/watch-reconciliation-fallback-disk.ts +239 -100
- package/src/serve/watch-reconciliation-fallback.ts +35 -5
- package/src/serve/watch-reconciliation-shared.ts +8 -3
- package/src/serve/watch-reconciliation.ts +7 -0
- package/src/serve/watch-service-flush.ts +10 -0
- package/src/serve/watch-service-lifecycle.ts +2 -0
- package/src/serve/watch-service-snapshot.ts +27 -3
- package/src/serve/watch-service.ts +1 -0
- package/src/serve/watch-snapshot-availability.ts +51 -0
- package/src/serve/watch-snapshot-handles.ts +117 -37
- package/src/serve/watch-snapshot-libc.ts +141 -22
- package/src/serve/watch-snapshot-ops.ts +151 -9
- package/src/serve/watch-snapshot-scan.ts +3 -0
- package/src/serve/watch-snapshot-types.ts +45 -3
- package/browser-extension/artifacts/gno-browser-clipper-v1.34.6.zip.sha256 +0 -1
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Source-availability boundaries shared by watcher snapshot operations.
|
|
3
|
+
*
|
|
4
|
+
* @module src/serve/watch-snapshot-availability
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
// node:path — Bun has no path join helper.
|
|
8
|
+
import { join } from "node:path";
|
|
9
|
+
|
|
10
|
+
import type { WatcherSnapshotFs } from "./watch-snapshot-types";
|
|
11
|
+
|
|
12
|
+
import {
|
|
13
|
+
isUnprovenDirectoryResult,
|
|
14
|
+
type DirectoryAvailabilityPort,
|
|
15
|
+
} from "../ingestion/source-availability";
|
|
16
|
+
import { readDirectChildren } from "./watch-snapshot-scan";
|
|
17
|
+
|
|
18
|
+
export async function directoryAllowsDescent(
|
|
19
|
+
rootAbs: string,
|
|
20
|
+
dirRel: string,
|
|
21
|
+
classifier: DirectoryAvailabilityPort | undefined
|
|
22
|
+
): Promise<boolean> {
|
|
23
|
+
if (!classifier || classifier.mode === "any") {
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
const absPath = dirRel === "" ? rootAbs : join(rootAbs, dirRel);
|
|
27
|
+
const classified = await classifier.classify(absPath);
|
|
28
|
+
return !isUnprovenDirectoryResult(classified);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export async function readAvailableDirectory(
|
|
32
|
+
rootAbs: string,
|
|
33
|
+
dirRel: string,
|
|
34
|
+
fs: WatcherSnapshotFs,
|
|
35
|
+
maxEntries: number,
|
|
36
|
+
classifier: DirectoryAvailabilityPort | undefined
|
|
37
|
+
): Promise<
|
|
38
|
+
Awaited<ReturnType<typeof readDirectChildren>> | { status: "unproven" }
|
|
39
|
+
> {
|
|
40
|
+
if (!classifier || classifier.mode === "any") {
|
|
41
|
+
return readDirectChildren(rootAbs, dirRel, fs, maxEntries);
|
|
42
|
+
}
|
|
43
|
+
if (!fs.readDirectChildrenSync) {
|
|
44
|
+
return { status: "unproven" };
|
|
45
|
+
}
|
|
46
|
+
const absPath = dirRel === "" ? rootAbs : join(rootAbs, dirRel);
|
|
47
|
+
const read = classifier.readDirectory(absPath, () =>
|
|
48
|
+
fs.readDirectChildrenSync!(rootAbs, dirRel, maxEntries)
|
|
49
|
+
);
|
|
50
|
+
return read.kind === "available" ? read.value : { status: "unproven" };
|
|
51
|
+
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Production anchored directory handles (openat / fdopendir).
|
|
3
3
|
*
|
|
4
|
-
* Uses Bun FFI for fd-relative open/enumerate and node:fs
|
|
5
|
-
* open
|
|
4
|
+
* Uses Bun FFI for fd-relative open/enumerate/no-follow metadata and node:fs
|
|
5
|
+
* structure ops for directory open + fstat confirmation. No new dependencies.
|
|
6
6
|
*
|
|
7
7
|
* Windows and runtimes without a safe anchored path report
|
|
8
8
|
* `supportsAnchoredHandles: false` so callers fall back rather than claiming
|
|
@@ -18,13 +18,19 @@ import { join } from "node:path";
|
|
|
18
18
|
|
|
19
19
|
import type { LoadedLibc } from "./watch-snapshot-libc";
|
|
20
20
|
import type {
|
|
21
|
+
SnapshotEntryFingerprint,
|
|
21
22
|
WatcherDirHandle,
|
|
22
23
|
WatcherSnapshotFs,
|
|
23
24
|
WatcherSnapshotStat,
|
|
24
25
|
} from "./watch-snapshot-types";
|
|
25
26
|
|
|
26
|
-
import {
|
|
27
|
-
|
|
27
|
+
import {
|
|
28
|
+
loadLibc,
|
|
29
|
+
openatOrThrow,
|
|
30
|
+
readDirNames,
|
|
31
|
+
statatNoFollowOrThrow,
|
|
32
|
+
} from "./watch-snapshot-libc";
|
|
33
|
+
import { fingerprintFromStat, isMissingFsError } from "./watch-snapshot-types";
|
|
28
34
|
|
|
29
35
|
type NativeDir = {
|
|
30
36
|
fd: number;
|
|
@@ -48,32 +54,118 @@ function requireNative(handle: WatcherDirHandle): NativeDir {
|
|
|
48
54
|
return native;
|
|
49
55
|
}
|
|
50
56
|
|
|
51
|
-
function
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
}
|
|
57
|
+
function openNativeDirByRel(
|
|
58
|
+
libc: LoadedLibc,
|
|
59
|
+
rootAbs: string,
|
|
60
|
+
dirRel: string
|
|
61
|
+
): number {
|
|
62
|
+
let fd = openSync(rootAbs, libc.openDirFlags);
|
|
63
|
+
try {
|
|
64
|
+
for (const segment of dirRel.split("/").filter(Boolean)) {
|
|
65
|
+
const child = openatOrThrow(
|
|
66
|
+
libc,
|
|
67
|
+
fd,
|
|
68
|
+
segment,
|
|
69
|
+
libc.openChildFlags,
|
|
70
|
+
"openat"
|
|
71
|
+
);
|
|
72
|
+
closeSync(fd);
|
|
73
|
+
fd = child;
|
|
74
|
+
}
|
|
75
|
+
return fd;
|
|
76
|
+
} catch (cause) {
|
|
77
|
+
closeSync(fd);
|
|
78
|
+
throw cause;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function readDirectChildrenNative(
|
|
83
|
+
libc: LoadedLibc,
|
|
84
|
+
rootAbs: string,
|
|
85
|
+
dirRel: string,
|
|
86
|
+
maxEntries: number
|
|
87
|
+
): ReturnType<NonNullable<WatcherSnapshotFs["readDirectChildrenSync"]>> {
|
|
88
|
+
if (!Number.isInteger(maxEntries) || maxEntries < 0) {
|
|
89
|
+
return {
|
|
90
|
+
status: "scan_failed",
|
|
91
|
+
cause: new Error("maxEntries must be a non-negative integer"),
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
let fd: number;
|
|
96
|
+
try {
|
|
97
|
+
fd = openNativeDirByRel(libc, rootAbs, dirRel);
|
|
98
|
+
} catch (cause) {
|
|
99
|
+
return isMissingFsError(cause)
|
|
100
|
+
? { status: "missing" }
|
|
101
|
+
: { status: "scan_failed", cause };
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
try {
|
|
105
|
+
const listed = readDirNames(libc, fd, maxEntries);
|
|
106
|
+
if (listed.status === "overflow") {
|
|
107
|
+
return listed;
|
|
108
|
+
}
|
|
109
|
+
const entries = new Map<string, SnapshotEntryFingerprint>();
|
|
110
|
+
listed.names.sort((left, right) =>
|
|
111
|
+
left < right ? -1 : left > right ? 1 : 0
|
|
112
|
+
);
|
|
113
|
+
for (const name of listed.names) {
|
|
114
|
+
if (name.includes("/") || name.includes("\\") || name.includes("\0")) {
|
|
115
|
+
return {
|
|
116
|
+
status: "scan_failed",
|
|
117
|
+
cause: new Error(`Invalid directory entry name: ${name}`),
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
if (entries.size >= maxEntries) {
|
|
121
|
+
return { status: "overflow" };
|
|
122
|
+
}
|
|
123
|
+
try {
|
|
124
|
+
const fingerprinted = fingerprintFromStat(
|
|
125
|
+
statatNoFollowOrThrow(libc, fd, name)
|
|
126
|
+
);
|
|
127
|
+
if (!fingerprinted.ok) {
|
|
128
|
+
return { status: "unreliable_metadata" };
|
|
129
|
+
}
|
|
130
|
+
entries.set(name, fingerprinted.fingerprint);
|
|
131
|
+
} catch (cause) {
|
|
132
|
+
return { status: "scan_failed", cause };
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return { status: "present", entries };
|
|
136
|
+
} catch (cause) {
|
|
137
|
+
return isMissingFsError(cause)
|
|
138
|
+
? { status: "missing" }
|
|
139
|
+
: { status: "scan_failed", cause };
|
|
140
|
+
} finally {
|
|
141
|
+
closeSync(fd);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function lstatChildByRelNative(
|
|
146
|
+
libc: LoadedLibc,
|
|
147
|
+
rootAbs: string,
|
|
148
|
+
parentRel: string,
|
|
149
|
+
name: string
|
|
150
|
+
): WatcherSnapshotStat {
|
|
151
|
+
const parentFd = openNativeDirByRel(libc, rootAbs, parentRel);
|
|
152
|
+
try {
|
|
153
|
+
return statatNoFollowOrThrow(libc, parentFd, name);
|
|
154
|
+
} finally {
|
|
155
|
+
closeSync(parentFd);
|
|
156
|
+
}
|
|
71
157
|
}
|
|
72
158
|
|
|
73
159
|
function createNativeAnchoredFs(libc: LoadedLibc): WatcherSnapshotFs {
|
|
74
160
|
return {
|
|
75
161
|
supportsAnchoredHandles: true,
|
|
76
162
|
|
|
163
|
+
readDirectChildrenSync: (rootAbs, dirRel, maxEntries) =>
|
|
164
|
+
readDirectChildrenNative(libc, rootAbs, dirRel, maxEntries),
|
|
165
|
+
|
|
166
|
+
lstatChildByRelSync: (rootAbs, parentRel, name) =>
|
|
167
|
+
lstatChildByRelNative(libc, rootAbs, parentRel, name),
|
|
168
|
+
|
|
77
169
|
async openDir(absPath: string): Promise<WatcherDirHandle> {
|
|
78
170
|
const fd = openSync(absPath, libc.openDirFlags);
|
|
79
171
|
return asHandle({ fd });
|
|
@@ -89,19 +181,7 @@ function createNativeAnchoredFs(libc: LoadedLibc): WatcherSnapshotFs {
|
|
|
89
181
|
name: string
|
|
90
182
|
): Promise<WatcherSnapshotStat> {
|
|
91
183
|
const native = requireNative(handle);
|
|
92
|
-
|
|
93
|
-
libc,
|
|
94
|
-
native.fd,
|
|
95
|
-
name,
|
|
96
|
-
libc.openLstatFlags,
|
|
97
|
-
"openat"
|
|
98
|
-
);
|
|
99
|
-
try {
|
|
100
|
-
const stat = fstatSync(fd, { bigint: true });
|
|
101
|
-
return mapStats(stat);
|
|
102
|
-
} finally {
|
|
103
|
-
closeSync(fd);
|
|
104
|
-
}
|
|
184
|
+
return statatNoFollowOrThrow(libc, native.fd, name);
|
|
105
185
|
},
|
|
106
186
|
|
|
107
187
|
async openChildDir(
|
|
@@ -14,6 +14,12 @@ import { constants as fsConstants } from "node:fs";
|
|
|
14
14
|
|
|
15
15
|
export type LibcSymbols = {
|
|
16
16
|
openat: (dirfd: number, path: Pointer, flags: number) => number;
|
|
17
|
+
fstatat: (
|
|
18
|
+
dirfd: number,
|
|
19
|
+
path: Pointer,
|
|
20
|
+
statBuffer: Pointer,
|
|
21
|
+
flags: number
|
|
22
|
+
) => number;
|
|
17
23
|
dup: (fd: number) => number;
|
|
18
24
|
close: (fd: number) => number;
|
|
19
25
|
fdopendir: (fd: number) => Pointer | null;
|
|
@@ -42,7 +48,33 @@ export type LoadedLibc = {
|
|
|
42
48
|
dirent: DirentLayout;
|
|
43
49
|
openChildFlags: number;
|
|
44
50
|
openDirFlags: number;
|
|
45
|
-
|
|
51
|
+
atSymlinkNoFollow: number;
|
|
52
|
+
statLayout: NativeStatLayout;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
type NativeStatLayout = {
|
|
56
|
+
size: number;
|
|
57
|
+
devOffset: number;
|
|
58
|
+
devBytes: 4 | 8;
|
|
59
|
+
modeOffset: number;
|
|
60
|
+
modeBytes: 2 | 4;
|
|
61
|
+
inoOffset: number;
|
|
62
|
+
sizeOffset: number;
|
|
63
|
+
mtimeSecOffset: number;
|
|
64
|
+
mtimeNsecOffset: number;
|
|
65
|
+
ctimeSecOffset: number;
|
|
66
|
+
ctimeNsecOffset: number;
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
export type FdRelativeStat = {
|
|
70
|
+
isFile(): boolean;
|
|
71
|
+
isDirectory(): boolean;
|
|
72
|
+
isSymbolicLink(): boolean;
|
|
73
|
+
dev: bigint | number;
|
|
74
|
+
ino: bigint;
|
|
75
|
+
size: bigint;
|
|
76
|
+
mtimeNs: bigint;
|
|
77
|
+
ctimeNs: bigint;
|
|
46
78
|
};
|
|
47
79
|
|
|
48
80
|
let cachedLibc: LoadedLibc | null | undefined;
|
|
@@ -82,6 +114,10 @@ function openLibcSymbols(
|
|
|
82
114
|
args: [FFIType.i32, FFIType.cstring, FFIType.i32],
|
|
83
115
|
returns: FFIType.i32,
|
|
84
116
|
},
|
|
117
|
+
fstatat: {
|
|
118
|
+
args: [FFIType.i32, FFIType.cstring, FFIType.ptr, FFIType.i32],
|
|
119
|
+
returns: FFIType.i32,
|
|
120
|
+
},
|
|
85
121
|
dup: { args: [FFIType.i32], returns: FFIType.i32 },
|
|
86
122
|
close: { args: [FFIType.i32], returns: FFIType.i32 },
|
|
87
123
|
fdopendir: { args: [FFIType.i32], returns: FFIType.ptr },
|
|
@@ -121,6 +157,7 @@ export function loadLibc(): LoadedLibc | null {
|
|
|
121
157
|
|
|
122
158
|
const raw = library.symbols as Record<string, unknown>;
|
|
123
159
|
const openat = asLibcFn<LibcSymbols["openat"]>(raw.openat);
|
|
160
|
+
const fstatat = asLibcFn<LibcSymbols["fstatat"]>(raw.fstatat);
|
|
124
161
|
const dup = asLibcFn<LibcSymbols["dup"]>(raw.dup);
|
|
125
162
|
const close = asLibcFn<LibcSymbols["close"]>(raw.close);
|
|
126
163
|
const fdopendir = asLibcFn<LibcSymbols["fdopendir"]>(raw.fdopendir);
|
|
@@ -129,6 +166,7 @@ export function loadLibc(): LoadedLibc | null {
|
|
|
129
166
|
const errnoFn = asLibcFn<LibcSymbols["errnoPtr"]>(raw[errnoName]);
|
|
130
167
|
if (
|
|
131
168
|
!openat ||
|
|
169
|
+
!fstatat ||
|
|
132
170
|
!dup ||
|
|
133
171
|
!close ||
|
|
134
172
|
!fdopendir ||
|
|
@@ -144,32 +182,15 @@ export function loadLibc(): LoadedLibc | null {
|
|
|
144
182
|
const O_DIRECTORY = fsConstants.O_DIRECTORY;
|
|
145
183
|
const O_NOFOLLOW = fsConstants.O_NOFOLLOW;
|
|
146
184
|
|
|
147
|
-
let openLstatFlags: number;
|
|
148
|
-
if (platform === "darwin") {
|
|
149
|
-
// Darwin O_SYMLINK opens the symlink inode itself (lstat-like).
|
|
150
|
-
// Without it, O_RDONLY would follow — refuse rather than silent follow.
|
|
151
|
-
// O_NONBLOCK is required: plain O_RDONLY|O_SYMLINK can block forever on a
|
|
152
|
-
// FIFO with no writer (and similarly hang on some device nodes).
|
|
153
|
-
const O_SYMLINK = (fsConstants as { O_SYMLINK?: number }).O_SYMLINK;
|
|
154
|
-
if (O_SYMLINK === undefined || O_SYMLINK === 0) {
|
|
155
|
-
cachedLibc = null;
|
|
156
|
-
return null;
|
|
157
|
-
}
|
|
158
|
-
const O_NONBLOCK = fsConstants.O_NONBLOCK ?? 0;
|
|
159
|
-
openLstatFlags = O_RDONLY | O_SYMLINK | O_NONBLOCK;
|
|
160
|
-
} else {
|
|
161
|
-
// Linux O_PATH|O_NOFOLLOW is the portable no-follow open for any type.
|
|
162
|
-
// O_PATH does not block on FIFOs/sockets; no extra O_NONBLOCK needed.
|
|
163
|
-
const O_PATH = (fsConstants as { O_PATH?: number }).O_PATH ?? 0o10_000_000;
|
|
164
|
-
openLstatFlags = O_RDONLY | O_PATH | O_NOFOLLOW;
|
|
165
|
-
}
|
|
166
|
-
|
|
167
185
|
const openDirFlags = O_RDONLY | O_DIRECTORY | O_NOFOLLOW;
|
|
186
|
+
const atSymlinkNoFollow = platform === "darwin" ? 0x20 : 0x100;
|
|
187
|
+
const statLayout = nativeStatLayout(platform, process.arch);
|
|
168
188
|
|
|
169
189
|
cachedLibc = {
|
|
170
190
|
library,
|
|
171
191
|
symbols: {
|
|
172
192
|
openat,
|
|
193
|
+
fstatat,
|
|
173
194
|
dup,
|
|
174
195
|
close,
|
|
175
196
|
fdopendir,
|
|
@@ -180,11 +201,43 @@ export function loadLibc(): LoadedLibc | null {
|
|
|
180
201
|
dirent: direntLayoutFor(platform),
|
|
181
202
|
openChildFlags: openDirFlags,
|
|
182
203
|
openDirFlags,
|
|
183
|
-
|
|
204
|
+
atSymlinkNoFollow,
|
|
205
|
+
statLayout,
|
|
184
206
|
};
|
|
185
207
|
return cachedLibc;
|
|
186
208
|
}
|
|
187
209
|
|
|
210
|
+
function nativeStatLayout(platform: string, arch: string): NativeStatLayout {
|
|
211
|
+
if (platform === "darwin") {
|
|
212
|
+
return {
|
|
213
|
+
size: 144,
|
|
214
|
+
devOffset: 0,
|
|
215
|
+
devBytes: 4,
|
|
216
|
+
modeOffset: 4,
|
|
217
|
+
modeBytes: 2,
|
|
218
|
+
inoOffset: 8,
|
|
219
|
+
sizeOffset: 96,
|
|
220
|
+
mtimeSecOffset: 48,
|
|
221
|
+
mtimeNsecOffset: 56,
|
|
222
|
+
ctimeSecOffset: 64,
|
|
223
|
+
ctimeNsecOffset: 72,
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
return {
|
|
227
|
+
size: 144,
|
|
228
|
+
devOffset: 0,
|
|
229
|
+
devBytes: 8,
|
|
230
|
+
modeOffset: arch === "x64" ? 24 : 16,
|
|
231
|
+
modeBytes: 4,
|
|
232
|
+
inoOffset: 8,
|
|
233
|
+
sizeOffset: 48,
|
|
234
|
+
mtimeSecOffset: 88,
|
|
235
|
+
mtimeNsecOffset: 96,
|
|
236
|
+
ctimeSecOffset: 104,
|
|
237
|
+
ctimeNsecOffset: 112,
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
|
|
188
241
|
function asLibcFn<T extends (...args: never[]) => unknown>(
|
|
189
242
|
value: unknown
|
|
190
243
|
): T | null {
|
|
@@ -389,3 +442,69 @@ export function openatOrThrow(
|
|
|
389
442
|
}
|
|
390
443
|
return fd;
|
|
391
444
|
}
|
|
445
|
+
|
|
446
|
+
/**
|
|
447
|
+
* Read no-follow metadata for one direct child without opening its content.
|
|
448
|
+
* This avoids materializing dataless File Provider files while retaining the
|
|
449
|
+
* parent-fd anchoring and symlink identity required by watcher snapshots.
|
|
450
|
+
*/
|
|
451
|
+
export function statatNoFollowOrThrow(
|
|
452
|
+
libc: LoadedLibc,
|
|
453
|
+
dirfd: number,
|
|
454
|
+
name: string
|
|
455
|
+
): FdRelativeStat {
|
|
456
|
+
if (name.includes("\0") || name.includes("/") || name.includes("\\")) {
|
|
457
|
+
throw Object.assign(new Error(`Invalid directory entry name: ${name}`), {
|
|
458
|
+
code: "EINVAL",
|
|
459
|
+
});
|
|
460
|
+
}
|
|
461
|
+
const nameBuffer = Buffer.from(`${name}\0`);
|
|
462
|
+
const statBuffer = Buffer.alloc(libc.statLayout.size);
|
|
463
|
+
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
464
|
+
const result = libc.symbols.fstatat(
|
|
465
|
+
dirfd,
|
|
466
|
+
ptr(nameBuffer),
|
|
467
|
+
ptr(statBuffer),
|
|
468
|
+
libc.atSymlinkNoFollow
|
|
469
|
+
);
|
|
470
|
+
if (result >= 0) {
|
|
471
|
+
break;
|
|
472
|
+
}
|
|
473
|
+
const errno = readErrno(libc);
|
|
474
|
+
// Darwin/Bun can transiently surface ENOENT during large fd-relative stat
|
|
475
|
+
// batches. Retry the same anchored lookup once. A child that truly
|
|
476
|
+
// disappeared still fails on the second probe, so callers retain the
|
|
477
|
+
// scan-failed authority boundary instead of accepting a partial image.
|
|
478
|
+
if (errno !== 2 || attempt > 0) {
|
|
479
|
+
throw errnoError(errno, "fstatat", name);
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
const layout = libc.statLayout;
|
|
484
|
+
const mode =
|
|
485
|
+
layout.modeBytes === 2
|
|
486
|
+
? statBuffer.readUInt16LE(layout.modeOffset)
|
|
487
|
+
: statBuffer.readUInt32LE(layout.modeOffset);
|
|
488
|
+
const fileType = mode & 0o170_000;
|
|
489
|
+
const dev =
|
|
490
|
+
layout.devBytes === 4
|
|
491
|
+
? statBuffer.readUInt32LE(layout.devOffset)
|
|
492
|
+
: statBuffer.readBigUInt64LE(layout.devOffset);
|
|
493
|
+
const mtimeNs =
|
|
494
|
+
statBuffer.readBigInt64LE(layout.mtimeSecOffset) * 1_000_000_000n +
|
|
495
|
+
statBuffer.readBigInt64LE(layout.mtimeNsecOffset);
|
|
496
|
+
const ctimeNs =
|
|
497
|
+
statBuffer.readBigInt64LE(layout.ctimeSecOffset) * 1_000_000_000n +
|
|
498
|
+
statBuffer.readBigInt64LE(layout.ctimeNsecOffset);
|
|
499
|
+
|
|
500
|
+
return {
|
|
501
|
+
isFile: () => fileType === 0o100_000,
|
|
502
|
+
isDirectory: () => fileType === 0o040_000,
|
|
503
|
+
isSymbolicLink: () => fileType === 0o120_000,
|
|
504
|
+
dev,
|
|
505
|
+
ino: statBuffer.readBigUInt64LE(layout.inoOffset),
|
|
506
|
+
size: statBuffer.readBigInt64LE(layout.sizeOffset),
|
|
507
|
+
mtimeNs,
|
|
508
|
+
ctimeNs,
|
|
509
|
+
};
|
|
510
|
+
}
|