From 409eb9f2a5164fcd4d8cd6c0d5944d337126d174 Mon Sep 17 00:00:00 2001 From: Dmitry Volyntsev Date: Wed, 3 Nov 2021 15:46:15 +0000 Subject: [PATCH] Added fs.Stats, fs.stat() and friends. --- auto/stat | 58 ++++ configure | 1 + src/njs_builtin.c | 1 + src/njs_fs.c | 722 ++++++++++++++++++++++++++++++++++++++- src/njs_fs.h | 1 + src/njs_value.h | 1 + src/njs_vm.h | 1 + test/fs/methods.js | 186 ++++++++++ test/njs_expect_test.exp | 8 +- 9 files changed, 970 insertions(+), 9 deletions(-) create mode 100644 auto/stat diff --git a/auto/stat b/auto/stat new file mode 100644 index 00000000..12b209a8 --- /dev/null +++ b/auto/stat @@ -0,0 +1,58 @@ + +# Copyright (C) Dmitry Volyntsev +# Copyright (C) NGINX, Inc. + + +njs_feature="stat.st_atimespec" +njs_feature_name=NJS_HAVE_STAT_ATIMESPEC +njs_feature_run=no +njs_feature_incs= +njs_feature_libs= +njs_feature_test="#include + + int main(void) { + struct stat st; + + if (fstat(0, &st) != 0) { + return 1; + } + + return (int) st.st_atimespec.tv_sec; + }" +. auto/feature + + +njs_feature="stat.st_birthtim" +njs_feature_name=NJS_HAVE_STAT_BIRTHTIM +njs_feature_incs= +njs_feature_libs= +njs_feature_test="#include + + int main(void) { + struct stat st; + + if (fstat(0, &st) != 0) { + return 1; + } + + return (int) st.st_birthtim.tv_sec; + }" +. auto/feature + + +njs_feature="stat.st_atim" +njs_feature_name=NJS_HAVE_STAT_ATIM +njs_feature_incs= +njs_feature_libs= +njs_feature_test="#include + + int main(void) { + struct stat st; + + if (fstat(0, &st) != 0) { + return 1; + } + + return (int) st.st_atim.tv_sec; + }" +. auto/feature diff --git a/configure b/configure index 9e848233..5ff6738f 100755 --- a/configure +++ b/configure @@ -23,6 +23,7 @@ set -u . auto/time . auto/memalign . auto/getrandom +. auto/stat . auto/explicit_bzero . auto/pcre . auto/readline diff --git a/src/njs_builtin.c b/src/njs_builtin.c index f4868491..4cd96897 100644 --- a/src/njs_builtin.c +++ b/src/njs_builtin.c @@ -89,6 +89,7 @@ static const njs_object_type_init_t *const &njs_iterator_type_init, &njs_array_iterator_type_init, &njs_dirent_type_init, + &njs_stats_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 8ed51852..129978f8 100644 --- a/src/njs_fs.c +++ b/src/njs_fs.c @@ -12,12 +12,12 @@ #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 DT_REG 1 +#define DT_CHR 2 +#define DT_LNK 3 +#define DT_BLK 4 +#define DT_FIFO 5 +#define DT_SOCK 6 #define NJS_DT_INVALID 0xffffffff #define njs_dentry_type(_dentry) \ @@ -50,9 +50,15 @@ typedef enum { } njs_fs_writemode_t; +typedef enum { + NJS_FS_STAT, + NJS_FS_LSTAT, +} njs_fs_statmode_t; + + typedef struct { - njs_str_t name; - int value; + njs_str_t name; + int value; } njs_fs_entry_t; @@ -74,6 +80,48 @@ typedef enum { } njs_ftw_type_t; +typedef struct { + long tv_sec; + long tv_nsec; +} njs_timespec_t; + + +typedef struct { + uint64_t st_dev; + uint64_t st_mode; + uint64_t st_nlink; + uint64_t st_uid; + uint64_t st_gid; + uint64_t st_rdev; + uint64_t st_ino; + uint64_t st_size; + uint64_t st_blksize; + uint64_t st_blocks; + njs_timespec_t st_atim; + njs_timespec_t st_mtim; + njs_timespec_t st_ctim; + njs_timespec_t st_birthtim; +} njs_stat_t; + + +typedef enum { + NJS_FS_STAT_DEV, + NJS_FS_STAT_INO, + NJS_FS_STAT_MODE, + NJS_FS_STAT_NLINK, + NJS_FS_STAT_UID, + NJS_FS_STAT_GID, + NJS_FS_STAT_RDEV, + NJS_FS_STAT_SIZE, + NJS_FS_STAT_BLKSIZE, + NJS_FS_STAT_BLOCKS, + NJS_FS_STAT_ATIME, + NJS_FS_STAT_BIRTHTIME, + NJS_FS_STAT_CTIME, + NJS_FS_STAT_MTIME, +} njs_stat_prop_t; + + typedef njs_int_t (*njs_file_tree_walk_cb_t)(const char *, const struct stat *, njs_ftw_type_t); @@ -105,6 +153,9 @@ static njs_int_t njs_fs_add_event(njs_vm_t *vm, const njs_value_t *callback, 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_int_t njs_fs_stats_create(njs_vm_t *vm, struct stat *st, + njs_value_t *retval); + static njs_fs_entry_t njs_flags_table[] = { { njs_str("a"), O_APPEND | O_CREAT | O_WRONLY }, { njs_str("a+"), O_APPEND | O_CREAT | O_RDWR }, @@ -1009,6 +1060,102 @@ done: } +static njs_int_t +njs_fs_stat(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, + njs_index_t magic) +{ + njs_int_t ret; + njs_bool_t throw; + struct stat sb; + const char *path; + njs_value_t retval, *callback, *options; + njs_fs_calltype_t calltype; + char path_buf[NJS_MAX_PATH + 1]; + + static const njs_value_t string_bigint = njs_string("bigint"); + static const njs_value_t string_throw = njs_string("throwIfNoEntry"); + + path = njs_fs_path(vm, path_buf, njs_arg(args, nargs, 1), "path"); + if (njs_slow_path(path == NULL)) { + return NJS_ERROR; + } + + callback = NULL; + calltype = magic & 3; + 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); + } + } + + throw = 1; + + switch (options->type) { + case NJS_UNDEFINED: + break; + + default: + if (!njs_is_object(options)) { + njs_type_error(vm, "Unknown options type: \"%s\" " + "(an object required)", + njs_type_string(options->type)); + return NJS_ERROR; + } + + ret = njs_value_property(vm, options, njs_value_arg(&string_bigint), + &retval); + if (njs_slow_path(ret == NJS_ERROR)) { + return ret; + } + + if (njs_bool(&retval)) { + njs_type_error(vm, "\"bigint\" is not supported"); + return NJS_ERROR; + } + + if (calltype == NJS_FS_DIRECT) { + ret = njs_value_property(vm, options, njs_value_arg(&string_throw), + &retval); + if (njs_slow_path(ret == NJS_ERROR)) { + return ret; + } + + throw = njs_bool(&retval); + } + } + + ret = ((magic >> 2) == NJS_FS_STAT) ? stat(path, &sb) : lstat(path, &sb); + if (njs_slow_path(ret != 0)) { + if (errno != ENOENT || throw) { + ret = njs_fs_error(vm, + ((magic >> 2) == NJS_FS_STAT) ? "stat" : "lstat", + strerror(errno), path, errno, &retval); + if (njs_slow_path(ret != NJS_OK)) { + return NJS_ERROR; + } + } else { + njs_set_undefined(&retval); + } + + return njs_fs_result(vm, &retval, calltype, callback, 2); + } + + ret = njs_fs_stats_create(vm, &sb, &retval); + if (njs_slow_path(ret != NJS_OK)) { + return NJS_ERROR; + } + + return njs_fs_result(vm, &retval, calltype, callback, 2); +} + + static njs_int_t njs_fs_fd_read(njs_vm_t *vm, int fd, njs_str_t *data) { @@ -1870,6 +2017,511 @@ const njs_object_type_init_t njs_dirent_type_init = { }; +static void +njs_fs_to_stat(njs_stat_t *dst, struct stat *st) +{ + dst->st_dev = st->st_dev; + dst->st_mode = st->st_mode; + dst->st_nlink = st->st_nlink; + dst->st_uid = st->st_uid; + dst->st_gid = st->st_gid; + dst->st_rdev = st->st_rdev; + dst->st_ino = st->st_ino; + dst->st_size = st->st_size; + dst->st_blksize = st->st_blksize; + dst->st_blocks = st->st_blocks; + +#if (NJS_HAVE_STAT_ATIMESPEC) + + dst->st_atim.tv_sec = st->st_atimespec.tv_sec; + dst->st_atim.tv_nsec = st->st_atimespec.tv_nsec; + dst->st_mtim.tv_sec = st->st_mtimespec.tv_sec; + dst->st_mtim.tv_nsec = st->st_mtimespec.tv_nsec; + dst->st_ctim.tv_sec = st->st_ctimespec.tv_sec; + dst->st_ctim.tv_nsec = st->st_ctimespec.tv_nsec; + dst->st_birthtim.tv_sec = st->st_birthtimespec.tv_sec; + dst->st_birthtim.tv_nsec = st->st_birthtimespec.tv_nsec; + +#elif (NJS_HAVE_STAT_ATIM) + + dst->st_atim.tv_sec = st->st_atim.tv_sec; + dst->st_atim.tv_nsec = st->st_atim.tv_nsec; + dst->st_mtim.tv_sec = st->st_mtim.tv_sec; + dst->st_mtim.tv_nsec = st->st_mtim.tv_nsec; + dst->st_ctim.tv_sec = st->st_ctim.tv_sec; + dst->st_ctim.tv_nsec = st->st_ctim.tv_nsec; + +#if (NJS_HAVE_STAT_BIRTHTIM) + dst->st_birthtim.tv_sec = st->st_birthtim.tv_sec; + dst->st_birthtim.tv_nsec = st->st_birthtim.tv_nsec; +#else + dst->st_birthtim.tv_sec = st->st_ctim.tv_sec; + dst->st_birthtim.tv_nsec = st->st_ctim.tv_nsec; +#endif + +#else + + dst->st_atim.tv_sec = st->st_atime; + dst->st_atim.tv_nsec = 0; + dst->st_mtim.tv_sec = st->st_mtime; + dst->st_mtim.tv_nsec = 0; + dst->st_ctim.tv_sec = st->st_ctime; + dst->st_ctim.tv_nsec = 0; + dst->st_birthtim.tv_sec = st->st_ctime; + dst->st_birthtim.tv_nsec = 0; + +#endif +} + + +static njs_int_t +njs_fs_stats_create(njs_vm_t *vm, struct stat *st, njs_value_t *retval) +{ + njs_stat_t *copy; + njs_object_value_t *stat; + + stat = njs_object_value_alloc(vm, NJS_OBJ_TYPE_FS_STATS, 0, NULL); + if (njs_slow_path(stat == NULL)) { + return NJS_ERROR; + } + + stat->object.shared_hash = + vm->prototypes[NJS_OBJ_TYPE_FS_STATS].object.shared_hash; + + copy = njs_mp_alloc(vm->mem_pool, sizeof(njs_stat_t)); + if (copy == NULL) { + njs_memory_error(vm); + return NJS_ERROR; + } + + njs_fs_to_stat(copy, st); + + njs_set_data(&stat->value, copy, NJS_DATA_TAG_FS_STAT); + njs_set_object_value(retval, stat); + + return NJS_OK; +} + + +static njs_int_t +njs_stats_constructor(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, + njs_index_t unused) +{ + njs_type_error(vm, "Stats is not a constructor"); + return NJS_ERROR; +} + + +static njs_int_t +njs_fs_stats_test(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, + njs_index_t testtype) +{ + unsigned mask; + njs_stat_t *st; + njs_value_t *this; + + this = njs_argument(args, 0); + + if (njs_slow_path(!njs_is_object_data(this, NJS_DATA_TAG_FS_STAT))) { + return NJS_DECLINED; + } + + st = njs_object_data(this); + + switch (testtype) { + case DT_DIR: + mask = S_IFDIR; + break; + + case DT_REG: + mask = S_IFREG; + break; + + case DT_CHR: + mask = S_IFCHR; + break; + + case DT_LNK: + mask = S_IFLNK; + break; + + case DT_BLK: + mask = S_IFBLK; + break; + + case DT_FIFO: + mask = S_IFIFO; + break; + + case DT_SOCK: + default: + mask = S_IFSOCK; + } + + njs_set_boolean(&vm->retval, (st->st_mode & S_IFMT) == mask); + + return NJS_OK; +} + + +static njs_int_t +njs_fs_stats_prop(njs_vm_t *vm, njs_object_prop_t *prop, njs_value_t *value, + njs_value_t *setval, njs_value_t *retval) +{ + double v; + njs_date_t *date; + njs_stat_t *st; + +#define njs_fs_time_ms(ts) ((ts)->tv_sec * 1000.0 + (ts)->tv_nsec / 1000000.0) + + if (njs_slow_path(!njs_is_object_data(value, NJS_DATA_TAG_FS_STAT))) { + return NJS_DECLINED; + } + + st = njs_object_data(value); + + switch (prop->value.data.magic16) { + case NJS_FS_STAT_DEV: + v = st->st_dev; + break; + + case NJS_FS_STAT_INO: + v = st->st_ino; + break; + + case NJS_FS_STAT_MODE: + v = st->st_mode; + break; + + case NJS_FS_STAT_NLINK: + v = st->st_nlink; + break; + + case NJS_FS_STAT_UID: + v = st->st_uid; + break; + + case NJS_FS_STAT_GID: + v = st->st_gid; + break; + + case NJS_FS_STAT_RDEV: + v = st->st_rdev; + break; + + case NJS_FS_STAT_SIZE: + v = st->st_size; + break; + + case NJS_FS_STAT_BLKSIZE: + v = st->st_blksize; + break; + + case NJS_FS_STAT_BLOCKS: + v = st->st_blocks; + break; + + case NJS_FS_STAT_ATIME: + v = njs_fs_time_ms(&st->st_atim); + break; + + case NJS_FS_STAT_BIRTHTIME: + v = njs_fs_time_ms(&st->st_birthtim); + break; + + case NJS_FS_STAT_CTIME: + v = njs_fs_time_ms(&st->st_ctim); + break; + + case NJS_FS_STAT_MTIME: + default: + v = njs_fs_time_ms(&st->st_mtim); + break; + } + + switch (prop->value.data.magic32) { + case NJS_NUMBER: + njs_set_number(retval, v); + break; + + case NJS_DATE: + default: + date = njs_date_alloc(vm, v); + if (njs_slow_path(date == NULL)) { + return NJS_ERROR; + } + + njs_set_date(retval, date); + break; + } + + return NJS_OK; +} + + +static const njs_object_prop_t njs_stats_constructor_properties[] = +{ + { + .type = NJS_PROPERTY, + .name = njs_string("name"), + .value = njs_string("Stats"), + .configurable = 1, + }, + + { + .type = NJS_PROPERTY, + .name = njs_string("length"), + .value = njs_value(NJS_NUMBER, 1, 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_stats_constructor_init = { + njs_stats_constructor_properties, + njs_nitems(njs_stats_constructor_properties), +}; + + +static const njs_object_prop_t njs_stats_prototype_properties[] = +{ + { + .type = NJS_PROPERTY, + .name = njs_wellknown_symbol(NJS_SYMBOL_TO_STRING_TAG), + .value = njs_string("Stats"), + .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("isBlockDevice"), + .value = njs_native_function2(njs_fs_stats_test, 0, DT_BLK), + .writable = 1, + .configurable = 1, + }, + + { + .type = NJS_PROPERTY, + .name = njs_long_string("isCharacterDevice"), + .value = njs_native_function2(njs_fs_stats_test, 0, DT_CHR), + .writable = 1, + .configurable = 1, + }, + + { + .type = NJS_PROPERTY, + .name = njs_string("isDirectory"), + .value = njs_native_function2(njs_fs_stats_test, 0, DT_DIR), + .writable = 1, + .configurable = 1, + }, + + { + .type = NJS_PROPERTY, + .name = njs_string("isFIFO"), + .value = njs_native_function2(njs_fs_stats_test, 0, DT_FIFO), + .writable = 1, + .configurable = 1, + }, + + { + .type = NJS_PROPERTY, + .name = njs_string("isFile"), + .value = njs_native_function2(njs_fs_stats_test, 0, DT_REG), + .writable = 1, + .configurable = 1, + }, + + { + .type = NJS_PROPERTY, + .name = njs_string("isSocket"), + .value = njs_native_function2(njs_fs_stats_test, 0, DT_SOCK), + .writable = 1, + .configurable = 1, + }, + + { + .type = NJS_PROPERTY, + .name = njs_string("isSymbolicLink"), + .value = njs_native_function2(njs_fs_stats_test, 0, DT_LNK), + .writable = 1, + .configurable = 1, + }, + + { + .type = NJS_PROPERTY_HANDLER, + .name = njs_string("dev"), + .value = njs_prop_handler2(njs_fs_stats_prop, NJS_FS_STAT_DEV, + NJS_NUMBER), + .enumerable = 1, + }, + + { + .type = NJS_PROPERTY_HANDLER, + .name = njs_string("ino"), + .value = njs_prop_handler2(njs_fs_stats_prop, NJS_FS_STAT_INO, + NJS_NUMBER), + .enumerable = 1, + }, + + { + .type = NJS_PROPERTY_HANDLER, + .name = njs_string("mode"), + .value = njs_prop_handler2(njs_fs_stats_prop, NJS_FS_STAT_MODE, + NJS_NUMBER), + .enumerable = 1, + }, + + { + .type = NJS_PROPERTY_HANDLER, + .name = njs_string("nlink"), + .value = njs_prop_handler2(njs_fs_stats_prop, NJS_FS_STAT_NLINK, + NJS_NUMBER), + .enumerable = 1, + }, + + { + .type = NJS_PROPERTY_HANDLER, + .name = njs_string("uid"), + .value = njs_prop_handler2(njs_fs_stats_prop, NJS_FS_STAT_UID, + NJS_NUMBER), + .enumerable = 1, + }, + + { + .type = NJS_PROPERTY_HANDLER, + .name = njs_string("gid"), + .value = njs_prop_handler2(njs_fs_stats_prop, NJS_FS_STAT_GID, + NJS_NUMBER), + .enumerable = 1, + }, + + { + .type = NJS_PROPERTY_HANDLER, + .name = njs_string("rdev"), + .value = njs_prop_handler2(njs_fs_stats_prop, NJS_FS_STAT_RDEV, + NJS_NUMBER), + .enumerable = 1, + }, + + { + .type = NJS_PROPERTY_HANDLER, + .name = njs_string("size"), + .value = njs_prop_handler2(njs_fs_stats_prop, NJS_FS_STAT_SIZE, + NJS_NUMBER), + .enumerable = 1, + }, + + { + .type = NJS_PROPERTY_HANDLER, + .name = njs_string("blksize"), + .value = njs_prop_handler2(njs_fs_stats_prop, NJS_FS_STAT_BLKSIZE, + NJS_NUMBER), + .enumerable = 1, + }, + + { + .type = NJS_PROPERTY_HANDLER, + .name = njs_string("blocks"), + .value = njs_prop_handler2(njs_fs_stats_prop, NJS_FS_STAT_BLOCKS, + NJS_NUMBER), + .enumerable = 1, + }, + + { + .type = NJS_PROPERTY_HANDLER, + .name = njs_string("atimeMs"), + .value = njs_prop_handler2(njs_fs_stats_prop, NJS_FS_STAT_ATIME, + NJS_NUMBER), + .enumerable = 1, + }, + + { + .type = NJS_PROPERTY_HANDLER, + .name = njs_string("birthtimeMs"), + .value = njs_prop_handler2(njs_fs_stats_prop, NJS_FS_STAT_BIRTHTIME, + NJS_NUMBER), + .enumerable = 1, + }, + + { + .type = NJS_PROPERTY_HANDLER, + .name = njs_string("ctimeMs"), + .value = njs_prop_handler2(njs_fs_stats_prop, NJS_FS_STAT_CTIME, + NJS_NUMBER), + .enumerable = 1, + }, + + { + .type = NJS_PROPERTY_HANDLER, + .name = njs_string("mtimeMs"), + .value = njs_prop_handler2(njs_fs_stats_prop, NJS_FS_STAT_MTIME, + NJS_NUMBER), + .enumerable = 1, + }, + + { + .type = NJS_PROPERTY_HANDLER, + .name = njs_string("atime"), + .value = njs_prop_handler2(njs_fs_stats_prop, NJS_FS_STAT_ATIME, + NJS_DATE), + .enumerable = 1, + }, + + { + .type = NJS_PROPERTY_HANDLER, + .name = njs_string("birthtime"), + .value = njs_prop_handler2(njs_fs_stats_prop, NJS_FS_STAT_BIRTHTIME, + NJS_DATE), + .enumerable = 1, + }, + + { + .type = NJS_PROPERTY_HANDLER, + .name = njs_string("ctime"), + .value = njs_prop_handler2(njs_fs_stats_prop, NJS_FS_STAT_CTIME, + NJS_DATE), + .enumerable = 1, + }, + + { + .type = NJS_PROPERTY_HANDLER, + .name = njs_string("mtime"), + .value = njs_prop_handler2(njs_fs_stats_prop, NJS_FS_STAT_MTIME, + NJS_DATE), + .enumerable = 1, + }, +}; + + +const njs_object_init_t njs_stats_prototype_init = { + njs_stats_prototype_properties, + njs_nitems(njs_stats_prototype_properties), +}; + + +const njs_object_type_init_t njs_stats_type_init = { + .constructor = njs_native_ctor(njs_stats_constructor, 0, 0), + .prototype_props = &njs_stats_prototype_init, + .constructor_props = &njs_stats_constructor_init, + .prototype_value = { .object = { .type = NJS_OBJECT } }, +}; + + static const njs_object_prop_t njs_fs_promises_properties[] = { { @@ -1938,6 +2590,24 @@ static const njs_object_prop_t njs_fs_promises_properties[] = .configurable = 1, }, + { + .type = NJS_PROPERTY, + .name = njs_string("lstat"), + .value = njs_native_function2(njs_fs_stat, 0, + njs_fs_magic(NJS_FS_PROMISE, NJS_FS_LSTAT)), + .writable = 1, + .configurable = 1, + }, + + { + .type = NJS_PROPERTY, + .name = njs_string("stat"), + .value = njs_native_function2(njs_fs_stat, 0, + njs_fs_magic(NJS_FS_PROMISE, NJS_FS_STAT)), + .writable = 1, + .configurable = 1, + }, + { .type = NJS_PROPERTY, .name = njs_string("symlink"), @@ -2231,6 +2901,42 @@ static const njs_object_prop_t njs_fs_object_properties[] = .writable = 1, .configurable = 1, }, + + { + .type = NJS_PROPERTY, + .name = njs_string("lstat"), + .value = njs_native_function2(njs_fs_stat, 0, + njs_fs_magic(NJS_FS_CALLBACK, NJS_FS_LSTAT)), + .writable = 1, + .configurable = 1, + }, + + { + .type = NJS_PROPERTY, + .name = njs_string("lstatSync"), + .value = njs_native_function2(njs_fs_stat, 0, + njs_fs_magic(NJS_FS_DIRECT, NJS_FS_LSTAT)), + .writable = 1, + .configurable = 1, + }, + + { + .type = NJS_PROPERTY, + .name = njs_string("stat"), + .value = njs_native_function2(njs_fs_stat, 0, + njs_fs_magic(NJS_FS_CALLBACK, NJS_FS_STAT)), + .writable = 1, + .configurable = 1, + }, + + { + .type = NJS_PROPERTY, + .name = njs_string("statSync"), + .value = njs_native_function2(njs_fs_stat, 0, + njs_fs_magic(NJS_FS_DIRECT, NJS_FS_STAT)), + .writable = 1, + .configurable = 1, + }, }; diff --git a/src/njs_fs.h b/src/njs_fs.h index b850e50c..6d06c2a2 100644 --- a/src/njs_fs.h +++ b/src/njs_fs.h @@ -11,5 +11,6 @@ extern const njs_object_init_t njs_fs_object_init; extern const njs_object_type_init_t njs_dirent_type_init; +extern const njs_object_type_init_t njs_stats_type_init; #endif /* _NJS_FS_H_INCLUDED_ */ diff --git a/src/njs_value.h b/src/njs_value.h index 70d0e670..96485d1c 100644 --- a/src/njs_value.h +++ b/src/njs_value.h @@ -72,6 +72,7 @@ typedef enum { NJS_DATA_TAG_TEXT_ENCODER, NJS_DATA_TAG_TEXT_DECODER, NJS_DATA_TAG_ARRAY_ITERATOR, + NJS_DATA_TAG_FS_STAT, NJS_DATA_TAG_MAX } njs_data_tag_t; diff --git a/src/njs_vm.h b/src/njs_vm.h index c4f2fe20..48c22e76 100644 --- a/src/njs_vm.h +++ b/src/njs_vm.h @@ -57,6 +57,7 @@ typedef enum { NJS_OBJ_TYPE_ITERATOR, NJS_OBJ_TYPE_ARRAY_ITERATOR, NJS_OBJ_TYPE_FS_DIRENT, + NJS_OBJ_TYPE_FS_STATS, NJS_OBJ_TYPE_CRYPTO_HASH, NJS_OBJ_TYPE_CRYPTO_HMAC, NJS_OBJ_TYPE_TYPED_ARRAY, diff --git a/test/fs/methods.js b/test/fs/methods.js index 7421e701..0d422245 100644 --- a/test/fs/methods.js +++ b/test/fs/methods.js @@ -355,6 +355,186 @@ let realpathP_tsuite = { tests: realpath_tests, }; +async function stat_test(params) { + if (params.init) { + params.init(params); + } + + let stat = await method(params.method, params).catch(e => ({error:e})); + + if (params.check && !params.check(stat, params)) { + throw Error(`${params.method} failed check`); + } + + return 'SUCCESS'; +} + +function contains(arr, elts) { + return elts.every(el => { + let r = arr.some(v => el == v); + + if (!r) { + throw Error(`${el} is not found`); + } + + return r; + }); +} + +let stat_tests = [ + { args: ["/invalid_path"], + check: (err, params) => { + let e = err.error; + + if (e.syscall != params.method) { + throw Error(`${e.syscall} unexpected syscall`); + } + + if (e.code != "ENOENT") { + throw Error(`${e.code} unexpected code`); + } + + return true; + } }, + + { args: ["@_link"], + init: (params) => { + let lname = params.args[0]; + let fname = lname.slice(0, -5); + + /* making symbolic link. */ + + try { fs.unlinkSync(fname); fs.unlinkSync(lname); } catch (e) {} + + fs.writeFileSync(fname, fname); + + fname = fs.realpathSync(fname); + fs.symlinkSync(fname, lname); + }, + + check: (st, params) => { + switch (params.method) { + case "stat": + if (!st.isFile()) { + throw Error(`${params.args[0]} is not a file`); + } + + break; + + case "lstat": + if (!st.isSymbolicLink()) { + throw Error(`${params.args[0]} is not a link`); + } + + break; + } + + return true; + } }, + + { args: ["./build/"], + check: (st) => contains(Object.keys(st), + [ "atime", "atimeMs", "birthtime", "birthtimeMs", + "blksize", "blocks", "ctime", "ctimeMs", "dev", + "gid", "ino", "mode", "mtime", "mtimeMs","nlink", + "rdev", "size", "uid" ]) }, + + { args: ["./build/"], + check: (st) => Object.keys(st).every(p => { + let v = st[p]; + if (p == 'atime' || p == 'ctime' || p == 'mtime' || p == 'birthtime') { + if (!(v instanceof Date)) { + throw Error(`${p} is not an instance of Date`); + } + + return true; + } + + if ((typeof v) != 'number') { + throw Error(`${p} is not an instance of Number`); + } + + return true; + }) }, + + { args: ["./build/"], + check: (st) => ['atime', 'birthtime', 'ctime', 'mtime'].every(p => { + let date = st[p].valueOf(); + let num = st[p + 'Ms']; + + if (Math.abs(date - num) > 1) { + throw Error(`${p}:${date} != ${p+'Ms'}:${num}`); + } + + return true; + }) }, + + { args: ["./build/"], + check: (st) => ['isBlockDevice', + 'isCharacterDevice', + 'isDirectory', + 'isFIFO', + 'isFile', + 'isSocket', + 'isSymbolicLink'].every(m => { + + let r = st[m](); + if (!(r == (m == 'isDirectory'))) { + throw Error(`${m} is ${r}`); + } + + return true; + }) }, +]; + +let stat_tsuite = { + name: "fs stat", + T: stat_test, + prepare_args: p, + opts: { type: "callback", method: "stat" }, + tests: stat_tests, +}; + +let statSync_tsuite = { + name: "fs statSync", + T: stat_test, + prepare_args: p, + opts: { type: "sync", method: "stat" }, + tests: stat_tests, +}; + +let statP_tsuite = { + name: "fsp stat", + T: stat_test, + prepare_args: p, + opts: { type: "promise", method: "stat" }, + tests: stat_tests, +}; + +let lstat_tsuite = { + name: "fs lstat", + T: stat_test, + prepare_args: p, + opts: { type: "callback", method: "lstat" }, + tests: stat_tests, +}; + +let lstatSync_tsuite = { + name: "fs lstatSync", + T: stat_test, + prepare_args: p, + opts: { type: "sync", method: "lstat" }, + tests: stat_tests, +}; + +let lstatP_tsuite = { + name: "fsp lstat", + T: stat_test, + prepare_args: p, + opts: { type: "promise", method: "lstat" }, + tests: stat_tests, +}; + run([ readFile_tsuite, readFileSync_tsuite, @@ -368,4 +548,10 @@ run([ realpath_tsuite, realpathSync_tsuite, realpathP_tsuite, + stat_tsuite, + statSync_tsuite, + statP_tsuite, + lstat_tsuite, + lstatSync_tsuite, + lstatP_tsuite, ]); diff --git a/test/njs_expect_test.exp b/test/njs_expect_test.exp index b28a0e79..8d0a4a37 100644 --- a/test/njs_expect_test.exp +++ b/test/njs_expect_test.exp @@ -833,7 +833,13 @@ fs appendFileSync SUCCESS fsp appendFile SUCCESS fs realpath SUCCESS fs realpathSync SUCCESS -fsp realpath SUCCESS" +fsp realpath SUCCESS +fs stat SUCCESS +fs statSync SUCCESS +fsp stat SUCCESS +fs lstat SUCCESS +fs lstatSync SUCCESS +fsp lstat SUCCESS" njs_run {"./test/js/fs_promises_001.js"} \ "init ok true -- 2.47.3