Something went wrong. Try again.
A breadth-first version of the UNIX find command tavianator.com/projects/bfs.html
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606360736083609361036113612361336143615361636173618361936203621362236233624362536263627362836293630363136323633363436353636363736383639364036413642364336443645364636473648364936503651365236533654365536563657365836593660366136623663366436653666366736683669367036713672367336743675367636773678367936803681368236833684368536863687368836893690369136923693369436953696369736983699370037013702370337043705370637073708370937103711371237133714371537163717371837193720372137223723372437253726372737283729373037313732373337343735373637373738373937403741374237433744374537463747374837493750375137523753375437553756375737583759376037613762376337643765376637673768376937703771377237733774377537763777377837793780378137823783378437853786378737883789379037913792379337943795379637973798379938003801380238033804380538063807380838093810381138123813381438153816381738183819382038213822382338243825382638273828382938303831383238333834383538363837383838393840384138423843384438453846384738483849385038513852385338543855385638573858385938603861386238633864386538663867386838693870387138723873387438753876387738783879388038813882388338843885388638873888388938903891389238933894389538963897389838993900390139023903390439053906390739083909391039113912391339143915391639173918391939203921392239233924392539263927392839293930393139323933393439353936393739383939394039413942394339443945394639473948394939503951395239533954395539563957395839593960396139623963396439653966396739683969397039713972397339743975397639773978397939803981// Copyright © Tavian Barnes <tavianator@tavianator.com>// SPDX-License-Identifier: 0BSD
/** * The command line parser. Expressions are parsed by recursive descent, with a * grammar described in the comments of the parse_*() functions. The parser * also accepts flags and paths at any point in the expression, by treating * flags like always-true options, and skipping over paths wherever they appear. */
#include "parse.h"
#include "alloc.h"#include "bfs.h"#include "bfstd.h"#include "bftw.h"#include "color.h"#include "ctx.h"#include "diag.h"#include "dir.h"#include "eval.h"#include "exec.h"#include "expr.h"#include "fsade.h"#include "list.h"#include "opt.h"#include "printf.h"#include "pwcache.h"#include "sanity.h"#include "stat.h"#include "typo.h"#include "xregex.h"#include "xspawn.h"#include "xtime.h"
#include <errno.h>#include <fcntl.h>#include <fnmatch.h>#include <grp.h>#include <limits.h>#include <pwd.h>#include <stdarg.h>#include <stdio.h>#include <stdlib.h>#include <string.h>#include <sys/stat.h>#include <sys/types.h>#include <time.h>#include <unistd.h>
// Strings printed by -D tree for "fake" expressionsstatic char *fake_and_arg = "-and";static char *fake_hidden_arg = "-hidden";static char *fake_or_arg = "-or";static char *fake_print_arg = "-print";static char *fake_true_arg = "-true";
/** * Color use flags. */enum use_color { COLOR_NEVER, COLOR_AUTO, COLOR_ALWAYS,};
/** * Command line parser state. */struct bfs_parser { /** The command line being constructed. */ struct bfs_ctx *ctx; /** The command line arguments being parsed. */ char **argv; /** The name of this program. */ const char *command;
/** The current regex flags to use. */ enum bfs_regex_type regex_type;
/** Whether stdout is a terminal. */ bool stdout_tty; /** Whether -color or -nocolor has been passed. */ enum use_color use_color; /** Whether a -print action is implied. */ bool implicit_print; /** Whether the expression has started. */ bool expr_started; /** Whether an information option like -help or -version was passed. */ bool just_info; /** Whether we are currently parsing an -exclude expression. */ bool excluding;
/** The last non-path argument. */ char **last_arg; /** A "-depth"-type expression, if any. */ const struct bfs_expr *depth_expr; /** A "-limit" expression, if any. */ const struct bfs_expr *limit_expr; /** A "-prune" expression, if any. */ const struct bfs_expr *prune_expr; /** A "-mount" expression, if any. */ const struct bfs_expr *mount_expr; /** An "-xdev" expression, if any. */ const struct bfs_expr *xdev_expr; /** A "-files0-from" expression, if any. */ const struct bfs_expr *files0_expr; /** An expression that consumes stdin, if any. */ const struct bfs_expr *stdin_expr;
/** The current time (maybe modified by -daystart). */ struct timespec now;};
/** * Print a low-level error message during parsing. */static void parse_perror(const struct bfs_parser *parser, const char *str) { bfs_perror(parser->ctx, str);}
/** Initialize an empty highlighted range. */static void init_highlight(const struct bfs_ctx *ctx, bool *args) { for (size_t i = 0; i < ctx->argc; ++i) { args[i] = false; }}
/** Highlight a range of command line arguments. */static void highlight_args(const struct bfs_ctx *ctx, char **argv, size_t argc, bool *args) { size_t i = argv - ctx->argv; for (size_t j = 0; j < argc; ++j) { bfs_assert(i + j < ctx->argc); args[i + j] = true; }}
/** * Print an error message during parsing. */[[_printf(2, 3)]]static void parse_error(const struct bfs_parser *parser, const char *format, ...) { const struct bfs_ctx *ctx = parser->ctx;
bool highlight[ctx->argc]; init_highlight(ctx, highlight); highlight_args(ctx, parser->argv, 1, highlight); bfs_argv_error(ctx, highlight);
va_list args; va_start(args, format); bfs_verror(parser->ctx, format, args); va_end(args);}
/** * Print an error about some command line arguments. */[[_printf(4, 5)]]static void parse_argv_error(const struct bfs_parser *parser, char **argv, size_t argc, const char *format, ...) { const struct bfs_ctx *ctx = parser->ctx;
bool highlight[ctx->argc]; init_highlight(ctx, highlight); highlight_args(ctx, argv, argc, highlight); bfs_argv_error(ctx, highlight);
va_list args; va_start(args, format); bfs_verror(ctx, format, args); va_end(args);}
/** * Print an error about conflicting command line arguments. */[[_printf(4, 5)]]static void parse_conflict_error(const struct bfs_parser *parser, const struct bfs_expr *expr1, const struct bfs_expr *expr2, const char *format, ...) { const struct bfs_ctx *ctx = parser->ctx;
bool highlight[ctx->argc]; init_highlight(ctx, highlight); highlight_args(ctx, expr1->argv, expr1->argc, highlight); highlight_args(ctx, expr2->argv, expr2->argc, highlight); bfs_argv_error(ctx, highlight);
va_list args; va_start(args, format); bfs_verror(ctx, format, args); va_end(args);}
/** * Print an error about an expression. */[[_printf(3, 4)]]static void parse_expr_error(const struct bfs_parser *parser, const struct bfs_expr *expr, const char *format, ...) { const struct bfs_ctx *ctx = parser->ctx;
bfs_expr_error(ctx, expr);
va_list args; va_start(args, format); bfs_verror(ctx, format, args); va_end(args);}
/** * Print a warning message during parsing. */[[_printf(2, 3)]]static bool parse_warning(const struct bfs_parser *parser, const char *format, ...) { const struct bfs_ctx *ctx = parser->ctx;
bool highlight[ctx->argc]; init_highlight(ctx, highlight); highlight_args(ctx, parser->argv, 1, highlight); if (!bfs_argv_warning(ctx, highlight)) { return false; }
va_list args; va_start(args, format); bool ret = bfs_vwarning(parser->ctx, format, args); va_end(args); return ret;}
/** * Print a warning about conflicting command line arguments. */[[_printf(4, 5)]]static bool parse_conflict_warning(const struct bfs_parser *parser, const struct bfs_expr *expr1, const struct bfs_expr *expr2, const char *format, ...) { const struct bfs_ctx *ctx = parser->ctx;
bool highlight[ctx->argc]; init_highlight(ctx, highlight); highlight_args(ctx, expr1->argv, expr1->argc, highlight); highlight_args(ctx, expr2->argv, expr2->argc, highlight); if (!bfs_argv_warning(ctx, highlight)) { return false; }
va_list args; va_start(args, format); bool ret = bfs_vwarning(ctx, format, args); va_end(args); return ret;}
/** * Print a warning about an expression. */[[_printf(3, 4)]]static bool parse_expr_warning(const struct bfs_parser *parser, const struct bfs_expr *expr, const char *format, ...) { const struct bfs_ctx *ctx = parser->ctx;
if (!bfs_expr_warning(ctx, expr)) { return false; }
va_list args; va_start(args, format); bool ret = bfs_vwarning(ctx, format, args); va_end(args); return ret;}
/** * Report an error if stdin is already consumed, then consume it. */static bool consume_stdin(struct bfs_parser *parser, const struct bfs_expr *expr) { if (parser->stdin_expr) { parse_conflict_error(parser, parser->stdin_expr, expr, "%pX and %pX can't both use standard input.\n", parser->stdin_expr, expr); return false; }
parser->stdin_expr = expr; return true;}
/** * Allocate a new expression. */static struct bfs_expr *parse_new_expr(const struct bfs_parser *parser, bfs_eval_fn *eval_fn, size_t argc, char **argv, enum bfs_kind kind) { struct bfs_expr *expr = bfs_expr_new(parser->ctx, eval_fn, argc, argv, kind); if (!expr) { parse_perror(parser, "bfs_expr_new()"); } return expr;}
/** * Create a new unary expression. */static struct bfs_expr *new_unary_expr(const struct bfs_parser *parser, bfs_eval_fn *eval_fn, struct bfs_expr *rhs, char **argv) { struct bfs_expr *expr = parse_new_expr(parser, eval_fn, 1, argv, BFS_OPERATOR); if (!expr) { return NULL; }
bfs_assert(bfs_expr_is_parent(expr)); bfs_expr_append(expr, rhs); return expr;}
/** * Create a new binary expression. */static struct bfs_expr *new_binary_expr(const struct bfs_parser *parser, bfs_eval_fn *eval_fn, struct bfs_expr *lhs, struct bfs_expr *rhs, char **argv) { struct bfs_expr *expr = parse_new_expr(parser, eval_fn, 1, argv, BFS_OPERATOR); if (!expr) { return NULL; }
bfs_assert(bfs_expr_is_parent(expr)); bfs_expr_append(expr, lhs); bfs_expr_append(expr, rhs); return expr;}
/** * Fill in a "-print"-type expression. */static void init_print_expr(struct bfs_parser *parser, struct bfs_expr *expr) { expr->cfile = parser->ctx->cout; expr->path = NULL;}
/** * Open a file for an expression. */static int expr_open(struct bfs_parser *parser, struct bfs_expr *expr, const char *path) { struct bfs_ctx *ctx = parser->ctx;
FILE *file = NULL; CFILE *cfile = NULL;
file = xfopen(path, O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC); if (!file) { goto fail; }
cfile = cfwrap(file, parser->use_color ? ctx->colors : NULL, true); if (!cfile) { goto fail; }
CFILE *dedup = bfs_ctx_dedup(ctx, cfile, path); if (!dedup) { goto fail; }
if (dedup != cfile) { cfclose(cfile); }
expr->cfile = dedup; expr->path = path; return 0;
fail: parse_expr_error(parser, expr, "%s.\n", errstr()); if (cfile) { cfclose(cfile); } else if (file) { fclose(file); } return -1;}
/** * Invoke bfs_stat() on an argument. */static int stat_arg(const struct bfs_parser *parser, char **arg, struct bfs_stat *sb) { const struct bfs_ctx *ctx = parser->ctx;
bool follow = ctx->flags & (BFTW_FOLLOW_ROOTS | BFTW_FOLLOW_ALL); enum bfs_stat_flags flags = follow ? BFS_STAT_TRYFOLLOW : BFS_STAT_NOFOLLOW;
int ret = bfs_stat(AT_FDCWD, *arg, flags, sb); if (ret != 0) { parse_argv_error(parser, arg, 1, "%s.\n", errstr()); } return ret;}
/** * Parse the expression specified on the command line. */static struct bfs_expr *parse_expr(struct bfs_parser *parser);
/** * Advance by a single token. */static char **parser_advance(struct bfs_parser *parser, enum bfs_kind kind, size_t argc) { struct bfs_ctx *ctx = parser->ctx;
if (kind != BFS_FLAG && kind != BFS_PATH) { parser->expr_started = true; }
if (kind != BFS_PATH) { parser->last_arg = parser->argv; }
size_t i = parser->argv - ctx->argv; ctx->kinds[i] = kind;
char **argv = parser->argv; parser->argv += argc; return argv;}
/** * Parse a root path. */static int parse_root(struct bfs_parser *parser, const char *path) { struct bfs_ctx *ctx = parser->ctx; const char **root = RESERVE(const char *, &ctx->paths, &ctx->npaths); if (!root) { parse_perror(parser, "RESERVE()"); return -1; }
*root = strdup(path); if (!*root) { --ctx->npaths; parse_perror(parser, "strdup()"); return -1; }
return 0;}
/** * While parsing an expression, skip any paths and add them to ctx->paths. */static int skip_paths(struct bfs_parser *parser) { while (true) { const char *arg = parser->argv[0]; if (!arg) { return 0; }
if (arg[0] == '-') { if (strcmp(arg, "--") == 0) { // find uses -- to separate flags from the rest // of the command line. We allow mixing flags // and paths/predicates, so we just ignore --. parser_advance(parser, BFS_FLAG, 1); continue; } if (strcmp(arg, "-") != 0) { // - by itself is a file name. Anything else // starting with - is a flag/predicate. return 0; } }
// By POSIX, these are always options if (strcmp(arg, "(") == 0 || strcmp(arg, "!") == 0) { return 0; }
if (parser->expr_started) { // By POSIX, these can be paths. We only treat them as // such at the beginning of the command line. if (strcmp(arg, ")") == 0 || strcmp(arg, ",") == 0) { return 0; } }
if (parser->excluding) { parse_warning(parser, "This path will not be excluded. Use a test like ${blu}-name${rs} or ${blu}-path${rs}\n"); bfs_warning(parser->ctx, "within ${red}-exclude${rs} to exclude matching files.\n\n"); }
if (parse_root(parser, arg) != 0) { return -1; }
parser_advance(parser, BFS_PATH, 1); }}
/** Integer parsing flags. */enum int_flags { IF_BASE_MASK = 0x03F, IF_INT = 0x040, IF_LONG = 0x080, IF_LONG_LONG = 0x0C0, IF_SIZE_MASK = 0x0C0, IF_UNSIGNED = 0x100, IF_PARTIAL_OK = 0x200, IF_QUIET = 0x400,};
/** * Parse an integer. */static const char *parse_int(const struct bfs_parser *parser, char **arg, const char *str, void *result, enum int_flags flags) { int base = flags & IF_BASE_MASK; if (base == 0) { base = 10; }
char *endptr; long long value; if (xstrtoll(str, &endptr, base, &value) != 0) { if (errno == ERANGE) { goto range; } else { goto bad; } }
if (!(flags & IF_PARTIAL_OK) && *endptr != '\0') { goto bad; }
if ((flags & IF_UNSIGNED) && value < 0) { goto negative; }
switch (flags & IF_SIZE_MASK) { case IF_INT: if (value < INT_MIN || value > INT_MAX) { goto range; } *(int *)result = value; break;
case IF_LONG: if (value < LONG_MIN || value > LONG_MAX) { goto range; } *(long *)result = value; break;
case IF_LONG_LONG: *(long long *)result = value; break;
default: bfs_bug("Invalid int size"); goto bad; }
return endptr;
bad: if (!(flags & IF_QUIET)) { parse_argv_error(parser, arg, 1, "${bld}%pq${rs} is not a valid integer.\n", str); } return NULL;
negative: if (!(flags & IF_QUIET)) { parse_argv_error(parser, arg, 1, "Negative integer ${bld}%pq${rs} is not allowed here.\n", str); } return NULL;
range: if (!(flags & IF_QUIET)) { parse_argv_error(parser, arg, 1, "${bld}%pq${rs} is too large an integer.\n", str); } return NULL;}
/** * Parse an integer and a comparison flag. */static const char *parse_icmp(const struct bfs_parser *parser, struct bfs_expr *expr, enum int_flags flags) { char **arg = &expr->argv[1]; const char *str = *arg; switch (str[0]) { case '-': expr->int_cmp = BFS_INT_LESS; ++str; break; case '+': expr->int_cmp = BFS_INT_GREATER; ++str; break; default: expr->int_cmp = BFS_INT_EQUAL; break; }
return parse_int(parser, arg, str, &expr->num, flags | IF_LONG_LONG | IF_UNSIGNED);}
/** * Check if a string could be an integer comparison. */static bool looks_like_icmp(const char *str) { int i;
// One +/- for the comparison flag, one for the sign for (i = 0; i < 2; ++i) { if (str[i] != '-' && str[i] != '+') { break; } }
return str[i] >= '0' && str[i] <= '9';}
/** * Parse a single flag. */static struct bfs_expr *parse_flag(struct bfs_parser *parser, size_t argc) { char **argv = parser_advance(parser, BFS_FLAG, argc); return parse_new_expr(parser, eval_true, argc, argv, BFS_FLAG);}
/** * Parse a flag that doesn't take a value. */static struct bfs_expr *parse_nullary_flag(struct bfs_parser *parser) { return parse_flag(parser, 1);}
/** * Parse a flag that takes a value. */static struct bfs_expr *parse_unary_flag(struct bfs_parser *parser) { const char *arg = parser->argv[0]; char flag = arg[strlen(arg) - 1];
const char *value = parser->argv[1]; if (!value) { parse_error(parser, "${cyn}-%c${rs} needs a value.\n", flag); return NULL; }
return parse_flag(parser, 2);}
/** * Parse a prefix flag like -O3, -j8, etc. */static struct bfs_expr *parse_prefix_flag(struct bfs_parser *parser, char flag, bool allow_separate, const char **value) { const char *arg = parser->argv[0];
const char *suffix = strchr(arg, flag) + 1; if (*suffix) { *value = suffix; return parse_nullary_flag(parser); }
suffix = parser->argv[1]; if (allow_separate && suffix) { *value = suffix; } else { parse_error(parser, "${cyn}-%c${rs} needs a value.\n", flag); return NULL; }
return parse_unary_flag(parser);}
/** * Parse a single option. */static struct bfs_expr *parse_option(struct bfs_parser *parser, size_t argc) { char **argv = parser_advance(parser, BFS_OPTION, argc); return parse_new_expr(parser, eval_true, argc, argv, BFS_OPTION);}
/** * Parse an option that doesn't take a value. */static struct bfs_expr *parse_nullary_option(struct bfs_parser *parser) { return parse_option(parser, 1);}
/** * Parse an option that takes a value. */static struct bfs_expr *parse_unary_option(struct bfs_parser *parser) { const char *arg = parser->argv[0]; const char *value = parser->argv[1]; if (!value) { parse_error(parser, "${blu}%s${rs} needs a value.\n", arg); return NULL; }
return parse_option(parser, 2);}
/** * Parse a single test. */static struct bfs_expr *parse_test(struct bfs_parser *parser, bfs_eval_fn *eval_fn, size_t argc) { char **argv = parser_advance(parser, BFS_TEST, argc); return parse_new_expr(parser, eval_fn, argc, argv, BFS_TEST);}
/** * Parse a test that doesn't take a value. */static struct bfs_expr *parse_nullary_test(struct bfs_parser *parser, bfs_eval_fn *eval_fn) { return parse_test(parser, eval_fn, 1);}
/** * Parse a test that takes a value. */static struct bfs_expr *parse_unary_test(struct bfs_parser *parser, bfs_eval_fn *eval_fn) { const char *arg = parser->argv[0]; const char *value = parser->argv[1]; if (!value) { parse_error(parser, "${blu}%s${rs} needs a value.\n", arg); return NULL; }
return parse_test(parser, eval_fn, 2);}
/** * Parse a single action. */static struct bfs_expr *parse_action(struct bfs_parser *parser, bfs_eval_fn *eval_fn, size_t argc) { char **argv = parser_advance(parser, BFS_ACTION, argc);
if (parser->excluding) { parse_argv_error(parser, argv, argc, "This action is not supported within ${red}-exclude${rs}.\n"); return NULL; }
if (eval_fn != eval_limit && eval_fn != eval_prune && eval_fn != eval_quit) { parser->implicit_print = false; }
return parse_new_expr(parser, eval_fn, argc, argv, BFS_ACTION);}
/** * Parse an action that takes no arguments. */static struct bfs_expr *parse_nullary_action(struct bfs_parser *parser, bfs_eval_fn *eval_fn) { return parse_action(parser, eval_fn, 1);}
/** * Parse an action that takes one argument. */static struct bfs_expr *parse_unary_action(struct bfs_parser *parser, bfs_eval_fn *eval_fn) { const char *arg = parser->argv[0]; const char *value = parser->argv[1]; if (!value) { parse_error(parser, "${blu}%s${rs} needs a value.\n", arg); return NULL; }
return parse_action(parser, eval_fn, 2);}
/** * Parse a test expression with integer data and a comparison flag. */static struct bfs_expr *parse_test_icmp(struct bfs_parser *parser, bfs_eval_fn *eval_fn) { struct bfs_expr *expr = parse_unary_test(parser, eval_fn); if (!expr) { return NULL; }
if (!parse_icmp(parser, expr, 0)) { return NULL; }
return expr;}
/** * Print usage information for -D. */static void debug_help(CFILE *cfile) { cfprintf(cfile, "Supported debug flags:\n\n");
cfprintf(cfile, " ${bld}help${rs}: This message.\n"); cfprintf(cfile, " ${bld}cost${rs}: Show cost estimates.\n"); cfprintf(cfile, " ${bld}exec${rs}: Print executed command details.\n"); cfprintf(cfile, " ${bld}opt${rs}: Print optimization details.\n"); cfprintf(cfile, " ${bld}rates${rs}: Print predicate success rates.\n"); cfprintf(cfile, " ${bld}search${rs}: Trace the filesystem traversal.\n"); cfprintf(cfile, " ${bld}stat${rs}: Trace all stat() calls.\n"); cfprintf(cfile, " ${bld}tree${rs}: Print the parse tree.\n"); cfprintf(cfile, " ${bld}all${rs}: All debug flags at once.\n");}
/** Check if a substring matches a debug flag. */static bool parse_debug_flag(const char *flag, size_t len, const char *expected) { if (len == strlen(expected)) { return strncmp(flag, expected, len) == 0; } else { return false; }}
/** * Parse -D FLAG. */static struct bfs_expr *parse_debug(struct bfs_parser *parser, int arg1, int arg2) { struct bfs_ctx *ctx = parser->ctx;
const char *flags; struct bfs_expr *expr = parse_prefix_flag(parser, 'D', true, &flags); if (!expr) { cfprintf(ctx->cerr, "\n"); debug_help(ctx->cerr); return NULL; }
bool unrecognized = false;
for (const char *flag = flags, *next; flag; flag = next) { size_t len = strcspn(flag, ","); if (flag[len]) { next = flag + len + 1; } else { next = NULL; }
if (parse_debug_flag(flag, len, "help")) { debug_help(ctx->cout); parser->just_info = true; return NULL; } else if (parse_debug_flag(flag, len, "all")) { ctx->debug = DEBUG_ALL; continue; }
enum debug_flags i; for (i = 1; DEBUG_ALL & i; i <<= 1) { const char *name = debug_flag_name(i); if (parse_debug_flag(flag, len, name)) { break; } }
if (DEBUG_ALL & i) { ctx->debug |= i; } else { if (parse_expr_warning(parser, expr, "Unrecognized debug flag ${bld}")) { fwrite(flag, 1, len, stderr); cfprintf(ctx->cerr, "${rs}.\n\n"); unrecognized = true; } } }
if (unrecognized) { debug_help(ctx->cerr); cfprintf(ctx->cerr, "\n"); }
return expr;}
/** * Parse -On. */static struct bfs_expr *parse_optlevel(struct bfs_parser *parser, int arg1, int arg2) { const char *arg; struct bfs_expr *expr = parse_prefix_flag(parser, 'O', false, &arg); if (!expr) { return NULL; }
int *optlevel = &parser->ctx->optlevel;
if (strcmp(arg, "fast") == 0) { *optlevel = 4; } else if (!parse_int(parser, expr->argv, arg, optlevel, IF_INT | IF_UNSIGNED)) { return NULL; }
if (*optlevel > 4) { parse_expr_warning(parser, expr, "${cyn}-O${bld}%s${rs} is the same as ${cyn}-O${bld}4${rs}.\n\n", arg); }
return expr;}
/** * Parse -[PHL], -follow. */static struct bfs_expr *parse_follow(struct bfs_parser *parser, int flags, int option) { struct bfs_ctx *ctx = parser->ctx; ctx->flags &= ~(BFTW_FOLLOW_ROOTS | BFTW_FOLLOW_ALL); ctx->flags |= flags; if (option) { return parse_nullary_option(parser); } else { return parse_nullary_flag(parser); }}
/** * Parse -X. */static struct bfs_expr *parse_xargs_safe(struct bfs_parser *parser, int arg1, int arg2) { parser->ctx->xargs_safe = true; return parse_nullary_flag(parser);}
/** * Parse -executable, -readable, -writable */static struct bfs_expr *parse_access(struct bfs_parser *parser, int flag, int arg2) { struct bfs_expr *expr = parse_nullary_test(parser, eval_access); if (expr) { expr->num = flag; } return expr;}
/** * Parse -acl. */static struct bfs_expr *parse_acl(struct bfs_parser *parser, int flag, int arg2) {#if BFS_CAN_CHECK_ACL return parse_nullary_test(parser, eval_acl);#else parse_error(parser, "Missing platform support.\n"); return NULL;#endif}
/** * Parse -[aBcm]?newer. */static struct bfs_expr *parse_newer(struct bfs_parser *parser, int field, int arg2) { struct bfs_expr *expr = parse_unary_test(parser, eval_newer); if (!expr) { return NULL; }
struct bfs_stat sb; if (stat_arg(parser, &expr->argv[1], &sb) != 0) { return NULL; }
expr->reftime = sb.mtime; expr->stat_field = field; return expr;}
/** * Parse -[aBcm]min. */static struct bfs_expr *parse_min(struct bfs_parser *parser, int field, int arg2) { struct bfs_expr *expr = parse_test_icmp(parser, eval_time); if (!expr) { return NULL; }
expr->reftime = parser->now; expr->stat_field = field; expr->time_unit = BFS_MINUTES; return expr;}
/** * Parse -[aBcm]time. */static struct bfs_expr *parse_time(struct bfs_parser *parser, int field, int arg2) { struct bfs_expr *expr = parse_unary_test(parser, eval_time); if (!expr) { return NULL; }
expr->reftime = parser->now; expr->stat_field = field;
const char *tail = parse_icmp(parser, expr, IF_PARTIAL_OK); if (!tail) { return NULL; }
if (!*tail) { expr->time_unit = BFS_DAYS; return expr; }
unsigned long long time = expr->num; expr->num = 0;
while (true) { switch (*tail) { case 'w': time *= 7; [[fallthrough]]; case 'd': time *= 24; [[fallthrough]]; case 'h': time *= 60; [[fallthrough]]; case 'm': time *= 60; [[fallthrough]]; case 's': break; default: parse_expr_error(parser, expr, "Unknown time unit ${bld}%c${rs}.\n", *tail); return NULL; }
expr->num += time;
if (!*++tail) { break; }
tail = parse_int(parser, &expr->argv[1], tail, &time, IF_PARTIAL_OK | IF_LONG_LONG | IF_UNSIGNED); if (!tail) { return NULL; } if (!*tail) { parse_expr_error(parser, expr, "Missing time unit.\n"); return NULL; } }
expr->time_unit = BFS_SECONDS; return expr;}
/** * Parse -capable. */static struct bfs_expr *parse_capable(struct bfs_parser *parser, int flag, int arg2) {#if BFS_CAN_CHECK_CAPABILITIES return parse_nullary_test(parser, eval_capable);#else parse_error(parser, "Missing platform support.\n"); return NULL;#endif}
/** * Parse -(no)?color. */static struct bfs_expr *parse_color(struct bfs_parser *parser, int color, int arg2) { struct bfs_expr *expr = parse_nullary_option(parser); if (!expr) { return NULL; }
struct bfs_ctx *ctx = parser->ctx; struct colors *colors = ctx->colors;
if (color) { if (!colors) { parse_expr_error(parser, expr, "Error parsing $$LS_COLORS: %s.\n", xstrerror(ctx->colors_error)); return NULL; }
parser->use_color = COLOR_ALWAYS; ctx->cout->colors = colors; ctx->cerr->colors = colors; } else { parser->use_color = COLOR_NEVER; ctx->cout->colors = NULL; ctx->cerr->colors = NULL; }
return expr;}
/** * Common code for fnmatch() tests. */static struct bfs_expr *parse_fnmatch(const struct bfs_parser *parser, struct bfs_expr *expr, bool casefold) { if (!expr) { return NULL; }
expr->pattern = expr->argv[1];
if (casefold) {#ifdef FNM_CASEFOLD expr->fnm_flags = FNM_CASEFOLD;#else parse_expr_error(parser, expr, "Missing platform support.\n"); return NULL;#endif } else { expr->fnm_flags = 0; }
// POSIX says, about fnmatch(): // // If pattern ends with an unescaped <backslash>, fnmatch() shall // return a non-zero value (indicating either no match or an error). // // But not all implementations obey this, so check for it ourselves. size_t i, len = strlen(expr->pattern); for (i = 0; i < len; ++i) { if (expr->pattern[len - i - 1] != '\\') { break; } } if (i % 2 != 0) { parse_expr_warning(parser, expr, "Unescaped trailing backslash.\n\n"); expr->eval_fn = eval_false; return expr; }
// strcmp() can be much faster than fnmatch() since it doesn't have to // parse the pattern, so special-case patterns with no wildcards. // // https://pubs.opengroup.org/onlinepubs/9799919799/utilities/V3_chap02.html#tag_19_14_01 expr->literal = strcspn(expr->pattern, "?*\\[") == len;
return expr;}
/** * Parse -context. */static struct bfs_expr *parse_context(struct bfs_parser *parser, int flag, int arg2) {#if BFS_CAN_CHECK_CONTEXT struct bfs_expr *expr = parse_unary_test(parser, eval_context); return parse_fnmatch(parser, expr, false);#else parse_error(parser, "Missing platform support.\n"); return NULL;#endif}
/** * Parse -{false,true}. */static struct bfs_expr *parse_const(struct bfs_parser *parser, int value, int arg2) { return parse_nullary_test(parser, value ? eval_true : eval_false);}
/** * Parse -daystart. */static struct bfs_expr *parse_daystart(struct bfs_parser *parser, int arg1, int arg2) { struct tm tm; if (!localtime_r(&parser->now.tv_sec, &tm)) { parse_perror(parser, "localtime_r()"); return NULL; }
if (tm.tm_hour || tm.tm_min || tm.tm_sec || parser->now.tv_nsec) { ++tm.tm_mday; } tm.tm_hour = 0; tm.tm_min = 0; tm.tm_sec = 0;
time_t time; if (xmktime(&tm, &time) != 0) { parse_perror(parser, "xmktime()"); return NULL; }
parser->now.tv_sec = time; parser->now.tv_nsec = 0;
return parse_nullary_option(parser);}
/** * Parse -delete. */static struct bfs_expr *parse_delete(struct bfs_parser *parser, int arg1, int arg2) { struct bfs_expr *expr = parse_nullary_action(parser, eval_delete); if (!expr) { return NULL; }
struct bfs_ctx *ctx = parser->ctx; ctx->flags |= BFTW_POST_ORDER; ctx->dangerous = true;
parser->depth_expr = expr; return expr;}
/** * Parse -d. */static struct bfs_expr *parse_depth(struct bfs_parser *parser, int flag, int arg2) { struct bfs_expr *expr = flag ? parse_nullary_flag(parser) : parse_nullary_option(parser); if (!expr) { return NULL; }
parser->ctx->flags |= BFTW_POST_ORDER; parser->depth_expr = expr; return expr;}
/** * Parse -depth [N]. */static struct bfs_expr *parse_depth_n(struct bfs_parser *parser, int arg1, int arg2) { const char *arg = parser->argv[1]; if (arg && looks_like_icmp(arg)) { return parse_test_icmp(parser, eval_depth); } else { return parse_depth(parser, arg1, arg2); }}
/** * Parse -{min,max}depth N. */static struct bfs_expr *parse_depth_limit(struct bfs_parser *parser, int is_min, int arg2) { struct bfs_expr *expr = parse_unary_option(parser); if (!expr) { return NULL; }
struct bfs_ctx *ctx = parser->ctx; int *depth = is_min ? &ctx->mindepth : &ctx->maxdepth; char **arg = &expr->argv[1]; if (!parse_int(parser, arg, *arg, depth, IF_INT | IF_UNSIGNED)) { return NULL; }
return expr;}
/** * Parse -empty. */static struct bfs_expr *parse_empty(struct bfs_parser *parser, int arg1, int arg2) { struct bfs_expr *expr = parse_nullary_test(parser, eval_empty); if (expr) { // For opendir() expr->ephemeral_fds = 1; } return expr;}
/** Check for unsafe relative paths in $PATH. */static const char *unsafe_path(const struct bfs_exec *execbuf) { if (!(execbuf->flags & BFS_EXEC_CHDIR)) { // Not -execdir or -okdir return NULL; }
const char *exe = execbuf->tmpl_argv[0]; if (strchr(exe, '/')) { // No $PATH lookups for /foo or foo/bar return NULL; }
if (strstr(exe, "{}")) { // Substituted paths always contain a / return NULL; }
const char *path = getenv("PATH"); while (path) { if (path[0] != '/') { // Relative $PATH component! return path; }
path = strchr(path, ':'); if (path) { ++path; } }
// No relative components in $PATH return NULL;}
/** * Parse -exec(dir)?/-ok(dir)?. */static struct bfs_expr *parse_exec(struct bfs_parser *parser, int flags, int arg2) { struct bfs_ctx *ctx = parser->ctx;
struct bfs_exec *execbuf = bfs_exec_parse(ctx, parser->argv, flags); if (!execbuf) { return NULL; }
struct bfs_expr *expr = parse_action(parser, eval_exec, execbuf->tmpl_argc + 2); if (!expr) { bfs_exec_free(execbuf); return NULL; }
expr->exec = execbuf;
// For pipe() in bfs_spawn() expr->ephemeral_fds = 2;
const char *unsafe = unsafe_path(execbuf); if (unsafe) { size_t len = strcspn(unsafe, ":"); char *comp = strndup(unsafe, len); if (comp) { parse_expr_error(parser, expr, "This action would be unsafe, since ${bld}$$PATH${rs} contains the relative path ${bld}%pq${rs}\n", comp); free(comp); } else { parse_perror(parser, "strndup()"); } return NULL; }
if (execbuf->flags & BFS_EXEC_CHDIR) { // To dup() the parent directory if (execbuf->flags & BFS_EXEC_MULTI) { ++expr->persistent_fds; } else { ++expr->ephemeral_fds; } }
if (execbuf->flags & BFS_EXEC_CONFIRM) { if (!consume_stdin(parser, expr)) { return NULL; } } else { ctx->dangerous = true; }
return expr;}
/** * Parse -exit [STATUS]. */static struct bfs_expr *parse_exit(struct bfs_parser *parser, int arg1, int arg2) { size_t argc = 1; const char *value = parser->argv[1];
int status = EXIT_SUCCESS; if (value && parse_int(parser, NULL, value, &status, IF_INT | IF_UNSIGNED | IF_QUIET)) { argc = 2; }
struct bfs_expr *expr = parse_action(parser, eval_exit, argc); if (expr) { expr->num = status; } return expr;}
/** * Parse -f PATH. */static struct bfs_expr *parse_f(struct bfs_parser *parser, int arg1, int arg2) { struct bfs_ctx *ctx = parser->ctx;
struct bfs_expr *expr = parse_unary_flag(parser); if (!expr) { return NULL; }
// Mark the path as a path, not a regular argument size_t i = expr->argv - ctx->argv; ctx->kinds[i + 1] = BFS_PATH;
if (parse_root(parser, expr->argv[1]) != 0) { return NULL; }
return expr;}
/** * Parse -files0-from PATH. */static struct bfs_expr *parse_files0_from(struct bfs_parser *parser, int arg1, int arg2) { struct bfs_expr *expr = parse_unary_option(parser); if (!expr) { return NULL; }
// For compatibility with GNU find, // // bfs -files0-from a -files0-from b // // should *only* use b, not a. So stash the expression here and only // process the last one at the end of parsing. parser->files0_expr = expr; return expr;}
/** * Parse -flags FLAGS. */static struct bfs_expr *parse_flags(struct bfs_parser *parser, int arg1, int arg2) { struct bfs_expr *expr = parse_unary_test(parser, eval_flags); if (!expr) { return NULL; }
const char *flags = expr->argv[1]; switch (flags[0]) { case '-': expr->flags_cmp = BFS_MODE_ALL; ++flags; break; case '+': expr->flags_cmp = BFS_MODE_ANY; ++flags; break; default: expr->flags_cmp = BFS_MODE_EQUAL; break; }
if (xstrtofflags(&flags, &expr->set_flags, &expr->clear_flags) != 0) { if (errno == ENOTSUP) { parse_expr_error(parser, expr, "Missing platform support.\n"); } else { parse_expr_error(parser, expr, "Invalid flags.\n"); } return NULL; }
return expr;}
/** * Parse -fls FILE. */static struct bfs_expr *parse_fls(struct bfs_parser *parser, int arg1, int arg2) { struct bfs_expr *expr = parse_unary_action(parser, eval_fls); if (!expr) { return NULL; }
if (expr_open(parser, expr, expr->argv[1]) != 0) { return NULL; }
return expr;}
/** * Parse -fprint FILE. */static struct bfs_expr *parse_fprint(struct bfs_parser *parser, int arg1, int arg2) { struct bfs_expr *expr = parse_unary_action(parser, eval_fprint); if (!expr) { return NULL; }
if (expr_open(parser, expr, expr->argv[1]) != 0) { return NULL; }
return expr;}
/** * Parse -fprint0 FILE. */static struct bfs_expr *parse_fprint0(struct bfs_parser *parser, int arg1, int arg2) { struct bfs_expr *expr = parse_unary_action(parser, eval_fprint0); if (!expr) { return NULL; }
if (expr_open(parser, expr, expr->argv[1]) != 0) { return NULL; }
return expr;}
/** * Parse -fprintf FILE FORMAT. */static struct bfs_expr *parse_fprintf(struct bfs_parser *parser, int arg1, int arg2) { const char *arg = parser->argv[0];
const char *file = parser->argv[1]; if (!file) { parse_error(parser, "${blu}%s${rs} needs a file.\n", arg); return NULL; }
const char *format = parser->argv[2]; if (!format) { parse_error(parser, "${blu}%s${rs} needs a format string.\n", arg); return NULL; }
struct bfs_expr *expr = parse_action(parser, eval_fprintf, 3); if (!expr) { return NULL; }
if (expr_open(parser, expr, file) != 0) { return NULL; }
if (bfs_printf_parse(parser->ctx, expr, format) != 0) { return NULL; }
return expr;}
/** * Parse -fstype TYPE. */static struct bfs_expr *parse_fstype(struct bfs_parser *parser, int arg1, int arg2) { struct bfs_expr *expr = parse_unary_test(parser, eval_fstype); if (!expr) { return NULL; }
if (!bfs_ctx_mtab(parser->ctx)) { parse_expr_error(parser, expr, "Couldn't parse the mount table: %s.\n", errstr()); return NULL; }
return expr;}
/** * Parse -gid/-group. */static struct bfs_expr *parse_group(struct bfs_parser *parser, int arg1, int arg2) { struct bfs_expr *expr = parse_unary_test(parser, eval_gid); if (!expr) { return NULL; }
const struct group *grp = bfs_getgrnam(parser->ctx->groups, expr->argv[1]); if (grp) { expr->num = grp->gr_gid; expr->int_cmp = BFS_INT_EQUAL; } else if (looks_like_icmp(expr->argv[1])) { if (!parse_icmp(parser, expr, 0)) { return NULL; } } else if (errno) { parse_expr_error(parser, expr, "%s.\n", errstr()); return NULL; } else { parse_expr_error(parser, expr, "No such group.\n"); return NULL; }
return expr;}
/** * Parse -unique. */static struct bfs_expr *parse_unique(struct bfs_parser *parser, int arg1, int arg2) { parser->ctx->unique = true; return parse_nullary_option(parser);}
/** * Parse -used N. */static struct bfs_expr *parse_used(struct bfs_parser *parser, int arg1, int arg2) { return parse_test_icmp(parser, eval_used);}
/** * Parse -uid/-user. */static struct bfs_expr *parse_user(struct bfs_parser *parser, int arg1, int arg2) { struct bfs_expr *expr = parse_unary_test(parser, eval_uid); if (!expr) { return NULL; }
const struct passwd *pwd = bfs_getpwnam(parser->ctx->users, expr->argv[1]); if (pwd) { expr->num = pwd->pw_uid; expr->int_cmp = BFS_INT_EQUAL; } else if (looks_like_icmp(expr->argv[1])) { if (!parse_icmp(parser, expr, 0)) { return NULL; } } else if (errno) { parse_expr_error(parser, expr, "%s.\n", errstr()); return NULL; } else { parse_expr_error(parser, expr, "No such user.\n"); return NULL; }
return expr;}
/** * Parse -hidden. */static struct bfs_expr *parse_hidden(struct bfs_parser *parser, int arg1, int arg2) { return parse_nullary_test(parser, eval_hidden);}
/** * Parse -(no)?ignore_readdir_race. */static struct bfs_expr *parse_ignore_races(struct bfs_parser *parser, int ignore, int arg2) { parser->ctx->ignore_races = ignore; return parse_nullary_option(parser);}
/** * Parse -inum N. */static struct bfs_expr *parse_inum(struct bfs_parser *parser, int arg1, int arg2) { return parse_test_icmp(parser, eval_inum);}
/** * Parse -j<n>. */static struct bfs_expr *parse_jobs(struct bfs_parser *parser, int arg1, int arg2) { const char *arg; struct bfs_expr *expr = parse_prefix_flag(parser, 'j', false, &arg); if (!expr) { return NULL; }
unsigned int n; if (!parse_int(parser, expr->argv, arg, &n, IF_INT | IF_UNSIGNED)) { return NULL; }
if (n == 0) { parse_expr_error(parser, expr, "${bld}0${rs} is not enough threads.\n"); return NULL; }
parser->ctx->threads = n; return expr;}
/** * Parse -limit N. */static struct bfs_expr *parse_limit(struct bfs_parser *parser, int arg1, int arg2) { struct bfs_expr *expr = parse_unary_action(parser, eval_limit); if (!expr) { return NULL; }
char **arg = &expr->argv[1]; if (!parse_int(parser, arg, *arg, &expr->num, IF_LONG_LONG)) { return NULL; }
if (expr->num <= 0) { parse_expr_error(parser, expr, "The %pX must be at least ${bld}1${rs}.\n", expr); return NULL; }
parser->limit_expr = expr; return expr;}
/** * Parse -links N. */static struct bfs_expr *parse_links(struct bfs_parser *parser, int arg1, int arg2) { return parse_test_icmp(parser, eval_links);}
/** * Parse -ls. */static struct bfs_expr *parse_ls(struct bfs_parser *parser, int arg1, int arg2) { struct bfs_expr *expr = parse_nullary_action(parser, eval_fls); if (!expr) { return NULL; }
init_print_expr(parser, expr); return expr;}
/** * Parse -mount. */static struct bfs_expr *parse_mount(struct bfs_parser *parser, int arg1, int arg2) { struct bfs_expr *expr = parse_nullary_option(parser); if (!expr) { return NULL; }
parser->ctx->flags |= BFTW_SKIP_MOUNTS; parser->mount_expr = expr; return expr;}
/** * Parse -i?name. */static struct bfs_expr *parse_name(struct bfs_parser *parser, int casefold, int arg2) { struct bfs_expr *expr = parse_unary_test(parser, eval_name); return parse_fnmatch(parser, expr, casefold);}
/** * Parse -i?path, -i?wholename. */static struct bfs_expr *parse_path(struct bfs_parser *parser, int casefold, int arg2) { struct bfs_expr *expr = parse_unary_test(parser, eval_path); return parse_fnmatch(parser, expr, casefold);}
/** * Parse -i?lname. */static struct bfs_expr *parse_lname(struct bfs_parser *parser, int casefold, int arg2) { struct bfs_expr *expr = parse_unary_test(parser, eval_lname); return parse_fnmatch(parser, expr, casefold);}
/** Get the bfs_stat_field for X/Y in -newerXY. */static enum bfs_stat_field parse_newerxy_field(char c) { switch (c) { case 'a': return BFS_STAT_ATIME; case 'B': return BFS_STAT_BTIME; case 'c': return BFS_STAT_CTIME; case 'm': return BFS_STAT_MTIME; default: return 0; }}
/** Parse an explicit reference timestamp for -newerXt and -*since. */static int parse_reftime(const struct bfs_parser *parser, struct bfs_expr *expr) { if (xgetdate(expr->argv[1], &expr->reftime) == 0) { return 0; } else if (errno != EINVAL) { parse_expr_error(parser, expr, "%s.\n", errstr()); return -1; }
parse_expr_error(parser, expr, "Invalid timestamp.\n\n"); fprintf(stderr, "Supported timestamp formats are ISO 8601-like, e.g.\n\n");
struct tm tm; if (!localtime_r(&parser->now.tv_sec, &tm)) { parse_perror(parser, "localtime_r()"); return -1; }
int year = tm.tm_year + 1900; int month = tm.tm_mon + 1; fprintf(stderr, " - %04d-%02d-%02d\n", year, month, tm.tm_mday); fprintf(stderr, " - %04d-%02d-%02dT%02d:%02d:%02d\n", year, month, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec);
#if BFS_HAS_TM_GMTOFF int gmtoff = tm.tm_gmtoff;#else int gmtoff = -timezone;#endif int tz_hour = gmtoff / 3600; int tz_min = (labs(gmtoff) / 60) % 60; fprintf(stderr, " - %04d-%02d-%02dT%02d:%02d:%02d%+03d:%02d\n", year, month, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec, tz_hour, tz_min);
if (!gmtime_r(&parser->now.tv_sec, &tm)) { parse_perror(parser, "gmtime_r()"); return -1; }
year = tm.tm_year + 1900; month = tm.tm_mon + 1; fprintf(stderr, " - %04d-%02d-%02dT%02d:%02d:%02dZ\n", year, month, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec);
return -1;}
/** * Parse -newerXY. */static struct bfs_expr *parse_newerxy(struct bfs_parser *parser, int arg1, int arg2) { const char *arg = parser->argv[0]; if (strlen(arg) != 8) { parse_error(parser, "Expected ${blu}-newer${bld}XY${rs}; found ${blu}-newer${bld}%pq${rs}.\n", arg + 6); return NULL; }
struct bfs_expr *expr = parse_unary_test(parser, eval_newer); if (!expr) { return NULL; }
expr->stat_field = parse_newerxy_field(arg[6]); if (!expr->stat_field) { parse_expr_error(parser, expr, "For ${blu}-newer${bld}XY${rs}, ${bld}X${rs} should be ${bld}a${rs}, ${bld}c${rs}, ${bld}m${rs}, or ${bld}B${rs}, not ${err}%c${rs}.\n", arg[6]); return NULL; }
if (arg[7] == 't') { if (parse_reftime(parser, expr) != 0) { return NULL; } } else { enum bfs_stat_field field = parse_newerxy_field(arg[7]); if (!field) { parse_expr_error(parser, expr, "For ${blu}-newer${bld}XY${rs}, ${bld}Y${rs} should be ${bld}a${rs}, ${bld}c${rs}, ${bld}m${rs}, ${bld}B${rs}, or ${bld}t${rs}, not ${err}%c${rs}.\n", arg[7]); return NULL; }
struct bfs_stat sb; if (stat_arg(parser, &expr->argv[1], &sb) != 0) { return NULL; }
const struct timespec *reftime = bfs_stat_time(&sb, field); if (!reftime) { parse_expr_error(parser, expr, "Couldn't get file %s.\n", bfs_stat_field_name(field)); return NULL; }
expr->reftime = *reftime; }
return expr;}
/** * Parse -noerror. */static struct bfs_expr *parse_noerror(struct bfs_parser *parser, int arg1, int arg2) { parser->ctx->ignore_errors = true; return parse_nullary_option(parser);}
/** * Parse -nogroup. */static struct bfs_expr *parse_nogroup(struct bfs_parser *parser, int arg1, int arg2) { struct bfs_expr *expr = parse_nullary_test(parser, eval_nogroup); if (expr) { // Who knows how many FDs getgrgid_r() needs? expr->ephemeral_fds = 3; } return expr;}
/** * Parse -nohidden. */static struct bfs_expr *parse_nohidden(struct bfs_parser *parser, int arg1, int arg2) { struct bfs_expr *hidden = parse_new_expr(parser, eval_hidden, 1, &fake_hidden_arg, BFS_TEST); if (!hidden) { return NULL; }
bfs_expr_append(parser->ctx->exclude, hidden); return parse_nullary_option(parser);}
/** * Parse -noleaf. */static struct bfs_expr *parse_noleaf(struct bfs_parser *parser, int arg1, int arg2) { struct bfs_expr *expr = parse_nullary_option(parser); if (!expr) { return NULL; }
parse_expr_warning(parser, expr, "${ex}%s${rs} does not apply the optimization that %px inhibits.\n\n", BFS_COMMAND, expr); return expr;}
/** * Parse -nouser. */static struct bfs_expr *parse_nouser(struct bfs_parser *parser, int arg1, int arg2) { struct bfs_expr *expr = parse_nullary_test(parser, eval_nouser); if (expr) { // Who knows how many FDs getpwuid_r() needs? expr->ephemeral_fds = 3; } return expr;}
/** * Parse a permission mode like chmod(1). */static int parse_mode(const struct bfs_parser *parser, const char *mode, struct bfs_expr *expr) { if (mode[0] >= '0' && mode[0] <= '9') { unsigned int parsed; if (!parse_int(parser, NULL, mode, &parsed, 8 | IF_INT | IF_UNSIGNED | IF_QUIET)) { goto fail; } if (parsed > 07777) { goto fail; }
expr->file_mode = parsed; expr->dir_mode = parsed; return 0; }
mode_t umask = parser->ctx->umask;
expr->file_mode = 0; expr->dir_mode = 0;
// Parse the same grammar as chmod(1), which looks like this: // // MODE : CLAUSE ["," CLAUSE]* // // CLAUSE : WHO* ACTION+ // // WHO : "u" | "g" | "o" | "a" // // ACTION : OP PERM* // | OP PERMCOPY // // OP : "+" | "-" | "=" // // PERM : "r" | "w" | "x" | "X" | "s" | "t" // // PERMCOPY : "u" | "g" | "o"
// State machine state enum { MODE_CLAUSE, MODE_WHO, MODE_ACTION, MODE_ACTION_APPLY, MODE_OP, MODE_PERM, } state = MODE_CLAUSE;
enum { MODE_PLUS, MODE_MINUS, MODE_EQUALS, } op uninit(MODE_EQUALS);
mode_t who uninit(0); mode_t mask uninit(0); mode_t file_change uninit(0); mode_t dir_change uninit(0);
const char *i = mode; while (true) { switch (state) { case MODE_CLAUSE: who = 0; mask = 0777; state = MODE_WHO; [[fallthrough]];
case MODE_WHO: switch (*i) { case 'u': who |= 0700; break; case 'g': who |= 0070; break; case 'o': who |= 0007; break; case 'a': who |= 0777; break; default: state = MODE_ACTION; continue; } break;
case MODE_ACTION_APPLY: switch (op) { case MODE_EQUALS: expr->file_mode &= ~who; expr->dir_mode &= ~who; [[fallthrough]]; case MODE_PLUS: expr->file_mode |= file_change; expr->dir_mode |= dir_change; break; case MODE_MINUS: expr->file_mode &= ~file_change; expr->dir_mode &= ~dir_change; break; } [[fallthrough]];
case MODE_ACTION: if (who == 0) { who = 0777; mask = who & ~umask; } else { mask = who; }
switch (*i) { case '+': op = MODE_PLUS; state = MODE_OP; break; case '-': op = MODE_MINUS; state = MODE_OP; break; case '=': op = MODE_EQUALS; state = MODE_OP; break;
case ',': if (state == MODE_ACTION_APPLY) { state = MODE_CLAUSE; } else { goto fail; } break;
case '\0': if (state == MODE_ACTION_APPLY) { goto done; } else { goto fail; }
default: goto fail; } break;
case MODE_OP: switch (*i) { case 'u': file_change = (expr->file_mode >> 6) & 07; dir_change = (expr->dir_mode >> 6) & 07; break; case 'g': file_change = (expr->file_mode >> 3) & 07; dir_change = (expr->dir_mode >> 3) & 07; break; case 'o': file_change = expr->file_mode & 07; dir_change = expr->dir_mode & 07; break;
default: file_change = 0; dir_change = 0; state = MODE_PERM; continue; }
file_change |= (file_change << 6) | (file_change << 3); file_change &= mask; dir_change |= (dir_change << 6) | (dir_change << 3); dir_change &= mask; state = MODE_ACTION_APPLY; break;
case MODE_PERM: switch (*i) { case 'r': file_change |= mask & 0444; dir_change |= mask & 0444; break; case 'w': file_change |= mask & 0222; dir_change |= mask & 0222; break; case 'x': file_change |= mask & 0111; [[fallthrough]]; case 'X': dir_change |= mask & 0111; break; case 's': if (who & 0700) { file_change |= S_ISUID; dir_change |= S_ISUID; } if (who & 0070) { file_change |= S_ISGID; dir_change |= S_ISGID; } break; case 't': if (who & 0007) { file_change |= S_ISVTX; dir_change |= S_ISVTX; } break; default: state = MODE_ACTION_APPLY; continue; } break; }
++i; }
done: return 0;
fail: parse_expr_error(parser, expr, "Invalid mode.\n"); return -1;}
/** * Parse -perm MODE. */static struct bfs_expr *parse_perm(struct bfs_parser *parser, int field, int arg2) { struct bfs_expr *expr = parse_unary_test(parser, eval_perm); if (!expr) { return NULL; }
const char *mode = expr->argv[1]; switch (mode[0]) { case '-': expr->mode_cmp = BFS_MODE_ALL; ++mode; break; case '/': expr->mode_cmp = BFS_MODE_ANY; ++mode; break; case '+': if (mode[1] >= '0' && mode[1] <= '9') { expr->mode_cmp = BFS_MODE_ANY; ++mode; break; } [[fallthrough]]; default: expr->mode_cmp = BFS_MODE_EQUAL; break; }
if (parse_mode(parser, mode, expr) != 0) { return NULL; }
return expr;}
/** * Parse -print. */static struct bfs_expr *parse_print(struct bfs_parser *parser, int arg1, int arg2) { struct bfs_expr *expr = parse_nullary_action(parser, eval_fprint); if (expr) { init_print_expr(parser, expr); } return expr;}
/** * Parse -print0. */static struct bfs_expr *parse_print0(struct bfs_parser *parser, int arg1, int arg2) { struct bfs_expr *expr = parse_nullary_action(parser, eval_fprint0); if (expr) { init_print_expr(parser, expr); } return expr;}
/** * Parse -printf FORMAT. */static struct bfs_expr *parse_printf(struct bfs_parser *parser, int arg1, int arg2) { struct bfs_expr *expr = parse_unary_action(parser, eval_fprintf); if (!expr) { return NULL; }
init_print_expr(parser, expr);
if (bfs_printf_parse(parser->ctx, expr, expr->argv[1]) != 0) { return NULL; }
return expr;}
/** * Parse -printx. */static struct bfs_expr *parse_printx(struct bfs_parser *parser, int arg1, int arg2) { struct bfs_expr *expr = parse_nullary_action(parser, eval_fprintx); if (expr) { init_print_expr(parser, expr); } return expr;}
/** * Parse -prune. */static struct bfs_expr *parse_prune(struct bfs_parser *parser, int arg1, int arg2) { struct bfs_expr *expr = parse_nullary_action(parser, eval_prune); if (!expr) { return NULL; }
parser->prune_expr = expr; return expr;}
/** * Parse -quit. */static struct bfs_expr *parse_quit(struct bfs_parser *parser, int arg1, int arg2) { return parse_nullary_action(parser, eval_quit);}
/** * Parse -i?regex. */static struct bfs_expr *parse_regex(struct bfs_parser *parser, int flags, int arg2) { struct bfs_expr *expr = parse_unary_test(parser, eval_regex); if (!expr) { return NULL; }
if (bfs_regcomp(&expr->regex, expr->argv[1], parser->regex_type, flags) != 0) { if (expr->regex) { char *str = bfs_regerror(expr->regex); if (str) { parse_expr_error(parser, expr, "%s.\n", str); free(str); } else { parse_perror(parser, "bfs_regerror()"); } } else { parse_perror(parser, "bfs_regcomp()"); }
return NULL; }
return expr;}
/** * Parse -E. */static struct bfs_expr *parse_regex_extended(struct bfs_parser *parser, int arg1, int arg2) { parser->regex_type = BFS_REGEX_POSIX_EXTENDED; return parse_nullary_flag(parser);}
/** * Parse -regextype TYPE. */static struct bfs_expr *parse_regextype(struct bfs_parser *parser, int arg1, int arg2) { struct bfs_ctx *ctx = parser->ctx; CFILE *cfile = ctx->cerr;
struct bfs_expr *expr = parse_unary_option(parser); if (!expr) { cfprintf(cfile, "\n"); goto list_types; }
// See https://www.gnu.org/software/gnulib/manual/html_node/Predefined-Syntaxes.html const char *type = expr->argv[1]; if (strcmp(type, "posix-basic") == 0 || strcmp(type, "posix-minimal-basic") == 0 || strcmp(type, "ed") == 0 || strcmp(type, "sed") == 0) { parser->regex_type = BFS_REGEX_POSIX_BASIC; } else if (strcmp(type, "posix-extended") == 0) { parser->regex_type = BFS_REGEX_POSIX_EXTENDED;#if BFS_WITH_ONIGURUMA } else if (strcmp(type, "awk") == 0 || strcmp(type, "posix-awk") == 0) { parser->regex_type = BFS_REGEX_AWK; } else if (strcmp(type, "gnu-awk") == 0) { parser->regex_type = BFS_REGEX_GNU_AWK; } else if (strcmp(type, "emacs") == 0) { parser->regex_type = BFS_REGEX_EMACS; } else if (strcmp(type, "grep") == 0) { parser->regex_type = BFS_REGEX_GREP; } else if (strcmp(type, "egrep") == 0 || strcmp(type, "posix-egrep") == 0) { parser->regex_type = BFS_REGEX_EGREP; } else if (strcmp(type, "findutils-default") == 0) { parser->regex_type = BFS_REGEX_GNU_FIND;#endif } else if (strcmp(type, "help") == 0) { parser->just_info = true; cfile = ctx->cout; goto list_types; } else { parse_expr_error(parser, expr, "Unsupported regex type.\n\n"); goto list_types; }
return expr;
list_types: cfprintf(cfile, "Supported types are:\n\n"); cfprintf(cfile, " ${bld}posix-basic${rs}: POSIX basic regular expressions (BRE)\n"); cfprintf(cfile, " ${bld}ed${rs}: Like ${grn}ed${rs} (same as ${bld}posix-basic${rs})\n"); cfprintf(cfile, " ${bld}sed${rs}: Like ${grn}sed${rs} (same as ${bld}posix-basic${rs})\n\n");
cfprintf(cfile, " ${bld}posix-extended${rs}: POSIX extended regular expressions (ERE)\n\n");
#if BFS_WITH_ONIGURUMA cfprintf(cfile, " [${bld}posix-${rs}]${bld}awk${rs}: Like ${grn}awk${rs}\n"); cfprintf(cfile, " ${bld}gnu-awk${rs}: Like GNU ${grn}awk${rs}\n\n");
cfprintf(cfile, " ${bld}emacs${rs}: Like ${grn}emacs${rs}\n\n");
cfprintf(cfile, " ${bld}grep${rs}: Like ${grn}grep${rs}\n"); cfprintf(cfile, " [${bld}posix-${rs}]${bld}egrep${rs}: Like ${grn}grep${rs} ${cyn}-E${rs}\n\n");
cfprintf(cfile, " ${bld}findutils-default${rs}: Like GNU ${grn}find${rs}\n");#endif return NULL;}
/** * Parse -s. */static struct bfs_expr *parse_s(struct bfs_parser *parser, int arg1, int arg2) { parser->ctx->flags |= BFTW_SORT; return parse_nullary_flag(parser);}
/** * Parse -samefile FILE. */static struct bfs_expr *parse_samefile(struct bfs_parser *parser, int arg1, int arg2) { struct bfs_expr *expr = parse_unary_test(parser, eval_samefile); if (!expr) { return NULL; }
struct bfs_stat sb; if (stat_arg(parser, &expr->argv[1], &sb) != 0) { return NULL; }
expr->dev = sb.dev; expr->ino = sb.ino; return expr;}
/** * Parse -S STRATEGY. */static struct bfs_expr *parse_search_strategy(struct bfs_parser *parser, int arg1, int arg2) { struct bfs_ctx *ctx = parser->ctx; CFILE *cfile = ctx->cerr;
const char *arg; struct bfs_expr *expr = parse_prefix_flag(parser, 'S', true, &arg); if (!expr) { cfprintf(cfile, "\n"); goto list_strategies; }
if (strcmp(arg, "bfs") == 0) { ctx->strategy = BFTW_BFS; } else if (strcmp(arg, "dfs") == 0) { ctx->strategy = BFTW_DFS; } else if (strcmp(arg, "ids") == 0) { ctx->strategy = BFTW_IDS; } else if (strcmp(arg, "eds") == 0) { ctx->strategy = BFTW_EDS; } else if (strcmp(arg, "help") == 0) { parser->just_info = true; cfile = ctx->cout; goto list_strategies; } else { parse_expr_error(parser, expr, "Unrecognized search strategy.\n\n"); goto list_strategies; }
return expr;
list_strategies: cfprintf(cfile, "Supported search strategies:\n\n"); cfprintf(cfile, " ${bld}bfs${rs}: breadth-first search\n"); cfprintf(cfile, " ${bld}dfs${rs}: depth-first search\n"); cfprintf(cfile, " ${bld}ids${rs}: iterative deepening search\n"); cfprintf(cfile, " ${bld}eds${rs}: exponential deepening search\n"); return NULL;}
/** * Parse -[aBcm]?since. */static struct bfs_expr *parse_since(struct bfs_parser *parser, int field, int arg2) { struct bfs_expr *expr = parse_unary_test(parser, eval_newer); if (!expr) { return NULL; }
if (parse_reftime(parser, expr) != 0) { return NULL; }
expr->stat_field = field; return expr;}
/** * Parse -size N[cwbkMGTP]?. */static struct bfs_expr *parse_size(struct bfs_parser *parser, int arg1, int arg2) { struct bfs_expr *expr = parse_unary_test(parser, eval_size); if (!expr) { return NULL; }
const char *unit = parse_icmp(parser, expr, IF_PARTIAL_OK); if (!unit) { return NULL; }
if (strlen(unit) > 1) { goto bad_unit; }
switch (*unit) { case '\0': case 'b': expr->size_unit = BFS_BLOCKS; break; case 'c': expr->size_unit = BFS_BYTES; break; case 'w': expr->size_unit = BFS_WORDS; break; case 'k': expr->size_unit = BFS_KB; break; case 'M': expr->size_unit = BFS_MB; break; case 'G': expr->size_unit = BFS_GB; break; case 'T': expr->size_unit = BFS_TB; break; case 'P': expr->size_unit = BFS_PB; break;
default: bad_unit: parse_expr_error(parser, expr, "Expected a size unit (one of ${bld}cwbkMGTP${rs}); found ${err}%pq${rs}.\n", unit); return NULL; }
return expr;}
/** * Parse -sparse. */static struct bfs_expr *parse_sparse(struct bfs_parser *parser, int arg1, int arg2) { return parse_nullary_test(parser, eval_sparse);}
/** * Parse -status. */static struct bfs_expr *parse_status(struct bfs_parser *parser, int arg1, int arg2) { parser->ctx->status = true; return parse_nullary_option(parser);}
/** * Parse -x?type [bcdpflswD]. */static struct bfs_expr *parse_type(struct bfs_parser *parser, int x, int arg2) { struct bfs_ctx *ctx = parser->ctx;
bfs_eval_fn *eval = x ? eval_xtype : eval_type; struct bfs_expr *expr = parse_unary_test(parser, eval); if (!expr) { return NULL; }
expr->num = 0;
const char *c = expr->argv[1]; while (true) { switch (*c) { case 'b': expr->num |= 1 << BFS_BLK; break; case 'c': expr->num |= 1 << BFS_CHR; break; case 'd': expr->num |= 1 << BFS_DIR; break; case 'D': expr->num |= 1 << BFS_DOOR; break; case 'p': expr->num |= 1 << BFS_FIFO; break; case 'f': expr->num |= 1 << BFS_REG; break; case 'l': expr->num |= 1 << BFS_LNK; break; case 's': expr->num |= 1 << BFS_SOCK; break; case 'w': expr->num |= 1 << BFS_WHT; ctx->flags |= BFTW_WHITEOUTS; break;
case '\0': parse_expr_error(parser, expr, "Expected a type flag.\n"); return NULL;
default: parse_expr_error(parser, expr, "Unknown type flag ${err}%c${rs}; expected one of [${bld}bcdpflswD${rs}].\n", *c); return NULL; }
++c; if (*c == '\0') { break; } else if (*c == ',') { ++c; continue; } else { parse_expr_error(parser, expr, "Types must be comma-separated.\n"); return NULL; } }
return expr;}
/** * Parse -(no)?warn. */static struct bfs_expr *parse_warn(struct bfs_parser *parser, int warn, int arg2) { parser->ctx->warn = warn; return parse_nullary_option(parser);}
/** * Parse -xattr. */static struct bfs_expr *parse_xattr(struct bfs_parser *parser, int arg1, int arg2) {#if BFS_CAN_CHECK_XATTRS return parse_nullary_test(parser, eval_xattr);#else parse_error(parser, "Missing platform support.\n"); return NULL;#endif}
/** * Parse -xattrname. */static struct bfs_expr *parse_xattrname(struct bfs_parser *parser, int arg1, int arg2) {#if BFS_CAN_CHECK_XATTRS return parse_unary_test(parser, eval_xattrname);#else parse_error(parser, "Missing platform support.\n"); return NULL;#endif}
/** * Parse -xdev. */static struct bfs_expr *parse_xdev(struct bfs_parser *parser, int arg1, int arg2) { struct bfs_expr *expr = parse_nullary_option(parser); if (!expr) { return NULL; }
parser->ctx->flags |= BFTW_PRUNE_MOUNTS; parser->xdev_expr = expr; return expr;}
/** * Launch a pager for the help output. */static CFILE *launch_pager(pid_t *pid, CFILE *cout) { char *pager = getenv("PAGER");
char *exe; if (pager && pager[0]) { exe = bfs_spawn_resolve(pager); } else { exe = bfs_spawn_resolve("less"); if (!exe) { exe = bfs_spawn_resolve("more"); } } if (!exe) { goto fail; }
int pipefd[2]; if (pipe(pipefd) != 0) { goto fail_exe; }
FILE *file = fdopen(pipefd[1], "w"); if (!file) { goto fail_pipe; } pipefd[1] = -1;
CFILE *ret = cfwrap(file, NULL, true); if (!ret) { goto fail_file; } file = NULL;
struct bfs_spawn ctx; if (bfs_spawn_init(&ctx) != 0) { goto fail_ret; }
if (bfs_spawn_addclose(&ctx, fileno(ret->file)) != 0) { goto fail_ctx; } if (bfs_spawn_adddup2(&ctx, pipefd[0], STDIN_FILENO) != 0) { goto fail_ctx; } if (bfs_spawn_addclose(&ctx, pipefd[0]) != 0) { goto fail_ctx; }
char *argv[] = { exe, NULL, NULL, };
const char *cmd = exe + xbaseoff(exe); if (strcmp(cmd, "less") == 0) { // We know less supports colors, other pagers may not ret->colors = cout->colors; argv[1] = "-FKRX"; }
*pid = bfs_spawn(exe, &ctx, argv, NULL); if (*pid < 0) { goto fail_ctx; }
xclose(pipefd[0]); bfs_spawn_destroy(&ctx); free(exe); return ret;
fail_ctx: bfs_spawn_destroy(&ctx);fail_ret: cfclose(ret);fail_file: if (file) { fclose(file); }fail_pipe: if (pipefd[1] >= 0) { xclose(pipefd[1]); } if (pipefd[0] >= 0) { xclose(pipefd[0]); }fail_exe: free(exe);fail: return cout;}
/** * "Parse" -help. */static struct bfs_expr *parse_help(struct bfs_parser *parser, int arg1, int arg2) { CFILE *cout = parser->ctx->cout;
pid_t pager = -1; if (parser->stdout_tty) { cout = launch_pager(&pager, cout); }
cfprintf(cout, "Usage: ${ex}%s${rs} [${cyn}flags${rs}...] [${mag}paths${rs}...] [${blu}expression${rs}...]\n\n", parser->command);
cfprintf(cout, "${ex}%s${rs} is compatible with ${ex}find${rs}, with some extensions. " "${cyn}Flags${rs} (${cyn}-H${rs}/${cyn}-L${rs}/${cyn}-P${rs} etc.), ${mag}paths${rs},\n" "and ${blu}expressions${rs} may be freely mixed in any order.\n\n", BFS_COMMAND);
cfprintf(cout, "${bld}Flags:${rs}\n\n");
cfprintf(cout, " ${cyn}-H${rs}\n"); cfprintf(cout, " Follow symbolic links on the command line, but not while searching\n"); cfprintf(cout, " ${cyn}-L${rs}\n"); cfprintf(cout, " Follow all symbolic links\n"); cfprintf(cout, " ${cyn}-P${rs}\n"); cfprintf(cout, " Never follow symbolic links (the default)\n");
cfprintf(cout, " ${cyn}-E${rs}\n"); cfprintf(cout, " Use extended regular expressions (same as ${blu}-regextype${rs} ${bld}posix-extended${rs})\n"); cfprintf(cout, " ${cyn}-X${rs}\n"); cfprintf(cout, " Filter out files with non-${ex}xargs${rs}-safe names\n"); cfprintf(cout, " ${cyn}-d${rs}\n"); cfprintf(cout, " Search in post-order (same as ${blu}-depth${rs})\n"); cfprintf(cout, " ${cyn}-s${rs}\n"); cfprintf(cout, " Visit directory entries in sorted order\n"); cfprintf(cout, " ${cyn}-x${rs}\n"); cfprintf(cout, " Don't descend into other mount points (same as ${blu}-xdev${rs})\n");
cfprintf(cout, " ${cyn}-f${rs} ${mag}PATH${rs}\n"); cfprintf(cout, " Treat ${mag}PATH${rs} as a path to search (useful if it begins with a dash)\n"); cfprintf(cout, " ${cyn}-D${rs} ${bld}FLAG${rs}\n"); cfprintf(cout, " Turn on a debugging flag (see ${cyn}-D${rs} ${bld}help${rs})\n"); cfprintf(cout, " ${cyn}-O${bld}N${rs}\n"); cfprintf(cout, " Enable optimization level ${bld}N${rs} (default: ${bld}3${rs})\n"); cfprintf(cout, " ${cyn}-S${rs} ${bld}bfs${rs}|${bld}dfs${rs}|${bld}ids${rs}|${bld}eds${rs}\n"); cfprintf(cout, " Use ${bld}b${rs}readth-${bld}f${rs}irst/${bld}d${rs}epth-${bld}f${rs}irst/${bld}i${rs}terative/${bld}e${rs}xponential ${bld}d${rs}eepening ${bld}s${rs}earch\n"); cfprintf(cout, " (default: ${cyn}-S${rs} ${bld}bfs${rs})\n"); cfprintf(cout, " ${cyn}-j${bld}N${rs}\n"); cfprintf(cout, " Search with ${bld}N${rs} threads in parallel (default: number of CPUs, up to ${bld}8${rs})\n\n");
cfprintf(cout, "${bld}Operators:${rs}\n\n");
cfprintf(cout, " ${red}(${rs} ${blu}expression${rs} ${red})${rs}\n\n");
cfprintf(cout, " ${red}!${rs} ${blu}expression${rs}\n"); cfprintf(cout, " ${red}-not${rs} ${blu}expression${rs}\n\n");
cfprintf(cout, " ${blu}expression${rs} ${blu}expression${rs}\n"); cfprintf(cout, " ${blu}expression${rs} ${red}-a${rs} ${blu}expression${rs}\n"); cfprintf(cout, " ${blu}expression${rs} ${red}-and${rs} ${blu}expression${rs}\n\n");
cfprintf(cout, " ${blu}expression${rs} ${red}-o${rs} ${blu}expression${rs}\n"); cfprintf(cout, " ${blu}expression${rs} ${red}-or${rs} ${blu}expression${rs}\n\n");
cfprintf(cout, " ${blu}expression${rs} ${red},${rs} ${blu}expression${rs}\n\n");
cfprintf(cout, "${bld}Special forms:${rs}\n\n");
cfprintf(cout, " ${red}-exclude${rs} ${blu}expression${rs}\n"); cfprintf(cout, " Exclude all paths matching the ${blu}expression${rs} from the search.\n\n");
cfprintf(cout, "${bld}Options:${rs}\n\n");
cfprintf(cout, " ${blu}-color${rs}\n"); cfprintf(cout, " ${blu}-nocolor${rs}\n"); cfprintf(cout, " Turn colors on or off (default: ${blu}-color${rs} if outputting to a terminal,\n"); cfprintf(cout, " ${blu}-nocolor${rs} otherwise)\n"); cfprintf(cout, " ${blu}-daystart${rs}\n"); cfprintf(cout, " Measure times relative to the start of today\n"); cfprintf(cout, " ${blu}-depth${rs}\n"); cfprintf(cout, " Search in post-order (descendents first)\n"); cfprintf(cout, " ${blu}-files0-from${rs} ${bld}FILE${rs}\n"); cfprintf(cout, " Search the NUL ('\\0')-separated paths from ${bld}FILE${rs} (${bld}-${rs} for standard input).\n"); cfprintf(cout, " ${blu}-follow${rs}\n"); cfprintf(cout, " Follow all symbolic links (same as ${cyn}-L${rs})\n"); cfprintf(cout, " ${blu}-ignore_readdir_race${rs}\n"); cfprintf(cout, " ${blu}-noignore_readdir_race${rs}\n"); cfprintf(cout, " Whether to report an error if ${ex}%s${rs} detects that the file tree is modified\n", BFS_COMMAND); cfprintf(cout, " during the search (default: ${blu}-noignore_readdir_race${rs})\n"); cfprintf(cout, " ${blu}-maxdepth${rs} ${bld}N${rs}\n"); cfprintf(cout, " ${blu}-mindepth${rs} ${bld}N${rs}\n"); cfprintf(cout, " Ignore files deeper/shallower than ${bld}N${rs}\n"); cfprintf(cout, " ${blu}-mount${rs}\n"); cfprintf(cout, " Exclude mount points entirely from the results\n"); cfprintf(cout, " ${blu}-noerror${rs}\n"); cfprintf(cout, " Ignore any errors that occur during traversal\n"); cfprintf(cout, " ${blu}-nohidden${rs}\n"); cfprintf(cout, " Exclude hidden files\n"); cfprintf(cout, " ${blu}-noleaf${rs}\n"); cfprintf(cout, " Ignored; for compatibility with GNU find\n"); cfprintf(cout, " ${blu}-regextype${rs} ${bld}TYPE${rs}\n"); cfprintf(cout, " Use ${bld}TYPE${rs}-flavored regexes (default: ${bld}posix-basic${rs}; see ${blu}-regextype${rs} ${bld}help${rs})\n"); cfprintf(cout, " ${blu}-status${rs}\n"); cfprintf(cout, " Display a status bar while searching\n"); cfprintf(cout, " ${blu}-unique${rs}\n"); cfprintf(cout, " Skip any files that have already been seen\n"); cfprintf(cout, " ${blu}-warn${rs}\n"); cfprintf(cout, " ${blu}-nowarn${rs}\n"); cfprintf(cout, " Turn on or off warnings about the command line\n"); cfprintf(cout, " ${blu}-xdev${rs}\n"); cfprintf(cout, " Don't descend into other mount points\n\n");
cfprintf(cout, "${bld}Tests:${rs}\n\n");
#if BFS_CAN_CHECK_ACL cfprintf(cout, " ${blu}-acl${rs}\n"); cfprintf(cout, " Find files with a non-trivial Access Control List\n");#endif cfprintf(cout, " ${blu}-${rs}[${blu}aBcm${rs}]${blu}min${rs} ${bld}[-+]N${rs}\n"); cfprintf(cout, " Find files ${blu}a${rs}ccessed/${blu}B${rs}irthed/${blu}c${rs}hanged/${blu}m${rs}odified ${bld}N${rs} minutes ago\n"); cfprintf(cout, " ${blu}-${rs}[${blu}aBcm${rs}]${blu}newer${rs} ${bld}FILE${rs}\n"); cfprintf(cout, " Find files ${blu}a${rs}ccessed/${blu}B${rs}irthed/${blu}c${rs}hanged/${blu}m${rs}odified more recently than ${bld}FILE${rs} was\n" " modified\n"); cfprintf(cout, " ${blu}-${rs}[${blu}aBcm${rs}]${blu}since${rs} ${bld}TIME${rs}\n"); cfprintf(cout, " Find files ${blu}a${rs}ccessed/${blu}B${rs}irthed/${blu}c${rs}hanged/${blu}m${rs}odified more recently than ${bld}TIME${rs}\n"); cfprintf(cout, " ${blu}-${rs}[${blu}aBcm${rs}]${blu}time${rs} ${bld}[-+]N${rs}\n"); cfprintf(cout, " Find files ${blu}a${rs}ccessed/${blu}B${rs}irthed/${blu}c${rs}hanged/${blu}m${rs}odified ${bld}N${rs} days ago\n");#if BFS_CAN_CHECK_CAPABILITIES cfprintf(cout, " ${blu}-capable${rs}\n"); cfprintf(cout, " Find files with POSIX.1e capabilities set\n");#endif#if BFS_CAN_CHECK_CONTEXT cfprintf(cout, " ${blu}-context${rs} ${bld}GLOB${rs}\n"); cfprintf(cout, " Find files with SELinux context matching a glob pattern\n");#endif cfprintf(cout, " ${blu}-depth${rs} ${bld}[-+]N${rs}\n"); cfprintf(cout, " Find files with depth ${bld}N${rs}\n"); cfprintf(cout, " ${blu}-empty${rs}\n"); cfprintf(cout, " Find empty files/directories\n"); cfprintf(cout, " ${blu}-executable${rs}\n"); cfprintf(cout, " ${blu}-readable${rs}\n"); cfprintf(cout, " ${blu}-writable${rs}\n"); cfprintf(cout, " Find files the current user can execute/read/write\n"); cfprintf(cout, " ${blu}-false${rs}\n"); cfprintf(cout, " ${blu}-true${rs}\n"); cfprintf(cout, " Always false/true\n"); cfprintf(cout, " ${blu}-fstype${rs} ${bld}TYPE${rs}\n"); cfprintf(cout, " Find files on file systems with the given ${bld}TYPE${rs}\n"); cfprintf(cout, " ${blu}-gid${rs} ${bld}[-+]N${rs}\n"); cfprintf(cout, " ${blu}-uid${rs} ${bld}[-+]N${rs}\n"); cfprintf(cout, " Find files owned by group/user ID ${bld}N${rs}\n"); cfprintf(cout, " ${blu}-group${rs} ${bld}NAME${rs}\n"); cfprintf(cout, " ${blu}-user${rs} ${bld}NAME${rs}\n"); cfprintf(cout, " Find files owned by the group/user ${bld}NAME${rs}\n"); cfprintf(cout, " ${blu}-hidden${rs}\n"); cfprintf(cout, " Find hidden files\n");#ifdef FNM_CASEFOLD cfprintf(cout, " ${blu}-ilname${rs} ${bld}GLOB${rs}\n"); cfprintf(cout, " ${blu}-iname${rs} ${bld}GLOB${rs}\n"); cfprintf(cout, " ${blu}-ipath${rs} ${bld}GLOB${rs}\n"); cfprintf(cout, " ${blu}-iregex${rs} ${bld}REGEX${rs}\n"); cfprintf(cout, " ${blu}-iwholename${rs} ${bld}GLOB${rs}\n"); cfprintf(cout, " Case-insensitive versions of ${blu}-lname${rs}/${blu}-name${rs}/${blu}-path${rs}" "/${blu}-regex${rs}/${blu}-wholename${rs}\n");#endif cfprintf(cout, " ${blu}-inum${rs} ${bld}[-+]N${rs}\n"); cfprintf(cout, " Find files with inode number ${bld}N${rs}\n"); cfprintf(cout, " ${blu}-links${rs} ${bld}[-+]N${rs}\n"); cfprintf(cout, " Find files with ${bld}N${rs} hard links\n"); cfprintf(cout, " ${blu}-lname${rs} ${bld}GLOB${rs}\n"); cfprintf(cout, " Find symbolic links whose target matches the ${bld}GLOB${rs}\n"); cfprintf(cout, " ${blu}-name${rs} ${bld}GLOB${rs}\n"); cfprintf(cout, " Find files whose name matches the ${bld}GLOB${rs}\n"); cfprintf(cout, " ${blu}-newer${rs} ${bld}FILE${rs}\n"); cfprintf(cout, " Find files newer than ${bld}FILE${rs}\n"); cfprintf(cout, " ${blu}-newer${bld}XY${rs} ${bld}REFERENCE${rs}\n"); cfprintf(cout, " Find files whose ${bld}X${rs} time is newer than the ${bld}Y${rs} time of" " ${bld}REFERENCE${rs}. ${bld}X${rs} and ${bld}Y${rs}\n"); cfprintf(cout, " can be any of [${bld}aBcm${rs}]. ${bld}Y${rs} may also be ${bld}t${rs} to parse ${bld}REFERENCE${rs} an explicit\n"); cfprintf(cout, " timestamp.\n"); cfprintf(cout, " ${blu}-nogroup${rs}\n"); cfprintf(cout, " ${blu}-nouser${rs}\n"); cfprintf(cout, " Find files owned by nonexistent groups/users\n"); cfprintf(cout, " ${blu}-path${rs} ${bld}GLOB${rs}\n"); cfprintf(cout, " ${blu}-wholename${rs} ${bld}GLOB${rs}\n"); cfprintf(cout, " Find files whose entire path matches the ${bld}GLOB${rs}\n"); cfprintf(cout, " ${blu}-perm${rs} ${bld}[-]MODE${rs}\n"); cfprintf(cout, " Find files with a matching mode\n"); cfprintf(cout, " ${blu}-regex${rs} ${bld}REGEX${rs}\n"); cfprintf(cout, " Find files whose entire path matches the regular expression ${bld}REGEX${rs}\n"); cfprintf(cout, " ${blu}-samefile${rs} ${bld}FILE${rs}\n"); cfprintf(cout, " Find hard links to ${bld}FILE${rs}\n"); cfprintf(cout, " ${blu}-since${rs} ${bld}TIME${rs}\n"); cfprintf(cout, " Find files modified since ${bld}TIME${rs}\n"); cfprintf(cout, " ${blu}-size${rs} ${bld}[-+]N[cwbkMGTP]${rs}\n"); cfprintf(cout, " Find files with the given size, in 1-byte ${bld}c${rs}haracters, 2-byte ${bld}w${rs}ords,\n"); cfprintf(cout, " 512-byte ${bld}b${rs}locks (default), or ${bld}k${rs}iB/${bld}M${rs}iB/${bld}G${rs}iB/${bld}T${rs}iB/${bld}P${rs}iB\n"); cfprintf(cout, " ${blu}-sparse${rs}\n"); cfprintf(cout, " Find files that occupy fewer disk blocks than expected\n"); cfprintf(cout, " ${blu}-type${rs} ${bld}[bcdlpfswD]${rs}\n"); cfprintf(cout, " Find files of the given type\n"); cfprintf(cout, " ${blu}-used${rs} ${bld}[-+]N${rs}\n"); cfprintf(cout, " Find files last accessed ${bld}N${rs} days after they were changed\n");#if BFS_CAN_CHECK_XATTRS cfprintf(cout, " ${blu}-xattr${rs}\n"); cfprintf(cout, " Find files with extended attributes\n"); cfprintf(cout, " ${blu}-xattrname${rs} ${bld}NAME${rs}\n"); cfprintf(cout, " Find files with the extended attribute ${bld}NAME${rs}\n");#endif cfprintf(cout, " ${blu}-xtype${rs} ${bld}[bcdlpfswD]${rs}\n"); cfprintf(cout, " Find files of the given type, following links when ${blu}-type${rs} would not, and\n"); cfprintf(cout, " vice versa\n\n");
cfprintf(cout, "${bld}Actions:${rs}\n\n");
cfprintf(cout, " ${blu}-delete${rs}\n"); cfprintf(cout, " ${blu}-rm${rs}\n"); cfprintf(cout, " Delete any found files (implies ${blu}-depth${rs})\n"); cfprintf(cout, " ${blu}-exec${rs} ${bld}command ... {} ;${rs}\n"); cfprintf(cout, " Execute a command\n"); cfprintf(cout, " ${blu}-exec${rs} ${bld}command ... {} +${rs}\n"); cfprintf(cout, " Execute a command with multiple files at once\n"); cfprintf(cout, " ${blu}-ok${rs} ${bld}command ... {} ;${rs}\n"); cfprintf(cout, " Prompt the user whether to execute a command\n"); cfprintf(cout, " ${blu}-execdir${rs} ${bld}command ... {} ;${rs}\n"); cfprintf(cout, " ${blu}-execdir${rs} ${bld}command ... {} +${rs}\n"); cfprintf(cout, " ${blu}-okdir${rs} ${bld}command ... {} ;${rs}\n"); cfprintf(cout, " Like ${blu}-exec${rs}/${blu}-ok${rs}, but run the command in the same directory as the found\n"); cfprintf(cout, " file(s)\n"); cfprintf(cout, " ${blu}-exit${rs} [${bld}STATUS${rs}]\n"); cfprintf(cout, " Exit immediately with the given status (%d if unspecified)\n", EXIT_SUCCESS); cfprintf(cout, " ${blu}-fls${rs} ${bld}FILE${rs}\n"); cfprintf(cout, " ${blu}-fprint${rs} ${bld}FILE${rs}\n"); cfprintf(cout, " ${blu}-fprint0${rs} ${bld}FILE${rs}\n"); cfprintf(cout, " ${blu}-fprintf${rs} ${bld}FILE${rs} ${bld}FORMAT${rs}\n"); cfprintf(cout, " Like ${blu}-ls${rs}/${blu}-print${rs}/${blu}-print0${rs}/${blu}-printf${rs}, but write to ${bld}FILE${rs} instead of standard\n" " output\n"); cfprintf(cout, " ${blu}-limit${rs} ${bld}N${rs}\n"); cfprintf(cout, " Quit after this action is evaluated ${bld}N${rs} times\n"); cfprintf(cout, " ${blu}-ls${rs}\n"); cfprintf(cout, " List files like ${ex}ls${rs} ${bld}-dils${rs}\n"); cfprintf(cout, " ${blu}-print${rs}\n"); cfprintf(cout, " Print the path to the found file\n"); cfprintf(cout, " ${blu}-print0${rs}\n"); cfprintf(cout, " Like ${blu}-print${rs}, but use the null character ('\\0') as a separator rather than\n"); cfprintf(cout, " newlines\n"); cfprintf(cout, " ${blu}-printf${rs} ${bld}FORMAT${rs}\n"); cfprintf(cout, " Print according to a format string (see ${ex}man${rs} ${bld}find${rs}). The additional format\n"); cfprintf(cout, " directives %%w and %%W${bld}k${rs} for printing file birth times are supported.\n"); cfprintf(cout, " ${blu}-printx${rs}\n"); cfprintf(cout, " Like ${blu}-print${rs}, but escape whitespace and quotation characters, to make the\n"); cfprintf(cout, " output safe for ${ex}xargs${rs}. Consider using ${blu}-print0${rs} and ${ex}xargs${rs} ${bld}-0${rs} instead.\n"); cfprintf(cout, " ${blu}-prune${rs}\n"); cfprintf(cout, " Don't descend into this directory\n"); cfprintf(cout, " ${blu}-quit${rs}\n"); cfprintf(cout, " Quit immediately\n"); cfprintf(cout, " ${blu}-version${rs}\n"); cfprintf(cout, " Print version information\n"); cfprintf(cout, " ${blu}-help${rs}\n"); cfprintf(cout, " Print this help message\n\n");
cfprintf(cout, "%s\n", BFS_HOMEPAGE);
if (pager > 0) { cfclose(cout); xwaitpid(pager, NULL, 0); }
parser->just_info = true; return NULL;}
/** Print the bfs "logo". */static void print_logo(CFILE *cout) { if (!cout->colors) { goto boring; }
size_t vwidth = xstrwidth(bfs_version); dchar *spaces = dstrepeat(" ", vwidth); dchar *lines = dstrepeat("─", vwidth); if (!spaces || !lines) { dstrfree(lines); dstrfree(spaces); goto boring; }
// We do ----\r<emoji> rather than <emoji>--- so we don't have to assume // anything about the width of the emoji cfprintf(cout, "╭─────%s╮\r📂\n", lines); cfprintf(cout, "├${ex}b${rs} %s │\n", spaces); cfprintf(cout, "╰├${ex}f${rs} ${bld}%s${rs} │\n", bfs_version); cfprintf(cout, " ╰├${ex}s${rs} %s │\n", spaces); cfprintf(cout, " ╰──%s─╯\n\n", lines);
dstrfree(lines); dstrfree(spaces); return;
boring: printf("%s %s\n\n", BFS_COMMAND, bfs_version);}
/** * "Parse" -version. */static struct bfs_expr *parse_version(struct bfs_parser *parser, int arg1, int arg2) { print_logo(parser->ctx->cout);
printf("Copyright © Tavian Barnes and the bfs contributors\n"); printf("No rights reserved (https://opensource.org/license/0BSD)\n\n");
printf("CONFFLAGS := %s\n", bfs_confflags); printf("CC := %s\n", bfs_cc); printf("CPPFLAGS := %s\n", bfs_cppflags); printf("CFLAGS := %s\n", bfs_cflags); printf("AR := %s\n", bfs_ar); printf("ARFLAGS := %s\n", bfs_arflags); printf("LDFLAGS := %s\n", bfs_ldflags); printf("LDLIBS := %s\n", bfs_ldlibs);
printf("\n%s\n", BFS_HOMEPAGE);
parser->just_info = true; return NULL;}
/** Parser callback function type. */typedef struct bfs_expr *parse_fn(struct bfs_parser *parser, int arg1, int arg2);
/** * An entry in the parse table for primary expressions. */struct table_entry { char *arg; enum bfs_kind kind; parse_fn *parse; int arg1; int arg2; bool prefix; bool needs_arg;};
/** * The parse table for primary expressions. */static const struct table_entry parse_table[] = { {"--", BFS_FLAG}, {"--help", BFS_ACTION, parse_help}, {"--version", BFS_ACTION, parse_version}, {"-Bmin", BFS_TEST, parse_min, BFS_STAT_BTIME}, {"-Bnewer", BFS_TEST, parse_newer, BFS_STAT_BTIME}, {"-Bsince", BFS_TEST, parse_since, BFS_STAT_BTIME}, {"-Btime", BFS_TEST, parse_time, BFS_STAT_BTIME}, {"-D", BFS_FLAG, parse_debug, .prefix = true}, {"-E", BFS_FLAG, parse_regex_extended}, {"-H", BFS_FLAG, parse_follow, BFTW_FOLLOW_ROOTS, false}, {"-L", BFS_FLAG, parse_follow, BFTW_FOLLOW_ALL, false}, {"-O", BFS_FLAG, parse_optlevel, .prefix = true}, {"-P", BFS_FLAG, parse_follow, 0, false}, {"-S", BFS_FLAG, parse_search_strategy, .prefix = true}, {"-X", BFS_FLAG, parse_xargs_safe}, {"-a", BFS_OPERATOR}, {"-acl", BFS_TEST, parse_acl}, {"-amin", BFS_TEST, parse_min, BFS_STAT_ATIME}, {"-and", BFS_OPERATOR}, {"-anewer", BFS_TEST, parse_newer, BFS_STAT_ATIME}, {"-asince", BFS_TEST, parse_since, BFS_STAT_ATIME}, {"-atime", BFS_TEST, parse_time, BFS_STAT_ATIME}, {"-capable", BFS_TEST, parse_capable}, {"-cmin", BFS_TEST, parse_min, BFS_STAT_CTIME}, {"-cnewer", BFS_TEST, parse_newer, BFS_STAT_CTIME}, {"-color", BFS_OPTION, parse_color, true}, {"-context", BFS_TEST, parse_context, true}, {"-csince", BFS_TEST, parse_since, BFS_STAT_CTIME}, {"-ctime", BFS_TEST, parse_time, BFS_STAT_CTIME}, {"-d", BFS_FLAG, parse_depth, true}, {"-daystart", BFS_OPTION, parse_daystart}, {"-delete", BFS_ACTION, parse_delete}, {"-depth", BFS_OPTION, parse_depth_n, false}, {"-empty", BFS_TEST, parse_empty}, {"-exclude", BFS_OPERATOR}, {"-exec", BFS_ACTION, parse_exec, 0}, {"-execdir", BFS_ACTION, parse_exec, BFS_EXEC_CHDIR}, {"-executable", BFS_TEST, parse_access, X_OK}, {"-exit", BFS_ACTION, parse_exit}, {"-f", BFS_FLAG, parse_f, .needs_arg = true}, {"-false", BFS_TEST, parse_const, false}, {"-files0-from", BFS_OPTION, parse_files0_from}, {"-flags", BFS_TEST, parse_flags}, {"-fls", BFS_ACTION, parse_fls}, {"-follow", BFS_OPTION, parse_follow, BFTW_FOLLOW_ALL, true}, {"-fprint", BFS_ACTION, parse_fprint}, {"-fprint0", BFS_ACTION, parse_fprint0}, {"-fprintf", BFS_ACTION, parse_fprintf}, {"-fstype", BFS_TEST, parse_fstype}, {"-gid", BFS_TEST, parse_group}, {"-group", BFS_TEST, parse_group}, {"-help", BFS_ACTION, parse_help}, {"-hidden", BFS_TEST, parse_hidden}, {"-ignore_readdir_race", BFS_OPTION, parse_ignore_races, true}, {"-ilname", BFS_TEST, parse_lname, true}, {"-iname", BFS_TEST, parse_name, true}, {"-inum", BFS_TEST, parse_inum}, {"-ipath", BFS_TEST, parse_path, true}, {"-iregex", BFS_TEST, parse_regex, BFS_REGEX_ICASE}, {"-iwholename", BFS_TEST, parse_path, true}, {"-j", BFS_FLAG, parse_jobs, .prefix = true}, {"-limit", BFS_ACTION, parse_limit}, {"-links", BFS_TEST, parse_links}, {"-lname", BFS_TEST, parse_lname, false}, {"-ls", BFS_ACTION, parse_ls}, {"-maxdepth", BFS_OPTION, parse_depth_limit, false}, {"-mindepth", BFS_OPTION, parse_depth_limit, true}, {"-mmin", BFS_TEST, parse_min, BFS_STAT_MTIME}, {"-mnewer", BFS_TEST, parse_newer, BFS_STAT_MTIME}, {"-mount", BFS_OPTION, parse_mount}, {"-msince", BFS_TEST, parse_since, BFS_STAT_MTIME}, {"-mtime", BFS_TEST, parse_time, BFS_STAT_MTIME}, {"-name", BFS_TEST, parse_name, false}, {"-newer", BFS_TEST, parse_newer, BFS_STAT_MTIME}, {"-newer", BFS_TEST, parse_newerxy, .prefix = true}, {"-nocolor", BFS_OPTION, parse_color, false}, {"-noerror", BFS_OPTION, parse_noerror}, {"-nogroup", BFS_TEST, parse_nogroup}, {"-nohidden", BFS_TEST, parse_nohidden}, {"-noignore_readdir_race", BFS_OPTION, parse_ignore_races, false}, {"-noleaf", BFS_OPTION, parse_noleaf}, {"-not", BFS_OPERATOR}, {"-nouser", BFS_TEST, parse_nouser}, {"-nowarn", BFS_OPTION, parse_warn, false}, {"-o", BFS_OPERATOR}, {"-ok", BFS_ACTION, parse_exec, BFS_EXEC_CONFIRM}, {"-okdir", BFS_ACTION, parse_exec, BFS_EXEC_CONFIRM | BFS_EXEC_CHDIR}, {"-or", BFS_OPERATOR}, {"-path", BFS_TEST, parse_path, false}, {"-perm", BFS_TEST, parse_perm}, {"-print", BFS_ACTION, parse_print}, {"-print0", BFS_ACTION, parse_print0}, {"-printf", BFS_ACTION, parse_printf}, {"-printx", BFS_ACTION, parse_printx}, {"-prune", BFS_ACTION, parse_prune}, {"-quit", BFS_ACTION, parse_quit}, {"-readable", BFS_TEST, parse_access, R_OK}, {"-regex", BFS_TEST, parse_regex, 0}, {"-regextype", BFS_OPTION, parse_regextype}, {"-rm", BFS_ACTION, parse_delete}, {"-s", BFS_FLAG, parse_s}, {"-samefile", BFS_TEST, parse_samefile}, {"-since", BFS_TEST, parse_since, BFS_STAT_MTIME}, {"-size", BFS_TEST, parse_size}, {"-sparse", BFS_TEST, parse_sparse}, {"-status", BFS_OPTION, parse_status}, {"-true", BFS_TEST, parse_const, true}, {"-type", BFS_TEST, parse_type, false}, {"-uid", BFS_TEST, parse_user}, {"-unique", BFS_OPTION, parse_unique}, {"-used", BFS_TEST, parse_used}, {"-user", BFS_TEST, parse_user}, {"-version", BFS_ACTION, parse_version}, {"-warn", BFS_OPTION, parse_warn, true}, {"-wholename", BFS_TEST, parse_path, false}, {"-writable", BFS_TEST, parse_access, W_OK}, {"-x", BFS_FLAG, parse_xdev}, {"-xattr", BFS_TEST, parse_xattr}, {"-xattrname", BFS_TEST, parse_xattrname}, {"-xdev", BFS_OPTION, parse_xdev}, {"-xtype", BFS_TEST, parse_type, true}, {0},};
/** Look up an argument in the parse table. */static const struct table_entry *table_lookup(const char *arg) { for (const struct table_entry *entry = parse_table; entry->arg; ++entry) { bool match; if (entry->prefix) { match = strncmp(arg, entry->arg, strlen(entry->arg)) == 0; } else { match = strcmp(arg, entry->arg) == 0; } if (match) { return entry; } }
return NULL;}
/** Look up a single-character flag in the parse table. */static const struct table_entry *flag_lookup(char flag) { for (const struct table_entry *entry = parse_table; entry->arg; ++entry) { enum bfs_kind kind = entry->kind; if (kind == BFS_FLAG && entry->arg[1] == flag && !entry->arg[2]) { return entry; } }
return NULL;}
/** Check for a multi-flag argument like -LEXO2. */static bool is_flag_group(const char *arg) { // We enforce that at least one flag in a flag group must be a capital // letter, to avoid ambiguity with primary expressions bool has_upper = false;
// Flags that take an argument must appear last bool needs_arg = false;
for (size_t i = 1; arg[i]; ++i) { char c = arg[i]; if (c >= 'A' && c <= 'Z') { has_upper = true; }
if (needs_arg) { return false; }
const struct table_entry *entry = flag_lookup(c); if (!entry || !entry->parse) { return false; }
if (entry->prefix) { // The rest is the flag's argument break; }
needs_arg |= entry->needs_arg; }
return has_upper;}
/** Parse a multi-flag argument. */static struct bfs_expr *parse_flag_group(struct bfs_parser *parser) { struct bfs_expr *expr = NULL;
char **start = parser->argv; char **end = start; const char *arg = start[0];
for (size_t i = 1; arg[i]; ++i) { parser->argv = start;
const struct table_entry *entry = flag_lookup(arg[i]); expr = entry->parse(parser, entry->arg1, entry->arg2);
if (parser->argv > end) { end = parser->argv; }
if (!expr || entry->prefix) { break; } }
if (expr) { bfs_assert(parser->argv == end, "Didn't eat enough tokens"); }
return expr;}
/** Search for a fuzzy match in the parse table. */static const struct table_entry *table_lookup_fuzzy(const char *arg) { const struct table_entry *best = NULL; int best_dist = INT_MAX;
for (const struct table_entry *entry = parse_table; entry->arg; ++entry) { int dist = typo_distance(arg, entry->arg); if (!best || dist < best_dist) { best = entry; best_dist = dist; } }
return best;}
/** * PRIMARY : OPTION * | TEST * | ACTION */static struct bfs_expr *parse_primary(struct bfs_parser *parser) { struct bfs_ctx *ctx = parser->ctx;
// Paths are already skipped at this point const char *arg = parser->argv[0];
if (arg[0] != '-') { goto unexpected; }
const struct table_entry *match = table_lookup(arg); if (match) { if (match->parse) { goto matched; } else { goto unexpected; } }
if (is_flag_group(arg)) { return parse_flag_group(parser); }
match = table_lookup_fuzzy(arg);
CFILE *cerr = ctx->cerr; parse_error(parser, "Unknown argument; did you mean "); switch (match->kind) { case BFS_FLAG: cfprintf(cerr, "${cyn}%s${rs}?", match->arg); break; case BFS_OPERATOR: cfprintf(cerr, "${red}%s${rs}?", match->arg); break; default: cfprintf(cerr, "${blu}%s${rs}?", match->arg); break; }
if (!ctx->interactive || !match->parse) { fprintf(stderr, "\n"); goto unmatched; }
fprintf(stderr, " "); if (ynprompt() <= 0) { goto unmatched; }
fprintf(stderr, "\n"); parser->argv[0] = match->arg;
matched: return match->parse(parser, match->arg1, match->arg2);
unmatched: return NULL;
unexpected: parse_error(parser, "Expected a predicate.\n"); return NULL;}
/** * FACTOR : "(" EXPR ")" * | "!" FACTOR | "-not" FACTOR * | "-exclude" FACTOR * | PRIMARY */static struct bfs_expr *parse_factor(struct bfs_parser *parser) { if (skip_paths(parser) != 0) { return NULL; }
const char *arg = parser->argv[0]; if (!arg) { parse_argv_error(parser, parser->last_arg, 1, "Expression terminated prematurely here.\n"); return NULL; }
if (strcmp(arg, "(") == 0) { parser_advance(parser, BFS_OPERATOR, 1);
struct bfs_expr *expr = parse_expr(parser); if (!expr) { return NULL; }
if (skip_paths(parser) != 0) { return NULL; }
arg = parser->argv[0]; if (!arg || strcmp(arg, ")") != 0) { parse_argv_error(parser, parser->last_arg, 1, "Expected a ${red})${rs}.\n"); return NULL; }
parser_advance(parser, BFS_OPERATOR, 1); return expr; } else if (strcmp(arg, "-exclude") == 0) { if (parser->excluding) { parse_error(parser, "${err}%s${rs} is not supported within ${red}-exclude${rs}.\n", arg); return NULL; }
char **argv = parser_advance(parser, BFS_OPERATOR, 1); parser->excluding = true;
struct bfs_expr *factor = parse_factor(parser); if (!factor) { return NULL; }
parser->excluding = false;
bfs_expr_append(parser->ctx->exclude, factor); return parse_new_expr(parser, eval_true, parser->argv - argv, argv, BFS_OPERATOR); } else if (strcmp(arg, "!") == 0 || strcmp(arg, "-not") == 0) { char **argv = parser_advance(parser, BFS_OPERATOR, 1);
struct bfs_expr *factor = parse_factor(parser); if (!factor) { return NULL; }
return new_unary_expr(parser, eval_not, factor, argv); } else { return parse_primary(parser); }}
/** * TERM : FACTOR * | TERM FACTOR * | TERM "-a" FACTOR * | TERM "-and" FACTOR */static struct bfs_expr *parse_term(struct bfs_parser *parser) { struct bfs_expr *term = parse_factor(parser);
while (term) { if (skip_paths(parser) != 0) { return NULL; }
const char *arg = parser->argv[0]; if (!arg) { break; }
if (strcmp(arg, "-o") == 0 || strcmp(arg, "-or") == 0 || strcmp(arg, ",") == 0 || strcmp(arg, ")") == 0) { break; }
char **argv = &fake_and_arg; if (strcmp(arg, "-a") == 0 || strcmp(arg, "-and") == 0) { argv = parser_advance(parser, BFS_OPERATOR, 1); }
struct bfs_expr *lhs = term; struct bfs_expr *rhs = parse_factor(parser); if (!rhs) { return NULL; }
term = new_binary_expr(parser, eval_and, lhs, rhs, argv); }
return term;}
/** * CLAUSE : TERM * | CLAUSE "-o" TERM * | CLAUSE "-or" TERM */static struct bfs_expr *parse_clause(struct bfs_parser *parser) { struct bfs_expr *clause = parse_term(parser);
while (clause) { if (skip_paths(parser) != 0) { return NULL; }
const char *arg = parser->argv[0]; if (!arg) { break; }
if (strcmp(arg, "-o") != 0 && strcmp(arg, "-or") != 0) { break; }
char **argv = parser_advance(parser, BFS_OPERATOR, 1);
struct bfs_expr *lhs = clause; struct bfs_expr *rhs = parse_term(parser); if (!rhs) { return NULL; }
clause = new_binary_expr(parser, eval_or, lhs, rhs, argv); }
return clause;}
/** * EXPR : CLAUSE * | EXPR "," CLAUSE */static struct bfs_expr *parse_expr(struct bfs_parser *parser) { struct bfs_expr *expr = parse_clause(parser);
while (expr) { if (skip_paths(parser) != 0) { return NULL; }
const char *arg = parser->argv[0]; if (!arg) { break; }
if (strcmp(arg, ",") != 0) { break; }
char **argv = parser_advance(parser, BFS_OPERATOR, 1);
struct bfs_expr *lhs = expr; struct bfs_expr *rhs = parse_clause(parser); if (!rhs) { return NULL; }
expr = new_binary_expr(parser, eval_comma, lhs, rhs, argv); }
return expr;}
/** Handle -files0-from after parsing. */static int parse_files0_roots(struct bfs_parser *parser) { const struct bfs_ctx *ctx = parser->ctx; const struct bfs_expr *expr = parser->files0_expr;
if (ctx->npaths > 0) { bool highlight[ctx->argc]; init_highlight(ctx, highlight); highlight_args(ctx, expr->argv, expr->argc, highlight);
for (size_t i = 0; i < ctx->argc; ++i) { if (ctx->kinds[i] == BFS_PATH) { highlight[i] = true; } }
bfs_argv_error(ctx, highlight); bfs_error(ctx, "Cannot combine %pX with explicit root paths.\n", expr); return -1; }
const char *from = expr->argv[1];
FILE *file; if (strcmp(from, "-") == 0) { if (!consume_stdin(parser, expr)) { return -1; } file = stdin; } else { file = xfopen(from, O_RDONLY | O_CLOEXEC); } if (!file) { parse_expr_error(parser, expr, "%s.\n", errstr()); return -1; }
while (true) { char *path = xgetdelim(file, '\0'); if (!path) { if (errno) { goto fail; } else { break; } }
int ret = parse_root(parser, path); free(path); if (ret != 0) { goto fail; } }
if (file != stdin) { fclose(file); }
return 0;
fail: if (file != stdin) { fclose(file); } return -1;}
/** * Parse the top-level expression. */static struct bfs_expr *parse_whole_expr(struct bfs_parser *parser) { struct bfs_ctx *ctx = parser->ctx;
if (skip_paths(parser) != 0) { return NULL; }
struct bfs_expr *expr; if (parser->argv[0]) { expr = parse_expr(parser); } else { expr = parse_new_expr(parser, eval_true, 1, &fake_true_arg, BFS_TEST); } if (!expr) { return NULL; }
if (parser->argv[0]) { parse_error(parser, "Unexpected argument.\n"); return NULL; }
if (parser->files0_expr) { if (parse_files0_roots(parser) != 0) { return NULL; } } else if (ctx->npaths == 0) { if (parse_root(parser, ".") != 0) { return NULL; } }
if (parser->implicit_print) { const struct bfs_expr *limit = parser->limit_expr; if (limit) { parse_expr_error(parser, limit, "With %pX, you must specify an action explicitly; for example, ${blu}-print${rs} %px.\n", limit, limit); return NULL; }
struct bfs_expr *print = parse_new_expr(parser, eval_fprint, 1, &fake_print_arg, BFS_ACTION); if (!print) { return NULL; } init_print_expr(parser, print);
expr = new_binary_expr(parser, eval_and, expr, print, &fake_and_arg); if (!expr) { return NULL; } }
if (parser->mount_expr && parser->xdev_expr) { parse_conflict_warning(parser, parser->mount_expr, parser->xdev_expr, "%px is redundant in the presence of %px.\n\n", parser->xdev_expr, parser->mount_expr); }
if (ctx->warn && parser->depth_expr && parser->prune_expr) { parse_conflict_warning(parser, parser->depth_expr, parser->prune_expr, "%px does not work in the presence of %px.\n", parser->prune_expr, parser->depth_expr);
if (ctx->interactive) { bfs_warning(ctx, "Do you want to continue? "); if (ynprompt() <= 0) { return NULL; } }
fprintf(stderr, "\n"); }
return expr;}
static const char *bftw_strategy_name(enum bftw_strategy strategy) { switch (strategy) { case BFTW_BFS: return "bfs"; case BFTW_DFS: return "dfs"; case BFTW_IDS: return "ids"; case BFTW_EDS: return "eds"; }
bfs_bug("Invalid strategy"); return "???";}
static void dump_expr_multiline(const struct bfs_ctx *ctx, enum debug_flags flag, const struct bfs_expr *expr, int indent, int rparens) { bfs_debug_prefix(ctx, flag);
for (int i = 0; i < indent; ++i) { cfprintf(ctx->cerr, " "); }
bool close = true;
if (bfs_expr_is_parent(expr)) { if (SLIST_EMPTY(&expr->children)) { cfprintf(ctx->cerr, "(${red}%s${rs}", expr->argv[0]); ++rparens; } else { cfprintf(ctx->cerr, "(${red}%s${rs}\n", expr->argv[0]); for_expr (child, expr) { int parens = child->next ? 0 : rparens + 1; dump_expr_multiline(ctx, flag, child, indent + 1, parens); } close = false; } } else { if (flag == DEBUG_RATES) { cfprintf(ctx->cerr, "%pE", expr); } else { cfprintf(ctx->cerr, "%pe", expr); } }
if (close) { for (int i = 0; i < rparens; ++i) { cfprintf(ctx->cerr, ")"); } cfprintf(ctx->cerr, "\n"); }}
void bfs_ctx_dump(const struct bfs_ctx *ctx, enum debug_flags flag) { if (!bfs_debug_prefix(ctx, flag)) { return; }
CFILE *cerr = ctx->cerr;
cfprintf(cerr, "${ex}%s${rs}", ctx->argv[0]);
if (ctx->flags & BFTW_FOLLOW_ALL) { cfprintf(cerr, " ${cyn}-L${rs}"); } else if (ctx->flags & BFTW_FOLLOW_ROOTS) { cfprintf(cerr, " ${cyn}-H${rs}"); } else { cfprintf(cerr, " ${cyn}-P${rs}"); }
if (ctx->xargs_safe) { cfprintf(cerr, " ${cyn}-X${rs}"); }
if (ctx->flags & BFTW_SORT) { cfprintf(cerr, " ${cyn}-s${rs}"); }
cfprintf(cerr, " ${cyn}-j${bld}%d${rs}", ctx->threads);
if (ctx->optlevel != 3) { cfprintf(cerr, " ${cyn}-O${bld}%d${rs}", ctx->optlevel); }
cfprintf(cerr, " ${cyn}-S${rs} ${bld}%s${rs}", bftw_strategy_name(ctx->strategy));
enum debug_flags debug = ctx->debug; if (debug == DEBUG_ALL) { cfprintf(cerr, " ${cyn}-D${rs} ${bld}all${rs}"); } else if (debug) { cfprintf(cerr, " ${cyn}-D${rs} "); for (enum debug_flags i = 1; DEBUG_ALL & i; i <<= 1) { if (debug & i) { cfprintf(cerr, "${bld}%s${rs}", debug_flag_name(i)); debug ^= i; if (debug) { cfprintf(cerr, ","); } } } }
for (size_t i = 0; i < ctx->npaths; ++i) { const char *path = ctx->paths[i]; char c = path[0]; if (c == '-' || c == '(' || c == ')' || c == '!' || c == ',') { cfprintf(cerr, " ${cyn}-f${rs}"); } cfprintf(cerr, " ${mag}%pq${rs}", path); }
if (ctx->cout->colors) { cfprintf(cerr, " ${blu}-color${rs}"); } else { cfprintf(cerr, " ${blu}-nocolor${rs}"); } if (ctx->flags & BFTW_POST_ORDER) { cfprintf(cerr, " ${blu}-depth${rs}"); } if (ctx->ignore_races) { cfprintf(cerr, " ${blu}-ignore_readdir_race${rs}"); } if (ctx->mindepth != 0) { cfprintf(cerr, " ${blu}-mindepth${rs} ${bld}%d${rs}", ctx->mindepth); } if (ctx->maxdepth != INT_MAX) { cfprintf(cerr, " ${blu}-maxdepth${rs} ${bld}%d${rs}", ctx->maxdepth); } if (ctx->flags & BFTW_SKIP_MOUNTS) { cfprintf(cerr, " ${blu}-mount${rs}"); } if (ctx->status) { cfprintf(cerr, " ${blu}-status${rs}"); } if (ctx->unique) { cfprintf(cerr, " ${blu}-unique${rs}"); } if ((ctx->flags & (BFTW_SKIP_MOUNTS | BFTW_PRUNE_MOUNTS)) == BFTW_PRUNE_MOUNTS) { cfprintf(cerr, " ${blu}-xdev${rs}"); }
fputs("\n", stderr);
bfs_debug(ctx, flag, "(${red}-exclude${rs}\n"); dump_expr_multiline(ctx, flag, ctx->exclude, 1, 1);
dump_expr_multiline(ctx, flag, ctx->expr, 0, 0);}
/** * Dump the estimated costs. */static void dump_costs(const struct bfs_ctx *ctx) { const struct bfs_expr *expr = ctx->expr; bfs_debug(ctx, DEBUG_COST, " Cost: ~${ylw}%g${rs}\n", expr->cost); bfs_debug(ctx, DEBUG_COST, "Probability: ~${ylw}%g%%${rs}\n", 100.0 * expr->probability);}
struct bfs_ctx *bfs_parse_cmdline(int argc, char *argv[]) { struct bfs_ctx *ctx = bfs_ctx_new(); if (!ctx) { perror("bfs_ctx_new()"); goto fail; }
static char *default_argv[] = {BFS_COMMAND, NULL}; if (argc < 1) { argc = 1; argv = default_argv; }
ctx->argc = argc; ctx->argv = xmemdup(argv, sizeof_array(char *, argc + 1)); if (!ctx->argv) { perror("xmemdup()"); goto fail; }
ctx->kinds = ZALLOC_ARRAY(enum bfs_kind, argc); if (!ctx->kinds) { perror("zalloc()"); goto fail; }
enum use_color use_color = COLOR_AUTO; const char *no_color = getenv("NO_COLOR"); if (no_color && *no_color) { // https://no-color.org/ use_color = COLOR_NEVER; }
ctx->colors = parse_colors(); if (!ctx->colors) { ctx->colors_error = errno; }
ctx->cerr = cfwrap(stderr, use_color ? ctx->colors : NULL, false); if (!ctx->cerr) { perror("cfwrap()"); goto fail; }
ctx->cout = cfwrap(stdout, use_color ? ctx->colors : NULL, false); if (!ctx->cout) { bfs_perror(ctx, "cfwrap()"); goto fail; }
if (!bfs_ctx_dedup(ctx, ctx->cout, NULL) || !bfs_ctx_dedup(ctx, ctx->cerr, NULL)) { bfs_perror(ctx, "bfs_ctx_dedup()"); goto fail; }
bool stdin_tty = isatty(STDIN_FILENO); bool stdout_tty = isatty(STDOUT_FILENO); bool stderr_tty = isatty(STDERR_FILENO);
if (getenv("POSIXLY_CORRECT")) { ctx->posixly_correct = true; } else { ctx->warn = stdin_tty; } ctx->interactive = stdin_tty && stderr_tty;
struct bfs_parser parser = { .ctx = ctx, .argv = ctx->argv + 1, .command = ctx->argv[0], .regex_type = BFS_REGEX_POSIX_BASIC, .stdout_tty = stdout_tty, .use_color = use_color, .implicit_print = true, .just_info = false, .excluding = false, .last_arg = NULL, .depth_expr = NULL, .prune_expr = NULL, .mount_expr = NULL, .xdev_expr = NULL, .stdin_expr = NULL, .now = ctx->now, };
ctx->exclude = parse_new_expr(&parser, eval_or, 1, &fake_or_arg, BFS_OPERATOR); if (!ctx->exclude) { goto fail; }
ctx->expr = parse_whole_expr(&parser); if (!ctx->expr) { if (parser.just_info) { goto done; } else { goto fail; } }
if (parser.use_color == COLOR_AUTO && !ctx->colors) { bfs_warning(ctx, "Error parsing $$LS_COLORS: %s.\n\n", xstrerror(ctx->colors_error)); }
if (bfs_optimize(ctx) != 0) { if (errno != 0) { bfs_perror(ctx, "bfs_optimize()"); } goto fail; }
if ((ctx->flags & BFTW_FOLLOW_ALL) && !ctx->unique) { // We need bftw() to detect cycles unless -unique does it for us ctx->flags |= BFTW_DETECT_CYCLES; }
bfs_ctx_dump(ctx, DEBUG_TREE); dump_costs(ctx);
done: return ctx;
fail: bfs_ctx_free(ctx); return NULL;}