xref: /third_party/NuttX/fs/driver/fs_devsyslog.c (revision beacf11b)
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
78enum 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
89struct 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
107static struct syslog_dev_s g_sysdev;
108static 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
125static 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
169static 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
190static 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
209static 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
240int 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
358errout_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
377int 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
539errout_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