diff --git a/configure.ac b/configure.ac
index 05d80d5..a490260 100644
--- a/configure.ac
+++ b/configure.ac
@@ -105,7 +105,7 @@ AC_CHECK_DECLS([sys_siglist], [], [], [[#include <signal.h>]])
 AC_CHECK_MEMBERS([struct stat.st_mtim.tv_nsec, struct stat.st_mtimensec],
   [], [], [[#include <sys/stat.h>]])
 AC_CHECK_MEMBERS([struct ifreq.ifr_flagshigh], [], [], [[#include <net/if.h>]])
-AC_CHECK_FUNCS([mlockall strnlen getloadavg statvfs getmntent_r])
+AC_CHECK_FUNCS([mlockall strnlen getloadavg statvfs getmntent_r realpath])
 AC_CHECK_HEADERS([mntent.h sys/statvfs.h linux/types.h linux/if_ether.h stdatomic.h])
 AC_CHECK_HEADERS([net/if_mib.h], [], [], [[#include <sys/types.h>
 #include <net/if.h>]])
diff --git a/lib/util.c b/lib/util.c
index 94311ac..6bb6d9e 100644
--- a/lib/util.c
+++ b/lib/util.c
@@ -2098,6 +2098,58 @@ is_stdout_a_tty(void)
     return (isatty(STDOUT_FILENO) && t && strcmp(t, "dumb") != 0);
 }
 
+/* Derives, from the pathname pointed to by path, an absolute pathname that
+ * names the same file, whose resolution does not involve '.', '..' or
+ * symbolic links. If path is null or does not exist, returns NULL.
+ */
+char *
+ovs_realpath(const char *path)
+{
+    if (!path) {
+        return NULL;
+    }
+    char *realpath_res = NULL;
+
+#ifdef HAVE_REALPATH
+    realpath_res = realpath(path, NULL);
+    if (realpath_res == NULL) {
+        goto error;
+    }
+    struct stat s;
+    int err = stat(realpath_res, &s);
+    if (err) {
+        goto error;
+    }
+    return realpath_res;
+#elif defined(_WIN32)
+    realpath_res = xstrdup(path);
+    DWORD ret = GetFullPathName(path, strlen(realpath_res), realpath_res,
+                                NULL);
+    if (ret == 0) {
+        goto error;
+    } else if (ret > strlen(realpath_res)) {
+        DWORD new_ret;
+        char *new_path = (char *)xrealloc(realpath_res, ret);
+        if (new_path == NULL) {
+            goto error;
+        }
+        realpath_res = new_path;
+        new_ret = GetFullPathName(path, strlen(realpath_res), realpath_res,
+                                  NULL);
+        if (new_ret == 0 || new_ret != ret ) {
+            goto error;
+        }
+    }
+    return realpath_res;
+#else
+#error Need realpath() on this platform
+#endif
+
+error:
+    free(realpath_res);
+    return NULL;
+}
+
 #ifdef _WIN32
 
 char *
diff --git a/lib/util.h b/lib/util.h
index 41c5ea6..8d7f335 100644
--- a/lib/util.h
+++ b/lib/util.h
@@ -220,6 +220,7 @@ char *dir_name(const char *file_name);
 char *base_name(const char *file_name);
 #endif
 char *abs_file_name(const char *dir, const char *file_name);
+char *ovs_realpath(const char *path);
 
 char *follow_symlinks(const char *filename);
 
diff --git a/tests/library.at b/tests/library.at
index e1bac92..b6561e5 100644
--- a/tests/library.at
+++ b/tests/library.at
@@ -234,3 +234,8 @@ AT_CLEANUP
 AT_SETUP([test rcu])
 AT_CHECK([ovstest test-rcu-quiesce], [0], [])
 AT_CLEANUP
+
+AT_SETUP([test ovs_realpath])
+AT_CHECK([ovstest test-util realpath \
+/tmp/../tmp/../usr/bin/.//../share:/usr/share /tmpNOEXISTS:], [0], [])
+AT_CLEANUP
diff --git a/tests/test-util.c b/tests/test-util.c
index ef45903..2755200 100644
--- a/tests/test-util.c
+++ b/tests/test-util.c
@@ -1128,6 +1128,28 @@ test_snprintf(struct ovs_cmdl_context *ctx OVS_UNUSED)
     ovs_assert(snprintf(NULL, 0, "abcde") == 5);
 }
 
+static void
+test_ovs_realpath(struct ovs_cmdl_context *ctx)
+{
+    int i;
+    for (i = 1; i < ctx->argc; i++) {
+        char *str_toks = NULL;
+        char *path = strtok_r(ctx->argv[i], ":", &str_toks);
+        ovs_assert(path != NULL);
+
+        char *check = strtok_r(NULL, ":", &str_toks);
+        char *rpath = ovs_realpath(path);
+
+        if (check) {
+            ovs_assert(rpath != NULL);
+            ovs_assert(!strcmp(check, rpath));
+            free(rpath);
+        } else {
+            ovs_assert(rpath == NULL);
+        }
+    }
+}
+
 #ifndef _WIN32
 static void
 test_file_name(struct ovs_cmdl_context *ctx)
@@ -1164,6 +1186,7 @@ static const struct ovs_cmdl_command commands[] = {
     {"assert", NULL, 0, 0, test_assert},
     {"ovs_scan", NULL, 0, 0, test_ovs_scan},
     {"snprintf", NULL, 0, 0, test_snprintf},
+    {"realpath", NULL, 1, INT_MAX, test_ovs_realpath},
 #ifndef _WIN32
     {"file_name", NULL, 1, INT_MAX, test_file_name},
 #endif
