diff --git a/usr.sbin/quot/quot.8 b/usr.sbin/quot/quot.8 --- a/usr.sbin/quot/quot.8 +++ b/usr.sbin/quot/quot.8 @@ -27,7 +27,7 @@ .\" OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF .\" ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. .\" -.Dd October 15, 2025 +.Dd November 13, 2025 .Dt QUOT 8 .Os .Sh NAME @@ -60,6 +60,7 @@ Given a list of inodes (plus some optional data on each line) in the standard input, for each file print out the owner (plus the remainder of the input line). +Lines that do not begin with a number are ignored. This is traditionally used in the pipe: .Bd -literal -offset indent diff --git a/usr.sbin/quot/quot.c b/usr.sbin/quot/quot.c --- a/usr.sbin/quot/quot.c +++ b/usr.sbin/quot/quot.c @@ -41,6 +41,7 @@ #include #include #include +#include #include #include #include @@ -390,41 +391,43 @@ static void donames(int fd, struct fs *super) { - int c; - ino_t maxino; - uintmax_t inode; union dinode *dp; + char *end, *line; + size_t cap; + ssize_t len; + intmax_t inode, maxino; maxino = super->fs_ncg * super->fs_ipg - 1; - /* first skip the name of the filesystem */ - while ((c = getchar()) != EOF && (c < '0' || c > '9')) - while ((c = getchar()) != EOF && c != '\n'); - ungetc(c, stdin); - while (scanf("%ju", &inode) == 1) { - if (inode > maxino) { - warnx("illegal inode %ju", inode); - return; + line = NULL; + cap = 0; + while ((len = getline(&line, &cap, stdin)) > 0) { + if (len > 0 && line[len - 1] == '\n') + line[--len] = '\0'; + inode = strtoimax(line, &end, 10); + /* + * Silently ignore lines that do not begin with a number. + * For backward compatibility reasons, we do not require + * the optional comment to be preceded by whitespace. + */ + if (end == line) + continue; + if (inode <= 0 || inode > maxino) { + warnx("invalid inode %jd", inode); + continue; } if ((dp = get_inode(fd, super, inode)) != NULL && !isfree(super, dp)) { printf("%s\t", user(DIP(super, dp, di_uid))->name); /* now skip whitespace */ - while ((c = getchar()) == ' ' || c == '\t') - /* nothing */; + while (*end == ' ' || *end == '\t') + end++; /* and print out the remainder of the input line */ - while (c != EOF && c != '\n') { - putchar(c); - c = getchar(); - } - putchar('\n'); + printf("%s\n", end); } else { /* skip this line */ - while ((c = getchar()) != EOF && c != '\n') - /* nothing */; } - if (c == EOF) - break; } + free(line); } static void diff --git a/usr.sbin/quot/tests/quot_test.sh b/usr.sbin/quot/tests/quot_test.sh --- a/usr.sbin/quot/tests/quot_test.sh +++ b/usr.sbin/quot/tests/quot_test.sh @@ -15,6 +15,8 @@ atf_check mount /dev/$dev "$mnt" echo "/dev/$dev: ($mnt)" >expect printf "%5d\t%5d\t%-8s\n" 8 2 "#0" >>expect + printf "%s\n" "/dev/$dev" >ninput + echo "/dev/$dev: ($mnt)" >nexpect } # Create a directory owned by a given UID @@ -23,12 +25,25 @@ local uid=$1 atf_check install -d -o $uid -g 0 mnt/$uid printf "%5d\t%5d\t%-8s\n" 4 1 "#$uid" >>expect + ls -di mnt/$uid >>ninput + printf "%s\t%s\n" "#$uid" mnt/$uid >>nexpect } # Perform the tests quot_test() { local dev=$(cat dev) + # Deliberately add invalid lines to our -n input before the + # valid ones to verify that quot does not abort on first + # error. Note that quot deliberately ignores initial lines + # that don't start with a number, and that after encountering + # at least one line that does start with a number, quot would + # previously terminate on encountering one that doesn't (now + # it simply ignores them). This also tests that we don't + # require whitespace between the inode number and the comment. + echo "0zero" >>ninput + echo "invalid" >>ninput + echo "-1minusone" >>ninput # Create inodes owned by a large number of users to exercise # hash collisions and rehashing. The code uses an open hash # table that starts out with only 8 entries and doubles every @@ -50,6 +65,10 @@ atf_check mount -ur /dev/$dev atf_check -o file:expect quot -fkN /dev/$dev atf_check -o file:expect quot -fkN $(realpath mnt) + # Test -n option + atf_check -o file:nexpect \ + -e inline:"quot: invalid inode 0\nquot: invalid inode -1\n" \ + quot -Nn /dev/$dev