Skip to content

Commit 74bffda

Browse files
committed
fs/memory-provider: passed all apitests
1 parent 1e720a7 commit 74bffda

31 files changed

+1126
-101
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/api/APIError.js

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
* along with this program. If not, see <https://www.gnu.org/licenses/>.
1818
*/
1919
const { URLSearchParams } = require("node:url");
20+
const config = require("../config");
2021
const { quot } = require('@heyputer/putility').libs.string;
2122

2223
/**
@@ -518,14 +519,19 @@ module.exports = class APIError {
518519
* is set to null. The first argument is used as the status code.
519520
*
520521
* @static
521-
* @param {number} status
522-
* @param {string|Error} message_or_source one of the following:
522+
* @param {number|string} status
523+
* @param {object} source
524+
* @param {string|Error|object} fields one of the following:
523525
* - a string to use as the error message
524526
* - an Error object to use as the source of the error
525527
* - an object with a message property to use as the error message
526528
* @returns
527529
*/
528530
static create (status, source, fields = {}) {
531+
if ( config.env === 'dev' ) {
532+
console.trace('APIError.create', status, source, fields);
533+
}
534+
529535
// Just the error code
530536
if ( typeof status === 'string' ) {
531537
const code = this.codes[status];

src/backend/src/filesystem/FSNodeContext.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -288,7 +288,7 @@ module.exports = class FSNodeContext {
288288
controls,
289289
});
290290

291-
if ( entry === null ) {
291+
if ( ! entry ) {
292292
this.found = false;
293293
this.entry = false;
294294
} else {

src/backend/src/filesystem/hl_operations/hl_copy.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -159,8 +159,8 @@ class HLCopy extends HLFilesystemOperation {
159159
throw APIError.create('source_and_dest_are_the_same');
160160
}
161161

162-
if ( await is_ancestor_of(source.mysql_id, parent.mysql_id) ) {
163-
throw APIError('cannot_copy_item_into_itself');
162+
if ( await is_ancestor_of(source.uid, parent.uid) ) {
163+
throw APIError.create('cannot_copy_item_into_itself');
164164
}
165165

166166
let overwritten;

src/backend/src/filesystem/hl_operations/hl_mkdir.js

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -287,7 +287,7 @@ class HLMkdir extends HLFilesystemOperation {
287287
// "top_parent" is the immediate parent of the target directory
288288
// (e.g: /home/foo/bar -> /home/foo)
289289
const top_parent = values.create_missing_parents
290-
? await this._create_top_parent({ top_parent: parent_node })
290+
? await this._create_dir(parent_node)
291291
: await this._get_existing_top_parent({ top_parent: parent_node })
292292
;
293293

@@ -331,12 +331,14 @@ class HLMkdir extends HLFilesystemOperation {
331331
});
332332
}
333333
else if ( dedupe_name ) {
334-
const fsEntryFetcher = context.get('services').get('fsEntryFetcher');
334+
const fs = context.get('services').get('filesystem');
335+
const parent_selector = parent_node.selector;
335336
for ( let i=1 ;; i++ ) {
336337
let try_new_name = `${target_basename} (${i})`;
337-
const exists = await fsEntryFetcher.nameExistsUnderParent(
338-
existing.entry.parent_uid, try_new_name
339-
);
338+
const selector = new NodeChildSelector(parent_selector, try_new_name);
339+
const exists = await parent_node.provider.quick_check({
340+
selector,
341+
});
340342
if ( ! exists ) {
341343
target_basename = try_new_name;
342344
break;
@@ -468,22 +470,30 @@ class HLMkdir extends HLFilesystemOperation {
468470
return node;
469471
}
470472

471-
async _create_top_parent ({ top_parent }) {
472-
if ( await top_parent.exists() ) {
473-
if ( ! top_parent.entry.is_dir ) {
473+
/**
474+
* Creates a directory and all its ancestors.
475+
*
476+
* @param {FSNodeContext} dir - The directory to create.
477+
* @returns {Promise<FSNodeContext>} The created directory.
478+
*/
479+
async _create_dir (dir) {
480+
console.log('CREATING DIR', dir.selector.describe());
481+
482+
if ( await dir.exists() ) {
483+
if ( ! dir.entry.is_dir ) {
474484
throw APIError.create('dest_is_not_a_directory');
475485
}
476-
return top_parent;
486+
return dir;
477487
}
478488

479489
const maybe_path_selector =
480-
top_parent.get_selector_of_type(NodePathSelector);
490+
dir.get_selector_of_type(NodePathSelector);
481491

482492
if ( ! maybe_path_selector ) {
483493
throw APIError.create('dest_does_not_exist');
484494
}
485495

486-
const path = maybe_path_selector.value;
496+
let path = maybe_path_selector.value;
487497

488498
const fs = this.context.get('services').get('filesystem');
489499

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

Lines changed: 12 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");
@@ -115,10 +116,13 @@ class LLRead extends LLFilesystemOperation {
115116
},
116117
async function create_S3_read_stream (a) {
117118
const context = a.iget('context');
118-
const storage = context.get('storage');
119119

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

122+
const svc_mountpoint = context.get('services').get('mountpoint');
123+
const provider = await svc_mountpoint.get_provider(fsNode.selector);
124+
const storage = svc_mountpoint.get_storage(provider.constructor);
125+
122126
// Empty object here is in the case of local fiesystem,
123127
// where s3:location will return null.
124128
// TODO: storage interface shouldn't have S3-specific properties.
@@ -133,6 +137,8 @@ class LLRead extends LLFilesystemOperation {
133137
...(has_range ? {
134138
range: `bytes=${offset}-${offset+length-1}`
135139
} : {}),
140+
141+
memory_file: fsNode.entry,
136142
}));
137143

138144
a.set('stream', stream);
@@ -144,8 +150,11 @@ class LLRead extends LLFilesystemOperation {
144150
const { fsNode, stream, has_range } = a.values();
145151

146152
if ( ! has_range ) {
147-
const res = await svc_fileCache.maybe_store(fsNode, stream);
148-
if ( res.stream ) a.set('stream', res.stream);
153+
// only cache for non-memoryfs providers
154+
if ( ! (fsNode.provider instanceof MemoryFSProvider) ) {
155+
const res = await svc_fileCache.maybe_store(fsNode, stream);
156+
if ( res.stream ) a.set('stream', res.stream);
157+
}
149158
}
150159
},
151160
async function return_stream (a) {

src/backend/src/filesystem/ll_operations/ll_rmdir.js

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
* along with this program. If not, see <https://www.gnu.org/licenses/>.
1818
*/
1919
const APIError = require("../../api/APIError");
20+
const { MemoryFSProvider } = require("../../modules/puterfs/customfs/MemoryFSProvider");
2021
const { ParallelTasks } = require("../../util/otelutil");
2122
const FSNodeContext = require("../FSNodeContext");
2223
const { NodeUIDSelector } = require("../node/selectors");
@@ -102,14 +103,27 @@ class LLRmDir extends LLFilesystemOperation {
102103
}
103104

104105
await tasks.awaitAll();
105-
if ( ! descendants_only ) {
106-
await target.provider.rmdir({
106+
107+
// TODO (xiaochen): consolidate these two branches
108+
if ( target.provider instanceof MemoryFSProvider ) {
109+
await target.provider.rmdir( {
107110
context,
108111
node: target,
109112
options: {
110-
ignore_not_empty: true,
113+
recursive,
114+
descendants_only,
111115
},
112-
});
116+
} );
117+
} else {
118+
if ( ! descendants_only ) {
119+
await target.provider.rmdir( {
120+
context,
121+
node: target,
122+
options: {
123+
ignore_not_empty: true,
124+
},
125+
} );
126+
}
113127
}
114128
}
115129
}

src/backend/src/filesystem/node/selectors.js

Lines changed: 48 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,14 @@
1919
const _path = require('path');
2020
const { PuterPath } = require('../lib/PuterPath');
2121

22-
class NodePathSelector {
22+
class NodeSelector {
23+
path = null;
24+
uid = null;
25+
}
26+
27+
class NodePathSelector extends NodeSelector {
2328
constructor (path) {
29+
super();
2430
this.value = path;
2531
}
2632

@@ -34,8 +40,9 @@ class NodePathSelector {
3440
}
3541
}
3642

37-
class NodeUIDSelector {
43+
class NodeUIDSelector extends NodeSelector {
3844
constructor (uid) {
45+
super();
3946
this.value = uid;
4047
}
4148

@@ -58,8 +65,9 @@ class NodeUIDSelector {
5865
}
5966
}
6067

61-
class NodeInternalIDSelector {
68+
class NodeInternalIDSelector extends NodeSelector {
6269
constructor (service, id, debugInfo) {
70+
super();
6371
this.service = service;
6472
this.id = id;
6573
this.debugInfo = debugInfo;
@@ -81,23 +89,28 @@ class NodeInternalIDSelector {
8189
}
8290
}
8391

84-
class NodeChildSelector {
92+
class NodeChildSelector extends NodeSelector {
8593
constructor (parent, name) {
94+
super();
8695
this.parent = parent;
8796
this.name = name;
8897
}
8998

9099
setPropertiesKnownBySelector (node) {
91100
node.name = this.name;
92-
// no properties known
101+
102+
try_infer_attributes(this);
103+
if ( this.path ) {
104+
node.path = this.path;
105+
}
93106
}
94107

95108
describe () {
96109
return this.parent.describe() + '/' + this.name;
97110
}
98111
}
99112

100-
class RootNodeSelector {
113+
class RootNodeSelector extends NodeSelector {
101114
static entry = {
102115
is_dir: true,
103116
is_root: true,
@@ -110,6 +123,7 @@ class RootNodeSelector {
110123
node.uid = PuterPath.NULL_UUID;
111124
}
112125
constructor () {
126+
super();
113127
this.entry = this.constructor.entry;
114128
}
115129

@@ -118,8 +132,9 @@ class RootNodeSelector {
118132
}
119133
}
120134

121-
class NodeRawEntrySelector {
135+
class NodeRawEntrySelector extends NodeSelector {
122136
constructor (entry) {
137+
super();
123138
// Fix entries from get_descendants
124139
if ( ! entry.uuid && entry.uid ) {
125140
entry.uuid = entry.uid;
@@ -145,6 +160,30 @@ class NodeRawEntrySelector {
145160
}
146161
}
147162

163+
/**
164+
* Try to infer following attributes for a selector:
165+
* - path
166+
* - uid
167+
*
168+
* @param {NodeSelector} selector
169+
*/
170+
function try_infer_attributes (selector) {
171+
if ( selector instanceof NodePathSelector ) {
172+
selector.path = selector.value;
173+
} else if ( selector instanceof NodeUIDSelector ) {
174+
selector.uid = selector.value;
175+
} else if ( selector instanceof NodeChildSelector ) {
176+
try_infer_attributes(selector.parent);
177+
if ( selector.parent.path ) {
178+
selector.path = _path.join(selector.parent.path, selector.name);
179+
}
180+
} else if ( selector instanceof RootNodeSelector ) {
181+
selector.path = '/';
182+
} else {
183+
// give up
184+
}
185+
}
186+
148187
const relativeSelector = (parent, path) => {
149188
if ( path === '.' ) return parent;
150189
if ( path.startsWith('..') ) {
@@ -162,11 +201,13 @@ const relativeSelector = (parent, path) => {
162201
}
163202

164203
module.exports = {
204+
NodeSelector,
165205
NodePathSelector,
166206
NodeUIDSelector,
167207
NodeInternalIDSelector,
168208
NodeChildSelector,
169209
RootNodeSelector,
170210
NodeRawEntrySelector,
171211
relativeSelector,
212+
try_infer_attributes,
172213
};

0 commit comments

Comments
 (0)