From ad956c58e70138f6838c8de7b73276b08385a403 Mon Sep 17 00:00:00 2001 From: "Artem S. Povalyukhin" Date: Tue, 11 Feb 2020 01:48:24 +0300 Subject: [PATCH] Added fs.symlink(), fs.unlink(), fs.realpath() and friends. --- 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(-) create mode 100644 test/js/fs_promises_003.js create mode 100644 test/js/fs_promises_004.js diff --git a/src/njs_fs.c b/src/njs_fs.c index 5c746296..7a1b543a 100644 --- a/src/njs_fs.c +++ b/src/njs_fs.c @@ -527,6 +527,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) { @@ -880,6 +1075,30 @@ static const njs_object_prop_t njs_fs_promises_properties[] = .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_object_properties[] = .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 --git a/src/test/njs_interactive_test.c b/src/test/njs_interactive_test.c index cfba40b2..e8547297 100644 --- a/src/test/njs_interactive_test.c +++ b/src/test/njs_interactive_test.c @@ -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 --git a/src/test/njs_unit_test.c b/src/test/njs_unit_test.c index b6361921..d278fae0 100644 --- a/src/test/njs_unit_test.c +++ b/src/test/njs_unit_test.c @@ -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 --git a/test/js/fs_promises_002.js b/test/js/fs_promises_002.js index 394616e5..0ad3e728 100644 --- a/test/js/fs_promises_002.js +++ b/test/js/fs_promises_002.js @@ -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 --git a/test/js/fs_promises_003.js b/test/js/fs_promises_003.js new file mode 100644 index 00000000..671a97e9 --- /dev/null +++ b/test/js/fs_promises_003.js @@ -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 --git a/test/js/fs_promises_004.js b/test/js/fs_promises_004.js new file mode 100644 index 00000000..2d6f760a --- /dev/null +++ b/test/js/fs_promises_004.js @@ -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 --git a/test/njs_expect_test.exp b/test/njs_expect_test.exp index 1731d2c9..3ed1c961 100644 --- a/test/njs_expect_test.exp +++ b/test/njs_expect_test.exp @@ -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" -- 2.47.3