LINE_MAX only applies to teletypes in canonical mode: when stdin is a file, it could still very much tear; start off at 512 for the sprintf(), then use getline() like in write. The line wrapping has one suboptimal edge-case: $ wall < all Broadcast message from nabijaczleweli@tarta (pts/4) (Tue Mar 14 22:31:25 2023): ^N^O^P^Q^R^S^T^U^V^W^X^Y^Z^[^\^]^^^_ !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJ KLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~^?\200\201\202\203\204\205\206 \207\210\211\212\213\214\215\216\217\220\221\222\223\224\225\226\227\230\231\232 \233\234\235\236\237\240\241\242\243\244\245\246\247\250\251\252\253\254\255\256 \257\260\261\262\263\264\265\266\267\270\271\272\273\274\275\276\277\300\301\302 \303\304\305\306\307\310\311\312\313\314\315\316\317\320\321\322\323\324\325\326 \327\330\331\332\333\334\335\336\337\340\341\342\343\344\345\346\347\350\351\352 \353\354\355\356\357\360\361\362\363\364\365\366\367\370\371\372\373\374\375\376 \377 but that's a pathological input, and the result is still infinitely better than it was before, so fixing that is more trouble than it's worth. Bug-Debian: https://bugs.debian.org/826596 --- include/carefulputc.h | 42 +++++++++++++++++++++++++++++++++--------- login-utils/last.c | 2 +- term-utils/wall.c | 38 ++++++-------------------------------- term-utils/write.c | 2 +- 4 files changed, 41 insertions(+), 43 deletions(-) diff --git a/include/carefulputc.h b/include/carefulputc.h index 740add68e..3cc6f7ff9 100644 --- a/include/carefulputc.h +++ b/include/carefulputc.h @@ -6,6 +6,7 @@ #include #ifdef HAVE_WIDECHAR #include +#include #endif #include @@ -15,18 +16,35 @@ * A puts() for use in write and wall (that sometimes are sgid tty). * It avoids control and invalid characters. * The locale of the recipient is nominally unknown, - * but it's a solid bet that the encoding is compatible with the author's. + * but it's a solid bet that it's compatible with the author's. + * Use soft_width=0 to disable wrapping. */ -static inline int fputs_careful(const char * s, FILE *fp, const char ctrl, bool cr_lf) +static inline int fputs_careful(const char * s, FILE *fp, const char ctrl, bool cr_lf, int soft_width) { - int ret = 0; + int ret = 0, col = 0; for (size_t slen = strlen(s); *s; ++s, --slen) { - if (*s == '\n') + if (*s == '\t') + col += (7 - (col % 8)) - 1; + else if (*s == '\r') + col = -1; + else if (*s == '\a') + --col; + + if ((soft_width && col >= soft_width) || *s == '\n') { + if (soft_width) { + fprintf(fp, "%*s", soft_width - col, ""); + col = 0; + } ret = fputs(cr_lf ? "\r\n" : "\n", fp); - else if (isprint(*s) || *s == '\a' || *s == '\t' || *s == '\r') + if (*s == '\n' || ret < 0) + goto wrote; + } + + if (isprint(*s) || *s == '\a' || *s == '\t' || *s == '\r') { ret = putc(*s, fp); - else if (!c_isascii(*s)) { + ++col; + } else if (!c_isascii(*s)) { #ifdef HAVE_WIDECHAR wchar_t w; size_t clen = mbtowc(&w, s, slen); @@ -35,21 +53,27 @@ static inline int fputs_careful(const char * s, FILE *fp, const char ctrl, bool case (size_t)-1: // EILSEQ mbtowc(NULL, NULL, 0); nonprint: - ret = fprintf(fp, "\\%3hho", *s); + col += ret = fprintf(fp, "\\%3hho", *s); break; default: if(!iswprint(w)) goto nonprint; ret = fwrite(s, 1, clen, fp); + if (soft_width) + col += wcwidth(w); s += clen - 1; slen -= clen - 1; break; } #else - ret = fprintf(fp, "\\%3hho", *s); + col += ret = fprintf(fp, "\\%3hho", *s); #endif - } else + } else { ret = fputs((char[]){ ctrl, *s ^ 0x40, '\0' }, fp); + col += 2; + } + + wrote: if (ret < 0) return EOF; } diff --git a/login-utils/last.c b/login-utils/last.c index 1b45dbf24..37c6abe97 100644 --- a/login-utils/last.c +++ b/login-utils/last.c @@ -547,7 +547,7 @@ static int list(const struct last_control *ctl, struct utmpx *p, time_t logout_t /* * Print out "final" string safely. */ - fputs_careful(final, stdout, '*', false); + fputs_careful(final, stdout, '*', false, 0); if (len < 0 || (size_t)len >= sizeof(final)) putchar('\n'); diff --git a/term-utils/wall.c b/term-utils/wall.c index a51a92829..377db4518 100644 --- a/term-utils/wall.c +++ b/term-utils/wall.c @@ -274,29 +274,13 @@ int main(int argc, char **argv) exit(EXIT_SUCCESS); } -static void buf_putc_careful(FILE *fs, int c) -{ - if (isprint(c) || c == '\a' || c == '\t' || c == '\r' || c == '\n') - fputc(c, fs); - else if (!c_isascii(c)) - fprintf(fs, "\\%3o", (unsigned char)c); - else - fputs((char[]){ '^', c ^ 0x40, '\0' }, fs); -} - static char *makemsg(char *fname, char **mvec, int mvecsz, size_t *mbufsize, int print_banner) { - register int ch, cnt; - char *p, *lbuf, *retbuf; + char *lbuf, *retbuf; FILE * fs = open_memstream(&retbuf, mbufsize); - long line_max; - - line_max = sysconf(_SC_LINE_MAX); - if (line_max <= 0) - line_max = 512; - - lbuf = xmalloc(line_max); + size_t lbuflen = 512; + lbuf = xmalloc(lbuflen); if (print_banner == TRUE) { char *hostname = xgethostname(); @@ -329,7 +313,7 @@ static char *makemsg(char *fname, char **mvec, int mvecsz, will not overflow as long as %d takes at most 100 chars */ fprintf(fs, "\r%*s\r\n", TERM_WIDTH, " "); - snprintf(lbuf, line_max, + snprintf(lbuf, lbuflen, _("Broadcast message from %s@%s (%s) (%s):"), whom, hostname, where, date); fprintf(fs, "%-*.*s\007\007\r\n", TERM_WIDTH, TERM_WIDTH, lbuf); @@ -373,18 +357,8 @@ static char *makemsg(char *fname, char **mvec, int mvecsz, /* * Read message from stdin. */ - while (fgets(lbuf, line_max, stdin)) { - for (cnt = 0, p = lbuf; (ch = *p) != '\0'; ++p, ++cnt) { - if (cnt == TERM_WIDTH || ch == '\n') { - fprintf(fs, "%*s\r\n", TERM_WIDTH - cnt, ""); - cnt = 0; - } - if (ch == '\t') - cnt += (7 - (cnt % 8)); - if (ch != '\n') - buf_putc_careful(fs, ch); - } - } + while (getline(&lbuf, &lbuflen, stdin) >= 0) + fputs_careful(lbuf, fs, '^', true, TERM_WIDTH); } fprintf(fs, "%*s\r\n", TERM_WIDTH, " "); diff --git a/term-utils/write.c b/term-utils/write.c index b485e28fd..a5a21280c 100644 --- a/term-utils/write.c +++ b/term-utils/write.c @@ -276,7 +276,7 @@ static void do_write(const struct write_control *ctl) if (signal_received) break; - if (fputs_careful(line, stdout, '^', true) == EOF) + if (fputs_careful(line, stdout, '^', true, 0) == EOF) err(EXIT_FAILURE, _("carefulputc failed")); } free(line); -- 2.30.2