Adding support for Buffer objects in "fs" methods.

fs.writeFile(), fs.appendFile() and friends may accept an instance of
Buffer as an argument.

Also, fs.readFile() and friends now return an instance of Buffer instead of
Byte-string when encoding is not provided.

Added Buffer encoding for fs.readdir(), fs.realpath() and friends.
diff --git a/src/njs_fs.c b/src/njs_fs.c
index 158f092..1a20743 100644
--- a/src/njs_fs.c
+++ b/src/njs_fs.c
@@ -50,13 +50,6 @@
 } njs_fs_writemode_t;
 
 
-typedef enum {
-    NJS_FS_ENC_INVALID,
-    NJS_FS_ENC_NONE,
-    NJS_FS_ENC_UTF8,
-} njs_fs_encoding_t;
-
-
 typedef struct {
     njs_str_t   name;
     int         value;
@@ -92,19 +85,19 @@
 static njs_int_t njs_fs_result(njs_vm_t *vm, njs_value_t *result,
     njs_index_t calltype, const njs_value_t* callback, njs_uint_t nargs);
 
-static njs_int_t
-njs_file_tree_walk(const char *path, njs_file_tree_walk_cb_t cb, int fd_limit,
-    njs_ftw_flags_t flags);
+static njs_int_t njs_file_tree_walk(const char *path,
+    njs_file_tree_walk_cb_t cb, int fd_limit, njs_ftw_flags_t flags);
 
 static njs_int_t njs_fs_make_path(njs_vm_t *vm, const char *path, mode_t md,
     njs_bool_t recursive, njs_value_t *retval);
 static njs_int_t njs_fs_rmtree(njs_vm_t *vm, const char *path,
     njs_bool_t recursive, njs_value_t *retval);
 
+static njs_int_t njs_fs_path(njs_vm_t *vm, const char **dst,
+    const njs_value_t* src, const njs_str_t *prop_name);
 static int njs_fs_flags(njs_vm_t *vm, njs_value_t *value, int default_flags);
 static mode_t njs_fs_mode(njs_vm_t *vm, njs_value_t *value,
     mode_t default_mode);
-static njs_fs_encoding_t njs_fs_encoding(njs_vm_t *vm, njs_value_t *value);
 
 static njs_int_t njs_fs_add_event(njs_vm_t *vm, const njs_value_t *callback,
     const njs_value_t *args, njs_uint_t nargs);
@@ -135,44 +128,28 @@
 };
 
 
-njs_inline njs_int_t
-njs_fs_path_arg(njs_vm_t *vm, const char **dst, const njs_value_t* src,
-    const njs_str_t *prop_name)
-{
-    if (njs_slow_path(!njs_is_string(src))) {
-        njs_type_error(vm, "\"%V\" must be a string", prop_name);
-        return NJS_ERROR;
-    }
-
-    *dst = njs_string_to_c_string(vm, njs_value_arg(src));
-    if (njs_slow_path(*dst == NULL)) {
-        return NJS_ERROR;
-    }
-
-    return NJS_OK;
-}
+static const njs_value_t  string_flag = njs_string("flag");
+static const njs_value_t  string_mode = njs_string("mode");
+static const njs_value_t  string_buffer = njs_string("buffer");
+static const njs_value_t  string_encoding = njs_string("encoding");
+static const njs_value_t  string_recursive = njs_string("recursive");
 
 
 static njs_int_t
 njs_fs_read_file(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
     njs_index_t calltype)
 {
-    int                fd, flags;
-    u_char             *start;
-    size_t             size;
-    ssize_t            length;
-    njs_str_t          data;
-    njs_int_t          ret;
-    const char         *file_path;
-    njs_value_t        flag, encoding, retval, *callback, *options, *path;
-    struct stat        sb;
-    njs_fs_encoding_t  enc;
-
-    static const njs_value_t  string_flag = njs_string("flag");
-    static const njs_value_t  string_encoding = njs_string("encoding");
+    int                          fd, flags;
+    njs_str_t                    data;
+    njs_int_t                    ret;
+    const char                   *file_path;
+    njs_value_t                  flag, encode, retval, *callback, *options,
+                                 *path;
+    struct stat                  sb;
+    const njs_buffer_encoding_t  *encoding;
 
     path = njs_arg(args, nargs, 1);
-    ret = njs_fs_path_arg(vm, &file_path, path, &njs_str_value("path"));
+    ret = njs_fs_path(vm, &file_path, path, &njs_str_value("path"));
     if (njs_slow_path(ret != NJS_OK)) {
         return ret;
     }
@@ -194,11 +171,11 @@
     }
 
     njs_set_undefined(&flag);
-    njs_set_undefined(&encoding);
+    njs_set_undefined(&encode);
 
     switch (options->type) {
     case NJS_STRING:
-        encoding = *options;
+        encode = *options;
         break;
 
     case NJS_UNDEFINED:
@@ -219,7 +196,7 @@
         }
 
         ret = njs_value_property(vm, options, njs_value_arg(&string_encoding),
-                                 &encoding);
+                                 &encode);
         if (njs_slow_path(ret == NJS_ERROR)) {
             return ret;
         }
@@ -230,9 +207,12 @@
         return NJS_ERROR;
     }
 
-    enc = njs_fs_encoding(vm, &encoding);
-    if (njs_slow_path(enc == NJS_FS_ENC_INVALID)) {
-        return NJS_ERROR;
+    encoding = NULL;
+    if (njs_is_defined(&encode)) {
+        encoding = njs_buffer_encoding(vm, &encode);
+        if (njs_slow_path(encoding == NULL)) {
+            return NJS_ERROR;
+        }
     }
 
     fd = open(file_path, flags);
@@ -252,88 +232,25 @@
         goto done;
     }
 
-    if (enc == NJS_FS_ENC_UTF8) {
-        length = sb.st_size;
+    data.start = NULL;
+    data.length = sb.st_size;
 
-        if (length > NJS_STRING_MAP_STRIDE) {
-            /*
-             * At this point length is not known, in order to set it to
-             * the correct value after file is read, we need to ensure that
-             * offset_map is allocated by njs_string_alloc(). This can be
-             * achieved by making length != size.
-             */
-            length += 1;
+    ret = njs_fs_fd_read(vm, fd, &data);
+    if (njs_slow_path(ret != NJS_OK)) {
+        if (ret == NJS_DECLINED) {
+            ret = njs_fs_error(vm, "read", strerror(errno), path, errno,
+                               &retval);
         }
 
-    } else {
-        length = 0;
+        goto done;
     }
 
-    size = sb.st_size;
-
-    if (njs_fast_path(size != 0)) {
-        start = njs_string_alloc(vm, &retval, size, length);
-        if (njs_slow_path(start == NULL)) {
-            ret = NJS_ERROR;
-            goto done;
-        }
-
-        data.start = start;
-        data.length = size;
-
-        ret = njs_fs_fd_read(vm, fd, &data);
-        if (njs_slow_path(ret != NJS_OK)) {
-            if (ret == NJS_DECLINED) {
-                ret = njs_fs_error(vm, "read", strerror(errno), path, errno,
-                                   &retval);
-            }
-
-            goto done;
-        }
-
-        if (njs_slow_path(data.length < size)) {
-            /* Pseudo-files may return less data than declared by st_size. */
-            njs_string_truncate(&retval, data.length, length);
-        }
-
-        size = data.length;
-        start = data.start;
+    if (encoding == NULL) {
+        ret = njs_buffer_set(vm, &retval, data.start, data.length);
 
     } else {
-        /* size of the file is not known in advance. */
-
-        data.length = 0;
-
-        ret = njs_fs_fd_read(vm, fd, &data);
-        if (njs_slow_path(ret != NJS_OK)) {
-            if (ret == NJS_DECLINED) {
-                ret = njs_fs_error(vm, "read", strerror(errno), path, errno,
-                                   &retval);
-            }
-
-            goto done;
-        }
-
-        size = data.length;
-        start = data.start;
-
-        ret = njs_string_new(vm, &retval, start, size, length);
-        if (njs_slow_path(ret != NJS_OK)) {
-            goto done;
-        }
-    }
-
-    if (enc == NJS_FS_ENC_UTF8) {
-        length = njs_utf8_length(start, size);
-
-        if (length >= 0) {
-            njs_string_length_set(&retval, length);
-
-        } else {
-            ret = njs_fs_error(vm, NULL, "Non-UTF8 file, convertion "
-                               "is not implemented", path, 0, &retval);
-            goto done;
-        }
+        ret = encoding->encode(vm, &retval, &data);
+        njs_mp_free(vm->mem_pool, data.start);
     }
 
 done:
@@ -354,34 +271,26 @@
 njs_fs_write_file(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
     njs_index_t magic)
 {
-    int                fd, flags;
-    u_char             *p, *end;
-    mode_t             md;
-    ssize_t            n;
-    njs_str_t          content;
-    njs_int_t          ret;
-    const char         *file_path;
-    njs_value_t        flag, mode, encoding, retval,
-                       *path, *data, *callback, *options;
-    njs_fs_encoding_t  enc;
-    njs_fs_calltype_t  calltype;
-
-    static const njs_value_t  string_flag = njs_string("flag");
-    static const njs_value_t  string_mode = njs_string("mode");
-    static const njs_value_t  string_encoding = njs_string("encoding");
+    int                          fd, flags;
+    u_char                       *p, *end;
+    mode_t                       md;
+    ssize_t                      n;
+    njs_str_t                    content;
+    njs_int_t                    ret;
+    const char                   *file_path;
+    njs_value_t                  flag, mode, encode, retval, *path, *data,
+                                 *callback, *options;
+    njs_typed_array_t            *array;
+    njs_fs_calltype_t            calltype;
+    njs_array_buffer_t           *buffer;
+    const njs_buffer_encoding_t  *encoding;
 
     path = njs_arg(args, nargs, 1);
-    ret = njs_fs_path_arg(vm, &file_path, path, &njs_str_value("path"));
+    ret = njs_fs_path(vm, &file_path, path, &njs_str_value("path"));
     if (njs_slow_path(ret != NJS_OK)) {
         return ret;
     }
 
-    data = njs_arg(args, nargs, 2);
-    if (njs_slow_path(!njs_is_string(data))) {
-        njs_type_error(vm, "\"data\" must be a string");
-        return NJS_ERROR;
-    }
-
     callback = NULL;
     calltype = magic & 3;
     options = njs_arg(args, nargs, 3);
@@ -400,11 +309,11 @@
 
     njs_set_undefined(&flag);
     njs_set_undefined(&mode);
-    njs_set_undefined(&encoding);
+    njs_set_undefined(&encode);
 
     switch (options->type) {
     case NJS_STRING:
-        encoding = *options;
+        encode = *options;
         break;
 
     case NJS_UNDEFINED:
@@ -431,12 +340,49 @@
         }
 
         ret = njs_value_property(vm, options, njs_value_arg(&string_encoding),
-                                 &encoding);
+                                 &encode);
         if (njs_slow_path(ret == NJS_ERROR)) {
             return ret;
         }
     }
 
+    data = njs_arg(args, nargs, 2);
+
+    switch (data->type) {
+    case NJS_TYPED_ARRAY:
+    case NJS_DATA_VIEW:
+        array = njs_typed_array(data);
+        buffer = array->buffer;
+        if (njs_slow_path(njs_is_detached_buffer(buffer))) {
+            njs_type_error(vm, "detached buffer");
+            return NJS_ERROR;
+        }
+
+        content.start = &buffer->u.u8[array->offset];
+        content.length = array->byte_length;
+        break;
+
+    case NJS_STRING:
+    default:
+        encoding = njs_buffer_encoding(vm, &encode);
+        if (njs_slow_path(encoding == NULL)) {
+            return NJS_ERROR;
+        }
+
+        ret = njs_value_to_string(vm, &retval, data);
+        if (njs_slow_path(ret != NJS_OK)) {
+            return NJS_ERROR;
+        }
+
+        ret = njs_buffer_decode_string(vm, &retval, &retval, encoding);
+        if (njs_slow_path(ret != NJS_OK)) {
+            return NJS_ERROR;
+        }
+
+        njs_string_get(&retval, &content);
+        break;
+    }
+
     flags = njs_fs_flags(vm, &flag, O_CREAT | O_WRONLY);
     if (njs_slow_path(flags == -1)) {
         return NJS_ERROR;
@@ -449,19 +395,12 @@
         return NJS_ERROR;
     }
 
-    enc = njs_fs_encoding(vm, &encoding);
-    if (njs_slow_path(enc == NJS_FS_ENC_INVALID)) {
-        return NJS_ERROR;
-    }
-
     fd = open(file_path, flags, md);
     if (njs_slow_path(fd < 0)) {
         ret = njs_fs_error(vm, "open", strerror(errno), path, errno, &retval);
         goto done;
     }
 
-    njs_string_get(data, &content);
-
     p = content.start;
     end = p + content.length;
 
@@ -515,13 +454,13 @@
         }
     }
 
-    ret = njs_fs_path_arg(vm, &old_path, njs_arg(args, nargs, 1),
+    ret = njs_fs_path(vm, &old_path, njs_arg(args, nargs, 1),
                           &njs_str_value("oldPath"));
     if (njs_slow_path(ret != NJS_OK)) {
         return ret;
     }
 
-    ret = njs_fs_path_arg(vm, &new_path, njs_arg(args, nargs, 2),
+    ret = njs_fs_path(vm, &new_path, njs_arg(args, nargs, 2),
                           &njs_str_value("newPath"));
     if (njs_slow_path(ret != NJS_OK)) {
         return ret;
@@ -552,7 +491,7 @@
     njs_value_t  retval, *path, *callback, *mode;
 
     path = njs_arg(args, nargs, 1);
-    ret = njs_fs_path_arg(vm, &file_path, path, &njs_str_value("path"));
+    ret = njs_fs_path(vm, &file_path, path, &njs_str_value("path"));
     if (njs_slow_path(ret != NJS_OK)) {
         return ret;
     }
@@ -610,13 +549,13 @@
     njs_value_t  retval, *target, *path, *callback, *type;
 
     target = njs_arg(args, nargs, 1);
-    ret = njs_fs_path_arg(vm, &target_path, target, &njs_str_value("target"));
+    ret = njs_fs_path(vm, &target_path, target, &njs_str_value("target"));
     if (njs_slow_path(ret != NJS_OK)) {
         return ret;
     }
 
     path = njs_arg(args, nargs, 2);
-    ret = njs_fs_path_arg(vm, &file_path, path, &njs_str_value("path"));
+    ret = njs_fs_path(vm, &file_path, path, &njs_str_value("path"));
     if (njs_slow_path(ret != NJS_OK)) {
         return ret;
     }
@@ -666,7 +605,7 @@
     njs_value_t  retval, *path, *callback;
 
     path = njs_arg(args, nargs, 1);
-    ret = njs_fs_path_arg(vm, &file_path, path, &njs_str_value("path"));
+    ret = njs_fs_path(vm, &file_path, path, &njs_str_value("path"));
     if (njs_slow_path(ret != NJS_OK)) {
         return ret;
     }
@@ -700,19 +639,15 @@
 njs_fs_realpath(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
     njs_index_t calltype)
 {
-    u_char             *resolved_path;
-    size_t             size;
-    ssize_t            length;
-    njs_int_t          ret;
-    const char         *file_path;
-    njs_value_t        encoding, retval, *path, *callback, *options;
-    njs_fs_encoding_t  enc;
-    char               path_buf[MAXPATHLEN];
-
-    static const njs_value_t  string_encoding = njs_string("encoding");
+    njs_int_t                    ret;
+    njs_str_t                    s;
+    const char                   *file_path;
+    njs_value_t                  encode, retval, *path, *callback, *options;
+    const njs_buffer_encoding_t  *encoding;
+    char                         path_buf[MAXPATHLEN];
 
     path = njs_arg(args, nargs, 1);
-    ret = njs_fs_path_arg(vm, &file_path, path, &njs_str_value("path"));
+    ret = njs_fs_path(vm, &file_path, path, &njs_str_value("path"));
     if (njs_slow_path(ret != NJS_OK)) {
         return ret;
     }
@@ -732,11 +667,11 @@
         }
     }
 
-    njs_set_undefined(&encoding);
+    njs_set_undefined(&encode);
 
     switch (options->type) {
     case NJS_STRING:
-        encoding = *options;
+        encode = *options;
         break;
 
     case NJS_UNDEFINED:
@@ -751,33 +686,34 @@
         }
 
         ret = njs_value_property(vm, options, njs_value_arg(&string_encoding),
-                                 &encoding);
+                                 &encode);
         if (njs_slow_path(ret == NJS_ERROR)) {
             return ret;
         }
     }
 
-    enc = njs_fs_encoding(vm, &encoding);
-    if (njs_slow_path(enc == NJS_FS_ENC_INVALID)) {
-        return NJS_ERROR;
+    encoding = NULL;
+    if (!njs_is_string(&encode) || !njs_string_eq(&encode, &string_buffer)) {
+        encoding = njs_buffer_encoding(vm, &encode);
+        if (njs_slow_path(encoding == NULL)) {
+            return NJS_ERROR;
+        }
     }
 
-    resolved_path = (u_char *) realpath(file_path, path_buf);
-    if (njs_slow_path(resolved_path == NULL)) {
+    s.start = (u_char *) realpath(file_path, path_buf);
+    if (njs_slow_path(s.start == NULL)) {
         ret = njs_fs_error(vm, "realpath", strerror(errno), path, errno,
                            &retval);
         goto done;
     }
 
-    size = njs_strlen(resolved_path);
-    length = njs_utf8_length(resolved_path, size);
-    if (njs_slow_path(length < 0)) {
-        length = 0;
-    }
+    s.length = njs_strlen(s.start);
 
-    ret = njs_string_new(vm, &retval, resolved_path, size, length);
-    if (njs_slow_path(ret != NJS_OK)) {
-        return NJS_ERROR;
+    if (encoding == NULL) {
+        ret = njs_buffer_new(vm, &retval, s.start, s.length);
+
+    } else {
+        ret = encoding->encode(vm, &retval, &s);
     }
 
 done:
@@ -799,11 +735,8 @@
     const char   *file_path;
     njs_value_t  mode, recursive, retval, *path, *callback, *options;
 
-    static const njs_value_t  string_mode = njs_string("mode");
-    static const njs_value_t  string_recursive = njs_string("recursive");
-
     path = njs_arg(args, nargs, 1);
-    ret = njs_fs_path_arg(vm, &file_path, path, &njs_str_value("path"));
+    ret = njs_fs_path(vm, &file_path, path, &njs_str_value("path"));
     if (njs_slow_path(ret != NJS_OK)) {
         return ret;
     }
@@ -878,10 +811,8 @@
     const char   *file_path;
     njs_value_t  recursive, retval, *path, *callback, *options;
 
-    static const njs_value_t  string_recursive = njs_string("recursive");
-
     path = njs_arg(args, nargs, 1);
-    ret = njs_fs_path_arg(vm, &file_path, path, &njs_str_value("path"));
+    ret = njs_fs_path(vm, &file_path, path, &njs_str_value("path"));
     if (njs_slow_path(ret != NJS_OK)) {
         return ret;
     }
@@ -935,23 +866,20 @@
 njs_fs_readdir(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
     njs_index_t calltype)
 {
-    DIR                *dir;
-    u_char             *d_name;
-    size_t             size;
-    ssize_t            length;
-    njs_int_t          ret;
-    const char         *dir_path;
-    njs_value_t        encoding, types, ename, etype, retval, *path, *callback,
-                       *options, *value;
-    njs_array_t        *results;
-    struct dirent      *entry;
-    njs_fs_encoding_t  enc;
+    DIR                          *dir;
+    njs_str_t                    s;
+    njs_int_t                    ret;
+    const char                   *dir_path;
+    njs_value_t                  encode, types, ename, etype, retval, *path,
+                                 *callback, *options, *value;
+    njs_array_t                  *results;
+    struct dirent                *entry;
+    const njs_buffer_encoding_t  *encoding;
 
-    static const njs_value_t  string_encoding = njs_string("encoding");
     static const njs_value_t  string_types = njs_string("withFileTypes");
 
     path = njs_arg(args, nargs, 1);
-    ret = njs_fs_path_arg(vm, &dir_path, path, &njs_str_value("path"));
+    ret = njs_fs_path(vm, &dir_path, path, &njs_str_value("path"));
     if (njs_slow_path(ret != NJS_OK)) {
         return ret;
     }
@@ -971,11 +899,11 @@
     }
 
     njs_set_false(&types);
-    njs_set_undefined(&encoding);
+    njs_set_undefined(&encode);
 
     switch (options->type) {
     case NJS_STRING:
-        encoding = *options;
+        encode = *options;
         break;
 
     case NJS_UNDEFINED:
@@ -990,7 +918,7 @@
         }
 
         ret = njs_value_property(vm, options, njs_value_arg(&string_encoding),
-                                 &encoding);
+                                 &encode);
         if (njs_slow_path(ret == NJS_ERROR)) {
             return ret;
         }
@@ -1002,9 +930,12 @@
         }
     }
 
-    enc = njs_fs_encoding(vm, &encoding);
-    if (njs_slow_path(enc == NJS_FS_ENC_INVALID)) {
-        return NJS_ERROR;
+    encoding = NULL;
+    if (!njs_is_string(&encode) || !njs_string_eq(&encode, &string_buffer)) {
+        encoding = njs_buffer_encoding(vm, &encode);
+        if (njs_slow_path(encoding == NULL)) {
+            return NJS_ERROR;
+        }
     }
 
     results = njs_array_alloc(vm, 1, 0, NJS_ARRAY_SPARE);
@@ -1033,41 +964,38 @@
             goto done;
         }
 
-        d_name = (u_char *) entry->d_name;
+        s.start = (u_char *) entry->d_name;
+        s.length = njs_strlen(s.start);
 
-        size = njs_strlen(d_name);
-        length = njs_utf8_length(d_name, size);
-        if (njs_slow_path(length < 0)) {
-            length = 0;
-        }
-
-        if ((length == 1 && d_name[0] == '.')
-            || (length == 2 && (d_name[0] == '.' && d_name[1] == '.')))
+        if ((s.length == 1 && s.start[0] == '.')
+            || (s.length == 2 && (s.start[0] == '.' && s.start[1] == '.')))
         {
             continue;
         }
 
-        if (njs_fast_path(!njs_is_true(&types))) {
-            ret = njs_array_string_add(vm, results, d_name, size, length);
-            if (njs_slow_path(ret != NJS_OK)) {
-                goto done;
-            }
-
-            continue;
-        }
-
-        ret = njs_string_new(vm, &ename, d_name, size, length);
-        if (njs_slow_path(ret != NJS_OK)) {
-            goto done;
-        }
-
-        njs_set_number(&etype, njs_dentry_type(entry));
-
         value = njs_array_push(vm, results);
         if (njs_slow_path(value == NULL)) {
             goto done;
         }
 
+        if (encoding == NULL) {
+            ret = njs_buffer_set(vm, &ename, s.start, s.length);
+
+        } else {
+            ret = encoding->encode(vm, &ename, &s);
+        }
+
+        if (njs_slow_path(ret != NJS_OK)) {
+            goto done;
+        }
+
+        if (njs_fast_path(!njs_is_true(&types))) {
+            *value = ename;
+            continue;
+        }
+
+        njs_set_number(&etype, njs_dentry_type(entry));
+
         ret = njs_fs_dirent_create(vm, &ename, &etype, value);
         if (njs_slow_path(ret != NJS_OK)) {
             goto done;
@@ -1099,12 +1027,12 @@
 
     if (size == 0) {
         size = 4096;
+    }
 
-        data->start = njs_mp_alloc(vm->mem_pool, size);
-        if (data->start == NULL) {
-            njs_memory_error(vm);
-            return NJS_ERROR;
-        }
+    data->start = njs_mp_alloc(vm->mem_pool, size);
+    if (data->start == NULL) {
+        njs_memory_error(vm);
+        return NJS_ERROR;
     }
 
     p = data->start;
@@ -1474,6 +1402,61 @@
 }
 
 
+static njs_int_t
+njs_fs_path(njs_vm_t *vm, const char **dst, const njs_value_t* src,
+    const njs_str_t *prop_name)
+{
+    u_char              *data, *p, *start;
+    njs_typed_array_t   *array;
+    njs_array_buffer_t  *buffer;
+
+    switch (src->type) {
+    case NJS_STRING:
+        *dst = njs_string_to_c_string(vm, njs_value_arg(src));
+        if (njs_slow_path(*dst == NULL)) {
+            return NJS_ERROR;
+        }
+
+        break;
+
+    case NJS_TYPED_ARRAY:
+    case NJS_DATA_VIEW:
+        array = njs_typed_array(src);
+        buffer = array->buffer;
+        if (njs_slow_path(njs_is_detached_buffer(buffer))) {
+            njs_type_error(vm, "detached buffer");
+            return NJS_ERROR;
+        }
+
+        start = &buffer->u.u8[array->offset];
+
+        if (njs_slow_path(memchr(start, '\0', array->byte_length) != 0)) {
+            njs_type_error(vm, "\"%V\" must be a Buffer without null bytes",
+                           prop_name);
+            return NJS_ERROR;
+        }
+
+        data = njs_mp_alloc(vm->mem_pool, array->byte_length + 1);
+        if (njs_slow_path(data == NULL)) {
+            njs_memory_error(vm);
+            return NJS_ERROR;
+        }
+
+        p = njs_cpymem(data, start, array->byte_length);
+        *p++ = '\0';
+
+        *dst = (char *) data;
+        break;
+
+    default:
+        njs_type_error(vm, "\"%V\" must be a string or Buffer", prop_name);
+        return NJS_ERROR;
+    }
+
+    return NJS_OK;
+}
+
+
 static int
 njs_fs_flags(njs_vm_t *vm, njs_value_t *value, int default_flags)
 {
@@ -1526,32 +1509,6 @@
 }
 
 
-static njs_fs_encoding_t
-njs_fs_encoding(njs_vm_t *vm, njs_value_t *value)
-{
-    njs_str_t  enc;
-    njs_int_t  ret;
-
-    if (njs_is_undefined(value)) {
-        return NJS_FS_ENC_NONE;
-    }
-
-    ret = njs_value_to_string(vm, value, value);
-    if (njs_slow_path(ret != NJS_OK)) {
-        return NJS_FS_ENC_INVALID;
-    }
-
-    njs_string_get(value, &enc);
-
-    if (enc.length != 4 || memcmp(enc.start, "utf8", 4) != 0) {
-        njs_type_error(vm, "Unknown encoding: \"%V\"", &enc);
-        return NJS_FS_ENC_INVALID;
-    }
-
-    return NJS_FS_ENC_UTF8;
-}
-
-
 static njs_int_t
 njs_fs_error(njs_vm_t *vm, const char *syscall, const char *description,
     njs_value_t *path, int errn, njs_value_t *retval)
diff --git a/src/test/njs_unit_test.c b/src/test/njs_unit_test.c
index c683814..661bf6a 100644
--- a/src/test/njs_unit_test.c
+++ b/src/test/njs_unit_test.c
@@ -17013,145 +17013,146 @@
     /* require('fs').readFile() */
 
     { njs_str("var fs = require('fs');"
-                 "fs.readFile()"),
-      njs_str("TypeError: \"path\" must be a string") },
+              "fs.readFile()"),
+      njs_str("TypeError: \"path\" must be a string or Buffer") },
 
     { njs_str("var fs = require('fs');"
-                 "fs.readFile('/njs_unknown_path')"),
+              "var path = Buffer.from('/broken'); path[3] = 0;"
+              "fs.readFile(path)"),
+      njs_str("TypeError: \"path\" must be a Buffer without null bytes") },
+
+    { njs_str("var fs = require('fs');"
+              "fs.readFile('/njs_unknown_path')"),
       njs_str("TypeError: \"callback\" must be a function") },
 
     { njs_str("var fs = require('fs');"
-                 "fs.readFile('/njs_unknown_path', 'utf8')"),
+              "fs.readFile('/njs_unknown_path', 'utf8')"),
       njs_str("TypeError: \"callback\" must be a function") },
 
     { njs_str("var fs = require('fs');"
-                 "fs.readFile('/njs_unknown_path', {flag:'xx'})"),
+              "fs.readFile('/njs_unknown_path', {flag:'xx'})"),
       njs_str("TypeError: \"callback\" must be a function") },
 
     { njs_str("var fs = require('fs');"
-                 "fs.readFile('/njs_unknown_path', {flag:'xx'}, 1)"),
+              "fs.readFile('/njs_unknown_path', {flag:'xx'}, 1)"),
       njs_str("TypeError: \"callback\" must be a function") },
 
     { njs_str("var fs = require('fs');"
-                 "fs.readFile('/njs_unknown_path', {flag:'xx'}, function () {})"),
+              "fs.readFile('/njs_unknown_path', {flag:'xx'}, function () {})"),
       njs_str("TypeError: Unknown file open flags: \"xx\"") },
 
     { njs_str("var fs = require('fs');"
-                 "fs.readFile('/njs_unknown_path', {encoding:'ascii'}, function () {})"),
-      njs_str("TypeError: Unknown encoding: \"ascii\"") },
+              "fs.readFile('/njs_unknown_path', {encoding:'ascii'}, function () {})"),
+      njs_str("TypeError: \"ascii\" encoding is not supported") },
 
     { njs_str("var fs = require('fs');"
-                 "fs.readFile('/njs_unknown_path', 'ascii', function () {})"),
-      njs_str("TypeError: Unknown encoding: \"ascii\"") },
+              "fs.readFile('/njs_unknown_path', 'ascii', function () {})"),
+      njs_str("TypeError: \"ascii\" encoding is not supported") },
 
     /* require('fs').readFileSync() */
 
     { njs_str("var fs = require('fs');"
-                 "fs.readFileSync()"),
-      njs_str("TypeError: \"path\" must be a string") },
+              "fs.readFileSync()"),
+      njs_str("TypeError: \"path\" must be a string or Buffer") },
 
     { njs_str("var fs = require('fs');"
-                 "fs.readFileSync({})"),
-      njs_str("TypeError: \"path\" must be a string") },
+              "fs.readFileSync({})"),
+      njs_str("TypeError: \"path\" must be a string or Buffer") },
 
     { njs_str("var fs = require('fs');"
-                 "fs.readFileSync('/njs_unknown_path', {flag:'xx'})"),
+              "fs.readFileSync('/njs_unknown_path', {flag:'xx'})"),
       njs_str("TypeError: Unknown file open flags: \"xx\"") },
 
     { njs_str("var fs = require('fs');"
-                 "fs.readFileSync('/njs_unknown_path', {encoding:'ascii'})"),
-      njs_str("TypeError: Unknown encoding: \"ascii\"") },
+              "fs.readFileSync(Buffer.from('/njs_unknown_path'), {encoding:'ascii'})"),
+      njs_str("TypeError: \"ascii\" encoding is not supported") },
 
     { njs_str("var fs = require('fs');"
-                 "fs.readFileSync('/njs_unknown_path', 'ascii')"),
-      njs_str("TypeError: Unknown encoding: \"ascii\"") },
+              "fs.readFileSync('/njs_unknown_path', 'ascii')"),
+      njs_str("TypeError: \"ascii\" encoding is not supported") },
 
     { njs_str("var fs = require('fs');"
-                 "fs.readFileSync('/njs_unknown_path', true)"),
+              "fs.readFileSync('/njs_unknown_path', true)"),
       njs_str("TypeError: Unknown options type: \"boolean\" (a string or object required)") },
 
 
     /* require('fs').writeFile() */
 
     { njs_str("var fs = require('fs');"
-                 "fs.writeFile()"),
-      njs_str("TypeError: \"path\" must be a string") },
+              "fs.writeFile()"),
+      njs_str("TypeError: \"path\" must be a string or Buffer") },
 
     { njs_str("var fs = require('fs');"
-                 "fs.writeFile({}, '', function () {})"),
-      njs_str("TypeError: \"path\" must be a string") },
+              "fs.writeFile({}, '', function () {})"),
+      njs_str("TypeError: \"path\" must be a string or Buffer") },
 
     { njs_str("var fs = require('fs');"
-                 "fs.writeFile('/njs_unknown_path')"),
-      njs_str("TypeError: \"data\" must be a string") },
-
-    { njs_str("var fs = require('fs');"
-                 "fs.writeFile('/njs_unknown_path', '')"),
+              "fs.writeFile('/njs_unknown_path')"),
       njs_str("TypeError: \"callback\" must be a function") },
 
     { njs_str("var fs = require('fs');"
-                 "fs.writeFile('/njs_unknown_path', '', undefined)"),
+              "fs.writeFile('/njs_unknown_path', '')"),
       njs_str("TypeError: \"callback\" must be a function") },
 
     { njs_str("var fs = require('fs');"
-                 "fs.writeFile('/njs_unknown_path', '', 'utf8')"),
+              "fs.writeFile('/njs_unknown_path', '', undefined)"),
       njs_str("TypeError: \"callback\" must be a function") },
 
     { njs_str("var fs = require('fs');"
-                 "fs.writeFile('/njs_unknown_path', '', {flag:'xx'}, function () {})"),
+              "fs.writeFile('/njs_unknown_path', '', 'utf8')"),
+      njs_str("TypeError: \"callback\" must be a function") },
+
+    { njs_str("var fs = require('fs');"
+              "fs.writeFile('/njs_unknown_path', '', {flag:'xx'}, function () {})"),
       njs_str("TypeError: Unknown file open flags: \"xx\"") },
 
     { njs_str("var fs = require('fs');"
-                 "fs.writeFile('/njs_unknown_path', '', {encoding:'ascii'}, function () {})"),
-      njs_str("TypeError: Unknown encoding: \"ascii\"") },
+              "fs.writeFile('/njs_unknown_path', '', {encoding:'ascii'}, function () {})"),
+      njs_str("TypeError: \"ascii\" encoding is not supported") },
 
     { njs_str("var fs = require('fs');"
-                 "fs.writeFile('/njs_unknown_path', '', 'ascii', function () {})"),
-      njs_str("TypeError: Unknown encoding: \"ascii\"") },
+              "fs.writeFile('/njs_unknown_path', '', 'ascii', function () {})"),
+      njs_str("TypeError: \"ascii\" encoding is not supported") },
 
     { njs_str("var fs = require('fs');"
-                 "fs.writeFile('/njs_unknown_path', '', true, function () {})"),
+              "fs.writeFile('/njs_unknown_path', '', true, function () {})"),
       njs_str("TypeError: Unknown options type: \"boolean\" (a string or object required)") },
 
     /* require('fs').writeFileSync() */
 
     { njs_str("var fs = require('fs');"
-                 "fs.writeFileSync()"),
-      njs_str("TypeError: \"path\" must be a string") },
+              "fs.writeFileSync()"),
+      njs_str("TypeError: \"path\" must be a string or Buffer") },
 
     { njs_str("var fs = require('fs');"
-                 "fs.writeFileSync('/njs_unknown_path')"),
-      njs_str("TypeError: \"data\" must be a string") },
+              "fs.writeFileSync({}, '')"),
+      njs_str("TypeError: \"path\" must be a string or Buffer") },
 
     { njs_str("var fs = require('fs');"
-                 "fs.writeFileSync({}, '')"),
-      njs_str("TypeError: \"path\" must be a string") },
-
-    { njs_str("var fs = require('fs');"
-                 "fs.writeFileSync('/njs_unknown_path', '', {flag:'xx'})"),
+              "fs.writeFileSync('/njs_unknown_path', '', {flag:'xx'})"),
       njs_str("TypeError: Unknown file open flags: \"xx\"") },
 
     { njs_str("var fs = require('fs');"
-                 "fs.writeFileSync('/njs_unknown_path', '', {encoding:'ascii'})"),
-      njs_str("TypeError: Unknown encoding: \"ascii\"") },
+              "fs.writeFileSync('/njs_unknown_path', '', {encoding:'ascii'})"),
+      njs_str("TypeError: \"ascii\" encoding is not supported") },
 
     { njs_str("var fs = require('fs');"
-                 "fs.writeFileSync('/njs_unknown_path', '', 'ascii')"),
-      njs_str("TypeError: Unknown encoding: \"ascii\"") },
+              "fs.writeFileSync('/njs_unknown_path', '', 'ascii')"),
+      njs_str("TypeError: \"ascii\" encoding is not supported") },
 
     { njs_str("var fs = require('fs');"
-                 "fs.writeFileSync('/njs_unknown_path', '', true)"),
+              "fs.writeFileSync('/njs_unknown_path', '', true)"),
       njs_str("TypeError: Unknown options type: \"boolean\" (a string or object required)") },
 
     /* require('fs').renameSync() */
 
     { njs_str("var fs = require('fs');"
               "fs.renameSync()"),
-      njs_str("TypeError: \"oldPath\" must be a string") },
+      njs_str("TypeError: \"oldPath\" must be a string or Buffer") },
 
     { njs_str("var fs = require('fs');"
               "fs.renameSync('/njs_unknown_path')"),
-      njs_str("TypeError: \"newPath\" must be a string") },
+      njs_str("TypeError: \"newPath\" must be a string or Buffer") },
 
     { njs_str("var fs = require('fs');"
               "[undefined, null, false, NaN, Symbol(), {}, Object('/njs_unknown_path')]"
@@ -17171,7 +17172,7 @@
 
     { njs_str("var fs = require('fs');"
               "fs.access()"),
-      njs_str("TypeError: \"path\" must be a string") },
+      njs_str("TypeError: \"path\" must be a string or Buffer") },
 
     { njs_str("var fs = require('fs');"
               "fs.access('/njs_unknown_path')"),
@@ -17189,7 +17190,7 @@
 
     { njs_str("var fs = require('fs');"
               "fs.accessSync()"),
-      njs_str("TypeError: \"path\" must be a string") },
+      njs_str("TypeError: \"path\" must be a string or Buffer") },
 
     { njs_str("var fs = require('fs');"
               "fs.accessSync('/njs_unknown_path', 'fail')"),
diff --git a/test/js/fs_appendFile.js b/test/js/fs_appendFile.js
new file mode 100644
index 0000000..7a6fa39
--- /dev/null
+++ b/test/js/fs_appendFile.js
@@ -0,0 +1,61 @@
+var fs = require('fs');
+var fname = './build/test/fs_appendFile';
+
+var argv = process.argv.slice(2);
+
+var data = (() => {
+    var value = argv[0];
+    var type = argv[1];
+    var offset = argv[2] ? parseInt(argv[2]) : 0;
+
+    switch (type) {
+    case 'Buffer':
+        return Buffer.from(Buffer.from(value).buffer, offset);
+    case 'DataView':
+        return new DataView(Buffer.from(value).buffer, offset);
+    case 'Object':
+        return {toString(){return value}};
+    case 'String':
+        return String(value);
+    case 'Symbol':
+        return Symbol(value);
+    case 'Uint8Array':
+        return new Uint8Array(Buffer.from(value).buffer, offset);
+    default:
+        throw new Error(`Unknown data type:${type}`);
+    }
+})();
+
+var options = (() => {
+    var encoding = argv[2];
+    var mode = argv[3] ? parseInt(argv[3].slice(2), 8) : 0;
+
+    if (encoding && mode) {
+        return {encoding, mode};
+
+    } else if (encoding) {
+        return encoding;
+    }
+
+    return undefined;
+})();
+
+try { fs.unlinkSync(fname); } catch (e) {}
+
+function done(e) {
+    if (e) {throw e};
+    var data = fs.readFileSync(fname);
+    console.log(String(data));
+}
+
+function append(cb) {
+    if (options) {
+        var path = Buffer.from(`@${fname}`).slice(1);
+        fs.appendFile(path, data, options, cb);
+
+    } else {
+        fs.appendFile(fname, data, cb);
+    }
+}
+
+append((e) => {if (e) {throw e}; append(done);})
diff --git a/test/js/fs_appendFileSync.js b/test/js/fs_appendFileSync.js
new file mode 100644
index 0000000..b6372b8
--- /dev/null
+++ b/test/js/fs_appendFileSync.js
@@ -0,0 +1,59 @@
+var fs = require('fs');
+var fname = './build/test/fs_appendFileSync';
+
+var argv = process.argv.slice(2);
+
+var data = (() => {
+    var value = argv[0];
+    var type = argv[1];
+    var offset = argv[2] ? parseInt(argv[2]) : 0;
+
+    switch (type) {
+    case 'Buffer':
+        return Buffer.from(Buffer.from(value).buffer, offset);
+    case 'DataView':
+        return new DataView(Buffer.from(value).buffer, offset);
+    case 'Object':
+        return {toString(){return value}};
+    case 'String':
+        return String(value);
+    case 'Symbol':
+        return Symbol(value);
+    case 'Uint8Array':
+        return new Uint8Array(Buffer.from(value).buffer, offset);
+    default:
+        throw new Error(`Unknown data type:${type}`);
+    }
+})();
+
+var options = (() => {
+    var encoding = argv[2];
+    var mode = argv[3] ? parseInt(argv[3].slice(2), 8) : 0;
+
+    if (encoding && mode) {
+        return {encoding, mode};
+
+    } else if (encoding) {
+        return encoding;
+    }
+
+    return undefined;
+})();
+
+function append() {
+    if (options) {
+        var path = Buffer.from(`@${fname}`).slice(1);
+        fs.appendFileSync(path, data, options);
+
+    } else {
+        fs.appendFileSync(fname, data);
+    }
+}
+
+try { fs.unlinkSync(fname); } catch (e) {}
+
+append();
+append();
+
+var ret = fs.readFileSync(fname);
+console.log(String(ret));
diff --git a/test/js/fs_promises_001.js b/test/js/fs_promises_001.js
index 477b254..22a1efc 100644
--- a/test/js/fs_promises_001.js
+++ b/test/js/fs_promises_001.js
@@ -45,11 +45,6 @@
     } catch (e) {
         console.log('error 2 ok', e instanceof TypeError)
     }
-    try {
-        return fs.writeFile(fname);
-    } catch (e) {
-        console.log('error 3 ok', e instanceof TypeError)
-    }
 })
 .then((data) => {
     console.log('errors ok');
diff --git a/test/js/fs_promises_007.js b/test/js/fs_promises_007.js
index 3dccf27..e02796c 100644
--- a/test/js/fs_promises_007.js
+++ b/test/js/fs_promises_007.js
@@ -96,6 +96,16 @@
             throw new Error('fs.readdirSync - error 6');
         }
 
+        var dir_buffer = fs.readdirSync(dname, {encoding:'buffer'});
+        if (dir_buffer.length != 3 || !(dir_buffer[0] instanceof Buffer)) {
+            throw new Error('fs.readdirSync - error 7');
+        }
+
+        var dir_buffer_types = fs.readdirSync(dname, {encoding:'buffer', withFileTypes: true});
+        if (dir_buffer_types.length != 3 || !(dir_buffer_types[0].name instanceof Buffer)) {
+            throw new Error('fs.readdirSync - error 8');
+        }
+
         resolve();
 
     } catch (e) {
@@ -171,7 +181,7 @@
     console.log('test fs.readdirSync');
 })
 .catch((e) => {
-    console.log('test fs.readdirSync failed', JSON.stringify(e));
+    console.log('test fs.readdirSync failed', e, JSON.stringify(e));
 })
 
 .then(testCallback)
@@ -179,7 +189,7 @@
     console.log('test fs.readdir');
 })
 .catch((e) => {
-    console.log('test fs.readdir failed', JSON.stringify(e));
+    console.log('test fs.readdir failed', e, JSON.stringify(e));
 })
 
 .then(() => {
@@ -227,5 +237,5 @@
     console.log('test fsp.readdir');
 })
 .catch((e) => {
-    console.log('test fsp.readdir failed', JSON.stringify(e));
+    console.log('test fsp.readdir failed', e, JSON.stringify(e));
 });
diff --git a/test/js/fs_readFile.js b/test/js/fs_readFile.js
new file mode 100644
index 0000000..c7a97ea
--- /dev/null
+++ b/test/js/fs_readFile.js
@@ -0,0 +1,40 @@
+var fs = require('fs');
+
+var argv = process.argv.slice(2);
+var fname = argv[0];
+
+var options = (() => {
+    var encoding = argv[1];
+    var flags = argv[2];
+
+    if (encoding && flags) {
+        return {encoding, flags};
+
+    } else if (encoding) {
+        return encoding;
+    }
+
+    return undefined;
+})();
+
+function type(v) {
+    if (v instanceof Buffer) {
+        return 'Buffer';
+    }
+
+    return typeof v;
+}
+
+function done(e, data) {
+    if (e) {console.log(JSON.stringify(e))};
+	console.log(String(data), type(data), data.length);
+}
+
+if (options) {
+	var path = Buffer.from(`@${fname}`).slice(1);
+	fs.readFile(path, options, done);
+
+} else {
+	fs.readFile(fname, done);
+}
+
diff --git a/test/js/fs_readFileSync.js b/test/js/fs_readFileSync.js
new file mode 100644
index 0000000..2e4d811
--- /dev/null
+++ b/test/js/fs_readFileSync.js
@@ -0,0 +1,43 @@
+var fs = require('fs');
+
+var argv = process.argv.slice(2);
+var fname = argv[0];
+
+var options = (() => {
+    var encoding = argv[1];
+    var flags = argv[2];
+
+    if (encoding && flags) {
+        return {encoding, flags};
+
+    } else if (encoding) {
+        return encoding;
+    }
+
+    return undefined;
+})();
+
+function type(v) {
+    if (v instanceof Buffer) {
+        return 'Buffer';
+    }
+
+    return typeof v;
+}
+
+var data;
+
+try {
+    if (options) {
+        var path = Buffer.from(`@${fname}`).slice(1);
+        data = fs.readFileSync(path, options);
+
+    } else {
+        data = fs.readFileSync(fname);
+    }
+
+} catch (e) {
+    console.log(JSON.stringify(e));
+}
+
+console.log(String(data), type(data), data.length);
diff --git a/test/js/fs_writeFile.js b/test/js/fs_writeFile.js
new file mode 100644
index 0000000..daa752b
--- /dev/null
+++ b/test/js/fs_writeFile.js
@@ -0,0 +1,57 @@
+var fs = require('fs');
+var fname = './build/test/fs_writeFile';
+
+var argv = process.argv.slice(2);
+
+var data = (() => {
+    var value = argv[0];
+    var type = argv[1];
+    var offset = argv[2] ? parseInt(argv[2]) : 0;
+
+    switch (type) {
+    case 'Buffer':
+        return Buffer.from(Buffer.from(value).buffer, offset);
+    case 'DataView':
+        return new DataView(Buffer.from(value).buffer, offset);
+    case 'Object':
+        return {toString(){return value}};
+    case 'String':
+        return String(value);
+    case 'Symbol':
+        return Symbol(value);
+    case 'Uint8Array':
+        return new Uint8Array(Buffer.from(value).buffer, offset);
+    default:
+        throw new Error(`Unknown data type:${type}`);
+    }
+})();
+
+var options = (() => {
+    var encoding = argv[2];
+    var mode = argv[3] ? parseInt(argv[3].slice(2), 8) : 0;
+
+    if (encoding && mode) {
+        return {encoding, mode};
+
+    } else if (encoding) {
+        return encoding;
+    }
+
+    return undefined;
+})();
+
+try { fs.unlinkSync(fname); } catch (e) {}
+
+function cb(e) {
+    if (e) {throw e};
+    var data = fs.readFileSync(fname);
+    console.log(String(data));
+}
+
+if (options) {
+    var path = Buffer.from(`@${fname}`).slice(1);
+    fs.writeFile(path, data, options, cb);
+
+} else {
+    fs.writeFile(fname, data, cb);
+}
diff --git a/test/js/fs_writeFileSync.js b/test/js/fs_writeFileSync.js
new file mode 100644
index 0000000..29621f2
--- /dev/null
+++ b/test/js/fs_writeFileSync.js
@@ -0,0 +1,54 @@
+var fs = require('fs');
+var fname = './build/test/fs_writeFileSync';
+
+var argv = process.argv.slice(2);
+
+var data = (() => {
+    var value = argv[0];
+    var type = argv[1];
+    var offset = argv[2] ? parseInt(argv[2]) : 0;
+
+    switch (type) {
+    case 'Buffer':
+        return Buffer.from(Buffer.from(value).buffer, offset);
+    case 'DataView':
+        return new DataView(Buffer.from(value).buffer, offset);
+    case 'Object':
+        return {toString(){return value}};
+    case 'String':
+        return String(value);
+    case 'Symbol':
+        return Symbol(value);
+    case 'Uint8Array':
+        return new Uint8Array(Buffer.from(value).buffer, offset);
+    default:
+        throw new Error(`Unknown data type:${type}`);
+    }
+})();
+
+var options = (() => {
+    var encoding = argv[2];
+    var mode = argv[3] ? parseInt(argv[3].slice(2), 8) : 0;
+
+    if (encoding && mode) {
+        return {encoding, mode};
+
+    } else if (encoding) {
+        return encoding;
+    }
+
+    return undefined;
+})();
+
+try { fs.unlinkSync(fname); } catch (e) {}
+
+if (options) {
+    var path = Buffer.from(`@${fname}`).slice(1);
+    fs.writeFileSync(path, data, options);
+
+} else {
+    fs.writeFileSync(fname, data);
+}
+
+var ret = fs.readFileSync(fname);
+console.log(String(ret));
diff --git a/test/njs_expect_test.exp b/test/njs_expect_test.exp
index e891dcf..f5b1ea6 100644
--- a/test/njs_expect_test.exp
+++ b/test/njs_expect_test.exp
@@ -461,55 +461,22 @@
 
 # require('fs').readFile()
 
-njs_test {
-    {"var fs = require('fs')\r\n"
-     "undefined\r\n>> "}
-    {"fs.readFile('test/fs/utf8', 'utf8', (...args) => void console.log(args.length))\r\n"
-     "undefined\r\n2\r\n>> "}
-}
+njs_run {"./test/js/fs_readFile.js" "test/fs/utf8"} "αβZγ Buffer 7"
+njs_run {"./test/js/fs_readFile.js" "test/fs/utf8" "utf8"} "αβZγ string 4"
+njs_run {"./test/js/fs_readFile.js" "test/fs/utf8" "utf8" "r+"} "αβZγ string 4"
+njs_run {"./test/js/fs_readFile.js" "test/fs/nonexistent"} \
+    "{\"errno\":2,\"code\":\"ENOENT\",\"path\":\"test/fs/nonexistent\",\"syscall\":\"open\"}"
+njs_run {"./test/js/fs_readFile.js" "test/fs/non_utf8" "utf8"} "�� string 2"
+njs_run {"./test/js/fs_readFile.js" "test/fs/non_utf8" "hex"} "8080 string 4"
+njs_run {"./test/js/fs_readFile.js" "test/fs/non_utf8" "base64"} "gIA= string 4"
 
 njs_test {
     {"var fs = require('fs')\r\n"
      "undefined\r\n>> "}
-    {"fs.readFile('test/fs/utf8', 'utf8', function (e, data) {console.log(data[2]+data.length)})\r\n"
-     "undefined\r\nZ4\r\n>> "}
-}
-
-njs_test {
-    {"var fs = require('fs')\r\n"
-     "undefined\r\n>> "}
-    {"fs.readFile('test/fs/utf8', function (e, data) {console.log(data[4]+data.length)})\r\n"
-     "undefined\r\nZ7\r\n>> "}
-}
-
-njs_test {
-    {"var fs = require('fs')\r\n"
-     "undefined\r\n>> "}
-    {"fs.readFile('test/fs/utf8', {encoding:'utf8',flag:'r+'}, function (e, data) {console.log(data)})\r\n"
-     "undefined\r\nαβZγ\r\n>> "}
-}
-
-njs_test {
-    {"var fs = require('fs')\r\n"
-     "undefined\r\n>> "}
-    {"fs.readFile('test/fs/ascii', function (e, data) {console.log(data[599])})\r\n"
-     "undefined\r\nx\r\n>> "}
-    {"fs.readFile('test/fs/ascii', {encoding:'utf8',flag:'r+'}, function (e, data) {console.log(data[599])})\r\n"
-     "undefined\r\nx\r\n>> "}
-}
-
-njs_test {
-    {"var fs = require('fs'); \r\n"
-     "undefined\r\n>> "}
-    {"fs.readFile('test/fs/nonexistent', 'utf8', function (e) {console.log(JSON.stringify(e))})\r\n"
-     "undefined\r\n{\"errno\":2,\"code\":\"ENOENT\",\"path\":\"test/fs/nonexistent\",\"syscall\":\"open\"}\r\n>> "}
-}
-
-njs_test {
-    {"var fs = require('fs'); \r\n"
-     "undefined\r\n>> "}
-    {"fs.readFile('test/fs/nonexistent', {encoding:'utf8', flag:'r+'}, function (e) {console.log(e)})\r\n"
-     "undefined\r\nError: No such file or directory\r\n>> "}
+    {"fs.readFile('test/fs/ascii', 'utf8', function (e, data) {console.log(data[599], data[600])})\r\n"
+     "undefined\r\nx undefined\r\n>> "}
+    {"fs.readFile('test/fs/ascii', {encoding:'utf8',flag:'r+'}, function (e, data) {console.log(data[599], data[600])})\r\n"
+     "undefined\r\nx undefined\r\n>> "}
 }
 
 njs_test {
@@ -523,60 +490,20 @@
 
 # require('fs').readFileSync()
 
-njs_test {
-    {"var fs = require('fs')\r\n"
-     "undefined\r\n>> "}
-    {"fs.readFileSync('test/fs/utf8').toString('base64')\r\n"
-     "'zrHOslrOsw=='\r\n>> "}
-}
+njs_run {"./test/js/fs_readFileSync.js" "test/fs/utf8"} "αβZγ Buffer 7"
+njs_run {"./test/js/fs_readFileSync.js" "test/fs/utf8" "utf8"} "αβZγ string 4"
+njs_run {"./test/js/fs_readFileSync.js" "test/fs/utf8" "utf8" "r+"} "αβZγ string 4"
+njs_run {"./test/js/fs_readFileSync.js" "test/fs/nonexistent"} \
+    "{\"errno\":2,\"code\":\"ENOENT\",\"path\":\"test/fs/nonexistent\",\"syscall\":\"open\"}"
+njs_run {"./test/js/fs_readFileSync.js" "test/fs/non_utf8" "utf8"} "�� string 2"
+njs_run {"./test/js/fs_readFileSync.js" "test/fs/non_utf8" "hex"} "8080 string 4"
+njs_run {"./test/js/fs_readFileSync.js" "test/fs/non_utf8" "base64"} "gIA= string 4"
 
 njs_test {
     {"var fs = require('fs')\r\n"
      "undefined\r\n>> "}
-    {"fs.readFileSync('test/fs/utf8', 'utf8')[2]\r\n"
-     "'Z'\r\n>> "}
-}
-
-njs_test {
-    {"var fs = require('fs')\r\n"
-     "undefined\r\n>> "}
-    {"fs.readFileSync('test/fs/utf8')[4]\r\n"
-     "'Z'\r\n>> "}
-}
-
-njs_test {
-    {"var fs = require('fs')\r\n"
-     "undefined\r\n>> "}
-    {"fs.readFileSync('test/fs/utf8', {encoding:'utf8',flag:'r+'})\r\n"
-     "'αβZγ'\r\n>> "}
-}
-
-njs_test {
-    {"var fs = require('fs'), fn = 'test/fs/ascii'\r\n"
-     "undefined\r\n>> "}
-    {"fs.readFileSync(fn)[599] + fs.readFileSync(fn, 'utf8')[599]\r\n"
-     "'xx'\r\n>> "}
-}
-
-njs_test {
-    {"var fs = require('fs'); \r\n"
-     "undefined\r\n>> "}
-    {"try { fs.readFileSync('test/fs/nonexistent')} catch (e) {console.log(JSON.stringify(e))}\r\n"
-     "{\"errno\":2,\"code\":\"ENOENT\",\"path\":\"test/fs/nonexistent\",\"syscall\":\"open\"}\r\nundefined\r\n>> "}
-}
-
-njs_test {
-    {"var fs = require('fs')\r\n"
-     "undefined\r\n>> "}
-    {"fs.readFileSync('test/fs/non_utf8').charCodeAt(1)\r\n"
-     "128"}
-}
-
-njs_test {
-    {"var fs = require('fs')\r\n"
-     "undefined\r\n>> "}
-    {"fs.readFileSync('test/fs/non_utf8', 'utf8')\r\n"
-     "Error: Non-UTF8 file, convertion is not implemented"}
+    {"fs.readFileSync('test/fs/non_utf8', 'utf8').charCodeAt(1)\r\n"
+     "65533"}
 }
 
 njs_test {
@@ -594,46 +521,19 @@
 
 # require('fs').writeFile()
 
-exec rm -fr ./build/test/file2
+njs_run {"./test/js/fs_writeFile.js" "ABCD" "Buffer" "1"} "BCD"
+njs_run {"./test/js/fs_writeFile.js" "ABC" "DataView"} "ABC"
+njs_run {"./test/js/fs_writeFile.js" "414243" "Object" "hex"} "ABC"
+njs_run {"./test/js/fs_writeFile.js" "ABC" "String"} "ABC"
+njs_run {"./test/js/fs_writeFile.js" "ABC" "Symbol"} "TypeError: Cannot convert a Symbol value to a string*"
+njs_run {"./test/js/fs_writeFile.js" "ABC" "Uint8Array"} "ABC"
 
-njs_test {
-    {"var fs = require('fs'), fn = './build/test/file2';\r\n"
-     "undefined\r\n>> "}
-    {"function h1(e) {if (e) {throw e}; console.log(fs.readFileSync(fn))}\r\n"
-     "undefined\r\n>> "}
-    {"fs.writeFile(fn, 'ABC', h1)\r\n"
-     "undefined\r\nABC\r\n>> "}
-}
-
-njs_test {
-    {"var fs = require('fs'), fn = './build/test/file2';\r\n"
-     "undefined\r\n>> "}
-    {"fs.writeFile(fn, 'ABC', (...args) => void console.log(args.length))\r\n"
-     "undefined\r\n1\r\n>> "}
-}
-
-njs_test {
-    {"var fs = require('fs'), fn = './build/test/file2';\r\n"
-     "undefined\r\n>> "}
-    {"fs.writeFile(fn, 'ABC', 'utf8', function (e) { if (e) {throw e}; console.log(fs.readFileSync(fn))})\r\n"
-     "undefined\r\nABC\r\n>> "}
-}
-
-njs_test {
-    {"var fs = require('fs'), fn = './build/test/file2';\r\n"
-     "undefined\r\n>> "}
-    {"fs.writeFile(fn, 'ABC', {encoding:'utf8', mode:0o666}, function (e) { if (e) {throw e}; console.log(fs.readFileSync(fn))})\r\n"
-     "undefined\r\nABC\r\n>> "}
-}
-
-exec rm -fr ./build/test/wo_file
-
-njs_test {
-    {"var fs = require('fs'), fn = './build/test/wo_file';\r\n"
-     "undefined\r\n>> "}
-    {"fs.writeFile(fn, 'ABC', {mode:0o222}, function (e) {console.log(fs.readFileSync(fn))})\r\n"
-     "Error: Permission denied"}
-}
+njs_run {"./test/js/fs_writeFile.js" "ABC" "String" "utf8"} "ABC"
+njs_run {"./test/js/fs_writeFile.js" "ABC" "String" "utf8" "0o666"} "ABC"
+njs_run {"./test/js/fs_writeFile.js" "ABC" "String" "utf8" "0o222"} "Error: Permission denied*"
+njs_run {"./test/js/fs_writeFile.js" "414243" "String" "hex"} "ABC"
+njs_run {"./test/js/fs_writeFile.js" "QUJD" "String" "base64"} "ABC"
+njs_run {"./test/js/fs_writeFile.js" "QUJD" "String" "base64url"} "ABC"
 
 njs_test {
     {"var fs = require('fs')\r\n"
@@ -644,25 +544,19 @@
 
 # require('fs').writeFileSync()
 
-exec rm -fr ./build/test/file2
+njs_run {"./test/js/fs_writeFileSync.js" "ABCD" "Buffer" "1"} "BCD"
+njs_run {"./test/js/fs_writeFileSync.js" "ABC" "DataView"} "ABC"
+njs_run {"./test/js/fs_writeFileSync.js" "414243" "Object" "hex"} "ABC"
+njs_run {"./test/js/fs_writeFileSync.js" "ABC" "String"} "ABC"
+njs_run {"./test/js/fs_writeFileSync.js" "ABC" "Symbol"} "TypeError: Cannot convert a Symbol value to a string*"
+njs_run {"./test/js/fs_writeFileSync.js" "ABC" "Uint8Array"} "ABC"
 
-njs_test {
-    {"var fs = require('fs'), fn = './build/test/file2';\r\n"
-     "undefined\r\n>> "}
-    {"fs.writeFileSync(fn, 'ABC')\r\n"
-     "undefined\r\n>> "}
-    {"fs.readFileSync(fn)\r\n"
-     "'ABC'\r\n>> "}
-}
-
-njs_test {
-    {"var fs = require('fs'), fn = './build/test/file2';\r\n"
-     "undefined\r\n>> "}
-    {"fs.writeFileSync(fn, 'ABC', 'utf8')\r\n"
-     "undefined\r\n>> "}
-    {"fs.readFileSync(fn)\r\n"
-     "'ABC'\r\n>> "}
-}
+njs_run {"./test/js/fs_writeFileSync.js" "ABC" "String" "utf8"} "ABC"
+njs_run {"./test/js/fs_writeFileSync.js" "ABC" "String" "utf8" "0o666"} "ABC"
+njs_run {"./test/js/fs_writeFileSync.js" "ABC" "String" "utf8" "0o222"} "Error: Permission denied*"
+njs_run {"./test/js/fs_writeFileSync.js" "78797a" "String" "hex"} "xyz"
+njs_run {"./test/js/fs_writeFileSync.js" "eHl6" "String" "base64"} "xyz"
+njs_run {"./test/js/fs_writeFileSync.js" "eHl6" "String" "base64url"} "xyz"
 
 njs_test {
     {"var fs = require('fs'), fn = './build/test/file2';\r\n"
@@ -671,57 +565,41 @@
      "undefined\r\n>> "}
     {"fs.writeFileSync(fn, 'ABC')\r\n"
      "undefined\r\n>> "}
-    {"fs.readFileSync(fn)\r\n"
+    {"fs.readFileSync(fn, 'utf8')\r\n"
      "'ABC'\r\n>> "}
 }
 
-njs_test {
-    {"var fs = require('fs'), fn = './build/test/file2';\r\n"
-     "undefined\r\n>> "}
-    {"fs.writeFileSync(fn, 'ABC', {encoding:'utf8', mode:0o666})\r\n"
-     "undefined\r\n>> "}
-    {"fs.readFileSync(fn)\r\n"
-     "'ABC'\r\n>> "}
-}
-
-exec rm -fr ./build/test/wo_file
-
-njs_test {
-    {"var fs = require('fs'), fn = './build/test/wo_file';\r\n"
-     "undefined\r\n>> "}
-    {"fs.writeFileSync(fn, 'ABC', {mode:0o222}); fs.readFileSync(fn)\r\n"
-     "Error: Permission denied"}
-}
-
 # require('fs').appendFile()
 
-exec rm -fr ./build/test/file2
+njs_run {"./test/js/fs_appendFile.js" "ABCD" "Buffer" "1"} "BCDBCD"
+njs_run {"./test/js/fs_appendFile.js" "ABC" "DataView"} "ABCABC"
+njs_run {"./test/js/fs_appendFile.js" "414243" "Object" "hex"} "ABCABC"
+njs_run {"./test/js/fs_appendFile.js" "ABC" "String"} "ABCABC"
+njs_run {"./test/js/fs_appendFile.js" "ABC" "Symbol"} "TypeError: Cannot convert a Symbol value to a string*"
+njs_run {"./test/js/fs_appendFile.js" "ABC" "Uint8Array"} "ABCABC"
 
-njs_test {
-    {"var fs = require('fs'), fn = './build/test/file2';\r\n"
-     "undefined\r\n>> "}
-    {"function h1(e) {console.log(fs.readFileSync(fn))}\r\n"
-     "undefined\r\n>> "}
-    {"function h2(e) {fs.appendFile(fn, 'ABC', h1)}\r\n"
-     "undefined\r\n>> "}
-    {"fs.appendFile(fn, 'ABC', h2)\r\n"
-     "undefined\r\nABCABC\r\n>> "}
-}
+njs_run {"./test/js/fs_appendFile.js" "ABC" "String" "utf8"} "ABC"
+njs_run {"./test/js/fs_appendFile.js" "ABC" "String" "utf8" "0o666"} "ABC"
+njs_run {"./test/js/fs_appendFile.js" "ABC" "String" "utf8" "0o222"} "Error: Permission denied*"
+njs_run {"./test/js/fs_appendFile.js" "414243" "String" "hex"} "ABC"
+njs_run {"./test/js/fs_appendFile.js" "QUJD" "String" "base64"} "ABC"
+njs_run {"./test/js/fs_appendFile.js" "QUJD" "String" "base64url"} "ABC"
 
 # require('fs').appendFileSync()
 
-exec rm -fr ./build/test/file2
+njs_run {"./test/js/fs_appendFileSync.js" "ABCD" "Buffer" "1"} "BCDBCD"
+njs_run {"./test/js/fs_appendFileSync.js" "ABC" "DataView"} "ABCABC"
+njs_run {"./test/js/fs_appendFileSync.js" "414243" "Object" "hex"} "ABCABC"
+njs_run {"./test/js/fs_appendFileSync.js" "ABC" "String"} "ABCABC"
+njs_run {"./test/js/fs_appendFileSync.js" "ABC" "Symbol"} "TypeError: Cannot convert a Symbol value to a string*"
+njs_run {"./test/js/fs_appendFileSync.js" "ABC" "Uint8Array"} "ABCABC"
 
-njs_test {
-    {"var fs = require('fs'), fn = './build/test/file2';\r\n"
-     "undefined\r\n>> "}
-    {"fs.appendFileSync(fn, 'ABC')\r\n"
-     "undefined\r\n>> "}
-    {"fs.appendFileSync(fn, 'ABC')\r\n"
-     "undefined\r\n>> "}
-    {"fs.readFileSync(fn)\r\n"
-     "'ABCABC'\r\n>> "}
-}
+njs_run {"./test/js/fs_appendFileSync.js" "ABC" "String" "utf8"} "ABC"
+njs_run {"./test/js/fs_appendFileSync.js" "ABC" "String" "utf8" "0o666"} "ABC"
+njs_run {"./test/js/fs_appendFileSync.js" "ABC" "String" "utf8" "0o222"} "Error: Permission denied*"
+njs_run {"./test/js/fs_appendFileSync.js" "414243" "String" "hex"} "ABC"
+njs_run {"./test/js/fs_appendFileSync.js" "QUJD" "String" "base64"} "ABC"
+njs_run {"./test/js/fs_appendFileSync.js" "QUJD" "String" "base64url"} "ABC"
 
 # require('fs').renameSync()
 
@@ -734,15 +612,28 @@
      "undefined\r\n>> "}
     {"fs.renameSync(fn1, fn2)\r\n"
      "undefined\r\n>> "}
-    {"fs.readFileSync(fn2)\r\n"
+    {"String(fs.readFileSync(fn2))\r\n"
      "'ABC'\r\n>> "}
 }
 
 njs_test {
+    {"var fs = require('fs'), fn = './build/test/file2'\r\n"
+     "undefined\r\n>> "}
+    {"fs.writeFileSync(fn, 'ABC')\r\n"
+     "undefined\r\n>> "}
+    {"fs.renameSync(fn, 'test/fs/')\r\n"
+     "Error: Not a directory*"}
+}
+
+# require('fs').realpathSync()
+
+njs_test {
     {"var fs = require('fs')\r\n"
      "undefined\r\n>> "}
-    {"fs.renameSync('build/test/file2', 'test/fs/')\r\n"
-     "Error: Not a directory*"}
+    {"fs.realpathSync('./build/test/..').endsWith('build')\r\n"
+     "true\r\n>> "}
+    {"fs.realpathSync('./build/test/..', {encoding:'buffer'}) instanceof Buffer\r\n"
+     "true\r\n>> "}
 }
 
 njs_run {"-c" "setTimeout(() => {console.log('A'.repeat(1024))}, 0); ref"} \
@@ -1110,7 +1001,6 @@
 chain ok true
 error 1 ok true
 error 2 ok true
-error 3 ok true
 errors ok"
 
 njs_run {"./test/js/fs_promises_002.js"} \