feat: unify system status codes
This commit is contained in:
1 parent
93894b8fe5
commit
8a3ffb2be1
46 files changed
+1499
-489
No files matched your search
@@ -0,0 +1,216 @@
|
||||
/*
|
||||
* capabilities.h
|
||||
* Shared reader for the capability grant table (0:/config/capabilities.toml)
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <montauk/syscall.h>
|
||||
#include <montauk/toml.h>
|
||||
#include <montauk/config.h>
|
||||
#include <montauk/heap.h>
|
||||
|
||||
namespace montauk {
|
||||
namespace caps {
|
||||
|
||||
inline constexpr const char* GRANT_CONFIG = "capabilities";
|
||||
inline constexpr const char* GRANT_PREFIX = "grant.";
|
||||
|
||||
struct CapName {
|
||||
const char* name;
|
||||
uint64_t bit;
|
||||
};
|
||||
|
||||
// Names as they appear in the config file. Kept in the same order as the
|
||||
// CAP_* bit definitions in Api/Syscall.hpp.
|
||||
inline constexpr CapName NAMES[] = {
|
||||
{"process_admin", montauk::abi::CAP_PROCESS_ADMIN},
|
||||
{"power_request", montauk::abi::CAP_POWER_REQUEST},
|
||||
{"power_control", montauk::abi::CAP_POWER_CONTROL},
|
||||
{"suspend", montauk::abi::CAP_SUSPEND},
|
||||
{"storage_admin", montauk::abi::CAP_STORAGE_ADMIN},
|
||||
{"raw_storage", montauk::abi::CAP_RAW_STORAGE},
|
||||
{"network_admin", montauk::abi::CAP_NETWORK_ADMIN},
|
||||
{"set_time", montauk::abi::CAP_SET_TIME},
|
||||
{"user_admin", montauk::abi::CAP_USER_ADMIN},
|
||||
{"display_admin", montauk::abi::CAP_DISPLAY_ADMIN},
|
||||
{"device_admin", montauk::abi::CAP_DEVICE_ADMIN},
|
||||
{"log_read", montauk::abi::CAP_LOG_READ},
|
||||
{"system_image", montauk::abi::CAP_SYSTEM_IMAGE},
|
||||
{"compositor", montauk::abi::CAP_COMPOSITOR},
|
||||
};
|
||||
|
||||
// Never implied by "all": each must be named explicitly in a grant.
|
||||
inline constexpr uint64_t EXPLICIT_ONLY =
|
||||
montauk::abi::CAP_SYSTEM_IMAGE | montauk::abi::CAP_COMPOSITOR;
|
||||
|
||||
inline uint64_t bit_for_name(const char* name) {
|
||||
if (name == nullptr || name[0] == '\0') return 0;
|
||||
// "all" means "everything this launcher may pass on".
|
||||
if (montauk::streq(name, "all"))
|
||||
return montauk::abi::CAP_ALL & ~EXPLICIT_ONLY;
|
||||
for (const auto& entry : NAMES) {
|
||||
if (montauk::streq(entry.name, name)) return entry.bit;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// True when a grant for `path` can apply: the program is installed under
|
||||
// 0:/os or 0:/apps, which cannot be rewritten without system_image. The
|
||||
// kernel enforces this (Fs::IsSystemImage) by starting any other program
|
||||
// without capabilities; this copy of the rule is only for display.
|
||||
inline bool is_installed_path(const char* path) {
|
||||
if (path == nullptr) return false;
|
||||
const char* p = path;
|
||||
uint64_t drive = 0;
|
||||
if (*p < '0' || *p > '9') return false;
|
||||
while (*p >= '0' && *p <= '9') drive = drive * 10 + (uint64_t)(*p++ - '0');
|
||||
if (*p != ':' || drive != 0) return false;
|
||||
p++;
|
||||
static const char* const roots[] = {"/os", "/apps"};
|
||||
for (const char* root : roots) {
|
||||
int i = 0;
|
||||
while (root[i]) {
|
||||
char c = p[i];
|
||||
if (c >= 'A' && c <= 'Z') c = (char)(c + ('a' - 'A'));
|
||||
if (c != root[i]) break;
|
||||
i++;
|
||||
}
|
||||
if (root[i] == '\0' && (p[i] == '\0' || p[i] == '/')) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Read an array-of-strings key into a capability mask.
|
||||
inline uint64_t mask_from_key(const montauk::toml::Doc& doc, const char* key) {
|
||||
montauk::toml::Value* arr = doc.get_array(key);
|
||||
if (arr == nullptr) return 0;
|
||||
|
||||
uint64_t mask = 0;
|
||||
for (int i = 0; i < arr->array.count; i++) {
|
||||
montauk::toml::Value* item = arr->array.items[i];
|
||||
if (item == nullptr || item->type != montauk::toml::Type::String) continue;
|
||||
mask |= bit_for_name(item->str);
|
||||
}
|
||||
return mask;
|
||||
}
|
||||
|
||||
// Append `suffix` to the "grant.<id>." stem of `path_key`.
|
||||
// Returns false if the key is not of that shape or does not fit.
|
||||
inline bool build_sibling_key(const char* path_key, const char* suffix,
|
||||
char* out, int outSz) {
|
||||
int prefixLen = 0;
|
||||
for (; GRANT_PREFIX[prefixLen]; prefixLen++) {
|
||||
if (path_key[prefixLen] != GRANT_PREFIX[prefixLen]) return false;
|
||||
}
|
||||
|
||||
// Copy through the final '.' so "grant.foo.path" yields "grant.foo.".
|
||||
int lastDot = -1;
|
||||
for (int i = 0; path_key[i]; i++) {
|
||||
if (path_key[i] == '.') lastDot = i;
|
||||
}
|
||||
if (lastDot < prefixLen) return false;
|
||||
|
||||
int n = 0;
|
||||
for (; n <= lastDot && n < outSz - 1; n++) out[n] = path_key[n];
|
||||
for (int i = 0; suffix[i] && n < outSz - 1; i++) out[n++] = suffix[i];
|
||||
out[n] = '\0';
|
||||
return true;
|
||||
}
|
||||
|
||||
// Look up the grant declared for `binary_path`. Returns false when the
|
||||
// path has no entry, which is the common case and means "no authority".
|
||||
inline bool lookup(const char* binary_path,
|
||||
montauk::abi::SpawnCapabilities& out) {
|
||||
out = {0, 0};
|
||||
if (binary_path == nullptr || binary_path[0] == '\0') return false;
|
||||
|
||||
montauk::toml::Doc doc = montauk::config::load(GRANT_CONFIG);
|
||||
|
||||
bool found = false;
|
||||
for (int i = 0; i < doc.entries.count && !found; i++) {
|
||||
montauk::toml::Value* entry = doc.entries.items[i];
|
||||
if (entry == nullptr || entry->key == nullptr) continue;
|
||||
if (entry->type != montauk::toml::Type::String) continue;
|
||||
|
||||
char sibling[128];
|
||||
if (!build_sibling_key(entry->key, "path", sibling, sizeof(sibling))) continue;
|
||||
if (!montauk::streq(sibling, entry->key)) continue;
|
||||
if (!montauk::streq(entry->str, binary_path)) continue;
|
||||
|
||||
build_sibling_key(entry->key, "effective", sibling, sizeof(sibling));
|
||||
uint64_t effective = mask_from_key(doc, sibling);
|
||||
build_sibling_key(entry->key, "delegable", sibling, sizeof(sibling));
|
||||
uint64_t delegable = mask_from_key(doc, sibling);
|
||||
|
||||
out.effective = effective;
|
||||
out.delegable = delegable;
|
||||
found = true;
|
||||
}
|
||||
|
||||
doc.destroy();
|
||||
return found;
|
||||
}
|
||||
|
||||
// The calling process's own capability masks.
|
||||
inline bool self(montauk::abi::SpawnCapabilities& out) {
|
||||
out = {0, 0};
|
||||
return montauk::getcaps(&out) == 0;
|
||||
}
|
||||
|
||||
inline uint64_t self_delegable() {
|
||||
montauk::abi::SpawnCapabilities mine;
|
||||
return self(mine) ? mine.delegable : 0;
|
||||
}
|
||||
|
||||
// Build a spawn request for `binary_path`, clamped to what the caller may
|
||||
// actually delegate.
|
||||
inline montauk::abi::SpawnCapabilities for_binary(const char* binary_path,
|
||||
uint64_t caller_delegable) {
|
||||
montauk::abi::SpawnCapabilities caps{0, 0};
|
||||
|
||||
if (caller_delegable == 0) return caps;
|
||||
|
||||
if (!lookup(binary_path, caps)) return caps;
|
||||
|
||||
caps.effective &= caller_delegable;
|
||||
caps.delegable &= caller_delegable;
|
||||
return caps;
|
||||
}
|
||||
|
||||
// The calling process's delegable set, cached.
|
||||
inline uint64_t my_delegable() {
|
||||
static uint64_t cached = 0;
|
||||
static bool loaded = false;
|
||||
if (!loaded) {
|
||||
cached = self_delegable();
|
||||
loaded = true;
|
||||
}
|
||||
return cached;
|
||||
}
|
||||
|
||||
struct LaunchOptions {
|
||||
const char* args = nullptr;
|
||||
// Owner identity for the child. Requires CAP_USER_ADMIN and is
|
||||
// reserved for session creators (init, login);
|
||||
const char* user = nullptr;
|
||||
// True for a console host that reads and writes the child's I/O
|
||||
// through the childio calls rather than sharing its own.
|
||||
bool redirected = false;
|
||||
};
|
||||
|
||||
// Unified launch helper.
|
||||
inline int launch(const char* path, const LaunchOptions& opt = {}) {
|
||||
if (path == nullptr || path[0] == '\0') return -1;
|
||||
|
||||
if (opt.redirected && opt.user != nullptr) return -1;
|
||||
|
||||
montauk::abi::SpawnCapabilities caps = for_binary(path, my_delegable());
|
||||
return opt.redirected
|
||||
? spawn_redir_with_caps(path, opt.args, caps)
|
||||
: spawn_with_caps(path, opt.args, opt.user, caps);
|
||||
}
|
||||
|
||||
} // namespace caps
|
||||
} // namespace montauk
|
||||
@@ -0,0 +1,209 @@
|
||||
/*
|
||||
* consent.h
|
||||
* First-use consent: prompt wording and the stored-decision table
|
||||
* (0:/config/consent.toml).
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <montauk/syscall.h>
|
||||
#include <montauk/string.h>
|
||||
#include <montauk/toml.h>
|
||||
#include <montauk/config.h>
|
||||
#include <montauk/capabilities.h>
|
||||
|
||||
namespace montauk {
|
||||
namespace consent {
|
||||
|
||||
inline constexpr const char* STORE_CONFIG = "consent";
|
||||
inline constexpr const char* STORE_PREFIX = "decision.";
|
||||
|
||||
struct CapText {
|
||||
uint64_t bit;
|
||||
const char* action; // completes "<App> wants to ..."
|
||||
const char* icon; // shown in the prompt
|
||||
};
|
||||
|
||||
inline constexpr CapText TEXTS[] = {
|
||||
{montauk::abi::CAP_STORAGE_ADMIN, "format, partition and mount disks",
|
||||
"0:/os/icons/drive-harddisk.svg"},
|
||||
{montauk::abi::CAP_NETWORK_ADMIN, "change network settings and join Wi-Fi networks",
|
||||
"0:/os/icons/network-wired.svg"},
|
||||
{montauk::abi::CAP_SET_TIME, "change the system clock and time zone",
|
||||
"0:/os/icons/preferences-system-time.svg"},
|
||||
{montauk::abi::CAP_DISPLAY_ADMIN, "change the screen resolution and brightness",
|
||||
"0:/os/icons/preferences-desktop-display.svg"},
|
||||
{montauk::abi::CAP_LOG_READ, "read the system log, which can reveal what other apps and devices are doing",
|
||||
"0:/os/icons/utilities-log-viewer.svg"},
|
||||
};
|
||||
|
||||
// Fallback for a capability without its own icon.
|
||||
inline constexpr const char* DEFAULT_ICON = "0:/os/icons/lock.svg";
|
||||
|
||||
inline const char* action_for(uint64_t bit) {
|
||||
for (const auto& t : TEXTS) {
|
||||
if (t.bit == bit) return t.action;
|
||||
}
|
||||
return "use a protected system capability";
|
||||
}
|
||||
|
||||
inline const char* icon_for(uint64_t bit) {
|
||||
for (const auto& t : TEXTS) {
|
||||
if (t.bit == bit) return t.icon;
|
||||
}
|
||||
return DEFAULT_ICON;
|
||||
}
|
||||
|
||||
inline const char* name_for(uint64_t bit) {
|
||||
for (const auto& n : montauk::caps::NAMES) {
|
||||
if (n.bit == bit) return n.name;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
// One stored decision, as read from the table. Only programs installed
|
||||
// under 0:/os or 0:/apps are ever stored: those paths cannot be rewritten
|
||||
// without CAP_SYSTEM_IMAGE, so the path alone identifies the program.
|
||||
// The kernel reports this as ConsentRequest::imageProtected.
|
||||
struct Decision {
|
||||
char id[16];
|
||||
char path[256];
|
||||
uint64_t capability;
|
||||
bool allow;
|
||||
};
|
||||
|
||||
namespace detail {
|
||||
inline void key(char* out, int cap, const char* id, const char* field) {
|
||||
int n = 0;
|
||||
for (const char* p = STORE_PREFIX; *p && n < cap - 1; p++) out[n++] = *p;
|
||||
for (const char* p = id; *p && n < cap - 1; p++) out[n++] = *p;
|
||||
if (n < cap - 1) out[n++] = '.';
|
||||
for (const char* p = field; *p && n < cap - 1; p++) out[n++] = *p;
|
||||
out[n] = '\0';
|
||||
}
|
||||
|
||||
// Ids of "decision.<id>.path" entries, in file order.
|
||||
inline int collect_ids(const montauk::toml::Doc& doc, char (*ids)[16], int max) {
|
||||
int count = 0;
|
||||
int prefixLen = montauk::slen(STORE_PREFIX);
|
||||
for (int i = 0; i < doc.entries.count && count < max; i++) {
|
||||
montauk::toml::Value* v = doc.entries.items[i];
|
||||
if (v == nullptr || v->key == nullptr) continue;
|
||||
if (!montauk::starts_with(v->key, STORE_PREFIX)) continue;
|
||||
const char* rest = v->key + prefixLen;
|
||||
int dot = 0;
|
||||
while (rest[dot] && rest[dot] != '.') dot++;
|
||||
if (rest[dot] != '.' || !montauk::streq(rest + dot + 1, "path")) continue;
|
||||
if (dot <= 0 || dot >= 16) continue;
|
||||
montauk::memcpy(ids[count], rest, dot);
|
||||
ids[count][dot] = '\0';
|
||||
count++;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
inline bool read(const montauk::toml::Doc& doc, const char* id, Decision& out) {
|
||||
char k[64];
|
||||
montauk::memset(&out, 0, sizeof(out));
|
||||
montauk::strncpy(out.id, id, sizeof(out.id));
|
||||
key(k, sizeof(k), id, "path");
|
||||
montauk::strncpy(out.path, doc.get_string(k, ""), sizeof(out.path));
|
||||
key(k, sizeof(k), id, "capability");
|
||||
out.capability = montauk::caps::bit_for_name(doc.get_string(k, ""));
|
||||
key(k, sizeof(k), id, "decision");
|
||||
out.allow = montauk::streq(doc.get_string(k, ""), "allow");
|
||||
// "all" or an unknown name is never a valid stored decision.
|
||||
return out.path[0] &&
|
||||
(out.capability & montauk::abi::CAP_CONSENT_ELIGIBLE) != 0 &&
|
||||
(out.capability & (out.capability - 1)) == 0;
|
||||
}
|
||||
|
||||
inline void remove_id(montauk::toml::Doc* doc, const char* id) {
|
||||
static const char* fields[] = {"path", "capability", "decision"};
|
||||
char k[64];
|
||||
for (const char* f : fields) {
|
||||
key(k, sizeof(k), id, f);
|
||||
montauk::config::unset(doc, k);
|
||||
}
|
||||
}
|
||||
} // namespace detail
|
||||
|
||||
// Read up to `max` stored decisions. Returns the count.
|
||||
inline int load_all(Decision* out, int max) {
|
||||
montauk::toml::Doc doc = montauk::config::load(STORE_CONFIG);
|
||||
char ids[128][16];
|
||||
int idCount = detail::collect_ids(doc, ids, 128);
|
||||
int count = 0;
|
||||
for (int i = 0; i < idCount && count < max; i++) {
|
||||
if (detail::read(doc, ids[i], out[count])) count++;
|
||||
}
|
||||
doc.destroy();
|
||||
return count;
|
||||
}
|
||||
|
||||
// Stored answer for (path, capability): 1 allow, 0 deny, -1 none.
|
||||
inline int lookup(const char* path, uint64_t capability) {
|
||||
montauk::toml::Doc doc = montauk::config::load(STORE_CONFIG);
|
||||
char ids[128][16];
|
||||
int idCount = detail::collect_ids(doc, ids, 128);
|
||||
int result = -1;
|
||||
for (int i = 0; i < idCount && result < 0; i++) {
|
||||
Decision d;
|
||||
if (!detail::read(doc, ids[i], d)) continue;
|
||||
if (d.capability == capability && montauk::streq(d.path, path)) {
|
||||
result = d.allow ? 1 : 0;
|
||||
}
|
||||
}
|
||||
doc.destroy();
|
||||
return result;
|
||||
}
|
||||
|
||||
// Store an answer, replacing any earlier one for the same path and
|
||||
// capability. Returns 0 on success, negative when the table cannot be
|
||||
// written.
|
||||
inline int record(const char* path, uint64_t capability, bool allow) {
|
||||
montauk::toml::Doc doc = montauk::config::load(STORE_CONFIG);
|
||||
char ids[128][16];
|
||||
int idCount = detail::collect_ids(doc, ids, 128);
|
||||
int nextId = 1;
|
||||
for (int i = 0; i < idCount; i++) {
|
||||
Decision d;
|
||||
bool valid = detail::read(doc, ids[i], d);
|
||||
if (valid && d.capability == capability && montauk::streq(d.path, path))
|
||||
detail::remove_id(&doc, ids[i]);
|
||||
int n = 0;
|
||||
for (const char* p = ids[i]; *p >= '0' && *p <= '9'; p++) n = n * 10 + (*p - '0');
|
||||
if (n >= nextId) nextId = n + 1;
|
||||
}
|
||||
|
||||
char id[16];
|
||||
int len = 0;
|
||||
char rev[16];
|
||||
for (int n = nextId; n > 0 && len < 15; n /= 10) rev[len++] = (char)('0' + n % 10);
|
||||
for (int i = 0; i < len; i++) id[i] = rev[len - 1 - i];
|
||||
id[len] = '\0';
|
||||
|
||||
char k[64];
|
||||
detail::key(k, sizeof(k), id, "path");
|
||||
montauk::config::set_string(&doc, k, path);
|
||||
detail::key(k, sizeof(k), id, "capability");
|
||||
montauk::config::set_string(&doc, k, name_for(capability));
|
||||
detail::key(k, sizeof(k), id, "decision");
|
||||
montauk::config::set_string(&doc, k, allow ? "allow" : "deny");
|
||||
|
||||
int rc = montauk::config::save(STORE_CONFIG, &doc);
|
||||
doc.destroy();
|
||||
return rc;
|
||||
}
|
||||
|
||||
// Drop one stored decision by id; the program is asked again next time.
|
||||
inline int forget(const char* id) {
|
||||
montauk::toml::Doc doc = montauk::config::load(STORE_CONFIG);
|
||||
detail::remove_id(&doc, id);
|
||||
int rc = montauk::config::save(STORE_CONFIG, &doc);
|
||||
doc.destroy();
|
||||
return rc;
|
||||
}
|
||||
|
||||
} // namespace consent
|
||||
} // namespace montauk
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* statuscodes.h
|
||||
* MontaukOS status codes
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
// Userspace mirror of kernel/src/Common/StatusCodes.hpp. Also usable from C
|
||||
// (the libc includes it), where the enumerators are unqualified.
|
||||
#ifdef __cplusplus
|
||||
namespace montauk {
|
||||
#endif
|
||||
|
||||
enum StatusCodes {
|
||||
Success = 0,
|
||||
GenericFailure = -1,
|
||||
PermissionDenied = -2,
|
||||
CapabilityDenied = -3,
|
||||
SystemIntegrityError = -4,
|
||||
BadPointer = -5,
|
||||
InvalidArgument = -6,
|
||||
UnsupportedOp = -7,
|
||||
NoDevice = -8,
|
||||
IoFailure = -9,
|
||||
BadInvocation = -10,
|
||||
NoSuchPath = -11,
|
||||
Busy = -12,
|
||||
BufferTooSmall = -13,
|
||||
OutOfMemory = -14
|
||||
};
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
#pragma once
|
||||
#include <Api/Syscall.hpp>
|
||||
#include <montauk/statuscodes.h>
|
||||
|
||||
namespace montauk {
|
||||
|
||||
@@ -120,6 +121,28 @@ namespace montauk {
|
||||
inline int spawn(const char* path, const char* args = nullptr) {
|
||||
return (int)syscall2(montauk::abi::SYS_SPAWN, (uint64_t)path, (uint64_t)args);
|
||||
}
|
||||
inline int spawn_with_caps(const char* path, const char* args,
|
||||
const char* user,
|
||||
const montauk::abi::SpawnCapabilities& capabilities) {
|
||||
return (int)syscall4(montauk::abi::SYS_SPAWN_CAPS, (uint64_t)path,
|
||||
(uint64_t)args, (uint64_t)user,
|
||||
(uint64_t)&capabilities);
|
||||
}
|
||||
// The calling process's own capability masks. Returns 0 on success.
|
||||
inline int getcaps(montauk::abi::SpawnCapabilities* out) {
|
||||
return (int)syscall1(montauk::abi::SYS_GETCAPS, (uint64_t)out);
|
||||
}
|
||||
|
||||
// Consent agent.
|
||||
inline int consent_register() {
|
||||
return (int)syscall0(montauk::abi::SYS_CONSENT_REGISTER);
|
||||
}
|
||||
inline int consent_next(montauk::abi::ConsentRequest* out) {
|
||||
return (int)syscall1(montauk::abi::SYS_CONSENT_NEXT, (uint64_t)out);
|
||||
}
|
||||
inline int consent_reply(uint64_t id, int decision) {
|
||||
return (int)syscall2(montauk::abi::SYS_CONSENT_REPLY, id, (uint64_t)decision);
|
||||
}
|
||||
inline int chdir(const char* path) {
|
||||
return (int)syscall1(montauk::abi::SYS_CHDIR, (uint64_t)path);
|
||||
}
|
||||
@@ -184,6 +207,10 @@ namespace montauk {
|
||||
|
||||
// Memory
|
||||
inline void* alloc(uint64_t size) { return (void*)syscall1(montauk::abi::SYS_ALLOC, size); }
|
||||
// As alloc(), but the kernel commits the whole range immediately. Use it
|
||||
// for a buffer that is about to be written end to end: the lazy path costs
|
||||
// one page fault, one mutex acquire and one VMA walk per 4 KiB.
|
||||
inline void* alloc_eager(uint64_t size) { return (void*)syscall1(montauk::abi::SYS_ALLOC_EAGER, size); }
|
||||
inline void free(void* ptr) { syscall1(montauk::abi::SYS_FREE, (uint64_t)ptr); }
|
||||
|
||||
// Timekeeping
|
||||
@@ -391,7 +418,10 @@ namespace montauk {
|
||||
}
|
||||
|
||||
// Timezone offset (total minutes from UTC)
|
||||
inline void settz(int offset_minutes) { syscall1(montauk::abi::SYS_SETTZ, (uint64_t)(int64_t)offset_minutes); }
|
||||
inline int settz(int offset_minutes) {
|
||||
return (int)syscall1(montauk::abi::SYS_SETTZ,
|
||||
(uint64_t)(int64_t)offset_minutes);
|
||||
}
|
||||
inline int gettz() { return (int)syscall0(montauk::abi::SYS_GETTZ); }
|
||||
|
||||
// Random number generation
|
||||
@@ -400,14 +430,12 @@ namespace montauk {
|
||||
}
|
||||
|
||||
// Power management
|
||||
[[noreturn]] inline void reset() {
|
||||
syscall0(montauk::abi::SYS_RESET);
|
||||
__builtin_unreachable();
|
||||
inline int reset() {
|
||||
return (int)syscall0(montauk::abi::SYS_RESET);
|
||||
}
|
||||
|
||||
[[noreturn]] inline void shutdown() {
|
||||
syscall0(montauk::abi::SYS_SHUTDOWN);
|
||||
__builtin_unreachable();
|
||||
inline int shutdown() {
|
||||
return (int)syscall0(montauk::abi::SYS_SHUTDOWN);
|
||||
}
|
||||
|
||||
inline int suspend() {
|
||||
@@ -423,6 +451,13 @@ namespace montauk {
|
||||
return (int)syscall1(montauk::abi::SYS_POWER_REQUEST, (uint64_t)(int64_t)action);
|
||||
}
|
||||
|
||||
// Non-destructive read of the pending request, for a session leader that
|
||||
// must stand down when something inside its session (the shell's shutdown
|
||||
// builtin, say) asked for power-off. Returns POWER_REQ_QUERY when idle.
|
||||
inline int power_request_pending() {
|
||||
return power_request(montauk::abi::POWER_REQ_PEEK);
|
||||
}
|
||||
|
||||
// Mouse
|
||||
inline void mouse_state(montauk::abi::MouseState* out) { syscall1(montauk::abi::SYS_MOUSESTATE, (uint64_t)out); }
|
||||
inline void set_mouse_bounds(int32_t maxX, int32_t maxY) {
|
||||
@@ -447,6 +482,13 @@ namespace montauk {
|
||||
inline int spawn_redir(const char* path, const char* args = nullptr) {
|
||||
return (int)syscall2(montauk::abi::SYS_SPAWN_REDIR, (uint64_t)path, (uint64_t)args);
|
||||
}
|
||||
inline int spawn_redir_with_caps(
|
||||
const char* path, const char* args,
|
||||
const montauk::abi::SpawnCapabilities& capabilities) {
|
||||
return (int)syscall3(montauk::abi::SYS_SPAWN_REDIR_CAPS,
|
||||
(uint64_t)path, (uint64_t)args,
|
||||
(uint64_t)&capabilities);
|
||||
}
|
||||
inline int childio_read(int childPid, char* buf, int maxLen) {
|
||||
return (int)syscall3(montauk::abi::SYS_CHILDIO_READ, (uint64_t)childPid, (uint64_t)buf, (uint64_t)maxLen);
|
||||
}
|
||||
@@ -484,6 +526,10 @@ namespace montauk {
|
||||
inline int partlist(montauk::abi::PartInfo* buf, int max) {
|
||||
return (int)syscall2(montauk::abi::SYS_PARTLIST, (uint64_t)buf, (uint64_t)max);
|
||||
}
|
||||
// One entry per mounted volume (one filesystem, i.e. one drive number).
|
||||
inline int volinfo(montauk::abi::VolInfo* buf, int max) {
|
||||
return (int)syscall2(montauk::abi::SYS_VOLINFO, (uint64_t)buf, (uint64_t)max);
|
||||
}
|
||||
|
||||
// Raw block device I/O (driver-agnostic)
|
||||
inline int64_t disk_read(int blockDev, uint64_t lba, uint32_t sectorCount, void* buf) {
|
||||
@@ -613,6 +659,13 @@ namespace montauk {
|
||||
return (int)syscall1(montauk::abi::SYS_BTINFO, (uint64_t)buf);
|
||||
}
|
||||
|
||||
// ACPI battery status. Returns 0 on success, or -1 on a machine with no
|
||||
// battery device at all -- which is the signal not to draw an indicator.
|
||||
// A laptop with the pack removed still returns 0, with present == 0.
|
||||
inline int battery_info(montauk::abi::BatteryInfo* out) {
|
||||
return (int)syscall1(montauk::abi::SYS_BATTERYINFO, (uint64_t)out);
|
||||
}
|
||||
|
||||
// Wi-Fi. scan() runs a full channel sweep and blocks until it finishes or
|
||||
// timeoutMs elapses, then fills buf with the networks seen; it returns the
|
||||
// number of entries written, or -1 when no adapter is ready.
|
||||
|
||||
@@ -219,6 +219,16 @@ namespace user {
|
||||
return false;
|
||||
}
|
||||
|
||||
inline bool is_admin(const char* username) {
|
||||
UserInfo users[MAX_USERS];
|
||||
int count = load_users(users, MAX_USERS);
|
||||
for (int i = 0; i < count; i++) {
|
||||
if (montauk::streq(users[i].username, username))
|
||||
return montauk::streq(users[i].role, "admin");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// ---- User management ----
|
||||
|
||||
inline bool create_user(const char* username, const char* display_name,
|
||||
|
||||
Reference in new issue
Block a user