Skip to content

Commit c0f341b

Browse files
committed
fs_mem: support pointing to not yet existing target in symlink
1 parent 6a1f480 commit c0f341b

File tree

1 file changed

+134
-7
lines changed

1 file changed

+134
-7
lines changed

src/fs_mem.ts

Lines changed: 134 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -411,6 +411,7 @@ export class OpenDirectory extends Fd {
411411
inode.parent = parent_entry;
412412
}
413413
parent_entry.contents.set(filename, inode);
414+
parent_entry.resolvePendingLinkTarget(filename, inode);
414415

415416
return wasi.ERRNO_SUCCESS;
416417
}
@@ -423,10 +424,10 @@ export class OpenDirectory extends Fd {
423424

424425
const {
425426
ret: parent_ret,
426-
parent_entry,
427-
filename,
427+
parent_entry: link_parent,
428+
filename: link_filename,
428429
} = this.dir.get_parent_dir_and_entry_for_path(new_path, true);
429-
if (parent_entry == null || filename == null) {
430+
if (link_parent == null || link_filename == null) {
430431
return parent_ret;
431432
}
432433

@@ -435,12 +436,32 @@ export class OpenDirectory extends Fd {
435436
return old_ret;
436437
}
437438

438-
const { ret: entry_ret, entry } = parent_entry.get_entry_for_path(old_path);
439-
if (entry == null) {
440-
return entry_ret;
439+
const {
440+
ret: target_parent_ret,
441+
parent_entry: target_parent,
442+
filename: target_filename,
443+
entry: target_entry,
444+
} = link_parent.get_parent_dir_and_entry_for_path(old_path, true);
445+
if (target_parent == null || target_filename == null) {
446+
return target_parent_ret;
447+
}
448+
449+
if (target_entry != null) {
450+
return this.path_link(new_path_str, target_entry, true);
441451
}
442452

443-
return this.path_link(new_path_str, entry, true);
453+
const placeholder = new PendingSymlink(
454+
link_parent,
455+
link_filename,
456+
target_parent,
457+
target_filename,
458+
);
459+
const link_ret = this.path_link(new_path_str, placeholder, true);
460+
if (link_ret != wasi.ERRNO_SUCCESS) {
461+
return link_ret;
462+
}
463+
target_parent.registerPendingLinkTarget(target_filename, placeholder);
464+
return wasi.ERRNO_SUCCESS;
444465
}
445466

446467
path_unlink(path_str: string): { ret: number; inode_obj: InodeMem | null } {
@@ -463,6 +484,7 @@ export class OpenDirectory extends Fd {
463484
return { ret: wasi.ERRNO_NOENT, inode_obj: null };
464485
}
465486

487+
detachPendingSymlink(entry);
466488
parent_entry.contents.delete(filename);
467489

468490
return { ret: wasi.ERRNO_SUCCESS, inode_obj: entry };
@@ -486,6 +508,7 @@ export class OpenDirectory extends Fd {
486508
if (entry.stat().filetype === wasi.FILETYPE_DIRECTORY) {
487509
return wasi.ERRNO_ISDIR;
488510
}
511+
detachPendingSymlink(entry);
489512
parent_entry.contents.delete(filename);
490513
return wasi.ERRNO_SUCCESS;
491514
}
@@ -713,6 +736,7 @@ class Path {
713736
export class Directory extends InodeMem {
714737
contents: Map<string, InodeMem>;
715738
parent: Directory | null = null;
739+
private pendingSymlinkTargets?: Map<string, PendingSymlink[]>;
716740

717741
constructor(contents: Map<string, InodeMem> | [string, InodeMem][]) {
718742
super();
@@ -879,9 +903,112 @@ export class Directory extends InodeMem {
879903
}
880904
parent_entry.contents.set(filename, new_child);
881905
entry = new_child;
906+
parent_entry.resolvePendingLinkTarget(filename, entry);
882907

883908
return { ret: wasi.ERRNO_SUCCESS, entry };
884909
}
910+
911+
registerPendingLinkTarget(name: string, pending: PendingSymlink): void {
912+
if (this.pendingSymlinkTargets == undefined) {
913+
this.pendingSymlinkTargets = new Map();
914+
}
915+
const pendingList = this.pendingSymlinkTargets.get(name);
916+
if (pendingList == undefined) {
917+
this.pendingSymlinkTargets.set(name, [pending]);
918+
} else {
919+
pendingList.push(pending);
920+
}
921+
}
922+
923+
removePendingLinkTarget(name: string, pending: PendingSymlink): void {
924+
const pendingList = this.pendingSymlinkTargets?.get(name);
925+
if (pendingList == undefined) {
926+
return;
927+
}
928+
const idx = pendingList.indexOf(pending);
929+
if (idx != -1) {
930+
pendingList.splice(idx, 1);
931+
}
932+
if (pendingList.length == 0) {
933+
this.pendingSymlinkTargets?.delete(name);
934+
}
935+
}
936+
937+
resolvePendingLinkTarget(name: string, entry: InodeMem): void {
938+
const pendingList = this.pendingSymlinkTargets?.get(name);
939+
if (pendingList == undefined) {
940+
return;
941+
}
942+
this.pendingSymlinkTargets?.delete(name);
943+
for (const pending of pendingList) {
944+
pending.resolve(entry);
945+
}
946+
}
947+
}
948+
949+
class PendingSymlink extends InodeMem {
950+
private readonly parent: Directory;
951+
private readonly filename: string;
952+
private targetDir: Directory | null;
953+
private targetName: string | null;
954+
955+
constructor(
956+
parent: Directory,
957+
filename: string,
958+
targetDir: Directory,
959+
targetName: string,
960+
) {
961+
super();
962+
this.parent = parent;
963+
this.filename = filename;
964+
this.targetDir = targetDir;
965+
this.targetName = targetName;
966+
}
967+
968+
resolve(target: InodeMem): void {
969+
if (this.parent.contents.get(this.filename) !== this) {
970+
this.detach();
971+
return;
972+
}
973+
if (target instanceof Directory) {
974+
target.parent = this.parent;
975+
}
976+
this.parent.contents.set(this.filename, target);
977+
this.detach();
978+
}
979+
980+
path_open(
981+
_oflags: number,
982+
_fs_rights_base: bigint,
983+
_fd_flags: number,
984+
): { ret: number; fd_obj: Fd | null } {
985+
return { ret: wasi.ERRNO_NOENT, fd_obj: null };
986+
}
987+
988+
stat(): wasi.Filestat {
989+
return new wasi.Filestat(
990+
this.ino,
991+
wasi.FILETYPE_SYMBOLIC_LINK,
992+
0n,
993+
this.atim,
994+
this.mtim,
995+
this.ctim,
996+
);
997+
}
998+
999+
detach(): void {
1000+
if (this.targetDir != null && this.targetName != null) {
1001+
this.targetDir.removePendingLinkTarget(this.targetName, this);
1002+
this.targetDir = null;
1003+
this.targetName = null;
1004+
}
1005+
}
1006+
}
1007+
1008+
function detachPendingSymlink(entry: InodeMem): void {
1009+
if (entry instanceof PendingSymlink) {
1010+
entry.detach();
1011+
}
8851012
}
8861013

8871014
export class ConsoleStdout extends Fd {

0 commit comments

Comments
 (0)