1use linuxutils_common::man::ManContent;
2
3pub const MAN: ManContent = ManContent::empty();
4
5use clap::Parser;
6use std::{
7 io::{self, BufRead},
8 os::unix::net::UnixDatagram,
9 process::ExitCode,
10};
11
12const LOG_SOCKET: &str = "/dev/log";
13
14const FACILITIES: &[(&str, u8)] = &[
15 ("kern", 0),
16 ("user", 1),
17 ("mail", 2),
18 ("daemon", 3),
19 ("auth", 4),
20 ("syslog", 5),
21 ("lpr", 6),
22 ("news", 7),
23 ("uucp", 8),
24 ("cron", 9),
25 ("authpriv", 10),
26 ("ftp", 11),
27 ("local0", 16),
28 ("local1", 17),
29 ("local2", 18),
30 ("local3", 19),
31 ("local4", 20),
32 ("local5", 21),
33 ("local6", 22),
34 ("local7", 23),
35 ("security", 4),
36];
37
38const LEVELS: &[(&str, u8)] = &[
39 ("emerg", 0),
40 ("alert", 1),
41 ("crit", 2),
42 ("err", 3),
43 ("warning", 4),
44 ("notice", 5),
45 ("info", 6),
46 ("debug", 7),
47 ("panic", 0),
48 ("error", 3),
49 ("warn", 4),
50];
51
52#[derive(Parser)]
53#[command(name = "logger", about = "Enter messages into the system log")]
54pub struct Args {
55 #[arg(short = 'i')]
57 log_pid: bool,
58
59 #[arg(short = 'f', long = "file")]
61 file: Option<String>,
62
63 #[arg(short = 'e', long = "skip-empty")]
65 skip_empty: bool,
66
67 #[arg(short = 'p', long = "priority", default_value = "user.notice")]
69 priority: String,
70
71 #[arg(short = 's', long = "stderr")]
73 stderr: bool,
74
75 #[arg(short = 't', long = "tag")]
77 tag: Option<String>,
78
79 #[arg(short = 'u', long = "socket")]
81 socket: Option<String>,
82
83 message: Vec<String>,
85}
86
87fn parse_facility(name: &str) -> Result<u8, String> {
88 for &(n, code) in FACILITIES {
89 if n.eq_ignore_ascii_case(name) {
90 return Ok(code);
91 }
92 }
93 Err(format!("unknown facility: {name}"))
94}
95
96fn parse_level(name: &str) -> Result<u8, String> {
97 for &(n, code) in LEVELS {
98 if n.eq_ignore_ascii_case(name) {
99 return Ok(code);
100 }
101 }
102 Err(format!("unknown level: {name}"))
103}
104
105fn parse_priority(s: &str) -> Result<u32, String> {
106 if let Ok(n) = s.parse::<u32>() {
107 return Ok(n);
108 }
109 let (fac_name, level_name) = s
110 .split_once('.')
111 .ok_or_else(|| format!("invalid priority: {s}"))?;
112 let fac = parse_facility(fac_name)? as u32;
113 let level = parse_level(level_name)? as u32;
114 Ok(fac * 8 + level)
115}
116
117fn format_message(
118 priority: u32,
119 tag: &str,
120 pid: Option<u32>,
121 message: &str,
122) -> String {
123 if let Some(pid) = pid {
124 format!("<{priority}>{tag}[{pid}]: {message}")
125 } else {
126 format!("<{priority}>{tag}: {message}")
127 }
128}
129
130fn default_tag() -> String {
131 std::env::var("USER")
132 .unwrap_or_else(|_| rustix::process::getuid().as_raw().to_string())
133}
134
135pub fn run(args: Args) -> ExitCode {
136 let priority = match parse_priority(&args.priority) {
137 Ok(p) => p,
138 Err(e) => {
139 eprintln!("logger: {e}");
140 return ExitCode::FAILURE;
141 }
142 };
143
144 let tag = args.tag.unwrap_or_else(default_tag);
145 let pid = if args.log_pid {
146 Some(std::process::id())
147 } else {
148 None
149 };
150
151 let socket_path = args.socket.as_deref().unwrap_or(LOG_SOCKET);
152 let socket = match UnixDatagram::unbound() {
153 Ok(s) => s,
154 Err(e) => {
155 eprintln!("logger: failed to create socket: {e}");
156 return ExitCode::FAILURE;
157 }
158 };
159
160 if let Err(e) = socket.connect(socket_path) {
161 eprintln!("logger: failed to connect to {socket_path}: {e}");
162 return ExitCode::FAILURE;
163 }
164
165 let mut failed = false;
166 let mut send = |message: &str| {
167 let formatted = format_message(priority, &tag, pid, message);
168 if args.stderr {
169 eprintln!("{tag}: {message}");
170 }
171 if let Err(e) = socket.send(formatted.as_bytes()) {
172 eprintln!("logger: send failed: {e}");
173 failed = true;
174 }
175 };
176
177 if let Some(ref file) = args.file {
178 let reader: Box<dyn BufRead> = if file == "-" {
179 Box::new(io::stdin().lock())
180 } else {
181 match std::fs::File::open(file) {
182 Ok(f) => Box::new(io::BufReader::new(f)),
183 Err(e) => {
184 eprintln!("logger: failed to open {file}: {e}");
185 return ExitCode::FAILURE;
186 }
187 }
188 };
189 for line in reader.lines() {
190 match line {
191 Ok(line) => {
192 if args.skip_empty && line.is_empty() {
193 continue;
194 }
195 send(&line);
196 }
197 Err(e) => {
198 eprintln!("logger: read error: {e}");
199 return ExitCode::FAILURE;
200 }
201 }
202 }
203 } else if !args.message.is_empty() {
204 send(&args.message.join(" "));
205 } else {
206 let stdin = io::stdin();
207 for line in stdin.lock().lines() {
208 match line {
209 Ok(line) => {
210 if args.skip_empty && line.is_empty() {
211 continue;
212 }
213 send(&line);
214 }
215 Err(e) => {
216 eprintln!("logger: read error: {e}");
217 return ExitCode::FAILURE;
218 }
219 }
220 }
221 }
222
223 if failed {
224 ExitCode::FAILURE
225 } else {
226 ExitCode::SUCCESS
227 }
228}