Commit 21f27291 authored by Linus Torvalds's avatar Linus Torvalds

Merge tag 'driver-core-3.5-rc5' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/driver-core

Pull driver Core fixes from Greg Kroah-Hartman:
 "Here is a number of printk() fixes, specifically a few reported by the
  crazy blog program that ships in SUSE releases (that's "boot log" and
  not "web log", it predates the general "blog" terminology by many
  years), and the restoration of the continuation line functionality
  reported by Stephen and others.  Yes, the changes seem a bit big this
  late in the cycle, but I've been beating on them for a while now, and
  Stephen has even optimized it a bit, so all looks good to me.

  The other change in here is a Documentation update for the stable
  kernel rules describing how some distro patches should be backported,
  to hopefully drive a bit more response from the distros to the stable
  kernel releases.

  Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>"

* tag 'driver-core-3.5-rc5' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/driver-core:
  printk: Optimize if statement logic where newline exists
  printk: flush continuation lines immediately to console
  syslog: fill buffer with more than a single message for SYSLOG_ACTION_READ
  Revert "printk: return -EINVAL if the message len is bigger than the buf size"
  printk: fix regression in SYSLOG_ACTION_CLEAR
  stable: Allow merging of backports for serious user-visible performance issues
parents 02529ba2 d3620822
...@@ -12,6 +12,12 @@ Rules on what kind of patches are accepted, and which ones are not, into the ...@@ -12,6 +12,12 @@ Rules on what kind of patches are accepted, and which ones are not, into the
marked CONFIG_BROKEN), an oops, a hang, data corruption, a real marked CONFIG_BROKEN), an oops, a hang, data corruption, a real
security issue, or some "oh, that's not good" issue. In short, something security issue, or some "oh, that's not good" issue. In short, something
critical. critical.
- Serious issues as reported by a user of a distribution kernel may also
be considered if they fix a notable performance or interactivity issue.
As these fixes are not as obvious and have a higher risk of a subtle
regression they should only be submitted by a distribution kernel
maintainer and include an addendum linking to a bugzilla entry if it
exists and additional information on the user-visible impact.
- New device IDs and quirks are also accepted. - New device IDs and quirks are also accepted.
- No "theoretical race condition" issues, unless an explanation of how the - No "theoretical race condition" issues, unless an explanation of how the
race can be exploited is also provided. race can be exploited is also provided.
......
...@@ -193,12 +193,19 @@ static int console_may_schedule; ...@@ -193,12 +193,19 @@ static int console_may_schedule;
* separated by ',', and find the message after the ';' character. * separated by ',', and find the message after the ';' character.
*/ */
enum log_flags {
LOG_DEFAULT = 0,
LOG_NOCONS = 1, /* already flushed, do not print to console */
};
struct log { struct log {
u64 ts_nsec; /* timestamp in nanoseconds */ u64 ts_nsec; /* timestamp in nanoseconds */
u16 len; /* length of entire record */ u16 len; /* length of entire record */
u16 text_len; /* length of text buffer */ u16 text_len; /* length of text buffer */
u16 dict_len; /* length of dictionary 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 */
}; };
/* /*
...@@ -286,6 +293,7 @@ static u32 log_next(u32 idx) ...@@ -286,6 +293,7 @@ static u32 log_next(u32 idx)
/* insert record into the buffer, discard old ones, update heads */ /* insert record into the buffer, discard old ones, update heads */
static void log_store(int facility, int level, static void log_store(int facility, int level,
enum log_flags flags, u64 ts_nsec,
const char *dict, u16 dict_len, const char *dict, u16 dict_len,
const char *text, u16 text_len) const char *text, u16 text_len)
{ {
...@@ -329,7 +337,12 @@ static void log_store(int facility, int level, ...@@ -329,7 +337,12 @@ static void log_store(int facility, int level,
msg->text_len = text_len; msg->text_len = text_len;
memcpy(log_dict(msg), dict, dict_len); memcpy(log_dict(msg), dict, dict_len);
msg->dict_len = dict_len; msg->dict_len = dict_len;
msg->level = (facility << 3) | (level & 7); 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(); msg->ts_nsec = local_clock();
memset(log_dict(msg) + dict_len, 0, pad_len); memset(log_dict(msg) + dict_len, 0, pad_len);
msg->len = sizeof(struct log) + text_len + dict_len + pad_len; msg->len = sizeof(struct log) + text_len + dict_len + pad_len;
...@@ -446,7 +459,7 @@ static ssize_t devkmsg_read(struct file *file, char __user *buf, ...@@ -446,7 +459,7 @@ static ssize_t devkmsg_read(struct file *file, char __user *buf,
ts_usec = msg->ts_nsec; ts_usec = msg->ts_nsec;
do_div(ts_usec, 1000); do_div(ts_usec, 1000);
len = sprintf(user->buf, "%u,%llu,%llu;", 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 */ /* escape non-printable characters */
for (i = 0; i < msg->text_len; i++) { for (i = 0; i < msg->text_len; i++) {
...@@ -787,6 +800,21 @@ static bool printk_time; ...@@ -787,6 +800,21 @@ static bool printk_time;
#endif #endif
module_param_named(time, printk_time, bool, S_IRUGO | S_IWUSR); 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) static size_t print_prefix(const struct log *msg, bool syslog, char *buf)
{ {
size_t len = 0; size_t len = 0;
...@@ -803,18 +831,7 @@ static size_t print_prefix(const struct log *msg, bool syslog, char *buf) ...@@ -803,18 +831,7 @@ static size_t print_prefix(const struct log *msg, bool syslog, char *buf)
} }
} }
if (printk_time) { len += print_time(msg->ts_nsec, buf ? buf + len : NULL);
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;
}
}
return len; return len;
} }
...@@ -862,28 +879,49 @@ static int syslog_print(char __user *buf, int size) ...@@ -862,28 +879,49 @@ static int syslog_print(char __user *buf, int size)
{ {
char *text; char *text;
struct log *msg; struct log *msg;
int len; int len = 0;
text = kmalloc(LOG_LINE_MAX, GFP_KERNEL); text = kmalloc(LOG_LINE_MAX, GFP_KERNEL);
if (!text) if (!text)
return -ENOMEM; return -ENOMEM;
while (size > 0) {
size_t n;
raw_spin_lock_irq(&logbuf_lock); raw_spin_lock_irq(&logbuf_lock);
if (syslog_seq < log_first_seq) { if (syslog_seq < log_first_seq) {
/* messages are gone, move to first one */ /* messages are gone, move to first one */
syslog_seq = log_first_seq; syslog_seq = log_first_seq;
syslog_idx = log_first_idx; syslog_idx = log_first_idx;
} }
if (syslog_seq == log_next_seq) {
raw_spin_unlock_irq(&logbuf_lock);
break;
}
msg = log_from_idx(syslog_idx); msg = log_from_idx(syslog_idx);
len = msg_print_text(msg, true, text, LOG_LINE_MAX); n = msg_print_text(msg, true, text, LOG_LINE_MAX);
if (n <= size) {
syslog_idx = log_next(syslog_idx); syslog_idx = log_next(syslog_idx);
syslog_seq++; syslog_seq++;
} else
n = 0;
raw_spin_unlock_irq(&logbuf_lock); raw_spin_unlock_irq(&logbuf_lock);
if (len > size) if (!n)
len = -EINVAL; break;
else if (len > 0 && copy_to_user(buf, text, len))
len += n;
size -= n;
buf += n;
n = copy_to_user(buf - n, text, n);
if (n) {
len -= n;
if (!len)
len = -EFAULT; len = -EFAULT;
break;
}
}
kfree(text); kfree(text);
return len; return len;
...@@ -1040,6 +1078,7 @@ int do_syslog(int type, char __user *buf, int len, bool from_file) ...@@ -1040,6 +1078,7 @@ int do_syslog(int type, char __user *buf, int len, bool from_file)
/* Clear ring buffer */ /* Clear ring buffer */
case SYSLOG_ACTION_CLEAR: case SYSLOG_ACTION_CLEAR:
syslog_print_all(NULL, 0, true); syslog_print_all(NULL, 0, true);
break;
/* Disable logging to console */ /* Disable logging to console */
case SYSLOG_ACTION_CONSOLE_OFF: case SYSLOG_ACTION_CONSOLE_OFF:
if (saved_console_loglevel == -1) if (saved_console_loglevel == -1)
...@@ -1272,15 +1311,92 @@ static inline void printk_delay(void) ...@@ -1272,15 +1311,92 @@ 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, asmlinkage int vprintk_emit(int facility, int level,
const char *dict, size_t dictlen, const char *dict, size_t dictlen,
const char *fmt, va_list args) const char *fmt, va_list args)
{ {
static int recursion_bug; 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]; static char textbuf[LOG_LINE_MAX];
char *text = textbuf; char *text = textbuf;
size_t text_len; size_t text_len;
...@@ -1326,7 +1442,8 @@ asmlinkage int vprintk_emit(int facility, int level, ...@@ -1326,7 +1442,8 @@ asmlinkage int vprintk_emit(int facility, int level,
recursion_bug = 0; recursion_bug = 0;
printed_len += strlen(recursion_msg); printed_len += strlen(recursion_msg);
/* emit KERN_CRIT message */ /* emit KERN_CRIT message */
log_store(0, 2, NULL, 0, recursion_msg, printed_len); log_store(0, 2, LOG_DEFAULT, 0,
NULL, 0, recursion_msg, printed_len);
} }
/* /*
...@@ -1364,55 +1481,37 @@ asmlinkage int vprintk_emit(int facility, int level, ...@@ -1364,55 +1481,37 @@ asmlinkage int vprintk_emit(int facility, int level,
} }
if (!newline) { if (!newline) {
if (cont_len && (prefix || cont_task != current)) {
/* /*
* Flush earlier buffer, which is either from a * Flush the conflicting buffer. An earlier newline was missing,
* different thread, or when we got a new prefix. * or another task also prints continuation lines.
*/ */
log_store(facility, cont_level, NULL, 0, cont_buf, cont_len); if (cont.len && (prefix || cont.owner != current))
cont_len = 0; cont_flush();
}
if (!cont_len) { /* buffer line if possible, otherwise store it right away */
cont_level = level; if (!cont_add(facility, level, text, text_len))
cont_task = current; log_store(facility, level, LOG_DEFAULT, 0,
} dict, dictlen, text, text_len);
/* 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;
} else { } else {
if (cont_len && cont_task == current) { bool stored = false;
if (prefix) {
/* /*
* New prefix from the same thread; flush. We * If an earlier newline was missing and it was the same task,
* either got no earlier newline, or we race * either merge it with the current buffer and flush, or if
* with an interrupt. * there was a race with interrupts (prefix == true) then just
*/ * flush it out and store this line separately.
log_store(facility, cont_level, */
NULL, 0, cont_buf, cont_len); if (cont.len && cont.owner == current) {
cont_len = 0; if (!prefix)
} stored = cont_add(facility, level, text, text_len);
cont_flush();
/* 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 (!stored)
log_store(facility, level, LOG_DEFAULT, 0,
dict, dictlen, text, text_len);
} }
printed_len += text_len;
/* /*
* Try to acquire and then immediately release the console semaphore. * Try to acquire and then immediately release the console semaphore.
...@@ -1499,11 +1598,18 @@ EXPORT_SYMBOL(printk); ...@@ -1499,11 +1598,18 @@ EXPORT_SYMBOL(printk);
#else #else
#define LOG_LINE_MAX 0 #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 struct log *log_from_idx(u32 idx) { return NULL; }
static u32 log_next(u32 idx) { return 0; } static u32 log_next(u32 idx) { return 0; }
static void call_console_drivers(int level, const char *text, size_t len) {} 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, static size_t msg_print_text(const struct log *msg, bool syslog,
char *buf, size_t size) { return 0; } char *buf, size_t size) { return 0; }
static size_t cont_print_text(char *text, size_t size) { return 0; }
#endif /* CONFIG_PRINTK */ #endif /* CONFIG_PRINTK */
...@@ -1795,6 +1901,7 @@ static u32 console_idx; ...@@ -1795,6 +1901,7 @@ static u32 console_idx;
*/ */
void console_unlock(void) void console_unlock(void)
{ {
static char text[LOG_LINE_MAX];
static u64 seen_seq; static u64 seen_seq;
unsigned long flags; unsigned long flags;
bool wake_klogd = false; bool wake_klogd = false;
...@@ -1807,10 +1914,23 @@ void console_unlock(void) ...@@ -1807,10 +1914,23 @@ void console_unlock(void)
console_may_schedule = 0; 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: again:
for (;;) { for (;;) {
struct log *msg; struct log *msg;
static char text[LOG_LINE_MAX];
size_t len; size_t len;
int level; int level;
...@@ -1825,13 +1945,22 @@ void console_unlock(void) ...@@ -1825,13 +1945,22 @@ void console_unlock(void)
console_seq = log_first_seq; console_seq = log_first_seq;
console_idx = log_first_idx; console_idx = log_first_idx;
} }
skip:
if (console_seq == log_next_seq) if (console_seq == log_next_seq)
break; break;
msg = log_from_idx(console_idx); msg = log_from_idx(console_idx);
level = msg->level & 7; 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++;
goto skip;
}
level = msg->level;
len = msg_print_text(msg, false, text, sizeof(text)); len = msg_print_text(msg, false, text, sizeof(text));
console_idx = log_next(console_idx); console_idx = log_next(console_idx);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment