[libc-commits] [libc] 0b24b47 - [libc] Add the implementation of the fdopen function (#94186)

via libc-commits libc-commits at lists.llvm.org
Fri Jun 14 16:17:48 PDT 2024


Author: Xu Zhang
Date: 2024-06-14T16:17:44-07:00
New Revision: 0b24b4706982907799c838d80e06b3aa08420549

URL: https://github.com/llvm/llvm-project/commit/0b24b4706982907799c838d80e06b3aa08420549
DIFF: https://github.com/llvm/llvm-project/commit/0b24b4706982907799c838d80e06b3aa08420549.diff

LOG: [libc] Add the implementation of the fdopen function (#94186)

Fixes #93711 .
This patch implements the ``fdopen`` function. Given that ``fdopen`` 
internally calls ``fcntl``, the implementation of ``fcntl`` has been
moved to the ``__support/OSUtil``, where it serves as an internal public
function.

Added: 
    libc/src/__support/OSUtil/fcntl.h
    libc/src/__support/OSUtil/linux/fcntl.cpp
    libc/src/stdio/fdopen.h
    libc/src/stdio/linux/fdopen.cpp
    libc/test/src/stdio/fdopen_test.cpp

Modified: 
    libc/config/linux/aarch64/entrypoints.txt
    libc/config/linux/riscv/entrypoints.txt
    libc/config/linux/x86_64/entrypoints.txt
    libc/spec/posix.td
    libc/src/__support/File/linux/file.cpp
    libc/src/__support/File/linux/file.h
    libc/src/__support/OSUtil/linux/CMakeLists.txt
    libc/src/fcntl/linux/CMakeLists.txt
    libc/src/fcntl/linux/fcntl.cpp
    libc/src/stdio/CMakeLists.txt
    libc/src/stdio/linux/CMakeLists.txt
    libc/test/src/fcntl/fcntl_test.cpp
    libc/test/src/stdio/CMakeLists.txt

Removed: 
    


################################################################################
diff  --git a/libc/config/linux/aarch64/entrypoints.txt b/libc/config/linux/aarch64/entrypoints.txt
index 905f7e4871384..dfed6acbdf257 100644
--- a/libc/config/linux/aarch64/entrypoints.txt
+++ b/libc/config/linux/aarch64/entrypoints.txt
@@ -201,6 +201,7 @@ set(TARGET_LIBC_ENTRYPOINTS
     libc.src.stdio.snprintf
     libc.src.stdio.vsprintf
     libc.src.stdio.vsnprintf
+    libc.src.stdio.fdopen
     #libc.src.stdio.sscanf
     #libc.src.stdio.scanf
     #libc.src.stdio.fscanf

diff  --git a/libc/config/linux/riscv/entrypoints.txt b/libc/config/linux/riscv/entrypoints.txt
index 67abf851b4d50..e12d6b3957e51 100644
--- a/libc/config/linux/riscv/entrypoints.txt
+++ b/libc/config/linux/riscv/entrypoints.txt
@@ -209,6 +209,7 @@ set(TARGET_LIBC_ENTRYPOINTS
     libc.src.stdio.sscanf
     libc.src.stdio.scanf
     libc.src.stdio.fscanf
+    libc.src.stdio.fdopen
 
     # sys/mman.h entrypoints
     libc.src.sys.mman.madvise

diff  --git a/libc/config/linux/x86_64/entrypoints.txt b/libc/config/linux/x86_64/entrypoints.txt
index a5a634f61b7e7..cfe35167ca32e 100644
--- a/libc/config/linux/x86_64/entrypoints.txt
+++ b/libc/config/linux/x86_64/entrypoints.txt
@@ -214,6 +214,7 @@ set(TARGET_LIBC_ENTRYPOINTS
     libc.src.stdio.scanf
     libc.src.stdio.fscanf
     libc.src.stdio.fileno
+    libc.src.stdio.fdopen
 
     # sys/epoll.h entrypoints
     libc.src.sys.epoll.epoll_create

diff  --git a/libc/spec/posix.td b/libc/spec/posix.td
index 40b9fa09dd61c..d14047548e104 100644
--- a/libc/spec/posix.td
+++ b/libc/spec/posix.td
@@ -1345,6 +1345,11 @@ def POSIX : StandardSpec<"POSIX"> {
             RetValSpec<IntType>,
             [ArgSpec<FILEPtr>]
           >,
+          FunctionSpec<
+            "fdopen",
+            RetValSpec<FILEPtr>,
+            [ArgSpec<IntType>, ArgSpec<ConstCharPtr>]
+          >,
       ]
   >;
 

diff  --git a/libc/src/__support/File/linux/file.cpp b/libc/src/__support/File/linux/file.cpp
index b84da64cbe635..00ff93846c6bb 100644
--- a/libc/src/__support/File/linux/file.cpp
+++ b/libc/src/__support/File/linux/file.cpp
@@ -8,10 +8,10 @@
 
 #include "file.h"
 
-#include "src/__support/File/file.h"
-
 #include "src/__support/CPP/new.h"
+#include "src/__support/File/file.h"
 #include "src/__support/File/linux/lseekImpl.h"
+#include "src/__support/OSUtil/fcntl.h"
 #include "src/__support/OSUtil/syscall.h" // For internal syscall function.
 #include "src/errno/libc_errno.h"         // For error macros
 
@@ -119,6 +119,60 @@ ErrorOr<File *> openfile(const char *path, const char *mode) {
   return file;
 }
 
+ErrorOr<LinuxFile *> create_file_from_fd(int fd, const char *mode) {
+  using ModeFlags = File::ModeFlags;
+  ModeFlags modeflags = File::mode_flags(mode);
+  if (modeflags == 0) {
+    return Error(EINVAL);
+  }
+
+  int fd_flags = internal::fcntl(fd, F_GETFL);
+  if (fd_flags == -1) {
+    return Error(EBADF);
+  }
+
+  using OpenMode = File::OpenMode;
+  if (((fd_flags & O_ACCMODE) == O_RDONLY &&
+       !(modeflags & static_cast<ModeFlags>(OpenMode::READ))) ||
+      ((fd_flags & O_ACCMODE) == O_WRONLY &&
+       !(modeflags & static_cast<ModeFlags>(OpenMode::WRITE)))) {
+    return Error(EINVAL);
+  }
+
+  bool do_seek = false;
+  if ((modeflags & static_cast<ModeFlags>(OpenMode::APPEND)) &&
+      !(fd_flags & O_APPEND)) {
+    do_seek = true;
+    if (internal::fcntl(fd, F_SETFL,
+                        reinterpret_cast<void *>(fd_flags | O_APPEND)) == -1) {
+      return Error(EBADF);
+    }
+  }
+
+  uint8_t *buffer;
+  {
+    AllocChecker ac;
+    buffer = new (ac) uint8_t[File::DEFAULT_BUFFER_SIZE];
+    if (!ac) {
+      return Error(ENOMEM);
+    }
+  }
+  AllocChecker ac;
+  auto *file = new (ac)
+      LinuxFile(fd, buffer, File::DEFAULT_BUFFER_SIZE, _IOFBF, true, modeflags);
+  if (!ac) {
+    return Error(ENOMEM);
+  }
+  if (do_seek) {
+    auto result = file->seek(0, SEEK_END);
+    if (!result.has_value()) {
+      free(file);
+      return Error(result.error());
+    }
+  }
+  return file;
+}
+
 int get_fileno(File *f) {
   auto *lf = reinterpret_cast<LinuxFile *>(f);
   return lf->get_fd();

diff  --git a/libc/src/__support/File/linux/file.h b/libc/src/__support/File/linux/file.h
index 24e71b133c481..7d3770e1cdd71 100644
--- a/libc/src/__support/File/linux/file.h
+++ b/libc/src/__support/File/linux/file.h
@@ -29,4 +29,7 @@ class LinuxFile : public File {
   int get_fd() const { return fd; }
 };
 
+// Create a File object and associate it with a fd.
+ErrorOr<LinuxFile *> create_file_from_fd(int fd, const char *mode);
+
 } // namespace LIBC_NAMESPACE

diff  --git a/libc/src/__support/OSUtil/fcntl.h b/libc/src/__support/OSUtil/fcntl.h
new file mode 100644
index 0000000000000..d934545c28d76
--- /dev/null
+++ b/libc/src/__support/OSUtil/fcntl.h
@@ -0,0 +1,17 @@
+//===-- Implementation header of internal fcntl 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
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_SRC___SUPPORT_OSUTIL_FCNTL_H
+#define LLVM_LIBC_SRC___SUPPORT_OSUTIL_FCNTL_H
+
+namespace LIBC_NAMESPACE::internal {
+
+int fcntl(int fd, int cmd, void *arg = nullptr);
+
+} // namespace LIBC_NAMESPACE::internal
+
+#endif // LLVM_LIBC_SRC___SUPPORT_OSUTIL_FCNTL_H

diff  --git a/libc/src/__support/OSUtil/linux/CMakeLists.txt b/libc/src/__support/OSUtil/linux/CMakeLists.txt
index 9a55232d532fe..78b117fd19439 100644
--- a/libc/src/__support/OSUtil/linux/CMakeLists.txt
+++ b/libc/src/__support/OSUtil/linux/CMakeLists.txt
@@ -8,6 +8,7 @@ add_object_library(
   linux_util
   SRCS
     exit.cpp
+    fcntl.cpp
   HDRS
     io.h
     syscall.h
@@ -15,4 +16,9 @@ add_object_library(
     .${LIBC_TARGET_ARCHITECTURE}.linux_${LIBC_TARGET_ARCHITECTURE}_util
     libc.src.__support.common
     libc.src.__support.CPP.string_view
+    libc.src.errno.errno
+    libc.hdr.fcntl_macros
+    libc.hdr.types.struct_flock
+    libc.hdr.types.struct_flock64
+    libc.hdr.types.struct_f_owner_ex
 )

diff  --git a/libc/src/__support/OSUtil/linux/fcntl.cpp b/libc/src/__support/OSUtil/linux/fcntl.cpp
new file mode 100644
index 0000000000000..7dc416a7916df
--- /dev/null
+++ b/libc/src/__support/OSUtil/linux/fcntl.cpp
@@ -0,0 +1,94 @@
+//===-- Implementation of internal fcntl ----------------------------------===//
+//
+// 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/OSUtil/fcntl.h"
+
+#include "hdr/fcntl_macros.h"
+#include "hdr/types/struct_f_owner_ex.h"
+#include "hdr/types/struct_flock.h"
+#include "hdr/types/struct_flock64.h"
+#include "src/__support/OSUtil/syscall.h" // For internal syscall function.
+#include "src/__support/common.h"
+#include "src/errno/libc_errno.h"
+
+#include <stdarg.h>
+#include <sys/syscall.h> // For syscall numbers.
+
+namespace LIBC_NAMESPACE::internal {
+
+int fcntl(int fd, int cmd, void *arg) {
+  switch (cmd) {
+  case F_OFD_SETLKW: {
+    struct flock *flk = reinterpret_cast<struct flock *>(arg);
+    // convert the struct to a flock64
+    struct flock64 flk64;
+    flk64.l_type = flk->l_type;
+    flk64.l_whence = flk->l_whence;
+    flk64.l_start = flk->l_start;
+    flk64.l_len = flk->l_len;
+    flk64.l_pid = flk->l_pid;
+    // create a syscall
+    return LIBC_NAMESPACE::syscall_impl<int>(SYS_fcntl, fd, cmd, &flk64);
+  }
+  case F_OFD_GETLK:
+  case F_OFD_SETLK: {
+    struct flock *flk = reinterpret_cast<struct flock *>(arg);
+    // convert the struct to a flock64
+    struct flock64 flk64;
+    flk64.l_type = flk->l_type;
+    flk64.l_whence = flk->l_whence;
+    flk64.l_start = flk->l_start;
+    flk64.l_len = flk->l_len;
+    flk64.l_pid = flk->l_pid;
+    // create a syscall
+    int retVal = LIBC_NAMESPACE::syscall_impl<int>(SYS_fcntl, fd, cmd, &flk64);
+    // On failure, return
+    if (retVal == -1)
+      return -1;
+    // Check for overflow, i.e. the offsets are not the same when cast
+    // to off_t from off64_t.
+    if (static_cast<off_t>(flk64.l_len) != flk64.l_len ||
+        static_cast<off_t>(flk64.l_start) != flk64.l_start) {
+      libc_errno = EOVERFLOW;
+      return -1;
+    }
+    // Now copy back into flk, in case flk64 got modified
+    flk->l_type = flk64.l_type;
+    flk->l_whence = flk64.l_whence;
+    flk->l_start = flk64.l_start;
+    flk->l_len = flk64.l_len;
+    flk->l_pid = flk64.l_pid;
+    return retVal;
+  }
+  case F_GETOWN: {
+    struct f_owner_ex fex;
+    int retVal =
+        LIBC_NAMESPACE::syscall_impl<int>(SYS_fcntl, fd, F_GETOWN_EX, &fex);
+    if (retVal == -EINVAL)
+      return LIBC_NAMESPACE::syscall_impl<int>(SYS_fcntl, fd, cmd,
+                                               reinterpret_cast<void *>(arg));
+    if (static_cast<unsigned long>(retVal) <= -4096UL)
+      return fex.type == F_OWNER_PGRP ? -fex.pid : fex.pid;
+
+    libc_errno = -retVal;
+    return -1;
+  }
+  // The general case
+  default: {
+    int retVal = LIBC_NAMESPACE::syscall_impl<int>(
+        SYS_fcntl, fd, cmd, reinterpret_cast<void *>(arg));
+    if (retVal >= 0) {
+      return retVal;
+    }
+    libc_errno = -retVal;
+    return -1;
+  }
+  }
+}
+
+} // namespace LIBC_NAMESPACE::internal

diff  --git a/libc/src/fcntl/linux/CMakeLists.txt b/libc/src/fcntl/linux/CMakeLists.txt
index 732b7beac41bf..ee8ae63b8cf06 100644
--- a/libc/src/fcntl/linux/CMakeLists.txt
+++ b/libc/src/fcntl/linux/CMakeLists.txt
@@ -18,12 +18,7 @@ add_entrypoint_object(
     ../fcntl.h
   DEPENDS
     libc.include.fcntl
-    libc.hdr.types.struct_flock
-    libc.hdr.types.struct_flock64
-    libc.hdr.types.struct_f_owner_ex
-    libc.hdr.fcntl_macros
     libc.src.__support.OSUtil.osutil
-    libc.src.errno.errno
 )
 
 add_entrypoint_object(

diff  --git a/libc/src/fcntl/linux/fcntl.cpp b/libc/src/fcntl/linux/fcntl.cpp
index 24a20fb364109..3875889d9d0b9 100644
--- a/libc/src/fcntl/linux/fcntl.cpp
+++ b/libc/src/fcntl/linux/fcntl.cpp
@@ -8,86 +8,20 @@
 
 #include "src/fcntl/fcntl.h"
 
-#include "hdr/fcntl_macros.h"
-#include "hdr/types/struct_f_owner_ex.h"
-#include "hdr/types/struct_flock.h"
-#include "hdr/types/struct_flock64.h"
-#include "src/__support/OSUtil/syscall.h" // For internal syscall function.
+#include "src/__support/OSUtil/fcntl.h"
 #include "src/__support/common.h"
-#include "src/errno/libc_errno.h"
 
 #include <stdarg.h>
-#include <sys/syscall.h> // For syscall numbers.
 
-// The OFD file locks require special handling for LARGEFILES
 namespace LIBC_NAMESPACE {
+
 LLVM_LIBC_FUNCTION(int, fcntl, (int fd, int cmd, ...)) {
   void *arg;
   va_list varargs;
   va_start(varargs, cmd);
   arg = va_arg(varargs, void *);
   va_end(varargs);
-
-  switch (cmd) {
-  case F_SETLKW:
-    return syscall_impl<int>(SYS_fcntl, fd, cmd, arg);
-  case F_OFD_SETLKW: {
-    struct flock *flk = reinterpret_cast<struct flock *>(arg);
-    // convert the struct to a flock64
-    struct flock64 flk64;
-    flk64.l_type = flk->l_type;
-    flk64.l_whence = flk->l_whence;
-    flk64.l_start = flk->l_start;
-    flk64.l_len = flk->l_len;
-    flk64.l_pid = flk->l_pid;
-    // create a syscall
-    return syscall_impl<int>(SYS_fcntl, fd, cmd, &flk64);
-  }
-  case F_OFD_GETLK:
-  case F_OFD_SETLK: {
-    struct flock *flk = reinterpret_cast<struct flock *>(arg);
-    // convert the struct to a flock64
-    struct flock64 flk64;
-    flk64.l_type = flk->l_type;
-    flk64.l_whence = flk->l_whence;
-    flk64.l_start = flk->l_start;
-    flk64.l_len = flk->l_len;
-    flk64.l_pid = flk->l_pid;
-    // create a syscall
-    int retVal = syscall_impl<int>(SYS_fcntl, fd, cmd, &flk64);
-    // On failure, return
-    if (retVal == -1)
-      return -1;
-    // Check for overflow, i.e. the offsets are not the same when cast
-    // to off_t from off64_t.
-    if (static_cast<off_t>(flk64.l_len) != flk64.l_len ||
-        static_cast<off_t>(flk64.l_start) != flk64.l_start) {
-      libc_errno = EOVERFLOW;
-      return -1;
-    }
-    // Now copy back into flk, in case flk64 got modified
-    flk->l_type = flk64.l_type;
-    flk->l_whence = flk64.l_whence;
-    flk->l_start = flk64.l_start;
-    flk->l_len = flk64.l_len;
-    flk->l_pid = flk64.l_pid;
-    return retVal;
-  }
-  case F_GETOWN: {
-    struct f_owner_ex fex;
-    int retVal = syscall_impl<int>(SYS_fcntl, fd, F_GETOWN_EX, &fex);
-    if (retVal == -EINVAL)
-      return syscall_impl<int>(SYS_fcntl, fd, cmd,
-                               reinterpret_cast<void *>(arg));
-    if (static_cast<unsigned long>(retVal) <= -4096UL)
-      return fex.type == F_OWNER_PGRP ? -fex.pid : fex.pid;
-
-    libc_errno = -retVal;
-    return -1;
-  }
-  // The general case
-  default:
-    return syscall_impl<int>(SYS_fcntl, fd, cmd, reinterpret_cast<void *>(arg));
-  }
+  return LIBC_NAMESPACE::internal::fcntl(fd, cmd, arg);
 }
+
 } // namespace LIBC_NAMESPACE

diff  --git a/libc/src/stdio/CMakeLists.txt b/libc/src/stdio/CMakeLists.txt
index 7cf3278b3061c..a659d9e847a9e 100644
--- a/libc/src/stdio/CMakeLists.txt
+++ b/libc/src/stdio/CMakeLists.txt
@@ -230,6 +230,13 @@ add_entrypoint_object(
     .${LIBC_TARGET_OS}.rename
 )
 
+add_entrypoint_object(
+  fdopen
+  ALIAS
+  DEPENDS
+    .${LIBC_TARGET_OS}.fdopen
+)
+
 # These entrypoints have multiple potential implementations.
 add_stdio_entrypoint_object(feof)
 add_stdio_entrypoint_object(feof_unlocked)

diff  --git a/libc/src/stdio/fdopen.h b/libc/src/stdio/fdopen.h
new file mode 100644
index 0000000000000..158a133e7e131
--- /dev/null
+++ b/libc/src/stdio/fdopen.h
@@ -0,0 +1,20 @@
+//===-- Implementation header of open ---------------------------*- 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_FDOPEN_H
+#define LLVM_LIBC_SRC_STDIO_FDOPEN_H
+
+#include <stdio.h>
+
+namespace LIBC_NAMESPACE {
+
+FILE *fdopen(int fd, const char *mode);
+
+} // namespace LIBC_NAMESPACE
+
+#endif // LLVM_LIBC_SRC_STDIO_FDOPEN_H

diff  --git a/libc/src/stdio/linux/CMakeLists.txt b/libc/src/stdio/linux/CMakeLists.txt
index a08ff0ba4832f..fa36732a159be 100644
--- a/libc/src/stdio/linux/CMakeLists.txt
+++ b/libc/src/stdio/linux/CMakeLists.txt
@@ -24,3 +24,15 @@ add_entrypoint_object(
     libc.src.__support.OSUtil.osutil
     libc.src.errno.errno
 )
+
+add_entrypoint_object(
+  fdopen
+  SRCS
+    fdopen.cpp
+  HDRS
+    ../fdopen.h
+  DEPENDS
+    libc.include.stdio
+    libc.src.__support.File.file
+    libc.src.__support.File.platform_file
+)

diff  --git a/libc/src/stdio/linux/fdopen.cpp b/libc/src/stdio/linux/fdopen.cpp
new file mode 100644
index 0000000000000..a1d08eecbfdee
--- /dev/null
+++ b/libc/src/stdio/linux/fdopen.cpp
@@ -0,0 +1,25 @@
+//===-- Implementation of fdopen --------------------------------*- 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
+//
+//===----------------------------------------------------------------------===//
+
+#include "src/stdio/fdopen.h"
+
+#include "src/__support/File/linux/file.h"
+#include "src/errno/libc_errno.h"
+
+namespace LIBC_NAMESPACE {
+
+LLVM_LIBC_FUNCTION(::FILE *, fdopen, (int fd, const char *mode)) {
+  auto result = LIBC_NAMESPACE::create_file_from_fd(fd, mode);
+  if (!result.has_value()) {
+    libc_errno = result.error();
+    return nullptr;
+  }
+  return reinterpret_cast<::FILE *>(result.value());
+}
+
+} // namespace LIBC_NAMESPACE

diff  --git a/libc/test/src/fcntl/fcntl_test.cpp b/libc/test/src/fcntl/fcntl_test.cpp
index c5cbb61b4ed8a..fc909acc1aa0b 100644
--- a/libc/test/src/fcntl/fcntl_test.cpp
+++ b/libc/test/src/fcntl/fcntl_test.cpp
@@ -153,3 +153,13 @@ TEST(LlvmLibcFcntlTest, FcntlGetLkWrite) {
 
   ASSERT_THAT(LIBC_NAMESPACE::close(fd), Succeeds(0));
 }
+
+TEST(LlvmLibcFcntlTest, UseAfterClose) {
+  using LIBC_NAMESPACE::testing::ErrnoSetterMatcher::Succeeds;
+  constexpr const char *TEST_FILE_NAME = "testdata/fcntl_use_after_close.test";
+  auto TEST_FILE = libc_make_test_file_path(TEST_FILE_NAME);
+  int fd = LIBC_NAMESPACE::open(TEST_FILE, O_CREAT | O_TRUNC | O_RDWR, S_IRWXU);
+  ASSERT_THAT(LIBC_NAMESPACE::close(fd), Succeeds(0));
+  ASSERT_EQ(-1, LIBC_NAMESPACE::fcntl(fd, F_GETFL));
+  ASSERT_ERRNO_EQ(EBADF);
+}

diff  --git a/libc/test/src/stdio/CMakeLists.txt b/libc/test/src/stdio/CMakeLists.txt
index 03c43eaefebe5..5eb8c9577893b 100644
--- a/libc/test/src/stdio/CMakeLists.txt
+++ b/libc/test/src/stdio/CMakeLists.txt
@@ -369,6 +369,22 @@ if(${LIBC_TARGET_OS} STREQUAL "linux")
       libc.src.unistd.close
       libc.test.UnitTest.ErrnoSetterMatcher
   )
+
+  add_libc_test(
+    fdopen_test
+    SUITE
+      libc_stdio_unittests
+    SRCS
+      fdopen_test.cpp
+    DEPENDS
+      libc.src.fcntl.open
+      libc.src.stdio.fclose
+      libc.src.stdio.fdopen
+      libc.src.stdio.fgets
+      libc.src.stdio.fputs
+      libc.src.unistd.close
+      libc.test.UnitTest.ErrnoSetterMatcher
+  )
 endif()
 
 add_libc_test(

diff  --git a/libc/test/src/stdio/fdopen_test.cpp b/libc/test/src/stdio/fdopen_test.cpp
new file mode 100644
index 0000000000000..ef36cff2ffbd5
--- /dev/null
+++ b/libc/test/src/stdio/fdopen_test.cpp
@@ -0,0 +1,89 @@
+//===-- Unittest for fdopen -----------------------------------------------===//
+//
+// 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/fdopen.h"
+
+#include "hdr/fcntl_macros.h"
+#include "src/errno/libc_errno.h"
+#include "src/fcntl/open.h"
+#include "src/stdio/fclose.h"
+#include "src/stdio/fgets.h"
+#include "src/stdio/fputs.h"
+#include "src/unistd/close.h"
+#include "test/UnitTest/ErrnoSetterMatcher.h"
+#include "test/UnitTest/Test.h"
+
+#include <sys/stat.h> // For S_IRWXU
+
+TEST(LlvmLibcStdioFdopenTest, WriteAppendRead) {
+  using LIBC_NAMESPACE::testing::ErrnoSetterMatcher::Succeeds;
+  LIBC_NAMESPACE::libc_errno = 0;
+  constexpr const char *TEST_FILE_NAME = "testdata/write_read_append.test";
+  auto TEST_FILE = libc_make_test_file_path(TEST_FILE_NAME);
+  int fd = LIBC_NAMESPACE::open(TEST_FILE, O_CREAT | O_TRUNC | O_RDWR, S_IRWXU);
+  auto *fp = LIBC_NAMESPACE::fdopen(fd, "w");
+  ASSERT_ERRNO_SUCCESS();
+  ASSERT_TRUE(nullptr != fp);
+  constexpr const char HELLO[] = "Hello";
+  LIBC_NAMESPACE::fputs(HELLO, fp);
+  LIBC_NAMESPACE::fclose(fp);
+  ASSERT_ERRNO_SUCCESS();
+
+  constexpr const char LLVM[] = "LLVM";
+  int fd2 = LIBC_NAMESPACE::open(TEST_FILE, O_CREAT | O_RDWR);
+  auto *fp2 = LIBC_NAMESPACE::fdopen(fd2, "a");
+  ASSERT_ERRNO_SUCCESS();
+  ASSERT_TRUE(nullptr != fp2);
+  LIBC_NAMESPACE::fputs(LLVM, fp2);
+  LIBC_NAMESPACE::fclose(fp2);
+  ASSERT_ERRNO_SUCCESS();
+
+  int fd3 = LIBC_NAMESPACE::open(TEST_FILE, O_CREAT | O_RDWR);
+  auto *fp3 = LIBC_NAMESPACE::fdopen(fd3, "r");
+  char buffer[10];
+  LIBC_NAMESPACE::fgets(buffer, sizeof(buffer), fp3);
+  ASSERT_STREQ("HelloLLVM", buffer);
+  LIBC_NAMESPACE::fclose(fp3);
+  ASSERT_ERRNO_SUCCESS();
+}
+
+TEST(LlvmLibcStdioFdopenTest, InvalidFd) {
+  LIBC_NAMESPACE::libc_errno = 0;
+  constexpr const char *TEST_FILE_NAME = "testdata/invalid_fd.test";
+  auto TEST_FILE = libc_make_test_file_path(TEST_FILE_NAME);
+  int fd = LIBC_NAMESPACE::open(TEST_FILE, O_CREAT | O_TRUNC);
+  LIBC_NAMESPACE::close(fd);
+  // With `fd` already closed, `fdopen` should fail and set the `errno` to EBADF
+  auto *fp = LIBC_NAMESPACE::fdopen(fd, "r");
+  ASSERT_ERRNO_EQ(EBADF);
+  ASSERT_TRUE(nullptr == fp);
+}
+
+TEST(LlvmLibcStdioFdopenTest, InvalidMode) {
+  LIBC_NAMESPACE::libc_errno = 0;
+  constexpr const char *TEST_FILE_NAME = "testdata/invalid_mode.test";
+  auto TEST_FILE = libc_make_test_file_path(TEST_FILE_NAME);
+  int fd = LIBC_NAMESPACE::open(TEST_FILE, O_CREAT | O_RDONLY, S_IRWXU);
+  ASSERT_ERRNO_SUCCESS();
+  ASSERT_GT(fd, 0);
+
+  // `Mode` must be one of "r", "w" or "a"
+  auto *fp = LIBC_NAMESPACE::fdopen(fd, "m+");
+  ASSERT_ERRNO_EQ(EINVAL);
+  ASSERT_TRUE(nullptr == fp);
+
+  // If the mode argument is invalid, then `fdopen` returns a nullptr and sets
+  // the `errno` to EINVAL. In this case the `mode` param can only be "r" or
+  // "r+"
+  auto *fp2 = LIBC_NAMESPACE::fdopen(fd, "w");
+  ASSERT_ERRNO_EQ(EINVAL);
+  ASSERT_TRUE(nullptr == fp2);
+  LIBC_NAMESPACE::libc_errno = 0;
+  LIBC_NAMESPACE::close(fd);
+  ASSERT_ERRNO_SUCCESS();
+}


        


More information about the libc-commits mailing list