From e0edb5dd0d1ff6f0743647d450326705e394f6b0 Mon Sep 17 00:00:00 2001
From: Giorgos Ntemiris <ntemirisgiorgos3@gmail.com>
Date: Sun, 25 Aug 2019 19:58:38 +0200
Subject: [PATCH 01/19] wip

---
 lib/fs.js                |  4 ++--
 lib/internal/fs/utils.js | 11 ++++++++---
 2 files changed, 10 insertions(+), 5 deletions(-)

diff --git a/lib/fs.js b/lib/fs.js
index 5c7d907f5e546d..c330cbed2cdbdb 100644
--- a/lib/fs.js
+++ b/lib/fs.js
@@ -1170,7 +1170,7 @@ function fchownSync(fd, uid, gid) {
   handleErrorFromBinding(ctx);
 }
 
-function chown(path, uid, gid, callback) {
+function chown(path, uid, gid, options, callback) {
   callback = makeCallback(callback);
   path = getValidatedPath(path);
   validateUint32(uid, 'uid');
@@ -1181,7 +1181,7 @@ function chown(path, uid, gid, callback) {
   binding.chown(pathModule.toNamespacedPath(path), uid, gid, req);
 }
 
-function chownSync(path, uid, gid) {
+function chownSync(path, uid, gid, options) {
   path = getValidatedPath(path);
   validateUint32(uid, 'uid');
   validateUint32(gid, 'gid');
diff --git a/lib/internal/fs/utils.js b/lib/internal/fs/utils.js
index fb060d23e66ffc..0772ce01cc0908 100644
--- a/lib/internal/fs/utils.js
+++ b/lib/internal/fs/utils.js
@@ -552,6 +552,11 @@ const validateRmdirOptions = hideStackFrames((options) => {
   return options;
 });
 
+// ex1. from: {uid: 1, gid: 1}
+// ex2. preserveRoot: true
+// ex3. noPreserveRoot: false
+// ex4. reference: 'some/path'
+
 
 module.exports = {
   assertEncoding,
@@ -560,19 +565,19 @@ module.exports = {
   Dirent,
   getDirents,
   getOptions,
+  getStatsFromBinding,
   getValidatedPath,
   nullCheck,
   preprocessSymlinkDestination,
   realpathCacheKey: Symbol('realpathCacheKey'),
-  getStatsFromBinding,
+  Stats,
   stringToFlags,
   stringToSymlinkType,
-  Stats,
   toUnixTimestamp,
   validateBufferArray,
   validateOffsetLengthRead,
   validateOffsetLengthWrite,
   validatePath,
   validateRmdirOptions,
-  warnOnNonPortableTemplate
+  warnOnNonPortableTemplate,
 };

From d90aec55f2c670d8e7fac70f1c504788ac89d645 Mon Sep 17 00:00:00 2001
From: Giorgos Ntemiris <ntemirisgiorgos3@gmail.com>
Date: Sun, 25 Aug 2019 19:58:56 +0200
Subject: [PATCH 02/19] wip

---
 lib/internal/fs/utils.js | 21 +++++++++++++++++++++
 1 file changed, 21 insertions(+)

diff --git a/lib/internal/fs/utils.js b/lib/internal/fs/utils.js
index 0772ce01cc0908..b2df0e254cdc26 100644
--- a/lib/internal/fs/utils.js
+++ b/lib/internal/fs/utils.js
@@ -552,11 +552,31 @@ const validateRmdirOptions = hideStackFrames((options) => {
   return options;
 });
 
+// @TODO: Add additional options to match chown cli utility.
 // ex1. from: {uid: 1, gid: 1}
 // ex2. preserveRoot: true
 // ex3. noPreserveRoot: false
 // ex4. reference: 'some/path'
 
+const defaultChownOptions = {
+  recursive: false,
+};
+
+const validateChownOptions = hideStackFrames((options) => {
+  if (options === undefined)
+    return defaultChownOptions;
+  if (options === null || typeof options !== 'object')
+    throw new ERR_INVALID_ARG_TYPE('options', 'object', options);
+
+  options = { ...defaultRmdirOptions, ...options };
+
+  if (typeof options.recursive !== 'boolean')
+    throw new ERR_INVALID_ARG_TYPE('recursive', 'boolean', options.recursive);
+
+  return options;
+
+});
+
 
 module.exports = {
   assertEncoding,
@@ -575,6 +595,7 @@ module.exports = {
   stringToSymlinkType,
   toUnixTimestamp,
   validateBufferArray,
+  validateChownOptions,
   validateOffsetLengthRead,
   validateOffsetLengthWrite,
   validatePath,

From fe0bafd8ee05d73beaf69db7628a357267187237 Mon Sep 17 00:00:00 2001
From: Giorgos Ntemiris <ntemirisgiorgos3@gmail.com>
Date: Tue, 27 Aug 2019 21:04:00 +0200
Subject: [PATCH 03/19] wip

---
 lib/fs.js                | 15 +++++++++++++++
 lib/internal/fs/utils.js |  7 +------
 2 files changed, 16 insertions(+), 6 deletions(-)

diff --git a/lib/fs.js b/lib/fs.js
index c330cbed2cdbdb..d52021240222bc 100644
--- a/lib/fs.js
+++ b/lib/fs.js
@@ -1185,9 +1185,24 @@ function chownSync(path, uid, gid, options) {
   path = getValidatedPath(path);
   validateUint32(uid, 'uid');
   validateUint32(gid, 'gid');
+
+  const { recursive = false } = options;
+
   const ctx = { path };
   binding.chown(pathModule.toNamespacedPath(path), uid, gid, undefined, ctx);
   handleErrorFromBinding(ctx);
+
+  if (!recursive) { return; }
+
+  const nextPath = path.split(pathModule.sep);
+
+  nextPath.pop();
+
+  if (!nextPath.length) {
+    return;
+  }
+  const pathToStr = nextPath.join(path.sep);
+  chownSync(pathToStr, uid, gid, options);
 }
 
 function utimes(path, atime, mtime, callback) {
diff --git a/lib/internal/fs/utils.js b/lib/internal/fs/utils.js
index b2df0e254cdc26..2497a039067e24 100644
--- a/lib/internal/fs/utils.js
+++ b/lib/internal/fs/utils.js
@@ -553,11 +553,6 @@ const validateRmdirOptions = hideStackFrames((options) => {
 });
 
 // @TODO: Add additional options to match chown cli utility.
-// ex1. from: {uid: 1, gid: 1}
-// ex2. preserveRoot: true
-// ex3. noPreserveRoot: false
-// ex4. reference: 'some/path'
-
 const defaultChownOptions = {
   recursive: false,
 };
@@ -568,7 +563,7 @@ const validateChownOptions = hideStackFrames((options) => {
   if (options === null || typeof options !== 'object')
     throw new ERR_INVALID_ARG_TYPE('options', 'object', options);
 
-  options = { ...defaultRmdirOptions, ...options };
+  options = { ...defaultChownOptions, ...options };
 
   if (typeof options.recursive !== 'boolean')
     throw new ERR_INVALID_ARG_TYPE('recursive', 'boolean', options.recursive);

From 28b7c12166743d0d840959ad8ed791f0ed4f192c Mon Sep 17 00:00:00 2001
From: Giorgos Ntemiris <ntemirisgiorgos3@gmail.com>
Date: Wed, 28 Aug 2019 08:40:50 +0200
Subject: [PATCH 04/19] wip

---
 lib/fs.js | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/lib/fs.js b/lib/fs.js
index d52021240222bc..482752cd0976eb 100644
--- a/lib/fs.js
+++ b/lib/fs.js
@@ -1176,9 +1176,12 @@ function chown(path, uid, gid, options, callback) {
   validateUint32(uid, 'uid');
   validateUint32(gid, 'gid');
 
+  const { recursive = false } = options;
+
   const req = new FSReqCallback();
   req.oncomplete = callback;
   binding.chown(pathModule.toNamespacedPath(path), uid, gid, req);
+
 }
 
 function chownSync(path, uid, gid, options) {

From 4ad2bbd87054e09bf1cf6dd76412bb30f78eb623 Mon Sep 17 00:00:00 2001
From: Giorgos Ntemiris <ntemirisgiorgos3@gmail.com>
Date: Wed, 28 Aug 2019 21:34:17 +0200
Subject: [PATCH 05/19] add test for chown rec

---
 lib/fs.js                                |  3 -
 test/parallel/test-fs-chown-recursive.js | 71 ++++++++++++++++++++++++
 2 files changed, 71 insertions(+), 3 deletions(-)
 create mode 100644 test/parallel/test-fs-chown-recursive.js

diff --git a/lib/fs.js b/lib/fs.js
index 482752cd0976eb..d52021240222bc 100644
--- a/lib/fs.js
+++ b/lib/fs.js
@@ -1176,12 +1176,9 @@ function chown(path, uid, gid, options, callback) {
   validateUint32(uid, 'uid');
   validateUint32(gid, 'gid');
 
-  const { recursive = false } = options;
-
   const req = new FSReqCallback();
   req.oncomplete = callback;
   binding.chown(pathModule.toNamespacedPath(path), uid, gid, req);
-
 }
 
 function chownSync(path, uid, gid, options) {
diff --git a/test/parallel/test-fs-chown-recursive.js b/test/parallel/test-fs-chown-recursive.js
new file mode 100644
index 00000000000000..b084ace8c0e8df
--- /dev/null
+++ b/test/parallel/test-fs-chown-recursive.js
@@ -0,0 +1,71 @@
+// Flags: --expose-internals
+'use strict';
+const common = require('../common');
+const tmpdir = require('../common/tmpdir');
+const assert = require('assert');
+const fs = require('fs');
+const path = require('path');
+const { validateChownOptions } = require('internal/fs/utils');
+let count = 0;
+
+tmpdir.refresh();
+
+function makeNonEmptyDirectory() {
+  const dirname = `chown-recursive-${count}`;
+  fs.mkdirSync(path.join(dirname, 'foo', 'bar', 'baz'), { recursive: true });
+  fs.writeFileSync(path.join(dirname, 'text.txt'), 'hello', 'utf8');
+  count++;
+  return dirname;
+}
+
+// Test the synchronous version.
+{
+  const dir = makeNonEmptyDirectory();
+
+  // Recursive removal should succeed.
+  fs.chownSync(dir, 1, 1, { recursive: true });
+
+  // No error should occur if recursive and the directory does not exist.
+  fs.chownSync(dir, 1, 1, { recursive: true });
+
+  // Attempted removal should fail now because the directory is gone.
+  common.expectsError(() => fs.chownSync(dir), { syscall: 'chown' });
+}
+
+// Test input validation.
+{
+  const defaults = {
+    recursive: false
+  };
+  const modified = {
+    recursive: true
+  };
+
+  assert.deepStrictEqual(validateChownOptions(), defaults);
+  assert.deepStrictEqual(validateChownOptions({}), defaults);
+  assert.deepStrictEqual(validateChownOptions(modified), modified);
+  assert.deepStrictEqual(validateChownOptions({
+  }), {
+    recursive: false
+  });
+
+  [null, 'foo', 5, NaN].forEach((bad) => {
+    common.expectsError(() => {
+      validateChownOptions(bad);
+    }, {
+      code: 'ERR_INVALID_ARG_TYPE',
+      type: TypeError,
+      message: /^The "options" argument must be of type object\./
+    });
+  });
+
+  [undefined, null, 'foo', Infinity, function() {}].forEach((bad) => {
+    common.expectsError(() => {
+      validateChownOptions({ recursive: bad });
+    }, {
+      code: 'ERR_INVALID_ARG_TYPE',
+      type: TypeError,
+      message: /^The "recursive" argument must be of type boolean\./
+    });
+  });
+}

From 5dd2b85047e492d18cd14f5912781e33917603ec Mon Sep 17 00:00:00 2001
From: Giorgos Ntemiris <ntemirisgiorgos3@gmail.com>
Date: Fri, 30 Aug 2019 09:33:48 +0200
Subject: [PATCH 06/19] wip

---
 lib/internal/fs/chownrec.js | 14 ++++++++++++++
 1 file changed, 14 insertions(+)
 create mode 100644 lib/internal/fs/chownrec.js

diff --git a/lib/internal/fs/chownrec.js b/lib/internal/fs/chownrec.js
new file mode 100644
index 00000000000000..17b221420abf47
--- /dev/null
+++ b/lib/internal/fs/chownrec.js
@@ -0,0 +1,14 @@
+'use strict';
+
+const { readdirSync, lstatSync, chownSync } = require('fs');
+
+function _chownRecSync(path, uid, gid, options) {
+  const stats = lstatSync(path);
+  if (stats !== undefined && stats.isDirectory()) {
+    const files = readdirSync(path);
+    files.forEach((file) =>
+      chownSync(path, uid, gid, options));
+  } else {
+    chownSync(path, uid, gid, options);
+  }
+}

From 0c33fc998c70f1c4e10a04ed8ccb63c7b7c6d203 Mon Sep 17 00:00:00 2001
From: Giorgos Ntemiris <ntemirisgiorgos3@gmail.com>
Date: Sun, 1 Sep 2019 18:56:40 +0200
Subject: [PATCH 07/19] wip

---
 lib/fs.js                                     | 24 +++++++++----------
 .../fs/{chownrec.js => chown_recursive.js}    |  8 +++++--
 test/parallel/test-fs-chown-recursive.js      | 16 ++++---------
 3 files changed, 23 insertions(+), 25 deletions(-)
 rename lib/internal/fs/{chownrec.js => chown_recursive.js} (55%)

diff --git a/lib/fs.js b/lib/fs.js
index d52021240222bc..54d1f59edfaaf1 100644
--- a/lib/fs.js
+++ b/lib/fs.js
@@ -103,6 +103,8 @@ let ReadStream;
 let WriteStream;
 let rimraf;
 let rimrafSync;
+let chownRecursive;
+let chownRecursiveSync;
 
 // These have to be separate because of how graceful-fs happens to do it's
 // monkeypatching.
@@ -745,6 +747,11 @@ function lazyLoadRimraf() {
     ({ rimraf, rimrafSync } = require('internal/fs/rimraf'));
 }
 
+function lazyLoadChownRecursive() {
+  if (chownRecursive === undefined)
+    ({ chownRecursive, chownRecursiveSync } = require('internal/fs/chown_recursive'));
+}
+
 function rmdir(path, options, callback) {
   if (typeof options === 'function') {
     callback = options;
@@ -1188,21 +1195,14 @@ function chownSync(path, uid, gid, options) {
 
   const { recursive = false } = options;
 
+  if (recursive) {
+    lazyLoadChownRecursive();
+    chownRecursiveSync(path, uid, gid, options);
+  }
+
   const ctx = { path };
   binding.chown(pathModule.toNamespacedPath(path), uid, gid, undefined, ctx);
   handleErrorFromBinding(ctx);
-
-  if (!recursive) { return; }
-
-  const nextPath = path.split(pathModule.sep);
-
-  nextPath.pop();
-
-  if (!nextPath.length) {
-    return;
-  }
-  const pathToStr = nextPath.join(path.sep);
-  chownSync(pathToStr, uid, gid, options);
 }
 
 function utimes(path, atime, mtime, callback) {
diff --git a/lib/internal/fs/chownrec.js b/lib/internal/fs/chown_recursive.js
similarity index 55%
rename from lib/internal/fs/chownrec.js
rename to lib/internal/fs/chown_recursive.js
index 17b221420abf47..2c02ea3323ceb9 100644
--- a/lib/internal/fs/chownrec.js
+++ b/lib/internal/fs/chown_recursive.js
@@ -2,13 +2,17 @@
 
 const { readdirSync, lstatSync, chownSync } = require('fs');
 
-function _chownRecSync(path, uid, gid, options) {
+function chownRecursiveSync(path, uid, gid, options) {
   const stats = lstatSync(path);
   if (stats !== undefined && stats.isDirectory()) {
     const files = readdirSync(path);
     files.forEach((file) =>
-      chownSync(path, uid, gid, options));
+      chownRecursiveSync(path, uid, gid, options));
   } else {
     chownSync(path, uid, gid, options);
   }
 }
+
+function chownRecursive(path, uid, gid, options, callback) {}
+
+module.exports = { chownRecursiveSync, chownRecursive };
diff --git a/test/parallel/test-fs-chown-recursive.js b/test/parallel/test-fs-chown-recursive.js
index b084ace8c0e8df..fd0650a0bff9b2 100644
--- a/test/parallel/test-fs-chown-recursive.js
+++ b/test/parallel/test-fs-chown-recursive.js
@@ -22,14 +22,8 @@ function makeNonEmptyDirectory() {
 {
   const dir = makeNonEmptyDirectory();
 
-  // Recursive removal should succeed.
+  // Recursive chown should succeed.
   fs.chownSync(dir, 1, 1, { recursive: true });
-
-  // No error should occur if recursive and the directory does not exist.
-  fs.chownSync(dir, 1, 1, { recursive: true });
-
-  // Attempted removal should fail now because the directory is gone.
-  common.expectsError(() => fs.chownSync(dir), { syscall: 'chown' });
 }
 
 // Test input validation.
@@ -49,9 +43,9 @@ function makeNonEmptyDirectory() {
     recursive: false
   });
 
-  [null, 'foo', 5, NaN].forEach((bad) => {
+  [null, 'foo', 5, NaN].forEach((badArg) => {
     common.expectsError(() => {
-      validateChownOptions(bad);
+      validateChownOptions(badArg);
     }, {
       code: 'ERR_INVALID_ARG_TYPE',
       type: TypeError,
@@ -59,9 +53,9 @@ function makeNonEmptyDirectory() {
     });
   });
 
-  [undefined, null, 'foo', Infinity, function() {}].forEach((bad) => {
+  [undefined, null, 'foo', Infinity, function() {}].forEach((badValue) => {
     common.expectsError(() => {
-      validateChownOptions({ recursive: bad });
+      validateChownOptions({ recursive: badValue });
     }, {
       code: 'ERR_INVALID_ARG_TYPE',
       type: TypeError,

From f89d2ae2a58e9c7a1bea28a534de3c4a741e0381 Mon Sep 17 00:00:00 2001
From: Giorgos Ntemiris <ntemirisgiorgos3@gmail.com>
Date: Sun, 1 Sep 2019 20:09:59 +0200
Subject: [PATCH 08/19] wip

---
 lib/internal/fs/chown_recursive.js | 92 +++++++++++++++++++++++++++++-
 1 file changed, 90 insertions(+), 2 deletions(-)

diff --git a/lib/internal/fs/chown_recursive.js b/lib/internal/fs/chown_recursive.js
index 2c02ea3323ceb9..6ffb64e0ca0c7b 100644
--- a/lib/internal/fs/chown_recursive.js
+++ b/lib/internal/fs/chown_recursive.js
@@ -1,6 +1,20 @@
+/**
+ * Chown recursively similar to chown -R
+ */
 'use strict';
 
-const { readdirSync, lstatSync, chownSync } = require('fs');
+const {
+  readdir,
+  readdirSync,
+  lstat,
+  lstatSync,
+  chown,
+  chownSync
+} = require('fs');
+
+const { join } = require('path');
+const notEmptyErrorCodes = new Set(['ENOTEMPTY', 'EEXIST']);
+const { setTimeout } = require('timers');
 
 function chownRecursiveSync(path, uid, gid, options) {
   const stats = lstatSync(path);
@@ -13,6 +27,80 @@ function chownRecursiveSync(path, uid, gid, options) {
   }
 }
 
-function chownRecursive(path, uid, gid, options, callback) {}
+
+function _chownChildren(path, uid, gid, options, callback) {
+  readdir(path, (err, files) => {
+    if (err)
+      return callback(err);
+
+    let numFiles = files.length;
+
+    if (numFiles === 0)
+      return chown(path, uid, gid, callback);
+
+    let done = false;
+
+    files.forEach((child) => {
+      chownRecursive(join(path, child), uid, gid, options, (err) => {
+        if (done)
+          return;
+
+        if (err) {
+          done = true;
+          return callback(err);
+        }
+
+        numFiles--;
+        if (numFiles === 0)
+          chown(path, uid, gid, callback);
+      });
+    });
+  });
+}
+
+function _chown(path, uid, gid, options, originalErr, callback) {
+  chown(path, (err) => {
+    if (err) {
+      if (notEmptyErrorCodes.has(err.code))
+        return _chownChildren(path, uid, gid, options, callback);
+      if (err.code === 'ENOTDIR')
+        return callback(originalErr);
+    }
+    callback(err);
+  });
+}
+
+function _chownRecursive(path, uid, gid, options, callback) {
+  lstat(path, (err, stats) => {
+    if (err) {
+      if (err.code === 'ENOENT')
+        return callback(null);
+    } else if (stats.isDirectory()) {
+      return _chown(path, uid, gid, options, err, callback);
+    }
+  });
+}
+
+function chownRecursive(path, uid, gid, options, callback) {
+  let timeout = 0;  // For EMFILE handling.
+  let busyTries = 0;
+  _chownRecursive(path, uid, gid, options, function CB(err) {
+    if (err) {
+      if ((err.code === 'EBUSY' || err.code === 'ENOTEMPTY' ||
+           err.code === 'EPERM') && busyTries < options.maxBusyTries) {
+        busyTries++;
+        return setTimeout(_chownRecursive, busyTries * 100, path, options, CB);
+      }
+
+      if (err.code === 'EMFILE' && timeout < options.emfileWait)
+        return setTimeout(_chownRecursive, timeout++, path, options, CB);
+
+      // The file is already gone.
+      if (err.code === 'ENOENT')
+        err = null;
+    }
+    callback(err);
+  })};
+
 
 module.exports = { chownRecursiveSync, chownRecursive };

From 7c4d015e8c23f1d21e36ba600de0f00efd91d451 Mon Sep 17 00:00:00 2001
From: Giorgos Ntemiris <ntemirisgiorgos3@gmail.com>
Date: Sun, 1 Sep 2019 20:11:20 +0200
Subject: [PATCH 09/19] wip

---
 lib/internal/fs/chown_recursive.js | 7 ++-----
 1 file changed, 2 insertions(+), 5 deletions(-)

diff --git a/lib/internal/fs/chown_recursive.js b/lib/internal/fs/chown_recursive.js
index 6ffb64e0ca0c7b..8ccd0739851a47 100644
--- a/lib/internal/fs/chown_recursive.js
+++ b/lib/internal/fs/chown_recursive.js
@@ -94,13 +94,10 @@ function chownRecursive(path, uid, gid, options, callback) {
 
       if (err.code === 'EMFILE' && timeout < options.emfileWait)
         return setTimeout(_chownRecursive, timeout++, path, options, CB);
-
-      // The file is already gone.
-      if (err.code === 'ENOENT')
-        err = null;
     }
     callback(err);
-  })};
+  });
+}
 
 
 module.exports = { chownRecursiveSync, chownRecursive };

From e6c215d316d78480ff301b36d1c91f11984758e0 Mon Sep 17 00:00:00 2001
From: Giorgos Ntemiris <ntemirisgiorgos3@gmail.com>
Date: Sun, 1 Sep 2019 20:15:42 +0200
Subject: [PATCH 10/19] update fs.js

---
 lib/fs.js | 17 ++++++++++++++---
 1 file changed, 14 insertions(+), 3 deletions(-)

diff --git a/lib/fs.js b/lib/fs.js
index 54d1f59edfaaf1..04d3cf240edc98 100644
--- a/lib/fs.js
+++ b/lib/fs.js
@@ -76,8 +76,9 @@ const {
   validateOffsetLengthRead,
   validateOffsetLengthWrite,
   validatePath,
+  validateChownOptions,
   validateRmdirOptions,
-  warnOnNonPortableTemplate
+  warnOnNonPortableTemplate,
 } = require('internal/fs/utils');
 const {
   CHAR_FORWARD_SLASH,
@@ -749,7 +750,8 @@ function lazyLoadRimraf() {
 
 function lazyLoadChownRecursive() {
   if (chownRecursive === undefined)
-    ({ chownRecursive, chownRecursiveSync } = require('internal/fs/chown_recursive'));
+    ({ chownRecursive,
+       chownRecursiveSync } = require('internal/fs/chown_recursive'));
 }
 
 function rmdir(path, options, callback) {
@@ -1183,6 +1185,15 @@ function chown(path, uid, gid, options, callback) {
   validateUint32(uid, 'uid');
   validateUint32(gid, 'gid');
 
+  callback = makeCallback(callback);
+  path = pathModule.toNamespacedPath(getValidatedPath(path));
+  options = validateChownOptions(options);
+
+  if (options.recursive) {
+    lazyLoadChownRecursive();
+    return chownRecursive(path, uid, gid, options, callback);
+  }
+
   const req = new FSReqCallback();
   req.oncomplete = callback;
   binding.chown(pathModule.toNamespacedPath(path), uid, gid, req);
@@ -1197,7 +1208,7 @@ function chownSync(path, uid, gid, options) {
 
   if (recursive) {
     lazyLoadChownRecursive();
-    chownRecursiveSync(path, uid, gid, options);
+    return chownRecursiveSync(path, uid, gid, options);
   }
 
   const ctx = { path };

From 29d9a1522dae72e84068c226ae19bc3f5c1fed3b Mon Sep 17 00:00:00 2001
From: Giorgos Ntemiris <ntemirisgiorgos3@gmail.com>
Date: Sun, 1 Sep 2019 20:26:23 +0200
Subject: [PATCH 11/19] update docs

remove some rimraf retries
---
 doc/api/fs.md                      | 16 ++++++++++++++--
 lib/internal/fs/chown_recursive.js | 10 ++--------
 2 files changed, 16 insertions(+), 10 deletions(-)

diff --git a/doc/api/fs.md b/doc/api/fs.md
index 7e8266a90ec63a..5c3dc153eedc2e 100644
--- a/doc/api/fs.md
+++ b/doc/api/fs.md
@@ -1318,7 +1318,7 @@ this API: [`fs.chmod()`][].
 
 See also: chmod(2).
 
-## fs.chown(path, uid, gid, callback)
+## fs.chown(path, uid, gid[, options], callback)
 <!-- YAML
 added: v0.1.97
 changes:
@@ -1336,9 +1336,15 @@ changes:
                  it will emit a deprecation warning with id DEP0013.
 -->
 
+> Stability: 1 - Recursive chown is experimental.
+
 * `path` {string|Buffer|URL}
 * `uid` {integer}
 * `gid` {integer}
+* `options` {object}
+  * `recursive` {boolean} If `true`, perform a recursive directory chown. In
+  recursive mode, errors are not reported if `path` does not exist, and
+  operations are retried on failure. **Default:** `false`.
 * `callback` {Function}
   * `err` {Error}
 
@@ -1347,7 +1353,7 @@ possible exception are given to the completion callback.
 
 See also: chown(2).
 
-## fs.chownSync(path, uid, gid)
+## fs.chownSync(path, uid, gid[, options])
 <!-- YAML
 added: v0.1.97
 changes:
@@ -1357,9 +1363,15 @@ changes:
                  protocol. Support is currently still *experimental*.
 -->
 
+> Stability: 1 - Recursive removal is experimental.
+
 * `path` {string|Buffer|URL}
 * `uid` {integer}
 * `gid` {integer}
+* `options` {Object}
+  * `recursive` {boolean} If `true`, perform a recursive directory chown. In
+  recursive mode, errors are not reported if `path` does not exist, and
+  operations are retried on failure. **Default:** `false`.
 
 Synchronously changes owner and group of a file. Returns `undefined`.
 This is the synchronous version of [`fs.chown()`][].
diff --git a/lib/internal/fs/chown_recursive.js b/lib/internal/fs/chown_recursive.js
index 8ccd0739851a47..9412dff7a665c0 100644
--- a/lib/internal/fs/chown_recursive.js
+++ b/lib/internal/fs/chown_recursive.js
@@ -1,5 +1,6 @@
 /**
  * Chown recursively similar to chown -R
+ * code is similar to rimraf except less functionality
  */
 'use strict';
 
@@ -83,16 +84,9 @@ function _chownRecursive(path, uid, gid, options, callback) {
 
 function chownRecursive(path, uid, gid, options, callback) {
   let timeout = 0;  // For EMFILE handling.
-  let busyTries = 0;
   _chownRecursive(path, uid, gid, options, function CB(err) {
     if (err) {
-      if ((err.code === 'EBUSY' || err.code === 'ENOTEMPTY' ||
-           err.code === 'EPERM') && busyTries < options.maxBusyTries) {
-        busyTries++;
-        return setTimeout(_chownRecursive, busyTries * 100, path, options, CB);
-      }
-
-      if (err.code === 'EMFILE' && timeout < options.emfileWait)
+      if (err.code === 'EMFILE')
         return setTimeout(_chownRecursive, timeout++, path, options, CB);
     }
     callback(err);

From 620d27211e350de465e72e1903f3cbbaedc8950a Mon Sep 17 00:00:00 2001
From: Giorgos Ntemiris <ntemirisgiorgos3@gmail.com>
Date: Mon, 2 Sep 2019 08:55:14 +0200
Subject: [PATCH 12/19] update empty codes

---
 lib/internal/fs/chown_recursive.js | 7 ++-----
 1 file changed, 2 insertions(+), 5 deletions(-)

diff --git a/lib/internal/fs/chown_recursive.js b/lib/internal/fs/chown_recursive.js
index 9412dff7a665c0..36579457feabae 100644
--- a/lib/internal/fs/chown_recursive.js
+++ b/lib/internal/fs/chown_recursive.js
@@ -1,7 +1,4 @@
-/**
- * Chown recursively similar to chown -R
- * code is similar to rimraf except less functionality
- */
+// Chown recursively similar to chown -R
 'use strict';
 
 const {
@@ -14,7 +11,7 @@ const {
 } = require('fs');
 
 const { join } = require('path');
-const notEmptyErrorCodes = new Set(['ENOTEMPTY', 'EEXIST']);
+const notEmptyErrorCodes = new Set(['EEXIST']);
 const { setTimeout } = require('timers');
 
 function chownRecursiveSync(path, uid, gid, options) {

From 3bbd03e30283c32e674b409a6ce65a1e765e1914 Mon Sep 17 00:00:00 2001
From: Giorgos Ntemiris <ntemirisgiorgos3@gmail.com>
Date: Mon, 2 Sep 2019 21:02:19 +0200
Subject: [PATCH 13/19] create directory structure

---
 test/parallel/test-fs-chown-recursive.js | 33 ++++++++++++++++++------
 1 file changed, 25 insertions(+), 8 deletions(-)

diff --git a/test/parallel/test-fs-chown-recursive.js b/test/parallel/test-fs-chown-recursive.js
index fd0650a0bff9b2..1873bf37935f65 100644
--- a/test/parallel/test-fs-chown-recursive.js
+++ b/test/parallel/test-fs-chown-recursive.js
@@ -6,21 +6,38 @@ const assert = require('assert');
 const fs = require('fs');
 const path = require('path');
 const { validateChownOptions } = require('internal/fs/utils');
-let count = 0;
 
 tmpdir.refresh();
 
-function makeNonEmptyDirectory() {
-  const dirname = `chown-recursive-${count}`;
-  fs.mkdirSync(path.join(dirname, 'foo', 'bar', 'baz'), { recursive: true });
-  fs.writeFileSync(path.join(dirname, 'text.txt'), 'hello', 'utf8');
-  count++;
-  return dirname;
+/*
+foo
+|_ bar
+|    |_ file1.test
+|_ baz
+|    |_ file2.test
+|_ bax
+     |_ foo
+          |_ file3.test
+*/
+function makeDirectories() {
+  const dirname = 'chown-recursive';
+
+  const foobarPath = path.join(dirname, 'foo', 'bar');
+  fs.mkdirSync(foobarPath, { recursive: true });
+  fs.writeFileSync(path.join(foobarPath, 'file1.test', 'file1'));
+
+  const foobazPath = path.join(dirname, 'foo', 'baz');
+  fs.mkdirSync(foobazPath, { recursive: true });
+  fs.writeFileSync(path.join(foobazPath, 'file2.test', 'file1'));
+
+  const foobaxPath = path.join(dirname, 'foo', 'bax', 'foo');
+  fs.mkdirSync(foobaxPath, { recursive: true });
+  fs.writeFileSync(path.join(foobaxPath, 'file3.test', 'file3'));
 }
 
 // Test the synchronous version.
 {
-  const dir = makeNonEmptyDirectory();
+  const dir = makeDirectories();
 
   // Recursive chown should succeed.
   fs.chownSync(dir, 1, 1, { recursive: true });

From f86104d11d54fbc53f692f08e8e90200da561841 Mon Sep 17 00:00:00 2001
From: Giorgos Ntemiris <ntemirisgiorgos3@gmail.com>
Date: Mon, 2 Sep 2019 21:38:37 +0200
Subject: [PATCH 14/19] update tests

---
 test/parallel/test-fs-chown-recursive.js | 57 +++++++++++++++++-------
 1 file changed, 40 insertions(+), 17 deletions(-)

diff --git a/test/parallel/test-fs-chown-recursive.js b/test/parallel/test-fs-chown-recursive.js
index 1873bf37935f65..5a655cbc3b3527 100644
--- a/test/parallel/test-fs-chown-recursive.js
+++ b/test/parallel/test-fs-chown-recursive.js
@@ -9,16 +9,17 @@ const { validateChownOptions } = require('internal/fs/utils');
 
 tmpdir.refresh();
 
-/*
-foo
-|_ bar
-|    |_ file1.test
-|_ baz
-|    |_ file2.test
-|_ bax
-     |_ foo
-          |_ file3.test
-*/
+const filePaths = [
+  'foo/bar/file1.test',
+  'foo/bar',
+  'foo/baz/file2.test',
+  'foo/baz',
+  'foo/bax/file3.test',
+  'foo/bax',
+  'foo/file4.test',
+  'foo'
+];
+
 function makeDirectories() {
   const dirname = 'chown-recursive';
 
@@ -35,13 +36,6 @@ function makeDirectories() {
   fs.writeFileSync(path.join(foobaxPath, 'file3.test', 'file3'));
 }
 
-// Test the synchronous version.
-{
-  const dir = makeDirectories();
-
-  // Recursive chown should succeed.
-  fs.chownSync(dir, 1, 1, { recursive: true });
-}
 
 // Test input validation.
 {
@@ -80,3 +74,32 @@ function makeDirectories() {
     });
   });
 }
+
+// Test the synchronous version.
+{
+  const dir = makeDirectories();
+
+  // Recursive chown should succeed.
+  fs.chownSync(dir, 1, 1, { recursive: true });
+
+  filePaths.forEach((pathToCheck) => {
+    const stat = fs.lstatSync(pathToCheck);
+    assert.ok(stat.uid === 1, `uid for ${pathToCheck} should equal 1`);
+    assert.ok(stat.gid === 1, `gid for ${pathToCheck} should equal 1`);
+  });
+
+  fs.rmdirSync('foo');
+
+}
+
+// test async version.
+{
+  makeDirectories();
+  fs.chown(tmpdir.path, 1, 1, { recursive: true }, common.mustCall(() => {
+    filePaths.forEach((pathToCheck) => {
+      const stat = fs.lstatSync(pathToCheck);
+      assert.ok(stat.uid === 1, `uid for ${pathToCheck} should equal 1`);
+      assert.ok(stat.gid === 1, `gid for ${pathToCheck} should equal 1`);
+    });
+  }, 1));
+}

From 12435526b68c68fd5a3fc8b516e739c75fee3154 Mon Sep 17 00:00:00 2001
From: Giorgos Ntemiris <ntemirisgiorgos3@gmail.com>
Date: Mon, 2 Sep 2019 21:43:19 +0200
Subject: [PATCH 15/19] renamed to chownR and chownRSync, removed comments

---
 lib/fs.js                          | 20 ++++++++++----------
 lib/internal/fs/chown_recursive.js | 18 ++++++++----------
 2 files changed, 18 insertions(+), 20 deletions(-)

diff --git a/lib/fs.js b/lib/fs.js
index 04d3cf240edc98..9e5822eff167d8 100644
--- a/lib/fs.js
+++ b/lib/fs.js
@@ -104,8 +104,8 @@ let ReadStream;
 let WriteStream;
 let rimraf;
 let rimrafSync;
-let chownRecursive;
-let chownRecursiveSync;
+let chownR;
+let chownRSync;
 
 // These have to be separate because of how graceful-fs happens to do it's
 // monkeypatching.
@@ -748,10 +748,10 @@ function lazyLoadRimraf() {
     ({ rimraf, rimrafSync } = require('internal/fs/rimraf'));
 }
 
-function lazyLoadChownRecursive() {
-  if (chownRecursive === undefined)
-    ({ chownRecursive,
-       chownRecursiveSync } = require('internal/fs/chown_recursive'));
+function lazyLoadChownR() {
+  if (chownR === undefined)
+    ({ chownR,
+       chownRSync } = require('internal/fs/chown_recursive'));
 }
 
 function rmdir(path, options, callback) {
@@ -1190,8 +1190,8 @@ function chown(path, uid, gid, options, callback) {
   options = validateChownOptions(options);
 
   if (options.recursive) {
-    lazyLoadChownRecursive();
-    return chownRecursive(path, uid, gid, options, callback);
+    lazyLoadChownR();
+    return chownR(path, uid, gid, options, callback);
   }
 
   const req = new FSReqCallback();
@@ -1207,8 +1207,8 @@ function chownSync(path, uid, gid, options) {
   const { recursive = false } = options;
 
   if (recursive) {
-    lazyLoadChownRecursive();
-    return chownRecursiveSync(path, uid, gid, options);
+    lazyLoadChownR();
+    return chownRSync(path, uid, gid, options);
   }
 
   const ctx = { path };
diff --git a/lib/internal/fs/chown_recursive.js b/lib/internal/fs/chown_recursive.js
index 36579457feabae..34326fd70abe4c 100644
--- a/lib/internal/fs/chown_recursive.js
+++ b/lib/internal/fs/chown_recursive.js
@@ -1,4 +1,3 @@
-// Chown recursively similar to chown -R
 'use strict';
 
 const {
@@ -14,18 +13,17 @@ const { join } = require('path');
 const notEmptyErrorCodes = new Set(['EEXIST']);
 const { setTimeout } = require('timers');
 
-function chownRecursiveSync(path, uid, gid, options) {
+function chownRSync(path, uid, gid, options) {
   const stats = lstatSync(path);
   if (stats !== undefined && stats.isDirectory()) {
     const files = readdirSync(path);
     files.forEach((file) =>
-      chownRecursiveSync(path, uid, gid, options));
+      chownRSync(path, uid, gid, options));
   } else {
     chownSync(path, uid, gid, options);
   }
 }
 
-
 function _chownChildren(path, uid, gid, options, callback) {
   readdir(path, (err, files) => {
     if (err)
@@ -39,7 +37,7 @@ function _chownChildren(path, uid, gid, options, callback) {
     let done = false;
 
     files.forEach((child) => {
-      chownRecursive(join(path, child), uid, gid, options, (err) => {
+      chownR(join(path, child), uid, gid, options, (err) => {
         if (done)
           return;
 
@@ -68,7 +66,7 @@ function _chown(path, uid, gid, options, originalErr, callback) {
   });
 }
 
-function _chownRecursive(path, uid, gid, options, callback) {
+function _chownR(path, uid, gid, options, callback) {
   lstat(path, (err, stats) => {
     if (err) {
       if (err.code === 'ENOENT')
@@ -79,16 +77,16 @@ function _chownRecursive(path, uid, gid, options, callback) {
   });
 }
 
-function chownRecursive(path, uid, gid, options, callback) {
+function chownR(path, uid, gid, options, callback) {
   let timeout = 0;  // For EMFILE handling.
-  _chownRecursive(path, uid, gid, options, function CB(err) {
+  _chownR(path, uid, gid, options, function CB(err) {
     if (err) {
       if (err.code === 'EMFILE')
-        return setTimeout(_chownRecursive, timeout++, path, options, CB);
+        return setTimeout(_chownR, timeout++, path, options, CB);
     }
     callback(err);
   });
 }
 
 
-module.exports = { chownRecursiveSync, chownRecursive };
+module.exports = { chownRSync, chownR };

From 2ecbf6e071acc4d20ea818ff21b5c10d43017ff4 Mon Sep 17 00:00:00 2001
From: Giorgos Ntemiris <ntemirisgiorgos3@gmail.com>
Date: Tue, 3 Sep 2019 21:51:28 +0200
Subject: [PATCH 16/19] update test

---
 test/parallel/test-fs-chown-recursive.js | 8 +++++---
 1 file changed, 5 insertions(+), 3 deletions(-)

diff --git a/test/parallel/test-fs-chown-recursive.js b/test/parallel/test-fs-chown-recursive.js
index 5a655cbc3b3527..3442082fad3933 100644
--- a/test/parallel/test-fs-chown-recursive.js
+++ b/test/parallel/test-fs-chown-recursive.js
@@ -25,15 +25,17 @@ function makeDirectories() {
 
   const foobarPath = path.join(dirname, 'foo', 'bar');
   fs.mkdirSync(foobarPath, { recursive: true });
-  fs.writeFileSync(path.join(foobarPath, 'file1.test', 'file1'));
+  fs.writeFileSync(path.join(foobarPath, 'file1.test'), 'file1');
 
   const foobazPath = path.join(dirname, 'foo', 'baz');
   fs.mkdirSync(foobazPath, { recursive: true });
-  fs.writeFileSync(path.join(foobazPath, 'file2.test', 'file1'));
+  fs.writeFileSync(path.join(foobazPath, 'file2.test'), 'file1');
 
   const foobaxPath = path.join(dirname, 'foo', 'bax', 'foo');
   fs.mkdirSync(foobaxPath, { recursive: true });
-  fs.writeFileSync(path.join(foobaxPath, 'file3.test', 'file3'));
+  fs.writeFileSync(path.join(foobaxPath, 'file3.test'), 'file3');
+
+  return dirname;
 }
 
 

From c6b7808876e8208582042026ac5153b08365c84d Mon Sep 17 00:00:00 2001
From: Giorgos Ntemiris <ntemirisgiorgos3@gmail.com>
Date: Wed, 4 Sep 2019 20:29:13 +0200
Subject: [PATCH 17/19] changes

- include in node.gyp
- simplify test
---
 lib/fs.js                                |  4 +-
 node.gyp                                 |  1 +
 test/parallel/test-fs-chown-recursive.js | 61 +++++++++++-------------
 3 files changed, 29 insertions(+), 37 deletions(-)

diff --git a/lib/fs.js b/lib/fs.js
index 9e5822eff167d8..b6cd615b5b7f69 100644
--- a/lib/fs.js
+++ b/lib/fs.js
@@ -1204,9 +1204,7 @@ function chownSync(path, uid, gid, options) {
   validateUint32(uid, 'uid');
   validateUint32(gid, 'gid');
 
-  const { recursive = false } = options;
-
-  if (recursive) {
+  if (options.recursive) {
     lazyLoadChownR();
     return chownRSync(path, uid, gid, options);
   }
diff --git a/node.gyp b/node.gyp
index 1d45f5117144b0..8af9cfe36d6c59 100644
--- a/node.gyp
+++ b/node.gyp
@@ -125,6 +125,7 @@
       'lib/internal/fs/promises.js',
       'lib/internal/fs/read_file_context.js',
       'lib/internal/fs/rimraf.js',
+      'lib/internal/fs/chown_recursive.js',
       'lib/internal/fs/streams.js',
       'lib/internal/fs/sync_write_stream.js',
       'lib/internal/fs/utils.js',
diff --git a/test/parallel/test-fs-chown-recursive.js b/test/parallel/test-fs-chown-recursive.js
index 3442082fad3933..6b23cc7b372845 100644
--- a/test/parallel/test-fs-chown-recursive.js
+++ b/test/parallel/test-fs-chown-recursive.js
@@ -9,33 +9,26 @@ const { validateChownOptions } = require('internal/fs/utils');
 
 tmpdir.refresh();
 
-const filePaths = [
+const dirname = 'chown-recursive';
+
+const paths = [
   'foo/bar/file1.test',
   'foo/bar',
-  'foo/baz/file2.test',
-  'foo/baz',
-  'foo/bax/file3.test',
-  'foo/bax',
-  'foo/file4.test',
+  'foo/file2.test',
   'foo'
 ];
 
-function makeDirectories() {
-  const dirname = 'chown-recursive';
-
-  const foobarPath = path.join(dirname, 'foo', 'bar');
-  fs.mkdirSync(foobarPath, { recursive: true });
-  fs.writeFileSync(path.join(foobarPath, 'file1.test'), 'file1');
+const expectUID = 1;
+const expectGID = 1;
 
-  const foobazPath = path.join(dirname, 'foo', 'baz');
-  fs.mkdirSync(foobazPath, { recursive: true });
-  fs.writeFileSync(path.join(foobazPath, 'file2.test'), 'file1');
+const mainPath = path.join(tmpdir.path, dirname);
+const fooPath = path.join(mainPath, 'foo');
 
-  const foobaxPath = path.join(dirname, 'foo', 'bax', 'foo');
-  fs.mkdirSync(foobaxPath, { recursive: true });
-  fs.writeFileSync(path.join(foobaxPath, 'file3.test'), 'file3');
-
-  return dirname;
+function makeDirectories() {
+  fs.mkdirSync(fooPath, { recursive: true });
+  fs.mkdirSync(path.join(fooPath, 'bar'));
+  fs.writeFileSync(path.join(fooPath, 'bar', 'file1.test'), 'file1');
+  fs.writeFileSync(path.join(fooPath, 'file2.test'), 'file2');
 }
 
 
@@ -79,29 +72,29 @@ function makeDirectories() {
 
 // Test the synchronous version.
 {
-  const dir = makeDirectories();
+  makeDirectories();
 
   // Recursive chown should succeed.
-  fs.chownSync(dir, 1, 1, { recursive: true });
-
-  filePaths.forEach((pathToCheck) => {
-    const stat = fs.lstatSync(pathToCheck);
-    assert.ok(stat.uid === 1, `uid for ${pathToCheck} should equal 1`);
-    assert.ok(stat.gid === 1, `gid for ${pathToCheck} should equal 1`);
+  fs.chownSync(fooPath, expectUID, expectGID, { recursive: true });
+
+  paths.forEach((p) => {
+    const stat = fs.lstatSync(path.join(fooPath, p));
+    assert.ok(stat.uid === expectUID,
+              `uid for ${p} should equal ${expectUID}`);
+    assert.ok(stat.gid === expectGID,
+              `gid for ${p} should equal ${expectGID}`);
   });
-
-  fs.rmdirSync('foo');
-
 }
-
+/*
 // test async version.
 {
   makeDirectories();
-  fs.chown(tmpdir.path, 1, 1, { recursive: true }, common.mustCall(() => {
+  fs.chown(tmpdir.path, expectUID, expectGID, { recursive: true }, common.mustCall(() => {
     filePaths.forEach((pathToCheck) => {
       const stat = fs.lstatSync(pathToCheck);
-      assert.ok(stat.uid === 1, `uid for ${pathToCheck} should equal 1`);
-      assert.ok(stat.gid === 1, `gid for ${pathToCheck} should equal 1`);
+      assert.ok(stat.uid === expectUID, `uid for ${pathToCheck} should equal ${expectUID}`);
+      assert.ok(stat.gid === expectGID, `gid for ${pathToCheck} should equal ${expectGID}`);
     });
   }, 1));
 }
+*/

From 9fce609593f536ed9aa647f56440f56ab974d37f Mon Sep 17 00:00:00 2001
From: Giorgos Ntemiris <ntemirisgiorgos3@gmail.com>
Date: Wed, 4 Sep 2019 21:12:23 +0200
Subject: [PATCH 18/19] wip

pass correct options
update asserts
---
 lib/fs.js                                |  2 ++
 lib/internal/fs/chown_recursive.js       | 24 +++++++++++++++++++-----
 test/parallel/test-fs-chown-recursive.js | 14 +++++++-------
 3 files changed, 28 insertions(+), 12 deletions(-)

diff --git a/lib/fs.js b/lib/fs.js
index b6cd615b5b7f69..61e8c7d8157da4 100644
--- a/lib/fs.js
+++ b/lib/fs.js
@@ -1204,6 +1204,8 @@ function chownSync(path, uid, gid, options) {
   validateUint32(uid, 'uid');
   validateUint32(gid, 'gid');
 
+  options = validateChownOptions(options);
+
   if (options.recursive) {
     lazyLoadChownR();
     return chownRSync(path, uid, gid, options);
diff --git a/lib/internal/fs/chown_recursive.js b/lib/internal/fs/chown_recursive.js
index 34326fd70abe4c..1ce90e30ba2e9a 100644
--- a/lib/internal/fs/chown_recursive.js
+++ b/lib/internal/fs/chown_recursive.js
@@ -9,21 +9,35 @@ const {
   chownSync
 } = require('fs');
 
-const { join } = require('path');
+const { join, resolve } = require('path');
 const notEmptyErrorCodes = new Set(['EEXIST']);
 const { setTimeout } = require('timers');
 
 function chownRSync(path, uid, gid, options) {
   const stats = lstatSync(path);
   if (stats !== undefined && stats.isDirectory()) {
-    const files = readdirSync(path);
-    files.forEach((file) =>
-      chownRSync(path, uid, gid, options));
+    const childrenPaths = readdirSync(path);
+    childrenPaths.forEach((childPath) =>
+      _chownRChildrenSync(path, childPath, uid, gid, options));
   } else {
-    chownSync(path, uid, gid, options);
+    chownSync(path, uid, gid);
   }
 }
 
+function _chownRChildrenSync(path, childPath, uid, gid, options) {
+  if (typeof childPath === 'string') {
+    const stats = lstatSync(resolve(path, childPath));
+    stats.name = childPath;
+    childPath = stats;
+  }
+
+  if (childPath.isDirectory()) {
+    chownRSync(resolve(path, childPath.name), uid, gid, options);
+  }
+
+  chownSync(path, uid, gid);
+}
+
 function _chownChildren(path, uid, gid, options, callback) {
   readdir(path, (err, files) => {
     if (err)
diff --git a/test/parallel/test-fs-chown-recursive.js b/test/parallel/test-fs-chown-recursive.js
index 6b23cc7b372845..b69c20193dd68c 100644
--- a/test/parallel/test-fs-chown-recursive.js
+++ b/test/parallel/test-fs-chown-recursive.js
@@ -12,9 +12,9 @@ tmpdir.refresh();
 const dirname = 'chown-recursive';
 
 const paths = [
-  'foo/bar/file1.test',
-  'foo/bar',
-  'foo/file2.test',
+  'bar/file1.test',
+  'bar',
+  'file2.test',
   'foo'
 ];
 
@@ -79,10 +79,10 @@ function makeDirectories() {
 
   paths.forEach((p) => {
     const stat = fs.lstatSync(path.join(fooPath, p));
-    assert.ok(stat.uid === expectUID,
-              `uid for ${p} should equal ${expectUID}`);
-    assert.ok(stat.gid === expectGID,
-              `gid for ${p} should equal ${expectGID}`);
+    assert.strictEqual(stat.uid, expectUID,
+                       `uid for ${p} should equal ${expectUID}`);
+    assert.strictEqual(stat.gid, expectGID,
+                       `gid for ${p} should equal ${expectGID}`);
   });
 }
 /*

From 173cd605ad36d4cfe5dfbb6e77f5309700fb8a40 Mon Sep 17 00:00:00 2001
From: Giorgos Ntemiris <ntemirisgiorgos3@gmail.com>
Date: Sun, 8 Sep 2019 18:20:11 +0200
Subject: [PATCH 19/19] wip

---
 lib/fs.js                                |  4 +--
 lib/internal/fs/chown_recursive.js       |  4 +--
 test/parallel/test-fs-chown-recursive.js | 33 +++++++++++++++++-------
 3 files changed, 27 insertions(+), 14 deletions(-)

diff --git a/lib/fs.js b/lib/fs.js
index 61e8c7d8157da4..9c6a1e1f878bf6 100644
--- a/lib/fs.js
+++ b/lib/fs.js
@@ -1191,7 +1191,7 @@ function chown(path, uid, gid, options, callback) {
 
   if (options.recursive) {
     lazyLoadChownR();
-    return chownR(path, uid, gid, options, callback);
+    chownR(path, uid, gid, options, callback);
   }
 
   const req = new FSReqCallback();
@@ -1208,7 +1208,7 @@ function chownSync(path, uid, gid, options) {
 
   if (options.recursive) {
     lazyLoadChownR();
-    return chownRSync(path, uid, gid, options);
+    chownRSync(path, uid, gid, options);
   }
 
   const ctx = { path };
diff --git a/lib/internal/fs/chown_recursive.js b/lib/internal/fs/chown_recursive.js
index 1ce90e30ba2e9a..540af8a758afbf 100644
--- a/lib/internal/fs/chown_recursive.js
+++ b/lib/internal/fs/chown_recursive.js
@@ -16,7 +16,7 @@ const { setTimeout } = require('timers');
 function chownRSync(path, uid, gid, options) {
   const stats = lstatSync(path);
   if (stats !== undefined && stats.isDirectory()) {
-    const childrenPaths = readdirSync(path);
+    const childrenPaths = readdirSync(path, { withFileTypes: true });
     childrenPaths.forEach((childPath) =>
       _chownRChildrenSync(path, childPath, uid, gid, options));
   } else {
@@ -35,7 +35,7 @@ function _chownRChildrenSync(path, childPath, uid, gid, options) {
     chownRSync(resolve(path, childPath.name), uid, gid, options);
   }
 
-  chownSync(path, uid, gid);
+  chownSync(resolve(path, childPath.name), uid, gid);
 }
 
 function _chownChildren(path, uid, gid, options, callback) {
diff --git a/test/parallel/test-fs-chown-recursive.js b/test/parallel/test-fs-chown-recursive.js
index b69c20193dd68c..2c8a663b0ab55b 100644
--- a/test/parallel/test-fs-chown-recursive.js
+++ b/test/parallel/test-fs-chown-recursive.js
@@ -7,28 +7,37 @@ const fs = require('fs');
 const path = require('path');
 const { validateChownOptions } = require('internal/fs/utils');
 
-tmpdir.refresh();
-
 const dirname = 'chown-recursive';
 
+/**
+ * Temporary dir structure
+ *
+ * .tmp.0
+ *    └── chown-recursive
+ *        └── foo
+ *            ├── bar
+ *            │   └── file1.test
+ *            └── file2.test
+ */
+
 const paths = [
   'bar/file1.test',
   'bar',
   'file2.test',
-  'foo'
+  '.' // refers to foo
 ];
 
 const expectUID = 1;
 const expectGID = 1;
 
 const mainPath = path.join(tmpdir.path, dirname);
-const fooPath = path.join(mainPath, 'foo');
+const fooPath = path.resolve(mainPath, 'foo');
 
 function makeDirectories() {
   fs.mkdirSync(fooPath, { recursive: true });
-  fs.mkdirSync(path.join(fooPath, 'bar'));
-  fs.writeFileSync(path.join(fooPath, 'bar', 'file1.test'), 'file1');
-  fs.writeFileSync(path.join(fooPath, 'file2.test'), 'file2');
+  fs.mkdirSync(path.resolve(fooPath, 'bar'));
+  fs.writeFileSync(path.resolve(fooPath, 'bar', 'file1.test'), 'file1');
+  fs.writeFileSync(path.resolve(fooPath, 'file2.test'), 'file2');
 }
 
 
@@ -72,17 +81,21 @@ function makeDirectories() {
 
 // Test the synchronous version.
 {
+
   makeDirectories();
 
   // Recursive chown should succeed.
   fs.chownSync(fooPath, expectUID, expectGID, { recursive: true });
 
   paths.forEach((p) => {
-    const stat = fs.lstatSync(path.join(fooPath, p));
+    const pathToEvaluate = path.join(fooPath, p);
+    const stat = fs.lstatSync(pathToEvaluate);
     assert.strictEqual(stat.uid, expectUID,
-                       `uid for ${p} should equal ${expectUID}`);
+                       `uid for ${p} should equal ${expectUID} \
+                       for path ${pathToEvaluate}`);
     assert.strictEqual(stat.gid, expectGID,
-                       `gid for ${p} should equal ${expectGID}`);
+                       `gid for ${p} should equal ${expectGID} \
+                       for path ${pathToEvaluate}`);
   });
 }
 /*