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