| // Copyright 2016 The Fuchsia Authors. All rights reserved. | 
 | // Use of this source code is governed by a BSD-style license that can be | 
 | // found in the LICENSE file. | 
 |  | 
 | #include <dirent.h> | 
 | #include <fcntl.h> | 
 | #include <fidl/fuchsia.io/cpp/wire.h> | 
 | #include <lib/fdio/fdio.h> | 
 | #include <lib/fdio/io.h> | 
 | #include <lib/fdio/namespace.h> | 
 | #include <lib/fdio/private.h> | 
 | #include <lib/fdio/unsafe.h> | 
 | #include <lib/fdio/vfs.h> | 
 | #include <lib/stdcompat/string_view.h> | 
 | #include <lib/zxio/posix_mode.h> | 
 | #include <lib/zxio/types.h> | 
 | #include <poll.h> | 
 | #include <sys/file.h> | 
 | #include <sys/ioctl.h> | 
 | #include <sys/mman.h> | 
 | #include <sys/select.h> | 
 | #include <sys/socket.h> | 
 | #include <sys/stat.h> | 
 | #include <sys/statfs.h> | 
 | #include <sys/statvfs.h> | 
 | #include <sys/uio.h> | 
 | #include <threads.h> | 
 | #include <utime.h> | 
 | #include <zircon/compiler.h> | 
 | #include <zircon/process.h> | 
 | #include <zircon/processargs.h> | 
 | #include <zircon/syscalls.h> | 
 |  | 
 | #include <cstdarg> | 
 | #include <thread> | 
 | #include <utility> | 
 | #include <variant> | 
 |  | 
 | #include <fbl/auto_lock.h> | 
 | #include <fbl/string_buffer.h> | 
 | #include <safemath/checked_math.h> | 
 |  | 
 | #include "sdk/lib/fdio/cleanpath.h" | 
 | #include "sdk/lib/fdio/fdio_unistd.h" | 
 | #include "sdk/lib/fdio/internal.h" | 
 | #include "sdk/lib/fdio/zxio.h" | 
 |  | 
 | namespace fio = fuchsia_io; | 
 |  | 
 | static_assert(IOFLAG_CLOEXEC == FD_CLOEXEC, "Unexpected fdio flags value"); | 
 |  | 
 | // non-thread-safe emulation of unistd io functions using the fdio transports | 
 |  | 
 | // Constexpr function to force initialization at program load time. Otherwise initialization may | 
 | // occur after |__libc_extension_init|, wiping the fd table *after* it has been filled in with valid | 
 | // entries; musl invokes |__libc_start_init| after |__libc_extensions_init|. | 
 | // | 
 | // There are no language guarantees here. C++20 provides the constinit specifier, and clang provides | 
 | // the [[clang::require_constant_initialization]] attribute, but until we are on C++20 this is the | 
 | // best we can do. | 
 | // | 
 | // Note that even moving the initialization to |__libc_extensions_init| doesn't work out in the | 
 | // presence of sanitizers that deliberately initialize with garbage *after* |__libc_extensions_init| | 
 | // runs. | 
 | fdio_state_t __fdio_global_state = []() constexpr { | 
 |   return fdio_state_t{ | 
 |       .cwd_path = fdio_internal::PathBuffer('/'), | 
 |   }; | 
 | } | 
 | (); | 
 |  | 
 | // Verify the O_* flags which align with fuchsia.io. | 
 | static_assert(O_PATH == static_cast<uint32_t>(fio::wire::OpenFlags::kNodeReference), | 
 |               "Open Flag mismatch"); | 
 | static_assert(O_CREAT == static_cast<uint32_t>(fio::wire::OpenFlags::kCreate), | 
 |               "Open Flag mismatch"); | 
 | static_assert(O_EXCL == static_cast<uint32_t>(fio::wire::OpenFlags::kCreateIfAbsent), | 
 |               "Open Flag mismatch"); | 
 | static_assert(O_TRUNC == static_cast<uint32_t>(fio::wire::OpenFlags::kTruncate), | 
 |               "Open Flag mismatch"); | 
 | static_assert(O_DIRECTORY == static_cast<uint32_t>(fio::wire::OpenFlags::kDirectory), | 
 |               "Open Flag mismatch"); | 
 | static_assert(O_APPEND == static_cast<uint32_t>(fio::wire::OpenFlags::kAppend), | 
 |               "Open Flag mismatch"); | 
 |  | 
 | // The mask of "1:1" flags which match between both open flag representations. | 
 | constexpr fio::wire::OpenFlags kZxioFsMask = | 
 |     fio::wire::OpenFlags::kNodeReference | fio::wire::OpenFlags::kCreate | | 
 |     fio::wire::OpenFlags::kCreateIfAbsent | fio::wire::OpenFlags::kTruncate | | 
 |     fio::wire::OpenFlags::kDirectory | fio::wire::OpenFlags::kAppend | | 
 |     fio::wire::OpenFlags::kCloneSameRights; | 
 |  | 
 | // TODO(fxbug.dev/81185): Remove kOpenFlagPosixDeprecated after clients have updated to a newer SDK. | 
 | constexpr fio::wire::OpenFlags kZxioFsFlags = | 
 |     kZxioFsMask | fio::wire::OpenFlags::kPosixWritable | fio::wire::OpenFlags::kPosixExecutable | | 
 |     fio::wire::OpenFlags::kPosixDeprecated | fio::wire::OpenFlags::kNotDirectory | | 
 |     fio::wire::OpenFlags::kCloneSameRights; | 
 |  | 
 | // Verify that the remaining O_* flags don't overlap with the ZXIO_FS flags. | 
 | static_assert(!(O_RDONLY & static_cast<uint32_t>(kZxioFsFlags)), | 
 |               "Unexpected collision with static_cast<uint32_t>(kZxioFsFlags)"); | 
 | static_assert(!(O_WRONLY & static_cast<uint32_t>(kZxioFsFlags)), | 
 |               "Unexpected collision with static_cast<uint32_t>(kZxioFsFlags)"); | 
 | static_assert(!(O_RDWR & static_cast<uint32_t>(kZxioFsFlags)), | 
 |               "Unexpected collision with static_cast<uint32_t>(kZxioFsFlags)"); | 
 | static_assert(!(O_NONBLOCK & static_cast<uint32_t>(kZxioFsFlags)), | 
 |               "Unexpected collision with static_cast<uint32_t>(kZxioFsFlags)"); | 
 | static_assert(!(O_DSYNC & static_cast<uint32_t>(kZxioFsFlags)), | 
 |               "Unexpected collision with static_cast<uint32_t>(kZxioFsFlags)"); | 
 | static_assert(!(O_SYNC & static_cast<uint32_t>(kZxioFsFlags)), | 
 |               "Unexpected collision with static_cast<uint32_t>(kZxioFsFlags)"); | 
 | static_assert(!(O_RSYNC & static_cast<uint32_t>(kZxioFsFlags)), | 
 |               "Unexpected collision with static_cast<uint32_t>(kZxioFsFlags)"); | 
 | static_assert(!(O_NOFOLLOW & static_cast<uint32_t>(kZxioFsFlags)), | 
 |               "Unexpected collision with static_cast<uint32_t>(kZxioFsFlags)"); | 
 | static_assert(!(O_CLOEXEC & static_cast<uint32_t>(kZxioFsFlags)), | 
 |               "Unexpected collision with static_cast<uint32_t>(kZxioFsFlags)"); | 
 | static_assert(!(O_NOCTTY & static_cast<uint32_t>(kZxioFsFlags)), | 
 |               "Unexpected collision with static_cast<uint32_t>(kZxioFsFlags)"); | 
 | static_assert(!(O_ASYNC & static_cast<uint32_t>(kZxioFsFlags)), | 
 |               "Unexpected collision with static_cast<uint32_t>(kZxioFsFlags)"); | 
 | static_assert(!(O_DIRECT & static_cast<uint32_t>(kZxioFsFlags)), | 
 |               "Unexpected collision with static_cast<uint32_t>(kZxioFsFlags)"); | 
 | static_assert(!(O_LARGEFILE & static_cast<uint32_t>(kZxioFsFlags)), | 
 |               "Unexpected collision with static_cast<uint32_t>(kZxioFsFlags)"); | 
 | static_assert(!(O_NOATIME & static_cast<uint32_t>(kZxioFsFlags)), | 
 |               "Unexpected collision with static_cast<uint32_t>(kZxioFsFlags)"); | 
 | static_assert(!(O_TMPFILE & static_cast<uint32_t>(kZxioFsFlags)), | 
 |               "Unexpected collision with static_cast<uint32_t>(kZxioFsFlags)"); | 
 |  | 
 | static fio::wire::OpenFlags fdio_flags_to_zxio(uint32_t flags) { | 
 |   fio::wire::OpenFlags rights = {}; | 
 |   switch (flags & O_ACCMODE) { | 
 |     case O_RDONLY: | 
 |       rights |= fio::wire::OpenFlags::kRightReadable; | 
 |       break; | 
 |     case O_WRONLY: | 
 |       rights |= fio::wire::OpenFlags::kRightWritable; | 
 |       break; | 
 |     case O_RDWR: | 
 |       rights |= fio::wire::OpenFlags::kRightReadable | fio::wire::OpenFlags::kRightWritable; | 
 |       break; | 
 |   } | 
 |  | 
 |   fio::wire::OpenFlags result = rights | fio::wire::OpenFlags::kDescribe | | 
 |                                 (static_cast<fio::wire::OpenFlags>(flags) & kZxioFsMask); | 
 |  | 
 |   if (!(result & fio::wire::OpenFlags::kNodeReference)) { | 
 |     result |= fio::wire::OpenFlags::kPosixWritable | fio::wire::OpenFlags::kPosixExecutable; | 
 |   } | 
 |   return result; | 
 | } | 
 |  | 
 | static uint32_t zxio_flags_to_fdio(fio::wire::OpenFlags flags) { | 
 |   uint32_t result = 0; | 
 |   if ((flags & (fio::wire::OpenFlags::kRightReadable | fio::wire::OpenFlags::kRightWritable)) == | 
 |       (fio::wire::OpenFlags::kRightReadable | fio::wire::OpenFlags::kRightWritable)) { | 
 |     result |= O_RDWR; | 
 |   } else if (flags & fio::wire::OpenFlags::kRightWritable) { | 
 |     result |= O_WRONLY; | 
 |   } else { | 
 |     result |= O_RDONLY; | 
 |   } | 
 |  | 
 |   result |= static_cast<uint32_t>(flags & kZxioFsMask); | 
 |   return result; | 
 | } | 
 |  | 
 | fdio_ptr fdio_iodir(int dirfd, std::string_view& in_out_path) { | 
 |   bool root = cpp20::starts_with(in_out_path, '/'); | 
 |   if (root) { | 
 |     // Since we are sending a request to the root handle, the | 
 |     // rest of the in_out_path should be canonicalized as a relative | 
 |     // path (relative to this root handle). | 
 |     while (cpp20::starts_with(in_out_path, '/')) { | 
 |       in_out_path.remove_prefix(1); | 
 |       if (in_out_path.empty()) { | 
 |         in_out_path = std::string_view("."); | 
 |       } | 
 |     } | 
 |   } | 
 |   fbl::AutoLock lock(&fdio_lock); | 
 |   if (root) { | 
 |     return fdio_root_handle.get(); | 
 |   } | 
 |   if (dirfd == AT_FDCWD) { | 
 |     return fdio_cwd_handle.get(); | 
 |   } | 
 |   return fd_to_io_locked(dirfd); | 
 | } | 
 |  | 
 | namespace fdio_internal { | 
 |  | 
 | zx::status<fdio_ptr> open_at_impl(int dirfd, const char* path, int flags, uint32_t mode, | 
 |                                   bool enforce_eisdir) { | 
 |   // Emulate EISDIR behavior from | 
 |   // http://pubs.opengroup.org/onlinepubs/9699919799/functions/open.html | 
 |   bool flags_incompatible_with_directory = | 
 |       ((flags & ~O_PATH & O_ACCMODE) != O_RDONLY) || (flags & O_CREAT); | 
 |   fio::wire::OpenFlags fio_flags = fdio_flags_to_zxio(static_cast<uint32_t>(flags)); | 
 |  | 
 |   return open_at_impl(dirfd, path, fio_flags, mode, | 
 |                       { | 
 |                           .disallow_directory = enforce_eisdir && flags_incompatible_with_directory, | 
 |                           .allow_absolute_path = true, | 
 |                       }); | 
 | } | 
 |  | 
 | zx::status<fdio_ptr> open_at_impl(int dirfd, const char* path, fio::wire::OpenFlags flags, | 
 |                                   uint32_t mode, OpenAtOptions options) { | 
 |   if (path == nullptr) { | 
 |     return zx::error(ZX_ERR_INVALID_ARGS); | 
 |   } | 
 |   if (path[0] == '\0') { | 
 |     return zx::error(ZX_ERR_NOT_FOUND); | 
 |   } | 
 |  | 
 |   fdio_internal::PathBuffer clean_buffer; | 
 |   bool has_ending_slash; | 
 |   bool cleaned = CleanPath(path, &clean_buffer, &has_ending_slash); | 
 |   if (!cleaned) { | 
 |     return zx::error(ZX_ERR_BAD_PATH); | 
 |   } | 
 |  | 
 |   std::string_view clean = clean_buffer; | 
 |  | 
 |   // Some callers such as the fdio_open_..._at() family do not permit absolute paths. | 
 |   if (!options.allow_absolute_path && cpp20::starts_with(clean, '/')) { | 
 |     return zx::error(ZX_ERR_INVALID_ARGS); | 
 |   } | 
 |  | 
 |   fdio_ptr iodir = fdio_iodir(dirfd, clean); | 
 |   if (iodir == nullptr) { | 
 |     return zx::error(ZX_ERR_BAD_HANDLE); | 
 |   } | 
 |  | 
 |   if (options.disallow_directory && has_ending_slash) { | 
 |     return zx::error(ZX_ERR_NOT_FILE); | 
 |   } | 
 |  | 
 |   if (has_ending_slash) { | 
 |     flags |= fio::wire::OpenFlags::kDirectory; | 
 |   } | 
 |  | 
 |   if (!(flags & fio::wire::OpenFlags::kDirectory)) { | 
 |     // At this point we're not sure if the path refers to a directory. | 
 |     // To emulate EISDIR behavior, if the flags are not compatible with directory, | 
 |     // use this flag to instruct open to error if the path turns out to be a directory. | 
 |     // Otherwise, opening a directory with O_RDWR will incorrectly succeed. | 
 |     if (options.disallow_directory) { | 
 |       flags |= fio::wire::OpenFlags::kNotDirectory; | 
 |     } | 
 |   } | 
 |   if (flags & fio::wire::OpenFlags::kNodeReference) { | 
 |     flags &= fio::wire::kOpenFlagsAllowedWithNodeReference; | 
 |   } | 
 |   return iodir->open(clean, flags, mode); | 
 | } | 
 |  | 
 | // Open |path| from the |dirfd| directory, enforcing the POSIX EISDIR error condition. Specifically, | 
 | // ZX_ERR_NOT_FILE will be returned when opening a directory with write access/O_CREAT. | 
 | zx::status<fdio_ptr> open_at(int dirfd, const char* path, int flags, uint32_t mode) { | 
 |   return open_at_impl(dirfd, path, flags, mode, true); | 
 | } | 
 |  | 
 | // Open |path| from the |dirfd| directory, but allow creating directories/opening them with | 
 | // write access. Note that this differs from POSIX behavior. | 
 | zx::status<fdio_ptr> open_at_ignore_eisdir(int dirfd, const char* path, int flags, uint32_t mode) { | 
 |   return open_at_impl(dirfd, path, flags, mode, false); | 
 | } | 
 |  | 
 | // Open |path| from the current working directory, respecting EISDIR. | 
 | zx::status<fdio_ptr> open(const char* path, int flags, uint32_t mode) { | 
 |   return open_at(AT_FDCWD, path, flags, mode); | 
 | } | 
 |  | 
 | void update_cwd_path(const char* path) __TA_REQUIRES(fdio_cwd_lock) { | 
 |   if (path[0] == '/') { | 
 |     // it's "absolute", but we'll still parse it as relative (from /) | 
 |     // so that we normalize the path (resolving, ., .., //, etc) | 
 |     fdio_cwd_path.Set("/"); | 
 |     path++; | 
 |   } | 
 |  | 
 |   size_t seglen; | 
 |   const char* next; | 
 |   for (; path[0]; path = next) { | 
 |     next = strchr(path, '/'); | 
 |     if (next == nullptr) { | 
 |       seglen = strlen(path); | 
 |       next = path + seglen; | 
 |     } else { | 
 |       seglen = next - path; | 
 |       next++; | 
 |     } | 
 |     if (seglen == 0) { | 
 |       // empty segment, skip | 
 |       continue; | 
 |     } | 
 |     if ((seglen == 1) && (path[0] == '.')) { | 
 |       // no-change segment, skip | 
 |       continue; | 
 |     } | 
 |     if ((seglen == 2) && (path[0] == '.') && (path[1] == '.')) { | 
 |       // parent directory, remove the trailing path segment from cwd_path | 
 |       char* x = strrchr(fdio_cwd_path.data(), '/'); | 
 |       if (x == nullptr) { | 
 |         // shouldn't ever happen | 
 |         goto wat; | 
 |       } | 
 |       // remove the current trailing path segment from cwd | 
 |       if (x == fdio_cwd_path.data()) { | 
 |         // but never remove the first / | 
 |         fdio_cwd_path[1] = 0; | 
 |       } else { | 
 |         x[0] = 0; | 
 |       } | 
 |       continue; | 
 |     } | 
 |     // regular path segment, append to cwd_path | 
 |     size_t len = fdio_cwd_path.length(); | 
 |     if ((len + seglen + 2) >= PATH_MAX) { | 
 |       // doesn't fit, shouldn't happen, but... | 
 |       goto wat; | 
 |     } | 
 |     if (len != 1) { | 
 |       // if len is 1, path is "/", so don't append a '/' | 
 |       fdio_cwd_path.Append('/'); | 
 |     } | 
 |     fdio_cwd_path.Append(path, seglen); | 
 |   } | 
 |   return; | 
 |  | 
 | wat: | 
 |   fdio_cwd_path.Set("(unknown"); | 
 | } | 
 |  | 
 | // Buffer used to store a single path component and its null terminator. | 
 | using NameBuffer = fbl::StringBuffer<NAME_MAX>; | 
 |  | 
 | // Opens the directory containing path | 
 | // | 
 | // Returns the last component of the path in `out`.  If `is_dir_out` is nullptr, | 
 | // a trailing slash will be added to the name if the last component happens to | 
 | // be a directory.  Otherwise, `is_dir_out` will be set to indicate whether the | 
 | // last component is a directory. | 
 | zx::status<fdio_ptr> opendir_containing_at(int dirfd, const char* path, NameBuffer* out, | 
 |                                            bool* is_dir_out) { | 
 |   if (path == nullptr) { | 
 |     return zx::error(ZX_ERR_INVALID_ARGS); | 
 |   } | 
 |  | 
 |   fdio_internal::PathBuffer clean_buffer; | 
 |   bool is_dir; | 
 |   bool cleaned = fdio_internal::CleanPath(path, &clean_buffer, &is_dir); | 
 |   if (!cleaned) { | 
 |     return zx::error(ZX_ERR_BAD_PATH); | 
 |   } | 
 |   std::string_view clean = clean_buffer; | 
 |  | 
 |   fdio_ptr iodir = fdio_iodir(dirfd, clean); | 
 |   if (iodir == nullptr) { | 
 |     return zx::error(ZX_ERR_BAD_HANDLE); | 
 |   } | 
 |  | 
 |   // Split the clean path into everything up to the last slash and the last component. | 
 |   std::string_view name = clean; | 
 |   std::string_view base; | 
 |   auto last_slash = clean.rfind('/'); | 
 |   if (last_slash != std::string_view::npos) { | 
 |     name.remove_prefix(last_slash + 1); | 
 |     base = clean.substr(0, last_slash); | 
 |   } | 
 |  | 
 |   if (name.length() + (is_dir ? 1 : 0) > NAME_MAX) { | 
 |     return zx::error(ZX_ERR_BAD_PATH); | 
 |   } | 
 |  | 
 |   // Copy the trailing 'name' to out. | 
 |   out->Append(name); | 
 |   if (is_dir_out) { | 
 |     *is_dir_out = is_dir; | 
 |   } else if (is_dir) { | 
 |     // TODO(fxbug.dev/37408): Propagate whether path is directory without using | 
 |     // trailing backslash to simplify server-side path parsing. | 
 |     // This might require refactoring trailing backslash checks out of | 
 |     // lower filesystem layers and associated FIDL APIs. | 
 |  | 
 |     out->Append('/'); | 
 |   } | 
 |  | 
 |   if (base.empty() && !cpp20::starts_with(name, '/')) { | 
 |     base = "."; | 
 |   } | 
 |  | 
 |   return iodir->open(base, fdio_flags_to_zxio(O_RDONLY | O_DIRECTORY), 0); | 
 | } | 
 |  | 
 | }  // namespace fdio_internal | 
 |  | 
 | // hook into libc process startup | 
 | // this is called prior to main to set up the fdio world | 
 | // and thus does not use the fdio_lock | 
 | // | 
 | // extern "C" is required here, since the corresponding declaration is in an internal musl header: | 
 | // zircon/third_party/ulib/musl/src/internal/libc.h | 
 | extern "C" __EXPORT void __libc_extensions_init(uint32_t handle_count, zx_handle_t handle[], | 
 |                                                 uint32_t handle_info[], uint32_t name_count, | 
 |                                                 char** names) __TA_NO_THREAD_SAFETY_ANALYSIS { | 
 |   { | 
 |     zx_status_t status = fdio_ns_create(&fdio_root_ns); | 
 |     ZX_ASSERT_MSG(status == ZX_OK, "Failed to create root namespace: %s", | 
 |                   zx_status_get_string(status)); | 
 |   } | 
 |  | 
 |   fdio_ptr use_for_stdio = nullptr; | 
 |  | 
 |   // extract handles we care about | 
 |   for (uint32_t n = 0; n < handle_count; n++) { | 
 |     unsigned arg = PA_HND_ARG(handle_info[n]); | 
 |     zx_handle_t h = handle[n]; | 
 |  | 
 |     // precalculate the fd from |arg|, for FDIO cases to use. | 
 |     unsigned arg_fd = arg & (~FDIO_FLAG_USE_FOR_STDIO); | 
 |  | 
 |     switch (PA_HND_TYPE(handle_info[n])) { | 
 |       case PA_FD: { | 
 |         zx::status io = fdio::create(zx::handle(h)); | 
 |         if (io.is_error()) { | 
 |           continue; | 
 |         } | 
 |         ZX_ASSERT_MSG(arg_fd < FDIO_MAX_FD, | 
 |                       "unreasonably large fd number %u in PA_FD (must be less than %u)", arg_fd, | 
 |                       FDIO_MAX_FD); | 
 |         ZX_ASSERT_MSG(fdio_fdtab[arg_fd].try_set(io.value()), "duplicate fd number %u in PA_FD", | 
 |                       arg_fd); | 
 |  | 
 |         if (arg & FDIO_FLAG_USE_FOR_STDIO) { | 
 |           use_for_stdio = std::move(io.value()); | 
 |         } | 
 |  | 
 |         handle[n] = 0; | 
 |         handle_info[n] = 0; | 
 |  | 
 |         break; | 
 |       } | 
 |       case PA_NS_DIR: | 
 |         if (arg < name_count) { | 
 |           fdio_ns_bind(fdio_root_ns, names[arg], h); | 
 |         } | 
 |         // we always continue here to not steal the | 
 |         // handles from higher level code that may | 
 |         // also need access to the namespace | 
 |         continue; | 
 |       default: | 
 |         // unknown handle, leave it alone | 
 |         continue; | 
 |     } | 
 |   } | 
 |  | 
 |   { | 
 |     const char* cwd = getenv("PWD"); | 
 |     fdio_internal::update_cwd_path(cwd ? cwd : "/"); | 
 |   } | 
 |  | 
 |   if (use_for_stdio == nullptr) { | 
 |     zx::status null = fdio_internal::zxio::create_null(); | 
 |     ZX_ASSERT_MSG(null.is_ok(), "%s", null.status_string()); | 
 |     use_for_stdio = std::move(null.value()); | 
 |   } | 
 |  | 
 |   // configure stdin/out/err if not init'd | 
 |   for (uint32_t n = 0; n < 3; n++) { | 
 |     fdio_fdtab[n].try_set(use_for_stdio); | 
 |   } | 
 |  | 
 |   fdio_ptr default_io = nullptr; | 
 |   auto get_default = [&default_io]() { | 
 |     if (default_io == nullptr) { | 
 |       zx::status default_result = fdio_internal::zxio::create(); | 
 |       ZX_ASSERT_MSG(default_result.is_ok(), "%s", default_result.status_string()); | 
 |       default_io = std::move(default_result.value()); | 
 |     } | 
 |     return default_io; | 
 |   }; | 
 |  | 
 |   zx::status root = fdio_ns_open_root(fdio_root_ns); | 
 |   if (root.is_ok()) { | 
 |     ZX_ASSERT(fdio_root_handle.try_set(root.value())); | 
 |     zx::status cwd = fdio_internal::open(fdio_cwd_path.c_str(), O_RDONLY | O_DIRECTORY, 0); | 
 |     if (cwd.is_ok()) { | 
 |       ZX_ASSERT(fdio_cwd_handle.try_set(cwd.value())); | 
 |     } else { | 
 |       ZX_ASSERT(fdio_cwd_handle.try_set(get_default())); | 
 |     } | 
 |   } else { | 
 |     ZX_ASSERT(fdio_root_handle.try_set(get_default())); | 
 |     ZX_ASSERT(fdio_cwd_handle.try_set(get_default())); | 
 |   } | 
 | } | 
 |  | 
 | // Clean up during process teardown. This runs after atexit hooks in | 
 | // libc. It continues to hold the fdio lock until process exit, to | 
 | // prevent other threads from racing on file descriptors. | 
 | // | 
 | // extern "C" is required here, since the corresponding declaration is in an internal musl header: | 
 | // zircon/third_party/ulib/musl/src/internal/libc.h | 
 | extern "C" __EXPORT void __libc_extensions_fini(void) __TA_ACQUIRE(fdio_lock) { | 
 |   mtx_lock(&fdio_lock); | 
 |   __UNUSED auto root = fdio_root_handle.release(); | 
 |   __UNUSED auto cwd = fdio_cwd_handle.release(); | 
 |   for (auto& var : fdio_fdtab) { | 
 |     __UNUSED fdio_ptr io = var.release(); | 
 |   } | 
 | } | 
 |  | 
 | __EXPORT | 
 | zx_status_t fdio_ns_get_installed(fdio_ns_t** ns) { | 
 |   zx_status_t status = ZX_OK; | 
 |   fbl::AutoLock lock(&fdio_lock); | 
 |   if (fdio_root_ns == nullptr) { | 
 |     status = ZX_ERR_NOT_FOUND; | 
 |   } else { | 
 |     *ns = fdio_root_ns; | 
 |   } | 
 |   return status; | 
 | } | 
 |  | 
 | zx_status_t fdio_wait(const fdio_ptr& io, uint32_t events, zx::time deadline, | 
 |                       uint32_t* out_pending) { | 
 |   zx_handle_t h = ZX_HANDLE_INVALID; | 
 |   zx_signals_t signals = 0; | 
 |   io->wait_begin(events, &h, &signals); | 
 |   if (h == ZX_HANDLE_INVALID) { | 
 |     // Wait operation is not applicable to the handle. | 
 |     return ZX_ERR_WRONG_TYPE; | 
 |   } | 
 |  | 
 |   zx_signals_t pending; | 
 |   zx_status_t status = zx_object_wait_one(h, signals, deadline.get(), &pending); | 
 |   if (status == ZX_OK || status == ZX_ERR_TIMED_OUT) { | 
 |     io->wait_end(pending, &events); | 
 |     if (out_pending != nullptr) { | 
 |       *out_pending = events; | 
 |     } | 
 |   } | 
 |  | 
 |   return status; | 
 | } | 
 |  | 
 | static zx_status_t fdio_stat(const fdio_ptr& io, struct stat* s) { | 
 |   zxio_node_attributes_t attr; | 
 |   zx_status_t status = io->get_attr(&attr); | 
 |   if (status != ZX_OK) { | 
 |     return status; | 
 |   } | 
 |  | 
 |   memset(s, 0, sizeof(struct stat)); | 
 |   s->st_mode = zxio_get_posix_mode(attr.protocols, attr.abilities); | 
 |   s->st_ino = attr.has.id ? attr.id : fio::wire::kInoUnknown; | 
 |   s->st_size = attr.content_size; | 
 |   s->st_blksize = VNATTR_BLKSIZE; | 
 |   s->st_blocks = attr.storage_size / VNATTR_BLKSIZE; | 
 |   s->st_nlink = attr.link_count; | 
 |   s->st_ctim.tv_sec = attr.creation_time / ZX_SEC(1); | 
 |   s->st_ctim.tv_nsec = attr.creation_time % ZX_SEC(1); | 
 |   s->st_mtim.tv_sec = attr.modification_time / ZX_SEC(1); | 
 |   s->st_mtim.tv_nsec = attr.modification_time % ZX_SEC(1); | 
 |   return ZX_OK; | 
 | } | 
 |  | 
 | // The functions from here on provide implementations of fd and path | 
 | // centric posix-y io operations. | 
 |  | 
 | // extern "C" is required here, since the corresponding declaration is in an internal musl header: | 
 | // zircon/third_party/ulib/musl/src/internal/stdio_impl.h | 
 | extern "C" __EXPORT zx_status_t _mmap_get_vmo_from_fd(int mmap_prot, int mmap_flags, int fd, | 
 |                                                       zx_handle_t* out_vmo) { | 
 |   assert(out_vmo != nullptr); | 
 |   fdio_ptr io = fd_to_io(fd); | 
 |   if (io == nullptr) { | 
 |     return ZX_ERR_BAD_HANDLE; | 
 |   } | 
 |  | 
 |   // Convert mmap flags into respective ZXIO flags. | 
 |   zxio_vmo_flags_t zxio_flags = 0; | 
 |  | 
 |   // Handle protection bits and mode flags. | 
 |   zxio_flags |= (mmap_prot & PROT_READ) ? ZXIO_VMO_READ : 0; | 
 |   zxio_flags |= (mmap_prot & PROT_WRITE) ? ZXIO_VMO_WRITE : 0; | 
 |   zxio_flags |= (mmap_prot & PROT_EXEC) ? ZXIO_VMO_EXECUTE : 0; | 
 |   zxio_flags |= (mmap_flags & MAP_PRIVATE) ? ZXIO_VMO_PRIVATE_CLONE : 0; | 
 |   // We cannot specify ZXIO_VMO_SHARED_BUFFER as not all filesystems support shared mappings. | 
 |   // This does not affect behavior of filesystems that do not support writable shared mappings. | 
 |   // Filesystems which support PROT_WRITE + MAP_SHARED can enable the `supports_mmap_shared_write` | 
 |   // option in the fs_test suite to validate this case. | 
 |  | 
 |   return zxio_vmo_get(&io->zxio_storage().io, zxio_flags, out_vmo); | 
 | } | 
 |  | 
 | __EXPORT | 
 | int unlinkat(int dirfd, const char* path, int flags) { | 
 |   fdio_internal::NameBuffer name; | 
 |   bool is_dir; | 
 |   zx::status io = fdio_internal::opendir_containing_at(dirfd, path, &name, &is_dir); | 
 |   if (io.is_error()) { | 
 |     return ERROR(io.status_value()); | 
 |   } | 
 |   if (is_dir) { | 
 |     flags |= AT_REMOVEDIR; | 
 |   } | 
 |   return STATUS(io->unlink(name, flags)); | 
 | } | 
 |  | 
 | __EXPORT | 
 | ssize_t readv(int fd, const struct iovec* iov, int iovcnt) { | 
 |   struct msghdr msg = {}; | 
 |   msg.msg_iov = const_cast<struct iovec*>(iov); | 
 |   msg.msg_iovlen = iovcnt; | 
 |   return recvmsg(fd, &msg, 0); | 
 | } | 
 |  | 
 | __EXPORT | 
 | ssize_t writev(int fd, const struct iovec* iov, int iovcnt) { | 
 |   struct msghdr msg = {}; | 
 |   msg.msg_iov = const_cast<struct iovec*>(iov); | 
 |   msg.msg_iovlen = iovcnt; | 
 |   return sendmsg(fd, &msg, 0); | 
 | } | 
 |  | 
 | __EXPORT | 
 | ssize_t preadv(int fd, const struct iovec* iov, int iovcnt, off_t offset) { | 
 |   zx_off_t zx_offset; | 
 |   if (!safemath::MakeCheckedNum(offset).AssignIfValid(&zx_offset)) { | 
 |     return ERRNO(EINVAL); | 
 |   } | 
 |   fdio_ptr io = fd_to_io(fd); | 
 |   if (io == nullptr) { | 
 |     return ERRNO(EBADF); | 
 |   } | 
 |   const bool blocking = (io->ioflag() & IOFLAG_NONBLOCK) == 0; | 
 |   zx::time deadline = zx::deadline_after(io->rcvtimeo()); | 
 |  | 
 |   zx_iovec_t zx_iov[iovcnt]; | 
 |   for (int i = 0; i < iovcnt; ++i) { | 
 |     zx_iov[i] = { | 
 |         .buffer = iov[i].iov_base, | 
 |         .capacity = iov[i].iov_len, | 
 |     }; | 
 |   } | 
 |  | 
 |   for (;;) { | 
 |     size_t actual; | 
 |     zx_status_t status = | 
 |         zxio_readv_at(&io->zxio_storage().io, zx_offset, zx_iov, iovcnt, 0, &actual); | 
 |     if (status == ZX_ERR_SHOULD_WAIT && blocking) { | 
 |       status = fdio_wait(io, FDIO_EVT_READABLE, deadline, nullptr); | 
 |       if (status == ZX_OK) { | 
 |         continue; | 
 |       } | 
 |       if (status == ZX_ERR_TIMED_OUT) { | 
 |         status = ZX_ERR_SHOULD_WAIT; | 
 |       } | 
 |     } | 
 |     if (status != ZX_OK) { | 
 |       return ERROR(status); | 
 |     } | 
 |     return actual; | 
 |   } | 
 | } | 
 |  | 
 | __EXPORT | 
 | ssize_t pwritev(int fd, const struct iovec* iov, int iovcnt, off_t offset) { | 
 |   zx_off_t zx_offset; | 
 |   if (!safemath::MakeCheckedNum(offset).AssignIfValid(&zx_offset)) { | 
 |     return ERRNO(EINVAL); | 
 |   } | 
 |   fdio_ptr io = fd_to_io(fd); | 
 |   if (io == nullptr) { | 
 |     return ERRNO(EBADF); | 
 |   } | 
 |   const bool blocking = (io->ioflag() & IOFLAG_NONBLOCK) == 0; | 
 |   zx::time deadline = zx::deadline_after(io->sndtimeo()); | 
 |  | 
 |   zx_iovec_t zx_iov[iovcnt]; | 
 |   for (int i = 0; i < iovcnt; ++i) { | 
 |     zx_iov[i] = { | 
 |         .buffer = iov[i].iov_base, | 
 |         .capacity = iov[i].iov_len, | 
 |     }; | 
 |   } | 
 |  | 
 |   for (;;) { | 
 |     size_t actual; | 
 |     zx_status_t status = | 
 |         zxio_writev_at(&io->zxio_storage().io, zx_offset, zx_iov, iovcnt, 0, &actual); | 
 |     if (status == ZX_ERR_SHOULD_WAIT && blocking) { | 
 |       status = fdio_wait(io, FDIO_EVT_WRITABLE, deadline, nullptr); | 
 |       if (status == ZX_OK) { | 
 |         continue; | 
 |       } | 
 |       if (status == ZX_ERR_TIMED_OUT) { | 
 |         status = ZX_ERR_SHOULD_WAIT; | 
 |       } | 
 |     } | 
 |     if (status != ZX_OK) { | 
 |       return ERROR(status); | 
 |     } | 
 |     return actual; | 
 |   } | 
 | } | 
 |  | 
 | __EXPORT | 
 | ssize_t pread(int fd, void* buf, size_t count, off_t offset) { | 
 |   struct iovec iov = {}; | 
 |   iov.iov_base = buf; | 
 |   iov.iov_len = count; | 
 |   return preadv(fd, &iov, 1, offset); | 
 | } | 
 |  | 
 | __EXPORT | 
 | ssize_t pwrite(int fd, const void* buf, size_t count, off_t offset) { | 
 |   struct iovec iov = {}; | 
 |   iov.iov_base = const_cast<void*>(buf); | 
 |   iov.iov_len = count; | 
 |   return pwritev(fd, &iov, 1, offset); | 
 | } | 
 |  | 
 | __EXPORT | 
 | ssize_t read(int fd, void* buf, size_t count) { | 
 |   struct iovec iov = {}; | 
 |   iov.iov_base = buf; | 
 |   iov.iov_len = count; | 
 |   return readv(fd, &iov, 1); | 
 | } | 
 |  | 
 | __EXPORT | 
 | ssize_t write(int fd, const void* buf, size_t count) { | 
 |   struct iovec iov = {}; | 
 |   iov.iov_base = const_cast<void*>(buf); | 
 |   iov.iov_len = count; | 
 |   return writev(fd, &iov, 1); | 
 | } | 
 |  | 
 | __EXPORT | 
 | int close(int fd) { | 
 |   fdio_ptr io = unbind_from_fd(fd); | 
 |   if (io == nullptr) { | 
 |     return ERRNO(EBADF); | 
 |   } | 
 |   std::variant reference = GetLastReference(std::move(io)); | 
 |   auto* ptr = std::get_if<fdio::last_reference>(&reference); | 
 |   if (ptr) { | 
 |     return STATUS(ptr->Close()); | 
 |   } | 
 |   return 0; | 
 | } | 
 |  | 
 | __EXPORT | 
 | int dup2(int oldfd, int newfd) { | 
 |   if (newfd < 0 || newfd >= FDIO_MAX_FD) { | 
 |     return ERRNO(EBADF); | 
 |   } | 
 |   // Don't release under lock. | 
 |   fdio_ptr io_to_close = nullptr; | 
 |   { | 
 |     fbl::AutoLock lock(&fdio_lock); | 
 |     fdio_ptr io = fd_to_io_locked(oldfd); | 
 |     if (io == nullptr) { | 
 |       return ERRNO(EBADF); | 
 |     } | 
 |     io_to_close = fdio_fdtab[newfd].replace(io); | 
 |   } | 
 |   return newfd; | 
 | } | 
 |  | 
 | __EXPORT | 
 | int dup(int oldfd) { | 
 |   fbl::AutoLock lock(&fdio_lock); | 
 |   fdio_ptr io = fd_to_io_locked(oldfd); | 
 |   if (io == nullptr) { | 
 |     return ERRNO(EBADF); | 
 |   } | 
 |   std::optional fd = bind_to_fd_locked(io); | 
 |   if (fd.has_value()) { | 
 |     return fd.value(); | 
 |   } | 
 |   return ERRNO(EMFILE); | 
 | } | 
 |  | 
 | __EXPORT | 
 | int dup3(int oldfd, int newfd, int flags) { | 
 |   // dup3 differs from dup2 in that it fails with EINVAL, rather | 
 |   // than being a no op, on being given the same fd for both old and | 
 |   // new. | 
 |   if (oldfd == newfd) { | 
 |     return ERRNO(EINVAL); | 
 |   } | 
 |  | 
 |   if (flags != 0 && flags != O_CLOEXEC) { | 
 |     return ERRNO(EINVAL); | 
 |   } | 
 |  | 
 |   // TODO(fxbug.dev/30920) Implement O_CLOEXEC. | 
 |   return dup2(oldfd, newfd); | 
 | } | 
 |  | 
 | __EXPORT | 
 | int fcntl(int fd, int cmd, ...) { | 
 | // Note that it is not safe to pull out the int out of the | 
 | // variadic arguments at the top level, as callers are not | 
 | // required to pass anything for many of the commands. | 
 | #define GET_INT_ARG(ARG)       \ | 
 |   va_list args;                \ | 
 |   va_start(args, cmd);         \ | 
 |   int ARG = va_arg(args, int); \ | 
 |   va_end(args) | 
 |  | 
 |   switch (cmd) { | 
 |     case F_DUPFD: | 
 |     case F_DUPFD_CLOEXEC: { | 
 |       // TODO(fxbug.dev/30920) Implement CLOEXEC. | 
 |       GET_INT_ARG(starting_fd); | 
 |       if (starting_fd < 0) { | 
 |         return ERRNO(EINVAL); | 
 |       } | 
 |       fbl::AutoLock lock(&fdio_lock); | 
 |       fdio_ptr io = fd_to_io_locked(fd); | 
 |       if (io == nullptr) { | 
 |         return ERRNO(EBADF); | 
 |       } | 
 |       for (fd = starting_fd; fd < FDIO_MAX_FD; fd++) { | 
 |         if (fdio_fdtab[fd].try_set(io)) { | 
 |           return fd; | 
 |         } | 
 |       } | 
 |       return ERRNO(EMFILE); | 
 |     } | 
 |     case F_GETFD: { | 
 |       fdio_ptr io = fd_to_io(fd); | 
 |       if (io == nullptr) { | 
 |         return ERRNO(EBADF); | 
 |       } | 
 |       int flags = static_cast<int>(io->ioflag() & IOFLAG_FD_FLAGS); | 
 |       // POSIX mandates that the return value be nonnegative if successful. | 
 |       assert(flags >= 0); | 
 |       return flags; | 
 |     } | 
 |     case F_SETFD: { | 
 |       fdio_ptr io = fd_to_io(fd); | 
 |       if (io == nullptr) { | 
 |         return ERRNO(EBADF); | 
 |       } | 
 |       GET_INT_ARG(flags); | 
 |       // TODO(fxbug.dev/30920) Implement CLOEXEC. | 
 |       io->ioflag() &= ~IOFLAG_FD_FLAGS; | 
 |       io->ioflag() |= static_cast<uint32_t>(flags) & IOFLAG_FD_FLAGS; | 
 |       return 0; | 
 |     } | 
 |     case F_GETFL: { | 
 |       fdio_ptr io = fd_to_io(fd); | 
 |       if (io == nullptr) { | 
 |         return ERRNO(EBADF); | 
 |       } | 
 |       fio::wire::OpenFlags flags; | 
 |       zx_status_t r = io->get_flags(&flags); | 
 |       if (r == ZX_ERR_NOT_SUPPORTED) { | 
 |         // We treat this as non-fatal, as it's valid for a remote to | 
 |         // simply not support FCNTL, but we still want to correctly | 
 |         // report the state of the (local) NONBLOCK flag | 
 |         flags = {}; | 
 |         r = ZX_OK; | 
 |       } | 
 |       uint32_t fdio_flags = zxio_flags_to_fdio(flags); | 
 |       if (io->ioflag() & IOFLAG_NONBLOCK) { | 
 |         fdio_flags |= O_NONBLOCK; | 
 |       } | 
 |       if (r != ZX_OK) { | 
 |         return ERROR(r); | 
 |       } | 
 |       return fdio_flags; | 
 |     } | 
 |     case F_SETFL: { | 
 |       fdio_ptr io = fd_to_io(fd); | 
 |       if (io == nullptr) { | 
 |         return ERRNO(EBADF); | 
 |       } | 
 |       GET_INT_ARG(n); | 
 |  | 
 |       fio::wire::OpenFlags flags = fdio_flags_to_zxio(n & ~O_NONBLOCK); | 
 |       zx_status_t r = io->set_flags(flags); | 
 |  | 
 |       // Some remotes don't support setting flags; we | 
 |       // can adjust their local flags anyway if NONBLOCK | 
 |       // is the only bit being toggled. | 
 |       if (r == ZX_ERR_NOT_SUPPORTED && ((n | O_NONBLOCK) == O_NONBLOCK)) { | 
 |         r = ZX_OK; | 
 |       } | 
 |  | 
 |       if (r != ZX_OK) { | 
 |         n = ERROR(r); | 
 |       } else { | 
 |         if (n & O_NONBLOCK) { | 
 |           io->ioflag() |= IOFLAG_NONBLOCK; | 
 |         } else { | 
 |           io->ioflag() &= ~IOFLAG_NONBLOCK; | 
 |         } | 
 |         n = 0; | 
 |       } | 
 |       return n; | 
 |     } | 
 |     case F_GETOWN: | 
 |     case F_SETOWN: | 
 |       // TODO(kulakowski) Socket support. | 
 |       return ERRNO(ENOSYS); | 
 |     case F_GETLK: | 
 |     case F_SETLK: | 
 |     case F_SETLKW: | 
 |       // TODO(kulakowski) Advisory file locking support. | 
 |       return ERRNO(ENOSYS); | 
 |     default: | 
 |       return ERRNO(EINVAL); | 
 |   } | 
 |  | 
 | #undef GET_INT_ARG | 
 | } | 
 |  | 
 | __EXPORT | 
 | int flock(int fd, int operation) { | 
 |   zxio_advisory_lock_req_t lock_req; | 
 |   lock_req.wait = true; | 
 |   if (operation & LOCK_NB) { | 
 |     lock_req.wait = false; | 
 |     operation &= ~LOCK_NB; | 
 |   } | 
 |   switch (operation) { | 
 |     case LOCK_SH: | 
 |       lock_req.type = ADVISORY_LOCK_SHARED; | 
 |       break; | 
 |     case LOCK_EX: | 
 |       lock_req.type = ADVISORY_LOCK_EXCLUSIVE; | 
 |       break; | 
 |     case LOCK_UN: | 
 |       lock_req.type = ADVISORY_LOCK_UNLOCK; | 
 |       break; | 
 |     default: { | 
 |       return ERRNO(EINVAL); | 
 |     } | 
 |   } | 
 |  | 
 |   fdio_t* fdio = fdio_unsafe_fd_to_io(fd); | 
 |   if (fdio == nullptr) { | 
 |     return ERRNO(EBADF); | 
 |   } | 
 |   zxio_t* io = fdio_get_zxio(fdio); | 
 |   zx_status_t status = zxio_get_ops(io)->advisory_lock(io, &lock_req); | 
 |  | 
 |   fdio_unsafe_release(fdio); | 
 |   return STATUS(status); | 
 | } | 
 |  | 
 | __EXPORT | 
 | off_t lseek(int fd, off_t offset, int whence) { | 
 |   fdio_ptr io = fd_to_io(fd); | 
 |   if (io == nullptr) { | 
 |     return ERRNO(EBADF); | 
 |   } | 
 |  | 
 |   static_assert(SEEK_SET == ZXIO_SEEK_ORIGIN_START); | 
 |   static_assert(SEEK_CUR == ZXIO_SEEK_ORIGIN_CURRENT); | 
 |   static_assert(SEEK_END == ZXIO_SEEK_ORIGIN_END); | 
 |  | 
 |   size_t result = 0u; | 
 |   zx_status_t status = zxio_seek(&io->zxio_storage().io, whence, offset, &result); | 
 |   if (status == ZX_ERR_WRONG_TYPE) { | 
 |     // Although 'ESPIPE' is a bit of a misnomer, it is the valid errno | 
 |     // for any fd which does not implement seeking (i.e., for pipes, | 
 |     // sockets, etc). | 
 |     return ERRNO(ESPIPE); | 
 |   } | 
 |   return status != ZX_OK ? ERROR(status) : static_cast<off_t>(result); | 
 | } | 
 |  | 
 | static int truncateat(int dirfd, const char* path, off_t len) { | 
 |   zx::status io = fdio_internal::open_at(dirfd, path, O_WRONLY, 0); | 
 |   if (io.is_error()) { | 
 |     return ERROR(io.status_value()); | 
 |   } | 
 |   if (len < 0) { | 
 |     return ERRNO(EINVAL); | 
 |   } | 
 |   return STATUS(io->truncate(static_cast<uint64_t>(len))); | 
 | } | 
 |  | 
 | __EXPORT | 
 | int truncate(const char* path, off_t len) { return truncateat(AT_FDCWD, path, len); } | 
 |  | 
 | __EXPORT | 
 | int ftruncate(int fd, off_t len) { | 
 |   fdio_ptr io = fd_to_io(fd); | 
 |   if (io == nullptr) { | 
 |     return ERRNO(EBADF); | 
 |   } | 
 |   if (len < 0) { | 
 |     return ERRNO(EINVAL); | 
 |   } | 
 |   return STATUS(io->truncate(static_cast<uint64_t>(len))); | 
 | } | 
 |  | 
 | // Filesystem operations (such as rename and link) which act on multiple paths | 
 | // have some additional complexity on Zircon. These operations (eventually) act | 
 | // on two pairs of variables: a source parent vnode + name, and a target parent | 
 | // vnode + name. However, the loose coupling of these pairs can make their | 
 | // correspondence difficult, especially when accessing each parent vnode may | 
 | // involve crossing various filesystem boundaries. | 
 | // | 
 | // To resolve this problem, these kinds of operations involve: | 
 | // - Opening the source parent vnode directly. | 
 | // - Opening the target parent vnode directly, + acquiring a "vnode token". | 
 | // - Sending the real operation + names to the source parent vnode, along with | 
 | //   the "vnode token" representing the target parent vnode. | 
 | // | 
 | // Using zircon kernel primitives (cookies) to authenticate the vnode token, this | 
 | // allows these multi-path operations to mix absolute / relative paths and cross | 
 | // mount points with ease. | 
 | static int two_path_op_at(int olddirfd, const char* oldpath, int newdirfd, const char* newpath, | 
 |                           two_path_op fdio_t::*op_getter) { | 
 |   fdio_internal::NameBuffer oldname; | 
 |   zx::status io_oldparent = | 
 |       fdio_internal::opendir_containing_at(olddirfd, oldpath, &oldname, nullptr); | 
 |   if (io_oldparent.is_error()) { | 
 |     return ERROR(io_oldparent.status_value()); | 
 |   } | 
 |  | 
 |   fdio_internal::NameBuffer newname; | 
 |   zx::status io_newparent = | 
 |       fdio_internal::opendir_containing_at(newdirfd, newpath, &newname, nullptr); | 
 |   if (io_newparent.is_error()) { | 
 |     return ERROR(io_newparent.status_value()); | 
 |   } | 
 |  | 
 |   zx_handle_t token; | 
 |   zx_status_t status = io_newparent->get_token(&token); | 
 |   if (status != ZX_OK) { | 
 |     return ERROR(status); | 
 |   } | 
 |   return STATUS((io_oldparent.value().get()->*op_getter)(oldname, token, newname)); | 
 | } | 
 |  | 
 | __EXPORT | 
 | int renameat(int olddirfd, const char* oldpath, int newdirfd, const char* newpath) { | 
 |   return two_path_op_at(olddirfd, oldpath, newdirfd, newpath, &fdio_t::rename); | 
 | } | 
 |  | 
 | __EXPORT | 
 | int rename(const char* oldpath, const char* newpath) { | 
 |   return two_path_op_at(AT_FDCWD, oldpath, AT_FDCWD, newpath, &fdio_t::rename); | 
 | } | 
 |  | 
 | __EXPORT | 
 | int linkat(int olddirfd, const char* oldpath, int newdirfd, const char* newpath, int flags) { | 
 |   // Accept AT_SYMLINK_FOLLOW, but ignore it because Fuchsia does not support symlinks yet. | 
 |   if (flags & ~AT_SYMLINK_FOLLOW) { | 
 |     return ERRNO(EINVAL); | 
 |   } | 
 |  | 
 |   return two_path_op_at(olddirfd, oldpath, newdirfd, newpath, &fdio_t::link); | 
 | } | 
 |  | 
 | __EXPORT | 
 | int link(const char* oldpath, const char* newpath) { | 
 |   return two_path_op_at(AT_FDCWD, oldpath, AT_FDCWD, newpath, &fdio_t::link); | 
 | } | 
 |  | 
 | __EXPORT | 
 | int unlink(const char* path) { return unlinkat(AT_FDCWD, path, 0); } | 
 |  | 
 | static int vopenat(int dirfd, const char* path, int flags, va_list args) { | 
 |   uint32_t mode = 0; | 
 |   if (flags & O_CREAT) { | 
 |     if (flags & O_DIRECTORY) { | 
 |       // The behavior of open with O_CREAT | O_DIRECTORY is underspecified | 
 |       // in POSIX. To help avoid programmer error, we explicitly disallow | 
 |       // the combination. | 
 |       return ERRNO(EINVAL); | 
 |     } | 
 |     mode = va_arg(args, uint32_t) & 0777; | 
 |   } | 
 |   zx::status io = fdio_internal::open_at(dirfd, path, flags, mode); | 
 |   if (io.is_error()) { | 
 |     return ERROR(io.status_value()); | 
 |   } | 
 |   if (flags & O_NONBLOCK) { | 
 |     io->ioflag() |= IOFLAG_NONBLOCK; | 
 |   } | 
 |   std::optional fd = bind_to_fd(io.value()); | 
 |   if (fd.has_value()) { | 
 |     return fd.value(); | 
 |   } | 
 |   return ERRNO(EMFILE); | 
 | } | 
 |  | 
 | __EXPORT | 
 | int open(const char* path, int flags, ...) { | 
 |   va_list ap; | 
 |   va_start(ap, flags); | 
 |   int ret = vopenat(AT_FDCWD, path, flags, ap); | 
 |   va_end(ap); | 
 |   return ret; | 
 | } | 
 |  | 
 | __EXPORT | 
 | int openat(int dirfd, const char* path, int flags, ...) { | 
 |   va_list ap; | 
 |   va_start(ap, flags); | 
 |   int ret = vopenat(dirfd, path, flags, ap); | 
 |   va_end(ap); | 
 |   return ret; | 
 | } | 
 |  | 
 | __EXPORT | 
 | int mkdir(const char* path, mode_t mode) { return mkdirat(AT_FDCWD, path, mode); } | 
 |  | 
 | __EXPORT | 
 | int mkdirat(int dirfd, const char* path, mode_t mode) { | 
 |   mode = (mode & 0777) | S_IFDIR; | 
 |  | 
 |   return STATUS(fdio_internal::open_at_ignore_eisdir(dirfd, path, O_RDONLY | O_CREAT | O_EXCL, mode) | 
 |                     .status_value()); | 
 | } | 
 |  | 
 | __EXPORT | 
 | int fsync(int fd) { | 
 |   fdio_ptr io = fd_to_io(fd); | 
 |   if (io == nullptr) { | 
 |     return ERRNO(EBADF); | 
 |   } | 
 |   return STATUS(zxio_sync(&io->zxio_storage().io)); | 
 | } | 
 |  | 
 | __EXPORT | 
 | int fdatasync(int fd) { | 
 |   // TODO(smklein): fdatasync does not need to flush metadata under certain | 
 |   // circumstances -- however, for now, this implementation will appear | 
 |   // functionally the same (if a little slower). | 
 |   return fsync(fd); | 
 | } | 
 |  | 
 | __EXPORT | 
 | int syncfs(int fd) { | 
 |   // TODO(smklein): Currently, fsync syncs the entire filesystem, not just | 
 |   // the target file descriptor. These functions should use different sync | 
 |   // mechanisms, where fsync is more fine-grained. | 
 |   return fsync(fd); | 
 | } | 
 |  | 
 | __EXPORT | 
 | int fstat(int fd, struct stat* s) { | 
 |   fdio_ptr io = fd_to_io(fd); | 
 |   if (io == nullptr) { | 
 |     return ERRNO(EBADF); | 
 |   } | 
 |   return STATUS(fdio_stat(io, s)); | 
 | } | 
 |  | 
 | int fstatat(int dirfd, std::string_view filename, struct stat* s, int flags) { | 
 |   zx::status io = fdio_internal::open_at(dirfd, filename.data(), O_PATH, 0); | 
 |   if (io.is_error()) { | 
 |     return ERROR(io.status_value()); | 
 |   } | 
 |   return STATUS(fdio_stat(io.value(), s)); | 
 | } | 
 |  | 
 | __EXPORT | 
 | int fstatat(int dirfd, const char* fn, struct stat* s, int flags) { | 
 |   return fstatat(dirfd, std::string_view(fn), s, flags); | 
 | } | 
 |  | 
 | __EXPORT | 
 | int stat(const char* fn, struct stat* s) { return fstatat(AT_FDCWD, fn, s, 0); } | 
 |  | 
 | __EXPORT | 
 | int lstat(const char* path, struct stat* buf) { return stat(path, buf); } | 
 |  | 
 | __EXPORT | 
 | char* realpath(const char* __restrict filename, char* __restrict resolved) { | 
 |   ssize_t r; | 
 |   struct stat st; | 
 |   fdio_internal::PathBuffer tmp; | 
 |   bool is_dir; | 
 |  | 
 |   if (!filename) { | 
 |     errno = EINVAL; | 
 |     return nullptr; | 
 |   } | 
 |  | 
 |   if (filename[0] != '/') { | 
 |     // Convert 'filename' from a relative path to an absolute path. | 
 |     size_t file_len = strlen(filename); | 
 |     fdio_internal::PathBuffer tmp2; | 
 |     size_t cwd_len = 0; | 
 |     { | 
 |       fbl::AutoLock cwd_lock(&fdio_cwd_lock); | 
 |       cwd_len = fdio_cwd_path.length(); | 
 |       if (cwd_len + 1 + file_len >= PATH_MAX) { | 
 |         errno = ENAMETOOLONG; | 
 |         return nullptr; | 
 |       } | 
 |       tmp2.Append(fdio_cwd_path); | 
 |     } | 
 |     tmp2.Append('/'); | 
 |     tmp2.Append(filename); | 
 |     bool cleaned = fdio_internal::CleanPath(tmp2.data(), &tmp, &is_dir); | 
 |     if (!cleaned) { | 
 |       errno = EINVAL; | 
 |       return nullptr; | 
 |     } | 
 |   } else { | 
 |     // Clean the provided absolute path | 
 |     bool cleaned = fdio_internal::CleanPath(filename, &tmp, &is_dir); | 
 |     if (!cleaned) { | 
 |       errno = EINVAL; | 
 |       return nullptr; | 
 |     } | 
 |  | 
 |     r = fstatat(AT_FDCWD, tmp, &st, 0); | 
 |     if (r < 0) { | 
 |       return nullptr; | 
 |     } | 
 |   } | 
 |   return resolved ? strcpy(resolved, tmp.c_str()) : strdup(tmp.c_str()); | 
 | } | 
 |  | 
 | static zx_status_t zx_utimens(const fdio_ptr& io, const std::timespec times[2], int flags) { | 
 |   zxio_node_attributes_t attr = {}; | 
 |  | 
 |   zx_time_t modification_time; | 
 |   // Extract modify time. | 
 |   if (times == nullptr || times[1].tv_nsec == UTIME_NOW) { | 
 |     std::timespec ts; | 
 |     if (!std::timespec_get(&ts, TIME_UTC)) { | 
 |       return ZX_ERR_UNAVAILABLE; | 
 |     } | 
 |     modification_time = zx_time_from_timespec(ts); | 
 |   } else { | 
 |     modification_time = zx_time_from_timespec(times[1]); | 
 |   } | 
 |  | 
 |   if (times == nullptr || times[1].tv_nsec != UTIME_OMIT) { | 
 |     // For setattr, tell which fields are valid. | 
 |     ZXIO_NODE_ATTR_SET(attr, modification_time, modification_time); | 
 |   } | 
 |  | 
 |   // set time(s) on underlying object | 
 |   return io->set_attr(&attr); | 
 | } | 
 |  | 
 | __EXPORT | 
 | int utimensat(int dirfd, const char* path, const struct timespec times[2], int flags) { | 
 |   // TODO(orr): AT_SYMLINK_NOFOLLOW | 
 |   if ((flags & AT_SYMLINK_NOFOLLOW) != 0) { | 
 |     // Allow this flag - don't return an error.  Fuchsia does not support | 
 |     // symlinks, so don't break utilities (like tar) that use this flag. | 
 |   } | 
 |   zx::status io = fdio_internal::open_at_ignore_eisdir(dirfd, path, O_WRONLY, 0); | 
 |   if (io.is_error()) { | 
 |     return ERROR(io.status_value()); | 
 |   } | 
 |   return STATUS(zx_utimens(io.value(), times, 0)); | 
 | } | 
 |  | 
 | __EXPORT | 
 | int futimens(int fd, const struct timespec times[2]) { | 
 |   fdio_ptr io = fd_to_io(fd); | 
 |   if (io == nullptr) { | 
 |     return ERRNO(EBADF); | 
 |   } | 
 |   return STATUS(zx_utimens(io, times, 0)); | 
 | } | 
 |  | 
 | static int socketpair_create(int fd[2], uint32_t options, int flags) { | 
 |   constexpr int allowed_flags = O_NONBLOCK | O_CLOEXEC; | 
 |   if (flags & ~allowed_flags) { | 
 |     return ERRNO(EINVAL); | 
 |   } | 
 |  | 
 |   zx::status pair = fdio_internal::pipe::create_pair(options); | 
 |   if (pair.is_error()) { | 
 |     return ERROR(pair.status_value()); | 
 |   } | 
 |   auto [left, right] = pair.value(); | 
 |   std::array<fdio_ptr, 2> ios = {left, right}; | 
 |  | 
 |   if (flags & O_NONBLOCK) { | 
 |     left->ioflag() |= IOFLAG_NONBLOCK; | 
 |     right->ioflag() |= IOFLAG_NONBLOCK; | 
 |   } | 
 |  | 
 |   if (flags & O_CLOEXEC) { | 
 |     left->ioflag() |= IOFLAG_CLOEXEC; | 
 |     right->ioflag() |= IOFLAG_CLOEXEC; | 
 |   } | 
 |  | 
 |   size_t n = 0; | 
 |  | 
 |   fbl::AutoLock lock(&fdio_lock); | 
 |   for (int i = 0; i < static_cast<int>(fdio_fdtab.size()); ++i) { | 
 |     if (fdio_fdtab[i].try_set(ios[n])) { | 
 |       fd[n] = i; | 
 |       n++; | 
 |       if (n == 2) { | 
 |         return 0; | 
 |       } | 
 |     } | 
 |   } | 
 |   return ERRNO(EMFILE); | 
 | } | 
 |  | 
 | __EXPORT | 
 | int pipe2(int pipefd[2], int flags) { return socketpair_create(pipefd, 0, flags); } | 
 |  | 
 | __EXPORT | 
 | int pipe(int pipefd[2]) { return pipe2(pipefd, 0); } | 
 |  | 
 | __EXPORT | 
 | int socketpair(int domain, int type, int protocol, int fd[2]) { | 
 |   uint32_t options = 0; | 
 |  | 
 |   // Ignore SOCK_CLOEXEC. | 
 |   type = type & ~SOCK_CLOEXEC; | 
 |  | 
 |   switch (type) { | 
 |     case SOCK_DGRAM: | 
 |       options = ZX_SOCKET_DATAGRAM; | 
 |       break; | 
 |     case SOCK_STREAM: | 
 |       options = ZX_SOCKET_STREAM; | 
 |       break; | 
 |     default: | 
 |       errno = EPROTOTYPE; | 
 |       return -1; | 
 |   } | 
 |  | 
 |   if (domain != AF_UNIX) { | 
 |     errno = EAFNOSUPPORT; | 
 |     return -1; | 
 |   } | 
 |   if (protocol != 0) { | 
 |     errno = EPROTONOSUPPORT; | 
 |     return -1; | 
 |   } | 
 |  | 
 |   return socketpair_create(fd, options, 0); | 
 | } | 
 |  | 
 | __EXPORT | 
 | int faccessat(int dirfd, const char* filename, int amode, int flag) { | 
 |   // First, check that the flags and amode are valid. | 
 |   const int allowed_flags = AT_EACCESS; | 
 |   if (flag & (~allowed_flags)) { | 
 |     return ERRNO(EINVAL); | 
 |   } | 
 |  | 
 |   // amode is allowed to be either a subset of this mask, or just F_OK. | 
 |   const int allowed_modes = R_OK | W_OK | X_OK; | 
 |   if (amode != F_OK && (amode & (~allowed_modes))) { | 
 |     return ERRNO(EINVAL); | 
 |   } | 
 |  | 
 |   if (amode == F_OK) { | 
 |     // Check that the file exists a la fstatat. | 
 |     zx::status io = fdio_internal::open_at(dirfd, filename, O_PATH, 0); | 
 |     if (io.is_error()) { | 
 |       return ERROR(io.status_value()); | 
 |     } | 
 |     struct stat s; | 
 |     return STATUS(fdio_stat(io.value(), &s)); | 
 |   } | 
 |  | 
 |   // Check that the file has each of the permissions in mode. | 
 |   // Ignore X_OK, since it does not apply to our permission model | 
 |   amode &= ~X_OK; | 
 |   uint32_t rights_flags = 0; | 
 |   switch (amode & (R_OK | W_OK)) { | 
 |     case R_OK: | 
 |       rights_flags = O_RDONLY; | 
 |       break; | 
 |     case W_OK: | 
 |       rights_flags = O_WRONLY; | 
 |       break; | 
 |     case R_OK | W_OK: | 
 |       rights_flags = O_RDWR; | 
 |       break; | 
 |   } | 
 |   return STATUS( | 
 |       fdio_internal::open_at_ignore_eisdir(dirfd, filename, rights_flags, 0).status_value()); | 
 | } | 
 |  | 
 | __EXPORT | 
 | char* getcwd(char* buf, size_t size) { | 
 |   fdio_internal::PathBuffer tmp; | 
 |   if (buf == nullptr) { | 
 |     buf = tmp.data(); | 
 |     size = PATH_MAX; | 
 |   } else if (size == 0) { | 
 |     errno = EINVAL; | 
 |     return nullptr; | 
 |   } | 
 |  | 
 |   char* out = nullptr; | 
 |   { | 
 |     fbl::AutoLock lock(&fdio_cwd_lock); | 
 |     size_t len = fdio_cwd_path.length() + 1; | 
 |     if (len < size) { | 
 |       memcpy(buf, fdio_cwd_path.data(), len); | 
 |       out = buf; | 
 |     } else { | 
 |       errno = ERANGE; | 
 |     } | 
 |   } | 
 |  | 
 |   if (out == tmp.data()) { | 
 |     out = strdup(tmp.c_str()); | 
 |   } | 
 |   return out; | 
 | } | 
 |  | 
 | void fdio_chdir(fdio_ptr io, const char* path) { | 
 |   fbl::AutoLock cwd_lock(&fdio_cwd_lock); | 
 |   fdio_internal::update_cwd_path(path); | 
 |   fbl::AutoLock lock(&fdio_lock); | 
 |   fdio_cwd_handle.replace(std::move(io)); | 
 | } | 
 |  | 
 | __EXPORT | 
 | int chdir(const char* path) { | 
 |   zx::status io = fdio_internal::open(path, O_RDONLY | O_DIRECTORY, 0); | 
 |   if (io.is_error()) { | 
 |     return ERROR(io.status_value()); | 
 |   } | 
 |   fdio_chdir(io.value(), path); | 
 |   return 0; | 
 | } | 
 |  | 
 | static bool resolve_path(const char* relative, fdio_internal::PathBuffer* out_resolved) { | 
 |   bool is_dir = false; | 
 |   if (relative[0] == '/') { | 
 |     return fdio_internal::CleanPath(relative, out_resolved, &is_dir); | 
 |   } | 
 |  | 
 |   fdio_internal::PathBuffer buffer; | 
 |   { | 
 |     fbl::AutoLock cwd_lock(&fdio_cwd_lock); | 
 |     buffer.Append(fdio_cwd_path); | 
 |   } | 
 |   size_t cwd_length = buffer.length(); | 
 |   size_t relative_length = strlen(relative); | 
 |  | 
 |   if (cwd_length + relative_length + 2 > PATH_MAX) { | 
 |     return false; | 
 |   } | 
 |  | 
 |   buffer.Append('/'); | 
 |   buffer.Append(relative, relative_length); | 
 |   return fdio_internal::CleanPath(buffer.c_str(), out_resolved, &is_dir); | 
 | } | 
 |  | 
 | __EXPORT | 
 | int chroot(const char* path) { | 
 |   fdio_internal::PathBuffer root_path; | 
 |   bool resolved = resolve_path(path, &root_path); | 
 |   if (!resolved) { | 
 |     return ERRNO(ENAMETOOLONG); | 
 |   } | 
 |  | 
 |   zx::status io = fdio_internal::open(root_path.c_str(), O_RDONLY | O_DIRECTORY, 0); | 
 |   if (io.is_error()) { | 
 |     return ERROR(io.status_value()); | 
 |   } | 
 |  | 
 |   // Don't release under lock. | 
 |   fdio_ptr old_root = nullptr; | 
 |   { | 
 |     // We acquire the |cwd_lock| after calling |fdio_internal::open| because we cannot hold this | 
 |     // lock for the duration of the |fdio_internal::open| call. We are careful to pass an absolute | 
 |     // path to |fdio_internal::open| to ensure that we're using a consistent value for the |cwd| | 
 |     // throughout the |chroot| operation. If there is a concurrent call to |chdir| during the | 
 |     // |fdio_internal::open| operation, then we could end up in an inconsistent state, but the only | 
 |     // inconsistency would be the name we apply to the cwd session in the new chrooted namespace. | 
 |     fbl::AutoLock cwd_lock(&fdio_cwd_lock); | 
 |     fbl::AutoLock lock(&fdio_lock); | 
 |  | 
 |     zx_status_t status = fdio_ns_set_root(fdio_root_ns, io.value().get()); | 
 |     if (status != ZX_OK) { | 
 |       return ERROR(status); | 
 |     } | 
 |     old_root = fdio_root_handle.replace(io.value()); | 
 |  | 
 |     // We are now committed to the root. | 
 |  | 
 |     // If the new root path is a prefix of the cwd path, then we can express the current cwd as a | 
 |     // path in the new root by trimming off the prefix. Otherwise, we no longer have a name for the | 
 |     // cwd. | 
 |     if (root_path.length() > 1) { | 
 |       std::string_view cwd_view(fdio_cwd_path); | 
 |       if (cwd_view.find(root_path) == 0u && fdio_cwd_path[root_path.length()] == '/') { | 
 |         fdio_cwd_path.RemovePrefix(root_path.length()); | 
 |       } else { | 
 |         fdio_cwd_path.Set("(unreachable)"); | 
 |       } | 
 |     } | 
 |   } | 
 |  | 
 |   return 0; | 
 | } | 
 |  | 
 | struct __dirstream { | 
 |   mtx_t lock; | 
 |  | 
 |   // fd number of the directory under iteration. | 
 |   int fd; | 
 |  | 
 |   // The iterator object for reading directory entries. | 
 |   // This is only allocated during an iteration. | 
 |   std::unique_ptr<zxio_dirent_iterator_t> iterator; | 
 |  | 
 |   // A single directory entry returned to user; updated by |readdir|. | 
 |   struct dirent de = {}; | 
 | }; | 
 |  | 
 | static DIR* internal_opendir(int fd) { | 
 |   DIR* dir = new __dirstream; | 
 |   mtx_init(&dir->lock, mtx_plain); | 
 |   dir->fd = fd; | 
 |   return dir; | 
 | } | 
 |  | 
 | __EXPORT | 
 | DIR* opendir(const char* name) { | 
 |   int fd = open(name, O_RDONLY | O_DIRECTORY); | 
 |   if (fd < 0) | 
 |     return nullptr; | 
 |   DIR* dir = internal_opendir(fd); | 
 |   if (dir == nullptr) | 
 |     close(fd); | 
 |   return dir; | 
 | } | 
 |  | 
 | __EXPORT | 
 | DIR* fdopendir(int fd) { | 
 |   // Check the fd for validity, but we'll just store the fd | 
 |   // number so we don't save the fdio_t pointer. | 
 |   fdio_ptr io = fd_to_io(fd); | 
 |   if (io == nullptr) { | 
 |     errno = EBADF; | 
 |     return nullptr; | 
 |   } | 
 |   // TODO(mcgrathr): Technically this should verify that it's | 
 |   // really a directory and fail with ENOTDIR if not.  But | 
 |   // that's not so easy to do, so don't bother for now. | 
 |   return internal_opendir(fd); | 
 | } | 
 |  | 
 | __EXPORT | 
 | int closedir(DIR* dir) { | 
 |   if (dir->iterator) { | 
 |     fdio_ptr io = fd_to_io(dir->fd); | 
 |     io->dirent_iterator_destroy(dir->iterator.get()); | 
 |     dir->iterator.reset(); | 
 |   } | 
 |   close(dir->fd); | 
 |   delete dir; | 
 |   return 0; | 
 | } | 
 |  | 
 | __EXPORT | 
 | struct dirent* readdir(DIR* dir) { | 
 |   fbl::AutoLock lock(&dir->lock); | 
 |   struct dirent* de = &dir->de; | 
 |  | 
 |   fdio_ptr io = fd_to_io(dir->fd); | 
 |  | 
 |   // Lazy initialize the iterator. | 
 |   if (!dir->iterator) { | 
 |     dir->iterator = std::make_unique<zxio_dirent_iterator_t>(); | 
 |     zx_status_t status = io->dirent_iterator_init(dir->iterator.get(), &io->zxio_storage().io); | 
 |     if (status != ZX_OK) { | 
 |       errno = fdio_status_to_errno(status); | 
 |       return nullptr; | 
 |     } | 
 |   } | 
 |   // We need space for the maximum possible filename plus a null terminator. | 
 |   static_assert(sizeof(de->d_name) >= ZXIO_MAX_FILENAME + 1); | 
 |   zxio_dirent_t entry = {.name = de->d_name}; | 
 |   zx_status_t status = io->dirent_iterator_next(dir->iterator.get(), &entry); | 
 |   if (status == ZX_ERR_NOT_FOUND) { | 
 |     // Reached the end. | 
 |     return nullptr; | 
 |   } | 
 |   if (status != ZX_OK) { | 
 |     errno = fdio_status_to_errno(status); | 
 |     return nullptr; | 
 |   } | 
 |   // zxio doesn't null terminate this string, so we do. | 
 |   de->d_name[entry.name_length] = '\0'; | 
 |   de->d_ino = entry.has.id ? entry.id : fio::wire::kInoUnknown; | 
 |   de->d_off = 0; | 
 |   // The d_reclen field is nonstandard, but existing code | 
 |   // may expect it to be useful as an upper bound on the | 
 |   // length of the name. | 
 |   de->d_reclen = static_cast<uint16_t>(offsetof(struct dirent, d_name) + entry.name_length + 1); | 
 |   if (entry.has.protocols) { | 
 |     de->d_type = ([](zxio_node_protocols_t protocols) -> unsigned char { | 
 |       if (protocols & ZXIO_NODE_PROTOCOL_DIRECTORY) | 
 |         return DT_DIR; | 
 |       if (protocols & ZXIO_NODE_PROTOCOL_FILE) | 
 |         return DT_REG; | 
 |       if (protocols & ZXIO_NODE_PROTOCOL_DEVICE) | 
 |         return DT_BLK; | 
 |       if (protocols & ZXIO_NODE_PROTOCOL_TTY) | 
 |         return DT_CHR; | 
 |       // There is no good analogue for FIDL services in POSIX land. | 
 |       if (protocols & ZXIO_NODE_PROTOCOL_CONNECTOR) | 
 |         return DT_UNKNOWN; | 
 |       return DT_UNKNOWN; | 
 |     })(entry.protocols); | 
 |   } else { | 
 |     de->d_type = DT_UNKNOWN; | 
 |   } | 
 |   return de; | 
 | } | 
 |  | 
 | __EXPORT | 
 | void rewinddir(DIR* dir) { | 
 |   fbl::AutoLock lock(&dir->lock); | 
 |   if (dir->iterator) { | 
 |     fdio_ptr io = fd_to_io(dir->fd); | 
 |     io->dirent_iterator_destroy(dir->iterator.get()); | 
 |     dir->iterator.reset(); | 
 |   } | 
 | } | 
 |  | 
 | __EXPORT | 
 | int dirfd(DIR* dir) { return dir->fd; } | 
 |  | 
 | __EXPORT | 
 | int isatty(int fd) { | 
 |   fdio_ptr io = fd_to_io(fd); | 
 |   if (io == nullptr) { | 
 |     errno = EBADF; | 
 |     return 0; | 
 |   } | 
 |  | 
 |   bool tty; | 
 |   zx_status_t status = zxio_isatty(&io->zxio_storage().io, &tty); | 
 |   if (status != ZX_OK) { | 
 |     return ERROR(status); | 
 |   } | 
 |   if (tty) { | 
 |     return 1; | 
 |   } | 
 |   errno = ENOTTY; | 
 |   return 0; | 
 | } | 
 |  | 
 | __EXPORT | 
 | mode_t umask(mode_t mask) { | 
 |   mode_t oldmask; | 
 |   fbl::AutoLock lock(&fdio_lock); | 
 |   oldmask = __fdio_global_state.umask; | 
 |   __fdio_global_state.umask = mask & 0777; | 
 |   return oldmask; | 
 | } | 
 |  | 
 | // TODO: getrlimit(RLIMIT_NOFILE, ...) | 
 | constexpr size_t kMaxPollNfds = 1024; | 
 |  | 
 | __EXPORT | 
 | int ppoll(struct pollfd* fds, nfds_t n, const struct timespec* timeout_ts, | 
 |           const sigset_t* sigmask) { | 
 |   if (sigmask) { | 
 |     return ERRNO(ENOSYS); | 
 |   } | 
 |   if (n > kMaxPollNfds || n < 0) { | 
 |     return ERRNO(EINVAL); | 
 |   } | 
 |  | 
 |   auto timeout = zx::duration::infinite(); | 
 |   if (timeout_ts) { | 
 |     // Match Linux's validation strategy. See: | 
 |     // | 
 |     // https://github.com/torvalds/linux/blob/f40ddce/include/linux/time64.h#L84-L96 | 
 |     // | 
 |     // https://github.com/torvalds/linux/blob/f40ddce/include/vdso/time64.h#L11 | 
 |     if (timeout_ts->tv_sec < 0 || timeout_ts->tv_nsec < 0 || timeout_ts->tv_nsec >= 1000000000L) { | 
 |       return ERRNO(EINVAL); | 
 |     } | 
 |     timeout = zx::duration(*timeout_ts); | 
 |   } | 
 |  | 
 |   if (n == 0) { | 
 |     std::this_thread::sleep_for(std::chrono::nanoseconds(timeout.to_nsecs())); | 
 |     return 0; | 
 |   } | 
 |  | 
 |   // TODO(https://fxbug.dev/71558): investigate VLA alternatives. | 
 |   fdio_ptr ios[n]; | 
 |   // |items| is the set of handles to wait on and will contain up to |n| entries. Some | 
 |   // FDs do not contain a handle or do not have any applicable Zircon signals, so we | 
 |   // won't populate an entry in |items| for these FDs. Thus |items| may have fewer | 
 |   // entries than |n|. | 
 |   zx_wait_item_t items[n]; | 
 |   // |nitems| tracks the number of populated entries in |items|. | 
 |   size_t nitems = 0; | 
 |   // |items_set| keeps track of which entries in |fds| have a corresponding | 
 |   // entry in |items|. It is true for FDs that have an entry in |items|. | 
 |   bool items_set[n]; | 
 |  | 
 |   for (nfds_t i = 0; i < n; ++i) { | 
 |     auto& pfd = fds[i]; | 
 |     auto& io = ios[i]; | 
 |     if ((io = fd_to_io(pfd.fd)) == nullptr) { | 
 |       // fd is not opened | 
 |       pfd.revents = POLLNVAL; | 
 |       items_set[i] = false; | 
 |       continue; | 
 |     } | 
 |  | 
 |     zx_handle_t h = ZX_HANDLE_INVALID; | 
 |     zx_signals_t sigs = ZX_SIGNAL_NONE; | 
 |     io->wait_begin(pfd.events, &h, &sigs); | 
 |     if (sigs == ZX_SIGNAL_NONE) { | 
 |       // Skip waiting on this fd as there are no waitable signals. | 
 |       uint32_t events; | 
 |       io->wait_end(sigs, &events); | 
 |       pfd.revents = static_cast<int16_t>(events); | 
 |       items_set[i] = false; | 
 |       continue; | 
 |     } | 
 |     if (h == ZX_HANDLE_INVALID) { | 
 |       return ERROR(ZX_ERR_INVALID_ARGS); | 
 |     } | 
 |     pfd.revents = 0; | 
 |     items[nitems] = { | 
 |         .handle = h, | 
 |         .waitfor = sigs, | 
 |     }; | 
 |     items_set[i] = true; | 
 |     ++nitems; | 
 |   } | 
 |  | 
 |   if (nitems != 0) { | 
 |     zx_status_t status = | 
 |         zx::handle::wait_many(items, static_cast<uint32_t>(nitems), zx::deadline_after(timeout)); | 
 |     // pending signals could be reported on ZX_ERR_TIMED_OUT case as well | 
 |     if (!(status == ZX_OK || status == ZX_ERR_TIMED_OUT)) { | 
 |       return ERROR(status); | 
 |     } | 
 |   } | 
 |  | 
 |   int nfds = 0; | 
 |   // |items_index| is the index into the next entry in the |items| array. As not | 
 |   // all FDs in the wait set correspond to a kernel wait, the |items_index| | 
 |   // value corresponding to a particular FD can be lower than the index of that | 
 |   // FD in the |fds| array. | 
 |   size_t items_index = 0; | 
 |   for (nfds_t i = 0; i < n; ++i) { | 
 |     auto& pfd = fds[i]; | 
 |     auto& io = ios[i]; | 
 |  | 
 |     if (items_set[i]) { | 
 |       uint32_t events; | 
 |       io->wait_end(items[items_index].pending, &events); | 
 |       pfd.revents = static_cast<int16_t>(events); | 
 |       ++items_index; | 
 |     } | 
 |     // Mask unrequested events. Avoid clearing events that are ignored in pollfd::events. | 
 |     pfd.revents &= static_cast<int16_t>(pfd.events | POLLNVAL | POLLHUP | POLLERR); | 
 |     if (pfd.revents != 0) { | 
 |       ++nfds; | 
 |     } | 
 |   } | 
 |  | 
 |   return nfds; | 
 | } | 
 |  | 
 | __EXPORT | 
 | int poll(struct pollfd* fds, nfds_t n, int timeout) { | 
 |   struct timespec timeout_ts = { | 
 |       .tv_sec = timeout / 1000, | 
 |       .tv_nsec = (timeout % 1000) * 1000000, | 
 |   }; | 
 |   struct timespec* ts = timeout >= 0 ? &timeout_ts : nullptr; | 
 |   return ppoll(fds, n, ts, nullptr); | 
 | } | 
 |  | 
 | __EXPORT | 
 | int select(int n, fd_set* __restrict rfds, fd_set* __restrict wfds, fd_set* __restrict efds, | 
 |            struct timeval* __restrict tv) { | 
 |   if (n > FD_SETSIZE || n < 0) { | 
 |     return ERRNO(EINVAL); | 
 |   } | 
 |  | 
 |   auto timeout = zx::duration::infinite(); | 
 |   if (tv) { | 
 |     if (tv->tv_sec < 0 || tv->tv_usec < 0) { | 
 |       return ERRNO(EINVAL); | 
 |     } | 
 |     timeout = zx::sec(tv->tv_sec) + zx::usec(tv->tv_usec); | 
 |   } | 
 |  | 
 |   if (n == 0) { | 
 |     std::this_thread::sleep_for(std::chrono::nanoseconds(timeout.to_nsecs())); | 
 |     return 0; | 
 |   } | 
 |  | 
 |   // TODO(https://fxbug.dev/71558): investigate VLA alternatives. | 
 |   fdio_ptr ios[n]; | 
 |   zx_wait_item_t items[n]; | 
 |   size_t nitems = 0; | 
 |  | 
 |   for (int fd = 0; fd < n; ++fd) { | 
 |     uint32_t events = 0; | 
 |     if (rfds && FD_ISSET(fd, rfds)) | 
 |       events |= POLLIN; | 
 |     if (wfds && FD_ISSET(fd, wfds)) | 
 |       events |= POLLOUT; | 
 |     if (efds && FD_ISSET(fd, efds)) | 
 |       events |= POLLERR; | 
 |  | 
 |     auto& io = ios[fd]; | 
 |     if (events == 0) { | 
 |       io = nullptr; | 
 |       continue; | 
 |     } | 
 |  | 
 |     if ((io = fd_to_io(fd)) == nullptr) { | 
 |       return ERROR(ZX_ERR_INVALID_ARGS); | 
 |     } | 
 |  | 
 |     zx_handle_t h; | 
 |     zx_signals_t sigs; | 
 |     io->wait_begin(events, &h, &sigs); | 
 |     if (h == ZX_HANDLE_INVALID) { | 
 |       return ERROR(ZX_ERR_INVALID_ARGS); | 
 |     } | 
 |     items[nitems] = { | 
 |         .handle = h, | 
 |         .waitfor = sigs, | 
 |     }; | 
 |     ++nitems; | 
 |   } | 
 |  | 
 |   zx_status_t status = | 
 |       zx::handle::wait_many(items, static_cast<uint32_t>(nitems), zx::deadline_after(timeout)); | 
 |   // pending signals could be reported on ZX_ERR_TIMED_OUT case as well | 
 |   if (!(status == ZX_OK || status == ZX_ERR_TIMED_OUT)) { | 
 |     return ERROR(status); | 
 |   } | 
 |  | 
 |   int nfds = 0; | 
 |   size_t j = 0; | 
 |   for (int fd = 0; fd < n; fd++) { | 
 |     auto io = ios[fd]; | 
 |     if (io == nullptr) { | 
 |       // skip an invalid entry | 
 |       continue; | 
 |     } | 
 |     if (j < nitems) { | 
 |       uint32_t events = 0; | 
 |       io->wait_end(items[j].pending, &events); | 
 |       if (rfds && FD_ISSET(fd, rfds)) { | 
 |         if (events & POLLIN) { | 
 |           ++nfds; | 
 |         } else { | 
 |           FD_CLR(fd, rfds); | 
 |         } | 
 |       } | 
 |       if (wfds && FD_ISSET(fd, wfds)) { | 
 |         if (events & POLLOUT) { | 
 |           ++nfds; | 
 |         } else { | 
 |           FD_CLR(fd, wfds); | 
 |         } | 
 |       } | 
 |       if (efds && FD_ISSET(fd, efds)) { | 
 |         if (events & POLLERR) { | 
 |           ++nfds; | 
 |         } else { | 
 |           FD_CLR(fd, efds); | 
 |         } | 
 |       } | 
 |     } else { | 
 |       if (rfds) { | 
 |         FD_CLR(fd, rfds); | 
 |       } | 
 |       if (wfds) { | 
 |         FD_CLR(fd, wfds); | 
 |       } | 
 |       if (efds) { | 
 |         FD_CLR(fd, efds); | 
 |       } | 
 |     } | 
 |     ++j; | 
 |   } | 
 |  | 
 |   return nfds; | 
 | } | 
 |  | 
 | __EXPORT | 
 | int ioctl(int fd, int req, ...) { | 
 |   fdio_ptr io = fd_to_io(fd); | 
 |   if (io == nullptr) { | 
 |     return ERRNO(EBADF); | 
 |   } | 
 |  | 
 |   va_list ap; | 
 |   va_start(ap, req); | 
 |   Errno e = io->posix_ioctl(req, ap); | 
 |   va_end(ap); | 
 |   if (e.is_error()) { | 
 |     return ERRNO(e.e); | 
 |   } | 
 |   return 0; | 
 | } | 
 |  | 
 | __EXPORT | 
 | ssize_t sendto(int fd, const void* buf, size_t buflen, int flags, const struct sockaddr* addr, | 
 |                socklen_t addrlen) { | 
 |   struct iovec iov; | 
 |   iov.iov_base = const_cast<void*>(buf); | 
 |   iov.iov_len = buflen; | 
 |  | 
 |   struct msghdr msg = {}; | 
 |   msg.msg_name = const_cast<struct sockaddr*>(addr); | 
 |   msg.msg_namelen = addrlen; | 
 |   msg.msg_iov = &iov; | 
 |   msg.msg_iovlen = 1; | 
 |   return sendmsg(fd, &msg, flags); | 
 | } | 
 |  | 
 | __EXPORT | 
 | ssize_t recvfrom(int fd, void* __restrict buf, size_t buflen, int flags, | 
 |                  struct sockaddr* __restrict addr, socklen_t* __restrict addrlen) { | 
 |   struct iovec iov; | 
 |   iov.iov_base = buf; | 
 |   iov.iov_len = buflen; | 
 |  | 
 |   struct msghdr msg = {}; | 
 |   msg.msg_name = addr; | 
 |   if (addrlen != nullptr) { | 
 |     msg.msg_namelen = *addrlen; | 
 |   } | 
 |   msg.msg_iov = &iov; | 
 |   msg.msg_iovlen = 1; | 
 |  | 
 |   ssize_t n = recvmsg(fd, &msg, flags); | 
 |   if (addrlen != nullptr) { | 
 |     *addrlen = msg.msg_namelen; | 
 |   } | 
 |   return n; | 
 | } | 
 |  | 
 | __EXPORT | 
 | ssize_t sendmsg(int fd, const struct msghdr* msg, int flags) { | 
 |   fdio_ptr io = fd_to_io(fd); | 
 |   if (io == nullptr) { | 
 |     return ERRNO(EBADF); | 
 |   } | 
 |   auto& ioflag = io->ioflag(); | 
 |   // The |flags| are typically used to express intent *not* to issue SIGPIPE | 
 |   // via MSG_NOSIGNAL. Applications use this frequently to avoid having to | 
 |   // install additional signal handlers to handle cases where connection has | 
 |   // been closed by remote end. Signals aren't a notion on Fuchsia, so this | 
 |   // flag can be safely ignored. | 
 |   flags &= ~MSG_NOSIGNAL; | 
 |   const bool blocking = ((ioflag & IOFLAG_NONBLOCK) | (flags & MSG_DONTWAIT)) == 0; | 
 |   flags &= ~MSG_DONTWAIT; | 
 |   zx::time deadline = zx::deadline_after(io->sndtimeo()); | 
 |   for (;;) { | 
 |     size_t actual; | 
 |     int16_t out_code; | 
 |     zx_status_t status = io->sendmsg(msg, flags, &actual, &out_code); | 
 |     if (blocking) { | 
 |       switch (status) { | 
 |         case ZX_OK: | 
 |           if (out_code != EWOULDBLOCK) { | 
 |             break; | 
 |           } | 
 |           __FALLTHROUGH; | 
 |         case ZX_ERR_SHOULD_WAIT: | 
 |           status = fdio_wait(io, FDIO_EVT_WRITABLE, deadline, nullptr); | 
 |           if (status == ZX_OK) { | 
 |             continue; | 
 |           } | 
 |           if (status == ZX_ERR_TIMED_OUT) { | 
 |             status = ZX_ERR_SHOULD_WAIT; | 
 |           } | 
 |       } | 
 |     } | 
 |     if (status != ZX_OK) { | 
 |       return ERROR(status); | 
 |     } | 
 |     if (out_code) { | 
 |       return ERRNO(out_code); | 
 |     } | 
 |     return actual; | 
 |   } | 
 | } | 
 |  | 
 | __EXPORT | 
 | ssize_t recvmsg(int fd, struct msghdr* msg, int flags) { | 
 |   fdio_ptr io = fd_to_io(fd); | 
 |   if (io == nullptr) { | 
 |     return ERRNO(EBADF); | 
 |   } | 
 |   auto& ioflag = io->ioflag(); | 
 |   const bool blocking = ((ioflag & IOFLAG_NONBLOCK) | (flags & MSG_DONTWAIT)) == 0; | 
 |   flags &= ~MSG_DONTWAIT; | 
 |   zx::time deadline = zx::deadline_after(io->rcvtimeo()); | 
 |   for (;;) { | 
 |     size_t actual; | 
 |     int16_t out_code; | 
 |     zx_status_t status = io->recvmsg(msg, flags, &actual, &out_code); | 
 |     if (blocking) { | 
 |       switch (status) { | 
 |         case ZX_OK: | 
 |           if (out_code != EWOULDBLOCK) { | 
 |             break; | 
 |           } | 
 |           __FALLTHROUGH; | 
 |         case ZX_ERR_SHOULD_WAIT: | 
 |           status = fdio_wait(io, FDIO_EVT_READABLE, deadline, nullptr); | 
 |           if (status == ZX_OK) { | 
 |             continue; | 
 |           } | 
 |           if (status == ZX_ERR_TIMED_OUT) { | 
 |             status = ZX_ERR_SHOULD_WAIT; | 
 |           } | 
 |       } | 
 |     } | 
 |     if (status != ZX_OK) { | 
 |       return ERROR(status); | 
 |     } | 
 |     if (out_code) { | 
 |       return ERRNO(out_code); | 
 |     } | 
 |     return actual; | 
 |   } | 
 | } | 
 |  | 
 | __EXPORT | 
 | int shutdown(int fd, int how) { | 
 |   fdio_ptr io = fd_to_io(fd); | 
 |   if (io == nullptr) { | 
 |     return ERRNO(EBADF); | 
 |   } | 
 |  | 
 |   int16_t out_code; | 
 |   zx_status_t status = io->shutdown(how, &out_code); | 
 |   if (status != ZX_OK) { | 
 |     return ERROR(status); | 
 |   } | 
 |   if (out_code) { | 
 |     return ERRNO(out_code); | 
 |   } | 
 |   return out_code; | 
 | } | 
 |  | 
 | // The common denominator between the Linux-y fstatfs and the POSIX | 
 | // fstatvfs, which align on most fields. The fs version is more easily | 
 | // computed from the fuchsia_io::FilesystemInfo, so this takes a struct statfs. | 
 | static int fs_stat(int fd, struct statfs* buf) { | 
 |   fdio_ptr io = fd_to_io(fd); | 
 |   if (io == nullptr) { | 
 |     return ERRNO(EBADF); | 
 |   } | 
 |   zx_handle_t handle; | 
 |   zx_status_t status = io->borrow_channel(&handle); | 
 |   if (status != ZX_OK) { | 
 |     return ERROR(status); | 
 |   } | 
 |   auto directory = fidl::UnownedClientEnd<fuchsia_io::Directory>(handle); | 
 |   if (!directory.is_valid()) { | 
 |     return ERRNO(ENOTSUP); | 
 |   } | 
 |   auto result = fidl::WireCall(directory)->QueryFilesystem(); | 
 |   if (result.status() != ZX_OK) { | 
 |     return ERROR(result.status()); | 
 |   } | 
 |   fidl::WireResponse<fuchsia_io::Directory::QueryFilesystem>* response = result.Unwrap(); | 
 |   if (response->s != ZX_OK) { | 
 |     return ERROR(response->s); | 
 |   } | 
 |   fuchsia_io::wire::FilesystemInfo* info = response->info.get(); | 
 |   if (info == nullptr) { | 
 |     return ERRNO(EIO); | 
 |   } | 
 |  | 
 |   info->name[fuchsia_io::wire::kMaxFsNameBuffer - 1] = '\0'; | 
 |  | 
 |   struct statfs stats = {}; | 
 |  | 
 |   if (info->block_size) { | 
 |     stats.f_bsize = info->block_size; | 
 |     stats.f_blocks = info->total_bytes / stats.f_bsize; | 
 |     stats.f_bfree = stats.f_blocks - info->used_bytes / stats.f_bsize; | 
 |   } | 
 |   stats.f_bavail = stats.f_bfree; | 
 |   stats.f_files = info->total_nodes; | 
 |   stats.f_ffree = info->total_nodes - info->used_nodes; | 
 |   stats.f_namelen = info->max_filename_size; | 
 |   stats.f_type = info->fs_type; | 
 |   stats.f_fsid.__val[0] = static_cast<int>(info->fs_id & 0xffffffff); | 
 |   stats.f_fsid.__val[1] = static_cast<int>(info->fs_id >> 32u); | 
 |  | 
 |   *buf = stats; | 
 |   return 0; | 
 | } | 
 |  | 
 | __EXPORT | 
 | int fstatfs(int fd, struct statfs* buf) { return fs_stat(fd, buf); } | 
 |  | 
 | __EXPORT | 
 | int statfs(const char* path, struct statfs* buf) { | 
 |   int fd = open(path, O_RDONLY | O_CLOEXEC); | 
 |   if (fd < 0) { | 
 |     return fd; | 
 |   } | 
 |   int rv = fstatfs(fd, buf); | 
 |   close(fd); | 
 |   return rv; | 
 | } | 
 |  | 
 | __EXPORT | 
 | int fstatvfs(int fd, struct statvfs* buf) { | 
 |   struct statfs stats = {}; | 
 |   int result = fs_stat(fd, &stats); | 
 |   if (result >= 0) { | 
 |     struct statvfs vstats = {}; | 
 |  | 
 |     // The following fields are 1-1 between the Linux statfs | 
 |     // definition and the POSIX statvfs definition. | 
 |     vstats.f_bsize = stats.f_bsize; | 
 |     vstats.f_blocks = stats.f_blocks; | 
 |     vstats.f_bfree = stats.f_bfree; | 
 |     vstats.f_bavail = stats.f_bavail; | 
 |  | 
 |     vstats.f_files = stats.f_files; | 
 |     vstats.f_ffree = stats.f_ffree; | 
 |  | 
 |     vstats.f_flag = stats.f_flags; | 
 |  | 
 |     vstats.f_namemax = stats.f_namelen; | 
 |  | 
 |     // The following fields have slightly different semantics | 
 |     // between the two. | 
 |  | 
 |     // The two have different representations for the fsid. | 
 |     vstats.f_fsid = stats.f_fsid.__val[0] + ((static_cast<uint64_t>(stats.f_fsid.__val[1])) << 32); | 
 |  | 
 |     // The statvfs "fragment size" value best corresponds to the | 
 |     // FilesystemInfo "block size" value. | 
 |     vstats.f_frsize = stats.f_bsize; | 
 |  | 
 |     // The statvfs struct distinguishes between available files, | 
 |     // and available files for unprivileged processes. fuchsia.io | 
 |     // makes no such distinction, so use the same value for both. | 
 |     vstats.f_favail = stats.f_ffree; | 
 |  | 
 |     // Finally, the f_type and f_spare fields on struct statfs | 
 |     // have no equivalent for struct statvfs. | 
 |  | 
 |     *buf = vstats; | 
 |   } | 
 |   return result; | 
 | } | 
 |  | 
 | __EXPORT | 
 | int statvfs(const char* path, struct statvfs* buf) { | 
 |   int fd = open(path, O_RDONLY | O_CLOEXEC); | 
 |   if (fd < 0) { | 
 |     return fd; | 
 |   } | 
 |   int rv = fstatvfs(fd, buf); | 
 |   close(fd); | 
 |   return rv; | 
 | } | 
 |  | 
 | // extern "C" is required here, since the corresponding declaration is in an internal musl header: | 
 | // zircon/third_party/ulib/musl/src/internal/libc.h | 
 | extern "C" __EXPORT int _fd_open_max(void) { return FDIO_MAX_FD; } |