diff options
Diffstat (limited to 'kernel/printk.c')
| -rw-r--r-- | kernel/printk.c | 552 | 
1 files changed, 379 insertions, 173 deletions
diff --git a/kernel/printk.c b/kernel/printk.c index a2276b91676..ac4bc9e7946 100644 --- a/kernel/printk.c +++ b/kernel/printk.c @@ -193,12 +193,21 @@ static int console_may_schedule;   * separated by ',', and find the message after the ';' character.   */ +enum log_flags { +	LOG_NOCONS	= 1,	/* already flushed, do not print to console */ +	LOG_NEWLINE	= 2,	/* text ended with a newline */ +	LOG_PREFIX	= 4,	/* text started with a prefix */ +	LOG_CONT	= 8,	/* text is a fragment of a continuation line */ +}; +  struct log {  	u64 ts_nsec;		/* timestamp in nanoseconds */  	u16 len;		/* length of entire record */  	u16 text_len;		/* length of text buffer */  	u16 dict_len;		/* length of dictionary buffer */ -	u16 level;		/* syslog level + facility */ +	u8 facility;		/* syslog facility */ +	u8 flags:5;		/* internal record flags */ +	u8 level:3;		/* syslog level */  };  /* @@ -210,6 +219,8 @@ static DEFINE_RAW_SPINLOCK(logbuf_lock);  /* the next printk record to read by syslog(READ) or /proc/kmsg */  static u64 syslog_seq;  static u32 syslog_idx; +static enum log_flags syslog_prev; +static size_t syslog_partial;  /* index and sequence number of the first record stored in the buffer */  static u64 log_first_seq; @@ -286,6 +297,7 @@ static u32 log_next(u32 idx)  /* insert record into the buffer, discard old ones, update heads */  static void log_store(int facility, int level, +		      enum log_flags flags, u64 ts_nsec,  		      const char *dict, u16 dict_len,  		      const char *text, u16 text_len)  { @@ -329,8 +341,13 @@ static void log_store(int facility, int level,  	msg->text_len = text_len;  	memcpy(log_dict(msg), dict, dict_len);  	msg->dict_len = dict_len; -	msg->level = (facility << 3) | (level & 7); -	msg->ts_nsec = local_clock(); +	msg->facility = facility; +	msg->level = level & 7; +	msg->flags = flags & 0x1f; +	if (ts_nsec > 0) +		msg->ts_nsec = ts_nsec; +	else +		msg->ts_nsec = local_clock();  	memset(log_dict(msg) + dict_len, 0, pad_len);  	msg->len = sizeof(struct log) + text_len + dict_len + pad_len; @@ -417,20 +434,20 @@ static ssize_t devkmsg_read(struct file *file, char __user *buf,  	ret = mutex_lock_interruptible(&user->lock);  	if (ret)  		return ret; -	raw_spin_lock(&logbuf_lock); +	raw_spin_lock_irq(&logbuf_lock);  	while (user->seq == log_next_seq) {  		if (file->f_flags & O_NONBLOCK) {  			ret = -EAGAIN; -			raw_spin_unlock(&logbuf_lock); +			raw_spin_unlock_irq(&logbuf_lock);  			goto out;  		} -		raw_spin_unlock(&logbuf_lock); +		raw_spin_unlock_irq(&logbuf_lock);  		ret = wait_event_interruptible(log_wait,  					       user->seq != log_next_seq);  		if (ret)  			goto out; -		raw_spin_lock(&logbuf_lock); +		raw_spin_lock_irq(&logbuf_lock);  	}  	if (user->seq < log_first_seq) { @@ -438,7 +455,7 @@ static ssize_t devkmsg_read(struct file *file, char __user *buf,  		user->idx = log_first_idx;  		user->seq = log_first_seq;  		ret = -EPIPE; -		raw_spin_unlock(&logbuf_lock); +		raw_spin_unlock_irq(&logbuf_lock);  		goto out;  	} @@ -446,13 +463,13 @@ static ssize_t devkmsg_read(struct file *file, char __user *buf,  	ts_usec = msg->ts_nsec;  	do_div(ts_usec, 1000);  	len = sprintf(user->buf, "%u,%llu,%llu;", -		      msg->level, user->seq, ts_usec); +		      (msg->facility << 3) | msg->level, user->seq, ts_usec);  	/* escape non-printable characters */  	for (i = 0; i < msg->text_len; i++) {  		unsigned char c = log_text(msg)[i]; -		if (c < ' ' || c >= 128) +		if (c < ' ' || c >= 127 || c == '\\')  			len += sprintf(user->buf + len, "\\x%02x", c);  		else  			user->buf[len++] = c; @@ -476,7 +493,7 @@ static ssize_t devkmsg_read(struct file *file, char __user *buf,  				continue;  			} -			if (c < ' ' || c >= 128) { +			if (c < ' ' || c >= 127 || c == '\\') {  				len += sprintf(user->buf + len, "\\x%02x", c);  				continue;  			} @@ -488,7 +505,7 @@ static ssize_t devkmsg_read(struct file *file, char __user *buf,  	user->idx = log_next(user->idx);  	user->seq++; -	raw_spin_unlock(&logbuf_lock); +	raw_spin_unlock_irq(&logbuf_lock);  	if (len > count) {  		ret = -EINVAL; @@ -515,7 +532,7 @@ static loff_t devkmsg_llseek(struct file *file, loff_t offset, int whence)  	if (offset)  		return -ESPIPE; -	raw_spin_lock(&logbuf_lock); +	raw_spin_lock_irq(&logbuf_lock);  	switch (whence) {  	case SEEK_SET:  		/* the first record */ @@ -539,7 +556,7 @@ static loff_t devkmsg_llseek(struct file *file, loff_t offset, int whence)  	default:  		ret = -EINVAL;  	} -	raw_spin_unlock(&logbuf_lock); +	raw_spin_unlock_irq(&logbuf_lock);  	return ret;  } @@ -553,14 +570,14 @@ static unsigned int devkmsg_poll(struct file *file, poll_table *wait)  	poll_wait(file, &log_wait, wait); -	raw_spin_lock(&logbuf_lock); +	raw_spin_lock_irq(&logbuf_lock);  	if (user->seq < log_next_seq) {  		/* return error when data has vanished underneath us */  		if (user->seq < log_first_seq)  			ret = POLLIN|POLLRDNORM|POLLERR|POLLPRI;  		ret = POLLIN|POLLRDNORM;  	} -	raw_spin_unlock(&logbuf_lock); +	raw_spin_unlock_irq(&logbuf_lock);  	return ret;  } @@ -584,10 +601,10 @@ static int devkmsg_open(struct inode *inode, struct file *file)  	mutex_init(&user->lock); -	raw_spin_lock(&logbuf_lock); +	raw_spin_lock_irq(&logbuf_lock);  	user->idx = log_first_idx;  	user->seq = log_first_seq; -	raw_spin_unlock(&logbuf_lock); +	raw_spin_unlock_irq(&logbuf_lock);  	file->private_data = user;  	return 0; @@ -787,44 +804,64 @@ static bool printk_time;  #endif  module_param_named(time, printk_time, bool, S_IRUGO | S_IWUSR); +static size_t print_time(u64 ts, char *buf) +{ +	unsigned long rem_nsec; + +	if (!printk_time) +		return 0; + +	if (!buf) +		return 15; + +	rem_nsec = do_div(ts, 1000000000); +	return sprintf(buf, "[%5lu.%06lu] ", +		       (unsigned long)ts, rem_nsec / 1000); +} +  static size_t print_prefix(const struct log *msg, bool syslog, char *buf)  {  	size_t len = 0; +	unsigned int prefix = (msg->facility << 3) | msg->level;  	if (syslog) {  		if (buf) { -			len += sprintf(buf, "<%u>", msg->level); +			len += sprintf(buf, "<%u>", prefix);  		} else {  			len += 3; -			if (msg->level > 9) -				len++; -			if (msg->level > 99) +			if (prefix > 999) +				len += 3; +			else if (prefix > 99) +				len += 2; +			else if (prefix > 9)  				len++;  		}  	} -	if (printk_time) { -		if (buf) { -			unsigned long long ts = msg->ts_nsec; -			unsigned long rem_nsec = do_div(ts, 1000000000); - -			len += sprintf(buf + len, "[%5lu.%06lu] ", -					 (unsigned long) ts, rem_nsec / 1000); -		} else { -			len += 15; -		} -	} - +	len += print_time(msg->ts_nsec, buf ? buf + len : NULL);  	return len;  } -static size_t msg_print_text(const struct log *msg, bool syslog, -			     char *buf, size_t size) +static size_t msg_print_text(const struct log *msg, enum log_flags prev, +			     bool syslog, char *buf, size_t size)  {  	const char *text = log_text(msg);  	size_t text_size = msg->text_len; +	bool prefix = true; +	bool newline = true;  	size_t len = 0; +	if ((prev & LOG_CONT) && !(msg->flags & LOG_PREFIX)) +		prefix = false; + +	if (msg->flags & LOG_CONT) { +		if ((prev & LOG_CONT) && !(prev & LOG_NEWLINE)) +			prefix = false; + +		if (!(msg->flags & LOG_NEWLINE)) +			newline = false; +	} +  	do {  		const char *next = memchr(text, '\n', text_size);  		size_t text_len; @@ -842,16 +879,22 @@ static size_t msg_print_text(const struct log *msg, bool syslog,  			    text_len + 1>= size - len)  				break; -			len += print_prefix(msg, syslog, buf + len); +			if (prefix) +				len += print_prefix(msg, syslog, buf + len);  			memcpy(buf + len, text, text_len);  			len += text_len; -			buf[len++] = '\n'; +			if (next || newline) +				buf[len++] = '\n';  		} else {  			/* SYSLOG_ACTION_* buffer size only calculation */ -			len += print_prefix(msg, syslog, NULL); -			len += text_len + 1; +			if (prefix) +				len += print_prefix(msg, syslog, NULL); +			len += text_len; +			if (next || newline) +				len++;  		} +		prefix = true;  		text = next;  	} while (text); @@ -862,28 +905,60 @@ static int syslog_print(char __user *buf, int size)  {  	char *text;  	struct log *msg; -	int len; +	int len = 0;  	text = kmalloc(LOG_LINE_MAX, GFP_KERNEL);  	if (!text)  		return -ENOMEM; -	raw_spin_lock_irq(&logbuf_lock); -	if (syslog_seq < log_first_seq) { -		/* messages are gone, move to first one */ -		syslog_seq = log_first_seq; -		syslog_idx = log_first_idx; -	} -	msg = log_from_idx(syslog_idx); -	len = msg_print_text(msg, true, text, LOG_LINE_MAX); -	syslog_idx = log_next(syslog_idx); -	syslog_seq++; -	raw_spin_unlock_irq(&logbuf_lock); +	while (size > 0) { +		size_t n; +		size_t skip; -	if (len > size) -		len = -EINVAL; -	else if (len > 0 && copy_to_user(buf, text, len)) -		len = -EFAULT; +		raw_spin_lock_irq(&logbuf_lock); +		if (syslog_seq < log_first_seq) { +			/* messages are gone, move to first one */ +			syslog_seq = log_first_seq; +			syslog_idx = log_first_idx; +			syslog_prev = 0; +			syslog_partial = 0; +		} +		if (syslog_seq == log_next_seq) { +			raw_spin_unlock_irq(&logbuf_lock); +			break; +		} + +		skip = syslog_partial; +		msg = log_from_idx(syslog_idx); +		n = msg_print_text(msg, syslog_prev, true, text, LOG_LINE_MAX); +		if (n - syslog_partial <= size) { +			/* message fits into buffer, move forward */ +			syslog_idx = log_next(syslog_idx); +			syslog_seq++; +			syslog_prev = msg->flags; +			n -= syslog_partial; +			syslog_partial = 0; +		} else if (!len){ +			/* partial read(), remember position */ +			n = size; +			syslog_partial += n; +		} else +			n = 0; +		raw_spin_unlock_irq(&logbuf_lock); + +		if (!n) +			break; + +		if (copy_to_user(buf, text + skip, n)) { +			if (!len) +				len = -EFAULT; +			break; +		} + +		len += n; +		size -= n; +		buf += n; +	}  	kfree(text);  	return len; @@ -903,6 +978,7 @@ static int syslog_print_all(char __user *buf, int size, bool clear)  		u64 next_seq;  		u64 seq;  		u32 idx; +		enum log_flags prev;  		if (clear_seq < log_first_seq) {  			/* messages are gone, move to first available one */ @@ -916,10 +992,11 @@ static int syslog_print_all(char __user *buf, int size, bool clear)  		 */  		seq = clear_seq;  		idx = clear_idx; +		prev = 0;  		while (seq < log_next_seq) {  			struct log *msg = log_from_idx(idx); -			len += msg_print_text(msg, true, NULL, 0); +			len += msg_print_text(msg, prev, true, NULL, 0);  			idx = log_next(idx);  			seq++;  		} @@ -927,10 +1004,11 @@ static int syslog_print_all(char __user *buf, int size, bool clear)  		/* move first record forward until length fits into the buffer */  		seq = clear_seq;  		idx = clear_idx; +		prev = 0;  		while (len > size && seq < log_next_seq) {  			struct log *msg = log_from_idx(idx); -			len -= msg_print_text(msg, true, NULL, 0); +			len -= msg_print_text(msg, prev, true, NULL, 0);  			idx = log_next(idx);  			seq++;  		} @@ -939,17 +1017,19 @@ static int syslog_print_all(char __user *buf, int size, bool clear)  		next_seq = log_next_seq;  		len = 0; +		prev = 0;  		while (len >= 0 && seq < next_seq) {  			struct log *msg = log_from_idx(idx);  			int textlen; -			textlen = msg_print_text(msg, true, text, LOG_LINE_MAX); +			textlen = msg_print_text(msg, prev, true, text, LOG_LINE_MAX);  			if (textlen < 0) {  				len = textlen;  				break;  			}  			idx = log_next(idx);  			seq++; +			prev = msg->flags;  			raw_spin_unlock_irq(&logbuf_lock);  			if (copy_to_user(buf + len, text, textlen)) @@ -962,6 +1042,7 @@ static int syslog_print_all(char __user *buf, int size, bool clear)  				/* messages are gone, move to next one */  				seq = log_first_seq;  				idx = log_first_idx; +				prev = 0;  			}  		}  	} @@ -980,7 +1061,6 @@ 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); @@ -1007,17 +1087,11 @@ 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) { -			mutex_unlock(&syslog_mutex); +		if (error)  			goto out; -		}  		error = syslog_print(buf, len); -		mutex_unlock(&syslog_mutex);  		break;  	/* Read/clear last kernel messages */  	case SYSLOG_ACTION_READ_CLEAR: @@ -1040,6 +1114,7 @@ int do_syslog(int type, char __user *buf, int len, bool from_file)  	/* Clear ring buffer */  	case SYSLOG_ACTION_CLEAR:  		syslog_print_all(NULL, 0, true); +		break;  	/* Disable logging to console */  	case SYSLOG_ACTION_CONSOLE_OFF:  		if (saved_console_loglevel == -1) @@ -1072,6 +1147,8 @@ int do_syslog(int type, char __user *buf, int len, bool from_file)  			/* messages are gone, move to first one */  			syslog_seq = log_first_seq;  			syslog_idx = log_first_idx; +			syslog_prev = 0; +			syslog_partial = 0;  		}  		if (from_file) {  			/* @@ -1081,19 +1158,20 @@ int do_syslog(int type, char __user *buf, int len, bool from_file)  			 */  			error = log_next_idx - syslog_idx;  		} else { -			u64 seq; -			u32 idx; +			u64 seq = syslog_seq; +			u32 idx = syslog_idx; +			enum log_flags prev = syslog_prev;  			error = 0; -			seq = syslog_seq; -			idx = syslog_idx;  			while (seq < log_next_seq) {  				struct log *msg = log_from_idx(idx); -				error += msg_print_text(msg, true, NULL, 0); +				error += msg_print_text(msg, prev, true, NULL, 0);  				idx = log_next(idx);  				seq++; +				prev = msg->flags;  			} +			error -= syslog_partial;  		}  		raw_spin_unlock_irq(&logbuf_lock);  		break; @@ -1114,21 +1192,6 @@ SYSCALL_DEFINE3(syslog, int, type, char __user *, buf, int, len)  	return do_syslog(type, buf, len, SYSLOG_FROM_CALL);  } -#ifdef	CONFIG_KGDB_KDB -/* kdb dmesg command needs access to the syslog buffer.  do_syslog() - * uses locks so it cannot be used during debugging.  Just tell kdb - * where the start and end of the physical and logical logs are.  This - * is equivalent to do_syslog(3). - */ -void kdb_syslog_data(char *syslog_data[4]) -{ -	syslog_data[0] = log_buf; -	syslog_data[1] = log_buf + log_buf_len; -	syslog_data[2] = log_buf + log_first_idx; -	syslog_data[3] = log_buf + log_next_idx; -} -#endif	/* CONFIG_KGDB_KDB */ -  static bool __read_mostly ignore_loglevel;  static int __init ignore_loglevel_setup(char *str) @@ -1272,22 +1335,98 @@ static inline void printk_delay(void)  	}  } +/* + * Continuation lines are buffered, and not committed to the record buffer + * until the line is complete, or a race forces it. The line fragments + * though, are printed immediately to the consoles to ensure everything has + * reached the console in case of a kernel crash. + */ +static struct cont { +	char buf[LOG_LINE_MAX]; +	size_t len;			/* length == 0 means unused buffer */ +	size_t cons;			/* bytes written to console */ +	struct task_struct *owner;	/* task of first print*/ +	u64 ts_nsec;			/* time of first print */ +	u8 level;			/* log level of first message */ +	u8 facility;			/* log level of first message */ +	bool flushed:1;			/* buffer sealed and committed */ +} cont; + +static void cont_flush(void) +{ +	if (cont.flushed) +		return; +	if (cont.len == 0) +		return; + +	log_store(cont.facility, cont.level, LOG_NOCONS, cont.ts_nsec, +		  NULL, 0, cont.buf, cont.len); + +	cont.flushed = true; +} + +static bool cont_add(int facility, int level, const char *text, size_t len) +{ +	if (cont.len && cont.flushed) +		return false; + +	if (cont.len + len > sizeof(cont.buf)) { +		cont_flush(); +		return false; +	} + +	if (!cont.len) { +		cont.facility = facility; +		cont.level = level; +		cont.owner = current; +		cont.ts_nsec = local_clock(); +		cont.cons = 0; +		cont.flushed = false; +	} + +	memcpy(cont.buf + cont.len, text, len); +	cont.len += len; +	return true; +} + +static size_t cont_print_text(char *text, size_t size) +{ +	size_t textlen = 0; +	size_t len; + +	if (cont.cons == 0) { +		textlen += print_time(cont.ts_nsec, text); +		size -= textlen; +	} + +	len = cont.len - cont.cons; +	if (len > 0) { +		if (len+1 > size) +			len = size-1; +		memcpy(text + textlen, cont.buf + cont.cons, len); +		textlen += len; +		cont.cons = cont.len; +	} + +	if (cont.flushed) { +		text[textlen++] = '\n'; +		/* got everything, release buffer */ +		cont.len = 0; +	} +	return textlen; +} +  asmlinkage int vprintk_emit(int facility, int level,  			    const char *dict, size_t dictlen,  			    const char *fmt, va_list args)  {  	static int recursion_bug; -	static char cont_buf[LOG_LINE_MAX]; -	static size_t cont_len; -	static int cont_level; -	static struct task_struct *cont_task;  	static char textbuf[LOG_LINE_MAX];  	char *text = textbuf;  	size_t text_len; +	enum log_flags lflags = 0;  	unsigned long flags;  	int this_cpu; -	bool newline = false; -	bool prefix = false;  	int printed_len = 0;  	boot_delay_msec(); @@ -1326,7 +1465,8 @@ asmlinkage int vprintk_emit(int facility, int level,  		recursion_bug = 0;  		printed_len += strlen(recursion_msg);  		/* emit KERN_CRIT message */ -		log_store(0, 2, NULL, 0, recursion_msg, printed_len); +		log_store(0, 2, LOG_PREFIX|LOG_NEWLINE, 0, +			  NULL, 0, recursion_msg, printed_len);  	}  	/* @@ -1338,7 +1478,7 @@ asmlinkage int vprintk_emit(int facility, int level,  	/* mark and strip a trailing newline */  	if (text_len && text[text_len-1] == '\n') {  		text_len--; -		newline = true; +		lflags |= LOG_NEWLINE;  	}  	/* strip syslog prefix and extract log level or control flags */ @@ -1348,7 +1488,7 @@ asmlinkage int vprintk_emit(int facility, int level,  			if (level == -1)  				level = text[1] - '0';  		case 'd':	/* KERN_DEFAULT */ -			prefix = true; +			lflags |= LOG_PREFIX;  		case 'c':	/* KERN_CONT */  			text += 3;  			text_len -= 3; @@ -1358,61 +1498,41 @@ asmlinkage int vprintk_emit(int facility, int level,  	if (level == -1)  		level = default_message_loglevel; -	if (dict) { -		prefix = true; -		newline = true; -	} - -	if (!newline) { -		if (cont_len && (prefix || cont_task != current)) { -			/* -			 * Flush earlier buffer, which is either from a -			 * different thread, or when we got a new prefix. -			 */ -			log_store(facility, cont_level, NULL, 0, cont_buf, cont_len); -			cont_len = 0; -		} +	if (dict) +		lflags |= LOG_PREFIX|LOG_NEWLINE; -		if (!cont_len) { -			cont_level = level; -			cont_task = current; -		} +	if (!(lflags & LOG_NEWLINE)) { +		/* +		 * Flush the conflicting buffer. An earlier newline was missing, +		 * or another task also prints continuation lines. +		 */ +		if (cont.len && (lflags & LOG_PREFIX || cont.owner != current)) +			cont_flush(); -		/* buffer or append to earlier buffer from the same thread */ -		if (cont_len + text_len > sizeof(cont_buf)) -			text_len = sizeof(cont_buf) - cont_len; -		memcpy(cont_buf + cont_len, text, text_len); -		cont_len += text_len; +		/* buffer line if possible, otherwise store it right away */ +		if (!cont_add(facility, level, text, text_len)) +			log_store(facility, level, lflags | LOG_CONT, 0, +				  dict, dictlen, text, text_len);  	} else { -		if (cont_len && cont_task == current) { -			if (prefix) { -				/* -				 * New prefix from the same thread; flush. We -				 * either got no earlier newline, or we race -				 * with an interrupt. -				 */ -				log_store(facility, cont_level, -					  NULL, 0, cont_buf, cont_len); -				cont_len = 0; -			} +		bool stored = false; -			/* append to the earlier buffer and flush */ -			if (cont_len + text_len > sizeof(cont_buf)) -				text_len = sizeof(cont_buf) - cont_len; -			memcpy(cont_buf + cont_len, text, text_len); -			cont_len += text_len; -			log_store(facility, cont_level, -				  NULL, 0, cont_buf, cont_len); -			cont_len = 0; -			cont_task = NULL; -			printed_len = cont_len; -		} else { -			/* ordinary single and terminated line */ -			log_store(facility, level, -				  dict, dictlen, text, text_len); -			printed_len = text_len; +		/* +		 * If an earlier newline was missing and it was the same task, +		 * either merge it with the current buffer and flush, or if +		 * there was a race with interrupts (prefix == true) then just +		 * flush it out and store this line separately. +		 */ +		if (cont.len && cont.owner == current) { +			if (!(lflags & LOG_PREFIX)) +				stored = cont_add(facility, level, text, text_len); +			cont_flush();  		} + +		if (!stored) +			log_store(facility, level, lflags, 0, +				  dict, dictlen, text, text_len);  	} +	printed_len += text_len;  	/*  	 * Try to acquire and then immediately release the console semaphore. @@ -1499,11 +1619,18 @@ EXPORT_SYMBOL(printk);  #else  #define LOG_LINE_MAX 0 +static struct cont { +	size_t len; +	size_t cons; +	u8 level; +	bool flushed:1; +} cont;  static struct log *log_from_idx(u32 idx) { return NULL; }  static u32 log_next(u32 idx) { return 0; }  static void call_console_drivers(int level, const char *text, size_t len) {} -static size_t msg_print_text(const struct log *msg, bool syslog, -			     char *buf, size_t size) { return 0; } +static size_t msg_print_text(const struct log *msg, enum log_flags prev, +			     bool syslog, char *buf, size_t size) { return 0; } +static size_t cont_print_text(char *text, size_t size) { return 0; }  #endif /* CONFIG_PRINTK */ @@ -1778,6 +1905,7 @@ void wake_up_klogd(void)  /* the next printk record to write to the console */  static u64 console_seq;  static u32 console_idx; +static enum log_flags console_prev;  /**   * console_unlock - unlock the console system @@ -1795,6 +1923,7 @@ static u32 console_idx;   */  void console_unlock(void)  { +	static char text[LOG_LINE_MAX];  	static u64 seen_seq;  	unsigned long flags;  	bool wake_klogd = false; @@ -1807,10 +1936,23 @@ void console_unlock(void)  	console_may_schedule = 0; +	/* flush buffered message fragment immediately to console */ +	raw_spin_lock_irqsave(&logbuf_lock, flags); +	if (cont.len && (cont.cons < cont.len || cont.flushed)) { +		size_t len; + +		len = cont_print_text(text, sizeof(text)); +		raw_spin_unlock(&logbuf_lock); +		stop_critical_timings(); +		call_console_drivers(cont.level, text, len); +		start_critical_timings(); +		local_irq_restore(flags); +	} else +		raw_spin_unlock_irqrestore(&logbuf_lock, flags); +  again:  	for (;;) {  		struct log *msg; -		static char text[LOG_LINE_MAX];  		size_t len;  		int level; @@ -1824,18 +1966,35 @@ again:  			/* messages are gone, move to first one */  			console_seq = log_first_seq;  			console_idx = log_first_idx; +			console_prev = 0;  		} - +skip:  		if (console_seq == log_next_seq)  			break;  		msg = log_from_idx(console_idx); -		level = msg->level & 7; - -		len = msg_print_text(msg, false, text, sizeof(text)); +		if (msg->flags & LOG_NOCONS) { +			/* +			 * Skip record we have buffered and already printed +			 * directly to the console when we received it. +			 */ +			console_idx = log_next(console_idx); +			console_seq++; +			/* +			 * We will get here again when we register a new +			 * CON_PRINTBUFFER console. Clear the flag so we +			 * will properly dump everything later. +			 */ +			msg->flags &= ~LOG_NOCONS; +			goto skip; +		} +		level = msg->level; +		len = msg_print_text(msg, console_prev, false, +				     text, sizeof(text));  		console_idx = log_next(console_idx);  		console_seq++; +		console_prev = msg->flags;  		raw_spin_unlock(&logbuf_lock);  		stop_critical_timings();	/* don't trace print latency */ @@ -2098,6 +2257,7 @@ void register_console(struct console *newcon)  		raw_spin_lock_irqsave(&logbuf_lock, flags);  		console_seq = syslog_seq;  		console_idx = syslog_idx; +		console_prev = syslog_prev;  		raw_spin_unlock_irqrestore(&logbuf_lock, flags);  		/*  		 * We're about to replay the log buffer.  Only do this to the @@ -2350,7 +2510,7 @@ void kmsg_dump(enum kmsg_dump_reason reason)  }  /** - * kmsg_dump_get_line - retrieve one kmsg log line + * kmsg_dump_get_line_nolock - retrieve one kmsg log line (unlocked version)   * @dumper: registered kmsg dumper   * @syslog: include the "<4>" prefixes   * @line: buffer to copy the line to @@ -2365,11 +2525,12 @@ void kmsg_dump(enum kmsg_dump_reason reason)   *   * A return value of FALSE indicates that there are no more records to   * read. + * + * The function is similar to kmsg_dump_get_line(), but grabs no locks.   */ -bool kmsg_dump_get_line(struct kmsg_dumper *dumper, bool syslog, -			char *line, size_t size, size_t *len) +bool kmsg_dump_get_line_nolock(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; @@ -2377,7 +2538,6 @@ bool kmsg_dump_get_line(struct kmsg_dumper *dumper, bool syslog,  	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; @@ -2385,31 +2545,57 @@ bool kmsg_dump_get_line(struct kmsg_dumper *dumper, bool syslog,  	}  	/* last entry */ -	if (dumper->cur_seq >= log_next_seq) { -		raw_spin_unlock_irqrestore(&logbuf_lock, flags); +	if (dumper->cur_seq >= log_next_seq)  		goto out; -	}  	msg = log_from_idx(dumper->cur_idx); -	l = msg_print_text(msg, syslog, -			      line, size); +	l = msg_print_text(msg, 0, 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;  } + +/** + * 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; +	bool ret; + +	raw_spin_lock_irqsave(&logbuf_lock, flags); +	ret = kmsg_dump_get_line_nolock(dumper, syslog, line, size, len); +	raw_spin_unlock_irqrestore(&logbuf_lock, flags); + +	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 + * @buf: buffer to copy the line to   * @size: maximum size of the buffer   * @len: length of line placed into buffer   * @@ -2432,6 +2618,7 @@ bool kmsg_dump_get_buffer(struct kmsg_dumper *dumper, bool syslog,  	u32 idx;  	u64 next_seq;  	u32 next_idx; +	enum log_flags prev;  	size_t l = 0;  	bool ret = false; @@ -2454,23 +2641,27 @@ bool kmsg_dump_get_buffer(struct kmsg_dumper *dumper, bool syslog,  	/* calculate length of entire buffer */  	seq = dumper->cur_seq;  	idx = dumper->cur_idx; +	prev = 0;  	while (seq < dumper->next_seq) {  		struct log *msg = log_from_idx(idx); -		l += msg_print_text(msg, true, NULL, 0); +		l += msg_print_text(msg, prev, true, NULL, 0);  		idx = log_next(idx);  		seq++; +		prev = msg->flags;  	}  	/* move first record forward until length fits into the buffer */  	seq = dumper->cur_seq;  	idx = dumper->cur_idx; +	prev = 0;  	while (l > size && seq < dumper->next_seq) {  		struct log *msg = log_from_idx(idx); -		l -= msg_print_text(msg, true, NULL, 0); +		l -= msg_print_text(msg, prev, true, NULL, 0);  		idx = log_next(idx);  		seq++; +		prev = msg->flags;  	}  	/* last message in next interation */ @@ -2478,14 +2669,14 @@ bool kmsg_dump_get_buffer(struct kmsg_dumper *dumper, bool syslog,  	next_idx = idx;  	l = 0; +	prev = 0;  	while (seq < dumper->next_seq) {  		struct log *msg = log_from_idx(idx); -		l += msg_print_text(msg, syslog, -				    buf + l, size - l); - +		l += msg_print_text(msg, prev, syslog, buf + l, size - l);  		idx = log_next(idx);  		seq++; +		prev = msg->flags;  	}  	dumper->next_seq = next_seq; @@ -2500,6 +2691,24 @@ out:  EXPORT_SYMBOL_GPL(kmsg_dump_get_buffer);  /** + * kmsg_dump_rewind_nolock - reset the interator (unlocked version) + * @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. + * + * The function is similar to kmsg_dump_rewind(), but grabs no locks. + */ +void kmsg_dump_rewind_nolock(struct kmsg_dumper *dumper) +{ +	dumper->cur_seq = clear_seq; +	dumper->cur_idx = clear_idx; +	dumper->next_seq = log_next_seq; +	dumper->next_idx = log_next_idx; +} + +/**   * kmsg_dump_rewind - reset the interator   * @dumper: registered kmsg dumper   * @@ -2512,10 +2721,7 @@ 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; +	kmsg_dump_rewind_nolock(dumper);  	raw_spin_unlock_irqrestore(&logbuf_lock, flags);  }  EXPORT_SYMBOL_GPL(kmsg_dump_rewind);  |