• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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  }
64}
65
66assert.strictEqual(typeof fs.F_OK, 'number');
67assert.strictEqual(typeof fs.R_OK, 'number');
68assert.strictEqual(typeof fs.W_OK, 'number');
69assert.strictEqual(typeof fs.X_OK, 'number');
70
71const throwNextTick = (e) => { process.nextTick(() => { throw e; }); };
72
73fs.access(__filename, common.mustCall(function(...args) {
74  assert.deepStrictEqual(args, [null]);
75}));
76fs.promises.access(__filename)
77  .then(common.mustCall())
78  .catch(throwNextTick);
79fs.access(__filename, fs.R_OK, common.mustCall(function(...args) {
80  assert.deepStrictEqual(args, [null]);
81}));
82fs.promises.access(__filename, fs.R_OK)
83  .then(common.mustCall())
84  .catch(throwNextTick);
85fs.access(readOnlyFile, fs.F_OK | fs.R_OK, common.mustCall(function(...args) {
86  assert.deepStrictEqual(args, [null]);
87}));
88fs.promises.access(readOnlyFile, fs.F_OK | fs.R_OK)
89  .then(common.mustCall())
90  .catch(throwNextTick);
91
92{
93  const expectedError = (err) => {
94    assert.notStrictEqual(err, null);
95    assert.strictEqual(err.code, 'ENOENT');
96    assert.strictEqual(err.path, doesNotExist);
97  };
98  fs.access(doesNotExist, common.mustCall(expectedError));
99  fs.promises.access(doesNotExist)
100    .then(common.mustNotCall(), common.mustCall(expectedError))
101    .catch(throwNextTick);
102}
103
104{
105  function expectedError(err) {
106    assert.strictEqual(this, undefined);
107    if (hasWriteAccessForReadonlyFile) {
108      assert.ifError(err);
109    } else {
110      assert.notStrictEqual(err, null);
111      assert.strictEqual(err.path, readOnlyFile);
112    }
113  }
114  fs.access(readOnlyFile, fs.W_OK, common.mustCall(expectedError));
115  fs.promises.access(readOnlyFile, fs.W_OK)
116    .then(common.mustNotCall(), common.mustCall(expectedError))
117    .catch(throwNextTick);
118}
119
120{
121  const expectedError = (err) => {
122    assert.strictEqual(err.code, 'ERR_INVALID_ARG_TYPE');
123    assert.ok(err instanceof TypeError);
124    return true;
125  };
126  assert.throws(
127    () => { fs.access(100, fs.F_OK, common.mustNotCall()); },
128    expectedError
129  );
130
131  fs.promises.access(100, fs.F_OK)
132    .then(common.mustNotCall(), common.mustCall(expectedError))
133    .catch(throwNextTick);
134}
135
136assert.throws(
137  () => {
138    fs.access(__filename, fs.F_OK);
139  },
140  {
141    code: 'ERR_INVALID_CALLBACK',
142    name: 'TypeError'
143  });
144
145assert.throws(
146  () => {
147    fs.access(__filename, fs.F_OK, {});
148  },
149  {
150    code: 'ERR_INVALID_CALLBACK',
151    name: 'TypeError'
152  });
153
154// Regular access should not throw.
155fs.accessSync(__filename);
156const mode = fs.F_OK | fs.R_OK | fs.W_OK;
157fs.accessSync(readWriteFile, mode);
158
159// Invalid modes should throw.
160[
161  false,
162  1n,
163  { [Symbol.toPrimitive]() { return fs.R_OK; } },
164  [1],
165  'r',
166].forEach((mode, i) => {
167  console.log(mode, i);
168  assert.throws(
169    () => fs.access(readWriteFile, mode, common.mustNotCall()),
170    {
171      code: 'ERR_INVALID_ARG_TYPE',
172      message: /"mode" argument.+integer/
173    }
174  );
175  assert.throws(
176    () => fs.accessSync(readWriteFile, mode),
177    {
178      code: 'ERR_INVALID_ARG_TYPE',
179      message: /"mode" argument.+integer/
180    }
181  );
182});
183
184// Out of range modes should throw
185[
186  -1,
187  8,
188  Infinity,
189  NaN,
190].forEach((mode, i) => {
191  console.log(mode, i);
192  assert.throws(
193    () => fs.access(readWriteFile, mode, common.mustNotCall()),
194    {
195      code: 'ERR_OUT_OF_RANGE',
196      message: /"mode".+It must be an integer >= 0 && <= 7/
197    }
198  );
199  assert.throws(
200    () => fs.accessSync(readWriteFile, mode),
201    {
202      code: 'ERR_OUT_OF_RANGE',
203      message: /"mode".+It must be an integer >= 0 && <= 7/
204    }
205  );
206});
207
208assert.throws(
209  () => { fs.accessSync(doesNotExist); },
210  (err) => {
211    assert.strictEqual(err.code, 'ENOENT');
212    assert.strictEqual(err.path, doesNotExist);
213    assert.strictEqual(
214      err.message,
215      `ENOENT: no such file or directory, access '${doesNotExist}'`
216    );
217    assert.strictEqual(err.constructor, Error);
218    assert.strictEqual(err.syscall, 'access');
219    assert.strictEqual(err.errno, UV_ENOENT);
220    return true;
221  }
222);
223
224assert.throws(
225  () => { fs.accessSync(Buffer.from(doesNotExist)); },
226  (err) => {
227    assert.strictEqual(err.code, 'ENOENT');
228    assert.strictEqual(err.path, doesNotExist);
229    assert.strictEqual(
230      err.message,
231      `ENOENT: no such file or directory, access '${doesNotExist}'`
232    );
233    assert.strictEqual(err.constructor, Error);
234    assert.strictEqual(err.syscall, 'access');
235    assert.strictEqual(err.errno, UV_ENOENT);
236    return true;
237  }
238);
239