Skip to content

Commit af301b2

Browse files
richardlauMylesBorins
authored andcommitted
fs: fix infinite loop with async recursive mkdir on Windows
If `file` is a file then on Windows `mkdir` on `file/a` returns an `ENOENT` error while on POSIX the equivalent returns `ENOTDIR`. On the POSIX systems `ENOTDIR` would break out of the loop but on Windows the `ENOENT` would strip off the `a` and attempt to make `file` as a directory. This would return `EEXIST` but the code wasn't detecting that the existing path was a file and attempted to make `file/a` again. PR-URL: #27207 Fixes: #27198 Reviewed-By: Colin Ihrig <[email protected]> Reviewed-By: Sakthipriyan Vairamani <[email protected]> Reviewed-By: Ben Coe <[email protected]>
1 parent aceac05 commit af301b2

File tree

3 files changed

+83
-19
lines changed

3 files changed

+83
-19
lines changed

src/node_file.cc

+32-16
Original file line numberDiff line numberDiff line change
@@ -1289,8 +1289,15 @@ int MKDirpSync(uv_loop_t* loop, uv_fs_t* req, const std::string& path, int mode,
12891289
}
12901290
default:
12911291
uv_fs_req_cleanup(req);
1292+
int orig_err = err;
12921293
err = uv_fs_stat(loop, req, next_path.c_str(), nullptr);
1293-
if (err == 0 && !S_ISDIR(req->statbuf.st_mode)) return UV_EEXIST;
1294+
if (err == 0 && !S_ISDIR(req->statbuf.st_mode)) {
1295+
uv_fs_req_cleanup(req);
1296+
if (orig_err == UV_EEXIST && continuation_data.paths.size() > 0) {
1297+
return UV_ENOTDIR;
1298+
}
1299+
return UV_EEXIST;
1300+
}
12941301
if (err < 0) return err;
12951302
break;
12961303
}
@@ -1357,23 +1364,32 @@ int MKDirpAsync(uv_loop_t* loop,
13571364
break;
13581365
}
13591366
default:
1360-
if (err == UV_EEXIST &&
1361-
req_wrap->continuation_data->paths.size() > 0) {
1362-
uv_fs_req_cleanup(req);
1363-
MKDirpAsync(loop, req, path.c_str(),
1364-
req_wrap->continuation_data->mode, nullptr);
1365-
} else {
1367+
uv_fs_req_cleanup(req);
1368+
// Stash err for use in the callback.
1369+
req->data = reinterpret_cast<void*>(static_cast<intptr_t>(err));
1370+
int err = uv_fs_stat(loop, req, path.c_str(),
1371+
uv_fs_callback_t{[](uv_fs_t* req) {
1372+
FSReqBase* req_wrap = FSReqBase::from_req(req);
1373+
int err = req->result;
1374+
if (reinterpret_cast<intptr_t>(req->data) == UV_EEXIST &&
1375+
req_wrap->continuation_data->paths.size() > 0) {
1376+
if (err == 0 && S_ISDIR(req->statbuf.st_mode)) {
1377+
Environment* env = req_wrap->env();
1378+
uv_loop_t* loop = env->event_loop();
1379+
std::string path = req->path;
1380+
uv_fs_req_cleanup(req);
1381+
MKDirpAsync(loop, req, path.c_str(),
1382+
req_wrap->continuation_data->mode, nullptr);
1383+
return;
1384+
}
1385+
err = UV_ENOTDIR;
1386+
}
13661387
// verify that the path pointed to is actually a directory.
1388+
if (err == 0 && !S_ISDIR(req->statbuf.st_mode)) err = UV_EEXIST;
13671389
uv_fs_req_cleanup(req);
1368-
int err = uv_fs_stat(loop, req, path.c_str(),
1369-
uv_fs_callback_t{[](uv_fs_t* req) {
1370-
FSReqBase* req_wrap = FSReqBase::from_req(req);
1371-
int err = req->result;
1372-
if (err == 0 && !S_ISDIR(req->statbuf.st_mode)) err = UV_EEXIST;
1373-
req_wrap->continuation_data->Done(err);
1374-
}});
1375-
if (err < 0) req_wrap->continuation_data->Done(err);
1376-
}
1390+
req_wrap->continuation_data->Done(err);
1391+
}});
1392+
if (err < 0) req_wrap->continuation_data->Done(err);
13771393
break;
13781394
}
13791395
break;

test/parallel/test-fs-mkdir.js

+35-3
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,25 @@ function nextdir() {
132132
}
133133
}
134134

135-
// mkdirp when folder does not yet exist.
135+
// mkdirpSync when part of the path is a file.
136+
{
137+
const filename = path.join(tmpdir.path, nextdir(), nextdir());
138+
const pathname = path.join(filename, nextdir(), nextdir());
139+
140+
fs.mkdirSync(path.dirname(filename));
141+
fs.writeFileSync(filename, '', 'utf8');
142+
143+
try {
144+
fs.mkdirSync(pathname, { recursive: true });
145+
throw new Error('unreachable');
146+
} catch (err) {
147+
assert.notStrictEqual(err.message, 'unreachable');
148+
assert.strictEqual(err.code, 'ENOTDIR');
149+
assert.strictEqual(err.syscall, 'mkdir');
150+
}
151+
}
152+
153+
// `mkdirp` when folder does not yet exist.
136154
{
137155
const pathname = path.join(tmpdir.path, nextdir(), nextdir());
138156

@@ -149,11 +167,25 @@ function nextdir() {
149167

150168
fs.mkdirSync(path.dirname(pathname));
151169
fs.writeFileSync(pathname, '', 'utf8');
152-
fs.mkdir(pathname, { recursive: true }, (err) => {
170+
fs.mkdir(pathname, { recursive: true }, common.mustCall((err) => {
153171
assert.strictEqual(err.code, 'EEXIST');
154172
assert.strictEqual(err.syscall, 'mkdir');
155173
assert.strictEqual(fs.statSync(pathname).isDirectory(), false);
156-
});
174+
}));
175+
}
176+
177+
// `mkdirp` when part of the path is a file.
178+
{
179+
const filename = path.join(tmpdir.path, nextdir(), nextdir());
180+
const pathname = path.join(filename, nextdir(), nextdir());
181+
182+
fs.mkdirSync(path.dirname(filename));
183+
fs.writeFileSync(filename, '', 'utf8');
184+
fs.mkdir(pathname, { recursive: true }, common.mustCall((err) => {
185+
assert.strictEqual(err.code, 'ENOTDIR');
186+
assert.strictEqual(err.syscall, 'mkdir');
187+
assert.strictEqual(fs.existsSync(pathname), false);
188+
}));
157189
}
158190

159191
// mkdirpSync dirname loop

test/parallel/test-fs-promises.js

+16
Original file line numberDiff line numberDiff line change
@@ -310,6 +310,22 @@ async function getHandle(dest) {
310310
}
311311
}
312312

313+
// `mkdirp` when part of the path is a file.
314+
{
315+
const file = path.join(tmpDir, nextdir(), nextdir());
316+
const dir = path.join(file, nextdir(), nextdir());
317+
await mkdir(path.dirname(file));
318+
await writeFile(file);
319+
try {
320+
await mkdir(dir, { recursive: true });
321+
throw new Error('unreachable');
322+
} catch (err) {
323+
assert.notStrictEqual(err.message, 'unreachable');
324+
assert.strictEqual(err.code, 'ENOTDIR');
325+
assert.strictEqual(err.syscall, 'mkdir');
326+
}
327+
}
328+
313329
// mkdirp ./
314330
{
315331
const dir = path.resolve(tmpDir, `${nextdir()}/./${nextdir()}`);

0 commit comments

Comments
 (0)