diff options
author | Łukasz Stelmach <l.stelmach@samsung.com> | 2021-02-12 18:41:09 +0100 |
---|---|---|
committer | Seung-Woo Kim <sw0312.kim@samsung.com> | 2021-04-06 16:41:03 +0900 |
commit | 6aeb6c6d274f8e97c0d6d4dda22a55840b0d2ac7 (patch) | |
tree | fcd54014c7b8ae75d015d5c0b8aaad158648c1dc | |
parent | 6437e2a949a5fe05ee4374100181d326690ae2cc (diff) | |
download | linux-rpi3-6aeb6c6d274f8e97c0d6d4dda22a55840b0d2ac7.tar.gz linux-rpi3-6aeb6c6d274f8e97c0d6d4dda22a55840b0d2ac7.tar.bz2 linux-rpi3-6aeb6c6d274f8e97c0d6d4dda22a55840b0d2ac7.zip |
logger: accept untagged log entries
Add support for writing untagged log messages to /dev/log_* nodes. The
logger shall accept untagget messages after setting the tag and
the priority with appropriate ioctl() commands for particular file
descriptors.
Change-Id: I02bd7bfd843eaf316692413a48747009c42756f6
Signed-off-by: Łukasz Stelmach <l.stelmach@samsung.com>
-rw-r--r-- | drivers/staging/android/logger.c | 364 | ||||
-rw-r--r-- | drivers/staging/android/logger.h | 2 |
2 files changed, 331 insertions, 35 deletions
diff --git a/drivers/staging/android/logger.c b/drivers/staging/android/logger.c index 27b2686266bb..8988e4b61980 100644 --- a/drivers/staging/android/logger.c +++ b/drivers/staging/android/logger.c @@ -29,9 +29,10 @@ #include <linux/time.h> #include <linux/vmalloc.h> #include <linux/uio.h> -#include "logger.h" +#include <linux/fdtable.h> +#include <linux/file.h> -#include <asm/ioctls.h> +#include "logger.h" /** * struct logger_log - represents a specific log, such as 'main' or 'radio' @@ -64,6 +65,27 @@ struct logger_log { 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 @@ -104,12 +126,15 @@ static size_t logger_offset(struct logger_log *log, size_t n) */ 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 file->private_data; + + return writer->log; } /* @@ -409,6 +434,125 @@ static void fix_up_readers(struct logger_log *log, size_t len) 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, chunk_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 @@ -416,10 +560,16 @@ static void fix_up_readers(struct logger_log *log, size_t len) */ static ssize_t logger_write_iter(struct kiocb *iocb, struct iov_iter *from) { - struct logger_log *log = file_get_log(iocb->ki_filp); + 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); @@ -439,46 +589,120 @@ static ssize_t logger_write_iter(struct kiocb *iocb, struct iov_iter *from) mutex_lock(&log->mutex); - /* - * 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); + /* Prepend messages from STDOUT and STDERR with a tag and prio */ + if (from_stdio) { + char *p; + size_t chunk_len = 0; + size_t max_payload = LOGGER_ENTRY_MAX_PAYLOAD - writer->tag_len - 2; - 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); + if (writer->owner != current->group_leader) { + struct file *nfile; - /* 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)); + 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 = min_t(size_t, iov_iter_count(from), max_payload - 1); + + do { + + if (copy_from_iter(writer->buffer + writer->b_off, count, from) != count) { + mutex_unlock(&log->mutex); + return -EFAULT; + } - len = min(count, log->size - w_off); + /* TODO: replace NULL characters with new lines */ + p = strnrchr(writer->buffer + writer->b_off, count, '\n'); + if (p) { + *p++ = '\0'; + chunk_len = p - writer->buffer; + } else { + writer->buffer[count++] = '\0'; + chunk_len = count; + } + + 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 + count - chunk_len); + + /* new b_off points where the rimainder of the string ends */ + writer->b_off = writer->b_off + count - chunk_len; + writer->buffer[writer->b_off] = '\0'; + + } while ((count = 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 { - 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. + * 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. */ - mutex_unlock(&log->mutex); - return -EFAULT; - } + fix_up_readers(log, sizeof(struct logger_entry) + header.len); - if (copy_from_iter(log->buffer, count - len, from) != count - len) { - mutex_unlock(&log->mutex); - return -EFAULT; + 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); } - log->w_off = logger_offset(log, w_off + count); mutex_unlock(&log->mutex); /* wake up any blocked readers */ wake_up_interruptible(&log->wq); - return len; + return count; } static struct logger_log *get_log_from_minor(int minor) @@ -530,7 +754,16 @@ static int logger_open(struct inode *inode, struct file *file) file->private_data = reader; } else { - file->private_data = log; + 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; @@ -552,6 +785,12 @@ static int logger_release(struct inode *ignored, struct file *file) mutex_unlock(&log->mutex); kfree(reader); + } else { + struct logger_writer *writer = file->private_data; + + kfree(writer->tag); + kfree(writer->buffer); + kfree(writer); } return 0; @@ -606,10 +845,53 @@ static long logger_set_version(struct logger_reader *reader, void __user *arg) 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) +{ + int len; + char *p, *q; + + if (copy_from_user(&len, arg, sizeof(int))) + return -EFAULT; + + arg += sizeof(int); + + + p = kzalloc(len, GFP_KERNEL); + if (!p) + return -ENOMEM; + + if (copy_from_user(p, arg, len)) { + kfree(p); + return -EFAULT; + } + p[len-1] = '\0'; + + q = writer->tag; + writer->tag = p; + writer->tag_len = len - 1; /* without NULL */ + 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; @@ -648,10 +930,6 @@ static long logger_ioctl(struct file *file, unsigned int cmd, unsigned long arg) ret = 0; break; case LOGGER_FLUSH_LOG: - if (!(file->f_mode & FMODE_WRITE)) { - ret = -EBADF; - break; - } if (!(in_egroup_p(file_inode(file)->i_gid) || capable(CAP_SYSLOG))) { ret = -EPERM; @@ -678,6 +956,22 @@ static long logger_ioctl(struct file *file, unsigned int cmd, unsigned long arg) reader = file->private_data; ret = logger_set_version(reader, argp); break; + case LOGGER_SET_PRIO: /* 44552 */ + if (file->f_mode & FMODE_READ) { + 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) { + ret = -EBADF; + break; + } + writer = file->private_data; + ret = logger_set_tag(writer, argp); + break; } mutex_unlock(&log->mutex); diff --git a/drivers/staging/android/logger.h b/drivers/staging/android/logger.h index 70af7d805dff..b11e48ce9964 100644 --- a/drivers/staging/android/logger.h +++ b/drivers/staging/android/logger.h @@ -85,5 +85,7 @@ struct logger_entry { #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 */ |