[libc-commits] [libc] [libc] add proc number parser and sysconf wrapper (PR #194159)

Jeff Bailey via libc-commits libc-commits at lists.llvm.org
Sun Apr 26 12:29:37 PDT 2026


================
@@ -0,0 +1,189 @@
+//===------------- Linux sysinfo support -------------------------------------//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_SRC___SUPPORT_OSUTIL_LINUX_SYSINFO_H
+#define LLVM_LIBC_SRC___SUPPORT_OSUTIL_LINUX_SYSINFO_H
+
+#include "hdr/errno_macros.h"
+#include "src/__support/CPP/array.h"
+#include "src/__support/CPP/bit.h"
+#include "src/__support/CPP/optional.h"
+#include "src/__support/OSUtil/linux/syscall_wrappers/close.h"
+#include "src/__support/OSUtil/linux/syscall_wrappers/open.h"
+#include "src/__support/OSUtil/linux/syscall_wrappers/read.h"
+#include "src/__support/OSUtil/linux/syscall_wrappers/sched_getaffinity.h"
+#include "src/__support/ctype_utils.h"
+#include "src/__support/macros/config.h"
+
+namespace LIBC_NAMESPACE_DECL {
+namespace sysinfo {
+
+LIBC_INLINE_VAR constexpr char POSSIBLE_NPROC_PATH[] =
+    "/sys/devices/system/cpu/possible";
+LIBC_INLINE_VAR constexpr char ONLINE_NPROC_PATH[] =
+    "/sys/devices/system/cpu/online";
+
+// Parses Linux CPU-list syntax:
+//   list  := item (',' item)*
+//   item  := number | number '-' number
+//   number := [0-9]+
+class ProcParser {
+  enum class ProcParserState {
+    ParseUnstarted,
+    ParseNumber,
+    ParseRangeSeparator,
+    ParseRangeEnd
+  };
+
+  ProcParserState state;
+  cpp::array<char, 128> buffer;
+  int fd;
+  size_t cursor;
+  size_t buffer_end;
+  size_t cpu_count;
+  size_t current_number;
+  size_t range_start;
+  bool has_error;
+
+  LIBC_INLINE static int open_path(const char *path) {
+    ErrorOr<int> open_result =
+        linux_syscalls::open(path, O_RDONLY | O_CLOEXEC, 0);
+    return open_result ? *open_result : -1;
+  }
+
+  LIBC_INLINE cpp::optional<char> next_char() {
+    if (fd < 0)
+      return cpp::nullopt;
+
+    while (cursor == buffer_end) {
+      ErrorOr<ssize_t> bytes_read =
+          linux_syscalls::read(fd, buffer.data(), buffer.size());
+      if (!bytes_read) {
+        if (bytes_read.error() == EINTR)
+          continue;
+        has_error = true;
+        return cpp::nullopt;
+      }
+
+      if (*bytes_read == 0)
+        return cpp::nullopt;
+
+      cursor = 0;
+      buffer_end = static_cast<size_t>(*bytes_read);
+    }
+
+    return buffer[cursor++];
+  }
+
+  LIBC_INLINE bool finish_group() {
+    if (state == ProcParserState::ParseUnstarted)
+      return true;
+    if (state == ProcParserState::ParseRangeSeparator)
+      return false;
+
+    if (state == ProcParserState::ParseRangeEnd) {
+      if (current_number < range_start)
+        return false;
+      cpu_count += current_number - range_start + 1;
+    } else {
+      ++cpu_count;
+    }
+
+    current_number = 0;
+    range_start = 0;
+    state = ProcParserState::ParseUnstarted;
+    return true;
+  }
+
+  LIBC_INLINE bool consume(char ch) {
+    if (internal::isdigit(ch)) {
+      current_number = current_number * 10 + static_cast<size_t>(ch - '0');
+      if (state == ProcParserState::ParseUnstarted)
+        state = ProcParserState::ParseNumber;
+      else if (state == ProcParserState::ParseRangeSeparator)
+        state = ProcParserState::ParseRangeEnd;
+      return true;
+    }
+
+    if (ch == '-') {
+      if (state != ProcParserState::ParseNumber)
+        return false;
+      range_start = current_number;
+      current_number = 0;
+      state = ProcParserState::ParseRangeSeparator;
+      return true;
+    }
+
+    if (ch == ',' || ch == '\n')
+      return finish_group();
+
+    if (ch == ' ' || ch == '\t' || ch == '\r')
+      return state == ProcParserState::ParseUnstarted ? true : finish_group();
+
+    return false;
+  }
+
+public:
+  // Using string view isn't exactly correct because we demands null-terminated
+  // strings.
+  LIBC_INLINE explicit ProcParser(const char *path)
+      : state(ProcParserState::ParseUnstarted), buffer{}, fd(open_path(path)),
+        cursor(buffer.size()), buffer_end(buffer.size()), cpu_count(0),
+        current_number(0), range_start(0), has_error(fd < 0) {}
+
+  LIBC_INLINE ~ProcParser() {
+    if (fd >= 0)
+      linux_syscalls::close(fd);
+  }
+
+  LIBC_INLINE cpp::optional<size_t> parse() {
+    if (fd < 0)
+      return cpp::nullopt;
+
+    while (cpp::optional<char> ch = next_char())
+      if (!consume(*ch))
+        return cpp::nullopt;
+
+    if (has_error)
+      return cpp::nullopt;
+
+    if (!finish_group())
+      return cpp::nullopt;
+
+    if (cpu_count == 0)
+      return cpp::nullopt;
+    return cpu_count;
+  }
+};
+
+LIBC_INLINE cpp::optional<size_t> parse_nproc_from(const char *path) {
+  return ProcParser(path).parse();
+}
+
+LIBC_INLINE size_t parse_nproc_with_fallback_from(const char *path) {
+  if (cpp::optional<size_t> cpu_count = parse_nproc_from(path))
+    return *cpu_count;
+
+  cpp::array<unsigned char, 128> mask_buffer = {};
+
+  ErrorOr<int> affinity_result =
+      linux_syscalls::sched_getaffinity(0, mask_buffer);
+  if (!affinity_result)
+    return 1;
+
+  size_t cpu_count = 0;
+  for (size_t i = 0; i < mask_buffer.size(); ++i)
----------------
kaladron wrote:

```suggestion
  for (size_t i = 0; i < static_cast<size_t>(*affinity_result); ++i)
```

We can just iterate over the number returned from affinity_result for conciseness since we have the number handy.

But also, this is a good candidate for a std::span to ensure that we're not accidentally getting an array access wrong.

https://github.com/llvm/llvm-project/pull/194159


More information about the libc-commits mailing list