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 once5
/**6
* @file os.hpp7
* @brief cheatah `os` — Python-like operating-system interface over8
* `std::filesystem`, plus the `os.path` submodule. `import os` to use it.9
*10
* `import os` includes this header AND links `libcheatah_os`. Templated entry11
* 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 AddressSanitizer13
* (the `asan` preset) and Valgrind (`security/run-valgrind.sh`) on every QA-gate14
* run.15
*16
* @note Most calls touch the filesystem/environment, so they perform a syscall17
* 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>26
namespace cheatah::os {28
/// StringLike<T>: a std::string can be constructed from T — exactly what29
/// os.path.join() does (std::string(part)). Naming it yields a clear "constraint30
/// StringLike not satisfied" message while still accepting everything it does today31
/// (const char*, char arrays, std::string, std::string_view, …).32
template <typename T>33
concept 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.CwdAndCpuCount44
* @crtest OsCompileRun.Getcwd45
* @systest StdlibE2E.Os46
*/47
std::string getcwd();48
/**49
* Change the working directory.50
*51
* Sets the process's current directory; subsequent relative paths resolve52
* 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.MakedirsAndChdir57
* @crtest OsCompileRun.Chdir58
* @systest StdlibE2E.Os59
*/60
void 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 full65
* path), in unspecified order; `.` and `..` are not included. Throws if @p path66
* 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.ListdirAndRename72
* @crtest OsCompileRun.Listdir73
* @systest StdlibE2E.Os74
*/75
std::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 (use80
* makedirs to create missing parents). Does nothing if @p path already exists81
* as a directory.82
* @param path the directory to create.83
* @complexity O(1) + a syscall.84
* @alloc none.85
* @test CheatahOs.MakeDirExistsThenRemove86
* @crtest OsCompileRun.Mkdir87
* @systest StdlibE2E.Os88
*/89
void 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 yet94
* 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.MakedirsAndChdir99
* @crtest OsCompileRun.Makedirs100
* @systest StdlibE2E.Os101
*/102
void 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 missing107
* @p path is a no-op (no error). Note this is the same `fs::remove` used by108
* 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.MakeDirExistsThenRemove113
* @crtest OsCompileRun.Rmdir114
* @systest StdlibE2E.Os115
*/116
void 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 was121
* removed; a missing @p path returns false rather than throwing. Throws if122
* @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.FileQueriesIsfileAndGetsize128
* @crtest OsCompileRun.Remove129
* @systest StdlibE2E.Os130
*/131
bool remove(const std::string& path); // true if a file was removed132
/**133
* Rename/move @p src to @p dst.134
*135
* Moves or renames an entry; an existing @p dst is overwritten when permitted136
* 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.ListdirAndRename142
* @crtest OsCompileRun.Rename143
* @systest StdlibE2E.Os144
*/145
void 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 empty151
* 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's156
* 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.SetenvThenGetenv160
* @crtest OsCompileRun.Getenv161
* @systest StdlibE2E.Os162
*/163
std::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 existing168
* value is kept; otherwise it is created or replaced. The change affects only169
* 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 environment174
* 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.SetenvThenGetenv178
* @crtest OsCompileRun.Setenv179
* @systest StdlibE2E.Os180
*/181
void 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.PidAndSystem189
* @crtest OsCompileRun.Getpid190
* @systest StdlibE2E.Os191
*/192
int getpid();193
/**194
* Logical CPU count.195
*196
* Reports `std::thread::hardware_concurrency()`, the number of concurrent197
* threads supported; the standard allows it to return 0 when the value cannot198
* 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.CwdAndCpuCount203
* @crtest OsCompileRun.CpuCount204
* @systest StdlibE2E.Os205
*/206
unsigned cpu_count();207
/**208
* Run a shell command.209
*210
* Passes @p command to the system shell via `std::system` and blocks until it211
* finishes; the returned status is implementation-defined (on POSIX, a wait212
* 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, `;`/`|`) — never218
* build it from untrusted input.219
* @test CheatahOs.PidAndSystem220
* @crtest OsCompileRun.System221
* @systest StdlibE2E.Os222
*/223
int 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. Unlike229
* the `random` module (a deterministic, seedable PRNG), this is NOT reproducible and230
* must not be seeded. Throws `std::runtime_error` if the OS source cannot be read231
* (so a key is never built from non-random bytes), and `std::invalid_argument` for a232
* 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's236
* per-call limit; a single BCryptGenRandom call on Windows).237
* @alloc allocates the n-byte result.238
* @test CheatahOs.Urandom239
* @crtest OsCompileRun.Urandom240
* @systest StdlibE2E.Os241
*/242
std::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 are251
* 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.ModuleExt256
* @crtest OsCompileRun.ModuleExt257
* @systest StdlibE2E.Os258
*/259
std::string module_ext();261
/// os.path — the path-manipulation submodule.262
namespace path {264
/**265
* Join path components with the platform separator.266
*267
* Appends each component with `path::operator/=`, inserting a separator as268
* needed; following `std::filesystem` rules, an absolute component discards269
* 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.PathJoin276
* @crtest OsCompileRun.PathJoin277
* @systest StdlibE2E.Os278
*/279
template <StringLike... Parts>280
std::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();284
}286
/**287
* Path existence test.288
*289
* Follows symlinks and is true for any existing entry — file, directory, or290
* 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 this296
* check and any subsequent use (TOCTOU) — do not rely on it as a security check.297
* @test CheatahOs.MakeDirExistsThenRemove298
* @crtest OsCompileRun.PathExists299
* @systest StdlibE2E.Os300
*/301
bool 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-regular306
* 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.FileQueriesIsfileAndGetsize312
* @crtest OsCompileRun.PathIsfile313
* @systest StdlibE2E.Os314
*/315
bool isfile(const std::string& p);316
/**317
* Directory test.318
*319
* Returns false (rather than throwing) when @p p is missing or is not a320
* 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.MakeDirExistsThenRemove326
* @crtest OsCompileRun.PathIsdir327
* @systest StdlibE2E.Os328
*/329
bool isdir(const std::string& p);330
/**331
* Final path component.332
*333
* Returns the trailing filename component lexically, without touching the334
* filesystem; a path ending in a separator (e.g. `a/b/`) yields an empty335
* 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.PathBasenameDirname341
* @crtest OsCompileRun.PathBasename342
* @systest StdlibE2E.Os343
*/344
std::string basename(const std::string& p);345
/**346
* Parent path.347
*348
* Returns everything before the final component lexically, without touching the349
* filesystem; a bare filename with no separator (e.g. `file.txt`) yields an350
* 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.PathBasenameDirname356
* @crtest OsCompileRun.PathDirname357
* @systest StdlibE2E.Os358
*/359
std::string dirname(const std::string& p);360
/**361
* Absolute path.362
*363
* Prepends the current working directory to a relative @p p; it does not364
* collapse `.`/`..` segments or resolve symlinks (combine with normpath for365
* 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.AbspathAndNormpath371
* @crtest OsCompileRun.PathAbspath372
* @systest StdlibE2E.Os373
*/374
std::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.AbspathAndNormpath382
* @crtest OsCompileRun.PathNormpath383
* @systest StdlibE2E.Os384
*/385
std::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 or390
* 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.FileQueriesIsfileAndGetsize396
* @crtest OsCompileRun.PathGetsize397
* @systest StdlibE2E.Os398
*/399
std::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 two405
* results reproduces @p p; when there is no extension the whole path is the406
* root and the extension is empty. The extension includes its leading dot, and407
* 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 when410
* none).411
* @complexity O(n).412
* @alloc allocates the two result strings and a path temporary.413
* @test CheatahOs.PathSplitext414
* @crtest OsCompileRun.PathSplitext415
* @systest StdlibE2E.Os416
*/417
std::pair<std::string, std::string> splitext(const std::string& p);419
} // namespace path420
} // namespace cheatah::os