[Python-checkins] cpython: Issue #14626: Large refactoring of functions / parameters in the os module.

larry.hastings python-checkins at python.org
Sat Jun 23 01:30:50 CEST 2012


http://hg.python.org/cpython/rev/27f9c26fdd8b
changeset:   77597:27f9c26fdd8b
user:        Larry Hastings <larry at hastings.org>
date:        Fri Jun 22 16:30:09 2012 -0700
summary:
  Issue #14626: Large refactoring of functions / parameters in the os module.
Many functions now support "dir_fd" and "follow_symlinks" parameters;
some also support accepting an open file descriptor in place of of a path
string.  Added os.support_* collections as LBYL helpers.  Removed many
functions only previously seen in 3.3 alpha releases (often starting with
"f" or "l", or ending with "at").  Originally suggested by Serhiy Storchaka;
implemented by Larry Hastings.

files:
  Doc/library/os.rst      |   952 ++--
  Lib/os.py               |   117 +-
  Lib/shutil.py           |    61 +-
  Lib/test/support.py     |     4 +-
  Lib/test/test_os.py     |   166 +-
  Lib/test/test_posix.py  |   224 +-
  Lib/test/test_shutil.py |    12 +-
  Misc/NEWS               |    10 +-
  Modules/posixmodule.c   |  5288 ++++++++++++++------------
  9 files changed, 3603 insertions(+), 3231 deletions(-)


diff --git a/Doc/library/os.rst b/Doc/library/os.rst
--- a/Doc/library/os.rst
+++ b/Doc/library/os.rst
@@ -627,20 +627,6 @@
 descriptor directly will bypass the file object methods, ignoring aspects such
 as internal buffering of data.
 
-.. data:: AT_SYMLINK_NOFOLLOW
-          AT_EACCESS
-          AT_FDCWD
-          AT_REMOVEDIR
-          AT_SYMLINK_FOLLOW
-          UTIME_NOW
-          UTIME_OMIT
-
-   These parameters are used as flags to the \*at family of functions.
-
-   Availability: Unix.
-
-   .. versionadded:: 3.3
-
 
 .. function:: close(fd)
 
@@ -690,58 +676,23 @@
    Availability: Unix, Windows.
 
 
-.. function:: faccessat(dirfd, path, mode, flags=0)
-
-   Like :func:`access` but if *path* is relative, it is taken as relative to *dirfd*.
-   *flags* is optional and can be constructed by ORing together zero or more
-   of these values: :data:`AT_SYMLINK_NOFOLLOW`, :data:`AT_EACCESS`.
-   If *path* is relative and *dirfd* is the special value :data:`AT_FDCWD`, then *path*
-   is interpreted relative to the current working directory.
-
-   Availability: Unix.
-
-   .. versionadded:: 3.3
-
-
 .. function:: fchmod(fd, mode)
 
    Change the mode of the file given by *fd* to the numeric *mode*.  See the docs
    for :func:`chmod` for possible values of *mode*.
+   Equivalent to ``os.chmod(fd, mode)``.
 
    Availability: Unix.
 
 
-.. function:: fchmodat(dirfd, path, mode, flags=0)
-
-   Like :func:`chmod` but if *path* is relative, it is taken as relative to *dirfd*.
-   *flags* is optional and may be 0 or :data:`AT_SYMLINK_NOFOLLOW`.
-   If *path* is relative and *dirfd* is the special value :data:`AT_FDCWD`, then *path*
-   is interpreted relative to the current working directory.
+.. function:: fchown(fd, uid, gid)
+
+   Change the owner and group id of the file given by *fd* to the numeric *uid*
+   and *gid*.  To leave one of the ids unchanged, set it to -1.  Equivalent
+   to ``os.chown(fd, uid, gid)``.
 
    Availability: Unix.
 
-   .. versionadded:: 3.3
-
-
-.. function:: fchown(fd, uid, gid)
-
-   Change the owner and group id of the file given by *fd* to the numeric *uid*
-   and *gid*.  To leave one of the ids unchanged, set it to -1.
-
-   Availability: Unix.
-
-
-.. function:: fchownat(dirfd, path, uid, gid, flags=0)
-
-   Like :func:`chown` but if *path* is relative, it is taken as relative to *dirfd*.
-   *flags* is optional and may be 0 or :data:`AT_SYMLINK_NOFOLLOW`.
-   If *path* is relative and *dirfd* is the special value :data:`AT_FDCWD`, then *path*
-   is interpreted relative to the current working directory.
-
-   Availability: Unix.
-
-   .. versionadded:: 3.3
-
 
 .. function:: fdatasync(fd)
 
@@ -754,47 +705,6 @@
       This function is not available on MacOS.
 
 
-.. function:: fgetxattr(fd, attr)
-
-   This works exactly like :func:`getxattr` but operates on a file descriptor,
-   *fd*, instead of a path.
-
-   Availability: Linux
-
-   .. versionadded:: 3.3
-
-
-.. function:: flistxattr(fd)
-
-   This is exactly like :func:`listxattr` but operates on a file descriptor,
-   *fd*, instead of a path.
-
-   Availability: Linux
-
-   .. versionadded:: 3.3
-
-
-.. function:: flistdir(fd)
-
-   Like :func:`listdir`, but uses a file descriptor instead and always returns
-   strings.
-
-   Availability: Unix.
-
-   .. versionadded:: 3.3
-
-
-.. function:: fexecve(fd, args, env)
-
-   Execute the program specified by a file descriptor *fd* with arguments given
-   by *args* and environment given by *env*, replacing the current process.
-   *args* and *env* are given as in :func:`execve`.
-
-   Availability: Unix.
-
-   .. versionadded:: 3.3
-
-
 .. function:: fpathconf(fd, name)
 
    Return system configuration information relevant to an open file. *name*
@@ -819,18 +729,6 @@
 
    Availability: Unix, Windows.
 
-.. function:: fstatat(dirfd, path, flags=0)
-
-   Like :func:`stat` but if *path* is relative, it is taken as relative to *dirfd*.
-   *flags* is optional and may be 0 or :data:`AT_SYMLINK_NOFOLLOW`.
-   If *path* is relative and *dirfd* is the special value :data:`AT_FDCWD`, then *path*
-   is interpreted relative to the current working directory.
-
-   Availability: Unix.
-
-   .. versionadded:: 3.3
-
-
 .. function:: fstatvfs(fd)
 
    Return information about the filesystem containing the file associated with file
@@ -859,78 +757,6 @@
    Availability: Unix.
 
 
-.. function:: fremovexattr(fd, attr)
-
-   This works exactly like :func:`removexattr` but operates on a file
-   descriptor, *fd*, instead of a path.
-
-   Availability: Linux
-
-   .. versionadded:: 3.3
-
-
-.. function:: fsetxattr(fd, attr, value, flags=0)
-
-   This works exactly like :func:`setxattr` but on a file descriptor, *fd*,
-   instead of a path.
-
-
-   Availability: Linux
-
-   .. versionadded:: 3.3
-
-
-.. function:: futimesat(dirfd, path[, times])
-
-   Like :func:`utime` but if *path* is relative, it is taken as relative to *dirfd*.
-   If *path* is relative and *dirfd* is the special value :data:`AT_FDCWD`, then *path*
-   is interpreted relative to the current working directory.  *times* must be a
-   2-tuple of numbers, of the form ``(atime, mtime)``, or None.
-
-   Availability: Unix.
-
-   .. versionadded:: 3.3
-
-
-.. function:: futimens(fd[, atimes, mtimes])
-
-   Updates the timestamps of a file specified by the file descriptor *fd*, with
-   nanosecond precision.
-   If no second argument is given, set *atime* and *mtime* to the current time.
-   *atimes* and *mtimes* must be 2-tuples of numbers, of the form
-   ``(atime_sec, atime_nsec)`` and ``(mtime_sec, mtime_nsec)`` respectively,
-   or ``None``.
-   If *atime_nsec* or *mtime_nsec* is specified as :data:`UTIME_NOW`, the corresponding
-   timestamp is updated to the current time.
-   If *atime_nsec* or *mtime_nsec* is specified as :data:`UTIME_OMIT`, the corresponding
-   timestamp is not updated.
-
-   Availability: Unix.
-
-   .. versionadded:: 3.3
-
-
-.. data:: UTIME_NOW
-          UTIME_OMIT
-
-   Flags used with :func:`futimens` to specify that the timestamp must be
-   updated either to the current time or not updated at all.
-
-   Availability: Unix.
-
-   .. versionadded:: 3.3
-
-
-.. function:: futimes(fd[, times, *, ns=times])
-
-   Set the access and modified time of the file specified by the file
-   descriptor *fd* to the given values.  See :func:`utime` for proper
-   use of the *times* and *ns* arguments.
-   Availability: Unix.
-
-   .. versionadded:: 3.3
-
-
 .. function:: isatty(fd)
 
    Return ``True`` if the file descriptor *fd* is open and connected to a
@@ -939,20 +765,6 @@
    Availability: Unix.
 
 
-.. function:: linkat(srcfd, srcpath, dstfd, dstpath, flags=0)
-
-   Like :func:`link` but if *srcpath* is relative, it is taken as relative to *srcfd*
-   and if *dstpath* is relative, it is taken as relative to *dstfd*.
-   *flags* is optional and may be 0 or :data:`AT_SYMLINK_FOLLOW`.
-   If *srcpath* is relative and *srcfd* is the special value :data:`AT_FDCWD`, then
-   *srcpath* is interpreted relative to the current working directory. This
-   also applies for *dstpath*.
-
-   Availability: Unix.
-
-   .. versionadded:: 3.3
-
-
 .. function:: lockf(fd, cmd, len)
 
    Apply, test or remove a POSIX lock on an open file descriptor.
@@ -1000,51 +812,25 @@
       :data:`os.SEEK_HOLE` or :data:`os.SEEK_DATA`.
 
 
-.. function:: mkdirat(dirfd, path, mode=0o777)
-
-   Like :func:`mkdir` but if *path* is relative, it is taken as relative to *dirfd*.
-   If *path* is relative and *dirfd* is the special value :data:`AT_FDCWD`, then *path*
-   is interpreted relative to the current working directory.
-
-   Availability: Unix.
-
-   .. versionadded:: 3.3
-
-
-.. function:: mkfifoat(dirfd, path, mode=0o666)
-
-   Like :func:`mkfifo` but if *path* is relative, it is taken as relative to *dirfd*.
-   If *path* is relative and *dirfd* is the special value :data:`AT_FDCWD`, then *path*
-   is interpreted relative to the current working directory.
-
-   Availability: Unix.
-
-   .. versionadded:: 3.3
-
-
-.. function:: mknodat(dirfd, path, mode=0o600, device=0)
-
-   Like :func:`mknod` but if *path* is relative, it is taken as relative to *dirfd*.
-   If *path* is relative and *dirfd* is the special value :data:`AT_FDCWD`, then *path*
-   is interpreted relative to the current working directory.
-
-   Availability: Unix.
-
-   .. versionadded:: 3.3
-
-
-.. function:: open(file, flags[, mode])
+.. function:: open(file, flags, mode=0o777, *, dir_fd=None)
 
    Open the file *file* and set various flags according to *flags* and possibly
-   its mode according to *mode*.  The default *mode* is ``0o777`` (octal), and
-   the current umask value is first masked out.  Return the file descriptor for
-   the newly opened file.
+   its mode according to *mode*.  When computing *mode*, the current umask value
+   is first masked out.  Return the file descriptor for the newly opened file.
 
    For a description of the flag and mode values, see the C run-time documentation;
    flag constants (like :const:`O_RDONLY` and :const:`O_WRONLY`) are defined in
    this module too (see :ref:`open-constants`).  In particular, on Windows adding
    :const:`O_BINARY` is needed to open files in binary mode.
 
+   If *dir_fd* is not ``None``, it should be a file descriptor referring to a
+   directory, and *path* should be relative; path will then be relative to
+   that directory.  (If *path* is absolute, *dir_fd* is ignored.)
+   *dir_fd* may not be supported on your platform;
+   you can check whether or not it is available using
+   :data:`os.supports_dir_fd`.  If it is unavailable, using it will raise
+   a :exc:`NotImplementedError`.
+
    Availability: Unix, Windows.
 
    .. note::
@@ -1054,16 +840,8 @@
       :meth:`~file.read` and :meth:`~file.write` methods (and many more).  To
       wrap a file descriptor in a file object, use :func:`fdopen`.
 
-
-.. function:: openat(dirfd, path, flags, mode=0o777)
-
-   Like :func:`open` but if *path* is relative, it is taken as relative to *dirfd*.
-   If *path* is relative and *dirfd* is the special value :data:`AT_FDCWD`, then *path*
-   is interpreted relative to the current working directory.
-
-   Availability: Unix.
-
    .. versionadded:: 3.3
+      The *dir_fd* argument.
 
 
 .. function:: openpty()
@@ -1216,41 +994,6 @@
    .. versionadded:: 3.3
 
 
-.. function:: readlinkat(dirfd, path)
-
-   Like :func:`readlink` but if *path* is relative, it is taken as relative to *dirfd*.
-   If *path* is relative and *dirfd* is the special value :data:`AT_FDCWD`, then *path*
-   is interpreted relative to the current working directory.
-
-   Availability: Unix.
-
-   .. versionadded:: 3.3
-
-
-.. function:: renameat(olddirfd, oldpath, newdirfd, newpath)
-
-   Like :func:`rename` but if *oldpath* is relative, it is taken as relative to
-   *olddirfd* and if *newpath* is relative, it is taken as relative to *newdirfd*.
-   If *oldpath* is relative and *olddirfd* is the special value :data:`AT_FDCWD`, then
-   *oldpath* is interpreted relative to the current working directory. This
-   also applies for *newpath*.
-
-   Availability: Unix.
-
-   .. versionadded:: 3.3
-
-
-.. function:: symlinkat(src, dstfd, dst)
-
-   Like :func:`symlink` but if *dst* is relative, it is taken as relative to *dstfd*.
-   If *dst* is relative and *dstfd* is the special value :data:`AT_FDCWD`, then *dst*
-   is interpreted relative to the current working directory.
-
-   Availability: Unix.
-
-   .. versionadded:: 3.3
-
-
 .. function:: readv(fd, buffers)
 
    Read from a file descriptor into a number of writable buffers. *buffers* is
@@ -1287,38 +1030,6 @@
    Availability: Unix.
 
 
-.. function:: unlinkat(dirfd, path, flags=0)
-
-   Like :func:`unlink` but if *path* is relative, it is taken as relative to *dirfd*.
-   *flags* is optional and may be 0 or :data:`AT_REMOVEDIR`. If :data:`AT_REMOVEDIR` is
-   specified, :func:`unlinkat` behaves like :func:`rmdir`.
-   If *path* is relative and *dirfd* is the special value :data:`AT_FDCWD`, then *path*
-   is interpreted relative to the current working directory.
-
-   Availability: Unix.
-
-   .. versionadded:: 3.3
-
-
-.. function:: utimensat(dirfd, path[, atime=(atime_sec, atime_nsec), mtime=(mtime_sec, mtime_nsec), flags=0])
-
-   Updates the timestamps of a file with nanosecond precision.
-   The *atime* and *mtime* tuples default to ``None``, which sets those
-   values to the current time.
-   If *atime_nsec* or *mtime_nsec* is specified as :data:`UTIME_NOW`, the corresponding
-   timestamp is updated to the current time.
-   If *atime_nsec* or *mtime_nsec* is specified as :data:`UTIME_OMIT`, the corresponding
-   timestamp is not updated.
-   If *path* is relative, it is taken as relative to *dirfd*.
-   *flags* is optional and may be 0 (the default) or :data:`AT_SYMLINK_NOFOLLOW`.
-   If *path* is relative and *dirfd* is the special value :data:`AT_FDCWD`, then *path*
-   is interpreted relative to the current working directory.
-
-   Availability: Unix.
-
-   .. versionadded:: 3.3
-
-
 .. function:: write(fd, str)
 
    Write the bytestring in *str* to file descriptor *fd*. Return the number of
@@ -1460,7 +1171,7 @@
 Files and Directories
 ---------------------
 
-.. function:: access(path, mode)
+.. function:: access(path, mode, *, dir_fd=None, effective_ids=False, follow_symlinks=True)
 
    Use the real uid/gid to test for access to *path*.  Note that most operations
    will use the effective uid/gid, therefore this routine can be used in a
@@ -1471,6 +1182,27 @@
    :const:`False` if not. See the Unix man page :manpage:`access(2)` for more
    information.
 
+   If *dir_fd* is not ``None``, it should be a file descriptor referring to a
+   directory, and *path* should be relative; path will then be relative to
+   that directory.  (If *path* is absolute, *dir_fd* is ignored.)
+   *dir_fd* may not be supported on your platform;
+   you can check whether or not it is available using
+   :data:`os.supports_dir_fd`.  If it is unavailable, using it will raise
+   a :exc:`NotImplementedError`.
+
+   If *effective_ids* is ``True``, :func:`access` will perform its access
+   checks using the effective uid/gid instead of the real uid/gid.
+   *effective_ids* may not be supported on your platform; you can check whether
+   or not it is available using :data:`os.supports_effective_ids`.  If it is
+   unavailable, using it will raise a :exc:`NotImplementedError`.
+
+   If *follow_symlinks* is ``False``, and the last element of the path is a
+   symbolic link, :func:`access` will examine the symbolic link itself instead
+   of the file the link points to.  *follow_symlinks* may not be supported
+   on your platform; you can check whether or not it is available using
+   :data:`os.supports_follow_symlinks`.  If it is unavailable,
+   using it will raise a :exc:`NotImplementedError`.
+
    Availability: Unix, Windows.
 
    .. note::
@@ -1502,6 +1234,9 @@
       succeed, particularly for operations on network filesystems which may have
       permissions semantics beyond the usual POSIX permission-bit model.
 
+   .. versionchanged:: 3.3
+      Added the *dir_fd*, *effective_ids*, and *follow_symlinks* parameters.
+
 
 .. data:: F_OK
 
@@ -1533,14 +1268,24 @@
 
    Change the current working directory to *path*.
 
+   On some platforms, *path* may also be specified as an open file descriptor.
+   This functionality may not be supported on your platform; you can check
+   whether or not it is available using :data:`os.supports_fd`.  If it is
+   unavailable, using it will raise a :exc:`NotImplementedError`.
+
    Availability: Unix, Windows.
 
+   .. versionadded:: 3.3
+      Added support for specifying *path* as a file descriptor
+      on some platforms, and the *dir_fd*, *effective_ids*, and
+      *follow_symlinks* parameters.
+
 
 .. function:: fchdir(fd)
 
    Change the current working directory to the directory represented by the file
    descriptor *fd*.  The descriptor must refer to an opened directory, not an open
-   file.
+   file.  Equivalent to ``os.chdir(fd)``.
 
    Availability: Unix.
 
@@ -1559,7 +1304,7 @@
    Availability: Unix, Windows.
 
 
-.. function:: chflags(path, flags)
+.. function:: chflags(path, flags, *, follow_symlinks=True)
 
    Set the flags of *path* to the numeric *flags*. *flags* may take a combination
    (bitwise OR) of the following values (as defined in the :mod:`stat` module):
@@ -1577,8 +1322,18 @@
    * :data:`stat.SF_NOUNLINK`
    * :data:`stat.SF_SNAPSHOT`
 
+   If *follow_symlinks* is ``False``, and the last element of the path is a
+   symbolic link, :func:`follow_symlinks` will examine the symbolic link itself
+   instead of the file the link points to.  *follow_symlinks* may not be
+   supported on your platform; you can check whether or not it is available
+   using :data:`os.supports_follow_symlinks`.  If it is unavailable,
+   using it will raise a :exc:`NotImplementedError`.
+
    Availability: Unix.
 
+   .. versionadded:: 3.3
+      The *follow_symlinks* argument.
+
 
 .. function:: chroot(path)
 
@@ -1586,7 +1341,7 @@
    Unix.
 
 
-.. function:: chmod(path, mode)
+.. function:: chmod(path, mode, *, dir_fd=None, follow_symlinks=True)
 
    Change the mode of *path* to the numeric *mode*. *mode* may take one of the
    following values (as defined in the :mod:`stat` module) or bitwise ORed
@@ -1612,6 +1367,29 @@
    * :data:`stat.S_IWOTH`
    * :data:`stat.S_IXOTH`
 
+   On some platforms, *path* may also be specified as an open file descriptor.
+   This functionality may not be supported on your platform; you can check
+   whether or not it is available using :data:`os.supports_fd`.  If it is
+   unavailable, using it will raise a :exc:`NotImplementedError`.
+
+   If *dir_fd* is not ``None``, it should be a file descriptor referring to a
+   directory, and *path* should be relative; path will then be relative to
+   that directory.  (If *path* is absolute, *dir_fd* is ignored.)
+   *dir_fd* may not be supported on your platform;
+   you can check whether or not it is available using
+   :data:`os.supports_dir_fd`.  If it is unavailable, using it will raise
+   a :exc:`NotImplementedError`.
+
+   If *follow_symlinks* is ``False``, and the last element of the path is a
+   symbolic link, :func:`chmod` will examine the symbolic link itself instead
+   of the file the link points to.  *follow_symlinks* may not be supported
+   on your platform; you can check whether or not it is available using
+   :data:`os.supports_follow_symlinks`.  If it is unavailable,
+   using it will raise a :exc:`NotImplementedError`.
+
+   It is an error to use *dir_fd* or *follow_symlinks* when specifying
+   *path* as an open file descriptor.
+
    Availability: Unix, Windows.
 
    .. note::
@@ -1621,23 +1399,61 @@
       constants or a corresponding integer value).  All other bits are
       ignored.
 
-
-.. function:: chown(path, uid, gid)
+   .. versionadded:: 3.3
+      Added support for specifying *path* as an open file descriptor,
+      and the *dir_fd* and *follow_symlinks* arguments.
+
+
+.. function:: chown(path, uid, gid, *, dir_fd=None, follow_symlinks=True)
 
    Change the owner and group id of *path* to the numeric *uid* and *gid*. To leave
    one of the ids unchanged, set it to -1.
 
+   On some platforms, *path* may also be specified as an open file descriptor.
+   This functionality may not be supported on your platform; you can check
+   whether or not it is available using :data:`os.supports_fd`.  If it is
+   unavailable, using it will raise a :exc:`NotImplementedError`.
+
+   If *dir_fd* is not ``None``, it should be a file descriptor referring to a
+   directory, and *path* should be relative; path will then be relative to
+   that directory.  (If *path* is absolute, *dir_fd* is ignored.)
+   *dir_fd* may not be supported on your platform;
+   you can check whether or not it is available using
+   :data:`os.supports_dir_fd`.  If it is unavailable, using it will raise
+   a :exc:`NotImplementedError`.
+
+   If *follow_symlinks* is ``False``, and the last element of the path is a
+   symbolic link, :func:`chown` will examine the symbolic link itself instead
+   of the file the link points to.  *follow_symlinks* may not be supported
+   on your platform; you can check whether or not it is available using
+   :data:`os.supports_follow_symlinks`.  If it is unavailable,
+   using it will raise a :exc:`NotImplementedError`.
+
+   It is an error to use *dir_fd* or *follow_symlinks* when specifying
+   *path* as an open file descriptor.
+
    See :func:`shutil.chown` for a higher-level function that accepts names in
    addition to numeric ids.
 
    Availability: Unix.
 
-
-.. function:: getxattr(path, attr)
-
-   Return the value of the extended filesystem attribute *attr* for
-   *path*. *attr* can be bytes or str. If it is str, it is encoded with the
-   filesystem encoding.
+   .. versionadded:: 3.3
+      Added support for specifying an open file descriptor for *path*,
+      and the *dir_fd* and *follow_symlinks* arguments.
+
+
+.. function:: getxattr(path, attribute, *, follow_symlinks=True)
+
+   Return the value of the extended filesystem attribute *attribute* for
+   *path*. *attribute* can be bytes or str. If it is str, it is encoded
+   with the filesystem encoding.
+
+   *path* may be specified as either a string or an open file descriptor.
+
+   If *follow_symlinks* is ``False``, and the last element of the path is a
+   symbolic link, :func:`setxattr` will examine the symbolic link itself
+   instead of the file the link points to.  It is an error to use
+   *follow_symlinks* when specifying *path* as an open file descriptor.
 
    Availability: Linux
 
@@ -1648,6 +1464,7 @@
 
    Set the flags of *path* to the numeric *flags*, like :func:`chflags`, but do not
    follow symbolic links.
+   Equivalent to ``os.chflags(path, flags, follow_symlinks=False)``.
 
    Availability: Unix.
 
@@ -1657,6 +1474,7 @@
    Change the mode of *path* to the numeric *mode*. If path is a symlink, this
    affects the symlink rather than the target. See the docs for :func:`chmod`
    for possible values of *mode*.
+   Equivalent to ``os.chmod(path, mode, follow_symlinks=False)``.
 
    Availability: Unix.
 
@@ -1665,28 +1483,39 @@
 
    Change the owner and group id of *path* to the numeric *uid* and *gid*. This
    function will not follow symbolic links.
+   Equivalent to ``os.chown(path, uid, gid, follow_symlinks=False)``.
 
    Availability: Unix.
 
 
-.. function:: lgetxattr(path, attr)
-
-   This works exactly like :func:`getxattr` but doesn't follow symlinks.
-
-   Availability: Linux
-
-   .. versionadded:: 3.3
-
-
-.. function:: link(source, link_name)
-
-   Create a hard link pointing to *source* named *link_name*.
+.. function:: link(src, dst, *, src_dir_fd=None, dst_dir_fd=None, follow_symlinks=True)
+
+   Create a hard link pointing to *src* named *dst*.
+
+   If either *src_dir_fd* or *dst_dir_fd* is not ``None``, it should be a
+   file descriptor referring to a directory, and the corresponding path
+   (*src* or *dst*) should be relative; that path will then be relative to
+   that directory.  (If *src* is absolute, *src_dir_fd* is ignored; the same
+   goes for *dst* and *dst_dir_fd*.)
+   *src_dir_fd* and *dst_dir_fd* may not be supported on your platform;
+   you can check whether or not they are available using :data:`os.supports_dir_fd`.
+   If they are unavailable, using either will raise a :exc:`NotImplementedError`.
+
+   If *follow_symlinks* is ``False``, and the last element of *src* is a
+   symbolic link, :func:`link` will use the symbolic link itself instead
+   of the file the link points to.  *follow_symlinks* may not be supported
+   on your platform; you can check whether or not it is available using
+   :data:`os.supports_follow_symlinks`.  If it is unavailable,
+   using it will raise a :exc:`NotImplementedError`.
 
    Availability: Unix, Windows.
 
    .. versionchanged:: 3.2
       Added Windows support.
 
+   .. versionadded:: 3.3
+      Added the *src_dir_fd*, *dst_dir_fd*, and *follow_symlinks* arguments.
+
 
 .. function:: listdir(path='.')
 
@@ -1697,76 +1526,73 @@
    This function can be called with a bytes or string argument, and returns
    filenames of the same datatype.
 
+   On some platforms, *path* may also be specified as an open file descriptor.
+   This functionality may not be supported on your platform; you can check
+   whether or not it is available using :data:`os.supports_fd`.  If it is
+   unavailable, using it will raise a :exc:`NotImplementedError`.
+
    Availability: Unix, Windows.
 
    .. versionchanged:: 3.2
       The *path* parameter became optional.
 
-
-.. function:: listxattr(path)
-
-   Return a list of the extended filesystem attributes on *path*. Attributes are
-   returned as string decoded with the filesystem encoding.
+   .. versionadded:: 3.3
+      Added support for specifying an open file descriptor for *path*.
+
+.. function:: listxattr(path=None, *, follow_symlinks=True)
+
+   Return a list of the extended filesystem attributes on *path*.
+   The attributes in the list are represented as strings decoded
+   with the filesystem encoding.
+
+   *path* may be specified as either ``None``, a string, or an open file
+   descriptor.  If *path* is ``None``, :func:`listxattr` will examine the
+   current directory.
+
+   If *follow_symlinks* is ``False``, and the last element of the path is a
+   symbolic link, :func:`listxattr` will examine the symbolic link itself
+   instead of the file the link points to.  It is an error to use
+   *follow_symlinks* when specifying *path* as an open file descriptor.
 
    Availability: Linux
 
    .. versionadded:: 3.3
 
 
-.. function:: llistxattr(path)
-
-   This works exactly like :func:`listxattr` but doesn't follow symlinks.
-
-   Availability: Linux
-
-   .. versionadded:: 3.3
-
-
-.. function:: lremovexattr(path, attr)
-
-   This works exactly like :func:`removexattr` but doesn't follow symlinks.
-
-   Availability: Linux
-
-   .. versionadded:: 3.3
-
-
-.. function:: lsetxattr(path, attr, value, flags=0)
-
-   This works exactly like :func:`setxattr` but doesn't follow symlinks.
-
-   Availability: Linux
-
-   .. versionadded:: 3.3
-
-
-.. function:: lstat(path)
+.. function:: lstat(path, *, dir_fd=None)
 
    Perform the equivalent of an :c:func:`lstat` system call on the given path.
    Similar to :func:`~os.stat`, but does not follow symbolic links.  On
    platforms that do not support symbolic links, this is an alias for
-   :func:`~os.stat`.
+   :func:`~os.stat`.  (Equivalent to ``os.stat(path, follow_symlinks=False)``.)
+
+   If *dir_fd* is not ``None``, it should be a file descriptor referring to a
+   directory, and *path* should be relative; path will then be relative to
+   that directory.  (If *path* is absolute, *dir_fd* is ignored.)
+   *dir_fd* may not be supported on your platform;
+   you can check whether or not it is available using
+   :data:`os.supports_dir_fd`.  If it is unavailable, using it will raise
+   a :exc:`NotImplementedError`.
 
    .. versionchanged:: 3.2
       Added support for Windows 6.0 (Vista) symbolic links.
 
-
-.. function:: lutimes(path[, times, *, ns=times])
-
-   Like :func:`utime`, but if *path* is a symbolic link, it is not
-   dereferenced.  See :func:`utime` for proper use of the
-   *times* and *ns* arguments.
-
-   Availability: Unix.
-
-   .. versionadded:: 3.3
-
-
-.. function:: mkfifo(path[, mode])
-
-   Create a FIFO (a named pipe) named *path* with numeric mode *mode*.  The
-   default *mode* is ``0o666`` (octal).  The current umask value is first masked
-   out from the mode.
+   .. versionchanged:: 3.3
+      Added the *dir_fd* parameter.
+
+
+.. function:: mkfifo(path, mode=0o666, *, dir_fd=None)
+
+   Create a FIFO (a named pipe) named *path* with numeric mode *mode*.
+   The current umask value is first masked out from the mode.
+
+   If *dir_fd* is not ``None``, it should be a file descriptor referring to a
+   directory, and *path* should be relative; path will then be relative to
+   that directory.  (If *path* is absolute, *dir_fd* is ignored.)
+   *dir_fd* may not be supported on your platform;
+   you can check whether or not it is available using
+   :data:`os.supports_dir_fd`.  If it is unavailable, using it will raise
+   a :exc:`NotImplementedError`.
 
    FIFOs are pipes that can be accessed like regular files.  FIFOs exist until they
    are deleted (for example with :func:`os.unlink`). Generally, FIFOs are used as
@@ -1776,8 +1602,11 @@
 
    Availability: Unix.
 
-
-.. function:: mknod(filename[, mode=0o600[, device=0]])
+   .. versionadded:: 3.3
+      The *dir_fd* argument.
+
+
+.. function:: mknod(filename, mode=0o600, device=0, *, dir_fd=None)
 
    Create a filesystem node (file, device special file or named pipe) named
    *filename*. *mode* specifies both the permissions to use and the type of node
@@ -1787,6 +1616,17 @@
    *device* defines the newly created device special file (probably using
    :func:`os.makedev`), otherwise it is ignored.
 
+   If *dir_fd* is not ``None``, it should be a file descriptor referring to a
+   directory, and *path* should be relative; path will then be relative to
+   that directory.  (If *path* is absolute, *dir_fd* is ignored.)
+   *dir_fd* may not be supported on your platform;
+   you can check whether or not it is available using
+   :data:`os.supports_dir_fd`.  If it is unavailable, using it will raise
+   a :exc:`NotImplementedError`.
+
+   .. versionadded:: 3.3
+      The *dir_fd* argument.
+
 
 .. function:: major(device)
 
@@ -1805,18 +1645,30 @@
    Compose a raw device number from the major and minor device numbers.
 
 
-.. function:: mkdir(path[, mode])
-
-   Create a directory named *path* with numeric mode *mode*. The default *mode*
-   is ``0o777`` (octal).  On some systems, *mode* is ignored.  Where it is used,
-   the current umask value is first masked out.  If the directory already
+.. function:: mkdir(path, mode=0o777, *, dir_fd=None)
+
+   Create a directory named *path* with numeric mode *mode*.
+
+   On some systems, *mode* is ignored.  Where it is used, the current
+   umask value is first masked out.  If the directory already
    exists, :exc:`OSError` is raised.
 
+   If *dir_fd* is not ``None``, it should be a file descriptor referring to a
+   directory, and *path* should be relative; path will then be relative to
+   that directory.  (If *path* is absolute, *dir_fd* is ignored.)
+   *dir_fd* may not be supported on your platform;
+   you can check whether or not it is available using
+   :data:`os.supports_dir_fd`.  If it is unavailable, using it will raise
+   a :exc:`NotImplementedError`.
+
    It is also possible to create temporary directories; see the
    :mod:`tempfile` module's :func:`tempfile.mkdtemp` function.
 
    Availability: Unix, Windows.
 
+   .. versionadded:: 3.3
+      The *dir_fd* argument.
+
 
 .. function:: makedirs(path, mode=0o777, exist_ok=False)
 
@@ -1870,7 +1722,7 @@
    Unix.
 
 
-.. function:: readlink(path)
+.. function:: readlink(path, *, dir_fd=None)
 
    Return a string representing the path to which the symbolic link points.  The
    result may be either an absolute or relative pathname; if it is relative, it may
@@ -1881,23 +1733,49 @@
    and the call may raise an UnicodeDecodeError. If the *path* is a bytes
    object, the result will be a bytes object.
 
+   If *dir_fd* is not ``None``, it should be a file descriptor referring to a
+   directory, and *path* should be relative; path will then be relative to
+   that directory.  (If *path* is absolute, *dir_fd* is ignored.)
+   *dir_fd* may not be supported on your platform;
+   you can check whether or not it is available using
+   :data:`os.supports_dir_fd`.  If it is unavailable, using it will raise
+   a :exc:`NotImplementedError`.
+
    Availability: Unix, Windows
 
    .. versionchanged:: 3.2
       Added support for Windows 6.0 (Vista) symbolic links.
 
-
-.. function:: remove(path)
-
-   Remove (delete) the file *path*.  If *path* is a directory, :exc:`OSError` is
-   raised; see :func:`rmdir` below to remove a directory.  This is identical to
-   the :func:`unlink` function documented below.  On Windows, attempting to
-   remove a file that is in use causes an exception to be raised; on Unix, the
-   directory entry is removed but the storage allocated to the file is not made
-   available until the original file is no longer in use.
+   .. versionadded:: 3.3
+      The *dir_fd* argument.
+
+
+.. function:: remove(path, *, dir_fd=None, rmdir=False)
+
+   Remove (delete) the file *path*.  This function is identical to
+   :func:`os.unlink`.
+
+   Specify ``rmdir=True`` if *path* is a directory.  Failing to do so
+   will raise an exception; likewise, specifying ``rmdir=True`` when
+   *path* is not a directory will also raise an exception.
+
+   If *dir_fd* is not ``None``, it should be a file descriptor referring to a
+   directory, and *path* should be relative; path will then be relative to
+   that directory.  (If *path* is absolute, *dir_fd* is ignored.)
+   *dir_fd* may not be supported on your platform;
+   you can check whether or not it is available using
+   :data:`os.supports_dir_fd`.  If it is unavailable, using it will raise
+   a :exc:`NotImplementedError`.
+
+   On Windows, attempting to remove a file that is in use causes an exception to
+   be raised; on Unix, the directory entry is removed but the storage allocated
+   to the file is not made available until the original file is no longer in use.
 
    Availability: Unix, Windows.
 
+   .. versionadded:: 3.3
+      The *dir_fd* and *rmdir* arguments.
+
 
 .. function:: removedirs(path)
 
@@ -1913,18 +1791,26 @@
    successfully removed.
 
 
-.. function:: removexattr(path, attr)
-
-   Removes the extended filesystem attribute *attr* from *path*. *attr* should
-   be bytes or str. If it is a string, it is encoded with the filesystem
-   encoding.
+.. function:: removexattr(path, attribute, *, follow_symlinks=True)
+
+   Removes the extended filesystem attribute *attribute* from *path*.
+   *attribute* should be bytes or str. If it is a string, it is encoded
+   with the filesystem encoding.
+
+   *path* may be specified as either a string or an open file descriptor.
+
+   If *follow_symlinks* is ``False``, and the last element of the path is a
+   symbolic link, :func:`removexattr` will remove the attribute from the
+   symbolic link itself instead of the file the link points to.  It is an
+   error to use *follow_symlinks* when specifying *path* as an open file
+   descriptor.
 
    Availability: Linux
 
    .. versionadded:: 3.3
 
 
-.. function:: rename(src, dst)
+.. function:: rename(src, dst, *, src_dir_fd=None, dst_dir_fd=None)
 
    Rename the file or directory *src* to *dst*.  If *dst* is a directory,
    :exc:`OSError` will be raised.  On Unix, if *dst* exists and is a file, it will
@@ -1934,10 +1820,22 @@
    Windows, if *dst* already exists, :exc:`OSError` will be raised even if it is a
    file.
 
+   If either *src_dir_fd* or *dst_dir_fd* is not ``None``, it should be a
+   file descriptor referring to a directory, and the corresponding path
+   (*src* or *dst*) should be relative; that path will then be relative to
+   that directory.  (If *src* is absolute, *src_dir_fd* is ignored; the same
+   goes for *dst* and *dst_dir_fd*.)
+   *src_dir_fd* and *dst_dir_fd* may not be supported on your platform;
+   you can check whether or not they are available using :data:`os.supports_dir_fd`.
+   If they are unavailable, using either will raise a :exc:`NotImplementedError`.
+
    If you want cross-platform overwriting of the destination, use :func:`replace`.
 
    Availability: Unix, Windows.
 
+   .. versionadded:: 3.3
+      The *src_dir_fd* and *dst_dir_fd* arguments.
+
 
 .. function:: renames(old, new)
 
@@ -1952,7 +1850,7 @@
       permissions needed to remove the leaf directory or file.
 
 
-.. function:: replace(src, dst)
+.. function:: replace(src, dst, *, src_dir_fd=None, dst_dir_fd=None)
 
    Rename the file or directory *src* to *dst*.  If *dst* is a directory,
    :exc:`OSError` will be raised.  If *dst* exists and is a file, it will
@@ -1960,6 +1858,15 @@
    if *src* and *dst* are on different filesystems.  If successful,
    the renaming will be an atomic operation (this is a POSIX requirement).
 
+   If either *src_dir_fd* or *dst_dir_fd* is not ``None``, it should be a
+   file descriptor referring to a directory, and the corresponding path
+   (*src* or *dst*) should be relative; that path will then be relative to
+   that directory.  (If *src* is absolute, *src_dir_fd* is ignored; the same
+   goes for *dst* and *dst_dir_fd*.)
+   *src_dir_fd* and *dst_dir_fd* may not be supported on your platform;
+   you can check whether or not they are available using :data:`os.supports_dir_fd`.
+   If they are unavailable, using either will raise a :exc:`NotImplementedError`.
+
    Availability: Unix, Windows
 
    .. versionadded:: 3.3
@@ -1992,15 +1899,22 @@
    indicates the operation must replace an existing attribute.
 
 
-.. function:: setxattr(path, attr, value, flags=0)
-
-   Set the extended filesystem attribute *attr* on *path* to *value*. *attr*
-   must be a bytes or str with no embedded NULs. If it is str, it is encoded
-   with the filesystem encoding. *flags* may be :data:`XATTR_REPLACE` or
-   :data:`XATTR_CREATE`. If :data:`XATTR_REPLACE` is given and the attribute
-   does not exist, ``EEXISTS`` will be raised. If :data:`XATTR_CREATE` is given
-   and the attribute already exists, the attribute will not be created and
-   ``ENODATA`` will be raised.
+.. function:: setxattr(path, attribute, value, flags=0, *, follow_symlinks=True)
+
+   Set the extended filesystem attribute *attribute* on *path* to *value*.
+   *attribute* must be a bytes or str with no embedded NULs. If it is a str,
+   it is encoded with the filesystem encoding.  *flags* may be
+   :data:`XATTR_REPLACE` or :data:`XATTR_CREATE`. If :data:`XATTR_REPLACE` is
+   given and the attribute does not exist, ``EEXISTS`` will be raised.
+   If :data:`XATTR_CREATE` is given and the attribute already exists, the
+   attribute will not be created and ``ENODATA`` will be raised.
+
+   *path* may be specified as either a string or an open file descriptor.
+
+   If *follow_symlinks* is ``False``, and the last element of the path is a
+   symbolic link, :func:`setxattr` will examine the symbolic link itself
+   instead of the file the link points to.  It is an error to use
+   *follow_symlinks* when specifying *path* as an open file descriptor.
 
    Availability: Linux
 
@@ -2012,10 +1926,12 @@
    .. versionadded:: 3.3
 
 
-.. function:: stat(path)
+.. function:: stat(path, *, dir_fd=None, follow_symlinks=True)
 
    Perform the equivalent of a :c:func:`stat` system call on the given path.
-   (This function follows symlinks; to stat a symlink use :func:`lstat`.)
+   *path* may be specified as either a string or as an open file descriptor.
+   (This function normally follows symlinks; to stat a symlink add the argument
+   ``follow_symlinks=False``, or use :func:`lstat`.)
 
    The return value is an object whose attributes correspond roughly
    to the members of the :c:type:`stat` structure, namely:
@@ -2084,6 +2000,24 @@
    :attr:`st_gid`, :attr:`st_size`, :attr:`st_atime`, :attr:`st_mtime`,
    :attr:`st_ctime`. More items may be added at the end by some implementations.
 
+   If *dir_fd* is not ``None``, it should be a file descriptor referring to a
+   directory, and *path* should be relative; path will then be relative to
+   that directory.  (If *path* is absolute, *dir_fd* is ignored.)
+   *dir_fd* may not be supported on your platform;
+   you can check whether or not it is available using
+   :data:`os.supports_dir_fd`.  If it is unavailable, using it will raise
+   a :exc:`NotImplementedError`.
+
+   If *follow_symlinks* is ``False``, and the last element of the path is a
+   symbolic link, :func:`stat` will examine the symbolic link itself instead
+   of the file the link points to.  *follow_symlinks* may not be supported
+   on your platform; you can check whether or not it is available using
+   :data:`os.supports_follow_symlinks`.  If it is unavailable,
+   using it will raise a :exc:`NotImplementedError`.
+
+   It is an error to use *dir_fd* or *follow_symlinks* when specifying
+   *path* as an open file descriptor.
+
    .. index:: module: stat
 
    The standard module :mod:`stat` defines functions and constants that are useful
@@ -2104,7 +2038,9 @@
    Availability: Unix, Windows.
 
    .. versionadded:: 3.3
-      The :attr:`st_atime_ns`, :attr:`st_mtime_ns`,
+      Added the *dir_fd* and *follow_symlinks* arguments,
+      specifying a file descriptor instead of a path,
+      and the :attr:`st_atime_ns`, :attr:`st_mtime_ns`,
       and :attr:`st_ctime_ns` members.
 
 
@@ -2149,34 +2085,122 @@
    read-only, and if :const:`ST_NOSUID` is set, the semantics of
    setuid/setgid bits are disabled or not supported.
 
+   On some platforms, *path* may also be specified as an open file descriptor.
+   This functionality may not be supported on your platform; you can check
+   whether or not it is available using :data:`os.supports_fd`.  If it is
+   unavailable, using it will raise a :exc:`NotImplementedError`.
+
    .. versionchanged:: 3.2
       The :const:`ST_RDONLY` and :const:`ST_NOSUID` constants were added.
 
    Availability: Unix.
 
-
-.. function:: symlink(source, link_name)
-              symlink(source, link_name, target_is_directory=False)
+   .. versionadded:: 3.3
+      Added support for specifying an open file descriptor for *path*.
+
+
+.. data:: supports_dir_fd
+
+   An object implementing collections.Set indicating which functions in the
+   :mod:`os` permit use of their *dir_fd* parameter.  Different platforms
+   provide different functionality, and an option that might work on one might
+   be unsupported on another.  For consistency's sakes, functions that support
+   *dir_fd* always allow specifying the parameter, but will throw an exception
+   if the functionality is not actually available.
+
+   To check whether a particular function permits use of its *dir_fd*
+   parameter, use the ``in`` operator on ``supports_dir_fd``.  As an example,
+   this expression determines whether the *dir_fd* parameter of :func:`os.stat`
+   is locally available::
+
+       os.stat in os.supports_dir_fd
+
+   Currently *dir_fd* parameters only work on UNIX platforms;
+   none of them work on Windows.
+
+   .. versionadded:: 3.3
+
+.. data:: supports_effective_ids
+
+   An object implementing collections.Set indicating which functions in the
+   :mod:`os` permit use of the *effective_id* parameter for :func:`os.access`.
+   If the local platform supports it, the collection will contain
+   :func:`os.access`, otherwise it will be empty.
+
+   To check whether you can use the *effective_id* parameter for
+   :func:`os.access`, use the ``in`` operator on ``supports_dir_fd``, like so::
+
+       os.access in os.supports_effective_ids
+
+   Currently *effective_id* only works on UNIX platforms;
+   it does not work on Windows.
+
+   .. versionadded:: 3.3
+
+.. data:: supports_fd
+
+   An object implementing collections.Set indicating which functions in the
+   :mod:`os` permit specifying their *path* parameter as an open file
+   descriptor.  Different platforms provide different functionality, and an
+   option that might work on one might be unsupported on another.  For
+   consistency's sakes, functions that support *fd* always allow specifying
+   the parameter, but will throw an exception if the functionality is not
+   actually available.
+
+   To check whether a particular function permits specifying an open file
+   descriptor for its *path* parameter, use the ``in`` operator on
+   ``supports_fd``. As an example, this expression determines whether
+   :func:`os.chdir` accepts open file descriptors when called on your local
+   platform::
+
+       os.chdir in os.supports_fd
+
+   .. versionadded:: 3.3
+
+.. data:: supports_follow_symlinks
+
+   An object implementing collections.Set indicating which functions in the
+   :mod:`os` permit use of their *follow_symlinks* parameter.  Different
+   platforms provide different functionality, and an option that might work on
+   one might be unsupported on another.  For consistency's sakes, functions that
+   support *follow_symlinks* always allow specifying the parameter, but will
+   throw an exception if the functionality is not actually available.
+
+   To check whether a particular function permits use of its *follow_symlinks*
+   parameter, use the ``in`` operator on ``supports_follow_symlinks``.  As an
+   example, this expression determines whether the *follow_symlinks* parameter
+   of :func:`os.stat` is locally available::
+
+       os.stat in os.supports_follow_symlinks
+
+   .. versionadded:: 3.3
+
+.. function:: symlink(source, link_name, target_is_directory=False, *, dir_fd=None)
 
    Create a symbolic link pointing to *source* named *link_name*.
 
-   On Windows, symlink version takes an additional optional parameter,
-   *target_is_directory*, which defaults to ``False``.
-
-   On Windows, a symlink represents a file or a directory, and does not morph to
-   the target dynamically.  If *target_is_directory* is set to ``True``, the
-   symlink will be created as a directory symlink, otherwise as a file symlink
-   (the default).
+   On Windows, a symlink represents either a file or a directory, and does not
+   morph to the target dynamically.  If *target_is_directory* is set to ``True``,
+   the symlink will be created as a directory symlink, otherwise as a file symlink
+   (the default).  On non-Window platforms, *target_is_directory* is ignored.
 
    Symbolic link support was introduced in Windows 6.0 (Vista).  :func:`symlink`
    will raise a :exc:`NotImplementedError` on Windows versions earlier than 6.0.
 
+   If *dir_fd* is not ``None``, it should be a file descriptor referring to a
+   directory, and *path* should be relative; path will then be relative to
+   that directory.  (If *path* is absolute, *dir_fd* is ignored.)
+   *dir_fd* may not be supported on your platform;
+   you can check whether or not it is available using
+   :data:`os.supports_dir_fd`.  If it is unavailable, using it will raise
+   a :exc:`NotImplementedError`.
+
    .. note::
 
-      The *SeCreateSymbolicLinkPrivilege* is required in order to successfully
-      create symlinks. This privilege is not typically granted to regular
-      users but is available to accounts which can escalate privileges to the
-      administrator level. Either obtaining the privilege or running your
+      On Windows, the *SeCreateSymbolicLinkPrivilege* is required in order to
+      successfully create symlinks. This privilege is not typically granted to
+      regular users but is available to accounts which can escalate privileges
+      to the administrator level. Either obtaining the privilege or running your
       application as an administrator are ways to successfully create symlinks.
 
       :exc:`OSError` is raised when the function is called by an unprivileged
@@ -2187,6 +2211,10 @@
    .. versionchanged:: 3.2
       Added support for Windows 6.0 (Vista) symbolic links.
 
+   .. versionadded:: 3.3
+      Added the *dir_fd* argument, and now allow *target_is_directory*
+      on non-Windows platforms.
+
 
 .. function:: sync()
 
@@ -2207,37 +2235,39 @@
    .. versionadded:: 3.3
 
 
-.. function:: unlink(path)
+.. function:: unlink(path, *, dir_fd=None, rmdir=False)
 
    Remove (delete) the file *path*.  This is the same function as
    :func:`remove`; the :func:`unlink` name is its traditional Unix
-   name.
+   name.  Please see the documentation for :func:`remove` for
+   further information.
 
    Availability: Unix, Windows.
 
-
-.. function:: utime(path[, times, *, ns=(atime_ns, mtime_ns)])
+   .. versionadded:: 3.3
+      The *dir_fd* and *rmdir* parameters.
+
+
+.. function:: utime(path, times=None, *, ns=None, dir_fd=None, follow_symlinks=True)
 
    Set the access and modified times of the file specified by *path*.
 
    :func:`utime` takes two optional parameters, *times* and *ns*.
    These specify the times set on *path* and are used as follows:
 
-   - If *ns* is specified,
+   - If *ns* is not ``None``,
      it must be a 2-tuple of the form ``(atime_ns, mtime_ns)``
      where each member is an int expressing nanoseconds.
-   - If *times* is specified and is not ``None``,
+   - If *times* is not ``None``,
      it must be a 2-tuple of the form ``(atime, mtime)``
      where each member is an int or float expressing seconds.
-   - If *times* is specified as ``None``,
-     this is equivalent to specifying an ``(atime, mtime)``
+   - If *times* and *ns* are both ``None``,
+     this is equivalent to specifying ``ns=(atime_ns, mtime_ns)``
      where both times are the current time.
      (The effect is similar to running the Unix program
      :program:`touch` on *path*.)
-   - If neither *ns* nor *times* is specified, this is
-     equivalent to specifying *times* as ``None``.
-
-   Specifying both *times* and *ns* simultaneously is an error.
+
+   It is an error to specify tuples for both *times* and *ns*.
 
    Whether a directory can be given for *path*
    depends on whether the operating system implements directories as files
@@ -2248,10 +2278,34 @@
    use the *st_atime_ns* and *st_mtime_ns* fields from the :func:`os.stat`
    result object with the *ns* parameter to `utime`.
 
+   On some platforms, *path* may also be specified as an open file descriptor.
+   This functionality may not be supported on your platform; you can check
+   whether or not it is available using :data:`os.supports_fd`.  If it is
+   unavailable, using it will raise a :exc:`NotImplementedError`.
+
+   If *dir_fd* is not ``None``, it should be a file descriptor referring to a
+   directory, and *path* should be relative; path will then be relative to
+   that directory.  (If *path* is absolute, *dir_fd* is ignored.)
+   *dir_fd* may not be supported on your platform;
+   you can check whether or not it is available using
+   :data:`os.supports_dir_fd`.  If it is unavailable, using it will raise
+   a :exc:`NotImplementedError`.
+
+   If *follow_symlinks* is ``False``, and the last element of the path is a
+   symbolic link, :func:`utime` will examine the symbolic link itself instead
+   of the file the link points to.  *follow_symlinks* may not be supported
+   on your platform; you can check whether or not it is available using
+   :data:`os.supports_follow_symlinks`.  If it is unavailable,
+   using it will raise a :exc:`NotImplementedError`.
+
+   It is an error to use *dir_fd* or *follow_symlinks* when specifying
+   *path* as an open file descriptor.
+
    Availability: Unix, Windows.
 
    .. versionadded:: 3.3
-      The :attr:`ns` keyword parameter.
+      Added support for specifying an open file descriptor for *path*,
+      and the *dir_fd*, *follow_symlinks*, and *ns* parameters.
 
 
 .. function:: walk(top, topdown=True, onerror=None, followlinks=False)
@@ -2461,8 +2515,16 @@
    :func:`execlp`, :func:`execv`, and :func:`execvp` all cause the new process to
    inherit the environment of the current process.
 
+   For :func:`execve` on some platforms, *path* may also be specified as an open
+   file descriptor.  This functionality may not be supported on your platform;
+   you can check whether or not it is available using :data:`os.supports_fd`.
+   If it is unavailable, using it will raise a :exc:`NotImplementedError`.
+
    Availability: Unix, Windows.
 
+   .. versionadded:: 3.3
+      Added support for specifying an open file descriptor for *path*
+      for :func:`execve`.
 
 .. function:: _exit(n)
 
diff --git a/Lib/os.py b/Lib/os.py
--- a/Lib/os.py
+++ b/Lib/os.py
@@ -56,9 +56,10 @@
         pass
     import posixpath as path
 
-    import posix
-    __all__.extend(_get_exports_list(posix))
-    del posix
+    try:
+        from posix import _have_functions
+    except ImportError:
+        pass
 
 elif 'nt' in _names:
     name = 'nt'
@@ -75,6 +76,11 @@
     __all__.extend(_get_exports_list(nt))
     del nt
 
+    try:
+        from nt import _have_functions
+    except ImportError:
+        pass
+
 elif 'os2' in _names:
     name = 'os2'
     linesep = '\r\n'
@@ -94,6 +100,11 @@
     __all__.extend(_get_exports_list(os2))
     del os2
 
+    try:
+        from os2 import _have_functions
+    except ImportError:
+        pass
+
 elif 'ce' in _names:
     name = 'ce'
     linesep = '\r\n'
@@ -110,6 +121,11 @@
     __all__.extend(_get_exports_list(ce))
     del ce
 
+    try:
+        from ce import _have_functions
+    except ImportError:
+        pass
+
 else:
     raise ImportError('no os specific module found')
 
@@ -119,6 +135,84 @@
 
 del _names
 
+
+if _exists("_have_functions"):
+    _globals = globals()
+    def _add(str, fn):
+        if (fn in _globals) and (str in _have_functions):
+            _set.add(_globals[fn])
+
+    _set = set()
+    _add("HAVE_FACCESSAT",  "access")
+    _add("HAVE_FCHMODAT",   "chmod")
+    _add("HAVE_FCHOWNAT",   "chown")
+    _add("HAVE_FSTATAT",    "stat")
+    _add("HAVE_FUTIMESAT",  "utime")
+    _add("HAVE_LINKAT",     "link")
+    _add("HAVE_MKDIRAT",    "mkdir")
+    _add("HAVE_MKFIFOAT",   "mkfifo")
+    _add("HAVE_MKNODAT",    "mknod")
+    _add("HAVE_OPENAT",     "open")
+    _add("HAVE_READLINKAT", "readlink")
+    _add("HAVE_RENAMEAT",   "rename")
+    _add("HAVE_SYMLINKAT",  "symlink")
+    _add("HAVE_UNLINKAT",   "unlink")
+    _add("HAVE_UTIMENSAT",  "utime")
+    supports_dir_fd = _set
+
+    _set = set()
+    _add("HAVE_FACCESSAT",  "access")
+    supports_effective_ids = _set
+
+    _set = set()
+    _add("HAVE_FCHDIR",     "chdir")
+    _add("HAVE_FCHMOD",     "chmod")
+    _add("HAVE_FCHOWN",     "chown")
+    _add("HAVE_FDOPENDIR",  "listdir")
+    _add("HAVE_FEXECVE",    "execve")
+    _set.add(stat) # fstat always works
+    _add("HAVE_FUTIMENS",   "utime")
+    _add("HAVE_FUTIMES",    "utime")
+    if _exists("statvfs") and _exists("fstatvfs"): # mac os x10.3
+        _add("HAVE_FSTATVFS", "statvfs")
+    supports_fd = _set
+
+    _set = set()
+    _add("HAVE_FACCESSAT",  "access")
+    # Current linux (kernel 3.2, glibc 2.15) doesn't support lchmod.
+    # (The function exists, but it's a stub that always returns ENOSUP.)
+    # Now, linux *does* have fchmodat, which says it can ignore
+    # symbolic links.  But that doesn't work either (also returns ENOSUP).
+    # I'm guessing that if they fix fchmodat, they'll also add lchmod at
+    # the same time.  So, for now, assume that fchmodat doesn't support
+    # follow_symlinks unless lchmod works.
+    if ((sys.platform != "linux") or
+        ("HAVE_LCHMOD" in _have_functions)):
+        _add("HAVE_FCHMODAT",   "chmod")
+    _add("HAVE_FCHOWNAT",   "chown")
+    _add("HAVE_FSTATAT",    "stat")
+    _add("HAVE_LCHFLAGS",   "chflags")
+    _add("HAVE_LCHMOD",     "chmod")
+    if _exists("lchown"): # mac os x10.3
+        _add("HAVE_LCHOWN", "chown")
+    _add("HAVE_LINKAT",     "link")
+    _add("HAVE_LUTIMES",    "utime")
+    _add("HAVE_LSTAT",      "stat")
+    _add("HAVE_FSTATAT",    "stat")
+    _add("HAVE_UTIMENSAT",  "utime")
+    _add("MS_WINDOWS",      "stat")
+    supports_follow_symlinks = _set
+
+    _set = set()
+    _add("HAVE_UNLINKAT",   "unlink")
+    supports_remove_directory = _set
+
+    del _set
+    del _have_functions
+    del _globals
+    del _add
+
+
 # Python uses fixed values for the SEEK_ constants; they are mapped
 # to native constants if necessary in posixmodule.c
 # Other possible SEEK values are directly imported from posixmodule.c
@@ -318,7 +412,7 @@
 
 __all__.append("walk")
 
-if _exists("openat"):
+if open in supports_dir_fd:
 
     def fwalk(top, topdown=True, onerror=None, followlinks=False):
         """Directory tree generator.
@@ -343,7 +437,7 @@
         import os
         for root, dirs, files, rootfd in os.fwalk('python/Lib/email'):
             print(root, "consumes", end="")
-            print(sum([os.fstatat(rootfd, name).st_size for name in files]),
+            print(sum([os.stat(name, dir_fd=rootfd).st_size for name in files]),
                   end="")
             print("bytes in", len(files), "non-directory files")
             if 'CVS' in dirs:
@@ -365,10 +459,7 @@
         # necessary, it can be adapted to only require O(1) FDs, see issue
         # #13734.
 
-        # whether to follow symlinks
-        flag = 0 if followlinks else AT_SYMLINK_NOFOLLOW
-
-        names = flistdir(topfd)
+        names = listdir(topfd)
         dirs, nondirs = [], []
         for name in names:
             try:
@@ -376,14 +467,14 @@
                 # walk() which reports symlinks to directories as directories.
                 # We do however check for symlinks before recursing into
                 # a subdirectory.
-                if st.S_ISDIR(fstatat(topfd, name).st_mode):
+                if st.S_ISDIR(stat(name, dir_fd=topfd).st_mode):
                     dirs.append(name)
                 else:
                     nondirs.append(name)
             except FileNotFoundError:
                 try:
                     # Add dangling symlinks, ignore disappeared files
-                    if st.S_ISLNK(fstatat(topfd, name, AT_SYMLINK_NOFOLLOW)
+                    if st.S_ISLNK(stat(name, dir_fd=topfd, follow_symlinks=False)
                                 .st_mode):
                         nondirs.append(name)
                 except FileNotFoundError:
@@ -394,8 +485,8 @@
 
         for name in dirs:
             try:
-                orig_st = fstatat(topfd, name, flag)
-                dirfd = openat(topfd, name, O_RDONLY)
+                orig_st = stat(name, dir_fd=topfd, follow_symlinks=followlinks)
+                dirfd = open(name, O_RDONLY, dir_fd=topfd)
             except error as err:
                 if onerror is not None:
                     onerror(err)
diff --git a/Lib/shutil.py b/Lib/shutil.py
--- a/Lib/shutil.py
+++ b/Lib/shutil.py
@@ -139,27 +139,45 @@
     only if both `src` and `dst` are symlinks.
 
     """
-    def _nop(*args, ns=None):
+    def _nop(*args, ns=None, follow_symlinks=None):
         pass
 
-    if symlinks and os.path.islink(src) and os.path.islink(dst):
-        stat_func = os.lstat
-        utime_func = os.lutimes if hasattr(os, 'lutimes') else _nop
-        chmod_func = os.lchmod if hasattr(os, 'lchmod') else _nop
-        chflags_func = os.lchflags if hasattr(os, 'lchflags') else _nop
+    # follow symlinks (aka don't not follow symlinks)
+    follow = not (symlinks and os.path.islink(src) and os.path.islink(dst))
+    if follow:
+        # use the real function if it exists
+        def lookup(name):
+            return getattr(os, name, _nop)
     else:
-        stat_func = os.stat
-        utime_func = os.utime if hasattr(os, 'utime') else _nop
-        chmod_func = os.chmod if hasattr(os, 'chmod') else _nop
-        chflags_func = os.chflags if hasattr(os, 'chflags') else _nop
+        # use the real function only if it exists
+        # *and* it supports follow_symlinks
+        def lookup(name):
+            fn = getattr(os, name, _nop)
+            if fn in os.supports_follow_symlinks:
+                return fn
+            return _nop
 
-    st = stat_func(src)
+    st = lookup("stat")(src, follow_symlinks=follow)
     mode = stat.S_IMODE(st.st_mode)
-    utime_func(dst, ns=(st.st_atime_ns, st.st_mtime_ns))
-    chmod_func(dst, mode)
+    lookup("utime")(dst, ns=(st.st_atime_ns, st.st_mtime_ns),
+        follow_symlinks=follow)
+    try:
+        lookup("chmod")(dst, mode, follow_symlinks=follow)
+    except NotImplementedError:
+        # if we got a NotImplementedError, it's because
+        #   * follow_symlinks=False,
+        #   * lchown() is unavailable, and
+        #   * either
+        #       * fchownat() is unvailable or
+        #       * fchownat() doesn't implement AT_SYMLINK_NOFOLLOW.
+        #         (it returned ENOSUP.)
+        # therefore we're out of options--we simply cannot chown the
+        # symlink.  give up, suppress the error.
+        # (which is what shutil always did in this circumstance.)
+        pass
     if hasattr(st, 'st_flags'):
         try:
-            chflags_func(dst, st.st_flags)
+            lookup("chflags")(dst, st.st_flags, follow_symlinks=follow)
         except OSError as why:
             for err in 'EOPNOTSUPP', 'ENOTSUP':
                 if hasattr(errno, err) and why.errno == getattr(errno, err):
@@ -176,20 +194,11 @@
         If the optional flag `symlinks` is set, symlinks won't be followed.
 
         """
-        if symlinks:
-            listxattr = os.llistxattr
-            removexattr = os.lremovexattr
-            setxattr = os.lsetxattr
-            getxattr = os.lgetxattr
-        else:
-            listxattr = os.listxattr
-            removexattr = os.removexattr
-            setxattr = os.setxattr
-            getxattr = os.getxattr
 
-        for attr in listxattr(src):
+        for name in os.listxattr(src, follow_symlinks=symlinks):
             try:
-                setxattr(dst, attr, getxattr(src, attr))
+                value = os.getxattr(src, name, follow_symlinks=symlinks)
+                os.setxattr(dst, name, value, follow_symlinks=symlinks)
             except OSError as e:
                 if e.errno not in (errno.EPERM, errno.ENOTSUP, errno.ENODATA):
                     raise
diff --git a/Lib/test/support.py b/Lib/test/support.py
--- a/Lib/test/support.py
+++ b/Lib/test/support.py
@@ -1703,8 +1703,8 @@
                 try:
                     # TESTFN & tempfile may use different file systems with
                     # different capabilities
-                    os.fsetxattr(tmp_fp, b"user.test", b"")
-                    os.fsetxattr(fp.fileno(), b"user.test", b"")
+                    os.setxattr(tmp_fp, b"user.test", b"")
+                    os.setxattr(fp.fileno(), b"user.test", b"")
                     # Kernels < 2.6.39 don't respect setxattr flags.
                     kernel_version = platform.release()
                     m = re.match("2.6.(\d{1,2})", kernel_version)
diff --git a/Lib/test/test_os.py b/Lib/test/test_os.py
--- a/Lib/test/test_os.py
+++ b/Lib/test/test_os.py
@@ -345,40 +345,36 @@
             return os.utime(file, ns=times)
         self._test_utime_ns(utime_ns)
 
-    requires_lutimes = unittest.skipUnless(hasattr(os, 'lutimes'),
-                            "os.lutimes required for this test.")
-    requires_futimes = unittest.skipUnless(hasattr(os, 'futimes'),
-                            "os.futimes required for this test.")
+    requires_utime_dir_fd = unittest.skipUnless(
+                                os.utime in os.supports_dir_fd,
+                                "dir_fd support for utime required for this test.")
+    requires_utime_fd = unittest.skipUnless(
+                                os.utime in os.supports_fd,
+                                "fd support for utime required for this test.")
+    requires_utime_nofollow_symlinks = unittest.skipUnless(
+                                os.utime in os.supports_follow_symlinks,
+                                "follow_symlinks support for utime required for this test.")
 
-    @requires_lutimes
+    @requires_utime_nofollow_symlinks
     def test_lutimes_ns(self):
         def lutimes_ns(file, times):
-            return os.lutimes(file, ns=times)
+            return os.utime(file, ns=times, follow_symlinks=False)
         self._test_utime_ns(lutimes_ns)
 
-    @requires_futimes
+    @requires_utime_fd
     def test_futimes_ns(self):
         def futimes_ns(file, times):
             with open(file, "wb") as f:
-                os.futimes(f.fileno(), ns=times)
+                os.utime(f.fileno(), ns=times)
         self._test_utime_ns(futimes_ns, test_dir=False)
 
     def _utime_invalid_arguments(self, name, arg):
-        with self.assertRaises(RuntimeError):
+        with self.assertRaises(ValueError):
             getattr(os, name)(arg, (5, 5), ns=(5, 5))
 
     def test_utime_invalid_arguments(self):
         self._utime_invalid_arguments('utime', self.fname)
 
-    @requires_lutimes
-    def test_lutimes_invalid_arguments(self):
-        self._utime_invalid_arguments('lutimes', self.fname)
-
-    @requires_futimes
-    def test_futimes_invalid_arguments(self):
-        with open(self.fname, "wb") as f:
-            self._utime_invalid_arguments('futimes', f.fileno())
-
 
     @unittest.skipUnless(stat_supports_subsecond,
                          "os.stat() doesn't has a subsecond resolution")
@@ -402,64 +398,46 @@
             os.utime(filename, (atime, mtime))
         self._test_utime_subsecond(set_time)
 
-    @requires_futimes
+    @requires_utime_fd
     def test_futimes_subsecond(self):
         def set_time(filename, atime, mtime):
             with open(filename, "wb") as f:
-                os.futimes(f.fileno(), (atime, mtime))
+                os.utime(f.fileno(), times=(atime, mtime))
         self._test_utime_subsecond(set_time)
 
-    @unittest.skipUnless(hasattr(os, 'futimens'),
-                         "os.futimens required for this test.")
+    @requires_utime_fd
     def test_futimens_subsecond(self):
         def set_time(filename, atime, mtime):
             with open(filename, "wb") as f:
-                asec, ansec = divmod(atime, 1.0)
-                asec = int(asec)
-                ansec = int(ansec * 1e9)
-                msec, mnsec = divmod(mtime, 1.0)
-                msec = int(msec)
-                mnsec = int(mnsec * 1e9)
-                os.futimens(f.fileno(),
-                           (asec, ansec),
-                           (msec, mnsec))
+                os.utime(f.fileno(), times=(atime, mtime))
         self._test_utime_subsecond(set_time)
 
-    @unittest.skipUnless(hasattr(os, 'futimesat'),
-                         "os.futimesat required for this test.")
+    @requires_utime_dir_fd
     def test_futimesat_subsecond(self):
         def set_time(filename, atime, mtime):
             dirname = os.path.dirname(filename)
             dirfd = os.open(dirname, os.O_RDONLY)
             try:
-                os.futimesat(dirfd, os.path.basename(filename),
-                             (atime, mtime))
+                os.utime(os.path.basename(filename), dir_fd=dirfd,
+                             times=(atime, mtime))
             finally:
                 os.close(dirfd)
         self._test_utime_subsecond(set_time)
 
-    @requires_lutimes
+    @requires_utime_nofollow_symlinks
     def test_lutimes_subsecond(self):
         def set_time(filename, atime, mtime):
-            os.lutimes(filename, (atime, mtime))
+            os.utime(filename, (atime, mtime), follow_symlinks=False)
         self._test_utime_subsecond(set_time)
 
-    @unittest.skipUnless(hasattr(os, 'utimensat'),
-                         "os.utimensat required for this test.")
+    @requires_utime_dir_fd
     def test_utimensat_subsecond(self):
         def set_time(filename, atime, mtime):
             dirname = os.path.dirname(filename)
             dirfd = os.open(dirname, os.O_RDONLY)
             try:
-                asec, ansec = divmod(atime, 1.0)
-                asec = int(asec)
-                ansec = int(ansec * 1e9)
-                msec, mnsec = divmod(mtime, 1.0)
-                msec = int(msec)
-                mnsec = int(mnsec * 1e9)
-                os.utimensat(dirfd, os.path.basename(filename),
-                             (asec, ansec),
-                             (msec, mnsec))
+                os.utime(os.path.basename(filename), dir_fd=dirfd,
+                             times=(atime, mtime))
             finally:
                 os.close(dirfd)
         self._test_utime_subsecond(set_time)
@@ -782,8 +760,10 @@
             for root, dirs, files, rootfd in os.fwalk(*args):
                 # check that the FD is valid
                 os.fstat(rootfd)
-                # check that flistdir() returns consistent information
-                self.assertEqual(set(os.flistdir(rootfd)), set(dirs) | set(files))
+                # redundant check
+                os.stat(rootfd)
+                # check that listdir() returns consistent information
+                self.assertEqual(set(os.listdir(rootfd)), set(dirs) | set(files))
 
     def test_fd_leak(self):
         # Since we're opening a lot of FDs, we must be careful to avoid leaks:
@@ -802,13 +782,10 @@
         # cleanup
         for root, dirs, files, rootfd in os.fwalk(support.TESTFN, topdown=False):
             for name in files:
-                os.unlinkat(rootfd, name)
+                os.unlink(name, dir_fd=rootfd)
             for name in dirs:
-                st = os.fstatat(rootfd, name, os.AT_SYMLINK_NOFOLLOW)
-                if stat.S_ISDIR(st.st_mode):
-                    os.unlinkat(rootfd, name, os.AT_REMOVEDIR)
-                else:
-                    os.unlinkat(rootfd, name)
+                st = os.stat(name, dir_fd=rootfd, follow_symlinks=False)
+                os.unlink(name, dir_fd=rootfd, rmdir=stat.S_ISDIR(st.st_mode))
         os.rmdir(support.TESTFN)
 
 
@@ -1262,6 +1239,13 @@
             expected = self.unicodefn
             found = set(os.listdir(self.dir))
             self.assertEqual(found, expected)
+            # test listdir without arguments
+            current_directory = os.getcwd()
+            try:
+                os.chdir(os.sep)
+                self.assertEqual(set(os.listdir()), set(os.listdir(os.sep)))
+            finally:
+                os.chdir(current_directory)
 
         def test_open(self):
             for fn in self.unicodefn:
@@ -1846,79 +1830,97 @@
                         raise
 
 
- at support.skip_unless_xattr
+def supports_extended_attributes():
+    if not hasattr(os, "setxattr"):
+        return False
+    try:
+        with open(support.TESTFN, "wb") as fp:
+            try:
+                os.setxattr(fp.fileno(), b"user.test", b"")
+            except OSError:
+                return False
+    finally:
+        support.unlink(support.TESTFN)
+    # Kernels < 2.6.39 don't respect setxattr flags.
+    kernel_version = platform.release()
+    m = re.match("2.6.(\d{1,2})", kernel_version)
+    return m is None or int(m.group(1)) >= 39
+
+
+ at unittest.skipUnless(supports_extended_attributes(),
+                     "no non-broken extended attribute support")
 class ExtendedAttributeTests(unittest.TestCase):
 
     def tearDown(self):
         support.unlink(support.TESTFN)
 
-    def _check_xattrs_str(self, s, getxattr, setxattr, removexattr, listxattr):
+    def _check_xattrs_str(self, s, getxattr, setxattr, removexattr, listxattr, **kwargs):
         fn = support.TESTFN
         open(fn, "wb").close()
         with self.assertRaises(OSError) as cm:
-            getxattr(fn, s("user.test"))
+            getxattr(fn, s("user.test"), **kwargs)
         self.assertEqual(cm.exception.errno, errno.ENODATA)
         init_xattr = listxattr(fn)
         self.assertIsInstance(init_xattr, list)
-        setxattr(fn, s("user.test"), b"")
+        setxattr(fn, s("user.test"), b"", **kwargs)
         xattr = set(init_xattr)
         xattr.add("user.test")
         self.assertEqual(set(listxattr(fn)), xattr)
-        self.assertEqual(getxattr(fn, b"user.test"), b"")
-        setxattr(fn, s("user.test"), b"hello", os.XATTR_REPLACE)
-        self.assertEqual(getxattr(fn, b"user.test"), b"hello")
+        self.assertEqual(getxattr(fn, b"user.test", **kwargs), b"")
+        setxattr(fn, s("user.test"), b"hello", os.XATTR_REPLACE, **kwargs)
+        self.assertEqual(getxattr(fn, b"user.test", **kwargs), b"hello")
         with self.assertRaises(OSError) as cm:
-            setxattr(fn, s("user.test"), b"bye", os.XATTR_CREATE)
+            setxattr(fn, s("user.test"), b"bye", os.XATTR_CREATE, **kwargs)
         self.assertEqual(cm.exception.errno, errno.EEXIST)
         with self.assertRaises(OSError) as cm:
-            setxattr(fn, s("user.test2"), b"bye", os.XATTR_REPLACE)
+            setxattr(fn, s("user.test2"), b"bye", os.XATTR_REPLACE, **kwargs)
         self.assertEqual(cm.exception.errno, errno.ENODATA)
-        setxattr(fn, s("user.test2"), b"foo", os.XATTR_CREATE)
+        setxattr(fn, s("user.test2"), b"foo", os.XATTR_CREATE, **kwargs)
         xattr.add("user.test2")
         self.assertEqual(set(listxattr(fn)), xattr)
-        removexattr(fn, s("user.test"))
+        removexattr(fn, s("user.test"), **kwargs)
         with self.assertRaises(OSError) as cm:
-            getxattr(fn, s("user.test"))
+            getxattr(fn, s("user.test"), **kwargs)
         self.assertEqual(cm.exception.errno, errno.ENODATA)
         xattr.remove("user.test")
         self.assertEqual(set(listxattr(fn)), xattr)
-        self.assertEqual(getxattr(fn, s("user.test2")), b"foo")
-        setxattr(fn, s("user.test"), b"a"*1024)
-        self.assertEqual(getxattr(fn, s("user.test")), b"a"*1024)
-        removexattr(fn, s("user.test"))
+        self.assertEqual(getxattr(fn, s("user.test2"), **kwargs), b"foo")
+        setxattr(fn, s("user.test"), b"a"*1024, **kwargs)
+        self.assertEqual(getxattr(fn, s("user.test"), **kwargs), b"a"*1024)
+        removexattr(fn, s("user.test"), **kwargs)
         many = sorted("user.test{}".format(i) for i in range(100))
         for thing in many:
-            setxattr(fn, thing, b"x")
+            setxattr(fn, thing, b"x", **kwargs)
         self.assertEqual(set(listxattr(fn)), set(init_xattr) | set(many))
 
-    def _check_xattrs(self, *args):
+    def _check_xattrs(self, *args, **kwargs):
         def make_bytes(s):
             return bytes(s, "ascii")
-        self._check_xattrs_str(str, *args)
+        self._check_xattrs_str(str, *args, **kwargs)
         support.unlink(support.TESTFN)
-        self._check_xattrs_str(make_bytes, *args)
+        self._check_xattrs_str(make_bytes, *args, **kwargs)
 
     def test_simple(self):
         self._check_xattrs(os.getxattr, os.setxattr, os.removexattr,
                            os.listxattr)
 
     def test_lpath(self):
-        self._check_xattrs(os.lgetxattr, os.lsetxattr, os.lremovexattr,
-                           os.llistxattr)
+        self._check_xattrs(os.getxattr, os.setxattr, os.removexattr,
+                           os.listxattr, follow_symlinks=False)
 
     def test_fds(self):
         def getxattr(path, *args):
             with open(path, "rb") as fp:
-                return os.fgetxattr(fp.fileno(), *args)
+                return os.getxattr(fp.fileno(), *args)
         def setxattr(path, *args):
             with open(path, "wb") as fp:
-                os.fsetxattr(fp.fileno(), *args)
+                os.setxattr(fp.fileno(), *args)
         def removexattr(path, *args):
             with open(path, "wb") as fp:
-                os.fremovexattr(fp.fileno(), *args)
+                os.removexattr(fp.fileno(), *args)
         def listxattr(path, *args):
             with open(path, "rb") as fp:
-                return os.flistxattr(fp.fileno(), *args)
+                return os.listxattr(fp.fileno(), *args)
         self._check_xattrs(getxattr, setxattr, removexattr, listxattr)
 
 
diff --git a/Lib/test/test_posix.py b/Lib/test/test_posix.py
--- a/Lib/test/test_posix.py
+++ b/Lib/test/test_posix.py
@@ -129,6 +129,7 @@
             fp = open(support.TESTFN)
             try:
                 self.assertTrue(posix.fstatvfs(fp.fileno()))
+                self.assertTrue(posix.statvfs(fp.fileno()))
             finally:
                 fp.close()
 
@@ -150,7 +151,7 @@
             fp.flush()
         posix.truncate(support.TESTFN, 0)
 
-    @unittest.skipUnless(hasattr(posix, 'fexecve'), "test needs posix.fexecve()")
+    @unittest.skipUnless(getattr(os, 'execve', None) in os.supports_fd, "test needs execve() to support the fd parameter")
     @unittest.skipUnless(hasattr(os, 'fork'), "test needs os.fork()")
     @unittest.skipUnless(hasattr(os, 'waitpid'), "test needs os.waitpid()")
     def test_fexecve(self):
@@ -159,7 +160,7 @@
             pid = os.fork()
             if pid == 0:
                 os.chdir(os.path.split(sys.executable)[0])
-                posix.fexecve(fp, [sys.executable, '-c', 'pass'], os.environ)
+                posix.execve(fp, [sys.executable, '-c', 'pass'], os.environ)
             else:
                 self.assertEqual(os.waitpid(pid, 0), (pid, 0))
         finally:
@@ -234,45 +235,37 @@
         finally:
             os.close(fd)
 
-    @unittest.skipUnless(hasattr(posix, 'futimes'), "test needs posix.futimes()")
-    def test_futimes(self):
+    @unittest.skipUnless(os.utime in os.supports_fd, "test needs fd support in os.utime")
+    def test_utime_with_fd(self):
         now = time.time()
         fd = os.open(support.TESTFN, os.O_RDONLY)
         try:
-            posix.futimes(fd, None)
-            posix.futimes(fd)
-            self.assertRaises(TypeError, posix.futimes, fd, (None, None))
-            self.assertRaises(TypeError, posix.futimes, fd, (now, None))
-            self.assertRaises(TypeError, posix.futimes, fd, (None, now))
-            posix.futimes(fd, (int(now), int(now)))
-            posix.futimes(fd, (now, now))
+            posix.utime(fd)
+            posix.utime(fd, None)
+            self.assertRaises(TypeError, posix.utime, fd, (None, None))
+            self.assertRaises(TypeError, posix.utime, fd, (now, None))
+            self.assertRaises(TypeError, posix.utime, fd, (None, now))
+            posix.utime(fd, (int(now), int(now)))
+            posix.utime(fd, (now, now))
+            self.assertRaises(ValueError, posix.utime, fd, (now, now), ns=(now, now))
+            self.assertRaises(ValueError, posix.utime, fd, (now, 0), ns=(None, None))
+            self.assertRaises(ValueError, posix.utime, fd, (None, None), ns=(now, 0))
+            posix.utime(fd, (int(now), int((now - int(now)) * 1e9)))
+            posix.utime(fd, ns=(int(now), int((now - int(now)) * 1e9)))
+
         finally:
             os.close(fd)
 
-    @unittest.skipUnless(hasattr(posix, 'lutimes'), "test needs posix.lutimes()")
-    def test_lutimes(self):
+    @unittest.skipUnless(os.utime in os.supports_follow_symlinks, "test needs follow_symlinks support in os.utime")
+    def test_utime_nofollow_symlinks(self):
         now = time.time()
-        posix.lutimes(support.TESTFN, None)
-        self.assertRaises(TypeError, posix.lutimes, support.TESTFN, (None, None))
-        self.assertRaises(TypeError, posix.lutimes, support.TESTFN, (now, None))
-        self.assertRaises(TypeError, posix.lutimes, support.TESTFN, (None, now))
-        posix.lutimes(support.TESTFN, (int(now), int(now)))
-        posix.lutimes(support.TESTFN, (now, now))
-        posix.lutimes(support.TESTFN)
-
-    @unittest.skipUnless(hasattr(posix, 'futimens'), "test needs posix.futimens()")
-    def test_futimens(self):
-        now = time.time()
-        fd = os.open(support.TESTFN, os.O_RDONLY)
-        try:
-            self.assertRaises(TypeError, posix.futimens, fd, (None, None), (None, None))
-            self.assertRaises(TypeError, posix.futimens, fd, (now, 0), None)
-            self.assertRaises(TypeError, posix.futimens, fd, None, (now, 0))
-            posix.futimens(fd, (int(now), int((now - int(now)) * 1e9)),
-                    (int(now), int((now - int(now)) * 1e9)))
-            posix.futimens(fd)
-        finally:
-            os.close(fd)
+        posix.utime(support.TESTFN, None, follow_symlinks=False)
+        self.assertRaises(TypeError, posix.utime, support.TESTFN, (None, None), follow_symlinks=False)
+        self.assertRaises(TypeError, posix.utime, support.TESTFN, (now, None), follow_symlinks=False)
+        self.assertRaises(TypeError, posix.utime, support.TESTFN, (None, now), follow_symlinks=False)
+        posix.utime(support.TESTFN, (int(now), int(now)), follow_symlinks=False)
+        posix.utime(support.TESTFN, (now, now), follow_symlinks=False)
+        posix.utime(support.TESTFN, follow_symlinks=False)
 
     @unittest.skipUnless(hasattr(posix, 'writev'), "test needs posix.writev()")
     def test_writev(self):
@@ -364,6 +357,7 @@
             fp = open(support.TESTFN)
             try:
                 self.assertTrue(posix.fstat(fp.fileno()))
+                self.assertTrue(posix.stat(fp.fileno()))
             finally:
                 fp.close()
 
@@ -462,18 +456,18 @@
         if hasattr(posix, 'listdir'):
             self.assertTrue(support.TESTFN in posix.listdir())
 
-    @unittest.skipUnless(hasattr(posix, 'flistdir'), "test needs posix.flistdir()")
+    @unittest.skipUnless(os.listdir in os.supports_fd, "test needs fd support for os.listdir()")
     def test_flistdir(self):
         f = posix.open(posix.getcwd(), posix.O_RDONLY)
         self.addCleanup(posix.close, f)
         self.assertEqual(
             sorted(posix.listdir('.')),
-            sorted(posix.flistdir(f))
+            sorted(posix.listdir(f))
             )
         # Check that the fd offset was reset (issue #13739)
         self.assertEqual(
             sorted(posix.listdir('.')),
-            sorted(posix.flistdir(f))
+            sorted(posix.listdir(f))
             )
 
     def test_access(self):
@@ -532,10 +526,10 @@
             posix.utime(support.TESTFN, (int(now), int(now)))
             posix.utime(support.TESTFN, (now, now))
 
-    def _test_chflags_regular_file(self, chflags_func, target_file):
+    def _test_chflags_regular_file(self, chflags_func, target_file, **kwargs):
         st = os.stat(target_file)
         self.assertTrue(hasattr(st, 'st_flags'))
-        chflags_func(target_file, st.st_flags | stat.UF_IMMUTABLE)
+        chflags_func(target_file, st.st_flags | stat.UF_IMMUTABLE, **kwargs)
         try:
             new_st = os.stat(target_file)
             self.assertEqual(st.st_flags | stat.UF_IMMUTABLE, new_st.st_flags)
@@ -553,6 +547,7 @@
     @unittest.skipUnless(hasattr(posix, 'lchflags'), 'test needs os.lchflags()')
     def test_lchflags_regular_file(self):
         self._test_chflags_regular_file(posix.lchflags, support.TESTFN)
+        self._test_chflags_regular_file(posix.chflags, support.TESTFN, follow_symlinks=False)
 
     @unittest.skipUnless(hasattr(posix, 'lchflags'), 'test needs os.lchflags()')
     def test_lchflags_symlink(self):
@@ -564,17 +559,21 @@
         self.teardown_files.append(_DUMMY_SYMLINK)
         dummy_symlink_st = os.lstat(_DUMMY_SYMLINK)
 
-        posix.lchflags(_DUMMY_SYMLINK,
-                       dummy_symlink_st.st_flags | stat.UF_IMMUTABLE)
-        try:
-            new_testfn_st = os.stat(support.TESTFN)
-            new_dummy_symlink_st = os.lstat(_DUMMY_SYMLINK)
+        def chflags_nofollow(path, flags):
+            return posix.chflags(path, flags, follow_symlinks=False)
 
-            self.assertEqual(testfn_st.st_flags, new_testfn_st.st_flags)
-            self.assertEqual(dummy_symlink_st.st_flags | stat.UF_IMMUTABLE,
-                             new_dummy_symlink_st.st_flags)
-        finally:
-            posix.lchflags(_DUMMY_SYMLINK, dummy_symlink_st.st_flags)
+        for fn in (posix.lchflags, chflags_nofollow):
+            fn(_DUMMY_SYMLINK,
+                           dummy_symlink_st.st_flags | stat.UF_IMMUTABLE)
+            try:
+                new_testfn_st = os.stat(support.TESTFN)
+                new_dummy_symlink_st = os.lstat(_DUMMY_SYMLINK)
+
+                self.assertEqual(testfn_st.st_flags, new_testfn_st.st_flags)
+                self.assertEqual(dummy_symlink_st.st_flags | stat.UF_IMMUTABLE,
+                                 new_dummy_symlink_st.st_flags)
+            finally:
+                fn(_DUMMY_SYMLINK, dummy_symlink_st.st_flags)
 
     def test_environ(self):
         if os.name == "nt":
@@ -657,40 +656,40 @@
 
     # tests for the posix *at functions follow
 
-    @unittest.skipUnless(hasattr(posix, 'faccessat'), "test needs posix.faccessat()")
-    def test_faccessat(self):
+    @unittest.skipUnless(os.access in os.supports_dir_fd, "test needs dir_fd support for os.access()")
+    def test_access_dir_fd(self):
         f = posix.open(posix.getcwd(), posix.O_RDONLY)
         try:
-            self.assertTrue(posix.faccessat(f, support.TESTFN, os.R_OK))
+            self.assertTrue(posix.access(support.TESTFN, os.R_OK, dir_fd=f))
         finally:
             posix.close(f)
 
-    @unittest.skipUnless(hasattr(posix, 'fchmodat'), "test needs posix.fchmodat()")
-    def test_fchmodat(self):
+    @unittest.skipUnless(os.chmod in os.supports_dir_fd, "test needs dir_fd support in os.chmod()")
+    def test_chmod_dir_fd(self):
         os.chmod(support.TESTFN, stat.S_IRUSR)
 
         f = posix.open(posix.getcwd(), posix.O_RDONLY)
         try:
-            posix.fchmodat(f, support.TESTFN, stat.S_IRUSR | stat.S_IWUSR)
+            posix.chmod(support.TESTFN, stat.S_IRUSR | stat.S_IWUSR, dir_fd=f)
 
             s = posix.stat(support.TESTFN)
             self.assertEqual(s[0] & stat.S_IRWXU, stat.S_IRUSR | stat.S_IWUSR)
         finally:
             posix.close(f)
 
-    @unittest.skipUnless(hasattr(posix, 'fchownat'), "test needs posix.fchownat()")
-    def test_fchownat(self):
+    @unittest.skipUnless(os.chown in os.supports_dir_fd, "test needs dir_fd support in os.chown()")
+    def test_chown_dir_fd(self):
         support.unlink(support.TESTFN)
         support.create_empty_file(support.TESTFN)
 
         f = posix.open(posix.getcwd(), posix.O_RDONLY)
         try:
-            posix.fchownat(f, support.TESTFN, os.getuid(), os.getgid())
+            posix.chown(support.TESTFN, os.getuid(), os.getgid(), dir_fd=f)
         finally:
             posix.close(f)
 
-    @unittest.skipUnless(hasattr(posix, 'fstatat'), "test needs posix.fstatat()")
-    def test_fstatat(self):
+    @unittest.skipUnless(os.stat in os.supports_dir_fd, "test needs dir_fd support in os.stat()")
+    def test_stat_dir_fd(self):
         support.unlink(support.TESTFN)
         with open(support.TESTFN, 'w') as outfile:
             outfile.write("testline\n")
@@ -698,31 +697,41 @@
         f = posix.open(posix.getcwd(), posix.O_RDONLY)
         try:
             s1 = posix.stat(support.TESTFN)
-            s2 = posix.fstatat(f, support.TESTFN)
+            s2 = posix.stat(support.TESTFN, dir_fd=f)
             self.assertEqual(s1, s2)
         finally:
             posix.close(f)
 
-    @unittest.skipUnless(hasattr(posix, 'futimesat'), "test needs posix.futimesat()")
-    def test_futimesat(self):
+    @unittest.skipUnless(os.utime in os.supports_dir_fd, "test needs dir_fd support in os.utime()")
+    def test_utime_dir_fd(self):
         f = posix.open(posix.getcwd(), posix.O_RDONLY)
         try:
             now = time.time()
-            posix.futimesat(f, support.TESTFN, None)
-            posix.futimesat(f, support.TESTFN)
-            self.assertRaises(TypeError, posix.futimesat, f, support.TESTFN, (None, None))
-            self.assertRaises(TypeError, posix.futimesat, f, support.TESTFN, (now, None))
-            self.assertRaises(TypeError, posix.futimesat, f, support.TESTFN, (None, now))
-            posix.futimesat(f, support.TESTFN, (int(now), int(now)))
-            posix.futimesat(f, support.TESTFN, (now, now))
+            posix.utime(support.TESTFN, None, dir_fd=f)
+            posix.utime(support.TESTFN, dir_fd=f)
+            self.assertRaises(TypeError, posix.utime, support.TESTFN, now, dir_fd=f)
+            self.assertRaises(TypeError, posix.utime, support.TESTFN, (None, None), dir_fd=f)
+            self.assertRaises(TypeError, posix.utime, support.TESTFN, (now, None), dir_fd=f)
+            self.assertRaises(TypeError, posix.utime, support.TESTFN, (None, now), dir_fd=f)
+            self.assertRaises(TypeError, posix.utime, support.TESTFN, (now, "x"), dir_fd=f)
+            posix.utime(support.TESTFN, (int(now), int(now)), dir_fd=f)
+            posix.utime(support.TESTFN, (now, now), dir_fd=f)
+            posix.utime(support.TESTFN,
+                    (int(now), int((now - int(now)) * 1e9)), dir_fd=f)
+            posix.utime(support.TESTFN, dir_fd=f,
+                            times=(int(now), int((now - int(now)) * 1e9)))
+
+            if os.utime in os.supports_follow_symlinks:
+                posix.utime(support.TESTFN, follow_symlinks=False, dir_fd=f)
+
         finally:
             posix.close(f)
 
-    @unittest.skipUnless(hasattr(posix, 'linkat'), "test needs posix.linkat()")
-    def test_linkat(self):
+    @unittest.skipUnless(os.link in os.supports_dir_fd, "test needs dir_fd support in os.link()")
+    def test_link_dir_fd(self):
         f = posix.open(posix.getcwd(), posix.O_RDONLY)
         try:
-            posix.linkat(f, support.TESTFN, f, support.TESTFN + 'link')
+            posix.link(support.TESTFN, support.TESTFN + 'link', src_dir_fd=f, dst_dir_fd=f)
             # should have same inodes
             self.assertEqual(posix.stat(support.TESTFN)[1],
                 posix.stat(support.TESTFN + 'link')[1])
@@ -730,26 +739,26 @@
             posix.close(f)
             support.unlink(support.TESTFN + 'link')
 
-    @unittest.skipUnless(hasattr(posix, 'mkdirat'), "test needs posix.mkdirat()")
-    def test_mkdirat(self):
+    @unittest.skipUnless(os.mkdir in os.supports_dir_fd, "test needs dir_fd support in os.mkdir()")
+    def test_mkdir_dir_fd(self):
         f = posix.open(posix.getcwd(), posix.O_RDONLY)
         try:
-            posix.mkdirat(f, support.TESTFN + 'dir')
+            posix.mkdir(support.TESTFN + 'dir', dir_fd=f)
             posix.stat(support.TESTFN + 'dir') # should not raise exception
         finally:
             posix.close(f)
             support.rmtree(support.TESTFN + 'dir')
 
-    @unittest.skipUnless(hasattr(posix, 'mknodat') and hasattr(stat, 'S_IFIFO'),
-                         "don't have mknodat()/S_IFIFO")
-    def test_mknodat(self):
+    @unittest.skipUnless((os.mknod in os.supports_dir_fd) and hasattr(stat, 'S_IFIFO'),
+                         "test requires both stat.S_IFIFO and dir_fd support for os.mknod()")
+    def test_mknod_dir_fd(self):
         # Test using mknodat() to create a FIFO (the only use specified
         # by POSIX).
         support.unlink(support.TESTFN)
         mode = stat.S_IFIFO | stat.S_IRUSR | stat.S_IWUSR
         f = posix.open(posix.getcwd(), posix.O_RDONLY)
         try:
-            posix.mknodat(f, support.TESTFN, mode, 0)
+            posix.mknod(support.TESTFN, mode, 0, dir_fd=f)
         except OSError as e:
             # Some old systems don't allow unprivileged users to use
             # mknod(), or only support creating device nodes.
@@ -759,13 +768,13 @@
         finally:
             posix.close(f)
 
-    @unittest.skipUnless(hasattr(posix, 'openat'), "test needs posix.openat()")
-    def test_openat(self):
+    @unittest.skipUnless(os.open in os.supports_dir_fd, "test needs dir_fd support in os.open()")
+    def test_open_dir_fd(self):
         support.unlink(support.TESTFN)
         with open(support.TESTFN, 'w') as outfile:
             outfile.write("testline\n")
         a = posix.open(posix.getcwd(), posix.O_RDONLY)
-        b = posix.openat(a, support.TESTFN, posix.O_RDONLY)
+        b = posix.open(support.TESTFN, posix.O_RDONLY, dir_fd=a)
         try:
             res = posix.read(b, 9).decode(encoding="utf-8")
             self.assertEqual("testline\n", res)
@@ -773,24 +782,24 @@
             posix.close(a)
             posix.close(b)
 
-    @unittest.skipUnless(hasattr(posix, 'readlinkat'), "test needs posix.readlinkat()")
-    def test_readlinkat(self):
+    @unittest.skipUnless(os.readlink in os.supports_dir_fd, "test needs dir_fd support in os.readlink()")
+    def test_readlink_dir_fd(self):
         os.symlink(support.TESTFN, support.TESTFN + 'link')
         f = posix.open(posix.getcwd(), posix.O_RDONLY)
         try:
             self.assertEqual(posix.readlink(support.TESTFN + 'link'),
-                posix.readlinkat(f, support.TESTFN + 'link'))
+                posix.readlink(support.TESTFN + 'link', dir_fd=f))
         finally:
             support.unlink(support.TESTFN + 'link')
             posix.close(f)
 
-    @unittest.skipUnless(hasattr(posix, 'renameat'), "test needs posix.renameat()")
-    def test_renameat(self):
+    @unittest.skipUnless(os.rename in os.supports_dir_fd, "test needs dir_fd support in os.rename()")
+    def test_rename_dir_fd(self):
         support.unlink(support.TESTFN)
         support.create_empty_file(support.TESTFN + 'ren')
         f = posix.open(posix.getcwd(), posix.O_RDONLY)
         try:
-            posix.renameat(f, support.TESTFN + 'ren', f, support.TESTFN)
+            posix.rename(support.TESTFN + 'ren', support.TESTFN, src_dir_fd=f, dst_dir_fd=f)
         except:
             posix.rename(support.TESTFN + 'ren', support.TESTFN)
             raise
@@ -799,23 +808,23 @@
         finally:
             posix.close(f)
 
-    @unittest.skipUnless(hasattr(posix, 'symlinkat'), "test needs posix.symlinkat()")
-    def test_symlinkat(self):
+    @unittest.skipUnless(os.symlink in os.supports_dir_fd, "test needs dir_fd support in os.symlink()")
+    def test_symlink_dir_fd(self):
         f = posix.open(posix.getcwd(), posix.O_RDONLY)
         try:
-            posix.symlinkat(support.TESTFN, f, support.TESTFN + 'link')
+            posix.symlink(support.TESTFN, support.TESTFN + 'link', dir_fd=f)
             self.assertEqual(posix.readlink(support.TESTFN + 'link'), support.TESTFN)
         finally:
             posix.close(f)
             support.unlink(support.TESTFN + 'link')
 
-    @unittest.skipUnless(hasattr(posix, 'unlinkat'), "test needs posix.unlinkat()")
-    def test_unlinkat(self):
+    @unittest.skipUnless(os.unlink in os.supports_dir_fd, "test needs dir_fd support in os.unlink()")
+    def test_unlink_dir_fd(self):
         f = posix.open(posix.getcwd(), posix.O_RDONLY)
         support.create_empty_file(support.TESTFN + 'del')
         posix.stat(support.TESTFN + 'del') # should not throw exception
         try:
-            posix.unlinkat(f, support.TESTFN + 'del')
+            posix.unlink(support.TESTFN + 'del', dir_fd=f)
         except:
             support.unlink(support.TESTFN + 'del')
             raise
@@ -824,31 +833,12 @@
         finally:
             posix.close(f)
 
-    @unittest.skipUnless(hasattr(posix, 'utimensat'), "test needs posix.utimensat()")
-    def test_utimensat(self):
-        f = posix.open(posix.getcwd(), posix.O_RDONLY)
-        try:
-            now = time.time()
-            posix.utimensat(f, support.TESTFN, None, None)
-            posix.utimensat(f, support.TESTFN)
-            posix.utimensat(f, support.TESTFN, flags=os.AT_SYMLINK_NOFOLLOW)
-            self.assertRaises(TypeError, posix.utimensat, f, support.TESTFN, (None, None), (None, None))
-            self.assertRaises(TypeError, posix.utimensat, f, support.TESTFN, (now, 0), None)
-            self.assertRaises(TypeError, posix.utimensat, f, support.TESTFN, None, (now, 0))
-            posix.utimensat(f, support.TESTFN, (int(now), int((now - int(now)) * 1e9)),
-                    (int(now), int((now - int(now)) * 1e9)))
-            posix.utimensat(dirfd=f, path=support.TESTFN,
-                            atime=(int(now), int((now - int(now)) * 1e9)),
-                            mtime=(int(now), int((now - int(now)) * 1e9)))
-        finally:
-            posix.close(f)
-
-    @unittest.skipUnless(hasattr(posix, 'mkfifoat'), "don't have mkfifoat()")
-    def test_mkfifoat(self):
+    @unittest.skipUnless(os.mkfifo in os.supports_dir_fd, "test needs dir_fd support in os.mkfifo()")
+    def test_mkfifo_dir_fd(self):
         support.unlink(support.TESTFN)
         f = posix.open(posix.getcwd(), posix.O_RDONLY)
         try:
-            posix.mkfifoat(f, support.TESTFN, stat.S_IRUSR | stat.S_IWUSR)
+            posix.mkfifo(support.TESTFN, stat.S_IRUSR | stat.S_IWUSR, dir_fd=f)
             self.assertTrue(stat.S_ISFIFO(posix.stat(support.TESTFN).st_mode))
         finally:
             posix.close(f)
diff --git a/Lib/test/test_shutil.py b/Lib/test/test_shutil.py
--- a/Lib/test/test_shutil.py
+++ b/Lib/test/test_shutil.py
@@ -268,7 +268,7 @@
         # don't follow
         shutil.copystat(src_link, dst_link, symlinks=True)
         dst_link_stat = os.lstat(dst_link)
-        if hasattr(os, 'lutimes'):
+        if os.utime in os.supports_follow_symlinks:
             for attr in 'st_atime', 'st_mtime':
                 # The modification times may be truncated in the new file.
                 self.assertLessEqual(getattr(src_link_stat, attr),
@@ -334,11 +334,11 @@
         write_file(dst, 'bar')
         os_error = OSError(errno.EPERM, 'EPERM')
 
-        def _raise_on_user_foo(fname, attr, val):
+        def _raise_on_user_foo(fname, attr, val, **kwargs):
             if attr == 'user.foo':
                 raise os_error
             else:
-                orig_setxattr(fname, attr, val)
+                orig_setxattr(fname, attr, val, **kwargs)
         try:
             orig_setxattr = os.setxattr
             os.setxattr = _raise_on_user_foo
@@ -361,13 +361,13 @@
         write_file(src, 'foo')
         os.symlink(src, src_link)
         os.setxattr(src, 'trusted.foo', b'42')
-        os.lsetxattr(src_link, 'trusted.foo', b'43')
+        os.setxattr(src_link, 'trusted.foo', b'43', follow_symlinks=False)
         dst = os.path.join(tmp_dir, 'bar')
         dst_link = os.path.join(tmp_dir, 'qux')
         write_file(dst, 'bar')
         os.symlink(dst, dst_link)
         shutil._copyxattr(src_link, dst_link, symlinks=True)
-        self.assertEqual(os.lgetxattr(dst_link, 'trusted.foo'), b'43')
+        self.assertEqual(os.getxattr(dst_link, 'trusted.foo', follow_symlinks=False), b'43')
         self.assertRaises(OSError, os.getxattr, dst, 'trusted.foo')
         shutil._copyxattr(src_link, dst, symlinks=True)
         self.assertEqual(os.getxattr(dst, 'trusted.foo'), b'43')
@@ -419,7 +419,7 @@
         self.assertTrue(os.path.islink(dst))
         self.assertEqual(os.readlink(dst), os.readlink(src_link))
         dst_stat = os.lstat(dst)
-        if hasattr(os, 'lutimes'):
+        if os.utime in os.supports_follow_symlinks:
             for attr in 'st_atime', 'st_mtime':
                 # The modification times may be truncated in the new file.
                 self.assertLessEqual(getattr(src_link_stat, attr),
diff --git a/Misc/NEWS b/Misc/NEWS
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -40,6 +40,14 @@
 Library
 -------
 
+- Issue #14626: Large refactoring of functions / parameters in the os module.
+  Many functions now support "dir_fd" and "follow_symlinks" parameters;
+  some also support accepting an open file descriptor in place of of a path
+  string.  Added os.support_* collections as LBYL helpers.  Removed many
+  functions only previously seen in 3.3 alpha releases (often starting with
+  "f" or "l", or ending with "at").  Originally suggested by Serhiy Storchaka;
+  implemented by Larry Hastings.
+
 - Issue #15008: Implement PEP 362 "Signature Objects".
   Patch by Yury Selivanov.
 
@@ -56,7 +64,7 @@
 - Issue #9527: datetime.astimezone() method will now supply a class
   timezone instance corresponding to the system local timezone when
   called with no arguments.
-
+  
 - Issue #14653: email.utils.mktime_tz() no longer relies on system
   mktime() when timezone offest is supplied.
 
diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c
--- a/Modules/posixmodule.c
+++ b/Modules/posixmodule.c
@@ -379,10 +379,12 @@
 #undef STRUCT_STAT
 #if defined(MS_WIN64) || defined(MS_WINDOWS)
 #       define STAT win32_stat
+#       define LSTAT win32_lstat
 #       define FSTAT win32_fstat
 #       define STRUCT_STAT struct win32_stat
 #else
 #       define STAT stat
+#       define LSTAT lstat
 #       define FSTAT fstat
 #       define STRUCT_STAT struct stat
 #endif
@@ -398,6 +400,379 @@
 #endif
 #endif
 
+
+#ifdef MS_WINDOWS
+static int
+win32_warn_bytes_api()
+{
+    return PyErr_WarnEx(PyExc_DeprecationWarning,
+        "The Windows bytes API has been deprecated, "
+        "use Unicode filenames instead",
+        1);
+}
+#endif
+
+
+#ifdef AT_FDCWD
+#define DEFAULT_DIR_FD AT_FDCWD
+#else
+#define DEFAULT_DIR_FD (-100)
+#endif
+
+static int
+_fd_converter(PyObject *o, int *p, int default_value) {
+    long long_value;
+    if (o == Py_None) {
+        *p = default_value;
+        return 1;
+    }
+    if (PyFloat_Check(o)) {
+        PyErr_SetString(PyExc_TypeError,
+                        "integer argument expected, got float" );
+        return 0;
+    }
+    long_value = PyLong_AsLong(o);
+    if (long_value == -1 && PyErr_Occurred())
+        return 0;
+    if (long_value > INT_MAX) {
+        PyErr_SetString(PyExc_OverflowError,
+                        "signed integer is greater than maximum");
+        return 0;
+    }
+    if (long_value < INT_MIN) {
+        PyErr_SetString(PyExc_OverflowError,
+                        "signed integer is less than minimum");
+        return 0;
+    }
+    *p = (int)long_value;
+    return 1;
+}
+
+static int
+dir_fd_converter(PyObject *o, void *p) {
+    return _fd_converter(o, (int *)p, DEFAULT_DIR_FD);
+}
+
+
+
+/*
+ * A PyArg_ParseTuple "converter" function
+ * that handles filesystem paths in the manner
+ * preferred by the os module.
+ *
+ * path_converter accepts (Unicode) strings and their
+ * subclasses, and bytes and their subclasses.  What
+ * it does with the argument depends on the platform:
+ *
+ *   * On Windows, if we get a (Unicode) string we
+ *     extract the wchar_t * and return it; if we get
+ *     bytes we extract the char * and return that.
+ *
+ *   * On all other platforms, strings are encoded
+ *     to bytes using PyUnicode_FSConverter, then we
+ *     extract the char * from the bytes object and
+ *     return that.
+ *
+ * path_converter also optionally accepts signed
+ * integers (representing open file descriptors) instead
+ * of path strings.
+ *
+ * Input fields:
+ *   path.nullable
+ *     If nonzero, the path is permitted to be None.
+ *   path.allow_fd
+ *     If nonzero, the path is permitted to be a file handle
+ *     (a signed int) instead of a string.
+ *   path.function_name
+ *     If non-NULL, path_converter will use that as the name
+ *     of the function in error messages.
+ *     (If path.argument_name is NULL it omits the function name.)
+ *   path.argument_name
+ *     If non-NULL, path_converter will use that as the name
+ *     of the parameter in error messages.
+ *     (If path.argument_name is NULL it uses "path".)
+ *
+ * Output fields:
+ *   path.wide
+ *     Points to the path if it was expressed as Unicode
+ *     and was not encoded.  (Only used on Windows.)
+ *   path.narrow
+ *     Points to the path if it was expressed as bytes,
+ *     or it was Unicode and was encoded to bytes.
+ *   path.fd
+ *     Contains a file descriptor if path.accept_fd was true
+ *     and the caller provided a signed integer instead of any
+ *     sort of string.
+ *
+ *     WARNING: if your "path" parameter is optional, and is
+ *     unspecified, path_converter will never get called.
+ *     So if you set allow_fd, you *MUST* initialize path.fd = -1
+ *     yourself!
+ *   path.length
+ *     The length of the path in characters, if specified as
+ *     a string.
+ *   path.object
+ *     The original object passed in.
+ *   path.cleanup
+ *     For internal use only.  May point to a temporary object.
+ *     (Pay no attention to the man behind the curtain.)
+ *
+ *   At most one of path.wide or path.narrow will be non-NULL.
+ *   If path was None and path.nullable was set,
+ *     or if path was an integer and path.allow_fd was set,
+ *     both path.wide and path.narrow will be NULL
+ *     and path.length will be 0.
+ *   
+ *   path_converter takes care to not write to the path_t
+ *   unless it's successful.  However it must reset the
+ *   "cleanup" field each time it's called.
+ *
+ * Use as follows:
+ *      path_t path;
+ *      memset(&path, 0, sizeof(path));
+ *      PyArg_ParseTuple(args, "O&", path_converter, &path);
+ *      // ... use values from path ...
+ *      path_cleanup(&path);
+ *
+ * (Note that if PyArg_Parse fails you don't need to call
+ * path_cleanup().  However it is safe to do so.)
+ */
+typedef struct {
+    char *function_name;
+    char *argument_name;
+    int nullable;
+    int allow_fd;
+    wchar_t *wide;
+    char *narrow;
+    int fd;
+    Py_ssize_t length;
+    PyObject *object;
+    PyObject *cleanup;
+} path_t;
+
+static void
+path_cleanup(path_t *path) {
+    if (path->cleanup) {
+        Py_DECREF(path->cleanup);
+        path->cleanup = NULL;
+    }
+}
+
+static int
+path_converter(PyObject *o, void *p) {
+    path_t *path = (path_t *)p;
+    PyObject *unicode, *bytes;
+    Py_ssize_t length;
+    char *narrow;
+
+#define FORMAT_EXCEPTION(exc, fmt) \
+    PyErr_Format(exc, "%s%s" fmt, \
+        path->function_name ? path->function_name : "", \
+        path->function_name ? ": "                : "", \
+        path->argument_name ? path->argument_name : "path")
+
+    /* Py_CLEANUP_SUPPORTED support */
+    if (o == NULL) {
+        path_cleanup(path);
+        return 1;
+    }
+
+    /* ensure it's always safe to call path_cleanup() */
+    path->cleanup = NULL;
+
+    if (o == Py_None) {
+        if (!path->nullable) {
+            FORMAT_EXCEPTION(PyExc_TypeError,
+                             "can't specify None for %s argument");
+            return 0;
+        }
+        path->wide = NULL;
+        path->narrow = NULL;
+        path->length = 0;
+        path->object = o;
+        path->fd = -1;
+        return 1;
+    }
+
+    unicode = PyUnicode_FromObject(o);
+    if (unicode) {
+#ifdef MS_WINDOWS
+        wchar_t *wide;
+        length = PyUnicode_GET_SIZE(unicode);
+        if (length > 32767) {
+            FORMAT_EXCEPTION(PyExc_ValueError, "%s too long for Windows");
+            Py_DECREF(unicode);
+            return 0;
+        }
+
+        wide = PyUnicode_AsUnicode(unicode);
+        if (!wide) {
+            Py_DECREF(unicode);
+            return 0;
+        }
+
+        path->wide = wide;
+        path->narrow = NULL;
+        path->length = length;
+        path->object = o;
+        path->fd = -1;
+        path->cleanup = unicode;
+        return Py_CLEANUP_SUPPORTED;
+#else
+        int converted = PyUnicode_FSConverter(unicode, &bytes);
+        Py_DECREF(unicode);
+        if (!converted)
+            bytes = NULL;
+#endif
+    }
+    else {
+        PyErr_Clear();
+        bytes = PyBytes_FromObject(o);
+        if (!bytes) {
+            PyErr_Clear();
+            if (path->allow_fd) {
+                int fd;
+                /*
+                 * note: _fd_converter always permits None.
+                 * but we've already done our None check.
+                 * so o cannot be None at this point.
+                 */
+                int result = _fd_converter(o, &fd, -1);
+                if (result) {
+                    path->wide = NULL;
+                    path->narrow = NULL;
+                    path->length = 0;
+                    path->object = o;
+                    path->fd = fd;
+                    return result;
+                }
+            }
+        }
+    }
+
+    if (!bytes) {
+        if (!PyErr_Occurred())
+            FORMAT_EXCEPTION(PyExc_TypeError, "illegal type for %s parameter");
+        return 0;
+    }
+
+#ifdef MS_WINDOWS
+    if (win32_warn_bytes_api()) {
+        Py_DECREF(bytes);
+        return 0;
+    }
+#endif
+
+    length = PyBytes_GET_SIZE(bytes);
+#ifdef MS_WINDOWS
+    if (length > MAX_PATH) {
+        FORMAT_EXCEPTION(PyExc_ValueError, "%s too long for Windows");
+        Py_DECREF(bytes);
+        return 0;
+    }
+#endif
+
+    narrow = PyBytes_AS_STRING(bytes);
+    if (length != strlen(narrow)) {
+        FORMAT_EXCEPTION(PyExc_ValueError, "embedded NUL character in %s");
+        Py_DECREF(bytes);
+        return 0;
+    }
+
+    path->wide = NULL;
+    path->narrow = narrow;
+    path->length = length;
+    path->object = o;
+    path->fd = -1;
+    path->cleanup = bytes;
+    return Py_CLEANUP_SUPPORTED;
+}
+
+static void
+argument_unavailable_error(char *function_name, char *argument_name) {
+    PyErr_Format(PyExc_NotImplementedError,
+        "%s%s%s unavailable on this platform",
+        (function_name != NULL) ? function_name : "",
+        (function_name != NULL) ? ": ": "",
+        argument_name);
+}
+
+static int
+dir_fd_unavailable(PyObject *o, void *p) {
+    int *dir_fd = (int *)p;
+    int return_value = _fd_converter(o, dir_fd, DEFAULT_DIR_FD);
+    if (!return_value)
+        return 0;
+    if (*dir_fd == DEFAULT_DIR_FD)
+        return 1;
+    argument_unavailable_error(NULL, "dir_fd");
+    return 0;
+}
+
+static int
+fd_specified(char *function_name, int fd) {
+    if (fd == -1)
+        return 0;
+
+    argument_unavailable_error(function_name, "fd");
+    return 1;
+}
+
+static int
+follow_symlinks_specified(char *function_name, int follow_symlinks) {
+    if (follow_symlinks)
+        return 0;
+
+    argument_unavailable_error(function_name, "follow_symlinks");
+    return 1;
+}
+
+static int
+path_and_dir_fd_invalid(char *function_name, path_t *path, int dir_fd) {
+    if (!path->narrow && !path->wide && (dir_fd != DEFAULT_DIR_FD)) {
+        PyErr_Format(PyExc_ValueError,
+                     "%s: can't specify dir_fd without matching path",
+                     function_name);
+        return 1;
+    }
+    return 0;
+}
+
+static int
+dir_fd_and_fd_invalid(char *function_name, int dir_fd, int fd) {
+    if ((dir_fd != DEFAULT_DIR_FD) && (fd != -1)) {
+        PyErr_Format(PyExc_ValueError,
+                     "%s: can't specify both dir_fd and fd",
+                     function_name);
+        return 1;
+    }
+    return 0;
+}
+
+static int
+fd_and_follow_symlinks_invalid(char *function_name, int fd,
+                               int follow_symlinks) {
+    if ((fd > 0) && (!follow_symlinks)) {
+        PyErr_Format(PyExc_ValueError,
+                     "%s: cannot use fd and follow_symlinks together",
+                     function_name);
+        return 1;
+    }
+    return 0;
+}
+
+static int
+dir_fd_and_follow_symlinks_invalid(char *function_name, int dir_fd,
+                                   int follow_symlinks) {
+    if ((dir_fd != DEFAULT_DIR_FD) && (!follow_symlinks)) {
+        PyErr_Format(PyExc_ValueError,
+                     "%s: cannot use dir_fd and follow_symlinks together",
+                     function_name);
+        return 1;
+    }
+    return 0;
+}
+
 /* A helper used by a number of POSIX-only functions */
 #ifndef MS_WINDOWS
 static int
@@ -566,33 +941,6 @@
     return TRUE;
 }
 
-static int
-win32_warn_bytes_api()
-{
-    return PyErr_WarnEx(PyExc_DeprecationWarning,
-        "The Windows bytes API has been deprecated, "
-        "use Unicode filenames instead",
-        1);
-}
-
-static PyObject*
-win32_decode_filename(PyObject *obj)
-{
-    PyObject *unicode;
-    if (PyUnicode_Check(obj)) {
-        if (PyUnicode_READY(obj))
-            return NULL;
-        Py_INCREF(obj);
-        return obj;
-    }
-    if (!PyUnicode_FSDecoder(obj, &unicode))
-        return NULL;
-    if (win32_warn_bytes_api()) {
-        Py_DECREF(unicode);
-        return NULL;
-    }
-    return unicode;
-}
 #endif /* MS_WINDOWS */
 
 /* Return a dictionary corresponding to the POSIX environment table */
@@ -774,6 +1122,32 @@
 
 #endif /* MS_WINDOWS */
 
+/*
+ * Some functions return Win32 errors, others only ever use posix_error
+ * (this is for backwards compatibility with exceptions)
+ */
+static PyObject *
+path_posix_error(char *function_name, path_t *path)
+{
+    if (path->narrow)
+        return posix_error_with_filename(path->narrow);
+    return posix_error();
+}
+
+static PyObject *
+path_error(char *function_name, path_t *path)
+{
+#ifdef MS_WINDOWS
+    if (path->narrow)
+        return win32_error(function_name, path->narrow);
+    if (path->wide)
+        return win32_error_unicode(function_name, path->wide);
+    return win32_error(function_name, NULL);
+#else
+    return path_posix_error(function_name, path);
+#endif
+}
+
 #if defined(PYOS_OS2)
 /**********************************************************************
  *         Helper Function to Trim and Format OS/2 Messages
@@ -892,32 +1266,6 @@
     return Py_None;
 }
 
-static PyObject *
-posix_2str(PyObject *args,
-           char *format,
-           int (*func)(const char *, const char *))
-{
-    PyObject *opath1 = NULL, *opath2 = NULL;
-    char *path1, *path2;
-    int res;
-    if (!PyArg_ParseTuple(args, format,
-                          PyUnicode_FSConverter, &opath1,
-                          PyUnicode_FSConverter, &opath2)) {
-        return NULL;
-    }
-    path1 = PyBytes_AsString(opath1);
-    path2 = PyBytes_AsString(opath2);
-    Py_BEGIN_ALLOW_THREADS
-    res = (*func)(path1, path2);
-    Py_END_ALLOW_THREADS
-    Py_DECREF(opath1);
-    Py_DECREF(opath2);
-    if (res != 0)
-        /* XXX how to report both path1 and path2??? */
-        return posix_error();
-    Py_INCREF(Py_None);
-    return Py_None;
-}
 
 #ifdef MS_WINDOWS
 static PyObject*
@@ -1525,7 +1873,7 @@
 #endif /* MS_WINDOWS */
 
 PyDoc_STRVAR(stat_result__doc__,
-"stat_result: Result from stat or lstat.\n\n\
+"stat_result: Result from stat, fstat, or lstat.\n\n\
 This object may be accessed either as a tuple of\n\
   (mode, ino, dev, nlink, uid, gid, size, atime, mtime, ctime)\n\
 or via the attributes st_mode, st_ino, st_dev, st_nlink, st_uid, and so on.\n\
@@ -1879,132 +2227,238 @@
     return v;
 }
 
-static PyObject *
-posix_do_stat(PyObject *self, PyObject *args,
-              char *format,
-#ifdef __VMS
-              int (*statfunc)(const char *, STRUCT_STAT *, ...),
-#else
-              int (*statfunc)(const char *, STRUCT_STAT *),
-#endif
-              char *wformat,
-              int (*wstatfunc)(const wchar_t *, STRUCT_STAT *))
+/* POSIX methods */
+
+
+static PyObject *
+posix_do_stat(char *function_name, path_t *path,
+              int dir_fd, int follow_symlinks)
 {
     STRUCT_STAT st;
-    PyObject *opath;
-    char *path;
-    int res;
-    PyObject *result;
-
+    int result;
+
+#if !defined(MS_WINDOWS) && !defined(HAVE_FSTATAT) && !defined(HAVE_LSTAT)
+    if (follow_symlinks_specified(function_name, follow_symlinks))
+        return NULL;
+#endif
+
+    if (path_and_dir_fd_invalid("stat", path, dir_fd) ||
+        dir_fd_and_fd_invalid("stat", dir_fd, path->fd) ||
+        fd_and_follow_symlinks_invalid("stat", path->fd, follow_symlinks))
+        return NULL;
+
+    Py_BEGIN_ALLOW_THREADS
+    if (path->fd != -1)
+        result = FSTAT(path->fd, &st);
+    else
 #ifdef MS_WINDOWS
-    PyObject *po;
-    if (PyArg_ParseTuple(args, wformat, &po)) {
-        wchar_t *wpath = PyUnicode_AsUnicode(po);
-        if (wpath == NULL)
-            return NULL;
-
-        Py_BEGIN_ALLOW_THREADS
-        res = wstatfunc(wpath, &st);
-        Py_END_ALLOW_THREADS
-
-        if (res != 0)
-            return win32_error_object("stat", po);
-        return _pystat_fromstructstat(&st);
-    }
-    /* Drop the argument parsing error as narrow strings
-       are also valid. */
-    PyErr_Clear();
-#endif
-
-    if (!PyArg_ParseTuple(args, format,
-                          PyUnicode_FSConverter, &opath))
-        return NULL;
-#ifdef MS_WINDOWS
-    if (win32_warn_bytes_api()) {
-        Py_DECREF(opath);
-        return NULL;
-    }
-#endif
-    path = PyBytes_AsString(opath);
-    Py_BEGIN_ALLOW_THREADS
-    res = (*statfunc)(path, &st);
+    if (path->wide) {
+        if (follow_symlinks)
+            result = win32_stat_w(path->wide, &st);
+        else
+            result = win32_lstat_w(path->wide, &st);
+    }
+    else
+#endif
+#if defined(HAVE_LSTAT) || defined(MS_WINDOWS)
+    if ((!follow_symlinks) && (dir_fd == DEFAULT_DIR_FD))
+        result = LSTAT(path->narrow, &st);
+    else
+#endif
+#ifdef HAVE_FSTATAT
+    if ((dir_fd != DEFAULT_DIR_FD) || !follow_symlinks)
+        result = fstatat(dir_fd, path->narrow, &st,
+                         follow_symlinks ? 0 : AT_SYMLINK_NOFOLLOW);
+    else
+#endif
+        result = STAT(path->narrow, &st);
     Py_END_ALLOW_THREADS
 
-    if (res != 0) {
-#ifdef MS_WINDOWS
-        result = win32_error("stat", path);
-#else
-        result = posix_error_with_filename(path);
-#endif
-    }
-    else
-        result = _pystat_fromstructstat(&st);
-
-    Py_DECREF(opath);
-    return result;
-}
-
-/* POSIX methods */
+    if (result != 0)
+        return path_error("stat", path);
+
+    return _pystat_fromstructstat(&st);
+}
+
+PyDoc_STRVAR(posix_stat__doc__,
+"stat(path, *, dir_fd=None, follow_symlinks=True) -> stat result\n\n\
+Perform a stat system call on the given path.\n\
+\n\
+path may be specified as either a string or as an open file descriptor.\n\
+\n\
+If dir_fd is not None, it should be a file descriptor open to a directory,\n\
+  and path should be relative; path will then be relative to that directory.\n\
+  dir_fd may not be supported on your platform; if it is unavailable, using\n\
+  it will raise a NotImplementedError.\n\
+If follow_symlinks is False, and the last element of the path is a symbolic\n\
+  link, stat will examine the symbolic link itself instead of the file the\n\
+  link points to.\n\
+It is an error to use dir_fd or follow_symlinks when specifying path as\n\
+  an open file descriptor.");
+
+static PyObject *
+posix_stat(PyObject *self, PyObject *args, PyObject *kwargs)
+{
+    static char *keywords[] = {"path", "dir_fd", "follow_symlinks", NULL};
+    path_t path;
+    int dir_fd = DEFAULT_DIR_FD;
+    int follow_symlinks = 1;
+    PyObject *return_value;
+
+    memset(&path, 0, sizeof(path));
+    path.allow_fd = 1;
+    if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O&|$O&p:stat", keywords,
+        path_converter, &path,
+#ifdef HAVE_FSTATAT
+        dir_fd_converter, &dir_fd,
+#else
+        dir_fd_unavailable, &dir_fd,
+#endif
+        &follow_symlinks))
+        return NULL;
+    return_value = posix_do_stat("stat", &path, dir_fd, follow_symlinks);
+    path_cleanup(&path);
+    return return_value;
+}
+
+PyDoc_STRVAR(posix_lstat__doc__,
+"lstat(path, *, dir_fd=None) -> stat result\n\n\
+Like stat(), but do not follow symbolic links.\n\
+Equivalent to stat(path, follow_symlinks=False).");
+
+static PyObject *
+posix_lstat(PyObject *self, PyObject *args, PyObject *kwargs)
+{
+    static char *keywords[] = {"path", "dir_fd", NULL};
+    path_t path;
+    int dir_fd = DEFAULT_DIR_FD;
+    int follow_symlinks = 0;
+    PyObject *return_value;
+
+    memset(&path, 0, sizeof(path));
+    if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O&|$O&:lstat", keywords,
+        path_converter, &path,
+#ifdef HAVE_FSTATAT
+        dir_fd_converter, &dir_fd
+#else
+        dir_fd_unavailable, &dir_fd
+#endif
+        ))
+        return NULL;
+    return_value = posix_do_stat("stat", &path, dir_fd, follow_symlinks);
+    path_cleanup(&path);
+    return return_value;
+}
 
 PyDoc_STRVAR(posix_access__doc__,
-"access(path, mode) -> True if granted, False otherwise\n\n\
-Use the real uid/gid to test for access to a path.  Note that most\n\
-operations will use the effective uid/gid, therefore this routine can\n\
-be used in a suid/sgid environment to test if the invoking user has the\n\
-specified access to the path.  The mode argument can be F_OK to test\n\
-existence, or the inclusive-OR of R_OK, W_OK, and X_OK.");
-
-static PyObject *
-posix_access(PyObject *self, PyObject *args)
-{
-    const char *path;
+"access(path, mode, *, dir_fd=None, effective_ids=False,\
+ follow_symlinks=True)\n\n\
+Use the real uid/gid to test for access to a path.  Returns True if granted,\n\
+False otherwise.\n\
+\n\
+If dir_fd is not None, it should be a file descriptor open to a directory,\n\
+  and path should be relative; path will then be relative to that directory.\n\
+If effective_ids is True, access will use the effective uid/gid instead of\n\
+  the real uid/gid.\n\
+If follow_symlinks is False, and the last element of the path is a symbolic\n\
+  link, access will examine the symbolic link itself instead of the file the\n\
+  link points to.\n\
+dir_fd, effective_ids, and follow_symlinks may not be implemented\n\
+  on your platform.  If they are unavailable, using them will raise a\n\
+  NotImplementedError.\n\
+\n\
+Note that most operations will use the effective uid/gid, therefore this\n\
+  routine can be used in a suid/sgid environment to test if the invoking user\n\
+  has the specified access to the path.\n\
+The mode argument can be F_OK to test existence, or the inclusive-OR\n\
+  of R_OK, W_OK, and X_OK.");
+
+static PyObject *
+posix_access(PyObject *self, PyObject *args, PyObject *kwargs)
+{
+    static char *keywords[] = {"path", "mode", "dir_fd", "effective_ids",
+                                "follow_symlinks", NULL};
+    path_t path;
     int mode;
+    int dir_fd = DEFAULT_DIR_FD;
+    int effective_ids = 0;
+    int follow_symlinks = 1;
+    PyObject *return_value = NULL;
 
 #ifdef MS_WINDOWS
     DWORD attr;
-    PyObject *po;
-    if (PyArg_ParseTuple(args, "Ui:access", &po, &mode)) {
-        wchar_t* wpath = PyUnicode_AsUnicode(po);
-        if (wpath == NULL)
-            return NULL;
-        Py_BEGIN_ALLOW_THREADS
-        attr = GetFileAttributesW(wpath);
-        Py_END_ALLOW_THREADS
-        goto finish;
-    }
-    /* Drop the argument parsing error as narrow strings
-       are also valid. */
-    PyErr_Clear();
-    if (!PyArg_ParseTuple(args, "yi:access", &path, &mode))
-        return NULL;
-    if (win32_warn_bytes_api())
-        return NULL;
+#else
+    int result;
+#endif
+
+    memset(&path, 0, sizeof(path));
+    if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O&i|$O&pp:access", keywords,
+        path_converter, &path, &mode,
+#ifdef HAVE_FACCESSAT
+        dir_fd_converter, &dir_fd,
+#else
+        dir_fd_unavailable, &dir_fd,
+#endif
+        &effective_ids, &follow_symlinks))
+        return NULL;
+
+#ifndef HAVE_FACCESSAT
+    if (follow_symlinks_specified("access", follow_symlinks))
+        goto exit;
+
+    if (effective_ids) {
+        argument_unavailable_error("access", "effective_ids");
+        goto exit;
+    }
+#endif
+
+#ifdef MS_WINDOWS
     Py_BEGIN_ALLOW_THREADS
-    attr = GetFileAttributesA(path);
+    if (path.wide != NULL)
+        attr = GetFileAttributesW(path.wide);
+    else
+        attr = GetFileAttributesA(path.narrow);
     Py_END_ALLOW_THREADS
-finish:
-    if (attr == 0xFFFFFFFF)
-        /* File does not exist, or cannot read attributes */
-        return PyBool_FromLong(0);
-    /* Access is possible if either write access wasn't requested, or
-       the file isn't read-only, or if it's a directory, as there are
-       no read-only directories on Windows. */
-    return PyBool_FromLong(!(mode & 2)
-                           || !(attr & FILE_ATTRIBUTE_READONLY)
-                           || (attr & FILE_ATTRIBUTE_DIRECTORY));
-#else
-    PyObject *opath;
-    int res;
-    if (!PyArg_ParseTuple(args, "O&i:access",
-                          PyUnicode_FSConverter, &opath, &mode))
-        return NULL;
-    path = PyBytes_AsString(opath);
+
+    /*
+     * Access is possible if 
+     *   * we didn't get a -1, and
+     *     * write access wasn't requested,
+     *     * or the file isn't read-only,
+     *     * or it's a directory.
+     * (Directories cannot be read-only on Windows.)
+    */
+    return_value = PyBool_FromLong(
+        (attr != 0xFFFFFFFF) &&
+            ((mode & 2) ||
+            !(attr & FILE_ATTRIBUTE_READONLY) ||
+            (attr & FILE_ATTRIBUTE_DIRECTORY)));
+#else
+
     Py_BEGIN_ALLOW_THREADS
-    res = access(path, mode);
+#ifdef HAVE_FACCESSAT
+    if ((dir_fd != DEFAULT_DIR_FD) ||
+        effective_ids ||
+        !follow_symlinks) {
+        int flags = 0;
+        if (!follow_symlinks)
+            flags |= AT_SYMLINK_NOFOLLOW;
+        if (effective_ids)
+            flags |= AT_EACCESS;
+        result = faccessat(dir_fd, path.narrow, mode, flags);
+    }
+    else
+#endif
+        result = access(path.narrow, mode);
     Py_END_ALLOW_THREADS
-    Py_DECREF(opath);
-    return PyBool_FromLong(res == 0);
-#endif
+    return_value = PyBool_FromLong(!result);
+#endif
+
+#ifndef HAVE_FACCESSAT
+exit:
+#endif
+    path_cleanup(&path);
+    return return_value;
 }
 
 #ifndef F_OK
@@ -2075,27 +2529,66 @@
 
 PyDoc_STRVAR(posix_chdir__doc__,
 "chdir(path)\n\n\
-Change the current working directory to the specified path.");
-
-static PyObject *
-posix_chdir(PyObject *self, PyObject *args)
-{
+Change the current working directory to the specified path.\n\
+\n\
+path may always be specified as a string.\n\
+On some platforms, path may also be specified as an open file descriptor.\n\
+  If this functionality is unavailable, using it raises an exception.");
+
+static PyObject *
+posix_chdir(PyObject *self, PyObject *args, PyObject *kwargs)
+{
+    path_t path;
+    int result;
+    PyObject *return_value = NULL;
+    static char *keywords[] = {"path", NULL};
+
+    memset(&path, 0, sizeof(path));
+#ifdef HAVE_FCHDIR
+    path.allow_fd = 1;
+#endif
+    if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O&:chdir", keywords,
+        path_converter, &path
+        ))
+        return NULL;
+
+    Py_BEGIN_ALLOW_THREADS
 #ifdef MS_WINDOWS
-    return win32_1str(args, "chdir", "y:chdir", win32_chdir, "U:chdir", win32_wchdir);
+    if (path.wide)
+        result = win32_wchdir(path.wide);
+    else
+        result = win32_chdir(path.narrow);
+	result = !result; /* on unix, success = 0, on windows, success = !0 */
 #elif defined(PYOS_OS2) && defined(PYCC_GCC)
-    return posix_1str(args, "O&:chdir", _chdir2);
-#elif defined(__VMS)
-    return posix_1str(args, "O&:chdir", (int (*)(const char *))chdir);
-#else
-    return posix_1str(args, "O&:chdir", chdir);
-#endif
+    result = _chdir2(path.narrow);
+#else
+#ifdef HAVE_FCHDIR
+    if (path.fd != -1)
+        result = fchdir(path.fd);
+    else
+#endif
+        result = chdir(path.narrow);
+#endif
+    Py_END_ALLOW_THREADS
+
+    if (result) {
+        return_value = path_error("chdir", &path);
+        goto exit;
+    }
+
+    return_value = Py_None;
+    Py_INCREF(Py_None);
+    
+exit:
+    path_cleanup(&path);
+    return return_value;
 }
 
 #ifdef HAVE_FCHDIR
 PyDoc_STRVAR(posix_fchdir__doc__,
-"fchdir(fildes)\n\n\
-Change to the directory of the given file descriptor.  fildes must be\n\
-opened on a directory, not a file.");
+"fchdir(fd)\n\n\
+Change to the directory of the given file descriptor.  fd must be\n\
+opened on a directory, not a file.  Equivalent to os.chdir(fd).");
 
 static PyObject *
 posix_fchdir(PyObject *self, PyObject *fdobj)
@@ -2106,87 +2599,152 @@
 
 
 PyDoc_STRVAR(posix_chmod__doc__,
-"chmod(path, mode)\n\n\
-Change the access permissions of a file.");
-
-static PyObject *
-posix_chmod(PyObject *self, PyObject *args)
-{
-    PyObject *opath = NULL;
-    const char *path = NULL;
-    int i;
-    int res;
+"chmod(path, mode, *, dir_fd=None, follow_symlinks=True)\n\n\
+Change the access permissions of a file.\n\
+\n\
+path may always be specified as a string.\n\
+On some platforms, path may also be specified as an open file descriptor.\n\
+  If this functionality is unavailable, using it raises an exception.\n\
+If dir_fd is not None, it should be a file descriptor open to a directory,\n\
+  and path should be relative; path will then be relative to that directory.\n\
+If follow_symlinks is False, and the last element of the path is a symbolic\n\
+  link, chmod will modify the symbolic link itself instead of the file the\n\
+  link points to.\n\
+It is an error to use dir_fd or follow_symlinks when specifying path as\n\
+  an open file descriptor.\n\
+dir_fd and follow_symlinks may not be implemented on your platform.\n\
+  If they are unavailable, using them will raise a NotImplementedError.");
+
+static PyObject *
+posix_chmod(PyObject *self, PyObject *args, PyObject *kwargs)
+{
+    path_t path;
+    int mode;
+    int dir_fd = DEFAULT_DIR_FD;
+    int follow_symlinks = 1;
+    int result;
+    PyObject *return_value = NULL;
+    static char *keywords[] = {"path", "mode", "dir_fd",
+                               "follow_symlinks", NULL};
+
 #ifdef MS_WINDOWS
     DWORD attr;
-    PyObject *po;
-    if (PyArg_ParseTuple(args, "Ui|:chmod", &po, &i)) {
-        wchar_t *wpath = PyUnicode_AsUnicode(po);
-        if (wpath == NULL)
-            return NULL;
-        Py_BEGIN_ALLOW_THREADS
-        attr = GetFileAttributesW(wpath);
-        if (attr != 0xFFFFFFFF) {
-            if (i & _S_IWRITE)
-                attr &= ~FILE_ATTRIBUTE_READONLY;
-            else
-                attr |= FILE_ATTRIBUTE_READONLY;
-            res = SetFileAttributesW(wpath, attr);
-        }
-        else
-            res = 0;
-        Py_END_ALLOW_THREADS
-        if (!res)
-            return win32_error_object("chmod", po);
-        Py_INCREF(Py_None);
-        return Py_None;
-    }
-    /* Drop the argument parsing error as narrow strings
-       are also valid. */
-    PyErr_Clear();
-
-    if (!PyArg_ParseTuple(args, "yi:chmod", &path, &i))
-        return NULL;
-    if (win32_warn_bytes_api())
-        return NULL;
+#endif
+
+#ifdef HAVE_FCHMODAT
+    int fchmodat_nofollow_unsupported = 0;
+#endif
+
+    memset(&path, 0, sizeof(path));
+#ifdef HAVE_FCHMOD
+    path.allow_fd = 1;
+#endif
+    if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O&i|$O&p:chmod", keywords,
+        path_converter, &path,
+        &mode,
+#ifdef HAVE_FCHMODAT
+        dir_fd_converter, &dir_fd,
+#else
+        dir_fd_unavailable, &dir_fd,
+#endif
+        &follow_symlinks))
+        return NULL;
+
+#if !(defined(HAVE_FCHMODAT) || defined(HAVE_LCHMOD))
+    if (follow_symlinks_specified("chmod", follow_symlinks))
+        goto exit;
+#endif
+
+#ifdef MS_WINDOWS
     Py_BEGIN_ALLOW_THREADS
-    attr = GetFileAttributesA(path);
-    if (attr != 0xFFFFFFFF) {
-        if (i & _S_IWRITE)
+    if (path.wide)
+        attr = GetFileAttributesW(path.wide);
+    else
+        attr = GetFileAttributesA(path.narrow);
+    if (attr == 0xFFFFFFFF)
+        result = 0;
+    else {
+        if (mode & _S_IWRITE)
             attr &= ~FILE_ATTRIBUTE_READONLY;
         else
             attr |= FILE_ATTRIBUTE_READONLY;
-        res = SetFileAttributesA(path, attr);
-    }
+        if (path.wide)
+            result = SetFileAttributesW(path.wide, attr);
+        else
+            result = SetFileAttributesA(path.narrow, attr);
+    }
+    Py_END_ALLOW_THREADS
+
+    if (!result) {
+        return_value = win32_error_object("chmod", path.object);
+        goto exit;
+    }
+#else /* MS_WINDOWS */
+    Py_BEGIN_ALLOW_THREADS
+#ifdef HAVE_FCHMOD
+    if (path.fd != -1)
+        result = fchmod(path.fd, mode);
     else
-        res = 0;
+#endif
+#ifdef HAVE_LCHMOD
+    if ((!follow_symlinks) && (dir_fd == DEFAULT_DIR_FD))
+        result = lchmod(path.narrow, mode);
+    else
+#endif
+#ifdef HAVE_FCHMODAT
+    if ((dir_fd != DEFAULT_DIR_FD) || !follow_symlinks) {
+        /*
+         * fchmodat() doesn't currently support AT_SYMLINK_NOFOLLOW!
+         * The documentation specifically shows how to use it,
+         * and then says it isn't implemented yet.   (glibc 2.15)
+         *
+         * Once it is supported, os.chmod will automatically
+         * support dir_fd and follow_symlinks=False.  (Hopefully.)
+         * Until then, we need to be careful what exception we raise.
+         */
+        result = fchmodat(dir_fd, path.narrow, mode,
+                          follow_symlinks ? 0 : AT_SYMLINK_NOFOLLOW);
+        /*
+         * But wait!  We can't throw the exception without allowing threads,
+         * and we can't do that in this nested scope.  (Macro trickery, sigh.)
+         */
+        fchmodat_nofollow_unsupported =
+                         result && (errno == ENOTSUP) && !follow_symlinks;
+    }
+    else
+#endif
+        result = chmod(path.narrow, mode);
     Py_END_ALLOW_THREADS
-    if (!res) {
-        win32_error("chmod", path);
-        return NULL;
-    }
+
+    if (result) {
+#ifdef HAVE_FCHMODAT
+        if (fchmodat_nofollow_unsupported) {
+            if (dir_fd != DEFAULT_DIR_FD)
+                dir_fd_and_follow_symlinks_invalid("chmod",
+                                                   dir_fd, follow_symlinks);
+            else
+                follow_symlinks_specified("chmod", follow_symlinks);
+        }
+        else
+#endif
+            return_value = path_error("chmod", &path);
+        goto exit;
+    }
+#endif
+
     Py_INCREF(Py_None);
-    return Py_None;
-#else /* MS_WINDOWS */
-    if (!PyArg_ParseTuple(args, "O&i:chmod", PyUnicode_FSConverter,
-                          &opath, &i))
-        return NULL;
-    path = PyBytes_AsString(opath);
-    Py_BEGIN_ALLOW_THREADS
-    res = chmod(path, i);
-    Py_END_ALLOW_THREADS
-    if (res < 0)
-        return posix_error_with_allocated_filename(opath);
-    Py_DECREF(opath);
-    Py_INCREF(Py_None);
-    return Py_None;
-#endif
-}
+    return_value = Py_None;
+exit:
+    path_cleanup(&path);
+    return return_value;
+}
+
 
 #ifdef HAVE_FCHMOD
 PyDoc_STRVAR(posix_fchmod__doc__,
 "fchmod(fd, mode)\n\n\
 Change the access permissions of the file given by file\n\
-descriptor fd.");
+descriptor fd.  Equivalent to os.chmod(fd, mode).");
 
 static PyObject *
 posix_fchmod(PyObject *self, PyObject *args)
@@ -2207,7 +2765,8 @@
 PyDoc_STRVAR(posix_lchmod__doc__,
 "lchmod(path, mode)\n\n\
 Change the access permissions of a file. If path is a symlink, this\n\
-affects the link itself rather than the target.");
+affects the link itself rather than the target.\n\
+Equivalent to chmod(path, mode, follow_symlinks=False).");
 
 static PyObject *
 posix_lchmod(PyObject *self, PyObject *args)
@@ -2233,28 +2792,56 @@
 
 #ifdef HAVE_CHFLAGS
 PyDoc_STRVAR(posix_chflags__doc__,
-"chflags(path, flags)\n\n\
-Set file flags.");
-
-static PyObject *
-posix_chflags(PyObject *self, PyObject *args)
-{
-    PyObject *opath;
-    char *path;
+"chflags(path, flags, *, follow_symlinks=True)\n\n\
+Set file flags.\n\
+\n\
+If follow_symlinks is False, and the last element of the path is a symbolic\n\
+  link, chflags will change flags on the symbolic link itself instead of the\n\
+  file the link points to.\n\
+follow_symlinks may not be implemented on your platform.  If it is\n\
+unavailable, using it will raise a NotImplementedError.");
+
+static PyObject *
+posix_chflags(PyObject *self, PyObject *args, PyObject *kwargs)
+{
+    path_t path;
     unsigned long flags;
-    int res;
-    if (!PyArg_ParseTuple(args, "O&k:chflags",
-                          PyUnicode_FSConverter, &opath, &flags))
-        return NULL;
-    path = PyBytes_AsString(opath);
+    int follow_symlinks = 1;
+    int result;
+    PyObject *return_value;
+    static char *keywords[] = {"path", "flags", "follow_symlinks", NULL};
+
+    memset(&path, 0, sizeof(path));
+    if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O&k|$i:chflags", keywords,
+                          path_converter, &path,
+                          &flags, &follow_symlinks))
+        return NULL;
+
+#ifndef HAVE_LCHFLAGS
+    if (follow_symlinks_specified("chflags", follow_symlinks))
+        goto exit;
+#endif
+
     Py_BEGIN_ALLOW_THREADS
-    res = chflags(path, flags);
+#ifdef HAVE_LCHFLAGS
+    if (!follow_symlinks)
+        result = lchflags(path.narrow, flags);
+    else
+#endif
+        result = chflags(path.narrow, flags);
     Py_END_ALLOW_THREADS
-    if (res < 0)
-        return posix_error_with_allocated_filename(opath);
-    Py_DECREF(opath);
+
+    if (result) {
+        return_value = path_posix_error("chflags", &path);
+        goto exit;
+    }
+
+    return_value = Py_None;
     Py_INCREF(Py_None);
-    return Py_None;
+
+exit:
+    path_cleanup(&path);
+    return return_value;
 }
 #endif /* HAVE_CHFLAGS */
 
@@ -2262,7 +2849,8 @@
 PyDoc_STRVAR(posix_lchflags__doc__,
 "lchflags(path, flags)\n\n\
 Set file flags.\n\
-This function will not follow symbolic links.");
+This function will not follow symbolic links.\n\
+Equivalent to chflags(path, flags, follow_symlinks=False).");
 
 static PyObject *
 posix_lchflags(PyObject *self, PyObject *args)
@@ -2346,29 +2934,105 @@
 
 #ifdef HAVE_CHOWN
 PyDoc_STRVAR(posix_chown__doc__,
-"chown(path, uid, gid)\n\n\
-Change the owner and group id of path to the numeric uid and gid.");
-
-static PyObject *
-posix_chown(PyObject *self, PyObject *args)
-{
-    PyObject *opath;
-    char *path;
-    long uid, gid;
-    int res;
-    if (!PyArg_ParseTuple(args, "O&ll:chown",
-                          PyUnicode_FSConverter, &opath,
-                          &uid, &gid))
-        return NULL;
-    path = PyBytes_AsString(opath);
+"chown(path, uid, gid, *, dir_fd=None, follow_symlinks=True)\n\n\
+Change the owner and group id of path to the numeric uid and gid.\n\
+\n\
+path may always be specified as a string.\n\
+On some platforms, path may also be specified as an open file descriptor.\n\
+  If this functionality is unavailable, using it raises an exception.\n\
+If dir_fd is not None, it should be a file descriptor open to a directory,\n\
+  and path should be relative; path will then be relative to that directory.\n\
+If follow_symlinks is False, and the last element of the path is a symbolic\n\
+  link, chown will modify the symbolic link itself instead of the file the\n\
+  link points to.\n\
+It is an error to use dir_fd or follow_symlinks when specifying path as\n\
+  an open file descriptor.\n\
+dir_fd and follow_symlinks may not be implemented on your platform.\n\
+  If they are unavailable, using them will raise a NotImplementedError.");
+
+static PyObject *
+posix_chown(PyObject *self, PyObject *args, PyObject *kwargs)
+{
+    path_t path;
+    long uid_l, gid_l;
+    uid_t uid;
+    gid_t gid;
+    int dir_fd = DEFAULT_DIR_FD;
+    int follow_symlinks = 1;
+    int result;
+    PyObject *return_value = NULL;
+    static char *keywords[] = {"path", "uid", "gid", "dir_fd",
+                               "follow_symlinks", NULL};
+
+    memset(&path, 0, sizeof(path));
+#ifdef HAVE_FCHOWN
+    path.allow_fd = 1;
+#endif
+    if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O&ll|$O&p:chown", keywords,
+                                     path_converter, &path,
+                                     &uid_l, &gid_l,
+#ifdef HAVE_FCHOWNAT
+                                     dir_fd_converter, &dir_fd,
+#else
+                                     dir_fd_unavailable, &dir_fd,
+#endif
+                                     &follow_symlinks))
+        return NULL;
+
+#if !(defined(HAVE_LCHOWN) || defined(HAVE_FCHOWNAT))
+    if (follow_symlinks_specified("chown", follow_symlinks))
+        goto exit;
+#endif
+    if (dir_fd_and_fd_invalid("chown", dir_fd, path.fd) ||
+        fd_and_follow_symlinks_invalid("chown", path.fd, follow_symlinks))
+        goto exit;
+
+#ifdef __APPLE__
+    /*
+     * This is for Mac OS X 10.3, which doesn't have lchown.
+     * (But we still have an lchown symbol because of weak-linking.)
+     * It doesn't have fchownat either.  So there's no possibility
+     * of a graceful failover.
+     */    
+    if ((!follow_symlinks) && (lchown == NULL)) {
+        follow_symlinks_specified("chown", follow_symlinks);
+        goto exit;
+    }
+#endif
+
     Py_BEGIN_ALLOW_THREADS
-    res = chown(path, (uid_t) uid, (gid_t) gid);
+    uid = (uid_t)uid_l;
+    gid = (uid_t)gid_l;
+#ifdef HAVE_FCHOWN
+    if (path.fd != -1)
+        result = fchown(path.fd, uid, gid);
+    else
+#endif
+#ifdef HAVE_LCHOWN
+    if ((!follow_symlinks) && (dir_fd == DEFAULT_DIR_FD))
+        result = lchown(path.narrow, uid, gid);
+    else
+#endif
+#ifdef HAVE_FCHOWNAT
+    if ((dir_fd != DEFAULT_DIR_FD) || (!follow_symlinks))
+        result = fchownat(dir_fd, path.narrow, uid, gid,
+                          follow_symlinks ? 0 : AT_SYMLINK_NOFOLLOW);
+    else
+#endif
+        result = chown(path.narrow, uid, gid);
     Py_END_ALLOW_THREADS
-    if (res < 0)
-        return posix_error_with_allocated_filename(opath);
-    Py_DECREF(opath);
+
+    if (result) {
+        return_value = path_posix_error("chown", &path);
+        goto exit;
+    }
+
+    return_value = Py_None;
     Py_INCREF(Py_None);
-    return Py_None;
+
+exit:
+    path_cleanup(&path);
+    return return_value;
 }
 #endif /* HAVE_CHOWN */
 
@@ -2376,7 +3040,7 @@
 PyDoc_STRVAR(posix_fchown__doc__,
 "fchown(fd, uid, gid)\n\n\
 Change the owner and group id of the file given by file descriptor\n\
-fd to the numeric uid and gid.");
+fd to the numeric uid and gid.  Equivalent to os.chown(fd, uid, gid).");
 
 static PyObject *
 posix_fchown(PyObject *self, PyObject *args)
@@ -2399,7 +3063,8 @@
 PyDoc_STRVAR(posix_lchown__doc__,
 "lchown(path, uid, gid)\n\n\
 Change the owner and group id of path to the numeric uid and gid.\n\
-This function will not follow symbolic links.");
+This function will not follow symbolic links.\n\
+Equivalent to os.chown(path, uid, gid, follow_symlinks=False).");
 
 static PyObject *
 posix_lchown(PyObject *self, PyObject *args)
@@ -2501,125 +3166,186 @@
 }
 #endif
 
+#if ((!defined(HAVE_LINK)) && defined(MS_WINDOWS))
+#define HAVE_LINK 1
+#endif
 
 #ifdef HAVE_LINK
 PyDoc_STRVAR(posix_link__doc__,
-"link(src, dst)\n\n\
-Create a hard link to a file.");
-
-static PyObject *
-posix_link(PyObject *self, PyObject *args)
-{
-    return posix_2str(args, "O&O&:link", link);
-}
-#endif /* HAVE_LINK */
-
+"link(src, dst, *, src_dir_fd=None, dst_dir_fd=None, follow_symlinks=True)\n\n\
+Create a hard link to a file.\n\
+\n\
+If either src_dir_fd or dst_dir_fd is not None, it should be a file\n\
+  descriptor open to a directory, and the respective path string (src or dst)\n\
+  should be relative; the path will then be relative to that directory.\n\
+If follow_symlinks is False, and the last element of src is a symbolic\n\
+  link, link will create a link to the symbolic link itself instead of the\n\
+  file the link points to.\n\
+src_dir_fd, dst_dir_fd, and follow_symlinks may not be implemented on your\n\
+  platform.  If they are unavailable, using them will raise a\n\
+  NotImplementedError.");
+
+static PyObject *
+posix_link(PyObject *self, PyObject *args, PyObject *kwargs)
+{
+    path_t src, dst;
+    int src_dir_fd = DEFAULT_DIR_FD;
+    int dst_dir_fd = DEFAULT_DIR_FD;
+    int follow_symlinks = 1;
+    PyObject *return_value = NULL;
+    static char *keywords[] = {"src", "dst", "src_dir_fd", "dst_dir_fd",
+                               "follow_symlinks", NULL};
 #ifdef MS_WINDOWS
-PyDoc_STRVAR(win32_link__doc__,
-"link(src, dst)\n\n\
-Create a hard link to a file.");
-
-static PyObject *
-win32_link(PyObject *self, PyObject *args)
-{
-    PyObject *src, *dst;
-    BOOL ok;
-
-    if (PyArg_ParseTuple(args, "UU:link", &src, &dst))
-    {
-        wchar_t *wsrc, *wdst;
-
-        wsrc = PyUnicode_AsUnicode(src);
-        if (wsrc == NULL)
-            goto error;
-        wdst = PyUnicode_AsUnicode(dst);
-        if (wdst == NULL)
-            goto error;
-
-        Py_BEGIN_ALLOW_THREADS
-        ok = CreateHardLinkW(wdst, wsrc, NULL);
-        Py_END_ALLOW_THREADS
-
-        if (!ok)
-            return win32_error("link", NULL);
-        Py_RETURN_NONE;
-    }
-    else {
-        PyErr_Clear();
-        if (!PyArg_ParseTuple(args, "O&O&:link",
-                              PyUnicode_FSConverter, &src,
-                              PyUnicode_FSConverter, &dst))
-            return NULL;
-
-        if (win32_warn_bytes_api())
-            goto error;
-
-        Py_BEGIN_ALLOW_THREADS
-        ok = CreateHardLinkA(PyBytes_AS_STRING(dst),
-                             PyBytes_AS_STRING(src),
-                             NULL);
-        Py_END_ALLOW_THREADS
-
-        Py_XDECREF(src);
-        Py_XDECREF(dst);
-
-        if (!ok)
-            return win32_error("link", NULL);
-        Py_RETURN_NONE;
-
-    error:
-        Py_XDECREF(src);
-        Py_XDECREF(dst);
-        return NULL;
-    }
-}
-#endif /* MS_WINDOWS */
+    BOOL result;
+#else
+    int result;
+#endif
+
+    memset(&src, 0, sizeof(src));
+    memset(&dst, 0, sizeof(dst));
+    if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O&O&|O&O&p:link", keywords,
+            path_converter, &src,
+            path_converter, &dst,
+            dir_fd_converter, &src_dir_fd,
+            dir_fd_converter, &dst_dir_fd,
+            &follow_symlinks))
+        return NULL;
+
+#ifndef HAVE_LINKAT
+    if ((src_dir_fd != DEFAULT_DIR_FD) || (dst_dir_fd != DEFAULT_DIR_FD)) {
+        argument_unavailable_error("link", "src_dir_fd and dst_dir_fd");
+        goto exit;
+    }
+#endif
+
+    if ((src.narrow && dst.wide) || (src.wide && dst.narrow)) {
+        PyErr_SetString(PyExc_NotImplementedError,
+                        "link: src and dst must be the same type");
+        goto exit;
+    }
+
+#ifdef MS_WINDOWS
+    Py_BEGIN_ALLOW_THREADS
+    if (src.wide)
+        result = CreateHardLinkW(dst.wide, src.wide, NULL);
+    else
+        result = CreateHardLinkA(dst.narrow, src.narrow, NULL);
+    Py_END_ALLOW_THREADS
+
+    if (!result) {
+        return_value = win32_error_object("link", dst.object);
+        goto exit;
+    }
+#else
+    Py_BEGIN_ALLOW_THREADS
+#ifndef HAVE_LINKAT
+    if ((src_dir_fd != DEFAULT_DIR_FD) ||
+        (dst_dir_fd != DEFAULT_DIR_FD) ||
+        (!follow_symlinks))
+        result = linkat(src_dir_fd, src.narrow,
+            dst_dir_fd, dst.narrow,
+            follow_symlinks ? AT_SYMLINK_FOLLOW : 0);
+    else
+#endif
+        result = link(src.narrow, dst.narrow);
+    Py_END_ALLOW_THREADS
+
+    if (result) {
+        return_value = path_error("link", &dst);
+        goto exit;
+    }
+#endif
+
+    return_value = Py_None;
+    Py_INCREF(Py_None);
+
+exit:
+    path_cleanup(&src);
+    path_cleanup(&dst);
+    return return_value;
+}
+#endif
+
 
 
 PyDoc_STRVAR(posix_listdir__doc__,
-"listdir([path]) -> list_of_strings\n\n\
+"listdir(path='.') -> list_of_strings\n\n\
 Return a list containing the names of the entries in the directory.\n\
 \n\
-    path: path of directory to list (default: '.')\n\
+The list is in arbitrary order.  It does not include the special\n\
+entries '.' and '..' even if they are present in the directory.\n\
 \n\
-The list is in arbitrary order.  It does not include the special\n\
-entries '.' and '..' even if they are present in the directory.");
-
-static PyObject *
-posix_listdir(PyObject *self, PyObject *args)
-{
+path can always be specified as a string.\n\
+On some platforms, path may also be specified as an open file descriptor.\n\
+  If this functionality is unavailable, using it raises NotImplementedError.");
+
+static PyObject *
+posix_listdir(PyObject *self, PyObject *args, PyObject *kwargs)
+{
+    path_t path;
+    PyObject *list = NULL;
+    static char *keywords[] = {"path", NULL};
+    int fd = -1;
+
+#if defined(MS_WINDOWS) && !defined(HAVE_OPENDIR)
+    PyObject *v;
+    HANDLE hFindFile = INVALID_HANDLE_VALUE;
+    BOOL result;
+    WIN32_FIND_DATA FileData;
+    char namebuf[MAX_PATH+5]; /* Overallocate for \\*.*\0 */
+    char *bufptr = namebuf;
+    /* only claim to have space for MAX_PATH */
+    Py_ssize_t len = sizeof(namebuf)-5;
+    PyObject *po = NULL;
+    wchar_t *wnamebuf = NULL;
+#elif defined(PYOS_OS2)
+#ifndef MAX_PATH
+#define MAX_PATH    CCHMAXPATH
+#endif
+    char *pt;
+    PyObject *v;
+    char namebuf[MAX_PATH+5];
+    HDIR  hdir = 1;
+    ULONG srchcnt = 1;
+    FILEFINDBUF3   ep;
+    APIRET rc;
+#else
+    PyObject *v;
+    DIR *dirp = NULL;
+    struct dirent *ep;
+    int arg_is_unicode = 1;    
+#endif
+
+    memset(&path, 0, sizeof(path));
+    path.nullable = 1;
+#ifdef HAVE_FDOPENDIR
+    path.allow_fd = 1;
+    path.fd = -1;
+#endif
+    if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|O&:listdir", keywords,
+        path_converter, &path
+        ))
+        return NULL;
+
     /* XXX Should redo this putting the (now four) versions of opendir
        in separate files instead of having them all here... */
 #if defined(MS_WINDOWS) && !defined(HAVE_OPENDIR)
-
-    PyObject *d, *v;
-    HANDLE hFindFile;
-    BOOL result;
-    WIN32_FIND_DATA FileData;
-    const char *path;
-    Py_ssize_t pathlen;
-    char namebuf[MAX_PATH+5]; /* Overallocate for \\*.*\0 */
-    char *bufptr = namebuf;
-    Py_ssize_t len = sizeof(namebuf)-5; /* only claim to have space for MAX_PATH */
-
-    PyObject *po = NULL;
-    if (PyArg_ParseTuple(args, "|U:listdir", &po)) {
+    if (!path.narrow) {
         WIN32_FIND_DATAW wFileData;
-        wchar_t *wnamebuf, *po_wchars;
-
-        if (po == NULL) { /* Default arg: "." */
+        wchar_t *po_wchars;
+
+        if (!path.wide) { /* Default arg: "." */
             po_wchars = L".";
             len = 1;
         } else {
-            po_wchars = PyUnicode_AsUnicodeAndSize(po, &len);
-            if (po_wchars == NULL)
-                return NULL;
+            po_wchars = path.wide;
+            len = wcslen(path.wide);
         }
-        /* Overallocate for \\*.*\0 */
+        /* The +5 is so we can append "\\*.*\0" */
         wnamebuf = malloc((len + 5) * sizeof(wchar_t));
         if (!wnamebuf) {
             PyErr_NoMemory();
-            return NULL;
+            goto exit;
         }
         wcscpy(wnamebuf, po_wchars);
         if (len > 0) {
@@ -2628,38 +3354,36 @@
                 wnamebuf[len++] = L'\\';
             wcscpy(wnamebuf + len, L"*.*");
         }
-        if ((d = PyList_New(0)) == NULL) {
-            free(wnamebuf);
-            return NULL;
+        if ((list = PyList_New(0)) == NULL) {
+            goto exit;
         }
         Py_BEGIN_ALLOW_THREADS
         hFindFile = FindFirstFileW(wnamebuf, &wFileData);
         Py_END_ALLOW_THREADS
         if (hFindFile == INVALID_HANDLE_VALUE) {
             int error = GetLastError();
-            if (error == ERROR_FILE_NOT_FOUND) {
-                free(wnamebuf);
-                return d;
-            }
-            Py_DECREF(d);
+            if (error == ERROR_FILE_NOT_FOUND)
+                goto exit;
+            Py_DECREF(list);
+            list = NULL;
             win32_error_unicode("FindFirstFileW", wnamebuf);
-            free(wnamebuf);
-            return NULL;
+            goto exit;
         }
         do {
             /* Skip over . and .. */
             if (wcscmp(wFileData.cFileName, L".") != 0 &&
                 wcscmp(wFileData.cFileName, L"..") != 0) {
-                v = PyUnicode_FromWideChar(wFileData.cFileName, wcslen(wFileData.cFileName));
+                v = PyUnicode_FromWideChar(wFileData.cFileName,
+                                           wcslen(wFileData.cFileName));
                 if (v == NULL) {
-                    Py_DECREF(d);
-                    d = NULL;
+                    Py_DECREF(list);
+                    list = NULL;
                     break;
                 }
-                if (PyList_Append(d, v) != 0) {
+                if (PyList_Append(list, v) != 0) {
                     Py_DECREF(v);
-                    Py_DECREF(d);
-                    d = NULL;
+                    Py_DECREF(list);
+                    list = NULL;
                     break;
                 }
                 Py_DECREF(v);
@@ -2670,37 +3394,16 @@
             /* FindNextFile sets error to ERROR_NO_MORE_FILES if
                it got to the end of the directory. */
             if (!result && GetLastError() != ERROR_NO_MORE_FILES) {
-                Py_DECREF(d);
-                win32_error_unicode("FindNextFileW", wnamebuf);
-                FindClose(hFindFile);
-                free(wnamebuf);
-                return NULL;
+                Py_DECREF(list);
+                list = win32_error_unicode("FindNextFileW", wnamebuf);
+                goto exit;
             }
         } while (result == TRUE);
 
-        if (FindClose(hFindFile) == FALSE) {
-            Py_DECREF(d);
-            win32_error_unicode("FindClose", wnamebuf);
-            free(wnamebuf);
-            return NULL;
-        }
-        free(wnamebuf);
-        return d;
-    }
-    /* Drop the argument parsing error as narrow strings
-       are also valid. */
-    PyErr_Clear();
-
-    if (!PyArg_ParseTuple(args, "y#:listdir", &path, &pathlen))
-        return NULL;
-    if (win32_warn_bytes_api())
-        return NULL;
-    if (pathlen+1 > MAX_PATH) {
-        PyErr_SetString(PyExc_ValueError, "path too long");
-        return NULL;
-    }
-    strcpy(namebuf, path);
-    len = pathlen;
+        goto exit;
+    }
+    strcpy(namebuf, path.narrow);
+    len = path.length;
     if (len > 0) {
         char ch = namebuf[len-1];
         if (ch != SEP && ch != ALTSEP && ch != ':')
@@ -2708,7 +3411,7 @@
         strcpy(namebuf + len, "*.*");
     }
 
-    if ((d = PyList_New(0)) == NULL)
+    if ((list = PyList_New(0)) == NULL)
         return NULL;
 
     Py_BEGIN_ALLOW_THREADS
@@ -2717,9 +3420,10 @@
     if (hFindFile == INVALID_HANDLE_VALUE) {
         int error = GetLastError();
         if (error == ERROR_FILE_NOT_FOUND)
-            return d;
-        Py_DECREF(d);
-        return win32_error("FindFirstFile", namebuf);
+            goto exit;
+        Py_DECREF(list);
+        list = win32_error("FindFirstFile", namebuf);
+        goto exit;
     }
     do {
         /* Skip over . and .. */
@@ -2727,14 +3431,14 @@
             strcmp(FileData.cFileName, "..") != 0) {
             v = PyBytes_FromString(FileData.cFileName);
             if (v == NULL) {
-                Py_DECREF(d);
-                d = NULL;
+                Py_DECREF(list);
+                list = NULL;
                 break;
             }
-            if (PyList_Append(d, v) != 0) {
+            if (PyList_Append(list, v) != 0) {
                 Py_DECREF(v);
-                Py_DECREF(d);
-                d = NULL;
+                Py_DECREF(list);
+                list = NULL;
                 break;
             }
             Py_DECREF(v);
@@ -2745,46 +3449,33 @@
         /* FindNextFile sets error to ERROR_NO_MORE_FILES if
            it got to the end of the directory. */
         if (!result && GetLastError() != ERROR_NO_MORE_FILES) {
-            Py_DECREF(d);
-            win32_error("FindNextFile", namebuf);
-            FindClose(hFindFile);
-            return NULL;
+            Py_DECREF(list);
+            list = win32_error("FindNextFile", namebuf);
+            goto exit;
         }
     } while (result == TRUE);
 
-    if (FindClose(hFindFile) == FALSE) {
-        Py_DECREF(d);
-        return win32_error("FindClose", namebuf);
-    }
-
-    return d;
+exit:
+    if (hFindFile != INVALID_HANDLE_VALUE) {
+        if (FindClose(hFindFile) == FALSE) {
+            if (list != NULL) {
+                Py_DECREF(list);
+                list = win32_error_object("FindClose", path.object);
+            }
+        }
+    }
+    if (wnamebuf)
+        free(wnamebuf);
+    path_cleanup(&path);
+
+    return list;
 
 #elif defined(PYOS_OS2)
-
-#ifndef MAX_PATH
-#define MAX_PATH    CCHMAXPATH
-#endif
-    PyObject *oname;
-    char *name, *pt;
-    Py_ssize_t len;
-    PyObject *d, *v;
-    char namebuf[MAX_PATH+5];
-    HDIR  hdir = 1;
-    ULONG srchcnt = 1;
-    FILEFINDBUF3   ep;
-    APIRET rc;
-
-    if (!PyArg_ParseTuple(args, "O&:listdir",
-                          PyUnicode_FSConverter, &oname))
-        return NULL;
-    name = PyBytes_AsString(oname);
-    len = PyBytes_GET_SIZE(oname);
-    if (len >= MAX_PATH) {
-        Py_DECREF(oname);
+    if (path.length >= MAX_PATH) {
         PyErr_SetString(PyExc_ValueError, "path too long");
-        return NULL;
-    }
-    strcpy(namebuf, name);
+        goto exit;
+    }
+    strcpy(namebuf, path.narrow);
     for (pt = namebuf; *pt; pt++)
         if (*pt == ALTSEP)
             *pt = SEP;
@@ -2792,9 +3483,8 @@
         namebuf[len++] = SEP;
     strcpy(namebuf + len, "*.*");
 
-    if ((d = PyList_New(0)) == NULL) {
-        Py_DECREF(oname);
-        return NULL;
+    if ((list = PyList_New(0)) == NULL) {
+        goto exit;
     }
 
     rc = DosFindFirst(namebuf,         /* Wildcard Pattern to Match */
@@ -2806,7 +3496,9 @@
 
     if (rc != NO_ERROR) {
         errno = ENOENT;
-        return posix_error_with_allocated_filename(oname);
+        Py_DECREF(list);
+        list = posix_error_with_filename(path.narrow);
+        goto exit;
     }
 
     if (srchcnt > 0) { /* If Directory is NOT Totally Empty, */
@@ -2822,55 +3514,63 @@
 
             v = PyBytes_FromString(namebuf);
             if (v == NULL) {
-                Py_DECREF(d);
-                d = NULL;
+                Py_DECREF(list);
+                list = NULL;
                 break;
             }
-            if (PyList_Append(d, v) != 0) {
+            if (PyList_Append(list, v) != 0) {
                 Py_DECREF(v);
-                Py_DECREF(d);
-                d = NULL;
+                Py_DECREF(list);
+                list = NULL;
                 break;
             }
             Py_DECREF(v);
         } while (DosFindNext(hdir, &ep, sizeof(ep), &srchcnt) == NO_ERROR && srchcnt > 0);
     }
 
-    Py_DECREF(oname);
-    return d;
-#else
-    PyObject *oname;
-    char *name;
-    PyObject *d, *v;
-    DIR *dirp;
-    struct dirent *ep;
-    int arg_is_unicode = 1;
+exit:
+    path_cleanup(&path);
+
+    return list;
+#else
 
     errno = 0;
     /* v is never read, so it does not need to be initialized yet. */
-    if (!PyArg_ParseTuple(args, "|U:listdir", &v)) {
+    if (path.narrow && !PyArg_ParseTuple(args, "U:listdir", &v)) {
         arg_is_unicode = 0;
         PyErr_Clear();
     }
-    oname = NULL;
-    if (!PyArg_ParseTuple(args, "|O&:listdir", PyUnicode_FSConverter, &oname))
-        return NULL;
-    if (oname == NULL) { /* Default arg: "." */
-        oname = PyBytes_FromString(".");
-    }
-    name = PyBytes_AsString(oname);
-    Py_BEGIN_ALLOW_THREADS
-    dirp = opendir(name);
-    Py_END_ALLOW_THREADS
+#ifdef HAVE_FDOPENDIR
+    if (path.fd != -1) {
+        /* closedir() closes the FD, so we duplicate it */
+        Py_BEGIN_ALLOW_THREADS
+        fd = dup(path.fd);
+        Py_END_ALLOW_THREADS
+
+        if (fd == -1) {
+            list = posix_error();
+            goto exit;
+        }
+
+        Py_BEGIN_ALLOW_THREADS
+        dirp = fdopendir(fd);
+        Py_END_ALLOW_THREADS
+    }
+    else
+#endif
+    {
+        char *name = path.narrow ? path.narrow : ".";
+        Py_BEGIN_ALLOW_THREADS
+        dirp = opendir(name);
+        Py_END_ALLOW_THREADS
+    }
+
     if (dirp == NULL) {
-        return posix_error_with_allocated_filename(oname);
-    }
-    if ((d = PyList_New(0)) == NULL) {
-        Py_BEGIN_ALLOW_THREADS
-        closedir(dirp);
-        Py_END_ALLOW_THREADS
-        Py_DECREF(oname);
-        return NULL;
+        list = path_error("listdir", &path);
+        goto exit;
+    }
+    if ((list = PyList_New(0)) == NULL) {
+        goto exit;
     }
     for (;;) {
         errno = 0;
@@ -2881,11 +3581,9 @@
             if (errno == 0) {
                 break;
             } else {
-                Py_BEGIN_ALLOW_THREADS
-                closedir(dirp);
-                Py_END_ALLOW_THREADS
-                Py_DECREF(d);
-                return posix_error_with_allocated_filename(oname);
+                Py_DECREF(list);
+                list = path_error("listdir", &path);
+                goto exit;
             }
         }
         if (ep->d_name[0] == '.' &&
@@ -2897,101 +3595,33 @@
         else
             v = PyBytes_FromStringAndSize(ep->d_name, NAMLEN(ep));
         if (v == NULL) {
-            Py_CLEAR(d);
+            Py_CLEAR(list);
             break;
         }
-        if (PyList_Append(d, v) != 0) {
+        if (PyList_Append(list, v) != 0) {
             Py_DECREF(v);
-            Py_CLEAR(d);
+            Py_CLEAR(list);
             break;
         }
         Py_DECREF(v);
     }
-    Py_BEGIN_ALLOW_THREADS
-    closedir(dirp);
-    Py_END_ALLOW_THREADS
-    Py_DECREF(oname);
-
-    return d;
+
+exit:
+    if (dirp != NULL) {
+        Py_BEGIN_ALLOW_THREADS
+        if (fd > -1)
+            rewinddir(dirp);
+        closedir(dirp);
+        Py_END_ALLOW_THREADS
+    }
+
+    path_cleanup(&path);
+
+    return list;
 
 #endif /* which OS */
 }  /* end of posix_listdir */
 
-#ifdef HAVE_FDOPENDIR
-PyDoc_STRVAR(posix_flistdir__doc__,
-"flistdir(fd) -> list_of_strings\n\n\
-Like listdir(), but uses a file descriptor instead.");
-
-static PyObject *
-posix_flistdir(PyObject *self, PyObject *args)
-{
-    PyObject *d, *v;
-    DIR *dirp;
-    struct dirent *ep;
-    int fd;
-
-    errno = 0;
-    if (!PyArg_ParseTuple(args, "i:flistdir", &fd))
-        return NULL;
-    /* closedir() closes the FD, so we duplicate it */
-    fd = dup(fd);
-    if (fd < 0)
-        return posix_error();
-    Py_BEGIN_ALLOW_THREADS
-    dirp = fdopendir(fd);
-    Py_END_ALLOW_THREADS
-    if (dirp == NULL) {
-        close(fd);
-        return posix_error();
-    }
-    if ((d = PyList_New(0)) == NULL) {
-        Py_BEGIN_ALLOW_THREADS
-        closedir(dirp);
-        Py_END_ALLOW_THREADS
-        return NULL;
-    }
-    for (;;) {
-        errno = 0;
-        Py_BEGIN_ALLOW_THREADS
-        ep = readdir(dirp);
-        Py_END_ALLOW_THREADS
-        if (ep == NULL) {
-            if (errno == 0) {
-                break;
-            } else {
-                Py_BEGIN_ALLOW_THREADS
-                rewinddir(dirp);
-                closedir(dirp);
-                Py_END_ALLOW_THREADS
-                Py_DECREF(d);
-                return posix_error();
-            }
-        }
-        if (ep->d_name[0] == '.' &&
-            (NAMLEN(ep) == 1 ||
-             (ep->d_name[1] == '.' && NAMLEN(ep) == 2)))
-            continue;
-        v = PyUnicode_DecodeFSDefaultAndSize(ep->d_name, NAMLEN(ep));
-        if (v == NULL) {
-            Py_CLEAR(d);
-            break;
-        }
-        if (PyList_Append(d, v) != 0) {
-            Py_DECREF(v);
-            Py_CLEAR(d);
-            break;
-        }
-        Py_DECREF(v);
-    }
-    Py_BEGIN_ALLOW_THREADS
-    rewinddir(dirp);
-    closedir(dirp);
-    Py_END_ALLOW_THREADS
-
-    return d;
-}
-#endif
-
 #ifdef MS_WINDOWS
 /* A helper function for abspath on win32 */
 static PyObject *
@@ -3182,68 +3812,72 @@
 #endif /* MS_WINDOWS */
 
 PyDoc_STRVAR(posix_mkdir__doc__,
-"mkdir(path [, mode=0777])\n\n\
-Create a directory.");
-
-static PyObject *
-posix_mkdir(PyObject *self, PyObject *args)
-{
-    int res;
-    const char *path;
+"mkdir(path, mode=0o777, *, dir_fd=None)\n\n\
+Create a directory.\n\
+\n\
+If dir_fd is not None, it should be a file descriptor open to a directory,\n\
+  and path should be relative; path will then be relative to that directory.\n\
+dir_fd may not be implemented on your platform.\n\
+  If it is unavailable, using it will raise a NotImplementedError.\n\
+\n\
+The mode argument is ignored on Windows.");
+
+static PyObject *
+posix_mkdir(PyObject *self, PyObject *args, PyObject *kwargs)
+{
+    path_t path;
     int mode = 0777;
+    int dir_fd = DEFAULT_DIR_FD;
+    static char *keywords[] = {"path", "mode", "dir_fd", NULL};
+    PyObject *return_value = NULL;
+    int result;
+
+    memset(&path, 0, sizeof(path));
+    if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O&|i$O&:mkdir", keywords,
+        path_converter, &path, &mode,
+#ifdef HAVE_MKDIRAT
+        dir_fd_converter, &dir_fd
+#else
+        dir_fd_unavailable, &dir_fd
+#endif
+        ))
+        return NULL;
 
 #ifdef MS_WINDOWS
-    PyObject *po;
-    if (PyArg_ParseTuple(args, "U|i:mkdir", &po, &mode))
-    {
-        wchar_t *wpath = PyUnicode_AsUnicode(po);
-        if (wpath == NULL)
-            return NULL;
-
-        Py_BEGIN_ALLOW_THREADS
-        res = CreateDirectoryW(wpath, NULL);
-        Py_END_ALLOW_THREADS
-        if (!res)
-            return win32_error_object("mkdir", po);
-        Py_INCREF(Py_None);
-        return Py_None;
-    }
-    /* Drop the argument parsing error as narrow strings
-       are also valid. */
-    PyErr_Clear();
-    if (!PyArg_ParseTuple(args, "y|i:mkdir", &path, &mode))
-        return NULL;
-    if (win32_warn_bytes_api())
-        return NULL;
     Py_BEGIN_ALLOW_THREADS
-    res = CreateDirectoryA(path, NULL);
+    if (path.wide)
+        result = CreateDirectoryW(path.wide, NULL);
+    else
+        result = CreateDirectoryA(path.narrow, NULL);
     Py_END_ALLOW_THREADS
-    if (!res) {
-        win32_error("mkdir", path);
-        return NULL;
-    }
+
+    if (!result) {
+        return_value = win32_error_object("mkdir", path.object);
+        goto exit;
+    }
+#else
+    Py_BEGIN_ALLOW_THREADS
+#if HAVE_MKDIRAT
+    if (dir_fd != DEFAULT_DIR_FD)
+        result = mkdirat(dir_fd, path.narrow, mode);
+    else
+#endif
+#if ( defined(__WATCOMC__) || defined(PYCC_VACPP) ) && !defined(__QNX__)
+        result = mkdir(path.narrow);
+#else
+        result = mkdir(path.narrow, mode);
+#endif
+    Py_END_ALLOW_THREADS
+    if (result < 0) {
+        return_value = path_error("mkdir", &path);
+        goto exit;
+    }
+#endif
+    return_value = Py_None;
     Py_INCREF(Py_None);
-    return Py_None;
-#else
-    PyObject *opath;
-
-    if (!PyArg_ParseTuple(args, "O&|i:mkdir",
-                          PyUnicode_FSConverter, &opath, &mode))
-        return NULL;
-    path = PyBytes_AsString(opath);
-    Py_BEGIN_ALLOW_THREADS
-#if ( defined(__WATCOMC__) || defined(PYCC_VACPP) ) && !defined(__QNX__)
-    res = mkdir(path);
-#else
-    res = mkdir(path, mode);
-#endif
-    Py_END_ALLOW_THREADS
-    if (res < 0)
-        return posix_error_with_allocated_filename(opath);
-    Py_DECREF(opath);
-    Py_INCREF(Py_None);
-    return Py_None;
-#endif
+exit:
+    path_cleanup(&path);
+    return return_value;
 }
 
 
@@ -3332,85 +3966,118 @@
 
 
 static PyObject *
-internal_rename(PyObject *self, PyObject *args, int is_replace)
-{
+internal_rename(PyObject *args, PyObject *kwargs, int is_replace)
+{
+    char *function_name = is_replace ? "replace" : "rename";
+    path_t src;
+    path_t dst;
+    int src_dir_fd = DEFAULT_DIR_FD;
+    int dst_dir_fd = DEFAULT_DIR_FD;
+    int dir_fd_specified;
+    PyObject *return_value = NULL;
+    char format[24];
+    static char *keywords[] = {"src", "dst", "src_dir_fd", "dst_dir_fd", NULL};
+
 #ifdef MS_WINDOWS
-    PyObject *src, *dst;
     BOOL result;
     int flags = is_replace ? MOVEFILE_REPLACE_EXISTING : 0;
-    if (PyArg_ParseTuple(args,
-                         is_replace ? "UU:replace" : "UU:rename",
-                         &src, &dst))
-    {
-        wchar_t *wsrc, *wdst;
-
-        wsrc = PyUnicode_AsUnicode(src);
-        if (wsrc == NULL)
-            return NULL;
-        wdst = PyUnicode_AsUnicode(dst);
-        if (wdst == NULL)
-            return NULL;
-        Py_BEGIN_ALLOW_THREADS
-        result = MoveFileExW(wsrc, wdst, flags);
-        Py_END_ALLOW_THREADS
-        if (!result)
-            return win32_error(is_replace ? "replace" : "rename", NULL);
-        Py_INCREF(Py_None);
-        return Py_None;
-    }
-    else {
-        PyErr_Clear();
-        if (!PyArg_ParseTuple(args,
-                              is_replace ? "O&O&:replace" : "O&O&:rename",
-                              PyUnicode_FSConverter, &src,
-                              PyUnicode_FSConverter, &dst))
-            return NULL;
-
-        if (win32_warn_bytes_api())
-            goto error;
-
-        Py_BEGIN_ALLOW_THREADS
-        result = MoveFileExA(PyBytes_AS_STRING(src),
-                             PyBytes_AS_STRING(dst), flags);
-        Py_END_ALLOW_THREADS
-
-        Py_XDECREF(src);
-        Py_XDECREF(dst);
-
-        if (!result)
-            return win32_error(is_replace ? "replace" : "rename", NULL);
-        Py_INCREF(Py_None);
-        return Py_None;
-
-error:
-        Py_XDECREF(src);
-        Py_XDECREF(dst);
-        return NULL;
-    }
-#else
-    return posix_2str(args,
-                      is_replace ? "O&O&:replace" : "O&O&:rename", rename);
-#endif
+#else
+    int result;
+#endif
+
+    memset(&src, 0, sizeof(src));
+    memset(&dst, 0, sizeof(dst));
+    strcpy(format, "O&O&|$O&O&:");
+    strcat(format, function_name);
+    if (!PyArg_ParseTupleAndKeywords(args, kwargs, format, keywords,
+        path_converter, &src,
+        path_converter, &dst,
+        dir_fd_converter, &src_dir_fd,
+        dir_fd_converter, &dst_dir_fd))
+        return NULL;
+
+    dir_fd_specified = (src_dir_fd != DEFAULT_DIR_FD) ||
+                       (dst_dir_fd != DEFAULT_DIR_FD);
+#ifndef HAVE_RENAMEAT
+    if (dir_fd_specified) {
+        argument_unavailable_error(function_name, "src_dir_fd and dst_dir_fd");
+        goto exit;
+    }
+#endif
+
+    if ((src.narrow && dst.wide) || (src.wide && dst.narrow)) {
+        PyErr_Format(PyExc_ValueError,
+                     "%s: src and dst must be the same type", function_name);
+        goto exit;
+    }
+
+#ifdef MS_WINDOWS
+    Py_BEGIN_ALLOW_THREADS
+    if (src.wide)
+        result = MoveFileExW(src.wide, dst.wide, flags);
+    else
+        result = MoveFileExA(src.narrow, dst.narrow, flags);
+    Py_END_ALLOW_THREADS
+
+    if (!result) {
+        return_value = win32_error_object(function_name, dst.object);
+        goto exit;
+    }
+
+#else
+    Py_BEGIN_ALLOW_THREADS
+#ifdef HAVE_RENAMEAT
+    if (dir_fd_specified)
+        result = renameat(src_dir_fd, src.narrow, dst_dir_fd, dst.narrow);
+    else
+#endif
+        result = rename(src.narrow, dst.narrow);
+    Py_END_ALLOW_THREADS
+
+    if (result) {
+        return_value = path_error(function_name, &dst);
+        goto exit;
+    }
+#endif
+
+    Py_INCREF(Py_None);
+    return_value = Py_None;
+exit:
+    path_cleanup(&src);
+    path_cleanup(&dst);
+    return return_value;
 }
 
 PyDoc_STRVAR(posix_rename__doc__,
-"rename(old, new)\n\n\
-Rename a file or directory.");
-
-static PyObject *
-posix_rename(PyObject *self, PyObject *args)
-{
-    return internal_rename(self, args, 0);
+"rename(src, dst, *, src_dir_fd=None, dst_dir_fd=None)\n\n\
+Rename a file or directory.\n\
+\n\
+If either src_dir_fd or dst_dir_fd is not None, it should be a file\n\
+  descriptor open to a directory, and the respective path string (src or dst)\n\
+  should be relative; the path will then be relative to that directory.\n\
+src_dir_fd and dst_dir_fd, may not be implemented on your platform.\n\
+  If they are unavailable, using them will raise a NotImplementedError.");
+
+static PyObject *
+posix_rename(PyObject *self, PyObject *args, PyObject *kwargs)
+{
+    return internal_rename(args, kwargs, 0);
 }
 
 PyDoc_STRVAR(posix_replace__doc__,
-"replace(old, new)\n\n\
-Rename a file or directory, overwriting the destination.");
-
-static PyObject *
-posix_replace(PyObject *self, PyObject *args)
-{
-    return internal_rename(self, args, 1);
+"replace(src, dst, *, src_dir_fd=None, dst_dir_fd=None)\n\n\
+Rename a file or directory, overwriting the destination.\n\
+\n\
+If either src_dir_fd or dst_dir_fd is not None, it should be a file\n\
+  descriptor open to a directory, and the respective path string (src or dst)\n\
+  should be relative; the path will then be relative to that directory.\n\
+src_dir_fd and dst_dir_fd, may not be implemented on your platform.\n\
+  If they are unavailable, using them will raise a NotImplementedError.");
+
+static PyObject *
+posix_replace(PyObject *self, PyObject *args, PyObject *kwargs)
+{
+    return internal_rename(args, kwargs, 1);
 }
 
 PyDoc_STRVAR(posix_rmdir__doc__,
@@ -3428,21 +4095,6 @@
 }
 
 
-PyDoc_STRVAR(posix_stat__doc__,
-"stat(path) -> stat result\n\n\
-Perform a stat system call on the given path.");
-
-static PyObject *
-posix_stat(PyObject *self, PyObject *args)
-{
-#ifdef MS_WINDOWS
-    return posix_do_stat(self, args, "O&:stat", STAT, "U:stat", win32_stat_w);
-#else
-    return posix_do_stat(self, args, "O&:stat", STAT, NULL, NULL);
-#endif
-}
-
-
 #ifdef HAVE_SYSTEM
 PyDoc_STRVAR(posix_system__doc__,
 "system(command) -> exit_status\n\n\
@@ -3531,22 +4183,85 @@
 #endif /* MS_WINDOWS */
 
 PyDoc_STRVAR(posix_unlink__doc__,
-"unlink(path)\n\n\
-Remove a file (same as remove(path)).");
+"unlink(path, *, dir_fd=None, rmdir=False)\n\n\
+Remove a file (same as remove()).\n\
+\n\
+If dir_fd is not None, it should be a file descriptor open to a directory,\n\
+  and path should be relative; path will then be relative to that directory.\n\
+dir_fd may not be implemented on your platform.\n\
+  If it is unavailable, using it will raise a NotImplementedError.\n\
+If rmdir is True, unlink will behave like os.rmdir().");
 
 PyDoc_STRVAR(posix_remove__doc__,
-"remove(path)\n\n\
-Remove a file (same as unlink(path)).");
-
-static PyObject *
-posix_unlink(PyObject *self, PyObject *args)
-{
+"remove(path, *, dir_fd=None, rmdir=False)\n\n\
+Remove a file (same as unlink()).\n\
+\n\
+If dir_fd is not None, it should be a file descriptor open to a directory,\n\
+  and path should be relative; path will then be relative to that directory.\n\
+dir_fd may not be implemented on your platform.\n\
+  If it is unavailable, using it will raise a NotImplementedError.\n\
+If rmdir is True, remove will behave like os.rmdir().");
+
+static PyObject *
+posix_unlink(PyObject *self, PyObject *args, PyObject *kwargs)
+{
+    path_t path;
+    int dir_fd = DEFAULT_DIR_FD;
+    int remove_dir = 0;
+    static char *keywords[] = {"path", "dir_fd", "rmdir", NULL};
+    int result;
+    PyObject *return_value = NULL;
+
+    memset(&path, 0, sizeof(path));
+    if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O&|$O&p:unlink", keywords,
+            path_converter, &path,
+#ifdef HAVE_UNLINKAT
+            dir_fd_converter, &dir_fd,
+#else
+            dir_fd_unavailable, &dir_fd,
+#endif
+            &remove_dir))
+        return NULL;
+
+    Py_BEGIN_ALLOW_THREADS
 #ifdef MS_WINDOWS
-    return win32_1str(args, "remove", "y:remove", DeleteFileA,
-                      "U:remove", Py_DeleteFileW);
-#else
-    return posix_1str(args, "O&:remove", unlink);
-#endif
+    if (remove_dir) {
+        if (path.wide)
+            result = RemoveDirectoryW(path.wide);
+        else
+            result = RemoveDirectoryA(path.narrow);
+    }
+    else {
+        if (path.wide)
+            result = Py_DeleteFileW(path.wide);
+        else
+            result = DeleteFileA(path.narrow);
+    }
+    result = !result; /* Windows, success=1, UNIX, success=0 */
+#else
+    if (remove_dir && (dir_fd == DEFAULT_DIR_FD))
+        result = rmdir(path.narrow);
+    else
+#ifdef HAVE_UNLINKAT
+    if (dir_fd != DEFAULT_DIR_FD)
+        result = unlinkat(dir_fd, path.narrow, remove_dir ? AT_REMOVEDIR : 0);
+    else
+#endif /* HAVE_UNLINKAT */
+        result = unlink(path.narrow);
+#endif
+    Py_END_ALLOW_THREADS
+
+    if (result) {
+        return_value = path_error("unlink", &path);
+        goto exit;
+    }
+
+    return_value = Py_None;
+    Py_INCREF(Py_None);
+
+exit:
+    path_cleanup(&path);
+    return return_value;
 }
 
 
@@ -3576,6 +4291,178 @@
 #endif /* HAVE_UNAME */
 
 
+PyDoc_STRVAR(posix_utime__doc__,
+"utime(path, times=None, *, ns=None, dir_fd=None, follow_symlinks=True)\n\
+Set the access and modified time of path.\n\
+\n\
+path may always be specified as a string.\n\
+On some platforms, path may also be specified as an open file descriptor.\n\
+  If this functionality is unavailable, using it raises an exception.\n\
+\n\
+If times is not None, it must be a tuple (atime, mtime);\n\
+    atime and mtime should be expressed as float seconds since the epoch.\n\
+If ns is not None, it must be a tuple (atime_ns, mtime_ns);\n\
+    atime_ns and mtime_ns should be expressed as integer nanoseconds\n\
+    since the epoch.\n\
+If both times and ns are None, utime uses the current time.\n\
+Specifying tuples for both times and ns is an error.\n\
+\n\
+If dir_fd is not None, it should be a file descriptor open to a directory,\n\
+  and path should be relative; path will then be relative to that directory.\n\
+If follow_symlinks is False, and the last element of the path is a symbolic\n\
+  link, utime will modify the symbolic link itself instead of the file the\n\
+  link points to.\n\
+It is an error to use dir_fd or follow_symlinks when specifying path\n\
+  as an open file descriptor.\n\
+dir_fd and follow_symlinks may not be available on your platform.\n\
+  If they are unavailable, using them will raise a NotImplementedError.");
+
+typedef struct {
+    int    now;
+    time_t atime_s;
+    long   atime_ns;
+    time_t mtime_s;
+    long   mtime_ns;
+} utime_t;
+
+/*
+ * these macros assume that "utime" is a pointer to a utime_t
+ * they also intentionally leak the declaration of a pointer named "time"
+ */
+#define UTIME_TO_TIMESPEC \
+    struct timespec ts[2]; \
+    struct timespec *time; \
+    if (utime->now) \
+        time = NULL; \
+    else { \
+        ts[0].tv_sec = utime->atime_s; \
+        ts[0].tv_nsec = utime->atime_ns; \
+        ts[1].tv_sec = utime->mtime_s; \
+        ts[1].tv_nsec = utime->mtime_ns; \
+        time = ts; \
+    } \
+
+#define UTIME_TO_TIMEVAL \
+    struct timeval tv[2]; \
+    struct timeval *time; \
+    if (utime->now) \
+        time = NULL; \
+    else { \
+        tv[0].tv_sec = utime->atime_s; \
+        tv[0].tv_usec = utime->atime_ns / 1000; \
+        tv[1].tv_sec = utime->mtime_s; \
+        tv[1].tv_usec = utime->mtime_ns / 1000; \
+        time = tv; \
+    } \
+
+#define UTIME_TO_UTIMBUF \
+    struct utimbuf u[2]; \
+    struct utimbuf *time; \
+    if (utime->now) \
+        time = NULL; \
+    else { \
+        u.actime = utime->atime_s; \
+        u.modtime = utime->mtime_s; \
+        time = u; \
+    }
+
+#define UTIME_TO_TIME_T \
+    time_t timet[2]; \
+    struct timet time; \
+    if (utime->now) \
+        time = NULL; \
+    else { \
+        timet[0] = utime->atime_s; \
+        timet[1] = utime->mtime_s; \
+        time = &timet; \
+    } \
+
+
+#define UTIME_HAVE_DIR_FD (defined(HAVE_FUTIMESAT) || defined(HAVE_UTIMENSAT))
+
+#if UTIME_HAVE_DIR_FD
+
+static int
+utime_dir_fd(utime_t *utime, int dir_fd, char *path, int follow_symlinks)
+{
+#ifdef HAVE_UTIMENSAT
+    int flags = follow_symlinks ? 0 : AT_SYMLINK_NOFOLLOW;
+    UTIME_TO_TIMESPEC;
+    return utimensat(dir_fd, path, time, flags);
+#elif defined(HAVE_FUTIMESAT)
+    UTIME_TO_TIMEVAL;
+    /*
+     * follow_symlinks will never be false here;
+     * we only allow !follow_symlinks and dir_fd together
+     * if we have utimensat()
+     */
+    assert(follow_symlinks);
+    return futimesat(dir_fd, path, time);
+#endif
+}
+
+#endif
+
+#define UTIME_HAVE_FD (defined(HAVE_FUTIMES) || defined(HAVE_FUTIMENS))
+
+#if UTIME_HAVE_FD
+
+static int
+utime_fd(utime_t *utime, int fd)
+{
+#ifdef HAVE_FUTIMENS
+    UTIME_TO_TIMESPEC;
+    return futimens(fd, time);
+#else
+    UTIME_TO_TIMEVAL;
+    return futimes(fd, time);
+#endif
+}
+
+#endif
+
+
+#define UTIME_HAVE_NOFOLLOW_SYMLINKS \
+        (defined(HAVE_UTIMENSAT) || defined(HAVE_LUTIMES))
+
+#if UTIME_HAVE_NOFOLLOW_SYMLINKS
+
+static int
+utime_nofollow_symlinks(utime_t *utime, char *path)
+{
+#ifdef HAVE_UTIMENSAT
+    UTIME_TO_TIMESPEC;
+    return utimensat(DEFAULT_DIR_FD, path, time, AT_SYMLINK_NOFOLLOW);
+#else
+    UTIME_TO_TIMEVAL;
+    return lutimes(path, time);
+#endif
+}
+
+#endif
+
+#ifndef MS_WINDOWS
+
+static int
+utime_default(utime_t *utime, char *path)
+{
+#ifdef HAVE_UTIMENSAT
+    UTIME_TO_TIMESPEC;
+    return utimensat(DEFAULT_DIR_FD, path, time, 0);
+#elif defined(HAVE_UTIMES)
+    UTIME_TO_TIMEVAL;
+    return utimes(path, time);
+#elif defined(HAVE_UTIME_H)
+    UTIME_TO_UTIMBUF;
+    return utime(path, time);
+#else
+    UTIME_TO_TIME_T;
+    return utime(path, time);
+#endif
+}
+
+#endif
+
 static int
 split_py_long_to_s_and_ns(PyObject *py_long, time_t *s, long *ns)
 {
@@ -3597,250 +4484,133 @@
     return result;
 }
 
-
-typedef int (*parameter_converter_t)(PyObject *, void *);
-
-typedef struct {
-    /* input only */
-    char path_format;
-    parameter_converter_t converter;
-    char *function_name;
-    char *first_argument_name;
-    PyObject *args;
-    PyObject *kwargs;
-
-    /* input/output */
-    PyObject **path;
-
-    /* output only */
-    int now;
-    time_t atime_s;
-    long   atime_ns;
-    time_t mtime_s;
-    long   mtime_ns;
-} utime_arguments;
-
-#define DECLARE_UA(ua, fname) \
-    utime_arguments ua; \
-    memset(&ua, 0, sizeof(ua)); \
-    ua.function_name = fname; \
-    ua.args = args; \
-    ua.kwargs = kwargs; \
-    ua.first_argument_name = "path"; \
-
-/* UA_TO_FILETIME doesn't declare atime and mtime for you */
-#define UA_TO_FILETIME(ua, atime, mtime) \
-    time_t_to_FILE_TIME(ua.atime_s, ua.atime_ns, &atime); \
-    time_t_to_FILE_TIME(ua.mtime_s, ua.mtime_ns, &mtime)
-
-/* the rest of these macros declare the output variable for you */
-#define UA_TO_TIMESPEC(ua, ts) \
-    struct timespec ts[2]; \
-    ts[0].tv_sec = ua.atime_s; \
-    ts[0].tv_nsec = ua.atime_ns; \
-    ts[1].tv_sec = ua.mtime_s; \
-    ts[1].tv_nsec = ua.mtime_ns
-
-#define UA_TO_TIMEVAL(ua, tv) \
-    struct timeval tv[2]; \
-    tv[0].tv_sec = ua.atime_s; \
-    tv[0].tv_usec = ua.atime_ns / 1000; \
-    tv[1].tv_sec = ua.mtime_s; \
-    tv[1].tv_usec = ua.mtime_ns / 1000
-
-#define UA_TO_UTIMBUF(ua, u) \
-    struct utimbuf u; \
-    utimbuf.actime = ua.atime_s; \
-    utimbuf.modtime = ua.mtime_s
-
-#define UA_TO_TIME_T(ua, timet) \
-    time_t timet[2]; \
-    timet[0] = ua.atime_s; \
-    timet[1] = ua.mtime_s
-
-
-/*
- * utime_read_time_arguments() processes arguments for the utime
- * family of functions.
- */
-
-typedef enum {
-  UTIME_SUCCESS = 0,
-  UTIME_PARSE_FAILURE = 1,
-  UTIME_TIMES_AND_NS_COLLISION = 2,
-  UTIME_TIMES_CONVERSION_FAILURE = 3,
-  UTIME_NS_CONVERSION_FAILURE = 4,
-} utime_status;
-
-static utime_status
-utime_read_time_arguments(utime_arguments *ua)
-{
+static PyObject *
+posix_utime(PyObject *self, PyObject *args, PyObject *kwargs)
+{
+    path_t path;
     PyObject *times = NULL;
     PyObject *ns = NULL;
-    char format[24];
-    char *kwlist[4];
-    char **kw = kwlist;
-    utime_status return_value;
-    int parse_result;
-
-    *kw++ = ua->first_argument_name;
-    *kw++ = "times";
-    *kw++ = "ns";
-    *kw = NULL;
-
-    sprintf(format, "%c%s|O$O:%s",
-            ua->path_format,
-            ua->converter ? "&" : "",
-            ua->function_name);
-
-    if (ua->converter)
-        parse_result = PyArg_ParseTupleAndKeywords(ua->args, ua->kwargs,
-            format, kwlist, ua->converter, ua->path, &times, &ns);
-    else
-        parse_result = PyArg_ParseTupleAndKeywords(ua->args, ua->kwargs,
-            format, kwlist, ua->path, &times, &ns);
-
-    if (!parse_result)
-        return UTIME_PARSE_FAILURE;
-
-    if (times && ns) {
-        PyErr_Format(PyExc_RuntimeError,
-                     "%s: you may specify either 'times'"
-                     " or 'ns' but not both",
-                     ua->function_name);
-        return_value = UTIME_TIMES_AND_NS_COLLISION;
-        goto fail;
+    int dir_fd = DEFAULT_DIR_FD;
+    int follow_symlinks = 1;
+    char *keywords[] = {"path", "times", "ns", "dir_fd",
+                        "follow_symlinks", NULL};
+
+    utime_t utime;
+
+#ifdef MS_WINDOWS
+    HANDLE hFile;
+    FILETIME atime, mtime;
+#else
+    int result;
+#endif
+
+    PyObject *return_value = NULL;
+
+    memset(&path, 0, sizeof(path));
+#if UTIME_HAVE_FD
+    path.allow_fd = 1;
+#endif
+    if (!PyArg_ParseTupleAndKeywords(args, kwargs,
+            "O&|O$OO&p:utime", keywords,
+            path_converter, &path,
+            &times, &ns,
+#if UTIME_HAVE_DIR_FD
+            dir_fd_converter, &dir_fd,
+#else
+            dir_fd_unavailable, &dir_fd,
+#endif
+            &follow_symlinks
+            ))
+        return NULL;
+
+    if (times && (times != Py_None) && ns) {
+        PyErr_SetString(PyExc_ValueError,
+                     "utime: you may specify either 'times'"
+                     " or 'ns' but not both");
+        goto exit;
     }
 
     if (times && (times != Py_None)) {
         if (!PyTuple_CheckExact(times) || (PyTuple_Size(times) != 2)) {
-            PyErr_Format(PyExc_TypeError,
-                         "%s: 'times' must be either"
-                         " a tuple of two ints or None",
-                         ua->function_name);
-            return_value = UTIME_TIMES_CONVERSION_FAILURE;
-            goto fail;
+            PyErr_SetString(PyExc_TypeError,
+                         "utime: 'times' must be either"
+                         " a tuple of two ints or None");
+            goto exit;
         }
-        ua->now = 0;
+        utime.now = 0;
         if (_PyTime_ObjectToTimespec(PyTuple_GET_ITEM(times, 0),
-                                     &ua->atime_s, &ua->atime_ns) == -1 ||
+                                     &utime.atime_s, &utime.atime_ns) == -1 ||
             _PyTime_ObjectToTimespec(PyTuple_GET_ITEM(times, 1),
-                                     &ua->mtime_s, &ua->mtime_ns) == -1) {
-            return_value = UTIME_TIMES_CONVERSION_FAILURE;
-            goto fail;
+                                     &utime.mtime_s, &utime.mtime_ns) == -1) {
+            goto exit;
         }
-        return UTIME_SUCCESS;
-    }
-
-    if (ns) {
+    }
+    else if (ns) {
         if (!PyTuple_CheckExact(ns) || (PyTuple_Size(ns) != 2)) {
-            PyErr_Format(PyExc_TypeError,
-                         "%s: 'ns' must be a tuple of two ints",
-                         ua->function_name);
-            return_value = UTIME_NS_CONVERSION_FAILURE;
-            goto fail;
+            PyErr_SetString(PyExc_TypeError,
+                         "utime: 'ns' must be a tuple of two ints");
+            goto exit;
         }
-        ua->now = 0;
+        utime.now = 0;
         if (!split_py_long_to_s_and_ns(PyTuple_GET_ITEM(ns, 0),
-                                      &ua->atime_s, &ua->atime_ns) ||
+                                      &utime.atime_s, &utime.atime_ns) ||
             !split_py_long_to_s_and_ns(PyTuple_GET_ITEM(ns, 1),
-                                       &ua->mtime_s, &ua->mtime_ns)) {
-            return_value = UTIME_NS_CONVERSION_FAILURE;
-            goto fail;
+                                       &utime.mtime_s, &utime.mtime_ns)) {
+            goto exit;
         }
-        return UTIME_SUCCESS;
-    }
-
-    /* either times=None, or neither times nor ns was specified. use "now". */
-    ua->now = 1;
-    return UTIME_SUCCESS;
-
-  fail:
-    if (ua->converter)
-        Py_DECREF(*ua->path);
-    return return_value;
-}
-
-
-PyDoc_STRVAR(posix_utime__doc__,
-"utime(path[, times=(atime, mtime), *, ns=(atime_ns, mtime_ns)])\n\
-Set the access and modified time of the file.\n\
-If the second argument ('times') is specified,\n\
-    the values should be expressed as float seconds since the epoch.\n\
-If the keyword argument 'ns' is specified,\n\
-    the values should be expressed as integer nanoseconds since the epoch.\n\
-If neither the second nor the 'ns' argument is specified,\n\
-    utime uses the current time.\n\
-Specifying both 'times' and 'ns' is an error.");
-
-static PyObject *
-posix_utime(PyObject *self, PyObject *args, PyObject *kwargs)
-{
+    }
+    else {
+        /* times and ns are both None/unspecified. use "now". */
+        utime.now = 1;
+    }
+
+#if !UTIME_HAVE_NOFOLLOW_SYMLINKS
+    if (follow_symlinks_specified("utime", follow_symlinks))
+        goto exit;
+#endif
+
+    if (path_and_dir_fd_invalid("utime", &path, dir_fd) ||
+        dir_fd_and_fd_invalid("utime", dir_fd, path.fd) ||
+        fd_and_follow_symlinks_invalid("utime", path.fd, follow_symlinks))
+        goto exit;
+
+#if !defined(HAVE_UTIMENSAT)
+    if ((dir_fd != DEFAULT_DIR_FD) && (!follow_symlinks)) {
+        PyErr_SetString(PyExc_RuntimeError,
+                     "utime: cannot use dir_fd and follow_symlinks "
+                     "together on this platform");
+        goto exit;
+    }
+#endif
+
 #ifdef MS_WINDOWS
-    PyObject *upath;
-    HANDLE hFile;
-    PyObject *result = NULL;
-    FILETIME atime, mtime;
-
-    DECLARE_UA(ua, "utime");
-
-    ua.path_format = 'U';
-    ua.path = &upath;
-
-    switch (utime_read_time_arguments(&ua)) {
-    default:
-        return NULL;
-    case UTIME_SUCCESS: {
-        wchar_t *wpath = PyUnicode_AsUnicode(upath);
-        if (wpath == NULL)
-            return NULL;
-        Py_BEGIN_ALLOW_THREADS
-        hFile = CreateFileW(wpath, FILE_WRITE_ATTRIBUTES, 0,
+    Py_BEGIN_ALLOW_THREADS
+    if (path.wide)
+        hFile = CreateFileW(path.wide, FILE_WRITE_ATTRIBUTES, 0,
                             NULL, OPEN_EXISTING,
                             FILE_FLAG_BACKUP_SEMANTICS, NULL);
-        Py_END_ALLOW_THREADS
-        if (hFile == INVALID_HANDLE_VALUE)
-            return win32_error_object("utime", upath);
-        break;
-    }
-    case UTIME_PARSE_FAILURE: {
-        const char *apath;
-        /* Drop the argument parsing error as narrow strings
-           are also valid. */
-        PyErr_Clear();
-
-        ua.path_format = 'y';
-        ua.path = (PyObject **)&apath;
-        if (utime_read_time_arguments(&ua) != UTIME_SUCCESS)
-            return NULL;
-        if (win32_warn_bytes_api())
-            return NULL;
-
-        Py_BEGIN_ALLOW_THREADS
-        hFile = CreateFileA(apath, FILE_WRITE_ATTRIBUTES, 0,
+    else
+        hFile = CreateFileA(path.narrow, FILE_WRITE_ATTRIBUTES, 0,
                             NULL, OPEN_EXISTING,
                             FILE_FLAG_BACKUP_SEMANTICS, NULL);
-        Py_END_ALLOW_THREADS
-        if (hFile == INVALID_HANDLE_VALUE) {
-            win32_error("utime", apath);
-            return NULL;
-        }
-        break;
-    }
-
-    }
-
-    if (ua.now) {
+    Py_END_ALLOW_THREADS
+    if (hFile == INVALID_HANDLE_VALUE) {
+        win32_error_object("utime", path.object);
+        goto exit;
+    }
+
+    if (utime.now) {
         SYSTEMTIME now;
         GetSystemTime(&now);
         if (!SystemTimeToFileTime(&now, &mtime) ||
             !SystemTimeToFileTime(&now, &atime)) {
             win32_error("utime", NULL);
-            goto done;
+            goto exit;
         }
     }
     else {
-        UA_TO_FILETIME(ua, atime, mtime);
+        time_t_to_FILE_TIME(utime.atime_s, utime.atime_ns, &atime);
+        time_t_to_FILE_TIME(utime.mtime_s, utime.mtime_ns, &mtime);
     }
     if (!SetFileTime(hFile, NULL, &atime, &mtime)) {
         /* Avoid putting the file name into the error here,
@@ -3848,151 +4618,52 @@
            something is wrong with the file, when it also
            could be the time stamp that gives a problem. */
         win32_error("utime", NULL);
-        goto done;
-    }
+        goto exit;
+    }
+#else /* MS_WINDOWS */
+    Py_BEGIN_ALLOW_THREADS
+
+#if UTIME_HAVE_NOFOLLOW_SYMLINKS
+    if ((!follow_symlinks) && (dir_fd == DEFAULT_DIR_FD))
+        result = utime_nofollow_symlinks(&utime, path.narrow);
+    else
+#endif
+
+#if UTIME_HAVE_DIR_FD
+    if ((dir_fd != DEFAULT_DIR_FD) || (!follow_symlinks))
+        result = utime_dir_fd(&utime, dir_fd, path.narrow, follow_symlinks);
+    else
+#endif
+
+#if UTIME_HAVE_FD
+    if (path.fd != -1)
+        result = utime_fd(&utime, path.fd);
+    else
+#endif
+
+    result = utime_default(&utime, path.narrow);
+
+    Py_END_ALLOW_THREADS
+
+    if (result < 0) {
+        /* see previous comment about not putting filename in error here */
+        return_value = posix_error();
+        goto exit;
+    }
+
+#endif /* MS_WINDOWS */
+
     Py_INCREF(Py_None);
-    result = Py_None;
-done:
-    CloseHandle(hFile);
-    return result;
-#else /* MS_WINDOWS */
-    PyObject *opath;
-    char *path;
-    int res;
-
-    DECLARE_UA(ua, "utime");
-
-    ua.path_format = 'O';
-    ua.path = &opath;
-    ua.converter = PyUnicode_FSConverter;
-
-    if (utime_read_time_arguments(&ua) != UTIME_SUCCESS)
-        return NULL;
-    path = PyBytes_AsString(opath);
-    if (ua.now) {
-        Py_BEGIN_ALLOW_THREADS
-        res = utime(path, NULL);
-        Py_END_ALLOW_THREADS
-    }
-    else {
-        Py_BEGIN_ALLOW_THREADS
-#ifdef HAVE_UTIMENSAT
-        UA_TO_TIMESPEC(ua, buf);
-        res = utimensat(AT_FDCWD, path, buf, 0);
-#elif defined(HAVE_UTIMES)
-        UA_TO_TIMEVAL(ua, buf);
-        res = utimes(path, buf);
-#elif defined(HAVE_UTIME_H)
-        /* XXX should define struct utimbuf instead, above */
-        UA_TO_UTIMBUF(ua, buf);
-        res = utime(path, &buf);
-#else
-        UA_TO_TIME_T(ua, buf);
-        res = utime(path, buf);
-#endif
-        Py_END_ALLOW_THREADS
-    }
-
-    if (res < 0) {
-        return posix_error_with_allocated_filename(opath);
-    }
-    Py_DECREF(opath);
-    Py_RETURN_NONE;
-#undef UTIME_EXTRACT
-#endif /* MS_WINDOWS */
-}
-
-#ifdef HAVE_FUTIMES
-PyDoc_STRVAR(posix_futimes__doc__,
-"futimes(fd[, times=(atime, mtime), *, ns=(atime_ns, mtime_ns)])\n\
-Set the access and modified time of the file specified by the file\n\
-descriptor fd.  See utime for the semantics of the times and ns parameters.");
-
-static PyObject *
-posix_futimes(PyObject *self, PyObject *args, PyObject *kwargs)
-{
-    int res, fd;
-
-    DECLARE_UA(ua, "futimes");
-
-    ua.path_format = 'i';
-    ua.path = (PyObject **)&fd;
-    ua.first_argument_name = "fd";
-
-    if (utime_read_time_arguments(&ua) != UTIME_SUCCESS)
-        return NULL;
-
-    if (ua.now) {
-        Py_BEGIN_ALLOW_THREADS
-        res = futimes(fd, NULL);
-        Py_END_ALLOW_THREADS
-    }
-    else {
-        Py_BEGIN_ALLOW_THREADS
-        {
-#ifdef HAVE_FUTIMENS
-        UA_TO_TIMESPEC(ua, buf);
-        res = futimens(fd, buf);
-#else
-        UA_TO_TIMEVAL(ua, buf);
-        res = futimes(fd, buf);
-#endif
-        }
-        Py_END_ALLOW_THREADS
-    }
-    if (res < 0)
-        return posix_error();
-    Py_RETURN_NONE;
-}
-#endif
-
-#ifdef HAVE_LUTIMES
-PyDoc_STRVAR(posix_lutimes__doc__,
-"lutimes(path[, times=(atime, mtime), *, ns=(atime_ns, mtime_ns)])\n\
-Like utime(), but if path is a symbolic link, it is not dereferenced.");
-
-static PyObject *
-posix_lutimes(PyObject *self, PyObject *args, PyObject *kwargs)
-{
-    PyObject *opath;
-    const char *path;
-    int res;
-
-    DECLARE_UA(ua, "lutimes");
-
-    ua.path_format = 'O';
-    ua.path = &opath;
-    ua.converter = PyUnicode_FSConverter;
-
-    if (utime_read_time_arguments(&ua) != UTIME_SUCCESS)
-        return NULL;
-    path = PyBytes_AsString(opath);
-
-    if (ua.now) {
-        /* optional time values not given */
-        Py_BEGIN_ALLOW_THREADS
-        res = lutimes(path, NULL);
-        Py_END_ALLOW_THREADS
-    }
-    else {
-        Py_BEGIN_ALLOW_THREADS
-        {
-#ifdef HAVE_UTIMENSAT
-        UA_TO_TIMESPEC(ua, buf);
-        res = utimensat(AT_FDCWD, path, buf, AT_SYMLINK_NOFOLLOW);
-#else
-        UA_TO_TIMEVAL(ua, buf);
-        res = lutimes(path, buf);
-#endif
-        }
-        Py_END_ALLOW_THREADS
-    }
-    Py_DECREF(opath);
-    if (res < 0)
-        return posix_error();
-    Py_RETURN_NONE;
-}
-#endif
+    return_value = Py_None;
+
+exit:
+    path_cleanup(&path);
+#ifdef MS_WINDOWS
+    if (hFile != INVALID_HANDLE_VALUE)
+        CloseHandle(hFile);
+#endif
+    return return_value;
+}
 
 /* Process operations */
 
@@ -4206,121 +4877,79 @@
 \n\
     path: path of executable file\n\
     args: tuple or list of arguments\n\
-    env: dictionary of strings mapping to strings");
-
-static PyObject *
-posix_execve(PyObject *self, PyObject *args)
-{
-    PyObject *opath;
-    char *path;
+    env: dictionary of strings mapping to strings\n\
+\n\
+On some platforms, you may specify an open file descriptor for path;\n\
+  execve will execute the program the file descriptor is open to.\n\
+  If this functionality is unavailable, using it raises NotImplementedError.");
+
+static PyObject *
+posix_execve(PyObject *self, PyObject *args, PyObject *kwargs)
+{
+    path_t path;
     PyObject *argv, *env;
-    char **argvlist;
+    char **argvlist = NULL;
     char **envlist;
     Py_ssize_t argc, envc;
+    static char *keywords[] = {"path", "argv", "environment", NULL};
 
     /* execve has three arguments: (path, argv, env), where
        argv is a list or tuple of strings and env is a dictionary
        like posix.environ. */
 
-    if (!PyArg_ParseTuple(args, "O&OO:execve",
-                          PyUnicode_FSConverter,
-                          &opath, &argv, &env))
-        return NULL;
-    path = PyBytes_AsString(opath);
+    memset(&path, 0, sizeof(path));
+#ifdef HAVE_FEXECVE
+    path.allow_fd = 1;
+#endif
+    if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O&OO:execve", keywords,
+                          path_converter, &path,
+                          &argv, &env
+                          ))
+        return NULL;
+
     if (!PyList_Check(argv) && !PyTuple_Check(argv)) {
         PyErr_SetString(PyExc_TypeError,
-                        "execve() arg 2 must be a tuple or list");
-        goto fail_0;
+                        "execve: argv must be a tuple or list");
+        goto fail;
     }
     argc = PySequence_Size(argv);
     if (!PyMapping_Check(env)) {
         PyErr_SetString(PyExc_TypeError,
-                        "execve() arg 3 must be a mapping object");
-        goto fail_0;
+                        "execve: environment must be a mapping object");
+        goto fail;
     }
 
     argvlist = parse_arglist(argv, &argc);
     if (argvlist == NULL) {
-        goto fail_0;
-    }
-
-    envlist = parse_envlist(env, &envc);
-    if (envlist == NULL)
-        goto fail_1;
-
-    execve(path, argvlist, envlist);
-
-    /* If we get here it's definitely an error */
-
-    (void) posix_error();
-
-    while (--envc >= 0)
-        PyMem_DEL(envlist[envc]);
-    PyMem_DEL(envlist);
-  fail_1:
-    free_string_array(argvlist, argc);
-  fail_0:
-    Py_DECREF(opath);
-    return NULL;
-}
-#endif /* HAVE_EXECV */
-
-#ifdef HAVE_FEXECVE
-PyDoc_STRVAR(posix_fexecve__doc__,
-"fexecve(fd, args, env)\n\n\
-Execute the program specified by a file descriptor with arguments and\n\
-environment, replacing the current process.\n\
-\n\
-    fd: file descriptor of executable\n\
-    args: tuple or list of arguments\n\
-    env: dictionary of strings mapping to strings");
-
-static PyObject *
-posix_fexecve(PyObject *self, PyObject *args)
-{
-    int fd;
-    PyObject *argv, *env;
-    char **argvlist;
-    char **envlist;
-    Py_ssize_t argc, envc;
-
-    if (!PyArg_ParseTuple(args, "iOO:fexecve",
-                          &fd, &argv, &env))
-        return NULL;
-    if (!PyList_Check(argv) && !PyTuple_Check(argv)) {
-        PyErr_SetString(PyExc_TypeError,
-                        "fexecve() arg 2 must be a tuple or list");
-        return NULL;
-    }
-    argc = PySequence_Size(argv);
-    if (!PyMapping_Check(env)) {
-        PyErr_SetString(PyExc_TypeError,
-                        "fexecve() arg 3 must be a mapping object");
-        return NULL;
-    }
-
-    argvlist = parse_arglist(argv, &argc);
-    if (argvlist == NULL)
-        return NULL;
+        goto fail;
+    }
 
     envlist = parse_envlist(env, &envc);
     if (envlist == NULL)
         goto fail;
 
-    fexecve(fd, argvlist, envlist);
+#ifdef HAVE_FEXECVE
+    if (path.fd > -1)
+        fexecve(path.fd, argvlist, envlist);
+    else
+#endif
+        execve(path.narrow, argvlist, envlist);
 
     /* If we get here it's definitely an error */
 
-    (void) posix_error();
+    path_posix_error("execve", &path);
 
     while (--envc >= 0)
         PyMem_DEL(envlist[envc]);
     PyMem_DEL(envlist);
   fail:
-    free_string_array(argvlist, argc);
+    if (argvlist)
+        free_string_array(argvlist, argc);
+    path_cleanup(&path);
     return NULL;
 }
-#endif /* HAVE_FEXECVE */
+#endif /* HAVE_EXECV */
+
 
 #ifdef HAVE_SPAWNV
 PyDoc_STRVAR(posix_spawnv__doc__,
@@ -6433,107 +7062,224 @@
 #endif
 
 
-PyDoc_STRVAR(posix_lstat__doc__,
-"lstat(path) -> stat result\n\n\
-Like stat(path), but do not follow symbolic links.");
-
-static PyObject *
-posix_lstat(PyObject *self, PyObject *args)
-{
-#ifdef HAVE_LSTAT
-    return posix_do_stat(self, args, "O&:lstat", lstat, NULL, NULL);
-#else /* !HAVE_LSTAT */
+#if defined(HAVE_READLINK) || defined(MS_WINDOWS)
+PyDoc_STRVAR(readlink__doc__,
+"readlink(path, *, dir_fd=None) -> path\n\n\
+Return a string representing the path to which the symbolic link points.\n\
+\n\
+If dir_fd is not None, it should be a file descriptor open to a directory,\n\
+  and path should be relative; path will then be relative to that directory.\n\
+dir_fd may not be implemented on your platform.\n\
+  If it is unavailable, using it will raise a NotImplementedError.");
+#endif
+
+#ifdef HAVE_READLINK
+
+static PyObject *
+posix_readlink(PyObject *self, PyObject *args, PyObject *kwargs)
+{
+    path_t path;
+    int dir_fd = DEFAULT_DIR_FD;
+    char buffer[MAXPATHLEN];
+    ssize_t length;
+    PyObject *return_value = NULL;
+    static char *keywords[] = {"path", "dir_fd", NULL};
+
+    memset(&path, 0, sizeof(path));
+    if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O&|$O&:readlink", keywords,
+                          path_converter, &path,
+#ifdef HAVE_READLINKAT
+                          dir_fd_converter, &dir_fd
+#else
+                          dir_fd_unavailable, &dir_fd
+#endif
+                          ))
+        return NULL;
+
+    Py_BEGIN_ALLOW_THREADS
+#ifdef HAVE_READLINKAT
+    if (dir_fd != DEFAULT_DIR_FD)
+        length = readlinkat(dir_fd, path.narrow, buffer, sizeof(buffer));
+    else
+#endif
+        length = readlink(path.narrow, buffer, sizeof(buffer));
+    Py_END_ALLOW_THREADS
+
+    if (length < 0) {
+        return_value = path_posix_error("readlink", &path);
+        goto exit;
+    }
+
+    if (PyUnicode_Check(path.object))
+        return_value = PyUnicode_DecodeFSDefaultAndSize(buffer, length);
+    else
+        return_value = PyBytes_FromStringAndSize(buffer, length);
+exit:
+    path_cleanup(&path);
+    return return_value;
+}
+
+
+#endif /* HAVE_READLINK */
+
+
+#ifdef HAVE_SYMLINK
+PyDoc_STRVAR(posix_symlink__doc__,
+"symlink(src, dst, target_is_directory=False, *, dir_fd=None)\n\n\
+Create a symbolic link pointing to src named dst.\n\n\
+target_is_directory is required on Windows if the target is to be\n\
+  interpreted as a directory.  (On Windows, symlink requires\n\
+  Windows 6.0 or greater, and raises a NotImplementedError otherwise.)\n\
+  target_is_directory is ignored on non-Windows platforms.\n\
+\n\
+If dir_fd is not None, it should be a file descriptor open to a directory,\n\
+  and path should be relative; path will then be relative to that directory.\n\
+dir_fd may not be implemented on your platform.\n\
+  If it is unavailable, using it will raise a NotImplementedError.");
+
+#if defined(MS_WINDOWS)
+
+/* Grab CreateSymbolicLinkW dynamically from kernel32 */
+static DWORD (CALLBACK *Py_CreateSymbolicLinkW)(LPWSTR, LPWSTR, DWORD) = NULL;
+static DWORD (CALLBACK *Py_CreateSymbolicLinkA)(LPSTR, LPSTR, DWORD) = NULL;
+static int
+check_CreateSymbolicLink()
+{
+    HINSTANCE hKernel32;
+    /* only recheck */
+    if (Py_CreateSymbolicLinkW && Py_CreateSymbolicLinkA)
+        return 1;
+    hKernel32 = GetModuleHandleW(L"KERNEL32");
+    *(FARPROC*)&Py_CreateSymbolicLinkW = GetProcAddress(hKernel32,
+                                                        "CreateSymbolicLinkW");
+    *(FARPROC*)&Py_CreateSymbolicLinkA = GetProcAddress(hKernel32,
+                                                        "CreateSymbolicLinkA");
+    return (Py_CreateSymbolicLinkW && Py_CreateSymbolicLinkA);
+}
+
+#endif
+
+static PyObject *
+posix_symlink(PyObject *self, PyObject *args, PyObject *kwargs)
+{
+    path_t src;
+    path_t dst;
+    int dir_fd = DEFAULT_DIR_FD;
+    int target_is_directory = 0;
+    static char *keywords[] = {"src", "dst", "target_is_directory",
+                               "dir_fd", NULL};
+    PyObject *return_value;
 #ifdef MS_WINDOWS
-    return posix_do_stat(self, args, "O&:lstat", win32_lstat, "U:lstat",
-                         win32_lstat_w);
-#else
-    return posix_do_stat(self, args, "O&:lstat", STAT, NULL, NULL);
-#endif
-#endif /* !HAVE_LSTAT */
-}
-
-
-#ifdef HAVE_READLINK
-PyDoc_STRVAR(posix_readlink__doc__,
-"readlink(path) -> path\n\n\
-Return a string representing the path to which the symbolic link points.");
-
-static PyObject *
-posix_readlink(PyObject *self, PyObject *args)
-{
-    PyObject* v;
-    char buf[MAXPATHLEN];
-    PyObject *opath;
-    char *path;
-    int n;
-    int arg_is_unicode = 0;
-
-    if (!PyArg_ParseTuple(args, "O&:readlink",
-                          PyUnicode_FSConverter, &opath))
-        return NULL;
-    path = PyBytes_AsString(opath);
-    v = PySequence_GetItem(args, 0);
-    if (v == NULL) {
-        Py_DECREF(opath);
-        return NULL;
-    }
-
-    if (PyUnicode_Check(v)) {
-        arg_is_unicode = 1;
-    }
-    Py_DECREF(v);
-
+    DWORD result;
+#else
+    int result;
+#endif
+
+    memset(&src, 0, sizeof(src));
+    src.argument_name = "src";
+    memset(&dst, 0, sizeof(dst));
+    dst.argument_name = "dst";
+
+#ifdef MS_WINDOWS
+    if (!check_CreateSymbolicLink()) {
+        PyErr_SetString(PyExc_NotImplementedError,
+            "CreateSymbolicLink functions not found");
+		return NULL;
+	}
+    if (!win32_can_symlink) {
+        PyErr_SetString(PyExc_OSError, "symbolic link privilege not held");
+		return NULL;
+	}
+#endif
+
+    if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O&O&|i$O&:symlink",
+            keywords,
+            path_converter, &src,
+            path_converter, &dst,
+            &target_is_directory,
+#ifdef HAVE_SYMLINKAT
+            dir_fd_converter, &dir_fd
+#else
+            dir_fd_unavailable, &dir_fd
+#endif
+            ))
+        return NULL;
+
+    if ((src.narrow && dst.wide) || (src.wide && dst.narrow)) {
+        PyErr_SetString(PyExc_ValueError,
+            "symlink: src and dst must be the same type");
+        return_value = NULL;
+        goto exit;
+    }
+
+#ifdef MS_WINDOWS
     Py_BEGIN_ALLOW_THREADS
-    n = readlink(path, buf, (int) sizeof buf);
+    if (dst.wide)
+        result = Py_CreateSymbolicLinkW(dst.wide, src.wide,
+                                        target_is_directory);
+    else
+        result = Py_CreateSymbolicLinkA(dst.narrow, src.narrow,
+                                        target_is_directory);
     Py_END_ALLOW_THREADS
-    if (n < 0)
-        return posix_error_with_allocated_filename(opath);
-
-    Py_DECREF(opath);
-    if (arg_is_unicode)
-        return PyUnicode_DecodeFSDefaultAndSize(buf, n);
+
+    if (!result) {
+        return_value = win32_error_object("symlink", src.object);
+        goto exit;
+    }
+
+#else
+
+    Py_BEGIN_ALLOW_THREADS
+#if HAVE_SYMLINKAT
+    if (dir_fd != DEFAULT_DIR_FD)
+        result = symlinkat(src.narrow, dir_fd, dst.narrow);
     else
-        return PyBytes_FromStringAndSize(buf, n);
-}
-#endif /* HAVE_READLINK */
-
-
-#if defined(HAVE_SYMLINK) && !defined(MS_WINDOWS)
-PyDoc_STRVAR(posix_symlink__doc__,
-"symlink(src, dst)\n\n\
-Create a symbolic link pointing to src named dst.");
-
-static PyObject *
-posix_symlink(PyObject *self, PyObject *args)
-{
-    return posix_2str(args, "O&O&:symlink", symlink);
-}
+#endif
+        result = symlink(src.narrow, dst.narrow);
+    Py_END_ALLOW_THREADS
+
+    if (result) {
+        return_value = path_error("symlink", &dst);
+        goto exit;
+    }
+#endif
+
+    return_value = Py_None;
+    Py_INCREF(Py_None);
+    goto exit; /* silence "unused label" warning */
+exit:
+    path_cleanup(&src);
+    path_cleanup(&dst);
+    return return_value;
+}
+
 #endif /* HAVE_SYMLINK */
 
+
 #if !defined(HAVE_READLINK) && defined(MS_WINDOWS)
 
-PyDoc_STRVAR(win_readlink__doc__,
-"readlink(path) -> path\n\n\
-Return a string representing the path to which the symbolic link points.");
-
-/* Windows readlink implementation */
-static PyObject *
-win_readlink(PyObject *self, PyObject *args)
+static PyObject *
+win_readlink(PyObject *self, PyObject *args, PyObject *kwargs)
 {
     wchar_t *path;
     DWORD n_bytes_returned;
     DWORD io_result;
     PyObject *po, *result;
+	int dir_fd;
     HANDLE reparse_point_handle;
 
     char target_buffer[MAXIMUM_REPARSE_DATA_BUFFER_SIZE];
     REPARSE_DATA_BUFFER *rdb = (REPARSE_DATA_BUFFER *)target_buffer;
     wchar_t *print_name;
 
-    if (!PyArg_ParseTuple(args,
-                  "U:readlink",
-                  &po))
-        return NULL;
+    static char *keywords[] = {"path", "dir_fd", NULL};
+
+    if (!PyArg_ParseTupleAndKeywords(args, kwargs, "U|$O&:readlink", keywords,
+                          &po,
+                          dir_fd_unavailable, &dir_fd
+                          ))
+        return NULL;
+
     path = PyUnicode_AsUnicode(po);
     if (path == NULL)
         return NULL;
@@ -6585,90 +7331,6 @@
 
 #endif /* !defined(HAVE_READLINK) && defined(MS_WINDOWS) */
 
-#if defined(HAVE_SYMLINK) && defined(MS_WINDOWS)
-
-/* Grab CreateSymbolicLinkW dynamically from kernel32 */
-static int has_CreateSymbolicLinkW = 0;
-static DWORD (CALLBACK *Py_CreateSymbolicLinkW)(LPWSTR, LPWSTR, DWORD);
-static int
-check_CreateSymbolicLinkW()
-{
-    HINSTANCE hKernel32;
-    /* only recheck */
-    if (has_CreateSymbolicLinkW)
-        return has_CreateSymbolicLinkW;
-    hKernel32 = GetModuleHandleW(L"KERNEL32");
-    *(FARPROC*)&Py_CreateSymbolicLinkW = GetProcAddress(hKernel32,
-                                                        "CreateSymbolicLinkW");
-    if (Py_CreateSymbolicLinkW)
-        has_CreateSymbolicLinkW = 1;
-    return has_CreateSymbolicLinkW;
-}
-
-PyDoc_STRVAR(win_symlink__doc__,
-"symlink(src, dst, target_is_directory=False)\n\n\
-Create a symbolic link pointing to src named dst.\n\
-target_is_directory is required if the target is to be interpreted as\n\
-a directory.\n\
-This function requires Windows 6.0 or greater, and raises a\n\
-NotImplementedError otherwise.");
-
-static PyObject *
-win_symlink(PyObject *self, PyObject *args, PyObject *kwargs)
-{
-    static char *kwlist[] = {"src", "dest", "target_is_directory", NULL};
-    PyObject *osrc, *odest;
-    PyObject *usrc = NULL, *udest = NULL;
-    wchar_t *wsrc, *wdest;
-    int target_is_directory = 0;
-    DWORD res;
-
-    if (!check_CreateSymbolicLinkW())
-    {
-        /* raise NotImplementedError */
-        return PyErr_Format(PyExc_NotImplementedError,
-            "CreateSymbolicLinkW not found");
-    }
-    if (!PyArg_ParseTupleAndKeywords(
-            args, kwargs, "OO|i:symlink", kwlist,
-            &osrc, &odest, &target_is_directory))
-        return NULL;
-
-    usrc = win32_decode_filename(osrc);
-    if (!usrc)
-        return NULL;
-    udest = win32_decode_filename(odest);
-    if (!udest)
-        goto error;
-
-    if (win32_can_symlink == 0)
-        return PyErr_Format(PyExc_OSError, "symbolic link privilege not held");
-
-    wsrc = PyUnicode_AsUnicode(usrc);
-    if (wsrc == NULL)
-        goto error;
-    wdest = PyUnicode_AsUnicode(udest);
-    if (wsrc == NULL)
-        goto error;
-
-    Py_BEGIN_ALLOW_THREADS
-    res = Py_CreateSymbolicLinkW(wdest, wsrc, target_is_directory);
-    Py_END_ALLOW_THREADS
-
-    Py_DECREF(usrc);
-    Py_DECREF(udest);
-    if (!res)
-        return win32_error_object("symlink", osrc);
-
-    Py_INCREF(Py_None);
-    return Py_None;
-
-error:
-    Py_XDECREF(usrc);
-    Py_XDECREF(udest);
-    return NULL;
-}
-#endif /* defined(HAVE_SYMLINK) && defined(MS_WINDOWS) */
 
 #ifdef HAVE_TIMES
 #if defined(PYCC_VACPP) && defined(PYOS_OS2)
@@ -6848,57 +7510,68 @@
 /* Functions acting on file descriptors */
 
 PyDoc_STRVAR(posix_open__doc__,
-"open(filename, flag [, mode=0777]) -> fd\n\n\
-Open a file (for low level IO).");
-
-static PyObject *
-posix_open(PyObject *self, PyObject *args)
-{
-    PyObject *ofile;
-    char *file;
-    int flag;
+"open(path, flags, mode=0o777, *, dir_fd=None)\n\n\
+Open a file for low level IO.  Returns a file handle (integer).\n\
+\n\
+If dir_fd is not None, it should be a file descriptor open to a directory,\n\
+  and path should be relative; path will then be relative to that directory.\n\
+dir_fd may not be implemented on your platform.\n\
+  If it is unavailable, using it will raise a NotImplementedError.");
+
+static PyObject *
+posix_open(PyObject *self, PyObject *args, PyObject *kwargs)
+{
+    path_t path;
+    int flags;
     int mode = 0777;
+    int dir_fd = DEFAULT_DIR_FD;
     int fd;
-
+    PyObject *return_value = NULL;
+    static char *keywords[] = {"path", "flags", "mode", "dir_fd", NULL};
+
+    memset(&path, 0, sizeof(path));
+    if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O&i|i$O&:open", keywords,
+        path_converter, &path,
+        &flags, &mode,
+#ifdef HAVE_OPENAT
+        dir_fd_converter, &dir_fd
+#else
+        dir_fd_unavailable, &dir_fd
+#endif
+        ))
+        return NULL;
+
+    Py_BEGIN_ALLOW_THREADS
 #ifdef MS_WINDOWS
-    PyObject *po;
-    if (PyArg_ParseTuple(args, "Ui|i:open", &po, &flag, &mode)) {
-        wchar_t *wpath = PyUnicode_AsUnicode(po);
-        if (wpath == NULL)
-            return NULL;
-
-        Py_BEGIN_ALLOW_THREADS
-        fd = _wopen(wpath, flag, mode);
-        Py_END_ALLOW_THREADS
-        if (fd < 0)
-            return posix_error();
-        return PyLong_FromLong((long)fd);
-    }
-    /* Drop the argument parsing error as narrow strings
-       are also valid. */
-    PyErr_Clear();
-#endif
-
-    if (!PyArg_ParseTuple(args, "O&i|i:open",
-                          PyUnicode_FSConverter, &ofile,
-                          &flag, &mode))
-        return NULL;
+    if (path.wide)
+        fd = _wopen(path.wide, flags, mode);
+    else
+#endif
+#ifdef HAVE_OPENAT
+    if (dir_fd != DEFAULT_DIR_FD)
+        fd = openat(dir_fd, path.narrow, flags, mode);
+    else
+#endif
+        fd = open(path.narrow, flags, mode);
+    Py_END_ALLOW_THREADS
+
+    if (fd == -1) {
 #ifdef MS_WINDOWS
-    if (win32_warn_bytes_api()) {
-        Py_DECREF(ofile);
-        return NULL;
-    }
-#endif
-    file = PyBytes_AsString(ofile);
-    Py_BEGIN_ALLOW_THREADS
-    fd = open(file, flag, mode);
-    Py_END_ALLOW_THREADS
-    if (fd < 0)
-        return posix_error_with_allocated_filename(ofile);
-    Py_DECREF(ofile);
-    return PyLong_FromLong((long)fd);
-}
-
+        /* force use of posix_error here for exact backwards compatibility */
+        if (path.wide)
+            return_value = posix_error();
+        else
+#endif
+        return_value = path_error("open", &path);
+        goto exit;
+    }
+
+    return_value = PyLong_FromLong((long)fd);
+
+exit:
+    path_cleanup(&path);
+    return return_value;
+}
 
 PyDoc_STRVAR(posix_close__doc__,
 "close(fd)\n\n\
@@ -7407,7 +8080,8 @@
 
 PyDoc_STRVAR(posix_fstat__doc__,
 "fstat(fd) -> stat result\n\n\
-Like stat(), but for an open file descriptor.");
+Like stat(), but for an open file descriptor.\n\
+Equivalent to stat(fd=fd).");
 
 static PyObject *
 posix_fstat(PyObject *self, PyObject *args)
@@ -7589,63 +8263,118 @@
 
 #ifdef HAVE_MKFIFO
 PyDoc_STRVAR(posix_mkfifo__doc__,
-"mkfifo(filename [, mode=0666])\n\n\
-Create a FIFO (a POSIX named pipe).");
-
-static PyObject *
-posix_mkfifo(PyObject *self, PyObject *args)
-{
-    PyObject *opath;
-    char *filename;
+"mkfifo(path, mode=0o666, *, dir_fd=None)\n\n\
+Create a FIFO (a POSIX named pipe).\n\
+\n\
+If dir_fd is not None, it should be a file descriptor open to a directory,\n\
+  and path should be relative; path will then be relative to that directory.\n\
+dir_fd may not be implemented on your platform.\n\
+  If it is unavailable, using it will raise a NotImplementedError.");
+
+static PyObject *
+posix_mkfifo(PyObject *self, PyObject *args, PyObject *kwargs)
+{
+    path_t path;
     int mode = 0666;
-    int res;
-    if (!PyArg_ParseTuple(args, "O&|i:mkfifo", PyUnicode_FSConverter, &opath,
-                          &mode))
-        return NULL;
-    filename = PyBytes_AS_STRING(opath);
+    int dir_fd = DEFAULT_DIR_FD;
+    int result;
+    PyObject *return_value = NULL;
+    static char *keywords[] = {"path", "mode", "dir_fd", NULL};
+
+    memset(&path, 0, sizeof(path));
+    if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O&|i$O&:mkfifo", keywords,
+        path_converter, &path,
+        &mode,
+#ifdef HAVE_MKFIFOAT
+        dir_fd_converter, &dir_fd
+#else
+        dir_fd_unavailable, &dir_fd
+#endif
+        ))
+        return NULL;
+
     Py_BEGIN_ALLOW_THREADS
-    res = mkfifo(filename, mode);
+#ifdef HAVE_MKFIFOAT
+    if (dir_fd != DEFAULT_DIR_FD)
+        result = mkfifoat(dir_fd, path.narrow, mode);
+    else
+#endif
+        result = mkfifo(path.narrow, mode);
     Py_END_ALLOW_THREADS
-    Py_DECREF(opath);
-    if (res < 0)
-        return posix_error();
+
+    if (result < 0) {
+        return_value = posix_error();
+        goto exit;
+    }
+
+    return_value = Py_None;
     Py_INCREF(Py_None);
-    return Py_None;
-}
-#endif
-
+
+exit:
+    path_cleanup(&path);
+    return return_value;
+}
+#endif
 
 #if defined(HAVE_MKNOD) && defined(HAVE_MAKEDEV)
 PyDoc_STRVAR(posix_mknod__doc__,
-"mknod(filename [, mode=0600, device])\n\n\
+"mknod(filename, mode=0o600, device=0, *, dir_fd=None)\n\n\
 Create a filesystem node (file, device special file or named pipe)\n\
 named filename. mode specifies both the permissions to use and the\n\
 type of node to be created, being combined (bitwise OR) with one of\n\
 S_IFREG, S_IFCHR, S_IFBLK, and S_IFIFO. For S_IFCHR and S_IFBLK,\n\
 device defines the newly created device special file (probably using\n\
-os.makedev()), otherwise it is ignored.");
-
-
-static PyObject *
-posix_mknod(PyObject *self, PyObject *args)
-{
-    PyObject *opath;
-    char *filename;
-    int mode = 0600;
+os.makedev()), otherwise it is ignored.\n\
+\n\
+If dir_fd is not None, it should be a file descriptor open to a directory,\n\
+  and path should be relative; path will then be relative to that directory.\n\
+dir_fd may not be implemented on your platform.\n\
+  If it is unavailable, using it will raise a NotImplementedError.");
+
+
+static PyObject *
+posix_mknod(PyObject *self, PyObject *args, PyObject *kwargs)
+{
+    path_t path;
+    int mode = 0666;
     int device = 0;
-    int res;
-    if (!PyArg_ParseTuple(args, "O&|ii:mknod", PyUnicode_FSConverter, &opath,
-                          &mode, &device))
-        return NULL;
-    filename = PyBytes_AS_STRING(opath);
+    int dir_fd = DEFAULT_DIR_FD;
+    int result;
+    PyObject *return_value = NULL;
+    static char *keywords[] = {"path", "mode", "device", "dir_fd", NULL};
+
+    memset(&path, 0, sizeof(path));
+    if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O&|ii$O&:mknod", keywords,
+        path_converter, &path,
+        &mode, &device,
+#ifdef HAVE_MKNODAT
+        dir_fd_converter, &dir_fd
+#else
+        dir_fd_unavailable, &dir_fd
+#endif
+        ))
+        return NULL;
+
     Py_BEGIN_ALLOW_THREADS
-    res = mknod(filename, mode, device);
+#ifdef HAVE_MKNODAT
+    if (dir_fd != DEFAULT_DIR_FD)
+        result = mknodat(dir_fd, path.narrow, mode, device);
+    else
+#endif
+        result = mknod(path.narrow, mode, device);
     Py_END_ALLOW_THREADS
-    Py_DECREF(opath);
-    if (res < 0)
-        return posix_error();
+
+    if (result < 0) {
+        return_value = posix_error();
+        goto exit;
+    }
+
+    return_value = Py_None;
     Py_INCREF(Py_None);
-    return Py_None;
+    
+exit:
+    path_cleanup(&path);
+    return return_value;
 }
 #endif
 
@@ -8163,7 +8892,8 @@
 
 PyDoc_STRVAR(posix_fstatvfs__doc__,
 "fstatvfs(fd) -> statvfs result\n\n\
-Perform an fstatvfs system call on the given fd.");
+Perform an fstatvfs system call on the given fd.\n\
+Equivalent to statvfs(fd).");
 
 static PyObject *
 posix_fstatvfs(PyObject *self, PyObject *args)
@@ -8188,28 +8918,58 @@
 #include <sys/statvfs.h>
 
 PyDoc_STRVAR(posix_statvfs__doc__,
-"statvfs(path) -> statvfs result\n\n\
-Perform a statvfs system call on the given path.");
-
-static PyObject *
-posix_statvfs(PyObject *self, PyObject *args)
-{
-    PyObject *path;
-    int res;
+"statvfs(path)\n\n\
+Perform a statvfs system call on the given path.\n\
+\n\
+path may always be specified as a string.\n\
+On some platforms, path may also be specified as an open file descriptor.\n\
+  If this functionality is unavailable, using it raises an exception.");
+
+static PyObject *
+posix_statvfs(PyObject *self, PyObject *args, PyObject *kwargs)
+{
+    static char *keywords[] = {"path", NULL};
+    path_t path;
+    int result;
+    PyObject *return_value = NULL;
     struct statvfs st;
-    if (!PyArg_ParseTuple(args, "O&:statvfs", PyUnicode_FSConverter, &path))
-        return NULL;
+
+    memset(&path, 0, sizeof(path));
+#ifdef HAVE_FSTATVFS
+    path.allow_fd = 1;
+#endif
+    if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O&:statvfs", keywords,
+        path_converter, &path
+        ))
+        return NULL;
+
     Py_BEGIN_ALLOW_THREADS
-    res = statvfs(PyBytes_AS_STRING(path), &st);
+#ifdef HAVE_FSTATVFS
+    if (path.fd != -1) {
+#ifdef __APPLE__
+        /* handle weak-linking on Mac OS X 10.3 */
+        if (fstatvfs == NULL) {
+            fd_specified("statvfs", path.fd);
+            goto exit;
+        }
+#endif
+        result = fstatvfs(path.fd, &st);
+    }
+    else
+#endif
+        result = statvfs(path.narrow, &st);
     Py_END_ALLOW_THREADS
-    if (res != 0) {
-        posix_error_with_filename(PyBytes_AS_STRING(path));
-        Py_DECREF(path);
-        return NULL;
-    }
-    Py_DECREF(path);
-
-    return _pystatvfs_fromstructstatvfs(st);
+
+    if (result) {
+        return_value = path_posix_error("statvfs", &path);
+        goto exit;
+    }
+
+    return_value = _pystatvfs_fromstructstatvfs(st);
+
+exit:
+    path_cleanup(&path);
+    return return_value;
 }
 #endif /* HAVE_STATVFS */
 
@@ -9517,946 +10277,301 @@
 }
 #endif
 
-/* Posix *at family of functions:
-    faccessat, fchmodat, fchownat, fstatat, futimesat,
-    linkat, mkdirat, mknodat, openat, readlinkat, renameat, symlinkat,
-    unlinkat, utimensat, mkfifoat */
-
-#ifdef HAVE_FACCESSAT
-PyDoc_STRVAR(posix_faccessat__doc__,
-"faccessat(dirfd, path, mode, flags=0) -> True if granted, False otherwise\n\n\
-Like access() but if path is relative, it is taken as relative to dirfd.\n\
-flags is optional and can be constructed by ORing together zero or more\n\
-of these values: AT_SYMLINK_NOFOLLOW, AT_EACCESS.\n\
-If path is relative and dirfd is the special value AT_FDCWD, then path\n\
-is interpreted relative to the current working directory.");
-
-static PyObject *
-posix_faccessat(PyObject *self, PyObject *args)
-{
-    PyObject *opath;
-    char *path;
-    int mode;
-    int res;
-    int dirfd, flags = 0;
-    if (!PyArg_ParseTuple(args, "iO&i|i:faccessat",
-            &dirfd, PyUnicode_FSConverter, &opath, &mode, &flags))
-        return NULL;
-    path = PyBytes_AsString(opath);
-    Py_BEGIN_ALLOW_THREADS
-    res = faccessat(dirfd, path, mode, flags);
-    Py_END_ALLOW_THREADS
-    Py_DECREF(opath);
-    return PyBool_FromLong(res == 0);
-}
-#endif
-
-#ifdef HAVE_FCHMODAT
-PyDoc_STRVAR(posix_fchmodat__doc__,
-"fchmodat(dirfd, path, mode, flags=0)\n\n\
-Like chmod() but if path is relative, it is taken as relative to dirfd.\n\
-flags is optional and may be 0 or AT_SYMLINK_NOFOLLOW.\n\
-If path is relative and dirfd is the special value AT_FDCWD, then path\n\
-is interpreted relative to the current working directory.");
-
-static PyObject *
-posix_fchmodat(PyObject *self, PyObject *args)
-{
-    int dirfd, mode, res;
+#ifdef USE_XATTRS
+
+PyDoc_STRVAR(posix_getxattr__doc__,
+"getxattr(path, attribute, *, follow_symlinks=True) -> value\n\n\
+Return the value of extended attribute attribute on path.\n\
+\n\
+path may be either a string or an open file descriptor.\n\
+If follow_symlinks is False, and the last element of the path is a symbolic\n\
+  link, getxattr will examine the symbolic link itself instead of the file\n\
+  the link points to.");
+
+static PyObject *
+posix_getxattr(PyObject *self, PyObject *args, PyObject *kwargs)
+{
+    path_t path;
+    path_t attribute;
+    int follow_symlinks = 1;
+    PyObject *buffer = NULL;
+    int i;
+    static char *keywords[] = {"path", "attribute", "follow_symlinks", NULL};
+
+    memset(&path, 0, sizeof(path));
+    memset(&attribute, 0, sizeof(attribute));
+    path.allow_fd = 1;
+    if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O&O&|$p:getxattr", keywords,
+        path_converter, &path,
+        path_converter, &attribute,
+        &follow_symlinks))
+        return NULL;
+
+    if (fd_and_follow_symlinks_invalid("getxattr", path.fd, follow_symlinks))
+        goto exit;
+
+    for (i = 0; ; i++) {
+        void *ptr;
+        ssize_t result;
+        static Py_ssize_t buffer_sizes[] = {128, XATTR_SIZE_MAX, 0};
+        Py_ssize_t buffer_size = buffer_sizes[i];
+        if (!buffer_size) {
+            path_error("getxattr", &path);
+            goto exit;
+        }
+        buffer = PyBytes_FromStringAndSize(NULL, buffer_size);
+        if (!buffer)
+            goto exit;
+        ptr = PyBytes_AS_STRING(buffer);
+
+        Py_BEGIN_ALLOW_THREADS;
+        if (path.fd >= 0)
+            result = fgetxattr(path.fd, attribute.narrow, ptr, buffer_size);
+        else if (follow_symlinks)
+            result = getxattr(path.narrow, attribute.narrow, ptr, buffer_size);
+        else
+            result = lgetxattr(path.narrow, attribute.narrow, ptr, buffer_size);
+        Py_END_ALLOW_THREADS;
+
+        if (result < 0) {
+            Py_DECREF(buffer);
+            buffer = NULL;
+            if (errno == ERANGE)
+                continue;
+            path_error("getxattr", &path);
+            goto exit;
+        }
+
+        if (result != buffer_size) {
+            /* Can only shrink. */
+            _PyBytes_Resize(&buffer, result);
+        }
+        break;
+    }
+
+exit:
+    path_cleanup(&path);
+    path_cleanup(&attribute);
+    return buffer;
+}
+
+PyDoc_STRVAR(posix_setxattr__doc__,
+"setxattr(path, attribute, value, flags=0, *, follow_symlinks=True)\n\n\
+Set extended attribute attribute on path to value.\n\
+path may be either a string or an open file descriptor.\n\
+If follow_symlinks is False, and the last element of the path is a symbolic\n\
+  link, setxattr will modify the symbolic link itself instead of the file\n\
+  the link points to.");
+
+static PyObject *
+posix_setxattr(PyObject *self, PyObject *args, PyObject *kwargs)
+{
+    path_t path;
+    path_t attribute;
+    Py_buffer value;
     int flags = 0;
-    PyObject *opath;
-    char *path;
-
-    if (!PyArg_ParseTuple(args, "iO&i|i:fchmodat",
-            &dirfd, PyUnicode_FSConverter, &opath, &mode, &flags))
-        return NULL;
-
-    path = PyBytes_AsString(opath);
-
-    Py_BEGIN_ALLOW_THREADS
-    res = fchmodat(dirfd, path, mode, flags);
-    Py_END_ALLOW_THREADS
-    Py_DECREF(opath);
-    if (res < 0)
-        return posix_error();
-    Py_RETURN_NONE;
-}
-#endif /* HAVE_FCHMODAT */
-
-#ifdef HAVE_FCHOWNAT
-PyDoc_STRVAR(posix_fchownat__doc__,
-"fchownat(dirfd, path, uid, gid, flags=0)\n\n\
-Like chown() but if path is relative, it is taken as relative to dirfd.\n\
-flags is optional and may be 0 or AT_SYMLINK_NOFOLLOW.\n\
-If path is relative and dirfd is the special value AT_FDCWD, then path\n\
-is interpreted relative to the current working directory.");
-
-static PyObject *
-posix_fchownat(PyObject *self, PyObject *args)
-{
-    PyObject *opath;
-    int dirfd, res;
-    long uid, gid;
-    int flags = 0;
-    char *path;
-
-    if (!PyArg_ParseTuple(args, "iO&ll|i:fchownat",
-            &dirfd, PyUnicode_FSConverter, &opath, &uid, &gid, &flags))
-        return NULL;
-
-    path = PyBytes_AsString(opath);
-
-    Py_BEGIN_ALLOW_THREADS
-    res = fchownat(dirfd, path, (uid_t) uid, (gid_t) gid, flags);
-    Py_END_ALLOW_THREADS
-    Py_DECREF(opath);
-    if (res < 0)
-        return posix_error();
-    Py_RETURN_NONE;
-}
-#endif /* HAVE_FCHOWNAT */
-
-#ifdef HAVE_FSTATAT
-PyDoc_STRVAR(posix_fstatat__doc__,
-"fstatat(dirfd, path, flags=0) -> stat result\n\n\
-Like stat() but if path is relative, it is taken as relative to dirfd.\n\
-flags is optional and may be 0 or AT_SYMLINK_NOFOLLOW.\n\
-If path is relative and dirfd is the special value AT_FDCWD, then path\n\
-is interpreted relative to the current working directory.");
-
-static PyObject *
-posix_fstatat(PyObject *self, PyObject *args)
-{
-    PyObject *opath;
-    char *path;
-    STRUCT_STAT st;
-    int dirfd, res, flags = 0;
-
-    if (!PyArg_ParseTuple(args, "iO&|i:fstatat",
-            &dirfd, PyUnicode_FSConverter, &opath, &flags))
-        return NULL;
-    path = PyBytes_AsString(opath);
-
-    Py_BEGIN_ALLOW_THREADS
-    res = fstatat(dirfd, path, &st, flags);
-    Py_END_ALLOW_THREADS
-    Py_DECREF(opath);
-    if (res != 0)
-        return posix_error();
-
-    return _pystat_fromstructstat(&st);
-}
-#endif
-
-#ifdef HAVE_FUTIMESAT
-PyDoc_STRVAR(posix_futimesat__doc__,
-"futimesat(dirfd, path[, (atime, mtime)])\n\
-Like utime() but if path is relative, it is taken as relative to dirfd.\n\
-If path is relative and dirfd is the special value AT_FDCWD, then path\n\
-is interpreted relative to the current working directory.");
-
-static PyObject *
-posix_futimesat(PyObject *self, PyObject *args)
-{
-    PyObject *opath;
-    char *path;
-    int res, dirfd;
-    PyObject* arg = Py_None;
-    time_t atime, mtime;
-    long ansec, mnsec;
-
-    if (!PyArg_ParseTuple(args, "iO&|O:futimesat",
-            &dirfd, PyUnicode_FSConverter, &opath, &arg))
-        return NULL;
-    path = PyBytes_AsString(opath);
-    if (arg == Py_None) {
-        /* optional time values not given */
-        Py_BEGIN_ALLOW_THREADS
-        res = futimesat(dirfd, path, NULL);
-        Py_END_ALLOW_THREADS
-    }
-    else if (!PyTuple_Check(arg) || PyTuple_Size(arg) != 2) {
-        PyErr_SetString(PyExc_TypeError,
-                "futimesat() arg 3 must be a tuple (atime, mtime)");
-        Py_DECREF(opath);
-        return NULL;
-    }
-    else {
-        if (_PyTime_ObjectToTimespec(PyTuple_GET_ITEM(arg, 0),
-                                     &atime, &ansec) == -1) {
-            Py_DECREF(opath);
-            return NULL;
+    int follow_symlinks = 1;
+    int result;
+    PyObject *return_value = NULL;
+    static char *keywords[] = {"path", "attribute", "value",
+                               "flags", "follow_symlinks", NULL};
+
+    memset(&path, 0, sizeof(path));
+    path.allow_fd = 1;
+    memset(&attribute, 0, sizeof(attribute));
+    memset(&value, 0, sizeof(value));
+    if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O&O&y*|i$p:setxattr",
+        keywords,
+        path_converter, &path,
+        path_converter, &attribute,
+        &value, &flags,
+        &follow_symlinks))
+        return NULL;
+
+    if (fd_and_follow_symlinks_invalid("setxattr", path.fd, follow_symlinks))
+        goto exit;
+
+    Py_BEGIN_ALLOW_THREADS;
+    if (path.fd > -1)
+        result = fsetxattr(path.fd, attribute.narrow,
+                           value.buf, value.len, flags);
+    else if (follow_symlinks)
+        result = setxattr(path.narrow, attribute.narrow,
+                           value.buf, value.len, flags);
+    else
+        result = lsetxattr(path.narrow, attribute.narrow,
+                           value.buf, value.len, flags);
+    Py_END_ALLOW_THREADS;
+
+    if (result) {
+        return_value = path_error("setxattr", &path);
+        goto exit;
+    }
+
+    return_value = Py_None;
+    Py_INCREF(return_value);
+
+exit:
+    path_cleanup(&path);
+    path_cleanup(&attribute);
+    PyBuffer_Release(&value);
+
+    return return_value;
+}
+
+PyDoc_STRVAR(posix_removexattr__doc__,
+"removexattr(path, attribute, *, follow_symlinks=True)\n\n\
+Remove extended attribute attribute on path.\n\
+path may be either a string or an open file descriptor.\n\
+If follow_symlinks is False, and the last element of the path is a symbolic\n\
+  link, removexattr will modify the symbolic link itself instead of the file\n\
+  the link points to.");
+
+static PyObject *
+posix_removexattr(PyObject *self, PyObject *args, PyObject *kwargs)
+{
+    path_t path;
+    path_t attribute;
+    int follow_symlinks = 1;
+    int result;
+    PyObject *return_value = NULL;
+    static char *keywords[] = {"path", "attribute", "follow_symlinks", NULL};
+
+    memset(&path, 0, sizeof(path));
+    memset(&attribute, 0, sizeof(attribute));
+    path.allow_fd = 1;
+    if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O&O&|$p:removexattr",
+                                     keywords,
+                                     path_converter, &path,
+                                     path_converter, &attribute,
+                                     &follow_symlinks))
+        return NULL;
+
+    if (fd_and_follow_symlinks_invalid("removexattr", path.fd, follow_symlinks))
+        goto exit;
+
+    Py_BEGIN_ALLOW_THREADS;
+    if (path.fd > -1)
+        result = fremovexattr(path.fd, attribute.narrow);
+    else if (follow_symlinks)
+        result = removexattr(path.narrow, attribute.narrow);
+    else
+        result = lremovexattr(path.narrow, attribute.narrow);
+    Py_END_ALLOW_THREADS;
+
+    if (result) {
+        return_value = path_error("removexattr", &path);
+        goto exit;
+    }
+
+    return_value = Py_None;
+    Py_INCREF(return_value);
+
+exit:
+    path_cleanup(&path);
+    path_cleanup(&attribute);
+
+    return return_value;
+}
+
+PyDoc_STRVAR(posix_listxattr__doc__,
+"listxattr(path='.', *, follow_symlinks=True)\n\n\
+Return a list of extended attributes on path.\n\
+\n\
+path may be either None, a string, or an open file descriptor.\n\
+if path is None, listxattr will examine the current directory.\n\
+If follow_symlinks is False, and the last element of the path is a symbolic\n\
+  link, listxattr will examine the symbolic link itself instead of the file\n\
+  the link points to.");
+
+static PyObject *
+posix_listxattr(PyObject *self, PyObject *args, PyObject *kwargs)
+{
+    path_t path;
+    int follow_symlinks = 1;
+    Py_ssize_t i;
+    PyObject *result = NULL;
+    char *buffer = NULL;
+    char *name;
+    static char *keywords[] = {"path", "follow_symlinks", NULL};
+
+    memset(&path, 0, sizeof(path));
+    path.allow_fd = 1;
+    path.fd = -1;
+    if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|O&$p:listxattr", keywords,
+        path_converter, &path,
+        &follow_symlinks))
+        return NULL;
+
+    if (fd_and_follow_symlinks_invalid("listxattr", path.fd, follow_symlinks))
+        goto exit;
+
+    name = path.narrow ? path.narrow : ".";
+    for (i = 0; ; i++) {
+        char *start, *trace, *end;
+        ssize_t length;
+        static Py_ssize_t buffer_sizes[] = { 256, XATTR_LIST_MAX, 0 };
+        Py_ssize_t buffer_size = buffer_sizes[i];
+        if (!buffer_size) {
+            // ERANGE
+            path_error("listxattr", &path);
+            break;
         }
-        if (_PyTime_ObjectToTimespec(PyTuple_GET_ITEM(arg, 1),
-                                     &mtime, &mnsec) == -1) {
-            Py_DECREF(opath);
-            return NULL;
+        buffer = PyMem_MALLOC(buffer_size);
+        if (!buffer) {
+            PyErr_NoMemory();
+            break;
         }
 
-        Py_BEGIN_ALLOW_THREADS
-        {
-#ifdef HAVE_UTIMENSAT
-        struct timespec buf[2];
-        buf[0].tv_sec = atime;
-        buf[0].tv_nsec = ansec;
-        buf[1].tv_sec = mtime;
-        buf[1].tv_nsec = mnsec;
-        res = utimensat(dirfd, path, buf, 0);
-#else
-        struct timeval buf[2];
-        buf[0].tv_sec = atime;
-        buf[0].tv_usec = ansec / 1000;
-        buf[1].tv_sec = mtime;
-        buf[1].tv_usec = mnsec / 1000;
-        res = futimesat(dirfd, path, buf);
-#endif
+        Py_BEGIN_ALLOW_THREADS;
+        if (path.fd > -1)
+            length = flistxattr(path.fd, buffer, buffer_size);
+        else if (follow_symlinks)
+            length = listxattr(name, buffer, buffer_size);
+        else
+            length = llistxattr(name, buffer, buffer_size);
+        Py_END_ALLOW_THREADS;
+
+        if (length < 0) {
+            if (errno == ERANGE)
+                continue;
+            path_error("listxattr", &path);
+            break;
         }
-        Py_END_ALLOW_THREADS
-    }
-    Py_DECREF(opath);
-    if (res < 0) {
-        return posix_error();
-    }
-    Py_RETURN_NONE;
-}
-#endif
-
-#ifdef HAVE_LINKAT
-PyDoc_STRVAR(posix_linkat__doc__,
-"linkat(srcfd, srcpath, dstfd, dstpath, flags=0)\n\n\
-Like link() but if srcpath is relative, it is taken as relative to srcfd\n\
-and if dstpath is relative, it is taken as relative to dstfd.\n\
-flags is optional and may be 0 or AT_SYMLINK_FOLLOW.\n\
-If srcpath is relative and srcfd is the special value AT_FDCWD, then\n\
-srcpath is interpreted relative to the current working directory. This\n\
-also applies for dstpath.");
-
-static PyObject *
-posix_linkat(PyObject *self, PyObject *args)
-{
-    PyObject *osrc, *odst;
-    char *src, *dst;
-    int res, srcfd, dstfd;
-    int flags = 0;
-
-    if (!PyArg_ParseTuple(args, "iO&iO&|i:linkat",
-            &srcfd, PyUnicode_FSConverter, &osrc, &dstfd, PyUnicode_FSConverter, &odst, &flags))
-        return NULL;
-    src = PyBytes_AsString(osrc);
-    dst = PyBytes_AsString(odst);
-    Py_BEGIN_ALLOW_THREADS
-    res = linkat(srcfd, src, dstfd, dst, flags);
-    Py_END_ALLOW_THREADS
-    Py_DECREF(osrc);
-    Py_DECREF(odst);
-    if (res < 0)
-        return posix_error();
-    Py_RETURN_NONE;
-}
-#endif /* HAVE_LINKAT */
-
-#ifdef HAVE_MKDIRAT
-PyDoc_STRVAR(posix_mkdirat__doc__,
-"mkdirat(dirfd, path, mode=0o777)\n\n\
-Like mkdir() but if path is relative, it is taken as relative to dirfd.\n\
-If path is relative and dirfd is the special value AT_FDCWD, then path\n\
-is interpreted relative to the current working directory.");
-
-static PyObject *
-posix_mkdirat(PyObject *self, PyObject *args)
-{
-    int res, dirfd;
-    PyObject *opath;
-    char *path;
-    int mode = 0777;
-
-    if (!PyArg_ParseTuple(args, "iO&|i:mkdirat",
-            &dirfd, PyUnicode_FSConverter, &opath, &mode))
-        return NULL;
-    path = PyBytes_AsString(opath);
-    Py_BEGIN_ALLOW_THREADS
-    res = mkdirat(dirfd, path, mode);
-    Py_END_ALLOW_THREADS
-    Py_DECREF(opath);
-    if (res < 0)
-        return posix_error();
-    Py_RETURN_NONE;
-}
-#endif
-
-#if defined(HAVE_MKNODAT) && defined(HAVE_MAKEDEV)
-PyDoc_STRVAR(posix_mknodat__doc__,
-"mknodat(dirfd, path, mode=0o600, device=0)\n\n\
-Like mknod() but if path is relative, it is taken as relative to dirfd.\n\
-If path is relative and dirfd is the special value AT_FDCWD, then path\n\
-is interpreted relative to the current working directory.");
-
-static PyObject *
-posix_mknodat(PyObject *self, PyObject *args)
-{
-    PyObject *opath;
-    char *filename;
-    int mode = 0600;
-    int device = 0;
-    int res, dirfd;
-    if (!PyArg_ParseTuple(args, "iO&|ii:mknodat", &dirfd,
-            PyUnicode_FSConverter, &opath, &mode, &device))
-        return NULL;
-    filename = PyBytes_AS_STRING(opath);
-    Py_BEGIN_ALLOW_THREADS
-    res = mknodat(dirfd, filename, mode, device);
-    Py_END_ALLOW_THREADS
-    Py_DECREF(opath);
-    if (res < 0)
-        return posix_error();
-    Py_RETURN_NONE;
-}
-#endif
-
-#ifdef HAVE_OPENAT
-PyDoc_STRVAR(posix_openat__doc__,
-"openat(dirfd, path, flag, mode=0o777) -> fd\n\n\
-Like open() but if path is relative, it is taken as relative to dirfd.\n\
-If path is relative and dirfd is the special value AT_FDCWD, then path\n\
-is interpreted relative to the current working directory.");
-
-static PyObject *
-posix_openat(PyObject *self, PyObject *args)
-{
-    PyObject *ofile;
-    char *file;
-    int flag, dirfd, fd;
-    int mode = 0777;
-
-    if (!PyArg_ParseTuple(args, "iO&i|i:openat",
-            &dirfd, PyUnicode_FSConverter, &ofile,
-            &flag, &mode))
-        return NULL;
-    file = PyBytes_AsString(ofile);
-    Py_BEGIN_ALLOW_THREADS
-    fd = openat(dirfd, file, flag, mode);
-    Py_END_ALLOW_THREADS
-    Py_DECREF(ofile);
-    if (fd < 0)
-        return posix_error();
-    return PyLong_FromLong((long)fd);
-}
-#endif
-
-#ifdef HAVE_READLINKAT
-PyDoc_STRVAR(posix_readlinkat__doc__,
-"readlinkat(dirfd, path) -> path\n\n\
-Like readlink() but if path is relative, it is taken as relative to dirfd.\n\
-If path is relative and dirfd is the special value AT_FDCWD, then path\n\
-is interpreted relative to the current working directory.");
-
-static PyObject *
-posix_readlinkat(PyObject *self, PyObject *args)
-{
-    PyObject *v, *opath;
-    char buf[MAXPATHLEN];
-    char *path;
-    int n, dirfd;
-    int arg_is_unicode = 0;
-
-    if (!PyArg_ParseTuple(args, "iO&:readlinkat",
-            &dirfd, PyUnicode_FSConverter, &opath))
-        return NULL;
-    path = PyBytes_AsString(opath);
-    v = PySequence_GetItem(args, 1);
-    if (v == NULL) {
-        Py_DECREF(opath);
-        return NULL;
-    }
-
-    if (PyUnicode_Check(v)) {
-        arg_is_unicode = 1;
-    }
-    Py_DECREF(v);
-
-    Py_BEGIN_ALLOW_THREADS
-    n = readlinkat(dirfd, path, buf, (int) sizeof buf);
-    Py_END_ALLOW_THREADS
-    Py_DECREF(opath);
-    if (n < 0)
-        return posix_error();
-
-    if (arg_is_unicode)
-        return PyUnicode_DecodeFSDefaultAndSize(buf, n);
-    else
-        return PyBytes_FromStringAndSize(buf, n);
-}
-#endif /* HAVE_READLINKAT */
-
-#ifdef HAVE_RENAMEAT
-PyDoc_STRVAR(posix_renameat__doc__,
-"renameat(olddirfd, oldpath, newdirfd, newpath)\n\n\
-Like rename() but if oldpath is relative, it is taken as relative to\n\
-olddirfd and if newpath is relative, it is taken as relative to newdirfd.\n\
-If oldpath is relative and olddirfd is the special value AT_FDCWD, then\n\
-oldpath is interpreted relative to the current working directory. This\n\
-also applies for newpath.");
-
-static PyObject *
-posix_renameat(PyObject *self, PyObject *args)
-{
-    int res;
-    PyObject *opathold, *opathnew;
-    char *opath, *npath;
-    int oldfd, newfd;
-
-    if (!PyArg_ParseTuple(args, "iO&iO&:renameat",
-            &oldfd, PyUnicode_FSConverter, &opathold, &newfd, PyUnicode_FSConverter, &opathnew))
-        return NULL;
-    opath = PyBytes_AsString(opathold);
-    npath = PyBytes_AsString(opathnew);
-    Py_BEGIN_ALLOW_THREADS
-    res = renameat(oldfd, opath, newfd, npath);
-    Py_END_ALLOW_THREADS
-    Py_DECREF(opathold);
-    Py_DECREF(opathnew);
-    if (res < 0)
-        return posix_error();
-    Py_RETURN_NONE;
-}
-#endif
-
-#if HAVE_SYMLINKAT
-PyDoc_STRVAR(posix_symlinkat__doc__,
-"symlinkat(src, dstfd, dst)\n\n\
-Like symlink() but if dst is relative, it is taken as relative to dstfd.\n\
-If dst is relative and dstfd is the special value AT_FDCWD, then dst\n\
-is interpreted relative to the current working directory.");
-
-static PyObject *
-posix_symlinkat(PyObject *self, PyObject *args)
-{
-    int res, dstfd;
-    PyObject *osrc, *odst;
-    char *src, *dst;
-
-    if (!PyArg_ParseTuple(args, "O&iO&:symlinkat",
-            PyUnicode_FSConverter, &osrc, &dstfd, PyUnicode_FSConverter, &odst))
-        return NULL;
-    src = PyBytes_AsString(osrc);
-    dst = PyBytes_AsString(odst);
-    Py_BEGIN_ALLOW_THREADS
-    res = symlinkat(src, dstfd, dst);
-    Py_END_ALLOW_THREADS
-    Py_DECREF(osrc);
-    Py_DECREF(odst);
-    if (res < 0)
-        return posix_error();
-    Py_RETURN_NONE;
-}
-#endif /* HAVE_SYMLINKAT */
-
-#ifdef HAVE_UNLINKAT
-PyDoc_STRVAR(posix_unlinkat__doc__,
-"unlinkat(dirfd, path, flags=0)\n\n\
-Like unlink() but if path is relative, it is taken as relative to dirfd.\n\
-flags is optional and may be 0 or AT_REMOVEDIR. If AT_REMOVEDIR is\n\
-specified, unlinkat() behaves like rmdir().\n\
-If path is relative and dirfd is the special value AT_FDCWD, then path\n\
-is interpreted relative to the current working directory.");
-
-static PyObject *
-posix_unlinkat(PyObject *self, PyObject *args)
-{
-    int dirfd, res, flags = 0;
-    PyObject *opath;
-    char *path;
-
-    if (!PyArg_ParseTuple(args, "iO&|i:unlinkat",
-            &dirfd, PyUnicode_FSConverter, &opath, &flags))
-        return NULL;
-    path = PyBytes_AsString(opath);
-    Py_BEGIN_ALLOW_THREADS
-    res = unlinkat(dirfd, path, flags);
-    Py_END_ALLOW_THREADS
-    Py_DECREF(opath);
-    if (res < 0)
-        return posix_error();
-    Py_RETURN_NONE;
-}
-#endif
-
-#ifdef HAVE_UTIMENSAT
-PyDoc_STRVAR(posix_utimensat__doc__,
-"utimensat(dirfd, path[, atime=(atime_sec, atime_nsec),\n\
-    mtime=(mtime_sec, mtime_nsec), flags=0])\n\
-utimensat(dirfd, path, None, None, flags)\n\n\
-Updates the timestamps of a file with nanosecond precision. If path is\n\
-relative, it is taken as relative to dirfd.\n\
-If atime and mtime are both None, which is the default, set atime and\n\
-mtime to the current time.\n\
-flags is optional and may be 0 or AT_SYMLINK_NOFOLLOW.\n\
-If path is relative and dirfd is the special value AT_FDCWD, then path\n\
-is interpreted relative to the current working directory.\n\
-If *_nsec is specified as UTIME_NOW, the timestamp is updated to the\n\
-current time.\n\
-If *_nsec is specified as UTIME_OMIT, the timestamp is not updated.");
-
-static PyObject *
-posix_utimensat(PyObject *self, PyObject *args, PyObject *kwargs)
-{
-    PyObject *opath;
-    char *path;
-    int res, dirfd, flags = 0;
-    PyObject *atime = Py_None;
-    PyObject *mtime = Py_None;
-
-    static char *kwlist[] = {"dirfd", "path", "atime", "mtime", "flags", NULL};
-
-    struct timespec buf[2];
-
-    if (!PyArg_ParseTupleAndKeywords(args, kwargs, "iO&|OOi:utimensat", kwlist,
-            &dirfd, PyUnicode_FSConverter, &opath, &atime, &mtime, &flags))
-        return NULL;
-    path = PyBytes_AsString(opath);
-    if (atime == Py_None && mtime == Py_None) {
-        /* optional time values not given */
-        Py_BEGIN_ALLOW_THREADS
-        res = utimensat(dirfd, path, NULL, flags);
-        Py_END_ALLOW_THREADS
-    }
-    else if (!PyTuple_Check(atime) || PyTuple_Size(atime) != 2) {
-        PyErr_SetString(PyExc_TypeError,
-            "utimensat() arg 3 must be a tuple (atime_sec, atime_nsec)");
-        Py_DECREF(opath);
-        return NULL;
-    }
-    else if (!PyTuple_Check(mtime) || PyTuple_Size(mtime) != 2) {
-        PyErr_SetString(PyExc_TypeError,
-            "utimensat() arg 4 must be a tuple (mtime_sec, mtime_nsec)");
-        Py_DECREF(opath);
-        return NULL;
-    }
-    else {
-        if (!PyArg_ParseTuple(atime, "ll:utimensat",
-                &(buf[0].tv_sec), &(buf[0].tv_nsec))) {
-            Py_DECREF(opath);
-            return NULL;
+
+        result = PyList_New(0);
+        if (!result) {
+            goto exit;
         }
-        if (!PyArg_ParseTuple(mtime, "ll:utimensat",
-                &(buf[1].tv_sec), &(buf[1].tv_nsec))) {
-            Py_DECREF(opath);
-            return NULL;
+
+        end = buffer + length;
+        for (trace = start = buffer; trace != end; trace++) {
+            if (!*trace) {
+                int error;
+                PyObject *attribute = PyUnicode_DecodeFSDefaultAndSize(start,
+                                                                 trace - start);
+                if (!attribute) {
+                    Py_DECREF(result);
+                    result = NULL;
+                    goto exit;
+                }
+                error = PyList_Append(result, attribute);
+                Py_DECREF(attribute);
+                if (error) {
+                    Py_DECREF(result);
+                    result = NULL;
+                    goto exit;
+                }
+                start = trace + 1;
+            }
         }
-        Py_BEGIN_ALLOW_THREADS
-        res = utimensat(dirfd, path, buf, flags);
-        Py_END_ALLOW_THREADS
-    }
-    Py_DECREF(opath);
-    if (res < 0) {
-        return posix_error();
-    }
-    Py_RETURN_NONE;
-}
-#endif
-
-#ifdef HAVE_MKFIFOAT
-PyDoc_STRVAR(posix_mkfifoat__doc__,
-"mkfifoat(dirfd, path, mode=0o666)\n\n\
-Like mkfifo() but if path is relative, it is taken as relative to dirfd.\n\
-If path is relative and dirfd is the special value AT_FDCWD, then path\n\
-is interpreted relative to the current working directory.");
-
-static PyObject *
-posix_mkfifoat(PyObject *self, PyObject *args)
-{
-    PyObject *opath;
-    char *filename;
-    int mode = 0666;
-    int res, dirfd;
-    if (!PyArg_ParseTuple(args, "iO&|i:mkfifoat",
-            &dirfd, PyUnicode_FSConverter, &opath, &mode))
-        return NULL;
-    filename = PyBytes_AS_STRING(opath);
-    Py_BEGIN_ALLOW_THREADS
-    res = mkfifoat(dirfd, filename, mode);
-    Py_END_ALLOW_THREADS
-    Py_DECREF(opath);
-    if (res < 0)
-        return posix_error();
-    Py_RETURN_NONE;
-}
-#endif
-
-#ifdef USE_XATTRS
-
-static int
-try_getxattr(const char *path, const char *name,
-             ssize_t (*get)(const char *, const char *, void *, size_t),
-             Py_ssize_t buf_size, PyObject **res)
-{
-    PyObject *value;
-    Py_ssize_t len;
-
-    assert(buf_size <= XATTR_SIZE_MAX);
-    value = PyBytes_FromStringAndSize(NULL, buf_size);
-    if (!value)
-        return 0;
-    Py_BEGIN_ALLOW_THREADS;
-    len = get(path, name, PyBytes_AS_STRING(value), buf_size);
-    Py_END_ALLOW_THREADS;
-    if (len < 0) {
-        Py_DECREF(value);
-        if (errno == ERANGE) {
-            value = NULL;
-        }
-        else {
-            posix_error();
-            return 0;
-        }
-    }
-    else if (len != buf_size) {
-        /* Can only shrink. */
-        _PyBytes_Resize(&value, len);
-    }
-    *res = value;
-    return 1;
-}
-
-static PyObject *
-getxattr_common(const char *path, PyObject *name_obj,
-                ssize_t (*get)(const char *, const char *, void *, size_t))
-{
-    PyObject *value;
-    const char *name = PyBytes_AS_STRING(name_obj);
-
-    /* Try a small value first. */
-    if (!try_getxattr(path, name, get, 128, &value))
-        return NULL;
-    if (value)
-        return value;
-    /* Now the maximum possible one. */
-    if (!try_getxattr(path, name, get, XATTR_SIZE_MAX, &value))
-        return NULL;
-    assert(value);
-    return value;
-}
-
-PyDoc_STRVAR(posix_getxattr__doc__,
-"getxattr(path, attr) -> value\n\n\
-Return the value of extended attribute *name* on *path*.");
-
-static PyObject *
-posix_getxattr(PyObject *self, PyObject *args)
-{
-    PyObject *path, *res, *name;
-
-    if (!PyArg_ParseTuple(args, "O&O&:getxattr", PyUnicode_FSConverter, &path,
-                          PyUnicode_FSConverter, &name))
-        return NULL;
-    res = getxattr_common(PyBytes_AS_STRING(path), name, getxattr);
-    Py_DECREF(path);
-    Py_DECREF(name);
-    return res;
-}
-
-PyDoc_STRVAR(posix_lgetxattr__doc__,
-"lgetxattr(path, attr) -> value\n\n\
-Like getxattr but don't follow symlinks.");
-
-static PyObject *
-posix_lgetxattr(PyObject *self, PyObject *args)
-{
-    PyObject *path, *res, *name;
-
-    if (!PyArg_ParseTuple(args, "O&O&:lgetxattr", PyUnicode_FSConverter, &path,
-                          PyUnicode_FSConverter, &name))
-        return NULL;
-    res = getxattr_common(PyBytes_AS_STRING(path), name, lgetxattr);
-    Py_DECREF(path);
-    Py_DECREF(name);
-    return res;
-}
-
-static ssize_t
-wrap_fgetxattr(const char *path, const char *name, void *value, size_t size)
-{
-    /* Hack to share code. */
-    return fgetxattr((int)(Py_uintptr_t)path, name, value, size);
-}
-
-PyDoc_STRVAR(posix_fgetxattr__doc__,
-"fgetxattr(fd, attr) -> value\n\n\
-Like getxattr but operate on a fd instead of a path.");
-
-static PyObject *
-posix_fgetxattr(PyObject *self, PyObject *args)
-{
-    PyObject *res, *name;
-    int fd;
-
-    if (!PyArg_ParseTuple(args, "iO&:fgetxattr", &fd, PyUnicode_FSConverter, &name))
-        return NULL;
-    res = getxattr_common((const char *)(Py_uintptr_t)fd, name, wrap_fgetxattr);
-    Py_DECREF(name);
-    return res;
-}
-
-PyDoc_STRVAR(posix_setxattr__doc__,
-"setxattr(path, attr, value, flags=0)\n\n\
-Set extended attribute *attr* on *path* to *value*.");
-
-static PyObject *
-posix_setxattr(PyObject *self, PyObject *args)
-{
-    PyObject *path, *name;
-    Py_buffer data;
-    int flags = 0, err;
-
-    if (!PyArg_ParseTuple(args, "O&O&y*|i:setxattr", PyUnicode_FSConverter,
-                          &path, PyUnicode_FSConverter, &name, &data, &flags))
-        return NULL;
-    Py_BEGIN_ALLOW_THREADS;
-    err = setxattr(PyBytes_AS_STRING(path), PyBytes_AS_STRING(name),
-                   data.buf, data.len, flags);
-    Py_END_ALLOW_THREADS;
-    Py_DECREF(path);
-    Py_DECREF(name);
-    PyBuffer_Release(&data);
-    if (err)
-        return posix_error();
-    Py_RETURN_NONE;
-}
-
-PyDoc_STRVAR(posix_lsetxattr__doc__,
-"lsetxattr(path, attr, value, flags=0)\n\n\
-Like setxattr but don't follow symlinks.");
-
-static PyObject *
-posix_lsetxattr(PyObject *self, PyObject *args)
-{
-    PyObject *path, *name;
-    Py_buffer data;
-    int flags = 0, err;
-
-    if (!PyArg_ParseTuple(args, "O&O&y*|i:lsetxattr", PyUnicode_FSConverter,
-                          &path, PyUnicode_FSConverter, &name, &data, &flags))
-        return NULL;
-    Py_BEGIN_ALLOW_THREADS;
-    err = lsetxattr(PyBytes_AS_STRING(path), PyBytes_AS_STRING(name),
-                    data.buf, data.len, flags);
-    Py_END_ALLOW_THREADS;
-    Py_DECREF(path);
-    Py_DECREF(name);
-    PyBuffer_Release(&data);
-    if (err)
-        return posix_error();
-    Py_RETURN_NONE;
-}
-
-PyDoc_STRVAR(posix_fsetxattr__doc__,
-"fsetxattr(fd, attr, value, flags=0)\n\n\
-Like setxattr but operates on *fd* instead of a path.");
-
-static PyObject *
-posix_fsetxattr(PyObject *self, PyObject *args)
-{
-    Py_buffer data;
-    const char *name;
-    int fd, flags = 0, err;
-
-    if (!PyArg_ParseTuple(args, "iO&y*|i:fsetxattr", &fd, PyUnicode_FSConverter,
-                          &name, &data, &flags))
-        return NULL;
-    Py_BEGIN_ALLOW_THREADS;
-    err = fsetxattr(fd, PyBytes_AS_STRING(name), data.buf, data.len, flags);
-    Py_END_ALLOW_THREADS;
-    Py_DECREF(name);
-    PyBuffer_Release(&data);
-    if (err)
-        return posix_error();
-    Py_RETURN_NONE;
-}
-
-PyDoc_STRVAR(posix_removexattr__doc__,
-"removexattr(path, attr)\n\n\
-Remove extended attribute *attr* on *path*.");
-
-static PyObject *
-posix_removexattr(PyObject *self, PyObject *args)
-{
-    PyObject *path, *name;
-    int err;
-
-    if (!PyArg_ParseTuple(args, "O&O&:removexattr", PyUnicode_FSConverter, &path,
-                          PyUnicode_FSConverter, &name))
-        return NULL;
-    Py_BEGIN_ALLOW_THREADS;
-    err = removexattr(PyBytes_AS_STRING(path), PyBytes_AS_STRING(name));
-    Py_END_ALLOW_THREADS;
-    Py_DECREF(path);
-    Py_DECREF(name);
-    if (err)
-        return posix_error();
-    Py_RETURN_NONE;
-}
-
-PyDoc_STRVAR(posix_lremovexattr__doc__,
-"lremovexattr(path, attr)\n\n\
-Like removexattr but don't follow symlinks.");
-
-static PyObject *
-posix_lremovexattr(PyObject *self, PyObject *args)
-{
-    PyObject *path, *name;
-    int err;
-
-    if (!PyArg_ParseTuple(args, "O&O&:lremovexattr", PyUnicode_FSConverter, &path,
-                          PyUnicode_FSConverter, &name))
-        return NULL;
-    Py_BEGIN_ALLOW_THREADS;
-    err = lremovexattr(PyBytes_AS_STRING(path), PyBytes_AS_STRING(name));
-    Py_END_ALLOW_THREADS;
-    Py_DECREF(path);
-    Py_DECREF(name);
-    if (err)
-        return posix_error();
-    Py_RETURN_NONE;
-}
-
-PyDoc_STRVAR(posix_fremovexattr__doc__,
-"fremovexattr(fd, attr)\n\n\
-Like removexattr but operates on a file descriptor.");
-
-static PyObject *
-posix_fremovexattr(PyObject *self, PyObject *args)
-{
-    PyObject *name;
-    int fd, err;
-
-    if (!PyArg_ParseTuple(args, "iO&:fremovexattr", &fd,
-                          PyUnicode_FSConverter, &name))
-        return NULL;
-    Py_BEGIN_ALLOW_THREADS;
-    err = fremovexattr(fd, PyBytes_AS_STRING(name));
-    Py_END_ALLOW_THREADS;
-    Py_DECREF(name);
-    if (err)
-        return posix_error();
-    Py_RETURN_NONE;
-}
-
-static Py_ssize_t
-try_listxattr(const char *path, ssize_t (*list)(const char *, char *, size_t),
-              Py_ssize_t buf_size, char **buf)
-{
-    Py_ssize_t len;
-
-    *buf = PyMem_MALLOC(buf_size);
-    if (!*buf) {
-        PyErr_NoMemory();
-        return -1;
-    }
-    Py_BEGIN_ALLOW_THREADS;
-    len = list(path, *buf, buf_size);
-    Py_END_ALLOW_THREADS;
-    if (len < 0) {
-        PyMem_FREE(*buf);
-        if (errno != ERANGE)
-            posix_error();
-        return -1;
-    }
-    return len;
-}
-
-static PyObject *
-listxattr_common(const char *path, ssize_t (*list)(const char *, char *, size_t))
-{
-    PyObject *res, *attr;
-    Py_ssize_t len, err, start, i;
-    char *buf;
-
-    len = try_listxattr(path, list, 256, &buf);
-    if (len < 0) {
-        if (PyErr_Occurred())
-            return NULL;
-        len = try_listxattr(path, list, XATTR_LIST_MAX, &buf);
-        if (len < 0)
-            return NULL;
-    }
-    res = PyList_New(0);
-    if (!res) {
-        PyMem_FREE(buf);
-        return NULL;
-    }
-    for (start = i = 0; i < len; i++) {
-        if (!buf[i]) {
-            attr = PyUnicode_DecodeFSDefaultAndSize(&buf[start], i - start);
-            if (!attr) {
-                Py_DECREF(res);
-                PyMem_FREE(buf);
-                return NULL;
-            }
-            err = PyList_Append(res, attr);
-            Py_DECREF(attr);
-            if (err) {
-                Py_DECREF(res);
-                PyMem_FREE(buf);
-                return NULL;
-            }
-            start = i + 1;
-        }
-    }
-    PyMem_FREE(buf);
-    return res;
-}
-
-PyDoc_STRVAR(posix_listxattr__doc__,
-"listxattr(path)\n\n\
-Return a list of extended attributes on *path*.");
-
-static PyObject *
-posix_listxattr(PyObject *self, PyObject *args)
-{
-    PyObject *path, *res;
-
-    if (!PyArg_ParseTuple(args, "O&:listxattr", PyUnicode_FSConverter, &path))
-        return NULL;
-    res = listxattr_common(PyBytes_AS_STRING(path), listxattr);
-    Py_DECREF(path);
-    return res;
-}
-
-PyDoc_STRVAR(posix_llistxattr__doc__,
-"llistxattr(path)\n\n\
-Like listxattr but don't follow symlinks..");
-
-static PyObject *
-posix_llistxattr(PyObject *self, PyObject *args)
-{
-    PyObject *path, *res;
-
-    if (!PyArg_ParseTuple(args, "O&:llistxattr", PyUnicode_FSConverter, &path))
-        return NULL;
-    res = listxattr_common(PyBytes_AS_STRING(path), llistxattr);
-    Py_DECREF(path);
-    return res;
-}
-
-static ssize_t
-wrap_flistxattr(const char *path, char *buf, size_t len)
-{
-    /* Hack to share code. */
-    return flistxattr((int)(Py_uintptr_t)path, buf, len);
-}
-
-PyDoc_STRVAR(posix_flistxattr__doc__,
-"flistxattr(path)\n\n\
-Like flistxattr but operates on a file descriptor.");
-
-static PyObject *
-posix_flistxattr(PyObject *self, PyObject *args)
-{
-    long fd;
-
-    if (!PyArg_ParseTuple(args, "i:flistxattr", &fd))
-        return NULL;
-    return listxattr_common((const char *)(Py_uintptr_t)fd, wrap_flistxattr);
+    break;
+    }
+exit:
+    path_cleanup(&path);
+    if (buffer)
+        PyMem_FREE(buffer);
+    return result;
 }
 
 #endif /* USE_XATTRS */
@@ -10600,20 +10715,30 @@
 
 
 static PyMethodDef posix_methods[] = {
-    {"access",          posix_access, METH_VARARGS, posix_access__doc__},
+    {"access",          (PyCFunction)posix_access,
+                        METH_VARARGS | METH_KEYWORDS,
+                        posix_access__doc__},
 #ifdef HAVE_TTYNAME
     {"ttyname",         posix_ttyname, METH_VARARGS, posix_ttyname__doc__},
 #endif
-    {"chdir",           posix_chdir, METH_VARARGS, posix_chdir__doc__},
+    {"chdir",           (PyCFunction)posix_chdir,
+                        METH_VARARGS | METH_KEYWORDS,
+                        posix_chdir__doc__},
 #ifdef HAVE_CHFLAGS
-    {"chflags",         posix_chflags, METH_VARARGS, posix_chflags__doc__},
+    {"chflags",         (PyCFunction)posix_chflags,
+                        METH_VARARGS | METH_KEYWORDS,
+                        posix_chflags__doc__},
 #endif /* HAVE_CHFLAGS */
-    {"chmod",           posix_chmod, METH_VARARGS, posix_chmod__doc__},
+    {"chmod",           (PyCFunction)posix_chmod,
+                        METH_VARARGS | METH_KEYWORDS,
+                        posix_chmod__doc__},
 #ifdef HAVE_FCHMOD
     {"fchmod",          posix_fchmod, METH_VARARGS, posix_fchmod__doc__},
 #endif /* HAVE_FCHMOD */
 #ifdef HAVE_CHOWN
-    {"chown",           posix_chown, METH_VARARGS, posix_chown__doc__},
+    {"chown",           (PyCFunction)posix_chown,
+                        METH_VARARGS | METH_KEYWORDS,
+                        posix_chown__doc__},
 #endif /* HAVE_CHOWN */
 #ifdef HAVE_LCHMOD
     {"lchmod",          posix_lchmod, METH_VARARGS, posix_lchmod__doc__},
@@ -10639,15 +10764,20 @@
     {"getcwdb",         (PyCFunction)posix_getcwd_bytes,
     METH_NOARGS, posix_getcwdb__doc__},
 #endif
-#ifdef HAVE_LINK
-    {"link",            posix_link, METH_VARARGS, posix_link__doc__},
+#if defined(HAVE_LINK) || defined(MS_WINDOWS)
+    {"link",            (PyCFunction)posix_link,
+                        METH_VARARGS | METH_KEYWORDS,
+                        posix_link__doc__},
 #endif /* HAVE_LINK */
-    {"listdir",         posix_listdir, METH_VARARGS, posix_listdir__doc__},
-#ifdef HAVE_FDOPENDIR
-    {"flistdir",       posix_flistdir, METH_VARARGS, posix_flistdir__doc__},
-#endif
-    {"lstat",           posix_lstat, METH_VARARGS, posix_lstat__doc__},
-    {"mkdir",           posix_mkdir, METH_VARARGS, posix_mkdir__doc__},
+    {"listdir",         (PyCFunction)posix_listdir,
+                        METH_VARARGS | METH_KEYWORDS,
+                        posix_listdir__doc__},
+    {"lstat",           (PyCFunction)posix_lstat,
+                        METH_VARARGS | METH_KEYWORDS,
+                        posix_lstat__doc__},
+    {"mkdir",           (PyCFunction)posix_mkdir,
+                        METH_VARARGS | METH_KEYWORDS,
+                        posix_mkdir__doc__},
 #ifdef HAVE_NICE
     {"nice",            posix_nice, METH_VARARGS, posix_nice__doc__},
 #endif /* HAVE_NICE */
@@ -10658,23 +10788,31 @@
     {"setpriority",     posix_setpriority, METH_VARARGS, posix_setpriority__doc__},
 #endif /* HAVE_SETPRIORITY */
 #ifdef HAVE_READLINK
-    {"readlink",        posix_readlink, METH_VARARGS, posix_readlink__doc__},
+    {"readlink",        (PyCFunction)posix_readlink,
+                        METH_VARARGS | METH_KEYWORDS,
+                        readlink__doc__},
 #endif /* HAVE_READLINK */
 #if !defined(HAVE_READLINK) && defined(MS_WINDOWS)
-    {"readlink",        win_readlink, METH_VARARGS, win_readlink__doc__},
+    {"readlink",        (PyCFunction)win_readlink,
+                        METH_VARARGS | METH_KEYWORDS,
+                        readlink__doc__},
 #endif /* !defined(HAVE_READLINK) && defined(MS_WINDOWS) */
-    {"rename",          posix_rename, METH_VARARGS, posix_rename__doc__},
-    {"replace",         posix_replace, METH_VARARGS, posix_replace__doc__},
+    {"rename",          (PyCFunction)posix_rename,
+                        METH_VARARGS | METH_KEYWORDS,
+                        posix_rename__doc__},
+    {"replace",         (PyCFunction)posix_replace,
+                        METH_VARARGS | METH_KEYWORDS,
+                        posix_replace__doc__},
     {"rmdir",           posix_rmdir, METH_VARARGS, posix_rmdir__doc__},
-    {"stat",            posix_stat, METH_VARARGS, posix_stat__doc__},
+    {"stat",            (PyCFunction)posix_stat,
+                        METH_VARARGS | METH_KEYWORDS,
+                        posix_stat__doc__},
     {"stat_float_times", stat_float_times, METH_VARARGS, stat_float_times__doc__},
-#if defined(HAVE_SYMLINK) && !defined(MS_WINDOWS)
-    {"symlink",         posix_symlink, METH_VARARGS, posix_symlink__doc__},
+#if defined(HAVE_SYMLINK)
+    {"symlink",         (PyCFunction)posix_symlink,
+                        METH_VARARGS | METH_KEYWORDS,
+                        posix_symlink__doc__},
 #endif /* HAVE_SYMLINK */
-#if defined(HAVE_SYMLINK) && defined(MS_WINDOWS)
-    {"symlink", (PyCFunction)win_symlink, METH_VARARGS | METH_KEYWORDS,
-                 win_symlink__doc__},
-#endif /* defined(HAVE_SYMLINK) && defined(MS_WINDOWS) */
 #ifdef HAVE_SYSTEM
     {"system",          posix_system, METH_VARARGS, posix_system__doc__},
 #endif
@@ -10682,29 +10820,24 @@
 #ifdef HAVE_UNAME
     {"uname",           posix_uname, METH_NOARGS, posix_uname__doc__},
 #endif /* HAVE_UNAME */
-    {"unlink",          posix_unlink, METH_VARARGS, posix_unlink__doc__},
-    {"remove",          posix_unlink, METH_VARARGS, posix_remove__doc__},
+    {"unlink",          (PyCFunction)posix_unlink,
+                        METH_VARARGS | METH_KEYWORDS,
+                        posix_unlink__doc__},
+    {"remove",          (PyCFunction)posix_unlink,
+                        METH_VARARGS | METH_KEYWORDS,
+                        posix_remove__doc__},
     {"utime",           (PyCFunction)posix_utime,
                         METH_VARARGS | METH_KEYWORDS, posix_utime__doc__},
-#ifdef HAVE_FUTIMES
-    {"futimes",         (PyCFunction)posix_futimes,
-                        METH_VARARGS | METH_KEYWORDS, posix_futimes__doc__},
-#endif
-#ifdef HAVE_LUTIMES
-    {"lutimes",         (PyCFunction)posix_lutimes,
-                        METH_VARARGS | METH_KEYWORDS, posix_lutimes__doc__},
-#endif
 #ifdef HAVE_TIMES
     {"times",           posix_times, METH_NOARGS, posix_times__doc__},
 #endif /* HAVE_TIMES */
     {"_exit",           posix__exit, METH_VARARGS, posix__exit__doc__},
 #ifdef HAVE_EXECV
     {"execv",           posix_execv, METH_VARARGS, posix_execv__doc__},
-    {"execve",          posix_execve, METH_VARARGS, posix_execve__doc__},
+    {"execve",          (PyCFunction)posix_execve,
+                        METH_VARARGS | METH_KEYWORDS,
+                        posix_execve__doc__},
 #endif /* HAVE_EXECV */
-#ifdef HAVE_FEXECVE
-    {"fexecve",          posix_fexecve, METH_VARARGS, posix_fexecve__doc__},
-#endif
 #ifdef HAVE_SPAWNV
     {"spawnv",          posix_spawnv, METH_VARARGS, posix_spawnv__doc__},
     {"spawnve",         posix_spawnve, METH_VARARGS, posix_spawnve__doc__},
@@ -10791,7 +10924,6 @@
 #ifdef MS_WINDOWS
     {"startfile",       win32_startfile, METH_VARARGS, win32_startfile__doc__},
     {"kill",    win32_kill, METH_VARARGS, win32_kill__doc__},
-    {"link",    win32_link, METH_VARARGS, win32_link__doc__},
 #endif
 #ifdef HAVE_SETUID
     {"setuid",          posix_setuid, METH_VARARGS, posix_setuid__doc__},
@@ -10853,7 +10985,9 @@
 #ifdef HAVE_TCSETPGRP
     {"tcsetpgrp",       posix_tcsetpgrp, METH_VARARGS, posix_tcsetpgrp__doc__},
 #endif /* HAVE_TCSETPGRP */
-    {"open",            posix_open, METH_VARARGS, posix_open__doc__},
+    {"open",            (PyCFunction)posix_open,\
+                        METH_VARARGS | METH_KEYWORDS,
+                        posix_open__doc__},
     {"close",           posix_close, METH_VARARGS, posix_close__doc__},
     {"closerange",      posix_closerange, METH_VARARGS, posix_closerange__doc__},
     {"device_encoding", device_encoding, METH_VARARGS, device_encoding__doc__},
@@ -10890,10 +11024,14 @@
     {"pipe2",           posix_pipe2, METH_O, posix_pipe2__doc__},
 #endif
 #ifdef HAVE_MKFIFO
-    {"mkfifo",          posix_mkfifo, METH_VARARGS, posix_mkfifo__doc__},
+    {"mkfifo",          (PyCFunction)posix_mkfifo,
+                        METH_VARARGS | METH_KEYWORDS,
+                        posix_mkfifo__doc__},
 #endif
 #if defined(HAVE_MKNOD) && defined(HAVE_MAKEDEV)
-    {"mknod",           posix_mknod, METH_VARARGS, posix_mknod__doc__},
+    {"mknod",           (PyCFunction)posix_mknod,
+                        METH_VARARGS | METH_KEYWORDS,
+                        posix_mknod__doc__},
 #endif
 #ifdef HAVE_DEVICE_MACROS
     {"major",           posix_major, METH_VARARGS, posix_major__doc__},
@@ -10961,7 +11099,9 @@
     {"fstatvfs",        posix_fstatvfs, METH_VARARGS, posix_fstatvfs__doc__},
 #endif
 #if defined(HAVE_STATVFS) && defined(HAVE_SYS_STATVFS_H)
-    {"statvfs",         posix_statvfs, METH_VARARGS, posix_statvfs__doc__},
+    {"statvfs",         (PyCFunction)posix_statvfs,
+                        METH_VARARGS | METH_KEYWORDS,
+                        posix_statvfs__doc__},
 #endif
 #ifdef HAVE_CONFSTR
     {"confstr",         posix_confstr, METH_VARARGS, posix_confstr__doc__},
@@ -11000,67 +11140,19 @@
     {"getresgid",       posix_getresgid, METH_NOARGS, posix_getresgid__doc__},
 #endif
 
-/* posix *at family of functions */
-#ifdef HAVE_FACCESSAT
-    {"faccessat",       posix_faccessat, METH_VARARGS, posix_faccessat__doc__},
-#endif
-#ifdef HAVE_FCHMODAT
-    {"fchmodat",        posix_fchmodat, METH_VARARGS, posix_fchmodat__doc__},
-#endif /* HAVE_FCHMODAT */
-#ifdef HAVE_FCHOWNAT
-    {"fchownat",        posix_fchownat, METH_VARARGS, posix_fchownat__doc__},
-#endif /* HAVE_FCHOWNAT */
-#ifdef HAVE_FSTATAT
-    {"fstatat",         posix_fstatat, METH_VARARGS, posix_fstatat__doc__},
-#endif
-#ifdef HAVE_FUTIMESAT
-    {"futimesat",       posix_futimesat, METH_VARARGS, posix_futimesat__doc__},
-#endif
-#ifdef HAVE_LINKAT
-    {"linkat",          posix_linkat, METH_VARARGS, posix_linkat__doc__},
-#endif /* HAVE_LINKAT */
-#ifdef HAVE_MKDIRAT
-    {"mkdirat",         posix_mkdirat, METH_VARARGS, posix_mkdirat__doc__},
-#endif
-#if defined(HAVE_MKNODAT) && defined(HAVE_MAKEDEV)
-    {"mknodat",         posix_mknodat, METH_VARARGS, posix_mknodat__doc__},
-#endif
-#ifdef HAVE_OPENAT
-    {"openat",      posix_openat, METH_VARARGS, posix_openat__doc__},
-#endif
-#ifdef HAVE_READLINKAT
-    {"readlinkat",      posix_readlinkat, METH_VARARGS, posix_readlinkat__doc__},
-#endif /* HAVE_READLINKAT */
-#ifdef HAVE_RENAMEAT
-    {"renameat",        posix_renameat, METH_VARARGS, posix_renameat__doc__},
-#endif
-#if HAVE_SYMLINKAT
-    {"symlinkat",       posix_symlinkat, METH_VARARGS, posix_symlinkat__doc__},
-#endif /* HAVE_SYMLINKAT */
-#ifdef HAVE_UNLINKAT
-    {"unlinkat",        posix_unlinkat, METH_VARARGS, posix_unlinkat__doc__},
-#endif
-#ifdef HAVE_UTIMENSAT
-    {"utimensat",       (PyCFunction)posix_utimensat,
-                        METH_VARARGS | METH_KEYWORDS,
-                        posix_utimensat__doc__},
-#endif
-#ifdef HAVE_MKFIFOAT
-    {"mkfifoat",        posix_mkfifoat, METH_VARARGS, posix_mkfifoat__doc__},
-#endif
 #ifdef USE_XATTRS
-    {"setxattr", posix_setxattr, METH_VARARGS, posix_setxattr__doc__},
-    {"lsetxattr", posix_lsetxattr, METH_VARARGS, posix_lsetxattr__doc__},
-    {"fsetxattr", posix_fsetxattr, METH_VARARGS, posix_fsetxattr__doc__},
-    {"getxattr", posix_getxattr, METH_VARARGS, posix_getxattr__doc__},
-    {"lgetxattr", posix_lgetxattr, METH_VARARGS, posix_lgetxattr__doc__},
-    {"fgetxattr", posix_fgetxattr, METH_VARARGS, posix_fgetxattr__doc__},
-    {"removexattr", posix_removexattr, METH_VARARGS, posix_removexattr__doc__},
-    {"lremovexattr", posix_lremovexattr, METH_VARARGS, posix_lremovexattr__doc__},
-    {"fremovexattr", posix_fremovexattr, METH_VARARGS, posix_fremovexattr__doc__},
-    {"listxattr", posix_listxattr, METH_VARARGS, posix_listxattr__doc__},
-    {"llistxattr", posix_llistxattr, METH_VARARGS, posix_llistxattr__doc__},
-    {"flistxattr", posix_flistxattr, METH_VARARGS, posix_flistxattr__doc__},
+    {"setxattr", (PyCFunction)posix_setxattr,
+                 METH_VARARGS | METH_KEYWORDS,
+                 posix_setxattr__doc__},
+    {"getxattr", (PyCFunction)posix_getxattr,
+                 METH_VARARGS | METH_KEYWORDS,
+                 posix_getxattr__doc__},
+    {"removexattr", (PyCFunction)posix_removexattr,
+                 METH_VARARGS | METH_KEYWORDS,
+                 posix_removexattr__doc__},
+    {"listxattr", (PyCFunction)posix_listxattr,
+                 METH_VARARGS | METH_KEYWORDS,
+                 posix_listxattr__doc__},
 #endif
 #if defined(TERMSIZE_USE_CONIO) || defined(TERMSIZE_USE_IOCTL)
     {"get_terminal_size", get_terminal_size, METH_VARARGS, termsize__doc__},
@@ -11268,28 +11360,6 @@
 #ifdef O_ACCMODE
     if (ins(d, "O_ACCMODE", (long)O_ACCMODE)) return -1;
 #endif
-/* posix - constants for *at functions */
-#ifdef AT_SYMLINK_NOFOLLOW
-        if (ins(d, "AT_SYMLINK_NOFOLLOW", (long)AT_SYMLINK_NOFOLLOW)) return -1;
-#endif
-#ifdef AT_EACCESS
-        if (ins(d, "AT_EACCESS", (long)AT_EACCESS)) return -1;
-#endif
-#ifdef AT_FDCWD
-        if (ins(d, "AT_FDCWD", (long)AT_FDCWD)) return -1;
-#endif
-#ifdef AT_REMOVEDIR
-        if (ins(d, "AT_REMOVEDIR", (long)AT_REMOVEDIR)) return -1;
-#endif
-#ifdef AT_SYMLINK_FOLLOW
-        if (ins(d, "AT_SYMLINK_FOLLOW", (long)AT_SYMLINK_FOLLOW)) return -1;
-#endif
-#ifdef UTIME_NOW
-        if (ins(d, "UTIME_NOW", (long)UTIME_NOW)) return -1;
-#endif
-#ifdef UTIME_OMIT
-        if (ins(d, "UTIME_OMIT", (long)UTIME_OMIT)) return -1;
-#endif
 
 
 #ifdef SEEK_HOLE
@@ -11483,14 +11553,6 @@
     if (ins(d, "F_TEST", (long)F_TEST)) return -1;
 #endif
 
-    /* constants for futimens */
-#ifdef UTIME_NOW
-    if (ins(d, "UTIME_NOW", (long)UTIME_NOW)) return -1;
-#endif
-#ifdef UTIME_OMIT
-    if (ins(d, "UTIME_OMIT", (long)UTIME_OMIT)) return -1;
-#endif
-
 #ifdef HAVE_SPAWNV
 #if defined(PYOS_OS2) && defined(PYCC_GCC)
     if (ins(d, "P_WAIT", (long)P_WAIT)) return -1;
@@ -11613,10 +11675,130 @@
 };
 
 
+static char *have_functions[] = {
+
+#ifdef HAVE_FACCESSAT
+    "HAVE_FACCESSAT",
+#endif
+
+#ifdef HAVE_FCHDIR
+    "HAVE_FCHDIR",
+#endif
+
+#ifdef HAVE_FCHMOD
+    "HAVE_FCHMOD",
+#endif
+
+#ifdef HAVE_FCHMODAT
+    "HAVE_FCHMODAT",
+#endif
+
+#ifdef HAVE_FCHOWN
+    "HAVE_FCHOWN",
+#endif
+
+#ifdef HAVE_FEXECVE
+    "HAVE_FEXECVE",
+#endif
+
+#ifdef HAVE_FDOPENDIR
+    "HAVE_FDOPENDIR",
+#endif
+
+#ifdef HAVE_FSTATAT
+    "HAVE_FSTATAT",
+#endif
+
+#ifdef HAVE_FSTATVFS
+    "HAVE_FSTATVFS",
+#endif
+
+#ifdef HAVE_FUTIMENS
+    "HAVE_FUTIMENS",
+#endif
+
+#ifdef HAVE_FUTIMES
+    "HAVE_FUTIMES",
+#endif
+
+#ifdef HAVE_FUTIMESAT
+    "HAVE_FUTIMESAT",
+#endif
+
+#ifdef HAVE_LINKAT
+    "HAVE_LINKAT",
+#endif
+
+#ifdef HAVE_LCHFLAGS
+    "HAVE_LCHFLAGS",
+#endif
+
+#ifdef HAVE_LCHMOD
+    "HAVE_LCHMOD",
+#endif
+
+#ifdef HAVE_LCHOWN
+    "HAVE_LCHOWN",
+#endif
+
+#ifdef HAVE_LSTAT
+    "HAVE_LSTAT",
+#endif
+
+#ifdef HAVE_LUTIMES
+    "HAVE_LUTIMES",
+#endif
+
+#ifdef HAVE_MKDIRAT
+    "HAVE_MKDIRAT",
+#endif
+
+#ifdef HAVE_MKFIFOAT
+    "HAVE_MKFIFOAT",
+#endif
+
+#ifdef HAVE_MKNODAT
+    "HAVE_MKNODAT",
+#endif
+
+#ifdef HAVE_OPENAT
+    "HAVE_OPENAT",
+#endif
+
+#ifdef HAVE_READLINKAT
+    "HAVE_READLINKAT",
+#endif
+
+#ifdef HAVE_RENAMEAT
+    "HAVE_RENAMEAT",
+#endif
+
+#ifdef HAVE_SYMLINKAT
+    "HAVE_SYMLINKAT",
+#endif
+
+#ifdef HAVE_UNLINKAT
+    "HAVE_UNLINKAT",
+#endif
+
+#ifdef HAVE_UTIMENSAT
+    "HAVE_UTIMENSAT",
+#endif
+
+#ifdef MS_WINDOWS
+    "MS_WINDOWS",
+#endif
+
+    NULL
+};
+
+
 PyMODINIT_FUNC
 INITFUNC(void)
 {
     PyObject *m, *v;
+    PyObject *list;
+    char **trace;
 
 #if defined(HAVE_SYMLINK) && defined(MS_WINDOWS)
     win32_can_symlink = enable_symlink();
@@ -11704,7 +11886,6 @@
     Py_INCREF(&SchedParamType);
     PyModule_AddObject(m, "sched_param", (PyObject *)&SchedParamType);
 #endif
-    initialized = 1;
 
 #ifdef __APPLE__
     /*
@@ -11714,7 +11895,7 @@
      * currently active platform.
      *
      * This block allow one to use a python binary that was build on
-     * OSX 10.4 on OSX 10.3, without loosing access to new APIs on
+     * OSX 10.4 on OSX 10.3, without losing access to new APIs on
      * OSX 10.4.
      */
 #ifdef HAVE_FSTATVFS
@@ -11750,6 +11931,35 @@
     if (!billion)
         return NULL;
 
+    /* suppress "function not used" warnings */
+    {
+    int ignored;
+    fd_specified("", -1);
+    follow_symlinks_specified("", 1);
+    dir_fd_and_follow_symlinks_invalid("chmod", DEFAULT_DIR_FD, 1);
+    dir_fd_converter(Py_None, &ignored);
+    dir_fd_unavailable(Py_None, &ignored);
+    }
+
+    /*
+     * provide list of locally available functions
+     * so os.py can populate support_* lists
+     */
+    list = PyList_New(0);
+    if (!list)
+        return NULL;
+    for (trace = have_functions; *trace; trace++) {
+        PyObject *unicode = PyUnicode_DecodeASCII(*trace, strlen(*trace), NULL);
+        if (!unicode)
+            return NULL;
+        if (PyList_Append(list, unicode))
+            return NULL;
+        Py_DECREF(unicode);
+    }
+    PyModule_AddObject(m, "_have_functions", list);
+
+    initialized = 1;
+
     return m;
 
 }

-- 
Repository URL: http://hg.python.org/cpython


More information about the Python-checkins mailing list