@ai-ecoverse/slicc-kernel 1.57.1 → 1.58.1
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 +3 -2
- package/dist/driver-worker.js +3 -0
- package/dist/driver-worker.js.map +2 -2
- package/dist/driver.js +3 -0
- package/dist/driver.js.map +2 -2
- package/dist/index.js.map +2 -2
- package/dist/kernel-worker.js +286 -111
- package/dist/kernel-worker.js.map +4 -4
- package/dist/node-driver-worker.js +3 -0
- package/dist/node-driver-worker.js.map +2 -2
- package/dist/node-process-worker.js +255 -37
- package/dist/node-process-worker.js.map +3 -3
- package/dist/node.js +286 -111
- package/dist/node.js.map +4 -4
- package/dist/process-worker.js +255 -37
- package/dist/process-worker.js.map +3 -3
- package/package.json +1 -1
package/dist/kernel-worker.js
CHANGED
|
@@ -188,6 +188,9 @@ async function lutimesOf(fs, path, atime, mtime) {
|
|
|
188
188
|
if ((await fs.lstat(path)).isSymbolicLink) throw fsError("EOPNOTSUPP", path);
|
|
189
189
|
return fs.utimes(path, atime, mtime);
|
|
190
190
|
}
|
|
191
|
+
function mayLink(fs, path) {
|
|
192
|
+
return fs.links?.(path) ?? true;
|
|
193
|
+
}
|
|
191
194
|
function fsError(code, path) {
|
|
192
195
|
return Object.assign(new Error(`${code}: ${path}`), { code });
|
|
193
196
|
}
|
|
@@ -791,12 +794,12 @@ var OpfsFs = class {
|
|
|
791
794
|
const base = dir === "/" ? "" : dir;
|
|
792
795
|
const metas = await this.meta.get(children2.map(([name2]) => `${base}/${name2}`));
|
|
793
796
|
const handed2 = this.owners ? await this.handedTo(dir) : void 0;
|
|
794
|
-
const
|
|
797
|
+
const owned2 = (stat, entry) => handed2 ? { ...stat, ...ownOf(entry) ?? handed2 } : stat;
|
|
795
798
|
return Promise.all(
|
|
796
799
|
children2.map(async ([name2, child], i) => {
|
|
797
|
-
if (!isHandle(child)) return [name2,
|
|
800
|
+
if (!isHandle(child)) return [name2, owned2(this.linkStat(child), child)];
|
|
798
801
|
const stat = await this.statOf(`${base}/${name2}`, child, metas[i]).catch(() => null);
|
|
799
|
-
return [name2, stat &&
|
|
802
|
+
return [name2, stat && owned2(stat, metas[i])];
|
|
800
803
|
})
|
|
801
804
|
);
|
|
802
805
|
}
|
|
@@ -855,6 +858,12 @@ var OpfsFs = class {
|
|
|
855
858
|
await this.meta.remove([found.path]);
|
|
856
859
|
this.touch(parentOf2(found.path));
|
|
857
860
|
}
|
|
861
|
+
links() {
|
|
862
|
+
return false;
|
|
863
|
+
}
|
|
864
|
+
async link(from) {
|
|
865
|
+
throw fsError("EPERM", `${from}: this file system has no hard links`);
|
|
866
|
+
}
|
|
858
867
|
async rename(from, to) {
|
|
859
868
|
if (from === to) return;
|
|
860
869
|
const source = await this.locate(from, false);
|
|
@@ -5814,6 +5823,8 @@ function withCommandDirs(fs, names, userDir = async () => void 0) {
|
|
|
5814
5823
|
}
|
|
5815
5824
|
},
|
|
5816
5825
|
writeFile: (path, content, owner) => fs.writeFile(path, content, owner),
|
|
5826
|
+
link: (from, to, follow2) => fs.link(from, to, follow2),
|
|
5827
|
+
links: (path) => mayLink(fs, path),
|
|
5817
5828
|
async exists(path) {
|
|
5818
5829
|
return await fs.exists(path) || await linked(path) !== void 0;
|
|
5819
5830
|
},
|
|
@@ -5952,6 +5963,8 @@ var UnlinkedKeeper = class {
|
|
|
5952
5963
|
gate = new PathGate();
|
|
5953
5964
|
queue = Promise.resolve();
|
|
5954
5965
|
count = 0;
|
|
5966
|
+
links = /* @__PURE__ */ new Map();
|
|
5967
|
+
linked = /* @__PURE__ */ new Map();
|
|
5955
5968
|
constructor(fs, holders, rootOf, options = {}) {
|
|
5956
5969
|
const { locks, owner } = options;
|
|
5957
5970
|
this.fs = fs;
|
|
@@ -5971,8 +5984,15 @@ var UnlinkedKeeper = class {
|
|
|
5971
5984
|
wrap(fs = this.fs) {
|
|
5972
5985
|
const keeper = this;
|
|
5973
5986
|
const visible = (names) => names.filter((name2) => name2 !== UNLINKED_DIR);
|
|
5987
|
+
const counted = (st) => {
|
|
5988
|
+
const hidden = keeper.links.get(`${st.dev ?? 0}:${st.ino}`) ?? 0;
|
|
5989
|
+
return hidden === 0 || st.nlink === void 0 ? st : { ...st, nlink: Math.max(0, st.nlink - hidden) };
|
|
5990
|
+
};
|
|
5974
5991
|
return {
|
|
5975
5992
|
...fs,
|
|
5993
|
+
links: (path) => mayLink(fs, path),
|
|
5994
|
+
stat: async (path) => counted(await fs.stat(path)),
|
|
5995
|
+
lstat: async (path) => counted(await fs.lstat(path)),
|
|
5976
5996
|
async rm(path, options) {
|
|
5977
5997
|
const target = fs.resolvePath("/", path);
|
|
5978
5998
|
const pinned = await keeper.pin(target);
|
|
@@ -5986,7 +6006,7 @@ var UnlinkedKeeper = class {
|
|
|
5986
6006
|
await keeper.settle(pinned, () => fs.rename(from, to));
|
|
5987
6007
|
},
|
|
5988
6008
|
readdir: async (path) => visible(await fs.readdir(path)),
|
|
5989
|
-
readdirStat: async (path) => (await fs.readdirStat(path)).filter(([name2]) => name2 !== UNLINKED_DIR)
|
|
6009
|
+
readdirStat: async (path) => (await fs.readdirStat(path)).filter(([name2]) => name2 !== UNLINKED_DIR).map(([name2, st]) => [name2, st && counted(st)])
|
|
5990
6010
|
};
|
|
5991
6011
|
}
|
|
5992
6012
|
async settle(pinned, op) {
|
|
@@ -6036,6 +6056,7 @@ var UnlinkedKeeper = class {
|
|
|
6036
6056
|
for (const holder of held.get(path)) holder.unkeep(path, file);
|
|
6037
6057
|
await this.serial(async () => {
|
|
6038
6058
|
await Promise.allSettled([this.fs.rename(file.hidden, path)]);
|
|
6059
|
+
this.unlinkedName(file.hidden);
|
|
6039
6060
|
await this.tidy(parentOf3(file.hidden));
|
|
6040
6061
|
});
|
|
6041
6062
|
}
|
|
@@ -6058,6 +6079,7 @@ var UnlinkedKeeper = class {
|
|
|
6058
6079
|
try {
|
|
6059
6080
|
await this.fs.mkdir(dir, { recursive: true });
|
|
6060
6081
|
await this.fs.rename(path, hidden);
|
|
6082
|
+
await this.linkedName(hidden);
|
|
6061
6083
|
return hidden;
|
|
6062
6084
|
} catch {
|
|
6063
6085
|
await this.tidy(dir);
|
|
@@ -6074,8 +6096,25 @@ var UnlinkedKeeper = class {
|
|
|
6074
6096
|
return;
|
|
6075
6097
|
}
|
|
6076
6098
|
await this.fs.rm(hidden).catch(() => void 0);
|
|
6099
|
+
this.unlinkedName(hidden);
|
|
6077
6100
|
await this.tidy(parentOf3(hidden));
|
|
6078
6101
|
}
|
|
6102
|
+
async linkedName(hidden) {
|
|
6103
|
+
if (!mayLink(this.fs, hidden)) return;
|
|
6104
|
+
const st = await this.fs.lstat(hidden).catch(() => void 0);
|
|
6105
|
+
if (!st || (st.nlink ?? 1) < 2) return;
|
|
6106
|
+
const id2 = `${st.dev ?? 0}:${st.ino}`;
|
|
6107
|
+
this.linked.set(hidden, id2);
|
|
6108
|
+
this.links.set(id2, (this.links.get(id2) ?? 0) + 1);
|
|
6109
|
+
}
|
|
6110
|
+
unlinkedName(hidden) {
|
|
6111
|
+
const id2 = this.linked.get(hidden);
|
|
6112
|
+
if (id2 === void 0) return;
|
|
6113
|
+
this.linked.delete(hidden);
|
|
6114
|
+
const left = (this.links.get(id2) ?? 1) - 1;
|
|
6115
|
+
if (left > 0) this.links.set(id2, left);
|
|
6116
|
+
else this.links.delete(id2);
|
|
6117
|
+
}
|
|
6079
6118
|
async tidy(dir) {
|
|
6080
6119
|
const left = await this.fs.readdir(dir).catch(() => [UNLINKED_DIR]);
|
|
6081
6120
|
if (left.length === 0) await this.fs.rm(dir, { recursive: true }).catch(() => void 0);
|
|
@@ -6169,6 +6208,10 @@ var FsWatchers = class {
|
|
|
6169
6208
|
},
|
|
6170
6209
|
rm: after(fs.rm.bind(fs), (path) => [path]),
|
|
6171
6210
|
rename: after(fs.rename.bind(fs), (from, to) => [from, to]),
|
|
6211
|
+
link: after(
|
|
6212
|
+
(from, to, follow2) => fs.link(from, to, follow2),
|
|
6213
|
+
(_from, to) => [to]
|
|
6214
|
+
),
|
|
6172
6215
|
symlink: after(fs.symlink.bind(fs), (_target, path) => [path]),
|
|
6173
6216
|
chmod: after(fs.chmod.bind(fs), (path) => [path]),
|
|
6174
6217
|
...fs.chown ? { chown: after(fs.chown.bind(fs), (path) => [path]) } : {},
|
|
@@ -6382,6 +6425,62 @@ var ChildTable = class {
|
|
|
6382
6425
|
}
|
|
6383
6426
|
};
|
|
6384
6427
|
|
|
6428
|
+
// src/fs/walk.ts
|
|
6429
|
+
var MAX_LINKS4 = 40;
|
|
6430
|
+
function parentOf5(path) {
|
|
6431
|
+
return path.slice(0, path.lastIndexOf("/")) || "/";
|
|
6432
|
+
}
|
|
6433
|
+
async function mustBeDir(fs, path) {
|
|
6434
|
+
if (!fs.lstat) return;
|
|
6435
|
+
const st = await fs.lstat(path);
|
|
6436
|
+
if (st.isDirectory !== true) throw fsError("ENOTDIR", path);
|
|
6437
|
+
}
|
|
6438
|
+
async function linkOf(fs, path) {
|
|
6439
|
+
return fs.readlink?.(path).catch(() => void 0);
|
|
6440
|
+
}
|
|
6441
|
+
async function realDir(fs, dir, budget = { hops: 0 }) {
|
|
6442
|
+
const queue = dir.split("/").filter(Boolean);
|
|
6443
|
+
let at = "/";
|
|
6444
|
+
while (queue.length > 0) {
|
|
6445
|
+
const part = queue.shift();
|
|
6446
|
+
if (part === ".") continue;
|
|
6447
|
+
if (part === "..") {
|
|
6448
|
+
at = parentOf5(at);
|
|
6449
|
+
continue;
|
|
6450
|
+
}
|
|
6451
|
+
const next2 = at === "/" ? `/${part}` : `${at}/${part}`;
|
|
6452
|
+
const link2 = await linkOf(fs, next2);
|
|
6453
|
+
if (link2 === void 0) {
|
|
6454
|
+
if (queue.find((p) => p !== ".") === "..") await mustBeDir(fs, next2);
|
|
6455
|
+
at = next2;
|
|
6456
|
+
continue;
|
|
6457
|
+
}
|
|
6458
|
+
if (budget.hops >= MAX_LINKS4) throw fsError("ELOOP", dir);
|
|
6459
|
+
budget.hops++;
|
|
6460
|
+
queue.unshift(...link2.split("/").filter(Boolean));
|
|
6461
|
+
if (link2.startsWith("/")) at = "/";
|
|
6462
|
+
}
|
|
6463
|
+
return at;
|
|
6464
|
+
}
|
|
6465
|
+
function joinName(dir, name2) {
|
|
6466
|
+
return dir === "/" ? `/${name2}` : `${dir}/${name2}`;
|
|
6467
|
+
}
|
|
6468
|
+
async function realTarget(fs, path) {
|
|
6469
|
+
const budget = { hops: 0 };
|
|
6470
|
+
let dir = await realDir(fs, parentOf5(path), budget);
|
|
6471
|
+
let name2 = path.slice(path.lastIndexOf("/") + 1);
|
|
6472
|
+
for (; ; ) {
|
|
6473
|
+
const link2 = await linkOf(fs, joinName(dir, name2));
|
|
6474
|
+
if (link2 === void 0) break;
|
|
6475
|
+
if (budget.hops >= MAX_LINKS4) throw fsError("ELOOP", path);
|
|
6476
|
+
budget.hops++;
|
|
6477
|
+
const target = link2.startsWith("/") ? link2 : joinName(dir, link2);
|
|
6478
|
+
dir = await realDir(fs, parentOf5(target), budget);
|
|
6479
|
+
name2 = target.slice(target.lastIndexOf("/") + 1);
|
|
6480
|
+
}
|
|
6481
|
+
return joinName(dir, name2);
|
|
6482
|
+
}
|
|
6483
|
+
|
|
6385
6484
|
// src/mount/protocol.ts
|
|
6386
6485
|
var DRIVER_PROTOCOL = [1, 1];
|
|
6387
6486
|
function driverVersionError(theirs) {
|
|
@@ -6485,7 +6584,7 @@ var DriverConnection = class _DriverConnection {
|
|
|
6485
6584
|
};
|
|
6486
6585
|
|
|
6487
6586
|
// src/mount/mount-fs.ts
|
|
6488
|
-
var
|
|
6587
|
+
var MAX_LINKS5 = 40;
|
|
6489
6588
|
var STALE_RETRIES = 3;
|
|
6490
6589
|
var SETTLE_ROUNDS = 4;
|
|
6491
6590
|
var DEFAULT_IO = 8 * 1024 * 1024;
|
|
@@ -6649,8 +6748,13 @@ var HeldPaths = class extends Map {
|
|
|
6649
6748
|
return this.revoked.has(path);
|
|
6650
6749
|
}
|
|
6651
6750
|
};
|
|
6652
|
-
var
|
|
6751
|
+
var parentOf6 = (path) => path.slice(0, path.lastIndexOf("/")) || "/";
|
|
6653
6752
|
var readonly = (mount) => mount.caps.readonly === true || mount.options.ro !== void 0;
|
|
6753
|
+
function inodeless(mount, attr) {
|
|
6754
|
+
if (attr.ino !== void 0) return false;
|
|
6755
|
+
if (mount.caps.links) mount.caps = { ...mount.caps, links: false };
|
|
6756
|
+
return true;
|
|
6757
|
+
}
|
|
6654
6758
|
function statOf(attr, path, mount) {
|
|
6655
6759
|
const mtime = new Date(attr.mtime);
|
|
6656
6760
|
const fallback = attr.kind === "directory" ? 16877 : attr.kind === "symlink" ? 41471 : 33188;
|
|
@@ -6664,10 +6768,12 @@ function statOf(attr, path, mount) {
|
|
|
6664
6768
|
atime: mtime,
|
|
6665
6769
|
ctime: mtime,
|
|
6666
6770
|
ino: attr.ino ?? inodeOf(path),
|
|
6771
|
+
...!inodeless(mount, attr) && attr.nlink !== void 0 ? { nlink: attr.nlink } : {},
|
|
6667
6772
|
dev: mount.dev,
|
|
6668
6773
|
...readonly(mount) ? { readonly: true } : {},
|
|
6669
6774
|
...mount.maxFile > 0 ? { maxFile: mount.maxFile } : {},
|
|
6670
6775
|
...mount.caps.ranges ? { ranged: true } : {},
|
|
6776
|
+
...mount.caps.links ? { linkable: true } : {},
|
|
6671
6777
|
...attr.etag !== void 0 ? { version: attr.etag } : {}
|
|
6672
6778
|
};
|
|
6673
6779
|
}
|
|
@@ -6916,7 +7022,7 @@ var MountTable = class {
|
|
|
6916
7022
|
mount.epoch++;
|
|
6917
7023
|
for (const key of [...mount.attrs.keys()]) if (within4(key, path)) mount.attrs.delete(key);
|
|
6918
7024
|
mount.lists.delete(path);
|
|
6919
|
-
mount.lists.delete(
|
|
7025
|
+
mount.lists.delete(parentOf6(path));
|
|
6920
7026
|
for (const key of [...mount.lists.keys()]) if (within4(key, path)) mount.lists.delete(key);
|
|
6921
7027
|
}
|
|
6922
7028
|
async getattr(mount, path, rel2, settled = false) {
|
|
@@ -6990,9 +7096,9 @@ var MountFs = class {
|
|
|
6990
7096
|
throw err;
|
|
6991
7097
|
}
|
|
6992
7098
|
if (attr.kind !== "symlink") return real;
|
|
6993
|
-
if (hops >=
|
|
7099
|
+
if (hops >= MAX_LINKS5) throw errnoError("ELOOP", path);
|
|
6994
7100
|
const target = await found.mount.conn.call({ op: "readlink", path: found.rel });
|
|
6995
|
-
return this.follow(this.base.resolvePath(
|
|
7101
|
+
return this.follow(this.base.resolvePath(parentOf6(real), target), hops + 1, create);
|
|
6996
7102
|
}
|
|
6997
7103
|
async open(mount, rel2, write) {
|
|
6998
7104
|
return await mount.conn.call({
|
|
@@ -7322,6 +7428,34 @@ var MountFs = class {
|
|
|
7322
7428
|
this.table.forget(a.mount, normalizePath(to));
|
|
7323
7429
|
}
|
|
7324
7430
|
}
|
|
7431
|
+
links(path) {
|
|
7432
|
+
const found = this.at(path);
|
|
7433
|
+
return found ? found.mount.caps.links === true : mayLink(this.base, path);
|
|
7434
|
+
}
|
|
7435
|
+
async named(path) {
|
|
7436
|
+
const at = normalizePath(path);
|
|
7437
|
+
return joinName(await realDir(this, parentOf6(at)), at.slice(at.lastIndexOf("/") + 1));
|
|
7438
|
+
}
|
|
7439
|
+
async link(source, target, follow2 = false) {
|
|
7440
|
+
const from = follow2 ? await realTarget(this, normalizePath(source)) : await this.named(source);
|
|
7441
|
+
const to = await this.named(target);
|
|
7442
|
+
const a = this.at(from);
|
|
7443
|
+
const b = this.at(to);
|
|
7444
|
+
if (!a && !b) return this.base.link(from, to);
|
|
7445
|
+
if (!a || !b || a.mount !== b.mount) throw errnoError("EXDEV", `${from} -> ${to}`);
|
|
7446
|
+
this.writable(a.mount, normalizePath(to));
|
|
7447
|
+
if (a.mount.caps.links) await this.lstat(from);
|
|
7448
|
+
if (!a.mount.caps.links) {
|
|
7449
|
+
throw errnoError("EPERM", `${from}: this file system has no hard links`);
|
|
7450
|
+
}
|
|
7451
|
+
await this.table.commitUnder(from);
|
|
7452
|
+
try {
|
|
7453
|
+
await a.mount.conn.call({ op: "link", from: a.rel, to: b.rel });
|
|
7454
|
+
} finally {
|
|
7455
|
+
this.table.forget(a.mount, normalizePath(from));
|
|
7456
|
+
this.table.forget(a.mount, normalizePath(to));
|
|
7457
|
+
}
|
|
7458
|
+
}
|
|
7325
7459
|
async symlink(target, path, owner) {
|
|
7326
7460
|
const found = this.at(path);
|
|
7327
7461
|
if (!found) return this.base.symlink(target, path, owner);
|
|
@@ -7421,10 +7555,12 @@ function statJson(s) {
|
|
|
7421
7555
|
atimeMs: s.atime.getTime(),
|
|
7422
7556
|
ctimeMs: s.ctime.getTime(),
|
|
7423
7557
|
ino: s.ino,
|
|
7558
|
+
...s.nlink !== void 0 ? { nlink: s.nlink } : {},
|
|
7424
7559
|
...s.dev !== void 0 ? { dev: s.dev } : {},
|
|
7425
7560
|
...s.readonly ? { readonly: true } : {},
|
|
7426
7561
|
...s.maxFile ? { maxFile: s.maxFile } : {},
|
|
7427
7562
|
...s.ranged ? { ranged: true } : {},
|
|
7563
|
+
...s.linkable ? { linkable: true } : {},
|
|
7428
7564
|
...s.version !== void 0 ? { version: s.version } : {},
|
|
7429
7565
|
...s.uid !== void 0 ? { uid: s.uid } : {},
|
|
7430
7566
|
...s.gid !== void 0 ? { gid: s.gid } : {}
|
|
@@ -7493,6 +7629,9 @@ async function run(entry, path, req) {
|
|
|
7493
7629
|
entry.renamed?.(path, to);
|
|
7494
7630
|
return done;
|
|
7495
7631
|
}
|
|
7632
|
+
case "link":
|
|
7633
|
+
await fs.link(path, fs.resolvePath(cwd, req.arg2 ?? ""), req.mode === 1);
|
|
7634
|
+
return done;
|
|
7496
7635
|
case "unlink":
|
|
7497
7636
|
if ((await fs.lstat(path)).isDirectory) throw syncError("EISDIR", path);
|
|
7498
7637
|
await fs.rm(path);
|
|
@@ -7554,12 +7693,28 @@ async function ranged(fs, path, req) {
|
|
|
7554
7693
|
return done;
|
|
7555
7694
|
}
|
|
7556
7695
|
var UNLINKED_OPS = /* @__PURE__ */ new Set(["read", "pread", "pwrite", "truncate"]);
|
|
7557
|
-
var OWN_OPS = /* @__PURE__ */ new Set(["rm", "unlink", "rmdir", "rename"]);
|
|
7696
|
+
var OWN_OPS = /* @__PURE__ */ new Set(["rm", "unlink", "rmdir", "rename", "link"]);
|
|
7697
|
+
var TWO_PATHS = /* @__PURE__ */ new Set(["rename", "link"]);
|
|
7698
|
+
var UNLINKING_OPS = /* @__PURE__ */ new Set(["rm", "unlink", "rename"]);
|
|
7699
|
+
async function linkedElsewhere(fs, path, from) {
|
|
7700
|
+
if (!mayLink(fs, path)) return false;
|
|
7701
|
+
const st = await fs.lstat(path).catch(() => void 0);
|
|
7702
|
+
if (!st || (st.nlink ?? 1) < 2) return false;
|
|
7703
|
+
const source = from === void 0 ? void 0 : await fs.lstat(from).catch(() => void 0);
|
|
7704
|
+
return !(source && source.ino === st.ino && source.dev === st.dev);
|
|
7705
|
+
}
|
|
7558
7706
|
async function keptOr(entry, path, handle) {
|
|
7559
7707
|
if (handle !== void 0) return entry.keptFor?.(handle)?.hidden ?? path;
|
|
7560
7708
|
const kept = entry.unlinked?.(path);
|
|
7561
7709
|
return kept && !await entry.fs.exists(path) ? kept.hidden : path;
|
|
7562
7710
|
}
|
|
7711
|
+
async function owned(entry, own, req, path, target) {
|
|
7712
|
+
const source = target === path ? void 0 : path;
|
|
7713
|
+
if (UNLINKING_OPS.has(req.op) && await linkedElsewhere(entry.fs, target, source)) {
|
|
7714
|
+
return run(entry, path, req);
|
|
7715
|
+
}
|
|
7716
|
+
return own(target, () => run(entry, path, req));
|
|
7717
|
+
}
|
|
7563
7718
|
async function dispatchSyncFs(req) {
|
|
7564
7719
|
const entry = resolveSyncFsToken(req.token);
|
|
7565
7720
|
if (!entry) return { ok: false, errno: "EACCES", message: "sync-fs: unknown or revoked token" };
|
|
@@ -7583,8 +7738,8 @@ async function dispatchSyncFs(req) {
|
|
|
7583
7738
|
return await (entry.gate ? entry.gate(path, serve) : serve());
|
|
7584
7739
|
}
|
|
7585
7740
|
if (!entry.own || !OWN_OPS.has(req.op)) return await run(entry, path, req);
|
|
7586
|
-
const target = req.op
|
|
7587
|
-
return await entry.own
|
|
7741
|
+
const target = TWO_PATHS.has(req.op) ? fs.resolvePath(cwd, req.arg2 ?? "") : path;
|
|
7742
|
+
return await owned(entry, entry.own, req, path, target);
|
|
7588
7743
|
} catch (err) {
|
|
7589
7744
|
return toErrno(err);
|
|
7590
7745
|
}
|
|
@@ -9531,6 +9686,7 @@ var VfsNode = class {
|
|
|
9531
9686
|
fs;
|
|
9532
9687
|
path;
|
|
9533
9688
|
orphaned;
|
|
9689
|
+
id;
|
|
9534
9690
|
constructor(fs, path, contents2, orphaned = false, dirty = false, pin) {
|
|
9535
9691
|
this.fs = fs;
|
|
9536
9692
|
this.pin = pin;
|
|
@@ -9700,6 +9856,7 @@ var VfsNode = class {
|
|
|
9700
9856
|
var VfsNodes = class {
|
|
9701
9857
|
byPath = /* @__PURE__ */ new Map();
|
|
9702
9858
|
kept = /* @__PURE__ */ new Map();
|
|
9859
|
+
byId = /* @__PURE__ */ new Map();
|
|
9703
9860
|
keepsOnDisk = false;
|
|
9704
9861
|
fs;
|
|
9705
9862
|
entries = /* @__PURE__ */ new Map();
|
|
@@ -9719,7 +9876,7 @@ var VfsNodes = class {
|
|
|
9719
9876
|
return run2;
|
|
9720
9877
|
}
|
|
9721
9878
|
async entryKey(path) {
|
|
9722
|
-
const parent =
|
|
9879
|
+
const parent = parentOf5(path);
|
|
9723
9880
|
const dir = await this.real(parent, true, () => realDir(this.fs, parent));
|
|
9724
9881
|
return joinName(dir, path.slice(path.lastIndexOf("/") + 1));
|
|
9725
9882
|
}
|
|
@@ -9744,21 +9901,66 @@ var VfsNodes = class {
|
|
|
9744
9901
|
await this.fs.writeFile(path, new Uint8Array(0), owner);
|
|
9745
9902
|
});
|
|
9746
9903
|
}
|
|
9747
|
-
|
|
9748
|
-
|
|
9749
|
-
if (!
|
|
9750
|
-
|
|
9751
|
-
|
|
9752
|
-
|
|
9904
|
+
async identity(path) {
|
|
9905
|
+
const st = await this.fs.stat?.(path).catch(() => void 0);
|
|
9906
|
+
if (!st || st.ino === void 0 || (st.nlink ?? 1) < 2) return void 0;
|
|
9907
|
+
return `${st.dev ?? 0}:${st.ino}`;
|
|
9908
|
+
}
|
|
9909
|
+
async identify(path) {
|
|
9910
|
+
return this.byPath.has(path) || !mayLink(this.fs, path) ? void 0 : this.identity(path);
|
|
9911
|
+
}
|
|
9912
|
+
async linked(from, to) {
|
|
9913
|
+
const node = this.byPath.get(from);
|
|
9914
|
+
if (!node || node.orphaned) return;
|
|
9915
|
+
const id2 = node.id ?? await this.identity(to);
|
|
9916
|
+
if (id2 === void 0 || node.orphaned) return;
|
|
9917
|
+
this.know(node, id2);
|
|
9918
|
+
if (!this.byPath.has(to)) this.byPath.set(to, node);
|
|
9919
|
+
}
|
|
9920
|
+
know(node, id2) {
|
|
9921
|
+
node.id = id2;
|
|
9922
|
+
if (!this.byId.has(id2)) this.byId.set(id2, node);
|
|
9923
|
+
}
|
|
9924
|
+
namesOf(node) {
|
|
9925
|
+
const out = [];
|
|
9926
|
+
for (const [p, n] of this.byPath) if (n === node) out.push(p);
|
|
9927
|
+
return out;
|
|
9928
|
+
}
|
|
9929
|
+
forget(node) {
|
|
9930
|
+
for (const p of this.namesOf(node)) this.byPath.delete(p);
|
|
9931
|
+
if (node.id !== void 0 && this.byId.get(node.id) === node) this.byId.delete(node.id);
|
|
9932
|
+
}
|
|
9933
|
+
orphan(node) {
|
|
9934
|
+
node.orphaned = true;
|
|
9935
|
+
this.forget(node);
|
|
9936
|
+
}
|
|
9937
|
+
dropName(path, node) {
|
|
9938
|
+
const others = this.namesOf(node).filter((p) => p !== path);
|
|
9939
|
+
if (others.length === 0) return false;
|
|
9940
|
+
this.byPath.delete(path);
|
|
9941
|
+
if (node.path === path) node.renamedTo(others[0]);
|
|
9942
|
+
return true;
|
|
9943
|
+
}
|
|
9944
|
+
named(path, id2) {
|
|
9945
|
+
const node = this.byPath.get(path) ?? (id2 === void 0 ? void 0 : this.byId.get(id2));
|
|
9946
|
+
if (node && !this.byPath.has(path)) this.byPath.set(path, node);
|
|
9947
|
+
return node;
|
|
9948
|
+
}
|
|
9949
|
+
create(path, id2, node) {
|
|
9950
|
+
this.byPath.set(path, node);
|
|
9951
|
+
if (id2 !== void 0) this.know(node, id2);
|
|
9952
|
+
return node;
|
|
9953
|
+
}
|
|
9954
|
+
open(path, pin, writable = false, id2) {
|
|
9955
|
+
const node = this.named(path, id2) ?? this.create(path, id2, new VfsNode(this.fs, path, void 0, false, false, pin));
|
|
9753
9956
|
node.opens++;
|
|
9754
9957
|
if (writable) node.writers++;
|
|
9755
9958
|
return node;
|
|
9756
9959
|
}
|
|
9757
|
-
adopt(path, contents2, dirty, writable = false) {
|
|
9758
|
-
let node = this.
|
|
9960
|
+
adopt(path, contents2, dirty, writable = false, id2) {
|
|
9961
|
+
let node = this.named(path, id2);
|
|
9759
9962
|
if (!node) {
|
|
9760
|
-
node = new VfsNode(this.fs, path, contents2, false, dirty);
|
|
9761
|
-
this.byPath.set(path, node);
|
|
9963
|
+
node = this.create(path, id2, new VfsNode(this.fs, path, contents2, false, dirty));
|
|
9762
9964
|
} else if (dirty) {
|
|
9763
9965
|
node.replace(contents2);
|
|
9764
9966
|
}
|
|
@@ -9782,7 +9984,7 @@ var VfsNodes = class {
|
|
|
9782
9984
|
if (writable && node.writers === 0) this.onClosed?.(node.path);
|
|
9783
9985
|
return;
|
|
9784
9986
|
}
|
|
9785
|
-
|
|
9987
|
+
this.forget(node);
|
|
9786
9988
|
this.kept.get(node)?.release();
|
|
9787
9989
|
this.kept.delete(node);
|
|
9788
9990
|
this.onClosed?.(node.path);
|
|
@@ -9798,7 +10000,7 @@ var VfsNodes = class {
|
|
|
9798
10000
|
}
|
|
9799
10001
|
keep(path, file) {
|
|
9800
10002
|
const node = this.byPath.get(path);
|
|
9801
|
-
if (!node) {
|
|
10003
|
+
if (!node || this.dropName(path, node)) {
|
|
9802
10004
|
file.release();
|
|
9803
10005
|
return;
|
|
9804
10006
|
}
|
|
@@ -9827,33 +10029,32 @@ var VfsNodes = class {
|
|
|
9827
10029
|
}
|
|
9828
10030
|
}
|
|
9829
10031
|
revoke(prefix) {
|
|
9830
|
-
for (const [path, node] of this.byPath) {
|
|
10032
|
+
for (const [path, node] of [...this.byPath]) {
|
|
9831
10033
|
if (!within5(path, prefix)) continue;
|
|
9832
10034
|
node.revoke();
|
|
9833
|
-
this.
|
|
10035
|
+
this.forget(node);
|
|
9834
10036
|
}
|
|
9835
10037
|
}
|
|
9836
10038
|
unlinked(path) {
|
|
9837
10039
|
for (const [held, node] of [...this.byPath]) {
|
|
9838
|
-
if (!within5(held, path)) continue;
|
|
9839
|
-
|
|
9840
|
-
this.byPath.delete(held);
|
|
10040
|
+
if (!within5(held, path) || this.byPath.get(held) !== node) continue;
|
|
10041
|
+
if (!this.dropName(held, node)) this.orphan(node);
|
|
9841
10042
|
}
|
|
9842
10043
|
}
|
|
9843
10044
|
renamed(from, to) {
|
|
9844
10045
|
if (from === to) return;
|
|
9845
10046
|
const moved2 = [];
|
|
9846
|
-
for (const [p, node] of this.byPath) {
|
|
9847
|
-
if (within5(p, from)) moved2.push(node);
|
|
9848
|
-
else if (within5(p, to)) {
|
|
9849
|
-
node
|
|
9850
|
-
this.byPath.delete(p);
|
|
10047
|
+
for (const [p, node] of [...this.byPath]) {
|
|
10048
|
+
if (within5(p, from)) moved2.push([p, node]);
|
|
10049
|
+
else if (within5(p, to) && this.byPath.get(p) === node && !this.dropName(p, node)) {
|
|
10050
|
+
this.orphan(node);
|
|
9851
10051
|
}
|
|
9852
10052
|
}
|
|
9853
|
-
for (const
|
|
9854
|
-
|
|
9855
|
-
|
|
9856
|
-
|
|
10053
|
+
for (const [p] of moved2) this.byPath.delete(p);
|
|
10054
|
+
for (const [p, node] of moved2) {
|
|
10055
|
+
const next2 = to + p.slice(from.length);
|
|
10056
|
+
if (node.path === p) node.renamedTo(next2);
|
|
10057
|
+
this.byPath.set(next2, node);
|
|
9857
10058
|
}
|
|
9858
10059
|
}
|
|
9859
10060
|
};
|
|
@@ -9867,65 +10068,11 @@ async function present(fs, path, follow2 = false) {
|
|
|
9867
10068
|
throw err;
|
|
9868
10069
|
}
|
|
9869
10070
|
}
|
|
9870
|
-
var MAX_LINKS5 = 40;
|
|
9871
|
-
function parentOf6(path) {
|
|
9872
|
-
return path.slice(0, path.lastIndexOf("/")) || "/";
|
|
9873
|
-
}
|
|
9874
|
-
async function mustBeDir(fs, path) {
|
|
9875
|
-
if (!fs.lstat) return;
|
|
9876
|
-
const st = await fs.lstat(path);
|
|
9877
|
-
if (st.isDirectory !== true) throw fsError("ENOTDIR", path);
|
|
9878
|
-
}
|
|
9879
|
-
async function linkOf(fs, path) {
|
|
9880
|
-
return fs.readlink?.(path).catch(() => void 0);
|
|
9881
|
-
}
|
|
9882
|
-
async function realDir(fs, dir, budget = { hops: 0 }) {
|
|
9883
|
-
const queue = dir.split("/").filter(Boolean);
|
|
9884
|
-
let at = "/";
|
|
9885
|
-
while (queue.length > 0) {
|
|
9886
|
-
const part = queue.shift();
|
|
9887
|
-
if (part === ".") continue;
|
|
9888
|
-
if (part === "..") {
|
|
9889
|
-
at = parentOf6(at);
|
|
9890
|
-
continue;
|
|
9891
|
-
}
|
|
9892
|
-
const next2 = at === "/" ? `/${part}` : `${at}/${part}`;
|
|
9893
|
-
const link2 = await linkOf(fs, next2);
|
|
9894
|
-
if (link2 === void 0) {
|
|
9895
|
-
if (queue.find((p) => p !== ".") === "..") await mustBeDir(fs, next2);
|
|
9896
|
-
at = next2;
|
|
9897
|
-
continue;
|
|
9898
|
-
}
|
|
9899
|
-
if (budget.hops >= MAX_LINKS5) throw fsError("ELOOP", dir);
|
|
9900
|
-
budget.hops++;
|
|
9901
|
-
queue.unshift(...link2.split("/").filter(Boolean));
|
|
9902
|
-
if (link2.startsWith("/")) at = "/";
|
|
9903
|
-
}
|
|
9904
|
-
return at;
|
|
9905
|
-
}
|
|
9906
10071
|
function errCode(err) {
|
|
9907
10072
|
return err?.code;
|
|
9908
10073
|
}
|
|
9909
|
-
function joinName(dir, name2) {
|
|
9910
|
-
return dir === "/" ? `/${name2}` : `${dir}/${name2}`;
|
|
9911
|
-
}
|
|
9912
|
-
async function realTarget(fs, path) {
|
|
9913
|
-
const budget = { hops: 0 };
|
|
9914
|
-
let dir = await realDir(fs, parentOf6(path), budget);
|
|
9915
|
-
let name2 = path.slice(path.lastIndexOf("/") + 1);
|
|
9916
|
-
for (; ; ) {
|
|
9917
|
-
const link2 = await linkOf(fs, joinName(dir, name2));
|
|
9918
|
-
if (link2 === void 0) break;
|
|
9919
|
-
if (budget.hops >= MAX_LINKS5) throw fsError("ELOOP", path);
|
|
9920
|
-
budget.hops++;
|
|
9921
|
-
const target = link2.startsWith("/") ? link2 : joinName(dir, link2);
|
|
9922
|
-
dir = await realDir(fs, parentOf6(target), budget);
|
|
9923
|
-
name2 = target.slice(target.lastIndexOf("/") + 1);
|
|
9924
|
-
}
|
|
9925
|
-
return joinName(dir, name2);
|
|
9926
|
-
}
|
|
9927
10074
|
async function targetDir(fs, path) {
|
|
9928
|
-
return
|
|
10075
|
+
return parentOf5(await realTarget(fs, path).catch(() => path));
|
|
9929
10076
|
}
|
|
9930
10077
|
async function refuseReadonly(fs, path, flags, opts = {}) {
|
|
9931
10078
|
const writes = (flags & O_ACCMODE) !== 0 || opts.truncate === true;
|
|
@@ -9961,6 +10108,15 @@ function keepingOpen(fs, nodes) {
|
|
|
9961
10108
|
writeFile: (path, content, owner) => nodes.onEntry(fs.resolvePath("/", path), () => fs.writeFile(path, content, owner)),
|
|
9962
10109
|
createExclusive: async (path, owner) => nodes.createExclusive(await nodes.entryKey(fs.resolvePath("/", path)), owner),
|
|
9963
10110
|
symlink: (target, path, owner) => nodes.onEntry(fs.resolvePath("/", path), () => fs.symlink(target, path, owner)),
|
|
10111
|
+
links: (path) => mayLink(fs, path),
|
|
10112
|
+
link: async (from, to, follow2) => {
|
|
10113
|
+
const source = fs.resolvePath("/", from);
|
|
10114
|
+
const at = await (follow2 ? nodes.targetKey(source) : nodes.entryKey(source));
|
|
10115
|
+
await nodes.flush(at);
|
|
10116
|
+
const key = await nodes.entryKey(fs.resolvePath("/", to));
|
|
10117
|
+
await nodes.onEntry(key, () => fs.link(from, to, follow2));
|
|
10118
|
+
await nodes.linked(at, key);
|
|
10119
|
+
},
|
|
9964
10120
|
mkdir: (path, options) => nodes.onEntry(fs.resolvePath("/", path), () => fs.mkdir(path, options))
|
|
9965
10121
|
};
|
|
9966
10122
|
}
|
|
@@ -9968,7 +10124,7 @@ function vfsFile(fs, opts, nodes) {
|
|
|
9968
10124
|
const access = opts.flags & O_ACCMODE;
|
|
9969
10125
|
const readable = access !== O_WRONLY;
|
|
9970
10126
|
const writable = access === O_WRONLY || access === O_RDWR;
|
|
9971
|
-
const node = !nodes || opts.orphan ? new VfsNode(fs, opts.path, opts.contents, opts.orphan === true, opts.dirty === true) : opts.contents !== void 0 ? nodes.adopt(opts.path, opts.contents, opts.dirty === true, writable) : nodes.open(opts.path, opts.pin, writable);
|
|
10127
|
+
const node = !nodes || opts.orphan ? new VfsNode(fs, opts.path, opts.contents, opts.orphan === true, opts.dirty === true) : opts.contents !== void 0 ? nodes.adopt(opts.path, opts.contents, opts.dirty === true, writable, opts.id) : nodes.open(opts.path, opts.pin, writable, opts.id);
|
|
9972
10128
|
let openError;
|
|
9973
10129
|
const atOpen = (op) => {
|
|
9974
10130
|
void node.serial(op).catch((err) => {
|
|
@@ -10403,12 +10559,12 @@ var WasmProcess = class {
|
|
|
10403
10559
|
} else if (req.create) {
|
|
10404
10560
|
await nodes.onEntry(path, async () => {
|
|
10405
10561
|
const fresh = this.options.owners && !await present(this.options.fs, path, true);
|
|
10406
|
-
this.fds.installAt(fd, this.openVfsFile(open));
|
|
10562
|
+
this.fds.installAt(fd, this.openVfsFile(open, await nodes.identify(path)));
|
|
10407
10563
|
await this.fds.get(fd).file.stat?.();
|
|
10408
10564
|
if (fresh) await this.options.fs.chown?.(path, { ...this.newOwner(), kind: "new" });
|
|
10409
10565
|
});
|
|
10410
10566
|
} else {
|
|
10411
|
-
this.fds.installAt(fd, this.openVfsFile(open));
|
|
10567
|
+
this.fds.installAt(fd, this.openVfsFile(open, await nodes.identify(path)));
|
|
10412
10568
|
}
|
|
10413
10569
|
if (req.existing && !await present(this.options.fs, path, true)) {
|
|
10414
10570
|
throw new KernelError("ENOENT");
|
|
@@ -10439,7 +10595,7 @@ var WasmProcess = class {
|
|
|
10439
10595
|
);
|
|
10440
10596
|
return memoryFile(tree.read(path), path, req.position);
|
|
10441
10597
|
}
|
|
10442
|
-
openVfsFile(req) {
|
|
10598
|
+
openVfsFile(req, id2) {
|
|
10443
10599
|
return vfsFile(
|
|
10444
10600
|
this.options.fs,
|
|
10445
10601
|
{
|
|
@@ -10450,7 +10606,8 @@ var WasmProcess = class {
|
|
|
10450
10606
|
...req.orphan ? { orphan: true } : {},
|
|
10451
10607
|
...req.truncate ? { truncate: true } : {},
|
|
10452
10608
|
...req.create ? { create: true } : {},
|
|
10453
|
-
...req.pin ? { pin: req.pin } : {}
|
|
10609
|
+
...req.pin ? { pin: req.pin } : {},
|
|
10610
|
+
...id2 !== void 0 && !req.orphan ? { id: id2 } : {}
|
|
10454
10611
|
},
|
|
10455
10612
|
this.nodes
|
|
10456
10613
|
);
|
|
@@ -10598,6 +10755,7 @@ var WasmProcess = class {
|
|
|
10598
10755
|
}
|
|
10599
10756
|
if (req.path === void 0) throw new KernelError("EINVAL");
|
|
10600
10757
|
const path = req.orphan ? req.path : await this.keptOr(await this.nodes.targetKey(req.path), req.handle);
|
|
10758
|
+
const id2 = req.orphan ? void 0 : await this.nodes.identify(path);
|
|
10601
10759
|
if (!this.fds.get(req.fd).file.held) throw new KernelError("EBADF");
|
|
10602
10760
|
const file = vfsFile(
|
|
10603
10761
|
this.options.fs,
|
|
@@ -10608,7 +10766,8 @@ var WasmProcess = class {
|
|
|
10608
10766
|
...req.contents !== void 0 ? { contents: req.contents } : {},
|
|
10609
10767
|
...req.orphan ? { orphan: true } : {},
|
|
10610
10768
|
...req.dirty ? { dirty: true } : {},
|
|
10611
|
-
...req.pin ? { pin: req.pin } : {}
|
|
10769
|
+
...req.pin ? { pin: req.pin } : {},
|
|
10770
|
+
...id2 !== void 0 ? { id: id2 } : {}
|
|
10612
10771
|
},
|
|
10613
10772
|
this.nodes
|
|
10614
10773
|
);
|
|
@@ -11680,6 +11839,9 @@ async function dispatch(h, req) {
|
|
|
11680
11839
|
return h.unlink(req.path);
|
|
11681
11840
|
case "rename":
|
|
11682
11841
|
return h.rename(req.from, req.to);
|
|
11842
|
+
case "link":
|
|
11843
|
+
if (!h.link) throw fsError2("EPERM", "this file system has no hard links");
|
|
11844
|
+
return h.link(req.from, req.to);
|
|
11683
11845
|
case "symlink":
|
|
11684
11846
|
if (!h.symlink) throw fsError2("EPERM", "this file system has no symlinks");
|
|
11685
11847
|
return h.symlink(req.target, req.path);
|
|
@@ -12762,8 +12924,8 @@ var ChunkedBytes = class {
|
|
|
12762
12924
|
}
|
|
12763
12925
|
return out;
|
|
12764
12926
|
}
|
|
12765
|
-
write(offset, bytes,
|
|
12766
|
-
const whole =
|
|
12927
|
+
write(offset, bytes, owned2 = false) {
|
|
12928
|
+
const whole = owned2 && bytes.byteOffset === 0 && bytes.byteLength === bytes.buffer.byteLength;
|
|
12767
12929
|
for (let done4 = 0; done4 < bytes.length; ) {
|
|
12768
12930
|
const at = offset + done4;
|
|
12769
12931
|
const index = Math.floor(at / CHUNK3);
|
|
@@ -12805,12 +12967,17 @@ function tmpfs() {
|
|
|
12805
12967
|
mtime: Date.now(),
|
|
12806
12968
|
data: new ChunkedBytes(),
|
|
12807
12969
|
target: "",
|
|
12808
|
-
children: /* @__PURE__ */ new Map()
|
|
12970
|
+
children: /* @__PURE__ */ new Map(),
|
|
12971
|
+
links: 1
|
|
12809
12972
|
});
|
|
12810
12973
|
const root = make("directory", 16877);
|
|
12811
12974
|
const handles = /* @__PURE__ */ new Map();
|
|
12812
12975
|
let nextFh = 0;
|
|
12813
12976
|
const parts = (path) => path.split("/").filter(Boolean);
|
|
12977
|
+
const place = (dir, name2, node) => {
|
|
12978
|
+
dir.children.set(name2, node);
|
|
12979
|
+
dir.mtime = Date.now();
|
|
12980
|
+
};
|
|
12814
12981
|
function find(path) {
|
|
12815
12982
|
let node = root;
|
|
12816
12983
|
for (const name2 of parts(path)) {
|
|
@@ -12834,7 +13001,8 @@ function tmpfs() {
|
|
|
12834
13001
|
size: node.kind === "file" ? node.data.size : node.kind === "symlink" ? node.target.length : 4096,
|
|
12835
13002
|
mtime: node.mtime,
|
|
12836
13003
|
mode: node.mode,
|
|
12837
|
-
ino: node.ino
|
|
13004
|
+
ino: node.ino,
|
|
13005
|
+
...node.kind === "directory" ? {} : { nlink: node.links }
|
|
12838
13006
|
});
|
|
12839
13007
|
const handle = (fh) => {
|
|
12840
13008
|
const found = handles.get(fh);
|
|
@@ -12849,8 +13017,7 @@ function tmpfs() {
|
|
|
12849
13017
|
if (existing) return existing;
|
|
12850
13018
|
if (!flags.create) throw fsError2("ENOENT", path);
|
|
12851
13019
|
const node = make("file", 33188);
|
|
12852
|
-
dir
|
|
12853
|
-
dir.mtime = Date.now();
|
|
13020
|
+
place(dir, name2, node);
|
|
12854
13021
|
return node;
|
|
12855
13022
|
}
|
|
12856
13023
|
return {
|
|
@@ -12889,8 +13056,7 @@ function tmpfs() {
|
|
|
12889
13056
|
async mkdir(path) {
|
|
12890
13057
|
const { dir, name: name2 } = parent(path);
|
|
12891
13058
|
if (dir.children.has(name2)) throw fsError2("EEXIST", path);
|
|
12892
|
-
dir
|
|
12893
|
-
dir.mtime = Date.now();
|
|
13059
|
+
place(dir, name2, make("directory", 16877));
|
|
12894
13060
|
},
|
|
12895
13061
|
async rmdir(path) {
|
|
12896
13062
|
const { dir, name: name2 } = parent(path);
|
|
@@ -12907,6 +13073,7 @@ function tmpfs() {
|
|
|
12907
13073
|
if (!node) throw fsError2("ENOENT", path);
|
|
12908
13074
|
if (node.kind === "directory") throw fsError2("EISDIR", path);
|
|
12909
13075
|
dir.children.delete(name2);
|
|
13076
|
+
node.links--;
|
|
12910
13077
|
dir.mtime = Date.now();
|
|
12911
13078
|
},
|
|
12912
13079
|
async rename(from, to) {
|
|
@@ -12923,17 +13090,25 @@ function tmpfs() {
|
|
|
12923
13090
|
throw fsError2(node.kind === "directory" ? "ENOTEMPTY" : "EISDIR", to);
|
|
12924
13091
|
}
|
|
12925
13092
|
source.dir.children.delete(source.name);
|
|
13093
|
+
if (replaced) replaced.links--;
|
|
12926
13094
|
target.dir.children.set(target.name, node);
|
|
12927
13095
|
source.dir.mtime = Date.now();
|
|
12928
13096
|
target.dir.mtime = source.dir.mtime;
|
|
12929
13097
|
},
|
|
13098
|
+
async link(from, to) {
|
|
13099
|
+
const node = find(from);
|
|
13100
|
+
if (node.kind === "directory") throw fsError2("EPERM", `${from} is a directory`);
|
|
13101
|
+
const { dir, name: name2 } = parent(to);
|
|
13102
|
+
if (dir.children.has(name2)) throw fsError2("EEXIST", to);
|
|
13103
|
+
place(dir, name2, node);
|
|
13104
|
+
node.links++;
|
|
13105
|
+
},
|
|
12930
13106
|
async symlink(target, path) {
|
|
12931
13107
|
const { dir, name: name2 } = parent(path);
|
|
12932
13108
|
if (dir.children.has(name2)) throw fsError2("EEXIST", path);
|
|
12933
13109
|
const node = make("symlink", 41471);
|
|
12934
13110
|
node.target = target;
|
|
12935
|
-
dir
|
|
12936
|
-
dir.mtime = Date.now();
|
|
13111
|
+
place(dir, name2, node);
|
|
12937
13112
|
},
|
|
12938
13113
|
async readlink(path) {
|
|
12939
13114
|
const node = find(path);
|
|
@@ -13833,7 +14008,7 @@ var Launcher = class {
|
|
|
13833
14008
|
serveFilesystem(
|
|
13834
14009
|
port2,
|
|
13835
14010
|
tmpfs(),
|
|
13836
|
-
{ symlinks: true, chmod: true, linkTimes: true, ranges: true, attrTtl: 0 },
|
|
14011
|
+
{ symlinks: true, links: true, chmod: true, linkTimes: true, ranges: true, attrTtl: 0 },
|
|
13837
14012
|
{ owned: true }
|
|
13838
14013
|
);
|
|
13839
14014
|
return {
|