blob: 31bcd9f601cb275bb52fb09ba23dc544d00291db [file]
//===----------------------------------------------------------------------===//
//
// 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
/// Implementation of helper functions and parser for grp.
///
//===----------------------------------------------------------------------===//
#include "src/grp/grp_utils.h"
#include "hdr/errno_macros.h"
#include "hdr/func/free.h"
#include "hdr/func/malloc.h"
#include "hdr/func/realloc.h"
#include "hdr/stdint_proxy.h"
#include "hdr/types/gid_t.h"
#include "hdr/types/size_t.h"
#include "hdr/types/struct_group.h"
#include "src/__support/CPP/array.h"
#include "src/__support/CPP/limits.h"
#include "src/__support/CPP/span.h"
#include "src/__support/CPP/string_view.h"
#include "src/__support/OSUtil/linux/syscall_wrappers/setgroups.h"
#include "src/__support/ctype_utils.h"
#include "src/__support/error_or.h"
#include "src/__support/libc_assert.h"
#include "src/__support/macros/attributes.h"
#include "src/__support/macros/config.h"
#include "src/__support/macros/optimization.h"
#include "src/__support/pwd/dynamic_buffer.h"
#include "src/__support/pwd/field_tokenizer.h"
#include "src/__support/pwd/flat_file_db.h"
#include "src/__support/str_to_integer.h"
#ifndef LIBC_COPT_GROUP_FILE_PATH
#define LIBC_COPT_GROUP_FILE_PATH "/etc/group"
#endif
namespace LIBC_NAMESPACE_DECL {
namespace {
// TODO: Replace with cpp::count when available in
// src/__support/CPP/algorithm.h.
size_t count_group_members(cpp::span<const char> line) {
size_t max_members = 1;
for (char c : line) {
if (c == ',')
++max_members;
}
return max_members;
}
// Parse fixed fields (name, passwd, gid).
bool parse_group_fields(cpp::span<char> line, struct group *grp,
cpp::span<char> *members_out) {
if (line.empty() || !grp || !members_out)
return false;
pwd::FieldTokenizer tokenizer(line, ':');
const auto name = tokenizer.next_field();
if (!name || name->empty() || name->front() == '\0')
return false;
grp->gr_name = name->data();
const auto passwd = tokenizer.next_field();
if (!passwd)
return false;
grp->gr_passwd = passwd->data();
const auto gid_str = tokenizer.next_field();
if (!gid_str || gid_str->empty() || !internal::isdigit(gid_str->front()))
return false;
auto gid_res = internal::strtointeger<gid_t>(gid_str->data(), 10);
if (gid_res.has_error() || gid_res.parsed_len <= 0 ||
static_cast<size_t>(gid_res.parsed_len) + 1 != gid_str->size() ||
(*gid_str)[gid_res.parsed_len] != '\0')
return false;
grp->gr_gid = gid_res.value;
const auto members_field = tokenizer.next_field();
if (!members_field)
return false;
// Trailing delimiters or fields are invalid.
if (tokenizer.next_field())
return false;
*members_out = *members_field;
return true;
}
} // namespace
namespace pwd {
template <>
ErrorOr<void> parse_line<struct group>(cpp::span<char> line,
cpp::span<char> scratch,
struct group *grp) {
if (!grp || line.empty() || line.back() != '\0')
return Error(EINVAL);
// Line excluding terminating null byte.
const cpp::span<char> text = line.first(line.size() - 1);
for (const char c : text) {
if (c == '\0')
return Error(EINVAL);
}
const size_t max_members = count_group_members(text);
const uintptr_t tail = reinterpret_cast<uintptr_t>(scratch.data());
// Calculate padding needed to align scratch to alignof(char *) so
// that gr_mem pointer array elements can be safely stored.
const size_t pad =
(alignof(char *) - tail % alignof(char *)) % alignof(char *);
if (scratch.size() < pad)
return Error(ERANGE);
const size_t remaining_bytes = scratch.size() - pad;
if (remaining_bytes / sizeof(char *) < max_members + 1)
return Error(ERANGE);
const cpp::span<char *> mem_ptrs(
reinterpret_cast<char **>(scratch.subspan(pad).data()),
remaining_bytes / sizeof(char *));
if (!grp::parse_group_line(line, grp, mem_ptrs))
return Error(EINVAL);
return {};
}
} // namespace pwd
namespace grp {
bool parse_group_line(cpp::span<char> line, struct group *grp,
cpp::span<char *> mem_ptrs) {
if (mem_ptrs.empty())
return false;
cpp::span<char> members_field;
if (!parse_group_fields(line, grp, &members_field))
return false;
size_t member_count = 0;
if (!members_field.empty() && members_field.front() != '\0') {
pwd::FieldTokenizer member_tokenizer(members_field, ',');
while (const auto member = member_tokenizer.next_field()) {
if (member->empty() || member->front() == '\0')
continue;
if (member_count + 1 >= mem_ptrs.size())
return false;
mem_ptrs[member_count++] = member->data();
}
}
if (member_count >= mem_ptrs.size())
return false;
mem_ptrs[member_count] = nullptr;
grp->gr_mem = mem_ptrs.data();
return true;
}
namespace {
// Tracked alongside the database's own copy because the reentrant lookups open
// their own scoped database rather than sharing the iteration stream.
const char *group_file_path = LIBC_COPT_GROUP_FILE_PATH;
LIBC_CONSTINIT pwd::FlatFileDatabase<struct group>
db(LIBC_COPT_GROUP_FILE_PATH);
// Note: These static buffers are process-global and NOT protected by a mutex
// at this stage. POSIX getgrent is non-reentrant.
//
// getgrent, getgrnam, and getgrgid share a single static buffer and struct
// group, as allowed by POSIX. The buffer grows as needed to fit the largest
// record read so far, and is reused without shrinking. endgrent() closes the
// file stream without freeing the buffer so that pointers returned before
// endgrent() remain valid until the next non-reentrant call.
LIBC_CONSTINIT pwd::DynamicBuffer line_buffer;
LIBC_CONSTINIT struct group grp_entry = {};
// The lookups are shared between the caller-supplied fixed buffer used by the
// reentrant entrypoints and the process-global growable buffer used by the
// non-reentrant ones. Both scan a scoped stream of their own so that a lookup
// does not disturb an in-progress getgrent iteration.
template <typename BufferType>
ErrorOr<bool> lookup_by_name(cpp::string_view name, struct group *grp,
BufferType &buffer, const char *path) {
pwd::ScopedFlatFileDatabase<struct group> local_db(path);
const auto matcher = [name](const struct group &entry) {
return cpp::string_view(entry.gr_name) == name;
};
return local_db.lookup(matcher, grp, buffer);
}
template <typename BufferType>
ErrorOr<bool> lookup_by_gid(gid_t gid, struct group *grp, BufferType &buffer,
const char *path) {
pwd::ScopedFlatFileDatabase<struct group> local_db(path);
const auto matcher = [gid](const struct group &entry) {
return entry.gr_gid == gid;
};
return local_db.lookup(matcher, grp, buffer);
}
// Small-buffer-optimised container for collecting group IDs without duplicates.
// Initial storage is stack-local; falls back to dynamic allocation if the
// user belongs to more than 32 groups.
class GidList {
static constexpr size_t STATIC_CAP = 32;
cpp::array<gid_t, STATIC_CAP> static_buf = {};
gid_t *buf = static_buf.data();
size_t count = 0;
size_t cap = STATIC_CAP;
public:
LIBC_INLINE GidList() = default;
LIBC_INLINE ~GidList() {
if (buf != static_buf.data())
::free(buf);
}
GidList(const GidList &) = delete;
GidList &operator=(const GidList &) = delete;
[[nodiscard]] LIBC_INLINE cpp::span<const gid_t> span() const {
return {buf, count};
}
[[nodiscard]] LIBC_INLINE bool contains(gid_t gid) const {
for (gid_t g : span()) {
if (g == gid)
return true;
}
return false;
}
[[nodiscard]] LIBC_INLINE bool push_back(gid_t gid) {
if (count == cap) {
if (LIBC_UNLIKELY(cap > cpp::numeric_limits<size_t>::max() /
(2 * sizeof(gid_t))))
return false;
size_t new_cap = cap * 2;
void *new_buf = nullptr;
if (buf == static_buf.data()) {
new_buf = ::malloc(new_cap * sizeof(gid_t));
if (!new_buf)
return false;
for (size_t i = 0; i < count; ++i)
static_cast<gid_t *>(new_buf)[i] = static_buf[i];
} else {
new_buf = ::realloc(buf, new_cap * sizeof(gid_t));
if (!new_buf)
return false;
}
buf = static_cast<gid_t *>(new_buf);
cap = new_cap;
}
buf[count++] = gid;
return true;
}
[[nodiscard]] LIBC_INLINE size_t size() const { return count; }
[[nodiscard]] LIBC_INLINE const gid_t *data() const { return buf; }
[[nodiscard]] LIBC_INLINE gid_t operator[](size_t i) const {
LIBC_ASSERT(i < count);
return buf[i];
}
};
ErrorOr<void> populate_gid_list(cpp::string_view user, gid_t group,
GidList &gid_list, const char *path) {
if (!gid_list.push_back(group))
return Error(ENOMEM);
pwd::ScopedFlatFileDatabase<struct group> local_db(path ? path
: group_file_path);
pwd::ScopedDynamicBuffer buffer;
struct group entry = {};
const auto open_res = local_db.setdb();
if (open_res.has_value()) {
while (true) {
const auto next_res = local_db.getnext(&entry, buffer);
if (!next_res.has_value()) {
if (next_res.error() == ENOMEM)
return Error(ENOMEM);
break;
}
if (!next_res.value())
break;
bool is_member = false;
if (entry.gr_mem) {
for (char **m = entry.gr_mem; *m != nullptr; ++m) {
if (cpp::string_view(*m) == user) {
is_member = true;
break;
}
}
}
if (is_member && !gid_list.contains(entry.gr_gid)) {
if (!gid_list.push_back(entry.gr_gid))
return Error(ENOMEM);
}
}
}
return {};
}
} // namespace
void TESTONLY_set_group_path(const char *path) {
close();
line_buffer.release();
group_file_path = path ? path : LIBC_COPT_GROUP_FILE_PATH;
db.set_path(group_file_path);
}
void TESTONLY_reset_group_path() {
close();
line_buffer.release();
group_file_path = LIBC_COPT_GROUP_FILE_PATH;
db.set_path(group_file_path);
}
ErrorOr<void> open() { return db.setdb(); }
ErrorOr<void> close() { return db.enddb(); }
ErrorOr<struct group *> read_next() {
const auto res = db.getnext(&grp_entry, line_buffer);
if (!res.has_value())
return Error(res.error());
if (!res.value())
return nullptr;
return &grp_entry;
}
ErrorOr<bool> find_by_name(cpp::string_view name, struct group *grp,
cpp::span<char> buffer, const char *path) {
return lookup_by_name(name, grp, buffer, path ? path : group_file_path);
}
ErrorOr<bool> find_by_gid(gid_t gid, struct group *grp, cpp::span<char> buffer,
const char *path) {
return lookup_by_gid(gid, grp, buffer, path ? path : group_file_path);
}
ErrorOr<struct group *> find_by_name(cpp::string_view name) {
const auto res =
lookup_by_name(name, &grp_entry, line_buffer, group_file_path);
if (!res.has_value())
return Error(res.error());
if (!res.value())
return nullptr;
return &grp_entry;
}
ErrorOr<struct group *> find_by_gid(gid_t gid) {
const auto res = lookup_by_gid(gid, &grp_entry, line_buffer, group_file_path);
if (!res.has_value())
return Error(res.error());
if (!res.value())
return nullptr;
return &grp_entry;
}
ErrorOr<size_t> get_group_list(cpp::string_view user, gid_t group,
gid_t *groups, size_t ngroups,
const char *path) {
GidList gid_list;
const auto res = populate_gid_list(user, group, gid_list, path);
if (!res.has_value())
return Error(res.error());
const size_t copy_count =
ngroups < gid_list.size() ? ngroups : gid_list.size();
for (size_t i = 0; i < copy_count; ++i)
groups[i] = gid_list[i];
return gid_list.size();
}
ErrorOr<int> init_groups(cpp::string_view user, gid_t group, const char *path) {
GidList gid_list;
const auto res = populate_gid_list(user, group, gid_list, path);
if (!res.has_value())
return Error(res.error());
return linux_syscalls::setgroups(gid_list.size(), gid_list.data());
}
} // namespace grp
} // namespace LIBC_NAMESPACE_DECL