diff --git a/external/njs_fs_module.c b/external/njs_fs_module.c index de378cee1..f52218830 100644 --- 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) diff --git a/test/fs/methods.t.js b/test/fs/methods.t.js index 1d177e187..72afb4940 100644 --- 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,