On 2020-06-25, Petr Mladek <pmla...@suse.com> wrote:
>> --- a/kernel/printk/printk.c
>> +++ b/kernel/printk/printk.c
>> @@ -1344,72 +1304,150 @@ static size_t print_prefix(const struct printk_log 
>> *msg, bool syslog,
>>      return len;
>>  }
>>  
>> -static size_t msg_print_text(const struct printk_log *msg, bool syslog,
>> -                         bool time, char *buf, size_t size)
>> +static size_t record_print_text(struct printk_record *r, bool syslog,
>> +                            bool time)
>>  {
>> -    const char *text = log_text(msg);
>> -    size_t text_size = msg->text_len;
>> -    size_t len = 0;
>> +    size_t text_len = r->info->text_len;
>> +    size_t buf_size = r->text_buf_size;
>> +    char *text = r->text_buf;
>>      char prefix[PREFIX_MAX];
>> -    const size_t prefix_len = print_prefix(msg, syslog, time, prefix);
>> +    bool truncated = false;
>> +    size_t prefix_len;
>> +    size_t line_len;
>> +    size_t len = 0;
>> +    char *next;
>>  
>> -    do {
>> -            const char *next = memchr(text, '\n', text_size);
>> -            size_t text_len;
>> +    prefix_len = info_print_prefix(r->info, syslog, time, prefix);
>>  
>> +    /*
>> +     * Add the prefix for each line by shifting the rest of the text to
>> +     * make room for each prefix. If the buffer is not large enough for
>> +     * all the prefixes, then drop the trailing text and report the
>> +     * largest length that includes full lines with their prefixes.
>
> This should go up as the function description.
>
> Sigh, this function does (already did) many subltle decisions. We
> should mention them in the description. My current understanding is
> the following:
>
> /*
>  * Prepare the record for printing. The text is shifted in the given
>  * buffer to avoid a need for another one. The following operations
>  * are done:
>  *
>  *   - Add prefix for each line.
>  *   - Add the trailing newline that has been removed in vprintk_store().
>  *   - Drop truncated lines that do not longer fit into the buffer.
>  *
>  * Return: The length of the updated text, including the added
>  * prefixes, newline. The dropped line(s) are not counted.
>  */

OK.

>> +     *
>> +     * @text_len: bytes of unprocessed text
>> +     * @line_len: bytes of current line (newline _not_ included)
>> +     * @text:     pointer to beginning of current line
>> +     * @len:      number of bytes processed (size of r->text_buf done)
>
> Please, move this to the variable declaration.

I find it more helpful to see these critical definitions directly above
the loop. When I add them to the variable declarations, I find it really
ugly and hard to reference. Maybe you could show me an example of what
you want it to look like?

>> +     */
>> +    for (;;) {
>> +            next = memchr(text, '\n', text_len);
>>              if (next) {
>> -                    text_len = next - text;
>> -                    next++;
>> -                    text_size -= next - text;
>> +                    line_len = next - text;
>>              } else {
>> -                    text_len = text_size;
>> +                    /*
>> +                     * No newline. If the text was previously truncated,
>> +                     * assume this line was truncated and do not include
>> +                     * it.
>> +                     */
>
> The word "assume" is confusing. The last line _must_ have been truncated when
> "truncated" is set. I would write:
>
>                       /* Drop incomplete truncated lines. */

OK.

>> +                    if (truncated)
>> +                            break;
>> +                    line_len = text_len;
>>              }
>>  
>> -            if (buf) {
>> -                    if (prefix_len + text_len + 1 >= size - len)
>> +            /*
>> +             * Ensure there is enough buffer available to shift this line
>> +             * (and add a newline at the end).
>> +             */
>> +            if (len + prefix_len + line_len + 1 > buf_size)
>> +                    break;
>> +
>> +            /*
>> +             * Ensure there is enough buffer available to shift all
>> +             * remaining text (and add a newline at the end). If this
>> +             * test fails, then there must be a newline (i.e.
>> +             * text_len > line_len because the previous test succeeded).
>> +             */
>> +            if (len + prefix_len + text_len + 1 > buf_size) {
>> +                    /*
>> +                     * Truncate @text_len so that there is enough space
>> +                     * for a prefix. A newline will not be added because
>> +                     * the last line of the text is now truncated and
>> +                     * will not be included.
>> +                     */
>> +                    text_len = (buf_size - len) - prefix_len;
>
> This is confusing. The check requires that one more character gets truncated.
> And it should be perfectly fine to truncate '\n' from the previous
> line. The final '\n' is always added.

Agreed.

>> +
>> +                    /*
>> +                     * Ensure there is still a newline. Otherwise this
>> +                     * line may have been truncated and will not be
>> +                     * included.
>> +                     */
>> +                    if (memchr(text, '\n', text_len) == NULL)
>  >                            break;
>
> This looks superfluous. We do this check only when the first check of 
> "line_len"
> passed. It means that at least one line is printable.

You are correct.

> I would personally replace this part of the code by:
>
>               /*
>                * Truncate the remaining text when it does not longer
>                * fit into the buffer.
>                *
>                * Note that this check could fail only when
>                * text_len > line_len because the previous check
>                * passed.
>                */
>               if (len + prefix_len + text_len + 1 > buf_size) {
>                       text_len = buf_size - len - prefix_len - 1;
>                       truncated = 1;
>               }
>
> Or we could actually switch the two checks and do:
>
>               /*
>                * Truncate the text if it would not longer fit into
>                * the given buffer.
>                */
>               if (len + prefix_len + text_len + 1 > buf_size) {
>                       text_len = buf_size - len - prefix_len - 1;
>                       truncated = 1;
>               }
>
>               /* Drop even the current line when truncated */
>               if (line_len > text_len)
>                       break;

Yes, I like how you switched the checks. Also, the second check can be
nested inside the first one.

However, the "line_len > text_len" check will not work here because
text_len is unsigned and could underflow with this logic. I think doing
the math twice (as I did) is the simplest.

>> -                    memcpy(buf + len, prefix, prefix_len);
>> -                    len += prefix_len;
>> -                    memcpy(buf + len, text, text_len);
>> -                    len += text_len;
>> -                    buf[len++] = '\n';
>> -            } else {
>> -                    /* SYSLOG_ACTION_* buffer size only calculation */
>> -                    len += prefix_len + text_len + 1;
>> +                    /* Note that the last line will not be included. */
>> +                    truncated = true;
>>              }
>>  
>> -            text = next;
>> -    } while (text);
>> +            memmove(text + prefix_len, text, text_len);
>> +            memcpy(text, prefix, prefix_len);
>> +
>> +            /* Advance beyond the newly added prefix and existing line. */
>> +            text += prefix_len + line_len;
>> +
>> +            /* The remaining text has only decreased by the line. */
>> +            text_len -= line_len;
>> +
>> +            len += prefix_len + line_len + 1;
>> +
>> +            if (text_len) {
>> +                    /* Advance past the newline. */
>> +                    text++;
>> +                    text_len--;
>
> "text_len" might be zero at this point when the stored string ended
> with newline.
>
> It might happen already now when the original text ended by "\n\n".
> Only the very last '\n' gets removed in vprintk_store().
>
> It actually looks reasonable to do the extra cycle and printk
> the (requested) empty line.
>
> Except that we might end here also when the text was truncated right
> before the newline marker. Well, this is a corner case, the string
> is truncated anyway, so we could do whatever is easier in this case.

I agree that the code is quite tricky here. I will refactor as you have
suggested.

> Well, it will need another update after we remove logbuf_lock. I think that
> we will need to store the final '\n'. Or at least we would need to
> reserve a space for it.

What does the logbuf_lock have to do with this? What does this have to
do with reserving extra space? We are modifying the reader's buffer
in-place, not the datablock data.

> Anyway, it might deserve some comment somewhere. See below for
> alternative code.
>
>> +            } else {
>> +                    /* The final line, add a newline. */
>> +                    *text = '\n';
>> +                    break;
>> +            }
>
> I do not have strong opinion. The code does things by small steps that
> might be checked easily. But it has many steps and it might hide some
> subtle catches like the one commented right above.

Your suggestions to simplify are good.

>> +static size_t get_record_text_size(struct printk_info *info,
>> +                               unsigned int line_count,
>> +                               bool syslog, bool time)
>> +{
>> +    char prefix[PREFIX_MAX];
>> +    size_t prefix_len;
>> +
>> +    prefix_len = info_print_prefix(info, syslog, time, prefix);
>> +
>> +    /*
>> +     * Each line will be preceded with a prefix. The intermediate
>> +     * newlines are already within the text, but a final trailing
>> +     * newline will be added.
>> +     */
>> +    return ((prefix_len * line_count) + info->text_len + 1);
>
> Note: This might need an update when lockbuf_lock gets removed and the
> trailing newline is not longer removed. We will need to explicitly
> add it when it was missing.
>
> We might need to check LOG_NEWLINE flag here. Or come with even better
> solution.

Again, I do not understand what this has to do with the
lockbuf_lock. Please explain.

Below is how I would like to implement record_print_text() after
incorporating your suggestions. Please show me how you would like to see
the important variable role comments formatted. Thanks.

John Ogness

/*
 * Prepare the record for printing. The text is shifted within the given
 * buffer to avoid a need for another one. The following operations are
 * done:
 *
 *   - Add prefix for each line.
 *   - Add the trailing newline that has been removed in vprintk_store().
 *   - Drop truncated lines that do not longer fit into the buffer.
 *
 * Return: The length of the updated/prepared text, including the added
 * prefixes and the newline. The dropped line(s) are not counted.
 */
static size_t record_print_text(struct printk_record *r, bool syslog,
                                bool time)
{
        size_t text_len = r->info->text_len;
        size_t buf_size = r->text_buf_size;
        char *text = r->text_buf;
        char prefix[PREFIX_MAX];
        bool truncated = false;
        size_t prefix_len;
        size_t line_len;
        size_t len = 0;
        char *next;

        prefix_len = info_print_prefix(r->info, syslog, time, prefix);

        /*
         * @text_len: bytes of unprocessed text
         * @line_len: bytes of current line _without_ newline
         * @text:     pointer to beginning of current line
         * @len:      number of bytes prepared in r->text_buf
         */
        for (;;) {
                next = memchr(text, '\n', text_len);
                if (next) {
                        line_len = next - text;
                } else {
                        /* Drop truncated line(s). */
                        if (truncated)
                                break;
                        line_len = text_len;
                }

                /*
                 * Truncate the text if there is not enough space to add the
                 * prefix and a trailing newline.
                 */
                if (len + prefix_len + text_len + 1 > buf_size) {
                        /* Drop even the current line if no space. */
                        if (len + prefix_len + line_len + 1 > buf_size)
                                break;

                        text_len = buf_size - len - prefix_len - 1;
                        truncated = true;
                }

                memmove(text + prefix_len, text, text_len);
                memcpy(text, prefix, prefix_len);

                len += prefix_len + line_len + 1;

                if (text_len == line_len) {
                        /*
                         * Add the trailing newline removed in
                         * vprintk_store().
                         */
                        text[prefix_len + line_len] = '\n';
                        break;
                }

                /*
                 * Advance beyond the added prefix and the related line with
                 * its newline.
                 */
                text += prefix_len + line_len + 1;

                /*
                 * The remaining text has only decreased by the line with its
                 * newline.
                 *
                 * Note that @text_len can become zero. It happens when @text
                 * ended with a newline (either due to truncation or the
                 * original string ending with "\n\n"). The loop is correctly
                 * repeated and (if not truncated) an empty line with a prefix
                 * will be prepared.
                 */
                text_len -= line_len + 1;
        }

        return len;
}

_______________________________________________
kexec mailing list
kexec@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/kexec

Reply via email to