Draft: Add log-file size limit

This commit is contained in:
Gijs Burghoorn 2023-06-25 20:59:13 +02:00
parent 52e6e23407
commit 7ae1db45c9
5 changed files with 353 additions and 98 deletions

80
Cargo.lock generated
View file

@ -40,7 +40,7 @@ checksum = "a84cda67535339806297f1b331d6dd6320470d2a0fe65381e79ee9e156dd3d13"
dependencies = [
"bitflags",
"crossterm_winapi",
"libc 0.2.139",
"libc 0.2.147",
"mio",
"parking_lot",
"signal-hook",
@ -50,9 +50,9 @@ dependencies = [
[[package]]
name = "crossterm_winapi"
version = "0.9.0"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2ae1b35a484aa10e07fe0638d02301c5ad24de82d310ccbd2f3693da5f09bf1c"
checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b"
dependencies = [
"winapi",
]
@ -69,12 +69,12 @@ dependencies = [
[[package]]
name = "getrandom"
version = "0.2.8"
version = "0.2.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31"
checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427"
dependencies = [
"cfg-if",
"libc 0.2.139",
"libc 0.2.147",
"wasi",
]
@ -90,8 +90,9 @@ version = "0.3.1"
dependencies = [
"crossterm",
"env_logger",
"libc 0.2.139",
"libc 0.2.147",
"log",
"mio",
"nix",
"once_cell",
"pam",
@ -112,15 +113,15 @@ checksum = "e32a70cf75e5846d53a673923498228bbec6a8624708a9ea5645f075d6276122"
[[package]]
name = "libc"
version = "0.2.139"
version = "0.2.147"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79"
checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3"
[[package]]
name = "lock_api"
version = "0.4.9"
version = "0.4.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df"
checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16"
dependencies = [
"autocfg",
"scopeguard",
@ -128,12 +129,9 @@ dependencies = [
[[package]]
name = "log"
version = "0.4.17"
version = "0.4.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
dependencies = [
"cfg-if",
]
checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4"
[[package]]
name = "memoffset"
@ -150,7 +148,7 @@ version = "0.8.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2"
dependencies = [
"libc 0.2.139",
"libc 0.2.147",
"log",
"wasi",
"windows-sys",
@ -165,15 +163,15 @@ dependencies = [
"bitflags",
"cc",
"cfg-if",
"libc 0.2.139",
"libc 0.2.147",
"memoffset",
]
[[package]]
name = "once_cell"
version = "1.17.1"
version = "1.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3"
checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d"
[[package]]
name = "pam"
@ -181,7 +179,7 @@ version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa2bdc959c201c047004a1420a92aaa1dd1a6b64d5ef333aa3a4ac764fb93097"
dependencies = [
"libc 0.2.139",
"libc 0.2.147",
"pam-sys",
"users 0.8.1",
]
@ -192,7 +190,7 @@ version = "0.5.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd4858311a097f01a0006ef7d0cd50bca81ec430c949d7bf95cbefd202282434"
dependencies = [
"libc 0.2.139",
"libc 0.2.147",
]
[[package]]
@ -212,7 +210,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447"
dependencies = [
"cfg-if",
"libc 0.2.139",
"libc 0.2.147",
"redox_syscall",
"smallvec",
"windows-targets",
@ -235,18 +233,18 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
[[package]]
name = "proc-macro2"
version = "1.0.51"
version = "1.0.63"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d727cae5b39d21da60fa540906919ad737832fe0b1c165da3a34d6548c849d6"
checksum = "7b368fba921b0dce7e60f5e04ec15e565b3303972b42bcfde1d0713b881959eb"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.23"
version = "1.0.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b"
checksum = "1b9ab9c7eadfd8df19006f1cf1a4aed13540ed5cbc047010ece5826e10825488"
dependencies = [
"proc-macro2",
]
@ -257,7 +255,7 @@ version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
dependencies = [
"libc 0.2.139",
"libc 0.2.147",
"rand_chacha",
"rand_core",
]
@ -311,18 +309,18 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
[[package]]
name = "serde"
version = "1.0.152"
version = "1.0.164"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb"
checksum = "9e8c8cf938e98f769bc164923b06dce91cea1751522f46f8466461af04c9027d"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.152"
version = "1.0.164"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e"
checksum = "d9735b638ccc51c28bf6914d90a2e9725b377144fc612c49a611fddd1b631d68"
dependencies = [
"proc-macro2",
"quote",
@ -335,7 +333,7 @@ version = "0.3.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "732768f1176d21d09e076c23a93123d40bba92d50c4058da34d45c8de8e682b9"
dependencies = [
"libc 0.2.139",
"libc 0.2.147",
"signal-hook-registry",
]
@ -345,7 +343,7 @@ version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29ad2e15f37ec9a6cc544097b78a1ec90001e9f71b81338ca39f430adaca99af"
dependencies = [
"libc 0.2.139",
"libc 0.2.147",
"mio",
"signal-hook",
]
@ -356,7 +354,7 @@ version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1"
dependencies = [
"libc 0.2.139",
"libc 0.2.147",
]
[[package]]
@ -367,9 +365,9 @@ checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0"
[[package]]
name = "syn"
version = "1.0.109"
version = "2.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
checksum = "2efbeae7acf4eabd6bcdcbd11c92f45231ddda7539edc7806bd1a04a03b24616"
dependencies = [
"proc-macro2",
"quote",
@ -387,9 +385,9 @@ dependencies = [
[[package]]
name = "unicode-ident"
version = "1.0.6"
version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc"
checksum = "b15811caf2415fb889178633e7724bad2509101cde276048e013b9def5e51fa0"
[[package]]
name = "unicode-segmentation"
@ -409,7 +407,7 @@ version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fed7d0912567d35f88010c23dbaf865e9da8b5227295e8dc0f2fdd109155ab7"
dependencies = [
"libc 0.2.139",
"libc 0.2.147",
]
[[package]]
@ -418,7 +416,7 @@ version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24cc0f6d6f267b73e5a2cadf007ba8f9bc39c6a6f9666f8cf25ea809a153b032"
dependencies = [
"libc 0.2.139",
"libc 0.2.147",
"log",
]

View file

@ -21,6 +21,8 @@ ratatui = "0.21.0"
crossterm = "0.26"
unicode-width = "0.1"
mio = { version = "0.8.8", features = [ "os-poll", "os-ext" ] }
# Interacting with the kernel interfaces
rand = "0.8.4"
nix = "0.23.1"

View file

@ -1,8 +1,7 @@
use log::{error, info, warn};
use std::error::Error;
use std::fmt::Display;
use std::fs::{self, File};
use std::os::fd::{FromRawFd, IntoRawFd};
use std::fs;
use std::path::Path;
use users::get_user_groups;
@ -17,9 +16,11 @@ use crate::post_login::x::setup_x;
use nix::unistd::{Gid, Uid};
use self::wait_with_log::LemursChild;
use self::x::XSetupError;
pub(crate) mod env_variables;
mod wait_with_log;
mod x;
const SYSTEM_SHELL: &str = "/bin/sh";
@ -75,20 +76,6 @@ impl From<XSetupError> for EnvironmentStartError {
}
}
fn output_command_to_log(mut command: Command, log_path: &Path) -> Command {
if let Ok(file) = File::create(log_path) {
let fd = file.into_raw_fd();
command
.stdout(unsafe { Stdio::from_raw_fd(fd) })
.stderr(unsafe { Stdio::from_raw_fd(fd) });
} else {
warn!("Failed to create and open file to log into");
}
command
}
fn lower_command_permissions_to_user(
mut command: Command,
user_info: &AuthUserInfo<'_>,
@ -119,15 +106,19 @@ fn lower_command_permissions_to_user(
}
pub enum SpawnedEnvironment {
X11 { server: Child, client: Child },
Wayland(Child),
X11 {
server: LemursChild,
client: LemursChild,
},
Wayland(LemursChild),
Tty(Child),
}
impl SpawnedEnvironment {
pub fn pid(&self) -> u32 {
match self {
Self::X11 { client, .. } | Self::Wayland(client) | Self::Tty(client) => client.id(),
Self::X11 { client, .. } | Self::Wayland(client) => client.id(),
Self::Tty(client) => client.id(),
}
}
@ -146,8 +137,8 @@ impl SpawnedEnvironment {
}
};
info!("Killing X server");
match server.kill() {
info!("Telling X server to shut down");
match server.send_sigterm() {
Ok(_) => {}
Err(err) => error!("Failed to terminate X11. Reason: {err}"),
}
@ -158,7 +149,11 @@ impl SpawnedEnvironment {
Err(err) => error!("Failed to wait for X11. Reason: {err}"),
}
}
Self::Wayland(mut client) | Self::Tty(mut client) => match client.wait() {
Self::Wayland(mut client) => match client.wait() {
Ok(exit_code) => info!("Client exited with exit code `{exit_code}`"),
Err(err) => error!("Failed to wait for client. Reason: {err}"),
},
Self::Tty(mut client) => match client.wait() {
Ok(exit_code) => info!("Client exited with exit code `{exit_code}`"),
Err(err) => error!("Failed to wait for client. Reason: {err}"),
},
@ -181,16 +176,7 @@ impl PostLoginEnvironment {
let mut client = lower_command_permissions_to_user(Command::new(SYSTEM_SHELL), user_info);
let mut client = if config.do_log {
info!(
"Setup client to log `stdout` and `stderr` to '{log_path}'",
log_path = config.client_log_path
);
output_command_to_log(client, Path::new(&config.client_log_path))
} else {
client.stdout(Stdio::null()).stderr(Stdio::null());
client
};
let log_path = config.do_log.then_some(Path::new(&config.client_log_path));
if let Some(shell_login_flag) = shell_login_flag {
client.arg(shell_login_flag);
@ -201,13 +187,13 @@ impl PostLoginEnvironment {
match self {
PostLoginEnvironment::X { xinitrc_path } => {
info!("Starting X11 session");
let server = setup_x(process_env, user_info, config)
.map_err(EnvironmentStartError::XSetup)?;
let client = match client
.arg(format!("{} {}", "/etc/lemurs/xsetup.sh", xinitrc_path))
.spawn()
{
client.arg(format!("{} {}", "/etc/lemurs/xsetup.sh", xinitrc_path));
let client = match LemursChild::spawn(client, log_path) {
Ok(child) => child,
Err(err) => {
error!("Failed to start X11 environment. Reason '{}'", err);
@ -219,7 +205,10 @@ impl PostLoginEnvironment {
}
PostLoginEnvironment::Wayland { script_path } => {
info!("Starting Wayland session");
let child = match client.arg(script_path).spawn() {
client.arg(script_path);
let child = match LemursChild::spawn(client, log_path) {
Ok(child) => child,
Err(err) => {
error!("Failed to start Wayland Compositor. Reason '{err}'");

View file

@ -0,0 +1,275 @@
//! This module implements a thread that ensures that the log files don't exceed a specific size.
use std::fs::OpenOptions;
use std::io::{self, BufWriter};
use std::path::Path;
use std::process::{Child, Command, ExitStatus, Stdio};
use std::thread::JoinHandle;
use std::io::Read;
use log::info;
use mio::unix::pipe::Receiver;
use mio::{Events, Interest, Poll, Token, Waker};
/// 64MB
const LOG_WRITER_SIZE_LIMIT: usize = 67_108_864;
struct LimitSizeWriter<W: io::Write> {
writer: BufWriter<W>,
current_byte_count: usize,
size_limit: usize,
}
impl<W: io::Write> io::Write for LimitSizeWriter<W> {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
debug_assert!(self.current_byte_count <= self.size_limit);
if self.current_byte_count >= self.size_limit {
return Ok(buf.len());
}
let write_len = usize::min(buf.len(), self.size_limit - self.current_byte_count);
let written_len = self.writer.write(&buf[..write_len])?;
self.current_byte_count += write_len;
if written_len != write_len {
return Ok(written_len);
}
Ok(buf.len())
}
#[inline]
fn flush(&mut self) -> io::Result<()> {
self.writer.flush()
}
}
impl<W: io::Write> LimitSizeWriter<W> {
pub fn new(writer: W, size_limit: usize) -> Self {
Self {
writer: BufWriter::new(writer),
current_byte_count: 0,
size_limit,
}
}
}
/// This is a wrapper of the rust std `Child` struct.
///
/// This makes handling spawning, killing and waiting a lot easier to combine with the
/// output log files.
pub enum LemursChild {
NoLog(Child),
Log(LimitedOutputChild),
}
pub struct LimitedOutputChild {
process: Child,
log_thread: Option<(Waker, JoinHandle<io::Result<()>>)>,
}
impl LemursChild {
pub fn spawn(mut command: Command, log_path: Option<&Path>) -> io::Result<Self> {
Ok(match log_path {
None => Self::NoLog(
command
.stdin(Stdio::null())
.stdout(Stdio::null())
.stderr(Stdio::null())
.spawn()?,
),
Some(log_path) => Self::Log(LimitedOutputChild::spawn(command, log_path)?),
})
}
pub fn wait(&mut self) -> io::Result<ExitStatus> {
match self {
Self::NoLog(process) => process.wait(),
Self::Log(process) => process.wait(),
}
}
pub fn try_wait(&mut self) -> io::Result<Option<ExitStatus>> {
match self {
Self::NoLog(process) => process.try_wait(),
Self::Log(process) => process.try_wait(),
}
}
pub fn kill(&mut self) -> io::Result<()> {
match self {
Self::NoLog(process) => process.kill(),
Self::Log(process) => process.kill(),
}
}
pub fn send_sigterm(&self) -> io::Result<()> {
let i = unsafe { libc::kill(self.id() as libc::pid_t, libc::SIGTERM) };
Ok(())
}
pub fn id(&self) -> u32 {
match self {
Self::NoLog(process) => process.id(),
Self::Log(process) => process.id(),
}
}
}
impl LimitedOutputChild {
pub fn spawn(mut command: Command, log_path: &Path) -> io::Result<Self> {
const STDOUT_PIPE_RECV: Token = Token(0);
const STDERR_PIPE_RECV: Token = Token(1);
const WAKER_TOKEN: Token = Token(2);
let mut poll = Poll::new()?;
let mut events = Events::with_capacity(128);
command
.stdin(Stdio::null())
.stdout(Stdio::piped())
.stderr(Stdio::piped());
use io::Write;
let mut file_options = OpenOptions::new();
file_options.create(true);
file_options.write(true);
file_options.truncate(true);
let mut process = command.spawn()?;
let file = file_options.open(log_path)?;
let Some(stdout) = process.stdout.take() else {
return Err(io::Error::new(io::ErrorKind::Other, "Failed to grab stdout"));
};
let Some(stderr) = process.stderr.take() else {
return Err(io::Error::new(io::ErrorKind::Other, "Failed to grab stderr"));
};
let mut stdout_receiver = Receiver::from(stdout);
stdout_receiver.set_nonblocking(true)?;
poll.registry()
.register(&mut stdout_receiver, STDOUT_PIPE_RECV, Interest::READABLE)?;
let mut stderr_receiver = Receiver::from(stderr);
stderr_receiver.set_nonblocking(true)?;
poll.registry()
.register(&mut stderr_receiver, STDERR_PIPE_RECV, Interest::READABLE)?;
let waker = Waker::new(poll.registry(), WAKER_TOKEN)?;
let mut file_handle = LimitSizeWriter::new(file, LOG_WRITER_SIZE_LIMIT);
let join_handle = std::thread::spawn(move || loop {
poll.poll(&mut events, None)?;
fn forward_receiver_to_file(
receiver: &mut Receiver,
file_handle: &mut LimitSizeWriter<impl io::Write>,
is_read_closed: bool,
) -> io::Result<()> {
let mut buf = [0u8; 2048];
loop {
if is_read_closed {
let mut v = Vec::new();
receiver.read_to_end(&mut v)?;
file_handle.write_all(&v)?;
break;
}
match receiver.read(&mut buf) {
Err(err) if err.kind() == std::io::ErrorKind::WouldBlock => {
break;
}
Err(err) => return Err(err),
Ok(n) => {
file_handle.write_all(&buf[..n])?;
}
}
}
Ok(())
}
for event in events.iter() {
match event.token() {
x if x == WAKER_TOKEN => {
return Ok(());
}
x if x == STDOUT_PIPE_RECV => forward_receiver_to_file(
&mut stdout_receiver,
&mut file_handle,
event.is_read_closed(),
)?,
x if x == STDERR_PIPE_RECV => forward_receiver_to_file(
&mut stderr_receiver,
&mut file_handle,
event.is_read_closed(),
)?,
_ => {
return Err(io::Error::new(io::ErrorKind::Other, "Invalid event"));
}
}
}
});
Ok(Self {
process,
log_thread: Some((waker, join_handle)),
})
}
fn stop_logging(&mut self) -> io::Result<()> {
if let Some((waker, join_handle)) = self.log_thread.take() {
waker.wake()?;
info!("Joining with logging thread.");
join_handle
.join()
.expect("Failed to join with log thread")?;
}
Ok(())
}
pub fn wait(&mut self) -> io::Result<ExitStatus> {
let exit_status = self.process.wait()?;
self.stop_logging()?;
Ok(exit_status)
}
pub fn try_wait(&mut self) -> io::Result<Option<ExitStatus>> {
let exit_status = match self.process.try_wait()? {
None => return Ok(None),
Some(exit_status) => exit_status,
};
self.stop_logging()?;
Ok(Some(exit_status))
}
pub fn kill(&mut self) -> io::Result<()> {
self.process.kill()?;
self.stop_logging()?;
Ok(())
}
pub fn id(&self) -> u32 {
self.process.id()
}
}

View file

@ -7,7 +7,7 @@ use std::env;
use std::error::Error;
use std::fmt::Display;
use std::fs::remove_file;
use std::process::{Child, Command, Stdio};
use std::process::{Command, Stdio};
use std::sync::atomic::AtomicBool;
use std::{thread, time};
@ -18,7 +18,7 @@ use log::{error, info};
use crate::auth::AuthUserInfo;
use crate::config::Config;
use crate::env_container::EnvironmentContainer;
use crate::post_login::output_command_to_log;
use crate::post_login::wait_with_log::LemursChild;
const XSTART_CHECK_INTERVAL_MILLIS: u64 = 100;
@ -74,7 +74,7 @@ pub fn setup_x(
process_env: &mut EnvironmentContainer,
user_info: &AuthUserInfo,
config: &Config,
) -> Result<Child, XSetupError> {
) -> Result<LemursChild, XSetupError> {
use std::os::unix::process::CommandExt;
info!("Start setup of X server");
@ -137,25 +137,16 @@ pub fn setup_x(
let mut child = Command::new(super::SYSTEM_SHELL);
let mut child = if config.do_log {
info!(
"Setup XServer to log `stdout` and `stderr` to '{log_path}'",
log_path = config.xserver_log_path
);
output_command_to_log(child, Path::new(&config.xserver_log_path))
} else {
child.stdout(Stdio::null()).stderr(Stdio::null());
child
};
let log_path = config.do_log.then_some(Path::new(&config.xserver_log_path));
let mut child = child
child
.arg("-c")
.arg(format!("/usr/bin/X {display_value} vt{doubledigit_vtnr}"))
.spawn()
.map_err(|err| {
error!("Failed to start X server. Reason: {}", err);
XSetupError::XServerStart
})?;
.arg(format!("/usr/bin/X {display_value} vt{doubledigit_vtnr}"));
let mut child = LemursChild::spawn(child, log_path).map_err(|err| {
error!("Failed to start X server. Reason: {}", err);
XSetupError::XServerStart
})?;
// See note above
unsafe {