Something went wrong. Try again.
A breadth-first version of the UNIX find command tavianator.com/projects/bfs.html
Something went wrong. Try again.
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859// Copyright © Tavian Barnes <tavianator@tavianator.com>// SPDX-License-Identifier: 0BSD
/** * Implementation of all the primary expressions. */
#include "eval.h"
#include "atomic.h"#include "bar.h"#include "bfs.h"#include "bfstd.h"#include "bftw.h"#include "color.h"#include "ctx.h"#include "diag.h"#include "dir.h"#include "dstring.h"#include "exec.h"#include "expr.h"#include "fsade.h"#include "mtab.h"#include "printf.h"#include "pwcache.h"#include "sanity.h"#include "sighook.h"#include "stat.h"#include "trie.h"#include "xregex.h"#include "xtime.h"
#include <errno.h>#include <fcntl.h>#include <fnmatch.h>#include <grp.h>#include <pwd.h>#include <signal.h>#include <stdarg.h>#include <stdint.h>#include <stdio.h>#include <stdlib.h>#include <string.h>#include <strings.h>#include <sys/resource.h>#include <sys/time.h>#include <sys/types.h>#include <time.h>#include <unistd.h>#include <wchar.h>
#if BFS_HAS_SYSCTLBYNAME# include <sys/sysctl.h>#endif
struct bfs_eval { /** Data about the current file. */ const struct BFTW *ftwbuf; /** The bfs context. */ const struct bfs_ctx *ctx; /** The bftw() callback return value. */ enum bftw_action action; /** The bfs_eval() return value. */ int *ret; /** The number of errors that have occurred. */ size_t *nerrors; /** Whether to quit immediately. */ bool quit;};
/** * Print an error message. */[[_printf(2, 3)]]static void eval_error(struct bfs_eval *state, const char *format, ...) { const struct bfs_ctx *ctx = state->ctx;
++*state->nerrors; if (ctx->ignore_errors) { return; }
// By POSIX, any errors should be accompanied by a non-zero exit status *state->ret = EXIT_FAILURE;
CFILE *cerr = ctx->cerr;
bfs_error(ctx, "%pP: ", state->ftwbuf);
va_list args; va_start(args, format); cvfprintf(cerr, format, args); va_end(args);}
/** * Check if an error should be ignored. */static bool eval_should_ignore(const struct bfs_eval *state, int error) { return state->ctx->ignore_races && error_is_like(error, ENOENT) && state->ftwbuf->depth > 0;}
/** * Report an error that occurs during evaluation. */static void eval_report_error(struct bfs_eval *state) { if (!eval_should_ignore(state, errno)) { eval_error(state, "%s.\n", errstr()); }}
/** * Report an I/O error that occurs during evaluation. */static void eval_io_error(const struct bfs_expr *expr, struct bfs_eval *state) { if (expr->path) { eval_error(state, "'%s': %s.\n", expr->path, errstr()); } else { eval_error(state, "(standard output): %s.\n", errstr()); }
// Don't report the error again in bfs_ctx_free() clearerr(expr->cfile->file);}
/** * Perform a bfs_stat() call if necessary. */static const struct bfs_stat *eval_stat(struct bfs_eval *state) { const struct BFTW *ftwbuf = state->ftwbuf; const struct bfs_stat *ret = bftw_stat(ftwbuf, ftwbuf->stat_flags); if (!ret) { eval_report_error(state); } return ret;}
/** * Get the difference (in seconds) between two struct timespecs. */static time_t timespec_diff(const struct timespec *lhs, const struct timespec *rhs) { struct timespec diff = *lhs; timespec_sub(&diff, rhs); return diff.tv_sec;}
bool bfs_expr_cmp(const struct bfs_expr *expr, long long n) { switch (expr->int_cmp) { case BFS_INT_EQUAL: return n == expr->num; case BFS_INT_LESS: return n < expr->num; case BFS_INT_GREATER: return n > expr->num; }
bfs_bug("Invalid comparison mode"); return false;}
/** Common code for fnmatch() tests. */static bool eval_fnmatch(const struct bfs_expr *expr, const char *str) { if (expr->literal) {#ifdef FNM_CASEFOLD if (expr->fnm_flags & FNM_CASEFOLD) { return strcasecmp(expr->pattern, str) == 0; }#endif return strcmp(expr->pattern, str) == 0; } else { return fnmatch(expr->pattern, str, expr->fnm_flags) == 0; }}
/** * -true test. */bool eval_true(const struct bfs_expr *expr, struct bfs_eval *state) { return true;}
/** * -false test. */bool eval_false(const struct bfs_expr *expr, struct bfs_eval *state) { return false;}
/** * -executable, -readable, -writable tests. */bool eval_access(const struct bfs_expr *expr, struct bfs_eval *state) { const struct BFTW *ftwbuf = state->ftwbuf; return xfaccessat(ftwbuf->at_fd, ftwbuf->at_path, expr->num) == 0;}
/** * -acl test. */bool eval_acl(const struct bfs_expr *expr, struct bfs_eval *state) { int ret = bfs_check_acl(state->ftwbuf); if (ret >= 0) { return ret; } else { eval_report_error(state); return false; }}
/** * -capable test. */bool eval_capable(const struct bfs_expr *expr, struct bfs_eval *state) { int ret = bfs_check_capabilities(state->ftwbuf); if (ret >= 0) { return ret; } else { eval_report_error(state); return false; }}
/** * -context test. */bool eval_context(const struct bfs_expr *expr, struct bfs_eval *state) { char *con = bfs_getfilecon(state->ftwbuf); if (!con) { eval_report_error(state); return false; }
bool ret = eval_fnmatch(expr, con); bfs_freecon(con); return ret;}
/** * Get the given timespec field out of a stat buffer. */static const struct timespec *eval_stat_time(const struct bfs_stat *statbuf, enum bfs_stat_field field, struct bfs_eval *state) { const struct timespec *ret = bfs_stat_time(statbuf, field); if (!ret) { eval_error(state, "Couldn't get file %s: %s.\n", bfs_stat_field_name(field), errstr()); } return ret;}
/** * -[aBcm]?newer tests. */bool eval_newer(const struct bfs_expr *expr, struct bfs_eval *state) { const struct bfs_stat *statbuf = eval_stat(state); if (!statbuf) { return false; }
const struct timespec *time = eval_stat_time(statbuf, expr->stat_field, state); if (!time) { return false; }
return timespec_cmp(time, &expr->reftime) > 0;}
/** * -[aBcm]{min,time} tests. */bool eval_time(const struct bfs_expr *expr, struct bfs_eval *state) { const struct bfs_stat *statbuf = eval_stat(state); if (!statbuf) { return false; }
const struct timespec *time = eval_stat_time(statbuf, expr->stat_field, state); if (!time) { return false; }
time_t diff = timespec_diff(&expr->reftime, time); switch (expr->time_unit) { case BFS_DAYS: diff /= 60 * 24; [[fallthrough]]; case BFS_MINUTES: diff /= 60; [[fallthrough]]; case BFS_SECONDS: break; }
return bfs_expr_cmp(expr, diff);}
/** * -used test. */bool eval_used(const struct bfs_expr *expr, struct bfs_eval *state) { const struct bfs_stat *statbuf = eval_stat(state); if (!statbuf) { return false; }
const struct timespec *atime = eval_stat_time(statbuf, BFS_STAT_ATIME, state); const struct timespec *ctime = eval_stat_time(statbuf, BFS_STAT_CTIME, state); if (!atime || !ctime) { return false; }
long long diff = timespec_diff(atime, ctime); if (diff < 0) { return false; }
long long day_seconds = 60 * 60 * 24; diff = (diff + day_seconds - 1) / day_seconds; return bfs_expr_cmp(expr, diff);}
/** * -gid test. */bool eval_gid(const struct bfs_expr *expr, struct bfs_eval *state) { const struct bfs_stat *statbuf = eval_stat(state); if (!statbuf) { return false; }
return bfs_expr_cmp(expr, statbuf->gid);}
/** * -uid test. */bool eval_uid(const struct bfs_expr *expr, struct bfs_eval *state) { const struct bfs_stat *statbuf = eval_stat(state); if (!statbuf) { return false; }
return bfs_expr_cmp(expr, statbuf->uid);}
/** * -nogroup test. */bool eval_nogroup(const struct bfs_expr *expr, struct bfs_eval *state) { const struct bfs_stat *statbuf = eval_stat(state); if (!statbuf) { return false; }
const struct group *grp = bfs_getgrgid(state->ctx->groups, statbuf->gid); if (errno != 0) { eval_report_error(state); } return grp == NULL;}
/** * -nouser test. */bool eval_nouser(const struct bfs_expr *expr, struct bfs_eval *state) { const struct bfs_stat *statbuf = eval_stat(state); if (!statbuf) { return false; }
const struct passwd *pwd = bfs_getpwuid(state->ctx->users, statbuf->uid); if (errno != 0) { eval_report_error(state); } return pwd == NULL;}
/** * -delete action. */bool eval_delete(const struct bfs_expr *expr, struct bfs_eval *state) { const struct BFTW *ftwbuf = state->ftwbuf;
// Don't try to delete the current directory if (strcmp(ftwbuf->path, ".") == 0) { return true; }
int flag = 0;
// We need to know the actual type of the path, not what it points to enum bfs_type type = bftw_type(ftwbuf, BFS_STAT_NOFOLLOW); if (type == BFS_DIR) { flag |= AT_REMOVEDIR; } else if (type == BFS_ERROR) { eval_report_error(state); return false; }
if (unlinkat(ftwbuf->at_fd, ftwbuf->at_path, flag) != 0) { eval_report_error(state); return false; }
return true;}
/** Finish any pending -exec ... + operations. */static int eval_exec_finish(const struct bfs_expr *expr, const struct bfs_ctx *ctx) { int ret = 0;
if (expr->eval_fn == eval_exec) { if (bfs_exec_finish(expr->exec) != 0) { if (errno != 0) { bfs_error(ctx, "${blu}%pq${rs} ${bld}%pq${rs}: %s.\n", expr->argv[0], expr->argv[1], errstr()); } ret = -1; } }
for_expr (child, expr) { if (eval_exec_finish(child, ctx) != 0) { ret = -1; } }
return ret;}
/** * -exec[dir]/-ok[dir] actions. */bool eval_exec(const struct bfs_expr *expr, struct bfs_eval *state) { bool ret = bfs_exec(expr->exec, state->ftwbuf) == 0; if (errno != 0) { eval_error(state, "${blu}%pq${rs} ${bld}%pq${rs}: %s.\n", expr->argv[0], expr->argv[1], errstr()); } return ret;}
/** * -exit action. */bool eval_exit(const struct bfs_expr *expr, struct bfs_eval *state) { state->action = BFTW_STOP; *state->ret = expr->num; state->quit = true; return true;}
/** * -depth N test. */bool eval_depth(const struct bfs_expr *expr, struct bfs_eval *state) { return bfs_expr_cmp(expr, state->ftwbuf->depth);}
/** * -empty test. */bool eval_empty(const struct bfs_expr *expr, struct bfs_eval *state) { const struct BFTW *ftwbuf = state->ftwbuf; const struct bfs_stat *statbuf; struct bfs_dir *dir;
switch (ftwbuf->type) { case BFS_REG: statbuf = eval_stat(state); return statbuf && statbuf->size == 0;
case BFS_DIR: dir = bfs_allocdir(); if (!dir) { goto error; }
if (bfs_opendir(dir, ftwbuf->at_fd, ftwbuf->at_path, 0) != 0) { goto error; }
int did_read = bfs_readdir(dir, NULL); bfs_closedir(dir);
if (did_read < 0) { goto error; }
free(dir); return did_read == 0; error: eval_report_error(state); free(dir); return false;
default: return false; }}
/** * -flags test. */bool eval_flags(const struct bfs_expr *expr, struct bfs_eval *state) { const struct bfs_stat *statbuf = eval_stat(state); if (!statbuf) { return false; }
if (!(statbuf->mask & BFS_STAT_ATTRS)) { eval_error(state, "Couldn't get file %s.\n", bfs_stat_field_name(BFS_STAT_ATTRS)); return false; }
unsigned long flags = statbuf->attrs; unsigned long set = expr->set_flags; unsigned long clear = expr->clear_flags;
switch (expr->flags_cmp) { case BFS_MODE_EQUAL: return flags == set && !(flags & clear);
case BFS_MODE_ALL: return (flags & set) == set && !(flags & clear);
case BFS_MODE_ANY: return (flags & set) || (flags & clear) != clear; }
bfs_bug("Invalid comparison mode"); return false;}
/** * -fstype test. */bool eval_fstype(const struct bfs_expr *expr, struct bfs_eval *state) { const struct bfs_stat *statbuf = eval_stat(state); if (!statbuf) { return false; }
const struct bfs_mtab *mtab = bfs_ctx_mtab(state->ctx); if (!mtab) { eval_report_error(state); return false; }
const char *type = bfs_fstype(mtab, statbuf); if (!type) { eval_report_error(state); return false; }
return strcmp(type, expr->argv[1]) == 0;}
/** * -hidden test. */bool eval_hidden(const struct bfs_expr *expr, struct bfs_eval *state) { const struct BFTW *ftwbuf = state->ftwbuf; const char *name = ftwbuf->path + ftwbuf->nameoff;
// Don't treat "." or ".." as hidden directories. Otherwise we'd filter // out everything when given // // $ bfs . -nohidden // $ bfs .. -nohidden return name[0] == '.' && strcmp(name, ".") != 0 && strcmp(name, "..") != 0;}
/** * -inum test. */bool eval_inum(const struct bfs_expr *expr, struct bfs_eval *state) { const struct bfs_stat *statbuf = eval_stat(state); if (!statbuf) { return false; }
return bfs_expr_cmp(expr, statbuf->ino);}
/** * -links test. */bool eval_links(const struct bfs_expr *expr, struct bfs_eval *state) { const struct bfs_stat *statbuf = eval_stat(state); if (!statbuf) { return false; }
return bfs_expr_cmp(expr, statbuf->nlink);}
/** * -i?lname test. */bool eval_lname(const struct bfs_expr *expr, struct bfs_eval *state) { bool ret = false; char *name = NULL;
const struct BFTW *ftwbuf = state->ftwbuf; if (ftwbuf->type != BFS_LNK) { goto done; }
const struct bfs_stat *statbuf = bftw_cached_stat(ftwbuf, BFS_STAT_NOFOLLOW); size_t len = statbuf ? statbuf->size : 0;
name = xreadlinkat(ftwbuf->at_fd, ftwbuf->at_path, len); if (!name) { eval_report_error(state); goto done; }
ret = eval_fnmatch(expr, name);
done: free(name); return ret;}
/** * -i?name test. */bool eval_name(const struct bfs_expr *expr, struct bfs_eval *state) { bool ret = false; const struct BFTW *ftwbuf = state->ftwbuf;
const char *name = ftwbuf->path + ftwbuf->nameoff; char *copy = NULL; if (ftwbuf->depth == 0) { // Any trailing slashes are not part of the name. This can only // happen for the root path. name = copy = xbasename(name); if (!name) { eval_report_error(state); goto done; } }
ret = eval_fnmatch(expr, name);
done: free(copy); return ret;}
/** * -i?path test. */bool eval_path(const struct bfs_expr *expr, struct bfs_eval *state) { return eval_fnmatch(expr, state->ftwbuf->path);}
/** * -perm test. */bool eval_perm(const struct bfs_expr *expr, struct bfs_eval *state) { const struct bfs_stat *statbuf = eval_stat(state); if (!statbuf) { return false; }
mode_t mode = statbuf->mode; mode_t target; if (state->ftwbuf->type == BFS_DIR) { target = expr->dir_mode; } else { target = expr->file_mode; }
switch (expr->mode_cmp) { case BFS_MODE_EQUAL: return (mode & 07777) == target;
case BFS_MODE_ALL: return (mode & target) == target;
case BFS_MODE_ANY: return !(mode & target) == !target; }
bfs_bug("Invalid comparison mode"); return false;}
/** Print a user/group name/id, and update the column width. */static int print_owner(FILE *file, const char *name, uintmax_t id, int *width) { if (name) { int len = xstrwidth(name); if (*width < len) { *width = len; }
return fprintf(file, " %s%*s", name, *width - len, ""); } else { int ret = fprintf(file, " %-*ju", *width, id); if (ret >= 0 && *width < ret - 1) { *width = ret - 1; } return ret; }}
/** Print a file's modification time. */static int print_time(FILE *file, time_t time, time_t now) { struct tm tm; if (!localtime_r(&time, &tm)) { goto error; }
char time_str[256]; size_t time_ret;
time_t six_months_ago = now - 6 * 30 * 24 * 60 * 60; time_t tomorrow = now + 24 * 60 * 60; if (time <= six_months_ago || time >= tomorrow) { time_ret = strftime(time_str, sizeof(time_str), "%b %e %Y", &tm); } else { time_ret = strftime(time_str, sizeof(time_str), "%b %e %H:%M", &tm); }
if (time_ret == 0) { goto error; }
return fprintf(file, " %s", time_str);
error: return fprintf(file, " %jd", (intmax_t)time);}
/** * -f?ls action. */bool eval_fls(const struct bfs_expr *expr, struct bfs_eval *state) { CFILE *cfile = expr->cfile; FILE *file = cfile->file; const struct bfs_ctx *ctx = state->ctx; const struct BFTW *ftwbuf = state->ftwbuf; const struct bfs_stat *statbuf = eval_stat(state); if (!statbuf) { goto done; }
// ls -l prints non-path text in the "normal" color, so do the same if (cfprintf(cfile, "${no}") < 0) { goto error; }
uintmax_t ino = statbuf->ino; uintmax_t block_size = ctx->posixly_correct ? 512 : 1024; uintmax_t blocks = ((uintmax_t)statbuf->blocks * BFS_STAT_BLKSIZE + block_size - 1) / block_size; char mode[11]; xstrmode(statbuf->mode, mode); char acl = bfs_check_acl(ftwbuf) > 0 ? '+' : ' '; uintmax_t nlink = statbuf->nlink; if (fprintf(file, "%9ju %6ju %s%c %2ju", ino, blocks, mode, acl, nlink) < 0) { goto error; }
const struct passwd *pwd = bfs_getpwuid(ctx->users, statbuf->uid); static int uwidth = 8; if (print_owner(file, pwd ? pwd->pw_name : NULL, statbuf->uid, &uwidth) < 0) { goto error; }
const struct group *grp = bfs_getgrgid(ctx->groups, statbuf->gid); static int gwidth = 8; if (print_owner(file, grp ? grp->gr_name : NULL, statbuf->gid, &gwidth) < 0) { goto error; }
if (ftwbuf->type == BFS_BLK || ftwbuf->type == BFS_CHR) { int ma = xmajor(statbuf->rdev); int mi = xminor(statbuf->rdev); if (fprintf(file, " %3d, %3d", ma, mi) < 0) { goto error; } } else { uintmax_t size = statbuf->size; if (fprintf(file, " %8ju", size) < 0) { goto error; } }
time_t time = statbuf->mtime.tv_sec; time_t now = ctx->now.tv_sec; if (print_time(file, time, now) < 0) { goto error; }
if (cfprintf(cfile, "${rs} %pP", ftwbuf) < 0) { goto error; }
if (ftwbuf->type == BFS_LNK) { if (cfprintf(cfile, " -> %pL", ftwbuf) < 0) { goto error; } }
if (fputc('\n', file) == EOF) { goto error; }
done: return true;
error: eval_io_error(expr, state); return true;}
/** * -f?print action. */bool eval_fprint(const struct bfs_expr *expr, struct bfs_eval *state) { if (cfprintf(expr->cfile, "%pP\n", state->ftwbuf) < 0) { eval_io_error(expr, state); } return true;}
/** * -f?print0 action. */bool eval_fprint0(const struct bfs_expr *expr, struct bfs_eval *state) { const char *path = state->ftwbuf->path; size_t length = strlen(path) + 1; if (fwrite(path, 1, length, expr->cfile->file) != length) { eval_io_error(expr, state); } return true;}
/** * -f?printf action. */bool eval_fprintf(const struct bfs_expr *expr, struct bfs_eval *state) { if (bfs_printf(expr->cfile, expr->printf, state->ftwbuf) != 0) { eval_io_error(expr, state); }
return true;}
/** * -printx action. */bool eval_fprintx(const struct bfs_expr *expr, struct bfs_eval *state) { FILE *file = expr->cfile->file; const char *path = state->ftwbuf->path;
while (true) { size_t span = strcspn(path, " \t\n\\$'\"`"); if (fwrite(path, 1, span, file) != span) { goto error; } path += span;
char c = path[0]; if (!c) { break; }
char escaped[] = {'\\', c}; if (fwrite(escaped, 1, sizeof(escaped), file) != sizeof(escaped)) { goto error; } ++path; }
if (fputc('\n', file) == EOF) { goto error; }
return true;
error: eval_io_error(expr, state); return true;}
/** * -limit action. */bool eval_limit(const struct bfs_expr *expr, struct bfs_eval *state) { long long evals = expr->evaluations + 1; if (evals >= expr->num) { state->action = BFTW_STOP; state->quit = true; }
return true;}
/** * -prune action. */bool eval_prune(const struct bfs_expr *expr, struct bfs_eval *state) { state->action = BFTW_PRUNE; return true;}
/** * -quit action. */bool eval_quit(const struct bfs_expr *expr, struct bfs_eval *state) { state->action = BFTW_STOP; state->quit = true; return true;}
/** * -i?regex test. */bool eval_regex(const struct bfs_expr *expr, struct bfs_eval *state) { const char *path = state->ftwbuf->path;
int ret = bfs_regexec(expr->regex, path, BFS_REGEX_ANCHOR); if (ret < 0) { char *str = bfs_regerror(expr->regex); if (str) { eval_error(state, "%s.\n", str); free(str); } else { eval_error(state, "bfs_regerror(): %s.\n", errstr()); } }
return ret > 0;}
/** * -samefile test. */bool eval_samefile(const struct bfs_expr *expr, struct bfs_eval *state) { const struct bfs_stat *statbuf = eval_stat(state); if (!statbuf) { return false; }
return statbuf->dev == expr->dev && statbuf->ino == expr->ino;}
/** * -size test. */bool eval_size(const struct bfs_expr *expr, struct bfs_eval *state) { const struct bfs_stat *statbuf = eval_stat(state); if (!statbuf) { return false; }
off_t factor = bfs_size_unit_factor(expr->size_unit); off_t size = (statbuf->size + factor - 1) / factor; // Round up return bfs_expr_cmp(expr, size);}
/** * -sparse test. */bool eval_sparse(const struct bfs_expr *expr, struct bfs_eval *state) { const struct bfs_stat *statbuf = eval_stat(state); if (!statbuf) { return false; }
blkcnt_t expected = (statbuf->size + BFS_STAT_BLKSIZE - 1) / BFS_STAT_BLKSIZE; return statbuf->blocks < expected;}
/** * -type test. */bool eval_type(const struct bfs_expr *expr, struct bfs_eval *state) { return (1 << state->ftwbuf->type) & expr->num;}
/** * -xattr test. */bool eval_xattr(const struct bfs_expr *expr, struct bfs_eval *state) { int ret = bfs_check_xattrs(state->ftwbuf); if (ret >= 0) { return ret; } else { eval_report_error(state); return false; }}
/** * -xattrname test. */bool eval_xattrname(const struct bfs_expr *expr, struct bfs_eval *state) { int ret = bfs_check_xattr_named(state->ftwbuf, expr->argv[1]); if (ret >= 0) { return ret; } else { eval_report_error(state); return false; }}
/** * -xtype test. */bool eval_xtype(const struct bfs_expr *expr, struct bfs_eval *state) { const struct BFTW *ftwbuf = state->ftwbuf; enum bfs_stat_flags flags = ftwbuf->stat_flags ^ (BFS_STAT_NOFOLLOW | BFS_STAT_TRYFOLLOW); enum bfs_type type = bftw_type(ftwbuf, flags);
// GNU find treats ELOOP as a broken symbolic link for -xtype l // (but not -L -type l) if ((flags & BFS_STAT_TRYFOLLOW) && type == BFS_ERROR && errno == ELOOP) { type = BFS_LNK; }
if (type == BFS_ERROR) { eval_report_error(state); return false; } else { return (1 << type) & expr->num; }}
/** * clock_gettime() wrapper. */static int eval_gettime(struct bfs_eval *state, struct timespec *ts) { clockid_t clock = CLOCK_REALTIME;
#if defined(_POSIX_MONOTONIC_CLOCK) && _POSIX_MONOTONIC_CLOCK >= 0 if (sysoption(MONOTONIC_CLOCK) > 0) { clock = CLOCK_MONOTONIC; }#endif
int ret = clock_gettime(clock, ts); if (ret != 0) { bfs_warning(state->ctx, "%pP: clock_gettime(): %s.\n", state->ftwbuf, errstr()); } return ret;}
/** * Evaluate an expression. */static bool eval_expr(struct bfs_expr *expr, struct bfs_eval *state) { struct timespec start, end; bool time = state->ctx->debug & DEBUG_RATES; if (time) { if (eval_gettime(state, &start) != 0) { time = false; } }
bfs_assert(!state->quit);
bool ret = expr->eval_fn(expr, state);
if (time) { if (eval_gettime(state, &end) == 0) { timespec_sub(&end, &start); timespec_add(&expr->elapsed, &end); } }
++expr->evaluations; if (ret) { ++expr->successes; }
if (bfs_expr_never_returns(expr)) { bfs_assert(state->quit); } else if (!state->quit) { bfs_assert(!expr->always_true || ret); bfs_assert(!expr->always_false || !ret); }
return ret;}
/** * Evaluate a negation. */bool eval_not(const struct bfs_expr *expr, struct bfs_eval *state) { return !eval_expr(bfs_expr_children(expr), state);}
/** * Evaluate a conjunction. */bool eval_and(const struct bfs_expr *expr, struct bfs_eval *state) { for_expr (child, expr) { if (!eval_expr(child, state) || state->quit) { return false; } }
return true;}
/** * Evaluate a disjunction. */bool eval_or(const struct bfs_expr *expr, struct bfs_eval *state) { for_expr (child, expr) { if (eval_expr(child, state) || state->quit) { return true; } }
return false;}
/** * Evaluate the comma operator. */bool eval_comma(const struct bfs_expr *expr, struct bfs_eval *state) { bool ret uninit(false);
for_expr (child, expr) { ret = eval_expr(child, state); if (state->quit) { break; } }
return ret;}
/** Update the status bar. */static void eval_status(struct bfs_eval *state, struct bfs_bar *bar, size_t count) { size_t width = bfs_bar_width(bar); if (width < 3) { return; }
const struct BFTW *ftwbuf = state->ftwbuf;
dchar *status = NULL; dchar *rhs = dstrprintf(" (visited: %'zu; depth: %2zu)", count, ftwbuf->depth); if (!rhs) { return; }
size_t rhslen = xstrwidth(rhs); if (3 + rhslen > width) { dstrshrink(rhs, 0); rhslen = 0; }
status = dstralloc(0); if (!status) { goto out; }
const char *path = ftwbuf->path; size_t pathlen = ftwbuf->nameoff; if (ftwbuf->depth == 0) { pathlen = strlen(path); }
// Escape weird filename characters if (dstrnescat(&status, path, pathlen, WESC_TTY) != 0) { goto out; } pathlen = dstrlen(status);
// Try to make sure even wide characters fit in the status bar size_t pathmax = width - rhslen - 3; size_t pathwidth = 0; size_t lhslen = 0; mbstate_t mb = {0}; for (size_t i = lhslen; lhslen < pathlen; lhslen = i) { wint_t wc = xmbrtowc(status, &i, pathlen, &mb); int cwidth; if (wc == (wint_t)WEOF) { // Invalid byte sequence, assume a single-width '?' cwidth = 1; } else { cwidth = xwcwidth(wc); if (cwidth < 0) { cwidth = 0; } }
if (pathwidth + cwidth > pathmax) { break; } pathwidth += cwidth; } dstrshrink(status, lhslen);
if (dstrcat(&status, "...") != 0) { goto out; }
while (pathwidth < pathmax) { if (dstrapp(&status, ' ') != 0) { goto out; } ++pathwidth; }
if (dstrdcat(&status, rhs) != 0) { goto out; }
bfs_bar_update(bar, status);
out: dstrfree(status); dstrfree(rhs);}
/** Check if we've seen a file before. */static bool eval_file_unique(struct bfs_eval *state, struct trie *seen) { const struct bfs_stat *statbuf = eval_stat(state); if (!statbuf) { return false; }
bfs_file_id id; bfs_stat_id(statbuf, &id);
struct trie_leaf *leaf = trie_insert_mem(seen, id, sizeof(id)); if (!leaf) { eval_report_error(state); return false; }
if (leaf->value) { state->action = BFTW_PRUNE; return false; } else { leaf->value = leaf; return true; }}
#define DEBUG_FLAG(flags, flag) \ do { \ if ((flags & flag) || flags == flag) { \ fputs(#flag, stderr); \ flags ^= flag; \ if (flags) { \ fputs(" | ", stderr); \ } \ } \ } while (0)
/** * Log a stat() call. */static void debug_stat(const struct bfs_ctx *ctx, const struct BFTW *ftwbuf, enum bfs_stat_flags flags, int err) { bfs_debug_prefix(ctx, DEBUG_STAT);
fprintf(stderr, "bfs_stat("); if (ftwbuf->at_fd == (int)AT_FDCWD) { fprintf(stderr, "AT_FDCWD"); } else { size_t baselen = strlen(ftwbuf->path) - strlen(ftwbuf->at_path); fprintf(stderr, "\""); fwrite(ftwbuf->path, 1, baselen, stderr); fprintf(stderr, "\""); }
fprintf(stderr, ", \"%s\", ", ftwbuf->at_path);
DEBUG_FLAG(flags, BFS_STAT_FOLLOW); DEBUG_FLAG(flags, BFS_STAT_NOFOLLOW); DEBUG_FLAG(flags, BFS_STAT_TRYFOLLOW); DEBUG_FLAG(flags, BFS_STAT_NOSYNC);
fprintf(stderr, ") == %d", err == 0 ? 0 : -1);
if (err) { fprintf(stderr, " [%d]", err); }
fprintf(stderr, "\n");}
/** * Log any stat() calls that happened. */static void debug_stats(const struct bfs_ctx *ctx, const struct BFTW *ftwbuf) { if (!(ctx->debug & DEBUG_STAT)) { return; }
const struct bftw_stat *bufs = &ftwbuf->stat_bufs;
if (bufs->stat_err >= 0) { debug_stat(ctx, ftwbuf, BFS_STAT_FOLLOW, bufs->stat_err); }
if (bufs->lstat_err >= 0) { debug_stat(ctx, ftwbuf, BFS_STAT_NOFOLLOW, bufs->lstat_err); }}
#define DUMP_MAP(value) [value] = #value
/** * Dump the bfs_type for -D search. */static const char *dump_bfs_type(enum bfs_type type) { static const char *types[] = { DUMP_MAP(BFS_UNKNOWN), DUMP_MAP(BFS_BLK), DUMP_MAP(BFS_CHR), DUMP_MAP(BFS_DIR), DUMP_MAP(BFS_DOOR), DUMP_MAP(BFS_FIFO), DUMP_MAP(BFS_LNK), DUMP_MAP(BFS_PORT), DUMP_MAP(BFS_REG), DUMP_MAP(BFS_SOCK), DUMP_MAP(BFS_WHT), };
if (type == BFS_ERROR) { return "BFS_ERROR"; } else { return types[type]; }}
/** * Dump the bftw_visit for -D search. */static const char *dump_bftw_visit(enum bftw_visit visit) { static const char *visits[] = { DUMP_MAP(BFTW_PRE), DUMP_MAP(BFTW_POST), }; return visits[visit];}
/** * Dump the bftw_action for -D search. */static const char *dump_bftw_action(enum bftw_action action) { static const char *actions[] = { DUMP_MAP(BFTW_CONTINUE), DUMP_MAP(BFTW_PRUNE), DUMP_MAP(BFTW_STOP), }; return actions[action];}
/** * Type passed as the argument to the bftw() callback. */struct callback_args { /** The bfs context. */ const struct bfs_ctx *ctx;
/** The status bar. */ struct bfs_bar *bar; /** The SIGALRM hook. */ struct sighook *alrm_hook; /** The interval timer. */ struct timer *timer; /** Flag set by SIGALRM. */ atomic bool alrm_flag; /** Flag set by SIGINFO. */ atomic bool info_flag;
/** The number of files visited so far. */ size_t count;
/** The set of seen files. */ struct trie *seen;
/** The number of errors that have occurred. */ size_t nerrors; /** Eventual return value from bfs_eval(). */ int ret;};
/** Update the status bar in response to SIGALRM. */static void eval_sigalrm(int sig, siginfo_t *info, void *ptr) { struct callback_args *args = ptr; store(&args->alrm_flag, true, relaxed);}
/** Show/hide the bar in response to SIGINFO. */static void eval_siginfo(int sig, siginfo_t *info, void *ptr) { struct callback_args *args = ptr; store(&args->info_flag, true, relaxed);}
/** Show the status bar. */static void eval_show_bar(struct callback_args *args) { args->alrm_hook = sighook(SIGALRM, eval_sigalrm, args, SH_CONTINUE); if (!args->alrm_hook) { goto fail; }
args->bar = bfs_bar_show(); if (!args->bar) { goto fail; }
// Update the bar every 0.1s struct timespec ival = { .tv_nsec = 100 * 1000 * 1000 }; args->timer = xtimer_start(&ival); if (!args->timer) { goto fail; }
// Update the bar immediately store(&args->alrm_flag, true, relaxed);
return;
fail: bfs_warning(args->ctx, "Couldn't show status bar: %s.\n\n", errstr());
bfs_bar_hide(args->bar); args->bar = NULL;
sigunhook(args->alrm_hook); args->alrm_hook = NULL;}
/** Hide the status bar. */static void eval_hide_bar(struct callback_args *args) { xtimer_stop(args->timer); args->timer = NULL;
sigunhook(args->alrm_hook); args->alrm_hook = NULL;
bfs_bar_hide(args->bar); args->bar = NULL;}
/** * bftw() callback. */static enum bftw_action eval_callback(const struct BFTW *ftwbuf, void *ptr) { struct callback_args *args = ptr; ++args->count;
const struct bfs_ctx *ctx = args->ctx;
struct bfs_eval state; state.ftwbuf = ftwbuf; state.ctx = ctx; state.action = BFTW_CONTINUE; state.ret = &args->ret; state.nerrors = &args->nerrors; state.quit = false;
// Check whether SIGINFO was delivered and show/hide the bar if (exchange(&args->info_flag, false, relaxed)) { if (args->bar) { eval_hide_bar(args); } else { eval_show_bar(args); } }
if (exchange(&args->alrm_flag, false, relaxed)) { eval_status(&state, args->bar, args->count); }
if (ftwbuf->type == BFS_ERROR) { state.action = BFTW_PRUNE;
if (ftwbuf->error == ELOOP && ftwbuf->loopoff > 0) { char *loop = strndup(ftwbuf->path, ftwbuf->loopoff); if (loop) { eval_error(&state, "Filesystem loop back to ${di}%pq${rs}\n", loop); free(loop); goto done; } } else if (eval_should_ignore(&state, ftwbuf->error)) { goto done; }
eval_error(&state, "%s.\n", xstrerror(ftwbuf->error)); goto done; }
if (ctx->unique && ftwbuf->visit == BFTW_PRE) { if (!eval_file_unique(&state, args->seen)) { goto done; } }
if (eval_expr(ctx->exclude, &state)) { state.action = BFTW_PRUNE; goto done; }
if (ctx->xargs_safe && strpbrk(ftwbuf->path, " \t\n\'\"\\")) { eval_error(&state, "Path is not safe for xargs.\n"); state.action = BFTW_PRUNE; goto done; }
if (ctx->maxdepth < 0 || ftwbuf->depth >= (size_t)ctx->maxdepth) { state.action = BFTW_PRUNE; }
// In -depth mode, only handle directories on the BFTW_POST visit enum bftw_visit expected_visit = BFTW_PRE; if ((ctx->flags & BFTW_POST_ORDER) && (ctx->strategy == BFTW_IDS || ftwbuf->type == BFS_DIR) && ftwbuf->depth < (size_t)ctx->maxdepth) { expected_visit = BFTW_POST; }
if (ftwbuf->visit == expected_visit && ftwbuf->depth >= (size_t)ctx->mindepth && ftwbuf->depth <= (size_t)ctx->maxdepth) { eval_expr(ctx->expr, &state); }
done: debug_stats(ctx, ftwbuf);
if (bfs_debug(ctx, DEBUG_SEARCH, "eval_callback({\n")) { fprintf(stderr, "\t.path = \"%s\",\n", ftwbuf->path); fprintf(stderr, "\t.root = \"%s\",\n", ftwbuf->root); fprintf(stderr, "\t.depth = %zu,\n", ftwbuf->depth); fprintf(stderr, "\t.visit = %s,\n", dump_bftw_visit(ftwbuf->visit)); fprintf(stderr, "\t.type = %s,\n", dump_bfs_type(ftwbuf->type)); fprintf(stderr, "\t.error = %d,\n", ftwbuf->error); fprintf(stderr, "}) == %s\n", dump_bftw_action(state.action)); }
return state.action;}
#if BFS_HAS_SYSCTLBYNAME/** Simple wrapper over sysctlbyname() that only reads. */static int xsysctl(const char *name, void *ptr, size_t size) { size_t retsize = size; int ret = sysctlbyname(name, ptr, &retsize, NULL, 0); if (ret != 0) { return ret; }
if (retsize != size) { errno = ERANGE; return -1; }
return 0;}#endif
/** Get the system-wide open file limit if possible. */static rlim_t system_fdlimit(void) { rlim_t ret = RLIM_INFINITY;
#if BFS_HAS_SYSCTLBYNAME int32_t value; if (xsysctl("kern.maxfiles", &value, sizeof(value)) == 0) { ret = value; }#elif __linux__ FILE *file = xfopen("/proc/sys/fs/file-max", O_RDONLY | O_CLOEXEC); if (!file) { return ret; }
long value; if (fscanf(file, "%ld", &value) == 1) { ret = value; }
fclose(file);#endif
return ret;}
/** Get the per-process open file limit, if different from ulimit -n. */static rlim_t process_fdlimit(void) {#if BFS_HAS_SYSCTLBYNAME int32_t value; if (xsysctl("kern.maxfilesperproc", &value, sizeof(value)) == 0) { return value; }#endif
return RLIM_INFINITY;}
/** Raise RLIMIT_NOFILE if possible, and return the new limit. */static int raise_fdlimit(struct bfs_ctx *ctx) { rlim_t cur = ctx->orig_nofile.rlim_cur; rlim_t max = ctx->orig_nofile.rlim_max; if (!ctx->raise_nofile) { max = cur; }
// Default to 64k files rlim_t target = 64 << 10;
// Don't exceed ulimit -Hn target = rlim_min(target, max);
// FreeBSD/macOS have a fairly low system-wide limit that we should try to respect rlim_t sys_limit = system_fdlimit(); if (sys_limit != RLIM_INFINITY) { // Play nice and restrict ourselves to 1/16th of the system-wide limit target = rlim_min(target, sys_limit / 16); }
// FreeBSD/macOS have this limit separately from ulimit -n target = rlim_min(target, process_fdlimit());
if (rlim_cmp(target, cur) <= 0) { // No need to raise the limit return target; }
const struct rlimit rl = { .rlim_cur = target, .rlim_max = max, };
if (setrlimit(RLIMIT_NOFILE, &rl) != 0) { // Failed to raise, return the original limit return cur; }
ctx->cur_nofile = rl; return target;}
/** Preallocate the fd table in the kernel. */static void reserve_fds(int limit) { // Kernels typically implement the fd table as a dynamic array. // Growing the array can be expensive, especially if files are being // opened in parallel. We can work around this by allocating the // highest possible fd, forcing the kernel to grow the table upfront.
#ifdef F_DUPFD_CLOEXEC int fd = fcntl(STDIN_FILENO, F_DUPFD_CLOEXEC, limit - 1);#else int fd = fcntl(STDIN_FILENO, F_DUPFD, limit - 1);#endif if (fd >= 0) { xclose(fd); }}
/** Infer the number of file descriptors available to bftw(). */static int infer_fdlimit(const struct bfs_ctx *ctx, int limit) { // 3 for std{in,out,err} int nopen = 3 + ctx->nfiles;
// Check /proc/self/fd for the current number of open fds, if possible // (we may have inherited more than just the standard ones) struct bfs_dir *dir = bfs_allocdir(); if (!dir) { goto done; }
if (bfs_opendir(dir, AT_FDCWD, "/proc/self/fd", 0) != 0 && bfs_opendir(dir, AT_FDCWD, "/dev/fd", 0) != 0) { goto done; }
// Account for 'dir' itself nopen = -1;
while (bfs_readdir(dir, NULL) > 0) { ++nopen; } bfs_closedir(dir);done: free(dir);
int ret = limit - nopen; ret -= ctx->expr->persistent_fds; ret -= ctx->expr->ephemeral_fds;
// bftw() needs at least 2 available fds if (ret < 2) { ret = 2; }
return ret;}
/** * Dump the bftw() flags for -D search. */static void dump_bftw_flags(enum bftw_flags flags) { DEBUG_FLAG(flags, 0); DEBUG_FLAG(flags, BFTW_STAT); DEBUG_FLAG(flags, BFTW_RECOVER); DEBUG_FLAG(flags, BFTW_POST_ORDER); DEBUG_FLAG(flags, BFTW_FOLLOW_ROOTS); DEBUG_FLAG(flags, BFTW_FOLLOW_ALL); DEBUG_FLAG(flags, BFTW_DETECT_CYCLES); DEBUG_FLAG(flags, BFTW_SKIP_MOUNTS); DEBUG_FLAG(flags, BFTW_PRUNE_MOUNTS); DEBUG_FLAG(flags, BFTW_SORT); DEBUG_FLAG(flags, BFTW_BUFFER); DEBUG_FLAG(flags, BFTW_WHITEOUTS);
bfs_assert(flags == 0, "Missing bftw flag 0x%X", flags);}
/** * Dump the bftw_strategy for -D search. */static const char *dump_bftw_strategy(enum bftw_strategy strategy) { static const char *strategies[] = { DUMP_MAP(BFTW_BFS), DUMP_MAP(BFTW_DFS), DUMP_MAP(BFTW_IDS), DUMP_MAP(BFTW_EDS), }; return strategies[strategy];}
/** Check if we need to enable BFTW_BUFFER. */static bool eval_must_buffer(const struct bfs_expr *expr) {#if __FreeBSD__ // FreeBSD doesn't properly handle adding/removing directory entries // during readdir() on NFS mounts. Work around it by passing BFTW_BUFFER // whenever we could be mutating the directory ourselves through -delete // or -exec. We don't attempt to handle concurrent modification by other // processes, which are racey anyway. // // https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=57696 // https://github.com/tavianator/bfs/issues/67
if (expr->eval_fn == eval_delete || expr->eval_fn == eval_exec) { return true; }
for_expr (child, expr) { if (eval_must_buffer(child)) { return true; } }#endif // __FreeBSD__
return false;}
int bfs_eval(struct bfs_ctx *ctx) { if (!ctx->expr) { return EXIT_SUCCESS; }
struct callback_args args = { .ctx = ctx, .ret = EXIT_SUCCESS, };
if (ctx->status) { eval_show_bar(&args); }
#ifdef SIGINFO int siginfo = SIGINFO;#else int siginfo = SIGUSR1;#endif struct sighook *info_hook = sighook(siginfo, eval_siginfo, &args, SH_CONTINUE);
struct trie seen; if (ctx->unique) { trie_init(&seen); args.seen = &seen; }
int fdlimit = raise_fdlimit(ctx); reserve_fds(fdlimit); fdlimit = infer_fdlimit(ctx, fdlimit);
// -1 for the main thread int nthreads = ctx->threads - 1;
struct bftw_args bftw_args = { .paths = ctx->paths, .npaths = ctx->npaths, .callback = eval_callback, .ptr = &args, .nopenfd = fdlimit, .nthreads = nthreads, .flags = ctx->flags, .strategy = ctx->strategy, .mtab = bfs_ctx_mtab(ctx), };
if (eval_must_buffer(ctx->expr)) { bftw_args.flags |= BFTW_BUFFER; }
if (bfs_debug(ctx, DEBUG_SEARCH, "bftw({\n")) { fprintf(stderr, "\t.paths = {\n"); for (size_t i = 0; i < bftw_args.npaths; ++i) { fprintf(stderr, "\t\t\"%s\",\n", bftw_args.paths[i]); } fprintf(stderr, "\t},\n"); fprintf(stderr, "\t.npaths = %zu,\n", bftw_args.npaths); fprintf(stderr, "\t.callback = eval_callback,\n"); fprintf(stderr, "\t.ptr = &args,\n"); fprintf(stderr, "\t.nopenfd = %d,\n", bftw_args.nopenfd); fprintf(stderr, "\t.nthreads = %d,\n", bftw_args.nthreads); fprintf(stderr, "\t.flags = "); dump_bftw_flags(bftw_args.flags); fprintf(stderr, ",\n\t.strategy = %s,\n", dump_bftw_strategy(bftw_args.strategy)); fprintf(stderr, "\t.mtab = "); if (bftw_args.mtab) { fprintf(stderr, "ctx->mtab"); } else { fprintf(stderr, "NULL"); } fprintf(stderr, ",\n})\n"); }
if (bftw(&bftw_args) != 0) { args.ret = EXIT_FAILURE; bfs_perror(ctx, "bftw()"); }
if (eval_exec_finish(ctx->expr, ctx) != 0) { args.ret = EXIT_FAILURE; }
bfs_ctx_dump(ctx, DEBUG_RATES);
if (ctx->unique) { trie_destroy(&seen); }
sigunhook(info_hook); if (args.bar) { eval_hide_bar(&args); }
if (ctx->ignore_errors && args.nerrors > 0) { bfs_warning(ctx, "Suppressed errors: %zu\n", args.nerrors); }
return args.ret;}