diff --git a/libc/config/linux/aarch64/entrypoints.txt b/libc/config/linux/aarch64/entrypoints.txt index fcf1278eae723..9e042cd4a8acb 100644 --- a/libc/config/linux/aarch64/entrypoints.txt +++ b/libc/config/linux/aarch64/entrypoints.txt @@ -972,6 +972,7 @@ if(LLVM_LIBC_FULL_BUILD) libc.src.stdio.getc_unlocked libc.src.stdio.getchar libc.src.stdio.getchar_unlocked + libc.src.stdio.perror libc.src.stdio.putc libc.src.stdio.putchar libc.src.stdio.puts diff --git a/libc/config/linux/riscv/entrypoints.txt b/libc/config/linux/riscv/entrypoints.txt index 050fc2672a57e..db8f8a7cf0b74 100644 --- a/libc/config/linux/riscv/entrypoints.txt +++ b/libc/config/linux/riscv/entrypoints.txt @@ -1098,6 +1098,7 @@ if(LLVM_LIBC_FULL_BUILD) libc.src.stdio.getc_unlocked libc.src.stdio.getchar libc.src.stdio.getchar_unlocked + libc.src.stdio.perror libc.src.stdio.putc libc.src.stdio.putchar libc.src.stdio.puts diff --git a/libc/config/linux/x86_64/entrypoints.txt b/libc/config/linux/x86_64/entrypoints.txt index 6c9d83708b92f..c993ef8303a59 100644 --- a/libc/config/linux/x86_64/entrypoints.txt +++ b/libc/config/linux/x86_64/entrypoints.txt @@ -1116,6 +1116,7 @@ if(LLVM_LIBC_FULL_BUILD) libc.src.stdio.getc_unlocked libc.src.stdio.getchar libc.src.stdio.getchar_unlocked + libc.src.stdio.perror libc.src.stdio.putc libc.src.stdio.putchar libc.src.stdio.puts diff --git a/libc/include/stdio.yaml b/libc/include/stdio.yaml index 3d5164fa10ffb..2a0c563709984 100644 --- a/libc/include/stdio.yaml +++ b/libc/include/stdio.yaml @@ -249,6 +249,12 @@ functions: - POSIX return_type: int arguments: [] + - name: perror + standards: + - stdc + return_type: void + arguments: + - type: const char * - name: printf standards: - stdc diff --git a/libc/src/stdio/CMakeLists.txt b/libc/src/stdio/CMakeLists.txt index 63f6ed8a11f1d..b0a6ef1e291b5 100644 --- a/libc/src/stdio/CMakeLists.txt +++ b/libc/src/stdio/CMakeLists.txt @@ -221,6 +221,7 @@ add_stdio_entrypoint_object(fopen) add_stdio_entrypoint_object(fclose) add_stdio_entrypoint_object(fread_unlocked) add_stdio_entrypoint_object(fread) +add_stdio_entrypoint_object(perror) add_stdio_entrypoint_object(puts) add_stdio_entrypoint_object(fputs) add_stdio_entrypoint_object(fwrite_unlocked) diff --git a/libc/src/stdio/generic/CMakeLists.txt b/libc/src/stdio/generic/CMakeLists.txt index e1f4ed5c19497..6361822b61999 100644 --- a/libc/src/stdio/generic/CMakeLists.txt +++ b/libc/src/stdio/generic/CMakeLists.txt @@ -206,6 +206,21 @@ add_generic_entrypoint_object( libc.src.__support.File.platform_file ) +add_generic_entrypoint_object( + perror + SRCS + perror.cpp + HDRS + ../perror.h + DEPENDS + libc.src.errno.errno + libc.src.__support.StringUtil.error_to_string + libc.src.__support.CPP.string_view + libc.src.__support.File.file + libc.src.__support.File.platform_file + libc.src.__support.File.platform_stderr +) + add_generic_entrypoint_object( fputs SRCS diff --git a/libc/src/stdio/generic/perror.cpp b/libc/src/stdio/generic/perror.cpp new file mode 100644 index 0000000000000..68b4ad644caab --- /dev/null +++ b/libc/src/stdio/generic/perror.cpp @@ -0,0 +1,81 @@ +//===-- Implementation of perror ------------------------------------------===// +// +// 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/stdio/perror.h" +#include "src/__support/CPP/string_view.h" +#include "src/__support/File/file.h" +#include "src/__support/StringUtil/error_to_string.h" +#include "src/__support/libc_errno.h" +#include "src/__support/macros/config.h" + +namespace LIBC_NAMESPACE_DECL { + +static int write_out(cpp::string_view str_view, File *f) { + if (str_view.size() > 0) { + auto result = f->write_unlocked(str_view.data(), str_view.size()); + if (result.has_error()) + return result.error; + } + return 0; +} + +// separate function so that we can return early on error but still get the +// unlock. This function sets errno and should not be called elsewhere. +static void write_sequence(cpp::string_view str_view, + cpp::string_view err_str) { + int write_err; + // TODO: this seems like there should be some sort of queue system to + // deduplicate this code. + + // FORMAT: + // if str != nullptr and doesn't start with a null byte: + // "[str]: [strerror(errno)]\n" + // else + // "[strerror(errno)]\n" + if (str_view.size() > 0) { + write_err = write_out(str_view, LIBC_NAMESPACE::stderr); + if (write_err != 0) { + libc_errno = write_err; + return; + } + + write_err = write_out(": ", LIBC_NAMESPACE::stderr); + if (write_err != 0) { + libc_errno = write_err; + return; + } + } + + write_err = write_out(err_str, LIBC_NAMESPACE::stderr); + if (write_err != 0) { + libc_errno = write_err; + return; + } + + write_err = write_out("\n", LIBC_NAMESPACE::stderr); + if (write_err != 0) { + libc_errno = write_err; + return; + } +} + +LLVM_LIBC_FUNCTION(void, perror, (const char *str)) { + const char empty_str[1] = {'\0'}; + if (str == nullptr) + str = empty_str; + cpp::string_view str_view(str); + + cpp::string_view err_str = get_error_string(libc_errno); + + // We need to lock the stream to ensure the newline is always appended. + LIBC_NAMESPACE::stderr->lock(); + write_sequence(str_view, err_str); + LIBC_NAMESPACE::stderr->unlock(); +} + +} // namespace LIBC_NAMESPACE_DECL diff --git a/libc/src/stdio/perror.h b/libc/src/stdio/perror.h new file mode 100644 index 0000000000000..bf8d0af1df5d7 --- /dev/null +++ b/libc/src/stdio/perror.h @@ -0,0 +1,20 @@ +//===-- Implementation header of perror -------------------------*- 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_STDIO_PERROR_H +#define LLVM_LIBC_SRC_STDIO_PERROR_H + +#include "src/__support/macros/config.h" + +namespace LIBC_NAMESPACE_DECL { + +void perror(const char *s); + +} // namespace LIBC_NAMESPACE_DECL + +#endif // LLVM_LIBC_SRC_STDIO_PERROR_H diff --git a/libc/test/src/stdio/CMakeLists.txt b/libc/test/src/stdio/CMakeLists.txt index 01904a30504ed..ce2171f19597b 100644 --- a/libc/test/src/stdio/CMakeLists.txt +++ b/libc/test/src/stdio/CMakeLists.txt @@ -357,6 +357,18 @@ add_libc_test( libc.src.stdio.puts ) +add_libc_test( + perror_test + HERMETIC_TEST_ONLY # writes to libc's stderr + SUITE + libc_stdio_unittests + SRCS + perror_test.cpp + DEPENDS + libc.src.stdio.perror + libc.src.errno.errno +) + add_libc_test( fputs_test HERMETIC_TEST_ONLY # writes to libc's stdout and stderr diff --git a/libc/test/src/stdio/perror_test.cpp b/libc/test/src/stdio/perror_test.cpp new file mode 100644 index 0000000000000..9a97be2eff210 --- /dev/null +++ b/libc/test/src/stdio/perror_test.cpp @@ -0,0 +1,32 @@ +//===-- Unittests for perror ---------------------------------------------===// +// +// 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/stdio/perror.h" + +#include "src/__support/libc_errno.h" +#include "test/UnitTest/Test.h" + +// The standard says perror prints directly to stderr and returns nothing. This +// makes it rather difficult to test automatically. + +// TODO: figure out redirecting stderr so this test can check correctness. +TEST(LlvmLibcPerrorTest, PrintOut) { + LIBC_NAMESPACE::libc_errno = 0; + constexpr char simple[] = "A simple string"; + LIBC_NAMESPACE::perror(simple); + + // stick to stdc errno values, specifically 0, EDOM, ERANGE, and EILSEQ. + LIBC_NAMESPACE::libc_errno = EDOM; + LIBC_NAMESPACE::perror("Print this and an error"); + + LIBC_NAMESPACE::libc_errno = EILSEQ; + LIBC_NAMESPACE::perror("\0 shouldn't print this."); + + LIBC_NAMESPACE::libc_errno = ERANGE; + LIBC_NAMESPACE::perror(nullptr); +}