Skip to content

Commit fc33bb7

Browse files
committed
test: add test for mkdir recursive on read-only fs
mkdirSync with { recursive: true } on a read-only filesystem incorrectly throws ENOENT instead of the expected EROFS error. Two test cases are included: - On Linux with passwordless sudo, mounts a real read-only tmpfs to verify the error code against the actual kernel behavior. - Uses internalBinding to mock binding.mkdir and simulate EROFS, ensuring the error propagates correctly without requiring root. Refs: nodejs#47098 Refs: nodejs#48105 Signed-off-by: Kenny Yeo <[email protected]>
1 parent 43d5058 commit fc33bb7

1 file changed

Lines changed: 58 additions & 0 deletions

File tree

test/parallel/test-fs-mkdir.js

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
'use strict';
2323
const common = require('../common');
2424
const assert = require('assert');
25+
const child_process = require('child_process');
2526
const fs = require('fs');
2627
const path = require('path');
2728
const { isMainThread } = require('worker_threads');
@@ -184,6 +185,63 @@ function nextdir() {
184185
}));
185186
}
186187

188+
// `mkdirp` when folder was readonly (EROFS) - mocked via internalBinding.
189+
// Verifies that EROFS is propagated correctly without requiring root/sudo.
190+
if (typeof internalBinding !== 'undefined') {
191+
const fsBinding = internalBinding('fs');
192+
const originalMkdir = fsBinding.mkdir;
193+
fsBinding.mkdir = function(p) {
194+
const err = new Error(`EROFS: read-only file system, mkdir '${p}'`);
195+
err.code = 'EROFS';
196+
err.syscall = 'mkdir';
197+
err.path = p;
198+
throw err;
199+
};
200+
try {
201+
const pathname = path.join(tmpdir.path, nextdir(), nextdir());
202+
assert.throws(
203+
() => { fs.mkdirSync(pathname, { recursive: true }); },
204+
{ code: 'EROFS', message: /EROFS:.*mkdir/, name: 'Error', syscall: 'mkdir' }
205+
);
206+
} finally {
207+
fsBinding.mkdir = originalMkdir;
208+
}
209+
}
210+
211+
// `mkdirp` when folder was readonly.
212+
if (common.isLinux) {
213+
const roTmpfsPath = path.join(tmpdir.path, 'ro-tmpfs');
214+
fs.mkdirSync(roTmpfsPath);
215+
216+
const { status, stderr } = child_process.spawnSync(
217+
'sudo', ['-n', 'mount', '-t', 'tmpfs', '-o', 'ro', 'tmpfs', roTmpfsPath],
218+
{ stdio: 'pipe', encoding: 'utf8' }
219+
);
220+
221+
if (status !== 0) {
222+
console.warn(
223+
'Cannot test EROFS: passwordless sudo required to mount read-only tmpfs. ' +
224+
`Mount failed with status ${status}: ${stderr}`
225+
);
226+
} else {
227+
try {
228+
const pathname = path.join(roTmpfsPath, nextdir());
229+
assert.throws(
230+
() => { fs.mkdirSync(pathname, { recursive: true }); },
231+
{
232+
code: 'EROFS',
233+
message: /EROFS:.*mkdir/,
234+
name: 'Error',
235+
syscall: 'mkdir',
236+
}
237+
);
238+
} finally {
239+
child_process.spawnSync('sudo', ['-n', 'umount', roTmpfsPath]);
240+
}
241+
}
242+
fs.rmdirSync(roTmpfsPath);
243+
}
244+
187245
// `mkdirp` when path is a file.
188246
{
189247
const pathname = tmpdir.resolve(nextdir(), nextdir());

0 commit comments

Comments
 (0)