@zenfs/core 1.2.9 → 1.2.10
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/emulation/sync.d.ts +1 -1
- package/dist/emulation/sync.js +2 -2
- package/dist/file.js +1 -1
- package/dist/inode.js +3 -3
- package/dist/stats.js +30 -5
- package/package.json +1 -1
- package/src/emulation/sync.ts +2 -2
- package/src/file.ts +1 -1
- package/src/inode.ts +3 -3
- package/src/stats.ts +47 -5
- package/tests/fs/stat.test.ts +42 -0
- package/tests/fs/times.test.ts +9 -20
package/dist/emulation/sync.d.ts
CHANGED
|
@@ -64,7 +64,7 @@ export declare function writeFileSync(path: fs.PathOrFileDescriptor, data: FileC
|
|
|
64
64
|
* Asynchronously append data to a file, creating the file if it not yet exists.
|
|
65
65
|
* @option encoding Defaults to `'utf8'`.
|
|
66
66
|
* @option mode Defaults to `0644`.
|
|
67
|
-
* @option flag Defaults to `'a'`.
|
|
67
|
+
* @option flag Defaults to `'a+'`.
|
|
68
68
|
*/
|
|
69
69
|
export declare function appendFileSync(filename: fs.PathOrFileDescriptor, data: FileContents, _options?: fs.WriteFileOptions): void;
|
|
70
70
|
/**
|
package/dist/emulation/sync.js
CHANGED
|
@@ -272,12 +272,12 @@ writeFileSync;
|
|
|
272
272
|
* Asynchronously append data to a file, creating the file if it not yet exists.
|
|
273
273
|
* @option encoding Defaults to `'utf8'`.
|
|
274
274
|
* @option mode Defaults to `0644`.
|
|
275
|
-
* @option flag Defaults to `'a'`.
|
|
275
|
+
* @option flag Defaults to `'a+'`.
|
|
276
276
|
*/
|
|
277
277
|
export function appendFileSync(filename, data, _options = {}) {
|
|
278
278
|
const env_4 = { stack: [], error: void 0, hasError: false };
|
|
279
279
|
try {
|
|
280
|
-
const options = normalizeOptions(_options, 'utf8', 'a', 0o644);
|
|
280
|
+
const options = normalizeOptions(_options, 'utf8', 'a+', 0o644);
|
|
281
281
|
const flag = parseFlag(options.flag);
|
|
282
282
|
if (!isAppendable(flag)) {
|
|
283
283
|
throw new ErrnoError(Errno.EINVAL, 'Flag passed to appendFile must allow for appending.');
|
package/dist/file.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
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';
|
|
2
1
|
import { config } from './emulation/config.js';
|
|
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
5
|
import { Stats } from './stats.js';
|
package/dist/inode.js
CHANGED
|
@@ -36,8 +36,8 @@ var __setFunctionName = (this && this.__setFunctionName) || function (f, name, p
|
|
|
36
36
|
if (typeof name === "symbol") name = name.description ? "[".concat(name.description, "]") : "";
|
|
37
37
|
return Object.defineProperty(f, "name", { configurable: true, value: prefix ? "".concat(prefix, " ", name) : name });
|
|
38
38
|
};
|
|
39
|
+
import { deserialize, serialize, sizeof, struct, types as t } from 'utilium';
|
|
39
40
|
import { Stats } from './stats.js';
|
|
40
|
-
import { types as t, struct, sizeof, serialize, deserialize } from 'utilium';
|
|
41
41
|
/**
|
|
42
42
|
* Room inode
|
|
43
43
|
* @hidden
|
|
@@ -162,8 +162,8 @@ let Inode = (() => {
|
|
|
162
162
|
this.uid = stats.uid;
|
|
163
163
|
hasChanged = true;
|
|
164
164
|
}
|
|
165
|
-
if (this.
|
|
166
|
-
this.
|
|
165
|
+
if (this.gid !== stats.gid) {
|
|
166
|
+
this.gid = stats.gid;
|
|
167
167
|
hasChanged = true;
|
|
168
168
|
}
|
|
169
169
|
if (this.atimeMs !== stats.atimeMs) {
|
package/dist/stats.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { credentials } from './credentials.js';
|
|
2
|
-
import { S_IFBLK, S_IFCHR, S_IFDIR, S_IFIFO, S_IFLNK, S_IFMT, S_IFREG, S_IFSOCK,
|
|
2
|
+
import { R_OK, S_IFBLK, S_IFCHR, S_IFDIR, S_IFIFO, S_IFLNK, S_IFMT, S_IFREG, S_IFSOCK, S_IRGRP, S_IROTH, S_IRUSR, S_IWGRP, S_IWOTH, S_IWUSR, S_IXGRP, S_IXOTH, S_IXUSR, size_max, W_OK, X_OK, } from './emulation/constants.js';
|
|
3
3
|
/**
|
|
4
4
|
* Provides information about a particular entry in the file system.
|
|
5
5
|
* Common code used by both Stats and BigIntStats.
|
|
@@ -110,12 +110,37 @@ export class StatsCommon {
|
|
|
110
110
|
*/
|
|
111
111
|
hasAccess(mode) {
|
|
112
112
|
if (credentials.euid === 0 || credentials.egid === 0) {
|
|
113
|
-
//Running as root
|
|
113
|
+
// Running as root
|
|
114
114
|
return true;
|
|
115
115
|
}
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
116
|
+
let perm = 0;
|
|
117
|
+
// Owner permissions
|
|
118
|
+
if (credentials.uid === this.uid) {
|
|
119
|
+
if (this.mode & S_IRUSR)
|
|
120
|
+
perm |= R_OK;
|
|
121
|
+
if (this.mode & S_IWUSR)
|
|
122
|
+
perm |= W_OK;
|
|
123
|
+
if (this.mode & S_IXUSR)
|
|
124
|
+
perm |= X_OK;
|
|
125
|
+
}
|
|
126
|
+
// Group permissions
|
|
127
|
+
if (credentials.gid === this.gid) {
|
|
128
|
+
if (this.mode & S_IRGRP)
|
|
129
|
+
perm |= R_OK;
|
|
130
|
+
if (this.mode & S_IWGRP)
|
|
131
|
+
perm |= W_OK;
|
|
132
|
+
if (this.mode & S_IXGRP)
|
|
133
|
+
perm |= X_OK;
|
|
134
|
+
}
|
|
135
|
+
// Others permissions
|
|
136
|
+
if (this.mode & S_IROTH)
|
|
137
|
+
perm |= R_OK;
|
|
138
|
+
if (this.mode & S_IWOTH)
|
|
139
|
+
perm |= W_OK;
|
|
140
|
+
if (this.mode & S_IXOTH)
|
|
141
|
+
perm |= X_OK;
|
|
142
|
+
// Perform the access check
|
|
143
|
+
return (perm & mode) === mode;
|
|
119
144
|
}
|
|
120
145
|
/**
|
|
121
146
|
* Convert the current stats object into a credentials object
|
package/package.json
CHANGED
package/src/emulation/sync.ts
CHANGED
|
@@ -243,10 +243,10 @@ writeFileSync satisfies typeof fs.writeFileSync;
|
|
|
243
243
|
* Asynchronously append data to a file, creating the file if it not yet exists.
|
|
244
244
|
* @option encoding Defaults to `'utf8'`.
|
|
245
245
|
* @option mode Defaults to `0644`.
|
|
246
|
-
* @option flag Defaults to `'a'`.
|
|
246
|
+
* @option flag Defaults to `'a+'`.
|
|
247
247
|
*/
|
|
248
248
|
export function appendFileSync(filename: fs.PathOrFileDescriptor, data: FileContents, _options: fs.WriteFileOptions = {}): void {
|
|
249
|
-
const options = normalizeOptions(_options, 'utf8', 'a', 0o644);
|
|
249
|
+
const options = normalizeOptions(_options, 'utf8', 'a+', 0o644);
|
|
250
250
|
const flag = parseFlag(options.flag);
|
|
251
251
|
if (!isAppendable(flag)) {
|
|
252
252
|
throw new ErrnoError(Errno.EINVAL, 'Flag passed to appendFile must allow for appending.');
|
package/src/file.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { FileReadResult } from 'node:fs/promises';
|
|
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
2
|
import { config } from './emulation/config.js';
|
|
3
|
+
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';
|
|
4
4
|
import { Errno, ErrnoError } from './error.js';
|
|
5
5
|
import type { FileSystem } from './filesystem.js';
|
|
6
6
|
import './polyfills.js';
|
package/src/inode.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
import { deserialize, serialize, sizeof, struct, types as t } from 'utilium';
|
|
1
2
|
import { Stats, type StatsLike } from './stats.js';
|
|
2
|
-
import { types as t, struct, sizeof, serialize, deserialize } from 'utilium';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Alias for an ino.
|
|
@@ -108,8 +108,8 @@ export class Inode implements StatsLike {
|
|
|
108
108
|
hasChanged = true;
|
|
109
109
|
}
|
|
110
110
|
|
|
111
|
-
if (this.
|
|
112
|
-
this.
|
|
111
|
+
if (this.gid !== stats.gid) {
|
|
112
|
+
this.gid = stats.gid;
|
|
113
113
|
hasChanged = true;
|
|
114
114
|
}
|
|
115
115
|
|
package/src/stats.ts
CHANGED
|
@@ -1,6 +1,28 @@
|
|
|
1
1
|
import type * as Node from 'node:fs';
|
|
2
2
|
import { credentials, type Credentials } from './credentials.js';
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
R_OK,
|
|
5
|
+
S_IFBLK,
|
|
6
|
+
S_IFCHR,
|
|
7
|
+
S_IFDIR,
|
|
8
|
+
S_IFIFO,
|
|
9
|
+
S_IFLNK,
|
|
10
|
+
S_IFMT,
|
|
11
|
+
S_IFREG,
|
|
12
|
+
S_IFSOCK,
|
|
13
|
+
S_IRGRP,
|
|
14
|
+
S_IROTH,
|
|
15
|
+
S_IRUSR,
|
|
16
|
+
S_IWGRP,
|
|
17
|
+
S_IWOTH,
|
|
18
|
+
S_IWUSR,
|
|
19
|
+
S_IXGRP,
|
|
20
|
+
S_IXOTH,
|
|
21
|
+
S_IXUSR,
|
|
22
|
+
size_max,
|
|
23
|
+
W_OK,
|
|
24
|
+
X_OK,
|
|
25
|
+
} from './emulation/constants.js';
|
|
4
26
|
|
|
5
27
|
/**
|
|
6
28
|
* Indicates the type of a file. Applied to 'mode'.
|
|
@@ -222,13 +244,33 @@ export abstract class StatsCommon<T extends number | bigint> implements Node.Sta
|
|
|
222
244
|
*/
|
|
223
245
|
public hasAccess(mode: number): boolean {
|
|
224
246
|
if (credentials.euid === 0 || credentials.egid === 0) {
|
|
225
|
-
//Running as root
|
|
247
|
+
// Running as root
|
|
226
248
|
return true;
|
|
227
249
|
}
|
|
228
250
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
251
|
+
let perm = 0;
|
|
252
|
+
|
|
253
|
+
// Owner permissions
|
|
254
|
+
if (credentials.uid === this.uid) {
|
|
255
|
+
if (this.mode & S_IRUSR) perm |= R_OK;
|
|
256
|
+
if (this.mode & S_IWUSR) perm |= W_OK;
|
|
257
|
+
if (this.mode & S_IXUSR) perm |= X_OK;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// Group permissions
|
|
261
|
+
if (credentials.gid === this.gid) {
|
|
262
|
+
if (this.mode & S_IRGRP) perm |= R_OK;
|
|
263
|
+
if (this.mode & S_IWGRP) perm |= W_OK;
|
|
264
|
+
if (this.mode & S_IXGRP) perm |= X_OK;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// Others permissions
|
|
268
|
+
if (this.mode & S_IROTH) perm |= R_OK;
|
|
269
|
+
if (this.mode & S_IWOTH) perm |= W_OK;
|
|
270
|
+
if (this.mode & S_IXOTH) perm |= X_OK;
|
|
271
|
+
|
|
272
|
+
// Perform the access check
|
|
273
|
+
return (perm & mode) === mode;
|
|
232
274
|
}
|
|
233
275
|
|
|
234
276
|
/**
|
package/tests/fs/stat.test.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import assert from 'node:assert';
|
|
2
2
|
import { suite, test } from 'node:test';
|
|
3
|
+
import { credentials } from '../../dist/credentials.js';
|
|
3
4
|
import { Stats } from '../../dist/stats.js';
|
|
4
5
|
import { fs } from '../common.js';
|
|
5
6
|
|
|
@@ -34,6 +35,47 @@ suite('Stats', () => {
|
|
|
34
35
|
fs.close(fd);
|
|
35
36
|
});
|
|
36
37
|
|
|
38
|
+
test('hasAccess for non-root access', () => {
|
|
39
|
+
const newFile = 'new.txt';
|
|
40
|
+
|
|
41
|
+
fs.writeFileSync(newFile, 'hello', {
|
|
42
|
+
mode: 0o640, // allow group access
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
const prevCredentials = {
|
|
46
|
+
...credentials,
|
|
47
|
+
};
|
|
48
|
+
const uid = 33;
|
|
49
|
+
const nonRootCredentials = {
|
|
50
|
+
uid,
|
|
51
|
+
gid: uid,
|
|
52
|
+
euid: uid,
|
|
53
|
+
egid: uid,
|
|
54
|
+
suid: uid,
|
|
55
|
+
sgid: uid,
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
fs.chownSync(newFile, 0, nonRootCredentials.gid); // creating with root-user so that non-root user can access
|
|
59
|
+
|
|
60
|
+
Object.assign(credentials, nonRootCredentials);
|
|
61
|
+
const stat = fs.statSync(newFile);
|
|
62
|
+
|
|
63
|
+
assert.equal(stat.gid, nonRootCredentials.gid);
|
|
64
|
+
assert.equal(stat.uid, 0);
|
|
65
|
+
assert.equal(stat.hasAccess(fs.constants.R_OK), true);
|
|
66
|
+
assert.equal(stat.hasAccess(fs.constants.W_OK), false);
|
|
67
|
+
assert.equal(stat.hasAccess(fs.constants.X_OK), false);
|
|
68
|
+
// changing group
|
|
69
|
+
|
|
70
|
+
Object.assign(credentials, { ...nonRootCredentials, gid: 44 });
|
|
71
|
+
|
|
72
|
+
assert.equal(stat.hasAccess(fs.constants.R_OK), false);
|
|
73
|
+
assert.equal(stat.hasAccess(fs.constants.W_OK), false);
|
|
74
|
+
assert.equal(stat.hasAccess(fs.constants.X_OK), false);
|
|
75
|
+
|
|
76
|
+
Object.assign(credentials, prevCredentials);
|
|
77
|
+
});
|
|
78
|
+
|
|
37
79
|
test('stat file', async () => {
|
|
38
80
|
const stats = await fs.promises.stat(existing_file);
|
|
39
81
|
assert(!stats.isDirectory());
|
package/tests/fs/times.test.ts
CHANGED
|
@@ -5,14 +5,14 @@ import { ErrnoError } from '../../dist/error.js';
|
|
|
5
5
|
import { _toUnixTimestamp } from '../../dist/utils.js';
|
|
6
6
|
import { fs } from '../common.js';
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
const path = 'x.txt';
|
|
8
|
+
const path = 'x.txt';
|
|
10
9
|
|
|
10
|
+
suite('times', () => {
|
|
11
11
|
function expect_assert(resource: string | number, atime: Date | number, mtime: Date | number) {
|
|
12
12
|
const stats = typeof resource == 'string' ? fs.statSync(resource) : fs.fstatSync(resource);
|
|
13
13
|
// check up to single-second precision since sub-second precision is OS and fs dependent
|
|
14
|
-
assert(_toUnixTimestamp(atime)
|
|
15
|
-
assert(_toUnixTimestamp(mtime)
|
|
14
|
+
assert.equal(_toUnixTimestamp(atime), _toUnixTimestamp(stats.atime));
|
|
15
|
+
assert.equal(_toUnixTimestamp(mtime), _toUnixTimestamp(stats.mtime));
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
async function runTest(atime: Date | number, mtime: Date | number): Promise<void> {
|
|
@@ -24,8 +24,7 @@ suite('times', () => {
|
|
|
24
24
|
assert.strictEqual(error.code, 'ENOENT');
|
|
25
25
|
});
|
|
26
26
|
|
|
27
|
-
|
|
28
|
-
const handle = await fs.promises.open(path, 'r');
|
|
27
|
+
await using handle = await fs.promises.open(path, 'r');
|
|
29
28
|
|
|
30
29
|
await handle.utimes(atime, mtime);
|
|
31
30
|
expect_assert(handle.fd, atime, mtime);
|
|
@@ -33,16 +32,6 @@ suite('times', () => {
|
|
|
33
32
|
fs.utimesSync(path, atime, mtime);
|
|
34
33
|
expect_assert(path, atime, mtime);
|
|
35
34
|
|
|
36
|
-
// some systems don't have futimes
|
|
37
|
-
// if there's an error, it be ENOSYS
|
|
38
|
-
try {
|
|
39
|
-
fs.futimesSync(handle.fd, atime, mtime);
|
|
40
|
-
expect_assert(handle.fd, atime, mtime);
|
|
41
|
-
} catch (error: any) {
|
|
42
|
-
assert(error instanceof ErrnoError);
|
|
43
|
-
assert.strictEqual(error.code, 'ENOSYS');
|
|
44
|
-
}
|
|
45
|
-
|
|
46
35
|
try {
|
|
47
36
|
fs.utimesSync('foobarbaz', atime, mtime);
|
|
48
37
|
} catch (error: any) {
|
|
@@ -59,11 +48,11 @@ suite('times', () => {
|
|
|
59
48
|
}
|
|
60
49
|
|
|
61
50
|
test('utimes works', async () => {
|
|
62
|
-
await runTest(new Date('1982/09/10 13:37:00'), new Date('1982/09/10 13:37:00'));
|
|
63
|
-
await runTest(new Date(), new Date());
|
|
64
|
-
await runTest(123456.789, 123456.789);
|
|
51
|
+
await test('new Date(...)', () => runTest(new Date('1982/09/10 13:37:00'), new Date('1982/09/10 13:37:00')));
|
|
52
|
+
await test('new Date()', () => runTest(new Date(), new Date()));
|
|
53
|
+
await test('number', () => runTest(123456.789, 123456.789));
|
|
65
54
|
const stats = fs.statSync(path);
|
|
66
|
-
await runTest(stats.atime, stats.mtime);
|
|
55
|
+
await test('from stats', () => runTest(stats.atime, stats.mtime));
|
|
67
56
|
});
|
|
68
57
|
|
|
69
58
|
test('read changes atime', async () => {
|