Issue #15177: Added dir_fd parameter to os.fwalk().
diff --git a/Lib/os.py b/Lib/os.py
index f990627..381737f 100644
--- a/Lib/os.py
+++ b/Lib/os.py
@@ -422,9 +422,9 @@
 
 __all__.append("walk")
 
-if open in supports_dir_fd:
+if {open, stat} <= supports_dir_fd and {listdir, stat} <= supports_fd:
 
-    def fwalk(top, topdown=True, onerror=None, followlinks=False):
+    def fwalk(top=".", topdown=True, onerror=None, followlinks=False, *, dir_fd=None):
         """Directory tree generator.
 
         This behaves exactly like walk(), except that it yields a 4-tuple
@@ -434,9 +434,13 @@
         `dirpath`, `dirnames` and `filenames` are identical to walk() output,
         and `dirfd` is a file descriptor referring to the directory `dirpath`.
 
-        The advantage of walkfd() over walk() is that it's safe against symlink
+        The advantage of fwalk() over walk() is that it's safe against symlink
         races (when followlinks is False).
 
+        If dir_fd is not None, it should be a file descriptor open to a directory,
+          and top should be relative; top will then be relative to that directory.
+          (dir_fd is always supported for fwalk.)
+
         Caution:
         Since fwalk() yields file descriptors, those are only valid until the
         next iteration step, so you should dup() them if you want to keep them
@@ -455,11 +459,11 @@
         """
         # Note: To guard against symlink races, we use the standard
         # lstat()/open()/fstat() trick.
-        orig_st = lstat(top)
-        topfd = open(top, O_RDONLY)
+        orig_st = stat(top, follow_symlinks=False, dir_fd=dir_fd)
+        topfd = open(top, O_RDONLY, dir_fd=dir_fd)
         try:
             if (followlinks or (st.S_ISDIR(orig_st.st_mode) and
-                                path.samestat(orig_st, fstat(topfd)))):
+                                path.samestat(orig_st, stat(topfd)))):
                 yield from _fwalk(topfd, top, topdown, onerror, followlinks)
         finally:
             close(topfd)
@@ -502,7 +506,7 @@
                     onerror(err)
                 return
             try:
-                if followlinks or path.samestat(orig_st, fstat(dirfd)):
+                if followlinks or path.samestat(orig_st, stat(dirfd)):
                     dirpath = path.join(toppath, name)
                     yield from _fwalk(dirfd, dirpath, topdown, onerror, followlinks)
             finally:
diff --git a/Lib/test/test_os.py b/Lib/test/test_os.py
index 62a7dad..57de993 100644
--- a/Lib/test/test_os.py
+++ b/Lib/test/test_os.py
@@ -741,19 +741,38 @@
 class FwalkTests(WalkTests):
     """Tests for os.fwalk()."""
 
-    def test_compare_to_walk(self):
-        # compare with walk() results
+    def _compare_to_walk(self, walk_kwargs, fwalk_kwargs):
+        """
+        compare with walk() results.
+        """
         for topdown, followlinks in itertools.product((True, False), repeat=2):
-            args = support.TESTFN, topdown, None, followlinks
+            d = {'topdown': topdown, 'followlinks': followlinks}
+            walk_kwargs.update(d)
+            fwalk_kwargs.update(d)
+
             expected = {}
-            for root, dirs, files in os.walk(*args):
+            for root, dirs, files in os.walk(**walk_kwargs):
                 expected[root] = (set(dirs), set(files))
 
-            for root, dirs, files, rootfd in os.fwalk(*args):
+            for root, dirs, files, rootfd in os.fwalk(**fwalk_kwargs):
                 self.assertIn(root, expected)
                 self.assertEqual(expected[root], (set(dirs), set(files)))
 
+    def test_compare_to_walk(self):
+        kwargs = {'top': support.TESTFN}
+        self._compare_to_walk(kwargs, kwargs)
+
     def test_dir_fd(self):
+        try:
+            fd = os.open(".", os.O_RDONLY)
+            walk_kwargs = {'top': support.TESTFN}
+            fwalk_kwargs = walk_kwargs.copy()
+            fwalk_kwargs['dir_fd'] = fd
+            self._compare_to_walk(walk_kwargs, fwalk_kwargs)
+        finally:
+            os.close(fd)
+
+    def test_yields_correct_dir_fd(self):
         # check returned file descriptors
         for topdown, followlinks in itertools.product((True, False), repeat=2):
             args = support.TESTFN, topdown, None, followlinks