syd 3.58.0

rock-solid application kernel
Documentation
//
// Syd: rock-solid application kernel
// src/proc/info.rs: proc(5) system information
//
// Copyright (c) 2023, 2024, 2025, 2026 Ali Polatel <alip@chesswob.org>
//
// SPDX-License-Identifier: GPL-3.0

// SAFETY: This module has been liberated from unsafe code!
#![forbid(unsafe_code)]

use std::{ops::Deref, os::fd::AsFd, sync::LazyLock};

use serde::{ser::SerializeMap, Serialize, Serializer};

use super::util::{
    proc_char_dev, proc_char_dev_abi, proc_fs_nr_open, proc_ip_unprivileged_port_start,
    proc_mmap_min_addr, proc_open, proc_optmem_max, proc_pipe_max_size, proc_wmem_max,
};
use crate::{config::*, info, log_enabled, path::XPathCow, syslog::LogLevel, xfmt};

/// System values read once from proc(5)
pub static PROC_INFO: LazyLock<ProcInfo> = LazyLock::new(ProcInfo::new);

/// System values read once from proc(5).
pub struct ProcInfo {
    /// Character devices from proc_devices(5)
    pub char_dev: CharDevices,

    /// net.ipv4.ip_unprivileged_port_start sysctl(8)
    ///
    /// Limit is used for both IPv{4,6}, name is misleading.
    pub ip_unprivileged_port_start: u16,

    /// Minimum allowed mmap(2) address
    pub mmap_min_addr: u64,

    /// fs.nr_open from sysctl(8)
    pub nr_open: u64,

    /// net.core.optmem_max sysctl(8)
    pub optmem_max: usize,

    /// fs.pipe-max-size sysctl(8)
    pub pipe_max_size: u64,

    /// net.core.wmem_max sysctl(8) doubled
    pub sndbuf_max: usize,
}

impl Default for ProcInfo {
    fn default() -> Self {
        Self {
            char_dev: CharDevices::Borrowed(proc_char_dev_abi()),
            ip_unprivileged_port_start: DEFAULT_PORT_START,
            mmap_min_addr: *PAGE_SIZE,
            nr_open: DEFAULT_NR_OPEN,
            optmem_max: DEFAULT_OPTMEM_MAX,
            pipe_max_size: *PAGE_SIZE,
            sndbuf_max: sndbuf_max(DEFAULT_WMEM_MAX),
        }
    }
}

impl ProcInfo {
    /// Initialize proc(5) values.
    pub fn init() {
        let _ = &*PROC_INFO;
    }

    fn new() -> Self {
        let info = proc_open(None)
            .map(Self::read)
            .unwrap_or_else(|_| Self::default());

        if log_enabled!(LogLevel::Info) {
            info.log();
        }

        info
    }

    fn read<Fd: AsFd>(fd: Fd) -> Self {
        Self {
            char_dev: Self::read_char_dev(&fd),
            mmap_min_addr: Self::read_mmap_min_addr(&fd),
            nr_open: Self::read_nr_open(&fd),
            optmem_max: Self::read_optmem_max(&fd),
            pipe_max_size: Self::read_pipe_max_size(&fd),
            ip_unprivileged_port_start: Self::read_port_start(&fd),
            sndbuf_max: Self::read_sndbuf_max(&fd),
        }
    }

    fn log(&self) {
        self.log_char_dev();
        self.log_mmap_min_addr();
        self.log_nr_open();
        self.log_optmem_max();
        self.log_pipe_max_size();
        self.log_port_start();
        self.log_sndbuf_max();
    }

    fn read_char_dev<Fd: AsFd>(fd: Fd) -> CharDevices {
        match proc_char_dev(fd) {
            Ok(devs) => CharDevices::Owned(devs),
            Err(_) => CharDevices::Borrowed(proc_char_dev_abi()),
        }
    }

    fn log_char_dev(&self) {
        let num_char_dev = self.char_dev.len();

        info!("ctx": "run", "op": "check_char_devices",
            "num": num_char_dev, "dev": &*self.char_dev,
            "msg": xfmt!("registered {num_char_dev} character devices"));
    }

    // Enforce OpenBSD's hard-coded minimum mmap(2) address by capping at PAGE_SIZE;
    // See: https://github.com/openbsd/src/blob/94a7e27d04f23871848a8f225ef286e84969197a/sys/uvm/uvm_map.c#L235
    fn read_mmap_min_addr<Fd: AsFd>(fd: Fd) -> u64 {
        proc_mmap_min_addr(fd).unwrap_or(*PAGE_SIZE).max(*PAGE_SIZE)
    }

    fn log_mmap_min_addr(&self) {
        let mmap_min_addr = self.mmap_min_addr;

        info!("ctx": "run", "op": "check_mmap_min_addr", "min_addr": mmap_min_addr,
            "msg": xfmt!("set mmap minimum address to {mmap_min_addr} bytes"));
    }

    fn read_nr_open<Fd: AsFd>(fd: Fd) -> u64 {
        proc_fs_nr_open(fd).unwrap_or(DEFAULT_NR_OPEN)
    }

    fn log_nr_open(&self) {
        let nr_open = self.nr_open;

        info!("ctx": "run", "op": "check_nr_open", "nr_open": nr_open,
            "msg": xfmt!("set maximum number of open files to {nr_open}"));
    }

    fn read_optmem_max<Fd: AsFd>(fd: Fd) -> usize {
        proc_optmem_max(fd).unwrap_or(DEFAULT_OPTMEM_MAX)
    }

    fn log_optmem_max(&self) {
        let optmem_max = self.optmem_max;

        info!("ctx": "run", "op": "check_optmem_max", "optmem_max": optmem_max,
            "msg": xfmt!("set maximum ancillary buffer size to {optmem_max} bytes"));
    }

    fn read_pipe_max_size<Fd: AsFd>(fd: Fd) -> u64 {
        proc_pipe_max_size(fd).unwrap_or(*PAGE_SIZE)
    }

    fn log_pipe_max_size(&self) {
        let pipe_max_size = self.pipe_max_size;

        info!("ctx": "run", "op": "check_pipe_max_size", "pipe_max_size": pipe_max_size,
            "msg": xfmt!("set maximum pipe buffer size to {pipe_max_size} bytes"));
    }

    fn read_port_start<Fd: AsFd>(fd: Fd) -> u16 {
        proc_ip_unprivileged_port_start(fd).unwrap_or(DEFAULT_PORT_START)
    }

    fn log_port_start(&self) {
        let port_start = self.ip_unprivileged_port_start;

        info!("ctx": "run", "op": "check_port_start", "port_start": port_start,
            "msg": xfmt!("set first unprivileged port to {port_start}"));
    }

    fn read_sndbuf_max<Fd: AsFd>(fd: Fd) -> usize {
        sndbuf_max(proc_wmem_max(fd).unwrap_or(DEFAULT_WMEM_MAX))
    }

    fn log_sndbuf_max(&self) {
        let sndbuf_max = self.sndbuf_max;

        info!("ctx": "run", "op": "check_sndbuf_max", "sndbuf_max": sndbuf_max,
            "msg": xfmt!("set maximum socket send buffer to {sndbuf_max} bytes"));
    }
}

/// A character device from proc_devices(5).
#[derive(Debug, Eq, PartialEq)]
pub struct CharDevice {
    /// Character device major number.
    pub major: u32,
    /// Character device driver name.
    pub name: XPathCow<'static>,
}

/// Like `std::borrow::Cow` over `[CharDevice]`.
pub enum CharDevices {
    /// Owned set read from proc_devices(5).
    Owned(Vec<CharDevice>),
    /// Borrowed ABI as fallback.
    Borrowed(&'static [CharDevice]),
}

impl Deref for CharDevices {
    type Target = [CharDevice];

    fn deref(&self) -> &[CharDevice] {
        match self {
            Self::Owned(devs) => devs,
            Self::Borrowed(devs) => devs,
        }
    }
}

impl Serialize for CharDevice {
    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
        let mut map = serializer.serialize_map(Some(2))?;
        map.serialize_entry("major", &self.major)?;
        map.serialize_entry("name", &self.name)?;
        map.end()
    }
}

fn sndbuf_max(wmem_max: usize) -> usize {
    match wmem_max.checked_mul(2) {
        Some(sndbuf) => sndbuf.clamp(MIN_SEND_LEN, MAX_SNDBUF),
        None => MAX_SNDBUF,
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::path::XPathBuf;

    #[test]
    fn test_char_device_serialize_1() {
        let dev = CharDevice {
            major: 1,
            name: XPathCow::Owned(XPathBuf::try_from("mem").unwrap()),
        };
        let json = serde_json::to_string(&dev).unwrap();
        assert_eq!(json, r#"{"major":1,"name":"mem"}"#);
    }

    #[test]
    fn test_char_device_serialize_2() {
        let dev = CharDevice {
            major: 4,
            name: XPathCow::Owned(XPathBuf::try_from(&b"tty\x01"[..]).unwrap()),
        };
        let json = serde_json::to_string(&dev).unwrap();
        assert_eq!(json, r#"{"major":4,"name":"74747901"}"#);
    }
}