diff --git a/libc/config/linux/api.td b/libc/config/linux/api.td --- a/libc/config/linux/api.td +++ b/libc/config/linux/api.td @@ -12,6 +12,13 @@ }]; } +def SSizeT : TypeDecl<"ssize_t"> { + let Decl = [{ + #define __need_ssize_t + #include <__posix-types.h> + }]; +} + def OffT : TypeDecl<"off_t"> { let Decl = [{ #define __need_off_t @@ -308,3 +315,14 @@ "thrd_join", ]; } + +def UniStdAPI : PublicAPI<"unistd.h"> { + let TypeDeclarations = [ + SSizeT, + SizeT, + ]; + + let Functions = [ + "write", + ]; +} diff --git a/libc/include/CMakeLists.txt b/libc/include/CMakeLists.txt --- a/libc/include/CMakeLists.txt +++ b/libc/include/CMakeLists.txt @@ -91,6 +91,14 @@ .llvm_libc_common_h ) +add_gen_header( + unistd + DEF_FILE unistd.h.def + GEN_HDR unistd.h + DEPENDS + .llvm_libc_common_h +) + # TODO: Not all platforms will have a include/sys directory. Add the sys # directory and the targets for sys/*.h files conditional to the OS requiring # them. diff --git a/libc/include/__posix-types.h b/libc/include/__posix-types.h --- a/libc/include/__posix-types.h +++ b/libc/include/__posix-types.h @@ -14,3 +14,8 @@ typedef __INT64_TYPE__ off_t; #define __llvm_libc_off_t_defined #endif // __need_off_t + +#if defined(__need_ssize_t) && !defined(__llvm_libc_ssize_t_defined) +typedef __INT64_TYPE__ ssize_t; +#define __llvm_libc_ssize_t_defined +#endif // __need_ssize_t diff --git a/libc/include/unistd.h.def b/libc/include/unistd.h.def new file mode 100644 --- /dev/null +++ b/libc/include/unistd.h.def @@ -0,0 +1,16 @@ +//===-- C standard library header unistd.h --------------------------------===// +// +// 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_UNISTD_H +#define LLVM_LIBC_UNISTD_H + +#include <__llvm-libc-common.h> + +%%public_api() + +#endif // LLVM_LIBC_UNISTD_H diff --git a/libc/lib/CMakeLists.txt b/libc/lib/CMakeLists.txt --- a/libc/lib/CMakeLists.txt +++ b/libc/lib/CMakeLists.txt @@ -35,6 +35,9 @@ libc.src.threads.mtx_unlock libc.src.threads.thrd_create libc.src.threads.thrd_join + + # unistd.h entrypoints + libc.src.unistd.write ) add_entrypoint_library( diff --git a/libc/spec/posix.td b/libc/spec/posix.td --- a/libc/spec/posix.td +++ b/libc/spec/posix.td @@ -12,6 +12,7 @@ def POSIX : StandardSpec<"POSIX"> { NamedType OffTType = NamedType<"off_t">; + NamedType SSizeTType = NamedType<"ssize_t">; HeaderSpec Errno = HeaderSpec< "errno.h", @@ -174,9 +175,27 @@ ] >; + HeaderSpec UniStd = HeaderSpec< + "unistd.h", + [], // Macros + [ + SSizeTType, + SizeTType, + ], + [], // Enumerations + [ + FunctionSpec< + "write", + RetValSpec, + [ArgSpec, ArgSpec, ArgSpec] + >, + ] + >; + let Headers = [ Errno, SysMMan, Signal, + UniStd, ]; } diff --git a/libc/spec/spec.td b/libc/spec/spec.td --- a/libc/spec/spec.td +++ b/libc/spec/spec.td @@ -46,6 +46,7 @@ // Common types def VoidPtr : PtrType; +def ConstVoidPtr : ConstType; def SizeTType : NamedType<"size_t">; def FloatPtr : PtrType; diff --git a/libc/src/CMakeLists.txt b/libc/src/CMakeLists.txt --- a/libc/src/CMakeLists.txt +++ b/libc/src/CMakeLists.txt @@ -8,5 +8,6 @@ # TODO: Add this target conditional to the target OS. add_subdirectory(sys) add_subdirectory(threads) +add_subdirectory(unistd) add_subdirectory(__support) diff --git a/libc/src/unistd/CMakeLists.txt b/libc/src/unistd/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/libc/src/unistd/CMakeLists.txt @@ -0,0 +1,10 @@ +if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/${LIBC_TARGET_OS}) + add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/${LIBC_TARGET_OS}) +endif() + +add_entrypoint_object( + write + ALIAS + DEPENDS + .${LIBC_TARGET_OS}.write +) diff --git a/libc/src/unistd/linux/CMakeLists.txt b/libc/src/unistd/linux/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/libc/src/unistd/linux/CMakeLists.txt @@ -0,0 +1,12 @@ +add_entrypoint_object( + write + SRCS + write.cpp + HDRS + ../write.h + DEPENDS + libc.include.unistd + libc.config.linux.linux_syscall_h + libc.include.sys_syscall + libc.src.errno.__errno_location +) diff --git a/libc/src/unistd/linux/write.cpp b/libc/src/unistd/linux/write.cpp new file mode 100644 --- /dev/null +++ b/libc/src/unistd/linux/write.cpp @@ -0,0 +1,27 @@ +//===-- Linux implementation of write -------------------------------------===// +// +// 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/unistd/write.h" + +#include "config/linux/syscall.h" // For internal syscall function. +#include "include/sys/syscall.h" // For syscall numbers. +#include "src/__support/common.h" +#include "src/errno/llvmlibc_errno.h" + +namespace __llvm_libc { + +ssize_t LLVM_LIBC_ENTRYPOINT(write)(int fd, const void *buf, size_t count) { + long ret = __llvm_libc::syscall(SYS_write, fd, buf, count); + if (ret < 0) { + llvmlibc_errno = -ret; + return -1; + } + return ret; +} + +} // namespace __llvm_libc diff --git a/libc/src/unistd/write.h b/libc/src/unistd/write.h new file mode 100644 --- /dev/null +++ b/libc/src/unistd/write.h @@ -0,0 +1,21 @@ +//===-- Implementation header for write -------------------------*- 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_UNISTD_WRITE_H +#define LLVM_LIBC_SRC_UNISTD_WRITE_H + +#include "include/unistd.h" +#include + +namespace __llvm_libc { + +ssize_t write(int fd, const void *buf, size_t count); + +} // namespace __llvm_libc + +#endif // LLVM_LIBC_SRC_UNISTD_WRITE_H diff --git a/libc/test/src/CMakeLists.txt b/libc/test/src/CMakeLists.txt --- a/libc/test/src/CMakeLists.txt +++ b/libc/test/src/CMakeLists.txt @@ -7,3 +7,4 @@ add_subdirectory(string) add_subdirectory(sys) add_subdirectory(threads) +add_subdirectory(unistd) diff --git a/libc/test/src/unistd/CMakeLists.txt b/libc/test/src/unistd/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/libc/test/src/unistd/CMakeLists.txt @@ -0,0 +1,15 @@ +add_libc_testsuite(libc_unistd_unittests) + +add_libc_unittest( + write_test + SUITE + libc_unistd_unittests + SRCS + write_test.cpp + DEPENDS + libc.src.unistd.write + libc.include.errno + # TODO(sivachandra): Remove redundant deps. + libc.src.errno.__errno_location + libc.include.unistd +) diff --git a/libc/test/src/unistd/write_test.cpp b/libc/test/src/unistd/write_test.cpp new file mode 100644 --- /dev/null +++ b/libc/test/src/unistd/write_test.cpp @@ -0,0 +1,29 @@ +//===-- Unittests for write -----------------------------------------------===// +// +// 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 "include/errno.h" +#include "src/unistd/write.h" +#include "utils/UnitTest/ErrnoSetterMatcher.h" +#include "utils/UnitTest/Test.h" +#include "utils/testutils/FDReader.h" + +TEST(UniStd, WriteBasic) { + using __llvm_libc::testing::ErrnoSetterMatcher::Succeeds; + constexpr const char *hello = "hello"; + __llvm_libc::testutils::FDReader reader; + EXPECT_THAT(__llvm_libc::write(reader.getWriteFD(), hello, 5), Succeeds(5)); + EXPECT_TRUE(reader.matchWritten(hello)); +} + +TEST(UniStd, WriteFails) { + using __llvm_libc::testing::ErrnoSetterMatcher::Fails; + + EXPECT_THAT(__llvm_libc::write(-1, "", 1), Fails(EBADF)); + EXPECT_THAT(__llvm_libc::write(1, reinterpret_cast(-1), 1), + Fails(EFAULT)); +} diff --git a/libc/utils/testutils/CMakeLists.txt b/libc/utils/testutils/CMakeLists.txt --- a/libc/utils/testutils/CMakeLists.txt +++ b/libc/utils/testutils/CMakeLists.txt @@ -1,5 +1,6 @@ if(CMAKE_HOST_UNIX) set(EFFile ExecuteFunctionUnix.cpp) + set(FDReaderFile FDReaderUnix.cpp) endif() add_llvm_library( @@ -8,6 +9,8 @@ StreamWrapper.h ${EFFile} ExecuteFunction.h + ${FDReaderFile} + FDReader.h LINK_COMPONENTS Support ) diff --git a/libc/utils/testutils/FDReader.h b/libc/utils/testutils/FDReader.h new file mode 100644 --- /dev/null +++ b/libc/utils/testutils/FDReader.h @@ -0,0 +1,29 @@ +//===-- FDReader.h ----------------------------------------------*- 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_UTILS_TESTUTILS_FDREADER_H +#define LLVM_LIBC_UTILS_TESTUTILS_FDREADER_H + +namespace __llvm_libc { +namespace testutils { + +class FDReader { + int pipefd[2]; + +public: + FDReader(); + ~FDReader(); + + int getWriteFD() { return pipefd[1]; } + bool matchWritten(const char *); +}; + +} // namespace testutils +} // namespace __llvm_libc + +#endif // LLVM_LIBC_UTILS_TESTUTILS_FDREADER_H diff --git a/libc/utils/testutils/FDReaderUnix.cpp b/libc/utils/testutils/FDReaderUnix.cpp new file mode 100644 --- /dev/null +++ b/libc/utils/testutils/FDReaderUnix.cpp @@ -0,0 +1,41 @@ +//===-- FDReader.cpp ------------------------------------------------------===// +// +// 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 "FDReader.h" +#include "llvm/Support/MemoryBuffer.h" +#include +#include +#include + +namespace __llvm_libc { +namespace testutils { + +FDReader::FDReader() { + int err = ::pipe(pipefd); + assert(!err && "pipe(2) failed"); +} + +FDReader::~FDReader() { + ::close(pipefd[0]); + ::close(pipefd[1]); +} + +bool FDReader::matchWritten(const char *str) { + llvm::ErrorOr> bufOrErr = + llvm::MemoryBuffer::getOpenFile(pipefd[0], "", + /* FileSize (irrelevant) */ 0); + if (!bufOrErr) { + assert(0 && "Error reading from pipe"); + return false; + } + const llvm::MemoryBuffer &buf = **bufOrErr; + return !std::strncmp(buf.getBufferStart(), str, buf.getBufferSize()); +} + +} // namespace testutils +} // namespace __llvm_libc