From: Dmitry Volyntsev Date: Mon, 22 Aug 2022 23:49:27 +0000 (-0700) Subject: Added fs.FileHandle. X-Git-Tag: 0.7.7~9 X-Git-Url: http://www.kaiwu.me/postgresql/commit/static/gitweb.js?a=commitdiff_plain;h=6fe21e260095caa9c07fa9cbb4c0708fd95151d9;p=njs.git Added fs.FileHandle. The following methods are implemented: - fs.openSync(path[, flag[, mode]]) - fs.promises.open(path[, flag[, mode]]) - fs.fstatSync(fd) - fs.readSync(fd, buffer, offset[, length[, position]]) - fs.writeSync(fd, buffer, offset[, length[, position]]) - fs.writeSync(fd, string[, position[, encoding]]) The following properties of FileHandle are implemented: - filehandle.fd - filehandle.read(buffer, offset, [length[, position]]) - filehandle.stat() - filehandle.write(buffer, offset, [length[, position]]) - filehandle.write(string[, position[, encoding]]) - filehandle.close() --- diff --git a/external/njs_fs_module.c b/external/njs_fs_module.c index 71a1d27e..68549b27 100644 --- a/external/njs_fs_module.c +++ b/external/njs_fs_module.c @@ -56,6 +56,7 @@ typedef enum { typedef enum { NJS_FS_STAT, NJS_FS_LSTAT, + NJS_FS_FSTAT, } njs_fs_statmode_t; @@ -125,6 +126,18 @@ typedef enum { } njs_stat_prop_t; +typedef struct { + njs_int_t fd; + njs_vm_t *vm; +} njs_filehandle_t; + + +typedef struct { + njs_int_t bytes; + njs_value_t buffer; +} njs_bytes_struct_t; + + typedef njs_int_t (*njs_file_tree_walk_cb_t)(const char *, const struct stat *, njs_ftw_type_t); @@ -133,7 +146,13 @@ static njs_int_t njs_fs_access(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, njs_index_t calltype); static njs_int_t njs_fs_mkdir(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, njs_index_t calltype); -static njs_int_t njs_fs_read(njs_vm_t *vm, njs_value_t *args, +static njs_int_t njs_fs_open(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, + njs_index_t calltype); +static njs_int_t njs_fs_close(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, + njs_index_t calltype); +static njs_int_t njs_fs_read(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, + njs_index_t calltype); +static njs_int_t njs_fs_read_file(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, njs_index_t calltype); static njs_int_t njs_fs_readdir(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, njs_index_t calltype); @@ -149,7 +168,9 @@ static njs_int_t njs_fs_symlink(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, njs_index_t calltype); static njs_int_t njs_fs_unlink(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, njs_index_t calltype); -static njs_int_t njs_fs_write(njs_vm_t *vm, njs_value_t *args, +static njs_int_t njs_fs_write(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, + njs_index_t calltype); +static njs_int_t njs_fs_write_file(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, njs_index_t calltype); static njs_int_t njs_fs_constants(njs_vm_t *vm, njs_object_prop_t *prop, @@ -169,6 +190,18 @@ static njs_int_t njs_fs_stats_prop(njs_vm_t *vm, njs_object_prop_t *prop, static njs_int_t njs_fs_stats_create(njs_vm_t *vm, struct stat *st, njs_value_t *retval); +static njs_int_t njs_fs_filehandle_close(njs_vm_t *vm, njs_value_t *args, + njs_uint_t nargs, njs_index_t unused); +static njs_int_t njs_fs_filehandle_value_of(njs_vm_t *vm, njs_value_t *args, + njs_uint_t nargs, njs_index_t unused); +static njs_int_t njs_fs_filehandle_create(njs_vm_t *vm, int fd, + njs_bool_t shadow, njs_value_t *retval); + +static njs_int_t njs_fs_bytes_read_create(njs_vm_t *vm, int bytes, + njs_value_t *buffer, njs_value_t *retval); +static njs_int_t njs_fs_bytes_written_create(njs_vm_t *vm, int bytes, + njs_value_t *buffer, njs_value_t *retval); + static njs_int_t njs_fs_fd_read(njs_vm_t *vm, int fd, njs_str_t *data); static njs_int_t njs_fs_error(njs_vm_t *vm, const char *syscall, @@ -263,7 +296,7 @@ static njs_external_t njs_ext_fs[] = { .writable = 1, .configurable = 1, .u.method = { - .native = njs_fs_write, + .native = njs_fs_write_file, .magic8 = njs_fs_magic(NJS_FS_CALLBACK, NJS_FS_APPEND), } }, @@ -274,11 +307,22 @@ static njs_external_t njs_ext_fs[] = { .writable = 1, .configurable = 1, .u.method = { - .native = njs_fs_write, + .native = njs_fs_write_file, .magic8 = njs_fs_magic(NJS_FS_DIRECT, NJS_FS_APPEND), } }, + { + .flags = NJS_EXTERN_METHOD, + .name.string = njs_str("closeSync"), + .writable = 1, + .configurable = 1, + .u.method = { + .native = njs_fs_close, + .magic8 = NJS_FS_DIRECT, + } + }, + { .flags = NJS_EXTERN_PROPERTY, .name.string = njs_str("constants"), @@ -299,6 +343,17 @@ static njs_external_t njs_ext_fs[] = { } }, + { + .flags = NJS_EXTERN_METHOD, + .name.string = njs_str("fstatSync"), + .writable = 1, + .configurable = 1, + .u.method = { + .native = njs_fs_stat, + .magic8 = njs_fs_magic(NJS_FS_DIRECT, NJS_FS_FSTAT), + } + }, + { .flags = NJS_EXTERN_METHOD, .name.string = njs_str("lstat"), @@ -343,6 +398,17 @@ static njs_external_t njs_ext_fs[] = { } }, + { + .flags = NJS_EXTERN_METHOD, + .name.string = njs_str("openSync"), + .writable = 1, + .configurable = 1, + .u.method = { + .native = njs_fs_open, + .magic8 = NJS_FS_DIRECT, + } + }, + { .flags = NJS_EXTERN_PROPERTY, .name.string = njs_str("promises"), @@ -380,7 +446,7 @@ static njs_external_t njs_ext_fs[] = { .writable = 1, .configurable = 1, .u.method = { - .native = njs_fs_read, + .native = njs_fs_read_file, .magic8 = NJS_FS_CALLBACK, } }, @@ -390,6 +456,17 @@ static njs_external_t njs_ext_fs[] = { .name.string = njs_str("readFileSync"), .writable = 1, .configurable = 1, + .u.method = { + .native = njs_fs_read_file, + .magic8 = NJS_FS_DIRECT, + } + }, + + { + .flags = NJS_EXTERN_METHOD, + .name.string = njs_str("readSync"), + .writable = 1, + .configurable = 1, .u.method = { .native = njs_fs_read, .magic8 = NJS_FS_DIRECT, @@ -534,7 +611,7 @@ static njs_external_t njs_ext_fs[] = { .writable = 1, .configurable = 1, .u.method = { - .native = njs_fs_write, + .native = njs_fs_write_file, .magic8 = njs_fs_magic(NJS_FS_CALLBACK, NJS_FS_TRUNC), } }, @@ -545,11 +622,22 @@ static njs_external_t njs_ext_fs[] = { .writable = 1, .configurable = 1, .u.method = { - .native = njs_fs_write, + .native = njs_fs_write_file, .magic8 = njs_fs_magic(NJS_FS_DIRECT, NJS_FS_TRUNC), } }, + { + .flags = NJS_EXTERN_METHOD, + .name.string = njs_str("writeSync"), + .writable = 1, + .configurable = 1, + .u.method = { + .native = njs_fs_write, + .magic8 = NJS_FS_DIRECT, + } + }, + }; @@ -922,8 +1010,158 @@ static njs_external_t njs_ext_stats[] = { }; +static njs_external_t njs_ext_filehandle[] = { + + { + .flags = NJS_EXTERN_PROPERTY | NJS_EXTERN_SYMBOL, + .name.symbol = NJS_SYMBOL_TO_STRING_TAG, + .u.property = { + .value = "FileHandle", + } + }, + + { + .flags = NJS_EXTERN_METHOD, + .name.string = njs_str("close"), + .writable = 1, + .configurable = 1, + .u.method = { + .native = njs_fs_filehandle_close, + } + }, + + { + .flags = NJS_EXTERN_PROPERTY, + .name.string = njs_str("fd"), + .enumerable = 1, + .u.property = { + .handler = njs_external_property, + .magic32 = offsetof(njs_filehandle_t, fd), + .magic16 = NJS_EXTERN_TYPE_INT, + } + }, + + { + .flags = NJS_EXTERN_METHOD, + .name.string = njs_str("read"), + .writable = 1, + .configurable = 1, + .u.method = { + .native = njs_fs_read, + .magic8 = NJS_FS_PROMISE, + } + }, + + { + .flags = NJS_EXTERN_METHOD, + .name.string = njs_str("stat"), + .writable = 1, + .configurable = 1, + .u.method = { + .native = njs_fs_stat, + .magic8 = njs_fs_magic(NJS_FS_PROMISE, NJS_FS_FSTAT), + } + }, + + { + .flags = NJS_EXTERN_METHOD, + .name.string = njs_str("valueOf"), + .writable = 1, + .configurable = 1, + .u.method = { + .native = njs_fs_filehandle_value_of, + } + }, + + { + .flags = NJS_EXTERN_METHOD, + .name.string = njs_str("write"), + .writable = 1, + .configurable = 1, + .u.method = { + .native = njs_fs_write, + .magic8 = NJS_FS_PROMISE, + } + }, + +}; + + +static njs_external_t njs_ext_bytes_read[] = { + + { + .flags = NJS_EXTERN_PROPERTY | NJS_EXTERN_SYMBOL, + .name.symbol = NJS_SYMBOL_TO_STRING_TAG, + .u.property = { + .value = "BytesRead", + } + }, + + { + .flags = NJS_EXTERN_PROPERTY, + .name.string = njs_str("buffer"), + .enumerable = 1, + .u.property = { + .handler = njs_external_property, + .magic32 = offsetof(njs_bytes_struct_t, buffer), + .magic16 = NJS_EXTERN_TYPE_VALUE, + } + }, + + { + .flags = NJS_EXTERN_PROPERTY, + .name.string = njs_str("bytesRead"), + .enumerable = 1, + .u.property = { + .handler = njs_external_property, + .magic32 = offsetof(njs_bytes_struct_t, bytes), + .magic16 = NJS_EXTERN_TYPE_INT, + } + }, + +}; + + +static njs_external_t njs_ext_bytes_written[] = { + + { + .flags = NJS_EXTERN_PROPERTY | NJS_EXTERN_SYMBOL, + .name.symbol = NJS_SYMBOL_TO_STRING_TAG, + .u.property = { + .value = "BytesWritten", + } + }, + + { + .flags = NJS_EXTERN_PROPERTY, + .name.string = njs_str("buffer"), + .enumerable = 1, + .u.property = { + .handler = njs_external_property, + .magic32 = offsetof(njs_bytes_struct_t, buffer), + .magic16 = NJS_EXTERN_TYPE_VALUE, + } + }, + + { + .flags = NJS_EXTERN_PROPERTY, + .name.string = njs_str("bytesWritten"), + .enumerable = 1, + .u.property = { + .handler = njs_external_property, + .magic32 = offsetof(njs_bytes_struct_t, bytes), + .magic16 = NJS_EXTERN_TYPE_INT, + } + }, + +}; + + static njs_int_t njs_fs_stats_proto_id; static njs_int_t njs_fs_dirent_proto_id; +static njs_int_t njs_fs_filehandle_proto_id; +static njs_int_t njs_fs_bytes_read_proto_id; +static njs_int_t njs_fs_bytes_written_proto_id; njs_module_t njs_fs_module = { @@ -991,6 +1229,101 @@ njs_fs_access(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, } +static njs_int_t +njs_fs_open(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, + njs_index_t calltype) +{ + int fd, flags; + mode_t md; + njs_int_t ret; + const char *path; + njs_value_t retval, *value; + char path_buf[NJS_MAX_PATH + 1]; + + path = njs_fs_path(vm, path_buf, njs_arg(args, nargs, 1), "path"); + if (njs_slow_path(path == NULL)) { + return NJS_ERROR; + } + + value = njs_arg(args, nargs, 2); + if (njs_is_function(value)) { + value = njs_value_arg(&njs_value_undefined); + } + + flags = njs_fs_flags(vm, value, O_RDONLY); + if (njs_slow_path(flags == -1)) { + return NJS_ERROR; + } + + value = njs_arg(args, nargs, 3); + if (njs_is_function(value)) { + value = njs_value_arg(&njs_value_undefined); + } + + md = njs_fs_mode(vm, value, 0666); + if (njs_slow_path(md == (mode_t) -1)) { + return NJS_ERROR; + } + + fd = open(path, flags, md); + if (njs_slow_path(fd < 0)) { + ret = njs_fs_error(vm, "open", strerror(errno), path, errno, &retval); + goto done; + } + + ret = njs_fs_filehandle_create(vm, fd, calltype == NJS_FS_DIRECT, &retval); + if (njs_slow_path(ret != NJS_OK)) { + goto done; + } + + if (calltype == NJS_FS_DIRECT) { + njs_value_number_set(&retval, fd); + } + +done: + + if (ret == NJS_OK) { + return njs_fs_result(vm, &retval, calltype, NULL, 2); + } + + if (fd != -1) { + (void) close(fd); + } + + return NJS_ERROR; +} + + +static njs_int_t +njs_fs_close(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, + njs_index_t calltype) +{ + int64_t fd; + njs_int_t ret; + njs_value_t retval, *fh; + + fh = njs_arg(args, nargs, 1); + + ret = njs_value_to_integer(vm, fh, &fd); + if (njs_slow_path(ret != NJS_OK)) { + return ret; + } + + njs_set_undefined(&retval); + + ret = close((int) fd); + if (njs_slow_path(ret != 0)) { + ret = njs_fs_error(vm, "close", strerror(errno), NULL, errno, &retval); + } + + if (ret == NJS_OK) { + return njs_fs_result(vm, &retval, calltype, NULL, 1); + } + + return NJS_ERROR; +} + + static njs_int_t njs_fs_mkdir(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, njs_index_t calltype) @@ -1039,28 +1372,139 @@ njs_fs_mkdir(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, return NJS_ERROR; } - ret = njs_value_property(vm, options, njs_value_arg(&string_mode), - &mode); - if (njs_slow_path(ret == NJS_ERROR)) { - return ret; + ret = njs_value_property(vm, options, njs_value_arg(&string_mode), + &mode); + if (njs_slow_path(ret == NJS_ERROR)) { + return ret; + } + + ret = njs_value_property(vm, options, njs_value_arg(&string_recursive), + &recursive); + if (njs_slow_path(ret == NJS_ERROR)) { + return ret; + } + } + + md = njs_fs_mode(vm, &mode, 0777); + if (njs_slow_path(md == (mode_t) -1)) { + return NJS_ERROR; + } + + ret = njs_fs_make_path(vm, path, md, njs_is_true(&recursive), &retval); + + if (ret == NJS_OK) { + return njs_fs_result(vm, &retval, calltype, callback, 1); + } + + return NJS_ERROR; +} + + +static njs_int_t +njs_fs_read(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, + njs_index_t calltype) +{ + int64_t fd, length, pos, offset; + ssize_t n; + njs_int_t ret; + njs_str_t data; + njs_uint_t fd_offset; + njs_value_t retval, *buffer, *value; + njs_typed_array_t *array; + njs_array_buffer_t *array_buffer; + + fd_offset = !!(calltype == NJS_FS_DIRECT); + + ret = njs_value_to_integer(vm, njs_arg(args, nargs, fd_offset), &fd); + if (njs_slow_path(ret != NJS_OK)) { + return ret; + } + + pos = -1; + + /* + * fh.read(buffer, offset[, length[, position]]) + * fs.readSync(fd, buffer, offset[, length[, position]]) + */ + + buffer = njs_arg(args, nargs, fd_offset + 1); + array = njs_buffer_slot(vm, buffer, "buffer"); + if (njs_slow_path(array == NULL)) { + return NJS_ERROR; + } + + array_buffer = njs_typed_array_writable(vm, array); + if (njs_slow_path(array_buffer == NULL)) { + return NJS_ERROR; + } + + ret = njs_value_to_integer(vm, njs_arg(args, nargs, fd_offset + 2), + &offset); + if (njs_slow_path(ret != NJS_OK)) { + return ret; + } + + if (njs_slow_path(offset < 0 || (size_t) offset > array->byte_length)) { + njs_range_error(vm, "offset is out of range (must be <= %z)", + array->byte_length); + return NJS_ERROR; + } + + data.length = array->byte_length - offset; + data.start = &array_buffer->u.u8[array->offset + offset]; + + value = njs_arg(args, nargs, fd_offset + 3); + + if (njs_is_defined(value)) { + ret = njs_value_to_integer(vm, value, &length); + if (njs_slow_path(ret != NJS_OK)) { + return ret; + } + + if (njs_slow_path(length < 0 || (size_t) length > data.length)) { + njs_range_error(vm, "length is out of range (must be <= %z)", + data.length); + return NJS_ERROR; } - ret = njs_value_property(vm, options, njs_value_arg(&string_recursive), - &recursive); - if (njs_slow_path(ret == NJS_ERROR)) { + data.length = length; + } + + value = njs_arg(args, nargs, fd_offset + 4); + + if (!njs_is_null_or_undefined(value)) { + ret = njs_value_to_integer(vm, value, &pos); + if (njs_slow_path(ret != NJS_OK)) { return ret; } } - md = njs_fs_mode(vm, &mode, 0777); - if (njs_slow_path(md == (mode_t) -1)) { - return NJS_ERROR; + if (pos == -1) { + n = read(fd, data.start, data.length); + + } else { + n = pread(fd, data.start, data.length, pos); } - ret = njs_fs_make_path(vm, path, md, njs_is_true(&recursive), &retval); + if (njs_slow_path(n == -1)) { + ret = njs_fs_error(vm, "read", strerror(errno), NULL, errno, &retval); + goto done; + } + + if (calltype == NJS_FS_PROMISE) { + ret = njs_fs_bytes_read_create(vm, n, buffer, &retval); + if (njs_slow_path(ret != NJS_OK)) { + goto done; + } + + } else { + njs_value_number_set(&retval, n); + } + +done: if (ret == NJS_OK) { - return njs_fs_result(vm, &retval, calltype, callback, 1); + return njs_fs_result(vm, &retval, calltype, NULL, 1); } return NJS_ERROR; @@ -1068,7 +1512,7 @@ njs_fs_mkdir(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, static njs_int_t -njs_fs_read(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, +njs_fs_read_file(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, njs_index_t calltype) { int fd, flags; @@ -1552,7 +1996,9 @@ static njs_int_t njs_fs_stat(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, njs_index_t magic) { + int64_t fd; njs_int_t ret; + njs_uint_t fd_offset; njs_bool_t throw; struct stat sb; const char *path; @@ -1563,14 +2009,29 @@ njs_fs_stat(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, 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; + fd = -1; + path = NULL; + calltype = magic & 3; + + if ((magic >> 2) != NJS_FS_FSTAT) { + path = njs_fs_path(vm, path_buf, njs_arg(args, nargs, 1), "path"); + if (njs_slow_path(path == NULL)) { + return NJS_ERROR; + } + + options = njs_arg(args, nargs, 2); + + } else { + fd_offset = !!(calltype == NJS_FS_DIRECT); + ret = njs_value_to_integer(vm, njs_argument(args, fd_offset), &fd); + if (njs_slow_path(ret != NJS_OK)) { + return ret; + } + + options = njs_arg(args, nargs, fd_offset + 1); } 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)); @@ -1619,7 +2080,21 @@ njs_fs_stat(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, } } - ret = ((magic >> 2) == NJS_FS_STAT) ? stat(path, &sb) : lstat(path, &sb); + switch (magic >> 2) { + case NJS_FS_STAT: + ret = stat(path, &sb); + break; + + case NJS_FS_LSTAT: + ret = lstat(path, &sb); + break; + + case NJS_FS_FSTAT: + default: + ret = fstat(fd, &sb); + break; + } + if (njs_slow_path(ret != 0)) { if (errno != ENOENT || throw) { ret = njs_fs_error(vm, @@ -1740,6 +2215,151 @@ njs_fs_unlink(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, static njs_int_t njs_fs_write(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, + njs_index_t calltype) +{ + int64_t fd, length, pos, offset; + ssize_t n; + njs_int_t ret; + njs_str_t data; + njs_uint_t fd_offset; + njs_value_t retval, *buffer, *value; + const njs_buffer_encoding_t *encoding; + + fd_offset = !!(calltype == NJS_FS_DIRECT); + + ret = njs_value_to_integer(vm, njs_arg(args, nargs, fd_offset), &fd); + if (njs_slow_path(ret != NJS_OK)) { + return ret; + } + + buffer = njs_arg(args, nargs, fd_offset + 1); + + pos = -1; + encoding = NULL; + + /* + * fs.writeSync(fd, string[, position[, encoding]]) + * fh.write(string[, position[, encoding]]) + */ + + if (njs_is_string(buffer)) { + value = njs_arg(args, nargs, fd_offset + 2); + + if (!njs_is_null_or_undefined(value)) { + ret = njs_value_to_integer(vm, value, &pos); + if (njs_slow_path(ret != NJS_OK)) { + return ret; + } + } + + encoding = njs_buffer_encoding(vm, njs_arg(args, nargs, fd_offset + 3)); + if (njs_slow_path(encoding == NULL)) { + return NJS_ERROR; + } + + ret = njs_buffer_decode_string(vm, buffer, &retval, encoding); + if (njs_slow_path(ret != NJS_OK)) { + return NJS_ERROR; + } + + njs_string_get(&retval, &data); + + goto process; + } + + /* + * fh.write(buffer, offset[, length[, position]]) + * fs.writeSync(fd, buffer, offset[, length[, position]]) + */ + + ret = njs_vm_value_to_bytes(vm, &data, buffer); + if (njs_slow_path(ret != NJS_OK)) { + return NJS_ERROR; + } + + ret = njs_value_to_integer(vm, njs_arg(args, nargs, fd_offset + 2), + &offset); + if (njs_slow_path(ret != NJS_OK)) { + return ret; + } + + if (njs_slow_path(offset < 0 || (size_t) offset > data.length)) { + njs_range_error(vm, "offset is out of range (must be <= %z)", + data.length); + return NJS_ERROR; + } + + data.length -= offset; + data.start += offset; + + value = njs_arg(args, nargs, fd_offset + 3); + + if (njs_is_defined(value)) { + ret = njs_value_to_integer(vm, value, &length); + if (njs_slow_path(ret != NJS_OK)) { + return ret; + } + + if (njs_slow_path(length < 0 || (size_t) length > data.length)) { + njs_range_error(vm, "length is out of range (must be <= %z)", + data.length); + return NJS_ERROR; + } + + data.length = length; + } + + value = njs_arg(args, nargs, fd_offset + 4); + + if (!njs_is_null_or_undefined(value)) { + ret = njs_value_to_integer(vm, value, &pos); + if (njs_slow_path(ret != NJS_OK)) { + return ret; + } + } + +process: + + if (pos == -1) { + n = write(fd, data.start, data.length); + + } else { + n = pwrite(fd, data.start, data.length, pos); + } + + if (njs_slow_path(n == -1)) { + ret = njs_fs_error(vm, "write", strerror(errno), NULL, errno, &retval); + goto done; + } + + if (njs_slow_path((size_t) n != data.length)) { + ret = njs_fs_error(vm, "write", "failed to write all the data", NULL, + 0, &retval); + goto done; + } + + if (calltype == NJS_FS_PROMISE) { + ret = njs_fs_bytes_written_create(vm, n, buffer, &retval); + if (njs_slow_path(ret != NJS_OK)) { + goto done; + } + + } else { + njs_value_number_set(&retval, n); + } + +done: + + if (ret == NJS_OK) { + return njs_fs_result(vm, &retval, calltype, NULL, 1); + } + + return NJS_ERROR; +} + + +static njs_int_t +njs_fs_write_file(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, njs_index_t magic) { int fd, flags; @@ -2886,11 +3506,146 @@ njs_fs_stats_prop(njs_vm_t *vm, njs_object_prop_t *prop, njs_value_t *value, } +static njs_int_t +njs_fs_filehandle_close(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, + njs_index_t unused) +{ + njs_value_t retval; + njs_filehandle_t *fh; + + fh = njs_vm_external(vm, njs_fs_filehandle_proto_id, njs_argument(args, 0)); + if (njs_slow_path(fh == NULL)) { + njs_type_error(vm, "\"this\" is not a filehandle object"); + return NJS_ERROR; + } + + if (njs_slow_path(fh->fd == -1)) { + njs_type_error(vm, "file was already closed"); + return NJS_ERROR; + } + + (void) close(fh->fd); + fh->fd = -1; + + njs_set_undefined(&retval); + + return njs_fs_result(vm, &retval, NJS_FS_PROMISE, NULL, 1); +} + + +static njs_int_t +njs_fs_filehandle_value_of(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, + njs_index_t unused) +{ + njs_filehandle_t *fh; + + fh = njs_vm_external(vm, njs_fs_filehandle_proto_id, njs_argument(args, 0)); + if (njs_slow_path(fh == NULL)) { + njs_type_error(vm, "\"this\" is not a filehandle object"); + return NJS_ERROR; + } + + njs_set_number(njs_vm_retval(vm), fh->fd); + + return NJS_OK; +} + + +static void +njs_fs_filehandle_cleanup(void *data) +{ + njs_filehandle_t *fh = data; + + if (fh->vm != NULL && fh->fd != -1) { + njs_vm_warn(fh->vm, "closing file description %d on cleanup\n", fh->fd); + (void) close(fh->fd); + } +} + + +static njs_int_t +njs_fs_filehandle_create(njs_vm_t *vm, int fd, njs_bool_t shadow, + njs_value_t *retval) +{ + njs_filehandle_t *fh; + njs_mp_cleanup_t *cln; + + fh = njs_mp_alloc(vm->mem_pool, sizeof(njs_filehandle_t)); + if (njs_slow_path(fh == NULL)) { + njs_memory_error(vm); + return NJS_ERROR; + } + + fh->fd = fd; + fh->vm = !shadow ? vm : NULL; + + cln = njs_mp_cleanup_add(njs_vm_memory_pool(vm), 0); + if (cln == NULL) { + njs_memory_error(vm); + return NJS_ERROR; + } + + cln->handler = njs_fs_filehandle_cleanup; + cln->data = fh; + + return njs_vm_external_create(vm, retval, njs_fs_filehandle_proto_id, + fh, 0); +} + + +static njs_int_t +njs_fs_bytes_read_create(njs_vm_t *vm, int bytes, njs_value_t *buffer, + njs_value_t *retval) +{ + njs_bytes_struct_t *bs; + + bs = njs_mp_alloc(vm->mem_pool, sizeof(njs_bytes_struct_t)); + if (njs_slow_path(bs == NULL)) { + njs_memory_error(vm); + return NJS_ERROR; + } + + bs->bytes = bytes; + njs_value_assign(&bs->buffer, buffer); + + return njs_vm_external_create(vm, retval, njs_fs_bytes_read_proto_id, + bs, 0); +} + + +static njs_int_t +njs_fs_bytes_written_create(njs_vm_t *vm, int bytes, njs_value_t *buffer, + njs_value_t *retval) +{ + njs_bytes_struct_t *bs; + + bs = njs_mp_alloc(vm->mem_pool, sizeof(njs_bytes_struct_t)); + if (njs_slow_path(bs == NULL)) { + njs_memory_error(vm); + return NJS_ERROR; + } + + bs->bytes = bytes; + njs_value_assign(&bs->buffer, buffer); + + return njs_vm_external_create(vm, retval, njs_fs_bytes_written_proto_id, + bs, 0); +} + + static const njs_object_prop_t njs_fs_promises_properties[] = { { .type = NJS_PROPERTY, .name = njs_string("readFile"), + .value = njs_native_function2(njs_fs_read_file, 0, NJS_FS_PROMISE), + .writable = 1, + .configurable = 1, + }, + + { + .type = NJS_PROPERTY, + .name = njs_string("readSync"), .value = njs_native_function2(njs_fs_read, 0, NJS_FS_PROMISE), .writable = 1, .configurable = 1, @@ -2899,7 +3654,7 @@ static const njs_object_prop_t njs_fs_promises_properties[] = { .type = NJS_PROPERTY, .name = njs_string("appendFile"), - .value = njs_native_function2(njs_fs_write, 0, + .value = njs_native_function2(njs_fs_write_file, 0, njs_fs_magic(NJS_FS_PROMISE, NJS_FS_APPEND)), .writable = 1, .configurable = 1, @@ -2908,7 +3663,7 @@ static const njs_object_prop_t njs_fs_promises_properties[] = { .type = NJS_PROPERTY, .name = njs_string("writeFile"), - .value = njs_native_function2(njs_fs_write, 0, + .value = njs_native_function2(njs_fs_write_file, 0, njs_fs_magic(NJS_FS_PROMISE, NJS_FS_TRUNC)), .writable = 1, .configurable = 1, @@ -2930,6 +3685,22 @@ static const njs_object_prop_t njs_fs_promises_properties[] = .configurable = 1, }, + { + .type = NJS_PROPERTY, + .name = njs_string("open"), + .value = njs_native_function2(njs_fs_open, 0, NJS_FS_PROMISE), + .writable = 1, + .configurable = 1, + }, + + { + .type = NJS_PROPERTY, + .name = njs_string("close"), + .value = njs_native_function2(njs_fs_close, 0, NJS_FS_PROMISE), + .writable = 1, + .configurable = 1, + }, + { .type = NJS_PROPERTY, .name = njs_string("rename"), @@ -2954,6 +3725,15 @@ static const njs_object_prop_t njs_fs_promises_properties[] = .configurable = 1, }, + { + .type = NJS_PROPERTY, + .name = njs_string("fstat"), + .value = njs_native_function2(njs_fs_stat, 0, + njs_fs_magic(NJS_FS_PROMISE, NJS_FS_FSTAT)), + .writable = 1, + .configurable = 1, + }, + { .type = NJS_PROPERTY, .name = njs_string("lstat"), @@ -3079,6 +3859,27 @@ njs_fs_init(njs_vm_t *vm) return NJS_ERROR; } + njs_fs_filehandle_proto_id = njs_vm_external_prototype(vm, + njs_ext_filehandle, + njs_nitems(njs_ext_filehandle)); + if (njs_slow_path(njs_fs_filehandle_proto_id < 0)) { + return NJS_ERROR; + } + + njs_fs_bytes_read_proto_id = njs_vm_external_prototype(vm, + njs_ext_bytes_read, + njs_nitems(njs_ext_bytes_read)); + if (njs_slow_path(njs_fs_bytes_written_proto_id < 0)) { + return NJS_ERROR; + } + + njs_fs_bytes_written_proto_id = njs_vm_external_prototype(vm, + njs_ext_bytes_written, + njs_nitems(njs_ext_bytes_written)); + if (njs_slow_path(njs_fs_bytes_written_proto_id < 0)) { + return NJS_ERROR; + } + proto_id = njs_vm_external_prototype(vm, njs_ext_fs, njs_nitems(njs_ext_fs)); if (njs_slow_path(proto_id < 0)) { diff --git a/src/njs_buffer.c b/src/njs_buffer.c index 3df0d593..fcae9a38 100644 --- a/src/njs_buffer.c +++ b/src/njs_buffer.c @@ -618,7 +618,7 @@ njs_buffer_slot_internal(njs_vm_t *vm, njs_value_t *value) } -static njs_typed_array_t * +njs_typed_array_t * njs_buffer_slot(njs_vm_t *vm, njs_value_t *value, const char *name) { njs_typed_array_t *array; diff --git a/src/njs_buffer.h b/src/njs_buffer.h index ee42d94f..963c1fbd 100644 --- a/src/njs_buffer.h +++ b/src/njs_buffer.h @@ -21,6 +21,8 @@ typedef struct { } njs_buffer_encoding_t; +njs_typed_array_t *njs_buffer_slot(njs_vm_t *vm, njs_value_t *value, + const char *name); njs_int_t njs_buffer_set(njs_vm_t *vm, njs_value_t *value, const u_char *start, uint32_t size); njs_int_t njs_buffer_new(njs_vm_t *vm, njs_value_t *value, const u_char *start, diff --git a/test/fs/methods.t.js b/test/fs/methods.t.js index 54a8fb60..291db162 100644 --- a/test/fs/methods.t.js +++ b/test/fs/methods.t.js @@ -6,12 +6,14 @@ flags: [async] function p(args, default_opts) { let params = Object.assign({}, default_opts, args); - let fname = params.args[0]; + if (params.args) { + let fname = params.args[0]; - if (fname[0] == '@') { - let gen = `${test_dir}/fs_test_${Math.round(Math.random() * 1000000)}`; - params.args = params.args.map(v => v); - params.args[0] = gen + fname.slice(1); + if (fname[0] == '@') { + let gen = `${test_dir}/fs_test_${Math.round(Math.random() * 1000000)}`; + params.args = params.args.map(v => v); + params.args[0] = gen + fname.slice(1); + } } return params; @@ -54,7 +56,7 @@ async function method(name, params) { return data; } -async function read_test(params) { +async function readfile_test(params) { let data = await method("readFile", params).catch(e => ({error:e})); if (params.slice && !data.error) { @@ -88,7 +90,7 @@ async function read_test(params) { return 'SUCCESS'; } -let read_tests = () => [ +let readfile_tests = () => [ { args: ["test/fs/utf8"], expected: Buffer.from("αβZγ") }, { args: [Buffer.from("@test/fs/utf8").slice(1)], expected: Buffer.from("αβZγ") }, { args: ["test/fs/utf8", "utf8"], expected: "αβZγ" }, @@ -154,31 +156,31 @@ let read_tests = () => [ let readFile_tsuite = { name: "fs readFile", skip: () => (!has_fs() || !has_buffer()), - T: read_test, + T: readfile_test, prepare_args: p, opts: { type: "callback" }, - get tests() { return read_tests() }, + get tests() { return readfile_tests() }, }; let readFileSync_tsuite = { name: "fs readFileSync", skip: () => (!has_fs() || !has_buffer()), - T: read_test, + T: readfile_test, prepare_args: p, opts: { type: "sync" }, - get tests() { return read_tests() }, + get tests() { return readfile_tests() }, }; let readFileP_tsuite = { name: "fsp readFile", skip: () => (!has_fs() || !has_buffer()), - T: read_test, + T: readfile_test, prepare_args: p, opts: { type: "promise" }, - get tests() { return read_tests() }, + get tests() { return readfile_tests() }, }; -async function write_test(params) { +async function writefile_test(params) { let fname = params.args[0]; try { fs.unlinkSync(fname); } catch (e) {} @@ -208,7 +210,7 @@ async function write_test(params) { return 'SUCCESS'; } -let write_tests = () => [ +let writefile_tests = () => [ { args: ["@", Buffer.from(Buffer.alloc(4).fill(65).buffer, 1)], expected: Buffer.from("AAA") }, { args: ["@", Buffer.from("XYZ"), "utf8"], expected: Buffer.from("XYZ") }, @@ -243,28 +245,28 @@ let write_tests = () => [ let writeFile_tsuite = { name: "fs writeFile", skip: () => (!has_fs() || !has_buffer()), - T: write_test, + T: writefile_test, prepare_args: p, opts: { type: "callback" }, - get tests() { return write_tests() }, + get tests() { return writefile_tests() }, }; let writeFileSync_tsuite = { name: "fs writeFileSync", skip: () => (!has_fs() || !has_buffer()), - T: write_test, + T: writefile_test, prepare_args: p, opts: { type: "sync" }, - get tests() { return write_tests() }, + get tests() { return writefile_tests() }, }; let writeFileP_tsuite = { name: "fsp writeFile", skip: () => (!has_fs() || !has_buffer()), - T: write_test, + T: writefile_test, prepare_args: p, opts: { type: "promise" }, - get tests() { return write_tests() }, + get tests() { return writefile_tests() }, }; async function append_test(params) { @@ -401,14 +403,14 @@ let realpathP_tsuite = { get tests() { return realpath_tests() }, }; -async function stat_test(params) { +async function method_test(params) { if (params.init) { params.init(params); } - let stat = await method(params.method, params).catch(e => ({error:e})); + let ret = await method(params.method, params).catch(e => ({error:e})); - if (params.check && !params.check(stat, params)) { + if (params.check && !params.check(ret, params)) { throw Error(`${params.method} failed check`); } @@ -536,7 +538,7 @@ let stat_tests = () => [ let stat_tsuite = { name: "fs stat", skip: () => (!has_fs() || !has_fs_symbolic_link() || !has_buffer()), - T: stat_test, + T: method_test, prepare_args: p, opts: { type: "callback", method: "stat" }, get tests() { return stat_tests() }, @@ -545,7 +547,7 @@ let stat_tsuite = { let statSync_tsuite = { name: "fs statSync", skip: () => (!has_fs() || !has_fs_symbolic_link() || !has_buffer()), - T: stat_test, + T: method_test, prepare_args: p, opts: { type: "sync", method: "stat" }, get tests() { return stat_tests() }, @@ -554,7 +556,7 @@ let statSync_tsuite = { let statP_tsuite = { name: "fsp stat", skip: () => (!has_fs() || !has_fs_symbolic_link() || !has_buffer()), - T: stat_test, + T: method_test, prepare_args: p, opts: { type: "promise", method: "stat" }, get tests() { return stat_tests() }, @@ -563,7 +565,7 @@ let statP_tsuite = { let lstat_tsuite = { name: "fs lstat", skip: () => (!has_fs() || !has_fs_symbolic_link() || !has_buffer()), - T: stat_test, + T: method_test, prepare_args: p, opts: { type: "callback", method: "lstat" }, get tests() { return stat_tests() }, @@ -572,7 +574,7 @@ let lstat_tsuite = { let lstatSync_tsuite = { name: "fs lstatSync", skip: () => (!has_fs() || !has_fs_symbolic_link() || !has_buffer()), - T: stat_test, + T: method_test, prepare_args: p, opts: { type: "sync", method: "lstat" }, get tests() { return stat_tests() }, @@ -581,12 +583,566 @@ let lstatSync_tsuite = { let lstatP_tsuite = { name: "fsp lstat", skip: () => (!has_fs() || !has_fs_symbolic_link() || !has_buffer()), - T: stat_test, + T: method_test, prepare_args: p, opts: { type: "promise", method: "lstat" }, get tests() { return stat_tests() }, }; +let open_check = (fh, params) => { + if (params.type == 'promise') { + + try { + if (typeof fh.fd != 'number') { + throw Error(`filehandle.fd:${fh.fd} is not an instance of Number`); + } + + ['read', 'write', 'close', 'valueof'].every(v => { + if (typeof fh[v] != 'function') { + throw Error(`filehandle.close:${fh[v]} is not an instance of function`); + } + }); + + let mode = fs.fstatSync(fh.fd).mode & 0o777; + if (params.mode && params.mode != mode) { + throw Error(`opened mode ${mode} != ${params.mode}`); + } + + } finally { + fh.close(); + } + + } else { + + try { + if (typeof fh != 'number') { + throw Error(`fd:${fh} is not an instance of Number`); + } + + let mode = fs.fstatSync(fh).mode & 0o777; + if (params.mode && params.mode != mode) { + throw Error(`opened mode ${mode} != ${params.mode}`); + } + + } finally { + fs.closeSync(fh); + } + } + + return true; +}; + +let open_tests = () => [ + { + args: ["test/fs/ascii"], + check: open_check, + }, + + { + args: ["@", 'w', 0o600], + mode: 0o600, + check: open_check, + }, + + { + args: ["@", 'a', 0o700], + mode: 0o700, + check: open_check, + }, + + { + args: ["@", 'r'], + 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: ["/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; + }, + }, +]; + +let openSync_tsuite = { + name: "fs openSync", + skip: () => (!has_fs() || !has_buffer()), + T: method_test, + prepare_args: p, + opts: { type: "sync", method: "open" }, + get tests() { return open_tests() }, +}; + +let openP_tsuite = { + name: "fsp open", + skip: () => (!has_fs() || !has_buffer()), + T: method_test, + prepare_args: p, + opts: { type: "promise", method: "open" }, + get tests() { return open_tests() }, +}; + +let close_tests = () => [ + + { + args: [ fs.openSync("test/fs/ascii") ], + check: (undef, params) => undef === undefined, + }, + + { + args: [ (() => { let fd = fs.openSync("test/fs/ascii"); fs.closeSync(fd); return fd})() ], + check: (err, params) => { + let e = err.error; + + if (e.syscall != params.method) { + throw Error(`${e.syscall} unexpected syscall`); + } + + if (e.code != "EBADF") { + throw Error(`${e.code} unexpected code`); + } + + return true; + }, + }, +]; + +let closeSync_tsuite = { + name: "fs closeSync", + skip: () => (!has_fs() || !has_buffer()), + T: method_test, + prepare_args: p, + opts: { type: "sync", method: "close" }, + get tests() { return close_tests() }, +}; + +function read_test(params) { + let fd, err; + + let fn = `${test_dir}/fs_read_test_${Math.round(Math.random() * 1000000)}`; + let out = []; + + fs.writeFileSync(fn, params.content); + + try { + fd = fs.openSync(fn); + + let buffer = Buffer.alloc(4); + buffer.fill('#'); + + for (var i = 0; i < params.read.length; i++) { + let args = params.read[i].map(v => v); + args.unshift(buffer); + args.unshift(fd); + + let bytesRead = fs.readSync.apply(null, args); + + out.push([bytesRead, Buffer.from(buffer)]); + } + + } catch (e) { + if (!e.syscall && !params.check) { + throw e; + } + + err = e; + + } finally { + fs.closeSync(fd); + } + + if (!err && params.expected) { + let expected = params.expected; + + if (out.length != expected.length) { + throw Error(`unexpected readSync number of outputs ${out.length} != ${expected.length}`); + } + + for (var i = 0; i < expected.length; i++) { + if (expected[i][0] != out[i][0]) { + throw Error(`unexpected readSync bytesRead:${out[i][0]} != ${expected[i][0]}`); + } + + if (expected[i][1].compare(out[i][1]) != 0) { + throw Error(`unexpected readSync buffer:${out[i][1]} != ${expected[i][1]}`); + } + } + + } + + if (params.check && !params.check(err, params)) { + throw Error(`${params.method} failed check`); + } + + return 'SUCCESS'; +} + +async function readFh_test(params) { + let fh, err; + + let fn = `${test_dir}/fs_read_test_${Math.round(Math.random() * 1000000)}`; + let out = []; + + fs.writeFileSync(fn, params.content); + + try { + fh = await fs.promises.open(fn); + + let buffer = Buffer.alloc(4); + buffer.fill('#'); + + for (var i = 0; i < params.read.length; i++) { + let args = params.read[i].map(v => v); + args.unshift(buffer); + + let bs = await fh.read.apply(fh, args); + + out.push([bs.bytesRead, Buffer.from(bs.buffer)]); + } + + } catch (e) { + if (!e.syscall && !params.check) { + throw e; + } + + err = e; + + } finally { + await fh.close(); + } + + if (!err && params.expected) { + let expected = params.expected; + + if (out.length != expected.length) { + throw Error(`unexpected read number of outputs ${out.length} != ${expected.length}`); + } + + for (var i = 0; i < expected.length; i++) { + if (expected[i][0] != out[i][0]) { + throw Error(`unexpected read bytesRead:${out[i][0]} != ${expected[i][0]}`); + } + + if (expected[i][1].compare(out[i][1]) != 0) { + throw Error(`unexpected read buffer:${out[i][1]} != ${expected[i][1]}`); + } + } + + } + + if (params.check && !params.check(err, params)) { + throw Error(`${params.method} failed check`); + } + + return 'SUCCESS'; +} + +let read_tests = () => [ + + { + content: "ABC", + read: [ [0, 3], ], + expected: [ [3, Buffer.from("ABC#")], ], + }, + + { + content: "ABC", + read: [ [1, 2], ], + expected: [ [2, Buffer.from("#AB#")], ], + }, + + { + content: "ABC", + read: [ [1, 2, 1], ], + expected: [ [2, Buffer.from("#BC#")], ], + }, + + { + content: "__ABCDE", + read: [ + [0, 4], + [0, 4], + [2, 2, 0], + [0, 4, null], + ], + expected: [ + [4, Buffer.from("__AB")], + [3, Buffer.from("CDEB")], + [2, Buffer.from("CD__")], + [0, Buffer.from("CD__")], + ], + }, + + { + content: "ABC", + read: [ [0, 5], ], + check: (err, params) => { + if (err.name != "RangeError") { + throw Error(`${err.code} unexpected exception`); + } + + return true; + }, + }, + + { + content: "ABC", + read: [ [2, 3], ], + check: (err, params) => { + if (err.name != "RangeError") { + throw Error(`${err.code} unexpected exception`); + } + + return true; + }, + }, + +]; + +let readSync_tsuite = { + name: "fs readSync", + skip: () => (!has_fs() || !has_buffer()), + T: read_test, + prepare_args: p, + opts: {}, + get tests() { return read_tests() }, +}; + +let readFh_tsuite = { + name: "fh read", + skip: () => (!has_fs() || !has_buffer()), + T: readFh_test, + prepare_args: p, + opts: {}, + get tests() { return read_tests() }, +}; + +function write_test(params) { + let fd, err; + + try { + fd = fs.openSync.apply(null, params.args); + + for (var i = 0; i < params.write.length; i++) { + let args = params.write[i].map(v => v); + args.unshift(fd); + + let bytesWritten = fs.writeSync.apply(null, args); + + if (params.written && bytesWritten != params.written) { + throw Error(`bw.bytesWritten unexpected value:${bw.bytesWritten}`); + } + } + + } catch (e) { + if (!e.syscall && !params.check) { + throw e; + } + + err = e; + + } finally { + fs.closeSync(fd); + } + + if (!err && params.expected) { + let data = fs.readFileSync(params.args[0]); + + if (data.compare(params.expected) != 0) { + throw Error(`fh.write unexpected data:${data}`); + } + } + + if (params.check && !params.check(err, params)) { + throw Error(`${params.method} failed check`); + } + + return 'SUCCESS'; +} + +async function writeFh_test(params) { + let fh, err; + + try { + fh = await fs.promises.open.apply(null, params.args); + + for (var i = 0; i < params.write.length; i++) { + let bw = await fh.write.apply(fh, params.write[i]); + + if (params.written && bw.bytesWritten != params.written) { + throw Error(`bw.bytesWritten unexpected value:${bw.bytesWritten}`); + } + + if (params.buffer + && (typeof params.buffer == 'string' + && params.buffer != bw.buffer + || typeof params.buffer == 'object' + && params.buffer.compare(bw.buffer) != 0)) + { + throw Error(`bw.buffer unexpected value:${bw.buffer}`); + } + } + + } catch (e) { + if (!e.syscall && !params.check) { + throw e; + } + + err = e; + + } finally { + await fh.close(); + } + + if (!err && params.expected) { + let data = fs.readFileSync(params.args[0]); + + if (data.compare(params.expected) != 0) { + throw Error(`fh.write unexpected data:${data}`); + } + } + + if (params.check && !params.check(err, params)) { + throw Error(`${params.method} failed check`); + } + + return 'SUCCESS'; +} + +let write_tests = () => [ + + { + args: ["@", 'w'], + write: [ ["ABC", undefined], ["DE", null], ["F"], ], + expected: Buffer.from("ABCDEF"), + }, + + { + args: ["@", 'w'], + write: [ ["XXXXXX"], ["YYYY", 1], ["ZZ", 2], ], + expected: Buffer.from("XYZZYX"), + }, + + { + args: ["@", 'w'], + write: [ ["ABC", null, 'utf8'] ], + written: 3, + buffer: 'ABC', + expected: Buffer.from("ABC"), + }, + + { + args: ["test/fs/ascii"], + write: [ ["ABC"] ], + check: (err, params) => { + let e = err; + + if (e.syscall != 'write') { + throw Error(`${e.syscall} unexpected syscall`); + } + + if (e.code != "EBADF") { + throw Error(`${e.code} unexpected code`); + } + + return true; + }, + }, + + { + args: ["@", 'w'], + write: [ [Buffer.from("ABC"), 0, 3], + [Buffer.from("DE"), 0, 2, null], + [Buffer.from("F"), 0, 1], ], + expected: Buffer.from("ABCDEF"), + }, + + { + args: ["@", 'w'], + write: [ [Buffer.from("__XXXXXX"), 2], + [Buffer.from("__YYYY__"), 2, 4, 1], + [Buffer.from("ZZ"), 0, 2, 2], ], + expected: Buffer.from("XYZZYX"), + }, + + { + args: ["@", 'w'], + write: [ [Buffer.from("__ABC__"), 2, 3] ], + written: 3, + buffer: Buffer.from('__ABC__'), + expected: Buffer.from("ABC"), + }, + + { + args: ["@", 'w'], + write: [ [Buffer.from("__ABC__"), 7] ], + written: 0, + }, + + { + args: ["@", 'w'], + write: [ [Buffer.from("__ABC__"), 8] ], + check: (err, params) => { + if (err.name != "RangeError") { + throw Error(`${err.code} unexpected exception`); + } + + return true; + }, + }, + + { + args: ["@", 'w'], + write: [ [Buffer.from("__ABC__"), 7, 1] ], + check: (err, params) => { + if (err.name != "RangeError") { + throw Error(`${err.code} unexpected exception`); + } + + return true; + }, + }, +]; + +let writeSync_tsuite = { + name: "fs writeSync", + skip: () => (!has_fs() || !has_buffer()), + T: write_test, + prepare_args: p, + opts: {}, + get tests() { return write_tests() }, +}; + +let writeFh_tsuite = { + name: "fh write", + skip: () => (!has_fs() || !has_buffer()), + T: writeFh_test, + prepare_args: p, + opts: {}, + get tests() { return write_tests() }, +}; + run([ readFile_tsuite, readFileSync_tsuite, @@ -606,5 +1162,12 @@ run([ lstat_tsuite, lstatSync_tsuite, lstatP_tsuite, + openSync_tsuite, + openP_tsuite, + readSync_tsuite, + readFh_tsuite, + writeSync_tsuite, + writeFh_tsuite, + closeSync_tsuite, ]) .then($DONE, $DONE);