From: Karol Lewandowski Date: Thu, 22 Apr 2021 13:51:37 +0000 (+0200) Subject: Merge branch 'logger-linux-rpi3' into tizen X-Git-Tag: submit/tizen/20211126.100737~15 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=3e5892414f7c75da2a5180d02f757971225c1acd;p=platform%2Fkernel%2Flinux-tizen-modules-source.git Merge branch 'logger-linux-rpi3' into tizen This imports logger driver along with history. It's been obtained from: ssh://review.tizen.org:29418/platform/kernel/linux-rpi3 Change-Id: If7ffcb064bd6c8279a0cceb285f1db372aa22907 --- 3e5892414f7c75da2a5180d02f757971225c1acd diff --cc kernel/logger.c index 0000000,0000000..14fb7f7 new file mode 100644 --- /dev/null +++ b/kernel/logger.c @@@ -1,0 -1,0 +1,1117 @@@ ++/* ++ * drivers/misc/logger.c ++ * ++ * A Logging Subsystem ++ * ++ * Copyright (C) 2007-2008 Google, Inc. ++ * ++ * Robert Love ++ * ++ * 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 ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++ ++/** ++ * 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 ab, 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(¤t_log->misc); ++ vfree(current_log->buffer); ++ kfree(current_log->misc.name); ++ list_del(¤t_log->logs); ++ kfree(current_log); ++ } ++} ++ ++device_initcall(logger_init); ++module_exit(logger_exit); ++ ++MODULE_LICENSE("GPL"); ++MODULE_AUTHOR("Robert Love, "); ++MODULE_DESCRIPTION("Android Logger"); diff --cc kernel/logger.h index 0000000,0000000..8054a3e new file mode 100644 --- /dev/null +++ b/kernel/logger.h @@@ -1,0 -1,0 +1,105 @@@ ++/* include/linux/logger.h ++ * ++ * Copyright (C) 2007-2008 Google, Inc. ++ * Author: Robert Love ++ * ++ * 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 ++#include ++ ++/** ++ * 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 */