[njs] Improved fs.rmdir() to support recursive directory removal.

Dmitry Volyntsev xeioex at nginx.com
Tue Aug 11 14:32:20 UTC 2020


details:   https://hg.nginx.org/njs/rev/24de499877ca
branches:  
changeset: 1487:24de499877ca
user:      Artem S. Povalyukhin <artem.povaluhin at gmail.com>
date:      Sat Jul 25 15:49:03 2020 +0300
description:
Improved fs.rmdir() to support recursive directory removal.

diffstat:

 src/njs_fs.c               |  262 ++++++++++++++++++++++++++++++++++++++++++--
 test/js/fs_promises_009.js |  106 ++++++++++++++++++
 test/njs_expect_test.exp   |    3 +
 3 files changed, 359 insertions(+), 12 deletions(-)

diffs (415 lines):

diff -r a0f2c61c1c83 -r 24de499877ca src/njs_fs.c
--- a/src/njs_fs.c	Tue Aug 11 14:31:04 2020 +0000
+++ b/src/njs_fs.c	Sat Jul 25 15:49:03 2020 +0300
@@ -63,6 +63,28 @@ typedef struct {
 } njs_fs_entry_t;
 
 
+typedef enum {
+    NJS_FTW_PHYS = 1,
+    NJS_FTW_MOUNT = 2,
+    NJS_FTW_DEPTH = 8,
+} njs_ftw_flags_t;
+
+
+typedef enum {
+    NJS_FTW_F,
+    NJS_FTW_D,
+    NJS_FTW_DNR,
+    NJS_FTW_NS,
+    NJS_FTW_SL,
+    NJS_FTW_DP,
+    NJS_FTW_SLN,
+} njs_ftw_type_t;
+
+
+typedef int (*njs_file_tree_walk_cb_t)(const char *, const struct stat *,
+     njs_ftw_type_t);
+
+
 static njs_int_t njs_fs_fd_read(njs_vm_t *vm, int fd, njs_str_t *data);
 
 static njs_int_t njs_fs_error(njs_vm_t *vm, const char *syscall,
@@ -70,8 +92,14 @@ static njs_int_t njs_fs_error(njs_vm_t *
 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_file_tree_walk(const char *path, njs_file_tree_walk_cb_t cb, int fd_limit,
+    njs_ftw_flags_t flags);
+
 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 njs_int_t njs_fs_rmtree(njs_vm_t *vm, const char *path,
+    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,
@@ -893,18 +921,7 @@ njs_fs_rmdir(njs_vm_t *vm, njs_value_t *
         }
     }
 
-    if (njs_is_true(&recursive)) {
-        njs_type_error(vm, "\"options.recursive\" is not supported");
-        return NJS_ERROR;
-    }
-
-    njs_set_undefined(&retval);
-
-    ret = rmdir(file_path);
-    if (njs_slow_path(ret != 0)) {
-        ret = njs_fs_error(vm, "rmdir", strerror(errno), path, errno,
-                           &retval);
-    }
+    ret = njs_fs_rmtree(vm, file_path, njs_is_true(&recursive), &retval);
 
     if (ret == NJS_OK) {
         return njs_fs_result(vm, &retval, calltype, callback, 1);
@@ -1227,6 +1244,227 @@ failed:
 }
 
 
+typedef struct njs_ftw_trace_s  njs_ftw_trace_t;
+
+struct njs_ftw_trace_s {
+    struct njs_ftw_trace_s  *chain;
+    dev_t                   dev;
+    ino_t                   ino;
+};
+
+
+static int
+njs_ftw(char *path, njs_file_tree_walk_cb_t cb, int fd_limit,
+    njs_ftw_flags_t flags, njs_ftw_trace_t *parent)
+{
+    int              type, ret, dfd, err;
+    DIR              *d;
+    size_t           base, len, length;
+    const char       *d_name;
+    struct stat      st;
+    struct dirent    *entry;
+    njs_ftw_trace_t  trace, *h;
+
+    ret = (flags & NJS_FTW_PHYS) ? lstat(path, &st) : stat(path, &st);
+
+    if (ret < 0) {
+        if (!(flags & NJS_FTW_PHYS) && errno == ENOENT && !lstat(path, &st)) {
+            type = NJS_FTW_SLN;
+
+        } else if (errno != EACCES) {
+            return -1;
+
+        } else {
+            type = NJS_FTW_NS;
+        }
+
+    } else if (S_ISDIR(st.st_mode)) {
+        type = (flags & NJS_FTW_DEPTH) ? NJS_FTW_DP : NJS_FTW_D;
+
+    } else if (S_ISLNK(st.st_mode)) {
+        type = (flags & NJS_FTW_PHYS) ? NJS_FTW_SL : NJS_FTW_SLN;
+
+    } else {
+        type = NJS_FTW_F;
+    }
+
+    if ((flags & NJS_FTW_MOUNT) && parent != NULL && st.st_dev != parent->dev) {
+        return 0;
+    }
+
+    len = njs_strlen(path);
+    base = len && (path[len - 1] == '/') ? len - 1 : len;
+
+    trace.chain = parent;
+    trace.dev = st.st_dev;
+    trace.ino = st.st_ino;
+
+    dfd = 0;
+    err = 0;
+
+    if (type == NJS_FTW_D || type == NJS_FTW_DP) {
+        dfd = open(path, O_RDONLY);
+        err = errno;
+        if (dfd < 0 && err == EACCES) {
+            type = NJS_FTW_DNR;
+        }
+
+        if (fd_limit == 0) {
+            close(dfd);
+        }
+    }
+
+    if (!(flags & NJS_FTW_DEPTH)) {
+        ret = cb(path, &st, type);
+        if (njs_slow_path(ret != 0)) {
+            return ret;
+        }
+    }
+
+    for (h = parent; h != NULL; h = h->chain) {
+        if (h->dev == st.st_dev && h->ino == st.st_ino) {
+            return 0;
+        }
+    }
+
+    if ((type == NJS_FTW_D || type == NJS_FTW_DP) && fd_limit != 0) {
+        if (dfd < 0) {
+            errno = err;
+            return -1;
+        }
+
+        d = fdopendir(dfd);
+        if (njs_slow_path(d == NULL)) {
+            close(dfd);
+            return -1;
+        }
+
+        for ( ;; ) {
+            entry = readdir(d);
+
+            if (entry == NULL) {
+                break;
+            }
+
+            d_name = entry->d_name;
+            length = njs_strlen(d_name);
+
+            if ((length == 1 && d_name[0] == '.')
+                || (length == 2 && (d_name[0] == '.' && d_name[1] == '.')))
+            {
+                continue;
+            }
+
+            if (njs_slow_path(length >= (PATH_MAX - len))) {
+                errno = ENAMETOOLONG;
+                closedir(d);
+                return -1;
+            }
+
+            path[base] = '/';
+            strcpy(path + base + 1, d_name);
+
+            ret = njs_ftw(path, cb, fd_limit - 1, flags, &trace);
+            if (njs_slow_path(ret != 0)) {
+                closedir(d);
+                return ret;
+            }
+        }
+
+        closedir(d);
+    }
+
+    path[len] = '\0';
+
+    if (flags & NJS_FTW_DEPTH) {
+        ret = cb(path, &st, type);
+        if (njs_slow_path(ret != 0)) {
+            return ret;
+        }
+    }
+
+    return 0;
+}
+
+
+static njs_int_t
+njs_file_tree_walk(const char *path, njs_file_tree_walk_cb_t cb, int fd_limit,
+    njs_ftw_flags_t flags)
+{
+    size_t  len;
+    char    pathbuf[PATH_MAX + 1];
+
+    len = njs_strlen(path);
+    if (njs_slow_path(len > PATH_MAX)) {
+        errno = ENAMETOOLONG;
+        return -1;
+    }
+
+    memcpy(pathbuf, path, len + 1);
+
+    return njs_ftw(pathbuf, cb, fd_limit, flags, NULL);
+}
+
+
+static int
+njs_fs_rmtree_cb(const char *path, const struct stat *sb, njs_ftw_type_t type)
+{
+    njs_int_t  ret;
+
+    ret = remove(path);
+    if (ret != 0) {
+        return -1;
+    }
+
+    return 0;
+}
+
+
+static njs_int_t
+njs_fs_rmtree(njs_vm_t *vm, const char *path, njs_bool_t recursive,
+    njs_value_t *retval)
+{
+    size_t       size;
+    ssize_t      length;
+    njs_int_t    ret;
+    const char   *description;
+    njs_value_t  value;
+
+    njs_set_undefined(retval);
+
+    ret = rmdir(path);
+    if (ret == 0) {
+        return NJS_OK;
+    }
+
+    description = strerror(errno);
+
+    if (recursive && (errno == ENOTEMPTY || errno == EEXIST)) {
+        ret = njs_file_tree_walk(path, njs_fs_rmtree_cb, 16,
+                                 NJS_FTW_PHYS | NJS_FTW_MOUNT | NJS_FTW_DEPTH);
+
+        if (ret == 0) {
+            return NJS_OK;
+        }
+
+        description = strerror(errno);
+    }
+
+    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 (njs_slow_path(ret != NJS_OK)) {
+        return NJS_ERROR;
+    }
+
+    return njs_fs_error(vm, "rmdir", description, &value, errno, retval);
+}
+
+
 static int
 njs_fs_flags(njs_vm_t *vm, njs_value_t *value, int default_flags)
 {
diff -r a0f2c61c1c83 -r 24de499877ca test/js/fs_promises_009.js
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/js/fs_promises_009.js	Sat Jul 25 15:49:03 2020 +0300
@@ -0,0 +1,106 @@
+var fs = require('fs');
+var fsp  = fs.promises;
+var root = './build/test/';
+var dname = 'fs_promises_αβγ_009/';
+var lname = 'fs_promises_αβγ_009_lnk';
+var path = 'one/two/three/αβγ';
+
+
+var setContent = (root, path) => {
+    fs.mkdirSync(root + path, { recursive: true });
+    path
+        .split('/')
+        .forEach((x, i, a) => {
+            for (var j = 1; j < 10; ++j) {
+                var path = root + a.slice(0, i + 1).join('/') + '_file' + j;
+                fs.writeFileSync(path, path);
+            }
+        });
+};
+
+
+var isNode = () => process.argv[0].includes('node');
+
+
+var testSync = () => new Promise((resolve, reject) => {
+    try {
+        fs.unlinkSync(root + lname);
+    } catch (e) {
+    }
+    try {
+        fs.rmdirSync(root + dname, { recursive: true });
+    } catch (e) {
+    }
+
+    try {
+
+        fs.mkdirSync(root + dname);
+        fs.symlinkSync(dname, root + lname);
+        try {
+            fs.rmdirSync(root + lname);
+            throw new Error('fs.rmdirSync() - error 0');
+        } catch (e) {
+            if (e.code != "ENOTDIR") {
+                throw e;
+            }
+        }
+        fs.rmdirSync(root + dname);
+        fs.unlinkSync(root + lname);
+
+        if (!isNode()) {
+            fs.mkdirSync(root + dname);
+            fs.symlinkSync(dname, root + lname);
+            try {
+                fs.rmdirSync(root + lname, { recursive: true });
+                throw new Error('fs.rmdirSync() - error 1');
+            } catch (e) {
+                if (e.code != "ENOTDIR") {
+                    throw e;
+                }
+            }
+            fs.rmdirSync(root + dname);
+            fs.unlinkSync(root + lname);
+        }
+
+        fs.mkdirSync(root + dname, { mode: 0 });
+        fs.rmdirSync(root + dname, { recursive: true });
+
+        setContent(root + dname, path);
+        fs.rmdirSync(root + dname, { recursive: true });
+
+        try {
+            fs.accessSync(root + dname);
+            throw new Error('fs.rmdirSync() - error 2');
+        } catch (e) {
+            if (e.code != "ENOENT") {
+                throw e;
+            }
+        }
+
+        if (!isNode()) {
+            try {
+                fs.rmdirSync(root + dname, { recursive: true });
+                throw new Error('fs.rmdirSync() - error 3');
+            } catch (e) {
+                if (e.code != "ENOENT") {
+                    throw e;
+                }
+            }
+        }
+
+        resolve();
+
+    } catch (e) {
+        reject(e);
+    }
+});
+
+
+Promise.resolve()
+.then(testSync)
+.then(() => {
+    console.log('test recursive fs.rmdirSync()');
+})
+.catch((e) => {
+    console.log('test failed recursive fs.rmdirSync()', e.message, JSON.stringify(e));
+});
diff -r a0f2c61c1c83 -r 24de499877ca test/njs_expect_test.exp
--- a/test/njs_expect_test.exp	Tue Aug 11 14:31:04 2020 +0000
+++ b/test/njs_expect_test.exp	Sat Jul 25 15:49:03 2020 +0300
@@ -1143,3 +1143,6 @@ test fsp.readdir"
 
 njs_run {"./test/js/fs_promises_008.js"} \
 "test recursive fs.mkdirSync"
+
+njs_run {"./test/js/fs_promises_009.js"} \
+"test recursive fs.rmdirSync"


More information about the nginx-devel mailing list