@ai-ecoverse/slicc-kernel 1.0.0 → 1.2.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 +50 -4
- package/dist/index.js +45 -6
- package/dist/index.js.map +3 -3
- package/dist/kernel-worker.js +671 -175
- package/dist/kernel-worker.js.map +4 -4
- package/dist/process-worker.js +34 -17
- package/dist/process-worker.js.map +3 -3
- package/package.json +6 -7
package/dist/kernel-worker.js
CHANGED
|
@@ -1,3 +1,152 @@
|
|
|
1
|
+
// src/fs/meta.ts
|
|
2
|
+
var META_DB = "slicc-kernel";
|
|
3
|
+
var META_VERSION = 1;
|
|
4
|
+
var META_STORE = "entries";
|
|
5
|
+
function parentOf(path) {
|
|
6
|
+
return path.slice(0, path.lastIndexOf("/")) || "/";
|
|
7
|
+
}
|
|
8
|
+
function moved(entry, path) {
|
|
9
|
+
return entry.link === void 0 ? { ...entry, path } : { ...entry, path, dir: parentOf(path) };
|
|
10
|
+
}
|
|
11
|
+
function under(path, root) {
|
|
12
|
+
return path === root || path.startsWith(`${root}/`);
|
|
13
|
+
}
|
|
14
|
+
var MemoryMeta = class {
|
|
15
|
+
entries = /* @__PURE__ */ new Map();
|
|
16
|
+
async get(paths) {
|
|
17
|
+
return paths.map((path) => this.entries.get(path));
|
|
18
|
+
}
|
|
19
|
+
async update(path, change) {
|
|
20
|
+
this.set(path, change(this.entries.get(path)));
|
|
21
|
+
}
|
|
22
|
+
set(path, entry) {
|
|
23
|
+
if (entry) this.entries.set(path, entry);
|
|
24
|
+
else this.entries.delete(path);
|
|
25
|
+
}
|
|
26
|
+
async move(from, to, root) {
|
|
27
|
+
await this.remove([to]);
|
|
28
|
+
const carried = [...this.entries.values()].filter((entry) => under(entry.path, from));
|
|
29
|
+
for (const entry of carried) this.entries.delete(entry.path);
|
|
30
|
+
for (const entry of carried) {
|
|
31
|
+
const path = to + entry.path.slice(from.length);
|
|
32
|
+
if (path !== to) this.entries.set(path, moved(entry, path));
|
|
33
|
+
}
|
|
34
|
+
this.entries.set(to, moved(root(carried.find((entry) => entry.path === from)), to));
|
|
35
|
+
}
|
|
36
|
+
async remove(paths) {
|
|
37
|
+
for (const key of [...this.entries.keys()]) {
|
|
38
|
+
if (paths.some((path) => under(key, path))) this.entries.delete(key);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
async links(dir) {
|
|
42
|
+
return [...this.entries.values()].filter((entry) => entry.dir === dir);
|
|
43
|
+
}
|
|
44
|
+
async under(path) {
|
|
45
|
+
return [...this.entries.values()].filter((entry) => under(entry.path, path));
|
|
46
|
+
}
|
|
47
|
+
async all() {
|
|
48
|
+
return [...this.entries.values()];
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
function descendants(path) {
|
|
52
|
+
return IDBKeyRange.bound(`${path}/`, `${path}0`, false, true);
|
|
53
|
+
}
|
|
54
|
+
function settle(transaction, value) {
|
|
55
|
+
return new Promise((resolve, reject) => {
|
|
56
|
+
transaction.oncomplete = () => resolve(value());
|
|
57
|
+
transaction.onabort = () => reject(transaction.error ?? new Error("metadata transaction aborted"));
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
var IndexedDbMeta = class {
|
|
61
|
+
db;
|
|
62
|
+
constructor(db) {
|
|
63
|
+
this.db = db;
|
|
64
|
+
db.onversionchange = () => db.close();
|
|
65
|
+
}
|
|
66
|
+
transaction(mode) {
|
|
67
|
+
const transaction = this.db.transaction(META_STORE, mode);
|
|
68
|
+
return [transaction, transaction.objectStore(META_STORE)];
|
|
69
|
+
}
|
|
70
|
+
async get(paths) {
|
|
71
|
+
const [transaction, store] = this.transaction("readonly");
|
|
72
|
+
const found = paths.map(() => void 0);
|
|
73
|
+
paths.forEach((path, i) => {
|
|
74
|
+
const request = store.get(path);
|
|
75
|
+
request.onsuccess = () => {
|
|
76
|
+
found[i] = request.result;
|
|
77
|
+
};
|
|
78
|
+
});
|
|
79
|
+
return settle(transaction, () => found);
|
|
80
|
+
}
|
|
81
|
+
async update(path, change) {
|
|
82
|
+
const [transaction, store] = this.transaction("readwrite");
|
|
83
|
+
const request = store.get(path);
|
|
84
|
+
request.onsuccess = () => {
|
|
85
|
+
const next = change(request.result);
|
|
86
|
+
if (next) store.put(next);
|
|
87
|
+
else store.delete(path);
|
|
88
|
+
};
|
|
89
|
+
return settle(transaction, () => void 0);
|
|
90
|
+
}
|
|
91
|
+
async move(from, to, root) {
|
|
92
|
+
const [transaction, store] = this.transaction("readwrite");
|
|
93
|
+
store.delete(to);
|
|
94
|
+
store.delete(descendants(to));
|
|
95
|
+
const source = store.get(from);
|
|
96
|
+
const below = store.getAll(descendants(from));
|
|
97
|
+
below.onsuccess = () => {
|
|
98
|
+
store.delete(from);
|
|
99
|
+
store.delete(descendants(from));
|
|
100
|
+
for (const entry of below.result) {
|
|
101
|
+
store.put(moved(entry, to + entry.path.slice(from.length)));
|
|
102
|
+
}
|
|
103
|
+
store.put(moved(root(source.result), to));
|
|
104
|
+
};
|
|
105
|
+
return settle(transaction, () => void 0);
|
|
106
|
+
}
|
|
107
|
+
async remove(paths) {
|
|
108
|
+
const [transaction, store] = this.transaction("readwrite");
|
|
109
|
+
for (const path of paths) {
|
|
110
|
+
store.delete(path);
|
|
111
|
+
store.delete(descendants(path));
|
|
112
|
+
}
|
|
113
|
+
return settle(transaction, () => void 0);
|
|
114
|
+
}
|
|
115
|
+
async links(dir) {
|
|
116
|
+
const [transaction, store] = this.transaction("readonly");
|
|
117
|
+
const request = store.index("dir").getAll(dir);
|
|
118
|
+
return settle(transaction, () => request.result);
|
|
119
|
+
}
|
|
120
|
+
async under(path) {
|
|
121
|
+
const [transaction, store] = this.transaction("readonly");
|
|
122
|
+
const self = store.get(path);
|
|
123
|
+
const below = store.getAll(descendants(path));
|
|
124
|
+
return settle(
|
|
125
|
+
transaction,
|
|
126
|
+
() => [self.result, ...below.result].filter(
|
|
127
|
+
(entry) => entry !== void 0
|
|
128
|
+
)
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
async all() {
|
|
132
|
+
const [transaction, store] = this.transaction("readonly");
|
|
133
|
+
const request = store.getAll();
|
|
134
|
+
return settle(transaction, () => request.result);
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
function openMeta(name, factory) {
|
|
138
|
+
return new Promise((resolve, reject) => {
|
|
139
|
+
const request = factory.open(name, META_VERSION);
|
|
140
|
+
request.onupgradeneeded = (event) => {
|
|
141
|
+
if (event.oldVersion < 1) {
|
|
142
|
+
request.result.createObjectStore(META_STORE, { keyPath: "path" }).createIndex("dir", "dir");
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
request.onsuccess = () => resolve(new IndexedDbMeta(request.result));
|
|
146
|
+
request.onerror = () => reject(request.error);
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
|
|
1
150
|
// src/fs/types.ts
|
|
2
151
|
function fsError(code, path) {
|
|
3
152
|
return Object.assign(new Error(`${code}: ${path}`), { code });
|
|
@@ -14,11 +163,26 @@ function normalizePath(path) {
|
|
|
14
163
|
function resolvePath(base, path) {
|
|
15
164
|
return normalizePath(path.startsWith("/") ? path : `${base}/${path}`);
|
|
16
165
|
}
|
|
166
|
+
function inodeOf(path) {
|
|
167
|
+
let h1 = 3735928559;
|
|
168
|
+
let h2 = 1103547991;
|
|
169
|
+
for (let i = 0; i < path.length; i++) {
|
|
170
|
+
const c = path.charCodeAt(i);
|
|
171
|
+
h1 = Math.imul(h1 ^ c, 2654435761);
|
|
172
|
+
h2 = Math.imul(h2 ^ c, 1597334677);
|
|
173
|
+
}
|
|
174
|
+
h1 = Math.imul(h1 ^ h1 >>> 16, 2246822507) ^ Math.imul(h2 ^ h2 >>> 13, 3266489909);
|
|
175
|
+
h2 = Math.imul(h2 ^ h2 >>> 16, 2246822507) ^ Math.imul(h1 ^ h1 >>> 13, 3266489909);
|
|
176
|
+
return 4294967296 * (2097151 & h2) + (h1 >>> 0) || 1;
|
|
177
|
+
}
|
|
17
178
|
|
|
18
179
|
// src/fs/opfs.ts
|
|
19
180
|
var S_IFDIR = 16384;
|
|
20
181
|
var S_IFREG = 32768;
|
|
182
|
+
var S_IFLNK = 40960;
|
|
21
183
|
var PERM_MASK = 4095;
|
|
184
|
+
var READ_ATTEMPTS = 3;
|
|
185
|
+
var MAX_LINKS = 40;
|
|
22
186
|
var EXECUTABLE = /\/node_modules\/(?:@[^/]+\/)?[^/]+\/bin\//;
|
|
23
187
|
var ERRNO_BY_DOM_ERROR = {
|
|
24
188
|
NotFoundError: "ENOENT",
|
|
@@ -32,26 +196,43 @@ function translate(err, path) {
|
|
|
32
196
|
const name = err?.name;
|
|
33
197
|
return fsError(typeof name === "string" && ERRNO_BY_DOM_ERROR[name] || "EIO", path);
|
|
34
198
|
}
|
|
199
|
+
function missing(err) {
|
|
200
|
+
const code = err?.code;
|
|
201
|
+
return code === "ENOENT" || code === "ENOTDIR";
|
|
202
|
+
}
|
|
35
203
|
function split(path) {
|
|
36
204
|
return path.split("/").filter(Boolean);
|
|
37
205
|
}
|
|
206
|
+
function parentOf2(path) {
|
|
207
|
+
return path.slice(0, path.lastIndexOf("/")) || "/";
|
|
208
|
+
}
|
|
209
|
+
function baseOf(path) {
|
|
210
|
+
return path.slice(path.lastIndexOf("/") + 1);
|
|
211
|
+
}
|
|
212
|
+
function prefixes(path) {
|
|
213
|
+
const parts = split(path);
|
|
214
|
+
return parts.map((_, i) => `/${parts.slice(0, i + 1).join("/")}`);
|
|
215
|
+
}
|
|
38
216
|
function within(path, root) {
|
|
39
217
|
return path === root || path.startsWith(`${root}/`);
|
|
40
218
|
}
|
|
219
|
+
function isHandle(child) {
|
|
220
|
+
return "kind" in child;
|
|
221
|
+
}
|
|
41
222
|
var OpfsFs = class {
|
|
42
|
-
modes = /* @__PURE__ */ new Map();
|
|
43
|
-
times = /* @__PURE__ */ new Map();
|
|
44
223
|
root;
|
|
45
|
-
|
|
224
|
+
meta;
|
|
225
|
+
constructor(root, meta = new MemoryMeta()) {
|
|
46
226
|
this.root = root;
|
|
227
|
+
this.meta = meta;
|
|
47
228
|
}
|
|
48
229
|
resolvePath(base, path) {
|
|
49
230
|
return resolvePath(base, path);
|
|
50
231
|
}
|
|
51
|
-
async dir(path
|
|
232
|
+
async dir(path) {
|
|
52
233
|
let dir = this.root;
|
|
53
234
|
try {
|
|
54
|
-
for (const part of split(path)) dir = await dir.getDirectoryHandle(part
|
|
235
|
+
for (const part of split(path)) dir = await dir.getDirectoryHandle(part);
|
|
55
236
|
} catch (err) {
|
|
56
237
|
throw translate(err, path);
|
|
57
238
|
}
|
|
@@ -73,42 +254,117 @@ var OpfsFs = class {
|
|
|
73
254
|
}
|
|
74
255
|
return dir.getDirectoryHandle(name);
|
|
75
256
|
}
|
|
76
|
-
|
|
257
|
+
real(path) {
|
|
258
|
+
return this.handle(path).then(
|
|
259
|
+
() => true,
|
|
260
|
+
() => false
|
|
261
|
+
);
|
|
262
|
+
}
|
|
263
|
+
async linkAlong(path) {
|
|
264
|
+
for (const entry of await this.meta.get(prefixes(path))) {
|
|
265
|
+
if (entry?.link === void 0) continue;
|
|
266
|
+
if (!await this.real(entry.path) && await this.real(parentOf2(entry.path))) return entry;
|
|
267
|
+
}
|
|
268
|
+
return void 0;
|
|
269
|
+
}
|
|
270
|
+
async locate(path, follow) {
|
|
271
|
+
let current = path;
|
|
272
|
+
for (let hops = 0; hops <= MAX_LINKS; hops++) {
|
|
273
|
+
try {
|
|
274
|
+
return { path: current, handle: await this.handle(current) };
|
|
275
|
+
} catch (err) {
|
|
276
|
+
if (!missing(err)) throw err;
|
|
277
|
+
const link = await this.linkAlong(current);
|
|
278
|
+
if (!link) throw err;
|
|
279
|
+
if (link.path === current && !follow) return { path: current, link };
|
|
280
|
+
const rest = current.slice(link.path.length);
|
|
281
|
+
current = resolvePath(parentOf2(link.path), `${link.link}${rest}`);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
throw fsError("ELOOP", path);
|
|
285
|
+
}
|
|
286
|
+
mode(path, directory, meta) {
|
|
77
287
|
const fallback = directory || EXECUTABLE.test(path) ? 493 : 420;
|
|
78
|
-
return (directory ? S_IFDIR : S_IFREG) | (
|
|
288
|
+
return (directory ? S_IFDIR : S_IFREG) | (meta?.mode ?? fallback);
|
|
79
289
|
}
|
|
80
|
-
async
|
|
81
|
-
const handle = await this.handle(path);
|
|
290
|
+
async statOf(path, handle, meta) {
|
|
82
291
|
const directory = handle.kind === "directory";
|
|
83
292
|
const file = directory ? void 0 : await handle.getFile();
|
|
293
|
+
const modified = file?.lastModified ?? 0;
|
|
294
|
+
const mtime = meta?.mtimeMs !== void 0 && meta.mtimeFor === modified ? meta.mtimeMs : modified;
|
|
84
295
|
return {
|
|
85
296
|
isFile: !directory,
|
|
86
297
|
isDirectory: directory,
|
|
87
298
|
isSymbolicLink: false,
|
|
88
299
|
size: file?.size ?? 0,
|
|
89
|
-
mode: this.mode(path, directory),
|
|
90
|
-
mtime: new Date(
|
|
300
|
+
mode: this.mode(path, directory, meta),
|
|
301
|
+
mtime: new Date(mtime),
|
|
302
|
+
atime: new Date(meta?.atimeMs ?? mtime),
|
|
303
|
+
ctime: new Date(Math.max(meta?.ctimeMs ?? mtime, modified)),
|
|
304
|
+
ino: meta?.ino ?? inodeOf(path)
|
|
91
305
|
};
|
|
92
306
|
}
|
|
93
|
-
|
|
94
|
-
|
|
307
|
+
linkStat(entry) {
|
|
308
|
+
const time = new Date(entry.mtimeMs ?? 0);
|
|
309
|
+
return {
|
|
310
|
+
isFile: false,
|
|
311
|
+
isDirectory: false,
|
|
312
|
+
isSymbolicLink: true,
|
|
313
|
+
size: new TextEncoder().encode(entry.link).length,
|
|
314
|
+
mode: S_IFLNK | 511,
|
|
315
|
+
mtime: time,
|
|
316
|
+
atime: time,
|
|
317
|
+
ctime: new Date(entry.ctimeMs ?? 0),
|
|
318
|
+
ino: entry.ino ?? inodeOf(entry.path)
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
async describe(found) {
|
|
322
|
+
if (found.link) return this.linkStat(found.link);
|
|
323
|
+
const [meta] = await this.meta.get([found.path]);
|
|
324
|
+
return this.statOf(found.path, found.handle, meta);
|
|
325
|
+
}
|
|
326
|
+
async stat(path) {
|
|
327
|
+
return this.describe(await this.locate(path, true));
|
|
328
|
+
}
|
|
329
|
+
async lstat(path) {
|
|
330
|
+
return this.describe(await this.locate(path, false));
|
|
95
331
|
}
|
|
96
332
|
async exists(path) {
|
|
97
|
-
return this.
|
|
333
|
+
return this.locate(path, true).then(
|
|
98
334
|
() => true,
|
|
99
335
|
() => false
|
|
100
336
|
);
|
|
101
337
|
}
|
|
102
338
|
async readFileBuffer(path) {
|
|
103
|
-
const handle = await this.
|
|
339
|
+
const handle = (await this.locate(path, true)).handle;
|
|
104
340
|
if (handle.kind === "directory") throw fsError("EISDIR", path);
|
|
105
|
-
|
|
341
|
+
for (let attempt = 1; ; attempt++) {
|
|
342
|
+
try {
|
|
343
|
+
return new Uint8Array(await (await handle.getFile()).arrayBuffer());
|
|
344
|
+
} catch (err) {
|
|
345
|
+
if (err?.name !== "NotReadableError" || attempt >= READ_ATTEMPTS) {
|
|
346
|
+
throw translate(err, path);
|
|
347
|
+
}
|
|
348
|
+
await new Promise((resolve) => setTimeout(resolve, attempt * 10));
|
|
349
|
+
}
|
|
350
|
+
}
|
|
106
351
|
}
|
|
107
352
|
async readFile(path) {
|
|
108
353
|
return new TextDecoder().decode(await this.readFileBuffer(path));
|
|
109
354
|
}
|
|
355
|
+
async writeTarget(path) {
|
|
356
|
+
try {
|
|
357
|
+
return (await this.locate(path, true)).path;
|
|
358
|
+
} catch (err) {
|
|
359
|
+
if (!missing(err)) throw err;
|
|
360
|
+
}
|
|
361
|
+
const [entry] = await this.meta.get([path]);
|
|
362
|
+
const wanted = entry?.link === void 0 ? path : resolvePath(parentOf2(path), entry.link);
|
|
363
|
+
return resolvePath((await this.locate(parentOf2(wanted), true)).path, baseOf(wanted));
|
|
364
|
+
}
|
|
110
365
|
async writeFile(path, content) {
|
|
111
|
-
const
|
|
366
|
+
const target = await this.writeTarget(path);
|
|
367
|
+
const [dir, name] = await this.parent(target);
|
|
112
368
|
let file;
|
|
113
369
|
try {
|
|
114
370
|
file = await dir.getFileHandle(name, { create: true });
|
|
@@ -119,77 +375,146 @@ var OpfsFs = class {
|
|
|
119
375
|
const writable = await file.createWritable();
|
|
120
376
|
await writable.write(content);
|
|
121
377
|
await writable.close();
|
|
122
|
-
this.times.delete(path);
|
|
123
378
|
}
|
|
124
|
-
async
|
|
125
|
-
const
|
|
379
|
+
async listing(path) {
|
|
380
|
+
const found = await this.locate(path, true);
|
|
381
|
+
const handle = found.handle;
|
|
126
382
|
if (handle.kind !== "directory") throw fsError("ENOTDIR", path);
|
|
127
|
-
const
|
|
128
|
-
for await (const
|
|
129
|
-
|
|
383
|
+
const children = /* @__PURE__ */ new Map();
|
|
384
|
+
for await (const child of handle.values()) children.set(child.name, child);
|
|
385
|
+
for (const link of await this.meta.links(found.path)) {
|
|
386
|
+
const name = baseOf(link.path);
|
|
387
|
+
if (!children.has(name)) children.set(name, link);
|
|
388
|
+
}
|
|
389
|
+
return [found.path, [...children].sort(([a], [b]) => a < b ? -1 : 1)];
|
|
390
|
+
}
|
|
391
|
+
async readdir(path) {
|
|
392
|
+
return (await this.listing(path))[1].map(([name]) => name);
|
|
393
|
+
}
|
|
394
|
+
async readdirStat(path) {
|
|
395
|
+
const [dir, children] = await this.listing(path);
|
|
396
|
+
const base = dir === "/" ? "" : dir;
|
|
397
|
+
const metas = await this.meta.get(children.map(([name]) => `${base}/${name}`));
|
|
398
|
+
return Promise.all(
|
|
399
|
+
children.map(async ([name, child], i) => {
|
|
400
|
+
if (!isHandle(child)) return [name, this.linkStat(child)];
|
|
401
|
+
const stat = await this.statOf(`${base}/${name}`, child, metas[i]).catch(() => null);
|
|
402
|
+
return [name, stat];
|
|
403
|
+
})
|
|
404
|
+
);
|
|
130
405
|
}
|
|
131
406
|
async mkdir(path, options = {}) {
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
407
|
+
let found;
|
|
408
|
+
try {
|
|
409
|
+
found = await this.locate(path, true);
|
|
410
|
+
} catch (err) {
|
|
411
|
+
if (!missing(err)) throw err;
|
|
135
412
|
}
|
|
136
|
-
if (
|
|
137
|
-
|
|
138
|
-
|
|
413
|
+
if (found) {
|
|
414
|
+
const directory = found.handle?.kind === "directory";
|
|
415
|
+
if (options.recursive && directory) return;
|
|
416
|
+
throw fsError(options.recursive ? "ENOTDIR" : "EEXIST", path);
|
|
417
|
+
}
|
|
418
|
+
const [entry] = await this.meta.get([path]);
|
|
419
|
+
if (entry?.link !== void 0) throw fsError("EEXIST", path);
|
|
420
|
+
if (options.recursive) await this.mkdir(parentOf2(path), options);
|
|
421
|
+
const parent = await this.locate(parentOf2(path), true);
|
|
422
|
+
if (parent.handle?.kind !== "directory") throw fsError("ENOTDIR", path);
|
|
423
|
+
await parent.handle.getDirectoryHandle(baseOf(path), { create: true });
|
|
139
424
|
}
|
|
140
425
|
async rm(path, options = {}) {
|
|
426
|
+
let found;
|
|
141
427
|
try {
|
|
142
|
-
|
|
143
|
-
await dir.removeEntry(name, { recursive: options.recursive === true });
|
|
428
|
+
found = await this.locate(path, false);
|
|
144
429
|
} catch (err) {
|
|
145
430
|
const error = translate(err, path);
|
|
146
431
|
if (options.force && error.code === "ENOENT") return;
|
|
147
432
|
throw error;
|
|
148
433
|
}
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
}
|
|
156
|
-
carry(from, to) {
|
|
157
|
-
for (const map of [this.modes, this.times]) {
|
|
158
|
-
for (const [key, value] of [...map]) {
|
|
159
|
-
if (!within(key, from)) continue;
|
|
160
|
-
map.delete(key);
|
|
161
|
-
map.set(to + key.slice(from.length), value);
|
|
434
|
+
if (found.handle) {
|
|
435
|
+
const [dir, name] = await this.parent(found.path);
|
|
436
|
+
try {
|
|
437
|
+
await dir.removeEntry(name, { recursive: options.recursive === true });
|
|
438
|
+
} catch (err) {
|
|
439
|
+
throw translate(err, path);
|
|
162
440
|
}
|
|
163
441
|
}
|
|
442
|
+
await this.meta.remove([found.path]);
|
|
164
443
|
}
|
|
165
444
|
async rename(from, to) {
|
|
166
445
|
if (from === to) return;
|
|
167
|
-
const source = await this.
|
|
168
|
-
|
|
169
|
-
const
|
|
170
|
-
|
|
171
|
-
if (
|
|
172
|
-
|
|
446
|
+
const source = await this.locate(from, false);
|
|
447
|
+
const parent = await this.locate(parentOf2(to), true);
|
|
448
|
+
const destination = resolvePath(parent.path, baseOf(to));
|
|
449
|
+
const sourceStat = await this.describe(source);
|
|
450
|
+
if (sourceStat.isDirectory && within(destination, source.path)) throw fsError("EINVAL", to);
|
|
451
|
+
const target = await this.locate(destination, false).catch((err) => {
|
|
452
|
+
if (missing(err)) return null;
|
|
453
|
+
throw err;
|
|
454
|
+
});
|
|
455
|
+
const targetStat = target && await this.describe(target);
|
|
456
|
+
if (targetStat?.isDirectory && !sourceStat.isDirectory) throw fsError("EISDIR", to);
|
|
457
|
+
if (targetStat && !targetStat.isDirectory && sourceStat.isDirectory) {
|
|
458
|
+
throw fsError("ENOTDIR", to);
|
|
459
|
+
}
|
|
460
|
+
if (targetStat?.isDirectory && (await this.readdir(destination)).length > 0) {
|
|
173
461
|
throw fsError("ENOTEMPTY", to);
|
|
174
462
|
}
|
|
175
|
-
const
|
|
176
|
-
const
|
|
177
|
-
|
|
463
|
+
const now = Date.now();
|
|
464
|
+
const carry = (entry) => ({
|
|
465
|
+
ino: inodeOf(source.path),
|
|
466
|
+
...entry,
|
|
467
|
+
path: destination,
|
|
468
|
+
ctimeMs: now
|
|
469
|
+
});
|
|
470
|
+
if (source.link) {
|
|
471
|
+
if (target?.handle) await this.rm(destination);
|
|
472
|
+
await this.meta.move(source.path, destination, carry);
|
|
473
|
+
return;
|
|
474
|
+
}
|
|
475
|
+
const dir = parent.handle;
|
|
476
|
+
const name = baseOf(destination);
|
|
477
|
+
const swap = targetStat?.isDirectory === true;
|
|
178
478
|
const staging = swap ? `.${name}.${crypto.randomUUID()}` : name;
|
|
179
|
-
const
|
|
180
|
-
|
|
181
|
-
this.
|
|
182
|
-
|
|
183
|
-
if (!
|
|
184
|
-
|
|
185
|
-
if (
|
|
479
|
+
const timed = await this.explicitTimes(source.path);
|
|
480
|
+
const moved2 = await this.place(source.handle, dir, staging);
|
|
481
|
+
if (swap) await this.rm(destination, { recursive: true });
|
|
482
|
+
await this.meta.move(source.path, destination, carry);
|
|
483
|
+
if (!moved2) await this.rm(source.path, { recursive: true });
|
|
484
|
+
let copied = !moved2;
|
|
485
|
+
if (swap) {
|
|
486
|
+
const staged = await dir.getDirectoryHandle(staging);
|
|
487
|
+
const placed = await this.place(staged, dir, name);
|
|
488
|
+
if (!placed) await dir.removeEntry(staging, { recursive: true });
|
|
489
|
+
copied ||= !placed;
|
|
490
|
+
}
|
|
491
|
+
if (copied) await this.restamp(destination, timed);
|
|
492
|
+
}
|
|
493
|
+
async modified(path) {
|
|
494
|
+
const handle = await this.handle(path);
|
|
495
|
+
return handle.kind === "file" ? (await handle.getFile()).lastModified : 0;
|
|
496
|
+
}
|
|
497
|
+
async explicitTimes(path) {
|
|
498
|
+
const timed = [];
|
|
499
|
+
for (const entry of await this.meta.under(path)) {
|
|
500
|
+
if (entry.mtimeFor === void 0 || entry.link !== void 0) continue;
|
|
501
|
+
const current = await this.modified(entry.path).catch(() => void 0);
|
|
502
|
+
if (current === entry.mtimeFor) timed.push(entry.path.slice(path.length));
|
|
503
|
+
}
|
|
504
|
+
return timed;
|
|
505
|
+
}
|
|
506
|
+
async restamp(path, timed) {
|
|
507
|
+
for (const rest of timed) {
|
|
508
|
+
const mtimeFor = await this.modified(path + rest);
|
|
509
|
+
await this.meta.update(path + rest, (entry) => entry && { ...entry, mtimeFor });
|
|
510
|
+
}
|
|
186
511
|
}
|
|
187
512
|
async place(handle, dir, name) {
|
|
188
|
-
const
|
|
513
|
+
const moved2 = await handle.move?.(dir, name).then(
|
|
189
514
|
() => true,
|
|
190
515
|
() => false
|
|
191
516
|
);
|
|
192
|
-
if (
|
|
517
|
+
if (moved2) return true;
|
|
193
518
|
try {
|
|
194
519
|
await this.copy(handle, dir, name);
|
|
195
520
|
} catch (err) {
|
|
@@ -208,23 +533,123 @@ var OpfsFs = class {
|
|
|
208
533
|
const target = await dir.getDirectoryHandle(name, { create: true });
|
|
209
534
|
for await (const child of handle.values()) await this.copy(child, target, child.name);
|
|
210
535
|
}
|
|
211
|
-
async symlink(
|
|
212
|
-
|
|
536
|
+
async symlink(target, path) {
|
|
537
|
+
const parent = await this.locate(parentOf2(path), true);
|
|
538
|
+
if (parent.handle?.kind !== "directory") throw fsError("ENOTDIR", path);
|
|
539
|
+
const at = resolvePath(parent.path, baseOf(path));
|
|
540
|
+
if (await this.locate(at, false).then(
|
|
541
|
+
() => true,
|
|
542
|
+
(err) => !missing(err)
|
|
543
|
+
)) {
|
|
544
|
+
throw fsError("EEXIST", path);
|
|
545
|
+
}
|
|
546
|
+
const now = Date.now();
|
|
547
|
+
await this.meta.update(at, () => ({
|
|
548
|
+
path: at,
|
|
549
|
+
link: target,
|
|
550
|
+
dir: parent.path,
|
|
551
|
+
ino: inodeOf(at),
|
|
552
|
+
mtimeMs: now,
|
|
553
|
+
ctimeMs: now
|
|
554
|
+
}));
|
|
213
555
|
}
|
|
214
556
|
async readlink(path) {
|
|
215
|
-
await this.
|
|
557
|
+
const found = await this.locate(path, false);
|
|
558
|
+
if (found.link) return found.link.link;
|
|
216
559
|
throw fsError("EINVAL", path);
|
|
217
560
|
}
|
|
218
561
|
async chmod(path, mode) {
|
|
219
|
-
await this.
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
562
|
+
const found = await this.locate(path, true);
|
|
563
|
+
const ctimeMs = Date.now();
|
|
564
|
+
await this.meta.update(found.path, (entry) => ({
|
|
565
|
+
...entry,
|
|
566
|
+
path: found.path,
|
|
567
|
+
mode: mode & PERM_MASK,
|
|
568
|
+
ctimeMs
|
|
569
|
+
}));
|
|
570
|
+
}
|
|
571
|
+
async utimes(path, atime, mtime) {
|
|
572
|
+
const found = await this.locate(path, true);
|
|
573
|
+
const handle = found.handle;
|
|
574
|
+
const file = handle.kind === "file" ? await handle.getFile() : void 0;
|
|
575
|
+
const ctimeMs = Date.now();
|
|
576
|
+
await this.meta.update(found.path, (entry) => ({
|
|
577
|
+
...entry,
|
|
578
|
+
path: found.path,
|
|
579
|
+
atimeMs: atime.getTime(),
|
|
580
|
+
mtimeMs: mtime.getTime(),
|
|
581
|
+
mtimeFor: file?.lastModified ?? 0,
|
|
582
|
+
ctimeMs
|
|
583
|
+
}));
|
|
584
|
+
}
|
|
585
|
+
async reconcile() {
|
|
586
|
+
let removed = 0;
|
|
587
|
+
for (const entry of await this.meta.all()) {
|
|
588
|
+
const real = await this.real(entry.path);
|
|
589
|
+
const stale = entry.link === void 0 ? !real : real || !await this.real(parentOf2(entry.path));
|
|
590
|
+
if (!stale) continue;
|
|
591
|
+
await this.meta.update(entry.path, () => void 0);
|
|
592
|
+
removed++;
|
|
593
|
+
}
|
|
594
|
+
return removed;
|
|
225
595
|
}
|
|
226
596
|
};
|
|
227
597
|
|
|
598
|
+
// src/kernel/signals.ts
|
|
599
|
+
var SIG = {
|
|
600
|
+
HUP: 1,
|
|
601
|
+
INT: 2,
|
|
602
|
+
QUIT: 3,
|
|
603
|
+
ILL: 4,
|
|
604
|
+
TRAP: 5,
|
|
605
|
+
ABRT: 6,
|
|
606
|
+
BUS: 7,
|
|
607
|
+
FPE: 8,
|
|
608
|
+
KILL: 9,
|
|
609
|
+
USR1: 10,
|
|
610
|
+
SEGV: 11,
|
|
611
|
+
USR2: 12,
|
|
612
|
+
PIPE: 13,
|
|
613
|
+
ALRM: 14,
|
|
614
|
+
TERM: 15,
|
|
615
|
+
STKFLT: 16,
|
|
616
|
+
CHLD: 17,
|
|
617
|
+
CONT: 18,
|
|
618
|
+
STOP: 19,
|
|
619
|
+
TSTP: 20,
|
|
620
|
+
TTIN: 21,
|
|
621
|
+
TTOU: 22,
|
|
622
|
+
URG: 23,
|
|
623
|
+
XCPU: 24,
|
|
624
|
+
XFSZ: 25,
|
|
625
|
+
VTALRM: 26,
|
|
626
|
+
PROF: 27,
|
|
627
|
+
WINCH: 28,
|
|
628
|
+
POLL: 29,
|
|
629
|
+
PWR: 30,
|
|
630
|
+
SYS: 31
|
|
631
|
+
};
|
|
632
|
+
var IGNORED_BY_DEFAULT = /* @__PURE__ */ new Set([SIG.CHLD, SIG.URG, SIG.WINCH]);
|
|
633
|
+
var STOPPING = /* @__PURE__ */ new Set([SIG.STOP, SIG.TSTP, SIG.TTIN, SIG.TTOU]);
|
|
634
|
+
function defaultAction(sig) {
|
|
635
|
+
if (sig === SIG.CONT) return "continue";
|
|
636
|
+
if (STOPPING.has(sig)) return "stop";
|
|
637
|
+
return IGNORED_BY_DEFAULT.has(sig) ? "ignore" : "terminate";
|
|
638
|
+
}
|
|
639
|
+
function isSignal(sig) {
|
|
640
|
+
return Number.isInteger(sig) && sig >= 1 && sig <= 31;
|
|
641
|
+
}
|
|
642
|
+
function sigbit(sig) {
|
|
643
|
+
return 1 << sig;
|
|
644
|
+
}
|
|
645
|
+
var SIGNAL_BY_NAME = {
|
|
646
|
+
SIGINT: SIG.INT,
|
|
647
|
+
SIGTERM: SIG.TERM,
|
|
648
|
+
SIGKILL: SIG.KILL,
|
|
649
|
+
SIGSTOP: SIG.STOP,
|
|
650
|
+
SIGCONT: SIG.CONT
|
|
651
|
+
};
|
|
652
|
+
|
|
228
653
|
// src/commands.ts
|
|
229
654
|
var NAME = /^[A-Za-z0-9._+-]+$/;
|
|
230
655
|
var ENV_KEY = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
@@ -315,19 +740,34 @@ async function scanCommands(fs, modules) {
|
|
|
315
740
|
var S_IFDIR2 = 16384;
|
|
316
741
|
var S_IFREG2 = 32768;
|
|
317
742
|
var COMMAND = /^\/(?:usr\/)?bin\/([^/]+)$/;
|
|
318
|
-
|
|
743
|
+
var MAX_LINKS2 = 40;
|
|
744
|
+
function missing2(err) {
|
|
319
745
|
return err?.code === "ENOENT";
|
|
320
746
|
}
|
|
321
|
-
function synthetic(directory) {
|
|
747
|
+
function synthetic(directory, path) {
|
|
748
|
+
const epoch = /* @__PURE__ */ new Date(0);
|
|
322
749
|
return {
|
|
323
750
|
isFile: !directory,
|
|
324
751
|
isDirectory: directory,
|
|
325
752
|
isSymbolicLink: false,
|
|
326
753
|
size: 0,
|
|
327
754
|
mode: (directory ? S_IFDIR2 : S_IFREG2) | 493,
|
|
328
|
-
mtime:
|
|
755
|
+
mtime: epoch,
|
|
756
|
+
atime: epoch,
|
|
757
|
+
ctime: epoch,
|
|
758
|
+
ino: inodeOf(path)
|
|
329
759
|
};
|
|
330
760
|
}
|
|
761
|
+
async function followLinks(fs, path) {
|
|
762
|
+
let current = path;
|
|
763
|
+
for (let hops = 0; hops < MAX_LINKS2; hops++) {
|
|
764
|
+
const st = await fs.lstat(current).catch(() => void 0);
|
|
765
|
+
if (!st?.isSymbolicLink) return current;
|
|
766
|
+
const parent = current.slice(0, current.lastIndexOf("/")) || "/";
|
|
767
|
+
current = fs.resolvePath(parent, await fs.readlink(current));
|
|
768
|
+
}
|
|
769
|
+
return current;
|
|
770
|
+
}
|
|
331
771
|
function withCommandDirs(fs, names) {
|
|
332
772
|
async function children(path) {
|
|
333
773
|
if (path === "/") return ["bin", "usr"];
|
|
@@ -336,15 +776,18 @@ function withCommandDirs(fs, names) {
|
|
|
336
776
|
return void 0;
|
|
337
777
|
}
|
|
338
778
|
async function virtual(path) {
|
|
339
|
-
if (path === "/usr" || path === "/bin" || path === "/usr/bin") return synthetic(true);
|
|
779
|
+
if (path === "/usr" || path === "/bin" || path === "/usr/bin") return synthetic(true, path);
|
|
340
780
|
const name = COMMAND.exec(path)?.[1];
|
|
341
|
-
return name !== void 0 && (await names()).has(name) ? synthetic(false) : void 0;
|
|
781
|
+
return name !== void 0 && (await names()).has(name) ? synthetic(false, path) : void 0;
|
|
782
|
+
}
|
|
783
|
+
async function linked(path) {
|
|
784
|
+
return await virtual(path) ?? virtual(await followLinks(fs, path));
|
|
342
785
|
}
|
|
343
786
|
async function stat(path, real) {
|
|
344
787
|
try {
|
|
345
788
|
return await real(path);
|
|
346
789
|
} catch (err) {
|
|
347
|
-
const found =
|
|
790
|
+
const found = missing2(err) ? await linked(path) : void 0;
|
|
348
791
|
if (found) return found;
|
|
349
792
|
throw err;
|
|
350
793
|
}
|
|
@@ -356,13 +799,13 @@ function withCommandDirs(fs, names) {
|
|
|
356
799
|
try {
|
|
357
800
|
return await fs.readFileBuffer(path);
|
|
358
801
|
} catch (err) {
|
|
359
|
-
if (
|
|
802
|
+
if (missing2(err) && (await linked(path))?.isFile) return new Uint8Array(0);
|
|
360
803
|
throw err;
|
|
361
804
|
}
|
|
362
805
|
},
|
|
363
806
|
writeFile: (path, content) => fs.writeFile(path, content),
|
|
364
807
|
async exists(path) {
|
|
365
|
-
return await fs.exists(path) || await
|
|
808
|
+
return await fs.exists(path) || await linked(path) !== void 0;
|
|
366
809
|
},
|
|
367
810
|
stat: (path) => stat(path, (p) => fs.stat(p)),
|
|
368
811
|
lstat: (path) => stat(path, (p) => fs.lstat(p)),
|
|
@@ -370,11 +813,31 @@ function withCommandDirs(fs, names) {
|
|
|
370
813
|
const extra = await children(path);
|
|
371
814
|
if (!extra) return fs.readdir(path);
|
|
372
815
|
const real = await fs.readdir(path).catch((err) => {
|
|
373
|
-
if (
|
|
816
|
+
if (missing2(err)) return [];
|
|
374
817
|
throw err;
|
|
375
818
|
});
|
|
376
819
|
return [.../* @__PURE__ */ new Set([...real, ...extra])].sort();
|
|
377
820
|
},
|
|
821
|
+
async readdirStat(path) {
|
|
822
|
+
const extra = await children(path);
|
|
823
|
+
if (!extra) return fs.readdirStat(path);
|
|
824
|
+
const real = await fs.readdirStat(path).catch((err) => {
|
|
825
|
+
if (missing2(err)) return [];
|
|
826
|
+
throw err;
|
|
827
|
+
});
|
|
828
|
+
const seen = new Set(real.map(([name]) => name));
|
|
829
|
+
const base = path === "/" ? "" : path;
|
|
830
|
+
const added = extra.filter((name) => !seen.has(name));
|
|
831
|
+
const virtuals = await Promise.all(
|
|
832
|
+
added.map(
|
|
833
|
+
async (name) => [
|
|
834
|
+
name,
|
|
835
|
+
await virtual(`${base}/${name}`)
|
|
836
|
+
]
|
|
837
|
+
)
|
|
838
|
+
);
|
|
839
|
+
return [...real, ...virtuals].sort(([a], [b]) => a < b ? -1 : 1);
|
|
840
|
+
},
|
|
378
841
|
mkdir: (path, options) => fs.mkdir(path, options),
|
|
379
842
|
rm: (path, options) => fs.rm(path, options),
|
|
380
843
|
rename: (from, to) => fs.rename(from, to),
|
|
@@ -948,21 +1411,14 @@ function statJson(s) {
|
|
|
948
1411
|
isSymbolicLink: s.isSymbolicLink,
|
|
949
1412
|
size: s.size,
|
|
950
1413
|
mode: s.mode,
|
|
951
|
-
mtimeMs: s.mtime.getTime()
|
|
1414
|
+
mtimeMs: s.mtime.getTime(),
|
|
1415
|
+
atimeMs: s.atime.getTime(),
|
|
1416
|
+
ctimeMs: s.ctime.getTime(),
|
|
1417
|
+
ino: s.ino
|
|
952
1418
|
};
|
|
953
1419
|
}
|
|
954
1420
|
async function readdirStat(fs, dir) {
|
|
955
|
-
|
|
956
|
-
const base = dir === "/" ? "" : dir;
|
|
957
|
-
return Promise.all(
|
|
958
|
-
names.map(async (name) => {
|
|
959
|
-
try {
|
|
960
|
-
return [name, statJson(await fs.lstat(`${base}/${name}`))];
|
|
961
|
-
} catch {
|
|
962
|
-
return [name, null];
|
|
963
|
-
}
|
|
964
|
-
})
|
|
965
|
-
);
|
|
1421
|
+
return (await fs.readdirStat(dir)).map(([name, stat]) => [name, stat && statJson(stat)]);
|
|
966
1422
|
}
|
|
967
1423
|
var done = { ok: true, kind: "void" };
|
|
968
1424
|
function json(value) {
|
|
@@ -1103,7 +1559,7 @@ function attachSyncSabResponder(port, sab, token, opts) {
|
|
|
1103
1559
|
Atomics.notify(header, SAB_I_STATE);
|
|
1104
1560
|
if (offset + chunk >= entry.payload.byteLength) drop(id);
|
|
1105
1561
|
}
|
|
1106
|
-
function
|
|
1562
|
+
function settle2(id, result) {
|
|
1107
1563
|
if (disposed) return;
|
|
1108
1564
|
const { status, payload } = encodeSabResult(result);
|
|
1109
1565
|
drop(id);
|
|
@@ -1122,7 +1578,7 @@ function attachSyncSabResponder(port, sab, token, opts) {
|
|
|
1122
1578
|
const entry = pending.get(data.id);
|
|
1123
1579
|
const offset = data.offset;
|
|
1124
1580
|
if (!entry || typeof offset !== "number" || !Number.isInteger(offset) || offset < 0 || offset > entry.payload.byteLength) {
|
|
1125
|
-
|
|
1581
|
+
settle2(data.id, { ok: false, errno: "EIO", message: "sync-sab: bad continuation" });
|
|
1126
1582
|
return;
|
|
1127
1583
|
}
|
|
1128
1584
|
publish(data.id, entry, offset);
|
|
@@ -1136,7 +1592,7 @@ function attachSyncSabResponder(port, sab, token, opts) {
|
|
|
1136
1592
|
try {
|
|
1137
1593
|
dispatched = dispatch(req);
|
|
1138
1594
|
} catch (err) {
|
|
1139
|
-
|
|
1595
|
+
settle2(id, {
|
|
1140
1596
|
ok: false,
|
|
1141
1597
|
errno: "EIO",
|
|
1142
1598
|
message: err instanceof Error ? err.message : String(err)
|
|
@@ -1144,8 +1600,8 @@ function attachSyncSabResponder(port, sab, token, opts) {
|
|
|
1144
1600
|
return;
|
|
1145
1601
|
}
|
|
1146
1602
|
void dispatched.then(
|
|
1147
|
-
(result) =>
|
|
1148
|
-
(err) =>
|
|
1603
|
+
(result) => settle2(id, result),
|
|
1604
|
+
(err) => settle2(id, {
|
|
1149
1605
|
ok: false,
|
|
1150
1606
|
errno: "EIO",
|
|
1151
1607
|
message: err instanceof Error ? err.message : String(err)
|
|
@@ -1163,61 +1619,6 @@ function attachSyncSabResponder(port, sab, token, opts) {
|
|
|
1163
1619
|
};
|
|
1164
1620
|
}
|
|
1165
1621
|
|
|
1166
|
-
// src/kernel/signals.ts
|
|
1167
|
-
var SIG = {
|
|
1168
|
-
HUP: 1,
|
|
1169
|
-
INT: 2,
|
|
1170
|
-
QUIT: 3,
|
|
1171
|
-
ILL: 4,
|
|
1172
|
-
TRAP: 5,
|
|
1173
|
-
ABRT: 6,
|
|
1174
|
-
BUS: 7,
|
|
1175
|
-
FPE: 8,
|
|
1176
|
-
KILL: 9,
|
|
1177
|
-
USR1: 10,
|
|
1178
|
-
SEGV: 11,
|
|
1179
|
-
USR2: 12,
|
|
1180
|
-
PIPE: 13,
|
|
1181
|
-
ALRM: 14,
|
|
1182
|
-
TERM: 15,
|
|
1183
|
-
STKFLT: 16,
|
|
1184
|
-
CHLD: 17,
|
|
1185
|
-
CONT: 18,
|
|
1186
|
-
STOP: 19,
|
|
1187
|
-
TSTP: 20,
|
|
1188
|
-
TTIN: 21,
|
|
1189
|
-
TTOU: 22,
|
|
1190
|
-
URG: 23,
|
|
1191
|
-
XCPU: 24,
|
|
1192
|
-
XFSZ: 25,
|
|
1193
|
-
VTALRM: 26,
|
|
1194
|
-
PROF: 27,
|
|
1195
|
-
WINCH: 28,
|
|
1196
|
-
POLL: 29,
|
|
1197
|
-
PWR: 30,
|
|
1198
|
-
SYS: 31
|
|
1199
|
-
};
|
|
1200
|
-
var IGNORED_BY_DEFAULT = /* @__PURE__ */ new Set([SIG.CHLD, SIG.URG, SIG.WINCH]);
|
|
1201
|
-
var STOPPING = /* @__PURE__ */ new Set([SIG.STOP, SIG.TSTP, SIG.TTIN, SIG.TTOU]);
|
|
1202
|
-
function defaultAction(sig) {
|
|
1203
|
-
if (sig === SIG.CONT) return "continue";
|
|
1204
|
-
if (STOPPING.has(sig)) return "stop";
|
|
1205
|
-
return IGNORED_BY_DEFAULT.has(sig) ? "ignore" : "terminate";
|
|
1206
|
-
}
|
|
1207
|
-
function isSignal(sig) {
|
|
1208
|
-
return Number.isInteger(sig) && sig >= 1 && sig <= 31;
|
|
1209
|
-
}
|
|
1210
|
-
function sigbit(sig) {
|
|
1211
|
-
return 1 << sig;
|
|
1212
|
-
}
|
|
1213
|
-
var SIGNAL_BY_NAME = {
|
|
1214
|
-
SIGINT: SIG.INT,
|
|
1215
|
-
SIGTERM: SIG.TERM,
|
|
1216
|
-
SIGKILL: SIG.KILL,
|
|
1217
|
-
SIGSTOP: SIG.STOP,
|
|
1218
|
-
SIGCONT: SIG.CONT
|
|
1219
|
-
};
|
|
1220
|
-
|
|
1221
1622
|
// src/kernel/tty.ts
|
|
1222
1623
|
var ICRNL = 256;
|
|
1223
1624
|
var IUTF8 = 16384;
|
|
@@ -1878,15 +2279,15 @@ var VfsNodes = class {
|
|
|
1878
2279
|
}
|
|
1879
2280
|
renamed(from, to) {
|
|
1880
2281
|
if (from === to) return;
|
|
1881
|
-
const
|
|
2282
|
+
const moved2 = [];
|
|
1882
2283
|
for (const [p, node] of this.byPath) {
|
|
1883
|
-
if (within2(p, from))
|
|
2284
|
+
if (within2(p, from)) moved2.push(node);
|
|
1884
2285
|
else if (within2(p, to)) {
|
|
1885
2286
|
node.orphaned = true;
|
|
1886
2287
|
this.byPath.delete(p);
|
|
1887
2288
|
}
|
|
1888
2289
|
}
|
|
1889
|
-
for (const node of
|
|
2290
|
+
for (const node of moved2) {
|
|
1890
2291
|
this.byPath.delete(node.path);
|
|
1891
2292
|
node.path = to + node.path.slice(from.length);
|
|
1892
2293
|
this.byPath.set(node.path, node);
|
|
@@ -2547,8 +2948,8 @@ function spawnWasmProcess(opts) {
|
|
|
2547
2948
|
return dispatchSyncFs(req);
|
|
2548
2949
|
};
|
|
2549
2950
|
const responder = attachSyncSabResponder(worker, sab, token, { dispatch });
|
|
2550
|
-
let
|
|
2551
|
-
const exited = new Promise((resolve) =>
|
|
2951
|
+
let settle2;
|
|
2952
|
+
const exited = new Promise((resolve) => settle2 = resolve);
|
|
2552
2953
|
let done3 = false;
|
|
2553
2954
|
let endedBy;
|
|
2554
2955
|
const finish = (code, sig) => {
|
|
@@ -2561,8 +2962,8 @@ function spawnWasmProcess(opts) {
|
|
|
2561
2962
|
revokeSyncFsToken(token);
|
|
2562
2963
|
worker.terminate();
|
|
2563
2964
|
void process.exit().then(
|
|
2564
|
-
() =>
|
|
2565
|
-
() =>
|
|
2965
|
+
() => settle2(code),
|
|
2966
|
+
() => settle2(code)
|
|
2566
2967
|
);
|
|
2567
2968
|
};
|
|
2568
2969
|
const onMessage = (event) => {
|
|
@@ -2719,6 +3120,7 @@ var JobTable = class {
|
|
|
2719
3120
|
var COMMAND2 = /^\/(?:usr\/)?bin\/([^/]+)$/;
|
|
2720
3121
|
var SHEBANG_MAX = 256;
|
|
2721
3122
|
var NOT_FOUND = 127;
|
|
3123
|
+
var SHARED_DIRS = ["/tmp", "/home"];
|
|
2722
3124
|
var encoder3 = new TextEncoder();
|
|
2723
3125
|
function baseName(path) {
|
|
2724
3126
|
return path.slice(path.lastIndexOf("/") + 1);
|
|
@@ -2766,6 +3168,7 @@ var Launcher = class {
|
|
|
2766
3168
|
jobs = new JobTable();
|
|
2767
3169
|
ptys = new PtyTable((tty, sig) => this.jobs.signalOwnedForeground(tty, sig));
|
|
2768
3170
|
nextPid = 1e3;
|
|
3171
|
+
terminals = 0;
|
|
2769
3172
|
constructor(options) {
|
|
2770
3173
|
this.base = options.fs;
|
|
2771
3174
|
this.createWorker = options.createWorker;
|
|
@@ -2783,7 +3186,9 @@ var Launcher = class {
|
|
|
2783
3186
|
const command = (await this.commands()).get(name);
|
|
2784
3187
|
return command && targetOf(command);
|
|
2785
3188
|
}
|
|
2786
|
-
const glue = this.fs.resolvePath(cwd, file);
|
|
3189
|
+
const glue = await followLinks(this.base, this.fs.resolvePath(cwd, file));
|
|
3190
|
+
const linked = COMMAND2.exec(glue)?.[1];
|
|
3191
|
+
if (linked !== void 0) return this.resolve(`/bin/${linked}`, argv0, cwd);
|
|
2787
3192
|
const wasm = modulePath(glue);
|
|
2788
3193
|
if (!await this.base.exists(glue) || !await this.base.exists(wasm)) return void 0;
|
|
2789
3194
|
return { glue, wasm, argv0: baseName(argv0 || file) };
|
|
@@ -2815,6 +3220,10 @@ var Launcher = class {
|
|
|
2815
3220
|
if (direct) return { target: direct, args: [...direct.prefix ?? [], ...argv.slice(1)] };
|
|
2816
3221
|
return this.interpreted(file, argv, cwd);
|
|
2817
3222
|
}
|
|
3223
|
+
async unrunnable(file, cwd) {
|
|
3224
|
+
const head = await this.base.readFileBuffer(this.fs.resolvePath(cwd, file)).catch(() => null);
|
|
3225
|
+
return head && !(head[0] === 35 && head[1] === 33) ? "ENOEXEC" : "ENOENT";
|
|
3226
|
+
}
|
|
2818
3227
|
module(path) {
|
|
2819
3228
|
return this.base.stat(path).then((st) => {
|
|
2820
3229
|
const key = `${path}:${st.size}:${st.mtime.getTime()}`;
|
|
@@ -2889,7 +3298,7 @@ var Launcher = class {
|
|
|
2889
3298
|
const planned = await this.plan(req.file, req.argv, req.cwd);
|
|
2890
3299
|
if (!planned) {
|
|
2891
3300
|
await fds.closeAll();
|
|
2892
|
-
throw new SpawnError(
|
|
3301
|
+
throw new SpawnError(await this.unrunnable(req.file, req.cwd));
|
|
2893
3302
|
}
|
|
2894
3303
|
const handle = await this.launch(planned, { env: req.env, cwd: req.cwd, fds, report, ppid });
|
|
2895
3304
|
return childHandle(handle);
|
|
@@ -2905,10 +3314,59 @@ var Launcher = class {
|
|
|
2905
3314
|
if (sig !== 0) handle.signal(sig);
|
|
2906
3315
|
return true;
|
|
2907
3316
|
}
|
|
2908
|
-
async
|
|
3317
|
+
async prepare() {
|
|
3318
|
+
for (const dir of SHARED_DIRS) await this.base.mkdir(dir, { recursive: true });
|
|
3319
|
+
}
|
|
3320
|
+
environment(cwd, extra) {
|
|
3321
|
+
return { PATH: "/usr/bin:/bin", HOME: "/home", ...this.env, PWD: cwd, ...extra };
|
|
3322
|
+
}
|
|
3323
|
+
async starting(argv, dir) {
|
|
2909
3324
|
this.catalog = void 0;
|
|
2910
|
-
const cwd = this.fs.resolvePath("/",
|
|
2911
|
-
const
|
|
3325
|
+
const cwd = this.fs.resolvePath("/", dir ?? "/");
|
|
3326
|
+
const [file = ""] = argv;
|
|
3327
|
+
await this.base.mkdir(cwd, { recursive: true });
|
|
3328
|
+
return { cwd, file, planned: await this.plan(file, argv, cwd) };
|
|
3329
|
+
}
|
|
3330
|
+
async openTerminal(argv, options) {
|
|
3331
|
+
const { cwd, file, planned } = await this.starting(argv, options.cwd);
|
|
3332
|
+
if (!planned) throw new Error(`${file}: command not found`);
|
|
3333
|
+
let leader = 0;
|
|
3334
|
+
const tty = new KernelTty(
|
|
3335
|
+
{ write: (bytes) => options.onData(bytes.slice()) },
|
|
3336
|
+
(sig) => this.jobs.signalForeground(tty, leader, sig)
|
|
3337
|
+
);
|
|
3338
|
+
tty.name = `/dev/tty${++this.terminals}`;
|
|
3339
|
+
tty.setSize(options.cols ?? 80, options.rows ?? 24);
|
|
3340
|
+
const fds = new FdTable();
|
|
3341
|
+
const stdio = tty.file();
|
|
3342
|
+
fds.installAt(0, stdio);
|
|
3343
|
+
fds.installAt(1, stdio.retain());
|
|
3344
|
+
fds.installAt(2, stdio.retain());
|
|
3345
|
+
const env = this.environment(cwd, {
|
|
3346
|
+
TERM: "xterm-256color",
|
|
3347
|
+
COLORTERM: "truecolor",
|
|
3348
|
+
...options.env
|
|
3349
|
+
});
|
|
3350
|
+
const report = (message) => options.onData(encoder3.encode(`${message}\r
|
|
3351
|
+
`));
|
|
3352
|
+
const handle = await this.launch(planned, { env, cwd, fds, report });
|
|
3353
|
+
leader = handle.pid;
|
|
3354
|
+
void handle.exited.then(() => tty.hangup());
|
|
3355
|
+
return {
|
|
3356
|
+
pid: handle.pid,
|
|
3357
|
+
exited: handle.exited,
|
|
3358
|
+
write: (bytes) => tty.receive(bytes),
|
|
3359
|
+
resize: (cols, rows) => tty.resize(cols, rows),
|
|
3360
|
+
signal: (sig) => this.jobs.signalForeground(tty, leader, sig),
|
|
3361
|
+
close: () => {
|
|
3362
|
+
tty.signalHangup();
|
|
3363
|
+
tty.hangup();
|
|
3364
|
+
}
|
|
3365
|
+
};
|
|
3366
|
+
}
|
|
3367
|
+
async run(argv, options = {}) {
|
|
3368
|
+
const { cwd, file, planned } = await this.starting(argv, options.cwd);
|
|
3369
|
+
const env = this.environment(cwd, options.env);
|
|
2912
3370
|
const out = [];
|
|
2913
3371
|
const err = [];
|
|
2914
3372
|
const collect = (chunks, tee) => (bytes) => {
|
|
@@ -2919,9 +3377,6 @@ var Launcher = class {
|
|
|
2919
3377
|
const stderr = collect(err, options.onStderr);
|
|
2920
3378
|
const report = (message) => stderr(encoder3.encode(`${message}
|
|
2921
3379
|
`));
|
|
2922
|
-
const [file = ""] = argv;
|
|
2923
|
-
await this.base.mkdir(cwd, { recursive: true });
|
|
2924
|
-
const planned = await this.plan(file, argv, cwd);
|
|
2925
3380
|
if (!planned) {
|
|
2926
3381
|
report(`${file}: command not found`);
|
|
2927
3382
|
return { status: NOT_FOUND, stdout: concat2(out), stderr: concat2(err) };
|
|
@@ -2937,24 +3392,64 @@ var Launcher = class {
|
|
|
2937
3392
|
};
|
|
2938
3393
|
|
|
2939
3394
|
// src/serve.ts
|
|
3395
|
+
function signalNumber(name) {
|
|
3396
|
+
const key = name.slice(3);
|
|
3397
|
+
if (!name.startsWith("SIG") || !Object.hasOwn(SIG, key))
|
|
3398
|
+
throw new Error(`unknown signal ${name}`);
|
|
3399
|
+
return SIG[key];
|
|
3400
|
+
}
|
|
3401
|
+
function act(session, req) {
|
|
3402
|
+
if (req.action === "write") session.write(req.bytes);
|
|
3403
|
+
else if (req.action === "resize") session.resize(req.cols, req.rows);
|
|
3404
|
+
else if (req.action === "signal") session.signal(signalNumber(req.signal));
|
|
3405
|
+
else session.close();
|
|
3406
|
+
}
|
|
2940
3407
|
function serveKernel(port, deps) {
|
|
2941
3408
|
let launcher;
|
|
3409
|
+
const terminals = /* @__PURE__ */ new Map();
|
|
2942
3410
|
const reply = (id, body) => port.postMessage({ id, ...body });
|
|
3411
|
+
async function ready2() {
|
|
3412
|
+
if (!launcher) throw new Error("the kernel is not initialized");
|
|
3413
|
+
return launcher;
|
|
3414
|
+
}
|
|
3415
|
+
async function terminal(req) {
|
|
3416
|
+
const onData = (bytes) => reply(req.id, { fd: 1, bytes });
|
|
3417
|
+
const session = await (await ready2()).openTerminal(req.argv, { ...req.options, onData });
|
|
3418
|
+
terminals.set(req.id, session);
|
|
3419
|
+
reply(req.id, { started: session.pid });
|
|
3420
|
+
try {
|
|
3421
|
+
return await session.exited;
|
|
3422
|
+
} finally {
|
|
3423
|
+
terminals.delete(req.id);
|
|
3424
|
+
}
|
|
3425
|
+
}
|
|
2943
3426
|
async function handle(req) {
|
|
2944
3427
|
if (req.op === "init") {
|
|
2945
|
-
launcher = (req.root ? Promise.resolve(req.root) : deps.storage()).then(
|
|
2946
|
-
|
|
2947
|
-
|
|
3428
|
+
launcher = (req.root ? Promise.resolve(req.root) : deps.storage()).then(async (root) => {
|
|
3429
|
+
const name = req.metadata ?? META_DB;
|
|
3430
|
+
const meta = name === false ? void 0 : await deps.metadata?.(name);
|
|
3431
|
+
const fs = new OpfsFs(root, meta);
|
|
3432
|
+
await fs.reconcile();
|
|
3433
|
+
const started = new Launcher({
|
|
3434
|
+
fs,
|
|
2948
3435
|
createWorker: deps.createWorker,
|
|
2949
3436
|
...req.modules ? { modules: req.modules } : {},
|
|
2950
3437
|
...req.env ? { env: req.env } : {}
|
|
2951
|
-
})
|
|
2952
|
-
|
|
3438
|
+
});
|
|
3439
|
+
await started.prepare();
|
|
3440
|
+
return started;
|
|
3441
|
+
});
|
|
2953
3442
|
await launcher;
|
|
2954
3443
|
return true;
|
|
2955
3444
|
}
|
|
2956
|
-
if (
|
|
2957
|
-
|
|
3445
|
+
if (req.op === "open-terminal") return terminal(req);
|
|
3446
|
+
if (req.op === "terminal") {
|
|
3447
|
+
const session = terminals.get(req.terminal);
|
|
3448
|
+
if (!session) throw new Error(`no terminal ${req.terminal}`);
|
|
3449
|
+
act(session, req);
|
|
3450
|
+
return true;
|
|
3451
|
+
}
|
|
3452
|
+
return (await ready2()).run(req.argv, {
|
|
2958
3453
|
...req.options,
|
|
2959
3454
|
onStdout: (bytes) => reply(req.id, { fd: 1, bytes }),
|
|
2960
3455
|
onStderr: (bytes) => reply(req.id, { fd: 2, bytes })
|
|
@@ -2973,6 +3468,7 @@ function serveKernel(port, deps) {
|
|
|
2973
3468
|
var processWorker = new URL("./process-worker.js", import.meta.url);
|
|
2974
3469
|
serveKernel(globalThis, {
|
|
2975
3470
|
storage: () => navigator.storage.getDirectory(),
|
|
2976
|
-
createWorker: () => new Worker(processWorker, { type: "module" })
|
|
3471
|
+
createWorker: () => new Worker(processWorker, { type: "module" }),
|
|
3472
|
+
metadata: (name) => openMeta(name, indexedDB)
|
|
2977
3473
|
});
|
|
2978
3474
|
//# sourceMappingURL=kernel-worker.js.map
|