Improved fs.mkdir() to support recursive directory creation.
diff --git a/src/njs_fs.c b/src/njs_fs.c
index fad0678..251b950 100644
--- a/src/njs_fs.c
+++ b/src/njs_fs.c
@@ -70,6 +70,9 @@
 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_fs_make_path(njs_vm_t *vm, const char *path, mode_t md,
+    njs_bool_t recursive, njs_value_t *retval);
+
 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);
@@ -828,18 +831,8 @@
         return NJS_ERROR;
     }
 
-    if (njs_is_true(&recursive)) {
-        njs_type_error(vm, "\"options.recursive\" is not supported");
-        return NJS_ERROR;
-    }
-
-    njs_set_undefined(&retval);
-
-    ret = mkdir(file_path, md);
-    if (njs_slow_path(ret != 0)) {
-        ret = njs_fs_error(vm, "mkdir", strerror(errno), path, errno,
+    ret = njs_fs_make_path(vm, file_path, md, njs_is_true(&recursive),
                            &retval);
-    }
 
     if (ret == NJS_OK) {
         return njs_fs_result(vm, &retval, calltype, callback, 1);
@@ -1138,6 +1131,103 @@
 }
 
 
+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)
+{
+    size_t       size;
+    ssize_t      length;
+    njs_int_t    ret;
+    const char   *p, *prev;
+    njs_value_t  value;
+    struct stat  sb;
+    char         path_buf[MAXPATHLEN];
+
+    njs_set_undefined(retval);
+
+    if (!recursive) {
+        ret = mkdir(path, md);
+        if (ret != 0) {
+            goto failed;
+        }
+
+        return NJS_OK;
+    }
+
+    ret = stat(path, &sb);
+    if (ret == 0) {
+        if (!S_ISDIR(sb.st_mode)) {
+            errno = ENOTDIR;
+            goto failed;
+        }
+
+        return NJS_OK;
+    }
+
+    if (errno != ENOENT) {
+        goto failed;
+    }
+
+    p = path;
+    prev = p;
+
+    for ( ;; ) {
+        p = strchr(prev + 1, '/');
+        if (p == NULL) {
+            break;
+        }
+
+        if (njs_slow_path((p - path) > MAXPATHLEN)) {
+            njs_internal_error(vm, "too large path");
+            return NJS_OK;
+        }
+
+        memcpy(&path_buf[prev - path], &path[prev - path], p - prev);
+        path_buf[p - path] = '\0';
+
+        ret = stat(path_buf, &sb);
+        if (ret == 0) {
+            if (!S_ISDIR(sb.st_mode)) {
+                errno = ENOTDIR;
+                goto failed;
+            }
+
+        } else {
+            ret = mkdir(path_buf, md);
+            if (ret != 0) {
+                goto failed;
+            }
+        }
+
+        path_buf[p - path] = '/';
+        prev = p;
+    }
+
+    ret = mkdir(path, md);
+    if (ret != 0 && errno != EEXIST) {
+        goto failed;
+    }
+
+    return NJS_OK;
+
+failed:
+
+    size = njs_strlen(path);
+    length = njs_utf8_length((u_char *) path, size);
+    if (njs_slow_path(length < 0)) {
+        length = 0;
+    }
+
+    ret = njs_string_new(vm, &value, (u_char *) path, size, length);
+    if (ret != NJS_OK) {
+        return NJS_ERROR;
+    }
+
+    return njs_fs_error(vm, "mkdir", strerror(errno), &value, errno,
+                        retval);
+}
+
+
 static int
 njs_fs_flags(njs_vm_t *vm, njs_value_t *value, int default_flags)
 {
diff --git a/test/js/fs_promises_008.js b/test/js/fs_promises_008.js
new file mode 100644
index 0000000..8e83c30
--- /dev/null
+++ b/test/js/fs_promises_008.js
@@ -0,0 +1,79 @@
+var fs = require('fs');
+var fsp  = fs.promises;
+var dname = './build/test/fs_promises_αβγ_008/';
+var path = 'one/two/three/αβγ';
+
+var wipePath = (root, path, nofail) => {
+    path
+        .split('/')
+        .map((x, i, a) => {
+            return root + a.slice(0, i + 1).join('/');
+        })
+        .reverse()
+        .map((dir) => {
+            try {
+                fs.rmdirSync(dir);
+            } catch (e) {
+                if (!nofail) {
+                    throw e;
+                }
+            }
+        });
+};
+
+var testSync = () => new Promise((resolve, reject) => {
+    try {
+        wipePath(dname, path + '/' + path, true);
+        fs.rmdirSync(dname);
+    } catch (e) {
+    }
+
+    try {
+        fs.mkdirSync(dname);
+
+        fs.mkdirSync(dname, { recursive: true });
+        fs.mkdirSync(dname + '/', { recursive: true });
+        fs.mkdirSync(dname + '////', { recursive: true });
+
+        fs.mkdirSync(dname + path, { recursive: true });
+        wipePath(dname, path);
+
+        fs.mkdirSync(dname + '////' + path + '////' + path + '////', { recursive: true });
+        wipePath(dname, path + '/' + path);
+
+        try {
+            fs.mkdirSync(dname + path, { recursive: true, mode: 0 });
+        } catch (e) {
+            if (e.code != 'EACCES') {
+                reject(e);
+            }
+        }
+        wipePath(dname, path, true);
+
+        try {
+            fs.mkdirSync(dname + path, { recursive: true });
+            fs.writeFileSync(dname + path + '/one', 'not dir');
+            fs.mkdirSync(dname + path + '/' + path, { recursive: true });
+        } catch (e) {
+            if (e.code != 'ENOTDIR') {
+                reject(e);
+            }
+        }
+        fs.unlinkSync(dname + path + '/one');
+        wipePath(dname, path);
+
+        fs.rmdirSync(dname);
+        resolve();
+    } catch (e) {
+        reject(e);
+    }
+});
+
+Promise.resolve()
+.then(testSync)
+.then(() => {
+    console.log('test recursive fs.mkdirSync');
+})
+.catch((e) => {
+    console.log('test failed recursive fs.mkdirSync', JSON.stringify(e));
+})
diff --git a/test/njs_expect_test.exp b/test/njs_expect_test.exp
index 0923d00..c9ed529 100644
--- a/test/njs_expect_test.exp
+++ b/test/njs_expect_test.exp
@@ -1140,3 +1140,6 @@
 "test fs.readdirSync
 test fs.readdir
 test fsp.readdir"
+
+njs_run {"./test/js/fs_promises_008.js"} \
+"test recursive fs.mkdirSync"