syslog_rs/
common.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::fmt;
17use std::ops::{BitAnd, Shl};
18use std::path::Path;
19use std::sync::LazyLock;
20
21use nix::libc;
22
23
24use crate::portable;
25
26use super::error::SyRes;
27use super::throw_error;
28
29bitflags! {
30    /// Controls  the  operation  of openlog() and subsequent calls to syslog.
31    pub struct LogStat: libc::c_int 
32    {
33        /// Log the process ID with each message. (!todo)
34        const LOG_PID = libc::LOG_PID;
35        
36        /// Write directly to the system console if there is an error 
37        /// while sending to the system logger.
38        const LOG_CONS = libc::LOG_CONS;
39
40        /// The converse of LOG_NDELAY; opening of the connection is delayed 
41        /// until syslog() is called. (This is the default behaviour,and need 
42        /// not be specified.)
43        const LOG_ODELAY = libc::LOG_ODELAY;
44
45        /// Open the connection immediately
46        const LOG_NDELAY = libc::LOG_NDELAY;
47
48        /// Don't wait for child processes that may have been created 
49        /// while logging the message
50        const LOG_NOWAIT = libc::LOG_NOWAIT;
51        
52        /// Also log the message to stderr
53        const LOG_PERROR = 0x20;
54    }
55}
56
57#[cfg(feature = "build_sync")]
58impl LogStat
59{
60    #[inline]
61    pub(super)
62    fn send_to_stderr(&self, msg: &[Cow<'_, str>])
63    {
64        if self.intersects(LogStat::LOG_PERROR) == true
65        {
66            let stderr_lock = std::io::stderr().lock();
67            let newline = "\n";
68
69            let _ = send_to_fd(stderr_lock, msg, &newline);
70        }
71    }
72
73    #[inline]
74    pub(super)
75    fn send_to_syscons(&self, msg_payload: &[Cow<'_, str>])
76    {
77        use std::fs::File;
78        use std::os::unix::fs::OpenOptionsExt;
79
80        if self.intersects(LogStat::LOG_CONS)
81        {
82            let syscons = 
83                File
84                    ::options()
85                        .create(false)
86                        .read(false)
87                        .write(true)
88                        .custom_flags(libc::O_NONBLOCK | libc::O_CLOEXEC)
89                        .open(*PATH_CONSOLE);
90
91            if let Ok(file) = syscons
92            {
93                let newline = "\n";
94                let _ = send_to_fd(file, msg_payload, newline);
95            }
96        }
97    }
98}
99
100bitflags! {
101    pub(crate) struct LogMask: libc::c_int 
102    {
103        const LOG_FACMASK = libc::LOG_FACMASK;
104        const LOG_PRIMASK = libc::LOG_PRIMASK;
105    }
106}
107
108bitflags! {
109    /// This determines the importance of the message
110    pub struct Priority: libc::c_int 
111    {
112        /// system is unusable
113        const LOG_EMERG = libc::LOG_EMERG;
114
115        /// action must be taken immediately
116        const LOG_ALERT = libc::LOG_ALERT;
117
118        /// critical conditions
119        const LOG_CRIT = libc::LOG_CRIT;
120
121        /// error conditions
122        const LOG_ERR = libc::LOG_ERR;
123
124        /// warning conditions
125        const LOG_WARNING = libc::LOG_WARNING;
126
127        /// normal, but significant, condition
128        const LOG_NOTICE = libc::LOG_NOTICE;
129        
130        /// informational message
131        const LOG_INFO = libc::LOG_INFO;
132
133        /// debug-level message
134        const LOG_DEBUG = libc::LOG_DEBUG;
135    }
136}
137
138impl fmt::Display for Priority
139{
140    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result 
141    {
142        //let pri = self.bits & LogMask::LOG_PRIMASK;
143
144        if self.contains(Self::LOG_DEBUG) == true
145        {
146            write!(f, "[DEBUG]")
147        }
148        else if self.contains(Self::LOG_INFO) == true
149        {
150            write!(f, "[INFO]")
151        }
152        else if self.contains(Self::LOG_NOTICE) == true
153        {
154            write!(f, "[NOTICE]")
155        }
156        else if self.contains(Self::LOG_WARNING) == true
157        {
158            write!(f, "[WARNING]")
159        }
160        else if self.contains(Self::LOG_ERR) == true
161        {
162            write!(f, "[ERR]")
163        }
164        else if self.contains(Self::LOG_CRIT) == true
165        {
166            write!(f, "[CRIT]")
167        }
168        else if self.contains(Self::LOG_ALERT) == true
169        {
170            write!(f, "[ALERT]")
171        }
172        else if self.contains(Self::LOG_EMERG) == true
173        {
174            write!(f, "[EMERG]")
175        }
176        else
177        {
178            write!(f, "[UNKNOWN]")
179        }
180    }
181}
182
183impl Priority
184{
185    /// This function validates the `pri` for the incorrects bits set.
186    /// If bits are set incorrectly, resets the invalid bits with:
187    /// *pri & (LogMask::LOG_PRIMASK | LogMask::LOG_FACMASK).
188    ///
189    /// # Arguments
190    ///
191    /// * `pri` - a priority bits
192    ///
193    /// # Returns
194    /// 
195    /// * A [SyRes]. Ok() when valid or Err with error message
196    pub(crate) 
197    fn check_invalid_bits(&mut self) -> SyRes<()>
198    {
199    
200        if (self.bits() & !(LogMask::LOG_PRIMASK | LogMask::LOG_FACMASK )) != 0
201        {
202            let pri_old = self.clone();
203            
204            *self = unsafe { Self::from_bits_unchecked( self.bits() & (LogMask::LOG_PRIMASK | LogMask::LOG_FACMASK).bits() ) };
205
206            throw_error!("unknwon facility/priority: {:x}", pri_old);
207        }
208
209        return Ok(());
210    }
211
212    pub(crate) 
213    fn set_facility(&mut self, f: LogFacility)
214    {
215        *self = unsafe { Self::from_bits_unchecked(self.bits | f.bits() )};
216    }
217}
218
219bitflags! {
220    /// The facility argument is used to specify what type of program 
221    /// is logging the message.
222    pub struct LogFacility: libc::c_int 
223    {
224        /// kernel messages (these can't be generated from user processes)
225        const LOG_KERN = libc::LOG_KERN;
226
227        /// (default) generic user-level messages
228        const LOG_USER = libc::LOG_USER;
229
230        /// mail subsystem
231        const LOG_MAIL = libc::LOG_MAIL;
232
233        /// system daemons without separate facility value
234        const LOG_DAEMON = libc::LOG_DAEMON;
235        
236        /// security/authorization messages
237        const LOG_AUTH = libc::LOG_AUTH;
238
239        /// messages generated internally by syslogd(8)
240        const LOG_SYSLOG = libc::LOG_SYSLOG;
241
242        /// line printer subsystem
243        const LOG_LPR = libc::LOG_LPR;
244
245        /// USENET news subsystem
246        const LOG_NEWS = libc::LOG_NEWS;
247
248        /// UUCP subsystem
249        const LOG_UUCP = libc::LOG_UUCP;
250
251        /// reserved for local use
252        const LOG_LOCAL0 = libc::LOG_LOCAL0;
253
254        /// reserved for local use
255        const LOG_LOCAL1 = libc::LOG_LOCAL1;
256
257        /// reserved for local use
258        const LOG_LOCAL2 = libc::LOG_LOCAL2;
259        
260        /// reserved for local use
261        const LOG_LOCAL3 = libc::LOG_LOCAL3;
262
263        /// reserved for local use
264        const LOG_LOCAL4 = libc::LOG_LOCAL4;
265
266        /// reserved for local use
267        const LOG_LOCAL5 = libc::LOG_LOCAL5;
268
269        /// reserved for local use
270        const LOG_LOCAL6 = libc::LOG_LOCAL6;
271        
272        /// reserved for local use
273        const LOG_LOCAL7 = libc::LOG_LOCAL7;
274    }
275}
276
277/// max hostname size
278pub const MAXHOSTNAMELEN: usize = 256;
279
280/// mask to extract facility part
281pub const LOG_FACMASK: i32 = 0x03f8;
282
283/// Maximum number of characters of syslog message.
284/// According to RFC5424. However syslog-protocol also may state that 
285/// the max message will be defined by the transport layer.
286pub const MAXLINE: usize = 8192;
287
288/// RFC3164 limit
289pub const RFC3164_MAX_PAYLOAD_LEN: usize = 1024;
290
291#[cfg(all(feature = "udp_truncate_1024_bytes", feature = "udp_truncate_1440_bytes"))]
292compile_error!("either 'udp_truncate_1024_bytes' or 'udp_truncate_1440_bytes' should be enabled");
293
294// RFC5424 480 octets or limited by the (transport) MAX_DGRAM_LEN or other.
295#[cfg(feature = "udp_truncate_1024_bytes")]
296pub const RFC5424_UDP_MAX_PKT_LEN: usize  = 1024;
297
298#[cfg(any(feature = "udp_truncate_1440_bytes", all(not(feature = "udp_truncate_1440_bytes"), not(feature = "udp_truncate_1024_bytes"))))]
299pub const RFC5424_UDP_MAX_PKT_LEN: usize  = 2048;
300
301#[cfg(feature = "tcp_truncate_1024_bytes")]
302pub const RFC5424_TCP_MAX_PKT_LEN: usize  = 1024;
303
304#[cfg(feature = "tcp_truncate_2048_bytes")]
305pub const RFC5424_TCP_MAX_PKT_LEN: usize  = 2048;
306
307#[cfg(feature = "tcp_truncate_4096_bytes")]
308pub const RFC5424_TCP_MAX_PKT_LEN: usize  = 4096;
309
310#[cfg(feature = "tcp_truncate_max_bytes")]
311pub const RFC5424_TCP_MAX_PKT_LEN: usize  = MAXLINE;
312
313/// A max byte lenth of APPNAME (NILVALUE / 1*48PRINTUSASCII)
314pub const RFC_MAX_APP_NAME: usize = 48;
315
316/// A private enterprise number.
317pub const IANA_PRIV_ENT_NUM: u64 = 32473;
318
319/// RFC5424 defined value.
320pub const NILVALUE: &'static str = "-";
321
322/// RFC5424 escape character.
323pub const ESC_CHAR_REPL: &'static str = "#000";
324
325/// RFC5424 defined value (bytes).
326pub const NILVALUE_B: &'static [u8] = b"-";
327
328/// White space
329pub const WSPACE: &'static str = " ";
330
331/// Opening brace ('[', ABNF )
332pub const OBRACE: &'static str = "[";
333
334/// Closing brace (']', ABNF %d93)
335pub const CBRACE: &'static str = "]";
336
337/// Quote-character ('"', ABNF %d34)
338pub const QCHAR: &'static str = "\"";
339
340/// At-sign ("@", ABNF %d64)
341pub const ATSIGN: &'static str = "@";
342
343/// Eq-sign ("=", ABNF %d61)
344pub const EQSIGN: &'static str = "=";
345
346pub const NEXTLINE: &'static str = "\n";
347
348/// Unpriv socket
349pub const PATH_LOG: &'static str = "/var/run/log";
350
351/// Priviledged socket
352pub const PATH_LOG_PRIV: &'static str = "/var/run/logpriv";
353
354/// backward compatibility
355pub const PATH_OLDLOG: &'static str = "/dev/log";
356
357/// OSX compat
358pub const PATH_OSX: &'static str = "/var/run/syslog";
359
360/*
361pub static PATH_CONSOLE: LazyLock<CString> = LazyLock::new(|| 
362    {
363        CString::new("/dev/console").unwrap()
364    }
365);
366*/
367pub static PATH_CONSOLE: LazyLock<&Path> = LazyLock::new(|| 
368    {
369        Path::new("/dev/console")
370    }
371);
372
373pub static RFC5424_MAX_DGRAM: LazyLock<usize> = LazyLock::new(|| 
374    {
375        portable::get_local_dgram_maxdgram() as usize
376    }
377);
378
379
380
381/// LOG_MASK is used to create the priority mask in setlogmask. 
382/// For a single Priority mask
383/// used with [Priority]
384/// can be used with | & ! bit operations LOG_MASK()
385///
386/// # Examples
387/// 
388/// ```
389///     LOG_MASK!(Priority::LOG_ALERT) | LOG_MASK!(Priority::LOG_INFO)
390/// ```
391#[macro_export]
392macro_rules! LOG_MASK 
393{
394    ($($arg:tt)*) => (
395        (1 << $($arg)*)
396    )
397}
398
399/// LOG_MASK is used to create the priority mask in setlogmask
400/// For a mask UPTO specified
401/// used with [Priority]
402///
403/// # Examples
404/// 
405/// ```
406///     LOG_UPTO!(Priority::LOG_ALERT)
407/// ```
408#[macro_export]
409macro_rules! LOG_UPTO 
410{
411    ($($arg:tt)*) => (
412        ((1 << (($($arg)*) + 1)) - 1)
413    )
414}
415
416/// Returns the static configuration for internal log
417pub 
418fn get_internal_log() -> libc::c_int
419{
420    return 
421        Priority::LOG_ERR.bits() | 
422        (LogStat::LOG_CONS| LogStat::LOG_PERROR| LogStat::LOG_PID).bits();
423}
424
425impl Shl<Priority> for i32
426{
427    type Output = i32;
428
429    fn shl(self, rhs: Priority) -> i32 
430    {
431        let lhs = self;
432        return lhs << rhs.bits();
433    }
434}
435
436impl BitAnd<Priority> for i32
437{
438    type Output = i32;
439
440    #[inline]
441    fn bitand(self, rhs: Priority) -> i32
442    {
443        return self & rhs.bits();
444    }
445}
446
447impl BitAnd<LogMask> for Priority 
448{
449    type Output = Priority;
450
451    #[inline]
452    fn bitand(self, rhs: LogMask) -> Self::Output
453    {
454        return Self {bits: self.bits() & rhs.bits()};
455    }
456}
457
458impl BitAnd<LogMask> for LogFacility 
459{
460    type Output = LogFacility;
461
462    #[inline]
463    fn bitand(self, rhs: LogMask) -> Self::Output
464    {
465        return Self {bits: self.bits() & rhs.bits()};
466    }
467}
468
469impl BitAnd<LogMask> for i32 
470{
471    type Output = i32;
472
473    #[inline]
474    fn bitand(self, rhs: LogMask) -> i32
475    {
476        return self & rhs.bits();
477    }
478}
479
480#[cfg(feature = "build_sync")]
481pub(crate) mod sync_portion
482{
483    use std::borrow::Cow;
484    use std::io::Write;
485    use std::io::IoSlice;
486    use crate::error::SyRes;
487    use crate::map_error_os;
488
489    /// Sends to the FD i.e file of stderr, stdout or any which 
490    /// implements [Write] `write_vectored`.
491    ///
492    /// # Arguments
493    /// 
494    /// * `file_fd` - mutable consume of the container FD.
495    ///
496    /// * `msg` - a reference on array of data
497    ///
498    /// * `newline` - a new line string ref i.e "\n" or "\r\n"
499    pub(crate) 
500    fn send_to_fd<W>(mut file_fd: W, msg: &[Cow<'_, str>], newline: &str) -> SyRes<usize>
501    where W: Write
502    {
503        let mut iov_list: Vec<IoSlice<'_>> = Vec::with_capacity(msg.len() + 1);
504
505        msg.iter().for_each(|v| iov_list.push(IoSlice::new(v.as_bytes())));
506        iov_list.push(IoSlice::new(newline.as_bytes()));
507
508        return 
509            file_fd
510                .write_vectored(&iov_list)
511                .map_err(|e|
512                    map_error_os!(e, "send_to_fd() writev() failed")
513                );
514    }
515}
516
517#[cfg(feature = "build_sync")]
518pub(crate) use self::sync_portion::*;
519
520/// This function trancated 1 last UTF8 character from the string.
521///
522/// # Arguments
523///
524/// * `lt` - a string which is trucated
525/// 
526/// # Returns
527/// 
528/// * A reference to the ctruncated string
529pub 
530fn truncate(lt: &str) -> &str
531{
532    let ltt =
533        match lt.char_indices().nth(lt.len()-1) 
534        {
535            None => lt,
536            Some((idx, _)) => &lt[..idx],
537        };
538    return ltt;
539}
540
541/// Trancates the string up to closest to N byte equiv UTF8
542///  if string exceeds size
543/// 
544/// For example:  
545/// ボルテ 'e3 83 9c e3 83 ab e3 83 86' with N=3  
546/// will give 'ボ'  
547/// 
548/// ボルテ 'e3 83 9c e3 83 ab e3 83 86' with N=4  
549/// will give 'ボ' 
550/// 
551/// ボルテ 'e3 83 9c e3 83 ab e3 83 86' with N=1  
552/// will give ''
553/// 
554/// # Arguments
555///
556/// * `lt` - a string to truncate
557///
558/// * `n` - a size (in bytes, not in chars)
559/// 
560/// # Returns 
561///
562/// * A reference to [str] with the time `'t` which corresponds to
563/// the lifetile of the input argument `'t`.
564pub 
565fn truncate_n<'t>(lt: &'t str, n: usize) -> &'t str
566{
567    if lt.as_bytes().len() <= n
568    {
569        return lt;
570    }
571
572    let mut nn: usize = 0;
573    let mut cc = lt.chars();
574    let mut ln: usize;
575
576    loop 
577    {
578        match cc.next()
579        {
580            Some(r) =>
581            {
582                ln = r.len_utf8();
583                nn += ln;
584
585                if nn == n
586                {
587                    return &lt[..nn];
588                }
589                else if nn > n
590                {
591                    return &lt[..nn-ln];
592                }
593            },
594            None => 
595                return lt,
596        }
597    }
598}
599
600/// Checks if string are:
601/// ```text
602/// NOT EMPTY
603/// MUST be printable US-ASCII strings, and MUST
604/// NOT contain an at-sign ('@', ABNF %d64), an equal-sign ('=', ABNF
605/// %d61), a closing brace (']', ABNF %d93), a quote-character ('"',
606/// ABNF %d34), whitespace, or control characters
607/// ```
608pub
609fn check_printable(a: &str) -> SyRes<()>
610{
611    if a.is_empty() == true
612    {
613        throw_error!("empty SD value");
614    }
615
616    for p in a.chars()
617    {
618        if p.is_ascii() == false || p.is_ascii_graphic() == false || p == '@' || p == '=' || p == ']' || p == '\"'
619        {
620            throw_error!("incorrect char: '{:X}' in SD value", p as u64);
621        }
622    }
623
624    return Ok(());
625}
626
627
628pub 
629fn escape_chars(st: Cow<'static, str>) -> Cow<'static, str>
630{
631    let mut out = String::with_capacity(st.len());
632
633    for c in st.chars()
634    {
635        if c.is_control() == true
636        {
637            out.push_str(ESC_CHAR_REPL);
638        }
639        else if c == '\"' || c == '\\' || c == ']'
640        {
641            out.push('\\');
642            out.push(c);
643        }
644        else
645        {
646            out.push(c);
647        }
648    }
649
650    if st.len() == out.len()
651    {
652        return st;
653    }
654    else
655    {
656        return Cow::Owned(out);
657    }
658}
659
660
661#[cfg(test)]
662mod tests
663{
664    use std::borrow::Cow;
665
666    use super::*;
667
668    #[cfg(feature = "build_sync")]
669    #[test]
670    fn test_error_message()
671    {
672        /*use std::sync::Arc;
673        use std::thread;
674        use std::time::Duration;
675        use super::{LOG_MASK};*/
676
677        let testmsg = Cow::Borrowed("this is test message!");
678        let testmsg2 = Cow::Borrowed(" this is test message 2!");
679        let newline = "\n";
680        let stderr_lock = std::io::stderr().lock();
681        let res = send_to_fd(stderr_lock, &[testmsg, testmsg2], &newline);
682
683        println!("res: {:?}", res);
684
685        assert_eq!(res.is_ok(), true, "err: {}", res.err().unwrap());
686    
687        return;
688    }
689
690    #[test]
691    fn test_truncate()
692    {
693        let test = "cat\n";
694
695        let trunc = truncate(test);
696
697        assert_eq!("cat", trunc);
698    }
699
700    #[test]
701    fn test_priority_shl()
702    {
703        assert_eq!((1 << 5), (1 << Priority::LOG_NOTICE));
704    }
705
706    #[test]
707    fn test_truncate_n()
708    {
709        assert_eq!(truncate_n("abcde", 3), "abc");
710        assert_eq!(truncate_n("ボルテ", 4), "ボ");
711        assert_eq!(truncate_n("ボルテ", 5), "ボ");
712        assert_eq!(truncate_n("ボルテ", 6), "ボル");
713        assert_eq!(truncate_n("abcde", 0), "");
714        assert_eq!(truncate_n("abcde", 5), "abcde");
715        assert_eq!(truncate_n("abcde", 6), "abcde");
716        assert_eq!(truncate_n("ДАТА", 3), "Д");
717        assert_eq!(truncate_n("ДАТА", 4), "ДА");
718        assert_eq!(truncate_n("ДАТА", 1), "");
719    }
720
721}