@ai-ecoverse/slicc-kernel 1.85.0 → 1.86.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.
@@ -97,6 +97,9 @@ var MemoryMeta = class {
97
97
  async get(paths) {
98
98
  return paths.map((path) => this.entries.get(path));
99
99
  }
100
+ async fresh(path) {
101
+ return this.entries.get(path);
102
+ }
100
103
  async update(path, change) {
101
104
  this.set(path, change(this.entries.get(path)));
102
105
  }
@@ -193,6 +196,16 @@ var IndexedDbMeta = class {
193
196
  }
194
197
  return paths.map((path) => this.cache.get(path) ?? void 0);
195
198
  }
199
+ async fresh(path) {
200
+ const [transaction, store] = this.transaction("readonly");
201
+ const request2 = store.get(path);
202
+ let entry;
203
+ request2.onsuccess = () => {
204
+ entry = request2.result ?? void 0;
205
+ };
206
+ await settle(transaction, () => void 0);
207
+ return entry;
208
+ }
196
209
  async update(path, change) {
197
210
  this.settleRemoves();
198
211
  const [transaction, store] = this.transaction("readwrite");
@@ -207,6 +220,19 @@ var IndexedDbMeta = class {
207
220
  this.changed({ paths: [path] });
208
221
  this.cache.set(path, next2 ?? null);
209
222
  }
223
+ async updateMany(changes) {
224
+ const [transaction, store] = this.transaction("readwrite");
225
+ for (const { path, change } of changes) {
226
+ const request2 = store.get(path);
227
+ request2.onsuccess = () => store.put(change(request2.result));
228
+ }
229
+ await settle(transaction, () => void 0);
230
+ this.changed({ paths: changes.map(({ path }) => path) });
231
+ }
232
+ close() {
233
+ this.channel.close();
234
+ this.db.close();
235
+ }
210
236
  async move(from, to, root) {
211
237
  this.settleRemoves();
212
238
  const [transaction, store] = this.transaction("readwrite");
@@ -326,6 +352,12 @@ async function lutimesOf(fs, path, atime, mtime) {
326
352
  function mayLink(fs, path) {
327
353
  return fs.links?.(path) ?? true;
328
354
  }
355
+ function modeOption(mode) {
356
+ if (!Number.isInteger(mode) || mode < 0 || mode > 4095) {
357
+ throw fsError("EINVAL", `not a mode: ${String(mode)}`);
358
+ }
359
+ return mode;
360
+ }
329
361
  function fsError(code, path) {
330
362
  return Object.assign(new Error(`${code}: ${path}`), { code });
331
363
  }
@@ -857,7 +889,8 @@ function sidecar(meta) {
857
889
  const guarded2 = (op) => (...args) => meta[op].apply(meta, args).catch((err) => {
858
890
  throw translate(err, pathOf(args), "sidecar", false);
859
891
  });
860
- const ops = Object.fromEntries(META_OPS.map((op) => [op, guarded2(op)]));
892
+ const names = meta.fresh ? [...META_OPS, "fresh"] : META_OPS;
893
+ const ops = Object.fromEntries(names.map((op) => [op, guarded2(op)]));
861
894
  if (meta.absent) ops.absent = meta.absent.bind(meta);
862
895
  const { removeFile } = meta;
863
896
  if (removeFile)
@@ -985,6 +1018,24 @@ var OpfsFs = class {
985
1018
  committed(target) {
986
1019
  if (this.remote?.watched()) this.remote.wrote(target);
987
1020
  }
1021
+ counted = [];
1022
+ async stamp(path) {
1023
+ const entry = this.meta.fresh ? await this.meta.fresh(path) : (await this.meta.get([path]))[0];
1024
+ return String(entry?.writes ?? 0);
1025
+ }
1026
+ async counting(target) {
1027
+ if (!isProtected(this.counted, target)) return;
1028
+ await this.meta.update(target, (entry) => ({
1029
+ ...entry,
1030
+ path: target,
1031
+ writes: (entry?.writes ?? 0) + 1
1032
+ }));
1033
+ }
1034
+ async recounting(handle, target) {
1035
+ if (this.counted.length === 0) return;
1036
+ const where = await this.root.resolve(handle).catch(() => null);
1037
+ await this.counting(where ? `/${where.join("/")}` : target);
1038
+ }
988
1039
  resolvePath(base, path) {
989
1040
  return resolvePath(base, path);
990
1041
  }
@@ -1043,6 +1094,7 @@ var OpfsFs = class {
1043
1094
  guarded = [];
1044
1095
  protect(prefixes2) {
1045
1096
  this.guarded = prefixes2;
1097
+ this.counted = prefixes2;
1046
1098
  }
1047
1099
  unguarded() {
1048
1100
  return Object.create(this, { guarded: { value: [] } });
@@ -1379,9 +1431,10 @@ var OpfsFs = class {
1379
1431
  return this.retrying(
1380
1432
  path,
1381
1433
  async () => {
1434
+ const stamp = await this.stamp(path);
1382
1435
  const file = await handle.getFile();
1383
1436
  const bytes = new Uint8Array(await file.arrayBuffer());
1384
- return { bytes, version: `${file.lastModified}:${file.size}` };
1437
+ return { bytes, version: `${file.lastModified}:${file.size}:${stamp}` };
1385
1438
  },
1386
1439
  false
1387
1440
  );
@@ -1391,8 +1444,9 @@ var OpfsFs = class {
1391
1444
  return this.retrying(
1392
1445
  path,
1393
1446
  async () => {
1447
+ const stamp = await this.stamp(path);
1394
1448
  const file = await handle.getFile();
1395
- return `${file.lastModified}:${file.size}`;
1449
+ return `${file.lastModified}:${file.size}:${stamp}`;
1396
1450
  },
1397
1451
  false
1398
1452
  );
@@ -1407,14 +1461,24 @@ var OpfsFs = class {
1407
1461
  }
1408
1462
  async pwrite(path, offset, bytes) {
1409
1463
  const [handle, , target] = await this.fileFor(path);
1410
- await this.retrying(path, () => positioned(handle, (io) => io.write(offset, bytes)), true);
1464
+ await this.counting(target);
1465
+ try {
1466
+ await this.retrying(path, () => positioned(handle, (io) => io.write(offset, bytes)), true);
1467
+ } finally {
1468
+ await this.recounting(handle, target);
1469
+ }
1411
1470
  this.written(target);
1412
1471
  }
1413
1472
  async truncate(path, size) {
1414
1473
  const handle = await this.existing(path, true);
1415
1474
  const resolve2 = this.ranged || this.remote?.watched();
1416
- const target = resolve2 ? (await this.locate(path, true)).path : path;
1417
- await this.retrying(path, () => positioned(handle, (io) => io.truncate(size)), true);
1475
+ const target = resolve2 || this.counted.length > 0 ? (await this.locate(path, true)).path : path;
1476
+ await this.counting(target);
1477
+ try {
1478
+ await this.retrying(path, () => positioned(handle, (io) => io.truncate(size)), true);
1479
+ } finally {
1480
+ await this.recounting(handle, target);
1481
+ }
1418
1482
  this.written(target);
1419
1483
  }
1420
1484
  async writeSession(path, truncate) {
@@ -2339,7 +2403,9 @@ function id(value) {
2339
2403
  return value;
2340
2404
  }
2341
2405
  function changeCred(cur, change) {
2342
- if (typeof change !== "object" || change === null) throw fsError("EINVAL", "not a change");
2406
+ if (typeof change !== "object" || change === null || Array.isArray(change)) {
2407
+ throw fsError("EINVAL", "not a change");
2408
+ }
2343
2409
  const privileged = cur.euid === 0;
2344
2410
  const next2 = copyCred(cur);
2345
2411
  for (const keys of [UIDS, GIDS]) {
@@ -11843,7 +11909,7 @@ function ptySyscall(req, ctx) {
11843
11909
  const pair = ctx.ptys.get(req.n);
11844
11910
  ttyOpenable(ctx.guard, pair.slave);
11845
11911
  const slave = pair.openSlave();
11846
- const fd = ctx.fds.install(ctx.ttyFilter?.(slave) ?? slave, 3);
11912
+ const fd = ctx.fds.install(ctx.outputFilter?.(slave) ?? slave, 3);
11847
11913
  if (!req.noctty) ctx.jobs?.acquireTerminal(ctx.pid, pair.slave);
11848
11914
  return json4(fd);
11849
11915
  }
@@ -13009,8 +13075,8 @@ var WasmProcess = class {
13009
13075
  const { ptys, jobs } = this.options;
13010
13076
  const owner = this.options.owners ? this.ttyOwner() : void 0;
13011
13077
  const guard2 = this.guarded();
13012
- const { ttyFilter } = this.options;
13013
- const ctx = { pid: this.pid, fds: this.fds, ptys, jobs, guard: guard2, owner, ttyFilter };
13078
+ const { outputFilter } = this.options;
13079
+ const ctx = { pid: this.pid, fds: this.fds, ptys, jobs, guard: guard2, owner, outputFilter };
13014
13080
  return ptySyscall(req, ctx);
13015
13081
  }
13016
13082
  return await this.procSyscall(req, cancelled);
@@ -13187,7 +13253,7 @@ var WasmProcess = class {
13187
13253
  if (!tty) throw new KernelError("ENXIO");
13188
13254
  if (name2 !== void 0) ttyOpenable(this.guarded(), tty);
13189
13255
  const file = tty.file();
13190
- return this.fds.install(this.options.ttyFilter?.(file) ?? file, 3);
13256
+ return this.fds.install(this.options.outputFilter?.(file) ?? file, 3);
13191
13257
  }
13192
13258
  openSealed() {
13193
13259
  const broker = this.options.sealed;
@@ -13199,7 +13265,8 @@ var WasmProcess = class {
13199
13265
  if (!broker) throw new KernelError("ENOENT");
13200
13266
  const mode = flags2 & WRITE_ACCESS;
13201
13267
  const access2 = mode === 0 ? "read" : mode === 1 ? "write" : "both";
13202
- return this.fds.install(broker.open(access2, await this.clipboardStamp()), 3);
13268
+ const file = broker.open(access2, await this.clipboardStamp());
13269
+ return this.fds.install(this.options.outputFilter?.(file) ?? file, 3);
13203
13270
  }
13204
13271
  async clipboardStamp() {
13205
13272
  const shown = await this.options.identity?.();
@@ -13998,7 +14065,7 @@ function processOptions(opts, header3, hooks, created3) {
13998
14065
  ...inherited(opts),
13999
14066
  ...opts.owners ? { owners: true } : {},
14000
14067
  ...opts.sealed ? { sealed: opts.sealed } : {},
14001
- ...opts.ttyFilter ? { ttyFilter: opts.ttyFilter } : {},
14068
+ ...opts.outputFilter ? { outputFilter: opts.outputFilter } : {},
14002
14069
  ...opts.clipboard ? { clipboard: opts.clipboard, ...clipboardName(opts) } : {},
14003
14070
  ...opts.readonlyAt ? { readonlyAt: opts.readonlyAt } : {},
14004
14071
  ...opts.dac ? { guard: { dac: opts.dac, fs: opts.fs, created: created3 } } : {},
@@ -15000,7 +15067,7 @@ function scrubStdio(fds, scrubber) {
15000
15067
  const stdio = stdioFiles(fds);
15001
15068
  for (const fd of fds.numbers()) {
15002
15069
  const original = fds.get(fd);
15003
- if (!stdio.has(original) && !original.file.tty) continue;
15070
+ if (!stdio.has(original) && !original.file.tty && !original.file.clipboard) continue;
15004
15071
  const known = wrapped.get(original);
15005
15072
  const flags2 = fds.statusFlags(fd);
15006
15073
  const wrapper = known ? known.retain() : scrubbed(original.retain(), scrubber);
@@ -18079,7 +18146,7 @@ var Launcher = class {
18079
18146
  ...this.owners && req.program.abi === "wasi" ? { fresh: (path, umask) => this.freshModules.add(path, umask, pid) } : {},
18080
18147
  ...this.owners ? { owners: true } : {},
18081
18148
  sealed: this.sealed,
18082
- ...scrubs ? { ttyFilter: (file) => scrubbedAll(file, scrubs) } : {},
18149
+ ...scrubs ? { outputFilter: (file) => scrubbedAll(file, scrubs) } : {},
18083
18150
  clipboard: this.clipboard,
18084
18151
  ...this.dac ? { dac: this.dac } : {},
18085
18152
  program: req.program,
@@ -18612,17 +18679,21 @@ var Launcher = class {
18612
18679
  const real = await this.realLayoutPath("/tmp");
18613
18680
  const st = real === void 0 ? void 0 : await this.base.lstat(real).catch(() => void 0);
18614
18681
  if (real !== void 0 && st?.isDirectory && this.base.hasOwner && !await this.base.hasOwner(real)) {
18682
+ const names = await this.base.readdir(real).catch(() => void 0);
18615
18683
  let removed = 0;
18616
- for (const name2 of await this.base.readdir(real).catch(() => [])) {
18684
+ let left = names === void 0 ? 1 : 0;
18685
+ for (const name2 of names ?? []) {
18617
18686
  const path = joinName(real, name2);
18618
- if (await this.base.hasOwner(path) || this.inUse(path)) continue;
18619
- const failed3 = await this.base.rm(path, { recursive: true, force: true }).catch(String);
18687
+ if (await this.base.hasOwner(path)) continue;
18688
+ const failed3 = this.inUse(path) ? "busy" : await this.base.rm(path, { recursive: true, force: true }).catch(String);
18620
18689
  if (failed3 === void 0) removed++;
18690
+ else left++;
18621
18691
  }
18622
18692
  if (removed > 0) {
18623
18693
  console.warn(`slicc-kernel users: removed ${removed} entries from /tmp that had no owner`);
18624
18694
  }
18625
- await this.base.chown?.(real, { uid: 0, gid: 0, mode: 1023, follow: false });
18695
+ if (left === 0)
18696
+ await this.base.chown?.(real, { uid: 0, gid: 0, mode: 1023, follow: false });
18626
18697
  }
18627
18698
  if (st?.isDirectory) await this.base.chmod(real, 1023);
18628
18699
  }
@@ -19075,7 +19146,12 @@ var USERS_OPS = /* @__PURE__ */ new Set([
19075
19146
  "groups-add-member",
19076
19147
  "groups-remove-member"
19077
19148
  ]);
19078
- var ATTEST_OPS = /* @__PURE__ */ new Set(["attest", "watch-files", "unwatch-files"]);
19149
+ var ATTEST_OPS = /* @__PURE__ */ new Set([
19150
+ "attest",
19151
+ "watch-files",
19152
+ "unwatch-files",
19153
+ "chmod"
19154
+ ]);
19079
19155
  function isAttestRequest(req) {
19080
19156
  return ATTEST_OPS.has(req.op);
19081
19157
  }
@@ -19091,6 +19167,7 @@ function attestOps(port) {
19091
19167
  const watches = /* @__PURE__ */ new Map();
19092
19168
  return async (l, req) => {
19093
19169
  if (req.op === "attest") return l.attest(req.path);
19170
+ if (req.op === "chmod") return l.fs.chmod(req.path, modeOption(req.mode)).then(() => true);
19094
19171
  if (req.op === "unwatch-files") {
19095
19172
  watches.get(req.watch)?.();
19096
19173
  return watches.delete(req.watch);