From 3c405ae0b6c719f36903297a8c940172b0d4cb4c Mon Sep 17 00:00:00 2001 From: Collin Funk Date: Thu, 4 Dec 2025 22:14:31 -0800 Subject: [PATCH] pinky: promptly diagnose write errors MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit In some cases 'pinky' could run forever until interrupted: $ ln -s /dev/zero ~/.plan $ ln -s /dev/zero ~/.project $ timeout -v 5 pinky -l collin > /dev/full timeout: sending signal TERM to command ‘pinky’ After this change it will exit upon failing to write to standard output: $ timeout -v 5 ./src/pinky -l collin > /dev/full pinky: write error: No space left on device * src/pinky.c: Include fadvise.h, filenamecat.h, full-write.h, and ioblksize.h. (cat_file): New function. (print_entry): Check if standard output has it's error flag set after printing a user entry. (print_long_entry): Likewise. Use the new cat_file function. * NEWS: Mention the improvement. --- NEWS | 3 ++ src/pinky.c | 83 +++++++++++++++++++++++++---------------------------- 2 files changed, 42 insertions(+), 44 deletions(-) diff --git a/NEWS b/NEWS index 127d70ae08..b283b6908b 100644 --- a/NEWS +++ b/NEWS @@ -48,6 +48,9 @@ GNU coreutils NEWS -*- outline -*- csplit, ls, and sort, now handle a more complete set of terminating signals. + 'pinky' will now exit immediately upon receiving a write error, which is + significant when reading large plan or project files. + 'timeout' on Linux will always terminate the child in the case where the timeout process itself dies, like when it receives a KILL signal for example. diff --git a/src/pinky.c b/src/pinky.c index b49096b357..6414446bd9 100644 --- a/src/pinky.c +++ b/src/pinky.c @@ -26,7 +26,11 @@ #include "system.h" #include "canon-host.h" +#include "fadvise.h" +#include "filenamecat.h" +#include "full-write.h" #include "hard-locale.h" +#include "ioblksize.h" #include "readutmp.h" /* The official name of this program (e.g., no 'g' prefix). */ @@ -309,6 +313,36 @@ print_entry (STRUCT_UTMP const *utmp_ent) #endif putchar ('\n'); + + if (ferror (stdout)) + write_error (); +} + +/* If FILE exists in HOME, print it to standard output, preceded by HEADER. */ + +static void +cat_file (char const *header, char const *home, char const *file) +{ + char *full_name = file_name_concat (home, file, nullptr); + int fd = open (full_name, O_RDONLY); + + if (0 <= fd) + { + idx_t header_len = strlen (header); + if (write (STDOUT_FILENO, header, header_len) != header_len) + write_error (); + + fdadvise (fd, 0, 0, FADVISE_SEQUENTIAL); + + char buf[IO_BUFSIZE]; + for (ssize_t bytes_read; 0 < (bytes_read = read (fd, buf, sizeof buf));) + if (full_write (STDOUT_FILENO, buf, bytes_read) != bytes_read) + write_error (); + + close (fd); + } + + free (full_name); } /* Display a verbose line of information about UTMP_ENT. */ @@ -355,54 +389,15 @@ print_long_entry (const char name[]) } if (include_project) - { - FILE *stream; - char buf[1024]; - char const *const baseproject = "/.project"; - char *const project = - xmalloc (strlen (pw->pw_dir) + strlen (baseproject) + 1); - stpcpy (stpcpy (project, pw->pw_dir), baseproject); - - stream = fopen (project, "r"); - if (stream) - { - size_t bytes; - - printf (_("Project: ")); - - while ((bytes = fread (buf, 1, sizeof (buf), stream)) > 0) - fwrite (buf, 1, bytes, stdout); - fclose (stream); - } - - free (project); - } + cat_file (_("Project: "), pw->pw_dir, ".project"); if (include_plan) - { - FILE *stream; - char buf[1024]; - char const *const baseplan = "/.plan"; - char *const plan = - xmalloc (strlen (pw->pw_dir) + strlen (baseplan) + 1); - stpcpy (stpcpy (plan, pw->pw_dir), baseplan); - - stream = fopen (plan, "r"); - if (stream) - { - size_t bytes; - - printf (_("Plan:\n")); - - while ((bytes = fread (buf, 1, sizeof (buf), stream)) > 0) - fwrite (buf, 1, bytes, stdout); - fclose (stream); - } - - free (plan); - } + cat_file (_("Plan:\n"), pw->pw_dir, ".plan"); putchar ('\n'); + + if (ferror (stdout)) + write_error (); } /* Print the username of each valid entry and the number of valid entries -- 2.47.3