Remove spec §N.N citations from comments, keep them self-contained

Comments citing the spec document instead of just stating the fact
directly made their usefulness depend on cross-referencing a separate
file. Reworded each one to stand alone - same content, citation dropped,
folded into a normal sentence where it was mid-clause rather than
trailing. spec/porthole-spec.md itself is untouched.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-14 12:32:56 +02:00
parent 3d927e25d1
commit 40437bf78c
11 changed files with 39 additions and 40 deletions

View File

@@ -48,7 +48,7 @@ pub enum Commands {
Completions { shell: Shell }, Completions { shell: Shell },
/// Internal: runs the supervisor loop for one profile. Not for direct /// Internal: runs the supervisor loop for one profile. Not for direct
/// use - `open` spawns this itself (spec §3). /// use - `open` spawns this itself.
#[command(hide = true, name = "__supervise")] #[command(hide = true, name = "__supervise")]
Supervise { name: String }, Supervise { name: String },
} }

View File

@@ -18,8 +18,8 @@ pub fn run(args: CloseArgs) -> Result<()> {
} }
/// Stops `name`'s supervisor if one is actually running, and clears any /// Stops `name`'s supervisor if one is actually running, and clears any
/// stale instance file either way (spec §5.3) - shared with `remove` and /// stale instance file either way - shared with `remove` and `wipe`.
/// `wipe`. Returns whether anything was actually running. /// Returns whether anything was actually running.
pub fn close_instance(name: &str, force: bool) -> Result<bool> { pub fn close_instance(name: &str, force: bool) -> Result<bool> {
let Some(pid) = instance::running_pid(name)? else { let Some(pid) = instance::running_pid(name)? else {
instance::delete(name)?; // clears a stale file left by a crash instance::delete(name)?; // clears a stale file left by a crash
@@ -27,9 +27,9 @@ pub fn close_instance(name: &str, force: bool) -> Result<bool> {
}; };
if force { if force {
// spec §3 step 4: SIGKILL the whole process group (the supervisor // SIGKILL the whole process group (the supervisor is its own
// is its own group leader via setsid), not just the supervisor pid // group leader via setsid), not just the supervisor pid - a plain
// - a plain single-pid SIGKILL would leave `ssh` orphaned. // single-pid SIGKILL would leave `ssh` orphaned.
unsafe { libc::kill(-pid, libc::SIGKILL) }; unsafe { libc::kill(-pid, libc::SIGKILL) };
} else { } else {
unsafe { libc::kill(pid, libc::SIGTERM) }; unsafe { libc::kill(pid, libc::SIGTERM) };

View File

@@ -12,7 +12,7 @@ pub mod wipe;
use crate::cli::MappingArgs; use crate::cli::MappingArgs;
use crate::profile::ProfileEdits; use crate::profile::ProfileEdits;
/// Turns clap's `MappingArgs` into a `ProfileEdits` (spec §5.1). `--via` is /// Turns clap's `MappingArgs` into a `ProfileEdits`. `--via` is
/// collected by clap itself: `value_delimiter = ','` splits each /// collected by clap itself: `value_delimiter = ','` splits each
/// occurrence on commas, and the field being a `Vec` allows repeated /// occurrence on commas, and the field being a `Vec` allows repeated
/// `--via` flags, so both `--via a,b` and `--via a --via b` reach here as /// `--via` flags, so both `--via a,b` and `--via a --via b` reach here as

View File

@@ -1,5 +1,5 @@
//! `open` - spec §3/§5.2. Validates, then either runs the supervisor loop //! `open` validates, then either runs the supervisor loop inline
//! inline (`--foreground`) or spawns a detached copy of this binary //! (`--foreground`) or spawns a detached copy of this binary
//! (`porthole __supervise <name>`) and waits briefly for it to confirm. //! (`porthole __supervise <name>`) and waits briefly for it to confirm.
use crate::cli::OpenArgs; use crate::cli::OpenArgs;
@@ -25,8 +25,8 @@ pub fn run(args: OpenArgs) -> Result<()> {
} }
/// Opens every `reconnect: true` profile that isn't already running - the /// Opens every `reconnect: true` profile that isn't already running - the
/// hook external autostart mechanisms are meant to call (spec §5.2/§8). /// hook external autostart mechanisms are meant to call. Per-profile
/// Per-profile failures are warnings, not a whole-batch failure. /// failures are warnings, not a whole-batch failure.
fn open_all(once: bool) -> Result<()> { fn open_all(once: bool) -> Result<()> {
let profiles = profile::list_all()?; let profiles = profile::list_all()?;
let mut opened = 0; let mut opened = 0;
@@ -57,8 +57,8 @@ fn open_one(name: &str, foreground: bool, once: bool) -> Result<()> {
return Ok(()); return Ok(());
} }
// Clear a stale instance file left by a crash before spawning. The // Clear a stale instance file left by a crash before spawning. The
// lock, not this file, is the authority on "already open" (spec // lock, not this file, is the authority on "already open" - this just
// §5.2) - this just keeps `status` from reading stale state mid-spawn. // keeps `status` from reading stale state mid-spawn.
instance::delete(name)?; instance::delete(name)?;
if foreground { if foreground {
@@ -73,10 +73,10 @@ fn open_one(name: &str, foreground: bool, once: bool) -> Result<()> {
wait_for_confirmation(name) wait_for_confirmation(name)
} }
/// Spawns `porthole __supervise <name>` fully detached (spec §3 steps 1-2): /// Spawns `porthole __supervise <name>` fully detached: stdin from
/// stdin from `/dev/null`, stdout/stderr appended to the profile's log, and /// `/dev/null`, stdout/stderr appended to the profile's log, and
/// `setsid()` in the child so it leaves this process's session and survives /// `setsid()` in the child so it leaves this process's session and
/// the terminal closing. /// survives the terminal closing.
fn spawn_detached(name: &str, once: bool) -> Result<()> { fn spawn_detached(name: &str, once: bool) -> Result<()> {
let exe = std::env::current_exe()?; let exe = std::env::current_exe()?;
let log_path = instance::log_path(name); let log_path = instance::log_path(name);
@@ -99,8 +99,8 @@ fn spawn_detached(name: &str, once: bool) -> Result<()> {
/// Blocks briefly for the detached supervisor to reach a conclusive state, /// Blocks briefly for the detached supervisor to reach a conclusive state,
/// so an immediate failure (bad auth, bind conflict, unresolvable host) is /// so an immediate failure (bad auth, bind conflict, unresolvable host) is
/// reported with a non-zero exit instead of `open` appearing to succeed /// reported with a non-zero exit instead of `open` appearing to succeed.
/// (spec §5.2). The instance file's initial write is always /// The instance file's initial write is always
/// `State::Reconnecting`, since the first attempt has not concluded yet; /// `State::Reconnecting`, since the first attempt has not concluded yet;
/// that value is indistinguishable from "already failed once, backing /// that value is indistinguishable from "already failed once, backing
/// off". This function waits specifically for `Up` or `Error`, not merely /// off". This function waits specifically for `Up` or `Error`, not merely

View File

@@ -11,7 +11,7 @@ pub fn run(args: StatusArgs) -> Result<()> {
let inst = instance::load(&name)?; let inst = instance::load(&name)?;
// An instance file whose pid isn't actually alive means the supervisor // An instance file whose pid isn't actually alive means the supervisor
// crashed without cleaning up - report that, rather than trusting a // crashed without cleaning up - report that, rather than trusting a
// state the process table disagrees with (spec §2.2). // state the process table disagrees with.
let live = inst.as_ref().is_some_and(|i| instance::supervisor_alive(i.pid, &name)); let live = inst.as_ref().is_some_and(|i| instance::supervisor_alive(i.pid, &name));
if args.json { if args.json {

View File

@@ -42,9 +42,9 @@ pub fn run(args: WipeArgs) -> Result<()> {
} }
/// Kills any supervisor process not backed by a tracked profile (e.g. one /// Kills any supervisor process not backed by a tracked profile (e.g. one
/// orphaned after a crash), matched by cmdline rather than tracked state /// orphaned after a crash), matched by cmdline rather than tracked state.
/// (spec §5.8). Sends SIGTERM to each supervisor's process group so its /// Sends SIGTERM to each supervisor's process group so its `ssh` child is
/// `ssh` child is included. /// included.
fn kill_orphaned_supervisors() -> u32 { fn kill_orphaned_supervisors() -> u32 {
let mut killed = 0; let mut killed = 0;
let Ok(entries) = std::fs::read_dir("/proc") else { return 0 }; let Ok(entries) = std::fs::read_dir("/proc") else { return 0 };

View File

@@ -111,9 +111,8 @@ pub fn instance_path(name: &str) -> PathBuf { state_dir().join(format!("{name}.j
pub fn lock_path(name: &str) -> PathBuf { state_dir().join(format!("{name}.lock")) } pub fn lock_path(name: &str) -> PathBuf { state_dir().join(format!("{name}.lock")) }
pub fn log_path(name: &str) -> PathBuf { state_dir().join(format!("{name}.log")) } pub fn log_path(name: &str) -> PathBuf { state_dir().join(format!("{name}.log")) }
/// Loads the instance file for `name`, if any. `None` means closed - the /// Loads the instance file for `name`, if any. `None` means closed - its
/// absence of this file is the closed state (spec §2.2); there is no /// absence *is* the closed state; there is no separate enum value for it.
/// separate enum value for it.
pub fn load(name: &str) -> Result<Option<Instance>> pub fn load(name: &str) -> Result<Option<Instance>>
{ {
let path = instance_path(name); let path = instance_path(name);

View File

@@ -42,9 +42,9 @@ impl Kind
pub struct Profile { pub struct Profile {
pub name: String, pub name: String,
pub kind: Kind, pub kind: Kind,
/// Raw `-L/-R/-D` payload, without the flag itself - see spec §5.1. /// Raw `-L/-R/-D` payload, without the flag itself.
pub mapping: String, pub mapping: String,
/// Ordered hop list, each `[user@]host[:port]` - see spec §3.1. /// Ordered hop list, each `[user@]host[:port]`.
#[serde(default)] #[serde(default)]
pub via: Vec<String>, pub via: Vec<String>,
pub user: Option<String>, pub user: Option<String>,
@@ -132,8 +132,8 @@ impl Profile
/// Splits `via` into the `-J` jump-chain value (comma-joined, all but /// Splits `via` into the `-J` jump-chain value (comma-joined, all but
/// the last hop; `None` for a single-hop `via`) and the final `ssh` /// the last hop; `None` for a single-hop `via`) and the final `ssh`
/// connection target - see spec §3.1. Every path that constructs a /// connection target. Every path that constructs a `Profile` validates
/// `Profile` validates `via` as non-empty. /// `via` as non-empty.
pub fn ssh_target(&self) -> (Option<String>, &str) pub fn ssh_target(&self) -> (Option<String>, &str)
{ {
match self.via.split_last() { match self.via.split_last() {

View File

@@ -1,17 +1,17 @@
//! Builds the `ssh` invocation for a profile - spec §3.1. //! Builds the `ssh` invocation for a profile.
use std::process::{ Stdio, Command }; use std::process::{ Stdio, Command };
use crate::profile::Profile; use crate::profile::Profile;
/// Builds the `ssh` command for `profile`, stdio wired for the supervisor /// Builds the `ssh` command for `profile`, stdio wired for the supervisor
/// to capture (stdout/stderr piped so failure text can be classified per /// to capture (stdout/stderr piped so failure text can be classified;
/// spec §4.2; stdin from `/dev/null` since porthole never wants a shell). /// stdin from `/dev/null` since porthole never wants a shell).
pub fn build(profile: &Profile) -> Command { pub fn build(profile: &Profile) -> Command {
let mut cmd = Command::new("ssh"); let mut cmd = Command::new("ssh");
cmd.stdin(Stdio::null()).stdout(Stdio::piped()).stderr(Stdio::piped()); cmd.stdin(Stdio::null()).stdout(Stdio::piped()).stderr(Stdio::piped());
// Forced flags, see spec §3.1. // Flags forced on every invocation, not user-configurable.
cmd.args([ cmd.args([
"-o", "-o",
"BatchMode=yes", "BatchMode=yes",

View File

@@ -1,4 +1,4 @@
//! The `__supervise` loop - spec §3/§4. Runs as a detached, re-exec'd copy //! The `__supervise` loop runs as a detached, re-exec'd copy
//! of this same binary (`porthole __supervise <name>`, see `main.rs`); owns //! of this same binary (`porthole __supervise <name>`, see `main.rs`); owns
//! the `ssh` child process for one profile's entire supervised lifetime. //! the `ssh` child process for one profile's entire supervised lifetime.
@@ -15,10 +15,10 @@ use crate::profile::{ self, Profile };
use crate::instance::{ self, Lock, State, Instance }; use crate::instance::{ self, Lock, State, Instance };
/// How long a connection must survive before its uptime resets the backoff /// How long a connection must survive before its uptime resets the backoff
/// counter back to the base delay - spec §4.1. /// counter back to the base delay.
const STABLE_THRESHOLD_SECS: i64 = 60; const STABLE_THRESHOLD_SECS: i64 = 60;
/// Consecutive unrecognized (not pattern-matched) failures before porthole /// Consecutive unrecognized (not pattern-matched) failures before porthole
/// gives up on an apparently-permanently-broken profile - spec §4.2. /// gives up on an apparently-permanently-broken profile.
const MAX_UNRECOGNIZED_STREAK: u32 = 10; const MAX_UNRECOGNIZED_STREAK: u32 = 10;
/// How long `ssh` must stay alive before porthole treats it as connected; /// How long `ssh` must stay alive before porthole treats it as connected;
/// see `run_ssh_once` for the heuristic this backs. /// see `run_ssh_once` for the heuristic this backs.
@@ -32,7 +32,7 @@ extern "C" fn handle_sigterm(_sig: libc::c_int) { SHUTDOWN.store(true, Ordering:
/// Traps SIGTERM and SIGINT into a flag instead of the default /// Traps SIGTERM and SIGINT into a flag instead of the default
/// terminate-immediately behavior. This is how `close` (SIGTERM) and /// terminate-immediately behavior. This is how `close` (SIGTERM) and
/// `-f/--foreground`'s Ctrl-C (SIGINT, spec §5.2) are distinguished from a /// `-f/--foreground`'s Ctrl-C (SIGINT) are distinguished from a
/// dropped `ssh` connection: by which signal arrived, not by inferring /// dropped `ssh` connection: by which signal arrived, not by inferring
/// intent from `ssh`'s exit status. In foreground mode this function runs /// intent from `ssh`'s exit status. In foreground mode this function runs
/// in the process the terminal sends Ctrl-C to directly, since /// in the process the terminal sends Ctrl-C to directly, since
@@ -215,7 +215,7 @@ fn join_all<const N: usize>(handles: [Option<JoinHandle<()>>; N]) {
} }
} }
/// Classifies `ssh`'s captured stderr per spec §4.2. Fatal patterns stop /// Classifies `ssh`'s captured stderr. Fatal patterns stop
/// the reconnect loop outright; known-transient patterns retry without /// the reconnect loop outright; known-transient patterns retry without
/// counting toward the unrecognized-failure escalation; anything else /// counting toward the unrecognized-failure escalation; anything else
/// still retries, but does count toward it. /// still retries, but does count toward it.

View File

@@ -34,7 +34,7 @@
# ~/.config/porthole or ~/.local/state/porthole. # ~/.config/porthole or ~/.local/state/porthole.
# - Every profile created is named "$NAME_..." (default prefix # - Every profile created is named "$NAME_..." (default prefix
# porttestsuite); no operation targets anything outside that prefix. # porttestsuite); no operation targets anything outside that prefix.
# - `wipe` (spec §5.8 / src/commands/wipe.rs) kills ANY process on the # - `wipe` (src/commands/wipe.rs) kills ANY process on the
# whole system whose cmdline contains "__supervise", regardless of # whole system whose cmdline contains "__supervise", regardless of
# which state dir it belongs to - it is NOT scoped by the sandboxing # which state dir it belongs to - it is NOT scoped by the sandboxing
# above. Before running it, this script scans the real process table # above. Before running it, this script scans the real process table