Skip to main content

syslog_rs/sync/
syslog_sync_internal.rs

1/*-
2 * syslog-rs - a syslog client translated from libc to rust
3 * 
4 * Copyright 2025 Aleksandr Morozov
5 * 
6 * The syslog-rs crate can be redistributed and/or modified
7 * under the terms of either of the following licenses:
8 *
9 *   1. the Mozilla Public License Version 2.0 (the “MPL”) OR
10 *
11 *   2. The MIT License (MIT)
12 *                     
13 *   3. EUROPEAN UNION PUBLIC LICENCE v. 1.2 EUPL © the European Union 2007, 2016
14 */
15
16
17use std::io::{Error};
18
19use crate::formatters::{SyslogFormatted, SyslogFormatter};
20use crate::portable;
21use crate::common::*;
22use crate::error::{SyRes, SyslogError};
23use crate::SyslogDestination;
24
25use super::socket::*;
26
27#[cfg(target_family = "unix")]
28use nix::libc;
29
30#[derive(Debug, Clone)]
31pub(crate) struct LogItems
32{
33    /// An identification i.e program name, thread name
34    pub(crate) logtag: String, 
35
36    /// A pid of the program.
37    pub(crate) logpid: String,
38
39    /// A logmask
40    pub(crate) logmask: i32,
41
42    pub(crate) logstat: LogStat,
43
44    /// Holds the facility 
45    pub(crate) facility: LogFacility,
46}
47
48impl LogItems
49{
50    #[inline]
51    pub 
52    fn new(ident: Option<&str>, logmask: i32, logstat: LogStat, facility: LogFacility) -> Self
53    {
54        let mut log_inst = 
55            Self
56            {
57                logtag: String::new(),
58                logpid: portable::get_pid().to_string(),
59                logmask: logmask,
60                logstat: logstat,
61                facility: LogFacility::empty(),
62            };
63
64        log_inst.set_log_facility(facility);
65        log_inst.set_identity(ident);
66
67        return log_inst;
68    }
69
70    pub(crate) 
71    fn set_log_facility(&mut self, facility: LogFacility)
72    {
73        let log_facility =
74            if facility.is_empty() == false && 
75                (facility & !LogMask::LOG_FACMASK).is_empty() == true
76            {
77                facility
78            }
79            else
80            {
81                // default facility code
82                LogFacility::LOG_USER
83            };
84
85        self.facility = log_facility;
86
87        return;
88    }
89
90    pub(crate) 
91    fn set_identity(&mut self, ident: Option<&str>)
92    {
93        let logtag = 
94            match ident
95            {
96                Some(r) => 
97                    truncate_n(r, RFC_MAX_APP_NAME).to_string(),
98                None => 
99                    truncate_n(
100                        portable::p_getprogname()
101                            .unwrap_or("notavail".to_string())
102                            .as_str(),
103                        RFC_MAX_APP_NAME
104                    )
105                    .to_string()
106            };
107
108        self.logtag = logtag;
109
110        return;
111    }
112
113    #[inline]
114    pub 
115    fn get_progname(&self) -> &str
116    {
117        return &self.logtag;
118    }
119
120    #[inline]
121    pub 
122    fn get_pid(&self) -> &str
123    {
124        return &self.logtag;
125    }
126
127    #[inline]
128    fn is_logmasked(&self, pri: Priority) -> bool
129    {
130        return ((1 << (pri & LogMask::LOG_PRIMASK)) & self.logmask) == 0;
131    }
132
133    pub(crate) 
134    fn set_logmask(&mut self, logmask: i32) -> i32
135    {
136        let oldmask = self.logmask;
137
138        if logmask != 0
139        {
140            self.logmask = logmask;
141        }
142
143        return oldmask;
144    }
145
146    pub(crate) 
147    fn vsyslog1_msg<F, D>(&self, pri: Priority, fmt: &F) ->  Option<(SyslogFormatted, LogStat)>
148    where F: SyslogFormatter, D: SyslogDestination
149    {
150        /*
151        // check for invalid bits
152        if let Err(e) = pri.check_invalid_bits()
153        {
154            self.logstat.send_to_stderr(&e.to_string());
155        }
156        */
157
158        // check priority against setlogmask
159        if self.is_logmasked(pri) == true
160        {
161            return None;
162        }
163
164        // set default facility if not specified in pri
165        let pri_fac = SyslogMsgPriFac::set_facility(pri, self.facility);
166        
167       /* if (pri.bits() & LOG_FACMASK) == 0
168        {
169            
170            pri.set_facility(self.facility);
171        }
172        */
173
174        // set PID if needed
175        let msg_pid = 
176            if self.logstat.intersects(LogStat::LOG_PID) == true
177            {
178                Some(self.logpid.as_str())
179            }
180            else
181            {
182                None
183            };
184
185        // format msg
186        let msg_formatted = 
187            fmt.vsyslog1_format(D::SocketTap::get_max_msg_size(), pri_fac, &self.logtag, msg_pid);
188        
189        // output to stderr if required
190        self.logstat.send_to_stderr(msg_formatted.get_stderr_output());
191
192        return Some((msg_formatted, self.logstat));
193    }
194}
195
196
197 #[derive(Debug)] 
198pub(crate) struct SyslogSocketLockless<D: SyslogDestination>
199{
200    stream: D::SocketTap
201}
202
203impl<D: SyslogDestination> SyslogSocketLockless<D>
204{
205    pub(crate) 
206    fn new(logstat: LogStat, net_tap_prov: D) -> SyRes<Self>
207    {
208        let mut sock = 
209            D::SocketTap::new(net_tap_prov)?;
210
211        if logstat.contains(LogStat::LOG_NDELAY) == true
212        {
213            sock.connectlog()?;
214        }
215
216        return Ok(
217            Self{ stream: sock }
218        );
219    }
220
221    #[inline]
222    pub(crate) 
223    fn update_tap_data(&mut self, tap_data: D) -> SyRes<()>
224    {
225        self.stream.disconnectlog()?;
226        self.stream.update_tap_data(tap_data);
227
228        return self.stream.connectlog();
229    }
230
231    #[inline]
232    pub(crate) 
233    fn reconnectlog(&mut self) -> SyRes<()>
234    {
235        self.stream.disconnectlog()?;
236        self.stream.connectlog()?;
237
238        return Ok(());
239    }
240
241    #[inline]
242    pub(crate) 
243    fn connectlog(&mut self) -> SyRes<()>
244    {
245        return self.stream.connectlog();
246    }
247
248        /// Disconnects the unix stream from syslog.
249    /// Do not call this function from internal.
250    #[inline]
251    pub(crate) 
252    fn disconnectlog(&mut self) -> SyRes<()>
253    {
254        return self.stream.disconnectlog();
255    }
256
257    
258
259    /// A `send` error handler which is specific for `unix` OSes.
260    #[cfg(target_family = "unix")]
261    fn handle_error(&mut self, e: Error) -> Result<(), SyslogError>
262    {
263        use std::io::ErrorKind;
264        use std::thread::sleep;
265        use std::time::Duration;
266
267        // The socket is not connected.
268        if e.kind() == ErrorKind::NotConnected 
269        {
270           self.stream.connectlog()?;
271
272            return Ok(());
273        }
274        else 
275        {
276            if D::DEST_TYPE.is_network() == false
277            {
278                if let Some(libc::ENOBUFS) = e.raw_os_error()
279                {
280                    // scenario 2, not enoughspace
281                    if self.stream.get_type().is_priv() == true
282                    {
283                        use nix::errno::Errno;
284
285                        use crate::throw_error_errno;
286
287                        throw_error_errno!(Errno::ENOBUFS, "Not enough space in priv socket.");
288                    }
289
290                    sleep(Duration::from_micros(1));
291
292                    return Ok(());
293                }
294                else
295                {
296                    // scenario 1
297                    self.stream.disconnectlog()?;
298                    self.stream.connectlog()?;
299
300                    return Ok(());
301                    // if resend will fail then probably the scn 2 will take place
302                }   
303            }
304            else
305            {
306                self.stream.disconnectlog()?;
307                self.stream.connectlog()?;
308
309                return Ok(());
310            }
311        }
312    }
313
314    /// A `send` error handler which is specific for `windows` OS.
315    #[cfg(target_family = "windows")]
316    fn handle_error(&mut self, e: Error) -> Result<(), SyslogError>
317    {
318        use crate::DestinationType;
319        use crate::error::SyslogErrCode;
320
321        if D::DEST_TYPE == DestinationType::Local
322        {
323            // unwrap sshould never panic because `from_raw_os_error` is used.
324            let Ok(werr) = e.downcast::<windows::core::Error>()
325                else
326                {
327                    
328
329                    return Err(
330                        SyslogError::new(SyslogErrCode::InternalError, 
331                            "error downcast failed".into())
332                    );
333                };
334
335            return Err(
336                SyslogError::new(
337                    SyslogErrCode::InternalError, 
338                    werr.message()
339                )
340            );
341        }
342        else if D::DEST_TYPE == DestinationType::Network
343        {
344            self.stream.disconnectlog()?;
345            self.stream.connectlog()?;
346
347            return Ok(());
348        }
349        else
350        {
351            return Err(
352                SyslogError::new(
353                    SyslogErrCode::InternalError, 
354                    format!("IO error: {}", e)
355                )
356            );
357        }
358
359    }
360
361
362    /// There are two possible scenarios when send may fail:
363    /// 1. syslog temporary unavailable
364    /// 2. syslog out of buffer space
365    /// If we are connected to priv socket then in case of 1 we reopen connection
366    ///      and retry once.
367    /// If we are connected to unpriv then in case of 2 repeatedly retrying to send
368    ///      until syslog socket buffer space will be cleared
369    /// What about the rest scenarios???
370    pub(crate) 
371    fn vsyslog1(&mut self, logstat: LogStat, mut msg_formatted: SyslogFormatted) -> SyRes<()>
372    {
373        let fullmsg = msg_formatted.get_full_msg();
374
375        let error = 
376            loop
377            {
378                match self.stream.send(fullmsg.as_bytes())
379                {
380                    Ok(_) => 
381                        return Ok(()),
382                    Err(e) =>
383                    {
384                        match self.handle_error(e)
385                        {
386                            Ok(_) => 
387                                continue,
388                            Err(e) =>
389                            {
390                                logstat.send_to_stderr(&format!("connectlog() failed with {}", e));
391                                break e;
392                            }
393                        }
394                    }
395                }
396            };
397        
398
399        // If program reached this point then transmission over socket failed.
400        // Try to output message to console
401
402        let _ = logstat.send_to_syscons(msg_formatted.get_stderr_output());
403
404        return Err(error);
405    }
406
407}
408
409
410
411#[cfg(test)]
412mod tests
413{
414    use crate::LOG_MASK;
415
416    use super::*;
417
418    #[test]
419    fn test_log_cons()
420    { 
421        //let net_tap = TapTypeData::new_unix(None, true).unwrap();
422        
423        let msg = "header msg message payload";
424
425        let lsts = LogStat::LOG_CONS;
426        
427        lsts.send_to_syscons(msg);
428
429        return;
430    }
431
432    #[test]
433    fn test_bit_operations()
434    {
435
436        let correct = 
437            LogItems::new(Some("test1"), 0xFF, LogStat::LOG_PID, LogFacility::LOG_DAEMON);
438
439        assert_eq!(correct.facility, LogFacility::LOG_DAEMON);
440        assert_eq!((correct.facility & !LogFacility::LOG_DAEMON), LogFacility::empty());
441    }
442
443    /*
444    #[test]
445    fn test_bit_operations2()
446    {
447
448        let mut pri = Priority::LOG_ALERT;
449
450        let res = pri.check_invalid_bits();
451
452        assert_eq!(res.is_ok(), true);
453        assert_eq!(pri.bits(), Priority::LOG_ALERT.bits());
454    }
455    */
456
457    #[test]
458    fn test_set_priority()
459    {
460        let mut correct = 
461            LogItems::new(Some("test1"), 0xFF, LogStat::LOG_PID, LogFacility::LOG_DAEMON);
462
463        let ret = correct.set_logmask(LOG_MASK!(Priority::LOG_ERR));
464
465        assert_eq!(ret, 0xff);
466
467        let ret = correct.set_logmask(LOG_MASK!(Priority::LOG_ERR));
468
469        assert_eq!(ret, LOG_MASK!(Priority::LOG_ERR));
470
471        let ret = correct.is_logmasked(Priority::LOG_WARNING);
472        assert_eq!(ret, true);
473
474        let ret = correct.is_logmasked(Priority::LOG_ERR);
475        assert_eq!(ret, false);
476
477        let ret = correct.is_logmasked(Priority::LOG_CRIT);
478        assert_eq!(ret, true);
479    }
480}
481