The branch main has been updated by des: URL: https://cgit.FreeBSD.org/src/commit/?id=02f394281fd67dc15afb26c51d52f2b4cbf28065
commit 02f394281fd67dc15afb26c51d52f2b4cbf28065 Author: Dag-Erling Smørgrav <d...@freebsd.org> AuthorDate: 2025-08-07 01:23:32 +0000 Commit: Dag-Erling Smørgrav <d...@freebsd.org> CommitDate: 2025-08-07 01:23:32 +0000 find: Fix -printf Each -printf invocation created a memstream, then freed the underlying buffer without closing the stream, resulting in a segfault on exit when libc tried to flush all the streams. Drop the memstream, which isn't really needed. Furthermore: * Change escape() to return char * instead of const char * * Simplify the implementation of %h * Fix %M, which printed an extra space * Implement %l correctly * Implement %Y and %y * Add tests for everything except %S Fixes: 7b9c912c41f4 Reviewed by: imp Differential Revision: https://reviews.freebsd.org/D51776 --- usr.bin/find/find.1 | 13 +++- usr.bin/find/printf.c | 161 +++++++++++++++++++++++++--------------- usr.bin/find/tests/find_test.sh | 111 +++++++++++++++++++++++++++ 3 files changed, 223 insertions(+), 62 deletions(-) diff --git a/usr.bin/find/find.1 b/usr.bin/find/find.1 index b16c4bcc95a2..98521a98762d 100644 --- a/usr.bin/find/find.1 +++ b/usr.bin/find/find.1 @@ -1144,9 +1144,18 @@ Inode of the file. .It n Number of hard links. .It y -Unimplemented -- Type of the file +A single character representing the type of the file. .It Y -Unimplemented -- Type of the file with loop detection +A single character representing the type of the file. +If the file is a symbolic link, show information for the target of the +link instead, or +.Sq L +if the link loops, +.Sq N +if the target does not exist, or +.Sq ? +if any other error occurs while attempting to determine the type of +the target. .It a Access time of the file. .It A diff --git a/usr.bin/find/printf.c b/usr.bin/find/printf.c index 671d1d1dbb9a..c1be04376156 100644 --- a/usr.bin/find/printf.c +++ b/usr.bin/find/printf.c @@ -5,15 +5,18 @@ */ #include <sys/types.h> -#include <stdbool.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> + #include <err.h> +#include <errno.h> #include <fts.h> #include <grp.h> #include <pwd.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> #include <time.h> +#include <unistd.h> #include "find.h" @@ -32,7 +35,7 @@ isesc(char c) return (c >= 'a' && c <= 'v' && esc[c - 'a'] != c); } -static const char * +static char * escape(const char *str, bool *flush, bool *warned) { char c; @@ -147,7 +150,6 @@ fp_strftime(FILE *fp, time_t t, char mod) gmtime_r(&t, &tm); fmt[1] = mod; - printf("fmt is '%s'\n", fmt); if (strftime(buffer, sizeof(buffer), fmt, &tm) == 0) errx(1, "Format bad or data too long for buffer"); /* Can't really happen ??? */ fputs(buffer, fp); @@ -156,36 +158,36 @@ fp_strftime(FILE *fp, time_t t, char mod) void do_printf(PLAN *plan, FTSENT *entry, FILE *fout) { - const char *fmt, *path, *pend, *all; - char c; - FILE *fp; + char buf[4096]; + struct stat sb; + struct stat *sp; + const char *path, *pend; + char *all, *fmt; + ssize_t ret; + int c; bool flush, warned; - struct stat *sb; - char *tmp; - size_t tmplen; - fp = open_memstream(&tmp, &tmplen); warned = (plan->flags & F_HAS_WARNED) != 0; all = fmt = escape(plan->c_data, &flush, &warned); if (warned) plan->flags |= F_HAS_WARNED; - sb = entry->fts_statp; for (c = *fmt++; c; c = *fmt++) { + sp = entry->fts_statp; if (c != '%') { - putc(c, fp); + putc(c, fout); continue; } c = *fmt++; /* Style(9) deviation: case order same as gnu find info doc */ switch (c) { case '%': - putc(c, fp); + putc(c, fout); break; case 'p': /* Path to file */ - fputs(entry->fts_path, fp); + fputs(entry->fts_path, fout); break; case 'f': /* filename w/o dirs */ - fputs(entry->fts_name, fp); + fputs(entry->fts_name, fout); break; case 'h': /* @@ -195,98 +197,139 @@ do_printf(PLAN *plan, FTSENT *entry, FILE *fout) path = entry->fts_path; pend = strrchr(path, '/'); if (pend == NULL) - putc('.', fp); - else { - char *t = malloc(pend - path + 1); - memcpy(t, path, pend - path); - t[pend - path] = '\0'; - fputs(t, fp); - free(t); - } + putc('.', fout); + else + fwrite(path, pend - path, 1, fout); break; case 'P': /* file with command line arg rm'd -- HOW? fts_parent? */ errx(1, "%%%c is unimplemented", c); case 'H': /* Command line arg -- HOW? */ errx(1, "%%%c is unimplemented", c); case 'g': /* gid human readable */ - fputs(group_from_gid(sb->st_gid, 0), fp); + fputs(group_from_gid(sp->st_gid, 0), fout); break; case 'G': /* gid numeric */ - fprintf(fp, "%d", sb->st_gid); + fprintf(fout, "%d", sp->st_gid); break; case 'u': /* uid human readable */ - fputs(user_from_uid(sb->st_uid, 0), fp); + fputs(user_from_uid(sp->st_uid, 0), fout); break; case 'U': /* uid numeric */ - fprintf(fp, "%d", sb->st_uid); + fprintf(fout, "%d", sp->st_uid); break; case 'm': /* mode in octal */ - fprintf(fp, "%o", sb->st_mode & 07777); + fprintf(fout, "%o", sp->st_mode & 07777); break; - case 'M': { /* Mode in ls-standard form */ - char mode[12]; - strmode(sb->st_mode, mode); - fputs(mode, fp); + case 'M': /* Mode in ls-standard form */ + strmode(sp->st_mode, buf); + fwrite(buf, 10, 1, fout); break; - } case 'k': /* kbytes used by file */ - fprintf(fp, "%jd", (intmax_t)sb->st_blocks / 2); + fprintf(fout, "%jd", (intmax_t)sp->st_blocks / 2); break; case 'b': /* blocks used by file */ - fprintf(fp, "%jd", (intmax_t)sb->st_blocks); + fprintf(fout, "%jd", (intmax_t)sp->st_blocks); break; case 's': /* size in bytes of file */ - fprintf(fp, "%ju", (uintmax_t)sb->st_size); + fprintf(fout, "%ju", (uintmax_t)sp->st_size); break; case 'S': /* sparseness of file */ - fprintf(fp, "%3.1f", - (float)sb->st_blocks * 512 / (float)sb->st_size); + fprintf(fout, "%3.1f", + (float)sp->st_blocks * 512 / (float)sp->st_size); break; case 'd': /* Depth in tree */ - fprintf(fp, "%ld", entry->fts_level); + fprintf(fout, "%ld", entry->fts_level); break; case 'D': /* device number */ - fprintf(fp, "%ju", (uintmax_t)sb->st_dev); + fprintf(fout, "%ju", (uintmax_t)sp->st_dev); break; case 'F': /* Filesystem type */ errx(1, "%%%c is unimplemented", c); case 'l': /* object of symbolic link */ - fprintf(fp, "%s", entry->fts_accpath); + ret = readlink(entry->fts_accpath, buf, sizeof(buf)); + if (ret > 0) + fwrite(buf, ret, 1, fout); break; case 'i': /* inode # */ - fprintf(fp, "%ju", (uintmax_t)sb->st_ino); + fprintf(fout, "%ju", (uintmax_t)sp->st_ino); break; case 'n': /* number of hard links */ - fprintf(fp, "%ju", (uintmax_t)sb->st_nlink); + fprintf(fout, "%ju", (uintmax_t)sp->st_nlink); break; - case 'y': /* -type of file, incl 'l' */ - errx(1, "%%%c is unimplemented", c); case 'Y': /* -type of file, following 'l' types L loop ? error */ - errx(1, "%%%c is unimplemented", c); + if (S_ISLNK(sp->st_mode)) { + if (stat(entry->fts_accpath, &sb) != 0) { + switch (errno) { + case ELOOP: + putc('L', fout); + break; + case ENOENT: + putc('N', fout); + break; + default: + putc('?', fout); + break; + } + break; + } + sp = &sb; + } + /* FALLTHROUGH */ + case 'y': /* -type of file, incl 'l' */ + switch (sp->st_mode & S_IFMT) { + case S_IFIFO: + putc('p', fout); + break; + case S_IFCHR: + putc('c', fout); + break; + case S_IFDIR: + putc('d', fout); + break; + case S_IFBLK: + putc('b', fout); + break; + case S_IFREG: + putc('f', fout); + break; + case S_IFLNK: + putc('l', fout); + break; + case S_IFSOCK: + putc('s', fout); + break; + case S_IFWHT: + putc('w', fout); + break; + default: + putc('U', fout); + break; + } + break; case 'a': /* access time ctime */ - fp_ctime(fp, sb->st_atime); + fp_ctime(fout, sp->st_atime); break; case 'A': /* access time with next char strftime format */ - fp_strftime(fp, sb->st_atime, *fmt++); + fp_strftime(fout, sp->st_atime, *fmt++); break; case 'B': /* birth time with next char strftime format */ #ifdef HAVE_STRUCT_STAT_ST_BIRTHTIME - if (sb->st_birthtime != 0) - fp_strftime(fp, sb->st_birthtime, *fmt); + if (sp->st_birthtime != 0) + fp_strftime(fout, sp->st_birthtime, *fmt); #endif fmt++; break; /* blank on systems that don't support it */ case 'c': /* status change time ctime */ - fp_ctime(fp, sb->st_ctime); + fp_ctime(fout, sp->st_ctime); break; case 'C': /* status change time with next char strftime format */ - fp_strftime(fp, sb->st_ctime, *fmt++); + fp_strftime(fout, sp->st_ctime, *fmt++); break; case 't': /* modification change time ctime */ - fp_ctime(fp, sb->st_mtime); + fp_ctime(fout, sp->st_mtime); break; case 'T': /* modification time with next char strftime format */ - fp_strftime(fp, sb->st_mtime, *fmt++); + fp_strftime(fout, sp->st_mtime, *fmt++); break; case 'Z': /* empty string for compat SELinux context string */ break; @@ -299,9 +342,7 @@ do_printf(PLAN *plan, FTSENT *entry, FILE *fout) errx(1, "Unknown format %c '%s'", c, all); } } - fputs(tmp, fout); if (flush) fflush(fout); - free(__DECONST(char *, fmt)); - free(tmp); + free(all); } diff --git a/usr.bin/find/tests/find_test.sh b/usr.bin/find/tests/find_test.sh index 8b8c23688018..99d2f6af4d45 100755 --- a/usr.bin/find/tests/find_test.sh +++ b/usr.bin/find/tests/find_test.sh @@ -1,5 +1,8 @@ +#- +# SPDX-License-Identifier: BSD-2-Clause # # Copyright 2017, Conrad Meyer <c...@freebsd.org>. +# Copyright (c) 2025 Dag-Erling Smørgrav <d...@freebsd.org> # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are @@ -64,8 +67,116 @@ find_samefile_link_body() atf_check -s exit:0 -o "inline:test/link2\n" find test -samefile test/link2 } +atf_test_case find_printf +find_printf_head() +{ + atf_set "descr" "Test the -printf primary" +} +find_printf_body() +{ + mkdir dir + chmod 0755 dir + jot -b hello 1024 >dir/file + chmod 0644 dir/file + ln -s file dir/link + chmod -h 0444 dir/link + local db=$(stat -f %b dir) + local fb=$(stat -f %b dir/file) + local lb=$(stat -f %b dir/link) + + # paths + atf_check -o inline:"dir\ndir/file\ndir/link\n" \ + find -s dir -printf '%p\n' + atf_check -o inline:"dir\nfile\nlink\n" \ + find -s dir -printf '%f\n' + atf_check -o inline:".\ndir\ndir\n" \ + find -s dir -printf '%h\n' + atf_check -s exit:1 -e match:"unimplemented" -o ignore \ + find -s dir -printf '%P\n' + atf_check -s exit:1 -e match:"unimplemented" -o ignore \ + find -s dir -printf '%H\n' + + # group + atf_check -o inline:"$(stat -f %Sg dir dir/file dir/link)\n" \ + find -s dir -printf '%g\n' + atf_check -o inline:"$(stat -f %g dir dir/file dir/link)\n" \ + find -s dir -printf '%G\n' + + # owner + atf_check -o inline:"$(stat -f %Su dir dir/file dir/link)\n" \ + find -s dir -printf '%u\n' + atf_check -o inline:"$(stat -f %u dir dir/file dir/link)\n" \ + find -s dir -printf '%U\n' + + # mode + atf_check -o inline:"$(stat -f %Lp dir dir/file dir/link)\n" \ + find -s dir -printf '%m\n' + atf_check -o inline:"$(stat -f %Sp dir dir/file dir/link)\n" \ + find -s dir -printf '%M\n' + + # size + atf_check -o inline:"$((db/2))\n$((fb/2))\n$((lb/2))\n" \ + find -s dir -printf '%k\n' + atf_check -o inline:"$db\n$fb\n$lb\n" \ + find -s dir -printf '%b\n' + atf_check -o inline:"$(stat -f %z dir dir/file dir/link)\n" \ + find -s dir -printf '%s\n' + # XXX test %S properly + atf_check -o ignore \ + find -s dir -printf '%S\n' + atf_check -o inline:"0\n1\n1\n" \ + find -s dir -printf '%d\n' + + # device + atf_check -o inline:"$(stat -f %d dir dir/file dir/link)\n" \ + find -s dir -printf '%D\n' + atf_check -s exit:1 -e match:"unimplemented" -o ignore \ + find -s dir -printf '%F\n' + + # link target + atf_check -o inline:"\n\nfile\n" \ + find -s dir -printf '%l\n' + + # inode + atf_check -o inline:"$(stat -f %i dir dir/file dir/link)\n" \ + find -s dir -printf '%i\n' + + # nlinks + atf_check -o inline:"$(stat -f %l dir dir/file dir/link)\n" \ + find -s dir -printf '%n\n' + + # type + atf_check -o inline:"d\nf\nl\n" \ + find -s dir -printf '%y\n' + atf_check -o inline:"d\nf\nf\n" \ + find -s dir -printf '%Y\n' + + # access time + atf_check -o inline:"$(stat -f %Sa -t '%a %b %e %T %Y' dir dir/file dir/link)\n" \ + find -s dir -printf '%a\n' + atf_check -o inline:"$(stat -f %Sa -t '%e' dir dir/file dir/link)\n" \ + find -s dir -printf '%Ae\n' + + # birth time + atf_check -o inline:"$(stat -f %SB -t '%e' dir dir/file dir/link)\n" \ + find -s dir -printf '%Be\n' + + # inode change time + atf_check -o inline:"$(stat -f %Sc -t '%a %b %e %T %Y' dir dir/file dir/link)\n" \ + find -s dir -printf '%c\n' + atf_check -o inline:"$(stat -f %Sc -t '%e' dir dir/file dir/link)\n" \ + find -s dir -printf '%Ce\n' + + # modification time + atf_check -o inline:"$(stat -f %Sm -t '%a %b %e %T %Y' dir dir/file dir/link)\n" \ + find -s dir -printf '%t\n' + atf_check -o inline:"$(stat -f %Sm -t '%e' dir dir/file dir/link)\n" \ + find -s dir -printf '%Te\n' +} + atf_init_test_cases() { atf_add_test_case find_newer_link atf_add_test_case find_samefile_link + atf_add_test_case find_printf }