[libc-commits] [libc] [libc][getcwd] Refactor getcwd to use the syscall wrapper pattern (PR #204000)

Jackson Stogel via libc-commits libc-commits at lists.llvm.org
Mon Jun 15 23:02:20 PDT 2026


https://github.com/jtstogel updated https://github.com/llvm/llvm-project/pull/204000

>From 99e87c6cfc0289e7f35eec95b635b1c78fe4fa89 Mon Sep 17 00:00:00 2001
From: jtstogel <jtstogel at google.com>
Date: Mon, 15 Jun 2026 14:29:21 -0700
Subject: [PATCH 1/3] [libc][getcwd] Migrate getcwd to the linux syscall
 wrapper pattern

Allows for re-use by other entrypoints. This PR keeps the error handling
for unreachable paths in `syscall_wrappers::getcwd` since I imagine most
usages of getcwd would rather error than receive `"(unreachable) /..."`.
---
 .../linux/syscall_wrappers/CMakeLists.txt     | 14 ++++++
 .../OSUtil/linux/syscall_wrappers/getcwd.h    | 48 +++++++++++++++++++
 libc/src/unistd/linux/CMakeLists.txt          |  6 ++-
 libc/src/unistd/linux/getcwd.cpp              | 44 ++++++++---------
 4 files changed, 87 insertions(+), 25 deletions(-)
 create mode 100644 libc/src/__support/OSUtil/linux/syscall_wrappers/getcwd.h

diff --git a/libc/src/__support/OSUtil/linux/syscall_wrappers/CMakeLists.txt b/libc/src/__support/OSUtil/linux/syscall_wrappers/CMakeLists.txt
index 97848b1848660..36af7ffa898f1 100644
--- a/libc/src/__support/OSUtil/linux/syscall_wrappers/CMakeLists.txt
+++ b/libc/src/__support/OSUtil/linux/syscall_wrappers/CMakeLists.txt
@@ -542,6 +542,20 @@ add_header_library(
     libc.include.sys_syscall
 )
 
+add_header_library(
+  getcwd
+  HDRS
+    getcwd.h
+  DEPENDS
+    libc.hdr.errno_macros
+    libc.hdr.types.ssize_t
+    libc.include.sys_syscall
+    libc.src.__support.common
+    libc.src.__support.error_or
+    libc.src.__support.macros.config
+    libc.src.__support.OSUtil.osutil
+)
+
 add_header_library(
   link
   HDRS
diff --git a/libc/src/__support/OSUtil/linux/syscall_wrappers/getcwd.h b/libc/src/__support/OSUtil/linux/syscall_wrappers/getcwd.h
new file mode 100644
index 0000000000000..df7cf70ae21ba
--- /dev/null
+++ b/libc/src/__support/OSUtil/linux/syscall_wrappers/getcwd.h
@@ -0,0 +1,48 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// Syscall wrapper for getcwd.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_SRC___SUPPORT_OSUTIL_SYSCALL_WRAPPERS_GETCWD_H
+#define LLVM_LIBC_SRC___SUPPORT_OSUTIL_SYSCALL_WRAPPERS_GETCWD_H
+
+#include "hdr/errno_macros.h"
+#include "hdr/types/ssize_t.h"
+#include "src/__support/OSUtil/linux/syscall.h" // syscall_impl
+#include "src/__support/common.h"
+#include "src/__support/error_or.h"
+#include "src/__support/macros/config.h"
+
+#include <sys/syscall.h> // For syscall numbers
+
+namespace LIBC_NAMESPACE_DECL {
+namespace linux_syscalls {
+
+ErrorOr<ssize_t> getcwd(char *buf, size_t size) {
+  ssize_t ret = syscall_impl<ssize_t>(SYS_getcwd, buf, size);
+  if (ret < 0) {
+    return Error(static_cast<int>(-ret));
+  }
+
+  // Return ENOENT for unreachable paths. This can occur, for example,
+  // when getcwd is called in a directory after `chroot` has switched
+  // the filesystem root.
+  if (ret == 0 || buf[0] != '/') {
+    return Error(ENOENT);
+  }
+
+  return ret;
+}
+
+} // namespace linux_syscalls
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC___SUPPORT_OSUTIL_SYSCALL_WRAPPERS_GETCWD_H
diff --git a/libc/src/unistd/linux/CMakeLists.txt b/libc/src/unistd/linux/CMakeLists.txt
index be057b4f80972..bf99fd4ccb389 100644
--- a/libc/src/unistd/linux/CMakeLists.txt
+++ b/libc/src/unistd/linux/CMakeLists.txt
@@ -223,9 +223,11 @@ add_entrypoint_object(
     ../getcwd.h
   DEPENDS
     libc.hdr.types.size_t
-    libc.hdr.fcntl_macros
-    libc.include.unistd
+    libc.hdr.types.ssize_t
     libc.include.sys_syscall
+    libc.include.unistd
+    libc.src.__support.CPP.optional
+    libc.src.__support.OSUtil.linux.syscall_wrappers.getcwd
     libc.src.__support.OSUtil.osutil
     libc.src.errno.errno
 )
diff --git a/libc/src/unistd/linux/getcwd.cpp b/libc/src/unistd/linux/getcwd.cpp
index c0e475dd3e8ff..6ac166a5c0611 100644
--- a/libc/src/unistd/linux/getcwd.cpp
+++ b/libc/src/unistd/linux/getcwd.cpp
@@ -8,33 +8,21 @@
 
 #include "src/unistd/getcwd.h"
 
+#include "hdr/types/size_t.h"
+#include "hdr/types/ssize_t.h"
+#include "src/__support/CPP/optional.h"
+#include "src/__support/OSUtil/linux/syscall_wrappers/getcwd.h"
 #include "src/__support/OSUtil/syscall.h" // For internal syscall function.
 #include "src/__support/common.h"
+#include "src/__support/libc_errno.h"
 #include "src/__support/macros/config.h"
 #include "src/string/allocating_string_utils.h" // For strdup.
 
-#include "src/__support/libc_errno.h"
 #include <linux/limits.h> // This is safe to include without any name pollution.
-#include <sys/syscall.h> // For syscall numbers.
+#include <sys/syscall.h>  // For syscall numbers.
 
 namespace LIBC_NAMESPACE_DECL {
 
-namespace {
-
-bool getcwd_syscall(char *buf, size_t size) {
-  int ret = LIBC_NAMESPACE::syscall_impl<int>(SYS_getcwd, buf, size);
-  if (ret < 0) {
-    libc_errno = -ret;
-    return false;
-  } else if (ret == 0 || buf[0] != '/') {
-    libc_errno = ENOENT;
-    return false;
-  }
-  return true;
-}
-
-} // anonymous namespace
-
 LLVM_LIBC_FUNCTION(char *, getcwd, (char *buf, size_t size)) {
   if (buf == nullptr) {
     // We match glibc's behavior here and return the cwd in a malloc-ed buffer.
@@ -42,24 +30,34 @@ LLVM_LIBC_FUNCTION(char *, getcwd, (char *buf, size_t size)) {
     // into it. This way, if the syscall fails, we avoid unnecessary malloc
     // and free.
     char pathbuf[PATH_MAX];
-    if (!getcwd_syscall(pathbuf, PATH_MAX))
+
+    ErrorOr<ssize_t> bytes_written = linux_syscalls::getcwd(pathbuf, PATH_MAX);
+    if (!bytes_written) {
+      libc_errno = bytes_written.error();
       return nullptr;
-    auto cwd = internal::strdup(pathbuf);
+    }
+
+    cpp::optional<char *> cwd = internal::strdup(pathbuf);
     if (!cwd) {
       libc_errno = ENOMEM;
       return nullptr;
     }
     return *cwd;
-  } else if (size == 0) {
+  }
+
+  if (size == 0) {
     libc_errno = EINVAL;
     return nullptr;
   }
 
   // TODO: When buf is not sufficient, evaluate the full cwd path using
   // alternate approaches.
-
-  if (!getcwd_syscall(buf, size))
+  ErrorOr<ssize_t> bytes_written = linux_syscalls::getcwd(buf, size);
+  if (!bytes_written) {
+    libc_errno = bytes_written.error();
     return nullptr;
+  }
+
   return buf;
 }
 

>From 50f1fb245e518784639bfa6f238cf2e613b4c251 Mon Sep 17 00:00:00 2001
From: jtstogel <jtstogel at google.com>
Date: Mon, 15 Jun 2026 19:49:34 -0700
Subject: [PATCH 2/3] Add inline

---
 libc/src/__support/OSUtil/linux/syscall_wrappers/getcwd.h | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/libc/src/__support/OSUtil/linux/syscall_wrappers/getcwd.h b/libc/src/__support/OSUtil/linux/syscall_wrappers/getcwd.h
index df7cf70ae21ba..f0aa9cb3fbab8 100644
--- a/libc/src/__support/OSUtil/linux/syscall_wrappers/getcwd.h
+++ b/libc/src/__support/OSUtil/linux/syscall_wrappers/getcwd.h
@@ -26,7 +26,7 @@
 namespace LIBC_NAMESPACE_DECL {
 namespace linux_syscalls {
 
-ErrorOr<ssize_t> getcwd(char *buf, size_t size) {
+LIBC_INLINE ErrorOr<ssize_t> getcwd(char *buf, size_t size) {
   ssize_t ret = syscall_impl<ssize_t>(SYS_getcwd, buf, size);
   if (ret < 0) {
     return Error(static_cast<int>(-ret));

>From 93c5a7610e1e5ecc121df2edf555263db421a253 Mon Sep 17 00:00:00 2001
From: jtstogel <jtstogel at google.com>
Date: Mon, 15 Jun 2026 23:02:01 -0700
Subject: [PATCH 3/3] Fix includes and deps

---
 libc/src/unistd/linux/CMakeLists.txt | 6 +++---
 libc/src/unistd/linux/getcwd.cpp     | 2 --
 2 files changed, 3 insertions(+), 5 deletions(-)

diff --git a/libc/src/unistd/linux/CMakeLists.txt b/libc/src/unistd/linux/CMakeLists.txt
index bf99fd4ccb389..592349f1039fe 100644
--- a/libc/src/unistd/linux/CMakeLists.txt
+++ b/libc/src/unistd/linux/CMakeLists.txt
@@ -224,12 +224,12 @@ add_entrypoint_object(
   DEPENDS
     libc.hdr.types.size_t
     libc.hdr.types.ssize_t
-    libc.include.sys_syscall
-    libc.include.unistd
+    libc.src.__support.common
     libc.src.__support.CPP.optional
+    libc.src.__support.macros.config
     libc.src.__support.OSUtil.linux.syscall_wrappers.getcwd
-    libc.src.__support.OSUtil.osutil
     libc.src.errno.errno
+    libc.src.string.allocating_string_utils
 )
 
 add_entrypoint_object(
diff --git a/libc/src/unistd/linux/getcwd.cpp b/libc/src/unistd/linux/getcwd.cpp
index 6ac166a5c0611..4a4461ff6ca9a 100644
--- a/libc/src/unistd/linux/getcwd.cpp
+++ b/libc/src/unistd/linux/getcwd.cpp
@@ -12,14 +12,12 @@
 #include "hdr/types/ssize_t.h"
 #include "src/__support/CPP/optional.h"
 #include "src/__support/OSUtil/linux/syscall_wrappers/getcwd.h"
-#include "src/__support/OSUtil/syscall.h" // For internal syscall function.
 #include "src/__support/common.h"
 #include "src/__support/libc_errno.h"
 #include "src/__support/macros/config.h"
 #include "src/string/allocating_string_utils.h" // For strdup.
 
 #include <linux/limits.h> // This is safe to include without any name pollution.
-#include <sys/syscall.h>  // For syscall numbers.
 
 namespace LIBC_NAMESPACE_DECL {
 



More information about the libc-commits mailing list