summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorŁukasz Stelmach <l.stelmach@samsung.com>2021-02-12 18:41:09 +0100
committerSeung-Woo Kim <sw0312.kim@samsung.com>2021-04-06 16:41:03 +0900
commit6aeb6c6d274f8e97c0d6d4dda22a55840b0d2ac7 (patch)
treefcd54014c7b8ae75d015d5c0b8aaad158648c1dc
parent6437e2a949a5fe05ee4374100181d326690ae2cc (diff)
downloadlinux-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.c364
-rw-r--r--drivers/staging/android/logger.h2
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 */