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