diff --git a/ChangeLog b/ChangeLog index 59b7091e0..38817d964 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,24 @@ +2024-07-13 G. Branden Robinson + + * src/libs/libgroff/searchpath.cpp (is_directory): New function + uses stat(2) to check file name argument to see if it's a + directory, and returns a Boolean. + (search_path::open_file): + (search_path::open_file_cautious): Use `is_directory()` before + attempting to `fopen()` a file specification; fail and set + `errno` to `EISDIR` if it's a directory so that the caller + reports a useful diagnostic. + + * bootstrap.conf: Add gnulib `stat` module, because the story of + POSIX and non-POSIX systems alike trying and failing to sensibly + implement the fundamental Unix file system model is a sorry tale + of indifference and self-owns by rock star programmers. + {Seriously, read the "sys/stat.h" and "stat" sections of the + gnulib manual.} + + Fixes . Thanks to Dave + Kemper for the report. + 2024-07-13 G. Branden Robinson * tmac/an-ext.tmac: Fix incomplete changes to support `YS` with diff --git a/bootstrap.conf b/bootstrap.conf index aa96ac6df..20bee83f1 100644 --- a/bootstrap.conf +++ b/bootstrap.conf @@ -43,6 +43,7 @@ gnulib_modules=" fprintf-posix snprintf vsnprintf + stat stdbool-c99 stdint sys_wait diff --git a/src/libs/libgroff/searchpath.cpp b/src/libs/libgroff/searchpath.cpp index 0fc465b28..f11ef2295 100644 --- a/src/libs/libgroff/searchpath.cpp +++ b/src/libs/libgroff/searchpath.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 1989-2020 Free Software Foundation, Inc. +/* Copyright (C) 1989-2024 Free Software Foundation, Inc. Written by James Clark (jjc@jclark.com) This file is part of groff. @@ -26,6 +26,11 @@ along with this program. If not, see . */ #include #include +// for stat(2) +#include +#include +#include + #include "searchpath.h" #include "nonposix.h" @@ -35,6 +40,17 @@ along with this program. If not, see . */ # define relocate(path) strsave(path) #endif +static bool is_directory(const char *name) +{ + struct stat statbuf; + // If stat() fails, a later fopen() will fail anyway (he said + // TOCTTOUishly). + if ((stat(name, &statbuf) == 0) + && ((statbuf.st_mode & S_IFMT) == S_IFDIR)) + return true; + return false; +} + search_path::search_path(const char *envvar, const char *standard, int add_home, int add_current) { @@ -99,6 +115,10 @@ FILE *search_path::open_file(const char *name, char **pathp) { assert(name != 0 /* nullptr */); if (IS_ABSOLUTE(name) || *dirs == '\0') { + if (is_directory(name)) { + errno = EISDIR; + return 0 /* nullptr */; + } FILE *fp = fopen(name, "r"); if (fp != 0 /* nullptr */) { if (pathp != 0 /* nullptr */) @@ -128,6 +148,10 @@ FILE *search_path::open_file(const char *name, char **pathp) #if 0 fprintf(stderr, "trying '%s'\n", path); #endif + if (is_directory(name)) { + errno = EISDIR; + return 0 /* nullptr */; + } FILE *fp = fopen(path, "r"); int err = errno; if (fp != 0 /* nullptr */) { @@ -160,6 +184,10 @@ FILE *search_path::open_file_cautious(const char *name, char **pathp, return (reading ? stdin : stdout); } if (!reading || IS_ABSOLUTE(name) || *dirs == '\0') { + if (is_directory(name)) { + errno = EISDIR; + return 0 /* nullptr */; + } FILE *fp = fopen(name, mode); if (fp != 0 /* nullptr */) { if (pathp != 0 /* nullptr */) @@ -190,6 +218,10 @@ FILE *search_path::open_file_cautious(const char *name, char **pathp, #if 0 fprintf(stderr, "trying '%s'\n", path); #endif + if (is_directory(name)) { + errno = EISDIR; + return 0 /* nullptr */; + } FILE *fp = fopen(path, mode); int err = errno; if (fp != 0 /* nullptr */) {