fs_devsyslog.c 16.1 KB
Newer Older
W
wenjun 已提交
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
/****************************************************************************
 * fs/driver/fs_devsyslog.c
 *
 *   Copyright (C) 2012 Gregory Nutt. All rights reserved.
 *   Author: Gregory Nutt <gnutt@nuttx.org>
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 * 3. Neither the name NuttX nor the names of its contributors may be
 *    used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
 * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 ****************************************************************************/

/****************************************************************************
 * Included Files
 ****************************************************************************/

#include "los_task.h"
#include "vfs_config.h"
#include "sys/types.h"
#include "stdint.h"
#include "stdio.h"
#include "unistd.h"
#include "fcntl.h"
#include "semaphore.h"
#include "assert.h"
M
mucor 已提交
49
#include "fs/driver.h"
W
wenjun 已提交
50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186
#include "inode/inode.h"

#if defined(CONFIG_SYSLOG) && defined(CONFIG_SYSLOG_CHAR)

/****************************************************************************
 * Pre-processor Definitions
 ****************************************************************************/

/* Open the device/file write-only, try to create (file) it if it doesn't
 * exist, if the file that already exists, then append the new log data to
 * end of the file.
 */

#define SYSLOG_OFLAGS (O_WRONLY | O_CREAT | O_APPEND)

/* An invalid thread ID */

#define NO_HOLDER     ((pid_t)-1)

/****************************************************************************
 * Private Types
 ****************************************************************************/

/* This enumeration represents the state of the SYSLOG device interface */

enum syslog_state_e
{
  SYSLOG_UNINITIALIZED = 0, /* SYSLOG has not been initialized */
  SYSLOG_INITIALIZING,      /* SYSLOG is being initialized */
  SYSLOG_REOPEN,            /* SYSLOG open failed... try again later */
  SYSLOG_FAILURE,           /* SYSLOG open failed... don't try again */
  SYSLOG_OPENED,            /* SYSLOG device is open and ready to use */
};

/* This structure contains all SYSLOGing state information */

struct syslog_dev_s
{
  uint8_t     sl_state;     /* See enum syslog_state_e */
  sem_t       sl_sem;       /* Enforces mutually exclusive access */
  pid_t       sl_holder;    /* PID of the thread that holds the semaphore */
  struct file sl_file;      /* The syslog file structure */
};

/****************************************************************************
 * Private Function Prototypes
 ****************************************************************************/

/****************************************************************************
 * Private Data
 ****************************************************************************/

/* This is the device structure for the console or syslogging function. */

static struct syslog_dev_s g_sysdev;
static const uint8_t g_syscrlf[2] =
{
  '\r', '\n'
};

/****************************************************************************
 * Private Functions
 ****************************************************************************/

/****************************************************************************
 * Name: syslog_takesem
 *
 * Description:
 *   Write to the syslog device
 *
 ****************************************************************************/

static inline int syslog_takesem(void)
{
  pid_t me = getpid();
  int ret;

  /* Does this thread already hold the semaphore?  That could happen if
   * we wer called recursively, i.e., if the logic kicked off by
   * syslog_write() where to generate more debug output.  Return an error
   * in that case.
   */

  if (g_sysdev.sl_holder == me)
    {
      /* Return an error (instead of deadlocking) */

      return -EWOULDBLOCK;
    }

  /* Either the semaphore is available or is currently held by another
   * thread.  Wait for it to become available.
   */

  ret = sem_wait(&g_sysdev.sl_sem);
  if (ret < 0)
    {
      return -get_errno();
    }

  /* We hold the semaphore.  We can safely mark ourself as the holder
   * of the semaphore.
   */

  g_sysdev.sl_holder = me;
  return OK;
}

/****************************************************************************
 * Name: syslog_givesem
 *
 * Description:
 *   Write to the syslog device
 *
 ****************************************************************************/

static inline void syslog_givesem(void)
{
#ifdef CONFIG_DEBUG
  pid_t me = getpid();
  DEBUGASSERT(g_sysdev.sl_holder == me);
#endif

  /* Relinquish the semaphore */

  g_sysdev.sl_holder = NO_HOLDER;
  (void)sem_post(&g_sysdev.sl_sem);
}

/****************************************************************************
 * Name: syslog_write
 *
 * Description:
 *   Write to the syslog device
 *
 ****************************************************************************/

M
mucor 已提交
187
static inline ssize_t syslog_write(const void *buf, size_t nbytes)
W
wenjun 已提交
188
{
M
mucor 已提交
189
  struct inode *inode_ptr;
W
wenjun 已提交
190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207

  /* Let the driver perform the write */

  inode_ptr = g_sysdev.sl_file.f_inode;
  return inode_ptr->u.i_ops->write(&g_sysdev.sl_file, (const char *)buf, nbytes);
}

/****************************************************************************
 * Name: syslog_flush
 *
 * Description:
 *   Flush any buffer data in the file system to media.
 *
 ****************************************************************************/

#ifndef CONFIG_DISABLE_MOUNTPOINT
static inline void syslog_flush(void)
{
M
mucor 已提交
208
  struct inode *inode_ptr = g_sysdev.sl_file.f_inode;
W
wenjun 已提交
209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238

  /* Is this a mountpoint? Does it support the sync method? */

  if (INODE_IS_MOUNTPT(inode_ptr) && inode_ptr->u.i_mops->sync)
    {
      /* Yes... synchronize to the stream */

      (void)inode_ptr->u.i_mops->sync(&g_sysdev.sl_file);
    }
}
#endif

/****************************************************************************
 * Public Functions
 ****************************************************************************/

/****************************************************************************
 * Name: syslog_initialize
 *
 * Description:
 *   Initialize to use the character device (or file) at
 *   CONFIG_SYSLOG_DEVPATH as the SYSLOG sink.
 *
 *   NOTE that this implementation excludes using a network connection as
 *   SYSLOG device.  That would be a good extension.
 *
 ****************************************************************************/

int syslog_initialize(void)
{
M
mucor 已提交
239 240
  struct inode   *inode_ptr;
  const char     *relpath = NULL;
W
wenjun 已提交
241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544
  int                ret;
  struct inode_search_s desc;

  /* At this point, the only expected states are SYSLOG_UNINITIALIZED or
   * SYSLOG_REOPEN..  Not SYSLOG_INITIALIZING, SYSLOG_FAILURE, SYSLOG_OPENED.
   */

  DEBUGASSERT(g_sysdev.sl_state == SYSLOG_UNINITIALIZED ||
              g_sysdev.sl_state == SYSLOG_REOPEN);

  g_sysdev.sl_state = SYSLOG_INITIALIZING;

  /* Try to open the device.
   *
   * Note that we cannot just call open.  The syslog device must work on all
   * threads.  Open returns a file descriptor that is valid only for the
   * task that opened the device (and its pthread children).  Instead, we
   * essentially re-implement the guts of open() here so that we can get to
   * the thread-independent structures of the inode.
   */

  /* Get an inode for this file/device */

  SETUP_SEARCH(&desc, CONFIG_SYSLOG_DEVPATH, false);
  ret = inode_find(&desc);

  /* Get the search results */

  if (ret < 0)
    {
      /* The inode was not found.  In this case, we will attempt to re-open
       * the device repeatedly.  The assumption is that the device path is
       * valid but that the driver has not yet been registered.
       */

      g_sysdev.sl_state = SYSLOG_REOPEN;
      return -EACCES;
    }
  inode_ptr = desc.node;
  relpath = desc.relpath;

  /* Verify that the inode is valid and either a character driver or a
   * mountpoint.
   */

#ifndef CONFIG_DISABLE_MOUNTPOINT
  if ((!INODE_IS_DRIVER(inode_ptr) && !INODE_IS_MOUNTPT(inode_ptr)))
#else
  if (!INODE_IS_DRIVER(inode_ptr))
#endif
    {
      ret = -ENXIO;
      goto errout_with_inode;
    }

  /* Make sure that the "entity" at this inode supports write access */

  if (!inode_ptr->u.i_ops || !inode_ptr->u.i_ops->write)
    {
      ret = -EACCES;
      goto errout_with_inode;
    }

  /* Initialize the file structure */

  g_sysdev.sl_file.f_oflags = SYSLOG_OFLAGS;
  g_sysdev.sl_file.f_pos    = 0;
  g_sysdev.sl_file.f_inode  = inode_ptr;

  /* Perform the low-level open operation. */

  ret = OK;
  if (inode_ptr->u.i_ops->open)
    {
      /* Is the inode a mountpoint? */

#ifndef CONFIG_DISABLE_MOUNTPOINT
      if (INODE_IS_MOUNTPT(inode_ptr))
        {
          /* Yes.  Open the device write-only, try to create it if it
           * doesn't exist, if the file that already exists, then append the
           * new log data to end of the file.
           */

          ret = inode_ptr->u.i_mops->open(&g_sysdev.sl_file, relpath,
                                          SYSLOG_OFLAGS, 0666);
        }

      /* No... then it must be a character driver in the NuttX pseudo-
       * file system.
       */

      else
#endif
        {
          ret = inode_ptr->u.i_ops->open(&g_sysdev.sl_file);
        }
    }

  /* Was the file/device successfully opened? */

  if (ret < 0)
    {
      ret = -ret;
      goto errout_with_inode;
    }

  /* The SYSLOG device is open and ready for writing. */

  (void)sem_init(&g_sysdev.sl_sem, 0, 1);
  g_sysdev.sl_holder = NO_HOLDER;
  g_sysdev.sl_state  = SYSLOG_OPENED;
  return OK;

errout_with_inode:
  inode_release(inode_ptr);
  g_sysdev.sl_state = SYSLOG_FAILURE;
  return ret;
}

/****************************************************************************
 * Name: syslog_putc
 *
 * Description:
 *   This is the low-level system logging interface.  The debugging/syslogging
 *   interfaces are syslog() and lowsyslog().  The difference is is that
 *   the syslog() function writes to syslogging device (usually fd=1, stdout)
 *   whereas lowsyslog() uses a lower level interface that works from
 *   interrupt handlers.  This function is a a low-level interface used to
 *   implement lowsyslog().
 *
 ****************************************************************************/

int syslog_putc(int ch)
{
  ssize_t nbytes;
  uint8_t uch;
  int errcode;
  int ret;

  /* Ignore any output:
   *
   * (1) Before the SYSLOG device has been initialized.  This could happen
   *     from debug output that occurs early in the boot sequence before
   *     syslog_initialize() is called (SYSLOG_UNINITIALIZED).
   * (2) While the device is being initialized.  The case could happen if
   *     debug output is generated while syslog_initialize() executes
   *     (SYSLOG_INITIALIZING).
   * (3) While we are generating SYSLOG output.  The case could happen if
   *     debug output is generated while syslog_putc() executes
   *     (This case is actually handled inside of syslog_semtake()).
   * (4) Any debug output generated from interrupt handlers.  A disadvantage
   *     of using the generic character device for the SYSLOG is that it
   *     cannot handle debug output generated from interrupt level handlers.
   * (5) Any debug output generated from the IDLE loop.  The character
   *     driver interface is blocking and the IDLE thread is not permitted
   *     to block.
   * (6) If an irrecoverable failure occurred during initialization.  In
   *     this case, we won't ever bother to try again (ever).
   *
   * NOTE: That the third case is different.  It applies only to the thread
   * that currently holds the sl_sem sempaphore.  Other threads should wait.
   * that is why that case is handled in syslog_semtake().
   */

  /* Cases (4) and (5) */

  if (OS_INT_ACTIVE || getpid() == 0)
    {
      errcode = ENOSYS;
      goto errout_with_errcode;
    }

  /* We can save checks in the usual case:  That after the SYSLOG device
   * has been successfully opened.
   */

  if (g_sysdev.sl_state != SYSLOG_OPENED)
    {
      /* Case (1) and (2) */

      if (g_sysdev.sl_state == SYSLOG_UNINITIALIZED ||
          g_sysdev.sl_state == SYSLOG_INITIALIZING)
       {
         errcode = EAGAIN; /* Can't access the SYSLOG now... maybe next time? */
         goto errout_with_errcode;
       }

      /* Case (6) */

      if (g_sysdev.sl_state == SYSLOG_FAILURE)
        {
          errcode = ENXIO;  /* There is no SYSLOG device */
          goto errout_with_errcode;
        }

      /* syslog_initialize() is called as soon as enough of the operating
       * system is in place to support the open operation... but it is
       * possible that the SYSLOG device is not yet registered at that time.
       * In this case, we know that the system is sufficiently initialized
       * to support an attempt to re-open the SYSLOG device.
       *
       * NOTE that the scheduler is locked.  That is because we do not have
       * fully initialized semaphore capability until the SYSLOG device is
       * successfully initialized
       */

      LOS_TaskLock();
      if (g_sysdev.sl_state == SYSLOG_REOPEN)
        {
          /* Try again to initialize the device.  We may do this repeatedly
           * because the log device might be something that was not ready
           * the first time that syslog_initializee() was called (such as a
           * USB serial device that has not yet been connected or a file in
           * an NFS mounted file system that has not yet been mounted).
           */

          ret = syslog_initialize();
          if (ret < 0)
            {
              LOS_TaskUnlock();
              errcode = -ret;
              goto errout_with_errcode;
            }
        }

      LOS_TaskUnlock();
      DEBUGASSERT(g_sysdev.sl_state == SYSLOG_OPENED);
    }

  /* Ignore carriage returns */

  if (ch == '\r')
    {
      return ch;
    }

  /* The syslog device is ready for writing and we have something of
   * value to write.
   */

  ret = syslog_takesem();
  if (ret < 0)
    {
      /* We probably already hold the semaphore and were probably
       * re-entered by the logic kicked off by syslog_write().
       * We might also have been interrupted by a signal.  Either
       * way, we are outta here.
       */

      errcode = -ret;
      goto errout_with_errcode;
    }

  /* Pre-pend a newline with a carriage return. */

  if (ch == '\n')
    {
      /* Write the CR-LF sequence */

      nbytes = syslog_write(g_syscrlf, 2);

      /* Synchronize the file when each CR-LF is encountered (i.e.,
       * implements line buffering always).
       */

#ifndef CONFIG_DISABLE_MOUNTPOINT
      if (nbytes > 0)
        {
          syslog_flush();
        }
#endif
    }
  else
    {
      /* Write the non-newline character (and don't flush) */

      uch = (uint8_t)ch;
      nbytes = syslog_write(&uch, 1);
    }

  syslog_givesem();

  /* Check if the write was successful.  If not, nbytes will be
   * a negated errno value.
   */

  if (nbytes < 0)
    {
      errcode = -ret;
      goto errout_with_errcode;
    }

  return ch;

errout_with_errcode:
  if (errcode != 0)
    {
      set_errno(errcode);
    }
  return EOF;
}

#endif /* CONFIG_SYSLOG && CONFIG_SYSLOG_CHAR */