logger: accept untagged log entries 23/255623/7
authorŁukasz Stelmach <l.stelmach@samsung.com>
Fri, 12 Feb 2021 17:41:09 +0000 (18:41 +0100)
committerŁukasz Stelmach <l.stelmach@samsung.com>
Tue, 6 Apr 2021 06:37:59 +0000 (08:37 +0200)
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>
drivers/staging/android/logger.c
drivers/staging/android/logger.h

index 405160c..fb1c952 100644 (file)
 #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 len, w_off, 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 timespec64 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)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);
index 70af7d8..b11e48c 100644 (file)
@@ -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 */