cheatah
Source

stdlib/os/os.hpp

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#pragma once
5/**
6 * @file os.hpp
7 * @brief cheatah `os` — Python-like operating-system interface over
8 * `std::filesystem`, plus the `os.path` submodule. `import os` to use it.
9 *
10 * `import os` includes this header AND links `libcheatah_os`. Templated entry
11 * points (e.g. `os.path.join`) live here; the rest is compiled into the library.
12 * Unit tests: `stdlib/tests/os_test.cpp`; the suite runs under AddressSanitizer
13 * (the `asan` preset) and Valgrind (`security/run-valgrind.sh`) on every QA-gate
14 * run.
15 *
16 * @note Most calls touch the filesystem/environment, so they perform a syscall
17 * in addition to the cost noted per function; `n` is the path length.
18 */
19#include <cstdint>
20#include <filesystem>
21#include <string>
22#include <string_view>
23#include <utility>
24#include <vector>
26namespace cheatah::os {
28/// StringLike<T>: a std::string can be constructed from T — exactly what
29/// os.path.join() does (std::string(part)). Naming it yields a clear "constraint
30/// StringLike not satisfied" message while still accepting everything it does today
31/// (const char*, char arrays, std::string, std::string_view, …).
32template <typename T>
33concept StringLike = requires(const T& value) { std::string(value); };
35/**
36 * Current working directory.
37 *
38 * Queries the process's current directory via `std::filesystem::current_path`
39 * and returns it as an absolute path string.
40 * @return the absolute cwd.
41 * @complexity O(n) + a syscall.
42 * @alloc allocates the result string.
43 * @test CheatahOs.CwdAndCpuCount
44 * @crtest OsCompileRun.Getcwd
45 * @systest StdlibE2E.Os
46 */
47std::string getcwd();
48/**
49 * Change the working directory.
50 *
51 * Sets the process's current directory; subsequent relative paths resolve
52 * against it. Throws if @p path does not exist or is not a directory.
53 * @param path the target directory.
54 * @complexity O(1) + a syscall.
55 * @alloc allocates a path temporary.
56 * @test CheatahOs.MakedirsAndChdir
57 * @crtest OsCompileRun.Chdir
58 * @systest StdlibE2E.Os
59 */
60void chdir(const std::string& path);
61/**
62 * List a directory's entries (basenames only).
63 *
64 * Iterates @p path and returns each entry's filename component (not a full
65 * path), in unspecified order; `.` and `..` are not included. Throws if @p path
66 * does not exist or is not a directory.
67 * @param path the directory (default `.`).
68 * @return the entry names.
69 * @complexity O(entries) + syscalls.
70 * @alloc allocates a vector of strings.
71 * @test CheatahOs.ListdirAndRename
72 * @crtest OsCompileRun.Listdir
73 * @systest StdlibE2E.Os
74 */
75std::vector<std::string> listdir(const std::string& path = ".");
76/**
77 * Create a single directory.
78 *
79 * Creates the leaf directory only; the parent must already exist (use
80 * makedirs to create missing parents). Does nothing if @p path already exists
81 * as a directory.
82 * @param path the directory to create.
83 * @complexity O(1) + a syscall.
84 * @alloc none.
85 * @test CheatahOs.MakeDirExistsThenRemove
86 * @crtest OsCompileRun.Mkdir
87 * @systest StdlibE2E.Os
88 */
89void mkdir(const std::string& path);
90/**
91 * Create a directory and any missing parents.
92 *
93 * Creates @p path along with every intermediate directory that does not yet
94 * exist. Succeeds without error if the full path already exists as a directory.
95 * @param path the nested directory to create.
96 * @complexity O(depth) + syscalls.
97 * @alloc none.
98 * @test CheatahOs.MakedirsAndChdir
99 * @crtest OsCompileRun.Makedirs
100 * @systest StdlibE2E.Os
101 */
102void makedirs(const std::string& path);
103/**
104 * Remove an (empty) directory.
105 *
106 * Deletes a single, empty directory; throws if @p path is non-empty. A missing
107 * @p path is a no-op (no error). Note this is the same `fs::remove` used by
108 * remove(), so it will also delete a regular file at @p path.
109 * @param path the directory to remove.
110 * @complexity O(1) + a syscall.
111 * @alloc none.
112 * @test CheatahOs.MakeDirExistsThenRemove
113 * @crtest OsCompileRun.Rmdir
114 * @systest StdlibE2E.Os
115 */
116void rmdir(const std::string& path);
117/**
118 * Remove a file or empty directory.
119 *
120 * Deletes a single file or empty directory and returns whether anything was
121 * removed; a missing @p path returns false rather than throwing. Throws if
122 * @p path is a non-empty directory.
123 * @param path the entry to remove.
124 * @return true iff something was removed.
125 * @complexity O(1) + a syscall.
126 * @alloc none.
127 * @test CheatahOs.FileQueriesIsfileAndGetsize
128 * @crtest OsCompileRun.Remove
129 * @systest StdlibE2E.Os
130 */
131bool remove(const std::string& path); // true if a file was removed
132/**
133 * Rename/move @p src to @p dst.
134 *
135 * Moves or renames an entry; an existing @p dst is overwritten when permitted
136 * by the underlying `fs::rename`. Crossing filesystems or other failures throw.
137 * @param src source path.
138 * @param dst destination path.
139 * @complexity O(1) + a syscall.
140 * @alloc allocates two path temporaries.
141 * @test CheatahOs.ListdirAndRename
142 * @crtest OsCompileRun.Rename
143 * @systest StdlibE2E.Os
144 */
145void rename(const std::string& src, const std::string& dst);
147/**
148 * Read an environment variable.
149 *
150 * Returns @p fallback (default `""`) when the variable is unset; an empty
151 * string result therefore does not distinguish "unset" from "set to empty".
152 * @param name the variable name.
153 * @param fallback returned when unset.
154 * @return the value, or @p fallback.
155 * @complexity O(environment size) — `std::getenv` is a linear scan of the C library's
156 * environment table (no syscall).
157 * @alloc allocates the returned string.
158 * @concurrency reads the process-wide environment; a concurrent setenv() on another thread is a data race.
159 * @test CheatahOs.GetenvFallback, CheatahOs.SetenvThenGetenv
160 * @crtest OsCompileRun.Getenv
161 * @systest StdlibE2E.Os
162 */
163std::string getenv(const std::string& name, const std::string& fallback = "");
164/**
165 * Set an environment variable.
166 *
167 * When @p overwrite is false and the variable already exists, the existing
168 * value is kept; otherwise it is created or replaced. The change affects only
169 * this process and its future children.
170 * @param name the variable name.
171 * @param value the value to set.
172 * @param overwrite replace an existing value when true.
173 * @complexity O(environment size) — the C library scans and updates its environment
174 * table (no syscall).
175 * @alloc may allocate inside the C library's environment table.
176 * @concurrency mutates the process-wide environment; unsafe alongside a concurrent getenv()/setenv() on any thread.
177 * @test CheatahOs.SetenvThenGetenv
178 * @crtest OsCompileRun.Setenv
179 * @systest StdlibE2E.Os
180 */
181void setenv(const std::string& name, const std::string& value, bool overwrite = true);
183/**
184 * Process id.
185 * @return the current process's pid.
186 * @complexity O(1) + a syscall.
187 * @alloc none.
188 * @test CheatahOs.PidAndSystem
189 * @crtest OsCompileRun.Getpid
190 * @systest StdlibE2E.Os
191 */
192int getpid();
193/**
194 * Logical CPU count.
195 *
196 * Reports `std::thread::hardware_concurrency()`, the number of concurrent
197 * threads supported; the standard allows it to return 0 when the value cannot
198 * be determined, so callers should treat 0 as "unknown".
199 * @return the number of hardware threads (0 if undetermined).
200 * @complexity O(1).
201 * @alloc none.
202 * @test CheatahOs.CwdAndCpuCount
203 * @crtest OsCompileRun.CpuCount
204 * @systest StdlibE2E.Os
205 */
206unsigned cpu_count();
207/**
208 * Run a shell command.
209 *
210 * Passes @p command to the system shell via `std::system` and blocks until it
211 * finishes; the returned status is implementation-defined (on POSIX, a wait
212 * status, conventionally decoded so that 0 means success).
213 * @param command the command line.
214 * @return the command's exit status.
215 * @complexity O(1) here + the cost of the spawned process (fork/exec via the shell).
216 * @alloc none.
217 * @warning @p command is interpreted by the shell (quoting, expansion, `;`/`|`) — never
218 * build it from untrusted input.
219 * @test CheatahOs.PidAndSystem
220 * @crtest OsCompileRun.System
221 * @systest StdlibE2E.Os
222 */
223int system(const std::string& command);
224/**
225 * Cryptographically secure random bytes (like Python's `os.urandom`).
226 *
227 * Reads @p n bytes from the operating system's CSPRNG — `getentropy`/`/dev/urandom`
228 * on POSIX, `BCryptGenRandom` on Windows — suitable for keys and signatures. Unlike
229 * the `random` module (a deterministic, seedable PRNG), this is NOT reproducible and
230 * must not be seeded. Throws `std::runtime_error` if the OS source cannot be read
231 * (so a key is never built from non-random bytes), and `std::invalid_argument` for a
232 * negative @p n.
233 * @param n the number of bytes to return (must be non-negative).
234 * @return a string of @p n random bytes (may contain embedded NULs).
235 * @complexity O(n), plus one syscall per 256-byte chunk on POSIX (getentropy's
236 * per-call limit; a single BCryptGenRandom call on Windows).
237 * @alloc allocates the n-byte result.
238 * @test CheatahOs.Urandom
239 * @crtest OsCompileRun.Urandom
240 * @systest StdlibE2E.Os
241 */
242std::string urandom(int n);
244/**
245 * The loadable-module file extension for this platform.
246 *
247 * A compiled cheatah program is a native loadable module run by the `cheatah`
248 * host; its file extension is `.so` on Linux/BSD, `.dylib` on macOS, and `.dll`
249 * on Windows. Tools that build or name modules (e.g. the `biome` package manager)
250 * use this instead of hardcoding `.so`, so the paths they print and generate are
251 * correct on every platform. The result includes the leading dot.
252 * @return the platform module extension (e.g. `".so"`, `".dylib"`, `".dll"`).
253 * @complexity O(1).
254 * @alloc allocates the returned string.
255 * @test CheatahOs.ModuleExt
256 * @crtest OsCompileRun.ModuleExt
257 * @systest StdlibE2E.Os
258 */
259std::string module_ext();
261/// os.path — the path-manipulation submodule.
262namespace path {
264/**
265 * Join path components with the platform separator.
266 *
267 * Appends each component with `path::operator/=`, inserting a separator as
268 * needed; following `std::filesystem` rules, an absolute component discards
269 * everything joined before it. Purely lexical — the filesystem is not touched.
270 * @param first the first component.
271 * @param rest any further string-constructible components.
272 * @return e.g. `join("a","b","c") -> "a/b/c"`.
273 * @complexity O(total length).
274 * @alloc allocates the result string and per-part path temporaries.
275 * @test CheatahOs.PathJoin
276 * @crtest OsCompileRun.PathJoin
277 * @systest StdlibE2E.Os
278 */
279template <StringLike... Parts>
280std::string join(const std::string& first, const Parts&... rest) {
281 std::filesystem::path p(first);
282 ((p /= std::filesystem::path(std::string(rest))), ...);
283 return p.string();
286/**
287 * Path existence test.
288 *
289 * Follows symlinks and is true for any existing entry — file, directory, or
290 * other; returns false for a missing path.
291 * @param p the path.
292 * @return true iff @p p exists.
293 * @complexity O(n) + a syscall.
294 * @alloc none.
295 * @warning The answer is a snapshot: the entry can be created or removed between this
296 * check and any subsequent use (TOCTOU) — do not rely on it as a security check.
297 * @test CheatahOs.MakeDirExistsThenRemove
298 * @crtest OsCompileRun.PathExists
299 * @systest StdlibE2E.Os
300 */
301bool exists(const std::string& p);
302/**
303 * Regular-file test.
304 *
305 * Returns false (rather than throwing) when @p p is missing or is a non-regular
306 * entry such as a directory; symlinks are followed to their target.
307 * @param p the path.
308 * @return true iff @p p is a regular file.
309 * @complexity O(n) + a syscall.
310 * @alloc none.
311 * @test CheatahOs.FileQueriesIsfileAndGetsize
312 * @crtest OsCompileRun.PathIsfile
313 * @systest StdlibE2E.Os
314 */
315bool isfile(const std::string& p);
316/**
317 * Directory test.
318 *
319 * Returns false (rather than throwing) when @p p is missing or is not a
320 * directory; symlinks are followed to their target.
321 * @param p the path.
322 * @return true iff @p p is a directory.
323 * @complexity O(n) + a syscall.
324 * @alloc none.
325 * @test CheatahOs.MakeDirExistsThenRemove
326 * @crtest OsCompileRun.PathIsdir
327 * @systest StdlibE2E.Os
328 */
329bool isdir(const std::string& p);
330/**
331 * Final path component.
332 *
333 * Returns the trailing filename component lexically, without touching the
334 * filesystem; a path ending in a separator (e.g. `a/b/`) yields an empty
335 * string, matching `std::filesystem::path::filename`.
336 * @param p the path.
337 * @return the basename (filename).
338 * @complexity O(n).
339 * @alloc allocates a path temporary and the result string.
340 * @test CheatahOs.PathBasenameDirname
341 * @crtest OsCompileRun.PathBasename
342 * @systest StdlibE2E.Os
343 */
344std::string basename(const std::string& p);
345/**
346 * Parent path.
347 *
348 * Returns everything before the final component lexically, without touching the
349 * filesystem; a bare filename with no separator (e.g. `file.txt`) yields an
350 * empty string, matching `std::filesystem::path::parent_path`.
351 * @param p the path.
352 * @return the directory portion of @p p.
353 * @complexity O(n).
354 * @alloc allocates a path temporary and the result string.
355 * @test CheatahOs.PathBasenameDirname
356 * @crtest OsCompileRun.PathDirname
357 * @systest StdlibE2E.Os
358 */
359std::string dirname(const std::string& p);
360/**
361 * Absolute path.
362 *
363 * Prepends the current working directory to a relative @p p; it does not
364 * collapse `.`/`..` segments or resolve symlinks (combine with normpath for
365 * that), and @p p need not exist.
366 * @param p the path.
367 * @return @p p resolved against the cwd.
368 * @complexity O(n) + a syscall (reads the cwd).
369 * @alloc allocates the result string.
370 * @test CheatahOs.AbspathAndNormpath
371 * @crtest OsCompileRun.PathAbspath
372 * @systest StdlibE2E.Os
373 */
374std::string abspath(const std::string& p);
375/**
376 * Lexically normalized path (collapses current-dir and parent-dir segments).
377 * @param p the path.
378 * @return the normalized path.
379 * @complexity O(n) (purely lexical, no syscall).
380 * @alloc allocates a path temporary and the result string.
381 * @test CheatahOs.AbspathAndNormpath
382 * @crtest OsCompileRun.PathNormpath
383 * @systest StdlibE2E.Os
384 */
385std::string normpath(const std::string& p);
386/**
387 * File size in bytes.
388 *
389 * Defined only for regular files; querying a missing path, or a directory or
390 * other non-regular entry, throws rather than returning a sentinel.
391 * @param p the file path.
392 * @return @p p's size.
393 * @complexity O(1) + a syscall.
394 * @alloc none.
395 * @test CheatahOs.FileQueriesIsfileAndGetsize
396 * @crtest OsCompileRun.PathGetsize
397 * @systest StdlibE2E.Os
398 */
399std::uintmax_t getsize(const std::string& p);
401/**
402 * Split a path into root and extension.
403 *
404 * Splits at the last dot of the final component so that concatenating the two
405 * results reproduces @p p; when there is no extension the whole path is the
406 * root and the extension is empty. The extension includes its leading dot, and
407 * a leading-dot name (e.g. `.bashrc`) is treated as having no extension.
408 * @param p the path.
409 * @return e.g. `splitext("dir/file.purr") -> {"dir/file", ".purr"}` (empty extension when
410 * none).
411 * @complexity O(n).
412 * @alloc allocates the two result strings and a path temporary.
413 * @test CheatahOs.PathSplitext
414 * @crtest OsCompileRun.PathSplitext
415 * @systest StdlibE2E.Os
416 */
417std::pair<std::string, std::string> splitext(const std::string& p);
419} // namespace path
420} // namespace cheatah::os