diff --git a/pressure-vessel/utils.c b/pressure-vessel/utils.c
index d82cb81380f406b63b4e3dbb20c41d6f857c9b16..7834bcd79d3a84e99f47c472cd7f3098b60e15ed 100644
--- a/pressure-vessel/utils.c
+++ b/pressure-vessel/utils.c
@@ -37,6 +37,7 @@
 
 #include "steam-runtime-tools/glib-backports-internal.h"
 #include "steam-runtime-tools/resolve-in-sysroot-internal.h"
+#include "steam-runtime-tools/utils-internal.h"
 #include "flatpak-bwrap-private.h"
 #include "flatpak-utils-base-private.h"
 #include "flatpak-utils-private.h"
@@ -148,38 +149,13 @@ pv_get_current_dirs (gchar **cwd_p,
     {
       pwd = g_getenv ("PWD");
 
-      if (pwd != NULL && pv_is_same_file (pwd, cwd))
+      if (pwd != NULL && _srt_is_same_file (pwd, cwd))
         *cwd_l = g_strdup (pwd);
       else
         *cwd_l = g_strdup (cwd);
     }
 }
 
-/**
- * pv_is_same_file:
- * @a: a path
- * @b: a path
- *
- * Returns: %TRUE if a and b are names for the same inode.
- */
-gboolean
-pv_is_same_file (const gchar *a,
-                 const gchar *b)
-{
-  GStatBuf a_buffer, b_buffer;
-
-  g_return_val_if_fail (a != NULL, FALSE);
-  g_return_val_if_fail (b != NULL, FALSE);
-
-  if (strcmp (a, b) == 0)
-    return TRUE;
-
-  return (stat (a, &a_buffer) == 0
-          && stat (b, &b_buffer) == 0
-          && a_buffer.st_dev == b_buffer.st_dev
-          && a_buffer.st_ino == b_buffer.st_ino);
-}
-
 void
 pv_search_path_append (GString *search_path,
                        const gchar *item)
diff --git a/pressure-vessel/utils.h b/pressure-vessel/utils.h
index 109595344136d04c9aa16eed461c8fe4d8f01d7c..46fa32ecfccaf62cff4329ee54111e94a4c976b4 100644
--- a/pressure-vessel/utils.h
+++ b/pressure-vessel/utils.h
@@ -45,9 +45,6 @@ int pv_envp_cmp (const void *p1,
 void pv_get_current_dirs (gchar **cwd_p,
                           gchar **cwd_l);
 
-gboolean pv_is_same_file (const gchar *a,
-                          const gchar *b);
-
 void pv_search_path_append (GString *search_path,
                             const gchar *item);
 
diff --git a/pressure-vessel/wrap.c b/pressure-vessel/wrap.c
index e9b3aa26ae54783a266617be1f38bc96aa18a40f..1c727efc60205368b3b0a5e82d3a0c3500a72b3a 100644
--- a/pressure-vessel/wrap.c
+++ b/pressure-vessel/wrap.c
@@ -1973,7 +1973,7 @@ main (int argc,
 
   cwd_p_host = pv_current_namespace_path_to_host_path (cwd_p);
 
-  if (pv_is_same_file (home, cwd_p))
+  if (_srt_is_same_file (home, cwd_p))
     {
       g_debug ("Not making physical working directory \"%s\" available to "
                "container because it is the home directory",
diff --git a/steam-runtime-tools/utils-internal.h b/steam-runtime-tools/utils-internal.h
index 7e06650ce58a47fbef63880cb3268263afc1df48..6fdf671d7a8d15b51efbdfa214e55f38d1e45311 100644
--- a/steam-runtime-tools/utils-internal.h
+++ b/steam-runtime-tools/utils-internal.h
@@ -26,7 +26,9 @@
 
 #pragma once
 
+#include <fcntl.h>
 #include <stdio.h>
+#include <sys/stat.h>
 
 #include <glib.h>
 
@@ -103,3 +105,35 @@ G_GNUC_INTERNAL const char * const *_srt_peek_environ_nonnull (void);
 G_GNUC_INTERNAL void _srt_setenv_disable_gio_modules (void);
 
 G_GNUC_INTERNAL gboolean _srt_str_is_integer (const char *str);
+
+gboolean _srt_fstatat_is_same_file (int afd, const char *a,
+                                    int bfd, const char *b);
+
+/*
+ * _srt_is_same_stat:
+ * @a: a stat buffer
+ * @b: a stat buffer
+ *
+ * Returns: %TRUE if a and b identify the same inode
+ */
+ __attribute__((nonnull)) static inline gboolean
+_srt_is_same_stat (const struct stat *a,
+                   const struct stat *b)
+{
+  return (a->st_dev == b->st_dev && a->st_ino == b->st_ino);
+}
+
+/*
+ * _srt_is_same_file:
+ * @a: a path
+ * @b: a path
+ *
+ * Returns: %TRUE if a and b are names for the same inode.
+ */
+static inline gboolean
+_srt_is_same_file (const gchar *a,
+                   const gchar *b)
+{
+  return _srt_fstatat_is_same_file (AT_FDCWD, a,
+                                    AT_FDCWD, b);
+}
diff --git a/steam-runtime-tools/utils.c b/steam-runtime-tools/utils.c
index 5601a1778b291ea0d469f3629d41ba793e031c47..8aa4935433d684161b67e817c78a2ce310dfc3c3 100644
--- a/steam-runtime-tools/utils.c
+++ b/steam-runtime-tools/utils.c
@@ -986,3 +986,35 @@ _srt_str_is_integer (const char *str)
 
   return (*p == '\0');
 }
+
+/*
+ * _srt_fstatat_is_same_file:
+ * @afd: a file descriptor, `AT_FDCWD` or -1
+ * @a: a path
+ * @bfd: a file descriptor, `AT_FDCWD` or -1
+ * @b: a path
+ *
+ * Returns: %TRUE if a (relative to afd) and b (relative to bfd)
+ *  are names for the same inode.
+ */
+gboolean
+_srt_fstatat_is_same_file (int afd,
+                           const char *a,
+                           int bfd,
+                           const char *b)
+{
+  struct stat a_buffer, b_buffer;
+
+  g_return_val_if_fail (a != NULL, FALSE);
+  g_return_val_if_fail (b != NULL, FALSE);
+
+  afd = glnx_dirfd_canonicalize (afd);
+  bfd = glnx_dirfd_canonicalize (bfd);
+
+  if (afd == bfd && strcmp (a, b) == 0)
+    return TRUE;
+
+  return (fstatat (afd, a, &a_buffer, AT_EMPTY_PATH) == 0
+          && fstatat (bfd, b, &b_buffer, AT_EMPTY_PATH) == 0
+          && _srt_is_same_stat (&a_buffer, &b_buffer));
+}
diff --git a/tests/graphics.c b/tests/graphics.c
index 58fd4823828d538b5f5252f44f8f6371774617db..b8e527bb4daedf9a844630540fabfdf3be37dcc6 100644
--- a/tests/graphics.c
+++ b/tests/graphics.c
@@ -41,6 +41,7 @@
 #include "steam-runtime-tools/graphics-internal.h"
 #include "steam-runtime-tools/graphics-test-defines.h"
 #include "steam-runtime-tools/system-info.h"
+#include "steam-runtime-tools/utils-internal.h"
 #include "test-utils.h"
 
 static const char *argv0;
@@ -528,13 +529,6 @@ assert_egl_icd_no_error (SrtEglIcd *icd)
   assert_egl_icd (icd);
 }
 
-static gboolean
-same_stat (GStatBuf *left,
-           GStatBuf *right)
-{
-  return left->st_dev == right->st_dev && left->st_ino == right->st_ino;
-}
-
 /*
  * We don't assert that filenames are literally the same, because they
  * might canonicalize differently in the presence of symlinks: we just
@@ -544,15 +538,15 @@ static void
 assert_same_file (const char *expected,
                   const char *actual)
 {
-  GStatBuf expected_stat, actual_stat;
+  struct stat expected_stat, actual_stat;
 
-  if (g_stat (expected, &expected_stat) != 0)
+  if (stat (expected, &expected_stat) != 0)
     g_error ("stat %s: %s", expected, g_strerror (errno));
 
-  if (g_stat (actual, &actual_stat) != 0)
+  if (stat (actual, &actual_stat) != 0)
     g_error ("stat %s: %s", actual, g_strerror (errno));
 
-  if (!same_stat (&expected_stat, &actual_stat))
+  if (!_srt_is_same_stat (&expected_stat, &actual_stat))
     g_error ("%s is not the same file as %s", expected, actual);
 }
 
diff --git a/tests/pressure-vessel/utils.c b/tests/pressure-vessel/utils.c
index d900b89643b799d51e435957434848dca84b0296..8c123b760bbfd1c0b7536b1ab01029d7b29dc4dd 100644
--- a/tests/pressure-vessel/utils.c
+++ b/tests/pressure-vessel/utils.c
@@ -182,52 +182,6 @@ test_envp_cmp (Fixture *f,
   g_free (sort_this);
 }
 
-static void
-test_same_file (Fixture *f,
-                gconstpointer context)
-{
-  g_autoptr(GError) error = NULL;
-  g_autofree gchar *temp = NULL;
-  g_autofree gchar *hard_link_from = NULL;
-  g_autofree gchar *hard_link_to = NULL;
-  g_autofree gchar *symlink_to_dev_null = NULL;
-
-  g_assert_true (pv_is_same_file ("/dev/null", "/dev/null"));
-  g_assert_true (pv_is_same_file ("/nonexistent", "/nonexistent"));
-  g_assert_false (pv_is_same_file ("/dev/null", "/dev/zero"));
-  g_assert_false (pv_is_same_file ("/dev/null", "/nonexistent"));
-  g_assert_false (pv_is_same_file ("/nonexistent", "/dev/null"));
-  g_assert_false (pv_is_same_file ("/nonexistent", "/nonexistent/also"));
-
-  temp = g_dir_make_tmp (NULL, &error);
-  g_assert_no_error (error);
-  g_assert_nonnull (temp);
-
-  hard_link_from = g_build_filename (temp, "hard-link-from", NULL);
-  hard_link_to = g_build_filename (temp, "hard-link-to", NULL);
-  symlink_to_dev_null = g_build_filename (temp, "symlink", NULL);
-
-  g_file_set_contents (hard_link_from, "hello", -1, NULL);
-  g_assert_no_error (error);
-
-  if (link (hard_link_from, hard_link_to) != 0)
-    g_error ("Could not create hard link \"%s\" -> /dev/null: %s",
-             symlink_to_dev_null, g_strerror (errno));
-
-  g_assert_true (pv_is_same_file (hard_link_from, hard_link_to));
-  g_assert_false (pv_is_same_file (hard_link_from, "/dev/null"));
-
-  if (symlink ("/dev/null", symlink_to_dev_null) != 0)
-    g_error ("Could not create symlink \"%s\" -> /dev/null: %s",
-             symlink_to_dev_null, g_strerror (errno));
-
-  g_assert_true (pv_is_same_file (symlink_to_dev_null, "/dev/null"));
-  g_assert_false (pv_is_same_file (symlink_to_dev_null, "/dev/zero"));
-
-  glnx_shutil_rm_rf_at (-1, temp, NULL, &error);
-  g_assert_no_error (error);
-}
-
 static void
 test_search_path_append (Fixture *f,
                          gconstpointer context)
@@ -269,7 +223,6 @@ main (int argc,
   g_test_add ("/capture-output", Fixture, NULL,
               setup, test_capture_output, teardown);
   g_test_add ("/envp-cmp", Fixture, NULL, setup, test_envp_cmp, teardown);
-  g_test_add ("/same-file", Fixture, NULL, setup, test_same_file, teardown);
   g_test_add ("/search-path-append", Fixture, NULL,
               setup, test_search_path_append, teardown);
 
diff --git a/tests/utils.c b/tests/utils.c
index b7bb9102c94256d5de7bc36c382bbe9e8f7eee2f..312913182662f1900d3c155cddb6754ed1417226 100644
--- a/tests/utils.c
+++ b/tests/utils.c
@@ -299,6 +299,52 @@ filter_gameoverlayrenderer (Fixture *f,
   g_free (filtered_preload);
 }
 
+static void
+test_same_file (Fixture *f,
+                gconstpointer context)
+{
+  g_autoptr(GError) error = NULL;
+  g_autofree gchar *temp = NULL;
+  g_autofree gchar *hard_link_from = NULL;
+  g_autofree gchar *hard_link_to = NULL;
+  g_autofree gchar *symlink_to_dev_null = NULL;
+
+  g_assert_true (_srt_is_same_file ("/dev/null", "/dev/null"));
+  g_assert_true (_srt_is_same_file ("/nonexistent", "/nonexistent"));
+  g_assert_false (_srt_is_same_file ("/dev/null", "/dev/zero"));
+  g_assert_false (_srt_is_same_file ("/dev/null", "/nonexistent"));
+  g_assert_false (_srt_is_same_file ("/nonexistent", "/dev/null"));
+  g_assert_false (_srt_is_same_file ("/nonexistent", "/nonexistent/also"));
+
+  temp = g_dir_make_tmp (NULL, &error);
+  g_assert_no_error (error);
+  g_assert_nonnull (temp);
+
+  hard_link_from = g_build_filename (temp, "hard-link-from", NULL);
+  hard_link_to = g_build_filename (temp, "hard-link-to", NULL);
+  symlink_to_dev_null = g_build_filename (temp, "symlink", NULL);
+
+  g_file_set_contents (hard_link_from, "hello", -1, NULL);
+  g_assert_no_error (error);
+
+  if (link (hard_link_from, hard_link_to) != 0)
+    g_error ("Could not create hard link \"%s\" -> /dev/null: %s",
+             symlink_to_dev_null, g_strerror (errno));
+
+  g_assert_true (_srt_is_same_file (hard_link_from, hard_link_to));
+  g_assert_false (_srt_is_same_file (hard_link_from, "/dev/null"));
+
+  if (symlink ("/dev/null", symlink_to_dev_null) != 0)
+    g_error ("Could not create symlink \"%s\" -> /dev/null: %s",
+             symlink_to_dev_null, g_strerror (errno));
+
+  g_assert_true (_srt_is_same_file (symlink_to_dev_null, "/dev/null"));
+  g_assert_false (_srt_is_same_file (symlink_to_dev_null, "/dev/zero"));
+
+  glnx_shutil_rm_rf_at (-1, temp, NULL, &error);
+  g_assert_no_error (error);
+}
+
 static void
 test_str_is_integer (Fixture *f,
                      gconstpointer context)
@@ -384,6 +430,8 @@ main (int argc,
               setup, test_file_in_sysroot, teardown);
   g_test_add ("/utils/filter_gameoverlayrenderer", Fixture, NULL, setup,
               filter_gameoverlayrenderer, teardown);
+  g_test_add ("/utils/same-file", Fixture, NULL,
+              setup, test_same_file, teardown);
   g_test_add ("/utils/str_is_integer", Fixture, NULL,
               setup, test_str_is_integer, teardown);
   g_test_add ("/utils/uevent-field", Fixture, NULL,