Commit | Line | Data |
---|---|---|
f640bcb3 RK |
1 | /* |
2 | * This file is part of DisOrder | |
3 | * Copyright (C) 2008 Richard Kettlewell | |
4 | * | |
e7eb3a27 | 5 | * This program is free software: you can redistribute it and/or modify |
f640bcb3 | 6 | * it under the terms of the GNU General Public License as published by |
e7eb3a27 | 7 | * the Free Software Foundation, either version 3 of the License, or |
f640bcb3 | 8 | * (at your option) any later version. |
e7eb3a27 RK |
9 | * |
10 | * This program is distributed in the hope that it will be useful, | |
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
13 | * GNU General Public License for more details. | |
14 | * | |
f640bcb3 | 15 | * You should have received a copy of the GNU General Public License |
e7eb3a27 | 16 | * along with this program. If not, see <http://www.gnu.org/licenses/>. |
f640bcb3 RK |
17 | */ |
18 | ||
19 | /** @file lib/macros-builtin.c | |
20 | * @brief Built-in expansions | |
21 | * | |
59cf25c4 RK |
22 | * This is a grab-bag of non-domain-specific expansions |
23 | * | |
24 | * Documentation is generated from the comments at the head of each function. | |
25 | * The comment should have a '$' and the expansion name on the first line and | |
26 | * should have a blank line between each paragraph. | |
27 | * | |
28 | * To make a bulleted list, put a '-' at the start of each line. | |
29 | * | |
30 | * You can currently get away with troff markup but this is horribly ugly and | |
31 | * might be changed. | |
f640bcb3 RK |
32 | */ |
33 | ||
05b75f8d | 34 | #include "common.h" |
f640bcb3 | 35 | |
f640bcb3 | 36 | #include <errno.h> |
f640bcb3 | 37 | #include <unistd.h> |
b36be3a1 RK |
38 | #include <fcntl.h> |
39 | #include <sys/stat.h> | |
f640bcb3 | 40 | |
9faa7a88 | 41 | #include "hash.h" |
b36be3a1 | 42 | #include "mem.h" |
f640bcb3 RK |
43 | #include "macros.h" |
44 | #include "sink.h" | |
45 | #include "syscalls.h" | |
46 | #include "log.h" | |
47 | #include "wstat.h" | |
48 | #include "kvp.h" | |
b36be3a1 RK |
49 | #include "split.h" |
50 | #include "printf.h" | |
51 | #include "vector.h" | |
2257512d | 52 | #include "filepart.h" |
b36be3a1 RK |
53 | |
54 | static struct vector include_path; | |
f640bcb3 RK |
55 | |
56 | /** @brief Return 1 if @p s is 'true' else 0 */ | |
57 | int mx_str2bool(const char *s) { | |
58 | return !strcmp(s, "true"); | |
59 | } | |
60 | ||
61 | /** @brief Return "true" if @p n is nonzero else "false" */ | |
62 | const char *mx_bool2str(int n) { | |
63 | return n ? "true" : "false"; | |
64 | } | |
65 | ||
b36be3a1 | 66 | /** @brief Write a boolean result */ |
71634563 | 67 | int mx_bool_result(struct sink *output, int result) { |
b36be3a1 RK |
68 | if(sink_writes(output, mx_bool2str(result)) < 0) |
69 | return -1; | |
70 | else | |
71 | return 0; | |
72 | } | |
73 | ||
9faa7a88 | 74 | /** @brief Search the include path */ |
f2d306b4 | 75 | char *mx_find(const char *name, int report) { |
9faa7a88 RK |
76 | char *path; |
77 | int n; | |
78 | ||
79 | if(name[0] == '/') { | |
80 | if(access(name, O_RDONLY) < 0) { | |
f2d306b4 | 81 | if(report) |
2e9ba080 | 82 | disorder_error(errno, "cannot read %s", name); |
9faa7a88 RK |
83 | return 0; |
84 | } | |
85 | path = xstrdup(name); | |
86 | } else { | |
87 | /* Search the include path */ | |
88 | for(n = 0; n < include_path.nvec; ++n) { | |
89 | byte_xasprintf(&path, "%s/%s", include_path.vec[n], name); | |
90 | if(access(path, O_RDONLY) == 0) | |
91 | break; | |
92 | } | |
93 | if(n >= include_path.nvec) { | |
f2d306b4 | 94 | if(report) |
2e9ba080 | 95 | disorder_error(0, "cannot find '%s' in search path", name); |
9faa7a88 RK |
96 | return 0; |
97 | } | |
98 | } | |
99 | return path; | |
100 | } | |
101 | ||
2dc2f478 | 102 | /*$ @include{TEMPLATE} |
b36be3a1 RK |
103 | * |
104 | * Includes TEMPLATE. | |
105 | * | |
106 | * TEMPLATE can be an absolute filename starting with a '/'; only the file with | |
107 | * exactly this name will be included. | |
108 | * | |
109 | * Alternatively it can be a relative filename, not starting with a '/'. In | |
110 | * this case the file will be searched for in the include path. When searching | |
111 | * paths, unreadable files are treated as if they do not exist (rather than | |
112 | * matching then producing an error). | |
f640bcb3 | 113 | * |
b36be3a1 RK |
114 | * If the name chosen ends ".tmpl" then the file will be expanded as a |
115 | * template. Anything else is included byte-for-byte without further | |
116 | * modification. | |
117 | * | |
118 | * Only regular files are allowed (no devices, sockets or name pipes). | |
f640bcb3 RK |
119 | */ |
120 | static int exp_include(int attribute((unused)) nargs, | |
b36be3a1 RK |
121 | char **args, |
122 | struct sink *output, | |
123 | void *u) { | |
9faa7a88 | 124 | const char *path; |
b36be3a1 RK |
125 | int fd, n; |
126 | char buffer[4096]; | |
127 | struct stat sb; | |
b36be3a1 | 128 | |
f2d306b4 | 129 | if(!(path = mx_find(args[0], 1/*report*/))) { |
f5fdc06f | 130 | if(sink_printf(output, "[[cannot find '%s']]", args[0]) < 0) |
dd0f422a | 131 | return 0; |
9faa7a88 | 132 | return 0; |
b36be3a1 RK |
133 | } |
134 | /* If it's a template expand it */ | |
135 | if(strlen(path) >= 5 && !strncmp(path + strlen(path) - 5, ".tmpl", 5)) | |
136 | return mx_expand_file(path, output, u); | |
137 | /* Read the raw file. As with mx_expand_file() we insist that the file is a | |
138 | * regular file. */ | |
139 | if((fd = open(path, O_RDONLY)) < 0) | |
2e9ba080 | 140 | disorder_fatal(errno, "error opening %s", path); |
b36be3a1 | 141 | if(fstat(fd, &sb) < 0) |
2e9ba080 | 142 | disorder_fatal(errno, "error statting %s", path); |
b36be3a1 | 143 | if(!S_ISREG(sb.st_mode)) |
2e9ba080 | 144 | disorder_fatal(0, "%s: not a regular file", path); |
b36be3a1 RK |
145 | while((n = read(fd, buffer, sizeof buffer)) > 0) { |
146 | if(sink_write(output, buffer, n) < 0) { | |
147 | xclose(fd); | |
148 | return -1; | |
149 | } | |
150 | } | |
151 | if(n < 0) | |
2e9ba080 | 152 | disorder_fatal(errno, "error reading %s", path); |
b36be3a1 RK |
153 | xclose(fd); |
154 | return 0; | |
f640bcb3 RK |
155 | } |
156 | ||
2dc2f478 | 157 | /*$ @include{COMMAND} |
f640bcb3 | 158 | * |
32a6d08f | 159 | * Executes COMMAND via the shell (using "sh \-c") and copies its |
f640bcb3 RK |
160 | * standard output to the template output. The shell command output |
161 | * is not expanded or modified in any other way. | |
162 | * | |
163 | * The shell command's standard error is copied to the error log. | |
164 | * | |
165 | * If the shell exits nonzero then this is reported to the error log | |
166 | * but otherwise no special action is taken. | |
167 | */ | |
168 | static int exp_shell(int attribute((unused)) nargs, | |
169 | char **args, | |
170 | struct sink *output, | |
171 | void attribute((unused)) *u) { | |
172 | int w, p[2], n; | |
173 | char buffer[4096]; | |
174 | pid_t pid; | |
175 | ||
176 | xpipe(p); | |
177 | if(!(pid = xfork())) { | |
178 | exitfn = _exit; | |
179 | xclose(p[0]); | |
180 | xdup2(p[1], 1); | |
181 | xclose(p[1]); | |
182 | execlp("sh", "sh", "-c", args[0], (char *)0); | |
2e9ba080 | 183 | disorder_fatal(errno, "error executing sh"); |
f640bcb3 RK |
184 | } |
185 | xclose(p[1]); | |
186 | while((n = read(p[0], buffer, sizeof buffer))) { | |
187 | if(n < 0) { | |
188 | if(errno == EINTR) | |
189 | continue; | |
190 | else | |
2e9ba080 | 191 | disorder_fatal(errno, "error reading from pipe"); |
f640bcb3 RK |
192 | } |
193 | if(output->write(output, buffer, n) < 0) | |
194 | return -1; | |
195 | } | |
196 | xclose(p[0]); | |
197 | while((n = waitpid(pid, &w, 0)) < 0 && errno == EINTR) | |
198 | ; | |
199 | if(n < 0) | |
2e9ba080 | 200 | disorder_fatal(errno, "error calling waitpid"); |
f640bcb3 | 201 | if(w) |
2e9ba080 | 202 | disorder_error(0, "shell command '%s' %s", args[0], wstat(w)); |
f640bcb3 RK |
203 | return 0; |
204 | } | |
205 | ||
2dc2f478 | 206 | /*$ @if{CONDITION}{IF-TRUE}{IF-FALSE} |
f640bcb3 RK |
207 | * |
208 | * If CONDITION is "true" then evaluates to IF-TRUE. Otherwise | |
209 | * evaluates to IF-FALSE. The IF-FALSE part is optional. | |
210 | */ | |
211 | static int exp_if(int nargs, | |
212 | const struct mx_node **args, | |
213 | struct sink *output, | |
214 | void *u) { | |
215 | char *s; | |
216 | int rc; | |
217 | ||
b36be3a1 | 218 | if((rc = mx_expandstr(args[0], &s, u, "argument #0 (CONDITION)"))) |
f640bcb3 RK |
219 | return rc; |
220 | if(mx_str2bool(s)) | |
221 | return mx_expand(args[1], output, u); | |
222 | else if(nargs > 2) | |
223 | return mx_expand(args[2], output, u); | |
224 | else | |
225 | return 0; | |
226 | } | |
227 | ||
2dc2f478 | 228 | /*$ @and{BRANCH}{BRANCH}... |
f640bcb3 RK |
229 | * |
230 | * Expands to "true" if all the branches are "true" otherwise to "false". If | |
231 | * there are no brances then the result is "true". Only as many branches as | |
232 | * necessary to compute the answer are evaluated (starting from the first one), | |
233 | * so if later branches have side effects they may not take place. | |
234 | */ | |
235 | static int exp_and(int nargs, | |
236 | const struct mx_node **args, | |
237 | struct sink *output, | |
238 | void *u) { | |
239 | int n, result = 1, rc; | |
b36be3a1 | 240 | char *s, *argname; |
f640bcb3 RK |
241 | |
242 | for(n = 0; n < nargs; ++n) { | |
b36be3a1 RK |
243 | byte_xasprintf(&argname, "argument #%d", n); |
244 | if((rc = mx_expandstr(args[n], &s, u, argname))) | |
f640bcb3 RK |
245 | return rc; |
246 | if(!mx_str2bool(s)) { | |
247 | result = 0; | |
248 | break; | |
249 | } | |
250 | } | |
b36be3a1 | 251 | return mx_bool_result(output, result); |
f640bcb3 RK |
252 | } |
253 | ||
2dc2f478 | 254 | /*$ @or{BRANCH}{BRANCH}... |
f640bcb3 RK |
255 | * |
256 | * Expands to "true" if any of the branches are "true" otherwise to "false". | |
257 | * If there are no brances then the result is "false". Only as many branches | |
258 | * as necessary to compute the answer are evaluated (starting from the first | |
259 | * one), so if later branches have side effects they may not take place. | |
260 | */ | |
261 | static int exp_or(int nargs, | |
262 | const struct mx_node **args, | |
263 | struct sink *output, | |
264 | void *u) { | |
265 | int n, result = 0, rc; | |
b36be3a1 | 266 | char *s, *argname; |
f640bcb3 RK |
267 | |
268 | for(n = 0; n < nargs; ++n) { | |
b36be3a1 RK |
269 | byte_xasprintf(&argname, "argument #%d", n); |
270 | if((rc = mx_expandstr(args[n], &s, u, argname))) | |
f640bcb3 RK |
271 | return rc; |
272 | if(mx_str2bool(s)) { | |
273 | result = 1; | |
274 | break; | |
275 | } | |
276 | } | |
b36be3a1 | 277 | return mx_bool_result(output, result); |
f640bcb3 RK |
278 | } |
279 | ||
2dc2f478 | 280 | /*$ @not{CONDITION} |
f640bcb3 RK |
281 | * |
282 | * Expands to "true" unless CONDITION is "true" in which case "false". | |
283 | */ | |
284 | static int exp_not(int attribute((unused)) nargs, | |
285 | char **args, | |
286 | struct sink *output, | |
287 | void attribute((unused)) *u) { | |
b36be3a1 | 288 | return mx_bool_result(output, !mx_str2bool(args[0])); |
f640bcb3 RK |
289 | } |
290 | ||
2dc2f478 | 291 | /*$ @#{...} |
f640bcb3 RK |
292 | * |
293 | * Expands to nothing. The argument(s) are not fully evaluated, and no side | |
294 | * effects occur. | |
295 | */ | |
296 | static int exp_comment(int attribute((unused)) nargs, | |
297 | const struct mx_node attribute((unused)) **args, | |
298 | struct sink attribute((unused)) *output, | |
299 | void attribute((unused)) *u) { | |
300 | return 0; | |
301 | } | |
302 | ||
2dc2f478 | 303 | /*$ @urlquote{STRING} |
f640bcb3 RK |
304 | * |
305 | * URL-quotes a string, i.e. replaces any characters not safe to use unquoted | |
306 | * in a URL with %-encoded form. | |
307 | */ | |
308 | static int exp_urlquote(int attribute((unused)) nargs, | |
309 | char **args, | |
310 | struct sink *output, | |
311 | void attribute((unused)) *u) { | |
312 | if(sink_writes(output, urlencodestring(args[0])) < 0) | |
313 | return -1; | |
314 | else | |
315 | return 0; | |
316 | } | |
317 | ||
2dc2f478 | 318 | /*$ @eq{S1}{S2}... |
b36be3a1 RK |
319 | * |
320 | * Expands to "true" if all the arguments are identical, otherwise to "false" | |
321 | * (i.e. if any pair of arguments differs). | |
322 | * | |
323 | * If there are no arguments then expands to "true". Evaluates all arguments | |
324 | * (with their side effects) even if that's not strictly necessary to discover | |
325 | * the result. | |
326 | */ | |
327 | static int exp_eq(int nargs, | |
328 | char **args, | |
329 | struct sink *output, | |
330 | void attribute((unused)) *u) { | |
331 | int n, result = 1; | |
332 | ||
333 | for(n = 1; n < nargs; ++n) { | |
334 | if(strcmp(args[n], args[0])) { | |
335 | result = 0; | |
336 | break; | |
337 | } | |
338 | } | |
339 | return mx_bool_result(output, result); | |
340 | } | |
341 | ||
2dc2f478 | 342 | /*$ @ne{S1}{S2}... |
b36be3a1 RK |
343 | * |
344 | * Expands to "true" if all of the arguments differ from one another, otherwise | |
345 | * to "false" (i.e. if any value appears more than once). | |
346 | * | |
347 | * If there are no arguments then expands to "true". Evaluates all arguments | |
348 | * (with their side effects) even if that's not strictly necessary to discover | |
349 | * the result. | |
350 | */ | |
351 | static int exp_ne(int nargs, | |
352 | char **args, | |
353 | struct sink *output, | |
354 | void attribute((unused))*u) { | |
355 | hash *h = hash_new(sizeof (char *)); | |
356 | int n, result = 1; | |
357 | ||
358 | for(n = 0; n < nargs; ++n) | |
359 | if(hash_add(h, args[n], "", HASH_INSERT)) { | |
360 | result = 0; | |
361 | break; | |
362 | } | |
363 | return mx_bool_result(output, result); | |
364 | } | |
365 | ||
2dc2f478 | 366 | /*$ @discard{...} |
b36be3a1 RK |
367 | * |
368 | * Expands to nothing. Unlike the comment expansion @#{...}, side effects of | |
369 | * arguments are not suppressed. So this can be used to surround a collection | |
370 | * of macro definitions with whitespace, free text commentary, etc. | |
371 | */ | |
372 | static int exp_discard(int attribute((unused)) nargs, | |
373 | char attribute((unused)) **args, | |
374 | struct sink attribute((unused)) *output, | |
375 | void attribute((unused)) *u) { | |
376 | return 0; | |
377 | } | |
378 | ||
2dc2f478 | 379 | /*$ @define{NAME}{ARG1 ARG2...}{DEFINITION} |
b36be3a1 RK |
380 | * |
381 | * Define a macro. The macro will be called NAME and will act like an | |
382 | * expansion. When it is expanded, the expansion is replaced by DEFINITION, | |
383 | * with each occurence of @ARG1@ etc replaced by the parameters to the | |
384 | * expansion. | |
385 | */ | |
386 | static int exp_define(int attribute((unused)) nargs, | |
387 | const struct mx_node **args, | |
388 | struct sink attribute((unused)) *output, | |
389 | void attribute((unused)) *u) { | |
390 | char **as, *name, *argnames; | |
391 | int rc, nas; | |
392 | ||
393 | if((rc = mx_expandstr(args[0], &name, u, "argument #0 (NAME)"))) | |
394 | return rc; | |
395 | if((rc = mx_expandstr(args[1], &argnames, u, "argument #1 (ARGS)"))) | |
396 | return rc; | |
397 | as = split(argnames, &nas, 0, 0, 0); | |
398 | mx_register_macro(name, nas, as, args[2]); | |
399 | return 0; | |
400 | } | |
401 | ||
59cf25c4 | 402 | /*$ @basename{PATH} |
2257512d RK |
403 | * |
404 | * Expands to the UNQUOTED basename of PATH. | |
405 | */ | |
406 | static int exp_basename(int attribute((unused)) nargs, | |
407 | char **args, | |
408 | struct sink attribute((unused)) *output, | |
409 | void attribute((unused)) *u) { | |
410 | return sink_writes(output, d_basename(args[0])) < 0 ? -1 : 0; | |
411 | } | |
412 | ||
59cf25c4 | 413 | /*$ @dirname{PATH} |
2257512d RK |
414 | * |
415 | * Expands to the UNQUOTED directory name of PATH. | |
416 | */ | |
417 | static int exp_dirname(int attribute((unused)) nargs, | |
418 | char **args, | |
419 | struct sink attribute((unused)) *output, | |
420 | void attribute((unused)) *u) { | |
421 | return sink_writes(output, d_dirname(args[0])) < 0 ? -1 : 0; | |
422 | } | |
423 | ||
59cf25c4 | 424 | /*$ @q{STRING} |
721d8bf4 RK |
425 | * |
426 | * Expands to STRING. | |
427 | */ | |
428 | static int exp_q(int attribute((unused)) nargs, | |
429 | char **args, | |
430 | struct sink attribute((unused)) *output, | |
431 | void attribute((unused)) *u) { | |
432 | return sink_writes(output, args[0]) < 0 ? -1 : 0; | |
433 | } | |
434 | ||
dd0f422a | 435 | /** @brief Register built-in expansions */ |
f640bcb3 | 436 | void mx_register_builtin(void) { |
2257512d RK |
437 | mx_register("basename", 1, 1, exp_basename); |
438 | mx_register("dirname", 1, 1, exp_dirname); | |
b36be3a1 RK |
439 | mx_register("discard", 0, INT_MAX, exp_discard); |
440 | mx_register("eq", 0, INT_MAX, exp_eq); | |
f640bcb3 | 441 | mx_register("include", 1, 1, exp_include); |
b36be3a1 | 442 | mx_register("ne", 0, INT_MAX, exp_ne); |
f640bcb3 | 443 | mx_register("not", 1, 1, exp_not); |
b36be3a1 | 444 | mx_register("shell", 1, 1, exp_shell); |
f640bcb3 | 445 | mx_register("urlquote", 1, 1, exp_urlquote); |
721d8bf4 | 446 | mx_register("q", 1, 1, exp_q); |
2257512d RK |
447 | mx_register_magic("#", 0, INT_MAX, exp_comment); |
448 | mx_register_magic("and", 0, INT_MAX, exp_and); | |
449 | mx_register_magic("define", 3, 3, exp_define); | |
450 | mx_register_magic("if", 2, 3, exp_if); | |
451 | mx_register_magic("or", 0, INT_MAX, exp_or); | |
f640bcb3 RK |
452 | } |
453 | ||
dd0f422a RK |
454 | /** @brief Add a directory to the search path |
455 | * @param s Directory to add | |
456 | */ | |
457 | void mx_search_path(const char *s) { | |
458 | vector_append(&include_path, xstrdup(s)); | |
459 | } | |
460 | ||
f640bcb3 RK |
461 | /* |
462 | Local Variables: | |
463 | c-basic-offset:2 | |
464 | comment-column:40 | |
465 | fill-column:79 | |
466 | indent-tabs-mode:nil | |
467 | End: | |
468 | */ |