{ lib, fetchFromGitHub, runCommand, yallback, yara, }: /* TODO/CAUTION: I don't want to discourage use, but I'm not sure how stable the API is. Have fun, but be prepared to track changes! :) For _now_, binlore is basically a thin wrapper around ` | ` with support for running it on a derivation, saving the result in the store, and aggregating results from a set of packages. In the longer term, I suspect there are more uses for this general pattern (i.e., run some analysis tool that produces a deterministic output and cache the result per package...). I'm not sure how that'll look and if it'll be the case that binlore automatically collects all of them, or if you'll be configuring which "kind(s)" of lore it generates. Nailing that down will almost certainly mean reworking the API. */ let src = fetchFromGitHub { owner = "abathur"; repo = "binlore"; rev = "v0.3.0"; hash = "sha256-4Fs6HThfDhKRskuDJx2+hucl8crMRm10K6949JdIwPY="; }; /* binlore has one one more yallbacks responsible for routing the appropriate lore to a named file in the appropriate format. At some point I might try to do something fancy with this, but for now the answer to *all* questions about the lore are: the bare minimum to get resholve over the next feature hump in time to hopefully slip this feature in before the branch-off. */ # TODO: feeling really uninspired on the API loreDef = { # YARA rule file rules = (src + "/execers.yar"); # output filenames; "types" of lore types = [ "execers" "wrappers" ]; # shell rule callbacks; see github.com/abathur/yallback yallback = (src + "/execers.yall"); # TODO: # - echo for debug, can be removed at some point # - I really just wanted to put the bit after the pipe # in here, but I'm erring on the side of flexibility # since this form will make it easier to pilot other # uses of binlore. callback = lore: drv: '' if [[ -d "${drv}/bin" ]] || [[ -d "${drv}/lib" ]] || [[ -d "${drv}/libexec" ]]; then echo generating binlore for $drv by running: echo "${yara}/bin/yara --scan-list --recursive ${lore.rules} <(printf '%s\n' ${drv}/{bin,lib,libexec}) | ${yallback}/bin/yallback ${lore.yallback}" else echo "failed to generate binlore for $drv (none of ${drv}/{bin,lib,libexec} exist)" fi if [[ -d "${drv}/bin" ]] || [[ -d "${drv}/lib" ]] || [[ -d "${drv}/libexec" ]]; then ${yara}/bin/yara --scan-list --recursive ${lore.rules} <(printf '%s\n' ${drv}/{bin,lib,libexec}) | ${yallback}/bin/yallback ${lore.yallback} fi ''; }; in rec { /* Output a directory containing lore for multiple drvs. This will `make` lore for drv in drvs and then combine lore of the same type across all packages into a single file. When drvs are also specified in the strip argument, corresponding lore is made relative by stripping the path of each drv from matching entries. (This is mainly useful in a build process that uses a chain of two or more derivations where the output of one is the source for the next. See resholve for an example.) */ collect = { lore ? loreDef, drvs, strip ? [ ], }: (runCommand "more-binlore" { } '' mkdir $out for lorefile in ${toString lore.types}; do cat ${ lib.concatMapStrings (x: x + "/$lorefile ") ( map (make lore) (map lib.getBin (builtins.filter lib.isDerivation drvs)) ) } > $out/$lorefile substituteInPlace $out/$lorefile ${lib.concatMapStrings (x: "--replace-quiet '${x}/' '' ") strip} done ''); /* Output a directory containing lore for a single drv. This produces lore for the derivation (via lore.callback) and appends any lore that the derivation itself wrote to nix-support or which was overridden in drv.binlore. (passthru). > *Note*: Since the passthru is attached to all outputs, binlore > is an attrset namespaced by outputName to support packages with > executables in more than one output. Since the last entry wins, the effective priority is: drv.binlore. > $drv/nix-support > lore generated here by callback */ make = lore: drv: runCommand "${drv.name}-binlore" { drv = drv; } ( '' mkdir $out touch $out/{${builtins.concatStringsSep "," lore.types}} ${lore.callback lore drv} '' + # append lore from package's $out and drv.binlore.${drv.outputName} (last entry wins) '' for lore_type in ${builtins.toString lore.types}; do if [[ -f "${drv}/nix-support/$lore_type" ]]; then cat "${drv}/nix-support/$lore_type" >> "$out/$lore_type" fi '' + lib.optionalString (builtins.hasAttr "binlore" drv && builtins.hasAttr drv.outputName drv.binlore) '' if [[ -f "${drv.binlore."${drv.outputName}"}/$lore_type" ]]; then cat "${drv.binlore."${drv.outputName}"}/$lore_type" >> "$out/$lore_type" fi '' + '' done echo binlore for $drv written to $out '' ); /* Utility function for creating override lore for drv. We normally attach this lore to `drv.passthru.binlore.`. > *Notes*: > - Since the passthru is attached to all outputs, binlore is an > attrset namespaced by outputName to support packages with > executables in more than one output. You'll generally just use > `out` or `bin`. > - We can reconsider the passthru attr name if someone adds > a new lore provider. We settled on `.binlore` for now to make it > easier for people to figure out what this is for. The lore argument should be a Shell script (string) that generates the necessary lore. You can use arbitrary Shell, but this function includes a shell DSL you can use to declare/generate lore in most cases. It has the following functions: - `execer [...]` - `wrapper ` Writing every override explicitly in a Nix list would be tedious for large packages, but this small shell DSL enables us to express many overrides efficiently via pathname expansion/globbing. Here's a very general example of both functions: passthru.binlore.out = binlore.synthesize finalAttrs.finalPackage '' execer can bin/hello bin/{a,b,c} wrapper bin/hello bin/.hello-wrapped ''; And here's a specific example of how pathname expansion enables us to express lore for the single-binary variant of coreutils while being both explicit and (somewhat) efficient: passthru = {} // optionalAttrs (singleBinary != false) { binlore.out = binlore.synthesize coreutils '' execer can bin/{chroot,env,install,nice,nohup,runcon,sort,split,stdbuf,timeout} execer cannot bin/{[,b2sum,base32,base64,basename,basenc,cat,chcon,chgrp,chmod,chown,cksum,comm,cp,csplit,cut,date,dd,df,dir,dircolors,dirname,du,echo,expand,expr,factor,false,fmt,fold,groups,head,hostid,id,join,kill,link,ln,logname,ls,md5sum,mkdir,mkfifo,mknod,mktemp,mv,nl,nproc,numfmt,od,paste,pathchk,pinky,pr,printenv,printf,ptx,pwd,readlink,realpath,rm,rmdir,seq,sha1sum,sha224sum,sha256sum,sha384sum,sha512sum,shred,shuf,sleep,stat,stty,sum,sync,tac,tail,tee,test,touch,tr,true,truncate,tsort,tty,uname,unexpand,uniq,unlink,uptime,users,vdir,wc,who,whoami,yes} ''; }; Caution: Be thoughtful about using a bare wildcard (*) glob here. We should generally override lore only when a human understands if the executable will exec arbitrary user-passed executables. A bare glob can match new executables added in future package versions before anyone can audit them. */ synthesize = drv: loreSynthesizingScript: runCommand "${drv.name}-lore-override" { drv = drv; } ( '' execer(){ local verdict="$1" shift for path in "$@"; do if [[ -f "$PWD/$path" ]]; then echo "$verdict:$PWD/$path" else echo "error: Tried to synthesize execer lore for missing file: $PWD/$path" >&2 exit 2 fi done } >> $out/execers wrapper(){ local wrapper="$1" local original="$2" if [[ ! -f "$wrapper" ]]; then echo "error: Tried to synthesize wrapper lore for missing wrapper: $PWD/$wrapper" >&2 exit 2 fi if [[ ! -f "$original" ]]; then echo "error: Tried to synthesize wrapper lore for missing original: $PWD/$original" >&2 exit 2 fi echo "$PWD/$wrapper:$PWD/$original" } >> $out/wrappers mkdir $out # lore override commands are relative to the drv root cd $drv '' + loreSynthesizingScript ); }