From b12fc23cd0b09899f40b1dc3d06b71e269ba3e44 Mon Sep 17 00:00:00 2001 From: "Artem S. Povalyukhin" Date: Wed, 15 Jul 2020 15:51:06 +0300 Subject: [PATCH] Improved fs.mkdir() to support recursive directory creation. --- src/njs_fs.c | 112 +++++++++++++++++++++++++++++++++---- test/js/fs_promises_008.js | 79 ++++++++++++++++++++++++++ test/njs_expect_test.exp | 3 + 3 files changed, 183 insertions(+), 11 deletions(-) create mode 100644 test/js/fs_promises_008.js diff --git a/src/njs_fs.c b/src/njs_fs.c index fad0678b..251b9502 100644 --- a/src/njs_fs.c +++ b/src/njs_fs.c @@ -70,6 +70,9 @@ static njs_int_t njs_fs_error(njs_vm_t *vm, const char *syscall, static njs_int_t njs_fs_result(njs_vm_t *vm, njs_value_t *result, njs_index_t calltype, const njs_value_t* callback, njs_uint_t nargs); +static njs_int_t njs_fs_make_path(njs_vm_t *vm, const char *path, mode_t md, + njs_bool_t recursive, njs_value_t *retval); + static int njs_fs_flags(njs_vm_t *vm, njs_value_t *value, int default_flags); static mode_t njs_fs_mode(njs_vm_t *vm, njs_value_t *value, mode_t default_mode); @@ -828,18 +831,8 @@ njs_fs_mkdir(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, return NJS_ERROR; } - if (njs_is_true(&recursive)) { - njs_type_error(vm, "\"options.recursive\" is not supported"); - return NJS_ERROR; - } - - njs_set_undefined(&retval); - - ret = mkdir(file_path, md); - if (njs_slow_path(ret != 0)) { - ret = njs_fs_error(vm, "mkdir", strerror(errno), path, errno, + ret = njs_fs_make_path(vm, file_path, md, njs_is_true(&recursive), &retval); - } if (ret == NJS_OK) { return njs_fs_result(vm, &retval, calltype, callback, 1); @@ -1138,6 +1131,103 @@ njs_fs_fd_read(njs_vm_t *vm, int fd, njs_str_t *data) } +static njs_int_t +njs_fs_make_path(njs_vm_t *vm, const char *path, mode_t md, + njs_bool_t recursive, njs_value_t *retval) +{ + size_t size; + ssize_t length; + njs_int_t ret; + const char *p, *prev; + njs_value_t value; + struct stat sb; + char path_buf[MAXPATHLEN]; + + njs_set_undefined(retval); + + if (!recursive) { + ret = mkdir(path, md); + if (ret != 0) { + goto failed; + } + + return NJS_OK; + } + + ret = stat(path, &sb); + if (ret == 0) { + if (!S_ISDIR(sb.st_mode)) { + errno = ENOTDIR; + goto failed; + } + + return NJS_OK; + } + + if (errno != ENOENT) { + goto failed; + } + + p = path; + prev = p; + + for ( ;; ) { + p = strchr(prev + 1, '/'); + if (p == NULL) { + break; + } + + if (njs_slow_path((p - path) > MAXPATHLEN)) { + njs_internal_error(vm, "too large path"); + return NJS_OK; + } + + memcpy(&path_buf[prev - path], &path[prev - path], p - prev); + path_buf[p - path] = '\0'; + + ret = stat(path_buf, &sb); + if (ret == 0) { + if (!S_ISDIR(sb.st_mode)) { + errno = ENOTDIR; + goto failed; + } + + } else { + ret = mkdir(path_buf, md); + if (ret != 0) { + goto failed; + } + } + + path_buf[p - path] = '/'; + prev = p; + } + + ret = mkdir(path, md); + if (ret != 0 && errno != EEXIST) { + goto failed; + } + + return NJS_OK; + +failed: + + size = njs_strlen(path); + length = njs_utf8_length((u_char *) path, size); + if (njs_slow_path(length < 0)) { + length = 0; + } + + ret = njs_string_new(vm, &value, (u_char *) path, size, length); + if (ret != NJS_OK) { + return NJS_ERROR; + } + + return njs_fs_error(vm, "mkdir", strerror(errno), &value, errno, + retval); +} + + static int njs_fs_flags(njs_vm_t *vm, njs_value_t *value, int default_flags) { diff --git a/test/js/fs_promises_008.js b/test/js/fs_promises_008.js new file mode 100644 index 00000000..8e83c307 --- /dev/null +++ b/test/js/fs_promises_008.js @@ -0,0 +1,79 @@ +var fs = require('fs'); +var fsp = fs.promises; +var dname = './build/test/fs_promises_αβγ_008/'; +var path = 'one/two/three/αβγ'; + +var wipePath = (root, path, nofail) => { + path + .split('/') + .map((x, i, a) => { + return root + a.slice(0, i + 1).join('/'); + }) + .reverse() + .map((dir) => { + try { + fs.rmdirSync(dir); + } catch (e) { + if (!nofail) { + throw e; + } + } + }); +}; + +var testSync = () => new Promise((resolve, reject) => { + try { + wipePath(dname, path + '/' + path, true); + fs.rmdirSync(dname); + } catch (e) { + } + + try { + fs.mkdirSync(dname); + + fs.mkdirSync(dname, { recursive: true }); + fs.mkdirSync(dname + '/', { recursive: true }); + fs.mkdirSync(dname + '////', { recursive: true }); + + fs.mkdirSync(dname + path, { recursive: true }); + wipePath(dname, path); + + fs.mkdirSync(dname + '////' + path + '////' + path + '////', { recursive: true }); + wipePath(dname, path + '/' + path); + + try { + fs.mkdirSync(dname + path, { recursive: true, mode: 0 }); + } catch (e) { + if (e.code != 'EACCES') { + reject(e); + } + } + wipePath(dname, path, true); + + try { + fs.mkdirSync(dname + path, { recursive: true }); + fs.writeFileSync(dname + path + '/one', 'not dir'); + fs.mkdirSync(dname + path + '/' + path, { recursive: true }); + } catch (e) { + if (e.code != 'ENOTDIR') { + reject(e); + } + } + fs.unlinkSync(dname + path + '/one'); + wipePath(dname, path); + + fs.rmdirSync(dname); + resolve(); + } catch (e) { + reject(e); + } +}); + +Promise.resolve() +.then(testSync) +.then(() => { + console.log('test recursive fs.mkdirSync'); +}) +.catch((e) => { + console.log('test failed recursive fs.mkdirSync', JSON.stringify(e)); +}) diff --git a/test/njs_expect_test.exp b/test/njs_expect_test.exp index 0923d00c..c9ed529d 100644 --- a/test/njs_expect_test.exp +++ b/test/njs_expect_test.exp @@ -1140,3 +1140,6 @@ njs_run {"./test/js/fs_promises_007.js"} \ "test fs.readdirSync test fs.readdir test fsp.readdir" + +njs_run {"./test/js/fs_promises_008.js"} \ +"test recursive fs.mkdirSync" -- 2.47.3