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