Repository: nginx/njs (c)

Issue:
fs.readlink / fs.readlinkSync
Hello,

it appears fs implementation in njs does not includes `fs.readlink` nor `fs.readlinkSync`, possible to add it please?

thanks


Discussion:

Patch:
diff --git a/external/njs_fs_module.c b/external/njs_fs_module.c
--- a/external/njs_fs_module.c
+++ b/external/njs_fs_module.c
@@ -160,6 +160,8 @@ static njs_int_t njs_fs_read_file(njs_vm_t *vm, njs_value_t *args,
     njs_uint_t nargs, njs_index_t calltype, njs_value_t *retval);
 static njs_int_t njs_fs_readdir(njs_vm_t *vm, njs_value_t *args,
     njs_uint_t nargs, njs_index_t calltype, njs_value_t *retval);
+static njs_int_t njs_fs_readlink(njs_vm_t *vm, njs_value_t *args,
+    njs_uint_t nargs, njs_index_t calltype, njs_value_t *retval);
 static njs_int_t njs_fs_realpath(njs_vm_t *vm, njs_value_t *args,
     njs_uint_t nargs, njs_index_t calltype, njs_value_t *retval);
 static njs_int_t njs_fs_rename(njs_vm_t *vm, njs_value_t *args,
@@ -415,6 +417,17 @@ static njs_external_t  njs_ext_fs_promises[] = {
         }
     },
 
+    {
+        .flags = NJS_EXTERN_METHOD,
+        .name.string = njs_str("readlink"),
+        .writable = 1,
+        .configurable = 1,
+        .u.method = {
+            .native = njs_fs_readlink,
+            .magic8 = NJS_FS_PROMISE,
+        }
+    },
+
     {
         .flags = NJS_EXTERN_METHOD,
         .name.string = njs_str("realpath"),
@@ -726,6 +739,28 @@ static njs_external_t  njs_ext_fs[] = {
         }
     },
 
+    {
+        .flags = NJS_EXTERN_METHOD,
+        .name.string = njs_str("readlink"),
+        .writable = 1,
+        .configurable = 1,
+        .u.method = {
+            .native = njs_fs_readlink,
+            .magic8 = NJS_FS_CALLBACK,
+        }
+    },
+
+    {
+        .flags = NJS_EXTERN_METHOD,
+        .name.string = njs_str("readlinkSync"),
+        .writable = 1,
+        .configurable = 1,
+        .u.method = {
+            .native = njs_fs_readlink,
+            .magic8 = NJS_FS_DIRECT,
+        }
+    },
+
     {
         .flags = NJS_EXTERN_METHOD,
         .name.string = njs_str("realpath"),
@@ -2035,6 +2070,99 @@ njs_fs_readdir(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
 }
 
 
+static njs_int_t
+njs_fs_readlink(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
+    njs_index_t calltype, njs_value_t *retval)
+{
+    ssize_t                      n;
+    njs_int_t                    ret;
+    njs_str_t                    s;
+    const char                   *path;
+    njs_value_t                  *callback, *options;
+    njs_opaque_value_t           encode, result;
+    const njs_buffer_encoding_t  *encoding;
+    char                         path_buf[NJS_MAX_PATH + 1],
+                                 dst_buf[NJS_MAX_PATH + 1];
+
+    path = njs_fs_path(vm, path_buf, njs_arg(args, nargs, 1), "path");
+    if (njs_slow_path(path == NULL)) {
+        return NJS_ERROR;
+    }
+
+    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_value_is_function(callback)) {
+            njs_vm_type_error(vm, "\"callback\" must be a function");
+            return NJS_ERROR;
+        }
+
+        if (options == callback) {
+            options = njs_value_arg(&njs_value_undefined);
+        }
+    }
+
+    njs_value_undefined_set(njs_value_arg(&encode));
+
+    if (njs_value_is_string(options)) {
+        njs_value_assign(&encode, options);
+
+    } else if (!njs_value_is_undefined(options)) {
+        if (!njs_value_is_object(options)) {
+            njs_vm_type_error(vm, "Unknown options type "
+                              "(a string or object required)");
+            return NJS_ERROR;
+        }
+
+        (void) njs_vm_object_prop(vm, options, &string_encoding, &encode);
+    }
+
+    encoding = NULL;
+
+    if (njs_value_is_string(njs_value_arg(&encode))) {
+        njs_value_string_get(njs_value_arg(&encode), &s);
+
+    } else {
+        s.length = 0;
+        s.start = NULL;
+    }
+
+    if (!njs_strstr_eq(&s, &string_buffer)) {
+        encoding = njs_buffer_encoding(vm, njs_value_arg(&encode), 1);
+        if (njs_slow_path(encoding == NULL)) {
+            return NJS_ERROR;
+        }
+    }
+
+    s.start = (u_char *) dst_buf;
+    n = readlink(path, dst_buf, sizeof(dst_buf) - 1);
+    if (njs_slow_path(n < 0)) {
+        ret = njs_fs_error(vm, "readlink", strerror(errno), path, errno,
+                           &result);
+        goto done;
+    }
+
+    s.length = n;
+
+    if (encoding == NULL) {
+        ret = njs_buffer_new(vm, njs_value_arg(&result), s.start, s.length);
+
+    } else {
+        ret = encoding->encode(vm, njs_value_arg(&result), &s);
+    }
+
+done:
+
+    if (ret == NJS_OK) {
+        return njs_fs_result(vm, &result, calltype, callback, 2, retval);
+    }
+
+    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, njs_value_t *retval)


Test patch:
diff --git a/test/fs/methods.t.js b/test/fs/methods.t.js
--- a/test/fs/methods.t.js
+++ b/test/fs/methods.t.js
@@ -435,6 +435,56 @@ let realpathP_tsuite = {
     get tests() { return realpath_tests() },
 };
 
+async function readlink_test(params) {
+    let lname = params.args[0];
+    try { fs.unlinkSync(lname); } catch (e) {}
+    fs.symlinkSync("test/fs/ascii", lname);
+
+    let data = await method("readlink", params);
+
+    if (!params.check(data)) {
+        throw Error(`readlink failed check`);
+    }
+
+    return 'SUCCESS';
+}
+
+let readlink_tests = () => [
+    { args: [`${test_dir}/symlink`],
+      check: (data) => data.endsWith("test/fs/ascii") },
+    { args: [`${test_dir}/symlink`, {encoding:'buffer'}],
+      check: (data) => data instanceof Buffer },
+    { args: [`${test_dir}/symlink`, {encoding:'hex'}],
+      check: (data) => data.endsWith("746573742f66732f6173636969") },
+];
+
+let readlink_tsuite = {
+    name: "fs readlink",
+    skip: () => (!has_fs() || !has_buffer()),
+    T: readlink_test,
+    prepare_args: p,
+    opts: { type: "callback" },
+    get tests() { return readlink_tests() },
+};
+
+let readlinkSync_tsuite = {
+    name: "fs readlinkSync",
+    skip: () => (!has_fs() || !has_buffer()),
+    T: readlink_test,
+    prepare_args: p,
+    opts: { type: "sync" },
+    get tests() { return readlink_tests() },
+};
+
+let readlinkP_tsuite = {
+    name: "fsp readlink",
+    skip: () => (!has_fs() || !has_buffer()),
+    T: readlink_test,
+    prepare_args: p,
+    opts: { type: "promise" },
+    get tests() { return readlink_tests() },
+};
+
 async function method_test(params) {
     if (params.init) {
         params.init(params);
@@ -1190,6 +1240,9 @@ run([
     realpath_tsuite,
     realpathSync_tsuite,
     realpathP_tsuite,
+    readlink_tsuite,
+    readlinkSync_tsuite,
+    readlinkP_tsuite,
     stat_tsuite,
     statSync_tsuite,
     statP_tsuite,