2019-11-21 13:36:28 +00:00
|
|
|
use std::process::{Command, Stdio};
|
|
|
|
|
2020-04-17 12:11:25 +00:00
|
|
|
use anyhow::{Error};
|
2019-11-21 13:36:28 +00:00
|
|
|
use serde_json::{json, Value};
|
2019-01-25 16:17:30 +00:00
|
|
|
|
2020-04-16 15:08:19 +00:00
|
|
|
use proxmox::api::{api, ApiMethod, Router, RpcEnvironment, Permission};
|
2019-11-21 12:10:49 +00:00
|
|
|
|
2019-05-09 05:44:09 +00:00
|
|
|
use crate::api2::types::*;
|
2020-04-16 15:08:19 +00:00
|
|
|
use crate::config::acl::PRIV_SYS_AUDIT;
|
2019-05-09 05:44:09 +00:00
|
|
|
|
2019-01-25 17:20:51 +00:00
|
|
|
fn dump_journal(
|
|
|
|
start: Option<u64>,
|
|
|
|
limit: Option<u64>,
|
|
|
|
since: Option<&str>,
|
|
|
|
until: Option<&str>,
|
|
|
|
service: Option<&str>,
|
|
|
|
) -> Result<(u64, Vec<Value>), Error> {
|
|
|
|
|
2019-01-26 09:56:11 +00:00
|
|
|
let mut args = vec!["-o", "short", "--no-pager"];
|
2019-01-25 17:20:51 +00:00
|
|
|
|
2019-01-26 09:23:52 +00:00
|
|
|
if let Some(service) = service { args.extend(&["--unit", service]); }
|
|
|
|
if let Some(since) = since { args.extend(&["--since", since]); }
|
|
|
|
if let Some(until) = until { args.extend(&["--until", until]); }
|
2019-01-25 17:20:51 +00:00
|
|
|
|
|
|
|
let mut lines: Vec<Value> = vec![];
|
|
|
|
let mut limit = limit.unwrap_or(50);
|
|
|
|
let start = start.unwrap_or(0);
|
|
|
|
let mut count: u64 = 0;
|
|
|
|
|
2020-06-15 09:16:11 +00:00
|
|
|
let mut child = Command::new("journalctl")
|
2019-01-25 17:20:51 +00:00
|
|
|
.args(&args)
|
|
|
|
.stdout(Stdio::piped())
|
|
|
|
.spawn()?;
|
|
|
|
|
|
|
|
use std::io::{BufRead,BufReader};
|
|
|
|
|
|
|
|
if let Some(ref mut stdout) = child.stdout {
|
|
|
|
for line in BufReader::new(stdout).lines() {
|
2019-01-26 09:23:52 +00:00
|
|
|
match line {
|
|
|
|
Ok(line) => {
|
|
|
|
count += 1;
|
|
|
|
if count < start { continue };
|
2019-10-25 16:04:37 +00:00
|
|
|
if limit == 0 { continue };
|
2019-01-26 09:23:52 +00:00
|
|
|
|
|
|
|
lines.push(json!({ "n": count, "t": line }));
|
|
|
|
|
|
|
|
limit -= 1;
|
|
|
|
}
|
|
|
|
Err(err) => {
|
2019-01-26 09:56:11 +00:00
|
|
|
log::error!("reading journal failed: {}", err);
|
2019-01-26 09:23:52 +00:00
|
|
|
let _ = child.kill();
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2019-01-25 17:20:51 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-01-26 09:23:52 +00:00
|
|
|
let status = child.wait().unwrap();
|
|
|
|
if !status.success() {
|
2019-01-26 09:56:11 +00:00
|
|
|
log::error!("journalctl failed with {}", status);
|
2019-01-26 09:23:52 +00:00
|
|
|
}
|
2019-01-25 17:20:51 +00:00
|
|
|
|
|
|
|
// HACK: ExtJS store.guaranteeRange() does not like empty array
|
|
|
|
// so we add a line
|
|
|
|
if count == 0 {
|
|
|
|
count += 1;
|
|
|
|
lines.push(json!({ "n": count, "t": "no content"}));
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok((count, lines))
|
|
|
|
}
|
2019-01-25 16:17:30 +00:00
|
|
|
|
2019-12-17 12:26:14 +00:00
|
|
|
#[api(
|
|
|
|
protected: true,
|
|
|
|
input: {
|
|
|
|
properties: {
|
|
|
|
node: {
|
|
|
|
schema: NODE_SCHEMA,
|
|
|
|
},
|
|
|
|
start: {
|
|
|
|
type: Integer,
|
|
|
|
description: "Start line number.",
|
|
|
|
minimum: 0,
|
|
|
|
optional: true,
|
|
|
|
},
|
|
|
|
limit: {
|
|
|
|
type: Integer,
|
|
|
|
description: "Max. number of lines.",
|
|
|
|
optional: true,
|
|
|
|
minimum: 0,
|
|
|
|
},
|
|
|
|
since: {
|
|
|
|
type: String,
|
|
|
|
optional: true,
|
|
|
|
description: "Display all log since this date-time string.",
|
|
|
|
format: &SYSTEMD_DATETIME_FORMAT,
|
|
|
|
},
|
|
|
|
until: {
|
|
|
|
type: String,
|
|
|
|
optional: true,
|
|
|
|
description: "Display all log until this date-time string.",
|
|
|
|
format: &SYSTEMD_DATETIME_FORMAT,
|
|
|
|
},
|
|
|
|
service: {
|
|
|
|
type: String,
|
|
|
|
optional: true,
|
|
|
|
description: "Service ID.",
|
|
|
|
max_length: 128,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
returns: {
|
|
|
|
type: Object,
|
|
|
|
description: "Returns a list of syslog entries.",
|
|
|
|
properties: {
|
|
|
|
n: {
|
|
|
|
type: Integer,
|
|
|
|
description: "Line number.",
|
|
|
|
},
|
|
|
|
t: {
|
|
|
|
type: String,
|
|
|
|
description: "Line text.",
|
|
|
|
}
|
|
|
|
},
|
|
|
|
},
|
2020-04-16 15:08:19 +00:00
|
|
|
access: {
|
2020-04-30 07:30:00 +00:00
|
|
|
permission: &Permission::Privilege(&["system", "log"], PRIV_SYS_AUDIT, false),
|
2020-04-16 15:08:19 +00:00
|
|
|
},
|
2019-12-17 12:26:14 +00:00
|
|
|
)]
|
|
|
|
/// Read syslog entries.
|
2019-01-26 13:50:37 +00:00
|
|
|
fn get_syslog(
|
|
|
|
param: Value,
|
|
|
|
_info: &ApiMethod,
|
2020-05-18 07:57:35 +00:00
|
|
|
mut rpcenv: &mut dyn RpcEnvironment,
|
2019-01-26 13:50:37 +00:00
|
|
|
) -> Result<Value, Error> {
|
2019-01-25 16:17:30 +00:00
|
|
|
|
2019-01-25 17:20:51 +00:00
|
|
|
let (count, lines) = dump_journal(
|
|
|
|
param["start"].as_u64(),
|
|
|
|
param["limit"].as_u64(),
|
|
|
|
param["since"].as_str(),
|
|
|
|
param["until"].as_str(),
|
2019-01-26 09:23:52 +00:00
|
|
|
param["service"].as_str())?;
|
2019-01-25 17:20:51 +00:00
|
|
|
|
2020-05-18 07:57:35 +00:00
|
|
|
rpcenv["total"] = Value::from(count);
|
2019-01-25 16:17:30 +00:00
|
|
|
|
2019-01-25 17:20:51 +00:00
|
|
|
Ok(json!(lines))
|
2019-01-25 16:17:30 +00:00
|
|
|
}
|
|
|
|
|
2019-11-21 08:36:41 +00:00
|
|
|
pub const ROUTER: Router = Router::new()
|
2019-12-17 12:26:14 +00:00
|
|
|
.get(&API_METHOD_GET_SYSLOG);
|
2019-01-25 16:17:30 +00:00
|
|
|
|