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 none.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 none.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
* @test CheatahOs.GetenvFallback, CheatahOs.SetenvThenGetenv159
* @crtest OsCompileRun.Getenv160
* @systest StdlibE2E.Os161
*/162
std::string getenv(const std::string& name, const std::string& fallback = "");163
/**164
* Set an environment variable.165
*166
* When @p overwrite is false and the variable already exists, the existing167
* value is kept; otherwise it is created or replaced. The change affects only168
* this process and its future children.169
* @param name the variable name.170
* @param value the value to set.171
* @param overwrite replace an existing value when true.172
* @complexity O(environment size) — the C library scans and updates its environment173
* table (no syscall).174
* @alloc may allocate inside the C library's environment table.175
* @test CheatahOs.SetenvThenGetenv176
* @crtest OsCompileRun.Setenv177
* @systest StdlibE2E.Os178
*/179
void setenv(const std::string& name, const std::string& value, bool overwrite = true);181
/**182
* Process id.183
* @return the current process's pid.184
* @complexity O(1) + a syscall.185
* @alloc none.186
* @test CheatahOs.PidAndSystem187
* @crtest OsCompileRun.Getpid188
* @systest StdlibE2E.Os189
*/190
int getpid();191
/**192
* Logical CPU count.193
*194
* Reports `std::thread::hardware_concurrency()`, the number of concurrent195
* threads supported; the standard allows it to return 0 when the value cannot196
* be determined, so callers should treat 0 as "unknown".197
* @return the number of hardware threads (0 if undetermined).198
* @complexity O(1).199
* @alloc none.200
* @test CheatahOs.CwdAndCpuCount201
* @crtest OsCompileRun.CpuCount202
* @systest StdlibE2E.Os203
*/204
unsigned cpu_count();205
/**206
* Run a shell command.207
*208
* Passes @p command to the system shell via `std::system` and blocks until it209
* finishes; the returned status is implementation-defined (on POSIX, a wait210
* status, conventionally decoded so that 0 means success).211
* @param command the command line.212
* @return the command's exit status.213
* @complexity O(1) here + the cost of the spawned process (fork/exec via the shell).214
* @alloc none.215
* @warning @p command is interpreted by the shell (quoting, expansion, `;`/`|`) — never216
* build it from untrusted input.217
* @test CheatahOs.PidAndSystem218
* @crtest OsCompileRun.System219
* @systest StdlibE2E.Os220
*/221
int system(const std::string& command);222
/**223
* Cryptographically secure random bytes (like Python's `os.urandom`).224
*225
* Reads @p n bytes from the operating system's CSPRNG — `getentropy`/`/dev/urandom`226
* on POSIX, `BCryptGenRandom` on Windows — suitable for keys and signatures. Unlike227
* the `random` module (a deterministic, seedable PRNG), this is NOT reproducible and228
* must not be seeded. Throws `std::runtime_error` if the OS source cannot be read229
* (so a key is never built from non-random bytes), and `std::invalid_argument` for a230
* negative @p n.231
* @param n the number of bytes to return (must be non-negative).232
* @return a string of @p n random bytes (may contain embedded NULs).233
* @complexity O(n), plus one syscall per 256-byte chunk on POSIX (getentropy's234
* per-call limit; a single BCryptGenRandom call on Windows).235
* @alloc allocates the n-byte result.236
* @test CheatahOs.Urandom237
* @crtest OsCompileRun.Urandom238
* @systest StdlibE2E.Os239
*/240
std::string urandom(int n);242
/**243
* The loadable-module file extension for this platform.244
*245
* A compiled cheatah program is a native loadable module run by the `cheatah`246
* host; its file extension is `.so` on Linux/BSD, `.dylib` on macOS, and `.dll`247
* on Windows. Tools that build or name modules (e.g. the `biome` package manager)248
* use this instead of hardcoding `.so`, so the paths they print and generate are249
* correct on every platform. The result includes the leading dot.250
* @return the platform module extension (e.g. `".so"`, `".dylib"`, `".dll"`).251
* @complexity O(1).252
* @alloc allocates the returned string.253
* @test CheatahOs.ModuleExt254
* @crtest OsCompileRun.ModuleExt255
* @systest StdlibE2E.Os256
*/257
std::string module_ext();259
/// os.path — the path-manipulation submodule.260
namespace path {262
/**263
* Join path components with the platform separator.264
*265
* Appends each component with `path::operator/=`, inserting a separator as266
* needed; following `std::filesystem` rules, an absolute component discards267
* everything joined before it. Purely lexical — the filesystem is not touched.268
* @param first the first component.269
* @param rest any further string-constructible components.270
* @return e.g. `join("a","b","c") -> "a/b/c"`.271
* @complexity O(total length).272
* @alloc allocates the result string and per-part path temporaries.273
* @test CheatahOs.PathJoin274
* @crtest OsCompileRun.PathJoin275
* @systest StdlibE2E.Os276
*/277
template <StringLike... Parts>278
std::string join(const std::string& first, const Parts&... rest) {279
std::filesystem::path p(first);280
((p /= std::filesystem::path(std::string(rest))), ...);281
return p.string();282
}284
/**285
* Path existence test.286
*287
* Follows symlinks and is true for any existing entry — file, directory, or288
* other; returns false for a missing path.289
* @param p the path.290
* @return true iff @p p exists.291
* @complexity O(n) + a syscall.292
* @alloc none.293
* @warning The answer is a snapshot: the entry can be created or removed between this294
* check and any subsequent use (TOCTOU) — do not rely on it as a security check.295
* @test CheatahOs.MakeDirExistsThenRemove296
* @crtest OsCompileRun.PathExists297
* @systest StdlibE2E.Os298
*/299
bool exists(const std::string& p);300
/**301
* Regular-file test.302
*303
* Returns false (rather than throwing) when @p p is missing or is a non-regular304
* entry such as a directory; symlinks are followed to their target.305
* @param p the path.306
* @return true iff @p p is a regular file.307
* @complexity O(n) + a syscall.308
* @alloc none.309
* @test CheatahOs.FileQueriesIsfileAndGetsize310
* @crtest OsCompileRun.PathIsfile311
* @systest StdlibE2E.Os312
*/313
bool isfile(const std::string& p);314
/**315
* Directory test.316
*317
* Returns false (rather than throwing) when @p p is missing or is not a318
* directory; symlinks are followed to their target.319
* @param p the path.320
* @return true iff @p p is a directory.321
* @complexity O(n) + a syscall.322
* @alloc none.323
* @test CheatahOs.MakeDirExistsThenRemove324
* @crtest OsCompileRun.PathIsdir325
* @systest StdlibE2E.Os326
*/327
bool isdir(const std::string& p);328
/**329
* Final path component.330
*331
* Returns the trailing filename component lexically, without touching the332
* filesystem; a path ending in a separator (e.g. `a/b/`) yields an empty333
* string, matching `std::filesystem::path::filename`.334
* @param p the path.335
* @return the basename (filename).336
* @complexity O(n).337
* @alloc allocates a path temporary and the result string.338
* @test CheatahOs.PathBasenameDirname339
* @crtest OsCompileRun.PathBasename340
* @systest StdlibE2E.Os341
*/342
std::string basename(const std::string& p);343
/**344
* Parent path.345
*346
* Returns everything before the final component lexically, without touching the347
* filesystem; a bare filename with no separator (e.g. `file.txt`) yields an348
* empty string, matching `std::filesystem::path::parent_path`.349
* @param p the path.350
* @return the directory portion of @p p.351
* @complexity O(n).352
* @alloc allocates a path temporary and the result string.353
* @test CheatahOs.PathBasenameDirname354
* @crtest OsCompileRun.PathDirname355
* @systest StdlibE2E.Os356
*/357
std::string dirname(const std::string& p);358
/**359
* Absolute path.360
*361
* Prepends the current working directory to a relative @p p; it does not362
* collapse `.`/`..` segments or resolve symlinks (combine with normpath for363
* that), and @p p need not exist.364
* @param p the path.365
* @return @p p resolved against the cwd.366
* @complexity O(n) + a syscall (reads the cwd).367
* @alloc allocates the result string.368
* @test CheatahOs.AbspathAndNormpath369
* @crtest OsCompileRun.PathAbspath370
* @systest StdlibE2E.Os371
*/372
std::string abspath(const std::string& p);373
/**374
* Lexically normalized path (collapses current-dir and parent-dir segments).375
* @param p the path.376
* @return the normalized path.377
* @complexity O(n) (purely lexical, no syscall).378
* @alloc allocates a path temporary and the result string.379
* @test CheatahOs.AbspathAndNormpath380
* @crtest OsCompileRun.PathNormpath381
* @systest StdlibE2E.Os382
*/383
std::string normpath(const std::string& p);384
/**385
* File size in bytes.386
*387
* Defined only for regular files; querying a missing path, or a directory or388
* other non-regular entry, throws rather than returning a sentinel.389
* @param p the file path.390
* @return @p p's size.391
* @complexity O(1) + a syscall.392
* @alloc none.393
* @test CheatahOs.FileQueriesIsfileAndGetsize394
* @crtest OsCompileRun.PathGetsize395
* @systest StdlibE2E.Os396
*/397
std::uintmax_t getsize(const std::string& p);399
/**400
* Split a path into root and extension.401
*402
* Splits at the last dot of the final component so that concatenating the two403
* results reproduces @p p; when there is no extension the whole path is the404
* root and the extension is empty. The extension includes its leading dot, and405
* a leading-dot name (e.g. `.bashrc`) is treated as having no extension.406
* @param p the path.407
* @return e.g. `splitext("dir/file.purr") -> {"dir/file", ".purr"}` (empty extension when408
* none).409
* @complexity O(n).410
* @alloc allocates the two result strings and a path temporary.411
* @test CheatahOs.PathSplitext412
* @crtest OsCompileRun.PathSplitext413
* @systest StdlibE2E.Os414
*/415
std::pair<std::string, std::string> splitext(const std::string& p);417
} // namespace path418
} // namespace cheatah::os