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