syslog_rs/a_sync/syslog_async_internal.rs
1/*-
2 * syslog-rs - a syslog client translated from libc to rust
3 *
4 * Copyright 2025 Aleksandr Morozov
5 *
6 * Licensed under the EUPL, Version 1.2 or - as soon they will be approved by
7 * the European Commission - subsequent versions of the EUPL (the "Licence").
8 *
9 * You may not use this work except in compliance with the Licence.
10 *
11 * You may obtain a copy of the Licence at:
12 *
13 * https://joinup.ec.europa.eu/software/page/eupl
14 *
15 * Unless required by applicable law or agreed to in writing, software
16 * distributed under the Licence is distributed on an "AS IS" basis, WITHOUT
17 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
18 * Licence for the specific language governing permissions and limitations
19 * under the Licence.
20 */
21
22
23use std::borrow::Cow;
24use std::marker::PhantomData;
25
26use tokio::time::{sleep, Duration};
27
28use nix::libc;
29
30use crate::formatters::SyslogFormatter;
31use crate::map_error_os;
32use crate::portable;
33use crate::common::*;
34use crate::error::SyRes;
35use crate::socket::TapType;
36use crate::socket::TapTypeData;
37
38use super::async_socket::*;
39
40/// Internal structure with syslog setup
41#[derive(Debug)]
42pub struct AsyncSyslogInternal<F: SyslogFormatter + Send>
43{
44 /// An identification i.e program name, thread name
45 logtag: String,
46
47 /// A pid of the program.
48 logpid: String,
49
50 /// Defines how syslog operates
51 logstat: LogStat,
52
53 /// Holds the facility
54 facility: LogFacility,
55
56 /// A logmask
57 logmask: i32,
58
59 /// A stream
60 stream: Box<dyn AsyncSyslogTap + Send + Sync>,
61
62 _p: PhantomData<F>,
63}
64
65//unsafe impl Sync for SyslogInternal{}
66//unsafe impl Send for SyslogInternal{}
67
68// Drop is called when no more references are left.
69/*
70impl Drop for AsyncSyslogInternal
71{
72 fn drop(&mut self)
73 {
74 // requires await, so skip.
75 //self.disconnectlog();
76 }
77}
78 */
79
80impl<F: SyslogFormatter + Send> AsyncSyslogInternal<F>
81{
82 /// Creates new instance of [SyslogInternal] which contains all
83 /// client syslog logic.
84 ///
85 /// # Arguments
86 ///
87 /// * `ident` - An optional argument which takes ref to str. If none, the
88 /// ident will be set later. Yje ident will be trucated to 48 UTF8
89 /// chars.
90 ///
91 /// * `logstat` - A [LogStat] flags separated by '|'
92 ///
93 /// * `facility` - A [LogFacility] flag
94 ///
95 /// * `req_tap` - A type of the syslog instance reuired. See [TapTypeData].
96 ///
97 /// # Returns
98 ///
99 /// A [SyRes] is returned with following:
100 ///
101 /// * [Result::Ok] - with the instance.
102 ///
103 /// * [Result::Err] - with error description.
104 pub(crate)
105 fn new(
106 ident: Option<&str>,
107 logstat: LogStat,
108 facility: LogFacility,
109 req_tap: TapTypeData
110 ) -> SyRes<Self>
111 {
112 // check if log_facility is invalid
113 let log_facility =
114 if facility.is_empty() == false &&
115 (facility & !LogMask::LOG_FACMASK).is_empty() == true
116 {
117 facility
118 }
119 else
120 {
121 // default facility code
122 LogFacility::LOG_USER
123 };
124
125 let logtag =
126 match ident
127 {
128 Some(r) =>
129 truncate_n(r, RFC_MAX_APP_NAME).to_string(),
130 None =>
131 truncate_n(
132 portable::p_getprogname()
133 .unwrap_or("".to_string())
134 .as_str(),
135 RFC_MAX_APP_NAME
136 )
137 .to_string()
138 };
139
140 return Ok(
141 Self
142 {
143 logtag: logtag,
144 logpid: portable::get_pid().to_string(),
145 logstat: logstat,
146 facility: log_facility,
147 logmask: 0xff,
148 stream: AsyncTap::<()>::new(req_tap)?,
149 _p: PhantomData
150 }
151 );
152 }
153
154 pub(crate) async
155 fn update_tap_data(&mut self, tap_data: TapTypeData) -> SyRes<()>
156 {
157 let is_con = self.stream.is_connected();
158
159 if is_con == true
160 {
161 self
162 .stream
163 .disconnectlog()
164 .await
165 .map_err(|e|
166 map_error_os!(e, "update_tap_data() can not disconnect log properly")
167 )?;
168 }
169
170 self.stream.update_tap_data(tap_data)?;
171
172 if is_con == true
173 {
174 // replace with new instance
175 self.stream.connectlog().await?;
176 }
177
178 return Ok(());
179 }
180
181
182 pub(crate)
183 fn set_logmask(&mut self, logmask: i32) -> i32
184 {
185 let oldmask = self.logmask;
186
187 if logmask != 0
188 {
189 self.logmask = logmask;
190 }
191
192 return oldmask;
193 }
194
195 #[inline]
196 async
197 fn send_to_stderr(&self, msg: &[Cow<'_, str>])
198 {
199 if self.logstat.intersects(LogStat::LOG_PERROR) == true
200 {
201 let stderr_lock = tokio::io::stderr();
202
203 let newline = "\n";
204 let _ = async_send_to_fd(stderr_lock, msg, newline).await;
205 }
206 }
207
208 #[inline]
209 async
210 fn send_to_syscons(&self, msg_payload: &[Cow<'_, str>])
211 {
212 use tokio::fs::File;
213
214 if self.logstat.intersects(LogStat::LOG_CONS)
215 {
216 let syscons =
217 File
218 ::options()
219 .create(false)
220 .read(false)
221 .write(true)
222 .custom_flags(libc::O_NONBLOCK | libc::O_CLOEXEC)
223 .open(*PATH_CONSOLE)
224 .await;
225
226 if let Ok(file) = syscons
227 {
228 let newline = "\n";
229 let _ = async_send_to_fd(file, msg_payload, newline);
230 }
231 }
232 }
233
234 #[inline]
235 pub(crate)
236 fn is_logmasked(&self, pri: i32) -> bool
237 {
238 if ((1 << (pri & LogMask::LOG_PRIMASK)) & self.logmask) == 0
239 {
240 return true;
241 }
242
243 return false;
244 }
245
246 /// Returns the type of the socket.
247 #[inline]
248 pub(crate)
249 fn get_tap_type(&self) -> TapType
250 {
251 return self.stream.get_type();
252 }
253
254 #[inline]
255 pub(crate)
256 fn set_logtag<L: AsRef<str>>(&mut self, logtag: L, update_pid: bool)
257 {
258 self.logtag =
259 truncate_n(logtag.as_ref(), RFC_MAX_APP_NAME).to_string();
260
261 if update_pid == true
262 {
263 self.logpid = portable::get_pid().to_string();
264 }
265
266 return;
267 }
268
269 /// Disconnects the unix stream from syslog.
270 #[inline]
271 pub(crate) async
272 fn disconnectlog(&mut self) -> SyRes<()>
273 {
274 return
275 self
276 .stream
277 .disconnectlog()
278 .await
279 .map_err(|e| map_error_os!(e, "can not disconnect log properly"));
280 }
281
282 /// Connects unix stream to the syslog and sets up the properties of
283 /// the unix stream.
284 #[inline]
285 pub(crate) async
286 fn connectlog(&mut self) -> SyRes<()>
287 {
288 return self.stream.connectlog().await;
289 }
290
291 /// An internal function which is called by the syslog or vsyslog.
292 pub(crate) async
293 fn vsyslog1(&mut self, mut pri: Priority, fmt: &str)
294 {
295 // check for invalid bits
296 if let Err(e) = pri.check_invalid_bits()
297 {
298 self.send_to_stderr(&[Cow::Owned(e.to_string())]).await;
299 }
300
301 /*match check_invalid_bits(&mut pri)
302 {
303 Ok(_) => {},
304 Err(_e) => self.vsyslog1(get_internal_log(), fmt).await
305 }*/
306
307 // check priority against setlogmask
308 if self.is_logmasked(pri.bits()) == true
309 {
310 return;
311 }
312
313 // set default facility if not specified in pri
314 if (pri.bits() & LOG_FACMASK) == 0
315 {
316 pri.set_facility(self.facility);
317 }
318
319 let progname = self.logtag.clone();
320 let pid = self.logpid.clone();
321
322 let msg_formatted = F::vsyslog1_format(self.get_tap_type(), pri, &progname, &pid, fmt);
323
324 // output to stderr if required
325 self.send_to_stderr(msg_formatted.get_stderr_output()).await;
326
327 if self.stream.is_connected() == false
328 {
329 // open connection
330 match self.connectlog().await
331 {
332 Ok(_) => {},
333 Err(e) =>
334 {
335 self.send_to_stderr( &[Cow::Owned(e.into_inner())] ).await;
336 return;
337 }
338 }
339 }
340
341 let fullmsg = msg_formatted.concat();
342
343
344 // There are two possible scenarios when send may fail:
345 // 1. syslog temporary unavailable
346 // 2. syslog out of buffer space
347 // If we are connected to priv socket then in case of 1 we reopen connection
348 // and retry once.
349 // If we are connected to unpriv then in case of 2 repeatedly retrying to send
350 // until syslog socket buffer space will be cleared
351
352 loop
353 {
354 match self.stream.send(fullmsg.as_bytes()).await
355 {
356 Ok(_) => return,
357 Err(err) =>
358 {
359 if self.get_tap_type().is_network() == false
360 {
361 if let Some(libc::ENOBUFS) = err.raw_os_error()
362 {
363 // scenario 2
364 if self.get_tap_type().is_priv() == true
365 {
366 break;
367 }
368
369 sleep(Duration::from_micros(1)).await;
370 }
371 else
372 {
373 // scenario 1
374 let _ = self.disconnectlog().await;
375 match self.connectlog().await
376 {
377 Ok(_) => {},
378 Err(_e) => break,
379 }
380
381 // if resend will fail then probably the scn 2 will take place
382 }
383 }
384 else
385 {
386 let _ = self.disconnectlog().await;
387 match self.connectlog().await
388 {
389 Ok(_) => {},
390 Err(_e) => break,
391 }
392 }
393 }
394 }
395 } // loop
396
397
398 // If program reached this point then transmission over socket failed.
399 // Try to output message to console
400
401 self.send_to_syscons(msg_formatted.get_stderr_output()).await;
402 }
403}