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. EUROPEAN UNION PUBLIC LICENCE v. 1.2 EUPL © the European Union 2007, 2016
12 */
13
14
15use std::borrow::Cow;
16use std::marker::PhantomData;
17use std::thread::sleep;
18use std::time::Duration;
19
20use nix::libc;
21
22
23use crate::formatters::SyslogFormatter;
24use crate::map_error_os;
25use crate::portable;
26use crate::common::*;
27use crate::error::{SyRes/* , SyslogError*/};
28use crate::socket::TapType;
29use crate::SyslogDestination;
30
31use super::socket::*;
32
33/// A common instance which describes the syslog state
34#[derive(Debug)]
35pub struct SyncSyslogInternal<F: SyslogFormatter, D: SyslogDestination>
36{   
37    /// An identification i.e program name, thread name
38    logtag: String, 
39
40    /// A pid of the program.
41    logpid: String,
42
43    /// Defines how syslog operates
44    logstat: LogStat,
45
46    /// Holds the facility 
47    facility: LogFacility,
48
49    /// A logmask
50    logmask: i32,
51
52    /// A stream (unixdatagram, udp, tcp)
53    stream: D::SocketTap,
54
55    _p: PhantomData<F>,
56}
57
58
59impl<F: SyslogFormatter, D: SyslogDestination> Drop for SyncSyslogInternal<F, D> 
60{
61    fn drop(&mut self) 
62    {
63        // call disconnect log to sync all data
64        let _ = self.disconnectlog();
65    }
66}
67
68/// Private realization. It is assumed that functions which are called,
69/// are called after 'lock' is locked.
70impl<F: SyslogFormatter, D: SyslogDestination> SyncSyslogInternal<F, D>
71{
72    /// Creates new instance of [SyncSyslogInternal] which contains all 
73    /// client syslog logic.
74    ///
75    /// # Arguments
76    ///
77    /// * `ident` - An optional argument which takes ref to str. If none, the
78    ///     ident will be set later. Yje ident will be trucated to 48 UTF8
79    ///     chars.
80    /// 
81    /// * `logstat` - A [LogStat] flags separated by '|'
82    /// 
83    /// * `facility` - A [LogFacility] flag
84    /// 
85    /// * `req_tap` - A type of the syslog instance reuired. See [TapTypeData].
86    /// 
87    /// # Returns
88    ///
89    /// A [SyRes] is returned with following:
90    /// 
91    /// * [Result::Ok] - with the instance.
92    /// 
93    /// * [Result::Err] - with error description.
94    pub(crate) 
95    fn new(
96        ident: Option<&str>, 
97        logstat: LogStat, 
98        facility: LogFacility, 
99        req_tap: D
100    ) -> SyRes<Self>
101    {
102        // check if log_facility is invalid
103        let log_facility =
104            if facility.is_empty() == false && 
105                (facility & !LogMask::LOG_FACMASK).is_empty() == true
106            {
107                facility
108            }
109            else
110            {
111                // default facility code
112                LogFacility::LOG_USER
113            };
114
115        let logtag = 
116            match ident
117            {
118                Some(r) => 
119                    truncate_n(r, RFC_MAX_APP_NAME).to_string(),
120                None => 
121                    truncate_n(
122                        portable::p_getprogname()
123                            .unwrap_or("".to_string())
124                            .as_str(),
125                        RFC_MAX_APP_NAME
126                    )
127                    .to_string()
128            };
129
130        let sock = D::SocketTap::new(req_tap)?;
131            
132        return Ok(
133            Self
134            {
135                logtag: logtag,
136                logpid: portable::get_pid().to_string(),
137                logstat: logstat, 
138                facility: log_facility,
139                logmask: 0xff,
140                stream: sock,
141                _p: PhantomData
142            }
143        );
144    }
145
146    /// Tap data should be the same type.
147    pub(crate) 
148    fn update_tap_data(&mut self, tap_data: D) -> SyRes<()>
149    {
150        let is_con = self.stream.is_connected();
151
152        if is_con == true
153        {
154            self
155                .stream
156                .disconnectlog()
157                .map_err(|e|
158                    map_error_os!(e, "update_tap_data() can not disconnect log properly")
159                )?;
160        }
161
162        self.stream.update_tap_data(tap_data);
163
164        if is_con == true
165        {
166            // replace with new instance
167            self.stream.connectlog()?;
168        }
169
170        return Ok(());
171    }
172
173    #[inline]
174    pub(super)
175    fn send_to_stderr(&self, msg: &[Cow<'_, str>])
176    {
177        if self.logstat.intersects(LogStat::LOG_PERROR) == true
178        {
179            let stderr_lock = std::io::stderr().lock();
180            let newline = "\n";
181
182            let _ = send_to_fd(stderr_lock, msg, &newline);
183        }
184    }
185
186    #[inline]
187    fn send_to_syscons(&self, msg_payload: &[Cow<'_, str>])
188    {
189        use std::fs::File;
190        use std::os::unix::fs::OpenOptionsExt;
191
192        if self.logstat.intersects(LogStat::LOG_CONS)
193        {
194            let syscons = 
195                File
196                    ::options()
197                        .create(false)
198                        .read(false)
199                        .write(true)
200                        .custom_flags(libc::O_NONBLOCK | libc::O_CLOEXEC)
201                        .open(*PATH_CONSOLE);
202
203            if let Ok(file) = syscons
204            {
205                let newline = "\n";
206                let _ = send_to_fd(file, msg_payload, newline);
207            }
208        }
209    }
210
211    #[inline]
212    fn is_logmasked(&self, pri: i32) -> bool
213    {
214        return ((1 << (pri & LogMask::LOG_PRIMASK)) & self.logmask) == 0;
215    }
216
217    /// Returns the type of the socket.
218    #[inline]
219    pub(crate)
220    fn get_taptype(&self) -> TapType
221    {
222        return self.stream.get_type();
223    }
224
225    /// Returns the maximum msg size in bytes. (Full msg with headers.)
226    #[inline]
227    pub(crate) 
228    fn get_max_msg_size(&self) -> usize
229    {
230        return self.stream.get_max_msg_size();
231    }
232
233    pub(crate) 
234    fn set_logmask(&mut self, logmask: i32) -> i32
235    {
236        let oldmask = self.logmask;
237
238        if logmask != 0
239        {
240            self.logmask = logmask;
241        }
242
243        return oldmask;
244    }
245
246    #[inline]
247    pub(crate) 
248    fn set_logtag<L: AsRef<str>>(&mut self, logtag: L, update_pid: bool)
249    {
250        self.logtag = 
251            truncate_n(logtag.as_ref(), RFC_MAX_APP_NAME).to_string();
252
253        if update_pid == true
254        {
255            self.logpid = portable::get_pid().to_string();
256        }
257
258        return;
259    }
260
261    /// Disconnects the unix stream from syslog.
262    #[inline]
263    pub(crate) 
264    fn disconnectlog(&mut self) -> SyRes<()>
265    {
266        return 
267            self
268                .stream
269                .disconnectlog()
270                .map_err(|e| map_error_os!(e, "can not disconnect log properly"));
271    }
272
273    /// Connects unix stream to the syslog and sets up the properties of
274    /// the unix stream.
275    #[inline]
276    pub(crate) 
277    fn connectlog(&mut self) -> SyRes<()>
278    {
279        return self.stream.connectlog();
280    }
281
282    
283
284    /// An internal function which is called by the syslog or vsyslog.
285    /// A glibc implementation RFC3164
286    pub(crate) 
287    fn vsyslog1(&mut self, mut pri: Priority, fmt: &F)
288    {
289        // check for invalid bits
290        if let Err(e) = pri.check_invalid_bits()
291        {
292            self.send_to_stderr(&[Cow::Owned(e.to_string())]);
293        }
294
295        // check priority against setlogmask
296        if self.is_logmasked(pri.bits()) == true
297        {
298            return;
299        }
300
301        // set default facility if not specified in pri
302        if (pri.bits() & LOG_FACMASK) == 0
303        {
304            pri.set_facility(self.facility);
305        }
306
307        let progname = self.logtag.clone();
308        let pid = self.logpid.clone();
309
310        // format msg
311        let msg_formatted = 
312            F::vsyslog1_format(self.get_taptype(), self.get_max_msg_size(), pri, &progname, &pid, fmt);
313        
314        // output to stderr if required
315        self.send_to_stderr(msg_formatted.get_stderr_output());
316        
317        if self.stream.is_connected() == false
318        {
319            // open connection
320            match self.connectlog()
321            {
322                Ok(_) => {},
323                Err(e) =>
324                {
325                    self.send_to_stderr(&[Cow::Owned(e.into_inner())]);
326                    return;
327                }
328            }
329        }
330
331        let fullmsg = msg_formatted.concat();
332
333        // There are two possible scenarios when send may fail:
334        // 1. syslog temporary unavailable
335        // 2. syslog out of buffer space
336        // If we are connected to priv socket then in case of 1 we reopen connection
337        //      and retry once.
338        // If we are connected to unpriv then in case of 2 repeatedly retrying to send
339        //      until syslog socket buffer space will be cleared
340
341        loop
342        {
343            match self.stream.send(fullmsg.as_bytes())
344            {
345                Ok(_) => 
346                    return,
347                Err(err) =>
348                {  
349                    if self.get_taptype().is_network() == false
350                    { 
351                        if let Some(libc::ENOBUFS) = err.raw_os_error()
352                        {
353                            // scenario 2
354                            if self.get_taptype().is_priv() == true
355                            {
356                                break;
357                            }
358
359                            sleep(Duration::from_micros(1));
360                        }
361                        else
362                        {
363                            // scenario 1
364
365                            let _ = self.disconnectlog();
366                            match self.connectlog()
367                            {
368                                Ok(_) => {},
369                                Err(_e) => break,
370                            }
371
372                            // if resend will fail then probably the scn 2 will take place
373                        }   
374                    }
375                    else
376                    {
377                        let _ = self.disconnectlog();
378                        match self.connectlog()
379                        {
380                            Ok(_) => {},
381                            Err(e) => 
382                            {
383                                self.send_to_stderr(&[Cow::Owned(e.to_string())]);
384
385                                break;
386                            },
387                        }
388                    }
389                }
390            }
391        } // loop
392
393
394        // If program reached this point then transmission over socket failed.
395        // Try to output message to console
396
397        let _ = self.send_to_syscons(msg_formatted.get_stderr_output());
398
399        return;
400    }
401}
402
403
404#[cfg(test)]
405mod tests
406{
407    use super::*;
408
409    #[cfg(target_os = "linux")]
410    #[test]
411    fn test_log_cons()
412    {
413        use crate::{formatters::FormatRfc3146, SyslogLocal};
414
415        let net_tap = SyslogLocal::new();
416        
417        //let net_tap = TapTypeData::new_unix(None, true).unwrap();
418
419        // needs root priv
420        let correct = 
421            SyncSyslogInternal::<FormatRfc3146, _>::new(Some("test1"), LogStat::LOG_PID | LogStat::LOG_CONS, LogFacility::LOG_DAEMON, net_tap)
422                .unwrap();
423        
424        let msg = &[Cow::Borrowed("header msg message payload")];
425
426        
427        correct.send_to_syscons(msg);
428
429        return;
430    }
431
432    #[cfg(any(
433        target_os = "freebsd",
434        target_os = "dragonfly",
435        target_os = "openbsd",
436        target_os = "netbsd",
437        target_os = "macos"
438    ))]
439    #[test]
440    fn test_log_cons()
441    {
442        use crate::{formatters::FormatRfc5424, SyslogLocal};
443
444        let net_tap = SyslogLocal::new();
445        //let net_tap = TapTypeData::new_unix(None, true).unwrap();
446
447        // needs root priv
448        let correct = 
449            SyncSyslogInternal::<FormatRfc5424, _>::new(Some("test1"), LogStat::LOG_PID | LogStat::LOG_CONS, LogFacility::LOG_DAEMON, net_tap)
450                .unwrap();
451        
452        correct.send_to_syscons(&[Cow::Borrowed("header msg"), Cow::Borrowed("message payload")]);
453    }
454
455    #[cfg(target_os = "linux")]
456    #[test]
457    fn test_bit_operations()
458    {
459        use crate::{formatters::FormatRfc3146, SyslogLocal};
460
461        let net_tap = SyslogLocal::new();
462        //let net_tap = TapTypeData::new_unix(None, true).unwrap();
463
464        let correct = 
465            SyncSyslogInternal::<FormatRfc3146, _>::new(Some("test1"), LogStat::LOG_PID, LogFacility::LOG_DAEMON, net_tap)
466                .unwrap();
467
468        assert_eq!(correct.facility, LogFacility::LOG_DAEMON);
469        assert_eq!((correct.facility & !LogFacility::LOG_DAEMON), LogFacility::empty());
470    }
471
472	#[cfg(any(
473        target_os = "freebsd",
474        target_os = "dragonfly",
475        target_os = "openbsd",
476        target_os = "netbsd",
477        target_os = "macos"
478    ))]
479    #[test]
480    fn test_bit_operations()
481    {
482        use crate::{formatters::FormatRfc5424, SyslogLocal};
483
484        let net_tap = SyslogLocal::new();
485        //let net_tap = TapTypeData::new_unix(None, true).unwrap();
486
487        let correct = 
488            SyncSyslogInternal::<FormatRfc5424, _>::new(Some("test1"), LogStat::LOG_PID, LogFacility::LOG_DAEMON, net_tap)
489                .unwrap();
490
491        assert_eq!(correct.facility, LogFacility::LOG_DAEMON);
492        assert_eq!((correct.facility & !LogFacility::LOG_DAEMON), LogFacility::empty());
493    }
494
495    #[cfg(target_os = "linux")]
496    #[test]
497    fn test_bit_operations2()
498    {
499        use crate::{formatters::FormatRfc3146, SyslogLocal};
500
501        let net_tap = SyslogLocal::new();
502        //let net_tap = TapTypeData::new_unix(None, true).unwrap();
503
504        let _correct = 
505            SyncSyslogInternal::<FormatRfc3146, _>::new(Some("test2"), LogStat::LOG_PID, LogFacility::LOG_DAEMON, net_tap)
506                .unwrap();
507
508        let mut pri = Priority::LOG_ALERT;
509
510        let res = pri.check_invalid_bits();
511
512        assert_eq!(res.is_ok(), true);
513        assert_eq!(pri.bits(), Priority::LOG_ALERT.bits());
514    }
515
516	#[cfg(any(
517        target_os = "freebsd",
518        target_os = "dragonfly",
519        target_os = "openbsd",
520        target_os = "netbsd",
521        target_os = "macos"
522    ))]
523	#[test]
524    fn test_bit_operations2()
525    {
526        use crate::{formatters::FormatRfc5424, SyslogLocal};
527
528        let net_tap = SyslogLocal::new();
529        //let net_tap = TapTypeData::new_unix(None, true).unwrap();
530
531        let _correct = 
532            SyncSyslogInternal::<FormatRfc5424, _>::new(Some("test2"), LogStat::LOG_PID, LogFacility::LOG_DAEMON, net_tap)
533                .unwrap();
534
535        let mut pri = Priority::LOG_ALERT;
536
537        let res = pri.check_invalid_bits();
538
539        assert_eq!(res.is_ok(), true);
540        assert_eq!(pri.bits(), Priority::LOG_ALERT.bits());
541    }
542
543    #[cfg(target_os = "linux")]
544    #[test]
545    fn test_set_priority()
546    {
547        use crate::{ formatters::FormatRfc3146, SyslogLocal, LOG_MASK};
548
549        let net_tap = SyslogLocal::new();
550        //let net_tap = TapTypeData::new_unix(None, true).unwrap();
551
552        let mut correct = 
553            SyncSyslogInternal::<FormatRfc3146, _>::new(Some("test1"), LogStat::LOG_PID, LogFacility::LOG_DAEMON, net_tap)
554                .unwrap();
555
556        let ret = correct.set_logmask(LOG_MASK!(Priority::LOG_ERR));
557
558        assert_eq!(ret, 0xff);
559
560        let ret = correct.set_logmask(LOG_MASK!(Priority::LOG_ERR));
561
562        assert_eq!(ret, LOG_MASK!(Priority::LOG_ERR));
563
564        let ret = correct.is_logmasked(Priority::LOG_WARNING.bits());
565        assert_eq!(ret, true);
566
567        let ret = correct.is_logmasked(Priority::LOG_ERR.bits());
568        assert_eq!(ret, false);
569
570        let ret = correct.is_logmasked(Priority::LOG_CRIT.bits());
571        assert_eq!(ret, true);
572    }
573
574	#[cfg(any(
575		    target_os = "freebsd",
576		    target_os = "dragonfly",
577		    target_os = "openbsd",
578		    target_os = "netbsd",
579		    target_os = "macos"
580		))]
581	#[test]
582    fn test_set_priority()
583    {
584        use crate::{ formatters::FormatRfc5424, SyslogLocal, LOG_MASK};
585
586        let net_tap = SyslogLocal::new();
587        //let net_tap = TapTypeData::new_unix(None, true).unwrap();
588
589        let mut correct = 
590            SyncSyslogInternal::<FormatRfc5424, _>::new(Some("test1"), LogStat::LOG_PID, LogFacility::LOG_DAEMON, net_tap)
591                .unwrap();
592
593        let ret = correct.set_logmask(LOG_MASK!(Priority::LOG_ERR));
594
595        assert_eq!(ret, 0xff);
596
597        let ret = correct.set_logmask(LOG_MASK!(Priority::LOG_ERR));
598
599        assert_eq!(ret, LOG_MASK!(Priority::LOG_ERR));
600
601        let ret = correct.is_logmasked(Priority::LOG_WARNING.bits());
602        assert_eq!(ret, true);
603
604        let ret = correct.is_logmasked(Priority::LOG_ERR.bits());
605        assert_eq!(ret, false);
606
607        let ret = correct.is_logmasked(Priority::LOG_CRIT.bits());
608        assert_eq!(ret, true);
609    }
610
611    #[cfg(target_os = "linux")]
612    #[test]
613    fn test_set_priority2()
614    {
615        use crate::{ formatters::FormatRfc3146, SyslogLocal, LOG_MASK};
616        
617        let net_tap = SyslogLocal::new();
618        //let net_tap = TapTypeData::new_unix(None, true).unwrap();
619
620        let mut correct = 
621            SyncSyslogInternal::<FormatRfc3146, _>::new(Some("test1"), LogStat::LOG_PID, LogFacility::LOG_DAEMON, net_tap)
622                .unwrap();
623
624        let ret = correct.set_logmask(!LOG_MASK!(Priority::LOG_ERR));
625
626        assert_eq!(ret, 0xff);
627
628        let ret = correct.is_logmasked(Priority::LOG_WARNING.bits());
629        assert_eq!(ret, false);
630
631        let ret = correct.is_logmasked(Priority::LOG_ERR.bits());
632        assert_eq!(ret, true);
633
634        let ret = correct.is_logmasked(Priority::LOG_CRIT.bits());
635        assert_eq!(ret, false);
636    }
637
638	#[cfg(any(
639		target_os = "freebsd",
640		target_os = "dragonfly",
641		target_os = "openbsd",
642		target_os = "netbsd",
643		target_os = "macos"
644	))]
645	#[test]
646    fn test_set_priority2()
647    {
648        use crate::{ formatters::FormatRfc5424, SyslogLocal, LOG_MASK};
649        
650        let net_tap = SyslogLocal::new();
651        //let net_tap = TapTypeData::new_unix(None, true).unwrap();
652
653        let mut correct = 
654            SyncSyslogInternal::<FormatRfc5424, _>::new(Some("test1"), LogStat::LOG_PID, LogFacility::LOG_DAEMON, net_tap)
655                .unwrap();
656
657        let ret = correct.set_logmask(!LOG_MASK!(Priority::LOG_ERR));
658
659        assert_eq!(ret, 0xff);
660
661        let ret = correct.is_logmasked(Priority::LOG_WARNING.bits());
662        assert_eq!(ret, false);
663
664        let ret = correct.is_logmasked(Priority::LOG_ERR.bits());
665        assert_eq!(ret, true);
666
667        let ret = correct.is_logmasked(Priority::LOG_CRIT.bits());
668        assert_eq!(ret, false);
669    }
670}