--- /dev/null
+
+/*
+ * Copyright (C) Dmitry Volyntsev
+ * Copyright (C) NGINX, Inc.
+ */
+
+#include <nxt_auto_config.h>
+#include <nxt_alignment.h>
+#include <nxt_types.h>
+#include <nxt_clang.h>
+#include <nxt_string.h>
+#include <nxt_stub.h>
+#include <nxt_djb_hash.h>
+#include <nxt_array.h>
+#include <nxt_lvlhsh.h>
+#include <nxt_random.h>
+#include <nxt_mem_cache_pool.h>
+#include <njscript.h>
+#include <njs_vm.h>
+#include <njs_string.h>
+#include <njs_object.h>
+#include <njs_object_hash.h>
+#include <njs_function.h>
+#include <njs_error.h>
+#include <njs_fs.h>
+#include <string.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <errno.h>
+#include <stdio.h>
+
+
+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),
+};
{"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>> "}
+}
{ 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"),