diff --git a/glnx-dirfd.c b/glnx-dirfd.c
index af4cab825d8e562fa637e5bc5760548b79512e4f..4b7d5a04786d10cc8ee24b26ac72f0aa18b580e7 100644
--- a/glnx-dirfd.c
+++ b/glnx-dirfd.c
@@ -43,6 +43,9 @@ glnx_opendirat_with_errno (int           dfd,
   int flags = O_RDONLY | O_NONBLOCK | O_DIRECTORY | O_CLOEXEC | O_NOCTTY;
   if (!follow)
     flags |= O_NOFOLLOW;
+
+  dfd = glnx_dirfd_canonicalize (dfd);
+
   return openat (dfd, path, flags);
 }
 
diff --git a/glnx-dirfd.h b/glnx-dirfd.h
index 27cd03b87fad6bdca485730a51546e8088a3236d..585d16aff8bb566bb1ae080f0797b8d3b158652e 100644
--- a/glnx-dirfd.h
+++ b/glnx-dirfd.h
@@ -27,6 +27,23 @@
 #include <fcntl.h>
 
 G_BEGIN_DECLS
+ 
+/**
+ * glnx_dirfd_canonicalize:
+ * @fd: A directory file descriptor
+ *
+ * It's often convenient in programs to use `-1` for "unassigned fd",
+ * and also because gobject-introspection doesn't support `AT_FDCWD`,
+ * libglnx honors `-1` to mean `AT_FDCWD`.  This small inline function
+ * canonicalizes `-1 -> AT_FDCWD`.
+ */
+static inline int
+glnx_dirfd_canonicalize (int fd)
+{
+  if (fd == -1)
+    return AT_FDCWD;
+  return fd;
+}
 
 struct GLnxDirFdIterator {
   gboolean initialized;
diff --git a/glnx-fdio.c b/glnx-fdio.c
index 77be6e114e8aaca691b98a3a7d90cee5c10400a5..63dea2b9b96a32858ef45ab1359b6a3a2230e034 100644
--- a/glnx-fdio.c
+++ b/glnx-fdio.c
@@ -35,6 +35,7 @@
 #define BTRFS_IOC_CLONE _IOW(BTRFS_IOCTL_MAGIC, 9, int)
 
 #include <glnx-fdio.h>
+#include <glnx-dirfd.h>
 #include <glnx-errors.h>
 #include <glnx-xattrs.h>
 #include <glnx-backport-autoptr.h>
@@ -212,6 +213,8 @@ glnx_file_get_contents_utf8_at (int                   dfd,
   char *buf;
   gsize len;
 
+  dfd = glnx_dirfd_canonicalize (dfd);
+
   do
     fd = openat (dfd, subpath, O_RDONLY | O_NOCTTY | O_CLOEXEC);
   while (G_UNLIKELY (fd == -1 && errno == EINTR));
@@ -255,6 +258,8 @@ glnx_readlinkat_malloc (int            dfd,
 {
   size_t l = 100;
 
+  dfd = glnx_dirfd_canonicalize (dfd);
+
   for (;;)
     {
       char *c;
@@ -482,6 +487,9 @@ glnx_file_copy_at (int                   src_dfd,
   if (g_cancellable_set_error_if_cancelled (cancellable, error))
     goto out;
 
+  src_dfd = glnx_dirfd_canonicalize (src_dfd);
+  dest_dfd = glnx_dirfd_canonicalize (dest_dfd);
+
   if (S_ISLNK (src_stbuf->st_mode))
     {
       return copy_symlink_at (src_dfd, src_subpath, src_stbuf,
diff --git a/glnx-shutil.c b/glnx-shutil.c
index 9fcb65a0617f2649cdec95492c30b3f93f6137f0..967e364973a624da7fb47b71c7a031bfc4319b0c 100644
--- a/glnx-shutil.c
+++ b/glnx-shutil.c
@@ -118,7 +118,7 @@ glnx_shutil_rm_rf_children (GLnxDirFdIterator    *dfd_iter,
 
 /**
  * glnx_shutil_rm_rf_at:
- * @dfd: A directory file descriptor, or -1 for current
+ * @dfd: A directory file descriptor, or `AT_FDCWD` or `-1` for current
  * @path: Path
  * @cancellable: Cancellable
  * @error: Error
@@ -137,6 +137,8 @@ glnx_shutil_rm_rf_at (int                   dfd,
   glnx_fd_close int target_dfd = -1;
   g_auto(GLnxDirFdIterator) dfd_iter = { 0, };
 
+  dfd = glnx_dirfd_canonicalize (dfd);
+
   /* With O_NOFOLLOW first */
   target_dfd = openat (dfd, path,
                        O_RDONLY | O_NONBLOCK | O_DIRECTORY | O_CLOEXEC | O_NOFOLLOW);