[njs] Added fs.symlink(), fs.unlink(), fs.realpath() and friends.

Dmitry Volyntsev xeioex at nginx.com
Tue Feb 18 17:39:24 UTC 2020


details:   https://hg.nginx.org/njs/rev/f5adc2ea2d53
branches:  
changeset: 1333:f5adc2ea2d53
user:      Artem S. Povalyukhin <artem.povaluhin at gmail.com>
date:      Tue Feb 11 01:48:24 2020 +0300
description:
Added fs.symlink(), fs.unlink(), fs.realpath() and friends.

diffstat:

 src/njs_fs.c                    |  266 ++++++++++++++++++++++++++++++++++++++++
 src/test/njs_interactive_test.c |    9 +-
 src/test/njs_unit_test.c        |    9 +
 test/js/fs_promises_002.js      |    2 +-
 test/js/fs_promises_003.js      |  108 ++++++++++++++++
 test/js/fs_promises_004.js      |  200 ++++++++++++++++++++++++++++++
 test/njs_expect_test.exp        |   10 +
 7 files changed, 602 insertions(+), 2 deletions(-)

diffs (679 lines):

diff -r c60911765952 -r f5adc2ea2d53 src/njs_fs.c
--- a/src/njs_fs.c	Tue Feb 18 18:55:50 2020 +0300
+++ b/src/njs_fs.c	Tue Feb 11 01:48:24 2020 +0300
@@ -528,6 +528,201 @@ done:
 
 
 static njs_int_t
+njs_fs_symlink(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
+    njs_index_t calltype)
+{
+    njs_int_t    ret;
+    const char  *target_path, *file_path;
+    njs_value_t  retval, *target, *path, *callback, *type;
+
+    target = njs_arg(args, nargs, 1);
+    ret = njs_fs_path_arg(vm, &target_path, target, &njs_str_value("target"));
+    if (njs_slow_path(ret != NJS_OK)) {
+        return ret;
+    }
+
+    path = njs_arg(args, nargs, 2);
+    ret = njs_fs_path_arg(vm, &file_path, path, &njs_str_value("path"));
+    if (njs_slow_path(ret != NJS_OK)) {
+        return ret;
+    }
+
+    callback = NULL;
+    type = njs_arg(args, nargs, 3);
+
+    if (calltype == NJS_FS_CALLBACK) {
+        callback = njs_arg(args, nargs, njs_min(nargs - 1, 4));
+        if (!njs_is_function(callback)) {
+            njs_type_error(vm, "\"callback\" must be a function");
+            return NJS_ERROR;
+        }
+
+        if (type == callback) {
+            type = njs_value_arg(&njs_value_undefined);
+        }
+    }
+
+    if (njs_slow_path(!njs_is_undefined(type) && !njs_is_string(type))) {
+        njs_type_error(vm, "\"type\" must be a string");
+        return NJS_ERROR;
+    }
+
+    ret = symlink(target_path, file_path);
+    if (njs_slow_path(ret != 0)) {
+        ret = njs_fs_error(vm, "symlink", strerror(errno), path, errno,
+                           &retval);
+        goto done;
+    }
+
+    njs_set_undefined(&retval);
+
+done:
+
+    if (ret == NJS_OK) {
+        return njs_fs_result(vm, &retval, calltype, callback, 1);
+    }
+
+    return NJS_ERROR;
+}
+
+
+static njs_int_t
+njs_fs_unlink(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
+    njs_index_t calltype)
+{
+    njs_int_t    ret;
+    const char   *file_path;
+    njs_value_t  retval, *path, *callback;
+
+    path = njs_arg(args, nargs, 1);
+    ret = njs_fs_path_arg(vm, &file_path, path, &njs_str_value("path"));
+    if (njs_slow_path(ret != NJS_OK)) {
+        return ret;
+    }
+
+    callback = NULL;
+
+    if (calltype == NJS_FS_CALLBACK) {
+        callback = njs_arg(args, nargs, 2);
+        if (!njs_is_function(callback)) {
+            njs_type_error(vm, "\"callback\" must be a function");
+            return NJS_ERROR;
+        }
+    }
+
+    ret = unlink(file_path);
+    if (njs_slow_path(ret != 0)) {
+        ret = njs_fs_error(vm, "unlink", strerror(errno), path, errno, &retval);
+        goto done;
+    }
+
+    njs_set_undefined(&retval);
+
+done:
+
+    if (ret == NJS_OK) {
+        return njs_fs_result(vm, &retval, calltype, callback, 1);
+    }
+
+    return NJS_ERROR;
+}
+
+
+static njs_int_t
+njs_fs_realpath(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
+    njs_index_t calltype)
+{
+    u_char             *resolved_path;
+    size_t             size;
+    ssize_t            length;
+    njs_int_t          ret;
+    const char         *file_path;
+    njs_value_t        encoding, retval, *path, *callback, *options;
+    njs_fs_encoding_t  enc;
+    char               path_buf[MAXPATHLEN];
+
+    static const njs_value_t  string_encoding = njs_string("encoding");
+
+    path = njs_arg(args, nargs, 1);
+    ret = njs_fs_path_arg(vm, &file_path, path, &njs_str_value("path"));
+    if (njs_slow_path(ret != NJS_OK)) {
+        return ret;
+    }
+
+    callback = NULL;
+    options = njs_arg(args, nargs, 2);
+
+    if (calltype == NJS_FS_CALLBACK) {
+        callback = njs_arg(args, nargs, njs_min(nargs - 1, 3));
+        if (!njs_is_function(callback)) {
+            njs_type_error(vm, "\"callback\" must be a function");
+            return NJS_ERROR;
+        }
+
+        if (options == callback) {
+            options = njs_value_arg(&njs_value_undefined);
+        }
+    }
+
+    njs_set_undefined(&encoding);
+
+    switch (options->type) {
+    case NJS_STRING:
+        encoding = *options;
+        break;
+
+    case NJS_UNDEFINED:
+        break;
+
+    default:
+        if (!njs_is_object(options)) {
+            njs_type_error(vm, "Unknown options type: \"%s\" "
+                           "(a string or object required)",
+                           njs_type_string(options->type));
+            return NJS_ERROR;
+        }
+
+        ret = njs_value_property(vm, options, njs_value_arg(&string_encoding),
+                                 &encoding);
+        if (njs_slow_path(ret == NJS_ERROR)) {
+            return ret;
+        }
+    }
+
+    enc = njs_fs_encoding(vm, &encoding);
+    if (njs_slow_path(enc == NJS_FS_ENC_INVALID)) {
+        return NJS_ERROR;
+    }
+
+    resolved_path = (u_char *) realpath(file_path, path_buf);
+    if (njs_slow_path(resolved_path == NULL)) {
+        ret = njs_fs_error(vm, "realpath", strerror(errno), path, errno,
+                           &retval);
+        goto done;
+    }
+
+    size = njs_strlen(resolved_path);
+    length = njs_utf8_length(resolved_path, size);
+    if (njs_slow_path(length < 0)) {
+        length = 0;
+    }
+
+    ret = njs_string_new(vm, &retval, resolved_path, size, length);
+    if (njs_slow_path(ret != NJS_OK)) {
+        return NJS_ERROR;
+    }
+
+done:
+
+    if (ret == NJS_OK) {
+        return njs_fs_result(vm, &retval, calltype, callback, 2);
+    }
+
+    return NJS_ERROR;
+}
+
+
+static njs_int_t
 njs_fs_fd_read(njs_vm_t *vm, int fd, njs_str_t *data)
 {
     u_char   *p, *end, *start;
@@ -880,6 +1075,30 @@ static const njs_object_prop_t  njs_fs_p
         .writable = 1,
         .configurable = 1,
     },
+
+    {
+        .type = NJS_PROPERTY,
+        .name = njs_string("symlink"),
+        .value = njs_native_function2(njs_fs_symlink, 0, NJS_FS_PROMISE),
+        .writable = 1,
+        .configurable = 1,
+    },
+
+    {
+        .type = NJS_PROPERTY,
+        .name = njs_string("unlink"),
+        .value = njs_native_function2(njs_fs_unlink, 0, NJS_FS_PROMISE),
+        .writable = 1,
+        .configurable = 1,
+    },
+
+    {
+        .type = NJS_PROPERTY,
+        .name = njs_string("realpath"),
+        .value = njs_native_function2(njs_fs_realpath, 0, NJS_FS_PROMISE),
+        .writable = 1,
+        .configurable = 1,
+    },
 };
 
 
@@ -1039,6 +1258,53 @@ static const njs_object_prop_t  njs_fs_o
         .configurable = 1,
     },
 
+    {
+        .type = NJS_PROPERTY,
+        .name = njs_string("symlink"),
+        .value = njs_native_function2(njs_fs_symlink, 0, NJS_FS_CALLBACK),
+        .writable = 1,
+        .configurable = 1,
+    },
+
+    {
+        .type = NJS_PROPERTY,
+        .name = njs_string("symlinkSync"),
+        .value = njs_native_function2(njs_fs_symlink, 0, NJS_FS_DIRECT),
+        .writable = 1,
+        .configurable = 1,
+    },
+
+    {
+        .type = NJS_PROPERTY,
+        .name = njs_string("unlink"),
+        .value = njs_native_function2(njs_fs_unlink, 0, NJS_FS_CALLBACK),
+        .writable = 1,
+        .configurable = 1,
+    },
+
+    {
+        .type = NJS_PROPERTY,
+        .name = njs_string("unlinkSync"),
+        .value = njs_native_function2(njs_fs_unlink, 0, NJS_FS_DIRECT),
+        .writable = 1,
+        .configurable = 1,
+    },
+
+    {
+        .type = NJS_PROPERTY,
+        .name = njs_string("realpath"),
+        .value = njs_native_function2(njs_fs_realpath, 0, NJS_FS_CALLBACK),
+        .writable = 1,
+        .configurable = 1,
+    },
+
+    {
+        .type = NJS_PROPERTY,
+        .name = njs_string("realpathSync"),
+        .value = njs_native_function2(njs_fs_realpath, 0, NJS_FS_DIRECT),
+        .writable = 1,
+        .configurable = 1,
+    },
 };
 
 
diff -r c60911765952 -r f5adc2ea2d53 src/test/njs_interactive_test.c
--- a/src/test/njs_interactive_test.c	Tue Feb 18 18:55:50 2020 +0300
+++ b/src/test/njs_interactive_test.c	Tue Feb 11 01:48:24 2020 +0300
@@ -241,7 +241,14 @@ static njs_interactive_test_t  njs_test[
               " 'writeFile',"
               " 'writeFileSync',"
               " 'appendFile',"
-              " 'appendFileSync']"
+              " 'appendFileSync',"
+              " 'symlink',"
+              " 'symlinkSync',"
+              " 'unlink',"
+              " 'unlinkSync',"
+              " 'realpath',"
+              " 'realpathSync',"
+              "]"
               ".every(v=>{ try {fs[v]();} catch (e) { return e.stack.search(`fs.${v} `) >= 0}})" ENTER),
       njs_str("true") },
 
diff -r c60911765952 -r f5adc2ea2d53 src/test/njs_unit_test.c
--- a/src/test/njs_unit_test.c	Tue Feb 18 18:55:50 2020 +0300
+++ b/src/test/njs_unit_test.c	Tue Feb 11 01:48:24 2020 +0300
@@ -16159,6 +16159,12 @@ static njs_unit_test_t  njs_test[] =
                 "'writeFileSync',"
                 "'appendFile',"
                 "'appendFileSync',"
+                "'symlink',"
+                "'symlinkSync',"
+                "'unlink',"
+                "'unlinkSync',"
+                "'realpath',"
+                "'realpathSync',"
               "],"
               "test = (fname) =>"
                 "[undefined, null, false, NaN, Symbol(), {}, Object('/njs_unknown_path')]"
@@ -16181,6 +16187,9 @@ static njs_unit_test_t  njs_test[] =
                 "'readFile',"
                 "'writeFile',"
                 "'appendFile',"
+                "'symlink',"
+                "'unlink',"
+                "'realpath',"
               "];"
               "func.every((x) => typeof fs[x] == 'function')"),
       njs_str("true")},
diff -r c60911765952 -r f5adc2ea2d53 test/js/fs_promises_002.js
--- a/test/js/fs_promises_002.js	Tue Feb 18 18:55:50 2020 +0300
+++ b/test/js/fs_promises_002.js	Tue Feb 11 01:48:24 2020 +0300
@@ -1,6 +1,6 @@
 var fs = require('fs');
 var fsp  = fs.promises;
-var fname = '/tmp/njs_fs_promises_002';
+var fname = './build/test/fs_promises_002';
 
 var testSync = new Promise((resolve, reject) => {
     var failed = false;
diff -r c60911765952 -r f5adc2ea2d53 test/js/fs_promises_003.js
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/js/fs_promises_003.js	Tue Feb 11 01:48:24 2020 +0300
@@ -0,0 +1,108 @@
+var fs = require('fs');
+var fsp  = fs.promises;
+var fname = './build/test/fs_promises_003';
+
+
+var testSync = () => new Promise((resolve, reject) => {
+    try {
+        try {
+            fs.unlinkSync(fname);
+        } catch (e) {
+            void e;
+        }
+
+        try {
+            fs.unlinkSync(fname);
+            throw new Error('unlinkSync error 1');
+        } catch (e) {
+            if (e.syscall != 'unlink') {
+                throw e;
+            }
+        }
+
+        fs.writeFileSync(fname, fname);
+        fs.unlinkSync(fname);
+        try {
+            fs.accessSync(fname);
+            reject(new Error('unlinkSync error 2'));
+            return;
+        } catch (e) {
+            void e;
+        }
+
+        resolve();
+    } catch (e) {
+        reject(e);
+    }
+});
+
+
+var testCallback = () => new Promise((resolve, reject) => {
+    fs.unlink(fname, () => {
+        fs.unlink(fname, (err) => {
+            if (!err) {
+                reject(new Error('fs.unlink error 1'));
+                return;
+            }
+            if (err.syscall != 'unlink') {
+                reject(err);
+                return;
+            }
+
+            fs.writeFileSync(fname, fname);
+            fs.unlink(fname, (err) => {
+                if (err) {
+                    reject(err);
+                    return;
+                }
+                try {
+                    fs.accessSync(fname);
+                    reject(new Error('fs.unlink error 2'));
+                    return;
+                } catch (e) {
+                    void e;
+                }
+                resolve();
+            });
+        });
+    });
+});
+
+
+Promise.resolve()
+.then(testSync)
+.then(() => {
+    console.log('test fs.unlinkSync');
+})
+.catch((e) => {
+    console.log('test fs.unlinkSync failed', e);
+})
+
+.then(testCallback)
+.then(() => {
+    console.log('test fs.unlink');
+})
+.catch((e) => {
+    console.log('test fs.unlink failed', e);
+})
+
+.then(() => fsp.unlink(fname)
+            .catch(() => {}))
+.then(() => fsp.unlink(fname))
+            .then(() => { throw new Error('fsp.unlink error 1'); })
+.catch((e) => { if (e.syscall != 'unlink') { throw e; } })
+
+.then(() => {
+    fs.writeFileSync(fname, fname);
+    return fsp.unlink(fname);
+})
+.then(() => fsp.access(fname))
+            .then(() => { throw new Error('fsp.unlink error 2'); })
+.catch((e) => { if (e.syscall != 'access') { throw e; } })
+
+.then(() => {
+    console.log('test fsp.unlink');
+})
+.catch((e) => {
+    console.log('test fsp.unlink failed', e);
+});
diff -r c60911765952 -r f5adc2ea2d53 test/js/fs_promises_004.js
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/js/fs_promises_004.js	Tue Feb 11 01:48:24 2020 +0300
@@ -0,0 +1,200 @@
+var fs = require('fs');
+var fsp  = fs.promises;
+var dname = './build/test/';
+var fname = dname + 'fs_promises_004';
+var fname_utf8 = dname + 'fs_promises_αβγ_004';
+var lname = dname + 'fs_promises_004_lnk';
+
+
+var testSync = () => new Promise((resolve, reject) => {
+    try {
+        try {
+            fs.unlinkSync(fname);
+        } catch (e) {
+            void e;
+        }
+        try {
+            fs.unlinkSync(lname);
+        } catch (e) {
+            void e;
+        }
+
+        try {
+            fs.realpathSync(fname);
+            throw new Error('fs.realpathSync error 1');
+        } catch (e) {
+            if (e.syscall != 'realpath') { // e.code
+                throw e;
+            }
+        }
+
+        fs.writeFileSync(fname, fname);
+        fs.writeFileSync(fname_utf8, fname_utf8);
+
+        var rname = fs.realpathSync(fname);
+
+        fs.symlinkSync(rname, lname);
+
+        if (fs.realpathSync(lname) != rname) {
+            throw new Error('fs.symlinkSync error 2');
+        }
+
+        if (fs.readFileSync(lname) != fname) {
+            throw new Error('fs.symlinkSync error 3');
+        }
+
+        var rname_utf8 = fs.realpathSync(fname_utf8);
+        if (rname_utf8.slice(-7,-4) != 'αβγ') {
+            throw new Error('fs.realpathSync error 2');
+        }
+
+        fs.unlinkSync(lname);
+        fs.accessSync(fname);
+        fs.unlinkSync(fname);
+        fs.unlinkSync(fname_utf8);
+
+        resolve();
+
+    } catch (e) {
+        reject(e);
+    }
+});
+
+
+var testCallback = () => new Promise((resolve, reject) => {
+    try {
+        try {
+            fs.unlinkSync(fname);
+        } catch (e) {
+            void e;
+        }
+        try {
+            fs.unlinkSync(lname);
+        } catch (e) {
+            void e;
+        }
+
+        fs.realpath(fname, (err) => {
+            if (!err) {
+                reject(new Error('fs.realpath error 1'));
+                return;
+            }
+            if (err.syscall != 'realpath') {
+                reject(err);
+                return;
+            }
+
+            try {
+                fs.writeFileSync(fname, fname);
+            } catch (e) {
+                reject(e);
+                return;
+            }
+
+            fs.realpath(fname, (err, rname) => {
+                if (err) {
+                    reject(err);
+                    return;
+                }
+
+                fs.symlink(rname, lname, (err) => {
+                    if (err) {
+                        reject(err);
+                        return;
+                    }
+
+                    fs.realpath(lname, undefined, (err, xname) => {
+                        if (err) {
+                            reject(err);
+                            return;
+                        }
+
+                        if (rname != xname) {
+                            reject(new Error('fs.symlink error 1'));
+                            return;
+                        }
+
+                        try {
+                            if (fs.readFileSync(lname) != fname) {
+                                reject(new Error('fs.symlink error 2'));
+                                return;
+                            }
+
+                            fs.unlinkSync(lname);
+                            fs.accessSync(fname);
+                            fs.unlinkSync(fname);
+
+                        } catch (e) {
+                            reject(e);
+                            return;
+                        }
+
+                        resolve();
+                    });
+                });
+            });
+        });
+
+    } catch (e) {
+        reject(e);
+    }
+});
+
+
+Promise.resolve()
+.then(testSync)
+.then(() => {
+    console.log('test fs.symlinkSync');
+})
+.catch((e) => {
+    console.log('test fs.symlinkSync failed', e);
+})
+
+.then(testCallback)
+.then(() => {
+    console.log('test fs.symlink');
+})
+.catch((e) => {
+    console.log('test fs.symlink failed', e);
+})
+
+.then(() => fsp.unlink(fname)
+            .catch(() => {}))
+.then(() => fsp.unlink(lname)
+            .catch(() => {}))
+.then(() => fsp.realpath(fname)
+            .then(() => { throw new Error('fsp.realpath error 1') }))
+.catch((e) => {
+    if (e.syscall != 'realpath') {
+        throw e;
+    }
+})
+.then(() => {
+    fs.writeFileSync(fname, fname);
+
+    return fsp.realpath(fname);
+})
+.then((rname) => fsp.symlink(rname, lname)
+                 .then(() => rname))
+.then((rname) => fsp.realpath(lname)
+                 .then((xname) => {
+                     if (rname != xname) {
+                        throw new Error('fsp.symlink error 2');
+                     }
+                 }))
+.then(() => {
+    if (fs.readFileSync(lname) != fname) {
+        throw new Error('fsp.symlink error 3');
+    }
+
+    fs.unlinkSync(lname);
+    fs.accessSync(fname);
+    fs.unlinkSync(fname);
+})
+
+.then(() => {
+    console.log('test fsp.symlink');
+})
+.catch((e) => {
+    console.log('test fsp.symlink failed', e);
+});
diff -r c60911765952 -r f5adc2ea2d53 test/njs_expect_test.exp
--- a/test/njs_expect_test.exp	Tue Feb 18 18:55:50 2020 +0300
+++ b/test/njs_expect_test.exp	Tue Feb 11 01:48:24 2020 +0300
@@ -1096,3 +1096,13 @@ njs_run {"./test/js/fs_promises_002.js"}
 "testSync ok true
 testCallback ok true
 testPromise ok true"
+
+njs_run {"./test/js/fs_promises_003.js"} \
+"test fs.unlinkSync
+test fs.unlink
+test fsp.unlink"
+
+njs_run {"./test/js/fs_promises_004.js"} \
+"test fs.symlinkSync
+test fs.symlink
+test fsp.symlink"


More information about the nginx-devel mailing list