@zenfs/core 1.2.9 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/backends/fetch.js +1 -1
- package/dist/backends/memory.d.ts +1 -2
- package/dist/backends/overlay.js +2 -3
- package/dist/backends/port/fs.d.ts +2 -15
- package/dist/backends/store/fs.d.ts +17 -28
- package/dist/backends/store/fs.js +169 -191
- package/dist/backends/store/simple.d.ts +20 -21
- package/dist/backends/store/simple.js +24 -24
- package/dist/backends/store/store.d.ts +22 -23
- package/dist/backends/store/store.js +6 -6
- package/dist/config.d.ts +8 -0
- package/dist/config.js +2 -1
- package/dist/devices.d.ts +2 -3
- package/dist/emulation/cache.d.ts +40 -31
- package/dist/emulation/cache.js +62 -53
- package/dist/emulation/index.d.ts +1 -1
- package/dist/emulation/index.js +1 -1
- package/dist/emulation/promises.js +18 -17
- package/dist/emulation/shared.d.ts +6 -0
- package/dist/emulation/shared.js +13 -1
- package/dist/emulation/sync.d.ts +1 -1
- package/dist/emulation/sync.js +16 -15
- package/dist/file.d.ts +3 -15
- package/dist/file.js +7 -19
- package/dist/inode.d.ts +4 -13
- package/dist/inode.js +22 -29
- package/dist/mixins/async.d.ts +15 -10
- package/dist/mixins/async.js +3 -1
- package/dist/stats.js +30 -5
- package/dist/utils.d.ts +5 -7
- package/dist/utils.js +11 -20
- package/package.json +1 -1
- package/src/backends/fetch.ts +1 -1
- package/src/backends/memory.ts +1 -2
- package/src/backends/overlay.ts +2 -2
- package/src/backends/store/fs.ts +187 -220
- package/src/backends/store/simple.ts +36 -37
- package/src/backends/store/store.ts +25 -26
- package/src/config.ts +11 -1
- package/src/devices.ts +2 -3
- package/src/emulation/cache.ts +68 -60
- package/src/emulation/index.ts +1 -1
- package/src/emulation/promises.ts +20 -19
- package/src/emulation/shared.ts +13 -1
- package/src/emulation/sync.ts +16 -15
- package/src/file.ts +9 -21
- package/src/inode.ts +10 -31
- package/src/mixins/async.ts +27 -24
- package/src/stats.ts +47 -5
- package/src/utils.ts +11 -23
- package/tests/fs/dir.test.ts +21 -31
- package/tests/fs/directory.test.ts +6 -4
- package/tests/fs/links.test.ts +9 -2
- package/tests/fs/permissions.test.ts +2 -2
- package/tests/fs/stat.test.ts +42 -0
- package/tests/fs/times.test.ts +28 -28
- package/tests/setup/cow+fetch.ts +4 -2
package/src/backends/store/fs.ts
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
import { credentials } from '../../credentials.js';
|
|
2
|
-
import { S_IFDIR, S_IFREG } from '../../emulation/constants.js';
|
|
3
|
-
import { basename, dirname,
|
|
2
|
+
import { S_IFDIR, S_IFREG, S_ISGID, S_ISUID } from '../../emulation/constants.js';
|
|
3
|
+
import { basename, dirname, parse, resolve } from '../../emulation/path.js';
|
|
4
4
|
import { Errno, ErrnoError } from '../../error.js';
|
|
5
|
+
import type { File } from '../../file.js';
|
|
5
6
|
import { PreloadFile } from '../../file.js';
|
|
6
7
|
import { FileSystem, type FileSystemMetadata } from '../../filesystem.js';
|
|
7
|
-
import {
|
|
8
|
+
import { Inode, rootIno } from '../../inode.js';
|
|
8
9
|
import type { FileType, Stats } from '../../stats.js';
|
|
9
|
-
import { decodeDirListing, encodeUTF8,
|
|
10
|
+
import { decodeDirListing, encodeDirListing, encodeUTF8, randomBigInt } from '../../utils.js';
|
|
10
11
|
import type { Store, Transaction } from './store.js';
|
|
11
|
-
import
|
|
12
|
+
import { serialize } from 'utilium';
|
|
12
13
|
|
|
13
14
|
const maxInodeAllocTries = 5;
|
|
14
15
|
|
|
@@ -67,182 +68,174 @@ export class StoreFS<T extends Store = Store> extends FileSystem {
|
|
|
67
68
|
*/
|
|
68
69
|
public async rename(oldPath: string, newPath: string): Promise<void> {
|
|
69
70
|
await using tx = this.store.transaction();
|
|
70
|
-
const
|
|
71
|
-
|
|
72
|
-
newParent = dirname(newPath),
|
|
73
|
-
newName = basename(newPath),
|
|
71
|
+
const _old = parse(oldPath),
|
|
72
|
+
_new = parse(newPath),
|
|
74
73
|
// Remove oldPath from parent's directory listing.
|
|
75
|
-
oldDirNode = await this.
|
|
76
|
-
oldDirList = await this.
|
|
74
|
+
oldDirNode = await this.findInode(tx, _old.dir, 'rename'),
|
|
75
|
+
oldDirList = decodeDirListing(await this.get(tx, oldDirNode.data, _old.dir, 'rename'));
|
|
77
76
|
|
|
78
|
-
if (!oldDirList[
|
|
77
|
+
if (!oldDirList[_old.base]) {
|
|
79
78
|
throw ErrnoError.With('ENOENT', oldPath, 'rename');
|
|
80
79
|
}
|
|
81
|
-
const
|
|
82
|
-
delete oldDirList[
|
|
80
|
+
const ino: bigint = oldDirList[_old.base];
|
|
81
|
+
delete oldDirList[_old.base];
|
|
83
82
|
|
|
84
83
|
/*
|
|
85
84
|
Can't move a folder inside itself.
|
|
86
|
-
This ensures that the check passes only if `oldPath` is a subpath of `
|
|
85
|
+
This ensures that the check passes only if `oldPath` is a subpath of `_new.dir`.
|
|
87
86
|
We append '/' to avoid matching folders that are a substring of the bottom-most folder in the path.
|
|
88
87
|
*/
|
|
89
|
-
if ((
|
|
90
|
-
throw new ErrnoError(Errno.EBUSY,
|
|
88
|
+
if ((_new.dir + '/').indexOf(oldPath + '/') === 0) {
|
|
89
|
+
throw new ErrnoError(Errno.EBUSY, _old.dir);
|
|
91
90
|
}
|
|
92
91
|
|
|
93
92
|
// Add newPath to parent's directory listing.
|
|
94
93
|
|
|
95
|
-
const sameParent =
|
|
94
|
+
const sameParent = _new.dir == _old.dir;
|
|
96
95
|
|
|
97
|
-
// Prevent us from re-grabbing the same directory listing, which still contains `
|
|
98
|
-
const newDirNode: Inode = sameParent ? oldDirNode : await this.
|
|
99
|
-
const newDirList: typeof oldDirList = sameParent ? oldDirList : await this.
|
|
96
|
+
// Prevent us from re-grabbing the same directory listing, which still contains `old_path.base.`
|
|
97
|
+
const newDirNode: Inode = sameParent ? oldDirNode : await this.findInode(tx, _new.dir, 'rename');
|
|
98
|
+
const newDirList: typeof oldDirList = sameParent ? oldDirList : decodeDirListing(await this.get(tx, newDirNode.data, _new.dir, 'rename'));
|
|
100
99
|
|
|
101
|
-
if (newDirList[
|
|
100
|
+
if (newDirList[_new.base]) {
|
|
102
101
|
// If it's a file, delete it, if it's a directory, throw a permissions error.
|
|
103
|
-
const
|
|
104
|
-
if (!
|
|
102
|
+
const existing = new Inode(await this.get(tx, newDirList[_new.base], newPath, 'rename'));
|
|
103
|
+
if (!existing.toStats().isFile()) {
|
|
105
104
|
throw ErrnoError.With('EPERM', newPath, 'rename');
|
|
106
105
|
}
|
|
107
|
-
await tx.remove(
|
|
108
|
-
await tx.remove(newDirList[
|
|
106
|
+
await tx.remove(existing.data);
|
|
107
|
+
await tx.remove(newDirList[_new.base]);
|
|
109
108
|
}
|
|
110
|
-
newDirList[
|
|
109
|
+
newDirList[_new.base] = ino;
|
|
111
110
|
// Commit the two changed directory listings.
|
|
112
|
-
await tx.set(oldDirNode.
|
|
113
|
-
await tx.set(newDirNode.
|
|
111
|
+
await tx.set(oldDirNode.data, encodeDirListing(oldDirList));
|
|
112
|
+
await tx.set(newDirNode.data, encodeDirListing(newDirList));
|
|
114
113
|
await tx.commit();
|
|
115
114
|
}
|
|
116
115
|
|
|
117
116
|
public renameSync(oldPath: string, newPath: string): void {
|
|
118
117
|
using tx = this.store.transaction();
|
|
119
|
-
const
|
|
120
|
-
|
|
121
|
-
newParent = dirname(newPath),
|
|
122
|
-
newName = basename(newPath),
|
|
118
|
+
const _old = parse(oldPath),
|
|
119
|
+
_new = parse(newPath),
|
|
123
120
|
// Remove oldPath from parent's directory listing.
|
|
124
|
-
oldDirNode = this.
|
|
125
|
-
oldDirList = this.
|
|
121
|
+
oldDirNode = this.findInodeSync(tx, _old.dir, 'rename'),
|
|
122
|
+
oldDirList = decodeDirListing(this.getSync(tx, oldDirNode.data, _old.dir, 'rename'));
|
|
126
123
|
|
|
127
|
-
if (!oldDirList[
|
|
124
|
+
if (!oldDirList[_old.base]) {
|
|
128
125
|
throw ErrnoError.With('ENOENT', oldPath, 'rename');
|
|
129
126
|
}
|
|
130
|
-
const ino:
|
|
131
|
-
delete oldDirList[
|
|
127
|
+
const ino: bigint = oldDirList[_old.base];
|
|
128
|
+
delete oldDirList[_old.base];
|
|
132
129
|
|
|
133
130
|
/*
|
|
134
131
|
Can't move a folder inside itself.
|
|
135
|
-
This ensures that the check passes only if `oldPath` is a subpath of `
|
|
132
|
+
This ensures that the check passes only if `oldPath` is a subpath of `_new.dir`.
|
|
136
133
|
We append '/' to avoid matching folders that are a substring of the bottom-most folder in the path.
|
|
137
134
|
*/
|
|
138
|
-
if ((
|
|
139
|
-
throw new ErrnoError(Errno.EBUSY,
|
|
135
|
+
if ((_new.dir + '/').indexOf(oldPath + '/') == 0) {
|
|
136
|
+
throw new ErrnoError(Errno.EBUSY, _old.dir);
|
|
140
137
|
}
|
|
141
138
|
|
|
142
139
|
// Add newPath to parent's directory listing.
|
|
143
|
-
const sameParent =
|
|
140
|
+
const sameParent = _new.dir === _old.dir;
|
|
144
141
|
|
|
145
|
-
// Prevent us from re-grabbing the same directory listing, which still contains `
|
|
146
|
-
const newDirNode: Inode = sameParent ? oldDirNode : this.
|
|
147
|
-
const newDirList: typeof oldDirList = sameParent ? oldDirList : this.
|
|
142
|
+
// Prevent us from re-grabbing the same directory listing, which still contains `old_path.base.`
|
|
143
|
+
const newDirNode: Inode = sameParent ? oldDirNode : this.findInodeSync(tx, _new.dir, 'rename');
|
|
144
|
+
const newDirList: typeof oldDirList = sameParent ? oldDirList : decodeDirListing(this.getSync(tx, newDirNode.data, _new.dir, 'rename'));
|
|
148
145
|
|
|
149
|
-
if (newDirList[
|
|
146
|
+
if (newDirList[_new.base]) {
|
|
150
147
|
// If it's a file, delete it, if it's a directory, throw a permissions error.
|
|
151
|
-
const
|
|
152
|
-
if (!
|
|
148
|
+
const existing = new Inode(this.getSync(tx, newDirList[_new.base], newPath, 'rename'));
|
|
149
|
+
if (!existing.toStats().isFile()) {
|
|
153
150
|
throw ErrnoError.With('EPERM', newPath, 'rename');
|
|
154
151
|
}
|
|
155
|
-
tx.removeSync(
|
|
156
|
-
tx.removeSync(newDirList[
|
|
152
|
+
tx.removeSync(existing.data);
|
|
153
|
+
tx.removeSync(newDirList[_new.base]);
|
|
157
154
|
}
|
|
158
|
-
newDirList[
|
|
155
|
+
newDirList[_new.base] = ino;
|
|
159
156
|
|
|
160
157
|
// Commit the two changed directory listings.
|
|
161
|
-
tx.setSync(oldDirNode.
|
|
162
|
-
tx.setSync(newDirNode.
|
|
158
|
+
tx.setSync(oldDirNode.data, encodeDirListing(oldDirList));
|
|
159
|
+
tx.setSync(newDirNode.data, encodeDirListing(newDirList));
|
|
163
160
|
tx.commitSync();
|
|
164
161
|
}
|
|
165
162
|
|
|
166
163
|
public async stat(path: string): Promise<Stats> {
|
|
167
164
|
await using tx = this.store.transaction();
|
|
168
|
-
return (await this.
|
|
165
|
+
return (await this.findInode(tx, path, 'stat')).toStats();
|
|
169
166
|
}
|
|
170
167
|
|
|
171
168
|
public statSync(path: string): Stats {
|
|
172
169
|
using tx = this.store.transaction();
|
|
173
|
-
return this.
|
|
170
|
+
return this.findInodeSync(tx, path, 'stat').toStats();
|
|
174
171
|
}
|
|
175
172
|
|
|
176
173
|
public async createFile(path: string, flag: string, mode: number): Promise<File> {
|
|
177
|
-
const node = await this.commitNew(path, S_IFREG, mode, new Uint8Array(
|
|
178
|
-
return new PreloadFile(this, path, flag, node.toStats(), new Uint8Array(
|
|
174
|
+
const node = await this.commitNew(path, S_IFREG, mode, new Uint8Array(), 'createFile');
|
|
175
|
+
return new PreloadFile(this, path, flag, node.toStats(), new Uint8Array());
|
|
179
176
|
}
|
|
180
177
|
|
|
181
178
|
public createFileSync(path: string, flag: string, mode: number): File {
|
|
182
|
-
this.commitNewSync(path, S_IFREG, mode);
|
|
183
|
-
return this
|
|
179
|
+
const node = this.commitNewSync(path, S_IFREG, mode, new Uint8Array(), 'createFile');
|
|
180
|
+
return new PreloadFile(this, path, flag, node.toStats(), new Uint8Array());
|
|
184
181
|
}
|
|
185
182
|
|
|
186
183
|
public async openFile(path: string, flag: string): Promise<File> {
|
|
187
184
|
await using tx = this.store.transaction();
|
|
188
|
-
const node = await this.
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
throw ErrnoError.With('ENOENT', path, 'openFile');
|
|
192
|
-
}
|
|
185
|
+
const node = await this.findInode(tx, path, 'openFile');
|
|
186
|
+
const data = await this.get(tx, node.data, path, 'openFile');
|
|
187
|
+
|
|
193
188
|
return new PreloadFile(this, path, flag, node.toStats(), data);
|
|
194
189
|
}
|
|
195
190
|
|
|
196
191
|
public openFileSync(path: string, flag: string): File {
|
|
197
192
|
using tx = this.store.transaction();
|
|
198
|
-
const node = this.
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
throw ErrnoError.With('ENOENT', path, 'openFile');
|
|
202
|
-
}
|
|
193
|
+
const node = this.findInodeSync(tx, path, 'openFile');
|
|
194
|
+
const data = this.getSync(tx, node.data, path, 'openFile');
|
|
195
|
+
|
|
203
196
|
return new PreloadFile(this, path, flag, node.toStats(), data);
|
|
204
197
|
}
|
|
205
198
|
|
|
206
199
|
public async unlink(path: string): Promise<void> {
|
|
207
|
-
return this.remove(path, false);
|
|
200
|
+
return this.remove(path, false, 'unlink');
|
|
208
201
|
}
|
|
209
202
|
|
|
210
203
|
public unlinkSync(path: string): void {
|
|
211
|
-
this.removeSync(path, false);
|
|
204
|
+
this.removeSync(path, false, 'unlink');
|
|
212
205
|
}
|
|
213
206
|
|
|
214
207
|
public async rmdir(path: string): Promise<void> {
|
|
215
208
|
if ((await this.readdir(path)).length) {
|
|
216
209
|
throw ErrnoError.With('ENOTEMPTY', path, 'rmdir');
|
|
217
210
|
}
|
|
218
|
-
await this.remove(path, true);
|
|
211
|
+
await this.remove(path, true, 'rmdir');
|
|
219
212
|
}
|
|
220
213
|
|
|
221
214
|
public rmdirSync(path: string): void {
|
|
222
215
|
if (this.readdirSync(path).length) {
|
|
223
216
|
throw ErrnoError.With('ENOTEMPTY', path, 'rmdir');
|
|
224
217
|
}
|
|
225
|
-
this.removeSync(path, true);
|
|
218
|
+
this.removeSync(path, true, 'rmdir');
|
|
226
219
|
}
|
|
227
220
|
|
|
228
221
|
public async mkdir(path: string, mode: number): Promise<void> {
|
|
229
|
-
await this.commitNew(path, S_IFDIR, mode, encodeUTF8('{}'));
|
|
222
|
+
await this.commitNew(path, S_IFDIR, mode, encodeUTF8('{}'), 'mkdir');
|
|
230
223
|
}
|
|
231
224
|
|
|
232
225
|
public mkdirSync(path: string, mode: number): void {
|
|
233
|
-
this.commitNewSync(path, S_IFDIR, mode, encodeUTF8('{}'));
|
|
226
|
+
this.commitNewSync(path, S_IFDIR, mode, encodeUTF8('{}'), 'mkdir');
|
|
234
227
|
}
|
|
235
228
|
|
|
236
229
|
public async readdir(path: string): Promise<string[]> {
|
|
237
230
|
await using tx = this.store.transaction();
|
|
238
|
-
const node = await this.
|
|
239
|
-
return Object.keys(await this.
|
|
231
|
+
const node = await this.findInode(tx, path, 'readdir');
|
|
232
|
+
return Object.keys(decodeDirListing(await this.get(tx, node.data, path, 'readdir')));
|
|
240
233
|
}
|
|
241
234
|
|
|
242
235
|
public readdirSync(path: string): string[] {
|
|
243
236
|
using tx = this.store.transaction();
|
|
244
|
-
const node = this.
|
|
245
|
-
return Object.keys(this.
|
|
237
|
+
const node = this.findInodeSync(tx, path, 'readdir');
|
|
238
|
+
return Object.keys(decodeDirListing(this.getSync(tx, node.data, path, 'readdir')));
|
|
246
239
|
}
|
|
247
240
|
|
|
248
241
|
/**
|
|
@@ -252,15 +245,15 @@ export class StoreFS<T extends Store = Store> extends FileSystem {
|
|
|
252
245
|
public async sync(path: string, data: Uint8Array, stats: Readonly<Stats>): Promise<void> {
|
|
253
246
|
await using tx = this.store.transaction();
|
|
254
247
|
// We use _findInode because we actually need the INode id.
|
|
255
|
-
const fileInodeId = await this.
|
|
256
|
-
fileInode = await this.
|
|
248
|
+
const fileInodeId = await this._findInode(tx, path, 'sync'),
|
|
249
|
+
fileInode = new Inode(await this.get(tx, fileInodeId, path, 'sync')),
|
|
257
250
|
inodeChanged = fileInode.update(stats);
|
|
258
251
|
|
|
259
252
|
// Sync data.
|
|
260
|
-
await tx.set(fileInode.
|
|
253
|
+
await tx.set(fileInode.data, data);
|
|
261
254
|
// Sync metadata.
|
|
262
255
|
if (inodeChanged) {
|
|
263
|
-
await tx.set(fileInodeId, fileInode
|
|
256
|
+
await tx.set(fileInodeId, serialize(fileInode));
|
|
264
257
|
}
|
|
265
258
|
|
|
266
259
|
await tx.commit();
|
|
@@ -273,15 +266,15 @@ export class StoreFS<T extends Store = Store> extends FileSystem {
|
|
|
273
266
|
public syncSync(path: string, data: Uint8Array, stats: Readonly<Stats>): void {
|
|
274
267
|
using tx = this.store.transaction();
|
|
275
268
|
// We use _findInode because we actually need the INode id.
|
|
276
|
-
const fileInodeId = this.
|
|
277
|
-
fileInode = this.
|
|
269
|
+
const fileInodeId = this._findInodeSync(tx, path, 'sync'),
|
|
270
|
+
fileInode = new Inode(this.getSync(tx, fileInodeId, path, 'sync')),
|
|
278
271
|
inodeChanged = fileInode.update(stats);
|
|
279
272
|
|
|
280
273
|
// Sync data.
|
|
281
|
-
tx.setSync(fileInode.
|
|
274
|
+
tx.setSync(fileInode.data, data);
|
|
282
275
|
// Sync metadata.
|
|
283
276
|
if (inodeChanged) {
|
|
284
|
-
tx.setSync(fileInodeId, fileInode
|
|
277
|
+
tx.setSync(fileInodeId, serialize(fileInode));
|
|
285
278
|
}
|
|
286
279
|
|
|
287
280
|
tx.commitSync();
|
|
@@ -291,17 +284,17 @@ export class StoreFS<T extends Store = Store> extends FileSystem {
|
|
|
291
284
|
await using tx = this.store.transaction();
|
|
292
285
|
|
|
293
286
|
const newDir: string = dirname(link),
|
|
294
|
-
newDirNode = await this.
|
|
295
|
-
listing = await this.
|
|
287
|
+
newDirNode = await this.findInode(tx, newDir, 'link'),
|
|
288
|
+
listing = decodeDirListing(await this.get(tx, newDirNode.data, newDir, 'link'));
|
|
296
289
|
|
|
297
|
-
const ino = await this.
|
|
298
|
-
const node = await this.
|
|
290
|
+
const ino = await this._findInode(tx, target, 'link');
|
|
291
|
+
const node = new Inode(await this.get(tx, ino, target, 'link'));
|
|
299
292
|
|
|
300
293
|
node.nlink++;
|
|
301
294
|
listing[basename(link)] = ino;
|
|
302
295
|
|
|
303
|
-
tx.setSync(ino, node
|
|
304
|
-
tx.setSync(newDirNode.
|
|
296
|
+
tx.setSync(ino, serialize(node));
|
|
297
|
+
tx.setSync(newDirNode.data, encodeDirListing(listing));
|
|
305
298
|
tx.commitSync();
|
|
306
299
|
}
|
|
307
300
|
|
|
@@ -309,17 +302,17 @@ export class StoreFS<T extends Store = Store> extends FileSystem {
|
|
|
309
302
|
using tx = this.store.transaction();
|
|
310
303
|
|
|
311
304
|
const newDir: string = dirname(link),
|
|
312
|
-
newDirNode = this.
|
|
313
|
-
listing = this.
|
|
305
|
+
newDirNode = this.findInodeSync(tx, newDir, 'link'),
|
|
306
|
+
listing = decodeDirListing(this.getSync(tx, newDirNode.data, newDir, 'link'));
|
|
314
307
|
|
|
315
|
-
const ino = this.
|
|
316
|
-
const node = this.
|
|
308
|
+
const ino = this._findInodeSync(tx, target, 'link');
|
|
309
|
+
const node = new Inode(this.getSync(tx, ino, target, 'link'));
|
|
317
310
|
|
|
318
311
|
node.nlink++;
|
|
319
312
|
listing[basename(link)] = ino;
|
|
320
313
|
|
|
321
|
-
tx.setSync(ino, node
|
|
322
|
-
tx.setSync(newDirNode.
|
|
314
|
+
tx.setSync(ino, serialize(node));
|
|
315
|
+
tx.setSync(newDirNode.data, encodeDirListing(listing));
|
|
323
316
|
tx.commitSync();
|
|
324
317
|
}
|
|
325
318
|
|
|
@@ -333,10 +326,11 @@ export class StoreFS<T extends Store = Store> extends FileSystem {
|
|
|
333
326
|
}
|
|
334
327
|
// Create new inode. o777, owned by root:root
|
|
335
328
|
const inode = new Inode();
|
|
329
|
+
inode.ino = rootIno;
|
|
336
330
|
inode.mode = 0o777 | S_IFDIR;
|
|
337
331
|
// If the root doesn't exist, the first random ID shouldn't exist either.
|
|
338
|
-
await tx.set(inode.
|
|
339
|
-
await tx.set(rootIno, inode
|
|
332
|
+
await tx.set(inode.data, encodeUTF8('{}'));
|
|
333
|
+
await tx.set(rootIno, serialize(inode));
|
|
340
334
|
await tx.commit();
|
|
341
335
|
}
|
|
342
336
|
|
|
@@ -350,10 +344,11 @@ export class StoreFS<T extends Store = Store> extends FileSystem {
|
|
|
350
344
|
}
|
|
351
345
|
// Create new inode, mode o777, owned by root:root
|
|
352
346
|
const inode = new Inode();
|
|
347
|
+
inode.ino = rootIno;
|
|
353
348
|
inode.mode = 0o777 | S_IFDIR;
|
|
354
349
|
// If the root doesn't exist, the first random ID shouldn't exist either.
|
|
355
|
-
tx.setSync(inode.
|
|
356
|
-
tx.setSync(rootIno, inode
|
|
350
|
+
tx.setSync(inode.data, encodeUTF8('{}'));
|
|
351
|
+
tx.setSync(rootIno, serialize(inode));
|
|
357
352
|
tx.commitSync();
|
|
358
353
|
}
|
|
359
354
|
|
|
@@ -363,23 +358,23 @@ export class StoreFS<T extends Store = Store> extends FileSystem {
|
|
|
363
358
|
* @param filename The filename of the inode we are attempting to find, minus
|
|
364
359
|
* the parent.
|
|
365
360
|
*/
|
|
366
|
-
private async
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
throw new ErrnoError(Errno.EIO, 'Infinite loop detected while finding inode', currentPath);
|
|
361
|
+
private async _findInode(tx: Transaction, path: string, syscall: string, visited: Set<string> = new Set()): Promise<bigint> {
|
|
362
|
+
if (visited.has(path)) {
|
|
363
|
+
throw new ErrnoError(Errno.EIO, 'Infinite loop detected while finding inode', path);
|
|
370
364
|
}
|
|
371
365
|
|
|
372
|
-
visited.add(
|
|
366
|
+
visited.add(path);
|
|
373
367
|
|
|
374
|
-
if (
|
|
368
|
+
if (path == '/') {
|
|
375
369
|
return rootIno;
|
|
376
370
|
}
|
|
377
371
|
|
|
378
|
-
const
|
|
379
|
-
const
|
|
372
|
+
const { dir: parent, base: filename } = parse(path);
|
|
373
|
+
const inode = parent == '/' ? new Inode(await this.get(tx, rootIno, parent, syscall)) : await this.findInode(tx, parent, syscall, visited);
|
|
374
|
+
const dirList = decodeDirListing(await this.get(tx, inode.data, parent, syscall));
|
|
380
375
|
|
|
381
376
|
if (!(filename in dirList)) {
|
|
382
|
-
throw ErrnoError.With('ENOENT', resolve(parent, filename),
|
|
377
|
+
throw ErrnoError.With('ENOENT', resolve(parent, filename), syscall);
|
|
383
378
|
}
|
|
384
379
|
|
|
385
380
|
return dirList[filename];
|
|
@@ -392,23 +387,23 @@ export class StoreFS<T extends Store = Store> extends FileSystem {
|
|
|
392
387
|
* the parent.
|
|
393
388
|
* @return string The ID of the file's inode in the file system.
|
|
394
389
|
*/
|
|
395
|
-
protected
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
throw new ErrnoError(Errno.EIO, 'Infinite loop detected while finding inode', currentPath);
|
|
390
|
+
protected _findInodeSync(tx: Transaction, path: string, syscall: string, visited: Set<string> = new Set()): bigint {
|
|
391
|
+
if (visited.has(path)) {
|
|
392
|
+
throw new ErrnoError(Errno.EIO, 'Infinite loop detected while finding inode', path);
|
|
399
393
|
}
|
|
400
394
|
|
|
401
|
-
visited.add(
|
|
395
|
+
visited.add(path);
|
|
402
396
|
|
|
403
|
-
if (
|
|
397
|
+
if (path == '/') {
|
|
404
398
|
return rootIno;
|
|
405
399
|
}
|
|
406
400
|
|
|
407
|
-
const
|
|
408
|
-
const
|
|
401
|
+
const { dir: parent, base: filename } = parse(path);
|
|
402
|
+
const inode = parent == '/' ? new Inode(this.getSync(tx, rootIno, parent, syscall)) : this.findInodeSync(tx, parent, syscall, visited);
|
|
403
|
+
const dir = decodeDirListing(this.getSync(tx, inode.data, parent, syscall));
|
|
409
404
|
|
|
410
405
|
if (!(filename in dir)) {
|
|
411
|
-
throw ErrnoError.With('ENOENT', resolve(parent, filename),
|
|
406
|
+
throw ErrnoError.With('ENOENT', resolve(parent, filename), syscall);
|
|
412
407
|
}
|
|
413
408
|
|
|
414
409
|
return dir[filename];
|
|
@@ -419,9 +414,9 @@ export class StoreFS<T extends Store = Store> extends FileSystem {
|
|
|
419
414
|
* @param path The path to look up.
|
|
420
415
|
* @todo memoize/cache
|
|
421
416
|
*/
|
|
422
|
-
private async
|
|
423
|
-
const
|
|
424
|
-
return this.
|
|
417
|
+
private async findInode(tx: Transaction, path: string, syscall: string, visited: Set<string> = new Set()): Promise<Inode> {
|
|
418
|
+
const ino = await this._findInode(tx, path, syscall, visited);
|
|
419
|
+
return new Inode(await this.get(tx, ino, path, syscall));
|
|
425
420
|
}
|
|
426
421
|
|
|
427
422
|
/**
|
|
@@ -430,99 +425,68 @@ export class StoreFS<T extends Store = Store> extends FileSystem {
|
|
|
430
425
|
* @return The Inode of the path p.
|
|
431
426
|
* @todo memoize/cache
|
|
432
427
|
*/
|
|
433
|
-
protected
|
|
434
|
-
const ino = this.
|
|
435
|
-
return this.
|
|
428
|
+
protected findInodeSync(tx: Transaction, path: string, syscall: string, visited: Set<string> = new Set()): Inode {
|
|
429
|
+
const ino = this._findInodeSync(tx, path, syscall, visited);
|
|
430
|
+
return new Inode(this.getSync(tx, ino, path, syscall));
|
|
436
431
|
}
|
|
437
432
|
|
|
438
433
|
/**
|
|
439
|
-
* Given
|
|
434
|
+
* Given an ID, retrieves the corresponding data.
|
|
440
435
|
* @param tx The transaction to use.
|
|
441
436
|
* @param path The corresponding path to the file (used for error messages).
|
|
442
437
|
* @param id The ID to look up.
|
|
443
438
|
*/
|
|
444
|
-
private async
|
|
439
|
+
private async get(tx: Transaction, id: bigint, path: string, syscall: string): Promise<Uint8Array> {
|
|
445
440
|
const data = await tx.get(id);
|
|
446
441
|
if (!data) {
|
|
447
|
-
throw ErrnoError.With('ENOENT', path,
|
|
442
|
+
throw ErrnoError.With('ENOENT', path, syscall);
|
|
448
443
|
}
|
|
449
|
-
return
|
|
444
|
+
return data;
|
|
450
445
|
}
|
|
451
446
|
|
|
452
447
|
/**
|
|
453
|
-
* Given
|
|
448
|
+
* Given an ID, retrieves the corresponding data.
|
|
454
449
|
* @param tx The transaction to use.
|
|
455
450
|
* @param path The corresponding path to the file (used for error messages).
|
|
456
451
|
* @param id The ID to look up.
|
|
457
452
|
*/
|
|
458
|
-
|
|
453
|
+
private getSync(tx: Transaction, id: bigint, path: string, syscall: string): Uint8Array {
|
|
459
454
|
const data = tx.getSync(id);
|
|
460
455
|
if (!data) {
|
|
461
|
-
throw ErrnoError.With('ENOENT', path,
|
|
456
|
+
throw ErrnoError.With('ENOENT', path, syscall);
|
|
462
457
|
}
|
|
463
|
-
|
|
464
|
-
return inode;
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
/**
|
|
468
|
-
* Given the Inode of a directory, retrieves the corresponding directory
|
|
469
|
-
* listing.
|
|
470
|
-
*/
|
|
471
|
-
private async getDirListing(tx: Transaction, inode: Inode, path: string): Promise<{ [fileName: string]: Ino }> {
|
|
472
|
-
const data = await tx.get(inode.ino);
|
|
473
|
-
/*
|
|
474
|
-
Occurs when data is undefined,or corresponds to something other than a directory listing.
|
|
475
|
-
The latter should never occur unless the file system is corrupted.
|
|
476
|
-
*/
|
|
477
|
-
if (!data) {
|
|
478
|
-
throw ErrnoError.With('ENOENT', path, 'getDirListing');
|
|
479
|
-
}
|
|
480
|
-
|
|
481
|
-
return decodeDirListing(data);
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
/**
|
|
485
|
-
* Given the Inode of a directory, retrieves the corresponding directory listing.
|
|
486
|
-
*/
|
|
487
|
-
protected getDirListingSync(tx: Transaction, inode: Inode, p?: string): { [fileName: string]: Ino } {
|
|
488
|
-
const data = tx.getSync(inode.ino);
|
|
489
|
-
if (!data) {
|
|
490
|
-
throw ErrnoError.With('ENOENT', p, 'getDirListing');
|
|
491
|
-
}
|
|
492
|
-
return decodeDirListing(data);
|
|
458
|
+
return data;
|
|
493
459
|
}
|
|
494
460
|
|
|
495
461
|
/**
|
|
496
462
|
* Adds a new node under a random ID. Retries before giving up in
|
|
497
|
-
* the exceedingly unlikely chance that we try to reuse a random
|
|
463
|
+
* the exceedingly unlikely chance that we try to reuse a random id.
|
|
498
464
|
*/
|
|
499
|
-
private async
|
|
465
|
+
private async allocNew(tx: Transaction, path: string, syscall: string): Promise<bigint> {
|
|
500
466
|
for (let i = 0; i < maxInodeAllocTries; i++) {
|
|
501
|
-
const ino:
|
|
467
|
+
const ino: bigint = randomBigInt();
|
|
502
468
|
if (await tx.get(ino)) {
|
|
503
469
|
continue;
|
|
504
470
|
}
|
|
505
|
-
await tx.set(ino, data);
|
|
506
471
|
return ino;
|
|
507
472
|
}
|
|
508
|
-
throw new ErrnoError(Errno.ENOSPC, 'No
|
|
473
|
+
throw new ErrnoError(Errno.ENOSPC, 'No IDs available', path, syscall);
|
|
509
474
|
}
|
|
510
475
|
|
|
511
476
|
/**
|
|
512
477
|
* Creates a new node under a random ID. Retries before giving up in
|
|
513
|
-
* the exceedingly unlikely chance that we try to reuse a random
|
|
478
|
+
* the exceedingly unlikely chance that we try to reuse a random id.
|
|
514
479
|
* @return The ino that the data was stored under.
|
|
515
480
|
*/
|
|
516
|
-
|
|
481
|
+
private allocNewSync(tx: Transaction, path: string, syscall: string): bigint {
|
|
517
482
|
for (let i = 0; i < maxInodeAllocTries; i++) {
|
|
518
|
-
const ino:
|
|
483
|
+
const ino: bigint = randomBigInt();
|
|
519
484
|
if (tx.getSync(ino)) {
|
|
520
485
|
continue;
|
|
521
486
|
}
|
|
522
|
-
tx.setSync(ino, data);
|
|
523
487
|
return ino;
|
|
524
488
|
}
|
|
525
|
-
throw new ErrnoError(Errno.ENOSPC, 'No
|
|
489
|
+
throw new ErrnoError(Errno.ENOSPC, 'No IDs available', path, syscall);
|
|
526
490
|
}
|
|
527
491
|
|
|
528
492
|
/**
|
|
@@ -533,40 +497,41 @@ export class StoreFS<T extends Store = Store> extends FileSystem {
|
|
|
533
497
|
* @param mode The mode to create the new file with.
|
|
534
498
|
* @param data The data to store at the file's data node.
|
|
535
499
|
*/
|
|
536
|
-
private async commitNew(path: string, type: FileType, mode: number, data: Uint8Array): Promise<Inode> {
|
|
500
|
+
private async commitNew(path: string, type: FileType, mode: number, data: Uint8Array, syscall: string): Promise<Inode> {
|
|
537
501
|
/*
|
|
538
502
|
The root always exists.
|
|
539
503
|
If we don't check this prior to taking steps below,
|
|
540
504
|
we will create a file with name '' in root if path is '/'.
|
|
541
505
|
*/
|
|
542
506
|
if (path == '/') {
|
|
543
|
-
throw ErrnoError.With('EEXIST', path,
|
|
507
|
+
throw ErrnoError.With('EEXIST', path, syscall);
|
|
544
508
|
}
|
|
545
509
|
|
|
546
510
|
await using tx = this.store.transaction();
|
|
547
|
-
const parentPath = dirname(path),
|
|
548
|
-
parent = await this.findINode(tx, parentPath);
|
|
549
511
|
|
|
550
|
-
const fname =
|
|
551
|
-
|
|
512
|
+
const { dir: parentPath, base: fname } = parse(path);
|
|
513
|
+
const parent = await this.findInode(tx, parentPath, syscall);
|
|
514
|
+
const listing = decodeDirListing(await this.get(tx, parent.data, parentPath, syscall));
|
|
552
515
|
|
|
553
516
|
// Check if file already exists.
|
|
554
517
|
if (listing[fname]) {
|
|
555
|
-
|
|
556
|
-
throw ErrnoError.With('EEXIST', path, 'commitNew');
|
|
518
|
+
throw ErrnoError.With('EEXIST', path, syscall);
|
|
557
519
|
}
|
|
558
520
|
|
|
559
521
|
// Commit data.
|
|
560
522
|
const inode = new Inode();
|
|
561
|
-
inode.ino = await this.
|
|
523
|
+
inode.ino = await this.allocNew(tx, path, syscall);
|
|
524
|
+
inode.data = await this.allocNew(tx, path, syscall);
|
|
562
525
|
inode.mode = mode | type;
|
|
563
|
-
inode.uid = credentials.uid;
|
|
564
|
-
inode.gid = credentials.gid;
|
|
526
|
+
inode.uid = parent.mode & S_ISUID ? parent.uid : credentials.uid;
|
|
527
|
+
inode.gid = parent.mode & S_ISGID ? parent.gid : credentials.gid;
|
|
565
528
|
inode.size = data.length;
|
|
529
|
+
await tx.set(inode.ino, serialize(inode));
|
|
530
|
+
await tx.set(inode.data, data);
|
|
566
531
|
|
|
567
532
|
// Update and commit parent directory listing.
|
|
568
|
-
listing[fname] =
|
|
569
|
-
await tx.set(parent.
|
|
533
|
+
listing[fname] = inode.ino;
|
|
534
|
+
await tx.set(parent.data, encodeDirListing(listing));
|
|
570
535
|
await tx.commit();
|
|
571
536
|
return inode;
|
|
572
537
|
}
|
|
@@ -580,40 +545,43 @@ export class StoreFS<T extends Store = Store> extends FileSystem {
|
|
|
580
545
|
* @param data The data to store at the file's data node.
|
|
581
546
|
* @return The Inode for the new file.
|
|
582
547
|
*/
|
|
583
|
-
|
|
548
|
+
private commitNewSync(path: string, type: FileType, mode: number, data: Uint8Array, syscall: string): Inode {
|
|
584
549
|
/*
|
|
585
550
|
The root always exists.
|
|
586
551
|
If we don't check this prior to taking steps below,
|
|
587
552
|
we will create a file with name '' in root if path is '/'.
|
|
588
553
|
*/
|
|
589
554
|
if (path == '/') {
|
|
590
|
-
throw ErrnoError.With('EEXIST', path,
|
|
555
|
+
throw ErrnoError.With('EEXIST', path, syscall);
|
|
591
556
|
}
|
|
592
557
|
|
|
593
558
|
using tx = this.store.transaction();
|
|
594
|
-
const parentPath = dirname(path),
|
|
595
|
-
parent = this.findINodeSync(tx, parentPath);
|
|
596
559
|
|
|
597
|
-
const fname =
|
|
598
|
-
|
|
560
|
+
const { dir: parentPath, base: fname } = parse(path);
|
|
561
|
+
const parent = this.findInodeSync(tx, parentPath, syscall);
|
|
562
|
+
|
|
563
|
+
const listing = decodeDirListing(this.getSync(tx, parent.data, parentPath, syscall));
|
|
599
564
|
|
|
600
565
|
// Check if file already exists.
|
|
601
566
|
if (listing[fname]) {
|
|
602
|
-
throw ErrnoError.With('EEXIST', path,
|
|
567
|
+
throw ErrnoError.With('EEXIST', path, syscall);
|
|
603
568
|
}
|
|
604
569
|
|
|
605
570
|
// Commit data.
|
|
606
|
-
const
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
571
|
+
const inode = new Inode();
|
|
572
|
+
inode.ino = this.allocNewSync(tx, path, syscall);
|
|
573
|
+
inode.data = this.allocNewSync(tx, path, syscall);
|
|
574
|
+
inode.size = data.length;
|
|
575
|
+
inode.mode = mode | type;
|
|
576
|
+
inode.uid = parent.mode & S_ISUID ? parent.uid : credentials.uid;
|
|
577
|
+
inode.gid = parent.mode & S_ISGID ? parent.gid : credentials.gid;
|
|
612
578
|
// Update and commit parent directory listing.
|
|
613
|
-
|
|
614
|
-
tx.setSync(
|
|
579
|
+
tx.setSync(inode.ino, serialize(inode));
|
|
580
|
+
tx.setSync(inode.data, data);
|
|
581
|
+
listing[fname] = inode.ino;
|
|
582
|
+
tx.setSync(parent.data, encodeDirListing(listing));
|
|
615
583
|
tx.commitSync();
|
|
616
|
-
return
|
|
584
|
+
return inode;
|
|
617
585
|
}
|
|
618
586
|
|
|
619
587
|
/**
|
|
@@ -622,12 +590,12 @@ export class StoreFS<T extends Store = Store> extends FileSystem {
|
|
|
622
590
|
* @param isDir Does the path belong to a directory, or a file?
|
|
623
591
|
* @todo Update mtime.
|
|
624
592
|
*/
|
|
625
|
-
private async remove(path: string, isDir: boolean): Promise<void> {
|
|
593
|
+
private async remove(path: string, isDir: boolean, syscall: string): Promise<void> {
|
|
626
594
|
await using tx = this.store.transaction();
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
595
|
+
|
|
596
|
+
const { dir: parent, base: fileName } = parse(path),
|
|
597
|
+
parentNode = await this.findInode(tx, parent, syscall),
|
|
598
|
+
listing = decodeDirListing(await this.get(tx, parentNode.data, parent, syscall));
|
|
631
599
|
|
|
632
600
|
if (!listing[fileName]) {
|
|
633
601
|
throw ErrnoError.With('ENOENT', path, 'remove');
|
|
@@ -636,7 +604,7 @@ export class StoreFS<T extends Store = Store> extends FileSystem {
|
|
|
636
604
|
const fileIno = listing[fileName];
|
|
637
605
|
|
|
638
606
|
// Get file inode.
|
|
639
|
-
const fileNode = await this.
|
|
607
|
+
const fileNode = new Inode(await this.get(tx, fileIno, path, syscall));
|
|
640
608
|
|
|
641
609
|
// Remove from directory listing of parent.
|
|
642
610
|
delete listing[fileName];
|
|
@@ -645,11 +613,11 @@ export class StoreFS<T extends Store = Store> extends FileSystem {
|
|
|
645
613
|
throw ErrnoError.With('EISDIR', path, 'remove');
|
|
646
614
|
}
|
|
647
615
|
|
|
648
|
-
await tx.set(parentNode.
|
|
616
|
+
await tx.set(parentNode.data, encodeDirListing(listing));
|
|
649
617
|
|
|
650
618
|
if (--fileNode.nlink < 1) {
|
|
651
619
|
// remove file
|
|
652
|
-
await tx.remove(fileNode.
|
|
620
|
+
await tx.remove(fileNode.data);
|
|
653
621
|
await tx.remove(fileIno);
|
|
654
622
|
}
|
|
655
623
|
|
|
@@ -663,20 +631,19 @@ export class StoreFS<T extends Store = Store> extends FileSystem {
|
|
|
663
631
|
* @param isDir Does the path belong to a directory, or a file?
|
|
664
632
|
* @todo Update mtime.
|
|
665
633
|
*/
|
|
666
|
-
|
|
634
|
+
private removeSync(path: string, isDir: boolean, syscall: string): void {
|
|
667
635
|
using tx = this.store.transaction();
|
|
668
|
-
const parent:
|
|
669
|
-
parentNode = this.
|
|
670
|
-
listing = this.
|
|
671
|
-
|
|
672
|
-
fileIno: Ino = listing[fileName];
|
|
636
|
+
const { dir: parent, base: fileName } = parse(path),
|
|
637
|
+
parentNode = this.findInodeSync(tx, parent, syscall),
|
|
638
|
+
listing = decodeDirListing(this.getSync(tx, parentNode.data, parent, syscall)),
|
|
639
|
+
fileIno: bigint = listing[fileName];
|
|
673
640
|
|
|
674
641
|
if (!fileIno) {
|
|
675
642
|
throw ErrnoError.With('ENOENT', path, 'remove');
|
|
676
643
|
}
|
|
677
644
|
|
|
678
645
|
// Get file inode.
|
|
679
|
-
const fileNode = this.
|
|
646
|
+
const fileNode = new Inode(this.getSync(tx, fileIno, path, syscall));
|
|
680
647
|
|
|
681
648
|
// Remove from directory listing of parent.
|
|
682
649
|
delete listing[fileName];
|
|
@@ -686,11 +653,11 @@ export class StoreFS<T extends Store = Store> extends FileSystem {
|
|
|
686
653
|
}
|
|
687
654
|
|
|
688
655
|
// Update directory listing.
|
|
689
|
-
tx.setSync(parentNode.
|
|
656
|
+
tx.setSync(parentNode.data, encodeDirListing(listing));
|
|
690
657
|
|
|
691
658
|
if (--fileNode.nlink < 1) {
|
|
692
659
|
// remove file
|
|
693
|
-
tx.removeSync(fileNode.
|
|
660
|
+
tx.removeSync(fileNode.data);
|
|
694
661
|
tx.removeSync(fileIno);
|
|
695
662
|
}
|
|
696
663
|
|