From 44dd66ea936062231c55aaa378e01fe2a02e85ed Mon Sep 17 00:00:00 2001 From: Dmitry Volyntsev Date: Tue, 25 Nov 2025 19:08:10 -0800 Subject: [PATCH 1/2] FS: fixed fs.mkdir() and friends. --- external/njs_fs_module.c | 37 +++++++++---------- external/qjs_fs_module.c | 37 +++++++++---------- .../{mkdir2.t.mjs => mkdir_recursive.t.mjs} | 0 3 files changed, 36 insertions(+), 38 deletions(-) rename test/fs/{mkdir2.t.mjs => mkdir_recursive.t.mjs} (100%) diff --git a/external/njs_fs_module.c b/external/njs_fs_module.c index c56a685aa..33fb6111b 100644 --- a/external/njs_fs_module.c +++ b/external/njs_fs_module.c @@ -2950,28 +2950,27 @@ njs_fs_make_path(njs_vm_t *vm, char *path, mode_t md, njs_bool_t recursive, ret = mkdir(path, md); err = errno; - switch (ret) { - case 0: - break; - - case EACCES: - case ENOTDIR: - case EPERM: - goto failed; - - case EEXIST: - default: - ret = stat(path, &sb); - if (ret == 0) { - if (!S_ISDIR(sb.st_mode)) { - err = ENOTDIR; - goto failed; + if (ret != 0) { + switch (err) { + case EACCES: + case ENOTDIR: + case EPERM: + goto failed; + + case EEXIST: + default: + ret = stat(path, &sb); + if (ret == 0) { + if (!S_ISDIR(sb.st_mode)) { + err = ENOTDIR; + goto failed; + } + + break; } - break; + goto failed; } - - goto failed; } if (p == end) { diff --git a/external/qjs_fs_module.c b/external/qjs_fs_module.c index 4fcce40f8..9117072bc 100644 --- a/external/qjs_fs_module.c +++ b/external/qjs_fs_module.c @@ -513,28 +513,27 @@ qjs_fs_make_path(JSContext *cx, char *path, mode_t md, int recursive) ret = mkdir(path, md); err = errno; - switch (ret) { - case 0: - break; - - case EACCES: - case ENOTDIR: - case EPERM: - goto failed; - - case EEXIST: - default: - ret = stat(path, &sb); - if (ret == 0) { - if (!S_ISDIR(sb.st_mode)) { - err = ENOTDIR; - goto failed; + if (ret != 0) { + switch (err) { + case EACCES: + case ENOTDIR: + case EPERM: + goto failed; + + case EEXIST: + default: + ret = stat(path, &sb); + if (ret == 0) { + if (!S_ISDIR(sb.st_mode)) { + err = ENOTDIR; + goto failed; + } + + break; } - break; + goto failed; } - - goto failed; } if (p == end) { diff --git a/test/fs/mkdir2.t.mjs b/test/fs/mkdir_recursive.t.mjs similarity index 100% rename from test/fs/mkdir2.t.mjs rename to test/fs/mkdir_recursive.t.mjs From fec2c1fb53aed2d1b00ac5bc1ca4812fcb2d14da Mon Sep 17 00:00:00 2001 From: Dmitry Volyntsev Date: Tue, 25 Nov 2025 19:08:33 -0800 Subject: [PATCH 2/2] FS: fixed path restoration in fs.mkdir() and friends on error. --- external/njs_fs_module.c | 12 +++- external/qjs_fs_module.c | 12 +++- test/fs/mkdir_error_path.t.mjs | 122 +++++++++++++++++++++++++++++++++ 3 files changed, 140 insertions(+), 6 deletions(-) create mode 100644 test/fs/mkdir_error_path.t.mjs diff --git a/external/njs_fs_module.c b/external/njs_fs_module.c index 33fb6111b..903c095cc 100644 --- a/external/njs_fs_module.c +++ b/external/njs_fs_module.c @@ -2955,7 +2955,7 @@ njs_fs_make_path(njs_vm_t *vm, char *path, mode_t md, njs_bool_t recursive, case EACCES: case ENOTDIR: case EPERM: - goto failed; + goto failed_restore; case EEXIST: default: @@ -2963,13 +2963,13 @@ njs_fs_make_path(njs_vm_t *vm, char *path, mode_t md, njs_bool_t recursive, if (ret == 0) { if (!S_ISDIR(sb.st_mode)) { err = ENOTDIR; - goto failed; + goto failed_restore; } break; } - goto failed; + goto failed_restore; } } @@ -2983,6 +2983,12 @@ njs_fs_make_path(njs_vm_t *vm, char *path, mode_t md, njs_bool_t recursive, return NJS_OK; +failed_restore: + + if (p != end) { + path[p - path] = '/'; + } + failed: return njs_fs_error(vm, "mkdir", strerror(err), path, err, retval); diff --git a/external/qjs_fs_module.c b/external/qjs_fs_module.c index 9117072bc..af929db34 100644 --- a/external/qjs_fs_module.c +++ b/external/qjs_fs_module.c @@ -518,7 +518,7 @@ qjs_fs_make_path(JSContext *cx, char *path, mode_t md, int recursive) case EACCES: case ENOTDIR: case EPERM: - goto failed; + goto failed_restore; case EEXIST: default: @@ -526,13 +526,13 @@ qjs_fs_make_path(JSContext *cx, char *path, mode_t md, int recursive) if (ret == 0) { if (!S_ISDIR(sb.st_mode)) { err = ENOTDIR; - goto failed; + goto failed_restore; } break; } - goto failed; + goto failed_restore; } } @@ -546,6 +546,12 @@ qjs_fs_make_path(JSContext *cx, char *path, mode_t md, int recursive) return JS_UNDEFINED; +failed_restore: + + if (p != end) { + path[p - path] = '/'; + } + failed: return qjs_fs_error(cx, "mkdir", strerror(err), path, err); diff --git a/test/fs/mkdir_error_path.t.mjs b/test/fs/mkdir_error_path.t.mjs new file mode 100644 index 000000000..1d8b33c87 --- /dev/null +++ b/test/fs/mkdir_error_path.t.mjs @@ -0,0 +1,122 @@ +/*--- +includes: [compareArray.js, compatFs.js] +flags: [async] +---*/ + +var dname = `${test_dir}/mkdir_error_path`; + +let stages = []; + +var testSync = () => new Promise((resolve, reject) => { + try { + try { fs.unlinkSync(dname + '/a/b'); } catch (e) {} + try { fs.rmdirSync(dname + '/a'); } catch (e) {} + try { fs.rmdirSync(dname); } catch (e) {} + + fs.mkdirSync(dname + '/a', {recursive: true}); + fs.writeFileSync(dname + '/a/b', 'blocking file'); + + try { + fs.mkdirSync(dname + '/a/b/c/d', {recursive: true}); + reject(new Error('Expected ENOTDIR')); + } catch (e) { + if (e.code != 'ENOTDIR') { + reject(e); + } + + if (!e.path.includes('/c/d')) { + reject(new Error('Path truncated: ' + e.path)); + } + } + + fs.unlinkSync(dname + '/a/b'); + fs.rmdirSync(dname + '/a'); + fs.rmdirSync(dname); + + stages.push("mkdirSync"); + + resolve(); + } catch (e) { + reject(e); + } +}); + +var testCallback = () => new Promise((resolve, reject) => { + try { + try { fs.unlinkSync(dname + '/a/b'); } catch (e) {} + try { fs.rmdirSync(dname + '/a'); } catch (e) {} + try { fs.rmdirSync(dname); } catch (e) {} + + fs.mkdir(dname + '/a', {recursive: true}, (err) => { + if (err) { + reject(err); + return; + } + + fs.writeFile(dname + '/a/b', 'blocking file', (err) => { + if (err) { + reject(err); + return; + } + + fs.mkdir(dname + '/a/b/c/d', {recursive: true}, (err) => { + if (!err || err.code != 'ENOTDIR') { + reject(new Error('Expected ENOTDIR')); + return; + } + + if (!err.path.includes('/c/d')) { + reject(new Error('Path truncated: ' + err.path)); + return; + } + + fs.unlinkSync(dname + '/a/b'); + fs.rmdirSync(dname + '/a'); + fs.rmdirSync(dname); + + stages.push("mkdir"); + + resolve(); + }); + }); + }); + } catch (e) { + reject(e); + } +}); + +let testFsp = () => Promise.resolve() +.then(() => { + try { fs.unlinkSync(dname + '/a/b'); } catch (e) {} + try { fs.rmdirSync(dname + '/a'); } catch (e) {} + try { fs.rmdirSync(dname); } catch (e) {} +}) +.then(() => fsp.mkdir(dname + '/a', {recursive: true})) +.then(() => fsp.writeFile(dname + '/a/b', 'blocking file')) +.then(() => fsp.mkdir(dname + '/a/b/c/d', {recursive: true})) +.then(() => { + throw new Error('Expected ENOTDIR'); +}) +.catch((e) => { + if (e.code != 'ENOTDIR') { + throw e; + } + + if (!e.path.includes('/c/d')) { + throw new Error('Path truncated: ' + e.path); + } +}) +.then(() => fsp.unlink(dname + '/a/b')) +.then(() => fsp.rmdir(dname + '/a')) +.then(() => fsp.rmdir(dname)) +.then(() => { + stages.push("fsp.mkdir"); +}) + +let p = Promise.resolve() + .then(testSync) + .then(testCallback) + .then(testFsp) + .then(() => assert.compareArray(stages, ['mkdirSync', 'mkdir', 'fsp.mkdir'])) + +p.then($DONE, $DONE);