A tree of names
Disks only store numbered blocks. The file system turns them into a tree of named directories and files. Each file's metadata lives in an inode (size, permissions, which blocks hold its data); directories just map names to inodes. Resolving /docs/notes/todo.txt means walking that tree one name at a time.
The tree in code
function createFS() {
const root = { type: "dir", children: {} };
const parts = (path) => path.split("/").filter(Boolean);
function find(path) {
let node = root;
for (const name of parts(path)) {
if (node.type !== "dir" || !node.children[name]) return null;
node = node.children[name];
}
return node;
}
function parentOf(path) {
const names = parts(path);
const name = names.pop();
const dir = find("/" + names.join("/"));
if (!dir || dir.type !== "dir") throw new Error(`No such directory: /${names.join("/")}`);
return { dir, name };
}
return {
mkdir(path) {
let node = root;
for (const name of parts(path)) {
node.children[name] ??= { type: "dir", children: {} };
node = node.children[name];
if (node.type !== "dir") throw new Error(`Not a directory: ${path}`);
}
},
write(path, text) {
const { dir, name } = parentOf(path);
if (dir.children[name]?.type === "dir") throw new Error(`Is a directory: ${path}`);
dir.children[name] = { type: "file", data: text };
},
read(path) {
const node = find(path);
if (!node || node.type !== "file") throw new Error(`No such file: ${path}`);
return node.data;
},
ls(path = "/") {
const node = find(path);
if (!node || node.type !== "dir") throw new Error(`No such directory: ${path}`);
return Object.keys(node.children).sort().map((n) => (node.children[n].type === "dir" ? n + "/" : n));
},
rm(path) {
const { dir, name } = parentOf(path);
const node = dir.children[name];
if (!node) throw new Error(`No such file: ${path}`);
if (node.type === "dir" && Object.keys(node.children).length) throw new Error(`Directory not empty: ${path}`);
delete dir.children[name];
},
};
}