#![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};
pub static PROC_INFO: LazyLock<ProcInfo> = LazyLock::new(ProcInfo::new);
pub struct ProcInfo {
pub char_dev: CharDevices,
pub ip_unprivileged_port_start: u16,
pub mmap_min_addr: u64,
pub nr_open: u64,
pub optmem_max: usize,
pub pipe_max_size: u64,
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 {
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"));
}
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"));
}
}
#[derive(Debug, Eq, PartialEq)]
pub struct CharDevice {
pub major: u32,
pub name: XPathCow<'static>,
}
pub enum CharDevices {
Owned(Vec<CharDevice>),
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"}"#);
}
}