feat: implement kernel capability model
This commit is contained in:
@@ -0,0 +1,126 @@
|
||||
# MontaukOS capability grants
|
||||
#
|
||||
#
|
||||
# Capability names: process_admin, power_request, power_control, suspend,
|
||||
# storage_admin, raw_storage, network_admin, set_time, user_admin,
|
||||
# display_admin, device_admin, log_read, system_image, and "all".
|
||||
#
|
||||
# "all" is every capability except system_image, which must always be named
|
||||
# explicitly: it is write access to 0:/os and 0:/apps, and since grants are
|
||||
# keyed on binary path, holding it is equivalent to holding every capability
|
||||
# the system can issue from the next launch onwards. Nothing is granted it.
|
||||
#
|
||||
# A grant is always clamped by the kernel to what the launching process may
|
||||
# actually delegate, so "all" in a launcher entry means "whatever this session
|
||||
# was given", not "root".
|
||||
#
|
||||
#
|
||||
|
||||
# ==== System services (started by init) ====
|
||||
|
||||
[grant.dhcp]
|
||||
path = "0:/os/dhcp.elf"
|
||||
effective = ["network_admin"]
|
||||
|
||||
[grant.ntp]
|
||||
path = "0:/os/ntp.elf"
|
||||
effective = ["set_time"]
|
||||
|
||||
[grant.login]
|
||||
path = "0:/os/login.elf"
|
||||
permitted = ["all"]
|
||||
effective = [
|
||||
"power_control", "storage_admin", "device_admin",
|
||||
"user_admin", "process_admin", "log_read",
|
||||
]
|
||||
delegable = [
|
||||
"power_request", "suspend", "process_admin", "storage_admin",
|
||||
"raw_storage", "network_admin", "set_time", "user_admin",
|
||||
"display_admin", "device_admin", "log_read",
|
||||
]
|
||||
|
||||
[grant.sshd]
|
||||
path = "0:/os/sshd.elf"
|
||||
effective = ["user_admin"]
|
||||
|
||||
[grant.desktop]
|
||||
path = "0:/os/desktop.elf"
|
||||
effective = [
|
||||
"power_request", "suspend", "process_admin", "storage_admin",
|
||||
"raw_storage", "network_admin", "set_time", "user_admin",
|
||||
"display_admin", "device_admin", "log_read",
|
||||
]
|
||||
delegable = [
|
||||
"power_request", "suspend", "process_admin", "storage_admin",
|
||||
"raw_storage", "network_admin", "set_time", "user_admin",
|
||||
"display_admin", "device_admin", "log_read",
|
||||
]
|
||||
|
||||
# The console session launchers. terminal.elf needs no authority of its own;
|
||||
# it exists to pass the session's authority to the shell it hosts. The shell
|
||||
# in turn exercises only power_request and suspend, but delegates on the same
|
||||
# terms as the desktop so that the tools below work from a console. Both are
|
||||
# clamped to the launching session: a standard session narrows this to
|
||||
# power_request and suspend, and an unprivileged one to nothing.
|
||||
[grant.terminal]
|
||||
path = "0:/apps/terminal/terminal.elf"
|
||||
delegable = ["all"]
|
||||
|
||||
[grant.shell]
|
||||
path = "0:/os/shell.elf"
|
||||
effective = ["power_request", "suspend"]
|
||||
delegable = ["all"]
|
||||
|
||||
# ==== Settings and administrative applications ====
|
||||
|
||||
[grant.procmgr]
|
||||
path = "0:/apps/procmgr/procmgr.elf"
|
||||
effective = ["process_admin"]
|
||||
|
||||
# May restart the DHCP client, so it needs to pass network_admin on.
|
||||
[grant.network]
|
||||
path = "0:/apps/network/network.elf"
|
||||
effective = ["network_admin"]
|
||||
delegable = ["network_admin"]
|
||||
|
||||
[grant.display]
|
||||
path = "0:/apps/display/display.elf"
|
||||
effective = ["display_admin"]
|
||||
|
||||
[grant.disks]
|
||||
path = "0:/apps/disks/disks.elf"
|
||||
effective = ["storage_admin", "raw_storage"]
|
||||
|
||||
[grant.installer]
|
||||
path = "0:/apps/installer/installer.elf"
|
||||
effective = ["storage_admin", "raw_storage"]
|
||||
|
||||
[grant.timezone]
|
||||
path = "0:/apps/timezone/timezone.elf"
|
||||
effective = ["set_time"]
|
||||
|
||||
[grant.bluetooth]
|
||||
path = "0:/apps/bluetooth/bluetooth.elf"
|
||||
effective = ["device_admin"]
|
||||
|
||||
[grant.syslog]
|
||||
path = "0:/apps/syslog/syslog.elf"
|
||||
effective = ["log_read"]
|
||||
|
||||
[grant.sshserver]
|
||||
path = "0:/apps/sshserver/sshserver.elf"
|
||||
effective = ["user_admin"]
|
||||
|
||||
# ==== Console tools ====
|
||||
|
||||
[grant.ifconfig]
|
||||
path = "0:/os/ifconfig.elf"
|
||||
effective = ["network_admin"]
|
||||
|
||||
[grant.wifi]
|
||||
path = "0:/os/wifi.elf"
|
||||
effective = ["network_admin"]
|
||||
|
||||
[grant.sdr]
|
||||
path = "0:/os/sdr.elf"
|
||||
effective = ["device_admin"]
|
||||
@@ -250,6 +250,63 @@ namespace montauk::abi {
|
||||
|
||||
static constexpr uint64_t SYS_LOG_WRITE = 176; // (logMessage) -> 0
|
||||
static constexpr uint64_t SYS_TERMINAL_ATTACHED = 177; // () -> 1 when connected to a userspace terminal
|
||||
static constexpr uint64_t SYS_SPAWN_CAPS = 185;
|
||||
static constexpr uint64_t SYS_SPAWN_REDIR_CAPS = 186;
|
||||
|
||||
/* Kernel-owned process capabilities. User identities may namespace
|
||||
per-user resources, but never participate in authorization decisions. */
|
||||
static constexpr uint64_t CAP_PROCESS_ADMIN = 1ULL << 0;
|
||||
static constexpr uint64_t CAP_POWER_REQUEST = 1ULL << 1;
|
||||
static constexpr uint64_t CAP_POWER_CONTROL = 1ULL << 2;
|
||||
static constexpr uint64_t CAP_SUSPEND = 1ULL << 3;
|
||||
static constexpr uint64_t CAP_STORAGE_ADMIN = 1ULL << 4;
|
||||
static constexpr uint64_t CAP_RAW_STORAGE = 1ULL << 5;
|
||||
static constexpr uint64_t CAP_NETWORK_ADMIN = 1ULL << 6;
|
||||
static constexpr uint64_t CAP_SET_TIME = 1ULL << 7;
|
||||
static constexpr uint64_t CAP_USER_ADMIN = 1ULL << 8;
|
||||
static constexpr uint64_t CAP_DISPLAY_ADMIN = 1ULL << 9;
|
||||
static constexpr uint64_t CAP_DEVICE_ADMIN = 1ULL << 10;
|
||||
static constexpr uint64_t CAP_LOG_READ = 1ULL << 11;
|
||||
/* Write to the program images the system boots and runs (0:/os,
|
||||
0:/apps). Deliberately separate from CAP_STORAGE_ADMIN: grants are
|
||||
keyed on binary path, so writing an image is equivalent to acquiring
|
||||
whatever that image is granted at its next launch. Formatting a data
|
||||
volume must not carry that authority with it. */
|
||||
static constexpr uint64_t CAP_SYSTEM_IMAGE = 1ULL << 12;
|
||||
static constexpr uint64_t CAP_ALL = (1ULL << 13) - 1;
|
||||
static constexpr uint64_t CAP_STANDARD_SESSION = CAP_POWER_REQUEST | CAP_SUSPEND;
|
||||
static constexpr uint64_t CAP_ADMIN_SESSION =
|
||||
CAP_STANDARD_SESSION | CAP_PROCESS_ADMIN | CAP_STORAGE_ADMIN |
|
||||
CAP_RAW_STORAGE | CAP_NETWORK_ADMIN | CAP_SET_TIME | CAP_USER_ADMIN |
|
||||
CAP_DISPLAY_ADMIN | CAP_DEVICE_ADMIN | CAP_LOG_READ;
|
||||
static_assert((CAP_STANDARD_SESSION & ~CAP_ADMIN_SESSION) == 0);
|
||||
static_assert((CAP_ADMIN_SESSION & CAP_POWER_CONTROL) == 0,
|
||||
"final power control belongs only to the session supervisor");
|
||||
static_assert((CAP_ADMIN_SESSION & CAP_SYSTEM_IMAGE) == 0,
|
||||
"an admin session must not imply authority to rewrite the "
|
||||
"programs it launches; grant CAP_SYSTEM_IMAGE per binary");
|
||||
static constexpr int SYS_ERR_PERMISSION = -13;
|
||||
|
||||
struct SpawnCapabilities {
|
||||
uint64_t permitted;
|
||||
uint64_t effective;
|
||||
uint64_t delegable;
|
||||
};
|
||||
|
||||
constexpr bool ValidCapabilityDelegation(const SpawnCapabilities& child,
|
||||
uint64_t parentDelegable) {
|
||||
return (child.permitted & ~CAP_ALL) == 0 &&
|
||||
(child.effective & ~child.permitted) == 0 &&
|
||||
(child.delegable & ~child.permitted) == 0 &&
|
||||
(child.permitted & ~parentDelegable) == 0 &&
|
||||
(child.delegable & ~parentDelegable) == 0;
|
||||
}
|
||||
static_assert(ValidCapabilityDelegation(
|
||||
{CAP_NETWORK_ADMIN, CAP_NETWORK_ADMIN, 0}, CAP_NETWORK_ADMIN));
|
||||
static_assert(!ValidCapabilityDelegation(
|
||||
{CAP_NETWORK_ADMIN, CAP_NETWORK_ADMIN, CAP_NETWORK_ADMIN}, 0));
|
||||
static_assert(!ValidCapabilityDelegation(
|
||||
{CAP_NETWORK_ADMIN, CAP_NETWORK_ADMIN | CAP_SET_TIME, 0}, CAP_ALL));
|
||||
|
||||
|
||||
static constexpr int USB_ERR_INVALID = -1;
|
||||
@@ -264,10 +321,17 @@ namespace montauk::abi {
|
||||
// Graceful power-off request actions (SYS_POWER_REQUEST). The desktop posts
|
||||
// a pending action and exits; login.elf reads it, runs the shutdown stages,
|
||||
// then issues the matching SYS_SHUTDOWN / SYS_RESET.
|
||||
//
|
||||
// A request can also be posted from inside the session -- the shell's
|
||||
// shutdown builtin does. login only looks at it once the session leader
|
||||
// exits, so the leader has to notice and stand down: POWER_REQ_PEEK is the
|
||||
// non-destructive read it polls with. Only login consumes (QUERY), so a
|
||||
// leader that peeks cannot swallow the request it is meant to act on.
|
||||
enum PowerRequestAction : int {
|
||||
POWER_REQ_QUERY = 0, // read-and-clear the pending action
|
||||
POWER_REQ_SHUTDOWN = 1,
|
||||
POWER_REQ_REBOOT = 2,
|
||||
POWER_REQ_PEEK = 3, // read the pending action without clearing it
|
||||
};
|
||||
|
||||
static constexpr uint32_t CLIPBOARD_MAX_TEXT_BYTES = 256 * 1024;
|
||||
@@ -729,6 +793,9 @@ namespace montauk::abi {
|
||||
char name[64];
|
||||
uint64_t heapUsed; // Distance from UserHeapBase to high-water mark
|
||||
uint64_t cpuTimeMs; // accumulated scheduler runtime
|
||||
uint64_t permittedCaps;
|
||||
uint64_t effectiveCaps;
|
||||
uint64_t delegableCaps;
|
||||
};
|
||||
|
||||
struct MemStats {
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include <montauk/syscall.h>
|
||||
#include <montauk/string.h>
|
||||
#include <Api/Syscall.hpp>
|
||||
#include <montauk/capabilities.h>
|
||||
|
||||
namespace gui {
|
||||
|
||||
@@ -212,7 +213,19 @@ static inline void terminal_init(TerminalState* t, int cols, int rows) {
|
||||
terminal_init_cells(t, cols, rows, TERM_MAX_SCROLLBACK);
|
||||
t->cursor_visible = true;
|
||||
|
||||
t->child_pid = montauk::spawn_redir("0:/os/shell.elf");
|
||||
// A console shell is a session launcher, exactly like the desktop, and is
|
||||
// granted on exactly the same terms: 0:/config/capabilities.toml decides
|
||||
// what shell.elf receives, and the kernel clamps that to what this session
|
||||
// was actually delegated. Deriving the grant here instead would be a
|
||||
// second, hardcoded list of "capabilities a console may confer" -- one the
|
||||
// table cannot see and cannot keep in step with.
|
||||
static constexpr const char* kShellPath = "0:/os/shell.elf";
|
||||
montauk::abi::SpawnCapabilities caps =
|
||||
montauk::caps::for_binary(kShellPath, montauk::caps::self_delegable());
|
||||
|
||||
t->child_pid = (caps.permitted != 0)
|
||||
? montauk::spawn_redir_with_caps(kShellPath, nullptr, caps)
|
||||
: montauk::spawn_redir(kShellPath);
|
||||
if (t->child_pid > 0)
|
||||
montauk::childio_settermsz(t->child_pid, cols, rows);
|
||||
}
|
||||
|
||||
@@ -208,6 +208,8 @@ extern "C" {
|
||||
#define MTK_SYS_USB_BULK_IN_START 182
|
||||
#define MTK_SYS_USB_BULK_IN_STOP 183
|
||||
#define MTK_SYS_USB_BULK_IN_READ 184
|
||||
#define MTK_SYS_SPAWN_CAPS 185
|
||||
#define MTK_SYS_SPAWN_REDIR_CAPS 186
|
||||
/* @SYSCALLS-END */
|
||||
|
||||
#define MTK_SOCK_TCP 1
|
||||
@@ -683,8 +685,8 @@ static inline int mtk_set_unix_time(int64_t unix_seconds) {
|
||||
return (int)_mtk_syscall1(MTK_SYS_SETUNIXTIME, (long)unix_seconds);
|
||||
}
|
||||
|
||||
static inline void mtk_settz(int offset_minutes) {
|
||||
_mtk_syscall1(MTK_SYS_SETTZ, (long)offset_minutes);
|
||||
static inline int mtk_settz(int offset_minutes) {
|
||||
return (int)_mtk_syscall1(MTK_SYS_SETTZ, (long)offset_minutes);
|
||||
}
|
||||
|
||||
static inline int mtk_gettz(void) {
|
||||
@@ -883,12 +885,12 @@ static inline int mtk_audio_ctl(int handle, int cmd, int value) {
|
||||
Power management
|
||||
==================================================================== */
|
||||
|
||||
static inline void mtk_reset(void) {
|
||||
_mtk_syscall0(MTK_SYS_RESET);
|
||||
static inline int mtk_reset(void) {
|
||||
return (int)_mtk_syscall0(MTK_SYS_RESET);
|
||||
}
|
||||
|
||||
static inline void mtk_shutdown(void) {
|
||||
_mtk_syscall0(MTK_SYS_SHUTDOWN);
|
||||
static inline int mtk_shutdown(void) {
|
||||
return (int)_mtk_syscall0(MTK_SYS_SHUTDOWN);
|
||||
}
|
||||
|
||||
/* ====================================================================
|
||||
|
||||
@@ -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>
|
||||
|
||||
/*
|
||||
* Launchers (init, the desktop, the shell) look up the authority a program
|
||||
* should receive here instead of each carrying its own compiled-in table.
|
||||
*
|
||||
* This file is advisory, never authoritative. Every grant still goes
|
||||
* through SYS_SPAWN_CAPS and is validated in the kernel against the
|
||||
* caller's own delegable set, so nothing written here can produce authority
|
||||
* the kernel has not already delegated to the launcher. A missing,
|
||||
* truncated or hostile file can only ever result in a program receiving
|
||||
* less authority than intended. That is why the table can live in
|
||||
* userspace TOML: the kernel enumerates protected paths, userspace
|
||||
* interprets policy.
|
||||
*
|
||||
* Grants are keyed on the resolved binary path, which is what makes the
|
||||
* table safe to hand to init: pointing a privileged service entry at a
|
||||
* different executable looks up the new path, finds no entry, and grants
|
||||
* nothing. The kernel write-protects 0:/apps and 0:/os so the path cannot
|
||||
* be made to refer to a substituted image.
|
||||
*/
|
||||
|
||||
namespace montauk {
|
||||
namespace caps {
|
||||
|
||||
inline constexpr const char* GRANT_CONFIG = "capabilities";
|
||||
inline constexpr const char* GRANT_PREFIX = "grant.";
|
||||
inline constexpr int MAX_SCAN_PROCS = 256;
|
||||
|
||||
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},
|
||||
};
|
||||
|
||||
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", which the
|
||||
// caller-delegable clamp in for_binary() then narrows. It excludes
|
||||
// CAP_SYSTEM_IMAGE: authority to rewrite a program image is never
|
||||
// something a wildcard should hand out, only an explicit name.
|
||||
if (montauk::streq(name, "all"))
|
||||
return montauk::abi::CAP_ALL & ~montauk::abi::CAP_SYSTEM_IMAGE;
|
||||
for (const auto& entry : NAMES) {
|
||||
if (montauk::streq(entry.name, name)) return entry.bit;
|
||||
}
|
||||
// Unknown names are ignored rather than rejected. Failing closed
|
||||
// costs a program some authority; failing open would hand out
|
||||
// authority nobody asked for.
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Read an array-of-strings key into a capability mask. A missing key is
|
||||
// an empty mask, which is the correct default for an absent grant.
|
||||
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, 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);
|
||||
build_sibling_key(entry->key, "permitted", sibling, sizeof(sibling));
|
||||
uint64_t permitted = mask_from_key(doc, sibling);
|
||||
|
||||
// A grant that does not name `permitted` owns exactly what it can
|
||||
// use or pass on. Declaring it separately is only needed by a
|
||||
// supervisor that holds authority in reserve (login).
|
||||
if (permitted == 0) permitted = effective | delegable;
|
||||
|
||||
out.permitted = permitted;
|
||||
out.effective = effective;
|
||||
out.delegable = delegable;
|
||||
found = true;
|
||||
}
|
||||
|
||||
doc.destroy();
|
||||
return found;
|
||||
}
|
||||
|
||||
// The calling process's own capability masks.
|
||||
//
|
||||
// There is no syscall to ask "what am I?", so this scans the process table
|
||||
// for our own PID. The buffer is heap-allocated because ProcInfo is large
|
||||
// enough that MAX_SCAN_PROCS of them would be a ~29 KB stack frame.
|
||||
inline bool self(montauk::abi::SpawnCapabilities& out) {
|
||||
out = {0, 0, 0};
|
||||
|
||||
auto* table = (montauk::abi::ProcInfo*)montauk::malloc(
|
||||
sizeof(montauk::abi::ProcInfo) * MAX_SCAN_PROCS);
|
||||
if (table == nullptr) return false;
|
||||
|
||||
int count = montauk::proclist(table, MAX_SCAN_PROCS);
|
||||
int self_pid = montauk::getpid();
|
||||
|
||||
bool found = false;
|
||||
for (int i = 0; i < count; i++) {
|
||||
if (table[i].pid != self_pid) continue;
|
||||
out.permitted = table[i].permittedCaps;
|
||||
out.effective = table[i].effectiveCaps;
|
||||
out.delegable = table[i].delegableCaps;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
|
||||
montauk::mfree(table);
|
||||
return found;
|
||||
}
|
||||
|
||||
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. The kernel enforces the same bound; clamping here
|
||||
// means a launcher that holds less authority than the table declares
|
||||
// degrades to a reduced grant instead of failing the spawn outright.
|
||||
inline montauk::abi::SpawnCapabilities for_binary(const char* binary_path,
|
||||
uint64_t caller_delegable) {
|
||||
montauk::abi::SpawnCapabilities caps{0, 0, 0};
|
||||
|
||||
// A caller with nothing to delegate cannot produce a non-empty grant,
|
||||
// so skip the file read entirely. This is the common case: every
|
||||
// unprivileged session, on every launch.
|
||||
if (caller_delegable == 0) return caps;
|
||||
|
||||
if (!lookup(binary_path, caps)) return caps;
|
||||
|
||||
caps.permitted &= caller_delegable;
|
||||
caps.effective &= caps.permitted;
|
||||
caps.delegable &= caps.permitted;
|
||||
return caps;
|
||||
}
|
||||
|
||||
} // namespace caps
|
||||
} // namespace montauk
|
||||
@@ -120,6 +120,13 @@ 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);
|
||||
}
|
||||
inline int chdir(const char* path) {
|
||||
return (int)syscall1(montauk::abi::SYS_CHDIR, (uint64_t)path);
|
||||
}
|
||||
@@ -391,7 +398,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 +410,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 +431,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 +462,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);
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -129,8 +129,14 @@
|
||||
fontscale [n] Get or set terminal font scale -- see fontscale(1)
|
||||
lua Lua interpreter
|
||||
tcc TinyCC (in-system C compiler)
|
||||
reset Reboot the system
|
||||
shutdown Shut down the system
|
||||
reset / reboot Request a supervised system reboot
|
||||
shutdown / poweroff Request a supervised system shutdown
|
||||
suspend Enter ACPI sleep
|
||||
|
||||
Interactive shutdown and reboot are shell builtins: they post a
|
||||
capability-checked request and exit the console, allowing login.elf to
|
||||
flush filesystems and perform the final power operation. They do not grant
|
||||
CAP_POWER_CONTROL to the shell.
|
||||
|
||||
.SS Network commands
|
||||
ping <host> Send ICMP echo requests -- see ping(1)
|
||||
|
||||
+147
-35
@@ -3,7 +3,7 @@
|
||||
syscalls - overview of MontaukOS system calls
|
||||
|
||||
.SH DESCRIPTION
|
||||
MontaukOS provides 176 system calls (numbers 0-184, with numbers
|
||||
MontaukOS provides 178 system calls (numbers 0-186, with numbers
|
||||
140-148 reserved) for userspace programs. Syscalls use the x86-64
|
||||
SYSCALL instruction with the following register convention:
|
||||
|
||||
@@ -20,6 +20,66 @@
|
||||
montauk:: namespace. This page groups syscalls the same way the
|
||||
kernel source does (one subsystem header per group).
|
||||
|
||||
.SH CAPABILITY SECURITY
|
||||
Privileged authority is stored in kernel-owned process credentials, not
|
||||
inferred from a process name, PID, executable path, or merely from the
|
||||
owner name returned by SYS_GETUSER. The owner identity may namespace
|
||||
per-user resources such as the clipboard, but no owner name implies
|
||||
administrative authority. The kernel-created init process is
|
||||
the root of the delegation tree. Login authenticates a user and delegates
|
||||
the appropriate session capabilities; there is no special "system" user
|
||||
shortcut in the kernel.
|
||||
|
||||
Each process has three uint64_t masks, exposed in ProcInfo:
|
||||
|
||||
permitted capabilities owned by the process
|
||||
effective permitted capabilities accepted by syscall checks
|
||||
delegable permitted capabilities that may be given to children
|
||||
|
||||
Effective and delegable must be subsets of permitted. SYS_SPAWN and the
|
||||
other ordinary spawn variants give the child no capabilities. A parent
|
||||
uses SYS_SPAWN_CAPS to make an explicit delegation. Every requested
|
||||
permitted or delegable bit must be present in the parent's delegable mask,
|
||||
so a non-delegable grant cannot be propagated through another generation.
|
||||
Invalid or unauthorized requests return SYS_ERR_PERMISSION (-13).
|
||||
|
||||
Capability bits and protected operations are:
|
||||
|
||||
CAP_PROCESS_ADMIN kill unrelated processes or whole sessions
|
||||
CAP_POWER_REQUEST post a graceful shutdown/reboot request
|
||||
CAP_POWER_CONTROL consume power requests; reset or power off
|
||||
CAP_SUSPEND enter ACPI sleep
|
||||
CAP_STORAGE_ADMIN change partitions, mounts, or filesystems
|
||||
CAP_RAW_STORAGE raw disk reads and writes
|
||||
CAP_NETWORK_ADMIN change network/Wi-Fi configuration
|
||||
CAP_SET_TIME set wall-clock time or timezone
|
||||
CAP_USER_ADMIN manage users/trusted config; override owner at spawn
|
||||
CAP_DISPLAY_ADMIN set display mode or brightness
|
||||
CAP_DEVICE_ADMIN claim USB interfaces or change Bluetooth state
|
||||
CAP_LOG_READ read the kernel log
|
||||
CAP_SYSTEM_IMAGE write the program images in 0:/os and 0:/apps
|
||||
|
||||
CAP_STANDARD_SESSION contains CAP_POWER_REQUEST and CAP_SUSPEND.
|
||||
CAP_ADMIN_SESSION adds the administrative capabilities above except
|
||||
CAP_POWER_CONTROL and CAP_SYSTEM_IMAGE. Final shutdown/reset authority is
|
||||
deliberately retained by login, the trusted session supervisor.
|
||||
SYS_POWERINFO remains readable without a capability, so the powermgr GUI
|
||||
and power command are monitors, not privileged power daemons.
|
||||
|
||||
CAP_SYSTEM_IMAGE is likewise excluded from every session and from the
|
||||
"all" wildcard in 0:/config/capabilities.toml, and must be named
|
||||
explicitly to be granted. Capability grants are keyed on binary path, so
|
||||
write access to 0:/os or 0:/apps is equivalent to holding whatever those
|
||||
images are granted the next time a launcher runs them. Partitioning or
|
||||
formatting a volume is CAP_STORAGE_ADMIN and does not carry it.
|
||||
|
||||
The authentication and trusted-service configuration files users.toml,
|
||||
setup.toml, init.toml, and ssh.toml are readable by ordinary processes but
|
||||
require CAP_USER_ADMIN to create, replace, delete, or write. The kernel
|
||||
also protects the 0:/config directory entry against replacement. This
|
||||
prevents changing a userspace role string from becoming a route to new
|
||||
kernel authority at the next login or boot.
|
||||
|
||||
.SH PROCESS MANAGEMENT
|
||||
.B SYS_EXIT (0)
|
||||
Terminate the calling process.
|
||||
@@ -39,9 +99,18 @@
|
||||
|
||||
.B SYS_SPAWN (20)
|
||||
Spawn a new process from an ELF binary on the VFS. The child inherits
|
||||
a snapshot of the caller's environment.
|
||||
a snapshot of the caller's environment but no capabilities.
|
||||
int montauk::spawn(const char* path, const char* args = nullptr);
|
||||
|
||||
.B SYS_SPAWN_CAPS (185)
|
||||
Spawn a child with explicit permitted, effective, and delegable masks.
|
||||
The child masks must satisfy the subset rules described under CAPABILITY
|
||||
SECURITY. Passing a non-null user override additionally requires the
|
||||
caller to have effective CAP_USER_ADMIN; null inherits the parent owner.
|
||||
int montauk::spawn_with_caps(
|
||||
const char* path, const char* args, const char* user,
|
||||
const montauk::abi::SpawnCapabilities& capabilities);
|
||||
|
||||
.B SYS_WAITPID (23)
|
||||
Block until the given process has exited. Returns 0-255 for a normal
|
||||
exit, 256 plus the signal number if it was killed or crashed, or 0 if
|
||||
@@ -64,11 +133,13 @@
|
||||
|
||||
.B SYS_PROCLIST (61)
|
||||
List running processes (pid, parent, state, name, heap usage,
|
||||
accumulated CPU time).
|
||||
accumulated CPU time, and permitted/effective/delegable capability masks).
|
||||
int montauk::proclist(montauk::abi::ProcInfo* buf, int max);
|
||||
|
||||
.B SYS_KILL (62)
|
||||
Terminate another process by PID.
|
||||
Terminate a process by PID. Any process may terminate one of its own
|
||||
descendants. Terminating an unrelated process requires
|
||||
CAP_PROCESS_ADMIN.
|
||||
int montauk::kill(int pid);
|
||||
|
||||
.B SYS_SETSESSION (174)
|
||||
@@ -79,6 +150,7 @@
|
||||
.B SYS_KILLSESSION (175)
|
||||
Terminate all live processes in a process session. Returns the number of
|
||||
members signalled; repeat until zero to wait for complete teardown.
|
||||
Requires CAP_PROCESS_ADMIN.
|
||||
int montauk::killsession(int sessionId);
|
||||
|
||||
.B SYS_CHDIR (96)
|
||||
@@ -93,7 +165,8 @@
|
||||
|
||||
.B SYS_SETUSER (92)
|
||||
Associate a process with a logged-in user name (used by login/session
|
||||
management).
|
||||
management and per-user resource isolation). Requires CAP_USER_ADMIN.
|
||||
The name never grants capabilities or implies administrator status.
|
||||
int montauk::setuser(int pid, const char* name);
|
||||
|
||||
.B SYS_GETUSER (93)
|
||||
@@ -256,12 +329,14 @@
|
||||
|
||||
.B SYS_SETUNIXTIME (153)
|
||||
Set the system wall clock from a UTC Unix timestamp. Returns 0 on
|
||||
success or -1 if the timestamp is outside the supported range.
|
||||
success or -1 if the timestamp is outside the supported range. Requires
|
||||
CAP_SET_TIME.
|
||||
int montauk::set_unix_time(int64_t unixSeconds);
|
||||
|
||||
.B SYS_SETTZ (90)
|
||||
Set the system-wide timezone offset, in minutes from UTC.
|
||||
void montauk::settz(int offset_minutes);
|
||||
Set the system-wide timezone offset, in minutes from UTC. Requires
|
||||
CAP_SET_TIME.
|
||||
int montauk::settz(int offset_minutes);
|
||||
|
||||
.B SYS_GETTZ (91)
|
||||
Get the current timezone offset, in minutes from UTC.
|
||||
@@ -328,7 +403,8 @@
|
||||
void montauk::get_netcfg(montauk::abi::NetCfg* out);
|
||||
|
||||
.B SYS_SETNETCFG (38)
|
||||
Set the network configuration (IP, mask, gateway, DNS server).
|
||||
Set the network configuration (IP, mask, gateway, DNS server). Requires
|
||||
CAP_NETWORK_ADMIN.
|
||||
int montauk::set_netcfg(const montauk::abi::NetCfg* cfg);
|
||||
|
||||
.B SYS_NETSTATUS (125)
|
||||
@@ -346,6 +422,7 @@
|
||||
.B SYS_WIFI_SCAN (158)
|
||||
Perform a channel scan and block until it finishes or timeoutMs
|
||||
elapses. Returns the number of results, or -1 if no adapter is ready.
|
||||
Requires CAP_NETWORK_ADMIN because it changes radio state.
|
||||
int montauk::wifi_scan(montauk::abi::WifiNetwork* buf,
|
||||
int maxCount, uint32_t timeoutMs);
|
||||
|
||||
@@ -355,16 +432,18 @@
|
||||
|
||||
.B SYS_WIFI_CONNECT (160)
|
||||
Join a network and block until the link is up or the attempt fails.
|
||||
Requires CAP_NETWORK_ADMIN.
|
||||
int montauk::wifi_connect(const char* ssid,
|
||||
const char* password);
|
||||
|
||||
.B SYS_WIFI_DISCONNECT (161)
|
||||
Disconnect from the current Wi-Fi network.
|
||||
Disconnect from the current Wi-Fi network. Requires CAP_NETWORK_ADMIN.
|
||||
int montauk::wifi_disconnect();
|
||||
|
||||
.B SYS_WIFI_SCAN_START (162)
|
||||
Start a non-blocking channel scan. Returns 0 if started, 1 if a scan
|
||||
is already running, or -1 if no adapter is ready.
|
||||
is already running, or -1 if no adapter is ready. Requires
|
||||
CAP_NETWORK_ADMIN.
|
||||
int montauk::wifi_scan_start(uint32_t timeoutMs);
|
||||
|
||||
.B SYS_WIFI_RESULTS (163)
|
||||
@@ -374,7 +453,7 @@
|
||||
|
||||
.B SYS_WIFI_CONNECT_ASYNC (164)
|
||||
Start a non-blocking network join. Observe SYS_WIFI_INFO for progress
|
||||
and the final result.
|
||||
and the final result. Requires CAP_NETWORK_ADMIN.
|
||||
int montauk::wifi_connect_async(const char* ssid,
|
||||
const char* password);
|
||||
|
||||
@@ -450,11 +529,13 @@
|
||||
int maxCount);
|
||||
|
||||
.B SYS_DISPLAYSETMODE (156)
|
||||
Switch to a mode returned by SYS_DISPLAYMODES.
|
||||
Switch to a mode returned by SYS_DISPLAYMODES. Requires
|
||||
CAP_DISPLAY_ADMIN.
|
||||
int montauk::display_set_mode(int modeIndex);
|
||||
|
||||
.B SYS_DISPLAYBRIGHTNESS (157)
|
||||
Set brightness to 0-100 percent, or pass -1 to query it.
|
||||
Set brightness to 0-100 percent, or pass -1 to query it. Setting requires
|
||||
CAP_DISPLAY_ADMIN; querying does not.
|
||||
int montauk::display_brightness(int percent = -1);
|
||||
|
||||
.SH TERMINAL
|
||||
@@ -478,15 +559,18 @@
|
||||
|
||||
.SH POWER MANAGEMENT
|
||||
.B SYS_RESET (26)
|
||||
Reboot the system.
|
||||
[[noreturn]] void montauk::reset();
|
||||
Reboot the system. Requires CAP_POWER_CONTROL. A successful call does not
|
||||
return; an unauthorized call returns SYS_ERR_PERMISSION.
|
||||
int montauk::reset();
|
||||
|
||||
.B SYS_SHUTDOWN (27)
|
||||
Shut down the system.
|
||||
[[noreturn]] void montauk::shutdown();
|
||||
Shut down the system. Requires CAP_POWER_CONTROL. A successful call does
|
||||
not return; an unauthorized call returns SYS_ERR_PERMISSION.
|
||||
int montauk::shutdown();
|
||||
|
||||
.B SYS_SUSPEND (89)
|
||||
Enter ACPI S3 sleep. Returns after wake, 0 on success.
|
||||
Enter ACPI S3 sleep. Returns after wake, 0 on success. Requires
|
||||
CAP_SUSPEND.
|
||||
int montauk::suspend();
|
||||
|
||||
.B SYS_POWER_REQUEST (135)
|
||||
@@ -494,8 +578,16 @@
|
||||
posts a pending action (POWER_REQ_SHUTDOWN / POWER_REQ_REBOOT)
|
||||
then exits; login.elf reads it with POWER_REQ_QUERY
|
||||
(read-and-clear), runs the shutdown stages, and finally calls
|
||||
shutdown()/reset(). See montauk::abi::PowerRequestAction.
|
||||
shutdown()/reset(). Posting requires CAP_POWER_REQUEST; querying and
|
||||
consuming the request requires CAP_POWER_CONTROL. See
|
||||
montauk::abi::PowerRequestAction.
|
||||
|
||||
A request may also come from inside a session -- the shell's shutdown
|
||||
builtin posts one. Since login only reads the request after the session
|
||||
leader exits, the leader polls POWER_REQ_PEEK, a non-destructive read
|
||||
requiring only CAP_POWER_REQUEST, and exits when one is pending.
|
||||
int montauk::power_request(int action);
|
||||
int montauk::power_request_pending();
|
||||
|
||||
.B SYS_POWERINFO (149)
|
||||
Get the CPU power/thermal snapshot (HWP state, throttling,
|
||||
@@ -507,7 +599,7 @@
|
||||
|
||||
.SH KERNEL LOG
|
||||
.B SYS_LOG (46)
|
||||
Read from the kernel ring log buffer.
|
||||
Read from the kernel ring log buffer. Requires CAP_LOG_READ.
|
||||
int64_t montauk::read_log(char* buf, uint64_t size);
|
||||
|
||||
.B SYS_LOG_WRITE (176)
|
||||
@@ -520,9 +612,20 @@
|
||||
to the framebuffer console.
|
||||
|
||||
.B SYS_SPAWN_REDIR (49)
|
||||
Spawn a process with its console I/O redirected to the caller.
|
||||
Spawn a process with its console I/O redirected to the caller. The child
|
||||
receives no capabilities.
|
||||
int montauk::spawn_redir(const char* path, const char* args = nullptr);
|
||||
|
||||
.B SYS_SPAWN_REDIR_CAPS (186)
|
||||
Spawn a redirected child with explicit capability masks. It applies the
|
||||
same subset and parent-delegable checks as SYS_SPAWN_CAPS. An
|
||||
administrative console explicitly lets its shell delegate selected
|
||||
capabilities; the shell's executable policy gives each trusted tool a
|
||||
delegable mask of zero, preventing further propagation.
|
||||
int montauk::spawn_redir_with_caps(
|
||||
const char* path, const char* args,
|
||||
const montauk::abi::SpawnCapabilities& capabilities);
|
||||
|
||||
.B SYS_CHILDIO_READ (50)
|
||||
Read buffered output produced by a redirected child.
|
||||
int montauk::childio_read(int childPid, char* buf, int maxLen);
|
||||
@@ -625,37 +728,41 @@
|
||||
int montauk::partlist(montauk::abi::PartInfo* buf, int max);
|
||||
|
||||
.B SYS_DISKREAD (71)
|
||||
Raw, driver-agnostic sector read from a block device.
|
||||
Raw, driver-agnostic sector read from a block device. Requires
|
||||
CAP_RAW_STORAGE.
|
||||
int64_t montauk::disk_read(int blockDev, uint64_t lba,
|
||||
uint32_t sectorCount, void* buf);
|
||||
|
||||
.B SYS_DISKWRITE (72)
|
||||
Raw, driver-agnostic sector write to a block device.
|
||||
Raw, driver-agnostic sector write to a block device. Requires
|
||||
CAP_RAW_STORAGE.
|
||||
int64_t montauk::disk_write(int blockDev, uint64_t lba,
|
||||
uint32_t sectorCount, const void* buf);
|
||||
|
||||
.B SYS_GPTINIT (73)
|
||||
Initialize a fresh GPT partition table on a block device.
|
||||
Initialize a fresh GPT partition table on a block device. Requires
|
||||
CAP_STORAGE_ADMIN.
|
||||
int montauk::gpt_init(int blockDev);
|
||||
|
||||
.B SYS_GPTADD (74)
|
||||
Add a partition to an existing GPT table.
|
||||
Add a partition to an existing GPT table. Requires CAP_STORAGE_ADMIN.
|
||||
int montauk::gpt_add(const montauk::abi::GptAddParams* params);
|
||||
|
||||
.B SYS_FSMOUNT (75)
|
||||
Mount a partition's filesystem onto a drive number.
|
||||
Mount a partition's filesystem onto a drive number. Requires
|
||||
CAP_STORAGE_ADMIN.
|
||||
int montauk::fs_mount(int partIndex, int driveNum);
|
||||
|
||||
.B SYS_FSFORMAT (76)
|
||||
Format a partition with a filesystem (FS_TYPE_FAT32 or
|
||||
FS_TYPE_EXT2).
|
||||
FS_TYPE_EXT2). Requires CAP_STORAGE_ADMIN.
|
||||
int montauk::fs_format(const montauk::abi::FsFormatParams* params);
|
||||
|
||||
.B SYS_FS_SYNC (134)
|
||||
Flush all block-device write caches and cleanly unmount
|
||||
disk-backed volumes ahead of power-off. Returns the number of
|
||||
volumes unmounted. Part of the graceful shutdown sequence
|
||||
(see SYS_POWER_REQUEST).
|
||||
(see SYS_POWER_REQUEST). Requires CAP_STORAGE_ADMIN.
|
||||
int montauk::fs_sync();
|
||||
|
||||
.SH AUDIO
|
||||
@@ -708,16 +815,18 @@
|
||||
|
||||
.SH BLUETOOTH
|
||||
.B SYS_BTSCAN (84)
|
||||
Scan for discoverable Bluetooth devices for up to timeoutMs.
|
||||
Scan for discoverable Bluetooth devices for up to timeoutMs. Requires
|
||||
CAP_DEVICE_ADMIN because it changes radio state.
|
||||
int montauk::bt_scan(montauk::abi::BtScanResult* buf, int maxCount,
|
||||
uint32_t timeoutMs);
|
||||
|
||||
.B SYS_BTCONNECT (85)
|
||||
Connect (and pair/bond if needed) to a device by BD_ADDR.
|
||||
Connect (and pair/bond if needed) to a device by BD_ADDR. Requires
|
||||
CAP_DEVICE_ADMIN.
|
||||
int montauk::bt_connect(const uint8_t* bdAddr);
|
||||
|
||||
.B SYS_BTDISCONNECT (86)
|
||||
Disconnect from a device by BD_ADDR.
|
||||
Disconnect from a device by BD_ADDR. Requires CAP_DEVICE_ADMIN.
|
||||
int montauk::bt_disconnect(const uint8_t* bdAddr);
|
||||
|
||||
.B SYS_BTLIST (87)
|
||||
@@ -731,7 +840,8 @@
|
||||
.B SYS_BTSETADDR (137)
|
||||
Change the adapter's BD_ADDR (6-byte buffer, byte 0 is the
|
||||
least-significant octet). Volatile -- apply after the last
|
||||
controller reset and persist separately to bluetooth.toml.
|
||||
controller reset and persist separately to bluetooth.toml. Requires
|
||||
CAP_DEVICE_ADMIN.
|
||||
int montauk::bt_set_addr(const uint8_t* bdAddr);
|
||||
|
||||
.B SYS_BTBONDS (138)
|
||||
@@ -739,7 +849,8 @@
|
||||
int montauk::bt_bonds(montauk::abi::BtBondInfo* buf, int maxCount);
|
||||
|
||||
.B SYS_BTFORGET (139)
|
||||
Forget a paired device; it must re-pair next time.
|
||||
Forget a paired device; it must re-pair next time. Requires
|
||||
CAP_DEVICE_ADMIN.
|
||||
int montauk::bt_forget(const uint8_t* bdAddr);
|
||||
|
||||
.SH GENERIC USB INTERFACES
|
||||
@@ -759,7 +870,8 @@
|
||||
|
||||
.B SYS_USB_CLAIM (179)
|
||||
Exclusively claim an unbound interface. Returns a generation-checked handle
|
||||
owned by the calling process.
|
||||
owned by the calling process. Requires CAP_DEVICE_ADMIN; subsequent
|
||||
operations are authorized by ownership of that handle.
|
||||
int montauk::usb_claim(uint8_t slotId, uint8_t interfaceNumber);
|
||||
|
||||
.B SYS_USB_CLOSE (180)
|
||||
|
||||
@@ -6,33 +6,31 @@
|
||||
|
||||
#include "apps_common.hpp"
|
||||
|
||||
static void spawn_app(const char* path, const char* args = nullptr) {
|
||||
if (path && path[0]) {
|
||||
montauk::spawn(path, args);
|
||||
}
|
||||
static void spawn_app(DesktopState* ds, const char* path, const char* args = nullptr) {
|
||||
if (path && path[0]) desktop_spawn_app(ds, path, args);
|
||||
}
|
||||
|
||||
void open_terminal(DesktopState* ds) {
|
||||
const char* home = (ds && ds->home_dir[0]) ? ds->home_dir : nullptr;
|
||||
spawn_app("0:/apps/terminal/terminal.elf", home);
|
||||
spawn_app(ds, "0:/apps/terminal/terminal.elf", home);
|
||||
}
|
||||
|
||||
void open_calculator(DesktopState* ds) {
|
||||
(void)ds;
|
||||
spawn_app("0:/apps/calculator/calculator.elf");
|
||||
spawn_app(ds, "0:/apps/calculator/calculator.elf");
|
||||
}
|
||||
|
||||
void open_texteditor(DesktopState* ds) {
|
||||
(void)ds;
|
||||
spawn_app("0:/apps/texteditor/texteditor.elf");
|
||||
spawn_app(ds, "0:/apps/texteditor/texteditor.elf");
|
||||
}
|
||||
|
||||
void open_syslog(DesktopState* ds) {
|
||||
(void)ds;
|
||||
spawn_app("0:/apps/klog/syslog.elf");
|
||||
spawn_app(ds, "0:/apps/syslog/syslog.elf");
|
||||
}
|
||||
|
||||
void open_wordprocessor(DesktopState* ds) {
|
||||
(void)ds;
|
||||
spawn_app("0:/apps/wordprocessor/wordprocessor.elf");
|
||||
spawn_app(ds, "0:/apps/wordprocessor/wordprocessor.elf");
|
||||
}
|
||||
|
||||
@@ -25,6 +25,12 @@ inline void* operator new(unsigned long, void* p) { return p; }
|
||||
|
||||
using namespace gui;
|
||||
|
||||
// Central launch policy implemented by desktop_catalog.cpp. Standalone app
|
||||
// wrappers include this header directly, so keep the declaration here rather
|
||||
// than only in desktop_internal.hpp.
|
||||
int desktop_spawn_app(DesktopState* ds, const char* path,
|
||||
const char* args = nullptr);
|
||||
|
||||
// ============================================================================
|
||||
// Minimal snprintf
|
||||
// ============================================================================
|
||||
@@ -261,8 +267,8 @@ void open_sleep_dialog(DesktopState* ds);
|
||||
// user returns to the login screen, where the shutdown stages run (Bluetooth
|
||||
// teardown, filesystem flush) before the final ACPI power-off / reset. Pass
|
||||
// montauk::abi::POWER_REQ_SHUTDOWN or montauk::abi::POWER_REQ_REBOOT.
|
||||
[[noreturn]] inline void desktop_request_power(int action) {
|
||||
montauk::power_request(action);
|
||||
montauk::exit(0);
|
||||
inline void desktop_request_power(int action) {
|
||||
if (montauk::power_request(action) == 0)
|
||||
montauk::exit(0);
|
||||
}
|
||||
bool desktop_poll_external_windows(DesktopState* ds);
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
*/
|
||||
|
||||
#include "desktop_internal.hpp"
|
||||
#include <montauk/capabilities.h>
|
||||
|
||||
namespace {
|
||||
|
||||
@@ -24,6 +25,18 @@ static void sort_item_indices(DesktopState* ds, int* indices, int count) {
|
||||
|
||||
} // namespace
|
||||
|
||||
int desktop_spawn_app(DesktopState* ds, const char* path, const char* args) {
|
||||
if (!ds || !path || !path[0]) return -1;
|
||||
// The grant table decides what each program may receive; the kernel bounds
|
||||
// it by what this session was actually delegated, so a non-admin session
|
||||
// clamps to nothing without the desktop having to decide that itself.
|
||||
montauk::abi::SpawnCapabilities caps =
|
||||
montauk::caps::for_binary(path, montauk::caps::self_delegable());
|
||||
// Applications inherit the desktop's kernel-owned owner identity. An
|
||||
// explicit user override is reserved for trusted session creators.
|
||||
return montauk::spawn_with_caps(path, args, nullptr, caps);
|
||||
}
|
||||
|
||||
int desktop_list_item_indices(DesktopState* ds,
|
||||
DesktopItemSection section,
|
||||
int* out,
|
||||
@@ -54,9 +67,9 @@ bool desktop_launch_item(DesktopState* ds, const DesktopItem* item) {
|
||||
case DESKTOP_ITEM_LAUNCH_EXECUTABLE:
|
||||
if (!item->binary_path[0]) return false;
|
||||
if (item->launch_with_home) {
|
||||
return montauk::spawn(item->binary_path, ds->home_dir) >= 0;
|
||||
return desktop_spawn_app(ds, item->binary_path, ds->home_dir) >= 0;
|
||||
}
|
||||
return montauk::spawn(item->binary_path) >= 0;
|
||||
return desktop_spawn_app(ds, item->binary_path) >= 0;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -381,9 +381,9 @@ void gui::desktop_handle_mouse(DesktopState* ds) {
|
||||
} else if (!row.is_category) {
|
||||
if (row.external) {
|
||||
if (row.launch_with_home) {
|
||||
montauk::spawn(row.binary_path, ds->home_dir);
|
||||
desktop_spawn_app(ds, row.binary_path, ds->home_dir);
|
||||
} else {
|
||||
montauk::spawn(row.binary_path);
|
||||
desktop_spawn_app(ds, row.binary_path);
|
||||
}
|
||||
} else {
|
||||
desktop_launch_builtin(ds, row.app_id);
|
||||
|
||||
@@ -834,6 +834,19 @@ void gui::desktop_run(DesktopState* ds) {
|
||||
|
||||
if (now >= nextClockPollMs) {
|
||||
nextClockPollMs = now + 1000;
|
||||
|
||||
// A power request can be posted by anything in this session that
|
||||
// holds CAP_POWER_REQUEST -- the shell's shutdown builtin, run in
|
||||
// a terminal window, is the common case. login only reads it once
|
||||
// the session leader exits, so ending the session is our job; it
|
||||
// is the same handoff desktop_request_power() performs, just
|
||||
// reached from a request we did not originate.
|
||||
int pendingPower = montauk::power_request_pending();
|
||||
if (pendingPower == montauk::abi::POWER_REQ_SHUTDOWN ||
|
||||
pendingPower == montauk::abi::POWER_REQ_REBOOT) {
|
||||
montauk::exit(0);
|
||||
}
|
||||
|
||||
uint64_t clockToken = desktop_clock_token();
|
||||
if (clockToken != lastClockToken) {
|
||||
lastClockToken = clockToken;
|
||||
|
||||
@@ -328,7 +328,7 @@ bool desktop_wifi_poll(DesktopState* ds, uint64_t now) {
|
||||
if (ds->wifi_dhcp_pending && info.connected && ds->cached_net_cfg.ipAddress == 0) {
|
||||
ds->wifi_dhcp_pending = false;
|
||||
ds->wifi_dhcp_waiting = true;
|
||||
montauk::spawn("0:/os/dhcp.elf");
|
||||
desktop_spawn_app(ds, "0:/os/dhcp.elf");
|
||||
wifi_set_status(ds, "Requesting an address...");
|
||||
changed = true;
|
||||
} else if (ds->wifi_dhcp_pending && info.connected) {
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
|
||||
#include <montauk/syscall.h>
|
||||
#include <montauk/config.h>
|
||||
#include <montauk/capabilities.h>
|
||||
#include <libc/stdio.h>
|
||||
|
||||
// ---- ANSI color codes ----
|
||||
@@ -80,7 +81,11 @@ static bool run_service(const char* path, const char* name, bool wait = true) {
|
||||
snprintf(msg, sizeof(msg), "Starting %s", name);
|
||||
log_info(msg);
|
||||
|
||||
int pid = montauk::spawn(path);
|
||||
// Grants are keyed on the executable, not the service id, so repointing a
|
||||
// privileged entry in init.toml at another program transfers no authority.
|
||||
montauk::abi::SpawnCapabilities caps =
|
||||
montauk::caps::for_binary(path, montauk::caps::self_delegable());
|
||||
int pid = montauk::spawn_with_caps(path, nullptr, "system", caps);
|
||||
if (pid < 0) {
|
||||
snprintf(msg, sizeof(msg), "Failed to start %s", name);
|
||||
log_err(msg);
|
||||
|
||||
@@ -52,16 +52,30 @@ void launch_session(LoginState* ls) {
|
||||
montauk::memset(ls->password, 0, sizeof(ls->password));
|
||||
ls->password_len = 0;
|
||||
|
||||
// A session may pass on what it holds and no more. Authority is never
|
||||
// propagated implicitly by spawn(): the desktop and the console both
|
||||
// consult 0:/config/capabilities.toml to decide which program receives
|
||||
// which subset, and the kernel clamps that to this set. A standard
|
||||
// session is delegable for the same reason an admin one is -- otherwise
|
||||
// its own terminal could not hand the shell the suspend it already has.
|
||||
montauk::abi::SpawnCapabilities caps{};
|
||||
caps.permitted = montauk::user::is_admin(ls->username)
|
||||
? montauk::abi::CAP_ADMIN_SESSION
|
||||
: montauk::abi::CAP_STANDARD_SESSION;
|
||||
caps.effective = caps.permitted;
|
||||
caps.delegable = caps.permitted;
|
||||
|
||||
int pid;
|
||||
if (ls->session_mode == SESSION_CONSOLE) {
|
||||
char console_args[96];
|
||||
build_console_args(ls, console_args, (int)sizeof(console_args));
|
||||
pid = montauk::spawn("0:/apps/terminal/terminal.elf", console_args);
|
||||
pid = montauk::spawn_with_caps("0:/apps/terminal/terminal.elf",
|
||||
console_args, ls->username, caps);
|
||||
} else {
|
||||
pid = montauk::spawn("0:/os/desktop.elf", ls->username);
|
||||
pid = montauk::spawn_with_caps("0:/os/desktop.elf", ls->username,
|
||||
ls->username, caps);
|
||||
}
|
||||
if (pid >= 0) {
|
||||
montauk::setuser(pid, ls->username);
|
||||
montauk::waitpid(pid);
|
||||
if (ls->session_mode == SESSION_DESKTOP) {
|
||||
terminate_desktop_session(pid);
|
||||
|
||||
@@ -250,10 +250,12 @@ void perform_graceful_shutdown(LoginState* ls, int action) {
|
||||
|
||||
// ==== Stage 4: dispatch the ACPI power-off / reset ====
|
||||
show_stage(ls, heading, rebooting ? "Restarting now..." : "Powering off...");
|
||||
if (rebooting) {
|
||||
montauk::reset();
|
||||
} else {
|
||||
montauk::shutdown();
|
||||
}
|
||||
__builtin_unreachable();
|
||||
int rc = rebooting ? montauk::reset() : montauk::shutdown();
|
||||
// A successful power-control syscall never returns. If it does return,
|
||||
// keep the trusted supervisor alive and make the authorization failure
|
||||
// visible instead of falling through an unreachable-code assumption.
|
||||
show_stage(ls, heading, rc == montauk::abi::SYS_ERR_PERMISSION
|
||||
? "Power control permission denied."
|
||||
: "Power control failed.");
|
||||
for (;;) montauk::sleep_ms(1000);
|
||||
}
|
||||
|
||||
@@ -25,9 +25,16 @@ static void maybe_run_setup_session() {
|
||||
montauk::user::set_session(user);
|
||||
doc.destroy();
|
||||
|
||||
int pid = montauk::spawn("0:/os/desktop.elf", user);
|
||||
montauk::abi::SpawnCapabilities caps{};
|
||||
bool admin = montauk::streq(role, "admin");
|
||||
caps.permitted = caps.effective = admin
|
||||
? montauk::abi::CAP_ADMIN_SESSION
|
||||
: montauk::abi::CAP_STANDARD_SESSION;
|
||||
// Matches launch_session(): a session delegates from what it holds, and
|
||||
// the grant table decides which program receives which part of it.
|
||||
caps.delegable = caps.permitted;
|
||||
int pid = montauk::spawn_with_caps("0:/os/desktop.elf", user, user, caps);
|
||||
if (pid >= 0) {
|
||||
montauk::setuser(pid, user);
|
||||
montauk::waitpid(pid);
|
||||
terminate_desktop_session(pid);
|
||||
}
|
||||
|
||||
@@ -951,7 +951,13 @@ static void render() {
|
||||
}
|
||||
|
||||
static void launch_dhcp() {
|
||||
int pid = montauk::spawn("0:/os/dhcp.elf");
|
||||
montauk::abi::SpawnCapabilities caps{
|
||||
montauk::abi::CAP_NETWORK_ADMIN,
|
||||
montauk::abi::CAP_NETWORK_ADMIN,
|
||||
0
|
||||
};
|
||||
int pid = montauk::spawn_with_caps("0:/os/dhcp.elf", nullptr,
|
||||
nullptr, caps);
|
||||
if (pid >= 0) {
|
||||
set_status("DHCP client started");
|
||||
} else {
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <montauk/syscall.h>
|
||||
#include <montauk/string.h>
|
||||
#include <montauk/heap.h>
|
||||
#include <montauk/capabilities.h>
|
||||
#include <gui/mtk.hpp>
|
||||
|
||||
extern "C" {
|
||||
@@ -392,8 +393,26 @@ static bool refresh_state(bool force) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Ending another process needs CAP_PROCESS_ADMIN; without it the kernel only
|
||||
// permits killing our own descendants. Capabilities cannot change over a
|
||||
// process lifetime and this is consulted from the render path, so resolve it
|
||||
// once rather than per frame.
|
||||
static bool g_process_admin = false;
|
||||
static bool g_process_admin_known = false;
|
||||
|
||||
static bool process_admin_granted() {
|
||||
if (!g_process_admin_known) {
|
||||
montauk::abi::SpawnCapabilities mine;
|
||||
montauk::caps::self(mine);
|
||||
g_process_admin = (mine.effective & montauk::abi::CAP_PROCESS_ADMIN) != 0;
|
||||
g_process_admin_known = true;
|
||||
}
|
||||
return g_process_admin;
|
||||
}
|
||||
|
||||
static bool can_kill_selected() {
|
||||
return g_pm.active_tab == PM_TAB_PROCESSES
|
||||
return process_admin_granted()
|
||||
&& g_pm.active_tab == PM_TAB_PROCESSES
|
||||
&& g_pm.selected >= 0
|
||||
&& g_pm.selected < g_pm.proc_count
|
||||
&& g_pm.procs[g_pm.selected].pid != 0
|
||||
|
||||
@@ -8,5 +8,10 @@
|
||||
|
||||
extern "C" void _start() {
|
||||
montauk::print("Rebooting...\n");
|
||||
montauk::reset();
|
||||
int rc = montauk::reset();
|
||||
if (rc == montauk::abi::SYS_ERR_PERMISSION)
|
||||
montauk::print("reset: permission denied\n");
|
||||
else
|
||||
montauk::print("reset: reboot failed\n");
|
||||
montauk::exit(1);
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ endif
|
||||
PROG_INC := ../../include
|
||||
LINK_LD := ../../link.ld
|
||||
BINDIR := ../../bin
|
||||
LIBDIR := ../../lib
|
||||
OBJDIR := obj
|
||||
|
||||
# ---- C++ compiler flags ----
|
||||
@@ -63,9 +64,9 @@ TARGET := $(BINDIR)/os/shell.elf
|
||||
|
||||
all: $(TARGET)
|
||||
|
||||
$(TARGET): $(OBJS) $(LINK_LD) Makefile
|
||||
$(TARGET): $(OBJS) $(LINK_LD) Makefile $(LIBDIR)/libc/liblibc.a
|
||||
mkdir -p $(BINDIR)/os
|
||||
$(CXX) $(CXXFLAGS) $(LDFLAGS) $(OBJS) -o $@
|
||||
$(CXX) $(CXXFLAGS) $(LDFLAGS) $(OBJS) $(LIBDIR)/libc/liblibc.a -o $@
|
||||
|
||||
$(OBJDIR)/%.o: %.cpp shell.h Makefile
|
||||
mkdir -p $(OBJDIR)
|
||||
|
||||
@@ -68,6 +68,7 @@ void cmd_help() {
|
||||
montauk::print(" fontscale [n] Set terminal font scale (1-8)\n");
|
||||
montauk::print(" reset Reboot the system\n");
|
||||
montauk::print(" shutdown Shut down the system\n");
|
||||
montauk::print(" suspend Suspend the system\n");
|
||||
montauk::print("\n");
|
||||
montauk::print("Network commands:\n");
|
||||
montauk::print(" ping <ip> Send ICMP echo requests\n");
|
||||
|
||||
@@ -41,7 +41,17 @@ static void print_exit_code(int code) {
|
||||
|
||||
static bool try_exec(const char* path, const char* args) {
|
||||
if (!file_exists(path)) return false;
|
||||
int pid = montauk::spawn(path, args);
|
||||
// Least-privilege hygiene, NOT a security boundary: this never decides
|
||||
// what may run, only which capabilities a tool receives. The user in an
|
||||
// admin console can already do everything these tools do, so the point is
|
||||
// to keep a bug in one tool from reaching authority it has no use for.
|
||||
// The shell delegates on the same terms as the desktop; the table decides
|
||||
// per binary, and the kernel re-checks the bound on every spawn.
|
||||
montauk::abi::SpawnCapabilities caps =
|
||||
montauk::caps::for_binary(path, shell_delegable_caps);
|
||||
int pid = (caps.permitted != 0)
|
||||
? montauk::spawn_with_caps(path, args, nullptr, caps)
|
||||
: montauk::spawn(path, args);
|
||||
if (pid < 0) return false;
|
||||
print_exit_code(montauk::waitpid(pid));
|
||||
return true;
|
||||
|
||||
@@ -13,6 +13,7 @@ int current_drive = 0;
|
||||
int last_exit = 0;
|
||||
char session_user[32] = "";
|
||||
char session_home[64] = "";
|
||||
uint64_t shell_delegable_caps = 0;
|
||||
|
||||
void sync_cwd() {
|
||||
char abs[128];
|
||||
@@ -46,6 +47,8 @@ void read_session() {
|
||||
|
||||
scopy(session_home, "0:/users/", sizeof(session_home));
|
||||
scat(session_home, session_user, sizeof(session_home));
|
||||
|
||||
shell_delegable_caps = montauk::caps::self_delegable();
|
||||
}
|
||||
|
||||
// ---- Command history ----
|
||||
@@ -184,6 +187,29 @@ static int process_command(const char* line) {
|
||||
if (streq(cmd, "true")) { return 0; }
|
||||
if (streq(cmd, "false")) { return 1; }
|
||||
|
||||
// Route interactive power actions through the trusted login supervisor.
|
||||
// Posting the request and exiting lets terminal.elf close, after which
|
||||
// login performs filesystem/Bluetooth cleanup and the final power syscall.
|
||||
if (streq(cmd, "shutdown") || streq(cmd, "poweroff") || streq(cmd, "halt") ||
|
||||
streq(cmd, "reboot") || streq(cmd, "reset")) {
|
||||
int action = (streq(cmd, "reboot") || streq(cmd, "reset"))
|
||||
? montauk::abi::POWER_REQ_REBOOT
|
||||
: montauk::abi::POWER_REQ_SHUTDOWN;
|
||||
int rc = montauk::power_request(action);
|
||||
if (rc == 0) montauk::exit(0);
|
||||
montauk::print(rc == montauk::abi::SYS_ERR_PERMISSION
|
||||
? "power: permission denied\n"
|
||||
: "power: request failed\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (streq(cmd, "suspend")) {
|
||||
int rc = montauk::suspend();
|
||||
if (rc == montauk::abi::SYS_ERR_PERMISSION)
|
||||
montauk::print("suspend: permission denied\n");
|
||||
return rc == 0 ? 0 : 1;
|
||||
}
|
||||
|
||||
if (streq(cmd, "pwd")) {
|
||||
sync_cwd();
|
||||
char path[128];
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include <montauk/string.h>
|
||||
#include <montauk/heap.h>
|
||||
#include <montauk/config.h>
|
||||
#include <montauk/capabilities.h>
|
||||
|
||||
using montauk::slen;
|
||||
using montauk::streq;
|
||||
@@ -55,6 +56,7 @@ extern int current_drive;
|
||||
extern int last_exit;
|
||||
extern char session_user[32];
|
||||
extern char session_home[64];
|
||||
extern uint64_t shell_delegable_caps;
|
||||
|
||||
// ---- Inline path helpers ----
|
||||
|
||||
@@ -144,4 +146,4 @@ constexpr const char* shell_builtins[] = {
|
||||
"true",
|
||||
"false",
|
||||
"exit"
|
||||
};
|
||||
};
|
||||
|
||||
@@ -10,6 +10,15 @@ extern "C" void _start() {
|
||||
montauk::print("Shutting down...\n");
|
||||
// This low-level utility bypasses the login graceful-shutdown view, so flush
|
||||
// pending writes and unmount disk-backed volumes here before powering off.
|
||||
montauk::fs_sync();
|
||||
montauk::shutdown();
|
||||
int sync_rc = montauk::fs_sync();
|
||||
if (sync_rc == montauk::abi::SYS_ERR_PERMISSION) {
|
||||
montauk::print("shutdown: permission denied\n");
|
||||
montauk::exit(1);
|
||||
}
|
||||
int rc = montauk::shutdown();
|
||||
if (rc == montauk::abi::SYS_ERR_PERMISSION)
|
||||
montauk::print("shutdown: permission denied\n");
|
||||
else
|
||||
montauk::print("shutdown: power-off failed\n");
|
||||
montauk::exit(1);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user