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