]> git.kaiwu.me - njs.git/commitdiff
FS: fixed path restoration in fs.mkdir() and friends on error.
authorDmitry Volyntsev <xeioex@nginx.com>
Wed, 26 Nov 2025 03:08:33 +0000 (19:08 -0800)
committerDmitry Volyntsev <xeioexception@gmail.com>
Mon, 1 Dec 2025 17:46:23 +0000 (09:46 -0800)
external/njs_fs_module.c
external/qjs_fs_module.c
test/fs/mkdir_error_path.t.mjs [new file with mode: 0644]

index 33fb6111b6c7e8adbdc0bff2afeeeaa67791e99f..903c095cc130efa331f385a96703cbce2f8ebc4c 100644 (file)
@@ -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);
index 9117072bc943d481e181053ea3077e1de165ccb1..af929db34f5d9298cd57909a1875f552248ec7c0 100644 (file)
@@ -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 (file)
index 0000000..1d8b33c
--- /dev/null
@@ -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);