syslog_rs/a_sync/
syslog_async_internal.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::borrow::Cow;
24use std::marker::PhantomData;
25
26use tokio::time::{sleep, Duration};
27
28use nix::libc;
29
30use crate::formatters::SyslogFormatter;
31use crate::map_error_os;
32use crate::portable;
33use crate::common::*;
34use crate::error::SyRes;
35use crate::socket::TapType;
36use crate::socket::TapTypeData;
37
38use super::async_socket::*;
39
40/// Internal structure with syslog setup
41#[derive(Debug)]
42pub struct AsyncSyslogInternal<F: SyslogFormatter + Send>
43{
44    /// An identification i.e program name, thread name
45    logtag: String, 
46
47    /// A pid of the program.
48    logpid: String,
49
50    /// Defines how syslog operates
51    logstat: LogStat,
52
53    /// Holds the facility 
54    facility: LogFacility,
55
56    /// A logmask
57    logmask: i32,
58
59    /// A stream
60    stream: Box<dyn AsyncSyslogTap + Send + Sync>,
61
62    _p: PhantomData<F>,
63}
64
65//unsafe impl Sync for SyslogInternal{}
66//unsafe impl Send for SyslogInternal{}
67
68// Drop is called when no more references are left.
69/*
70impl Drop for AsyncSyslogInternal 
71{
72    fn drop(&mut self) 
73    {
74        // requires await, so skip.
75        //self.disconnectlog();
76    }
77}
78    */
79
80impl<F: SyslogFormatter + Send> AsyncSyslogInternal<F>
81{
82    /// Creates new instance of [SyslogInternal] which contains all 
83    /// client syslog logic.
84    ///
85    /// # Arguments
86    ///
87    /// * `ident` - An optional argument which takes ref to str. If none, the
88    ///     ident will be set later. Yje ident will be trucated to 48 UTF8
89    ///     chars.
90    /// 
91    /// * `logstat` - A [LogStat] flags separated by '|'
92    /// 
93    /// * `facility` - A [LogFacility] flag
94    /// 
95    /// * `req_tap` - A type of the syslog instance reuired. See [TapTypeData].
96    /// 
97    /// # Returns
98    ///
99    /// A [SyRes] is returned with following:
100    /// 
101    /// * [Result::Ok] - with the instance.
102    /// 
103    /// * [Result::Err] - with error description.
104    pub(crate) 
105    fn new(
106        ident: Option<&str>, 
107        logstat: LogStat, 
108        facility: LogFacility, 
109        req_tap: TapTypeData
110    ) -> SyRes<Self>
111    {
112        // check if log_facility is invalid
113        let log_facility =
114            if facility.is_empty() == false && 
115                (facility & !LogMask::LOG_FACMASK).is_empty() == true
116            {
117                facility
118            }
119            else
120            {
121                // default facility code
122                LogFacility::LOG_USER
123            };
124
125        let logtag = 
126            match ident
127            {
128                Some(r) => 
129                    truncate_n(r, RFC_MAX_APP_NAME).to_string(),
130                None => 
131                    truncate_n(
132                        portable::p_getprogname()
133                            .unwrap_or("".to_string())
134                            .as_str(),
135                        RFC_MAX_APP_NAME
136                    )
137                    .to_string()
138            };
139
140        return Ok(
141            Self
142            {
143                logtag: logtag,
144                logpid: portable::get_pid().to_string(),
145                logstat: logstat, 
146                facility: log_facility,
147                logmask: 0xff,
148                stream: AsyncTap::<()>::new(req_tap)?,
149                _p: PhantomData
150            }
151        );
152    }
153
154    pub(crate) async 
155    fn update_tap_data(&mut self, tap_data: TapTypeData) -> SyRes<()>
156    {
157        let is_con = self.stream.is_connected();
158
159        if is_con == true
160        {
161            self
162                .stream
163                .disconnectlog()
164                .await
165                .map_err(|e|
166                    map_error_os!(e, "update_tap_data() can not disconnect log properly")
167                )?;
168        }
169
170        self.stream.update_tap_data(tap_data)?;
171
172        if is_con == true
173        {
174            // replace with new instance
175            self.stream.connectlog().await?;
176        }
177
178        return Ok(());
179    }
180
181    
182    pub(crate) 
183    fn set_logmask(&mut self, logmask: i32) -> i32
184    {
185        let oldmask = self.logmask;
186
187        if logmask != 0
188        {
189            self.logmask = logmask;
190        }
191
192        return oldmask;
193    }
194 
195    #[inline]
196    async 
197    fn send_to_stderr(&self, msg: &[Cow<'_, str>])
198    {
199        if self.logstat.intersects(LogStat::LOG_PERROR) == true
200        {
201            let stderr_lock = tokio::io::stderr();
202
203            let newline = "\n";
204            let _ = async_send_to_fd(stderr_lock, msg, newline).await;
205        }
206    }
207
208    #[inline]
209    async 
210    fn send_to_syscons(&self, msg_payload: &[Cow<'_, str>])
211    {
212        use tokio::fs::File;
213
214        if self.logstat.intersects(LogStat::LOG_CONS)
215        {
216            let syscons = 
217                File
218                    ::options()
219                        .create(false)
220                        .read(false)
221                        .write(true)
222                        .custom_flags(libc::O_NONBLOCK | libc::O_CLOEXEC)
223                        .open(*PATH_CONSOLE)
224                        .await;
225
226            if let Ok(file) = syscons
227            {
228                let newline = "\n";
229                let _ = async_send_to_fd(file, msg_payload, newline);
230            }
231        }
232    }
233
234    #[inline]
235    pub(crate) 
236    fn is_logmasked(&self, pri: i32) -> bool
237    {
238        if ((1 << (pri & LogMask::LOG_PRIMASK)) & self.logmask) == 0
239        {
240            return true;
241        }
242
243        return false;
244    }
245
246    /// Returns the type of the socket.
247    #[inline]
248    pub(crate)
249    fn get_tap_type(&self) -> TapType
250    {
251        return self.stream.get_type();
252    }
253
254    #[inline]
255    pub(crate) 
256    fn set_logtag<L: AsRef<str>>(&mut self, logtag: L, update_pid: bool)
257    {
258        self.logtag = 
259            truncate_n(logtag.as_ref(), RFC_MAX_APP_NAME).to_string();
260
261        if update_pid == true
262        {
263            self.logpid = portable::get_pid().to_string();
264        }
265
266        return;
267    }
268
269    /// Disconnects the unix stream from syslog.
270    #[inline]
271    pub(crate) async 
272    fn disconnectlog(&mut self)  -> SyRes<()>
273    {
274        return 
275            self
276                .stream
277                .disconnectlog()
278                .await
279                .map_err(|e| map_error_os!(e, "can not disconnect log properly"));
280    }
281
282    /// Connects unix stream to the syslog and sets up the properties of
283    /// the unix stream.
284    #[inline]
285    pub(crate) async 
286    fn connectlog(&mut self) -> SyRes<()>
287    {
288        return self.stream.connectlog().await;
289    }
290    
291    /// An internal function which is called by the syslog or vsyslog.
292    pub(crate) async 
293    fn vsyslog1(&mut self, mut pri: Priority, fmt: &str)
294    {
295        // check for invalid bits
296        if let Err(e) = pri.check_invalid_bits()
297        {
298            self.send_to_stderr(&[Cow::Owned(e.to_string())]).await;
299        }
300
301        /*match check_invalid_bits(&mut pri)
302        {
303            Ok(_) => {},
304            Err(_e) => self.vsyslog1(get_internal_log(), fmt).await
305        }*/
306
307        // check priority against setlogmask
308        if self.is_logmasked(pri.bits()) == true
309        {
310            return;
311        }
312
313        // set default facility if not specified in pri
314        if (pri.bits() & LOG_FACMASK) == 0
315        {
316            pri.set_facility(self.facility);
317        }
318
319        let progname = self.logtag.clone();
320        let pid = self.logpid.clone();
321
322        let msg_formatted = F::vsyslog1_format(self.get_tap_type(), pri, &progname, &pid, fmt);
323
324        // output to stderr if required
325        self.send_to_stderr(msg_formatted.get_stderr_output()).await;
326
327        if self.stream.is_connected() == false
328        {
329            // open connection
330            match self.connectlog().await
331            {
332                Ok(_) => {},
333                Err(e) =>
334                {
335                    self.send_to_stderr( &[Cow::Owned(e.into_inner())] ).await;
336                    return;
337                }
338            }
339        }
340        
341        let fullmsg = msg_formatted.concat();
342
343
344        // There are two possible scenarios when send may fail:
345        // 1. syslog temporary unavailable
346        // 2. syslog out of buffer space
347        // If we are connected to priv socket then in case of 1 we reopen connection
348        //      and retry once.
349        // If we are connected to unpriv then in case of 2 repeatedly retrying to send
350        //      until syslog socket buffer space will be cleared
351
352        loop
353        {
354            match self.stream.send(fullmsg.as_bytes()).await
355            {
356                Ok(_) => return,
357                Err(err) =>
358                {   
359                    if self.get_tap_type().is_network() == false
360                    {
361                        if let Some(libc::ENOBUFS) = err.raw_os_error()
362                        {
363                            // scenario 2
364                            if self.get_tap_type().is_priv() == true
365                            {
366                                break;
367                            }
368
369                            sleep(Duration::from_micros(1)).await;
370                        }
371                        else
372                        {
373                            // scenario 1
374                            let _ = self.disconnectlog().await;
375                            match self.connectlog().await
376                            {
377                                Ok(_) => {},
378                                Err(_e) => break,
379                            }
380
381                            // if resend will fail then probably the scn 2 will take place
382                        } 
383                    }
384                    else
385                    {
386                        let _ = self.disconnectlog().await;
387                        match self.connectlog().await
388                        {
389                            Ok(_) => {},
390                            Err(_e) => break,
391                        }
392                    }  
393                }
394            }
395        } // loop
396
397
398        // If program reached this point then transmission over socket failed.
399        // Try to output message to console
400
401        self.send_to_syscons(msg_formatted.get_stderr_output()).await;
402    }
403}