diff --git a/extensions/puterfs/main.js b/extensions/puterfs/main.js
new file mode 100644
index 000000000..ab0b3e708
--- /dev/null
+++ b/extensions/puterfs/main.js
@@ -0,0 +1,605 @@
+/*
+ * Copyright (C) 2024-present Puter Technologies Inc.
+ *
+ * This file is part of Puter.
+ *
+ * Puter is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published
+ * by the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+const _path = require('node:path');
+const uuidv4 = require('uuid').v4;
+
+const { capabilities, selectors } = extension.import('fs');
+const { APIError } = extension.import('core');
+
+const {
+ NodePathSelector,
+ NodeUIDSelector,
+ NodeChildSelector,
+ try_infer_attributes,
+} = selectors;
+
+
+class MemoryFile {
+ /**
+ * @param {Object} param
+ * @param {string} param.path - Relative path from the mountpoint.
+ * @param {boolean} param.is_dir
+ * @param {Buffer|null} param.content - The content of the file, `null` if the file is a directory.
+ * @param {string|null} [param.parent_uid] - UID of parent directory; null for root.
+ */
+ constructor({ path, is_dir, content, parent_uid = null }) {
+ this.uuid = uuidv4();
+
+ this.is_public = true;
+ this.path = path;
+ this.name = _path.basename(path);
+ this.is_dir = is_dir;
+
+ this.content = content;
+
+ // parent_uid should reflect the actual parent's uid; null for root
+ this.parent_uid = parent_uid;
+
+ // TODO (xiaochen): return sensible values for "user_id", currently
+ // it must be 2 (admin) to pass the test.
+ this.user_id = 2;
+
+ // TODO (xiaochen): return sensible values for following fields
+ this.id = 123;
+ this.parent_id = 123;
+ this.immutable = 0;
+ this.is_shortcut = 0;
+ this.is_symlink = 0;
+ this.symlink_path = null;
+ this.created = Math.floor(Date.now() / 1000);
+ this.accessed = Math.floor(Date.now() / 1000);
+ this.modified = Math.floor(Date.now() / 1000);
+ this.size = is_dir ? 0 : content ? content.length : 0;
+ }
+}
+
+class MemoryFSProvider {
+ constructor(mountpoint) {
+ this.mountpoint = mountpoint;
+
+ // key: relative path from the mountpoint, always starts with `/`
+ // value: entry uuid
+ this.entriesByPath = new Map();
+
+ // key: entry uuid
+ // value: entry (MemoryFile)
+ //
+ // We declare 2 maps to support 2 lookup apis: by-path/by-uuid.
+ this.entriesByUUID = new Map();
+
+ const root = new MemoryFile({
+ path: '/',
+ is_dir: true,
+ content: null,
+ parent_uid: null,
+ });
+ this.entriesByPath.set('/', root.uuid);
+ this.entriesByUUID.set(root.uuid, root);
+ }
+
+ /**
+ * Get the capabilities of this filesystem provider.
+ *
+ * @returns {Set} - Set of capabilities supported by this provider.
+ */
+ get_capabilities() {
+ return new Set([
+ capabilities.READDIR_UUID_MODE,
+ capabilities.UUID,
+ capabilities.READ,
+ capabilities.WRITE,
+ capabilities.COPY_TREE,
+ ]);
+ }
+
+ /**
+ * Normalize the path to be relative to the mountpoint. Returns `/` if the path is empty/undefined.
+ *
+ * @param {string} path - The path to normalize.
+ * @returns {string} - The normalized path, always starts with `/`.
+ */
+ _inner_path(path) {
+ if (!path) {
+ return '/';
+ }
+
+ if (path.startsWith(this.mountpoint)) {
+ path = path.slice(this.mountpoint.length);
+ }
+
+ if (!path.startsWith('/')) {
+ path = '/' + path;
+ }
+
+ return path;
+ }
+
+ /**
+ * Check the integrity of the whole memory filesystem. Throws error if any violation is found.
+ *
+ * @returns {Promise}
+ */
+ _integrity_check() {
+ if (config.env !== 'dev') {
+ // only check in debug mode since it's expensive
+ return;
+ }
+
+ // check the 2 maps are consistent
+ if (this.entriesByPath.size !== this.entriesByUUID.size) {
+ throw new Error('Path map and UUID map have different sizes');
+ }
+
+ for (const [inner_path, uuid] of this.entriesByPath) {
+ const entry = this.entriesByUUID.get(uuid);
+
+ // entry should exist
+ if (!entry) {
+ throw new Error(`Entry ${uuid} does not exist`);
+ }
+
+ // path should match
+ if (this._inner_path(entry.path) !== inner_path) {
+ throw new Error(`Path ${inner_path} does not match entry ${uuid}`);
+ }
+
+ // uuid should match
+ if (entry.uuid !== uuid) {
+ throw new Error(`UUID ${uuid} does not match entry ${entry.uuid}`);
+ }
+
+ // parent should exist
+ if (entry.parent_uid) {
+ const parent_entry = this.entriesByUUID.get(entry.parent_uid);
+ if (!parent_entry) {
+ throw new Error(`Parent ${entry.parent_uid} does not exist`);
+ }
+ }
+
+ // parent's path should be a prefix of the entry's path
+ if (entry.parent_uid) {
+ const parent_entry = this.entriesByUUID.get(entry.parent_uid);
+ if (!entry.path.startsWith(parent_entry.path)) {
+ throw new Error(
+ `Parent ${entry.parent_uid} path ${parent_entry.path} is not a prefix of entry ${entry.path}`,
+ );
+ }
+ }
+
+ // parent should be a directory
+ if (entry.parent_uid) {
+ const parent_entry = this.entriesByUUID.get(entry.parent_uid);
+ if (!parent_entry.is_dir) {
+ throw new Error(`Parent ${entry.parent_uid} is not a directory`);
+ }
+ }
+ }
+ }
+
+ /**
+ * Check if a given node exists.
+ *
+ * @param {Object} param
+ * @param {NodePathSelector | NodeUIDSelector | NodeChildSelector | RootNodeSelector | NodeRawEntrySelector} param.selector - The selector used for checking.
+ * @returns {Promise} - True if the node exists, false otherwise.
+ */
+ async quick_check({ selector }) {
+ if (selector instanceof NodePathSelector) {
+ const inner_path = this._inner_path(selector.value);
+ return this.entriesByPath.has(inner_path);
+ }
+
+ if (selector instanceof NodeUIDSelector) {
+ return this.entriesByUUID.has(selector.value);
+ }
+
+ // fallback to stat
+ const entry = await this.stat({ selector });
+ return !!entry;
+ }
+
+ /**
+ * Performs a stat operation using the given selector.
+ *
+ * NB: Some returned fields currently contain placeholder values. And the
+ * `path` of the absolute path from the root.
+ *
+ * @param {Object} param
+ * @param {NodePathSelector | NodeUIDSelector | NodeChildSelector | RootNodeSelector | NodeRawEntrySelector} param.selector - The selector to stat.
+ * @returns {Promise} - The result of the stat operation, or `null` if the node doesn't exist.
+ */
+ async stat({ selector }) {
+ try_infer_attributes(selector);
+
+ let entry_uuid = null;
+
+ if (selector instanceof NodePathSelector) {
+ // stat by path
+ const inner_path = this._inner_path(selector.value);
+ entry_uuid = this.entriesByPath.get(inner_path);
+ } else if (selector instanceof NodeUIDSelector) {
+ // stat by uid
+ entry_uuid = selector.value;
+ } else if (selector instanceof NodeChildSelector) {
+ if (selector.path) {
+ // Shouldn't care about about parent when the "path" is present
+ // since it might have different provider.
+ return await this.stat({
+ selector: new NodePathSelector(selector.path),
+ });
+ } else {
+ // recursively stat the parent and then stat the child
+ const parent_entry = await this.stat({
+ selector: selector.parent,
+ });
+ if (parent_entry) {
+ const full_path = _path.join(parent_entry.path, selector.name);
+ return await this.stat({
+ selector: new NodePathSelector(full_path),
+ });
+ }
+ }
+ } else {
+ // other selectors shouldn't reach here, i.e., it's an internal logic error
+ throw APIError.create('invalid_node');
+ }
+
+ const entry = this.entriesByUUID.get(entry_uuid);
+ if (!entry) {
+ return null;
+ }
+
+ // Return a copied entry with `full_path`, since external code only cares
+ // about full path.
+ const copied_entry = { ...entry };
+ copied_entry.path = _path.join(this.mountpoint, entry.path);
+ return copied_entry;
+ }
+
+ /**
+ * Read directory contents.
+ *
+ * @param {Object} param
+ * @param {Context} param.context - The context of the operation.
+ * @param {FSNodeContext} param.node - The directory node to read.
+ * @returns {Promise} - Array of child UUIDs.
+ */
+ async readdir({ context, node }) {
+ // prerequistes: get required path via stat
+ const entry = await this.stat({ selector: node.selector });
+ if (!entry) {
+ throw APIError.create('invalid_node');
+ }
+
+ const inner_path = this._inner_path(entry.path);
+ const child_uuids = [];
+
+ // Find all entries that are direct children of this directory
+ for (const [path, uuid] of this.entriesByPath) {
+ if (path === inner_path) {
+ continue; // Skip the directory itself
+ }
+
+ const dirname = _path.dirname(path);
+ if (dirname === inner_path) {
+ child_uuids.push(uuid);
+ }
+ }
+
+ return child_uuids;
+ }
+
+ /**
+ * Create a new directory.
+ *
+ * @param {Object} param
+ * @param {Context} param.context - The context of the operation.
+ * @param {FSNodeContext} param.parent - The parent node to create the directory in. Must exist and be a directory.
+ * @param {string} param.name - The name of the new directory.
+ * @returns {Promise} - The new directory node.
+ */
+ async mkdir({ context, parent, name }) {
+ // prerequistes: get required path via stat
+ const parent_entry = await this.stat({ selector: parent.selector });
+ if (!parent_entry) {
+ throw APIError.create('invalid_node');
+ }
+
+ const full_path = _path.join(parent_entry.path, name);
+ const inner_path = this._inner_path(full_path);
+
+ let entry = null;
+ if (this.entriesByPath.has(inner_path)) {
+ throw APIError.create('item_with_same_name_exists', null, {
+ entry_name: full_path,
+ });
+ } else {
+ entry = new MemoryFile({
+ path: inner_path,
+ is_dir: true,
+ content: null,
+ parent_uid: parent_entry.uuid,
+ });
+ this.entriesByPath.set(inner_path, entry.uuid);
+ this.entriesByUUID.set(entry.uuid, entry);
+ }
+
+ // create the node
+ const fs = context.get('services').get('filesystem');
+ const node = await fs.node(new NodeUIDSelector(entry.uuid));
+ await node.fetchEntry();
+
+ this._integrity_check();
+
+ return node;
+ }
+
+ /**
+ * Remove a directory.
+ *
+ * @param {Object} param
+ * @param {Context} param.context
+ * @param {FSNodeContext} param.node: The directory to remove.
+ * @param {Object} param.options: The options for the operation.
+ * @returns {Promise}
+ */
+ async rmdir({ context, node, options = {} }) {
+ this._integrity_check();
+
+ // prerequistes: get required path via stat
+ const entry = await this.stat({ selector: node.selector });
+ if (!entry) {
+ throw APIError.create('invalid_node');
+ }
+
+ const inner_path = this._inner_path(entry.path);
+
+ // for mode: non-recursive
+ if (!options.recursive) {
+ const children = await this.readdir({ context, node });
+ if (children.length > 0) {
+ throw APIError.create('not_empty');
+ }
+ }
+
+ // remove all descendants
+ for (const [other_inner_path, other_entry_uuid] of this.entriesByPath) {
+ if (other_entry_uuid === entry.uuid) {
+ // skip the directory itself
+ continue;
+ }
+
+ if (other_inner_path.startsWith(inner_path)) {
+ this.entriesByPath.delete(other_inner_path);
+ this.entriesByUUID.delete(other_entry_uuid);
+ }
+ }
+
+ // for mode: non-descendants-only
+ if (!options.descendants_only) {
+ // remove the directory itself
+ this.entriesByPath.delete(inner_path);
+ this.entriesByUUID.delete(entry.uuid);
+ }
+
+ this._integrity_check();
+ }
+
+ /**
+ * Remove a file.
+ *
+ * @param {Object} param
+ * @param {Context} param.context
+ * @param {FSNodeContext} param.node: The file to remove.
+ * @returns {Promise}
+ */
+ async unlink({ context, node }) {
+ // prerequistes: get required path via stat
+ const entry = await this.stat({ selector: node.selector });
+ if (!entry) {
+ throw APIError.create('invalid_node');
+ }
+
+ const inner_path = this._inner_path(entry.path);
+ this.entriesByPath.delete(inner_path);
+ this.entriesByUUID.delete(entry.uuid);
+ }
+
+ /**
+ * Move a file.
+ *
+ * @param {Object} param
+ * @param {Context} param.context
+ * @param {FSNodeContext} param.node: The file to move.
+ * @param {FSNodeContext} param.new_parent: The new parent directory of the file.
+ * @param {string} param.new_name: The new name of the file.
+ * @param {Object} param.metadata: The metadata of the file.
+ * @returns {Promise}
+ */
+ async move({ context, node, new_parent, new_name, metadata }) {
+ // prerequistes: get required path via stat
+ const new_parent_entry = await this.stat({ selector: new_parent.selector });
+ if (!new_parent_entry) {
+ throw APIError.create('invalid_node');
+ }
+
+ // create the new entry
+ const new_full_path = _path.join(new_parent_entry.path, new_name);
+ const new_inner_path = this._inner_path(new_full_path);
+ const entry = new MemoryFile({
+ path: new_inner_path,
+ is_dir: node.entry.is_dir,
+ content: node.entry.content,
+ parent_uid: new_parent_entry.uuid,
+ });
+ entry.uuid = node.entry.uuid;
+ this.entriesByPath.set(new_inner_path, entry.uuid);
+ this.entriesByUUID.set(entry.uuid, entry);
+
+ // remove the old entry
+ const inner_path = this._inner_path(node.path);
+ this.entriesByPath.delete(inner_path);
+ // NB: should not delete the entry by uuid because uuid does not change
+ // after the move.
+
+ this._integrity_check();
+
+ return entry;
+ }
+
+ /**
+ * Copy a tree of files and directories.
+ *
+ * @param {Object} param
+ * @param {Context} param.context
+ * @param {FSNodeContext} param.source - The source node to copy.
+ * @param {FSNodeContext} param.parent - The parent directory for the copy.
+ * @param {string} param.target_name - The name for the copied item.
+ * @returns {Promise} - The copied node.
+ */
+ async copy_tree({ context, source, parent, target_name }) {
+ const fs = context.get('services').get('filesystem');
+
+ if (source.entry.is_dir) {
+ // Create the directory
+ const new_dir = await this.mkdir({ context, parent, name: target_name });
+
+ // Copy all children
+ const children = await this.readdir({ context, node: source });
+ for (const child_uuid of children) {
+ const child_node = await fs.node(new NodeUIDSelector(child_uuid));
+ await child_node.fetchEntry();
+ const child_name = child_node.entry.name;
+
+ await this.copy_tree({
+ context,
+ source: child_node,
+ parent: new_dir,
+ target_name: child_name,
+ });
+ }
+
+ return new_dir;
+ } else {
+ // Copy the file
+ const new_file = await this.write_new({
+ context,
+ parent,
+ name: target_name,
+ file: { stream: { read: () => source.entry.content } },
+ });
+ return new_file;
+ }
+ }
+
+ /**
+ * Write a new file to the filesystem. Throws an error if the destination
+ * already exists.
+ *
+ * @param {Object} param
+ * @param {Context} param.context
+ * @param {FSNodeContext} param.parent: The parent directory of the destination directory.
+ * @param {string} param.name: The name of the destination directory.
+ * @param {Object} param.file: The file to write.
+ * @returns {Promise}
+ */
+ async write_new({ context, parent, name, file }) {
+ // prerequistes: get required path via stat
+ const parent_entry = await this.stat({ selector: parent.selector });
+ if (!parent_entry) {
+ throw APIError.create('invalid_node');
+ }
+ const full_path = _path.join(parent_entry.path, name);
+ const inner_path = this._inner_path(full_path);
+
+ let entry = null;
+ if (this.entriesByPath.has(inner_path)) {
+ throw APIError.create('item_with_same_name_exists', null, {
+ entry_name: full_path,
+ });
+ } else {
+ entry = new MemoryFile({
+ path: inner_path,
+ is_dir: false,
+ content: file.stream.read(),
+ parent_uid: parent_entry.uuid,
+ });
+ this.entriesByPath.set(inner_path, entry.uuid);
+ this.entriesByUUID.set(entry.uuid, entry);
+ }
+
+ const fs = context.get('services').get('filesystem');
+ const node = await fs.node(new NodeUIDSelector(entry.uuid));
+ await node.fetchEntry();
+
+ this._integrity_check();
+
+ return node;
+ }
+
+ /**
+ * Overwrite an existing file. Throws an error if the destination does not
+ * exist.
+ *
+ * @param {Object} param
+ * @param {Context} param.context
+ * @param {FSNodeContext} param.node: The node to write to.
+ * @param {Object} param.file: The file to write.
+ * @returns {Promise}
+ */
+ async write_overwrite({ context, node, file }) {
+ const entry = await this.stat({ selector: node.selector });
+ if (!entry) {
+ throw APIError.create('invalid_node');
+ }
+ const inner_path = this._inner_path(entry.path);
+
+ this.entriesByPath.set(inner_path, entry.uuid);
+ let original_entry = this.entriesByUUID.get(entry.uuid);
+ if (!original_entry) {
+ throw new Error(`File ${entry.path} does not exist`);
+ } else {
+ if (original_entry.is_dir) {
+ throw new Error(`Cannot overwrite a directory`);
+ }
+
+ original_entry.content = file.stream.read();
+ original_entry.modified = Math.floor(Date.now() / 1000);
+ original_entry.size = original_entry.content ? original_entry.content.length : 0;
+ this.entriesByUUID.set(entry.uuid, original_entry);
+ }
+
+ const fs = context.get('services').get('filesystem');
+ node = await fs.node(new NodeUIDSelector(original_entry.uuid));
+ await node.fetchEntry();
+
+ this._integrity_check();
+
+ return node;
+ }
+}
+
+extension.on('create.filesystem-types', event => {
+ event.createFilesystemType('testfs', {
+ mount ({ path }) {
+ return new MemoryFSProvider(path);
+ }
+ });
+});
diff --git a/extensions/puterfs/package.json b/extensions/puterfs/package.json
new file mode 100644
index 000000000..46406097a
--- /dev/null
+++ b/extensions/puterfs/package.json
@@ -0,0 +1,6 @@
+{
+ "main": "main.js",
+ "dependencies": {
+ "uuid": "^13.0.0"
+ }
+}