1{ lib
2, fetchFromGitHub
3, runCommand
4, yallback
5, yara
6}:
7
8/* TODO/CAUTION:
9
10I don't want to discourage use, but I'm not sure how stable
11the API is. Have fun, but be prepared to track changes! :)
12
13For _now_, binlore is basically a thin wrapper around
14`<invoke yara> | <postprocess with yallback>` with support
15for running it on a derivation, saving the result in the
16store, and aggregating results from a set of packages.
17
18In the longer term, I suspect there are more uses for this
19general pattern (i.e., run some analysis tool that produces
20a deterministic output and cache the result per package...).
21
22I'm not sure how that'll look and if it'll be the case that
23binlore automatically collects all of them, or if you'll be
24configuring which "kind(s)" of lore it generates. Nailing
25that down will almost certainly mean reworking the API.
26
27*/
28
29let
30 src = fetchFromGitHub {
31 owner = "abathur";
32 repo = "binlore";
33 rev = "v0.3.0";
34 hash = "sha256-4Fs6HThfDhKRskuDJx2+hucl8crMRm10K6949JdIwPY=";
35 };
36 /*
37 binlore has one one more yallbacks responsible for
38 routing the appropriate lore to a named file in the
39 appropriate format. At some point I might try to do
40 something fancy with this, but for now the answer to
41 *all* questions about the lore are: the bare minimum
42 to get resholve over the next feature hump in time to
43 hopefully slip this feature in before the branch-off.
44 */
45 # TODO: feeling really uninspired on the API
46 loreDef = {
47 # YARA rule file
48 rules = (src + "/execers.yar");
49 # output filenames; "types" of lore
50 types = [ "execers" "wrappers" ];
51 # shell rule callbacks; see github.com/abathur/yallback
52 yallback = (src + "/execers.yall");
53 # TODO:
54 # - echo for debug, can be removed at some point
55 # - I really just wanted to put the bit after the pipe
56 # in here, but I'm erring on the side of flexibility
57 # since this form will make it easier to pilot other
58 # uses of binlore.
59 callback = lore: drv: ''
60 if [[ -d "${drv}/bin" ]] || [[ -d "${drv}/lib" ]] || [[ -d "${drv}/libexec" ]]; then
61 echo generating binlore for $drv by running:
62 echo "${yara}/bin/yara --scan-list --recursive ${lore.rules} <(printf '%s\n' ${drv}/{bin,lib,libexec}) | ${yallback}/bin/yallback ${lore.yallback}"
63 else
64 echo "failed to generate binlore for $drv (none of ${drv}/{bin,lib,libexec} exist)"
65 fi
66
67 if [[ -d "${drv}/bin" ]] || [[ -d "${drv}/lib" ]] || [[ -d "${drv}/libexec" ]]; then
68 ${yara}/bin/yara --scan-list --recursive ${lore.rules} <(printf '%s\n' ${drv}/{bin,lib,libexec}) | ${yallback}/bin/yallback ${lore.yallback}
69 fi
70 '';
71 };
72
73in rec {
74 /*
75 Output a directory containing lore for multiple drvs.
76
77 This will `make` lore for drv in drvs and then combine lore
78 of the same type across all packages into a single file.
79
80 When drvs are also specified in the strip argument, corresponding
81 lore is made relative by stripping the path of each drv from
82 matching entries. (This is mainly useful in a build process that
83 uses a chain of two or more derivations where the output of one
84 is the source for the next. See resholve for an example.)
85 */
86 collect = { lore ? loreDef, drvs, strip ? [ ] }: (runCommand "more-binlore" { } ''
87 mkdir $out
88 for lorefile in ${toString lore.types}; do
89 cat ${lib.concatMapStrings (x: x + "/$lorefile ") (map (make lore) (map lib.getBin (builtins.filter lib.isDerivation drvs)))} > $out/$lorefile
90 substituteInPlace $out/$lorefile ${lib.concatMapStrings (x: "--replace-quiet '${x}/' '' ") strip}
91 done
92 '');
93
94 /*
95 Output a directory containing lore for a single drv.
96
97 This produces lore for the derivation (via lore.callback) and
98 appends any lore that the derivation itself wrote to nix-support
99 or which was overridden in drv.binlore.<outputName> (passthru).
100
101 > *Note*: Since the passthru is attached to all outputs, binlore
102 > is an attrset namespaced by outputName to support packages with
103 > executables in more than one output.
104
105 Since the last entry wins, the effective priority is:
106 drv.binlore.<outputName> > $drv/nix-support > lore generated here by callback
107 */
108 make = lore: drv: runCommand "${drv.name}-binlore" {
109 drv = drv;
110 } (''
111 mkdir $out
112 touch $out/{${builtins.concatStringsSep "," lore.types}}
113
114 ${lore.callback lore drv}
115 '' +
116 # append lore from package's $out and drv.binlore.${drv.outputName} (last entry wins)
117 ''
118 for lore_type in ${builtins.toString lore.types}; do
119 if [[ -f "${drv}/nix-support/$lore_type" ]]; then
120 cat "${drv}/nix-support/$lore_type" >> "$out/$lore_type"
121 fi
122 '' + lib.optionalString (builtins.hasAttr "binlore" drv && builtins.hasAttr drv.outputName drv.binlore) ''
123 if [[ -f "${drv.binlore."${drv.outputName}"}/$lore_type" ]]; then
124 cat "${drv.binlore."${drv.outputName}"}/$lore_type" >> "$out/$lore_type"
125 fi
126 '' + ''
127 done
128
129 echo binlore for $drv written to $out
130 '');
131
132 /*
133 Utility function for creating override lore for drv.
134
135 We normally attach this lore to `drv.passthru.binlore.<outputName>`.
136
137 > *Notes*:
138 > - Since the passthru is attached to all outputs, binlore is an
139 > attrset namespaced by outputName to support packages with
140 > executables in more than one output. You'll generally just use
141 > `out` or `bin`.
142 > - We can reconsider the passthru attr name if someone adds
143 > a new lore provider. We settled on `.binlore` for now to make it
144 > easier for people to figure out what this is for.
145
146 The lore argument should be a Shell script (string) that generates
147 the necessary lore. You can use arbitrary Shell, but this function
148 includes a shell DSL you can use to declare/generate lore in most
149 cases. It has the following functions:
150
151 - `execer <verdict> [<path>...]`
152 - `wrapper <wrapper_path> <original_path>`
153
154 Writing every override explicitly in a Nix list would be tedious
155 for large packages, but this small shell DSL enables us to express
156 many overrides efficiently via pathname expansion/globbing.
157
158 Here's a very general example of both functions:
159
160 passthru.binlore.out = binlore.synthesize finalAttrs.finalPackage ''
161 execer can bin/hello bin/{a,b,c}
162 wrapper bin/hello bin/.hello-wrapped
163 '';
164
165 And here's a specific example of how pathname expansion enables us
166 to express lore for the single-binary variant of coreutils while
167 being both explicit and (somewhat) efficient:
168
169 passthru = {} // optionalAttrs (singleBinary != false) {
170 binlore.out = binlore.synthesize coreutils ''
171 execer can bin/{chroot,env,install,nice,nohup,runcon,sort,split,stdbuf,timeout}
172 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}
173 '';
174 };
175
176 Caution: Be thoughtful about using a bare wildcard (*) glob here.
177 We should generally override lore only when a human understands if
178 the executable will exec arbitrary user-passed executables. A bare
179 glob can match new executables added in future package versions
180 before anyone can audit them.
181 */
182 synthesize = drv: loreSynthesizingScript: runCommand "${drv.name}-lore-override" {
183 drv = drv;
184 } (''
185 execer(){
186 local verdict="$1"
187
188 shift
189
190 for path in "$@"; do
191 if [[ -f "$PWD/$path" ]]; then
192 echo "$verdict:$PWD/$path"
193 else
194 echo "error: Tried to synthesize execer lore for missing file: $PWD/$path" >&2
195 exit 2
196 fi
197 done
198 } >> $out/execers
199
200 wrapper(){
201 local wrapper="$1"
202 local original="$2"
203
204 if [[ ! -f "$wrapper" ]]; then
205 echo "error: Tried to synthesize wrapper lore for missing wrapper: $PWD/$wrapper" >&2
206 exit 2
207 fi
208
209 if [[ ! -f "$original" ]]; then
210 echo "error: Tried to synthesize wrapper lore for missing original: $PWD/$original" >&2
211 exit 2
212 fi
213
214 echo "$PWD/$wrapper:$PWD/$original"
215
216 } >> $out/wrappers
217
218 mkdir $out
219
220 # lore override commands are relative to the drv root
221 cd $drv
222
223 '' + loreSynthesizingScript);
224}