]> git.kaiwu.me - njs.git/commitdiff
Added fs.FileHandle.
authorDmitry Volyntsev <xeioex@nginx.com>
Mon, 22 Aug 2022 23:49:27 +0000 (16:49 -0700)
committerDmitry Volyntsev <xeioex@nginx.com>
Mon, 22 Aug 2022 23:49:27 +0000 (16:49 -0700)
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()

external/njs_fs_module.c
src/njs_buffer.c
src/njs_buffer.h
test/fs/methods.t.js

index 71a1d27e9fe37bd162f59f13a6639a7d52ec5283..68549b27d9e0ac90b9832c571f3359c0559bdcf2 100644 (file)
@@ -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)) {
index 3df0d5933f9fbeb3e538e4ddf339ba8920c67896..fcae9a38ae6168bf58663fb64ce58eb8ed21aa28 100644 (file)
@@ -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;
index ee42d94f2b96a73660005ede111f8dc1ea594c1d..963c1fbd4d536540dfb2922d36f919d0d88d3b21 100644 (file)
@@ -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,
index 54a8fb602a58c7fc0d409dca1ea8bc31b6aa9542..291db1627f699284af628cba3e44905f0c7252a0 100644 (file)
@@ -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);