| //===----------------------------------------------------------------------===// | |
| // | |
| // 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 | |
| // | |
| //===----------------------------------------------------------------------===// | |
| /// | |
| /// \file | |
| /// Helper functions for pwd. | |
| /// | |
| //===----------------------------------------------------------------------===// | |
| #include "src/pwd/pwd_utils.h" | |
| #include "hdr/errno_macros.h" | |
| #include "hdr/stdio_macros.h" | |
| #include "hdr/types/struct_passwd.h" | |
| #include "src/__support/CPP/span.h" | |
| #include "src/__support/File/file.h" | |
| #include "src/__support/ctype_utils.h" | |
| #include "src/__support/str_to_integer.h" | |
| #include "src/string/string_utils.h" | |
| #ifndef LIBC_COPT_PWD_FILE_PATH | |
| #define LIBC_COPT_PWD_FILE_PATH "/etc/passwd" | |
| #endif | |
| namespace LIBC_NAMESPACE_DECL { | |
| namespace internal { | |
| ErrorOr<struct passwd> parse_passwd_line(char *line) { | |
| if (!line) | |
| return Error(EINVAL); | |
| struct passwd pwd; | |
| char *context = line; | |
| pwd.pw_name = string_token<false>(nullptr, ":", &context); | |
| if (!pwd.pw_name) | |
| return Error(EINVAL); | |
| pwd.pw_passwd = string_token<false>(nullptr, ":", &context); | |
| if (!pwd.pw_passwd) | |
| return Error(EINVAL); | |
| char *uid_str = string_token<false>(nullptr, ":", &context); | |
| if (!uid_str || !isdigit(uid_str[0])) | |
| return Error(EINVAL); | |
| auto uid_res = strtointeger<uid_t>(uid_str, 10); | |
| if (uid_res.has_error() || uid_res.parsed_len == 0 || | |
| uid_str[uid_res.parsed_len] != '\0') | |
| return Error(EINVAL); | |
| pwd.pw_uid = uid_res.value; | |
| char *gid_str = string_token<false>(nullptr, ":", &context); | |
| if (!gid_str || !isdigit(gid_str[0])) | |
| return Error(EINVAL); | |
| auto gid_res = strtointeger<gid_t>(gid_str, 10); | |
| if (gid_res.has_error() || gid_res.parsed_len == 0 || | |
| gid_str[gid_res.parsed_len] != '\0') | |
| return Error(EINVAL); | |
| pwd.pw_gid = gid_res.value; | |
| pwd.pw_gecos = string_token<false>(nullptr, ":", &context); | |
| if (!pwd.pw_gecos) | |
| return Error(EINVAL); | |
| pwd.pw_dir = string_token<false>(nullptr, ":", &context); | |
| if (!pwd.pw_dir) | |
| return Error(EINVAL); | |
| pwd.pw_shell = string_token<false>(nullptr, ":", &context); | |
| if (!pwd.pw_shell) | |
| return Error(EINVAL); | |
| return pwd; | |
| } | |
| } // namespace internal | |
| namespace passwd { | |
| static File *pwd_file = nullptr; | |
| static const char *pwd_file_path = LIBC_COPT_PWD_FILE_PATH; | |
| // Note: These static buffers are process-global and NOT protected by a mutex | |
| // at this stage. POSIX getpwent is non-reentrant. | |
| static char line_buffer[1024]; | |
| static struct passwd pwd_entry; | |
| void TESTONLY_set_passwd_path(const char *path) { | |
| if (!path) | |
| return; | |
| if (pwd_file) { | |
| pwd_file->close(); | |
| pwd_file = nullptr; | |
| } | |
| pwd_file_path = path; | |
| } | |
| ErrorOr<void> open() { | |
| if (!pwd_file) { | |
| auto result = openfile(pwd_file_path, "r"); | |
| if (!result.has_value()) | |
| return Error(result.error()); | |
| pwd_file = result.value(); | |
| } else { | |
| auto result = pwd_file->seek(0, SEEK_SET); | |
| if (!result.has_value()) | |
| return Error(result.error()); | |
| } | |
| return {}; | |
| } | |
| ErrorOr<void> close() { | |
| if (pwd_file) { | |
| int result = pwd_file->close(); | |
| pwd_file = nullptr; | |
| if (result != 0) | |
| return Error(result); | |
| } | |
| return {}; | |
| } | |
| struct ReadLineResult { | |
| size_t bytes_read; | |
| bool truncated; | |
| }; | |
| // Reads a line from the given file into buf. | |
| static ErrorOr<ReadLineResult> read_line(File *f, cpp::span<char> buf) { | |
| if (!f || buf.empty()) | |
| return Error(EINVAL); | |
| f->lock(); | |
| size_t bytes_read = 0; | |
| FileIOResult result(0); | |
| bool truncated = false; | |
| for (char &ch : buf.first(buf.size() - 1)) { | |
| result = f->read_unlocked(&ch, 1); | |
| if (result.has_error()) { | |
| f->unlock(); | |
| return Error(result.error); | |
| } | |
| if (result.value != 1) | |
| break; | |
| ++bytes_read; | |
| if (ch == '\n') | |
| break; | |
| } | |
| if (result.value == 1 && bytes_read > 0 && buf[bytes_read - 1] != '\n') { | |
| truncated = true; | |
| char c = '\0'; | |
| while (true) { | |
| result = f->read_unlocked(&c, 1); | |
| if (result.has_error()) { | |
| f->unlock(); | |
| return Error(result.error); | |
| } | |
| if (result.value != 1 || c == '\n') | |
| break; | |
| } | |
| } | |
| bool has_error = f->error_unlocked(); | |
| f->unlock(); | |
| if (has_error) | |
| return Error(EIO); | |
| buf[bytes_read] = '\0'; | |
| return ReadLineResult{bytes_read, truncated}; | |
| } | |
| ErrorOr<struct passwd *> read_next() { | |
| if (!pwd_file) { | |
| auto result = open(); | |
| if (!result.has_value()) | |
| return Error(result.error()); | |
| } | |
| while (true) { | |
| auto result = read_line(pwd_file, line_buffer); | |
| if (!result.has_value()) | |
| return Error(result.error()); | |
| ReadLineResult res = result.value(); | |
| if (res.bytes_read == 0) | |
| return nullptr; | |
| if (res.truncated) | |
| return Error(EINVAL); | |
| size_t len = res.bytes_read; | |
| if (len > 0 && line_buffer[len - 1] == '\n') | |
| line_buffer[len - 1] = '\0'; | |
| auto passwd_or = internal::parse_passwd_line(line_buffer); | |
| if (!passwd_or.has_value()) | |
| return Error(passwd_or.error()); | |
| pwd_entry = passwd_or.value(); | |
| return &pwd_entry; | |
| } | |
| } | |
| } // namespace passwd | |
| } // namespace LIBC_NAMESPACE_DECL |