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