syslog_rs/
common.rs

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