@ai-ecoverse/slicc-kernel 1.1.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 +20 -2
- package/dist/index.js +2 -1
- package/dist/index.js.map +2 -2
- package/dist/kernel-worker.js +504 -106
- package/dist/kernel-worker.js.map +4 -4
- package/dist/process-worker.js +33 -17
- package/dist/process-worker.js.map +3 -3
- package/package.json +5 -6
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,12 +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;
|
|
22
184
|
var READ_ATTEMPTS = 3;
|
|
185
|
+
var MAX_LINKS = 40;
|
|
23
186
|
var EXECUTABLE = /\/node_modules\/(?:@[^/]+\/)?[^/]+\/bin\//;
|
|
24
187
|
var ERRNO_BY_DOM_ERROR = {
|
|
25
188
|
NotFoundError: "ENOENT",
|
|
@@ -33,26 +196,43 @@ function translate(err, path) {
|
|
|
33
196
|
const name = err?.name;
|
|
34
197
|
return fsError(typeof name === "string" && ERRNO_BY_DOM_ERROR[name] || "EIO", path);
|
|
35
198
|
}
|
|
199
|
+
function missing(err) {
|
|
200
|
+
const code = err?.code;
|
|
201
|
+
return code === "ENOENT" || code === "ENOTDIR";
|
|
202
|
+
}
|
|
36
203
|
function split(path) {
|
|
37
204
|
return path.split("/").filter(Boolean);
|
|
38
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
|
+
}
|
|
39
216
|
function within(path, root) {
|
|
40
217
|
return path === root || path.startsWith(`${root}/`);
|
|
41
218
|
}
|
|
219
|
+
function isHandle(child) {
|
|
220
|
+
return "kind" in child;
|
|
221
|
+
}
|
|
42
222
|
var OpfsFs = class {
|
|
43
|
-
modes = /* @__PURE__ */ new Map();
|
|
44
|
-
times = /* @__PURE__ */ new Map();
|
|
45
223
|
root;
|
|
46
|
-
|
|
224
|
+
meta;
|
|
225
|
+
constructor(root, meta = new MemoryMeta()) {
|
|
47
226
|
this.root = root;
|
|
227
|
+
this.meta = meta;
|
|
48
228
|
}
|
|
49
229
|
resolvePath(base, path) {
|
|
50
230
|
return resolvePath(base, path);
|
|
51
231
|
}
|
|
52
|
-
async dir(path
|
|
232
|
+
async dir(path) {
|
|
53
233
|
let dir = this.root;
|
|
54
234
|
try {
|
|
55
|
-
for (const part of split(path)) dir = await dir.getDirectoryHandle(part
|
|
235
|
+
for (const part of split(path)) dir = await dir.getDirectoryHandle(part);
|
|
56
236
|
} catch (err) {
|
|
57
237
|
throw translate(err, path);
|
|
58
238
|
}
|
|
@@ -74,34 +254,89 @@ var OpfsFs = class {
|
|
|
74
254
|
}
|
|
75
255
|
return dir.getDirectoryHandle(name);
|
|
76
256
|
}
|
|
77
|
-
|
|
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) {
|
|
78
287
|
const fallback = directory || EXECUTABLE.test(path) ? 493 : 420;
|
|
79
|
-
return (directory ? S_IFDIR : S_IFREG) | (
|
|
288
|
+
return (directory ? S_IFDIR : S_IFREG) | (meta?.mode ?? fallback);
|
|
80
289
|
}
|
|
81
|
-
async
|
|
82
|
-
const handle = await this.handle(path);
|
|
290
|
+
async statOf(path, handle, meta) {
|
|
83
291
|
const directory = handle.kind === "directory";
|
|
84
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;
|
|
85
295
|
return {
|
|
86
296
|
isFile: !directory,
|
|
87
297
|
isDirectory: directory,
|
|
88
298
|
isSymbolicLink: false,
|
|
89
299
|
size: file?.size ?? 0,
|
|
90
|
-
mode: this.mode(path, directory),
|
|
91
|
-
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)
|
|
305
|
+
};
|
|
306
|
+
}
|
|
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)
|
|
92
319
|
};
|
|
93
320
|
}
|
|
94
|
-
|
|
95
|
-
return this.
|
|
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));
|
|
96
331
|
}
|
|
97
332
|
async exists(path) {
|
|
98
|
-
return this.
|
|
333
|
+
return this.locate(path, true).then(
|
|
99
334
|
() => true,
|
|
100
335
|
() => false
|
|
101
336
|
);
|
|
102
337
|
}
|
|
103
338
|
async readFileBuffer(path) {
|
|
104
|
-
const handle = await this.
|
|
339
|
+
const handle = (await this.locate(path, true)).handle;
|
|
105
340
|
if (handle.kind === "directory") throw fsError("EISDIR", path);
|
|
106
341
|
for (let attempt = 1; ; attempt++) {
|
|
107
342
|
try {
|
|
@@ -117,8 +352,19 @@ var OpfsFs = class {
|
|
|
117
352
|
async readFile(path) {
|
|
118
353
|
return new TextDecoder().decode(await this.readFileBuffer(path));
|
|
119
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
|
+
}
|
|
120
365
|
async writeFile(path, content) {
|
|
121
|
-
const
|
|
366
|
+
const target = await this.writeTarget(path);
|
|
367
|
+
const [dir, name] = await this.parent(target);
|
|
122
368
|
let file;
|
|
123
369
|
try {
|
|
124
370
|
file = await dir.getFileHandle(name, { create: true });
|
|
@@ -129,77 +375,146 @@ var OpfsFs = class {
|
|
|
129
375
|
const writable = await file.createWritable();
|
|
130
376
|
await writable.write(content);
|
|
131
377
|
await writable.close();
|
|
132
|
-
this.times.delete(path);
|
|
133
378
|
}
|
|
134
|
-
async
|
|
135
|
-
const
|
|
379
|
+
async listing(path) {
|
|
380
|
+
const found = await this.locate(path, true);
|
|
381
|
+
const handle = found.handle;
|
|
136
382
|
if (handle.kind !== "directory") throw fsError("ENOTDIR", path);
|
|
137
|
-
const
|
|
138
|
-
for await (const
|
|
139
|
-
|
|
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
|
+
);
|
|
140
405
|
}
|
|
141
406
|
async mkdir(path, options = {}) {
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
407
|
+
let found;
|
|
408
|
+
try {
|
|
409
|
+
found = await this.locate(path, true);
|
|
410
|
+
} catch (err) {
|
|
411
|
+
if (!missing(err)) throw err;
|
|
145
412
|
}
|
|
146
|
-
if (
|
|
147
|
-
|
|
148
|
-
|
|
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 });
|
|
149
424
|
}
|
|
150
425
|
async rm(path, options = {}) {
|
|
426
|
+
let found;
|
|
151
427
|
try {
|
|
152
|
-
|
|
153
|
-
await dir.removeEntry(name, { recursive: options.recursive === true });
|
|
428
|
+
found = await this.locate(path, false);
|
|
154
429
|
} catch (err) {
|
|
155
430
|
const error = translate(err, path);
|
|
156
431
|
if (options.force && error.code === "ENOENT") return;
|
|
157
432
|
throw error;
|
|
158
433
|
}
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
}
|
|
166
|
-
carry(from, to) {
|
|
167
|
-
for (const map of [this.modes, this.times]) {
|
|
168
|
-
for (const [key, value] of [...map]) {
|
|
169
|
-
if (!within(key, from)) continue;
|
|
170
|
-
map.delete(key);
|
|
171
|
-
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);
|
|
172
440
|
}
|
|
173
441
|
}
|
|
442
|
+
await this.meta.remove([found.path]);
|
|
174
443
|
}
|
|
175
444
|
async rename(from, to) {
|
|
176
445
|
if (from === to) return;
|
|
177
|
-
const source = await this.
|
|
178
|
-
|
|
179
|
-
const
|
|
180
|
-
|
|
181
|
-
if (
|
|
182
|
-
|
|
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) {
|
|
183
461
|
throw fsError("ENOTEMPTY", to);
|
|
184
462
|
}
|
|
185
|
-
const
|
|
186
|
-
const
|
|
187
|
-
|
|
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;
|
|
188
478
|
const staging = swap ? `.${name}.${crypto.randomUUID()}` : name;
|
|
189
|
-
const
|
|
190
|
-
|
|
191
|
-
this.
|
|
192
|
-
|
|
193
|
-
if (!
|
|
194
|
-
|
|
195
|
-
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
|
+
}
|
|
196
511
|
}
|
|
197
512
|
async place(handle, dir, name) {
|
|
198
|
-
const
|
|
513
|
+
const moved2 = await handle.move?.(dir, name).then(
|
|
199
514
|
() => true,
|
|
200
515
|
() => false
|
|
201
516
|
);
|
|
202
|
-
if (
|
|
517
|
+
if (moved2) return true;
|
|
203
518
|
try {
|
|
204
519
|
await this.copy(handle, dir, name);
|
|
205
520
|
} catch (err) {
|
|
@@ -218,20 +533,65 @@ var OpfsFs = class {
|
|
|
218
533
|
const target = await dir.getDirectoryHandle(name, { create: true });
|
|
219
534
|
for await (const child of handle.values()) await this.copy(child, target, child.name);
|
|
220
535
|
}
|
|
221
|
-
async symlink(
|
|
222
|
-
|
|
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
|
+
}));
|
|
223
555
|
}
|
|
224
556
|
async readlink(path) {
|
|
225
|
-
await this.
|
|
557
|
+
const found = await this.locate(path, false);
|
|
558
|
+
if (found.link) return found.link.link;
|
|
226
559
|
throw fsError("EINVAL", path);
|
|
227
560
|
}
|
|
228
561
|
async chmod(path, mode) {
|
|
229
|
-
await this.
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
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;
|
|
235
595
|
}
|
|
236
596
|
};
|
|
237
597
|
|
|
@@ -380,19 +740,34 @@ async function scanCommands(fs, modules) {
|
|
|
380
740
|
var S_IFDIR2 = 16384;
|
|
381
741
|
var S_IFREG2 = 32768;
|
|
382
742
|
var COMMAND = /^\/(?:usr\/)?bin\/([^/]+)$/;
|
|
383
|
-
|
|
743
|
+
var MAX_LINKS2 = 40;
|
|
744
|
+
function missing2(err) {
|
|
384
745
|
return err?.code === "ENOENT";
|
|
385
746
|
}
|
|
386
|
-
function synthetic(directory) {
|
|
747
|
+
function synthetic(directory, path) {
|
|
748
|
+
const epoch = /* @__PURE__ */ new Date(0);
|
|
387
749
|
return {
|
|
388
750
|
isFile: !directory,
|
|
389
751
|
isDirectory: directory,
|
|
390
752
|
isSymbolicLink: false,
|
|
391
753
|
size: 0,
|
|
392
754
|
mode: (directory ? S_IFDIR2 : S_IFREG2) | 493,
|
|
393
|
-
mtime:
|
|
755
|
+
mtime: epoch,
|
|
756
|
+
atime: epoch,
|
|
757
|
+
ctime: epoch,
|
|
758
|
+
ino: inodeOf(path)
|
|
394
759
|
};
|
|
395
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
|
+
}
|
|
396
771
|
function withCommandDirs(fs, names) {
|
|
397
772
|
async function children(path) {
|
|
398
773
|
if (path === "/") return ["bin", "usr"];
|
|
@@ -401,15 +776,18 @@ function withCommandDirs(fs, names) {
|
|
|
401
776
|
return void 0;
|
|
402
777
|
}
|
|
403
778
|
async function virtual(path) {
|
|
404
|
-
if (path === "/usr" || path === "/bin" || path === "/usr/bin") return synthetic(true);
|
|
779
|
+
if (path === "/usr" || path === "/bin" || path === "/usr/bin") return synthetic(true, path);
|
|
405
780
|
const name = COMMAND.exec(path)?.[1];
|
|
406
|
-
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));
|
|
407
785
|
}
|
|
408
786
|
async function stat(path, real) {
|
|
409
787
|
try {
|
|
410
788
|
return await real(path);
|
|
411
789
|
} catch (err) {
|
|
412
|
-
const found =
|
|
790
|
+
const found = missing2(err) ? await linked(path) : void 0;
|
|
413
791
|
if (found) return found;
|
|
414
792
|
throw err;
|
|
415
793
|
}
|
|
@@ -421,13 +799,13 @@ function withCommandDirs(fs, names) {
|
|
|
421
799
|
try {
|
|
422
800
|
return await fs.readFileBuffer(path);
|
|
423
801
|
} catch (err) {
|
|
424
|
-
if (
|
|
802
|
+
if (missing2(err) && (await linked(path))?.isFile) return new Uint8Array(0);
|
|
425
803
|
throw err;
|
|
426
804
|
}
|
|
427
805
|
},
|
|
428
806
|
writeFile: (path, content) => fs.writeFile(path, content),
|
|
429
807
|
async exists(path) {
|
|
430
|
-
return await fs.exists(path) || await
|
|
808
|
+
return await fs.exists(path) || await linked(path) !== void 0;
|
|
431
809
|
},
|
|
432
810
|
stat: (path) => stat(path, (p) => fs.stat(p)),
|
|
433
811
|
lstat: (path) => stat(path, (p) => fs.lstat(p)),
|
|
@@ -435,11 +813,31 @@ function withCommandDirs(fs, names) {
|
|
|
435
813
|
const extra = await children(path);
|
|
436
814
|
if (!extra) return fs.readdir(path);
|
|
437
815
|
const real = await fs.readdir(path).catch((err) => {
|
|
438
|
-
if (
|
|
816
|
+
if (missing2(err)) return [];
|
|
439
817
|
throw err;
|
|
440
818
|
});
|
|
441
819
|
return [.../* @__PURE__ */ new Set([...real, ...extra])].sort();
|
|
442
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
|
+
},
|
|
443
841
|
mkdir: (path, options) => fs.mkdir(path, options),
|
|
444
842
|
rm: (path, options) => fs.rm(path, options),
|
|
445
843
|
rename: (from, to) => fs.rename(from, to),
|
|
@@ -1013,21 +1411,14 @@ function statJson(s) {
|
|
|
1013
1411
|
isSymbolicLink: s.isSymbolicLink,
|
|
1014
1412
|
size: s.size,
|
|
1015
1413
|
mode: s.mode,
|
|
1016
|
-
mtimeMs: s.mtime.getTime()
|
|
1414
|
+
mtimeMs: s.mtime.getTime(),
|
|
1415
|
+
atimeMs: s.atime.getTime(),
|
|
1416
|
+
ctimeMs: s.ctime.getTime(),
|
|
1417
|
+
ino: s.ino
|
|
1017
1418
|
};
|
|
1018
1419
|
}
|
|
1019
1420
|
async function readdirStat(fs, dir) {
|
|
1020
|
-
|
|
1021
|
-
const base = dir === "/" ? "" : dir;
|
|
1022
|
-
return Promise.all(
|
|
1023
|
-
names.map(async (name) => {
|
|
1024
|
-
try {
|
|
1025
|
-
return [name, statJson(await fs.lstat(`${base}/${name}`))];
|
|
1026
|
-
} catch {
|
|
1027
|
-
return [name, null];
|
|
1028
|
-
}
|
|
1029
|
-
})
|
|
1030
|
-
);
|
|
1421
|
+
return (await fs.readdirStat(dir)).map(([name, stat]) => [name, stat && statJson(stat)]);
|
|
1031
1422
|
}
|
|
1032
1423
|
var done = { ok: true, kind: "void" };
|
|
1033
1424
|
function json(value) {
|
|
@@ -1168,7 +1559,7 @@ function attachSyncSabResponder(port, sab, token, opts) {
|
|
|
1168
1559
|
Atomics.notify(header, SAB_I_STATE);
|
|
1169
1560
|
if (offset + chunk >= entry.payload.byteLength) drop(id);
|
|
1170
1561
|
}
|
|
1171
|
-
function
|
|
1562
|
+
function settle2(id, result) {
|
|
1172
1563
|
if (disposed) return;
|
|
1173
1564
|
const { status, payload } = encodeSabResult(result);
|
|
1174
1565
|
drop(id);
|
|
@@ -1187,7 +1578,7 @@ function attachSyncSabResponder(port, sab, token, opts) {
|
|
|
1187
1578
|
const entry = pending.get(data.id);
|
|
1188
1579
|
const offset = data.offset;
|
|
1189
1580
|
if (!entry || typeof offset !== "number" || !Number.isInteger(offset) || offset < 0 || offset > entry.payload.byteLength) {
|
|
1190
|
-
|
|
1581
|
+
settle2(data.id, { ok: false, errno: "EIO", message: "sync-sab: bad continuation" });
|
|
1191
1582
|
return;
|
|
1192
1583
|
}
|
|
1193
1584
|
publish(data.id, entry, offset);
|
|
@@ -1201,7 +1592,7 @@ function attachSyncSabResponder(port, sab, token, opts) {
|
|
|
1201
1592
|
try {
|
|
1202
1593
|
dispatched = dispatch(req);
|
|
1203
1594
|
} catch (err) {
|
|
1204
|
-
|
|
1595
|
+
settle2(id, {
|
|
1205
1596
|
ok: false,
|
|
1206
1597
|
errno: "EIO",
|
|
1207
1598
|
message: err instanceof Error ? err.message : String(err)
|
|
@@ -1209,8 +1600,8 @@ function attachSyncSabResponder(port, sab, token, opts) {
|
|
|
1209
1600
|
return;
|
|
1210
1601
|
}
|
|
1211
1602
|
void dispatched.then(
|
|
1212
|
-
(result) =>
|
|
1213
|
-
(err) =>
|
|
1603
|
+
(result) => settle2(id, result),
|
|
1604
|
+
(err) => settle2(id, {
|
|
1214
1605
|
ok: false,
|
|
1215
1606
|
errno: "EIO",
|
|
1216
1607
|
message: err instanceof Error ? err.message : String(err)
|
|
@@ -1888,15 +2279,15 @@ var VfsNodes = class {
|
|
|
1888
2279
|
}
|
|
1889
2280
|
renamed(from, to) {
|
|
1890
2281
|
if (from === to) return;
|
|
1891
|
-
const
|
|
2282
|
+
const moved2 = [];
|
|
1892
2283
|
for (const [p, node] of this.byPath) {
|
|
1893
|
-
if (within2(p, from))
|
|
2284
|
+
if (within2(p, from)) moved2.push(node);
|
|
1894
2285
|
else if (within2(p, to)) {
|
|
1895
2286
|
node.orphaned = true;
|
|
1896
2287
|
this.byPath.delete(p);
|
|
1897
2288
|
}
|
|
1898
2289
|
}
|
|
1899
|
-
for (const node of
|
|
2290
|
+
for (const node of moved2) {
|
|
1900
2291
|
this.byPath.delete(node.path);
|
|
1901
2292
|
node.path = to + node.path.slice(from.length);
|
|
1902
2293
|
this.byPath.set(node.path, node);
|
|
@@ -2557,8 +2948,8 @@ function spawnWasmProcess(opts) {
|
|
|
2557
2948
|
return dispatchSyncFs(req);
|
|
2558
2949
|
};
|
|
2559
2950
|
const responder = attachSyncSabResponder(worker, sab, token, { dispatch });
|
|
2560
|
-
let
|
|
2561
|
-
const exited = new Promise((resolve) =>
|
|
2951
|
+
let settle2;
|
|
2952
|
+
const exited = new Promise((resolve) => settle2 = resolve);
|
|
2562
2953
|
let done3 = false;
|
|
2563
2954
|
let endedBy;
|
|
2564
2955
|
const finish = (code, sig) => {
|
|
@@ -2571,8 +2962,8 @@ function spawnWasmProcess(opts) {
|
|
|
2571
2962
|
revokeSyncFsToken(token);
|
|
2572
2963
|
worker.terminate();
|
|
2573
2964
|
void process.exit().then(
|
|
2574
|
-
() =>
|
|
2575
|
-
() =>
|
|
2965
|
+
() => settle2(code),
|
|
2966
|
+
() => settle2(code)
|
|
2576
2967
|
);
|
|
2577
2968
|
};
|
|
2578
2969
|
const onMessage = (event) => {
|
|
@@ -2795,7 +3186,9 @@ var Launcher = class {
|
|
|
2795
3186
|
const command = (await this.commands()).get(name);
|
|
2796
3187
|
return command && targetOf(command);
|
|
2797
3188
|
}
|
|
2798
|
-
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);
|
|
2799
3192
|
const wasm = modulePath(glue);
|
|
2800
3193
|
if (!await this.base.exists(glue) || !await this.base.exists(wasm)) return void 0;
|
|
2801
3194
|
return { glue, wasm, argv0: baseName(argv0 || file) };
|
|
@@ -3033,8 +3426,12 @@ function serveKernel(port, deps) {
|
|
|
3033
3426
|
async function handle(req) {
|
|
3034
3427
|
if (req.op === "init") {
|
|
3035
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();
|
|
3036
3433
|
const started = new Launcher({
|
|
3037
|
-
fs
|
|
3434
|
+
fs,
|
|
3038
3435
|
createWorker: deps.createWorker,
|
|
3039
3436
|
...req.modules ? { modules: req.modules } : {},
|
|
3040
3437
|
...req.env ? { env: req.env } : {}
|
|
@@ -3071,6 +3468,7 @@ function serveKernel(port, deps) {
|
|
|
3071
3468
|
var processWorker = new URL("./process-worker.js", import.meta.url);
|
|
3072
3469
|
serveKernel(globalThis, {
|
|
3073
3470
|
storage: () => navigator.storage.getDirectory(),
|
|
3074
|
-
createWorker: () => new Worker(processWorker, { type: "module" })
|
|
3471
|
+
createWorker: () => new Worker(processWorker, { type: "module" }),
|
|
3472
|
+
metadata: (name) => openMeta(name, indexedDB)
|
|
3075
3473
|
});
|
|
3076
3474
|
//# sourceMappingURL=kernel-worker.js.map
|