diff options
Diffstat (limited to 'kernel/printk.c')
| -rw-r--r-- | kernel/printk.c | 241 | 
1 files changed, 208 insertions, 33 deletions
| diff --git a/kernel/printk.c b/kernel/printk.c index 32462d2b364a..a2276b916769 100644 --- a/kernel/printk.c +++ b/kernel/printk.c @@ -227,10 +227,10 @@ static u32 clear_idx;  #define LOG_LINE_MAX 1024  /* record buffer */ -#if !defined(CONFIG_64BIT) || defined(CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS) +#if defined(CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS)  #define LOG_ALIGN 4  #else -#define LOG_ALIGN 8 +#define LOG_ALIGN __alignof__(struct log)  #endif  #define __LOG_BUF_LEN (1 << CONFIG_LOG_BUF_SHIFT)  static char __log_buf[__LOG_BUF_LEN] __aligned(LOG_ALIGN); @@ -414,7 +414,9 @@ static ssize_t devkmsg_read(struct file *file, char __user *buf,  	if (!user)  		return -EBADF; -	mutex_lock(&user->lock); +	ret = mutex_lock_interruptible(&user->lock); +	if (ret) +		return ret;  	raw_spin_lock(&logbuf_lock);  	while (user->seq == log_next_seq) {  		if (file->f_flags & O_NONBLOCK) { @@ -878,7 +880,9 @@ static int syslog_print(char __user *buf, int size)  	syslog_seq++;  	raw_spin_unlock_irq(&logbuf_lock); -	if (len > 0 && copy_to_user(buf, text, len)) +	if (len > size) +		len = -EINVAL; +	else if (len > 0 && copy_to_user(buf, text, len))  		len = -EFAULT;  	kfree(text); @@ -909,7 +913,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 +923,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 +935,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; @@ -974,6 +980,7 @@ int do_syslog(int type, char __user *buf, int len, bool from_file)  {  	bool clear = false;  	static int saved_console_loglevel = -1; +	static DEFINE_MUTEX(syslog_mutex);  	int error;  	error = check_syslog_permissions(type, from_file); @@ -1000,11 +1007,17 @@ int do_syslog(int type, char __user *buf, int len, bool from_file)  			error = -EFAULT;  			goto out;  		} +		error = mutex_lock_interruptible(&syslog_mutex); +		if (error) +			goto out;  		error = wait_event_interruptible(log_wait,  						 syslog_seq != log_next_seq); -		if (error) +		if (error) { +			mutex_unlock(&syslog_mutex);  			goto out; +		}  		error = syslog_print(buf, len); +		mutex_unlock(&syslog_mutex);  		break;  	/* Read/clear last kernel messages */  	case SYSLOG_ACTION_READ_CLEAR: @@ -2300,48 +2313,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; +	} + +	/* last entry */ +	if (dumper->cur_seq >= log_next_seq) { +		raw_spin_unlock_irqrestore(&logbuf_lock, flags); +		goto out; +	} -	if (idx > log_next_idx) { -		s1 = log_buf; -		l1 = log_next_idx; +	msg = log_from_idx(dumper->cur_idx); +	l = msg_print_text(msg, syslog, +			      line, size); -		s2 = log_buf + idx; -		l2 = log_buf_len - idx; -	} else { -		s1 = ""; -		l1 = 0; +	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++; +	} -		s2 = log_buf + idx; -		l2 = log_next_idx - idx; +	/* 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); + +		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 | 
