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