syslog_rs/
portable.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#[cfg(target_os = "linux")]
17pub(crate) use self::portable_linux::*;
18
19#[cfg(any(
20    target_os = "freebsd",
21    target_os = "dragonfly",
22    target_os = "openbsd",
23    target_os = "netbsd",
24    target_os = "macos"
25))]
26pub(crate) use self::portable_bsd::*;
27
28#[cfg(target_family = "windows")]
29pub(crate) use self::portable_windows::*;
30
31/// A portable code for Windows.
32#[cfg(target_family = "windows")]
33pub mod portable_windows
34{
35    use std::
36    {
37        env, ffi::{CStr, c_void}, io::{self, ErrorKind}, mem::transmute, time::Duration
38    };
39
40    use windows::
41    {
42        Win32::
43        {
44            Foundation::HANDLE, 
45            Networking::WinSock::{self, SOCKET_ERROR}, 
46            System::
47            {
48                EventLog::{EVENTLOG_INFORMATION_TYPE, RegisterEventSourceA, ReportEventA}, 
49                SystemInformation
50            }
51        }, 
52        core::{Free, PCSTR}
53    };
54
55    use crate::{LogFacility, LogMask, NILVALUE, Priority, SyslogMsgPriFac, WindowsEvent, error::SyRes};
56
57    pub const DEFAULT_MAXGRAM_SIZE: u64 = 2048;
58
59    /// A dummy network socket realization for the Windows Event Log.
60    #[derive(Debug)]
61    pub struct EventLogLocal
62    {
63        /// windows::Win32::Foundation::HANDLE 
64        /// (but it is a *c_void which cannot be sent between threads)
65        evs: usize,
66    }
67
68    impl Drop for EventLogLocal
69    {
70        fn drop(&mut self) 
71        {
72            unsafe { HANDLE(self.evs as *mut c_void).free() };
73        }
74    }
75
76    impl EventLogLocal
77    {
78        pub 
79        fn new(winnie_ev: &WindowsEvent) -> io::Result<Self>
80        {
81            /*OpenEventLogA*/
82
83            let evs = 
84                unsafe 
85                {  
86                    RegisterEventSourceA(
87                        winnie_ev
88                            .get_server_unc()
89                            .map_or(
90                                PCSTR::null(), 
91                                |f| PCSTR::from_raw( transmute(f.as_ptr()) )
92                            ), 
93                        PCSTR::from_raw(
94                                transmute(winnie_ev.get_service_name().as_ptr())
95                            ) 
96                    )
97                }
98                .map_err(|e|
99                    io::Error::new(ErrorKind::ConnectionRefused, e)
100                )?;
101            
102            return Ok(Self{ evs: evs.0 as usize });
103        }
104
105        /// Sends message to log.
106        /// 
107        /// If `msg` starts with sign `|`, it contains a priority and facility data,
108        /// otherwise default values will be set. The `|` will be ommited from message.
109        /// It must be `NULL-terminated`!
110        /// 
111        /// The message may begin from '|'PRI'|', see `windows_10.rs` as example.
112        /// 
113        /// # Returns
114        /// 
115        /// * [ErrorKind::InvalidData] - if argument `msg` was not able to wrap into [CStr].
116        ///     Probably the `msg` is not NULL-terminated! The error description can be 
117        ///     obtained by downcasting the error to [std::ffi::FromBytesWithNulError].
118        /// 
119        /// * [ErrorKind::ConnectionRefused] - [ReportEventA] failed with error which can be
120        ///     obtained by downcasting the error into [windows::core::Error].
121        pub 
122        fn send(&self, msg: &[u8]) -> io::Result<usize>
123        {
124            if msg.len() == 0
125            {
126                return Ok(0);
127            }
128
129            let mut event_log_type = EVENTLOG_INFORMATION_TYPE;
130            let mut facility = LogFacility::LOG_USER.into_win_facility();
131            
132            if msg[0] == '|' as u8
133            {
134                for (idx, c) in msg.iter().enumerate().skip(1)
135                {
136                    if *c == '|' as u8
137                    {
138                        let Ok(pri_fac) = 
139                            SyslogMsgPriFac::try_from(&msg[1..idx])
140                                else { break };
141
142                        event_log_type = pri_fac.get_priority().into();
143                        facility = pri_fac.get_log_facility().into_win_facility();
144                        break;
145                    }
146
147                    if idx == 8 
148                    {
149                        break;
150                    }
151                }
152            }
153
154            let cmsg = 
155                CStr::from_bytes_with_nul(msg)
156                    .map_err(|e|
157                        io::Error::new(ErrorKind::InvalidData, e)
158                    )?;
159
160            let msgs =
161                 &[PCSTR::from_raw( unsafe { transmute(/*copied_msg.as_c_str().as_ptr()*/ cmsg.as_ptr()) })];
162
163            unsafe
164            {
165                ReportEventA(
166                    HANDLE(self.evs as *mut c_void), 
167                    event_log_type, //EVENTLOG_ERROR_TYPE,
168                    1, 
169                    0x20010000 | facility, 
170                    None, 
171                    0, 
172                    Some(msgs), 
173                    None
174                )
175                
176            }
177            .map_err(|e|
178                io::Error::new(ErrorKind::ConnectionRefused, e)
179            )?;
180
181            return Ok(msg.len());
182        }
183    }
184
185    /// Reutns the current process name, if available
186    pub(crate) 
187    fn p_getprogname() -> Option<String>
188    {
189        return 
190            env::current_exe()
191                .ok()
192                .map_or(None, 
193                    |path| 
194                    path
195                        .file_name()
196                        .map(|name| name.to_string_lossy().to_string())
197                );
198    }
199
200    pub(crate)
201    fn get_local_dgram_maxdgram() -> u64
202    {
203        return DEFAULT_MAXGRAM_SIZE;
204    }
205
206    pub(crate)
207    fn get_uptime() -> SyRes<Duration>
208    {
209        let ret: u64 = unsafe {SystemInformation::GetTickCount64() };
210
211        return Ok(Duration::from_millis(ret));
212    }
213
214    pub(crate)
215    fn portable_gethostname() -> SyRes<String>
216    {
217        let mut hostname = vec![0_u8; 512];
218        
219        let res = 
220            unsafe
221            {
222                WinSock::gethostname(&mut hostname)
223            };
224
225        if res == SOCKET_ERROR 
226        {
227            let err = unsafe { WinSock::WSAGetLastError() };
228
229            throw_error!("can bot obtain kern.boottime, err: '{}'", err.0);
230        }
231
232        return Ok(String::from_utf8(hostname).map_or(NILVALUE.into(), |f| f));
233    }
234}
235
236#[cfg(target_os = "linux")]
237pub mod portable_linux
238{
239    use std::ffi::CStr;
240    use std::path::Path;
241    use std::time::Duration;
242
243    use nix::libc;
244
245    use crate::error::SyRes;
246    use crate::{map_error};
247
248    pub const DEFAULT_MAXGRAM_SIZE: u64 = 2048;
249
250    #[link(name = "c")]
251    unsafe extern "C" {
252        pub static mut program_invocation_name : *mut libc::c_char ;
253    }
254
255    /// Returns the current program's name.
256    pub(crate) 
257    fn p_getprogname() -> Option<String>
258    {
259        let pn = unsafe{ program_invocation_name };
260
261        let temp = unsafe {CStr::from_ptr(pn)};
262
263        return 
264            temp
265                .to_str()
266                .ok()
267                .map_or(None, |r| Path::new(r).file_name().map(|r| r.to_string_lossy().into()));
268/*
269        match temp.to_str()
270        {
271            Ok(r) => 
272            {
273                let path = Path::new(r);
274                match path.file_name()
275                {
276                    Some(r) => return Some(r.to_string_lossy().into()),
277                    None => return None,
278                }
279            },
280            Err(_) => 
281                return None,
282        }
283   */
284    }
285
286    pub(crate) 
287    fn get_local_dgram_maxdgram() -> u64
288    {
289        return DEFAULT_MAXGRAM_SIZE;
290    }
291
292    pub(crate)
293    fn get_uptime() -> SyRes<Duration>
294    {
295        let sysinfo = 
296            nix::sys::sysinfo::sysinfo()
297                .map_err(|e|
298                    map_error!("get_uptime() sysinfo error: {}", e)
299                )?;
300
301        return Ok(sysinfo.uptime());
302    }
303
304    pub(crate)
305    fn portable_gethostname() -> SyRes<String>
306    {
307        return 
308            nix::unistd::gethostname()
309                .map_or_else(
310                    |e|
311                    Err(map_error!("gethostname() error: {}", e)),
312                    |hn| 
313                    hn.into_string()
314                        .map_or(
315                            Err(map_error!("gethostname() into_string() error")),
316                            |hostname| Ok(hostname)
317                        )
318                );
319    }
320}
321
322
323#[cfg(any(
324    target_os = "freebsd",
325    target_os = "dragonfly",
326    target_os = "openbsd",
327    target_os = "netbsd",
328    target_os = "macos"
329))]
330pub mod portable_bsd
331{
332    use std::{ffi::{CStr, CString}, time::Duration};
333
334    use chrono::Local;
335    use nix::{errno::Errno, libc::{self, timeval}};
336
337    use crate::error::SyRes;
338
339    pub const DEFAULT_MAXGRAM_SIZE: u64 = 2048;
340
341    #[link(name = "c")]
342    unsafe extern "C" {
343        fn getprogname() -> *const libc::c_char;
344    }
345
346    /// Reutns the current process name, if available
347    pub(crate) 
348    fn p_getprogname() -> Option<String>
349    {
350        let pn = unsafe { getprogname() };
351
352        let temp = unsafe {CStr::from_ptr(pn)};
353
354        match temp.to_str()
355        {
356            Ok(r) => 
357                return Some(r.to_string()),
358            Err(_) => 
359                return None,
360        }
361    
362    }
363
364    pub(crate) 
365    fn get_local_dgram_maxdgram() -> u64
366    {
367        let name = CString::new("net.local.dgram.maxdgram").unwrap();
368        let mut maxdgram: u64 = 0;
369        let mut maxdgram_size: libc::size_t = std::mem::size_of_val(&maxdgram) as libc::size_t;
370
371        let res = 
372            unsafe 
373            {
374                libc::sysctlbyname(
375                    name.as_ptr(), 
376                    &mut maxdgram as *mut _ as *mut libc::c_void, 
377                    &mut maxdgram_size as *mut _ as *mut libc::size_t,
378                    std::ptr::null(), 
379                    0)
380            };
381
382        if res == -1
383        {
384            if cfg!(feature = "dgram_sysctl_failure_panic")
385            {
386                let err = Errno::last_raw();
387                panic!("can not obtain MAXDGRAM from sysctl '{}', err: '{}'", name.to_string_lossy(), err);
388            }
389            else 
390            {
391                return DEFAULT_MAXGRAM_SIZE;
392            }
393        }
394        else if res == 0
395        {
396            return maxdgram;
397        }
398        else
399        {
400            panic!("can not obtain MAXDGRAM from sysctl '{}', unknwon res: '{}'", name.to_string_lossy(), res);
401        }
402    }
403
404    pub(crate)
405    fn get_uptime() -> SyRes<Duration>
406    {
407        let name = CString::new("kern.boottime").unwrap();
408
409        let mut boottime: timeval = unsafe { std::mem::zeroed() };
410        let mut size: libc::size_t = std::mem::size_of_val(&boottime) as libc::size_t;
411
412        let res = 
413            unsafe 
414            {
415                libc::sysctlbyname(
416                    name.as_ptr(), 
417                    &mut boottime as *mut _ as *mut libc::c_void, 
418                    &mut size as *mut _ as *mut libc::size_t,
419                    std::ptr::null(), 
420                    0)
421            };
422
423        
424        if res == -1
425        {
426            throw_error!("can bot obtain kern.boottime, err: '{}'", Errno::last_raw());
427        }
428        else if res == 0
429        {
430            let c = 
431                chrono::Duration::new(boottime.tv_sec, boottime.tv_usec as u32)
432                    .ok_or_else(||
433                        map_error!("timeval to chrono::Duration converion error")
434                    )?;
435            let now = Local::now();
436
437            let r = 
438                now
439                    .checked_sub_signed(c)
440                    .ok_or_else(||
441                        map_error!("error checked_sub_signed()")
442                    )?;
443            return Ok(Duration::from_millis(r.timestamp_millis() as u64));
444        }
445        else
446        {
447            throw_error!("can not obtain  kern.boottime from sysctl '{}', unknwon res: '{}'", name.to_string_lossy(), res);
448        }
449    }
450
451    pub(crate)
452    fn portable_gethostname() -> SyRes<String>
453    {
454        return 
455            nix::unistd::gethostname()
456                .map_or_else(
457                    |e|
458                    Err(map_error!("gethostname() error: {}", e)),
459                    |hn| 
460                    hn.into_string()
461                        .map_or(
462                            Err(map_error!("gethostname() into_string() error")),
463                            |hostname| Ok(hostname)
464                        )
465                );
466    }
467
468    #[cfg(test)]
469    mod tests
470    {
471        use crate::portable::get_uptime;
472
473        #[test]
474        fn test_get_uptime()
475        {
476            let res = get_uptime();
477            assert_eq!(res.is_ok(), true);
478
479            println!("{:?}", res.unwrap());
480        }
481    }
482}
483
484
485/// Returns pid of current process. Not thread id!
486#[inline]
487pub 
488fn get_pid() -> u32
489{
490    return std::process::id();
491}
492
493#[cfg(test)]
494mod test_portables
495{
496    use super::*;
497    
498    #[test]
499    fn test_get_procname()
500    {
501        let procname = p_getprogname();
502
503        println!("Processname is: {:?}", procname);
504
505        assert_eq!(procname.is_some(), true);
506        assert_eq!(procname.as_ref().unwrap().starts_with("syslog_rs"), true);
507    }
508
509    #[test]
510    fn test_maxgram()
511    {
512        let max = get_local_dgram_maxdgram();
513
514        println!("maxdgram: '{}'", max);
515
516        assert_eq!(max > 0, true);
517    }
518}