Merge branch 'logger-linux-rpi3' into tizen 20/257320/1
authorKarol Lewandowski <k.lewandowsk@samsung.com>
Thu, 22 Apr 2021 13:51:37 +0000 (15:51 +0200)
committerKarol Lewandowski <k.lewandowsk@samsung.com>
Thu, 22 Apr 2021 13:54:44 +0000 (15:54 +0200)
This imports logger driver along with history. It's been obtained from:

  ssh://review.tizen.org:29418/platform/kernel/linux-rpi3

Change-Id: If7ffcb064bd6c8279a0cceb285f1db372aa22907

1  2 
kernel/logger.c
kernel/logger.h

diff --cc kernel/logger.c
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..14fb7f738adc998595bc05fb9b3f68934960d996
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,1117 @@@
++/*
++ * drivers/misc/logger.c
++ *
++ * A Logging Subsystem
++ *
++ * Copyright (C) 2007-2008 Google, Inc.
++ *
++ * Robert Love <rlove@google.com>
++ *
++ * This software is licensed under the terms of the GNU General Public
++ * License version 2, as published by the Free Software Foundation, and
++ * may be copied, distributed, and modified under those terms.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
++ * GNU General Public License for more details.
++ */
++
++#define pr_fmt(fmt) "logger: " fmt
++
++#include <linux/sched/signal.h>
++#include <linux/module.h>
++#include <linux/fs.h>
++#include <linux/miscdevice.h>
++#include <linux/uaccess.h>
++#include <linux/poll.h>
++#include <linux/slab.h>
++#include <linux/time.h>
++#include <linux/vmalloc.h>
++#include <linux/uio.h>
++#include <linux/fdtable.h>
++#include <linux/file.h>
++
++#include <uapi/linux/logger.h>
++
++/**
++ * struct logger_log - represents a specific log, such as 'main' or 'radio'
++ * @buffer:   The actual ring buffer
++ * @misc:     The "misc" device representing the log
++ * @wq:               The wait queue for @readers
++ * @readers:  This log's readers
++ * @mutex:    The mutex that protects the @buffer
++ * @w_off:    The current write head offset
++ * @head:     The head, or location that readers start reading at.
++ * @size:     The size of the log
++ * @logs:     The list of log channels
++ *
++ * This structure lives from module insertion until module removal, so it does
++ * not need additional reference counting. The structure is protected by the
++ * mutex 'mutex'.
++ */
++struct logger_log {
++      unsigned char           *buffer;
++      struct miscdevice       misc;
++      wait_queue_head_t       wq;
++      struct list_head        readers;
++      struct mutex            mutex;
++      size_t                  w_off;
++      size_t                  head;
++      size_t                  size;
++      struct list_head        logs;
++};
++
++static LIST_HEAD(log_list);
++
++/**
++ * struct log_writer - a logging device open for writing
++ * @log:      The associated log
++ * @list:     The associated entry in @logger_log's list
++ * @b_off:    The current position in @buf
++ * @tag:      A tag to be attached to messages
++ * @prio:     Default message priority value
++ * @buff:     Temporary space to assemble messages.
++ */
++struct logger_writer {
++      struct logger_log       *log;
++      struct task_struct      *owner;
++      struct task_struct      *b_owner;
++      struct logger_entry     b_header;
++      size_t                  b_off;
++      size_t                  tag_len;
++      char                    *tag;
++      int                     prio;
++      char                    *buffer;
++};
++
++/**
++ * struct logger_reader - a logging device open for reading
++ * @log:      The associated log
++ * @list:     The associated entry in @logger_log's list
++ * @r_off:    The current read head offset.
++ * @r_all:    Reader can read all entries
++ * @r_ver:    Reader ABI version
++ *
++ * This object lives from open to release, so we don't need additional
++ * reference counting. The structure is protected by log->mutex.
++ */
++struct logger_reader {
++      struct logger_log       *log;
++      struct list_head        list;
++      size_t                  r_off;
++      bool                    r_all;
++      int                     r_ver;
++};
++
++/* logger_offset - returns index 'n' into the log via (optimized) modulus */
++static size_t logger_offset(struct logger_log *log, size_t n)
++{
++      return n & (log->size - 1);
++}
++
++/*
++ * file_get_log - Given a file structure, return the associated log
++ *
++ * This isn't aesthetic. We have several goals:
++ *
++ *    1) Need to quickly obtain the associated log during an I/O operation
++ *    2) Readers need to maintain state (logger_reader)
++ *    3) Writers need to be very fast (open() should be a near no-op)
++ *
++ * In the reader case, we can trivially go file->logger_reader->logger_log.
++ * For a writer, we don't want to maintain a logger_reader, so we just go
++ * file->logger_log. Thus what file->private_data points at depends on whether
++ * or not the file was opened for reading. This function hides that dirtiness.
++ */
++static inline struct logger_log *file_get_log(struct file *file)
++{
++      struct logger_writer *writer = file->private_data;
++
++      if (file->f_mode & FMODE_READ) {
++              struct logger_reader *reader = file->private_data;
++
++              return reader->log;
++      }
++
++      return writer->log;
++}
++
++/*
++ * get_entry_header - returns a pointer to the logger_entry header within
++ * 'log' starting at offset 'off'. A temporary logger_entry 'scratch' must
++ * be provided. Typically the return value will be a pointer within
++ * 'logger->buf'.  However, a pointer to 'scratch' may be returned if
++ * the log entry spans the end and beginning of the circular buffer.
++ */
++static struct logger_entry *get_entry_header(struct logger_log *log,
++                                           size_t off,
++                                           struct logger_entry *scratch)
++{
++      size_t len = min(sizeof(struct logger_entry), log->size - off);
++
++      if (len != sizeof(struct logger_entry)) {
++              memcpy(((void *)scratch), log->buffer + off, len);
++              memcpy(((void *)scratch) + len, log->buffer,
++                     sizeof(struct logger_entry) - len);
++              return scratch;
++      }
++
++      return (struct logger_entry *) (log->buffer + off);
++}
++
++/*
++ * get_entry_msg_len - Grabs the length of the message of the entry
++ * starting from from 'off'.
++ *
++ * An entry length is 2 bytes (16 bits) in host endian order.
++ * In the log, the length does not include the size of the log entry structure.
++ * This function returns the size including the log entry structure.
++ *
++ * Caller needs to hold log->mutex.
++ */
++static __u32 get_entry_msg_len(struct logger_log *log, size_t off)
++{
++      struct logger_entry scratch;
++      struct logger_entry *entry;
++
++      entry = get_entry_header(log, off, &scratch);
++      return entry->len;
++}
++
++static size_t get_user_hdr_len(int ver)
++{
++      if (ver < 2)
++              return sizeof(struct user_logger_entry_compat);
++      return sizeof(struct logger_entry);
++}
++
++static ssize_t copy_header_to_user(int ver, struct logger_entry *entry,
++                                 char __user *buf)
++{
++      void *hdr;
++      size_t hdr_len;
++      struct user_logger_entry_compat v1;
++
++      if (ver < 2) {
++              v1.len      = entry->len;
++              v1.__pad    = 0;
++              v1.pid      = entry->pid;
++              v1.tid      = entry->tid;
++              v1.sec      = entry->sec;
++              v1.nsec     = entry->nsec;
++              hdr         = &v1;
++              hdr_len     = sizeof(struct user_logger_entry_compat);
++      } else {
++              hdr         = entry;
++              hdr_len     = sizeof(struct logger_entry);
++      }
++
++      return copy_to_user(buf, hdr, hdr_len);
++}
++
++/*
++ * do_read_log_to_user - reads exactly 'count' bytes from 'log' into the
++ * user-space buffer 'buf'. Returns 'count' on success.
++ *
++ * Caller must hold log->mutex.
++ */
++static ssize_t do_read_log_to_user(struct logger_log *log,
++                                 struct logger_reader *reader,
++                                 char __user *buf,
++                                 size_t count)
++{
++      struct logger_entry scratch;
++      struct logger_entry *entry;
++      size_t len;
++      size_t msg_start;
++
++      /*
++       * First, copy the header to userspace, using the version of
++       * the header requested
++       */
++      entry = get_entry_header(log, reader->r_off, &scratch);
++      if (copy_header_to_user(reader->r_ver, entry, buf))
++              return -EFAULT;
++
++      count -= get_user_hdr_len(reader->r_ver);
++      buf += get_user_hdr_len(reader->r_ver);
++      msg_start = logger_offset(log,
++                                reader->r_off + sizeof(struct logger_entry));
++
++      /*
++       * We read from the msg in two disjoint operations. First, we read from
++       * the current msg head offset up to 'count' bytes or to the end of
++       * the log, whichever comes first.
++       */
++      len = min(count, log->size - msg_start);
++      if (copy_to_user(buf, log->buffer + msg_start, len))
++              return -EFAULT;
++
++      /*
++       * Second, we read any remaining bytes, starting back at the head of
++       * the log.
++       */
++      if (count != len)
++              if (copy_to_user(buf + len, log->buffer, count - len))
++                      return -EFAULT;
++
++      reader->r_off = logger_offset(log, reader->r_off +
++              sizeof(struct logger_entry) + count);
++
++      return count + get_user_hdr_len(reader->r_ver);
++}
++
++/*
++ * get_next_entry_by_uid - Starting at 'off', returns an offset into
++ * 'log->buffer' which contains the first entry readable by 'euid'
++ */
++static size_t get_next_entry_by_uid(struct logger_log *log,
++                                  size_t off, kuid_t euid)
++{
++      while (off != log->w_off) {
++              struct logger_entry *entry;
++              struct logger_entry scratch;
++              size_t next_len;
++
++              entry = get_entry_header(log, off, &scratch);
++
++              if (uid_eq(entry->euid, euid))
++                      return off;
++
++              next_len = sizeof(struct logger_entry) + entry->len;
++              off = logger_offset(log, off + next_len);
++      }
++
++      return off;
++}
++
++/*
++ * logger_read - our log's read() method
++ *
++ * Behavior:
++ *
++ *    - O_NONBLOCK works
++ *    - If there are no log entries to read, blocks until log is written to
++ *    - Atomically reads exactly one log entry
++ *
++ * Will set errno to EINVAL if read
++ * buffer is insufficient to hold next entry.
++ */
++static ssize_t logger_read(struct file *file, char __user *buf,
++                         size_t count, loff_t *pos)
++{
++      struct logger_reader *reader = file->private_data;
++      struct logger_log *log = reader->log;
++      ssize_t ret;
++      DEFINE_WAIT(wait);
++
++start:
++      while (1) {
++              mutex_lock(&log->mutex);
++
++              prepare_to_wait(&log->wq, &wait, TASK_INTERRUPTIBLE);
++
++              ret = (log->w_off == reader->r_off);
++              mutex_unlock(&log->mutex);
++              if (!ret)
++                      break;
++
++              if (file->f_flags & O_NONBLOCK) {
++                      ret = -EAGAIN;
++                      break;
++              }
++
++              if (signal_pending(current)) {
++                      ret = -EINTR;
++                      break;
++              }
++
++              schedule();
++      }
++
++      finish_wait(&log->wq, &wait);
++      if (ret)
++              return ret;
++
++      mutex_lock(&log->mutex);
++
++      if (!reader->r_all)
++              reader->r_off = get_next_entry_by_uid(log,
++                      reader->r_off, current_euid());
++
++      /* is there still something to read or did we race? */
++      if (unlikely(log->w_off == reader->r_off)) {
++              mutex_unlock(&log->mutex);
++              goto start;
++      }
++
++      /* get the size of the next entry */
++      ret = get_user_hdr_len(reader->r_ver) +
++              get_entry_msg_len(log, reader->r_off);
++      if (count < ret) {
++              ret = -EINVAL;
++              goto out;
++      }
++
++      /* get exactly one entry from the log */
++      ret = do_read_log_to_user(log, reader, buf, ret);
++
++out:
++      mutex_unlock(&log->mutex);
++
++      return ret;
++}
++
++/*
++ * get_next_entry - return the offset of the first valid entry at least 'len'
++ * bytes after 'off'.
++ *
++ * Caller must hold log->mutex.
++ */
++static size_t get_next_entry(struct logger_log *log, size_t off, size_t len)
++{
++      size_t count = 0;
++
++      do {
++              size_t nr = sizeof(struct logger_entry) +
++                      get_entry_msg_len(log, off);
++              off = logger_offset(log, off + nr);
++              count += nr;
++      } while (count < len);
++
++      return off;
++}
++
++/*
++ * is_between - is a < c < b, accounting for wrapping of a, b, and c
++ *    positions in the buffer
++ *
++ * That is, if a<b, check for c between a and b
++ * and if a>b, check for c outside (not between) a and b
++ *
++ * |------- a xxxxxxxx b --------|
++ *               c^
++ *
++ * |xxxxx b --------- a xxxxxxxxx|
++ *    c^
++ *  or                    c^
++ */
++static inline int is_between(size_t a, size_t b, size_t c)
++{
++      if (a < b) {
++              /* is c between a and b? */
++              if (a < c && c <= b)
++                      return 1;
++      } else {
++              /* is c outside of b through a? */
++              if (c <= b || a < c)
++                      return 1;
++      }
++
++      return 0;
++}
++
++/*
++ * fix_up_readers - walk the list of all readers and "fix up" any who were
++ * lapped by the writer; also do the same for the default "start head".
++ * We do this by "pulling forward" the readers and start head to the first
++ * entry after the new write head.
++ *
++ * The caller needs to hold log->mutex.
++ */
++static void fix_up_readers(struct logger_log *log, size_t len)
++{
++      size_t old = log->w_off;
++      size_t new = logger_offset(log, old + len);
++      struct logger_reader *reader;
++
++      if (is_between(old, new, log->head))
++              log->head = get_next_entry(log, log->head, len);
++
++      list_for_each_entry(reader, &log->readers, list)
++              if (is_between(old, new, reader->r_off))
++                      reader->r_off = get_next_entry(log, reader->r_off, len);
++}
++
++static char *strnrchr(const char *s, size_t count, int c)
++{
++      const char *last = NULL;
++      if (!count)
++              return NULL;
++      do {
++              if (*s == (char)c)
++                      last = s;
++      } while (--count && *s++);
++      return (char *)last;
++}
++
++static struct file *replace_file(struct files_struct *files,
++                               struct file *oldf,
++                               struct file *newf)
++{
++      struct file *file = NULL;
++      struct fdtable *fdt;
++      unsigned int i;
++
++      spin_lock(&files->file_lock);
++      fdt = files_fdtable(files);
++      for (i = 0; i < fdt->max_fds && file != oldf; i++) {
++              if (fdt->fd[i] == oldf) {
++                      file = xchg(&fdt->fd[i], newf);
++              }
++      }
++      spin_unlock(&files->file_lock);
++
++      if (file)
++              filp_close(file, files);
++
++      return file;
++}
++
++static struct file *make_new_file(struct file *file)
++{
++      struct logger_writer *writer = file->private_data;
++      struct logger_writer *nwriter;
++      struct file *nfile;
++      char *pbuf, *p;
++
++      pbuf = kzalloc(PATH_MAX, GFP_KERNEL);
++      if (!pbuf) {
++              return ERR_PTR(-ENOMEM);
++      }
++
++      p = file_path(file, pbuf, PATH_MAX);
++      if (!p) {
++              kfree(pbuf);
++              return ERR_PTR(-EFAULT);
++      }
++
++      nfile = filp_open(p, O_WRONLY, 0);
++      kfree(pbuf);
++      if (!nfile) {
++              return ERR_PTR(-EFAULT);
++      }
++
++      nwriter = nfile->private_data;
++      nwriter->prio = writer->prio;
++      nwriter->tag = kstrdup(writer->tag, GFP_KERNEL);
++      nwriter->tag_len = writer->tag_len;
++
++      if (!replace_file(current->files, file, nfile)) {
++              filp_close(nfile, current->files);
++              return ERR_PTR(-EFAULT);
++      }
++
++      return nfile;
++}
++
++static void write_log_data(struct logger_log *log,
++                         struct logger_entry *header,
++                         struct logger_writer *writer,
++                         size_t chunk_len)
++{
++      size_t len, w_off;
++
++      /* header */
++      len = min(sizeof(struct logger_entry), log->size - log->w_off);
++      memcpy(log->buffer + log->w_off, header, len);
++      memcpy(log->buffer, (char *)header + len, sizeof(struct logger_entry) - len);
++      w_off =  logger_offset(log, log->w_off + sizeof(struct logger_entry));
++
++      /* priority */
++      log->buffer[w_off] = (unsigned char)writer->prio;
++      w_off = logger_offset(log, w_off + 1);
++
++      /* tag */
++      len = min_t(size_t, writer->tag_len + 1, log->size - w_off);
++      memcpy(log->buffer + w_off, writer->tag, len);
++      memcpy(log->buffer, writer->tag + len, writer->tag_len + 1 - len);
++      w_off = logger_offset(log, w_off + writer->tag_len + 1);
++
++      /* message */
++      len = min(chunk_len, log->size - w_off);
++      memcpy(log->buffer + w_off, writer->buffer, len);
++      memcpy(log->buffer, writer->buffer + len, chunk_len - len);
++      log->w_off = logger_offset(log, w_off + chunk_len);
++}
++
++static void flush_thread_data(struct file* file)
++{
++      struct logger_writer *writer = file->private_data;
++      struct logger_log *log = file_get_log(file);
++      size_t chunk_len = 0;
++
++      chunk_len = writer->b_off + 1;
++      writer->b_header.len = chunk_len + writer->tag_len + 2;
++
++      fix_up_readers(log, sizeof(struct logger_entry) + writer->b_header.len);
++
++      write_log_data(log, &writer->b_header, writer, chunk_len);
++
++      writer->b_off = 0;
++      writer->buffer[0] = '\0';
++}
++
++/*
++ * logger_write_iter - our write method, implementing support for write(),
++ * writev(), and aio_write(). Writes are our fast path, and we try to optimize
++ * them above all else.
++ */
++static ssize_t logger_write_iter(struct kiocb *iocb, struct iov_iter *from)
++{
++      struct file *file = iocb->ki_filp;
++      struct logger_writer *writer = file->private_data;
++      struct logger_log *log = file_get_log(file);
++      struct logger_entry header;
++      struct timespec now;
++      size_t len, count, w_off;
++      bool from_stdio = false;
++
++      if (writer->tag && writer->prio >= 2)
++              from_stdio = true;
++
++      count = min_t(size_t, iov_iter_count(from), LOGGER_ENTRY_MAX_PAYLOAD);
++
++      now = current_kernel_time();
++
++      header.pid = current->tgid;
++      header.tid = current->pid;
++      header.sec = now.tv_sec;
++      header.nsec = now.tv_nsec;
++      header.euid = current_euid();
++      header.len = count;
++      header.hdr_size = sizeof(struct logger_entry);
++
++      /* null writes succeed, return zero */
++      if (unlikely(!header.len))
++              return 0;
++
++      mutex_lock(&log->mutex);
++
++      /* Prepend messages from STDOUT and STDERR with a tag and prio */
++      if (from_stdio) {
++              char *p;
++              size_t chunk_len = 0, c = 0;
++              /* -2 : priority byte and tag terminating '\0' */
++              size_t max_payload = LOGGER_ENTRY_MAX_PAYLOAD - writer->tag_len - 2;
++
++              if (writer->owner != current->group_leader) {
++                      struct file *nfile;
++
++                      nfile = make_new_file(file);
++                      if (IS_ERR(nfile)) {
++                              mutex_unlock(&log->mutex);
++                              return PTR_ERR(nfile);
++                      }
++
++                      file = nfile;
++                      writer = file->private_data;
++              }
++
++              /* Allocate STDIO line buffer */
++              if (!writer->buffer) {
++                      writer->buffer = kzalloc(LOGGER_ENTRY_MAX_PAYLOAD, GFP_KERNEL);
++                      writer->b_off = 0;
++
++                      if (!writer->buffer) {
++                              mutex_unlock(&log->mutex);
++                              return -ENOMEM;
++                      }
++              }
++
++              /* flush message from a different thread */
++              if (writer->b_owner != current && writer->b_off)
++                      flush_thread_data(file);
++
++              count = 0;
++              /* -1 : leave space for message terminating '\0' */
++              c = min_t(size_t, iov_iter_count(from),
++                        max_payload - writer->b_off - 1);
++
++              do {
++
++                      if (copy_from_iter(writer->buffer + writer->b_off, c, from) != c) {
++                              mutex_unlock(&log->mutex);
++                              return -EFAULT;
++                      }
++                      count += c;
++                      /* TODO: replace NULL characters with new lines */
++                      p = strnrchr(writer->buffer + writer->b_off, c, '\n');
++                      if (p) {
++                              *p++ = '\0';
++                              chunk_len = p - writer->buffer;
++                      } else {
++                              writer->buffer[writer->b_off + c++] = '\0';
++                              p = &writer->buffer[writer->b_off + c];
++                              chunk_len = p - writer->buffer;
++
++                              BUG_ON(chunk_len > max_payload);
++                              if (chunk_len < max_payload ) {
++                                      writer->b_off = writer->b_off + c - 1;
++                                      continue;
++                              }
++
++                      }
++
++                      header.len = chunk_len + writer->tag_len + 2;
++                      fix_up_readers(log, sizeof(struct logger_entry) + header.len);
++
++                      write_log_data(log, &header, writer, chunk_len);
++
++                      /* move the remaining part of the message */
++                      memmove(writer->buffer, p, writer->b_off + c - chunk_len);
++
++                      /* new b_off points where the rimainder of the string ends */
++                      writer->b_off = writer->b_off + c - chunk_len;
++                      writer->buffer[writer->b_off] = '\0';
++
++              } while ((c = min_t(size_t, iov_iter_count(from), max_payload - 1)));
++
++              /* save for remaining unfinished line */
++              writer->b_header = header;
++              writer->b_owner = current;
++      } else {
++
++              /*
++               * Fix up any readers, pulling them forward to the first readable
++               * entry after (what will be) the new write offset. We do this now
++               * because if we partially fail, we can end up with clobbered log
++               * entries that encroach on readable buffer.
++               */
++              fix_up_readers(log, sizeof(struct logger_entry) + header.len);
++
++              len = min(sizeof(header), log->size - log->w_off);
++              memcpy(log->buffer + log->w_off, &header, len);
++              memcpy(log->buffer, (char *)&header + len, sizeof(header) - len);
++
++              /* Work with a copy until we are ready to commit the whole entry */
++              w_off =  logger_offset(log, log->w_off + sizeof(struct logger_entry));
++
++              len = min(count, log->size - w_off);
++
++              if (copy_from_iter(log->buffer + w_off, len, from) != len) {
++                      /*
++                       * Note that by not updating log->w_off, this abandons the
++                       * portion of the new entry that *was* successfully
++                       * copied, just above.  This is intentional to avoid
++                       * message corruption from missing fragments.
++                       */
++                      mutex_unlock(&log->mutex);
++                      return -EFAULT;
++              }
++
++              if (copy_from_iter(log->buffer, count - len, from) != count - len) {
++                      mutex_unlock(&log->mutex);
++                      return -EFAULT;
++              }
++
++              log->w_off = logger_offset(log, w_off + count);
++      }
++
++      mutex_unlock(&log->mutex);
++
++      /* wake up any blocked readers */
++      wake_up_interruptible(&log->wq);
++
++      return count;
++}
++
++static struct logger_log *get_log_from_minor(int minor)
++{
++      struct logger_log *log;
++
++      list_for_each_entry(log, &log_list, logs)
++              if (log->misc.minor == minor)
++                      return log;
++      return NULL;
++}
++
++/*
++ * logger_open - the log's open() file operation
++ *
++ * Note how near a no-op this is in the write-only case. Keep it that way!
++ */
++static int logger_open(struct inode *inode, struct file *file)
++{
++      struct logger_log *log;
++      int ret;
++
++      ret = nonseekable_open(inode, file);
++      if (ret)
++              return ret;
++
++      log = get_log_from_minor(MINOR(inode->i_rdev));
++      if (!log)
++              return -ENODEV;
++
++      if (file->f_mode & FMODE_READ) {
++              struct logger_reader *reader;
++
++              reader = kmalloc(sizeof(struct logger_reader), GFP_KERNEL);
++              if (!reader)
++                      return -ENOMEM;
++
++              reader->log = log;
++              reader->r_ver = 1;
++              reader->r_all = in_egroup_p(inode->i_gid) ||
++                      capable(CAP_SYSLOG);
++
++              INIT_LIST_HEAD(&reader->list);
++
++              mutex_lock(&log->mutex);
++              reader->r_off = log->head;
++              list_add_tail(&reader->list, &log->readers);
++              mutex_unlock(&log->mutex);
++
++              file->private_data = reader;
++      } else {
++              struct logger_writer *writer;
++
++              writer = kzalloc(sizeof(struct logger_writer), GFP_KERNEL);
++              if (!writer)
++                      return -ENOMEM;
++
++              writer->log = log;
++              writer->owner = current->group_leader;
++
++              file->private_data = writer;
++      }
++
++      return 0;
++}
++
++/*
++ * logger_release - the log's release file operation
++ *
++ * Note this is a total no-op in the write-only case. Keep it that way!
++ */
++static int logger_release(struct inode *ignored, struct file *file)
++{
++      if (file->f_mode & FMODE_READ) {
++              struct logger_reader *reader = file->private_data;
++              struct logger_log *log = reader->log;
++
++              mutex_lock(&log->mutex);
++              list_del(&reader->list);
++              mutex_unlock(&log->mutex);
++
++              kfree(reader);
++      } else {
++              struct logger_writer *writer = file->private_data;
++
++              kfree(writer->tag);
++              kfree(writer->buffer);
++              kfree(writer);
++      }
++
++      return 0;
++}
++
++/*
++ * logger_poll - the log's poll file operation, for poll/select/epoll
++ *
++ * Note we always return POLLOUT, because you can always write() to the log.
++ * Note also that, strictly speaking, a return value of POLLIN does not
++ * guarantee that the log is readable without blocking, as there is a small
++ * chance that the writer can lap the reader in the interim between poll()
++ * returning and the read() request.
++ */
++static unsigned int logger_poll(struct file *file, poll_table *wait)
++{
++      struct logger_reader *reader;
++      struct logger_log *log;
++      unsigned int ret = POLLOUT | POLLWRNORM;
++
++      if (!(file->f_mode & FMODE_READ))
++              return ret;
++
++      reader = file->private_data;
++      log = reader->log;
++
++      poll_wait(file, &log->wq, wait);
++
++      mutex_lock(&log->mutex);
++      if (!reader->r_all)
++              reader->r_off = get_next_entry_by_uid(log,
++                      reader->r_off, current_euid());
++
++      if (log->w_off != reader->r_off)
++              ret |= POLLIN | POLLRDNORM;
++      mutex_unlock(&log->mutex);
++
++      return ret;
++}
++
++static long logger_set_version(struct logger_reader *reader, void __user *arg)
++{
++      int version;
++
++      if (copy_from_user(&version, arg, sizeof(int)))
++              return -EFAULT;
++
++      if ((version < 1) || (version > 2))
++              return -EINVAL;
++
++      reader->r_ver = version;
++      return 0;
++}
++
++static long logger_set_prio(struct logger_writer *writer, void __user *arg)
++{
++      int prio;
++
++      prio = (int)(uintptr_t)arg;
++
++      if ((prio < 2) || (prio > 7))
++              return -EINVAL;
++
++      writer->prio = prio;
++      return 0;
++}
++
++static long logger_set_tag(struct logger_writer *writer, void __user *arg)
++{
++      struct logger_set_tag tag;
++      int len;
++      char *p, *q;
++
++      if (copy_from_user(&tag, arg, sizeof(struct logger_set_tag)))
++              return -EFAULT;
++
++      if (tag.len > LOGGER_ENTRY_MAX_PAYLOAD)
++              return -EINVAL;
++
++      p = kzalloc(tag.len, GFP_KERNEL);
++      if (!p)
++              return -ENOMEM;
++
++      if (copy_from_user(p, (void*)(uintptr_t)tag.ptr, tag.len)) {
++              kfree(p);
++              return -EFAULT;
++      }
++      p[tag.len - 1] = '\0';
++      len = strlen(p);
++
++      q = writer->tag;
++      writer->tag = p;
++      writer->tag_len = len;
++      kfree(q);
++
++      return 0;
++}
++
++static long logger_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
++{
++      struct logger_log *log = file_get_log(file);
++      struct logger_reader *reader;
++      struct logger_writer *writer;
++      long ret = -EINVAL;
++      void __user *argp = (void __user *)arg;
++
++      mutex_lock(&log->mutex);
++
++      switch (cmd) {
++      case LOGGER_GET_LOG_BUF_SIZE:
++              ret = log->size;
++              break;
++      case LOGGER_GET_LOG_LEN:
++              if (!(file->f_mode & FMODE_READ)) {
++                      ret = -EBADF;
++                      break;
++              }
++              reader = file->private_data;
++              if (log->w_off >= reader->r_off)
++                      ret = log->w_off - reader->r_off;
++              else
++                      ret = (log->size - reader->r_off) + log->w_off;
++              break;
++      case LOGGER_GET_NEXT_ENTRY_LEN:
++              if (!(file->f_mode & FMODE_READ)) {
++                      ret = -EBADF;
++                      break;
++              }
++              reader = file->private_data;
++
++              if (!reader->r_all)
++                      reader->r_off = get_next_entry_by_uid(log,
++                              reader->r_off, current_euid());
++
++              if (log->w_off != reader->r_off)
++                      ret = get_user_hdr_len(reader->r_ver) +
++                              get_entry_msg_len(log, reader->r_off);
++              else
++                      ret = 0;
++              break;
++      case LOGGER_FLUSH_LOG:
++              if (!(in_egroup_p(file_inode(file)->i_gid) ||
++                    capable(CAP_SYSLOG))) {
++                      ret = -EPERM;
++                      break;
++              }
++              list_for_each_entry(reader, &log->readers, list)
++                      reader->r_off = log->w_off;
++              log->head = log->w_off;
++              ret = 0;
++              break;
++      case LOGGER_GET_VERSION:
++              if (!(file->f_mode & FMODE_READ)) {
++                      ret = -EBADF;
++                      break;
++              }
++              reader = file->private_data;
++              ret = reader->r_ver;
++              break;
++      case LOGGER_SET_VERSION:
++              if (!(file->f_mode & FMODE_READ)) {
++                      ret = -EBADF;
++                      break;
++              }
++              reader = file->private_data;
++              ret = logger_set_version(reader, argp);
++              break;
++      case LOGGER_SET_PRIO:   /* 44552 */
++              if ((file->f_mode & FMODE_READ) ||
++                  !(file->f_mode & FMODE_WRITE)) {
++                      ret = -EBADF;
++                      break;
++              }
++              writer = file->private_data;
++              ret = logger_set_prio(writer, argp);
++              break;
++      case LOGGER_SET_TAG:    /* 44551 */
++              if ((file->f_mode & FMODE_READ) ||
++                  !(file->f_mode & FMODE_WRITE)) {
++                      ret = -EBADF;
++                      break;
++              }
++              writer = file->private_data;
++              ret = logger_set_tag(writer, argp);
++              break;
++      }
++
++      mutex_unlock(&log->mutex);
++
++      return ret;
++}
++
++static const struct file_operations logger_fops = {
++      .owner = THIS_MODULE,
++      .read = logger_read,
++      .write_iter = logger_write_iter,
++      .poll = logger_poll,
++      .unlocked_ioctl = logger_ioctl,
++      .compat_ioctl = logger_ioctl,
++      .open = logger_open,
++      .release = logger_release,
++};
++
++/*
++ * Log size must must be a power of two, and greater than
++ * (LOGGER_ENTRY_MAX_PAYLOAD + sizeof(struct logger_entry)).
++ */
++static int __init create_log(char *log_name, int size)
++{
++      int ret = 0;
++      struct logger_log *log;
++      unsigned char *buffer;
++
++      buffer = vmalloc(size);
++      if (buffer == NULL)
++              return -ENOMEM;
++
++      log = kzalloc(sizeof(struct logger_log), GFP_KERNEL);
++      if (log == NULL) {
++              ret = -ENOMEM;
++              goto out_free_buffer;
++      }
++      log->buffer = buffer;
++
++      log->misc.minor = MISC_DYNAMIC_MINOR;
++      log->misc.name = kstrdup(log_name, GFP_KERNEL);
++      if (log->misc.name == NULL) {
++              ret = -ENOMEM;
++              goto out_free_log;
++      }
++
++      log->misc.fops = &logger_fops;
++      log->misc.parent = NULL;
++
++      init_waitqueue_head(&log->wq);
++      INIT_LIST_HEAD(&log->readers);
++      mutex_init(&log->mutex);
++      log->w_off = 0;
++      log->head = 0;
++      log->size = size;
++
++      INIT_LIST_HEAD(&log->logs);
++      list_add_tail(&log->logs, &log_list);
++
++      /* finally, initialize the misc device for this log */
++      ret = misc_register(&log->misc);
++      if (unlikely(ret)) {
++              pr_err("failed to register misc device for log '%s'!\n",
++                     log->misc.name);
++              goto out_free_misc_name;
++      }
++
++      pr_info("created %luK log '%s'\n",
++              (unsigned long)log->size >> 10, log->misc.name);
++
++      return 0;
++
++out_free_misc_name:
++      kfree(log->misc.name);
++
++out_free_log:
++      kfree(log);
++
++out_free_buffer:
++      vfree(buffer);
++      return ret;
++}
++
++static int __init logger_init(void)
++{
++      int ret;
++
++      ret = create_log(LOGGER_LOG_MAIN, 256*1024);
++      if (unlikely(ret))
++              goto out;
++
++      ret = create_log(LOGGER_LOG_EVENTS, 256*1024);
++      if (unlikely(ret))
++              goto out;
++
++      ret = create_log(LOGGER_LOG_RADIO, 256*1024);
++      if (unlikely(ret))
++              goto out;
++
++      ret = create_log(LOGGER_LOG_SYSTEM, 256*1024);
++      if (unlikely(ret))
++              goto out;
++
++out:
++      return ret;
++}
++
++static void __exit logger_exit(void)
++{
++      struct logger_log *current_log, *next_log;
++
++      list_for_each_entry_safe(current_log, next_log, &log_list, logs) {
++              /* we have to delete all the entry inside log_list */
++              misc_deregister(&current_log->misc);
++              vfree(current_log->buffer);
++              kfree(current_log->misc.name);
++              list_del(&current_log->logs);
++              kfree(current_log);
++      }
++}
++
++device_initcall(logger_init);
++module_exit(logger_exit);
++
++MODULE_LICENSE("GPL");
++MODULE_AUTHOR("Robert Love, <rlove@google.com>");
++MODULE_DESCRIPTION("Android Logger");
diff --cc kernel/logger.h
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..8054a3eeeaab35d27e1751cb2c9caf967dc85b53
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,105 @@@
++/* include/linux/logger.h
++ *
++ * Copyright (C) 2007-2008 Google, Inc.
++ * Author: Robert Love <rlove@android.com>
++ *
++ * This software is licensed under the terms of the GNU General Public
++ * License version 2, as published by the Free Software Foundation, and
++ * may be copied, distributed, and modified under those terms.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
++ * GNU General Public License for more details.
++ *
++ */
++
++#ifndef _LINUX_LOGGER_H
++#define _LINUX_LOGGER_H
++
++#include <linux/types.h>
++#include <linux/ioctl.h>
++
++/**
++ * struct user_logger_entry_compat - defines a single entry that is given to a logger
++ * @len:      The length of the payload
++ * @__pad:    Two bytes of padding that appear to be required
++ * @pid:      The generating process' process ID
++ * @tid:      The generating process' thread ID
++ * @sec:      The number of seconds that have elapsed since the Epoch
++ * @nsec:     The number of nanoseconds that have elapsed since @sec
++ * @msg:      The message that is to be logged
++ *
++ * The userspace structure for version 1 of the logger_entry ABI.
++ * This structure is returned to userspace unless the caller requests
++ * an upgrade to a newer ABI version.
++ */
++struct user_logger_entry_compat {
++      __u16           len;
++      __u16           __pad;
++      __s32           pid;
++      __s32           tid;
++      __s32           sec;
++      __s32           nsec;
++      char            msg[0];
++};
++
++/**
++ * struct logger_entry - defines a single entry that is given to a logger
++ * @len:      The length of the payload
++ * @hdr_size: sizeof(struct logger_entry_v2)
++ * @pid:      The generating process' process ID
++ * @tid:      The generating process' thread ID
++ * @sec:      The number of seconds that have elapsed since the Epoch
++ * @nsec:     The number of nanoseconds that have elapsed since @sec
++ * @euid:     Effective UID of logger
++ * @msg:      The message that is to be logged
++ *
++ * The structure for version 2 of the logger_entry ABI.
++ * This structure is returned to userspace if ioctl(LOGGER_SET_VERSION)
++ * is called with version >= 2
++ */
++struct logger_entry {
++      __u16           len;
++      __u16           hdr_size;
++      __s32           pid;
++      __s32           tid;
++      __s32           sec;
++      __s32           nsec;
++#ifndef __KERNEL__
++      __s32           euid;
++#else
++      kuid_t          euid;
++#endif
++      char            msg[0];
++};
++
++/**
++ * struct logger_set_tag
++ * @len:      Length of a NULL-terminated tag including '\0'
++ * @ptr:      Pointer to a user buffer containing the tag
++ */
++struct logger_set_tag {
++      __u64           len;
++      __u64           ptr;
++};
++
++#define LOGGER_LOG_RADIO      "log_radio"     /* radio-related messages */
++#define LOGGER_LOG_EVENTS     "log_events"    /* system/hardware events */
++#define LOGGER_LOG_SYSTEM     "log_system"    /* system/framework messages */
++#define LOGGER_LOG_MAIN               "log_main"      /* everything else */
++
++#define LOGGER_ENTRY_MAX_PAYLOAD      4076
++
++#define __LOGGERIO    0xAE
++
++#define LOGGER_GET_LOG_BUF_SIZE               _IO(__LOGGERIO, 1) /* size of log */
++#define LOGGER_GET_LOG_LEN            _IO(__LOGGERIO, 2) /* used log len */
++#define LOGGER_GET_NEXT_ENTRY_LEN     _IO(__LOGGERIO, 3) /* next entry len */
++#define LOGGER_FLUSH_LOG              _IO(__LOGGERIO, 4) /* flush log */
++#define LOGGER_GET_VERSION            _IO(__LOGGERIO, 5) /* abi version */
++#define LOGGER_SET_VERSION            _IO(__LOGGERIO, 6) /* abi version */
++#define LOGGER_SET_TAG                        _IO(__LOGGERIO, 7) /* stdio tag */
++#define LOGGER_SET_PRIO                       _IO(__LOGGERIO, 8) /* stdio priority */
++
++#endif /* _LINUX_LOGGER_H */