Skip to content

Commit

Permalink
FS: introduced fs.readlinkSync() and friends.
Browse files Browse the repository at this point in the history
This closes #802 feature request on Github.
  • Loading branch information
xeioex committed Oct 21, 2024
1 parent 1985390 commit 1b2a339
Show file tree
Hide file tree
Showing 2 changed files with 181 additions and 0 deletions.
128 changes: 128 additions & 0 deletions external/njs_fs_module.c
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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"),
Expand Down Expand Up @@ -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"),
Expand Down Expand Up @@ -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)
Expand Down
53 changes: 53 additions & 0 deletions test/fs/methods.t.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -1190,6 +1240,9 @@ run([
realpath_tsuite,
realpathSync_tsuite,
realpathP_tsuite,
readlink_tsuite,
readlinkSync_tsuite,
readlinkP_tsuite,
stat_tsuite,
statSync_tsuite,
statP_tsuite,
Expand Down

0 comments on commit 1b2a339

Please sign in to comment.