diff --git a/libc/config/linux/x86_64/entrypoints.txt b/libc/config/linux/x86_64/entrypoints.txt index 44191e1d9878..07e57d667e67 100644 --- a/libc/config/linux/x86_64/entrypoints.txt +++ b/libc/config/linux/x86_64/entrypoints.txt @@ -251,8 +251,10 @@ if(LLVM_LIBC_FULL_BUILD) 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/libc/spec/gnu_ext.td b/libc/spec/gnu_ext.td index 1985200fb9d3..ec22e8e0ee59 100644 --- a/libc/spec/gnu_ext.td +++ b/libc/spec/gnu_ext.td @@ -111,10 +111,30 @@ def GnuExtensions : StandardSpec<"GNUExtensions"> { ] >; + HeaderSpec PThread = HeaderSpec< + "pthread.h", + [], // Macros + [], // Types + [], // Enumerations + [ + FunctionSpec< + "pthread_setname_np", + RetValSpec, + [ArgSpec, ArgSpec] + >, + FunctionSpec< + "pthread_getname_np", + RetValSpec, + [ArgSpec, ArgSpec, ArgSpec] + >, + ] + >; + let Headers = [ CType, FEnv, Math, + PThread, StdIO, String, ]; diff --git a/libc/spec/posix.td b/libc/spec/posix.td index 4a8d08d9f7eb..a6560147a7b3 100644 --- a/libc/spec/posix.td +++ b/libc/spec/posix.td @@ -50,7 +50,6 @@ def POSIX : StandardSpec<"POSIX"> { ConstType ConstPThreadMutexTPtr = ConstType; ConstType ConstRestrictedPThreadMutexTPtr = ConstType; - NamedType PThreadTType = NamedType<"pthread_t">; PtrType PThreadTPtr = PtrType; RestrictedPtrType RestrictedPThreadTPtr = RestrictedPtrType; diff --git a/libc/spec/spec.td b/libc/spec/spec.td index d609f8e04ea3..3e5cbd2fedbf 100644 --- a/libc/spec/spec.td +++ b/libc/spec/spec.td @@ -107,6 +107,8 @@ def FILE : NamedType<"FILE">; def FILEPtr : PtrType; def FILERestrictedPtr : RestrictedPtrType; +def PThreadTType : NamedType<"pthread_t">; + //added because __assert_fail needs it. def UnsignedType : NamedType<"unsigned">; diff --git a/libc/src/__support/CPP/stringstream.h b/libc/src/__support/CPP/stringstream.h index 6b2ff881d96f..2fb467061f27 100644 --- a/libc/src/__support/CPP/stringstream.h +++ b/libc/src/__support/CPP/stringstream.h @@ -85,6 +85,8 @@ public: // 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/libc/src/__support/threads/CMakeLists.txt b/libc/src/__support/threads/CMakeLists.txt index 9bfcee4a3ace..41d0c47eb5c5 100644 --- a/libc/src/__support/threads/CMakeLists.txt +++ b/libc/src/__support/threads/CMakeLists.txt @@ -25,6 +25,8 @@ add_header_library( 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/libc/src/__support/threads/linux/CMakeLists.txt b/libc/src/__support/threads/linux/CMakeLists.txt index 5c7eb3ad8683..d26b130d580e 100644 --- a/libc/src/__support/threads/linux/CMakeLists.txt +++ b/libc/src/__support/threads/linux/CMakeLists.txt @@ -30,6 +30,8 @@ add_object_library( 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/libc/src/__support/threads/linux/thread.cpp b/libc/src/__support/threads/linux/thread.cpp index 83c85f9b5128..b1b9634119ea 100644 --- a/libc/src/__support/threads/linux/thread.cpp +++ b/libc/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 #endif +#include +#include #include +#include // For PR_SET_NAME #include // For CLONE_* flags. #include #include // For PROT_* and MAP_* definitions. @@ -33,6 +38,7 @@ static constexpr long MMAP_SYSCALL_NUMBER = SYS_mmap; #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 @@ bool Thread::operator==(const Thread &thread) const { 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::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/libc/src/__support/threads/thread.h b/libc/src/__support/threads/thread.h index 2ca17aa08dcf..088b967ee4b0 100644 --- a/libc/src/__support/threads/thread.h +++ b/libc/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 // For size_t @@ -165,6 +167,12 @@ struct Thread { // 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/libc/src/pthread/CMakeLists.txt b/libc/src/pthread/CMakeLists.txt index 4c45cc53a3e8..7d8a72197214 100644 --- a/libc/src/pthread/CMakeLists.txt +++ b/libc/src/pthread/CMakeLists.txt @@ -301,3 +301,29 @@ add_entrypoint_object( 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/libc/src/pthread/pthread_getname_np.cpp b/libc/src/pthread/pthread_getname_np.cpp new file mode 100644 index 000000000000..b4d538748254 --- /dev/null +++ b/libc/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 +#include + +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 name_buf(buf, len); + cpp::StringStream name_stream(name_buf); + return thread->get_name(name_stream); +} + +} // namespace __llvm_libc diff --git a/libc/src/pthread/pthread_getname_np.h b/libc/src/pthread/pthread_getname_np.h new file mode 100644 index 000000000000..da9c77150e0a --- /dev/null +++ b/libc/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 +#include + +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/libc/src/pthread/pthread_setname_np.cpp b/libc/src/pthread/pthread_setname_np.cpp new file mode 100644 index 000000000000..402cb88ab8b8 --- /dev/null +++ b/libc/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 + +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/libc/src/pthread/pthread_setname_np.h b/libc/src/pthread/pthread_setname_np.h new file mode 100644 index 000000000000..25b8c1f4f5e0 --- /dev/null +++ b/libc/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 + +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/libc/test/integration/src/pthread/CMakeLists.txt b/libc/test/integration/src/pthread/CMakeLists.txt index e751318bb001..09219227f3ab 100644 --- a/libc/test/integration/src/pthread/CMakeLists.txt +++ b/libc/test/integration/src/pthread/CMakeLists.txt @@ -53,3 +53,26 @@ add_integration_test( 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/libc/test/integration/src/pthread/pthread_name_test.cpp b/libc/test/integration/src/pthread/pthread_name_test.cpp new file mode 100644 index 000000000000..85fdd32d4896 --- /dev/null +++ b/libc/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 +#include + +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(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(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( + 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; +}