1 /****************************************************************************
2  * fs/driver/fs_devsyslog.c
3  *
4  * Copyright (c) 2023 Huawei Device Co., Ltd. All rights reserved.
5  * Based on NuttX originally written by Gregory Nutt
6  *
7  *   Copyright (C) 2012 Gregory Nutt. All rights reserved.
8  *   Author: Gregory Nutt <gnutt@nuttx.org>
9  *
10  * Redistribution and use in source and binary forms, with or without
11  * modification, are permitted provided that the following conditions
12  * are met:
13  *
14  * 1. Redistributions of source code must retain the above copyright
15  *    notice, this list of conditions and the following disclaimer.
16  * 2. Redistributions in binary form must reproduce the above copyright
17  *    notice, this list of conditions and the following disclaimer in
18  *    the documentation and/or other materials provided with the
19  *    distribution.
20  * 3. Neither the name NuttX nor the names of its contributors may be
21  *    used to endorse or promote products derived from this software
22  *    without specific prior written permission.
23  *
24  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
25  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
26  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
27  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
28  * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
29  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
30  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
31  * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
32  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
33  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
34  * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
35  * POSSIBILITY OF SUCH DAMAGE.
36  *
37  ****************************************************************************/
38 
39 /****************************************************************************
40  * Included Files
41  ****************************************************************************/
42 
43 #include "los_task.h"
44 #include "vfs_config.h"
45 #include "sys/types.h"
46 #include "stdint.h"
47 #include "stdio.h"
48 #include "unistd.h"
49 #include "fcntl.h"
50 #include "semaphore.h"
51 #include "assert.h"
52 #include "fs/driver.h"
53 #include "inode/inode.h"
54 
55 #if defined(CONFIG_SYSLOG) && defined(CONFIG_SYSLOG_CHAR)
56 
57 /****************************************************************************
58  * Pre-processor Definitions
59  ****************************************************************************/
60 
61 /* Open the device/file write-only, try to create (file) it if it doesn't
62  * exist, if the file that already exists, then append the new log data to
63  * end of the file.
64  */
65 
66 #define SYSLOG_OFLAGS (O_WRONLY | O_CREAT | O_APPEND)
67 
68 /* An invalid thread ID */
69 
70 #define NO_HOLDER     ((pid_t)-1)
71 
72 /****************************************************************************
73  * Private Types
74  ****************************************************************************/
75 
76 /* This enumeration represents the state of the SYSLOG device interface */
77 
78 enum syslog_state_e
79 {
80   SYSLOG_UNINITIALIZED = 0, /* SYSLOG has not been initialized */
81   SYSLOG_INITIALIZING,      /* SYSLOG is being initialized */
82   SYSLOG_REOPEN,            /* SYSLOG open failed... try again later */
83   SYSLOG_FAILURE,           /* SYSLOG open failed... don't try again */
84   SYSLOG_OPENED,            /* SYSLOG device is open and ready to use */
85 };
86 
87 /* This structure contains all SYSLOGing state information */
88 
89 struct syslog_dev_s
90 {
91   uint8_t     sl_state;     /* See enum syslog_state_e */
92   sem_t       sl_sem;       /* Enforces mutually exclusive access */
93   pid_t       sl_holder;    /* PID of the thread that holds the semaphore */
94   struct file sl_file;      /* The syslog file structure */
95 };
96 
97 /****************************************************************************
98  * Private Function Prototypes
99  ****************************************************************************/
100 
101 /****************************************************************************
102  * Private Data
103  ****************************************************************************/
104 
105 /* This is the device structure for the console or syslogging function. */
106 
107 static struct syslog_dev_s g_sysdev;
108 static const uint8_t g_syscrlf[2] =
109 {
110   '\r', '\n'
111 };
112 
113 /****************************************************************************
114  * Private Functions
115  ****************************************************************************/
116 
117 /****************************************************************************
118  * Name: syslog_takesem
119  *
120  * Description:
121  *   Write to the syslog device
122  *
123  ****************************************************************************/
124 
syslog_takesem(void)125 static inline int syslog_takesem(void)
126 {
127   pid_t me = getpid();
128   int ret;
129 
130   /* Does this thread already hold the semaphore?  That could happen if
131    * we wer called recursively, i.e., if the logic kicked off by
132    * syslog_write() where to generate more debug output.  Return an error
133    * in that case.
134    */
135 
136   if (g_sysdev.sl_holder == me)
137     {
138       /* Return an error (instead of deadlocking) */
139 
140       return -EWOULDBLOCK;
141     }
142 
143   /* Either the semaphore is available or is currently held by another
144    * thread.  Wait for it to become available.
145    */
146 
147   ret = sem_wait(&g_sysdev.sl_sem);
148   if (ret < 0)
149     {
150       return -get_errno();
151     }
152 
153   /* We hold the semaphore.  We can safely mark ourself as the holder
154    * of the semaphore.
155    */
156 
157   g_sysdev.sl_holder = me;
158   return OK;
159 }
160 
161 /****************************************************************************
162  * Name: syslog_givesem
163  *
164  * Description:
165  *   Write to the syslog device
166  *
167  ****************************************************************************/
168 
syslog_givesem(void)169 static inline void syslog_givesem(void)
170 {
171 #ifdef CONFIG_DEBUG
172   pid_t me = getpid();
173   DEBUGASSERT(g_sysdev.sl_holder == me);
174 #endif
175 
176   /* Relinquish the semaphore */
177 
178   g_sysdev.sl_holder = NO_HOLDER;
179   (void)sem_post(&g_sysdev.sl_sem);
180 }
181 
182 /****************************************************************************
183  * Name: syslog_write
184  *
185  * Description:
186  *   Write to the syslog device
187  *
188  ****************************************************************************/
189 
syslog_write(const void *buf, size_t nbytes)190 static inline ssize_t syslog_write(const void *buf, size_t nbytes)
191 {
192   struct inode *inode_ptr;
193 
194   /* Let the driver perform the write */
195 
196   inode_ptr = g_sysdev.sl_file.f_inode;
197   return inode_ptr->u.i_ops->write(&g_sysdev.sl_file, (const char *)buf, nbytes);
198 }
199 
200 /****************************************************************************
201  * Name: syslog_flush
202  *
203  * Description:
204  *   Flush any buffer data in the file system to media.
205  *
206  ****************************************************************************/
207 
208 #ifndef CONFIG_DISABLE_MOUNTPOINT
syslog_flush(void)209 static inline void syslog_flush(void)
210 {
211   struct inode *inode_ptr = g_sysdev.sl_file.f_inode;
212 
213   /* Is this a mountpoint? Does it support the sync method? */
214 
215   if (INODE_IS_MOUNTPT(inode_ptr) && inode_ptr->u.i_mops->sync)
216     {
217       /* Yes... synchronize to the stream */
218 
219       (void)inode_ptr->u.i_mops->sync(&g_sysdev.sl_file);
220     }
221 }
222 #endif
223 
224 /****************************************************************************
225  * Public Functions
226  ****************************************************************************/
227 
228 /****************************************************************************
229  * Name: syslog_initialize
230  *
231  * Description:
232  *   Initialize to use the character device (or file) at
233  *   CONFIG_SYSLOG_DEVPATH as the SYSLOG sink.
234  *
235  *   NOTE that this implementation excludes using a network connection as
236  *   SYSLOG device.  That would be a good extension.
237  *
238  ****************************************************************************/
239 
syslog_initialize(void)240 int syslog_initialize(void)
241 {
242   struct inode   *inode_ptr;
243   const char     *relpath = NULL;
244   int                ret;
245   struct inode_search_s desc;
246 
247   /* At this point, the only expected states are SYSLOG_UNINITIALIZED or
248    * SYSLOG_REOPEN..  Not SYSLOG_INITIALIZING, SYSLOG_FAILURE, SYSLOG_OPENED.
249    */
250 
251   DEBUGASSERT(g_sysdev.sl_state == SYSLOG_UNINITIALIZED ||
252               g_sysdev.sl_state == SYSLOG_REOPEN);
253 
254   g_sysdev.sl_state = SYSLOG_INITIALIZING;
255 
256   /* Try to open the device.
257    *
258    * Note that we cannot just call open.  The syslog device must work on all
259    * threads.  Open returns a file descriptor that is valid only for the
260    * task that opened the device (and its pthread children).  Instead, we
261    * essentially re-implement the guts of open() here so that we can get to
262    * the thread-independent structures of the inode.
263    */
264 
265   /* Get an inode for this file/device */
266 
267   SETUP_SEARCH(&desc, CONFIG_SYSLOG_DEVPATH, false);
268   ret = inode_find(&desc);
269 
270   /* Get the search results */
271 
272   if (ret < 0)
273     {
274       /* The inode was not found.  In this case, we will attempt to re-open
275        * the device repeatedly.  The assumption is that the device path is
276        * valid but that the driver has not yet been registered.
277        */
278 
279       g_sysdev.sl_state = SYSLOG_REOPEN;
280       return -EACCES;
281     }
282   inode_ptr = desc.node;
283   relpath = desc.relpath;
284 
285   /* Verify that the inode is valid and either a character driver or a
286    * mountpoint.
287    */
288 
289 #ifndef CONFIG_DISABLE_MOUNTPOINT
290   if ((!INODE_IS_DRIVER(inode_ptr) && !INODE_IS_MOUNTPT(inode_ptr)))
291 #else
292   if (!INODE_IS_DRIVER(inode_ptr))
293 #endif
294     {
295       ret = -ENXIO;
296       goto errout_with_inode;
297     }
298 
299   /* Make sure that the "entity" at this inode supports write access */
300 
301   if (!inode_ptr->u.i_ops || !inode_ptr->u.i_ops->write)
302     {
303       ret = -EACCES;
304       goto errout_with_inode;
305     }
306 
307   /* Initialize the file structure */
308 
309   g_sysdev.sl_file.f_oflags = SYSLOG_OFLAGS;
310   g_sysdev.sl_file.f_pos    = 0;
311   g_sysdev.sl_file.f_inode  = inode_ptr;
312 
313   /* Perform the low-level open operation. */
314 
315   ret = OK;
316   if (inode_ptr->u.i_ops->open)
317     {
318       /* Is the inode a mountpoint? */
319 
320 #ifndef CONFIG_DISABLE_MOUNTPOINT
321       if (INODE_IS_MOUNTPT(inode_ptr))
322         {
323           /* Yes.  Open the device write-only, try to create it if it
324            * doesn't exist, if the file that already exists, then append the
325            * new log data to end of the file.
326            */
327 
328           ret = inode_ptr->u.i_mops->open(&g_sysdev.sl_file, relpath,
329                                           SYSLOG_OFLAGS, 0666);
330         }
331 
332       /* No... then it must be a character driver in the NuttX pseudo-
333        * file system.
334        */
335 
336       else
337 #endif
338         {
339           ret = inode_ptr->u.i_ops->open(&g_sysdev.sl_file);
340         }
341     }
342 
343   /* Was the file/device successfully opened? */
344 
345   if (ret < 0)
346     {
347       ret = -ret;
348       goto errout_with_inode;
349     }
350 
351   /* The SYSLOG device is open and ready for writing. */
352 
353   (void)sem_init(&g_sysdev.sl_sem, 0, 1);
354   g_sysdev.sl_holder = NO_HOLDER;
355   g_sysdev.sl_state  = SYSLOG_OPENED;
356   return OK;
357 
358 errout_with_inode:
359   inode_release(inode_ptr);
360   g_sysdev.sl_state = SYSLOG_FAILURE;
361   return ret;
362 }
363 
364 /****************************************************************************
365  * Name: syslog_putc
366  *
367  * Description:
368  *   This is the low-level system logging interface.  The debugging/syslogging
369  *   interfaces are syslog() and lowsyslog().  The difference is is that
370  *   the syslog() function writes to syslogging device (usually fd=1, stdout)
371  *   whereas lowsyslog() uses a lower level interface that works from
372  *   interrupt handlers.  This function is a a low-level interface used to
373  *   implement lowsyslog().
374  *
375  ****************************************************************************/
376 
syslog_putc(int ch)377 int syslog_putc(int ch)
378 {
379   ssize_t nbytes;
380   uint8_t uch;
381   int errcode;
382   int ret;
383 
384   /* Ignore any output:
385    *
386    * (1) Before the SYSLOG device has been initialized.  This could happen
387    *     from debug output that occurs early in the boot sequence before
388    *     syslog_initialize() is called (SYSLOG_UNINITIALIZED).
389    * (2) While the device is being initialized.  The case could happen if
390    *     debug output is generated while syslog_initialize() executes
391    *     (SYSLOG_INITIALIZING).
392    * (3) While we are generating SYSLOG output.  The case could happen if
393    *     debug output is generated while syslog_putc() executes
394    *     (This case is actually handled inside of syslog_semtake()).
395    * (4) Any debug output generated from interrupt handlers.  A disadvantage
396    *     of using the generic character device for the SYSLOG is that it
397    *     cannot handle debug output generated from interrupt level handlers.
398    * (5) Any debug output generated from the IDLE loop.  The character
399    *     driver interface is blocking and the IDLE thread is not permitted
400    *     to block.
401    * (6) If an irrecoverable failure occurred during initialization.  In
402    *     this case, we won't ever bother to try again (ever).
403    *
404    * NOTE: That the third case is different.  It applies only to the thread
405    * that currently holds the sl_sem sempaphore.  Other threads should wait.
406    * that is why that case is handled in syslog_semtake().
407    */
408 
409   /* Cases (4) and (5) */
410 
411   if (OS_INT_ACTIVE || getpid() == 0)
412     {
413       errcode = ENOSYS;
414       goto errout_with_errcode;
415     }
416 
417   /* We can save checks in the usual case:  That after the SYSLOG device
418    * has been successfully opened.
419    */
420 
421   if (g_sysdev.sl_state != SYSLOG_OPENED)
422     {
423       /* Case (1) and (2) */
424 
425       if (g_sysdev.sl_state == SYSLOG_UNINITIALIZED ||
426           g_sysdev.sl_state == SYSLOG_INITIALIZING)
427        {
428          errcode = EAGAIN; /* Can't access the SYSLOG now... maybe next time? */
429          goto errout_with_errcode;
430        }
431 
432       /* Case (6) */
433 
434       if (g_sysdev.sl_state == SYSLOG_FAILURE)
435         {
436           errcode = ENXIO;  /* There is no SYSLOG device */
437           goto errout_with_errcode;
438         }
439 
440       /* syslog_initialize() is called as soon as enough of the operating
441        * system is in place to support the open operation... but it is
442        * possible that the SYSLOG device is not yet registered at that time.
443        * In this case, we know that the system is sufficiently initialized
444        * to support an attempt to re-open the SYSLOG device.
445        *
446        * NOTE that the scheduler is locked.  That is because we do not have
447        * fully initialized semaphore capability until the SYSLOG device is
448        * successfully initialized
449        */
450 
451       LOS_TaskLock();
452       if (g_sysdev.sl_state == SYSLOG_REOPEN)
453         {
454           /* Try again to initialize the device.  We may do this repeatedly
455            * because the log device might be something that was not ready
456            * the first time that syslog_initializee() was called (such as a
457            * USB serial device that has not yet been connected or a file in
458            * an NFS mounted file system that has not yet been mounted).
459            */
460 
461           ret = syslog_initialize();
462           if (ret < 0)
463             {
464               LOS_TaskUnlock();
465               errcode = -ret;
466               goto errout_with_errcode;
467             }
468         }
469 
470       LOS_TaskUnlock();
471       DEBUGASSERT(g_sysdev.sl_state == SYSLOG_OPENED);
472     }
473 
474   /* Ignore carriage returns */
475 
476   if (ch == '\r')
477     {
478       return ch;
479     }
480 
481   /* The syslog device is ready for writing and we have something of
482    * value to write.
483    */
484 
485   ret = syslog_takesem();
486   if (ret < 0)
487     {
488       /* We probably already hold the semaphore and were probably
489        * re-entered by the logic kicked off by syslog_write().
490        * We might also have been interrupted by a signal.  Either
491        * way, we are outta here.
492        */
493 
494       errcode = -ret;
495       goto errout_with_errcode;
496     }
497 
498   /* Pre-pend a newline with a carriage return. */
499 
500   if (ch == '\n')
501     {
502       /* Write the CR-LF sequence */
503 
504       nbytes = syslog_write(g_syscrlf, 2);
505 
506       /* Synchronize the file when each CR-LF is encountered (i.e.,
507        * implements line buffering always).
508        */
509 
510 #ifndef CONFIG_DISABLE_MOUNTPOINT
511       if (nbytes > 0)
512         {
513           syslog_flush();
514         }
515 #endif
516     }
517   else
518     {
519       /* Write the non-newline character (and don't flush) */
520 
521       uch = (uint8_t)ch;
522       nbytes = syslog_write(&uch, 1);
523     }
524 
525   syslog_givesem();
526 
527   /* Check if the write was successful.  If not, nbytes will be
528    * a negated errno value.
529    */
530 
531   if (nbytes < 0)
532     {
533       errcode = -ret;
534       goto errout_with_errcode;
535     }
536 
537   return ch;
538 
539 errout_with_errcode:
540   if (errcode != 0)
541     {
542       set_errno(errcode);
543     }
544   return EOF;
545 }
546 
547 #endif /* CONFIG_SYSLOG && CONFIG_SYSLOG_CHAR */
548