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::ErrorKind;
18use std::thread::sleep;
19use std::time::Duration;
20
21
22use instance_copy_on_write::ICoW;
23use nix::libc;
24
25
26use crate::error::SyslogErrCode;
27use crate::formatters::SyslogFormatted;
28use crate::formatters::SyslogFormatter;
29use crate::map_error_code;
30use crate::portable;
31use crate::common::*;
32use crate::error::{SyRes/* , SyslogError*/};
33use crate::SyslogDestination;
34
35use super::socket::*;
36
37
38#[derive(Debug, Clone)]
39pub(crate) struct LogItems
40{
41    /// An identification i.e program name, thread name
42    pub(crate) logtag: String, 
43
44    /// A pid of the program.
45    pub(crate) logpid: String,
46
47    /// A logmask
48    pub(crate) logmask: i32,
49
50    pub(crate) logstat: LogStat,
51
52    /// Holds the facility 
53    pub(crate) facility: LogFacility,
54}
55
56impl LogItems
57{
58    #[inline]
59    pub 
60    fn new(ident: Option<&str>, logmask: i32, logstat: LogStat, facility: LogFacility) -> Self
61    {
62        let mut log_inst = 
63            Self
64            {
65                logtag: String::new(),
66                logpid: portable::get_pid().to_string(),
67                logmask: logmask,
68                logstat: logstat,
69                facility: LogFacility::empty(),
70            };
71
72        log_inst.set_log_facility(facility);
73        log_inst.set_identity(ident);
74
75        return log_inst;
76    }
77
78    pub(crate) 
79    fn set_log_facility(&mut self, facility: LogFacility)
80    {
81        let log_facility =
82            if facility.is_empty() == false && 
83                (facility & !LogMask::LOG_FACMASK).is_empty() == true
84            {
85                facility
86            }
87            else
88            {
89                // default facility code
90                LogFacility::LOG_USER
91            };
92
93        self.facility = log_facility;
94
95        return;
96    }
97
98    pub(crate) 
99    fn set_identity(&mut self, ident: Option<&str>)
100    {
101        let logtag = 
102            match ident
103            {
104                Some(r) => 
105                    truncate_n(r, RFC_MAX_APP_NAME).to_string(),
106                None => 
107                    truncate_n(
108                        portable::p_getprogname()
109                            .unwrap_or("notavail".to_string())
110                            .as_str(),
111                        RFC_MAX_APP_NAME
112                    )
113                    .to_string()
114            };
115
116        self.logtag = logtag;
117
118        return;
119    }
120
121    #[inline]
122    pub 
123    fn get_progname(&self) -> &str
124    {
125        return &self.logtag;
126    }
127
128    #[inline]
129    pub 
130    fn get_pid(&self) -> &str
131    {
132        return &self.logtag;
133    }
134
135    #[inline]
136    fn is_logmasked(&self, pri: i32) -> bool
137    {
138        return ((1 << (pri & LogMask::LOG_PRIMASK)) & self.logmask) == 0;
139    }
140
141    pub(crate) 
142    fn set_logmask(&mut self, logmask: i32) -> i32
143    {
144        let oldmask = self.logmask;
145
146        if logmask != 0
147        {
148            self.logmask = logmask;
149        }
150
151        return oldmask;
152    }
153
154    pub(crate) 
155    fn vsyslog1_msg<F, D>(&self, mut pri: Priority, fmt: &F) ->  Option<(SyslogFormatted, LogStat)>
156    where F: SyslogFormatter, D: SyslogDestination
157    {
158        // check for invalid bits
159        if let Err(e) = pri.check_invalid_bits()
160        {
161            self.logstat.send_to_stderr(&e.to_string());
162        }
163
164        // check priority against setlogmask
165        if self.is_logmasked(pri.bits()) == true
166        {
167            return None;
168        }
169
170        // set default facility if not specified in pri
171        if (pri.bits() & LOG_FACMASK) == 0
172        {
173            pri.set_facility(self.facility);
174        }
175
176        // format msg
177        let msg_formatted = 
178            fmt.vsyslog1_format(D::SocketTap::get_max_msg_size(), pri, &self.logtag, &self.logpid);
179        
180        // output to stderr if required
181        self.logstat.send_to_stderr(msg_formatted.get_stderr_output());
182
183        return Some((msg_formatted, self.logstat));
184    }
185}
186
187#[derive(Debug)]
188pub(crate) struct SyslogSocket<D: SyslogDestination>
189{
190    stream: ICoW<D::SocketTap>
191}
192
193impl<D: SyslogDestination> SyslogSocket<D>
194{
195    pub(crate) 
196    fn new(logstat: LogStat, net_tap_prov: D) -> SyRes<Self>
197    {
198        let mut sock = 
199            D::SocketTap::new(net_tap_prov)?;
200
201        if logstat.contains(LogStat::LOG_NDELAY) == true
202        {
203            sock.connectlog()?;
204        }
205
206        return Ok(
207            Self{ stream: ICoW::new(sock) }
208        );
209    }
210
211    #[inline]
212    pub(crate) 
213    fn update_tap_data(&self, tap_data: D) -> SyRes<()>
214    {
215        
216        let mut lock = 
217            self
218                .stream
219                .try_clone_exclusivly()
220                .ok_or_else(||
221                    map_error_code!(CoWAlreadyLocked, "cannot exclusivly lock instance, already locked")
222                )?;
223
224        //lock.disconnectlog()?;
225
226        lock.update_tap_data(tap_data);
227
228        lock.connectlog()?;
229
230        lock.commit();
231        
232        return Ok(());
233    }
234
235    #[inline]
236    pub(crate) 
237    fn reconnectlog(&self) -> SyRes<()>
238    {
239        let mut lock = 
240            self
241                .stream
242                .try_clone_exclusivly()
243                .ok_or_else(||
244                    map_error_code!(CoWAlreadyLocked, "cannot exclusivly lock instance, already locked")
245                )?;
246
247        //lock.disconnectlog()?;
248        lock.connectlog()?;
249
250        lock.commit();
251
252        return Ok(());
253    }
254
255    #[inline]
256    pub(crate) 
257    fn connectlog(&self) -> SyRes<()>
258    {
259        let mut lock =  
260            self
261                .stream
262                .try_clone_exclusivly()
263                .ok_or_else(||
264                    map_error_code!(CoWAlreadyLocked, "cannot exclusivly lock instance, already locked")
265                )?;
266                /*.map_err(|e|
267                    map_error_code!(CoWAlreadyLocked, "cannot exclusivly lock instance, err: {}", e)
268                )?
269                .connectlog(); */
270
271        lock.connectlog()?;
272
273        lock.commit();
274
275        return Ok(());
276    }
277
278        /// Disconnects the unix stream from syslog.
279    /// Do not call this function from internal.
280    #[inline]
281    pub(crate) 
282    fn disconnectlog(&self) -> SyRes<()>
283    {
284        let lock =  
285            self
286                .stream
287                .try_clone_exclusivly()
288                .ok_or_else(||
289                    map_error_code!(CoWAlreadyLocked, "cannot exclusivly lock instance, already locked")
290                )?;
291
292                /*.map_err(|e|
293                    map_error_code!(CoWAlreadyLocked, "cannot exclusivly lock instance, err: {}", e)
294                )?
295                .disconnectlog();*/
296
297        lock.commit();
298
299        return Ok(());
300    }
301
302    /// There are two possible scenarios when send may fail:
303    /// 1. syslog temporary unavailable
304    /// 2. syslog out of buffer space
305    /// If we are connected to priv socket then in case of 1 we reopen connection
306    ///      and retry once.
307    /// If we are connected to unpriv then in case of 2 repeatedly retrying to send
308    ///      until syslog socket buffer space will be cleared
309    /// What about the rest scenarios???
310    pub(crate) 
311    fn vsyslog1(&self, logstat: LogStat, mut msg_formatted: SyslogFormatted)
312    {
313        let fullmsg = msg_formatted.get_full_msg();
314
315        let mut repeated = false;
316        loop
317        {
318            let (tap_type, res) = 
319                {
320                    let read0 = self.stream.read();
321
322                    (read0.get_type(), read0.send(fullmsg.as_bytes()))
323                };
324
325            match res
326            {
327                Ok(_) => 
328                    break,
329                Err(ref e) if e.kind() == ErrorKind::NotConnected => 
330                {
331                    if let Err(e) = self.connectlog()
332                    {
333                        if e.get_errcode() == SyslogErrCode::CoWAlreadyLocked
334                        {
335                            continue;
336                        }
337                        else
338                        {
339                            logstat.send_to_stderr(&format!("connectlog() failed with {}", e));
340                            break;
341                        }
342                    }
343                },
344                Err(err) =>
345                {
346                    if D::DEST_TYPE.is_network() == false
347                    {
348                        if let Some(libc::ENOBUFS) = err.raw_os_error()
349                        {
350                            // scenario 2, not enoughspace
351                            if tap_type.is_priv() == true
352                            {
353                                break;
354                            }
355
356                            sleep(Duration::from_micros(1));
357                        }
358                        else
359                        {
360                            // scenario 1
361                            if let Err(e) = self.reconnectlog()
362                            {
363                                if e.get_errcode() == SyslogErrCode::CoWAlreadyLocked
364                                {
365                                    continue;
366                                }
367                                else
368                                {
369                                    logstat.send_to_stderr(&format!("reconnectlog() failed with {}", e));
370                                    break;
371                                }
372                            }
373        
374                            // if resend will fail then probably the scn 2 will take place
375                        }   
376                    }
377                    else
378                    {
379                        if let Err(e) = self.connectlog()
380                        {
381                            if e.get_errcode() == SyslogErrCode::CoWAlreadyLocked
382                            {
383                                continue;
384                            }
385                            else
386                            {
387                                logstat.send_to_stderr(&format!("connectlog() failed with {}", e));
388                                break;
389                            }
390                        }
391                    }
392                    
393                }
394            }
395        }
396        
397
398        // If program reached this point then transmission over socket failed.
399        // Try to output message to console
400
401        let _ = logstat.send_to_syscons(msg_formatted.get_stderr_output());
402
403        return;
404    }
405
406}
407
408#[cfg(any(feature = "build_with_thread_local", feature = "build_with_queue"))]
409pub mod lockless
410{
411    use super::*;
412    
413    #[derive(Debug)] 
414    pub(crate) struct SyslogSocketLockless<D: SyslogDestination>
415    {
416        stream: D::SocketTap
417    }
418
419    impl<D: SyslogDestination> SyslogSocketLockless<D>
420    {
421        pub(crate) 
422        fn new(logstat: LogStat, net_tap_prov: D) -> SyRes<Self>
423        {
424            let mut sock = 
425                D::SocketTap::new(net_tap_prov)?;
426
427            if logstat.contains(LogStat::LOG_NDELAY) == true
428            {
429                sock.connectlog()?;
430            }
431
432            return Ok(
433                Self{ stream: sock }
434            );
435        }
436
437        #[inline]
438        pub(crate) 
439        fn update_tap_data(&mut self, tap_data: D) -> SyRes<()>
440        {
441            self.stream.disconnectlog()?;
442            self.stream.update_tap_data(tap_data);
443
444            return self.stream.connectlog();
445        }
446
447        #[inline]
448        pub(crate) 
449        fn reconnectlog(&mut self) -> SyRes<()>
450        {
451            self.stream.disconnectlog()?;
452            self.stream.connectlog()?;
453
454            return Ok(());
455        }
456
457        #[inline]
458        pub(crate) 
459        fn connectlog(&mut self) -> SyRes<()>
460        {
461            return self.stream.connectlog();
462        }
463
464            /// Disconnects the unix stream from syslog.
465        /// Do not call this function from internal.
466        #[inline]
467        pub(crate) 
468        fn disconnectlog(&mut self) -> SyRes<()>
469        {
470            return self.stream.disconnectlog();
471        }
472
473        /// There are two possible scenarios when send may fail:
474        /// 1. syslog temporary unavailable
475        /// 2. syslog out of buffer space
476        /// If we are connected to priv socket then in case of 1 we reopen connection
477        ///      and retry once.
478        /// If we are connected to unpriv then in case of 2 repeatedly retrying to send
479        ///      until syslog socket buffer space will be cleared
480        /// What about the rest scenarios??? 
481        pub(crate) 
482        fn vsyslog1(&mut self, logstat: LogStat, mut msg_formatted: SyslogFormatted)
483        {
484            if self.stream.is_connected() == false
485            {
486                if let Err(e) = self.stream.connectlog()
487                {
488                    logstat.send_to_stderr(&e.into_inner());
489                }
490            }
491
492            let fullmsg = msg_formatted.get_full_msg();
493
494            let mut repeated = false;
495            loop
496            {
497                match self.stream.send(fullmsg.as_bytes())
498                {
499                    Ok(_) => 
500                        break,
501                    Err(ref e) if e.kind() == ErrorKind::NotConnected => 
502                    {
503                        break;
504                    },
505                    // poisoned mutex
506                    Err(ref e) if e.kind() == ErrorKind::Deadlock =>
507                    {
508                        logstat.send_to_stderr(&e.to_string());
509                        return;
510                    },
511                    Err(err) =>
512                    {
513                        if D::DEST_TYPE.is_network() == false
514                        {
515                            if let Some(libc::ENOBUFS) = err.raw_os_error()
516                            {
517                                // scenario 2, not enoughspace
518                                if self.stream.get_type().is_priv() == true
519                                {
520                                    break;
521                                }
522
523                                sleep(Duration::from_micros(1));
524                            }
525                            else
526                            {
527                                // scenario 1
528                                let _ = self.stream.disconnectlog();
529                                if let Err(e) = self.stream.connectlog()
530                                {
531                                    logstat.send_to_stderr(&e.into_inner());
532                                    break;
533                                }
534                            }
535            
536                                // if resend will fail then probably the scn 2 will take place  
537                        }
538                        else
539                        {
540                            if repeated == false
541                            {
542                                repeated = true;
543
544                                let _ = self.stream.disconnectlog();
545                                if let Err(e) = self.stream.connectlog()
546                                {
547                                    logstat.send_to_stderr(&e.into_inner());
548                                    break;
549                                }
550                            }
551                            else
552                            {
553                                logstat.send_to_stderr("syslog: can not send to remote server");
554                                break;
555                            }
556                        }
557                        
558                    }
559                }
560            }
561            
562
563            // If program reached this point then transmission over socket failed.
564            // Try to output message to console
565
566            let _ = logstat.send_to_syscons(msg_formatted.get_stderr_output());
567
568            return;
569        }
570
571    }
572}
573
574#[cfg(any(feature = "build_with_thread_local", feature = "build_with_queue"))]
575pub use self::lockless::*;
576
577
578
579#[cfg(test)]
580mod tests
581{
582    use crate::LOG_MASK;
583
584    use super::*;
585
586    #[test]
587    fn test_log_cons()
588    {
589
590        
591        //let net_tap = TapTypeData::new_unix(None, true).unwrap();
592        
593        let msg = "header msg message payload";
594
595        let lsts = LogStat::LOG_CONS;
596        
597        lsts.send_to_syscons(msg);
598
599        return;
600    }
601
602    #[test]
603    fn test_bit_operations()
604    {
605
606        let correct = 
607            LogItems::new(Some("test1"), 0xFF, LogStat::LOG_PID, LogFacility::LOG_DAEMON);
608
609        assert_eq!(correct.facility, LogFacility::LOG_DAEMON);
610        assert_eq!((correct.facility & !LogFacility::LOG_DAEMON), LogFacility::empty());
611    }
612
613    #[test]
614    fn test_bit_operations2()
615    {
616
617        let mut pri = Priority::LOG_ALERT;
618
619        let res = pri.check_invalid_bits();
620
621        assert_eq!(res.is_ok(), true);
622        assert_eq!(pri.bits(), Priority::LOG_ALERT.bits());
623    }
624
625    #[test]
626    fn test_set_priority()
627    {
628        let mut correct = 
629            LogItems::new(Some("test1"), 0xFF, LogStat::LOG_PID, LogFacility::LOG_DAEMON);
630
631        let ret = correct.set_logmask(LOG_MASK!(Priority::LOG_ERR));
632
633        assert_eq!(ret, 0xff);
634
635        let ret = correct.set_logmask(LOG_MASK!(Priority::LOG_ERR));
636
637        assert_eq!(ret, LOG_MASK!(Priority::LOG_ERR));
638
639        let ret = correct.is_logmasked(Priority::LOG_WARNING.bits());
640        assert_eq!(ret, true);
641
642        let ret = correct.is_logmasked(Priority::LOG_ERR.bits());
643        assert_eq!(ret, false);
644
645        let ret = correct.is_logmasked(Priority::LOG_CRIT.bits());
646        assert_eq!(ret, true);
647    }
648}
649