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 @@ -153,6 +153,7 @@ SimpleMacroDef<"_IOFBF", "0">, SimpleMacroDef<"_IOLBF", "1">, SimpleMacroDef<"_IONBF", "2">, + SimpleMacroDef<"EOF", "-1">, ]; let Types = ["size_t", "FILE", "cookie_io_functions_t"]; } diff --git a/libc/config/linux/x86_64/entrypoints.txt b/libc/config/linux/x86_64/entrypoints.txt --- a/libc/config/linux/x86_64/entrypoints.txt +++ b/libc/config/linux/x86_64/entrypoints.txt @@ -324,6 +324,7 @@ libc.src.stdio.ferror_unlocked libc.src.stdio.fflush libc.src.stdio.fopen + libc.src.stdio.fputs libc.src.stdio.fopencookie libc.src.stdio.fread libc.src.stdio.fread_unlocked @@ -333,6 +334,7 @@ libc.src.stdio.fwrite_unlocked libc.src.stdio.fprintf libc.src.stdio.printf + libc.src.stdio.puts libc.src.stdio.stderr libc.src.stdio.stdout diff --git a/libc/docs/stdio.rst b/libc/docs/stdio.rst --- a/libc/docs/stdio.rst +++ b/libc/docs/stdio.rst @@ -72,7 +72,7 @@ ============= ========= Function_Name Available ============= ========= -remove +remove YES rename tmpnam ============= ========= @@ -91,7 +91,7 @@ getchar fread YES (f)putc -(f)puts +(f)puts YES putchar fwrite YES ungetc diff --git a/libc/spec/stdc.td b/libc/spec/stdc.td --- a/libc/spec/stdc.td +++ b/libc/spec/stdc.td @@ -496,6 +496,7 @@ Macro<"_IOFBF">, Macro<"_IOLBF">, Macro<"_IONBF">, + Macro<"EOF">, ], // Macros [ // Types SizeTType, @@ -534,6 +535,17 @@ [ArgSpec, ArgSpec] >, + FunctionSpec< + "fputs", + RetValSpec, + [ArgSpec, + ArgSpec] + >, + FunctionSpec< + "puts", + RetValSpec, + [ArgSpec] + >, FunctionSpec< "fread", RetValSpec, diff --git a/libc/src/__support/File/file.cpp b/libc/src/__support/File/file.cpp --- a/libc/src/__support/File/file.cpp +++ b/libc/src/__support/File/file.cpp @@ -131,7 +131,7 @@ size_t File::write_unlocked_lbf(const uint8_t *data, size_t len) { constexpr uint8_t NEWLINE_CHAR = '\n'; size_t last_newline = len; - for (size_t i = len; i > 1; --i) { + for (size_t i = len; i >= 1; --i) { if (data[i - 1] == NEWLINE_CHAR) { last_newline = i - 1; break; diff --git a/libc/src/stdio/CMakeLists.txt b/libc/src/stdio/CMakeLists.txt --- a/libc/src/stdio/CMakeLists.txt +++ b/libc/src/stdio/CMakeLists.txt @@ -184,6 +184,31 @@ libc.src.__support.File.platform_file ) +add_entrypoint_object( + fputs + SRCS + fputs.cpp + HDRS + fputs.h + DEPENDS + libc.include.stdio + libc.src.__support.File.file + libc.src.__support.File.platform_file +) + + +add_entrypoint_object( + puts + SRCS + puts.cpp + HDRS + puts.h + DEPENDS + libc.include.stdio + libc.src.__support.File.file + libc.src.__support.File.platform_file +) + add_entrypoint_object( fseek SRCS diff --git a/libc/src/stdio/fputs.h b/libc/src/stdio/fputs.h new file mode 100644 --- /dev/null +++ b/libc/src/stdio/fputs.h @@ -0,0 +1,20 @@ +//===-- Implementation header of fputs --------------------------*- 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_FPUTS_H +#define LLVM_LIBC_SRC_STDIO_FPUTS_H + +#include + +namespace __llvm_libc { + +int fputs(const char *__restrict str, ::FILE *__restrict stream); + +} // namespace __llvm_libc + +#endif // LLVM_LIBC_SRC_STDIO_FPUTS_H diff --git a/libc/src/stdio/fputs.cpp b/libc/src/stdio/fputs.cpp new file mode 100644 --- /dev/null +++ b/libc/src/stdio/fputs.cpp @@ -0,0 +1,30 @@ +//===-- Implementation of fputs -------------------------------------------===// +// +// 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/fputs.h" +#include "src/__support/CPP/string_view.h" +#include "src/__support/File/file.h" + +#include + +namespace __llvm_libc { + +LLVM_LIBC_FUNCTION(int, fputs, + (const char *__restrict str, ::FILE *__restrict stream)) { + cpp::string_view str_view(str); + + size_t written = reinterpret_cast<__llvm_libc::File *>(stream)->write( + str, str_view.size()); + if (str_view.size() != written) { + // The stream should be in an error state in this case. + return EOF; + } + return 0; +} + +} // namespace __llvm_libc diff --git a/libc/src/stdio/puts.h b/libc/src/stdio/puts.h new file mode 100644 --- /dev/null +++ b/libc/src/stdio/puts.h @@ -0,0 +1,20 @@ +//===-- Implementation header of puts ---------------------------*- 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_PUTS_H +#define LLVM_LIBC_SRC_STDIO_PUTS_H + +#include + +namespace __llvm_libc { + +int puts(const char *__restrict str); + +} // namespace __llvm_libc + +#endif // LLVM_LIBC_SRC_STDIO_PUTS_H diff --git a/libc/src/stdio/puts.cpp b/libc/src/stdio/puts.cpp new file mode 100644 --- /dev/null +++ b/libc/src/stdio/puts.cpp @@ -0,0 +1,32 @@ +//===-- Implementation of puts --------------------------------------------===// +// +// 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/puts.h" +#include "src/__support/CPP/string_view.h" +#include "src/__support/File/file.h" + +#include + +namespace __llvm_libc { + +LLVM_LIBC_FUNCTION(int, puts, (const char *__restrict str)) { + cpp::string_view str_view(str); + size_t written = __llvm_libc::stdout->write(str, str_view.size()); + if (str_view.size() != written) { + // The stream should be in an error state in this case. + return EOF; + } + written = __llvm_libc::stdout->write("\n", 1); + if (1 != written) { + // The stream should be in an error state in this case. + return EOF; + } + return 0; +} + +} // namespace __llvm_libc diff --git a/libc/test/src/stdio/CMakeLists.txt b/libc/test/src/stdio/CMakeLists.txt --- a/libc/test/src/stdio/CMakeLists.txt +++ b/libc/test/src/stdio/CMakeLists.txt @@ -15,6 +15,7 @@ libc.src.stdio.ferror libc.src.stdio.fflush libc.src.stdio.fopen + libc.src.stdio.fputs libc.src.stdio.fread libc.src.stdio.fseek libc.src.stdio.fwrite @@ -108,7 +109,16 @@ printf_test.cpp DEPENDS libc.src.stdio.printf - libc.src.fenv.fesetround +) + +add_libc_unittest( + puts_test + SUITE + libc_stdio_unittests + SRCS + puts_test.cpp + DEPENDS + libc.src.stdio.puts ) add_libc_unittest( diff --git a/libc/test/src/stdio/fileop_test.cpp b/libc/test/src/stdio/fileop_test.cpp --- a/libc/test/src/stdio/fileop_test.cpp +++ b/libc/test/src/stdio/fileop_test.cpp @@ -12,6 +12,7 @@ #include "src/stdio/ferror.h" #include "src/stdio/fflush.h" #include "src/stdio/fopen.h" +#include "src/stdio/fputs.h" #include "src/stdio/fread.h" #include "src/stdio/fseek.h" #include "src/stdio/fwrite.h" @@ -66,9 +67,38 @@ ASSERT_NE(errno, 0); errno = 0; + __llvm_libc::clearerr(file); + + // Should be an error to puts. + ASSERT_EQ(EOF, __llvm_libc::fputs(CONTENT, file)); + ASSERT_NE(__llvm_libc::ferror(file), 0); + ASSERT_NE(errno, 0); + errno = 0; + + __llvm_libc::clearerr(file); + ASSERT_EQ(__llvm_libc::ferror(file), 0); + + ASSERT_EQ(__llvm_libc::fclose(file), 0); + + // Now try puts. + file = __llvm_libc::fopen(FILENAME, "w"); + ASSERT_FALSE(file == nullptr); + // fputs returns a negative value on error (EOF) or any non-negative value on + // success. This assert checks that the return value is non-negative. + ASSERT_GE(__llvm_libc::fputs(CONTENT, file), 0); + __llvm_libc::clearerr(file); ASSERT_EQ(__llvm_libc::ferror(file), 0); + ASSERT_EQ(0, __llvm_libc::fclose(file)); + + file = __llvm_libc::fopen(FILENAME, "r"); + ASSERT_FALSE(file == nullptr); + + ASSERT_EQ(__llvm_libc::fread(read_data, 1, sizeof(CONTENT) - 1, file), + sizeof(CONTENT) - 1); + read_data[sizeof(CONTENT) - 1] = '\0'; + ASSERT_STREQ(read_data, CONTENT); ASSERT_EQ(__llvm_libc::fclose(file), 0); } diff --git a/libc/test/src/stdio/puts_test.cpp b/libc/test/src/stdio/puts_test.cpp new file mode 100644 --- /dev/null +++ b/libc/test/src/stdio/puts_test.cpp @@ -0,0 +1,28 @@ +//===-- Unittests for puts ---------------------------------------------===// +// +// 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/puts.h" + +#include "utils/UnitTest/Test.h" + +TEST(LlvmLibcPutsTest, PrintOut) { + int result; + + constexpr char simple[] = "A simple string"; + result = __llvm_libc::puts(simple); + EXPECT_GE(result, 0); + + // check that it appends a second newline at the end. + constexpr char numbers[] = "1234567890\n"; + result = __llvm_libc::puts(numbers); + EXPECT_GE(result, 0); + + constexpr char more[] = "1234 and more\n6789 and rhyme"; + result = __llvm_libc::puts(more); + EXPECT_GE(result, 0); +}