diff --git a/autoconf/configure.in b/autoconf/configure.in index 71584cd..2fb99a3 100644 --- a/autoconf/configure.in +++ b/autoconf/configure.in @@ -39,7 +39,7 @@ AC_SYS_LARGEFILE AC_HEADER_TIOCGWINSZ AC_CHECK_FUNCS(getopt_long getopt) AC_CHECK_HEADERS(getopt.h) -AC_CHECK_FUNCS(memcpy basename vsnprintf) +AC_CHECK_FUNCS(memcpy basename vsnprintf strlcat) AC_CHECK_HEADERS(limits.h) AC_ARG_ENABLE(debugging, diff --git a/autoconf/header.in b/autoconf/header.in index d137249..3686777 100644 --- a/autoconf/header.in +++ b/autoconf/header.in @@ -81,6 +81,9 @@ /* Define to 1 if you have the header file. */ #undef HAVE_STRING_H +/* Define to 1 if you have the `strlcat' function. */ +#undef HAVE_STRLCAT + /* Define to 1 if you have the header file. */ #undef HAVE_SYS_IPC_H diff --git a/doc/NEWS.md b/doc/NEWS.md index daa758c..dd2de7c 100644 --- a/doc/NEWS.md +++ b/doc/NEWS.md @@ -8,6 +8,7 @@ * cleanup: improved the output formatting of "`make test`" * cleanup: skip test 10 (for *SIGPIPE*) if GNU "`head`" is not available, so "`make test`" on stock OpenBSD 7.3 works * cleanup: replace all calls to `sprintf()` and `snprintf()` with a new wrapper function `pv_snprintf()` to centralise compatibility changes + * cleanup: replace all calls to `strcat()` with a wrapper `pv_strlcat()` to improve security and compatibility * cleanup: tidy up and fix compilation warning in "`--watchfd`" code * cleanup: replace all `write()` calls to the terminal with a wrapper `pv_write_retry()` for consistency diff --git a/src/include/pv-internal.h b/src/include/pv-internal.h index c36d072..632256c 100644 --- a/src/include/pv-internal.h +++ b/src/include/pv-internal.h @@ -48,6 +48,27 @@ typedef struct pvhistory { long double elapsed_sec; } pvhistory_t; +#define PV_SIZEOF_DEFAULT_FORMAT 512 +#define PV_SIZEOF_CWD 4096 +#define PV_SIZEOF_LASTOUTPUT_BUFFER 256 +#define PV_SIZEOF_STR_NAME 512 +#define PV_SIZEOF_STR_TRANSFERRED 128 +#define PV_SIZEOF_STR_BUFPERCENT 128 +#define PV_SIZEOF_STR_TIMER 128 +#define PV_SIZEOF_STR_RATE 128 +#define PV_SIZEOF_STR_AVERAGE_RATE 128 +#define PV_SIZEOF_STR_PROGRESS 1024 +#define PV_SIZEOF_STR_LASTOUTPUT 512 +#define PV_SIZEOF_STR_ETA 128 +#define PV_SIZEOF_STR_FINETA 128 +#define PV_FORMAT_ARRAY_MAX 100 +#define PV_SIZEOF_CRS_LOCK_FILE 1024 + +#define PV_SIZEOF_FILE_FDINFO 4096 +#define PV_SIZEOF_FILE_FD 4096 +#define PV_SIZEOF_FILE_FDPATH 4096 +#define PV_SIZEOF_DISPLAY_NAME 512 + /* * Structure for holding PV internal state. Opaque outside the PV library. @@ -83,14 +104,14 @@ struct pvstate_s { unsigned int width; /* screen width */ unsigned int height; /* screen height */ const char *name; /* display name */ - char default_format[512]; /* default format string */ + char default_format[PV_SIZEOF_DEFAULT_FORMAT]; /* default format string */ const char *format_string; /* output format string */ /****************** * Program status * ******************/ const char *program_name; /* program name for error reporting */ - char cwd[4096]; /* current working directory for relative path */ + char cwd[PV_SIZEOF_CWD]; /* current working directory for relative path */ const char *current_file; /* current file being read */ int exit_status; /* exit status to give (0=OK) */ @@ -132,24 +153,24 @@ struct pvstate_s { char *display_buffer; long display_buffer_size; int lastoutput_length; /* number of last-output bytes to show */ - unsigned char lastoutput_buffer[256]; + unsigned char lastoutput_buffer[PV_SIZEOF_LASTOUTPUT_BUFFER]; int prev_width; /* screen width last time we were called */ int prev_length; /* length of last string we output */ - char str_name[512]; - char str_transferred[128]; - char str_bufpercent[128]; - char str_timer[128]; - char str_rate[128]; - char str_average_rate[128]; - char str_progress[1024]; - char str_lastoutput[512]; - char str_eta[128]; - char str_fineta[128]; + char str_name[PV_SIZEOF_STR_NAME]; + char str_transferred[PV_SIZEOF_STR_TRANSFERRED]; + char str_bufpercent[PV_SIZEOF_STR_BUFPERCENT]; + char str_timer[PV_SIZEOF_STR_TIMER]; + char str_rate[PV_SIZEOF_STR_RATE]; + char str_average_rate[PV_SIZEOF_STR_AVERAGE_RATE]; + char str_progress[PV_SIZEOF_STR_PROGRESS]; + char str_lastoutput[PV_SIZEOF_STR_LASTOUTPUT]; + char str_eta[PV_SIZEOF_STR_ETA]; + char str_fineta[PV_SIZEOF_STR_FINETA]; unsigned long components_used; /* bitmask of components used */ struct { const char *string; int length; - } format[100]; + } format[PV_FORMAT_ARRAY_MAX]; bool display_visible; /* set once anything written to terminal */ /******************** @@ -166,7 +187,7 @@ struct pvstate_s { bool crs_noipc; /* set if we can't use IPC */ #endif /* HAVE_IPC */ int crs_lock_fd; /* fd of lockfile, -1 if none open */ - char crs_lock_file[1024]; + char crs_lock_file[PV_SIZEOF_CRS_LOCK_FILE]; int crs_y_start; /* our initial Y coordinate */ /******************* @@ -229,11 +250,11 @@ struct pvwatchfd_s { int watch_fd; /* fd to watch, -1 = not displayed */ #ifdef __APPLE__ #else - char file_fdinfo[4096]; /* path to /proc fdinfo file */ - char file_fd[4096]; /* path to /proc fd symlink */ + char file_fdinfo[PV_SIZEOF_FILE_FDINFO]; /* path to /proc fdinfo file */ + char file_fd[PV_SIZEOF_FILE_FD]; /* path to /proc fd symlink */ #endif - char file_fdpath[4096]; /* path to file that was opened */ - char display_name[512]; /* name to show on progress bar */ + char file_fdpath[PV_SIZEOF_FILE_FDPATH]; /* path to file that was opened */ + char display_name[PV_SIZEOF_DISPLAY_NAME]; /* name to show on progress bar */ struct stat sb_fd; /* stat of fd symlink */ struct stat sb_fd_link; /* lstat of fd symlink */ unsigned long long size; /* size of whole file, 0 if unknown */ diff --git a/src/include/pv.h b/src/include/pv.h index e354f68..2897831 100644 --- a/src/include/pv.h +++ b/src/include/pv.h @@ -56,11 +56,22 @@ extern unsigned long long pv_getnum_ull(const char *); extern int pv_getnum_check(const char *, pv_numtype_t); /* - * Wrapper for sprintf(), with less safe fallbacks for systems without that + * String handling wrappers. + */ + +/* + * Wrapper for sprintf(), falling back to sprintf() on systems without that * function. */ extern int pv_snprintf(char *, size_t, const char *, ...); +/* + * Implementation of strlcat() where it is unavailable: append a string to a + * buffer, constraining the buffer to a particular size and ensuring + * termination with '\0'. + */ +extern size_t pv_strlcat(char *, const char *, size_t); + /* * Main PV functions. */ diff --git a/src/main/options.c b/src/main/options.c index 1690dce..136defc 100644 --- a/src/main/options.c +++ b/src/main/options.c @@ -148,6 +148,7 @@ opts_t opts_parse(int argc, char **argv) /* "-s @" is valid, so allow it. */ if ('@' == *optarg) break; + /* falls through */ case 'A': case 'w': case 'H': diff --git a/src/pv/cursor.c b/src/pv/cursor.c index 4d6973c..43c8633 100644 --- a/src/pv/cursor.c +++ b/src/pv/cursor.c @@ -102,9 +102,9 @@ static void pv_crs_open_lockfile(pvstate_t state, int fd) if (!tmpdir) tmpdir = "/tmp"; - memset(state->crs_lock_file, 0, sizeof(state->crs_lock_file)); + memset(state->crs_lock_file, 0, PV_SIZEOF_CRS_LOCK_FILE); (void) pv_snprintf(state->crs_lock_file, - sizeof(state->crs_lock_file), + PV_SIZEOF_CRS_LOCK_FILE, "%s/pv-%s-%i.lock", tmpdir, basename(ttydev), (int) geteuid()); diff --git a/src/pv/display.c b/src/pv/display.c index 76f29c0..2219a37 100644 --- a/src/pv/display.c +++ b/src/pv/display.c @@ -286,11 +286,12 @@ static void pv__format_init(pvstate_t state) state->str_average_rate[0] = 0; state->str_progress[0] = 0; state->str_eta[0] = 0; - memset(state->format, 0, sizeof(state->format)); + memset(state->format, 0, + PV_FORMAT_ARRAY_MAX * sizeof(state->format[0])); if (state->name) { (void) pv_snprintf(state->str_name, - sizeof(state->str_name), "%9.500s:", + PV_SIZEOF_STR_NAME, "%9.500s:", state->name); } @@ -364,10 +365,8 @@ static void pv__format_init(pvstate_t state) state->format[segment].string = state->str_lastoutput; state->format[segment].length = 0; - if (num > sizeof(state->lastoutput_buffer)) - num = - sizeof - (state->lastoutput_buffer); + if (num > PV_SIZEOF_LASTOUTPUT_BUFFER) + num = PV_SIZEOF_LASTOUTPUT_BUFFER; if (num < 1) num = 1; state->lastoutput_length = num; @@ -658,15 +657,13 @@ static const char *pv__format(pvstate_t state, if ((state->components_used & PV_DISPLAY_BYTES) != 0) { if (state->bits) { (void) pv_snprintf(state->display_buffer, - sizeof - (state->display_buffer), + state->display_buffer_size, "%.99s%lld\n", numericprefix, 8 * total_bytes); } else { (void) pv_snprintf(state->display_buffer, - sizeof - (state->display_buffer), + state->display_buffer_size, "%.99s%lld\n", numericprefix, total_bytes); @@ -702,12 +699,12 @@ static const char *pv__format(pvstate_t state, if ((state->components_used & PV_DISPLAY_BYTES) != 0) { if (state->bits && !state->linemode) { pv__sizestr(state->str_transferred, - sizeof(state->str_transferred), "%s", + PV_SIZEOF_STR_TRANSFERRED, "%s", (long double) total_bytes * 8, "", _("b"), 1); } else { pv__sizestr(state->str_transferred, - sizeof(state->str_transferred), "%s", + PV_SIZEOF_STR_TRANSFERRED, "%s", (long double) total_bytes, "", _("B"), state->linemode ? 0 : 1); } @@ -717,7 +714,7 @@ static const char *pv__format(pvstate_t state, if ((state->components_used & PV_DISPLAY_BUFPERCENT) != 0) { if (state->buffer_size > 0) (void) pv_snprintf(state->str_bufpercent, - sizeof(state->str_bufpercent), + PV_SIZEOF_STR_BUFPERCENT, "{%3ld%%}", pv__calc_percentage (state->read_position - @@ -726,7 +723,7 @@ static const char *pv__format(pvstate_t state, #ifdef HAVE_SPLICE if (state->splice_used) (void) pv_snprintf(state->str_bufpercent, - sizeof(state->str_bufpercent), + PV_SIZEOF_STR_BUFPERCENT, "{%s}", "----"); #endif } @@ -747,7 +744,7 @@ static const char *pv__format(pvstate_t state, */ if (elapsed_sec > (long double) 86400.0L) { (void) pv_snprintf(state->str_timer, - sizeof(state->str_timer), + PV_SIZEOF_STR_TIMER, "%ld:%02ld:%02ld:%02ld", ((long) elapsed_sec) / 86400, (((long) elapsed_sec) / 3600) % @@ -756,7 +753,7 @@ static const char *pv__format(pvstate_t state, 60, ((long) elapsed_sec) % 60); } else { (void) pv_snprintf(state->str_timer, - sizeof(state->str_timer), + PV_SIZEOF_STR_TIMER, "%ld:%02ld:%02ld", ((long) elapsed_sec) / 3600, (((long) elapsed_sec) / 60) % @@ -768,11 +765,11 @@ static const char *pv__format(pvstate_t state, if ((state->components_used & PV_DISPLAY_RATE) != 0) { if (state->bits && !state->linemode) { pv__sizestr(state->str_rate, - sizeof(state->str_rate), "[%s]", + PV_SIZEOF_STR_RATE, "[%s]", 8 * rate, "", _("b/s"), 1); } else { pv__sizestr(state->str_rate, - sizeof(state->str_rate), "[%s]", rate, + PV_SIZEOF_STR_RATE, "[%s]", rate, _("/s"), _("B/s"), state->linemode ? 0 : 1); } @@ -782,12 +779,12 @@ static const char *pv__format(pvstate_t state, if ((state->components_used & PV_DISPLAY_AVERAGERATE) != 0) { if (state->bits && !state->linemode) { pv__sizestr(state->str_average_rate, - sizeof(state->str_average_rate), + PV_SIZEOF_STR_AVERAGE_RATE, "[%s]", 8 * average_rate, "", _("b/s"), 1); } else { pv__sizestr(state->str_average_rate, - sizeof(state->str_average_rate), + PV_SIZEOF_STR_AVERAGE_RATE, "[%s]", average_rate, _("/s"), _("B/s"), state->linemode ? 0 : 1); } @@ -824,14 +821,14 @@ static const char *pv__format(pvstate_t state, */ if (eta > 86400L) { (void) pv_snprintf(state->str_eta, - sizeof(state->str_eta), + PV_SIZEOF_STR_ETA, "%.16s %ld:%02ld:%02ld:%02ld", _("ETA"), eta / 86400, (eta / 3600) % 24, (eta / 60) % 60, eta % 60); } else { (void) pv_snprintf(state->str_eta, - sizeof(state->str_eta), + PV_SIZEOF_STR_ETA, "%.16s %ld:%02ld:%02ld", _("ETA"), eta / 3600, (eta / 60) % 60, eta % 60); @@ -843,8 +840,8 @@ static const char *pv__format(pvstate_t state, */ if (bytes_since_last < 0) { unsigned int i; - for (i = 0; i < sizeof(state->str_eta) - && state->str_eta[i] != 0; i++) { + for (i = 0; i < PV_SIZEOF_STR_ETA + && state->str_eta[i] != '\0'; i++) { state->str_eta[i] = ' '; } } @@ -896,19 +893,19 @@ static const char *pv__format(pvstate_t state, struct tm time = *time_ptr; (void) pv_snprintf(state->str_fineta, - sizeof(state->str_fineta), + PV_SIZEOF_STR_FINETA, "%.16s ", _("ETA")); strftime(state->str_fineta + strlen(state->str_fineta), - sizeof(state->str_fineta) - 1 - + PV_SIZEOF_STR_FINETA - 1 - strlen(state->str_fineta), time_format, &time); } if (!show_eta) { unsigned int i; - for (i = 0; i < sizeof(state->str_fineta) - && state->str_fineta[i] != 0; i++) { + for (i = 0; i < PV_SIZEOF_STR_FINETA + && state->str_fineta[i] != '\0'; i++) { state->str_fineta[i] = ' '; } } @@ -959,26 +956,32 @@ static const char *pv__format(pvstate_t state, available_width = 0; if (available_width > - (int) (sizeof(state->str_progress)) - 16) + (int) (PV_SIZEOF_STR_PROGRESS) - 16) available_width = - sizeof(state->str_progress) - 16; + PV_SIZEOF_STR_PROGRESS - 16; for (i = 0; i < (available_width * state->percentage) / 100 - 1; i++) { if (i < available_width) - strcat(state->str_progress, "="); + pv_strlcat(state->str_progress, + "=", + PV_SIZEOF_STR_PROGRESS); } if (i < available_width) { - strcat(state->str_progress, ">"); + pv_strlcat(state->str_progress, ">", + PV_SIZEOF_STR_PROGRESS); i++; } for (; i < available_width; i++) { - strcat(state->str_progress, " "); + pv_strlcat(state->str_progress, " ", + PV_SIZEOF_STR_PROGRESS); } - strcat(state->str_progress, "] "); - strcat(state->str_progress, pct); + pv_strlcat(state->str_progress, "] ", + PV_SIZEOF_STR_PROGRESS); + pv_strlcat(state->str_progress, pct, + PV_SIZEOF_STR_PROGRESS); } else { int p = state->percentage; @@ -989,9 +992,9 @@ static const char *pv__format(pvstate_t state, available_width = 0; if (available_width > - (int) (sizeof(state->str_progress)) - 16) + (int) (PV_SIZEOF_STR_PROGRESS) - 16) available_width = - sizeof(state->str_progress) - 16; + PV_SIZEOF_STR_PROGRESS - 16; debug("available_width: %d", available_width); @@ -999,13 +1002,18 @@ static const char *pv__format(pvstate_t state, p = 200 - p; for (i = 0; i < (available_width * p) / 100; i++) { if (i < available_width) - strcat(state->str_progress, " "); + pv_strlcat(state->str_progress, + " ", + PV_SIZEOF_STR_PROGRESS); } - strcat(state->str_progress, "<=>"); + pv_strlcat(state->str_progress, "<=>", + PV_SIZEOF_STR_PROGRESS); for (; i < available_width; i++) { - strcat(state->str_progress, " "); + pv_strlcat(state->str_progress, " ", + PV_SIZEOF_STR_PROGRESS); } - strcat(state->str_progress, "]"); + pv_strlcat(state->str_progress, "]", + PV_SIZEOF_STR_PROGRESS); } /* @@ -1072,7 +1080,8 @@ static const char *pv__format(pvstate_t state, while (--spaces_to_add >= 0) { spaces[spaces_to_add] = ' '; } - strcat(state->display_buffer, spaces); + pv_strlcat(state->display_buffer, spaces, + state->display_buffer_size); } state->prev_width = state->width; state->prev_length = output_length; diff --git a/src/pv/loop.c b/src/pv/loop.c index 1ecbcfa..247aeed 100644 --- a/src/pv/loop.c +++ b/src/pv/loop.c @@ -560,10 +560,9 @@ int pv_watchpid_loop(pvstate_t state) new_format_string[sizeof(new_format_string) - 1] = '\0'; state_copy.format_string = NULL; (void) pv_snprintf(state_copy.default_format, - sizeof(state_copy.default_format), + PV_SIZEOF_DEFAULT_FORMAT, "%.510s", new_format_string); - state_copy.default_format[sizeof(state_copy.default_format) - 1] = - '\0'; + state_copy.default_format[PV_SIZEOF_DEFAULT_FORMAT - 1] = '\0'; /* * Get things ready for the main loop. diff --git a/src/pv/state.c b/src/pv/state.c index aa0801a..3454a51 100644 --- a/src/pv/state.c +++ b/src/pv/state.c @@ -68,7 +68,7 @@ pvstate_t pv_state_alloc(const char *program_name) * Get the current working directory, if possible, as a base for * showing relative filenames with --watchfd. */ - if (NULL == getcwd(state->cwd, sizeof(state->cwd))) { + if (NULL == getcwd(state->cwd, PV_SIZEOF_CWD - 1)) { /* failed - will always show full path */ state->cwd[0] = '\0'; } @@ -76,6 +76,7 @@ pvstate_t pv_state_alloc(const char *program_name) /* CWD is root directory - always show full path */ state->cwd[0] = '\0'; } + state->cwd[PV_SIZEOF_CWD - 1] = '\0'; return state; } @@ -119,8 +120,8 @@ void pv_state_set_format(pvstate_t state, bool progress, { #define PV_ADDFORMAT(x,y) if (x) { \ if (state->default_format[0] != '\0') \ - strcat(state->default_format, " "); \ - strcat(state->default_format, y); \ + pv_strlcat(state->default_format, " ", sizeof(state->default_format)); \ + pv_strlcat(state->default_format, y, sizeof(state->default_format)); \ } state->default_format[0] = '\0'; diff --git a/src/pv/string.c b/src/pv/string.c index 89dd51a..a1b7ef7 100644 --- a/src/pv/string.c +++ b/src/pv/string.c @@ -11,9 +11,11 @@ #include #include +#include + /* - * Wrapper for sprintf(), with less safe fallbacks for systems without that + * Wrapper for sprintf(), falling back to sprintf() on systems without that * function. * * Returns -1 if "str" or "format" are NULL or if "size" is 0. @@ -49,4 +51,47 @@ int pv_snprintf(char *str, size_t size, const char *format, ...) return ret; } +/* + * Implementation of strlcat() where it is unavailable: append a string to a + * buffer, constraining the buffer to a particular size and ensuring + * termination with '\0'. + * + * Appends the string "src" to the buffer "dst", assuming "dst" is "dstsize" + * bytes long, and ensuring that "dst" is always terminated with a '\0' + * byte. + * + * Returns the intended length of the string, not including the terminating + * '\0', i.e. strlen(src)+strlen(dst), regardless of whether truncation + * occurred. + * + * Note that this implementation has the side effect that "dst" will always + * be terminated with a '\0' even if "src" was zero bytes long. + */ +size_t pv_strlcat(char *dst, const char *src, size_t dstsize) +{ +#ifdef HAVE_STRLCAT + return strlcat(dst, src, dstsize); +#else + size_t dstlen, srclen, available; + + if (NULL == dst) + return 0; + if (NULL == src) + return 0; + if (0 == dstsize) + return 0; + + dst[dstsize - 1] = '\0'; + dstlen = strlen(dst); + srclen = strlen(src); + + available = dstsize - dstlen; + if (available > 1) + (void) pv_snprintf(dst + dstlen, available, "%.*s", + available - 1, src); + + return dstlen + srclen; +#endif +} + /* EOF */ diff --git a/src/pv/watchpid.c b/src/pv/watchpid.c index e8cbc61..188c4c5 100644 --- a/src/pv/watchpid.c +++ b/src/pv/watchpid.c @@ -89,7 +89,7 @@ int pv_watchfd_info(pvstate_t state, pvwatchfd_t info, int automatic) } strlcpy(info->file_fdpath, vnodeInfo.pvip.vip_path, - sizeof(info->file_fdpath)); + PV_SIZEOF_FILE_FDPATH); info->size = 0; @@ -152,17 +152,17 @@ int pv_watchfd_info(pvstate_t state, pvwatchfd_t info, int automatic) info->watch_pid, strerror(errno)); return 1; } - (void) pv_snprintf(info->file_fdinfo, sizeof(info->file_fdinfo), + (void) pv_snprintf(info->file_fdinfo, PV_SIZEOF_FILE_FDINFO, "/proc/%u/fdinfo/%d", info->watch_pid, info->watch_fd); - (void) pv_snprintf(info->file_fd, sizeof(info->file_fd), + (void) pv_snprintf(info->file_fd, PV_SIZEOF_FILE_FD, "/proc/%u/fd/%d", info->watch_pid, info->watch_fd); - memset(info->file_fdpath, 0, sizeof(info->file_fdpath)); + memset(info->file_fdpath, 0, PV_SIZEOF_FILE_FDPATH); if (readlink (info->file_fd, info->file_fdpath, - sizeof(info->file_fdpath) - 1) < 0) { + PV_SIZEOF_FILE_FDPATH - 1) < 0) { if (!automatic) pv_error(state, "%s %u: %s %d: %s", _("pid"), @@ -527,7 +527,7 @@ void pv_watchpid_setname(pvstate_t state, pvwatchfd_t info) int path_length, cwd_length, max_display_length; char *file_fdpath = info->file_fdpath; - memset(info->display_name, 0, sizeof(info->display_name)); + memset(info->display_name, 0, PV_SIZEOF_DISPLAY_NAME); path_length = strlen(info->file_fdpath); cwd_length = strlen(state->cwd); @@ -542,7 +542,7 @@ void pv_watchpid_setname(pvstate_t state, pvwatchfd_t info) max_display_length = (state->width / 2) - 6; if (max_display_length >= path_length) { (void) pv_snprintf(info->display_name, - sizeof(info->display_name), + PV_SIZEOF_DISPLAY_NAME, "%4d:%.498s", info->watch_fd, file_fdpath); } else { @@ -552,7 +552,7 @@ void pv_watchpid_setname(pvstate_t state, pvwatchfd_t info) suffix_length = max_display_length - prefix_length - 3; (void) pv_snprintf(info->display_name, - sizeof(info->display_name), + PV_SIZEOF_DISPLAY_NAME, "%4d:%.*s...%.*s", info->watch_fd, prefix_length, file_fdpath, suffix_length,