[libc] Add GNU extension functions pthread_setname_np and pthread_getname_np.

Reviewed By: michaelrj, lntue

Differential Revision: https://reviews.llvm.org/D130872

GitOrigin-RevId: 658c84e4158fc185ff68e8399d0a149bde98a489
diff --git a/config/linux/x86_64/entrypoints.txt b/config/linux/x86_64/entrypoints.txt
index 44191e1..07e57d6 100644
--- a/config/linux/x86_64/entrypoints.txt
+++ b/config/linux/x86_64/entrypoints.txt
@@ -251,8 +251,10 @@
     libc.src.pthread.pthread_create
     libc.src.pthread.pthread_detach
     libc.src.pthread.pthread_equal
+    libc.src.pthread.pthread_getname_np
     libc.src.pthread.pthread_join
     libc.src.pthread.pthread_self
+    libc.src.pthread.pthread_setname_np
     libc.src.pthread.pthread_mutex_destroy
     libc.src.pthread.pthread_mutex_init
     libc.src.pthread.pthread_mutex_lock
diff --git a/spec/gnu_ext.td b/spec/gnu_ext.td
index 1985200..ec22e8e 100644
--- a/spec/gnu_ext.td
+++ b/spec/gnu_ext.td
@@ -111,10 +111,30 @@
       ]
   >;
 
+  HeaderSpec PThread = HeaderSpec<
+      "pthread.h",
+      [], // Macros
+      [], // Types
+      [], // Enumerations
+      [
+          FunctionSpec<
+              "pthread_setname_np",
+              RetValSpec<IntType>,
+              [ArgSpec<PThreadTType>, ArgSpec<ConstCharPtr>]
+          >,
+          FunctionSpec<
+              "pthread_getname_np",
+              RetValSpec<IntType>,
+              [ArgSpec<PThreadTType>, ArgSpec<CharPtr>, ArgSpec<SizeTType>]
+          >,
+      ]
+  >;
+
   let Headers = [
     CType,
     FEnv,
     Math,
+    PThread,
     StdIO,
     String,
   ];
diff --git a/spec/posix.td b/spec/posix.td
index 4a8d08d..a656014 100644
--- a/spec/posix.td
+++ b/spec/posix.td
@@ -50,7 +50,6 @@
   ConstType ConstPThreadMutexTPtr = ConstType<PThreadMutexTPtr>;
   ConstType ConstRestrictedPThreadMutexTPtr = ConstType<RestrictedPThreadMutexTPtr>;
 
-  NamedType PThreadTType = NamedType<"pthread_t">;
   PtrType PThreadTPtr = PtrType<PThreadTType>;
   RestrictedPtrType RestrictedPThreadTPtr = RestrictedPtrType<PThreadTType>;
 
diff --git a/spec/spec.td b/spec/spec.td
index d609f8e..3e5cbd2 100644
--- a/spec/spec.td
+++ b/spec/spec.td
@@ -107,6 +107,8 @@
 def FILEPtr : PtrType<FILE>;
 def FILERestrictedPtr : RestrictedPtrType<FILE>;
 
+def PThreadTType : NamedType<"pthread_t">;
+
 //added because __assert_fail needs it.
 def UnsignedType : NamedType<"unsigned">;
 
diff --git a/src/__support/CPP/stringstream.h b/src/__support/CPP/stringstream.h
index 6b2ff88..2fb4670 100644
--- a/src/__support/CPP/stringstream.h
+++ b/src/__support/CPP/stringstream.h
@@ -85,6 +85,8 @@
 
   // Return true if any write operation(s) failed due to insufficient size.
   bool overflow() const { return err; }
+
+  size_t bufsize() const { return data.size(); }
 };
 
 } // namespace cpp
diff --git a/src/__support/threads/CMakeLists.txt b/src/__support/threads/CMakeLists.txt
index 9bfcee4..41d0c47 100644
--- a/src/__support/threads/CMakeLists.txt
+++ b/src/__support/threads/CMakeLists.txt
@@ -25,6 +25,8 @@
   DEPENDS
     libc.src.__support.common
     libc.src.__support.CPP.atomic
+    libc.src.__support.CPP.string_view
+    libc.src.__support.CPP.stringstream
 )
 
 if(TARGET libc.src.__support.threads.${LIBC_TARGET_OS}.thread)
diff --git a/src/__support/threads/linux/CMakeLists.txt b/src/__support/threads/linux/CMakeLists.txt
index 5c7eb3a..d26b130 100644
--- a/src/__support/threads/linux/CMakeLists.txt
+++ b/src/__support/threads/linux/CMakeLists.txt
@@ -30,6 +30,8 @@
     libc.include.sys_syscall
     libc.src.__support.CPP.atomic
     libc.src.__support.CPP.error
+    libc.src.__support.CPP.stringstream
+    libc.src.__support.CPP.string_view
     libc.src.__support.threads.thread_common
   COMPILE_OPTIONS
     -O3
diff --git a/src/__support/threads/linux/thread.cpp b/src/__support/threads/linux/thread.cpp
index 83c85f9..b1b9634 100644
--- a/src/__support/threads/linux/thread.cpp
+++ b/src/__support/threads/linux/thread.cpp
@@ -8,8 +8,10 @@
 
 #include "src/__support/threads/thread.h"
 #include "config/linux/app.h"
+#include "src/__support/CPP/StringView.h"
 #include "src/__support/CPP/atomic.h"
 #include "src/__support/CPP/error.h"
+#include "src/__support/CPP/stringstream.h"
 #include "src/__support/OSUtil/syscall.h"           // For syscall functions.
 #include "src/__support/threads/linux/futex_word.h" // For FutexWordType
 
@@ -17,7 +19,10 @@
 #include <arm_acle.h>
 #endif
 
+#include <errno.h>
+#include <fcntl.h>
 #include <linux/futex.h>
+#include <linux/prctl.h> // For PR_SET_NAME
 #include <linux/sched.h> // For CLONE_* flags.
 #include <stdint.h>
 #include <sys/mman.h>    // For PROT_* and MAP_* definitions.
@@ -33,6 +38,7 @@
 #error "SYS_mmap or SYS_mmap2 not available on the target platform"
 #endif
 
+static constexpr size_t NAME_SIZE_MAX = 16; // Includes the null terminator
 static constexpr size_t DEFAULT_STACK_SIZE = (1 << 16); // 64KB
 static constexpr uint32_t CLEAR_TID_VALUE = 0xABCD1234;
 static constexpr unsigned CLONE_SYSCALL_FLAGS =
@@ -278,4 +284,95 @@
   return attrib->tid == thread.attrib->tid;
 }
 
+static constexpr cpp::StringView THREAD_NAME_PATH_PREFIX("/proc/self/task/");
+static constexpr size_t THREAD_NAME_PATH_SIZE =
+    THREAD_NAME_PATH_PREFIX.size() +
+    IntegerToString<int>::BUFSIZE + // Size of tid
+    1 +                             // For '/' character
+    5; // For the file name "comm" and the nullterminator.
+
+static void construct_thread_name_file_path(cpp::StringStream &stream,
+                                            int tid) {
+  stream << THREAD_NAME_PATH_PREFIX << tid << '/' << cpp::StringView("comm")
+         << cpp::StringStream::ENDS;
+}
+
+int Thread::set_name(const cpp::StringView &name) {
+  if (name.size() >= NAME_SIZE_MAX)
+    return ERANGE;
+
+  if (*this == self) {
+    // If we are setting the name of the current thread, then we can
+    // use the syscall to set the name.
+    int retval = __llvm_libc::syscall(SYS_prctl, PR_SET_NAME, name.data());
+    if (retval < 0)
+      return -retval;
+    else
+      return 0;
+  }
+
+  char path_name_buffer[THREAD_NAME_PATH_SIZE];
+  cpp::StringStream path_stream(path_name_buffer);
+  construct_thread_name_file_path(path_stream, attrib->tid);
+#ifdef SYS_open
+  int fd = __llvm_libc::syscall(SYS_open, path_name_buffer, O_RDWR);
+#else
+  int fd = __llvm_libc::syscall(SYS_openat, AT_FDCWD, path_name_buffer, O_RDWR);
+#endif
+  if (fd < 0)
+    return -fd;
+
+  int retval = __llvm_libc::syscall(SYS_write, fd, name.data(), name.size());
+  __llvm_libc::syscall(SYS_close, fd);
+
+  if (retval < 0)
+    return -retval;
+  else if (retval != int(name.size()))
+    return EIO;
+  else
+    return 0;
+}
+
+int Thread::get_name(cpp::StringStream &name) const {
+  if (name.bufsize() < NAME_SIZE_MAX)
+    return ERANGE;
+
+  char name_buffer[NAME_SIZE_MAX];
+
+  if (*this == self) {
+    // If we are getting the name of the current thread, then we can
+    // use the syscall to get the name.
+    int retval = __llvm_libc::syscall(SYS_prctl, PR_GET_NAME, name_buffer);
+    if (retval < 0)
+      return -retval;
+    name << name_buffer;
+    return 0;
+  }
+
+  char path_name_buffer[THREAD_NAME_PATH_SIZE];
+  cpp::StringStream path_stream(path_name_buffer);
+  construct_thread_name_file_path(path_stream, attrib->tid);
+#ifdef SYS_open
+  int fd = __llvm_libc::syscall(SYS_open, path_name_buffer, O_RDONLY);
+#else
+  int fd =
+      __llvm_libc::syscall(SYS_openat, AT_FDCWD, path_name_buffer, O_RDONLY);
+#endif
+  if (fd < 0)
+    return -fd;
+
+  int retval = __llvm_libc::syscall(SYS_read, fd, name_buffer, NAME_SIZE_MAX);
+  __llvm_libc::syscall(SYS_close, fd);
+  if (retval < 0)
+    return -retval;
+  if (retval == NAME_SIZE_MAX)
+    return ERANGE;
+  if (name_buffer[retval - 1] == '\n')
+    name_buffer[retval - 1] = '\0';
+  else
+    name_buffer[retval] = '\0';
+  name << name_buffer;
+  return 0;
+}
+
 } // namespace __llvm_libc
diff --git a/src/__support/threads/thread.h b/src/__support/threads/thread.h
index 2ca17aa..088b967 100644
--- a/src/__support/threads/thread.h
+++ b/src/__support/threads/thread.h
@@ -9,7 +9,9 @@
 #ifndef LLVM_LIBC_SRC_SUPPORT_THREADS_THREAD_H
 #define LLVM_LIBC_SRC_SUPPORT_THREADS_THREAD_H
 
+#include "src/__support/CPP/StringView.h"
 #include "src/__support/CPP/atomic.h"
+#include "src/__support/CPP/stringstream.h"
 #include "src/__support/architectures.h"
 
 #include <stddef.h> // For size_t
@@ -165,6 +167,12 @@
 
   // Return true if this thread is equal to the other thread.
   bool operator==(const Thread &other) const;
+
+  // Set the name of the thread. Return the error number on error.
+  int set_name(const cpp::StringView &name);
+
+  // Return the name of the thread in |name|. Return the error number of error.
+  int get_name(cpp::StringStream &name) const;
 };
 
 extern thread_local Thread self;
diff --git a/src/pthread/CMakeLists.txt b/src/pthread/CMakeLists.txt
index 4c45cc5..7d8a721 100644
--- a/src/pthread/CMakeLists.txt
+++ b/src/pthread/CMakeLists.txt
@@ -301,3 +301,29 @@
     libc.include.pthread
     libc.src.__support.threads.thread
 )
+
+add_entrypoint_object(
+  pthread_setname_np
+  SRCS
+    pthread_setname_np.cpp
+  HDRS
+    pthread_setname_np.h
+  DEPENDS
+    libc.include.pthread
+    libc.src.__support.CPP.array_ref
+    libc.src.__support.CPP.string_view
+    libc.src.__support.threads.thread
+)
+
+add_entrypoint_object(
+  pthread_getname_np
+  SRCS
+    pthread_getname_np.cpp
+  HDRS
+    pthread_getname_np.h
+  DEPENDS
+    libc.include.pthread
+    libc.src.__support.CPP.array_ref
+    libc.src.__support.CPP.stringstream
+    libc.src.__support.threads.thread
+)
diff --git a/src/pthread/pthread_getname_np.cpp b/src/pthread/pthread_getname_np.cpp
new file mode 100644
index 0000000..b4d5387
--- /dev/null
+++ b/src/pthread/pthread_getname_np.cpp
@@ -0,0 +1,32 @@
+//===-- Linux implementation of the pthread_setname_np function -----------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+#include "pthread_getname_np.h"
+
+#include "src/__support/CPP/ArrayRef.h"
+#include "src/__support/CPP/StringView.h"
+#include "src/__support/common.h"
+#include "src/__support/threads/thread.h"
+
+#include <pthread.h>
+#include <stddef.h>
+
+namespace __llvm_libc {
+
+static_assert(sizeof(pthread_t) == sizeof(__llvm_libc::Thread),
+              "Mismatch between pthread_t and internal Thread.");
+
+LLVM_LIBC_FUNCTION(int, pthread_getname_np,
+                   (pthread_t th, char *buf, size_t len)) {
+  auto *thread = reinterpret_cast<__llvm_libc::Thread *>(&th);
+  cpp::MutableArrayRef<char> name_buf(buf, len);
+  cpp::StringStream name_stream(name_buf);
+  return thread->get_name(name_stream);
+}
+
+} // namespace __llvm_libc
diff --git a/src/pthread/pthread_getname_np.h b/src/pthread/pthread_getname_np.h
new file mode 100644
index 0000000..da9c771
--- /dev/null
+++ b/src/pthread/pthread_getname_np.h
@@ -0,0 +1,21 @@
+//===-- Implementation header for pthread_getname_np function ---*- C++ -*-===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_SRC_PTHREAD_PTHREAD_GETNAME_NP_H
+#define LLVM_LIBC_SRC_PTHREAD_PTHREAD_GETNAME_NP_H
+
+#include <pthread.h>
+#include <stddef.h>
+
+namespace __llvm_libc {
+
+int pthread_getname_np(pthread_t, char *, size_t);
+
+} // namespace __llvm_libc
+
+#endif // LLVM_LIBC_SRC_PTHREAD_PTHREAD_GETNAME_NP_H
diff --git a/src/pthread/pthread_setname_np.cpp b/src/pthread/pthread_setname_np.cpp
new file mode 100644
index 0000000..402cb88
--- /dev/null
+++ b/src/pthread/pthread_setname_np.cpp
@@ -0,0 +1,28 @@
+//===-- Linux implementation of the pthread_setname_np function -----------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+#include "pthread_setname_np.h"
+
+#include "src/__support/CPP/StringView.h"
+#include "src/__support/CPP/error.h"
+#include "src/__support/common.h"
+#include "src/__support/threads/thread.h"
+
+#include <pthread.h>
+
+namespace __llvm_libc {
+
+static_assert(sizeof(pthread_t) == sizeof(__llvm_libc::Thread),
+              "Mismatch between pthread_t and internal Thread.");
+
+LLVM_LIBC_FUNCTION(int, pthread_setname_np, (pthread_t th, const char *name)) {
+  auto *thread = reinterpret_cast<__llvm_libc::Thread *>(&th);
+  return thread->set_name(cpp::StringView(name));
+}
+
+} // namespace __llvm_libc
diff --git a/src/pthread/pthread_setname_np.h b/src/pthread/pthread_setname_np.h
new file mode 100644
index 0000000..25b8c1f
--- /dev/null
+++ b/src/pthread/pthread_setname_np.h
@@ -0,0 +1,20 @@
+//===-- Implementation header for pthread_setname_np function ---*- C++ -*-===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_SRC_PTHREAD_PTHREAD_SETNAME_NP_H
+#define LLVM_LIBC_SRC_PTHREAD_PTHREAD_SETNAME_NP_H
+
+#include <pthread.h>
+
+namespace __llvm_libc {
+
+int pthread_setname_np(pthread_t, const char *name);
+
+} // namespace __llvm_libc
+
+#endif // LLVM_LIBC_SRC_PTHREAD_PTHREAD_SETNAME_NP_H
diff --git a/test/integration/src/pthread/CMakeLists.txt b/test/integration/src/pthread/CMakeLists.txt
index e751318..0921922 100644
--- a/test/integration/src/pthread/CMakeLists.txt
+++ b/test/integration/src/pthread/CMakeLists.txt
@@ -53,3 +53,26 @@
     libc.src.pthread.pthread_join
     libc.src.pthread.pthread_self
 )
+
+add_integration_test(
+  pthread_name_test
+  SUITE
+    libc-pthread-integration-tests
+  SRCS
+    pthread_name_test.cpp
+  LOADER
+    libc.loader.linux.crt1
+  DEPENDS
+    libc.include.errno
+    libc.include.pthread
+    libc.src.errno.errno
+    libc.src.pthread.pthread_create
+    libc.src.pthread.pthread_getname_np
+    libc.src.pthread.pthread_join
+    libc.src.pthread.pthread_mutex_destroy
+    libc.src.pthread.pthread_mutex_init
+    libc.src.pthread.pthread_mutex_lock
+    libc.src.pthread.pthread_mutex_unlock
+    libc.src.pthread.pthread_self
+    libc.src.pthread.pthread_setname_np
+)
diff --git a/test/integration/src/pthread/pthread_name_test.cpp b/test/integration/src/pthread/pthread_name_test.cpp
new file mode 100644
index 0000000..85fdd32
--- /dev/null
+++ b/test/integration/src/pthread/pthread_name_test.cpp
@@ -0,0 +1,83 @@
+//===-- Tests for pthread_equal -------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+#include "src/__support/CPP/StringView.h"
+#include "src/pthread/pthread_create.h"
+#include "src/pthread/pthread_getname_np.h"
+#include "src/pthread/pthread_join.h"
+#include "src/pthread/pthread_mutex_destroy.h"
+#include "src/pthread/pthread_mutex_init.h"
+#include "src/pthread/pthread_mutex_lock.h"
+#include "src/pthread/pthread_mutex_unlock.h"
+#include "src/pthread/pthread_self.h"
+#include "src/pthread/pthread_setname_np.h"
+
+#include "utils/IntegrationTest/test.h"
+
+#include <errno.h>
+#include <pthread.h>
+
+using StringView = __llvm_libc::cpp::StringView;
+
+char child_thread_name_buffer[16];
+pthread_mutex_t mutex;
+
+static void *child_func(void *) {
+  __llvm_libc::pthread_mutex_lock(&mutex);
+  auto self = __llvm_libc::pthread_self();
+  __llvm_libc::pthread_getname_np(self, child_thread_name_buffer, 16);
+  __llvm_libc::pthread_mutex_unlock(&mutex);
+  return nullptr;
+}
+
+TEST_MAIN() {
+  // We init and lock the mutex so that we guarantee that the child thread is
+  // waiting after startup.
+  ASSERT_EQ(__llvm_libc::pthread_mutex_init(&mutex, nullptr), 0);
+  ASSERT_EQ(__llvm_libc::pthread_mutex_lock(&mutex), 0);
+
+  auto main_thread = __llvm_libc::pthread_self();
+  const char MAIN_THREAD_NAME[] = "main_thread";
+  char thread_name_buffer[16];
+  ASSERT_EQ(__llvm_libc::pthread_setname_np(main_thread, MAIN_THREAD_NAME), 0);
+  ASSERT_EQ(
+      __llvm_libc::pthread_getname_np(main_thread, thread_name_buffer, 16), 0);
+  ASSERT_TRUE(StringView(MAIN_THREAD_NAME)
+                  .equals(StringView(
+                      reinterpret_cast<const char *>(thread_name_buffer))));
+
+  pthread_t th;
+  ASSERT_EQ(__llvm_libc::pthread_create(&th, nullptr, child_func, nullptr), 0);
+  // This new thread should of course not be equal to the main thread.
+  const char CHILD_THREAD_NAME[] = "child_thread";
+  ASSERT_EQ(__llvm_libc::pthread_setname_np(th, CHILD_THREAD_NAME), 0);
+  ASSERT_EQ(__llvm_libc::pthread_getname_np(th, thread_name_buffer, 16), 0);
+  ASSERT_TRUE(StringView(CHILD_THREAD_NAME)
+                  .equals(StringView(
+                      reinterpret_cast<const char *>(thread_name_buffer))));
+
+  ASSERT_EQ(__llvm_libc::pthread_mutex_unlock(&mutex), 0);
+
+  void *retval;
+  ASSERT_EQ(__llvm_libc::pthread_join(th, &retval), 0);
+  ASSERT_EQ(uintptr_t(retval), uintptr_t(nullptr));
+  // Make sure that the child thread saw it name correctly.
+  ASSERT_TRUE(StringView(CHILD_THREAD_NAME)
+                  .equals(StringView(reinterpret_cast<const char *>(
+                      child_thread_name_buffer))));
+
+  __llvm_libc::pthread_mutex_destroy(&mutex);
+
+  ASSERT_EQ(__llvm_libc::pthread_setname_np(main_thread,
+                                            "a really long name for a thread"),
+            ERANGE);
+  char smallbuf[1];
+  ASSERT_EQ(__llvm_libc::pthread_getname_np(main_thread, smallbuf, 1), ERANGE);
+
+  return 0;
+}