@zenfs/core 1.3.2 → 1.3.4
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/overlay.d.ts +1 -1
- package/dist/backends/port/fs.d.ts +1 -2
- package/dist/backends/port/fs.js +0 -3
- package/dist/config.js +2 -2
- package/dist/credentials.d.ts +14 -2
- package/dist/credentials.js +11 -9
- package/dist/emulation/promises.js +2 -2
- package/dist/emulation/shared.js +3 -1
- package/dist/emulation/sync.js +2 -2
- package/dist/emulation/watchers.js +9 -9
- package/dist/file.d.ts +1 -13
- package/dist/file.js +7 -23
- package/dist/stats.d.ts +11 -7
- package/dist/stats.js +14 -15
- package/package.json +1 -1
- package/src/backends/overlay.ts +1 -1
- package/src/backends/port/fs.ts +1 -5
- package/src/config.ts +2 -2
- package/src/credentials.ts +24 -9
- package/src/emulation/promises.ts +3 -3
- package/src/emulation/shared.ts +3 -1
- package/src/emulation/sync.ts +2 -2
- package/src/emulation/watchers.ts +10 -9
- package/src/file.ts +7 -37
- package/src/stats.ts +21 -18
- package/tests/fs/watch.test.ts +21 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { File } from '../file.js';
|
|
2
2
|
import type { FileSystemMetadata } from '../filesystem.js';
|
|
3
3
|
import { FileSystem } from '../filesystem.js';
|
|
4
|
-
import { Stats } from '../stats.js';
|
|
4
|
+
import type { Stats } from '../stats.js';
|
|
5
5
|
/**
|
|
6
6
|
* Configuration options for OverlayFS instances.
|
|
7
7
|
*/
|
|
@@ -4,7 +4,7 @@ import { type MountConfiguration } from '../../config.js';
|
|
|
4
4
|
import { File } from '../../file.js';
|
|
5
5
|
import { FileSystem, type FileSystemMetadata } from '../../filesystem.js';
|
|
6
6
|
import { Async } from '../../mixins/async.js';
|
|
7
|
-
import { Stats
|
|
7
|
+
import { Stats } from '../../stats.js';
|
|
8
8
|
import type { Backend, FilesystemOf } from '../backend.js';
|
|
9
9
|
import * as RPC from './rpc.js';
|
|
10
10
|
type FileMethods = Omit<ExtractProperties<File, (...args: any[]) => Promise<any>>, typeof Symbol.asyncDispose>;
|
|
@@ -37,7 +37,6 @@ export declare class PortFile extends File {
|
|
|
37
37
|
chmodSync(): void;
|
|
38
38
|
utimes(atime: Date, mtime: Date): Promise<void>;
|
|
39
39
|
utimesSync(): void;
|
|
40
|
-
_setType(type: FileType): Promise<void>;
|
|
41
40
|
_setTypeSync(): void;
|
|
42
41
|
close(): Promise<void>;
|
|
43
42
|
closeSync(): void;
|
package/dist/backends/port/fs.js
CHANGED
package/dist/config.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { checkOptions, isBackend, isBackendConfig } from './backends/backend.js';
|
|
2
|
-
import {
|
|
2
|
+
import { useCredentials } from './credentials.js';
|
|
3
3
|
import { DeviceFS } from './devices.js';
|
|
4
4
|
import * as cache from './emulation/cache.js';
|
|
5
5
|
import { config } from './emulation/config.js';
|
|
@@ -87,7 +87,7 @@ async function mount(path, mount) {
|
|
|
87
87
|
export async function configure(configuration) {
|
|
88
88
|
const uid = 'uid' in configuration ? configuration.uid || 0 : 0;
|
|
89
89
|
const gid = 'gid' in configuration ? configuration.gid || 0 : 0;
|
|
90
|
-
|
|
90
|
+
useCredentials({ uid, gid });
|
|
91
91
|
cache.stats.isEnabled = configuration.cacheStats ?? false;
|
|
92
92
|
cache.paths.isEnabled = configuration.cachePaths ?? false;
|
|
93
93
|
config.checkAccess = !configuration.disableAccessChecks;
|
package/dist/credentials.d.ts
CHANGED
|
@@ -10,9 +10,21 @@ export interface Credentials {
|
|
|
10
10
|
sgid: number;
|
|
11
11
|
euid: number;
|
|
12
12
|
egid: number;
|
|
13
|
+
/**
|
|
14
|
+
* List of group IDs.
|
|
15
|
+
*/
|
|
16
|
+
groups: number[];
|
|
13
17
|
}
|
|
14
18
|
export declare const credentials: Credentials;
|
|
19
|
+
export interface CredentialInit {
|
|
20
|
+
uid: number;
|
|
21
|
+
gid: number;
|
|
22
|
+
suid?: number;
|
|
23
|
+
sgid?: number;
|
|
24
|
+
euid?: number;
|
|
25
|
+
egid?: number;
|
|
26
|
+
}
|
|
15
27
|
/**
|
|
16
|
-
*
|
|
28
|
+
* Uses credentials from the provided uid and gid.
|
|
17
29
|
*/
|
|
18
|
-
export declare
|
|
30
|
+
export declare function useCredentials(source: CredentialInit): void;
|
package/dist/credentials.js
CHANGED
|
@@ -5,15 +5,17 @@ export const credentials = {
|
|
|
5
5
|
sgid: 0,
|
|
6
6
|
euid: 0,
|
|
7
7
|
egid: 0,
|
|
8
|
+
groups: [],
|
|
8
9
|
};
|
|
9
10
|
/**
|
|
10
|
-
*
|
|
11
|
+
* Uses credentials from the provided uid and gid.
|
|
11
12
|
*/
|
|
12
|
-
export
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
};
|
|
13
|
+
export function useCredentials(source) {
|
|
14
|
+
Object.assign(credentials, {
|
|
15
|
+
suid: source.uid,
|
|
16
|
+
sgid: source.gid,
|
|
17
|
+
euid: source.uid,
|
|
18
|
+
egid: source.gid,
|
|
19
|
+
...source,
|
|
20
|
+
});
|
|
21
|
+
}
|
|
@@ -710,7 +710,7 @@ export async function link(targetPath, linkPath) {
|
|
|
710
710
|
if (config.checkAccess && !(await stat(dirname(linkPath))).hasAccess(constants.W_OK)) {
|
|
711
711
|
throw ErrnoError.With('EACCES', dirname(linkPath), 'link');
|
|
712
712
|
}
|
|
713
|
-
if (config.checkAccess && !(await fs.stat(path)).hasAccess(constants.
|
|
713
|
+
if (config.checkAccess && !(await fs.stat(path)).hasAccess(constants.R_OK)) {
|
|
714
714
|
throw ErrnoError.With('EACCES', path, 'link');
|
|
715
715
|
}
|
|
716
716
|
return await fs.link(path, link.path);
|
|
@@ -737,7 +737,7 @@ export async function symlink(target, path, type = 'file') {
|
|
|
737
737
|
}
|
|
738
738
|
const handle = __addDisposableResource(env_5, await _open(path, 'w+', 0o644, false), true);
|
|
739
739
|
await handle.writeFile(target.toString());
|
|
740
|
-
await handle.file.
|
|
740
|
+
await handle.file.chmod(constants.S_IFLNK);
|
|
741
741
|
}
|
|
742
742
|
catch (e_5) {
|
|
743
743
|
env_5.error = e_5;
|
package/dist/emulation/shared.js
CHANGED
package/dist/emulation/sync.js
CHANGED
|
@@ -510,7 +510,7 @@ export function linkSync(targetPath, linkPath) {
|
|
|
510
510
|
throw ErrnoError.With('EXDEV', linkPath, 'link');
|
|
511
511
|
}
|
|
512
512
|
try {
|
|
513
|
-
if (config.checkAccess && !fs.statSync(path).hasAccess(constants.
|
|
513
|
+
if (config.checkAccess && !fs.statSync(path).hasAccess(constants.R_OK)) {
|
|
514
514
|
throw ErrnoError.With('EACCES', path, 'link');
|
|
515
515
|
}
|
|
516
516
|
return fs.linkSync(path, linkPath);
|
|
@@ -535,7 +535,7 @@ export function symlinkSync(target, path, type = 'file') {
|
|
|
535
535
|
}
|
|
536
536
|
writeFileSync(path, target.toString());
|
|
537
537
|
const file = _openSync(path, 'r+', 0o644, false);
|
|
538
|
-
file.
|
|
538
|
+
file.chmodSync(constants.S_IFLNK);
|
|
539
539
|
}
|
|
540
540
|
symlinkSync;
|
|
541
541
|
export function readlinkSync(path, options) {
|
|
@@ -2,7 +2,7 @@ import { EventEmitter } from 'eventemitter3';
|
|
|
2
2
|
import { ErrnoError } from '../error.js';
|
|
3
3
|
import { isStatsEqual } from '../stats.js';
|
|
4
4
|
import { normalizePath } from '../utils.js';
|
|
5
|
-
import {
|
|
5
|
+
import { basename, dirname } from './path.js';
|
|
6
6
|
import { statSync } from './sync.js';
|
|
7
7
|
/**
|
|
8
8
|
* Base class for file system watchers.
|
|
@@ -130,22 +130,22 @@ export function removeWatcher(path, watcher) {
|
|
|
130
130
|
}
|
|
131
131
|
}
|
|
132
132
|
export function emitChange(eventType, filename) {
|
|
133
|
-
|
|
133
|
+
filename = normalizePath(filename);
|
|
134
134
|
// Notify watchers on the specific file
|
|
135
|
-
if (watchers.has(
|
|
136
|
-
for (const watcher of watchers.get(
|
|
135
|
+
if (watchers.has(filename)) {
|
|
136
|
+
for (const watcher of watchers.get(filename)) {
|
|
137
137
|
watcher.emit('change', eventType, basename(filename));
|
|
138
138
|
}
|
|
139
139
|
}
|
|
140
140
|
// Notify watchers on parent directories if they are watching recursively
|
|
141
|
-
let parent =
|
|
142
|
-
while (parent !== normalizedFilename
|
|
141
|
+
let parent = filename, normalizedFilename;
|
|
142
|
+
while (parent !== normalizedFilename) {
|
|
143
|
+
normalizedFilename = parent;
|
|
144
|
+
parent = dirname(parent);
|
|
143
145
|
if (watchers.has(parent)) {
|
|
144
146
|
for (const watcher of watchers.get(parent)) {
|
|
145
|
-
watcher.emit('change', eventType,
|
|
147
|
+
watcher.emit('change', eventType, filename.slice(parent.length + (parent == '/' ? 0 : 1)));
|
|
146
148
|
}
|
|
147
149
|
}
|
|
148
|
-
normalizedFilename = parent;
|
|
149
|
-
parent = dirname(parent);
|
|
150
150
|
}
|
|
151
151
|
}
|
package/dist/file.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { FileReadResult } from 'node:fs/promises';
|
|
2
2
|
import type { FileSystem } from './filesystem.js';
|
|
3
3
|
import './polyfills.js';
|
|
4
|
-
import { Stats
|
|
4
|
+
import { Stats } from './stats.js';
|
|
5
5
|
/**
|
|
6
6
|
Typescript does not include a type declaration for resizable array buffers.
|
|
7
7
|
It has been standardized into ECMAScript though
|
|
@@ -123,16 +123,6 @@ export declare abstract class File<FS extends FileSystem = FileSystem> {
|
|
|
123
123
|
* Change the file timestamps of the file.
|
|
124
124
|
*/
|
|
125
125
|
abstract utimesSync(atime: Date, mtime: Date): void;
|
|
126
|
-
/**
|
|
127
|
-
* Set the file type
|
|
128
|
-
* @internal
|
|
129
|
-
*/
|
|
130
|
-
abstract _setType(type: FileType): Promise<void>;
|
|
131
|
-
/**
|
|
132
|
-
* Set the file type
|
|
133
|
-
* @internal
|
|
134
|
-
*/
|
|
135
|
-
abstract _setTypeSync(type: FileType): void;
|
|
136
126
|
}
|
|
137
127
|
/**
|
|
138
128
|
* An implementation of `File` that operates completely in-memory.
|
|
@@ -243,8 +233,6 @@ export declare class PreloadFile<FS extends FileSystem> extends File<FS> {
|
|
|
243
233
|
chownSync(uid: number, gid: number): void;
|
|
244
234
|
utimes(atime: Date, mtime: Date): Promise<void>;
|
|
245
235
|
utimesSync(atime: Date, mtime: Date): void;
|
|
246
|
-
_setType(type: FileType): Promise<void>;
|
|
247
|
-
_setTypeSync(type: FileType): void;
|
|
248
236
|
}
|
|
249
237
|
/**
|
|
250
238
|
* For the file systems which do not sync to anything.
|
package/dist/file.js
CHANGED
|
@@ -2,7 +2,7 @@ import { config } from './emulation/config.js';
|
|
|
2
2
|
import { O_APPEND, O_CREAT, O_EXCL, O_RDONLY, O_RDWR, O_SYNC, O_TRUNC, O_WRONLY, S_IFMT, size_max } from './emulation/constants.js';
|
|
3
3
|
import { Errno, ErrnoError } from './error.js';
|
|
4
4
|
import './polyfills.js';
|
|
5
|
-
import { Stats } from './stats.js';
|
|
5
|
+
import { _chown, Stats } from './stats.js';
|
|
6
6
|
const validFlags = ['r', 'r+', 'rs', 'rs+', 'w', 'wx', 'w+', 'wx+', 'a', 'ax', 'a+', 'ax+'];
|
|
7
7
|
export function parseFlag(flag) {
|
|
8
8
|
if (typeof flag === 'number') {
|
|
@@ -411,8 +411,8 @@ export class PreloadFile extends File {
|
|
|
411
411
|
throw ErrnoError.With('EBADF', this.path, 'File.chmod');
|
|
412
412
|
}
|
|
413
413
|
this.dirty = true;
|
|
414
|
-
this.stats.
|
|
415
|
-
if (config.syncImmediately)
|
|
414
|
+
this.stats.mode = (this.stats.mode & (mode > S_IFMT ? ~S_IFMT : S_IFMT)) | mode;
|
|
415
|
+
if (config.syncImmediately || mode > S_IFMT)
|
|
416
416
|
await this.sync();
|
|
417
417
|
}
|
|
418
418
|
chmodSync(mode) {
|
|
@@ -420,8 +420,8 @@ export class PreloadFile extends File {
|
|
|
420
420
|
throw ErrnoError.With('EBADF', this.path, 'File.chmod');
|
|
421
421
|
}
|
|
422
422
|
this.dirty = true;
|
|
423
|
-
this.stats.
|
|
424
|
-
if (config.syncImmediately)
|
|
423
|
+
this.stats.mode = (this.stats.mode & (mode > S_IFMT ? ~S_IFMT : S_IFMT)) | mode;
|
|
424
|
+
if (config.syncImmediately || mode > S_IFMT)
|
|
425
425
|
this.syncSync();
|
|
426
426
|
}
|
|
427
427
|
async chown(uid, gid) {
|
|
@@ -429,7 +429,7 @@ export class PreloadFile extends File {
|
|
|
429
429
|
throw ErrnoError.With('EBADF', this.path, 'File.chown');
|
|
430
430
|
}
|
|
431
431
|
this.dirty = true;
|
|
432
|
-
this.stats
|
|
432
|
+
_chown(this.stats, uid, gid);
|
|
433
433
|
if (config.syncImmediately)
|
|
434
434
|
await this.sync();
|
|
435
435
|
}
|
|
@@ -438,7 +438,7 @@ export class PreloadFile extends File {
|
|
|
438
438
|
throw ErrnoError.With('EBADF', this.path, 'File.chown');
|
|
439
439
|
}
|
|
440
440
|
this.dirty = true;
|
|
441
|
-
this.stats
|
|
441
|
+
_chown(this.stats, uid, gid);
|
|
442
442
|
if (config.syncImmediately)
|
|
443
443
|
this.syncSync();
|
|
444
444
|
}
|
|
@@ -462,22 +462,6 @@ export class PreloadFile extends File {
|
|
|
462
462
|
if (config.syncImmediately)
|
|
463
463
|
this.syncSync();
|
|
464
464
|
}
|
|
465
|
-
async _setType(type) {
|
|
466
|
-
if (this.closed) {
|
|
467
|
-
throw ErrnoError.With('EBADF', this.path, 'File._setType');
|
|
468
|
-
}
|
|
469
|
-
this.dirty = true;
|
|
470
|
-
this.stats.mode = (this.stats.mode & ~S_IFMT) | type;
|
|
471
|
-
await this.sync();
|
|
472
|
-
}
|
|
473
|
-
_setTypeSync(type) {
|
|
474
|
-
if (this.closed) {
|
|
475
|
-
throw ErrnoError.With('EBADF', this.path, 'File._setType');
|
|
476
|
-
}
|
|
477
|
-
this.dirty = true;
|
|
478
|
-
this.stats.mode = (this.stats.mode & ~S_IFMT) | type;
|
|
479
|
-
this.syncSync();
|
|
480
|
-
}
|
|
481
465
|
}
|
|
482
466
|
/**
|
|
483
467
|
* For the file systems which do not sync to anything.
|
package/dist/stats.d.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import type * as Node from 'node:fs';
|
|
2
|
-
import { type Credentials } from './credentials.js';
|
|
3
2
|
import { S_IFDIR, S_IFLNK, S_IFREG } from './emulation/constants.js';
|
|
4
3
|
/**
|
|
5
4
|
* Indicates the type of a file. Applied to 'mode'.
|
|
@@ -43,6 +42,10 @@ export interface StatsLike<T extends number | bigint = number | bigint> {
|
|
|
43
42
|
* Inode number
|
|
44
43
|
*/
|
|
45
44
|
ino: T;
|
|
45
|
+
/**
|
|
46
|
+
* Number of hard links
|
|
47
|
+
*/
|
|
48
|
+
nlink: T;
|
|
46
49
|
}
|
|
47
50
|
/**
|
|
48
51
|
* Provides information about a particular entry in the file system.
|
|
@@ -136,28 +139,29 @@ export declare abstract class StatsCommon<T extends number | bigint> implements
|
|
|
136
139
|
* @internal
|
|
137
140
|
*/
|
|
138
141
|
hasAccess(mode: number): boolean;
|
|
139
|
-
/**
|
|
140
|
-
* Convert the current stats object into a credentials object
|
|
141
|
-
* @internal
|
|
142
|
-
*/
|
|
143
|
-
cred(uid?: number, gid?: number): Credentials;
|
|
144
142
|
/**
|
|
145
143
|
* Change the mode of the file.
|
|
146
144
|
* We use this helper function to prevent messing up the type of the file.
|
|
147
145
|
* @internal
|
|
146
|
+
* @deprecated This will be removed in the next minor release since it is internal
|
|
148
147
|
*/
|
|
149
148
|
chmod(mode: number): void;
|
|
150
149
|
/**
|
|
151
150
|
* Change the owner user/group of the file.
|
|
152
151
|
* This function makes sure it is a valid UID/GID (that is, a 32 unsigned int)
|
|
153
152
|
* @internal
|
|
153
|
+
* @deprecated This will be removed in the next minor release since it is internal
|
|
154
154
|
*/
|
|
155
|
-
chown(uid: number
|
|
155
|
+
chown(uid: number, gid: number): void;
|
|
156
156
|
get atimeNs(): bigint;
|
|
157
157
|
get mtimeNs(): bigint;
|
|
158
158
|
get ctimeNs(): bigint;
|
|
159
159
|
get birthtimeNs(): bigint;
|
|
160
160
|
}
|
|
161
|
+
/**
|
|
162
|
+
* @hidden @internal
|
|
163
|
+
*/
|
|
164
|
+
export declare function _chown(stats: Partial<StatsLike<number>>, uid: number, gid: number): void;
|
|
161
165
|
/**
|
|
162
166
|
* Implementation of Node's `Stats`.
|
|
163
167
|
*
|
package/dist/stats.js
CHANGED
|
@@ -124,7 +124,7 @@ export class StatsCommon {
|
|
|
124
124
|
perm |= X_OK;
|
|
125
125
|
}
|
|
126
126
|
// Group permissions
|
|
127
|
-
if (credentials.gid === this.gid) {
|
|
127
|
+
if (credentials.gid === this.gid || credentials.groups.includes(Number(this.gid))) {
|
|
128
128
|
if (this.mode & S_IRGRP)
|
|
129
129
|
perm |= R_OK;
|
|
130
130
|
if (this.mode & S_IWGRP)
|
|
@@ -142,24 +142,11 @@ export class StatsCommon {
|
|
|
142
142
|
// Perform the access check
|
|
143
143
|
return (perm & mode) === mode;
|
|
144
144
|
}
|
|
145
|
-
/**
|
|
146
|
-
* Convert the current stats object into a credentials object
|
|
147
|
-
* @internal
|
|
148
|
-
*/
|
|
149
|
-
cred(uid = Number(this.uid), gid = Number(this.gid)) {
|
|
150
|
-
return {
|
|
151
|
-
uid,
|
|
152
|
-
gid,
|
|
153
|
-
suid: Number(this.uid),
|
|
154
|
-
sgid: Number(this.gid),
|
|
155
|
-
euid: uid,
|
|
156
|
-
egid: gid,
|
|
157
|
-
};
|
|
158
|
-
}
|
|
159
145
|
/**
|
|
160
146
|
* Change the mode of the file.
|
|
161
147
|
* We use this helper function to prevent messing up the type of the file.
|
|
162
148
|
* @internal
|
|
149
|
+
* @deprecated This will be removed in the next minor release since it is internal
|
|
163
150
|
*/
|
|
164
151
|
chmod(mode) {
|
|
165
152
|
this.mode = this._convert((this.mode & S_IFMT) | mode);
|
|
@@ -168,6 +155,7 @@ export class StatsCommon {
|
|
|
168
155
|
* Change the owner user/group of the file.
|
|
169
156
|
* This function makes sure it is a valid UID/GID (that is, a 32 unsigned int)
|
|
170
157
|
* @internal
|
|
158
|
+
* @deprecated This will be removed in the next minor release since it is internal
|
|
171
159
|
*/
|
|
172
160
|
chown(uid, gid) {
|
|
173
161
|
uid = Number(uid);
|
|
@@ -192,6 +180,17 @@ export class StatsCommon {
|
|
|
192
180
|
return BigInt(this.birthtimeMs) * 1000n;
|
|
193
181
|
}
|
|
194
182
|
}
|
|
183
|
+
/**
|
|
184
|
+
* @hidden @internal
|
|
185
|
+
*/
|
|
186
|
+
export function _chown(stats, uid, gid) {
|
|
187
|
+
if (!isNaN(uid) && 0 <= uid && uid < 2 ** 32) {
|
|
188
|
+
stats.uid = uid;
|
|
189
|
+
}
|
|
190
|
+
if (!isNaN(gid) && 0 <= gid && gid < 2 ** 32) {
|
|
191
|
+
stats.gid = gid;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
195
194
|
/**
|
|
196
195
|
* Implementation of Node's `Stats`.
|
|
197
196
|
*
|
package/package.json
CHANGED
package/src/backends/overlay.ts
CHANGED
|
@@ -5,7 +5,7 @@ import { PreloadFile, parseFlag } from '../file.js';
|
|
|
5
5
|
import type { FileSystemMetadata } from '../filesystem.js';
|
|
6
6
|
import { FileSystem } from '../filesystem.js';
|
|
7
7
|
import { Mutexed } from '../mixins/mutexed.js';
|
|
8
|
-
import { Stats } from '../stats.js';
|
|
8
|
+
import type { Stats } from '../stats.js';
|
|
9
9
|
import { decodeUTF8, encodeUTF8 } from '../utils.js';
|
|
10
10
|
import type { Backend } from './backend.js';
|
|
11
11
|
/** @internal */
|
package/src/backends/port/fs.ts
CHANGED
|
@@ -6,7 +6,7 @@ import { Errno, ErrnoError } from '../../error.js';
|
|
|
6
6
|
import { File } from '../../file.js';
|
|
7
7
|
import { FileSystem, type FileSystemMetadata } from '../../filesystem.js';
|
|
8
8
|
import { Async } from '../../mixins/async.js';
|
|
9
|
-
import { Stats
|
|
9
|
+
import { Stats } from '../../stats.js';
|
|
10
10
|
import type { Backend, FilesystemOf } from '../backend.js';
|
|
11
11
|
import { InMemory } from '../memory.js';
|
|
12
12
|
import * as RPC from './rpc.js';
|
|
@@ -104,10 +104,6 @@ export class PortFile extends File {
|
|
|
104
104
|
this._throwNoSync('utimes');
|
|
105
105
|
}
|
|
106
106
|
|
|
107
|
-
public _setType(type: FileType): Promise<void> {
|
|
108
|
-
return this.rpc('_setType', type);
|
|
109
|
-
}
|
|
110
|
-
|
|
111
107
|
public _setTypeSync(): void {
|
|
112
108
|
this._throwNoSync('_setType');
|
|
113
109
|
}
|
package/src/config.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Backend, BackendConfiguration, FilesystemOf, SharedConfig } from './backends/backend.js';
|
|
2
2
|
import { checkOptions, isBackend, isBackendConfig } from './backends/backend.js';
|
|
3
|
-
import {
|
|
3
|
+
import { useCredentials } from './credentials.js';
|
|
4
4
|
import { DeviceFS } from './devices.js';
|
|
5
5
|
import * as cache from './emulation/cache.js';
|
|
6
6
|
import { config } from './emulation/config.js';
|
|
@@ -188,7 +188,7 @@ export async function configure<T extends ConfigMounts>(configuration: Partial<C
|
|
|
188
188
|
const uid = 'uid' in configuration ? configuration.uid || 0 : 0;
|
|
189
189
|
const gid = 'gid' in configuration ? configuration.gid || 0 : 0;
|
|
190
190
|
|
|
191
|
-
|
|
191
|
+
useCredentials({ uid, gid });
|
|
192
192
|
|
|
193
193
|
cache.stats.isEnabled = configuration.cacheStats ?? false;
|
|
194
194
|
cache.paths.isEnabled = configuration.cachePaths ?? false;
|
package/src/credentials.ts
CHANGED
|
@@ -10,6 +10,10 @@ export interface Credentials {
|
|
|
10
10
|
sgid: number;
|
|
11
11
|
euid: number;
|
|
12
12
|
egid: number;
|
|
13
|
+
/**
|
|
14
|
+
* List of group IDs.
|
|
15
|
+
*/
|
|
16
|
+
groups: number[];
|
|
13
17
|
}
|
|
14
18
|
|
|
15
19
|
export const credentials: Credentials = {
|
|
@@ -19,16 +23,27 @@ export const credentials: Credentials = {
|
|
|
19
23
|
sgid: 0,
|
|
20
24
|
euid: 0,
|
|
21
25
|
egid: 0,
|
|
26
|
+
groups: [],
|
|
22
27
|
};
|
|
23
28
|
|
|
29
|
+
export interface CredentialInit {
|
|
30
|
+
uid: number;
|
|
31
|
+
gid: number;
|
|
32
|
+
suid?: number;
|
|
33
|
+
sgid?: number;
|
|
34
|
+
euid?: number;
|
|
35
|
+
egid?: number;
|
|
36
|
+
}
|
|
37
|
+
|
|
24
38
|
/**
|
|
25
|
-
*
|
|
39
|
+
* Uses credentials from the provided uid and gid.
|
|
26
40
|
*/
|
|
27
|
-
export
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
};
|
|
41
|
+
export function useCredentials(source: CredentialInit): void {
|
|
42
|
+
Object.assign(credentials, {
|
|
43
|
+
suid: source.uid,
|
|
44
|
+
sgid: source.gid,
|
|
45
|
+
euid: source.uid,
|
|
46
|
+
egid: source.gid,
|
|
47
|
+
...source,
|
|
48
|
+
});
|
|
49
|
+
}
|
|
@@ -471,7 +471,7 @@ export async function unlink(path: fs.PathLike): Promise<void> {
|
|
|
471
471
|
path = normalizePath(path);
|
|
472
472
|
const { fs, path: resolved } = resolveMount(path);
|
|
473
473
|
try {
|
|
474
|
-
if (config.checkAccess && !(await (cache.stats.get(path) || fs.stat(resolved)))
|
|
474
|
+
if (config.checkAccess && !(await (cache.stats.get(path) || fs.stat(resolved))).hasAccess(constants.W_OK)) {
|
|
475
475
|
throw ErrnoError.With('EACCES', resolved, 'unlink');
|
|
476
476
|
}
|
|
477
477
|
await fs.unlink(resolved);
|
|
@@ -795,7 +795,7 @@ export async function link(targetPath: fs.PathLike, linkPath: fs.PathLike): Prom
|
|
|
795
795
|
throw ErrnoError.With('EACCES', dirname(linkPath), 'link');
|
|
796
796
|
}
|
|
797
797
|
|
|
798
|
-
if (config.checkAccess && !(await fs.stat(path)).hasAccess(constants.
|
|
798
|
+
if (config.checkAccess && !(await fs.stat(path)).hasAccess(constants.R_OK)) {
|
|
799
799
|
throw ErrnoError.With('EACCES', path, 'link');
|
|
800
800
|
}
|
|
801
801
|
return await fs.link(path, link.path);
|
|
@@ -822,7 +822,7 @@ export async function symlink(target: fs.PathLike, path: fs.PathLike, type: fs.s
|
|
|
822
822
|
|
|
823
823
|
await using handle = await _open(path, 'w+', 0o644, false);
|
|
824
824
|
await handle.writeFile(target.toString());
|
|
825
|
-
await handle.file.
|
|
825
|
+
await handle.file.chmod(constants.S_IFLNK);
|
|
826
826
|
}
|
|
827
827
|
symlink satisfies typeof promises.symlink;
|
|
828
828
|
|
package/src/emulation/shared.ts
CHANGED
package/src/emulation/sync.ts
CHANGED
|
@@ -524,7 +524,7 @@ export function linkSync(targetPath: fs.PathLike, linkPath: fs.PathLike): void {
|
|
|
524
524
|
throw ErrnoError.With('EXDEV', linkPath, 'link');
|
|
525
525
|
}
|
|
526
526
|
try {
|
|
527
|
-
if (config.checkAccess && !fs.statSync(path).hasAccess(constants.
|
|
527
|
+
if (config.checkAccess && !fs.statSync(path).hasAccess(constants.R_OK)) {
|
|
528
528
|
throw ErrnoError.With('EACCES', path, 'link');
|
|
529
529
|
}
|
|
530
530
|
return fs.linkSync(path, linkPath);
|
|
@@ -550,7 +550,7 @@ export function symlinkSync(target: fs.PathLike, path: fs.PathLike, type: fs.sym
|
|
|
550
550
|
|
|
551
551
|
writeFileSync(path, target.toString());
|
|
552
552
|
const file = _openSync(path, 'r+', 0o644, false);
|
|
553
|
-
file.
|
|
553
|
+
file.chmodSync(constants.S_IFLNK);
|
|
554
554
|
}
|
|
555
555
|
symlinkSync satisfies typeof fs.symlinkSync;
|
|
556
556
|
|
|
@@ -4,7 +4,7 @@ import type * as fs from 'node:fs';
|
|
|
4
4
|
import { ErrnoError } from '../error.js';
|
|
5
5
|
import { isStatsEqual, type Stats } from '../stats.js';
|
|
6
6
|
import { normalizePath } from '../utils.js';
|
|
7
|
-
import {
|
|
7
|
+
import { basename, dirname } from './path.js';
|
|
8
8
|
import { statSync } from './sync.js';
|
|
9
9
|
|
|
10
10
|
/**
|
|
@@ -171,23 +171,24 @@ export function removeWatcher(path: string, watcher: FSWatcher) {
|
|
|
171
171
|
}
|
|
172
172
|
|
|
173
173
|
export function emitChange(eventType: fs.WatchEventType, filename: string) {
|
|
174
|
-
|
|
174
|
+
filename = normalizePath(filename);
|
|
175
175
|
// Notify watchers on the specific file
|
|
176
|
-
if (watchers.has(
|
|
177
|
-
for (const watcher of watchers.get(
|
|
176
|
+
if (watchers.has(filename)) {
|
|
177
|
+
for (const watcher of watchers.get(filename)!) {
|
|
178
178
|
watcher.emit('change', eventType, basename(filename));
|
|
179
179
|
}
|
|
180
180
|
}
|
|
181
181
|
|
|
182
182
|
// Notify watchers on parent directories if they are watching recursively
|
|
183
|
-
let parent =
|
|
184
|
-
|
|
183
|
+
let parent = filename,
|
|
184
|
+
normalizedFilename;
|
|
185
|
+
while (parent !== normalizedFilename) {
|
|
186
|
+
normalizedFilename = parent;
|
|
187
|
+
parent = dirname(parent);
|
|
185
188
|
if (watchers.has(parent)) {
|
|
186
189
|
for (const watcher of watchers.get(parent)!) {
|
|
187
|
-
watcher.emit('change', eventType,
|
|
190
|
+
watcher.emit('change', eventType, filename.slice(parent.length + (parent == '/' ? 0 : 1)));
|
|
188
191
|
}
|
|
189
192
|
}
|
|
190
|
-
normalizedFilename = parent;
|
|
191
|
-
parent = dirname(parent);
|
|
192
193
|
}
|
|
193
194
|
}
|
package/src/file.ts
CHANGED
|
@@ -4,7 +4,7 @@ import { O_APPEND, O_CREAT, O_EXCL, O_RDONLY, O_RDWR, O_SYNC, O_TRUNC, O_WRONLY,
|
|
|
4
4
|
import { Errno, ErrnoError } from './error.js';
|
|
5
5
|
import type { FileSystem } from './filesystem.js';
|
|
6
6
|
import './polyfills.js';
|
|
7
|
-
import {
|
|
7
|
+
import { _chown, Stats } from './stats.js';
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
Typescript does not include a type declaration for resizable array buffers.
|
|
@@ -251,18 +251,6 @@ export abstract class File<FS extends FileSystem = FileSystem> {
|
|
|
251
251
|
* Change the file timestamps of the file.
|
|
252
252
|
*/
|
|
253
253
|
public abstract utimesSync(atime: Date, mtime: Date): void;
|
|
254
|
-
|
|
255
|
-
/**
|
|
256
|
-
* Set the file type
|
|
257
|
-
* @internal
|
|
258
|
-
*/
|
|
259
|
-
public abstract _setType(type: FileType): Promise<void>;
|
|
260
|
-
|
|
261
|
-
/**
|
|
262
|
-
* Set the file type
|
|
263
|
-
* @internal
|
|
264
|
-
*/
|
|
265
|
-
public abstract _setTypeSync(type: FileType): void;
|
|
266
254
|
}
|
|
267
255
|
|
|
268
256
|
/**
|
|
@@ -573,8 +561,8 @@ export class PreloadFile<FS extends FileSystem> extends File<FS> {
|
|
|
573
561
|
throw ErrnoError.With('EBADF', this.path, 'File.chmod');
|
|
574
562
|
}
|
|
575
563
|
this.dirty = true;
|
|
576
|
-
this.stats.
|
|
577
|
-
if (config.syncImmediately) await this.sync();
|
|
564
|
+
this.stats.mode = (this.stats.mode & (mode > S_IFMT ? ~S_IFMT : S_IFMT)) | mode;
|
|
565
|
+
if (config.syncImmediately || mode > S_IFMT) await this.sync();
|
|
578
566
|
}
|
|
579
567
|
|
|
580
568
|
public chmodSync(mode: number): void {
|
|
@@ -582,8 +570,8 @@ export class PreloadFile<FS extends FileSystem> extends File<FS> {
|
|
|
582
570
|
throw ErrnoError.With('EBADF', this.path, 'File.chmod');
|
|
583
571
|
}
|
|
584
572
|
this.dirty = true;
|
|
585
|
-
this.stats.
|
|
586
|
-
if (config.syncImmediately) this.syncSync();
|
|
573
|
+
this.stats.mode = (this.stats.mode & (mode > S_IFMT ? ~S_IFMT : S_IFMT)) | mode;
|
|
574
|
+
if (config.syncImmediately || mode > S_IFMT) this.syncSync();
|
|
587
575
|
}
|
|
588
576
|
|
|
589
577
|
public async chown(uid: number, gid: number): Promise<void> {
|
|
@@ -591,7 +579,7 @@ export class PreloadFile<FS extends FileSystem> extends File<FS> {
|
|
|
591
579
|
throw ErrnoError.With('EBADF', this.path, 'File.chown');
|
|
592
580
|
}
|
|
593
581
|
this.dirty = true;
|
|
594
|
-
this.stats
|
|
582
|
+
_chown(this.stats, uid, gid);
|
|
595
583
|
if (config.syncImmediately) await this.sync();
|
|
596
584
|
}
|
|
597
585
|
|
|
@@ -600,7 +588,7 @@ export class PreloadFile<FS extends FileSystem> extends File<FS> {
|
|
|
600
588
|
throw ErrnoError.With('EBADF', this.path, 'File.chown');
|
|
601
589
|
}
|
|
602
590
|
this.dirty = true;
|
|
603
|
-
this.stats
|
|
591
|
+
_chown(this.stats, uid, gid);
|
|
604
592
|
if (config.syncImmediately) this.syncSync();
|
|
605
593
|
}
|
|
606
594
|
|
|
@@ -623,24 +611,6 @@ export class PreloadFile<FS extends FileSystem> extends File<FS> {
|
|
|
623
611
|
this.stats.mtime = mtime;
|
|
624
612
|
if (config.syncImmediately) this.syncSync();
|
|
625
613
|
}
|
|
626
|
-
|
|
627
|
-
public async _setType(type: FileType): Promise<void> {
|
|
628
|
-
if (this.closed) {
|
|
629
|
-
throw ErrnoError.With('EBADF', this.path, 'File._setType');
|
|
630
|
-
}
|
|
631
|
-
this.dirty = true;
|
|
632
|
-
this.stats.mode = (this.stats.mode & ~S_IFMT) | type;
|
|
633
|
-
await this.sync();
|
|
634
|
-
}
|
|
635
|
-
|
|
636
|
-
public _setTypeSync(type: FileType): void {
|
|
637
|
-
if (this.closed) {
|
|
638
|
-
throw ErrnoError.With('EBADF', this.path, 'File._setType');
|
|
639
|
-
}
|
|
640
|
-
this.dirty = true;
|
|
641
|
-
this.stats.mode = (this.stats.mode & ~S_IFMT) | type;
|
|
642
|
-
this.syncSync();
|
|
643
|
-
}
|
|
644
614
|
}
|
|
645
615
|
|
|
646
616
|
/**
|
package/src/stats.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type * as Node from 'node:fs';
|
|
2
|
-
import { credentials
|
|
2
|
+
import { credentials } from './credentials.js';
|
|
3
3
|
import {
|
|
4
4
|
R_OK,
|
|
5
5
|
S_IFBLK,
|
|
@@ -67,6 +67,10 @@ export interface StatsLike<T extends number | bigint = number | bigint> {
|
|
|
67
67
|
* Inode number
|
|
68
68
|
*/
|
|
69
69
|
ino: T;
|
|
70
|
+
/**
|
|
71
|
+
* Number of hard links
|
|
72
|
+
*/
|
|
73
|
+
nlink: T;
|
|
70
74
|
}
|
|
71
75
|
|
|
72
76
|
/**
|
|
@@ -258,7 +262,7 @@ export abstract class StatsCommon<T extends number | bigint> implements Node.Sta
|
|
|
258
262
|
}
|
|
259
263
|
|
|
260
264
|
// Group permissions
|
|
261
|
-
if (credentials.gid === this.gid) {
|
|
265
|
+
if (credentials.gid === this.gid || credentials.groups.includes(Number(this.gid))) {
|
|
262
266
|
if (this.mode & S_IRGRP) perm |= R_OK;
|
|
263
267
|
if (this.mode & S_IWGRP) perm |= W_OK;
|
|
264
268
|
if (this.mode & S_IXGRP) perm |= X_OK;
|
|
@@ -273,25 +277,11 @@ export abstract class StatsCommon<T extends number | bigint> implements Node.Sta
|
|
|
273
277
|
return (perm & mode) === mode;
|
|
274
278
|
}
|
|
275
279
|
|
|
276
|
-
/**
|
|
277
|
-
* Convert the current stats object into a credentials object
|
|
278
|
-
* @internal
|
|
279
|
-
*/
|
|
280
|
-
public cred(uid: number = Number(this.uid), gid: number = Number(this.gid)): Credentials {
|
|
281
|
-
return {
|
|
282
|
-
uid,
|
|
283
|
-
gid,
|
|
284
|
-
suid: Number(this.uid),
|
|
285
|
-
sgid: Number(this.gid),
|
|
286
|
-
euid: uid,
|
|
287
|
-
egid: gid,
|
|
288
|
-
};
|
|
289
|
-
}
|
|
290
|
-
|
|
291
280
|
/**
|
|
292
281
|
* Change the mode of the file.
|
|
293
282
|
* We use this helper function to prevent messing up the type of the file.
|
|
294
283
|
* @internal
|
|
284
|
+
* @deprecated This will be removed in the next minor release since it is internal
|
|
295
285
|
*/
|
|
296
286
|
public chmod(mode: number): void {
|
|
297
287
|
this.mode = this._convert((this.mode & S_IFMT) | mode);
|
|
@@ -301,8 +291,9 @@ export abstract class StatsCommon<T extends number | bigint> implements Node.Sta
|
|
|
301
291
|
* Change the owner user/group of the file.
|
|
302
292
|
* This function makes sure it is a valid UID/GID (that is, a 32 unsigned int)
|
|
303
293
|
* @internal
|
|
294
|
+
* @deprecated This will be removed in the next minor release since it is internal
|
|
304
295
|
*/
|
|
305
|
-
public chown(uid: number
|
|
296
|
+
public chown(uid: number, gid: number): void {
|
|
306
297
|
uid = Number(uid);
|
|
307
298
|
gid = Number(gid);
|
|
308
299
|
if (!isNaN(uid) && 0 <= uid && uid < 2 ** 32) {
|
|
@@ -330,6 +321,18 @@ export abstract class StatsCommon<T extends number | bigint> implements Node.Sta
|
|
|
330
321
|
}
|
|
331
322
|
}
|
|
332
323
|
|
|
324
|
+
/**
|
|
325
|
+
* @hidden @internal
|
|
326
|
+
*/
|
|
327
|
+
export function _chown(stats: Partial<StatsLike<number>>, uid: number, gid: number) {
|
|
328
|
+
if (!isNaN(uid) && 0 <= uid && uid < 2 ** 32) {
|
|
329
|
+
stats.uid = uid;
|
|
330
|
+
}
|
|
331
|
+
if (!isNaN(gid) && 0 <= gid && gid < 2 ** 32) {
|
|
332
|
+
stats.gid = gid;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
333
336
|
/**
|
|
334
337
|
* Implementation of Node's `Stats`.
|
|
335
338
|
*
|
package/tests/fs/watch.test.ts
CHANGED
|
@@ -115,6 +115,27 @@ suite('Watch Features', () => {
|
|
|
115
115
|
resolve();
|
|
116
116
|
})();
|
|
117
117
|
|
|
118
|
+
await fs.promises.unlink(tempFile);
|
|
119
|
+
await promise;
|
|
120
|
+
});
|
|
121
|
+
test('fs.promises.watch should detect file creations recursively', async () => {
|
|
122
|
+
const rootDir = '/';
|
|
123
|
+
const subDir = `${testDir}sub-dir`;
|
|
124
|
+
const tempFile = `${subDir}/tempFile.txt`;
|
|
125
|
+
await fs.promises.mkdir(subDir);
|
|
126
|
+
const watcher = fs.promises.watch(rootDir);
|
|
127
|
+
|
|
128
|
+
await fs.promises.writeFile(tempFile, 'Temporary content');
|
|
129
|
+
const { promise, resolve } = Promise.withResolvers<void>();
|
|
130
|
+
(async () => {
|
|
131
|
+
for await (const event of watcher) {
|
|
132
|
+
assert.equal(event.eventType, 'rename');
|
|
133
|
+
assert.equal(event.filename, tempFile.substring(rootDir.length));
|
|
134
|
+
break;
|
|
135
|
+
}
|
|
136
|
+
resolve();
|
|
137
|
+
})();
|
|
138
|
+
|
|
118
139
|
await fs.promises.unlink(tempFile);
|
|
119
140
|
await promise;
|
|
120
141
|
});
|