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: &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: &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/// Closing brace RFC3...
338pub const CBRACE_SEM: &'static str = "]:";
339
340/// Quote-character ('"', ABNF %d34)
341pub const QCHAR: &'static str = "\"";
342
343/// At-sign ("@", ABNF %d64)
344pub const ATSIGN: &'static str = "@";
345
346/// Eq-sign ("=", ABNF %d61)
347pub const EQSIGN: &'static str = "=";
348
349pub const NEXTLINE: &'static str = "\n";
350
351/// Unpriv socket
352pub const PATH_LOG: &'static str = "/var/run/log";
353
354/// Priviledged socket
355pub const PATH_LOG_PRIV: &'static str = "/var/run/logpriv";
356
357/// backward compatibility
358pub const PATH_OLDLOG: &'static str = "/dev/log";
359
360/// OSX compat
361pub const PATH_OSX: &'static str = "/var/run/syslog";
362
363/*
364pub static PATH_CONSOLE: LazyLock<CString> = LazyLock::new(|| 
365    {
366        CString::new("/dev/console").unwrap()
367    }
368);
369*/
370pub static PATH_CONSOLE: LazyLock<&Path> = LazyLock::new(|| 
371    {
372        Path::new("/dev/console")
373    }
374);
375
376pub static RFC5424_MAX_DGRAM: LazyLock<usize> = LazyLock::new(|| 
377    {
378        portable::get_local_dgram_maxdgram() as usize
379    }
380);
381
382
383
384/// LOG_MASK is used to create the priority mask in setlogmask. 
385/// For a single Priority mask
386/// used with [Priority]
387/// can be used with | & ! bit operations LOG_MASK()
388///
389/// # Examples
390/// 
391/// ```
392///     LOG_MASK!(Priority::LOG_ALERT) | LOG_MASK!(Priority::LOG_INFO)
393/// ```
394#[macro_export]
395macro_rules! LOG_MASK 
396{
397    ($($arg:tt)*) => (
398        (1 << $($arg)*)
399    )
400}
401
402/// LOG_MASK is used to create the priority mask in setlogmask
403/// For a mask UPTO specified
404/// used with [Priority]
405///
406/// # Examples
407/// 
408/// ```
409///     LOG_UPTO!(Priority::LOG_ALERT)
410/// ```
411#[macro_export]
412macro_rules! LOG_UPTO 
413{
414    ($($arg:tt)*) => (
415        ((1 << (($($arg)*) + 1)) - 1)
416    )
417}
418
419/// Returns the static configuration for internal log
420pub 
421fn get_internal_log() -> libc::c_int
422{
423    return 
424        Priority::LOG_ERR.bits() | 
425        (LogStat::LOG_CONS| LogStat::LOG_PERROR| LogStat::LOG_PID).bits();
426}
427
428impl Shl<Priority> for i32
429{
430    type Output = i32;
431
432    fn shl(self, rhs: Priority) -> i32 
433    {
434        let lhs = self;
435        return lhs << rhs.bits();
436    }
437}
438
439impl BitAnd<Priority> for i32
440{
441    type Output = i32;
442
443    #[inline]
444    fn bitand(self, rhs: Priority) -> i32
445    {
446        return self & rhs.bits();
447    }
448}
449
450impl BitAnd<LogMask> for Priority 
451{
452    type Output = Priority;
453
454    #[inline]
455    fn bitand(self, rhs: LogMask) -> Self::Output
456    {
457        return Self {bits: self.bits() & rhs.bits()};
458    }
459}
460
461impl BitAnd<LogMask> for LogFacility 
462{
463    type Output = LogFacility;
464
465    #[inline]
466    fn bitand(self, rhs: LogMask) -> Self::Output
467    {
468        return Self {bits: self.bits() & rhs.bits()};
469    }
470}
471
472impl BitAnd<LogMask> for i32 
473{
474    type Output = i32;
475
476    #[inline]
477    fn bitand(self, rhs: LogMask) -> i32
478    {
479        return self & rhs.bits();
480    }
481}
482
483#[cfg(feature = "build_sync")]
484pub(crate) mod sync_portion
485{
486    use std::io::Write;
487    use std::io::IoSlice;
488    use crate::error::SyRes;
489    use crate::map_error_os;
490
491    /// Sends to the FD i.e file of stderr, stdout or any which 
492    /// implements [Write] `write_vectored`.
493    ///
494    /// # Arguments
495    /// 
496    /// * `file_fd` - mutable consume of the container FD.
497    ///
498    /// * `msg` - a reference on array of data
499    ///
500    /// * `newline` - a new line string ref i.e "\n" or "\r\n"
501    pub(crate) 
502    fn send_to_fd<W>(mut file_fd: W, msg: &str, newline: &str) -> SyRes<usize>
503    where W: Write
504    {
505        return 
506            file_fd
507                .write_vectored(
508                    &[IoSlice::new(msg.as_bytes()), IoSlice::new(newline.as_bytes())]
509                )
510                .map_err(|e|
511                    map_error_os!(e, "send_to_fd() writev() failed")
512                );
513    }
514}
515
516#[cfg(feature = "build_sync")]
517pub(crate) use self::sync_portion::*;
518
519/// This function trancated 1 last UTF8 character from the string.
520///
521/// # Arguments
522///
523/// * `lt` - a string which is trucated
524/// 
525/// # Returns
526/// 
527/// * A reference to the ctruncated string
528pub 
529fn truncate(lt: &str) -> &str
530{
531    let ltt =
532        match lt.char_indices().nth(lt.len()-1) 
533        {
534            None => lt,
535            Some((idx, _)) => &lt[..idx],
536        };
537    return ltt;
538}
539
540/// Trancates the string up to closest to N byte equiv UTF8
541///  if string exceeds size
542/// 
543/// For example:  
544/// ボルテ 'e3 83 9c e3 83 ab e3 83 86' with N=3  
545/// will give 'ボ'  
546/// 
547/// ボルテ 'e3 83 9c e3 83 ab e3 83 86' with N=4  
548/// will give 'ボ' 
549/// 
550/// ボルテ 'e3 83 9c e3 83 ab e3 83 86' with N=1  
551/// will give ''
552/// 
553/// # Arguments
554///
555/// * `lt` - a string to truncate
556///
557/// * `n` - a size (in bytes, not in chars)
558/// 
559/// # Returns 
560///
561/// * A reference to [str] with the time `'t` which corresponds to
562/// the lifetile of the input argument `'t`.
563pub 
564fn truncate_n<'t>(lt: &'t str, n: usize) -> &'t str
565{
566    if lt.as_bytes().len() <= n
567    {
568        return lt;
569    }
570
571    let mut nn: usize = 0;
572    let mut cc = lt.chars();
573    let mut ln: usize;
574
575    loop 
576    {
577        match cc.next()
578        {
579            Some(r) =>
580            {
581                ln = r.len_utf8();
582                nn += ln;
583
584                if nn == n
585                {
586                    return &lt[..nn];
587                }
588                else if nn > n
589                {
590                    return &lt[..nn-ln];
591                }
592            },
593            None => 
594                return lt,
595        }
596    }
597}
598
599/// Checks if string are:
600/// ```text
601/// NOT EMPTY
602/// MUST be printable US-ASCII strings, and MUST
603/// NOT contain an at-sign ('@', ABNF %d64), an equal-sign ('=', ABNF
604/// %d61), a closing brace (']', ABNF %d93), a quote-character ('"',
605/// ABNF %d34), whitespace, or control characters
606/// ```
607pub
608fn check_printable(a: &str) -> SyRes<()>
609{
610    if a.is_empty() == true
611    {
612        throw_error!("empty SD value");
613    }
614
615    for p in a.chars()
616    {
617        if p.is_ascii() == false || p.is_ascii_graphic() == false || p == '@' || p == '=' || p == ']' || p == '\"'
618        {
619            throw_error!("incorrect char: '{:X}' in SD value", p as u64);
620        }
621    }
622
623    return Ok(());
624}
625
626
627pub 
628fn escape_chars(st: Cow<'static, str>) -> Cow<'static, str>
629{
630    let mut out = String::with_capacity(st.len());
631
632    for c in st.chars()
633    {
634        if c.is_control() == true
635        {
636            out.push_str(ESC_CHAR_REPL);
637        }
638        else if c == '\"' || c == '\\' || c == ']'
639        {
640            out.push('\\');
641            out.push(c);
642        }
643        else
644        {
645            out.push(c);
646        }
647    }
648
649    if st.len() == out.len()
650    {
651        return st;
652    }
653    else
654    {
655        return Cow::Owned(out);
656    }
657}
658
659
660#[cfg(test)]
661mod tests
662{
663    use super::*;
664
665    #[cfg(feature = "build_sync")]
666    #[test]
667    fn test_error_message()
668    {
669        /*use std::sync::Arc;
670        use std::thread;
671        use std::time::Duration;
672        use super::{LOG_MASK};*/
673
674        let testmsg = "this is test message!";
675        let newline = "\n";
676        let stderr_lock = std::io::stderr().lock();
677        let res = send_to_fd(stderr_lock, testmsg, &newline);
678
679        println!("res: {:?}", res);
680
681        assert_eq!(res.is_ok(), true, "err: {}", res.err().unwrap());
682    
683        return;
684    }
685
686    #[test]
687    fn test_truncate()
688    {
689        let test = "cat\n";
690
691        let trunc = truncate(test);
692
693        assert_eq!("cat", trunc);
694    }
695
696    #[test]
697    fn test_priority_shl()
698    {
699        assert_eq!((1 << 5), (1 << Priority::LOG_NOTICE));
700    }
701
702    #[test]
703    fn test_truncate_n()
704    {
705        assert_eq!(truncate_n("abcde", 3), "abc");
706        assert_eq!(truncate_n("ボルテ", 4), "ボ");
707        assert_eq!(truncate_n("ボルテ", 5), "ボ");
708        assert_eq!(truncate_n("ボルテ", 6), "ボル");
709        assert_eq!(truncate_n("abcde", 0), "");
710        assert_eq!(truncate_n("abcde", 5), "abcde");
711        assert_eq!(truncate_n("abcde", 6), "abcde");
712        assert_eq!(truncate_n("ДАТА", 3), "Д");
713        assert_eq!(truncate_n("ДАТА", 4), "ДА");
714        assert_eq!(truncate_n("ДАТА", 1), "");
715    }
716
717}