1// Flags: --expose-internals
2'use strict';
3
4// This tests that fs.access and fs.accessSync works as expected
5// and the errors thrown from these APIs include the desired properties
6
7const common = require('../common');
8if (!common.isWindows && process.getuid() === 0)
9  common.skip('as this test should not be run as `root`');
10
11if (common.isIBMi)
12  common.skip('IBMi has a different access permission mechanism');
13
14const assert = require('assert');
15const fs = require('fs');
16const path = require('path');
17
18const { internalBinding } = require('internal/test/binding');
19const { UV_ENOENT } = internalBinding('uv');
20
21const tmpdir = require('../common/tmpdir');
22const doesNotExist = path.join(tmpdir.path, '__this_should_not_exist');
23const readOnlyFile = path.join(tmpdir.path, 'read_only_file');
24const readWriteFile = path.join(tmpdir.path, 'read_write_file');
25
26function createFileWithPerms(file, mode) {
27  fs.writeFileSync(file, '');
28  fs.chmodSync(file, mode);
29}
30
31tmpdir.refresh();
32createFileWithPerms(readOnlyFile, 0o444);
33createFileWithPerms(readWriteFile, 0o666);
34
35// On non-Windows supported platforms, fs.access(readOnlyFile, W_OK, ...)
36// always succeeds if node runs as the super user, which is sometimes the
37// case for tests running on our continuous testing platform agents.
38//
39// In this case, this test tries to change its process user id to a
40// non-superuser user so that the test that checks for write access to a
41// read-only file can be more meaningful.
42//
43// The change of user id is done after creating the fixtures files for the same
44// reason: the test may be run as the superuser within a directory in which
45// only the superuser can create files, and thus it may need superuser
46// privileges to create them.
47//
48// There's not really any point in resetting the process' user id to 0 after
49// changing it to 'nobody', since in the case that the test runs without
50// superuser privilege, it is not possible to change its process user id to
51// superuser.
52//
53// It can prevent the test from removing files created before the change of user
54// id, but that's fine. In this case, it is the responsibility of the
55// continuous integration platform to take care of that.
56let hasWriteAccessForReadonlyFile = false;
57if (!common.isWindows && process.getuid() === 0) {
58  hasWriteAccessForReadonlyFile = true;
59  try {
60    process.setuid('nobody');
61    hasWriteAccessForReadonlyFile = false;
62  } catch {
63    // Continue regardless of error.
64  }
65}
66
67assert.strictEqual(typeof fs.F_OK, 'number');
68assert.strictEqual(typeof fs.R_OK, 'number');
69assert.strictEqual(typeof fs.W_OK, 'number');
70assert.strictEqual(typeof fs.X_OK, 'number');
71
72const throwNextTick = (e) => { process.nextTick(() => { throw e; }); };
73
74fs.access(__filename, common.mustCall(function(...args) {
75  assert.deepStrictEqual(args, [null]);
76}));
77fs.promises.access(__filename)
78  .then(common.mustCall())
79  .catch(throwNextTick);
80fs.access(__filename, fs.R_OK, common.mustCall(function(...args) {
81  assert.deepStrictEqual(args, [null]);
82}));
83fs.promises.access(__filename, fs.R_OK)
84  .then(common.mustCall())
85  .catch(throwNextTick);
86fs.access(readOnlyFile, fs.R_OK, common.mustCall(function(...args) {
87  assert.deepStrictEqual(args, [null]);
88}));
89fs.promises.access(readOnlyFile, fs.R_OK)
90  .then(common.mustCall())
91  .catch(throwNextTick);
92
93{
94  const expectedError = (err) => {
95    assert.notStrictEqual(err, null);
96    assert.strictEqual(err.code, 'ENOENT');
97    assert.strictEqual(err.path, doesNotExist);
98  };
99  fs.access(doesNotExist, common.mustCall(expectedError));
100  fs.promises.access(doesNotExist)
101    .then(common.mustNotCall(), common.mustCall(expectedError))
102    .catch(throwNextTick);
103}
104
105{
106  function expectedError(err) {
107    assert.strictEqual(this, undefined);
108    if (hasWriteAccessForReadonlyFile) {
109      assert.ifError(err);
110    } else {
111      assert.notStrictEqual(err, null);
112      assert.strictEqual(err.path, readOnlyFile);
113    }
114  }
115  fs.access(readOnlyFile, fs.W_OK, common.mustCall(expectedError));
116  fs.promises.access(readOnlyFile, fs.W_OK)
117    .then(common.mustNotCall(), common.mustCall(expectedError))
118    .catch(throwNextTick);
119}
120
121{
122  const expectedError = (err) => {
123    assert.strictEqual(err.code, 'ERR_INVALID_ARG_TYPE');
124    assert.ok(err instanceof TypeError);
125    return true;
126  };
127  assert.throws(
128    () => { fs.access(100, fs.F_OK, common.mustNotCall()); },
129    expectedError
130  );
131
132  fs.promises.access(100, fs.F_OK)
133    .then(common.mustNotCall(), common.mustCall(expectedError))
134    .catch(throwNextTick);
135}
136
137assert.throws(
138  () => {
139    fs.access(__filename, fs.F_OK);
140  },
141  {
142    code: 'ERR_INVALID_ARG_TYPE',
143    name: 'TypeError'
144  });
145
146assert.throws(
147  () => {
148    fs.access(__filename, fs.F_OK, common.mustNotMutateObjectDeep({}));
149  },
150  {
151    code: 'ERR_INVALID_ARG_TYPE',
152    name: 'TypeError'
153  });
154
155// Regular access should not throw.
156fs.accessSync(__filename);
157const mode = fs.R_OK | fs.W_OK;
158fs.accessSync(readWriteFile, mode);
159
160// Invalid modes should throw.
161[
162  false,
163  1n,
164  { [Symbol.toPrimitive]() { return fs.R_OK; } },
165  [1],
166  'r',
167].forEach((mode, i) => {
168  console.log(mode, i);
169  assert.throws(
170    () => fs.access(readWriteFile, mode, common.mustNotCall()),
171    {
172      code: 'ERR_INVALID_ARG_TYPE',
173    }
174  );
175  assert.throws(
176    () => fs.accessSync(readWriteFile, mode),
177    {
178      code: 'ERR_INVALID_ARG_TYPE',
179    }
180  );
181});
182
183// Out of range modes should throw
184[
185  -1,
186  8,
187  Infinity,
188  NaN,
189].forEach((mode, i) => {
190  console.log(mode, i);
191  assert.throws(
192    () => fs.access(readWriteFile, mode, common.mustNotCall()),
193    {
194      code: 'ERR_OUT_OF_RANGE',
195    }
196  );
197  assert.throws(
198    () => fs.accessSync(readWriteFile, mode),
199    {
200      code: 'ERR_OUT_OF_RANGE',
201    }
202  );
203});
204
205assert.throws(
206  () => { fs.accessSync(doesNotExist); },
207  (err) => {
208    assert.strictEqual(err.code, 'ENOENT');
209    assert.strictEqual(err.path, doesNotExist);
210    assert.strictEqual(
211      err.message,
212      `ENOENT: no such file or directory, access '${doesNotExist}'`
213    );
214    assert.strictEqual(err.constructor, Error);
215    assert.strictEqual(err.syscall, 'access');
216    assert.strictEqual(err.errno, UV_ENOENT);
217    return true;
218  }
219);
220
221assert.throws(
222  () => { fs.accessSync(Buffer.from(doesNotExist)); },
223  (err) => {
224    assert.strictEqual(err.code, 'ENOENT');
225    assert.strictEqual(err.path, doesNotExist);
226    assert.strictEqual(
227      err.message,
228      `ENOENT: no such file or directory, access '${doesNotExist}'`
229    );
230    assert.strictEqual(err.constructor, Error);
231    assert.strictEqual(err.syscall, 'access');
232    assert.strictEqual(err.errno, UV_ENOENT);
233    return true;
234  }
235);
236