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