cheatah
Source

stdlib/os/os.cpp

1// Copyright (c) 2026 BigBrain LLC. MIT-licensed (see LICENSE).
2// Original work; see ACKNOWLEDGMENTS.md for the open-source ideas we build upon.
3#include "os.hpp"
5#include <algorithm>
6#include <cstdlib>
7#include <stdexcept>
8#include <thread>
10#if defined(_WIN32)
11#include <process.h> // _getpid
12#include <windows.h>
13#include <bcrypt.h> // BCryptGenRandom
14#else
15#include <unistd.h> // getpid
16#include <sys/random.h> // getentropy
17#endif
19// Compiled (non-template) symbols of the os module. Linked into a cheatah
20// executable only when the program `import os`s.
21namespace cheatah::os {
23namespace fs = std::filesystem;
25std::string getcwd() { return fs::current_path().string(); }
26void chdir(const std::string& path) { fs::current_path(path); }
28std::vector<std::string> listdir(const std::string& path) {
29 std::vector<std::string> names;
30 for (const auto& entry : fs::directory_iterator(path)) {
31 names.push_back(entry.path().filename().string());
32 }
33 return names;
36void mkdir(const std::string& path) { fs::create_directory(path); }
37void makedirs(const std::string& path) { fs::create_directories(path); }
38void rmdir(const std::string& path) { fs::remove(path); }
39bool remove(const std::string& path) { return fs::remove(path); }
40void rename(const std::string& src, const std::string& dst) { fs::rename(src, dst); }
42std::string getenv(const std::string& name, const std::string& fallback) {
43 const char* v = std::getenv(name.c_str()); // NOLINT(concurrency-mt-unsafe): os.getenv IS the documented API; the same caveat as POSIX getenv applies
44 return (v != nullptr) ? std::string(v) : fallback;
46void setenv(const std::string& name, const std::string& value, bool overwrite) {
47#if defined(_WIN32)
48 if (!overwrite && std::getenv(name.c_str()) != nullptr) return; // _putenv_s always overwrites
49 ::_putenv_s(name.c_str(), value.c_str());
50#else
51 ::setenv(name.c_str(), value.c_str(), overwrite ? 1 : 0); // NOLINT(concurrency-mt-unsafe): os.setenv IS the documented API; the same caveat as POSIX setenv applies
52#endif
55#if defined(_WIN32)
56int getpid() { return static_cast<int>(::_getpid()); }
57#else
58int getpid() { return static_cast<int>(::getpid()); }
59#endif
60unsigned cpu_count() { return std::thread::hardware_concurrency(); }
61// NOLINT below: os.system IS the exposed shell-out API — the command processor is the
62// documented contract here, and the caller owns what they pass it (single-trust model).
63int system(const std::string& command) { return std::system(command.c_str()); } // NOLINT(cert-env33-c,concurrency-mt-unsafe)
65std::string urandom(int n) {
66 if (n < 0) throw std::invalid_argument("os.urandom: negative byte count");
67 std::string out(static_cast<std::size_t>(n), '\0');
68 if (n == 0) return out;
69#if defined(_WIN32)
70 if (::BCryptGenRandom(nullptr, reinterpret_cast<PUCHAR>(out.data()),
71 static_cast<ULONG>(out.size()),
72 BCRYPT_USE_SYSTEM_PREFERRED_RNG) != 0)
73 throw std::runtime_error("os.urandom: BCryptGenRandom failed");
74#else
75 // getentropy is limited to 256 bytes per call; loop for larger requests. A nonzero
76 // return means the OS could not supply randomness — fail loudly rather than hand back
77 // predictable bytes that a caller might turn into a key.
78 std::size_t off = 0;
79 while (off < out.size()) {
80 const std::size_t chunk = std::min<std::size_t>(out.size() - off, 256);
81 if (::getentropy(out.data() + off, chunk) != 0) throw std::runtime_error("os.urandom: getentropy failed");
82 off += chunk;
83 }
84#endif
85 return out;
88std::string module_ext() {
89#if defined(_WIN32)
90 return ".dll";
91#elif defined(__APPLE__)
92 return ".dylib";
93#else
94 return ".so";
95#endif
98namespace path {
100bool exists(const std::string& p) { return fs::exists(p); }
101bool isfile(const std::string& p) { return fs::is_regular_file(p); }
102bool isdir(const std::string& p) { return fs::is_directory(p); }
103std::string basename(const std::string& p) { return fs::path(p).filename().string(); }
104std::string dirname(const std::string& p) { return fs::path(p).parent_path().string(); }
105std::string abspath(const std::string& p) { return fs::absolute(p).string(); }
106std::string normpath(const std::string& p) { return fs::path(p).lexically_normal().string(); }
107std::uintmax_t getsize(const std::string& p) { return fs::file_size(p); }
109std::pair<std::string, std::string> splitext(const std::string& p) {
110 const std::string ext = fs::path(p).extension().string();
111 if (ext.empty()) {
112 return {p, ""};
113 }
114 return {p.substr(0, p.size() - ext.size()), ext};
117} // namespace path
118} // namespace cheatah::os