Skip to content

Commit 4bcc595

Browse files
committed
printk: reinstate KERN_CONT for printing continuation lines
Long long ago the kernel log buffer was a buffered stream of bytes, very much like stdio in user space. It supported log levels by scanning the stream and noticing the log level markers at the beginning of each line, but if you wanted to print a partial line in multiple chunks, you just did multiple printk() calls, and it just automatically worked. Except when it didn't, and you had very confusing output when different lines got all mixed up with each other. Then you got fragment lines mixing with each other, or with non-fragment lines, because it was traditionally impossible to tell whether a printk() call was a continuation or not. To at least help clarify the issue of continuation lines, we added a KERN_CONT marker back in 2007 to mark continuation lines: 4749252 ("printk: add KERN_CONT annotation"). That continuation marker was initially an empty string, and didn't actuall make any semantic difference. But it at least made it possible to annotate the source code, and have check-patch notice that a printk() didn't need or want a log level marker, because it was a continuation of a previous line. To avoid the ambiguity between a continuation line that had that KERN_CONT marker, and a printk with no level information at all, we then in 2009 made KERN_CONT be a real log level marker which meant that we could now reliably tell the difference between the two cases. 5fd29d6 ("printk: clean up handling of log-levels and newlines") and we could take advantage of that to make sure we didn't mix up continuation lines with lines that just didn't have any loglevel at all. Then, in 2012, the kernel log buffer was changed to be a "record" based log, where each line was a record that has a loglevel and a timestamp. You can see the beginning of that conversion in commits e11fea9 ("kmsg: export printk records to the /dev/kmsg interface") 7ff9554 ("printk: convert byte-buffer to variable-length record buffer") with a number of follow-up commits to fix some painful fallout from that conversion. Over all, it took a couple of months to sort out most of it. But the upside was that you could have concurrent readers (and writers) of the kernel log and not have lines with mixed output in them. And one particular pain-point for the record-based kernel logging was exactly the fragmentary lines that are generated in smaller chunks. In order to still log them as one recrod, the continuation lines need to be attached to the previous record properly. However the explicit continuation record marker that is actually useful for this exact case was actually removed in aroundm the same time by commit 61e99ab ("printk: remove the now unnecessary "C" annotation for KERN_CONT") due to the incorrect belief that KERN_CONT wasn't meaningful. The ambiguity between "is this a continuation line" or "is this a plain printk with no log level information" was reintroduced, and in fact became an even bigger pain point because there was now the whole record-level merging of kernel messages going on. This patch reinstates the KERN_CONT as a real non-empty string marker, so that the ambiguity is fixed once again. But it's not a plain revert of that original removal: in the four years since we made KERN_CONT an empty string again, not only has the format of the log level markers changed, we've also had some usage changes in this area. For example, some ACPI code seems to use KERN_CONT _together_ with a log level, and now uses both the KERN_CONT marker and (for example) a KERN_INFO marker to show that it's an informational continuation of a line. Which is actually not a bad idea - if the continuation line cannot be attached to its predecessor, without the log level information we don't know what log level to assign to it (and we traditionally just assigned it the default loglevel). So having both a log level and the KERN_CONT marker is not necessarily a bad idea, but it does mean that we need to actually iterate over potentially multiple markers, rather than just a single one. Also, since KERN_CONT was still conceptually needed, and encouraged, but didn't actually _do_ anything, we've also had the reverse problem: rather than having too many annotations it has too few, and there is bit rot with code that no longer marks the continuation lines with the KERN_CONT marker. So this patch not only re-instates the non-empty KERN_CONT marker, it also fixes up the cases of bit-rot I noticed in my own logs. There are probably other cases where KERN_CONT will be needed to be added, either because it is new code that never dealt with the need for KERN_CONT, or old code that has bitrotted without anybody noticing. That said, we should strive to avoid the need for KERN_CONT. It does result in real problems for logging, and should generally not be seen as a good feature. If we some day can get rid of the feature entirely, because nobody does any fragmented printk calls, that would be lovely. But until that point, let's at mark the code that relies on the hacky multi-fragment kernel printk's. Not only does it avoid the ambiguity, it also annotates code as "maybe this would be good to fix some day". (That said, particularly during single-threaded bootup, the downsides of KERN_CONT are very limited. Things get much hairier when you have multiple threads going on and user level reading and writing logs too). Signed-off-by: Linus Torvalds <[email protected]>
1 parent c8d2bc9 commit 4bcc595

File tree

5 files changed

+19
-23
lines changed

5 files changed

+19
-23
lines changed

drivers/tty/vt/vt.c

+3-3
Original file line numberDiff line numberDiff line change
@@ -3182,11 +3182,11 @@ static int do_bind_con_driver(const struct consw *csw, int first, int last,
31823182

31833183
pr_info("Console: switching ");
31843184
if (!deflt)
3185-
printk("consoles %d-%d ", first+1, last+1);
3185+
printk(KERN_CONT "consoles %d-%d ", first+1, last+1);
31863186
if (j >= 0) {
31873187
struct vc_data *vc = vc_cons[j].d;
31883188

3189-
printk("to %s %s %dx%d\n",
3189+
printk(KERN_CONT "to %s %s %dx%d\n",
31903190
vc->vc_can_do_color ? "colour" : "mono",
31913191
desc, vc->vc_cols, vc->vc_rows);
31923192

@@ -3195,7 +3195,7 @@ static int do_bind_con_driver(const struct consw *csw, int first, int last,
31953195
update_screen(vc);
31963196
}
31973197
} else
3198-
printk("to %s\n", desc);
3198+
printk(KERN_CONT "to %s\n", desc);
31993199

32003200
retval = 0;
32013201
err:

include/linux/kern_levels.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
* line that had no enclosing \n). Only to be used by core/arch code
2121
* during early bootup (a continued line is not SMP-safe otherwise).
2222
*/
23-
#define KERN_CONT ""
23+
#define KERN_CONT KERN_SOH "c"
2424

2525
/* integer equivalents of KERN_<LEVEL> */
2626
#define LOGLEVEL_SCHED -2 /* Deferred messages from sched code

include/linux/printk.h

+1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ static inline int printk_get_level(const char *buffer)
1616
switch (buffer[1]) {
1717
case '0' ... '7':
1818
case 'd': /* KERN_DEFAULT */
19+
case 'c': /* KERN_CONT */
1920
return buffer[1];
2021
}
2122
}

kernel/printk/printk.c

+12-17
Original file line numberDiff line numberDiff line change
@@ -655,11 +655,8 @@ static ssize_t msg_print_ext_header(char *buf, size_t size,
655655
* better readable output. 'c' in the record flags mark the first
656656
* fragment of a line, '+' the following.
657657
*/
658-
if (msg->flags & LOG_CONT && !(prev_flags & LOG_CONT))
659-
cont = 'c';
660-
else if ((msg->flags & LOG_CONT) ||
661-
((prev_flags & LOG_CONT) && !(msg->flags & LOG_PREFIX)))
662-
cont = '+';
658+
if (msg->flags & LOG_CONT)
659+
cont = (prev_flags & LOG_CONT) ? '+' : 'c';
663660

664661
return scnprintf(buf, size, "%u,%llu,%llu,%c;",
665662
(msg->facility << 3) | msg->level, seq, ts_usec, cont);
@@ -1819,25 +1816,23 @@ asmlinkage int vprintk_emit(int facility, int level,
18191816

18201817
/* strip kernel syslog prefix and extract log level or control flags */
18211818
if (facility == 0) {
1822-
int kern_level = printk_get_level(text);
1819+
int kern_level;
18231820

1824-
if (kern_level) {
1825-
const char *end_of_header = printk_skip_level(text);
1821+
while ((kern_level = printk_get_level(text)) != 0) {
18261822
switch (kern_level) {
18271823
case '0' ... '7':
18281824
if (level == LOGLEVEL_DEFAULT)
18291825
level = kern_level - '0';
18301826
/* fallthrough */
18311827
case 'd': /* KERN_DEFAULT */
18321828
lflags |= LOG_PREFIX;
1829+
break;
1830+
case 'c': /* KERN_CONT */
1831+
lflags |= LOG_CONT;
18331832
}
1834-
/*
1835-
* No need to check length here because vscnprintf
1836-
* put '\0' at the end of the string. Only valid and
1837-
* newly printed level is detected.
1838-
*/
1839-
text_len -= end_of_header - text;
1840-
text = (char *)end_of_header;
1833+
1834+
text_len -= 2;
1835+
text += 2;
18411836
}
18421837
}
18431838

@@ -1852,7 +1847,7 @@ asmlinkage int vprintk_emit(int facility, int level,
18521847
* Flush the conflicting buffer. An earlier newline was missing,
18531848
* or another task also prints continuation lines.
18541849
*/
1855-
if (cont.len && (lflags & LOG_PREFIX || cont.owner != current))
1850+
if (cont.len && (!(lflags & LOG_CONT) || cont.owner != current))
18561851
cont_flush(LOG_NEWLINE);
18571852

18581853
/* buffer line if possible, otherwise store it right away */
@@ -1874,7 +1869,7 @@ asmlinkage int vprintk_emit(int facility, int level,
18741869
* a newline, flush and append the newline.
18751870
*/
18761871
if (cont.len) {
1877-
if (cont.owner == current && !(lflags & LOG_PREFIX))
1872+
if (cont.owner == current && (lflags & LOG_CONT))
18781873
stored = cont_add(facility, level, text,
18791874
text_len);
18801875
cont_flush(LOG_NEWLINE);

security/selinux/ss/policydb.c

+2-2
Original file line numberDiff line numberDiff line change
@@ -527,9 +527,9 @@ static int policydb_index(struct policydb *p)
527527
printk(KERN_DEBUG "SELinux: %d users, %d roles, %d types, %d bools",
528528
p->p_users.nprim, p->p_roles.nprim, p->p_types.nprim, p->p_bools.nprim);
529529
if (p->mls_enabled)
530-
printk(", %d sens, %d cats", p->p_levels.nprim,
530+
printk(KERN_CONT ", %d sens, %d cats", p->p_levels.nprim,
531531
p->p_cats.nprim);
532-
printk("\n");
532+
printk(KERN_CONT "\n");
533533

534534
printk(KERN_DEBUG "SELinux: %d classes, %d rules\n",
535535
p->p_classes.nprim, p->te_avtab.nel);

0 commit comments

Comments
 (0)