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