|
@@ -909,7 +909,7 @@ static int syslog_print_all(char __user *buf, int size, bool clear)
|
|
|
/*
|
|
|
* Find first record that fits, including all following records,
|
|
|
* into the user-provided buffer for this dump.
|
|
|
- */
|
|
|
+ */
|
|
|
seq = clear_seq;
|
|
|
idx = clear_idx;
|
|
|
while (seq < log_next_seq) {
|
|
@@ -919,6 +919,8 @@ static int syslog_print_all(char __user *buf, int size, bool clear)
|
|
|
idx = log_next(idx);
|
|
|
seq++;
|
|
|
}
|
|
|
+
|
|
|
+ /* move first record forward until length fits into the buffer */
|
|
|
seq = clear_seq;
|
|
|
idx = clear_idx;
|
|
|
while (len > size && seq < log_next_seq) {
|
|
@@ -929,7 +931,7 @@ static int syslog_print_all(char __user *buf, int size, bool clear)
|
|
|
seq++;
|
|
|
}
|
|
|
|
|
|
- /* last message in this dump */
|
|
|
+ /* last message fitting into this dump */
|
|
|
next_seq = log_next_seq;
|
|
|
|
|
|
len = 0;
|
|
@@ -2300,48 +2302,210 @@ module_param_named(always_kmsg_dump, always_kmsg_dump, bool, S_IRUGO | S_IWUSR);
|
|
|
* kmsg_dump - dump kernel log to kernel message dumpers.
|
|
|
* @reason: the reason (oops, panic etc) for dumping
|
|
|
*
|
|
|
- * Iterate through each of the dump devices and call the oops/panic
|
|
|
- * callbacks with the log buffer.
|
|
|
+ * Call each of the registered dumper's dump() callback, which can
|
|
|
+ * retrieve the kmsg records with kmsg_dump_get_line() or
|
|
|
+ * kmsg_dump_get_buffer().
|
|
|
*/
|
|
|
void kmsg_dump(enum kmsg_dump_reason reason)
|
|
|
{
|
|
|
- u64 idx;
|
|
|
struct kmsg_dumper *dumper;
|
|
|
- const char *s1, *s2;
|
|
|
- unsigned long l1, l2;
|
|
|
unsigned long flags;
|
|
|
|
|
|
if ((reason > KMSG_DUMP_OOPS) && !always_kmsg_dump)
|
|
|
return;
|
|
|
|
|
|
- /* Theoretically, the log could move on after we do this, but
|
|
|
- there's not a lot we can do about that. The new messages
|
|
|
- will overwrite the start of what we dump. */
|
|
|
+ rcu_read_lock();
|
|
|
+ list_for_each_entry_rcu(dumper, &dump_list, list) {
|
|
|
+ if (dumper->max_reason && reason > dumper->max_reason)
|
|
|
+ continue;
|
|
|
+
|
|
|
+ /* initialize iterator with data about the stored records */
|
|
|
+ dumper->active = true;
|
|
|
+
|
|
|
+ raw_spin_lock_irqsave(&logbuf_lock, flags);
|
|
|
+ dumper->cur_seq = clear_seq;
|
|
|
+ dumper->cur_idx = clear_idx;
|
|
|
+ dumper->next_seq = log_next_seq;
|
|
|
+ dumper->next_idx = log_next_idx;
|
|
|
+ raw_spin_unlock_irqrestore(&logbuf_lock, flags);
|
|
|
+
|
|
|
+ /* invoke dumper which will iterate over records */
|
|
|
+ dumper->dump(dumper, reason);
|
|
|
+
|
|
|
+ /* reset iterator */
|
|
|
+ dumper->active = false;
|
|
|
+ }
|
|
|
+ rcu_read_unlock();
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * kmsg_dump_get_line - retrieve one kmsg log line
|
|
|
+ * @dumper: registered kmsg dumper
|
|
|
+ * @syslog: include the "<4>" prefixes
|
|
|
+ * @line: buffer to copy the line to
|
|
|
+ * @size: maximum size of the buffer
|
|
|
+ * @len: length of line placed into buffer
|
|
|
+ *
|
|
|
+ * Start at the beginning of the kmsg buffer, with the oldest kmsg
|
|
|
+ * record, and copy one record into the provided buffer.
|
|
|
+ *
|
|
|
+ * Consecutive calls will return the next available record moving
|
|
|
+ * towards the end of the buffer with the youngest messages.
|
|
|
+ *
|
|
|
+ * A return value of FALSE indicates that there are no more records to
|
|
|
+ * read.
|
|
|
+ */
|
|
|
+bool kmsg_dump_get_line(struct kmsg_dumper *dumper, bool syslog,
|
|
|
+ char *line, size_t size, size_t *len)
|
|
|
+{
|
|
|
+ unsigned long flags;
|
|
|
+ struct log *msg;
|
|
|
+ size_t l = 0;
|
|
|
+ bool ret = false;
|
|
|
+
|
|
|
+ if (!dumper->active)
|
|
|
+ goto out;
|
|
|
|
|
|
raw_spin_lock_irqsave(&logbuf_lock, flags);
|
|
|
- if (syslog_seq < log_first_seq)
|
|
|
- idx = syslog_idx;
|
|
|
- else
|
|
|
- idx = log_first_idx;
|
|
|
+ if (dumper->cur_seq < log_first_seq) {
|
|
|
+ /* messages are gone, move to first available one */
|
|
|
+ dumper->cur_seq = log_first_seq;
|
|
|
+ dumper->cur_idx = log_first_idx;
|
|
|
+ }
|
|
|
|
|
|
- if (idx > log_next_idx) {
|
|
|
- s1 = log_buf;
|
|
|
- l1 = log_next_idx;
|
|
|
+ /* last entry */
|
|
|
+ if (dumper->cur_seq >= log_next_seq) {
|
|
|
+ raw_spin_unlock_irqrestore(&logbuf_lock, flags);
|
|
|
+ goto out;
|
|
|
+ }
|
|
|
|
|
|
- s2 = log_buf + idx;
|
|
|
- l2 = log_buf_len - idx;
|
|
|
- } else {
|
|
|
- s1 = "";
|
|
|
- l1 = 0;
|
|
|
+ msg = log_from_idx(dumper->cur_idx);
|
|
|
+ l = msg_print_text(msg, syslog,
|
|
|
+ line, size);
|
|
|
+
|
|
|
+ dumper->cur_idx = log_next(dumper->cur_idx);
|
|
|
+ dumper->cur_seq++;
|
|
|
+ ret = true;
|
|
|
+ raw_spin_unlock_irqrestore(&logbuf_lock, flags);
|
|
|
+out:
|
|
|
+ if (len)
|
|
|
+ *len = l;
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+EXPORT_SYMBOL_GPL(kmsg_dump_get_line);
|
|
|
+
|
|
|
+/**
|
|
|
+ * kmsg_dump_get_buffer - copy kmsg log lines
|
|
|
+ * @dumper: registered kmsg dumper
|
|
|
+ * @syslog: include the "<4>" prefixes
|
|
|
+ * @line: buffer to copy the line to
|
|
|
+ * @size: maximum size of the buffer
|
|
|
+ * @len: length of line placed into buffer
|
|
|
+ *
|
|
|
+ * Start at the end of the kmsg buffer and fill the provided buffer
|
|
|
+ * with as many of the the *youngest* kmsg records that fit into it.
|
|
|
+ * If the buffer is large enough, all available kmsg records will be
|
|
|
+ * copied with a single call.
|
|
|
+ *
|
|
|
+ * Consecutive calls will fill the buffer with the next block of
|
|
|
+ * available older records, not including the earlier retrieved ones.
|
|
|
+ *
|
|
|
+ * A return value of FALSE indicates that there are no more records to
|
|
|
+ * read.
|
|
|
+ */
|
|
|
+bool kmsg_dump_get_buffer(struct kmsg_dumper *dumper, bool syslog,
|
|
|
+ char *buf, size_t size, size_t *len)
|
|
|
+{
|
|
|
+ unsigned long flags;
|
|
|
+ u64 seq;
|
|
|
+ u32 idx;
|
|
|
+ u64 next_seq;
|
|
|
+ u32 next_idx;
|
|
|
+ size_t l = 0;
|
|
|
+ bool ret = false;
|
|
|
+
|
|
|
+ if (!dumper->active)
|
|
|
+ goto out;
|
|
|
+
|
|
|
+ raw_spin_lock_irqsave(&logbuf_lock, flags);
|
|
|
+ if (dumper->cur_seq < log_first_seq) {
|
|
|
+ /* messages are gone, move to first available one */
|
|
|
+ dumper->cur_seq = log_first_seq;
|
|
|
+ dumper->cur_idx = log_first_idx;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* last entry */
|
|
|
+ if (dumper->cur_seq >= dumper->next_seq) {
|
|
|
+ raw_spin_unlock_irqrestore(&logbuf_lock, flags);
|
|
|
+ goto out;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* calculate length of entire buffer */
|
|
|
+ seq = dumper->cur_seq;
|
|
|
+ idx = dumper->cur_idx;
|
|
|
+ while (seq < dumper->next_seq) {
|
|
|
+ struct log *msg = log_from_idx(idx);
|
|
|
+
|
|
|
+ l += msg_print_text(msg, true, NULL, 0);
|
|
|
+ idx = log_next(idx);
|
|
|
+ seq++;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* move first record forward until length fits into the buffer */
|
|
|
+ seq = dumper->cur_seq;
|
|
|
+ idx = dumper->cur_idx;
|
|
|
+ while (l > size && seq < dumper->next_seq) {
|
|
|
+ struct log *msg = log_from_idx(idx);
|
|
|
|
|
|
- s2 = log_buf + idx;
|
|
|
- l2 = log_next_idx - idx;
|
|
|
+ l -= msg_print_text(msg, true, NULL, 0);
|
|
|
+ idx = log_next(idx);
|
|
|
+ seq++;
|
|
|
}
|
|
|
+
|
|
|
+ /* last message in next interation */
|
|
|
+ next_seq = seq;
|
|
|
+ next_idx = idx;
|
|
|
+
|
|
|
+ l = 0;
|
|
|
+ while (seq < dumper->next_seq) {
|
|
|
+ struct log *msg = log_from_idx(idx);
|
|
|
+
|
|
|
+ l += msg_print_text(msg, syslog,
|
|
|
+ buf + l, size - l);
|
|
|
+
|
|
|
+ idx = log_next(idx);
|
|
|
+ seq++;
|
|
|
+ }
|
|
|
+
|
|
|
+ dumper->next_seq = next_seq;
|
|
|
+ dumper->next_idx = next_idx;
|
|
|
+ ret = true;
|
|
|
raw_spin_unlock_irqrestore(&logbuf_lock, flags);
|
|
|
+out:
|
|
|
+ if (len)
|
|
|
+ *len = l;
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+EXPORT_SYMBOL_GPL(kmsg_dump_get_buffer);
|
|
|
|
|
|
- rcu_read_lock();
|
|
|
- list_for_each_entry_rcu(dumper, &dump_list, list)
|
|
|
- dumper->dump(dumper, reason, s1, l1, s2, l2);
|
|
|
- rcu_read_unlock();
|
|
|
+/**
|
|
|
+ * kmsg_dump_rewind - reset the interator
|
|
|
+ * @dumper: registered kmsg dumper
|
|
|
+ *
|
|
|
+ * Reset the dumper's iterator so that kmsg_dump_get_line() and
|
|
|
+ * kmsg_dump_get_buffer() can be called again and used multiple
|
|
|
+ * times within the same dumper.dump() callback.
|
|
|
+ */
|
|
|
+void kmsg_dump_rewind(struct kmsg_dumper *dumper)
|
|
|
+{
|
|
|
+ unsigned long flags;
|
|
|
+
|
|
|
+ raw_spin_lock_irqsave(&logbuf_lock, flags);
|
|
|
+ dumper->cur_seq = clear_seq;
|
|
|
+ dumper->cur_idx = clear_idx;
|
|
|
+ dumper->next_seq = log_next_seq;
|
|
|
+ dumper->next_idx = log_next_idx;
|
|
|
+ raw_spin_unlock_irqrestore(&logbuf_lock, flags);
|
|
|
}
|
|
|
+EXPORT_SYMBOL_GPL(kmsg_dump_rewind);
|
|
|
#endif
|