@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.
@@ -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
  /**
@@ -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.uid !== stats.uid) {
166
- this.uid = stats.uid;
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, S_IRWXG, S_IRWXO, S_IRWXU, size_max } from './emulation/constants.js';
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
- // Mask for
117
- const adjusted = (credentials.uid == this.uid ? S_IRWXU : 0) | (credentials.gid == this.gid ? S_IRWXG : 0) | S_IRWXO;
118
- return (mode & this.mode & adjusted) == mode;
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zenfs/core",
3
- "version": "1.2.9",
3
+ "version": "1.2.10",
4
4
  "description": "A filesystem, anywhere",
5
5
  "funding": {
6
6
  "type": "individual",
@@ -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.uid !== stats.uid) {
112
- this.uid = stats.uid;
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 { S_IFBLK, S_IFCHR, S_IFDIR, S_IFIFO, S_IFLNK, S_IFMT, S_IFREG, S_IFSOCK, S_IRWXG, S_IRWXO, S_IRWXU, size_max } from './emulation/constants.js';
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
- // Mask for
230
- const adjusted = (credentials.uid == this.uid ? S_IRWXU : 0) | (credentials.gid == this.gid ? S_IRWXG : 0) | S_IRWXO;
231
- return (mode & this.mode & adjusted) == mode;
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
  /**
@@ -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());
@@ -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
- suite('times', () => {
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) == _toUnixTimestamp(stats.atime));
15
- assert(_toUnixTimestamp(mtime) == _toUnixTimestamp(stats.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
- // don't close this fd
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 () => {