From: Dmitry Volyntsev Date: Fri, 17 Nov 2017 15:55:07 +0000 (+0300) Subject: Nodejs style file methods. X-Git-Tag: 0.1.15~5 X-Git-Url: http://www.kaiwu.me/postgresql/commit/?a=commitdiff_plain;h=35377fb27c0e4d566b5788396f7ecc7fce80a7e2;p=njs.git Nodejs style file methods. --- diff --git a/Makefile b/Makefile index f73607de..ee524cfb 100644 --- a/Makefile +++ b/Makefile @@ -22,6 +22,8 @@ $(NXT_BUILDDIR)/libnjs.a: \ $(NXT_BUILDDIR)/njs_date.o \ $(NXT_BUILDDIR)/njs_error.o \ $(NXT_BUILDDIR)/njs_math.o \ + $(NXT_BUILDDIR)/njs_module.o \ + $(NXT_BUILDDIR)/njs_fs.o \ $(NXT_BUILDDIR)/njs_extern.o \ $(NXT_BUILDDIR)/njs_variable.o \ $(NXT_BUILDDIR)/njs_builtin.o \ @@ -56,6 +58,8 @@ $(NXT_BUILDDIR)/libnjs.a: \ $(NXT_BUILDDIR)/njs_date.o \ $(NXT_BUILDDIR)/njs_error.o \ $(NXT_BUILDDIR)/njs_math.o \ + $(NXT_BUILDDIR)/njs_module.o \ + $(NXT_BUILDDIR)/njs_fs.o \ $(NXT_BUILDDIR)/njs_extern.o \ $(NXT_BUILDDIR)/njs_variable.o \ $(NXT_BUILDDIR)/njs_builtin.o \ @@ -299,6 +303,28 @@ $(NXT_BUILDDIR)/njs_math.o: \ -I$(NXT_LIB) -Injs \ njs/njs_math.c +$(NXT_BUILDDIR)/njs_module.o: \ + $(NXT_BUILDDIR)/libnxt.a \ + njs/njscript.h \ + njs/njs_vm.h \ + njs/njs_module.h \ + njs/njs_module.c \ + + $(NXT_CC) -c -o $(NXT_BUILDDIR)/njs_module.o $(NXT_CFLAGS) \ + -I$(NXT_LIB) -Injs \ + njs/njs_module.c + +$(NXT_BUILDDIR)/njs_fs.o: \ + $(NXT_BUILDDIR)/libnxt.a \ + njs/njscript.h \ + njs/njs_vm.h \ + njs/njs_fs.h \ + njs/njs_fs.c \ + + $(NXT_CC) -c -o $(NXT_BUILDDIR)/njs_fs.o $(NXT_CFLAGS) \ + -I$(NXT_LIB) -Injs \ + njs/njs_fs.c + $(NXT_BUILDDIR)/njs_extern.o: \ $(NXT_BUILDDIR)/libnxt.a \ njs/njscript.h \ @@ -332,6 +358,7 @@ $(NXT_BUILDDIR)/njs_builtin.o: \ njs/njs_string.h \ njs/njs_object.h \ njs/njs_array.h \ + njs/njs_module.h \ njs/njs_function.h \ njs/njs_regexp.h \ njs/njs_parser.h \ diff --git a/njs/njs_builtin.c b/njs/njs_builtin.c index de7da438..fa9378a2 100644 --- a/njs/njs_builtin.c +++ b/njs/njs_builtin.c @@ -30,6 +30,8 @@ #include #include #include +#include +#include #include #include @@ -54,6 +56,11 @@ const njs_object_init_t *njs_object_init[] = { }; +const njs_object_init_t *njs_module_init[] = { + &njs_fs_object_init /* fs */ +}; + + const njs_object_init_t *njs_prototype_init[] = { &njs_object_prototype_init, &njs_array_prototype_init, @@ -111,8 +118,10 @@ njs_builtin_objects_create(njs_vm_t *vm) { nxt_int_t ret; nxt_uint_t i; + njs_module_t *module; njs_object_t *objects; njs_function_t *functions, *constructors; + nxt_lvlhsh_query_t lhq; njs_object_prototype_t *prototypes; static const njs_object_prototype_t prototype_values[] = { @@ -193,6 +202,7 @@ njs_builtin_objects_create(njs_vm_t *vm) NULL, /* encodeURIComponent */ NULL, /* decodeURI */ NULL, /* decodeURIComponent */ + NULL, /* require */ }; static const njs_function_init_t native_functions[] = { @@ -208,6 +218,7 @@ njs_builtin_objects_create(njs_vm_t *vm) { njs_string_encode_uri_component, { NJS_SKIP_ARG, NJS_STRING_ARG } }, { njs_string_decode_uri, { NJS_SKIP_ARG, NJS_STRING_ARG } }, { njs_string_decode_uri_component, { NJS_SKIP_ARG, NJS_STRING_ARG } }, + { njs_module_require, { NJS_SKIP_ARG, NJS_STRING_ARG } }, }; static const njs_object_prop_t null_proto_property = { @@ -249,6 +260,37 @@ njs_builtin_objects_create(njs_vm_t *vm) objects[i].shared = 1; } + lhq.replace = 0; + lhq.proto = &njs_modules_hash_proto; + lhq.pool = vm->mem_cache_pool; + + for (i = NJS_MODULE_FS; i < NJS_MODULE_MAX; i++) { + module = nxt_mem_cache_zalloc(vm->mem_cache_pool, sizeof(njs_module_t)); + if (nxt_slow_path(module == NULL)) { + return NJS_ERROR; + } + + module->name = njs_module_init[i]->name; + + ret = njs_object_hash_create(vm, &module->object.shared_hash, + njs_module_init[i]->properties, + njs_module_init[i]->items); + if (nxt_slow_path(ret != NXT_OK)) { + return NXT_ERROR; + } + + module->object.shared = 1; + + lhq.key = module->name; + lhq.key_hash = nxt_djb_hash(lhq.key.start, lhq.key.length); + lhq.value = module; + + ret = nxt_lvlhsh_insert(&vm->modules_hash, &lhq); + if (nxt_fast_path(ret != NXT_OK)) { + return NXT_ERROR; + } + } + functions = vm->shared->functions; for (i = NJS_FUNCTION_EVAL; i < NJS_FUNCTION_MAX; i++) { @@ -857,10 +899,11 @@ njs_builtin_match_native_function(njs_vm_t *vm, njs_function_t *function, size_t len; nxt_str_t string; nxt_uint_t i; + njs_module_t *module; njs_object_t *objects; njs_function_t *constructors; njs_object_prop_t *prop; - nxt_lvlhsh_each_t lhe; + nxt_lvlhsh_each_t lhe, lhe_prop; njs_object_prototype_t *prototypes; objects = vm->shared->objects; @@ -978,5 +1021,44 @@ njs_builtin_match_native_function(njs_vm_t *vm, njs_function_t *function, } } + nxt_lvlhsh_each_init(&lhe, &njs_modules_hash_proto); + + for ( ;; ) { + module = nxt_lvlhsh_each(&vm->modules_hash, &lhe); + if (module == NULL) { + break; + } + + nxt_lvlhsh_each_init(&lhe_prop, &njs_object_hash_proto); + + for ( ;; ) { + prop = nxt_lvlhsh_each(&module->object.shared_hash, &lhe_prop); + if (prop == NULL) { + break; + } + + if (!njs_is_function(&prop->value)) { + continue; + } + + if (function == prop->value.data.u.function) { + njs_string_get(&prop->name, &string); + len = module->name.length + string.length + sizeof("."); + + buf = nxt_mem_cache_zalloc(vm->mem_cache_pool, len); + if (buf == NULL) { + return NXT_ERROR; + } + + snprintf(buf, len, "%s.%s", module->name.start, string.start); + + name->length = len; + name->start = (u_char *) buf; + + return NXT_OK; + } + } + } + return NXT_DECLINED; } diff --git a/njs/njs_builtin.h b/njs/njs_builtin.h index bac1a8a5..a5169b3d 100644 --- a/njs/njs_builtin.h +++ b/njs/njs_builtin.h @@ -9,6 +9,7 @@ extern const njs_object_init_t *njs_object_init[]; +extern const njs_object_init_t *njs_module_init[]; extern const njs_object_init_t *njs_prototype_init[]; extern const njs_object_init_t *njs_constructor_init[]; diff --git a/njs/njs_fs.c b/njs/njs_fs.c new file mode 100644 index 00000000..ea71ed89 --- /dev/null +++ b/njs/njs_fs.c @@ -0,0 +1,1095 @@ + +/* + * Copyright (C) Dmitry Volyntsev + * Copyright (C) NGINX, Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +typedef struct { + union { + njs_continuation_t cont; + u_char padding[NJS_CONTINUATION_SIZE]; + } u; + + nxt_bool_t done; +} njs_fs_cont_t; + + +typedef struct { + nxt_str_t name; + int value; +} njs_fs_entry_t; + + +static njs_ret_t njs_fs_read_file(njs_vm_t *vm, njs_value_t *args, + nxt_uint_t nargs, njs_index_t unused); +static njs_ret_t njs_fs_read_file_sync(njs_vm_t *vm, njs_value_t *args, + nxt_uint_t nargs, njs_index_t unused); +static njs_ret_t njs_fs_append_file(njs_vm_t *vm, njs_value_t *args, + nxt_uint_t nargs, njs_index_t unused); +static njs_ret_t njs_fs_write_file(njs_vm_t *vm, njs_value_t *args, + nxt_uint_t nargs, njs_index_t unused); +static njs_ret_t njs_fs_append_file_sync(njs_vm_t *vm, njs_value_t *args, + nxt_uint_t nargs, njs_index_t unused); +static njs_ret_t njs_fs_write_file_sync(njs_vm_t *vm, njs_value_t *args, + nxt_uint_t nargs, njs_index_t unused); +static njs_ret_t njs_fs_write_file_internal(njs_vm_t *vm, njs_value_t *args, + nxt_uint_t nargs, int default_flags); +static njs_ret_t njs_fs_write_file_sync_internal(njs_vm_t *vm, njs_value_t *args, + nxt_uint_t nargs, int default_flags); +static njs_ret_t njs_fs_done(njs_vm_t *vm, njs_value_t *args, + nxt_uint_t nargs, njs_index_t unused); + +static njs_ret_t njs_fs_error(njs_vm_t *vm, const char *syscall, + const char *description, njs_value_t *path, int errn, njs_value_t *retval); +static int njs_fs_flags(nxt_str_t *value); +static mode_t njs_fs_mode(njs_value_t *value); + + +static const njs_value_t njs_fs_errno_string = njs_string("errno"); +static const njs_value_t njs_fs_path_string = njs_string("path"); +static const njs_value_t njs_fs_syscall_string = njs_string("syscall"); + + +static njs_fs_entry_t njs_flags_table[] = { + { nxt_string("r"), O_RDONLY }, + { nxt_string("r+"), O_RDWR }, + { nxt_string("w"), O_TRUNC | O_CREAT | O_WRONLY }, + { nxt_string("w+"), O_TRUNC | O_CREAT | O_RDWR }, + { nxt_string("a"), O_APPEND | O_CREAT | O_WRONLY }, + { nxt_string("a+"), O_APPEND | O_CREAT | O_RDWR }, + { nxt_string("rs"), O_SYNC | O_RDONLY }, + { nxt_string("sr"), O_SYNC | O_RDONLY }, + { nxt_string("wx"), O_TRUNC | O_CREAT | O_EXCL | O_WRONLY }, + { nxt_string("xw"), O_TRUNC | O_CREAT | O_EXCL | O_WRONLY }, + { nxt_string("ax"), O_APPEND | O_CREAT | O_EXCL | O_WRONLY }, + { nxt_string("xa"), O_APPEND | O_CREAT | O_EXCL | O_WRONLY }, + { nxt_string("rs+"), O_SYNC | O_RDWR }, + { nxt_string("sr+"), O_SYNC | O_RDWR }, + { nxt_string("wx+"), O_TRUNC | O_CREAT | O_EXCL | O_RDWR }, + { nxt_string("xw+"), O_TRUNC | O_CREAT | O_EXCL | O_RDWR }, + { nxt_string("ax+"), O_APPEND | O_CREAT | O_EXCL | O_RDWR }, + { nxt_string("xa+"), O_APPEND | O_CREAT | O_EXCL | O_RDWR }, + { nxt_null_string, 0 } +}; + + +static njs_ret_t +njs_fs_read_file(njs_vm_t *vm, njs_value_t *args, nxt_uint_t nargs, + njs_index_t unused) +{ + int fd, errn, flags; + u_char *p, *start, *end; + ssize_t n, length; + nxt_str_t flag, encoding; + njs_ret_t ret; + const char *path, *syscall, *description; + struct stat sb; + njs_value_t *callback, arguments[3]; + njs_fs_cont_t *cont; + njs_object_prop_t *prop; + nxt_lvlhsh_query_t lhq; + + if (nxt_slow_path(nargs < 3)) { + njs_exception_type_error(vm, "too few arguments", NULL); + return NJS_ERROR; + } + + if (nxt_slow_path(!njs_is_string(&args[1]))) { + njs_exception_type_error(vm, "path must be a string", NULL); + return NJS_ERROR; + } + + flag.start = NULL; + encoding.length = 0; + encoding.start = NULL; + + if (!njs_is_function(&args[2])) { + if (njs_is_string(&args[2])) { + njs_string_get(&args[2], &encoding); + + } else if (njs_is_object(&args[2])) { + lhq.key_hash = NJS_FLAG_HASH; + lhq.key = nxt_string_value("flag"); + lhq.proto = &njs_object_hash_proto; + + ret = nxt_lvlhsh_find(&args[2].data.u.object->hash, &lhq); + if (ret == NXT_OK) { + prop = lhq.value; + njs_string_get(&prop->value, &flag); + } + + lhq.key_hash = NJS_ENCODING_HASH; + lhq.key = nxt_string_value("encoding"); + lhq.proto = &njs_object_hash_proto; + + ret = nxt_lvlhsh_find(&args[2].data.u.object->hash, &lhq); + if (ret == NXT_OK) { + prop = lhq.value; + njs_string_get(&prop->value, &encoding); + } + + } else { + njs_exception_type_error(vm, "Unknown options type " + "(a string or object required)", NULL); + return NJS_ERROR; + } + + if (nxt_slow_path(nargs < 4 || !njs_is_function(&args[3]))) { + njs_exception_type_error(vm, "callback must be a function", NULL); + return NJS_ERROR; + } + + callback = &args[3]; + + } else { + if (nxt_slow_path(!njs_is_function(&args[2]))) { + njs_exception_type_error(vm, "callback must be a function", NULL); + return NJS_ERROR; + } + + callback = &args[2]; + } + + if (flag.start == NULL) { + flag = nxt_string_value("r"); + } + + flags = njs_fs_flags(&flag); + if (nxt_slow_path(flags == -1)) { + njs_exception_type_error(vm, "Unknown file open flags: '%.*s'", + (int) flag.length, flag.start); + return NJS_ERROR; + } + + path = (char *) njs_string_to_c_string(vm, &args[1]); + if (nxt_slow_path(path == NULL)) { + return NJS_ERROR; + } + + if (encoding.length != 0 + && (encoding.length != 4 || memcmp(encoding.start, "utf8", 4) != 0)) + { + njs_exception_type_error(vm, "Unknown encoding: '%.*s'", + (int) encoding.length, encoding.start); + return NJS_ERROR; + } + + description = NULL; + + /* GCC 4 complains about uninitialized errn and syscall. */ + errn = 0; + syscall = NULL; + + fd = open(path, flags); + if (nxt_slow_path(fd < 0)) { + errn = errno; + description = strerror(errno); + syscall = "open"; + goto done; + } + + ret = fstat(fd, &sb); + if (nxt_slow_path(ret == -1)) { + errn = errno; + description = strerror(errno); + syscall = "stat"; + goto done; + } + + if (nxt_slow_path(!S_ISREG(sb.st_mode))) { + errn = 0; + description = "File is not regular"; + syscall = "stat"; + goto done; + } + + if (encoding.length != 0) { + length = sb.st_size; + + } else { + length = 0; + } + + start = njs_string_alloc(vm, &arguments[2], sb.st_size, length); + if (nxt_slow_path(start == NULL)) { + goto memory_error; + } + + p = start; + end = p + sb.st_size; + + while (p < end) { + n = read(fd, p, end - p); + if (nxt_slow_path(n == -1)) { + if (errno == EINTR) { + continue; + } + + errn = errno; + description = strerror(errno); + syscall = "read"; + goto done; + } + + p += n; + } + + if (encoding.length != 0) { + length = nxt_utf8_length(start, sb.st_size); + + if (length >= 0) { + njs_string_offset_map_init(start, sb.st_size); + njs_string_length_set(&arguments[2], length); + + } else { + errn = 0; + description = "Non-UTF8 file, convertion is not implemented"; + syscall = NULL; + goto done; + } + } + +done: + + if (fd > 0) { + close(fd); + } + + if (description != 0) { + ret = njs_fs_error(vm, syscall, description, &args[1], errn, + &arguments[1]); + + if (nxt_slow_path(ret != NJS_OK)) { + return NJS_ERROR; + } + + arguments[2] = njs_value_void; + + } else { + arguments[1] = njs_value_void; + } + + arguments[0] = njs_value_void; + + cont = njs_vm_continuation(vm); + cont->u.cont.function = njs_fs_done; + + return njs_function_apply(vm, callback->data.u.function, + arguments, 3, (njs_index_t) &vm->retval); + +memory_error: + + if (fd > 0) { + close(fd); + } + + njs_exception_memory_error(vm); + + return NJS_ERROR; +} + + +static njs_ret_t +njs_fs_read_file_sync(njs_vm_t *vm, njs_value_t *args, nxt_uint_t nargs, + njs_index_t unused) +{ + int fd, errn, flags; + u_char *p, *start, *end; + ssize_t n, length; + nxt_str_t flag, encoding; + njs_ret_t ret; + const char *path, *syscall, *description; + struct stat sb; + njs_object_prop_t *prop; + nxt_lvlhsh_query_t lhq; + + if (nxt_slow_path(nargs < 2)) { + njs_exception_type_error(vm, "too few arguments", NULL); + return NJS_ERROR; + } + + if (nxt_slow_path(!njs_is_string(&args[1]))) { + njs_exception_type_error(vm, "path must be a string", NULL); + return NJS_ERROR; + } + + flag.start = NULL; + encoding.length = 0; + encoding.start = NULL; + + if (nargs == 3) { + if (njs_is_string(&args[2])) { + njs_string_get(&args[2], &encoding); + + } else if (njs_is_object(&args[2])) { + lhq.key_hash = NJS_FLAG_HASH; + lhq.key = nxt_string_value("flag"); + lhq.proto = &njs_object_hash_proto; + + ret = nxt_lvlhsh_find(&args[2].data.u.object->hash, &lhq); + if (ret == NXT_OK) { + prop = lhq.value; + njs_string_get(&prop->value, &flag); + } + + lhq.key_hash = NJS_ENCODING_HASH; + lhq.key = nxt_string_value("encoding"); + lhq.proto = &njs_object_hash_proto; + + ret = nxt_lvlhsh_find(&args[2].data.u.object->hash, &lhq); + if (ret == NXT_OK) { + prop = lhq.value; + njs_string_get(&prop->value, &encoding); + } + + } else { + njs_exception_type_error(vm, "Unknown options type " + "(a string or object required)", NULL); + return NJS_ERROR; + } + } + + if (flag.start == NULL) { + flag = nxt_string_value("r"); + } + + flags = njs_fs_flags(&flag); + if (nxt_slow_path(flags == -1)) { + njs_exception_type_error(vm, "Unknown file open flags: '%.*s'", + (int) flag.length, flag.start); + return NJS_ERROR; + } + + path = (char *) njs_string_to_c_string(vm, &args[1]); + if (nxt_slow_path(path == NULL)) { + return NJS_ERROR; + } + + if (encoding.length != 0 + && (encoding.length != 4 || memcmp(encoding.start, "utf8", 4) != 0)) + { + njs_exception_type_error(vm, "Unknown encoding: '%.*s'", + (int) encoding.length, encoding.start); + return NJS_ERROR; + } + + description = NULL; + + /* GCC 4 complains about uninitialized errn and syscall. */ + errn = 0; + syscall = NULL; + + fd = open(path, flags); + if (nxt_slow_path(fd < 0)) { + errn = errno; + description = strerror(errno); + syscall = "open"; + goto done; + } + + ret = fstat(fd, &sb); + if (nxt_slow_path(ret == -1)) { + errn = errno; + description = strerror(errno); + syscall = "stat"; + goto done; + } + + if (nxt_slow_path(!S_ISREG(sb.st_mode))) { + errn = 0; + description = "File is not regular"; + syscall = "stat"; + goto done; + } + + if (encoding.length != 0) { + length = sb.st_size; + + } else { + length = 0; + } + + start = njs_string_alloc(vm, &vm->retval, sb.st_size, length); + if (nxt_slow_path(start == NULL)) { + goto memory_error; + } + + p = start; + end = p + sb.st_size; + + while (p < end) { + n = read(fd, p, end - p); + if (nxt_slow_path(n == -1)) { + if (errno == EINTR) { + continue; + } + + errn = errno; + description = strerror(errno); + syscall = "read"; + goto done; + } + + p += n; + } + + if (encoding.length != 0) { + length = nxt_utf8_length(start, sb.st_size); + + if (length >= 0) { + njs_string_offset_map_init(start, sb.st_size); + njs_string_length_set(&vm->retval, length); + + } else { + errn = 0; + description = "Non-UTF8 file, convertion is not implemented"; + syscall = NULL; + goto done; + } + } + +done: + + if (fd > 0) { + close(fd); + } + + if (description != 0) { + (void) njs_fs_error(vm, syscall, description, &args[1], errn, + &vm->retval); + + return NJS_ERROR; + } + + return NJS_OK; + +memory_error: + + if (fd > 0) { + close(fd); + } + + njs_exception_memory_error(vm); + + return NJS_ERROR; +} + + +static njs_ret_t +njs_fs_append_file(njs_vm_t *vm, njs_value_t *args, nxt_uint_t nargs, + njs_index_t unused) +{ + return njs_fs_write_file_internal(vm, args, nargs, + O_APPEND | O_CREAT | O_WRONLY); +} + + +static njs_ret_t +njs_fs_write_file(njs_vm_t *vm, njs_value_t *args, nxt_uint_t nargs, + njs_index_t unused) +{ + return njs_fs_write_file_internal(vm, args, nargs, + O_TRUNC | O_CREAT | O_WRONLY); +} + + +static njs_ret_t njs_fs_append_file_sync(njs_vm_t *vm, njs_value_t *args, + nxt_uint_t nargs, njs_index_t unused) +{ + return njs_fs_write_file_sync_internal(vm, args, nargs, + O_APPEND | O_CREAT | O_WRONLY); +} + + +static njs_ret_t njs_fs_write_file_sync(njs_vm_t *vm, njs_value_t *args, + nxt_uint_t nargs, njs_index_t unused) +{ + return njs_fs_write_file_sync_internal(vm, args, nargs, + O_TRUNC | O_CREAT | O_WRONLY); +} + + +static njs_ret_t njs_fs_write_file_internal(njs_vm_t *vm, njs_value_t *args, + nxt_uint_t nargs, int default_flags) +{ + int fd, errn, flags; + u_char *p, *end; + mode_t md; + ssize_t n; + nxt_str_t data, flag, encoding; + njs_ret_t ret; + const char *path, *syscall, *description; + njs_value_t *callback, *mode, arguments[2]; + njs_fs_cont_t *cont; + njs_object_prop_t *prop; + nxt_lvlhsh_query_t lhq; + + if (nxt_slow_path(nargs < 4)) { + njs_exception_type_error(vm, "too few arguments", NULL); + return NJS_ERROR; + } + + if (nxt_slow_path(!njs_is_string(&args[1]))) { + njs_exception_type_error(vm, "path must be a string", NULL); + return NJS_ERROR; + } + + if (nxt_slow_path(!njs_is_string(&args[2]))) { + njs_exception_type_error(vm, "data must be a string", NULL); + return NJS_ERROR; + } + + mode = NULL; + flag.start = NULL; + encoding.length = 0; + encoding.start = NULL; + + if (!njs_is_function(&args[3])) { + if (njs_is_string(&args[3])) { + njs_string_get(&args[3], &encoding); + + } else if (njs_is_object(&args[3])) { + lhq.key_hash = NJS_FLAG_HASH; + lhq.key = nxt_string_value("flag"); + lhq.proto = &njs_object_hash_proto; + + ret = nxt_lvlhsh_find(&args[3].data.u.object->hash, &lhq); + if (ret == NXT_OK) { + prop = lhq.value; + njs_string_get(&prop->value, &flag); + } + + lhq.key_hash = NJS_ENCODING_HASH; + lhq.key = nxt_string_value("encoding"); + lhq.proto = &njs_object_hash_proto; + + ret = nxt_lvlhsh_find(&args[3].data.u.object->hash, &lhq); + if (ret == NXT_OK) { + prop = lhq.value; + njs_string_get(&prop->value, &encoding); + } + + lhq.key_hash = NJS_MODE_HASH; + lhq.key = nxt_string_value("mode"); + lhq.proto = &njs_object_hash_proto; + + ret = nxt_lvlhsh_find(&args[3].data.u.object->hash, &lhq); + if (ret == NXT_OK) { + prop = lhq.value; + mode = &prop->value; + } + + } else { + njs_exception_type_error(vm, "Unknown options type " + "(a string or object required)", NULL); + return NJS_ERROR; + } + + if (nxt_slow_path(nargs < 5 || !njs_is_function(&args[4]))) { + njs_exception_type_error(vm, "callback must be a function", NULL); + return NJS_ERROR; + } + + callback = &args[4]; + + } else { + if (nxt_slow_path(!njs_is_function(&args[3]))) { + njs_exception_type_error(vm, "callback must be a function", NULL); + return NJS_ERROR; + } + + callback = &args[3]; + } + + if (flag.start != NULL) { + flags = njs_fs_flags(&flag); + if (nxt_slow_path(flags == -1)) { + njs_exception_type_error(vm, "Unknown file open flags: '%.*s'", + (int) flag.length, flag.start); + return NJS_ERROR; + } + + } else { + flags = default_flags; + } + + if (mode != NULL) { + md = njs_fs_mode(mode); + + } else { + md = 0666; + } + + path = (char *) njs_string_to_c_string(vm, &args[1]); + if (nxt_slow_path(path == NULL)) { + return NJS_ERROR; + } + + if (encoding.length != 0 + && (encoding.length != 4 || memcmp(encoding.start, "utf8", 4) != 0)) + { + njs_exception_type_error(vm, "Unknown encoding: '%.*s'", + (int) encoding.length, encoding.start); + return NJS_ERROR; + } + + description = NULL; + + /* GCC 4 complains about uninitialized errn and syscall. */ + errn = 0; + syscall = NULL; + + fd = open(path, flags, md); + if (nxt_slow_path(fd < 0)) { + errn = errno; + description = strerror(errno); + syscall = "open"; + goto done; + } + + njs_string_get(&args[2], &data); + + p = data.start; + end = p + data.length; + + while (p < end) { + n = write(fd, p, end - p); + if (nxt_slow_path(n == -1)) { + if (errno == EINTR) { + continue; + } + + errn = errno; + description = strerror(errno); + syscall = "write"; + goto done; + } + + p += n; + } + +done: + + if (fd > 0) { + close(fd); + } + + if (description != 0) { + ret = njs_fs_error(vm, syscall, description, &args[1], errn, + &arguments[1]); + + if (nxt_slow_path(ret != NJS_OK)) { + return NJS_ERROR; + } + + } else { + arguments[1] = njs_value_void; + } + + arguments[0] = njs_value_void; + + cont = njs_vm_continuation(vm); + cont->u.cont.function = njs_fs_done; + + return njs_function_apply(vm, callback->data.u.function, + arguments, 2, (njs_index_t) &vm->retval); +} + + +static njs_ret_t +njs_fs_write_file_sync_internal(njs_vm_t *vm, njs_value_t *args, + nxt_uint_t nargs, int default_flags) +{ + int fd, errn, flags; + u_char *p, *end; + mode_t md; + ssize_t n; + nxt_str_t data, flag, encoding; + njs_ret_t ret; + const char *path, *syscall, *description; + njs_value_t *mode; + njs_object_prop_t *prop; + nxt_lvlhsh_query_t lhq; + + if (nxt_slow_path(nargs < 3)) { + njs_exception_type_error(vm, "too few arguments", NULL); + return NJS_ERROR; + } + + if (nxt_slow_path(!njs_is_string(&args[1]))) { + njs_exception_type_error(vm, "path must be a string", NULL); + return NJS_ERROR; + } + + if (nxt_slow_path(!njs_is_string(&args[2]))) { + njs_exception_type_error(vm, "data must be a string", NULL); + return NJS_ERROR; + } + + mode = NULL; + flag.start = NULL; + encoding.length = 0; + encoding.start = NULL; + + if (nargs == 4) { + if (njs_is_string(&args[3])) { + njs_string_get(&args[3], &encoding); + + } else if (njs_is_object(&args[3])) { + lhq.key_hash = NJS_FLAG_HASH; + lhq.key = nxt_string_value("flag"); + lhq.proto = &njs_object_hash_proto; + + ret = nxt_lvlhsh_find(&args[3].data.u.object->hash, &lhq); + if (ret == NXT_OK) { + prop = lhq.value; + njs_string_get(&prop->value, &flag); + } + + lhq.key_hash = NJS_ENCODING_HASH; + lhq.key = nxt_string_value("encoding"); + lhq.proto = &njs_object_hash_proto; + + ret = nxt_lvlhsh_find(&args[3].data.u.object->hash, &lhq); + if (ret == NXT_OK) { + prop = lhq.value; + njs_string_get(&prop->value, &encoding); + } + + lhq.key_hash = NJS_MODE_HASH; + lhq.key = nxt_string_value("mode"); + lhq.proto = &njs_object_hash_proto; + + ret = nxt_lvlhsh_find(&args[3].data.u.object->hash, &lhq); + if (ret == NXT_OK) { + prop = lhq.value; + mode = &prop->value; + } + + } else { + njs_exception_type_error(vm, "Unknown options type " + "(a string or object required)", NULL); + return NJS_ERROR; + } + } + + if (flag.start != NULL) { + flags = njs_fs_flags(&flag); + if (nxt_slow_path(flags == -1)) { + njs_exception_type_error(vm, "Unknown file open flags: '%.*s'", + (int) flag.length, flag.start); + return NJS_ERROR; + } + + } else { + flags = default_flags; + } + + if (mode != NULL) { + md = njs_fs_mode(mode); + + } else { + md = 0666; + } + + path = (char *) njs_string_to_c_string(vm, &args[1]); + if (nxt_slow_path(path == NULL)) { + return NJS_ERROR; + } + + if (encoding.length != 0 + && (encoding.length != 4 || memcmp(encoding.start, "utf8", 4) != 0)) + { + njs_exception_type_error(vm, "Unknown encoding: '%.*s'", + (int) encoding.length, encoding.start); + return NJS_ERROR; + } + + description = NULL; + + /* GCC 4 complains about uninitialized errn and syscall. */ + errn = 0; + syscall = NULL; + + fd = open(path, flags, md); + if (nxt_slow_path(fd < 0)) { + errn = errno; + description = strerror(errno); + syscall = "open"; + goto done; + } + + njs_string_get(&args[2], &data); + + p = data.start; + end = p + data.length; + + while (p < end) { + n = write(fd, p, end - p); + if (nxt_slow_path(n == -1)) { + if (errno == EINTR) { + continue; + } + + errn = errno; + description = strerror(errno); + syscall = "write"; + goto done; + } + + p += n; + } + +done: + + if (fd > 0) { + close(fd); + } + + if (description != 0) { + ret = njs_fs_error(vm, syscall, description, &args[1], errn, + &vm->retval); + + if (nxt_slow_path(ret != NJS_OK)) { + return NJS_ERROR; + } + + } else { + vm->retval = njs_value_void; + } + + return NJS_OK; +} + + +static njs_ret_t njs_fs_done(njs_vm_t *vm, njs_value_t *args, + nxt_uint_t nargs, njs_index_t unused) +{ + vm->retval = njs_value_void; + + return NJS_OK; +} + + +static njs_ret_t njs_fs_error(njs_vm_t *vm, const char *syscall, + const char *description, njs_value_t *path, int errn, njs_value_t *retval) +{ + size_t size; + nxt_int_t ret; + njs_value_t string, value; + njs_object_t *error; + njs_object_prop_t *prop; + nxt_lvlhsh_query_t lhq; + + size = description != NULL ? strlen(description) : 0; + + ret = njs_string_new(vm, &string, (u_char *) description, size, size); + if (nxt_slow_path(ret != NXT_OK)) { + goto memory_error; + } + + error = njs_error_alloc(vm, NJS_OBJECT_ERROR, NULL, &string); + if (nxt_slow_path(error == NULL)) { + goto memory_error; + } + + lhq.replace = 0; + lhq.pool = vm->mem_cache_pool; + + if (errn != 0) { + lhq.key = nxt_string_value("errno"); + lhq.key_hash = NJS_ERRNO_HASH; + lhq.proto = &njs_object_hash_proto; + + value.data.type = NJS_NUMBER; + value.data.truth = 1; + value.data.u.number = errn; + + prop = njs_object_prop_alloc(vm, &njs_fs_errno_string, &value, 1); + if (nxt_slow_path(prop == NULL)) { + goto memory_error; + } + + lhq.value = prop; + + ret = nxt_lvlhsh_insert(&error->hash, &lhq); + if (nxt_slow_path(ret != NXT_OK)) { + njs_exception_internal_error(vm, NULL, NULL); + return NJS_ERROR; + } + } + + if (path != NULL) { + lhq.key = nxt_string_value("path"); + lhq.key_hash = NJS_PATH_HASH; + lhq.proto = &njs_object_hash_proto; + + prop = njs_object_prop_alloc(vm, &njs_fs_path_string, path, 1); + if (nxt_slow_path(prop == NULL)) { + goto memory_error; + } + + lhq.value = prop; + + ret = nxt_lvlhsh_insert(&error->hash, &lhq); + if (nxt_slow_path(ret != NXT_OK)) { + njs_exception_internal_error(vm, NULL, NULL); + return NJS_ERROR; + } + } + + if (syscall != NULL) { + size = strlen(syscall); + ret = njs_string_new(vm, &string, (u_char *) syscall, size, size); + if (nxt_slow_path(ret != NXT_OK)) { + goto memory_error; + } + + lhq.key = nxt_string_value("sycall"); + lhq.key_hash = NJS_SYSCALL_HASH; + lhq.proto = &njs_object_hash_proto; + + prop = njs_object_prop_alloc(vm, &njs_fs_syscall_string, &string, 1); + if (nxt_slow_path(prop == NULL)) { + goto memory_error; + } + + lhq.value = prop; + + ret = nxt_lvlhsh_insert(&error->hash, &lhq); + if (nxt_slow_path(ret != NXT_OK)) { + njs_exception_internal_error(vm, NULL, NULL); + return NJS_ERROR; + } + } + + retval->data.u.object = error; + retval->type = NJS_OBJECT_ERROR; + retval->data.truth = 1; + + return NJS_OK; + +memory_error: + + njs_exception_memory_error(vm); + + return NJS_ERROR; +} + + +static int +njs_fs_flags(nxt_str_t *value) +{ + njs_fs_entry_t *fl; + + for (fl = &njs_flags_table[0]; fl->name.length != 0; fl++) { + if (nxt_strstr_eq(value, &fl->name)) { + return fl->value; + } + } + + return -1; +} + + +static mode_t +njs_fs_mode(njs_value_t *value) +{ + switch (value->type) { + case NJS_OBJECT_NUMBER: + value = &value->data.u.object_value->value; + /* Fall through. */ + + case NJS_NUMBER: + return (mode_t) value->data.u.number; + + case NJS_OBJECT_STRING: + value = &value->data.u.object_value->value; + /* Fall through. */ + + case NJS_STRING: + return (mode_t) njs_string_to_number(value, 0); + + default: + return (mode_t) 0; + } +} + + +static const njs_object_prop_t njs_fs_object_properties[] = +{ + { + .type = NJS_METHOD, + .name = njs_string("readFile"), + .value = njs_native_function(njs_fs_read_file, + njs_continuation_size(njs_fs_cont_t), 0), + }, + + { + .type = NJS_METHOD, + .name = njs_string("readFileSync"), + .value = njs_native_function(njs_fs_read_file_sync, 0, 0), + }, + + { + .type = NJS_METHOD, + .name = njs_string("appendFile"), + .value = njs_native_function(njs_fs_append_file, + njs_continuation_size(njs_fs_cont_t), 0), + }, + + { + .type = NJS_METHOD, + .name = njs_string("appendFileSync"), + .value = njs_native_function(njs_fs_append_file_sync, + njs_continuation_size(njs_fs_cont_t), 0), + }, + + { + .type = NJS_METHOD, + .name = njs_string("writeFile"), + .value = njs_native_function(njs_fs_write_file, + njs_continuation_size(njs_fs_cont_t), 0), + }, + + { + .type = NJS_METHOD, + .name = njs_string("writeFileSync"), + .value = njs_native_function(njs_fs_write_file_sync, + njs_continuation_size(njs_fs_cont_t), 0), + }, + +}; + + +const njs_object_init_t njs_fs_object_init = { + nxt_string("fs"), + njs_fs_object_properties, + nxt_nitems(njs_fs_object_properties), +}; diff --git a/njs/njs_fs.h b/njs/njs_fs.h new file mode 100644 index 00000000..89e7fa61 --- /dev/null +++ b/njs/njs_fs.h @@ -0,0 +1,13 @@ + +/* + * Copyright (C) Dmitry Volyntsev + * Copyright (C) NGINX, Inc. + */ + +#ifndef _NJS_FS_H_INCLUDED_ +#define _NJS_FS_H_INCLUDED_ + +extern const njs_object_init_t njs_fs_object_init; + + +#endif /* _NJS_FS_H_INCLUDED_ */ diff --git a/njs/njs_generator.c b/njs/njs_generator.c index 885fc958..670580b3 100644 --- a/njs/njs_generator.c +++ b/njs/njs_generator.c @@ -319,6 +319,7 @@ njs_generator(njs_vm_t *vm, njs_parser_t *parser, njs_parser_node_t *node) case NJS_TOKEN_ENCODE_URI_COMPONENT: case NJS_TOKEN_DECODE_URI: case NJS_TOKEN_DECODE_URI_COMPONENT: + case NJS_TOKEN_REQUIRE: return njs_generate_builtin_object(vm, parser, node); case NJS_TOKEN_FUNCTION: diff --git a/njs/njs_lexer_keyword.c b/njs/njs_lexer_keyword.c index 3cd84763..88bb0f30 100644 --- a/njs/njs_lexer_keyword.c +++ b/njs/njs_lexer_keyword.c @@ -101,6 +101,7 @@ static const njs_keyword_t njs_keywords[] = { { nxt_string("encodeURIComponent"), NJS_TOKEN_ENCODE_URI_COMPONENT, 0 }, { nxt_string("decodeURI"), NJS_TOKEN_DECODE_URI, 0 }, { nxt_string("decodeURIComponent"), NJS_TOKEN_DECODE_URI_COMPONENT, 0 }, + { nxt_string("require"), NJS_TOKEN_REQUIRE, 0 }, /* Reserved words. */ diff --git a/njs/njs_module.c b/njs/njs_module.c new file mode 100644 index 00000000..b44c7430 --- /dev/null +++ b/njs/njs_module.c @@ -0,0 +1,85 @@ + +/* + * Copyright (C) Dmitry Volyntsev + * Copyright (C) NGINX, Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +static nxt_int_t +njs_module_hash_test(nxt_lvlhsh_query_t *lhq, void *data) +{ + njs_module_t *module; + + module = data; + + if (nxt_strstr_eq(&lhq->key, &module->name)) { + return NXT_OK; + } + + return NXT_DECLINED; +} + + +const nxt_lvlhsh_proto_t njs_modules_hash_proto + nxt_aligned(64) = +{ + NXT_LVLHSH_DEFAULT, + 0, + njs_module_hash_test, + njs_lvlhsh_alloc, + njs_lvlhsh_free, +}; + + +njs_ret_t njs_module_require(njs_vm_t *vm, njs_value_t *args, + nxt_uint_t nargs, njs_index_t unused) +{ + njs_module_t *module; + nxt_lvlhsh_query_t lhq; + + if (nargs < 2) { + njs_exception_type_error(vm, "missing path", NULL); + return NJS_ERROR; + } + + njs_string_get(&args[1], &lhq.key); + lhq.key_hash = nxt_djb_hash(lhq.key.start, lhq.key.length); + lhq.proto = &njs_modules_hash_proto; + + if (nxt_lvlhsh_find(&vm->modules_hash, &lhq) == NXT_OK) { + module = lhq.value; + module->object.__proto__ = &vm->prototypes[NJS_PROTOTYPE_OBJECT].object; + + vm->retval.data.u.object = &module->object; + vm->retval.type = NJS_OBJECT; + vm->retval.data.truth = 1; + + return NXT_OK; + } + + njs_exception_error(vm, "Cannot find module '%.*s'", (int) lhq.key.length, + lhq.key.start); + + return NJS_ERROR; +} diff --git a/njs/njs_module.h b/njs/njs_module.h new file mode 100644 index 00000000..441e2a7e --- /dev/null +++ b/njs/njs_module.h @@ -0,0 +1,22 @@ + +/* + * Copyright (C) Dmitry Volynsev + * Copyright (C) NGINX, Inc. + */ + +#ifndef _NJS_MODULE_H_INCLUDED_ +#define _NJS_MODULE_H_INCLUDED_ + +typedef struct { + nxt_str_t name; + njs_object_t object; +} njs_module_t; + + +njs_ret_t njs_module_require(njs_vm_t *vm, njs_value_t *args, + nxt_uint_t nargs, njs_index_t unused); + +extern const nxt_lvlhsh_proto_t njs_modules_hash_proto; + + +#endif /* _NJS_MODULE_H_INCLUDED_ */ diff --git a/njs/njs_object_hash.h b/njs/njs_object_hash.h index eb8ec083..8b7ce3ac 100644 --- a/njs/njs_object_hash.h +++ b/njs/njs_object_hash.h @@ -53,6 +53,35 @@ 'e'), 'n'), 'u'), 'm'), 'e'), 'r'), 'a'), 'b'), 'l'), 'e') +#define NJS_ERRNO_HASH \ + nxt_djb_hash_add( \ + nxt_djb_hash_add( \ + nxt_djb_hash_add( \ + nxt_djb_hash_add( \ + nxt_djb_hash_add(NXT_DJB_HASH_INIT, \ + 'e'), 'r'), 'r'), 'n'), 'o') + + +#define NJS_ENCODING_HASH \ + nxt_djb_hash_add( \ + nxt_djb_hash_add( \ + nxt_djb_hash_add( \ + nxt_djb_hash_add( \ + nxt_djb_hash_add( \ + nxt_djb_hash_add( \ + nxt_djb_hash_add( \ + nxt_djb_hash_add(NXT_DJB_HASH_INIT, \ + 'e'), 'n'), 'c'), 'o'), 'd'), 'i'), 'n'), 'g') + + +#define NJS_FLAG_HASH \ + nxt_djb_hash_add( \ + nxt_djb_hash_add( \ + nxt_djb_hash_add( \ + nxt_djb_hash_add(NXT_DJB_HASH_INIT, \ + 'f'), 'l'), 'a'), 'g') + + #define NJS_INDEX_HASH \ nxt_djb_hash_add( \ nxt_djb_hash_add( \ @@ -98,6 +127,33 @@ 'm'), 'e'), 's'), 's'), 'a'), 'g'), 'e') +#define NJS_MODE_HASH \ + nxt_djb_hash_add( \ + nxt_djb_hash_add( \ + nxt_djb_hash_add( \ + nxt_djb_hash_add(NXT_DJB_HASH_INIT, \ + 'm'), 'o'), 'd'), 'e') + + +#define NJS_SYSCALL_HASH \ + nxt_djb_hash_add( \ + nxt_djb_hash_add( \ + nxt_djb_hash_add( \ + nxt_djb_hash_add( \ + nxt_djb_hash_add( \ + nxt_djb_hash_add( \ + nxt_djb_hash_add(NXT_DJB_HASH_INIT, \ + 's'), 'y'), 's'), 'c'), 'a'), 'l'), 'l') + + +#define NJS_PATH_HASH \ + nxt_djb_hash_add( \ + nxt_djb_hash_add( \ + nxt_djb_hash_add( \ + nxt_djb_hash_add(NXT_DJB_HASH_INIT, \ + 'p'), 'a'), 't'), 'h') + + #define NJS_PROTOTYPE_HASH \ nxt_djb_hash_add( \ nxt_djb_hash_add( \ diff --git a/njs/njs_parser.c b/njs/njs_parser.c index d97e0827..1cdc6445 100644 --- a/njs/njs_parser.c +++ b/njs/njs_parser.c @@ -2071,6 +2071,7 @@ njs_parser_terminal(njs_vm_t *vm, njs_parser_t *parser, njs_token_t token) case NJS_TOKEN_ENCODE_URI_COMPONENT: case NJS_TOKEN_DECODE_URI: case NJS_TOKEN_DECODE_URI_COMPONENT: + case NJS_TOKEN_REQUIRE: return njs_parser_builtin_function(vm, parser, node); default: diff --git a/njs/njs_parser.h b/njs/njs_parser.h index f2fd25c4..149ce283 100644 --- a/njs/njs_parser.h +++ b/njs/njs_parser.h @@ -198,6 +198,7 @@ typedef enum { NJS_TOKEN_ENCODE_URI_COMPONENT, NJS_TOKEN_DECODE_URI, NJS_TOKEN_DECODE_URI_COMPONENT, + NJS_TOKEN_REQUIRE, NJS_TOKEN_RESERVED, } njs_token_t; diff --git a/njs/njs_string.c b/njs/njs_string.c index 91573738..cfd278d7 100644 --- a/njs/njs_string.c +++ b/njs/njs_string.c @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -3062,6 +3063,51 @@ njs_string_to_index(njs_value_t *value) } +/* + * If string value is null-terminated the corresponding C string + * is returned as is, otherwise the new copy is allocated with + * the terminating zero byte. + */ +u_char * +njs_string_to_c_string(njs_vm_t *vm, njs_value_t *value) +{ + u_char *p, *data, *start; + size_t size; + + if (value->short_string.size != NJS_STRING_LONG) { + start = value->short_string.start; + size = value->short_string.size; + + if (start[size] == '\0') { + return start; + + } else if (size < NJS_STRING_SHORT) { + start[size] = '\0'; + return start; + } + + } else { + start = value->data.u.string->start; + size = value->data.string_size; + + if (start[size] == '\0') { + return start; + } + } + + data = nxt_mem_cache_alloc(vm->mem_cache_pool, size + 1); + if (nxt_slow_path(data == NULL)) { + njs_exception_memory_error(vm); + return NULL; + } + + p = nxt_cpymem(data, start, size); + *p++ = '\0'; + + return data; +} + + static const njs_object_prop_t njs_string_prototype_properties[] = { { diff --git a/njs/njs_string.h b/njs/njs_string.h index 0f605c3c..4b282ff3 100644 --- a/njs/njs_string.h +++ b/njs/njs_string.h @@ -144,6 +144,7 @@ njs_ret_t njs_primitive_value_to_string(njs_vm_t *vm, njs_value_t *dst, const njs_value_t *src); double njs_string_to_index(njs_value_t *value); double njs_string_to_number(njs_value_t *value, nxt_bool_t parse_float); +u_char *njs_string_to_c_string(njs_vm_t *vm, njs_value_t *value); njs_ret_t njs_string_encode_uri(njs_vm_t *vm, njs_value_t *args, nxt_uint_t nargs, njs_index_t unused); njs_ret_t njs_string_encode_uri_component(njs_vm_t *vm, njs_value_t *args, diff --git a/njs/njs_vm.h b/njs/njs_vm.h index 00c96a1f..76abbf07 100644 --- a/njs/njs_vm.h +++ b/njs/njs_vm.h @@ -449,6 +449,16 @@ typedef njs_ret_t (*njs_vmcode_operation_t)(njs_vm_t *vm, njs_value_t *value1, } while (0) +#define njs_string_length_set(value, _length) \ + do { \ + if ((value)->short_string.size != NJS_STRING_LONG) { \ + (value)->short_string.length = length; \ + \ + } else { \ + (value)->data.u.string->length = length; \ + } \ + } while (0) + #define njs_is_primitive(value) \ ((value)->type <= NJS_STRING) @@ -836,6 +846,12 @@ enum njs_object_e { }; +enum njs_module_e { + NJS_MODULE_FS = 0, +#define NJS_MODULE_MAX (NJS_MODULE_FS + 1) +}; + + enum njs_function_e { NJS_FUNCTION_EVAL = 0, NJS_FUNCTION_TO_STRING, @@ -847,7 +863,8 @@ enum njs_function_e { NJS_FUNCTION_STRING_ENCODE_URI_COMPONENT, NJS_FUNCTION_STRING_DECODE_URI, NJS_FUNCTION_STRING_DECODE_URI_COMPONENT, -#define NJS_FUNCTION_MAX (NJS_FUNCTION_STRING_DECODE_URI_COMPONENT + 1) + NJS_FUNCTION_REQUIRE, +#define NJS_FUNCTION_MAX (NJS_FUNCTION_REQUIRE + 1) }; @@ -934,6 +951,7 @@ struct njs_vm_s { nxt_lvlhsh_t externals_hash; nxt_lvlhsh_t variables_hash; nxt_lvlhsh_t values_hash; + nxt_lvlhsh_t modules_hash; /* * The prototypes and constructors arrays must be together because diff --git a/njs/njscript.c b/njs/njscript.c index 6c6d23a7..ccafbaf7 100644 --- a/njs/njscript.c +++ b/njs/njscript.c @@ -159,6 +159,8 @@ njs_vm_create(njs_vm_opt_t *options) vm->shared->empty_regexp_pattern = pattern; + nxt_lvlhsh_init(&vm->modules_hash); + ret = njs_builtin_objects_create(vm); if (nxt_slow_path(ret != NXT_OK)) { return NULL; @@ -324,6 +326,7 @@ njs_vm_clone(njs_vm_t *vm, nxt_mem_cache_pool_t *mcp, void **external) nvm->variables_hash = vm->variables_hash; nvm->values_hash = vm->values_hash; + nvm->modules_hash = vm->modules_hash; nvm->externals_hash = vm->externals_hash; nvm->current = vm->current; diff --git a/njs/test/njs_expect_test.exp b/njs/test/njs_expect_test.exp index 0a4fae3f..a80c0892 100644 --- a/njs/test/njs_expect_test.exp +++ b/njs/test/njs_expect_test.exp @@ -182,3 +182,224 @@ njs_test { {"JSON.parse(Error()\r\n" "JSON.parse(Error()\r\nSyntaxError: Unexpected token \"\" in 1"} } + +# require('fs') + +set file [open /tmp/njs_test_file w] +puts -nonewline $file "αβZγ" +flush $file + +exec /bin/echo -ne {\x80\x80} > /tmp/njs_test_file_non_utf8 + +njs_test { + {"var fs = require('fs')\r\n" + "undefined\r\n>> "} + {"fs.read\t" + "fs.read\aFile"} +} + +# require('fs').readFile() + +njs_test { + {"var fs = require('fs')\r\n" + "undefined\r\n>> "} + {"fs.readFile('/tmp/njs_test_file', 'utf8', function (e, data) {console.log(data[2]+data.length)})\r\n" + "Z4\r\nundefined\r\n>> "} +} + +njs_test { + {"var fs = require('fs')\r\n" + "undefined\r\n>> "} + {"fs.readFile('/tmp/njs_test_file', function (e, data) {console.log(data[4]+data.length)})\r\n" + "Z7\r\nundefined\r\n>> "} +} + +njs_test { + {"var fs = require('fs')\r\n" + "undefined\r\n>> "} + {"fs.readFile('/tmp/njs_test_file', {encoding:'utf8',flag:'r+'}, function (e, data) {console.log(data)})\r\n" + "αβZγ\r\nundefined\r\n>> "} +} + +njs_test { + {"var fs = require('fs'); \r\n" + "undefined\r\n>> "} + {"fs.readFile('/tmp/njs_unknown_path', 'utf8', function (e) {console.log(JSON.stringify(e))})\r\n" + "{\"errno\":2,\"path\":\"/tmp/njs_unknown_path\",\"syscall\":\"open\"}\r\nundefined\r\n>> "} +} + +njs_test { + {"var fs = require('fs'); \r\n" + "undefined\r\n>> "} + {"fs.readFile('/tmp/njs_unknown_path', {encoding:'utf8', flag:'r+'}, function (e) {console.log(e)})\r\n" + "Error: No such file or directory\r\nundefined\r\n>> "} +} + +# require('fs').readFileSync() + +njs_test { + {"var fs = require('fs')\r\n" + "undefined\r\n>> "} + {"fs.readFileSync('/tmp/njs_test_file', 'utf8')[2]\r\n" + "Z\r\n>> "} +} + +njs_test { + {"var fs = require('fs')\r\n" + "undefined\r\n>> "} + {"fs.readFileSync('/tmp/njs_test_file')[4]\r\n" + "Z\r\n>> "} +} + +njs_test { + {"var fs = require('fs')\r\n" + "undefined\r\n>> "} + {"fs.readFileSync('/tmp/njs_test_file', {encoding:'utf8',flag:'r+'})\r\n" + "αβZγ\r\n>> "} +} + +njs_test { + {"var fs = require('fs'); \r\n" + "undefined\r\n>> "} + {"try { fs.readFileSync('/tmp/njs_unknown_path')} catch (e) {console.log(JSON.stringify(e))}\r\n" + "{\"errno\":2,\"path\":\"/tmp/njs_unknown_path\",\"syscall\":\"open\"}\r\nundefined\r\n>> "} +} + +njs_test { + {"var fs = require('fs')\r\n" + "undefined\r\n>> "} + {"fs.readFileSync('/tmp/njs_test_file_non_utf8').charCodeAt(1)\r\n" + "128"} +} + +njs_test { + {"var fs = require('fs')\r\n" + "undefined\r\n>> "} + {"fs.readFileSync('/tmp/njs_test_file_non_utf8', 'utf8')\r\n" + "Error: Non-UTF8 file, convertion is not implemented"} +} + + +# require('fs').writeFile() + +exec rm -fr /tmp/njs_test_file2 + +njs_test { + {"var fs = require('fs')\r\n" + "undefined\r\n>> "} + {"function h1(e) {if (e) {throw e}; console.log(fs.readFileSync('/tmp/njs_test_file2'))}\r\n" + "undefined\r\n>> "} + {"fs.writeFile('/tmp/njs_test_file2', 'ABC', h1)\r\n" + "ABC\r\nundefined\r\n>> "} +} + +njs_test { + {"var fs = require('fs')\r\n" + "undefined\r\n>> "} + {"fs.writeFile('/tmp/njs_test_file2', 'ABC', 'utf8', function (e) { if (e) {throw e}; console.log(fs.readFileSync('/tmp/njs_test_file2'))})\r\n" + "ABC\r\nundefined\r\n>> "} +} + +njs_test { + {"var fs = require('fs')\r\n" + "undefined\r\n>> "} + {"fs.writeFile('/tmp/njs_test_file2', 'ABC', {encoding:'utf8', mode:0o666}, function (e) { if (e) {throw e}; console.log(fs.readFileSync('/tmp/njs_test_file2'))})\r\n" + "ABC\r\nundefined\r\n>> "} +} + +exec rm -fr /tmp/njs_wo_file + +njs_test { + {"var fs = require('fs')\r\n" + "undefined\r\n>> "} + {"fs.writeFile('/tmp/njs_wo_file', 'ABC', {mode:0o222}, function (e) {console.log(fs.readFileSync('/tmp/njs_wo_file'))})\r\n" + "Error: Permission denied"} +} + +njs_test { + {"var fs = require('fs')\r\n" + "undefined\r\n>> "} + {"fs.writeFile('/invalid_path', 'ABC', function (e) { console.log(JSON.stringify(e))})\r\n" + "{\"errno\":13,\"path\":\"/invalid_path\",\"syscall\":\"open\"}\r\nundefined\r\n>> "} +} + +# require('fs').writeFileSync() + +exec rm -fr /tmp/njs_test_file2 + +njs_test { + {"var fs = require('fs')\r\n" + "undefined\r\n>> "} + {"fs.writeFileSync('/tmp/njs_test_file2', 'ABC')\r\n" + "undefined\r\n>> "} + {"fs.readFileSync('/tmp/njs_test_file2')\r\n" + "ABC\r\n>> "} +} + +njs_test { + {"var fs = require('fs')\r\n" + "undefined\r\n>> "} + {"fs.writeFileSync('/tmp/njs_test_file2', 'ABC', 'utf8')\r\n" + "undefined\r\n>> "} + {"fs.readFileSync('/tmp/njs_test_file2')\r\n" + "ABC\r\n>> "} +} + +njs_test { + {"var fs = require('fs')\r\n" + "undefined\r\n>> "} + {"fs.writeFileSync('/tmp/njs_test_file2', 'ABC')\r\n" + "undefined\r\n>> "} + {"fs.writeFileSync('/tmp/njs_test_file2', 'ABC')\r\n" + "undefined\r\n>> "} + {"fs.readFileSync('/tmp/njs_test_file2')\r\n" + "ABC\r\n>> "} +} + +njs_test { + {"var fs = require('fs')\r\n" + "undefined\r\n>> "} + {"fs.writeFileSync('/tmp/njs_test_file2', 'ABC', {encoding:'utf8', mode:0o666})\r\n" + "undefined\r\n>> "} + {"fs.readFileSync('/tmp/njs_test_file2')\r\n" + "ABC\r\n>> "} +} + +exec rm -fr /tmp/njs_wo_file + +njs_test { + {"var fs = require('fs')\r\n" + "undefined\r\n>> "} + {"fs.writeFileSync('/tmp/njs_wo_file', 'ABC', {mode:0o222}); fs.readFileSync('/tmp/njs_wo_file')\r\n" + "Error: Permission denied"} +} + +# require('fs').appendFile() + +exec rm -fr /tmp/njs_test_file2 + +njs_test { + {"var fs = require('fs')\r\n" + "undefined\r\n>> "} + {"function h1(e) {console.log(fs.readFileSync('/tmp/njs_test_file2'))}\r\n" + "undefined\r\n>> "} + {"function h2(e) {fs.appendFile('/tmp/njs_test_file2', 'ABC', h1)}\r\n" + "undefined\r\n>> "} + {"fs.appendFile('/tmp/njs_test_file2', 'ABC', h2)\r\n" + "ABCABC\r\nundefined\r\n>> "} +} + +# require('fs').appendFileSync() + +exec rm -fr /tmp/njs_test_file2 + +njs_test { + {"var fs = require('fs')\r\n" + "undefined\r\n>> "} + {"fs.appendFileSync('/tmp/njs_test_file2', 'ABC')\r\n" + "undefined\r\n>> "} + {"fs.appendFileSync('/tmp/njs_test_file2', 'ABC')\r\n" + "undefined\r\n>> "} + {"fs.readFileSync('/tmp/njs_test_file2')\r\n" + "ABCABC\r\n>> "} +} diff --git a/njs/test/njs_interactive_test.c b/njs/test/njs_interactive_test.c index 25030f4b..8a1ad75d 100644 --- a/njs/test/njs_interactive_test.c +++ b/njs/test/njs_interactive_test.c @@ -178,6 +178,11 @@ static njs_interactive_test_t njs_test[] = " at f (:1)\n" " at main (native)\n") }, + { nxt_string("var fs = require('fs'); fs.readFile()" ENTER), + nxt_string("TypeError: too few arguments\n" + " at fs.readFile (native)\n" + " at main (native)\n") }, + /* Exception in njs_vm_retval() */ { nxt_string("var o = { toString: function() { return [1] } }" ENTER diff --git a/njs/test/njs_unit_test.c b/njs/test/njs_unit_test.c index da640509..c2fb830f 100644 --- a/njs/test/njs_unit_test.c +++ b/njs/test/njs_unit_test.c @@ -8750,6 +8750,142 @@ static njs_unit_test_t njs_test[] = { nxt_string("var a = {}; a.a = a; JSON.stringify(a)"), nxt_string("TypeError: Nested too deep or a cyclic structure") }, + /* require(). */ + + { nxt_string("require('unknown_module')"), + nxt_string("Error: Cannot find module 'unknown_module'") }, + + { nxt_string("require()"), + nxt_string("TypeError: missing path") }, + + { nxt_string("var fs = require('fs'); typeof fs"), + nxt_string("object") }, + + /* require('fs').readFile() */ + + { nxt_string("var fs = require('fs');" + "fs.readFile()"), + nxt_string("TypeError: too few arguments") }, + + { nxt_string("var fs = require('fs');" + "fs.readFile('/njs_unknown_path')"), + nxt_string("TypeError: too few arguments") }, + + { nxt_string("var fs = require('fs');" + "fs.readFile('/njs_unknown_path', {flag:'xx'})"), + nxt_string("TypeError: callback must be a function") }, + + { nxt_string("var fs = require('fs');" + "fs.readFile('/njs_unknown_path', {flag:'xx'}, 1)"), + nxt_string("TypeError: callback must be a function") }, + + { nxt_string("var fs = require('fs');" + "fs.readFile('/njs_unknown_path', {flag:'xx'}, function () {})"), + nxt_string("TypeError: Unknown file open flags: 'xx'") }, + + { nxt_string("var fs = require('fs');" + "fs.readFile('/njs_unknown_path', {encoding:'ascii'}, function () {})"), + nxt_string("TypeError: Unknown encoding: 'ascii'") }, + + { nxt_string("var fs = require('fs');" + "fs.readFile('/njs_unknown_path', 'ascii', function () {})"), + nxt_string("TypeError: Unknown encoding: 'ascii'") }, + + /* require('fs').readFileSync() */ + + { nxt_string("var fs = require('fs');" + "fs.readFileSync()"), + nxt_string("TypeError: too few arguments") }, + + { nxt_string("var fs = require('fs');" + "fs.readFileSync({})"), + nxt_string("TypeError: path must be a string") }, + + { nxt_string("var fs = require('fs');" + "fs.readFileSync('/njs_unknown_path', {flag:'xx'})"), + nxt_string("TypeError: Unknown file open flags: 'xx'") }, + + { nxt_string("var fs = require('fs');" + "fs.readFileSync('/njs_unknown_path', {encoding:'ascii'})"), + nxt_string("TypeError: Unknown encoding: 'ascii'") }, + + { nxt_string("var fs = require('fs');" + "fs.readFileSync('/njs_unknown_path', 'ascii')"), + nxt_string("TypeError: Unknown encoding: 'ascii'") }, + + { nxt_string("var fs = require('fs');" + "fs.readFileSync('/njs_unknown_path', true)"), + nxt_string("TypeError: Unknown options type (a string or object required)") }, + + + /* require('fs').writeFile() */ + + { nxt_string("var fs = require('fs');" + "fs.writeFile()"), + nxt_string("TypeError: too few arguments") }, + + { nxt_string("var fs = require('fs');" + "fs.writeFile('/njs_unknown_path')"), + nxt_string("TypeError: too few arguments") }, + + { nxt_string("var fs = require('fs');" + "fs.writeFile('/njs_unknown_path', '')"), + nxt_string("TypeError: too few arguments") }, + + { nxt_string("var fs = require('fs');" + "fs.writeFile({}, '', function () {})"), + nxt_string("TypeError: path must be a string") }, + + { nxt_string("var fs = require('fs');" + "fs.writeFile('/njs_unknown_path', '', 'utf8')"), + nxt_string("TypeError: callback must be a function") }, + + { nxt_string("var fs = require('fs');" + "fs.writeFile('/njs_unknown_path', '', {flag:'xx'}, function () {})"), + nxt_string("TypeError: Unknown file open flags: 'xx'") }, + + { nxt_string("var fs = require('fs');" + "fs.writeFile('/njs_unknown_path', '', {encoding:'ascii'}, function () {})"), + nxt_string("TypeError: Unknown encoding: 'ascii'") }, + + { nxt_string("var fs = require('fs');" + "fs.writeFile('/njs_unknown_path', '', 'ascii', function () {})"), + nxt_string("TypeError: Unknown encoding: 'ascii'") }, + + { nxt_string("var fs = require('fs');" + "fs.writeFile('/njs_unknown_path', '', true, function () {})"), + nxt_string("TypeError: Unknown options type (a string or object required)") }, + + /* require('fs').writeFileSync() */ + + { nxt_string("var fs = require('fs');" + "fs.writeFileSync()"), + nxt_string("TypeError: too few arguments") }, + + { nxt_string("var fs = require('fs');" + "fs.writeFileSync('/njs_unknown_path')"), + nxt_string("TypeError: too few arguments") }, + + { nxt_string("var fs = require('fs');" + "fs.writeFileSync({}, '')"), + nxt_string("TypeError: path must be a string") }, + + { nxt_string("var fs = require('fs');" + "fs.writeFileSync('/njs_unknown_path', '', {flag:'xx'})"), + nxt_string("TypeError: Unknown file open flags: 'xx'") }, + + { nxt_string("var fs = require('fs');" + "fs.writeFileSync('/njs_unknown_path', '', {encoding:'ascii'})"), + nxt_string("TypeError: Unknown encoding: 'ascii'") }, + + { nxt_string("var fs = require('fs');" + "fs.writeFileSync('/njs_unknown_path', '', 'ascii')"), + nxt_string("TypeError: Unknown encoding: 'ascii'") }, + + { nxt_string("var fs = require('fs');" + "fs.writeFileSync('/njs_unknown_path', '', true)"), + nxt_string("TypeError: Unknown options type (a string or object required)") }, + /* Trick: number to boolean. */ { nxt_string("var a = 0; !!a"),