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