@zenfs/core 0.17.1 → 0.18.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/backend.d.ts +2 -3
- package/dist/backends/fetch.js +2 -2
- package/dist/backends/file_index.d.ts +14 -15
- package/dist/backends/file_index.js +3 -9
- package/dist/backends/overlay.d.ts +21 -22
- package/dist/backends/overlay.js +111 -114
- package/dist/backends/port/fs.d.ts +21 -22
- package/dist/backends/port/fs.js +23 -23
- package/dist/backends/store/fs.d.ts +20 -21
- package/dist/backends/store/fs.js +70 -138
- package/dist/browser.min.js +4 -4
- package/dist/browser.min.js.map +4 -4
- package/dist/config.js +2 -2
- package/dist/{cred.d.ts → credentials.d.ts} +3 -2
- package/dist/credentials.js +16 -0
- package/dist/emulation/async.d.ts +19 -4
- package/dist/emulation/async.js +55 -8
- package/dist/emulation/dir.d.ts +4 -7
- package/dist/emulation/dir.js +16 -24
- package/dist/emulation/promises.d.ts +3 -3
- package/dist/emulation/promises.js +103 -46
- package/dist/emulation/shared.d.ts +0 -3
- package/dist/emulation/shared.js +0 -6
- package/dist/emulation/sync.d.ts +3 -4
- package/dist/emulation/sync.js +107 -65
- package/dist/emulation/watchers.d.ts +40 -3
- package/dist/emulation/watchers.js +115 -9
- package/dist/error.d.ts +1 -1
- package/dist/error.js +1 -1
- package/dist/filesystem.d.ts +20 -21
- package/dist/filesystem.js +4 -4
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/mixins/async.d.ts +13 -14
- package/dist/mixins/async.js +45 -47
- package/dist/mixins/mutexed.d.ts +1 -1
- package/dist/mixins/mutexed.js +61 -53
- package/dist/mixins/readonly.d.ts +12 -13
- package/dist/mixins/readonly.js +12 -12
- package/dist/mixins/sync.js +20 -20
- package/dist/stats.d.ts +12 -5
- package/dist/stats.js +11 -2
- package/dist/utils.d.ts +3 -4
- package/dist/utils.js +7 -17
- package/package.json +2 -2
- package/src/backends/backend.ts +2 -3
- package/src/backends/fetch.ts +2 -2
- package/src/backends/file_index.ts +3 -12
- package/src/backends/overlay.ts +112 -116
- package/src/backends/port/fs.ts +25 -26
- package/src/backends/store/fs.ts +72 -151
- package/src/config.ts +3 -2
- package/src/{cred.ts → credentials.ts} +11 -2
- package/src/emulation/async.ts +72 -16
- package/src/emulation/dir.ts +21 -29
- package/src/emulation/promises.ts +107 -46
- package/src/emulation/shared.ts +0 -8
- package/src/emulation/sync.ts +109 -66
- package/src/emulation/watchers.ts +140 -10
- package/src/error.ts +1 -1
- package/src/filesystem.ts +22 -23
- package/src/index.ts +1 -1
- package/src/mixins/async.ts +54 -55
- package/src/mixins/mutexed.ts +62 -55
- package/src/mixins/readonly.ts +24 -25
- package/src/mixins/sync.ts +21 -22
- package/src/stats.ts +15 -5
- package/src/utils.ts +9 -26
- package/dist/cred.js +0 -8
package/src/backends/store/fs.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
1
|
+
import { credentials } from '../../credentials.js';
|
|
2
|
+
import { S_IFDIR, S_IFREG } from '../../emulation/constants.js';
|
|
3
3
|
import { basename, dirname, join, resolve } from '../../emulation/path.js';
|
|
4
4
|
import { Errno, ErrnoError } from '../../error.js';
|
|
5
|
-
import { PreloadFile
|
|
5
|
+
import { PreloadFile } from '../../file.js';
|
|
6
6
|
import { FileSystem, type FileSystemMetadata } from '../../filesystem.js';
|
|
7
7
|
import { type Ino, Inode, randomIno, rootIno } from '../../inode.js';
|
|
8
8
|
import type { FileType, Stats } from '../../stats.js';
|
|
@@ -64,7 +64,7 @@ export class StoreFS<T extends Store = Store> extends FileSystem {
|
|
|
64
64
|
/**
|
|
65
65
|
* @todo Make rename compatible with the cache.
|
|
66
66
|
*/
|
|
67
|
-
public async rename(oldPath: string, newPath: string
|
|
67
|
+
public async rename(oldPath: string, newPath: string): Promise<void> {
|
|
68
68
|
await using tx = this.store.transaction();
|
|
69
69
|
const oldParent = dirname(oldPath),
|
|
70
70
|
oldName = basename(oldPath),
|
|
@@ -74,10 +74,6 @@ export class StoreFS<T extends Store = Store> extends FileSystem {
|
|
|
74
74
|
oldDirNode = await this.findINode(tx, oldParent),
|
|
75
75
|
oldDirList = await this.getDirListing(tx, oldDirNode, oldParent);
|
|
76
76
|
|
|
77
|
-
if (!oldDirNode.toStats().hasAccess(W_OK, cred)) {
|
|
78
|
-
throw ErrnoError.With('EACCES', oldPath, 'rename');
|
|
79
|
-
}
|
|
80
|
-
|
|
81
77
|
if (!oldDirList[oldName]) {
|
|
82
78
|
throw ErrnoError.With('ENOENT', oldPath, 'rename');
|
|
83
79
|
}
|
|
@@ -105,15 +101,13 @@ export class StoreFS<T extends Store = Store> extends FileSystem {
|
|
|
105
101
|
}
|
|
106
102
|
|
|
107
103
|
if (newDirList[newName]) {
|
|
108
|
-
// If it's a file, delete it.
|
|
104
|
+
// If it's a file, delete it, if it's a directory, throw a permissions error.
|
|
109
105
|
const newNameNode = await this.getINode(tx, newDirList[newName], newPath);
|
|
110
|
-
if (newNameNode.toStats().isFile()) {
|
|
111
|
-
await tx.remove(newNameNode.ino);
|
|
112
|
-
await tx.remove(newDirList[newName]);
|
|
113
|
-
} else {
|
|
114
|
-
// If it's a directory, throw a permissions error.
|
|
106
|
+
if (!newNameNode.toStats().isFile()) {
|
|
115
107
|
throw ErrnoError.With('EPERM', newPath, 'rename');
|
|
116
108
|
}
|
|
109
|
+
await tx.remove(newNameNode.ino);
|
|
110
|
+
await tx.remove(newDirList[newName]);
|
|
117
111
|
}
|
|
118
112
|
newDirList[newName] = nodeId;
|
|
119
113
|
// Commit the two changed directory listings.
|
|
@@ -122,7 +116,7 @@ export class StoreFS<T extends Store = Store> extends FileSystem {
|
|
|
122
116
|
await tx.commit();
|
|
123
117
|
}
|
|
124
118
|
|
|
125
|
-
public renameSync(oldPath: string, newPath: string
|
|
119
|
+
public renameSync(oldPath: string, newPath: string): void {
|
|
126
120
|
using tx = this.store.transaction();
|
|
127
121
|
const oldParent = dirname(oldPath),
|
|
128
122
|
oldName = basename(oldPath),
|
|
@@ -132,10 +126,6 @@ export class StoreFS<T extends Store = Store> extends FileSystem {
|
|
|
132
126
|
oldDirNode = this.findINodeSync(tx, oldParent),
|
|
133
127
|
oldDirList = this.getDirListingSync(tx, oldDirNode, oldParent);
|
|
134
128
|
|
|
135
|
-
if (!oldDirNode.toStats().hasAccess(W_OK, cred)) {
|
|
136
|
-
throw ErrnoError.With('EACCES', oldPath, 'rename');
|
|
137
|
-
}
|
|
138
|
-
|
|
139
129
|
if (!oldDirList[oldName]) {
|
|
140
130
|
throw ErrnoError.With('ENOENT', oldPath, 'rename');
|
|
141
131
|
}
|
|
@@ -163,15 +153,13 @@ export class StoreFS<T extends Store = Store> extends FileSystem {
|
|
|
163
153
|
}
|
|
164
154
|
|
|
165
155
|
if (newDirList[newName]) {
|
|
166
|
-
// If it's a file, delete it.
|
|
156
|
+
// If it's a file, delete it, if it's a directory, throw a permissions error.
|
|
167
157
|
const newNameNode = this.getINodeSync(tx, newDirList[newName], newPath);
|
|
168
|
-
if (newNameNode.toStats().isFile()) {
|
|
169
|
-
tx.removeSync(newNameNode.ino);
|
|
170
|
-
tx.removeSync(newDirList[newName]);
|
|
171
|
-
} else {
|
|
172
|
-
// If it's a directory, throw a permissions error.
|
|
158
|
+
if (!newNameNode.toStats().isFile()) {
|
|
173
159
|
throw ErrnoError.With('EPERM', newPath, 'rename');
|
|
174
160
|
}
|
|
161
|
+
tx.removeSync(newNameNode.ino);
|
|
162
|
+
tx.removeSync(newDirList[newName]);
|
|
175
163
|
}
|
|
176
164
|
newDirList[newName] = ino;
|
|
177
165
|
|
|
@@ -181,114 +169,92 @@ export class StoreFS<T extends Store = Store> extends FileSystem {
|
|
|
181
169
|
tx.commitSync();
|
|
182
170
|
}
|
|
183
171
|
|
|
184
|
-
public async stat(path: string
|
|
172
|
+
public async stat(path: string): Promise<Stats> {
|
|
185
173
|
await using tx = this.store.transaction();
|
|
186
174
|
const inode = await this.findINode(tx, path);
|
|
187
175
|
if (!inode) {
|
|
188
176
|
throw ErrnoError.With('ENOENT', path, 'stat');
|
|
189
177
|
}
|
|
190
|
-
|
|
191
|
-
if (!stats.hasAccess(R_OK, cred)) {
|
|
192
|
-
throw ErrnoError.With('EACCES', path, 'stat');
|
|
193
|
-
}
|
|
194
|
-
return stats;
|
|
178
|
+
return inode.toStats();
|
|
195
179
|
}
|
|
196
180
|
|
|
197
|
-
public statSync(path: string
|
|
181
|
+
public statSync(path: string): Stats {
|
|
198
182
|
using tx = this.store.transaction();
|
|
199
183
|
// Get the inode to the item, convert it into a Stats object.
|
|
200
|
-
|
|
201
|
-
if (!stats.hasAccess(R_OK, cred)) {
|
|
202
|
-
throw ErrnoError.With('EACCES', path, 'stat');
|
|
203
|
-
}
|
|
204
|
-
return stats;
|
|
184
|
+
return this.findINodeSync(tx, path).toStats();
|
|
205
185
|
}
|
|
206
186
|
|
|
207
|
-
public async createFile(path: string, flag: string, mode: number
|
|
208
|
-
const node = await this.commitNew(path, S_IFREG, mode,
|
|
187
|
+
public async createFile(path: string, flag: string, mode: number): Promise<PreloadFile<this>> {
|
|
188
|
+
const node = await this.commitNew(path, S_IFREG, mode, new Uint8Array(0));
|
|
209
189
|
return new PreloadFile(this, path, flag, node.toStats(), new Uint8Array(0));
|
|
210
190
|
}
|
|
211
191
|
|
|
212
|
-
public createFileSync(path: string, flag: string, mode: number
|
|
213
|
-
this.commitNewSync(path, S_IFREG, mode
|
|
214
|
-
return this.openFileSync(path, flag
|
|
192
|
+
public createFileSync(path: string, flag: string, mode: number): PreloadFile<this> {
|
|
193
|
+
this.commitNewSync(path, S_IFREG, mode);
|
|
194
|
+
return this.openFileSync(path, flag);
|
|
215
195
|
}
|
|
216
196
|
|
|
217
|
-
public async openFile(path: string, flag: string
|
|
197
|
+
public async openFile(path: string, flag: string): Promise<PreloadFile<this>> {
|
|
218
198
|
await using tx = this.store.transaction();
|
|
219
199
|
const node = await this.findINode(tx, path),
|
|
220
200
|
data = await tx.get(node.ino);
|
|
221
|
-
if (!node.toStats().hasAccess(flagToMode(flag), cred)) {
|
|
222
|
-
throw ErrnoError.With('EACCES', path, 'openFile');
|
|
223
|
-
}
|
|
224
201
|
if (!data) {
|
|
225
202
|
throw ErrnoError.With('ENOENT', path, 'openFile');
|
|
226
203
|
}
|
|
227
204
|
return new PreloadFile(this, path, flag, node.toStats(), data);
|
|
228
205
|
}
|
|
229
206
|
|
|
230
|
-
public openFileSync(path: string, flag: string
|
|
207
|
+
public openFileSync(path: string, flag: string): PreloadFile<this> {
|
|
231
208
|
using tx = this.store.transaction();
|
|
232
209
|
const node = this.findINodeSync(tx, path),
|
|
233
210
|
data = tx.getSync(node.ino);
|
|
234
|
-
if (!node.toStats().hasAccess(flagToMode(flag), cred)) {
|
|
235
|
-
throw ErrnoError.With('EACCES', path, 'openFile');
|
|
236
|
-
}
|
|
237
211
|
if (!data) {
|
|
238
212
|
throw ErrnoError.With('ENOENT', path, 'openFile');
|
|
239
213
|
}
|
|
240
214
|
return new PreloadFile(this, path, flag, node.toStats(), data);
|
|
241
215
|
}
|
|
242
216
|
|
|
243
|
-
public async unlink(path: string
|
|
244
|
-
return this.remove(path, false
|
|
217
|
+
public async unlink(path: string): Promise<void> {
|
|
218
|
+
return this.remove(path, false);
|
|
245
219
|
}
|
|
246
220
|
|
|
247
|
-
public unlinkSync(path: string
|
|
248
|
-
this.removeSync(path, false
|
|
221
|
+
public unlinkSync(path: string): void {
|
|
222
|
+
this.removeSync(path, false);
|
|
249
223
|
}
|
|
250
224
|
|
|
251
|
-
public async rmdir(path: string
|
|
225
|
+
public async rmdir(path: string): Promise<void> {
|
|
252
226
|
// Check first if directory is empty.
|
|
253
|
-
|
|
254
|
-
if (list.length > 0) {
|
|
227
|
+
if ((await this.readdir(path)).length) {
|
|
255
228
|
throw ErrnoError.With('ENOTEMPTY', path, 'rmdir');
|
|
256
229
|
}
|
|
257
|
-
await this.remove(path, true
|
|
230
|
+
await this.remove(path, true);
|
|
258
231
|
}
|
|
259
232
|
|
|
260
|
-
public rmdirSync(path: string
|
|
233
|
+
public rmdirSync(path: string): void {
|
|
261
234
|
// Check first if directory is empty.
|
|
262
|
-
if (this.readdirSync(path
|
|
235
|
+
if (this.readdirSync(path).length) {
|
|
263
236
|
throw ErrnoError.With('ENOTEMPTY', path, 'rmdir');
|
|
264
|
-
} else {
|
|
265
|
-
this.removeSync(path, true, cred);
|
|
266
237
|
}
|
|
238
|
+
this.removeSync(path, true);
|
|
267
239
|
}
|
|
268
240
|
|
|
269
|
-
public async mkdir(path: string, mode: number
|
|
270
|
-
await this.commitNew(path, S_IFDIR, mode,
|
|
241
|
+
public async mkdir(path: string, mode: number): Promise<void> {
|
|
242
|
+
await this.commitNew(path, S_IFDIR, mode, encode('{}'));
|
|
271
243
|
}
|
|
272
244
|
|
|
273
|
-
public mkdirSync(path: string, mode: number
|
|
274
|
-
this.commitNewSync(path, S_IFDIR, mode,
|
|
245
|
+
public mkdirSync(path: string, mode: number): void {
|
|
246
|
+
this.commitNewSync(path, S_IFDIR, mode, encode('{}'));
|
|
275
247
|
}
|
|
276
248
|
|
|
277
|
-
public async readdir(path: string
|
|
249
|
+
public async readdir(path: string): Promise<string[]> {
|
|
278
250
|
await using tx = this.store.transaction();
|
|
279
251
|
const node = await this.findINode(tx, path);
|
|
280
|
-
if (!node.toStats().hasAccess(R_OK, cred)) {
|
|
281
|
-
throw ErrnoError.With('EACCES', path, 'readdur');
|
|
282
|
-
}
|
|
283
252
|
return Object.keys(await this.getDirListing(tx, node, path));
|
|
284
253
|
}
|
|
285
254
|
|
|
286
|
-
public readdirSync(path: string
|
|
255
|
+
public readdirSync(path: string): string[] {
|
|
287
256
|
using tx = this.store.transaction();
|
|
288
257
|
const node = this.findINodeSync(tx, path);
|
|
289
|
-
if (!node.toStats().hasAccess(R_OK, cred)) {
|
|
290
|
-
throw ErrnoError.With('EACCES', path, 'readdir');
|
|
291
|
-
}
|
|
292
258
|
return Object.keys(this.getDirListingSync(tx, node, path));
|
|
293
259
|
}
|
|
294
260
|
|
|
@@ -334,66 +300,39 @@ export class StoreFS<T extends Store = Store> extends FileSystem {
|
|
|
334
300
|
tx.commitSync();
|
|
335
301
|
}
|
|
336
302
|
|
|
337
|
-
public async link(
|
|
303
|
+
public async link(target: string, link: string): Promise<void> {
|
|
338
304
|
await using tx = this.store.transaction();
|
|
339
|
-
const existingDir: string = dirname(existing),
|
|
340
|
-
existingDirNode = await this.findINode(tx, existingDir);
|
|
341
305
|
|
|
342
|
-
|
|
343
|
-
throw ErrnoError.With('EACCES', existingDir, 'link');
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
const newDir: string = dirname(newpath),
|
|
306
|
+
const newDir: string = dirname(link),
|
|
347
307
|
newDirNode = await this.findINode(tx, newDir),
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
if (!newDirNode.toStats().hasAccess(W_OK, cred)) {
|
|
351
|
-
throw ErrnoError.With('EACCES', newDir, 'link');
|
|
352
|
-
}
|
|
308
|
+
listing = await this.getDirListing(tx, newDirNode, newDir);
|
|
353
309
|
|
|
354
|
-
const ino = await this._findINode(tx,
|
|
355
|
-
const node = await this.getINode(tx, ino,
|
|
356
|
-
|
|
357
|
-
if (!node.toStats().hasAccess(W_OK, cred)) {
|
|
358
|
-
throw ErrnoError.With('EACCES', newpath, 'link');
|
|
359
|
-
}
|
|
310
|
+
const ino = await this._findINode(tx, dirname(target), basename(target));
|
|
311
|
+
const node = await this.getINode(tx, ino, target);
|
|
360
312
|
|
|
361
313
|
node.nlink++;
|
|
362
|
-
|
|
314
|
+
listing[basename(link)] = ino;
|
|
363
315
|
|
|
364
316
|
tx.setSync(ino, node.data);
|
|
365
|
-
tx.setSync(newDirNode.ino, encodeDirListing(
|
|
317
|
+
tx.setSync(newDirNode.ino, encodeDirListing(listing));
|
|
366
318
|
tx.commitSync();
|
|
367
319
|
}
|
|
368
320
|
|
|
369
|
-
public linkSync(
|
|
321
|
+
public linkSync(target: string, link: string): void {
|
|
370
322
|
using tx = this.store.transaction();
|
|
371
|
-
const existingDir: string = dirname(existing),
|
|
372
|
-
existingDirNode = this.findINodeSync(tx, existingDir);
|
|
373
|
-
|
|
374
|
-
if (!existingDirNode.toStats().hasAccess(R_OK, cred)) {
|
|
375
|
-
throw ErrnoError.With('EACCES', existingDir, 'link');
|
|
376
|
-
}
|
|
377
323
|
|
|
378
|
-
const newDir: string = dirname(
|
|
324
|
+
const newDir: string = dirname(link),
|
|
379
325
|
newDirNode = this.findINodeSync(tx, newDir),
|
|
380
|
-
|
|
326
|
+
listing = this.getDirListingSync(tx, newDirNode, newDir);
|
|
381
327
|
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
const ino = this._findINodeSync(tx, existingDir, basename(existing));
|
|
387
|
-
const node = this.getINodeSync(tx, ino, existing);
|
|
328
|
+
const ino = this._findINodeSync(tx, dirname(target), basename(target));
|
|
329
|
+
const node = this.getINodeSync(tx, ino, target);
|
|
388
330
|
|
|
389
|
-
if (!node.toStats().hasAccess(W_OK, cred)) {
|
|
390
|
-
throw ErrnoError.With('EACCES', newpath, 'link');
|
|
391
|
-
}
|
|
392
331
|
node.nlink++;
|
|
393
|
-
|
|
332
|
+
listing[basename(link)] = ino;
|
|
394
333
|
|
|
395
334
|
tx.setSync(ino, node.data);
|
|
396
|
-
tx.setSync(newDirNode.ino, encodeDirListing(
|
|
335
|
+
tx.setSync(newDirNode.ino, encodeDirListing(listing));
|
|
397
336
|
tx.commitSync();
|
|
398
337
|
}
|
|
399
338
|
|
|
@@ -616,16 +555,11 @@ export class StoreFS<T extends Store = Store> extends FileSystem {
|
|
|
616
555
|
* @param cred The UID/GID to create the file with
|
|
617
556
|
* @param data The data to store at the file's data node.
|
|
618
557
|
*/
|
|
619
|
-
private async commitNew(path: string, type: FileType, mode: number,
|
|
558
|
+
private async commitNew(path: string, type: FileType, mode: number, data: Uint8Array): Promise<Inode> {
|
|
620
559
|
await using tx = this.store.transaction();
|
|
621
560
|
const parentPath = dirname(path),
|
|
622
561
|
parent = await this.findINode(tx, parentPath);
|
|
623
562
|
|
|
624
|
-
//Check that the creater has correct access
|
|
625
|
-
if (!parent.toStats().hasAccess(W_OK, cred)) {
|
|
626
|
-
throw ErrnoError.With('EACCES', path, 'commitNewFile');
|
|
627
|
-
}
|
|
628
|
-
|
|
629
563
|
const fname = basename(path),
|
|
630
564
|
listing = await this.getDirListing(tx, parent, parentPath);
|
|
631
565
|
|
|
@@ -635,21 +569,21 @@ export class StoreFS<T extends Store = Store> extends FileSystem {
|
|
|
635
569
|
we will create a file with name '' in root should path == '/'.
|
|
636
570
|
*/
|
|
637
571
|
if (path === '/') {
|
|
638
|
-
throw ErrnoError.With('EEXIST', path, '
|
|
572
|
+
throw ErrnoError.With('EEXIST', path, 'commitNew');
|
|
639
573
|
}
|
|
640
574
|
|
|
641
575
|
// Check if file already exists.
|
|
642
576
|
if (listing[fname]) {
|
|
643
577
|
await tx.abort();
|
|
644
|
-
throw ErrnoError.With('EEXIST', path, '
|
|
578
|
+
throw ErrnoError.With('EEXIST', path, 'commitNew');
|
|
645
579
|
}
|
|
646
580
|
|
|
647
581
|
// Commit data.
|
|
648
582
|
const inode = new Inode();
|
|
649
583
|
inode.ino = await this.addNew(tx, data, path);
|
|
650
584
|
inode.mode = mode | type;
|
|
651
|
-
inode.uid =
|
|
652
|
-
inode.gid =
|
|
585
|
+
inode.uid = credentials.uid;
|
|
586
|
+
inode.gid = credentials.gid;
|
|
653
587
|
inode.size = data.length;
|
|
654
588
|
|
|
655
589
|
// Update and commit parent directory listing.
|
|
@@ -668,16 +602,11 @@ export class StoreFS<T extends Store = Store> extends FileSystem {
|
|
|
668
602
|
* @param data The data to store at the file's data node.
|
|
669
603
|
* @return The Inode for the new file.
|
|
670
604
|
*/
|
|
671
|
-
protected commitNewSync(path: string, type: FileType, mode: number,
|
|
605
|
+
protected commitNewSync(path: string, type: FileType, mode: number, data: Uint8Array = new Uint8Array()): Inode {
|
|
672
606
|
using tx = this.store.transaction();
|
|
673
607
|
const parentPath = dirname(path),
|
|
674
608
|
parent = this.findINodeSync(tx, parentPath);
|
|
675
609
|
|
|
676
|
-
//Check that the creater has correct access
|
|
677
|
-
if (!parent.toStats().hasAccess(W_OK, cred)) {
|
|
678
|
-
throw ErrnoError.With('EACCES', path, 'commitNewFile');
|
|
679
|
-
}
|
|
680
|
-
|
|
681
610
|
const fname = basename(path),
|
|
682
611
|
listing = this.getDirListingSync(tx, parent, parentPath);
|
|
683
612
|
|
|
@@ -687,12 +616,12 @@ export class StoreFS<T extends Store = Store> extends FileSystem {
|
|
|
687
616
|
we will create a file with name '' in root should p == '/'.
|
|
688
617
|
*/
|
|
689
618
|
if (path === '/') {
|
|
690
|
-
throw ErrnoError.With('EEXIST', path, '
|
|
619
|
+
throw ErrnoError.With('EEXIST', path, 'commitNew');
|
|
691
620
|
}
|
|
692
621
|
|
|
693
622
|
// Check if file already exists.
|
|
694
623
|
if (listing[fname]) {
|
|
695
|
-
throw ErrnoError.With('EEXIST', path, '
|
|
624
|
+
throw ErrnoError.With('EEXIST', path, 'commitNew');
|
|
696
625
|
}
|
|
697
626
|
|
|
698
627
|
// Commit data.
|
|
@@ -700,8 +629,8 @@ export class StoreFS<T extends Store = Store> extends FileSystem {
|
|
|
700
629
|
node.ino = this.addNewSync(tx, data, path);
|
|
701
630
|
node.size = data.length;
|
|
702
631
|
node.mode = mode | type;
|
|
703
|
-
node.uid =
|
|
704
|
-
node.gid =
|
|
632
|
+
node.uid = credentials.uid;
|
|
633
|
+
node.gid = credentials.gid;
|
|
705
634
|
// Update and commit parent directory listing.
|
|
706
635
|
listing[fname] = this.addNewSync(tx, node.data, path);
|
|
707
636
|
tx.setSync(parent.ino, encodeDirListing(listing));
|
|
@@ -715,7 +644,7 @@ export class StoreFS<T extends Store = Store> extends FileSystem {
|
|
|
715
644
|
* @param isDir Does the path belong to a directory, or a file?
|
|
716
645
|
* @todo Update mtime.
|
|
717
646
|
*/
|
|
718
|
-
private async remove(path: string, isDir: boolean
|
|
647
|
+
private async remove(path: string, isDir: boolean): Promise<void> {
|
|
719
648
|
await using tx = this.store.transaction();
|
|
720
649
|
const parent: string = dirname(path),
|
|
721
650
|
parentNode = await this.findINode(tx, parent),
|
|
@@ -723,7 +652,7 @@ export class StoreFS<T extends Store = Store> extends FileSystem {
|
|
|
723
652
|
fileName: string = basename(path);
|
|
724
653
|
|
|
725
654
|
if (!listing[fileName]) {
|
|
726
|
-
throw ErrnoError.With('ENOENT', path, '
|
|
655
|
+
throw ErrnoError.With('ENOENT', path, 'remove');
|
|
727
656
|
}
|
|
728
657
|
|
|
729
658
|
const fileIno = listing[fileName];
|
|
@@ -731,19 +660,15 @@ export class StoreFS<T extends Store = Store> extends FileSystem {
|
|
|
731
660
|
// Get file inode.
|
|
732
661
|
const fileNode = await this.getINode(tx, fileIno, path);
|
|
733
662
|
|
|
734
|
-
if (!fileNode.toStats().hasAccess(W_OK, cred)) {
|
|
735
|
-
throw ErrnoError.With('EACCES', path, 'removeEntry');
|
|
736
|
-
}
|
|
737
|
-
|
|
738
663
|
// Remove from directory listing of parent.
|
|
739
664
|
delete listing[fileName];
|
|
740
665
|
|
|
741
666
|
if (!isDir && fileNode.toStats().isDirectory()) {
|
|
742
|
-
throw ErrnoError.With('EISDIR', path, '
|
|
667
|
+
throw ErrnoError.With('EISDIR', path, 'remove');
|
|
743
668
|
}
|
|
744
669
|
|
|
745
670
|
if (isDir && !fileNode.toStats().isDirectory()) {
|
|
746
|
-
throw ErrnoError.With('ENOTDIR', path, '
|
|
671
|
+
throw ErrnoError.With('ENOTDIR', path, 'remove');
|
|
747
672
|
}
|
|
748
673
|
|
|
749
674
|
await tx.set(parentNode.ino, encodeDirListing(listing));
|
|
@@ -764,7 +689,7 @@ export class StoreFS<T extends Store = Store> extends FileSystem {
|
|
|
764
689
|
* @param isDir Does the path belong to a directory, or a file?
|
|
765
690
|
* @todo Update mtime.
|
|
766
691
|
*/
|
|
767
|
-
protected removeSync(path: string, isDir: boolean
|
|
692
|
+
protected removeSync(path: string, isDir: boolean): void {
|
|
768
693
|
using tx = this.store.transaction();
|
|
769
694
|
const parent: string = dirname(path),
|
|
770
695
|
parentNode = this.findINodeSync(tx, parent),
|
|
@@ -773,25 +698,21 @@ export class StoreFS<T extends Store = Store> extends FileSystem {
|
|
|
773
698
|
fileIno: Ino = listing[fileName];
|
|
774
699
|
|
|
775
700
|
if (!fileIno) {
|
|
776
|
-
throw ErrnoError.With('ENOENT', path, '
|
|
701
|
+
throw ErrnoError.With('ENOENT', path, 'remove');
|
|
777
702
|
}
|
|
778
703
|
|
|
779
704
|
// Get file inode.
|
|
780
705
|
const fileNode = this.getINodeSync(tx, fileIno, path);
|
|
781
706
|
|
|
782
|
-
if (!fileNode.toStats().hasAccess(W_OK, cred)) {
|
|
783
|
-
throw ErrnoError.With('EACCES', path, 'removeEntry');
|
|
784
|
-
}
|
|
785
|
-
|
|
786
707
|
// Remove from directory listing of parent.
|
|
787
708
|
delete listing[fileName];
|
|
788
709
|
|
|
789
710
|
if (!isDir && fileNode.toStats().isDirectory()) {
|
|
790
|
-
throw ErrnoError.With('EISDIR', path, '
|
|
711
|
+
throw ErrnoError.With('EISDIR', path, 'remove');
|
|
791
712
|
}
|
|
792
713
|
|
|
793
714
|
if (isDir && !fileNode.toStats().isDirectory()) {
|
|
794
|
-
throw ErrnoError.With('ENOTDIR', path, '
|
|
715
|
+
throw ErrnoError.With('ENOTDIR', path, 'remove');
|
|
795
716
|
}
|
|
796
717
|
|
|
797
718
|
// Update directory listing.
|
package/src/config.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import type { Backend, BackendConfiguration, FilesystemOf, SharedConfig } from './backends/backend.js';
|
|
2
2
|
import { checkOptions, isBackend, isBackendConfig } from './backends/backend.js';
|
|
3
|
+
import { credentials } from './credentials.js';
|
|
3
4
|
import * as fs from './emulation/index.js';
|
|
4
5
|
import type { AbsolutePath } from './emulation/path.js';
|
|
5
|
-
import {
|
|
6
|
+
import type { MountObject } from './emulation/shared.js';
|
|
6
7
|
import { Errno, ErrnoError } from './error.js';
|
|
7
8
|
import { FileSystem } from './filesystem.js';
|
|
8
9
|
|
|
@@ -107,7 +108,7 @@ export async function configure<T extends ConfigMounts>(config: Partial<Configur
|
|
|
107
108
|
const uid = 'uid' in config ? config.uid || 0 : 0;
|
|
108
109
|
const gid = 'gid' in config ? config.gid || 0 : 0;
|
|
109
110
|
|
|
110
|
-
|
|
111
|
+
Object.assign(credentials, { uid, gid, suid: uid, sgid: gid, euid: uid, egid: gid });
|
|
111
112
|
|
|
112
113
|
if (!config.mounts) {
|
|
113
114
|
return;
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Similar to Linux's cred struct.
|
|
4
4
|
* @see https://github.com/torvalds/linux/blob/master/include/linux/cred.h
|
|
5
5
|
*/
|
|
6
|
-
export interface
|
|
6
|
+
export interface Credentials {
|
|
7
7
|
uid: number;
|
|
8
8
|
gid: number;
|
|
9
9
|
suid: number;
|
|
@@ -12,7 +12,16 @@ export interface Cred {
|
|
|
12
12
|
egid: number;
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
export const
|
|
15
|
+
export const credentials: Credentials = {
|
|
16
|
+
uid: 0,
|
|
17
|
+
gid: 0,
|
|
18
|
+
suid: 0,
|
|
19
|
+
sgid: 0,
|
|
20
|
+
euid: 0,
|
|
21
|
+
egid: 0,
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export const rootCredentials: Credentials = {
|
|
16
25
|
uid: 0,
|
|
17
26
|
gid: 0,
|
|
18
27
|
suid: 0,
|
package/src/emulation/async.ts
CHANGED
|
@@ -3,14 +3,14 @@ import type * as fs from 'node:fs';
|
|
|
3
3
|
import { Errno, ErrnoError } from '../error.js';
|
|
4
4
|
import type { FileContents } from '../filesystem.js';
|
|
5
5
|
import { BigIntStats, type Stats } from '../stats.js';
|
|
6
|
-
import { normalizeMode, type Callback } from '../utils.js';
|
|
6
|
+
import { normalizeMode, normalizePath, type Callback } from '../utils.js';
|
|
7
7
|
import { R_OK } from './constants.js';
|
|
8
8
|
import type { Dirent } from './dir.js';
|
|
9
9
|
import type { Dir } from './dir.js';
|
|
10
10
|
import * as promises from './promises.js';
|
|
11
11
|
import { fd2file } from './shared.js';
|
|
12
12
|
import { ReadStream, WriteStream } from './streams.js';
|
|
13
|
-
import { FSWatcher } from './watchers.js';
|
|
13
|
+
import { FSWatcher, StatWatcher } from './watchers.js';
|
|
14
14
|
|
|
15
15
|
const nop = () => {};
|
|
16
16
|
|
|
@@ -673,36 +673,92 @@ export function access(path: fs.PathLike, cbMode: number | Callback, cb: Callbac
|
|
|
673
673
|
}
|
|
674
674
|
access satisfies Omit<typeof fs.access, '__promisify__'>;
|
|
675
675
|
|
|
676
|
+
const statWatchers: Map<string, { watcher: StatWatcher; listeners: Set<(curr: Stats, prev: Stats) => void> }> = new Map();
|
|
677
|
+
|
|
676
678
|
/**
|
|
677
|
-
*
|
|
679
|
+
* Watch for changes on a file. The callback listener will be called each time the file is accessed.
|
|
680
|
+
*
|
|
681
|
+
* The `options` argument may be omitted. If provided, it should be an object with a `persistent` boolean and an `interval` number specifying the polling interval in milliseconds.
|
|
682
|
+
*
|
|
683
|
+
* When a change is detected, the `listener` callback is called with the current and previous `Stats` objects.
|
|
684
|
+
*
|
|
685
|
+
* @param path The path to the file to watch.
|
|
686
|
+
* @param options Optional options object specifying `persistent` and `interval`.
|
|
687
|
+
* @param listener The callback listener to be called when the file changes.
|
|
678
688
|
*/
|
|
679
689
|
export function watchFile(path: fs.PathLike, listener: (curr: Stats, prev: Stats) => void): void;
|
|
680
690
|
export function watchFile(path: fs.PathLike, options: { persistent?: boolean; interval?: number }, listener: (curr: Stats, prev: Stats) => void): void;
|
|
681
691
|
export function watchFile(
|
|
682
692
|
path: fs.PathLike,
|
|
683
693
|
optsListener: { persistent?: boolean; interval?: number } | ((curr: Stats, prev: Stats) => void),
|
|
684
|
-
listener
|
|
694
|
+
listener?: (curr: Stats, prev: Stats) => void
|
|
685
695
|
): void {
|
|
686
|
-
|
|
696
|
+
const normalizedPath = normalizePath(path.toString());
|
|
697
|
+
const options: { persistent?: boolean; interval?: number } = typeof optsListener != 'function' ? optsListener : {};
|
|
698
|
+
|
|
699
|
+
if (typeof optsListener === 'function') {
|
|
700
|
+
listener = optsListener;
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
if (!listener) {
|
|
704
|
+
throw new ErrnoError(Errno.EINVAL, 'No listener specified', path.toString(), 'watchFile');
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
if (statWatchers.has(normalizedPath)) {
|
|
708
|
+
const entry = statWatchers.get(normalizedPath);
|
|
709
|
+
if (entry) {
|
|
710
|
+
entry.listeners.add(listener);
|
|
711
|
+
}
|
|
712
|
+
return;
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
const watcher = new StatWatcher(normalizedPath, options);
|
|
716
|
+
watcher.on('change', (curr: Stats, prev: Stats) => {
|
|
717
|
+
const entry = statWatchers.get(normalizedPath);
|
|
718
|
+
if (!entry) {
|
|
719
|
+
return;
|
|
720
|
+
}
|
|
721
|
+
for (const listener of entry.listeners) {
|
|
722
|
+
listener(curr, prev);
|
|
723
|
+
}
|
|
724
|
+
});
|
|
725
|
+
statWatchers.set(normalizedPath, { watcher, listeners: new Set() });
|
|
687
726
|
}
|
|
688
727
|
watchFile satisfies Omit<typeof fs.watchFile, '__promisify__'>;
|
|
689
728
|
|
|
690
729
|
/**
|
|
691
|
-
*
|
|
730
|
+
* Stop watching for changes on a file.
|
|
731
|
+
*
|
|
732
|
+
* If the `listener` is specified, only that particular listener is removed.
|
|
733
|
+
* If no `listener` is specified, all listeners are removed, and the file is no longer watched.
|
|
734
|
+
*
|
|
735
|
+
* @param path The path to the file to stop watching.
|
|
736
|
+
* @param listener Optional listener to remove.
|
|
692
737
|
*/
|
|
693
738
|
export function unwatchFile(path: fs.PathLike, listener: (curr: Stats, prev: Stats) => void = nop): void {
|
|
694
|
-
|
|
739
|
+
const normalizedPath = normalizePath(path.toString());
|
|
740
|
+
|
|
741
|
+
const entry = statWatchers.get(normalizedPath);
|
|
742
|
+
if (entry) {
|
|
743
|
+
if (listener && listener !== nop) {
|
|
744
|
+
entry.listeners.delete(listener);
|
|
745
|
+
} else {
|
|
746
|
+
// If no listener is specified, remove all listeners
|
|
747
|
+
entry.listeners.clear();
|
|
748
|
+
}
|
|
749
|
+
if (entry.listeners.size === 0) {
|
|
750
|
+
// No more listeners, stop the watcher
|
|
751
|
+
entry.watcher.stop();
|
|
752
|
+
statWatchers.delete(normalizedPath);
|
|
753
|
+
}
|
|
754
|
+
}
|
|
695
755
|
}
|
|
696
756
|
unwatchFile satisfies Omit<typeof fs.unwatchFile, '__promisify__'>;
|
|
697
757
|
|
|
698
|
-
export function watch(path: fs.PathLike, listener?: (event: string, filename: string) => any):
|
|
699
|
-
export function watch(path: fs.PathLike, options: { persistent?: boolean }, listener?: (event: string, filename: string) => any):
|
|
700
|
-
export function watch(
|
|
701
|
-
path:
|
|
702
|
-
options?: fs.WatchOptions | ((event: string, filename: string) => any),
|
|
703
|
-
listener?: (event: string, filename: string) => any
|
|
704
|
-
): fs.FSWatcher {
|
|
705
|
-
const watcher = new FSWatcher<string>(typeof options == 'object' ? options : {});
|
|
758
|
+
export function watch(path: fs.PathLike, listener?: (event: string, filename: string) => any): FSWatcher;
|
|
759
|
+
export function watch(path: fs.PathLike, options: { persistent?: boolean }, listener?: (event: string, filename: string) => any): FSWatcher;
|
|
760
|
+
export function watch(path: fs.PathLike, options?: fs.WatchOptions | ((event: string, filename: string) => any), listener?: (event: string, filename: string) => any): FSWatcher {
|
|
761
|
+
const watcher = new FSWatcher<string>(normalizePath(path), typeof options == 'object' ? options : {});
|
|
706
762
|
listener = typeof options == 'function' ? options : listener;
|
|
707
763
|
watcher.on('change', listener || nop);
|
|
708
764
|
return watcher;
|
|
@@ -770,7 +826,7 @@ export function createReadStream(path: fs.PathLike, _options?: BufferEncoding |
|
|
|
770
826
|
handle
|
|
771
827
|
?.close()
|
|
772
828
|
.then(() => callback(error))
|
|
773
|
-
.catch(
|
|
829
|
+
.catch(nop);
|
|
774
830
|
},
|
|
775
831
|
});
|
|
776
832
|
|