Skip to content

Commit bc862d8

Browse files
committed
fs/memory-provider: write, read
1 parent 0bf1f5b commit bc862d8

File tree

11 files changed

+263
-25
lines changed

11 files changed

+263
-25
lines changed

src/backend/exports.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ const CoreModule = require("./src/CoreModule.js");
2020
const { Kernel } = require("./src/Kernel.js");
2121
const DatabaseModule = require("./src/DatabaseModule.js");
2222
const LocalDiskStorageModule = require("./src/LocalDiskStorageModule.js");
23+
const MemoryStorageModule = require("./src/MemoryStorageModule.js");
2324
const SelfHostedModule = require("./src/modules/selfhosted/SelfHostedModule.js");
2425
const { testlaunch } = require("./src/index.js");
2526
const BaseService = require("./src/services/BaseService.js");
@@ -71,6 +72,7 @@ module.exports = {
7172
WebModule,
7273
DatabaseModule,
7374
LocalDiskStorageModule,
75+
MemoryStorageModule,
7476
SelfHostedModule,
7577
TestDriversModule,
7678
PuterAIModule,
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/*
2+
* Copyright (C) 2024-present Puter Technologies Inc.
3+
*
4+
* This file is part of Puter.
5+
*
6+
* Puter is free software: you can redistribute it and/or modify
7+
* it under the terms of the GNU Affero General Public License as published
8+
* by the Free Software Foundation, either version 3 of the License, or
9+
* (at your option) any later version.
10+
*
11+
* This program is distributed in the hope that it will be useful,
12+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+
* GNU Affero General Public License for more details.
15+
*
16+
* You should have received a copy of the GNU Affero General Public License
17+
* along with this program. If not, see <https://www.gnu.org/licenses/>.
18+
*/
19+
class MemoryStorageModule {
20+
async install (context) {
21+
const services = context.get('services');
22+
const MemoryStorageService = require("./services/MemoryStorageService");
23+
services.registerService('memory-storage', MemoryStorageService);
24+
}
25+
}
26+
27+
module.exports = MemoryStorageModule;

src/backend/src/filesystem/hl_operations/hl_read.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,10 @@ class HLRead extends HLFilesystemOperation {
3434
version_id,
3535
} = this.values;
3636

37+
if ( fsNode.path.includes('tmp') ) {
38+
console.log('tmp');
39+
}
40+
3741
if ( ! await fsNode.exists() ) {
3842
throw APIError.create('subject_does_not_exist');
3943
}

src/backend/src/filesystem/ll_operations/ll_read.js

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
*/
1919
const APIError = require("../../api/APIError");
2020
const { Sequence } = require("../../codex/Sequence");
21+
const { MemoryFSProvider } = require("../../modules/puterfs/customfs/MemoryFSProvider");
2122

2223
const { DB_WRITE } = require("../../services/database/consts");
2324
const { buffer_to_stream } = require("../../util/streamutil");
@@ -70,6 +71,9 @@ class LLRead extends LLFilesystemOperation {
7071
async function calculate_has_range (a) {
7172
const { offset, length } = a.values();
7273
const fsNode = a.get('fsNode');
74+
if ( fsNode.entry.id === 123 ) {
75+
console.log('has_range', offset, length);
76+
}
7377
const has_range = (
7478
offset !== undefined &&
7579
offset !== 0
@@ -115,10 +119,13 @@ class LLRead extends LLFilesystemOperation {
115119
},
116120
async function create_S3_read_stream (a) {
117121
const context = a.iget('context');
118-
const storage = context.get('storage');
119122

120123
const { fsNode, version_id, offset, length, has_range } = a.values();
121124

125+
const svc_mountpoint = context.get('services').get('mountpoint');
126+
const provider = await svc_mountpoint.get_provider(fsNode.selector);
127+
const storage = svc_mountpoint.get_storage(provider.constructor);
128+
122129
// Empty object here is in the case of local fiesystem,
123130
// where s3:location will return null.
124131
// TODO: storage interface shouldn't have S3-specific properties.
@@ -133,6 +140,8 @@ class LLRead extends LLFilesystemOperation {
133140
...(has_range ? {
134141
range: `bytes=${offset}-${offset+length-1}`
135142
} : {}),
143+
144+
memory_file: fsNode.entry,
136145
}));
137146

138147
a.set('stream', stream);
@@ -144,8 +153,10 @@ class LLRead extends LLFilesystemOperation {
144153
const { fsNode, stream, has_range } = a.values();
145154

146155
if ( ! has_range ) {
147-
const res = await svc_fileCache.maybe_store(fsNode, stream);
148-
if ( res.stream ) a.set('stream', res.stream);
156+
if ( ! (fsNode.provider instanceof MemoryFSProvider) ) {
157+
const res = await svc_fileCache.maybe_store(fsNode, stream);
158+
if ( res.stream ) a.set('stream', res.stream);
159+
}
149160
}
150161
},
151162
async function return_stream (a) {

src/backend/src/modules/puterfs/MountpointService.js

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,9 @@ class MountpointService extends BaseService {
5757
* @returns {Promise<void>}
5858
*/
5959
async _init () {
60-
// Temporary solution - we'll develop this incrementally
61-
this.storage_ = null;
60+
// key: provider class (e.g: PuterFSProvider, MemoryFSProvider)
61+
// value: storage instance
62+
this.storage_ = {};
6263
}
6364

6465
async ['__on_boot.consolidation'] () {
@@ -145,15 +146,16 @@ class MountpointService extends BaseService {
145146
}
146147

147148
// Temporary solution - we'll develop this incrementally
148-
set_storage (storage) {
149-
this.storage_ = storage;
149+
set_storage (provider, storage) {
150+
this.storage_[provider] = storage;
150151
}
152+
151153
/**
152154
* Gets the current storage backend instance
153155
* @returns {Object} The storage backend instance
154156
*/
155-
get_storage () {
156-
return this.storage_;
157+
get_storage (provider) {
158+
return this.storage_[provider];
157159
}
158160
}
159161

src/backend/src/modules/puterfs/customfs/MemoryFSProvider.js

Lines changed: 104 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ const _path = require('path');
2222
const { Context } = require("../../../util/context");
2323
const { v4: uuidv4 } = require('uuid');
2424
const config = require("../../../config");
25-
const { try_infer_attributes, NodeChildSelector, NodePathSelector, NodeSelector } = require("../../../filesystem/node/selectors");
25+
const { try_infer_attributes, NodeChildSelector, NodePathSelector, NodeUIDSelector, NodeSelector } = require("../../../filesystem/node/selectors");
26+
const fsCapabilities = require("../../../filesystem/definitions/capabilities");
2627

2728
const path = require('path');
2829
const APIError = require("../../../api/APIError");
@@ -62,11 +63,10 @@ class MemoryFile {
6263
this.is_shortcut = 0;
6364
this.is_symlink = 0;
6465
this.symlink_path = null;
65-
this.created = 123;
66-
this.accessed = 123;
67-
this.modified = 123;
68-
69-
this.own
66+
this.created = Math.floor(Date.now() / 1000);
67+
this.accessed = Math.floor(Date.now() / 1000);
68+
this.modified = Math.floor(Date.now() / 1000);
69+
this.size = is_dir ? 0 : (content ? content.length : 0);
7070
}
7171
}
7272

@@ -94,6 +94,20 @@ class MemoryFSProvider {
9494
this.entriesByUUID.set(root.uuid, root);
9595
}
9696

97+
/**
98+
* Get the capabilities of this filesystem provider.
99+
*
100+
* @returns {Set} - Set of capabilities supported by this provider.
101+
*/
102+
get_capabilities() {
103+
return new Set([
104+
fsCapabilities.READDIR_UUID_MODE,
105+
fsCapabilities.UUID,
106+
fsCapabilities.READ,
107+
fsCapabilities.WRITE,
108+
]);
109+
}
110+
97111
/**
98112
* Normalize the path to be relative to the mountpoint. Returns `/` if the path is empty/undefined.
99113
*
@@ -119,7 +133,7 @@ class MemoryFSProvider {
119133
/**
120134
* Check the integrity of the whole memory filesystem and the input. Throws error if any violation is found.
121135
*
122-
* @param {MemoryFile} entry - The entry to check.
136+
* @param {MemoryFile|FSNodeContext} entry - The entry to check.
123137
* @returns {Promise<void>}
124138
*/
125139
_integrity_check (entry) {
@@ -130,7 +144,8 @@ class MemoryFSProvider {
130144

131145
if ( entry ) {
132146
// check all directories along the path are valid
133-
const inner_path = this._inner_path(entry.path);
147+
const path_to_check = 'entry' in entry ? entry.entry?.path : entry.path;
148+
const inner_path = this._inner_path(path_to_check);
134149
const path_components = inner_path.split('/');
135150
for ( let i = 2; i < path_components.length; i++ ) {
136151
const path_component = path_components.slice(0, i).join('/');
@@ -211,6 +226,33 @@ class MemoryFSProvider {
211226
return entry;
212227
}
213228

229+
/**
230+
* Read directory contents.
231+
*
232+
* @param {Object} param
233+
* @param {Context} param.context - The context of the operation.
234+
* @param {FSNodeContext} param.node - The directory node to read.
235+
* @returns {Promise<string[]>} - Array of child UUIDs.
236+
*/
237+
async readdir({ context, node }) {
238+
const inner_path = this._inner_path(node.path);
239+
const child_uuids = [];
240+
241+
// Find all entries that are direct children of this directory
242+
for (const [path, uuid] of this.entriesByPath) {
243+
if (path === inner_path) {
244+
continue; // Skip the directory itself
245+
}
246+
247+
const dirname = _path.dirname(path);
248+
if (dirname === inner_path) {
249+
child_uuids.push(uuid);
250+
}
251+
}
252+
253+
return child_uuids;
254+
}
255+
214256
/**
215257
* Create a new directory.
216258
*
@@ -301,7 +343,7 @@ class MemoryFSProvider {
301343
const new_inner_path = this._inner_path(new_full_path);
302344
const entry = new MemoryFile({
303345
full_path: new_full_path,
304-
is_dir: false,
346+
is_dir: node.entry.is_dir,
305347
content: node.entry.content,
306348
});
307349
entry.uuid = node.entry.uuid;
@@ -318,6 +360,49 @@ class MemoryFSProvider {
318360
return entry;
319361
}
320362

363+
/**
364+
* Copy a tree of files and directories.
365+
*
366+
* @param {Object} param
367+
* @param {Context} param.context
368+
* @param {FSNodeContext} param.source - The source node to copy.
369+
* @param {FSNodeContext} param.parent - The parent directory for the copy.
370+
* @param {string} param.target_name - The name for the copied item.
371+
* @returns {Promise<FSNodeContext>} - The copied node.
372+
*/
373+
async copy_tree({ context, source, parent, target_name }) {
374+
const fs = context.get('services').get('filesystem');
375+
376+
if (source.entry.is_dir) {
377+
// Create the directory
378+
const new_dir = await this.mkdir({ context, parent, name: target_name });
379+
380+
// Copy all children
381+
const children = await this.readdir({ context, node: source });
382+
for (const child_uuid of children) {
383+
const child_node = await fs.node(new NodeUIDSelector(child_uuid));
384+
const child_name = child_node.entry.name;
385+
await this.copy_tree({
386+
context,
387+
source: child_node,
388+
parent: new_dir,
389+
target_name: child_name
390+
});
391+
}
392+
393+
return new_dir;
394+
} else {
395+
// Copy the file
396+
const new_file = await this.write_new({
397+
context,
398+
parent,
399+
name: target_name,
400+
file: { stream: { read: () => source.entry.content } }
401+
});
402+
return new_file;
403+
}
404+
}
405+
321406
/**
322407
* Write a new file to the filesystem. Throws an error if the destination
323408
* already exists.
@@ -336,15 +421,14 @@ class MemoryFSProvider {
336421
const entry = new MemoryFile({
337422
full_path: full_path,
338423
is_dir: false,
339-
content: file.stream.readableBuffer,
424+
content: file.stream.read(),
340425
});
341426
this.entriesByPath.set(inner_path, entry.uuid);
342427
this.entriesByUUID.set(entry.uuid, entry);
343428

344429
const fs = context.get('services').get('filesystem');
345430
const node = await fs.node(entry.path);
346-
347-
this._integrity_check(node);
431+
await node.fetchEntry();
348432

349433
return node;
350434
}
@@ -371,12 +455,19 @@ class MemoryFSProvider {
371455
throw new Error(`Cannot overwrite a directory`);
372456
}
373457

374-
original_entry.content = file.stream.readableBuffer;
458+
original_entry.content = file.stream.read();
459+
original_entry.modified = Math.floor(Date.now() / 1000);
460+
original_entry.size = original_entry.content ? original_entry.content.length : 0;
375461
this.entriesByUUID.set(node.uid, original_entry);
376462
}
377463

378464
this._integrity_check(node);
379465

466+
// return node;
467+
const fs = context.get('services').get('filesystem');
468+
node = await fs.node(original_entry.path);
469+
await node.fetchEntry();
470+
380471
return node;
381472
}
382473
}

src/backend/src/modules/puterfs/lib/PuterFSProvider.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -761,7 +761,7 @@ class PuterFSProvider extends putility.AdvancedBase {
761761
const svc_event = svc.get('event');
762762

763763
const svc_mountpoint = svc.get('mountpoint');
764-
const storage = svc_mountpoint.get_storage();
764+
const storage = svc_mountpoint.get_storage(this.constructor);
765765

766766
bucket ??= config.s3_bucket;
767767
bucket_region ??= config.s3_region ?? config.region;

0 commit comments

Comments
 (0)