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