From: Artem S. Povalyukhin Date: Sun, 31 May 2020 05:45:41 +0000 (+0300) Subject: Added fs.Dirent, fs.readdir() and friends. X-Git-Tag: 0.4.2~29 X-Git-Url: http://www.kaiwu.me/postgresql/commit/?a=commitdiff_plain;h=aa595210669d7525fa8518ec0edf99975ead71db;p=njs.git Added fs.Dirent, fs.readdir() and friends. This closes #254 issue on Github. --- diff --git a/src/njs_builtin.c b/src/njs_builtin.c index b1a1cce7..8aa1ec4e 100644 --- a/src/njs_builtin.c +++ b/src/njs_builtin.c @@ -73,6 +73,7 @@ static const njs_object_type_init_t *const /* Hidden types. */ + &njs_dirent_type_init, &njs_hash_type_init, &njs_hmac_type_init, &njs_typed_array_type_init, diff --git a/src/njs_fs.c b/src/njs_fs.c index 4b95c95c..4f7f0519 100644 --- a/src/njs_fs.c +++ b/src/njs_fs.c @@ -7,6 +7,31 @@ #include +#include + +#if (NJS_SOLARIS) + +#define DT_DIR 0 +#define DT_REG 0 +#define DT_CHR 0 +#define DT_LNK 0 +#define DT_BLK 0 +#define DT_FIFO 0 +#define DT_SOCK 0 +#define NJS_DT_INVALID 0xffffffff + +#define njs_dentry_type(_dentry) \ + (NJS_DT_INVALID) + +#else + +#define NJS_DT_INVALID 0xffffffff + +#define njs_dentry_type(_dentry) \ + ((_dentry)->d_type) + +#endif + #define njs_fs_magic(calltype, mode) \ (((mode) << 2) | calltype) @@ -53,6 +78,8 @@ static njs_fs_encoding_t njs_fs_encoding(njs_vm_t *vm, njs_value_t *value); static njs_int_t njs_fs_add_event(njs_vm_t *vm, const njs_value_t *callback, const njs_value_t *args, njs_uint_t nargs); +static njs_int_t njs_fs_dirent_create(njs_vm_t *vm, njs_value_t *name, + njs_value_t *type, njs_value_t *retval); static njs_fs_entry_t njs_flags_table[] = { { njs_str("r"), O_RDONLY }, @@ -894,6 +921,163 @@ njs_fs_rmdir(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, } +static njs_int_t +njs_fs_readdir(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, + njs_index_t calltype) +{ + DIR *dir; + u_char *d_name; + size_t size; + ssize_t length; + njs_int_t ret; + const char *dir_path; + njs_value_t encoding, types, ename, etype, retval, *path, *callback, + *options, *value; + njs_array_t *results; + struct dirent *entry; + njs_fs_encoding_t enc; + + static const njs_value_t string_encoding = njs_string("encoding"); + static const njs_value_t string_types = njs_string("withFileTypes"); + + path = njs_arg(args, nargs, 1); + ret = njs_fs_path_arg(vm, &dir_path, path, &njs_str_value("path")); + if (njs_slow_path(ret != NJS_OK)) { + return ret; + } + + callback = NULL; + options = njs_arg(args, nargs, 2); + + if (njs_slow_path(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_false(&types); + 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; + } + + ret = njs_value_property(vm, options, njs_value_arg(&string_types), + &types); + 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; + } + + results = njs_array_alloc(vm, 1, 0, NJS_ARRAY_SPARE); + if (njs_slow_path(results == NULL)) { + return NJS_ERROR; + } + + njs_set_array(&retval, results); + + dir = opendir(dir_path); + if (njs_slow_path(dir == NULL)) { + ret = njs_fs_error(vm, "opendir", strerror(errno), path, errno, + &retval); + goto done; + } + + for ( ;; ) { + errno = 0; + entry = readdir(dir); + if (njs_slow_path(entry == NULL)) { + if (errno != 0) { + ret = njs_fs_error(vm, "readdir", strerror(errno), path, errno, + &retval); + } + + goto done; + } + + d_name = (u_char *) entry->d_name; + + size = njs_strlen(d_name); + length = njs_utf8_length(d_name, size); + if (njs_slow_path(length < 0)) { + length = 0; + } + + if ((length == 1 && d_name[0] == '.') + || (length == 2 && (d_name[0] == '.' && d_name[1] == '.'))) + { + continue; + } + + if (njs_fast_path(!njs_is_true(&types))) { + ret = njs_array_string_add(vm, results, d_name, size, length); + if (njs_slow_path(ret != NJS_OK)) { + goto done; + } + + continue; + } + + ret = njs_string_new(vm, &ename, d_name, size, length); + if (njs_slow_path(ret != NJS_OK)) { + goto done; + } + + njs_set_number(&etype, njs_dentry_type(entry)); + + value = njs_array_push(vm, results); + if (njs_slow_path(value == NULL)) { + goto done; + } + + ret = njs_fs_dirent_create(vm, &ename, &etype, value); + if (njs_slow_path(ret != NJS_OK)) { + goto done; + } + } + +done: + + if (dir != NULL) { + (void) closedir(dir); + } + + 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) { @@ -1231,6 +1415,205 @@ memory_error: } +static njs_int_t +njs_fs_dirent_create(njs_vm_t *vm, njs_value_t *name, njs_value_t *type, + njs_value_t *retval) +{ + njs_int_t ret; + njs_object_t *object; + + static const njs_value_t string_name = njs_string("name"); + static const njs_value_t string_type = njs_string("type"); + + object = njs_object_alloc(vm); + if (njs_slow_path(object == NULL)) { + return NJS_ERROR; + } + + object->__proto__ = &vm->prototypes[NJS_OBJ_TYPE_FS_DIRENT].object; + + njs_set_object(retval, object); + + ret = njs_value_property_set(vm, retval, njs_value_arg(&string_name), + name); + if (njs_slow_path(ret != NJS_OK)) { + return ret; + } + + /* TODO: use a private symbol as a key. */ + ret = njs_value_property_set(vm, retval, njs_value_arg(&string_type), + type); + if (njs_slow_path(ret != NJS_OK)) { + return ret; + } + + return NJS_OK; +} + + +static njs_int_t +njs_dirent_constructor(njs_vm_t *vm, njs_value_t *args, + njs_uint_t nargs, njs_index_t unused) +{ + if (njs_slow_path(!vm->top_frame->ctor)) { + njs_type_error(vm, "the Dirent constructor must be called with new"); + return NJS_ERROR; + } + + return njs_fs_dirent_create(vm, njs_arg(args, nargs, 1), + njs_arg(args, nargs, 2), &vm->retval); +} + + +static const njs_object_prop_t njs_dirent_constructor_properties[] = +{ + { + .type = NJS_PROPERTY, + .name = njs_string("name"), + .value = njs_string("Dirent"), + .configurable = 1, + }, + + { + .type = NJS_PROPERTY, + .name = njs_string("length"), + .value = njs_value(NJS_NUMBER, 1, 2.0), + .configurable = 1, + }, + + { + .type = NJS_PROPERTY_HANDLER, + .name = njs_string("prototype"), + .value = njs_prop_handler(njs_object_prototype_create), + }, +}; + + +const njs_object_init_t njs_dirent_constructor_init = { + njs_dirent_constructor_properties, + njs_nitems(njs_dirent_constructor_properties), +}; + + +static njs_int_t +njs_fs_dirent_test(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, + njs_index_t testtype) +{ + njs_int_t ret; + njs_value_t type, *this; + + static const njs_value_t string_type = njs_string("type"); + + this = njs_argument(args, 0); + + ret = njs_value_property(vm, this, njs_value_arg(&string_type), &type); + if (njs_slow_path(ret == NJS_ERROR)) { + return ret; + } + + if (njs_slow_path(njs_is_number(&type) + && (njs_number(&type) == NJS_DT_INVALID))) + { + njs_internal_error(vm, "dentry type is not supported on this platform"); + return NJS_ERROR; + } + + njs_set_boolean(&vm->retval, + njs_is_number(&type) && testtype == njs_number(&type)); + + return NJS_OK; +} + + +static const njs_object_prop_t njs_dirent_prototype_properties[] = +{ + { + .type = NJS_PROPERTY, + .name = njs_wellknown_symbol(NJS_SYMBOL_TO_STRING_TAG), + .value = njs_string("Dirent"), + .configurable = 1, + }, + + { + .type = NJS_PROPERTY_HANDLER, + .name = njs_string("constructor"), + .value = njs_prop_handler(njs_object_prototype_create_constructor), + .writable = 1, + .configurable = 1, + }, + + { + .type = NJS_PROPERTY, + .name = njs_string("isDirectory"), + .value = njs_native_function2(njs_fs_dirent_test, 0, DT_DIR), + .writable = 1, + .configurable = 1, + }, + + { + .type = NJS_PROPERTY, + .name = njs_string("isFile"), + .value = njs_native_function2(njs_fs_dirent_test, 0, DT_REG), + .writable = 1, + .configurable = 1, + }, + + { + .type = NJS_PROPERTY, + .name = njs_string("isBlockDevice"), + .value = njs_native_function2(njs_fs_dirent_test, 0, DT_BLK), + .writable = 1, + .configurable = 1, + }, + + { + .type = NJS_PROPERTY, + .name = njs_long_string("isCharacterDevice"), + .value = njs_native_function2(njs_fs_dirent_test, 0, DT_CHR), + .writable = 1, + .configurable = 1, + }, + + { + .type = NJS_PROPERTY, + .name = njs_string("isSymbolicLink"), + .value = njs_native_function2(njs_fs_dirent_test, 0, DT_LNK), + .writable = 1, + .configurable = 1, + }, + + { + .type = NJS_PROPERTY, + .name = njs_string("isFIFO"), + .value = njs_native_function2(njs_fs_dirent_test, 0, DT_FIFO), + .writable = 1, + .configurable = 1, + }, + + { + .type = NJS_PROPERTY, + .name = njs_string("isSocket"), + .value = njs_native_function2(njs_fs_dirent_test, 0, DT_SOCK), + .writable = 1, + .configurable = 1, + }, +}; + + +const njs_object_init_t njs_dirent_prototype_init = { + njs_dirent_prototype_properties, + njs_nitems(njs_dirent_prototype_properties), +}; + + +const njs_object_type_init_t njs_dirent_type_init = { + .constructor = njs_native_ctor(njs_dirent_constructor, 2, 0), + .prototype_props = &njs_dirent_prototype_init, + .constructor_props = &njs_dirent_constructor_init, + .prototype_value = { .object = { .type = NJS_OBJECT } }, +}; + + static const njs_object_prop_t njs_fs_promises_properties[] = { { @@ -1291,6 +1674,14 @@ static const njs_object_prop_t njs_fs_promises_properties[] = .configurable = 1, }, + { + .type = NJS_PROPERTY, + .name = njs_string("readdir"), + .value = njs_native_function2(njs_fs_readdir, 0, NJS_FS_PROMISE), + .writable = 1, + .configurable = 1, + }, + { .type = NJS_PROPERTY, .name = njs_string("symlink"), @@ -1397,6 +1788,14 @@ static const njs_object_prop_t njs_fs_object_properties[] = .value = njs_prop_handler(njs_fs_promises), }, + { + .type = NJS_PROPERTY, + .name = njs_string("Dirent"), + .value = _njs_native_function(njs_dirent_constructor, 2, 1, 0), + .writable = 1, + .configurable = 1, + }, + { .type = NJS_PROPERTY, .name = njs_string("access"), @@ -1560,6 +1959,22 @@ static const njs_object_prop_t njs_fs_object_properties[] = .writable = 1, .configurable = 1, }, + + { + .type = NJS_PROPERTY, + .name = njs_string("readdir"), + .value = njs_native_function2(njs_fs_readdir, 0, NJS_FS_CALLBACK), + .writable = 1, + .configurable = 1, + }, + + { + .type = NJS_PROPERTY, + .name = njs_string("readdirSync"), + .value = njs_native_function2(njs_fs_readdir, 0, NJS_FS_DIRECT), + .writable = 1, + .configurable = 1, + }, }; diff --git a/src/njs_fs.h b/src/njs_fs.h index db6a72e0..b850e50c 100644 --- a/src/njs_fs.h +++ b/src/njs_fs.h @@ -10,5 +10,6 @@ extern const njs_object_init_t njs_fs_object_init; +extern const njs_object_type_init_t njs_dirent_type_init; #endif /* _NJS_FS_H_INCLUDED_ */ diff --git a/src/njs_vm.h b/src/njs_vm.h index 05afebbe..48c8fea8 100644 --- a/src/njs_vm.h +++ b/src/njs_vm.h @@ -92,8 +92,9 @@ typedef enum { NJS_OBJ_TYPE_PROMISE, NJS_OBJ_TYPE_ARRAY_BUFFER, + NJS_OBJ_TYPE_FS_DIRENT, +#define NJS_OBJ_TYPE_HIDDEN_MIN (NJS_OBJ_TYPE_FS_DIRENT) NJS_OBJ_TYPE_CRYPTO_HASH, -#define NJS_OBJ_TYPE_HIDDEN_MIN (NJS_OBJ_TYPE_CRYPTO_HASH) NJS_OBJ_TYPE_CRYPTO_HMAC, NJS_OBJ_TYPE_TYPED_ARRAY, #define NJS_OBJ_TYPE_HIDDEN_MAX (NJS_OBJ_TYPE_TYPED_ARRAY + 1) diff --git a/src/test/njs_unit_test.c b/src/test/njs_unit_test.c index 7b5c8d4e..9292a0bd 100644 --- a/src/test/njs_unit_test.c +++ b/src/test/njs_unit_test.c @@ -16371,12 +16371,20 @@ static njs_unit_test_t njs_test[] = "'writeFileSync'," "'appendFile'," "'appendFileSync'," + "'rename'," + "'renameSync'," "'symlink'," "'symlinkSync'," "'unlink'," "'unlinkSync'," "'realpath'," "'realpathSync'," + "'mkdir'," + "'mkdirSync'," + "'rmdir'," + "'rmdirSync'," + "'readdir'," + "'readdirSync'," "]," "test = (fname) =>" "[undefined, null, false, NaN, Symbol(), {}, Object('/njs_unknown_path')]" @@ -16399,9 +16407,13 @@ static njs_unit_test_t njs_test[] = "'readFile'," "'writeFile'," "'appendFile'," + "'rename'," "'symlink'," "'unlink'," "'realpath'," + "'mkdir'," + "'rmdir'," + "'readdir'," "];" "func.every((x) => typeof fs[x] == 'function')"), njs_str("true")}, @@ -16423,6 +16435,35 @@ static njs_unit_test_t njs_test[] = "items.every((x) => typeof fsc[x] == 'number')"), njs_str("true")}, + /* require('fs').Dirent */ + + { njs_str("var fs = require('fs');" + "typeof fs.Dirent"), + njs_str("function") }, + + { njs_str("var fs = require('fs');" + "fs.Dirent('file', 123)"), + njs_str("TypeError: the Dirent constructor must be called with new") }, + + { njs_str("var fs = require('fs');" + "var e = new fs.Dirent('file', 123); [e.name, e.type]"), + njs_str("file,123") }, + + { njs_str("var " + "fs = require('fs')," + "e = new fs.Dirent('file', 0)," + "func = [" + "'isDirectory'," + "'isFile'," + "'isBlockDevice'," + "'isCharacterDevice'," + "'isSymbolicLink'," + "'isFIFO'," + "'isSocket'," + "];" + "func.every((x) => typeof e[x] == 'function')"), + njs_str("true")}, + /* require('crypto').createHash() */ { njs_str("var h = require('crypto').createHash('sha1');" diff --git a/test/js/fs_promises_007.js b/test/js/fs_promises_007.js new file mode 100644 index 00000000..3dccf275 --- /dev/null +++ b/test/js/fs_promises_007.js @@ -0,0 +1,231 @@ +var fs = require('fs'); +var fsp = fs.promises; +var dname = './build/test/fs_promises_007'; +var dname_utf8 = './build/test/fs_promises_αβγ_007'; +var fname = (d) => d + '/fs_promises_007_file'; +var lname = (d) => d + '/fs_promises_007_link'; +var cname = (d) => d + '/fs_promises_αβγ_007_dir'; + + +var dir_test = [cname(''), lname(''), fname('')].map((x) => x.substring(1)); +var match = (entry) => { + var idx = dir_test.indexOf(entry.name); + + try { + switch(idx) { + case 0: + return entry.isDirectory(); + case 1: + return entry.isSymbolicLink(); + case 2: + return entry.isFile(); + default: + return false; + } + } catch (e) { + if (e.name == 'InternalError') { + return true; + } + + throw e; + } +}; + + +var testSync = () => new Promise((resolve, reject) => { + try { + try { fs.rmdirSync(cname(dname)); } catch (e) {} + try { fs.rmdirSync(cname(dname_utf8)); } catch (e) {} + try { fs.unlinkSync(lname(dname)); } catch (e) {} + try { fs.unlinkSync(lname(dname_utf8)); } catch (e) {} + try { fs.unlinkSync(fname(dname)); } catch (e) {} + try { fs.unlinkSync(fname(dname_utf8)); } catch (e) {} + try { fs.rmdirSync(dname); } catch (e) {} + try { fs.rmdirSync(dname_utf8); } catch (e) {} + + try { + fs.readdirSync(dname); + throw new Error('fs.readdirSync - error 0'); + + } catch (e) { + if (e.code != 'ENOENT') { + // njs: e.syscall == 'opendir' + // node: e.syscall == 'scandir' + throw e; + } + } + + fs.mkdirSync(dname); + fs.mkdirSync(dname_utf8); + fs.writeFileSync(fname(dname), fname(dname)); + fs.writeFileSync(fname(dname_utf8), fname(dname_utf8)); + fs.symlinkSync(fname('.'), lname(dname)); + fs.symlinkSync(fname('.'), lname(dname_utf8)); + fs.mkdirSync(cname(dname)); + fs.mkdirSync(cname(dname_utf8)); + + var dir = fs.readdirSync(dname); + var dir_utf8 = fs.readdirSync(dname_utf8); + if (dir.length != dir_utf8.length || dir.length != 3) { + throw new Error('fs.readdirSync - error 1'); + } + + var test = dir.filter((x) => !dir_test.includes(x)); + if (test.length != 0) { + throw new Error('fs.readdirSync - error 2'); + } + + var test = dir_utf8.filter((x) => !dir_test.includes(x)); + if (test.length != 0) { + throw new Error('fs.readdirSync - error 3'); + } + + var dir = fs.readdirSync(dname, { withFileTypes: true }); + var dir_utf8 = fs.readdirSync(dname_utf8, { withFileTypes: true }); + if (dir.length != dir_utf8.length || dir.length != 3) { + throw new Error('fs.readdirSync - error 4'); + } + + var test = dir.filter((x) => !match(x)); + if (test.length != 0) { + throw new Error('fs.readdirSync - error 5'); + } + + var test = dir_utf8.filter((x) => !match(x)); + if (test.length != 0) { + throw new Error('fs.readdirSync - error 6'); + } + + resolve(); + + } catch (e) { + reject(e); + } +}); + + +var testCallback = () => new Promise((resolve, reject) => { + try { + try { fs.rmdirSync(cname(dname)); } catch (e) {} + try { fs.unlinkSync(lname(dname)); } catch (e) {} + try { fs.unlinkSync(fname(dname)); } catch (e) {} + try { fs.rmdirSync(dname); } catch (e) {} + + fs.readdir(dname, (err, files) => { + if (!err || err.code != 'ENOENT') { + reject(new Error('fs.readdir - error 1')); + } + + try { + fs.mkdirSync(dname); + fs.writeFileSync(fname(dname), fname(dname)); + fs.symlinkSync(fname('.'), lname(dname)); + fs.mkdirSync(cname(dname)); + + } catch (e) { + reject(e); + } + + fs.readdir(dname, (err, dir) => { + if (err) { + reject(err); + } + + if (dir.length != 3) { + reject(new Error('fs.readdir - error 2')); + } + + var test = dir.filter((x) => !dir_test.includes(x)); + if (test.length != 0) { + reject(new Error('fs.readdir - error 3')); + } + + fs.readdir(dname, { withFileTypes: true }, (err, dir) => { + if (err) { + reject(err); + } + + if (dir.length != 3) { + reject(new Error('fs.readdir - error 4')); + } + + var test = dir.filter((x) => !match(x)); + if (test.length != 0) { + reject(new Error('fs.readdir - error 5')); + } + + resolve(); + }); + }); + }); + + } catch (e) { + reject(e); + } +}); + + +Promise.resolve() +.then(testSync) +.then(() => { + console.log('test fs.readdirSync'); +}) +.catch((e) => { + console.log('test fs.readdirSync failed', JSON.stringify(e)); +}) + +.then(testCallback) +.then(() => { + console.log('test fs.readdir'); +}) +.catch((e) => { + console.log('test fs.readdir failed', JSON.stringify(e)); +}) + +.then(() => { + try { fs.rmdirSync(cname(dname)); } catch (e) {} + try { fs.unlinkSync(lname(dname)); } catch (e) {} + try { fs.unlinkSync(fname(dname)); } catch (e) {} + try { fs.rmdirSync(dname); } catch (e) {} +}) +.then(() => fsp.readdir(dname) + .then(() => { throw new Error('fsp.readdir - error 1'); })) +.catch((e) => { + if (e.code != 'ENOENT') { + throw e; + } +}) +.then(() => { + fs.mkdirSync(dname); + fs.writeFileSync(fname(dname), fname(dname)); + fs.symlinkSync(fname('.'), lname(dname)); + fs.mkdirSync(cname(dname)); +}) +.then(() => fsp.readdir(dname)) +.then((dir) => { + if (dir.length != 3) { + throw new Error('fsp.readdir - error 2'); + } + + var test = dir.filter((x) => !dir_test.includes(x)); + if (test.length != 0) { + throw new Error('fsp.readdir - error 3'); + } +}) +.then(() => fsp.readdir(dname, { withFileTypes: true })) +.then((dir) => { + if (dir.length != 3) { + throw new Error('fsp.readdir - error 4'); + } + + var test = dir.filter((x) => !match(x)); + if (test.length != 0) { + throw new Error('fsp.readdir - error 5'); + } +}) +.then(() => { + console.log('test fsp.readdir'); +}) +.catch((e) => { + console.log('test fsp.readdir failed', JSON.stringify(e)); +}); diff --git a/test/njs_expect_test.exp b/test/njs_expect_test.exp index 6c1a0492..9723f66a 100644 --- a/test/njs_expect_test.exp +++ b/test/njs_expect_test.exp @@ -459,15 +459,6 @@ njs_test { "queue.toString()\r\n'0,1,2,3,4,5'"} } -# require('fs') - -njs_test { - {"var fs = require('fs')\r\n" - "undefined\r\n>> "} - {"fs.read\t" - "fs.read\a*File"} -} - # require('fs').readFile() njs_test { @@ -1144,3 +1135,8 @@ njs_run {"./test/js/fs_promises_006.js"} \ "test fs.renameSync test fs.rename test fsp.rename" + +njs_run {"./test/js/fs_promises_007.js"} \ +"test fs.readdirSync +test fs.readdir +test fsp.readdir"