config.c revision 156702
1/*- 2 * Copyright (c) 2003-2006, Maxime Henrion <mux@FreeBSD.org> 3 * All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions 7 * are met: 8 * 1. Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * 2. Redistributions in binary form must reproduce the above copyright 11 * notice, this list of conditions and the following disclaimer in the 12 * documentation and/or other materials provided with the distribution. 13 * 14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 15 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 18 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 20 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 21 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 22 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 23 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 24 * SUCH DAMAGE. 25 * 26 * $FreeBSD: head/contrib/csup/config.c 156702 2006-03-14 03:51:13Z mux $ 27 */ 28 29#include <sys/types.h> 30#include <sys/stat.h> 31 32#include <errno.h> 33#include <fcntl.h> 34#include <stdio.h> 35#include <stdlib.h> 36#include <string.h> 37#include <unistd.h> 38 39#include "config.h" 40#include "globtree.h" 41#include "keyword.h" 42#include "misc.h" 43#include "parse.h" 44#include "stream.h" 45#include "token.h" 46 47static int config_parse_refusefiles(struct coll *); 48static int config_parse_refusefile(struct coll *, char *); 49 50extern FILE *yyin; 51 52/* These are globals because I can't think of a better way with yacc. */ 53static STAILQ_HEAD(, coll) colls; 54static struct coll *cur_coll; 55static struct coll *defaults; 56static struct coll *ovcoll; 57static int ovmask; 58static const char *cfgfile; 59 60/* 61 * Extract all the configuration information from the config 62 * file and some command line parameters. 63 */ 64struct config * 65config_init(const char *file, struct coll *override, int overridemask) 66{ 67 struct config *config; 68 struct coll *coll; 69 size_t slen; 70 char *prefix; 71 int error; 72 mode_t mask; 73 74 config = xmalloc(sizeof(struct config)); 75 memset(config, 0, sizeof(struct config)); 76 STAILQ_INIT(&colls); 77 78 defaults = coll_new(NULL); 79 /* Set the default umask. */ 80 mask = umask(0); 81 umask(mask); 82 defaults->co_umask = mask; 83 ovcoll = override; 84 ovmask = overridemask; 85 86 /* Extract a list of collections from the configuration file. */ 87 cur_coll = coll_new(defaults); 88 yyin = fopen(file, "r"); 89 if (yyin == NULL) { 90 lprintf(-1, "Cannot open \"%s\": %s\n", file, strerror(errno)); 91 goto bad; 92 } 93 cfgfile = file; 94 error = yyparse(); 95 fclose(yyin); 96 if (error) 97 goto bad; 98 99 memcpy(&config->colls, &colls, sizeof(colls)); 100 if (STAILQ_EMPTY(&config->colls)) { 101 lprintf(-1, "Empty supfile\n"); 102 goto bad; 103 } 104 105 /* Fixup the list of collections. */ 106 STAILQ_FOREACH(coll, &config->colls, co_next) { 107 if (coll->co_base == NULL) 108 coll->co_base = xstrdup("/usr/local/etc/cvsup"); 109 if (coll->co_colldir == NULL) 110 coll->co_colldir = "sup"; 111 if (coll->co_prefix == NULL) { 112 coll->co_prefix = xstrdup(coll->co_base); 113 /* 114 * If prefix is not an absolute pathname, it is 115 * interpreted relative to base. 116 */ 117 } else if (coll->co_prefix[0] != '/') { 118 slen = strlen(coll->co_base); 119 if (slen > 0 && coll->co_base[slen - 1] != '/') 120 xasprintf(&prefix, "%s/%s", coll->co_base, 121 coll->co_prefix); 122 else 123 xasprintf(&prefix, "%s%s", coll->co_base, 124 coll->co_prefix); 125 free(coll->co_prefix); 126 coll->co_prefix = prefix; 127 } 128 coll->co_prefixlen = strlen(coll->co_prefix); 129 /* Determine whether to checksum RCS files or not. */ 130 if (coll->co_options & CO_EXACTRCS) 131 coll->co_options |= CO_CHECKRCS; 132 else 133 coll->co_options &= ~CO_CHECKRCS; 134 /* In recent versions, we always try to set the file modes. */ 135 coll->co_options |= CO_SETMODE; 136 /* XXX We don't support the rsync updating algorithm yet. */ 137 coll->co_options |= CO_NORSYNC; 138 error = config_parse_refusefiles(coll); 139 if (error) 140 goto bad; 141 } 142 143 coll_free(cur_coll); 144 coll_free(defaults); 145 config->host = STAILQ_FIRST(&config->colls)->co_host; 146 return (config); 147bad: 148 coll_free(cur_coll); 149 coll_free(defaults); 150 config_free(config); 151 return (NULL); 152} 153 154int 155config_checkcolls(struct config *config) 156{ 157 char linkname[4]; 158 struct stat sb; 159 struct coll *coll; 160 int error, numvalid, ret; 161 162 numvalid = 0; 163 STAILQ_FOREACH(coll, &config->colls, co_next) { 164 error = stat(coll->co_prefix, &sb); 165 if (error || !S_ISDIR(sb.st_mode)) { 166 /* Skip this collection, and warn about it unless its 167 prefix is a symbolic link pointing to "SKIP". */ 168 coll->co_options |= CO_SKIP; 169 ret = readlink(coll->co_prefix, linkname, 170 sizeof(linkname)); 171 if (ret != 4 || memcmp(linkname, "SKIP", 4) != 0) { 172 lprintf(-1,"Nonexistent prefix \"%s\" for " 173 "%s/%s\n", coll->co_prefix, coll->co_name, 174 coll->co_release); 175 } 176 continue; 177 } 178 numvalid++; 179 } 180 return (numvalid); 181} 182 183static int 184config_parse_refusefiles(struct coll *coll) 185{ 186 char *collstem, *suffix, *supdir, *path; 187 int error; 188 189 if (coll->co_colldir[0] == '/') 190 supdir = xstrdup(coll->co_colldir); 191 else 192 xasprintf(&supdir, "%s/%s", coll->co_base, coll->co_colldir); 193 194 /* First, the global refuse file that applies to all collections. */ 195 xasprintf(&path, "%s/refuse", supdir); 196 error = config_parse_refusefile(coll, path); 197 free(path); 198 if (error) { 199 free(supdir); 200 return (error); 201 } 202 203 /* Next the per-collection refuse files that applies to all release/tag 204 combinations. */ 205 xasprintf(&collstem, "%s/%s/refuse", supdir, coll->co_name); 206 free(supdir); 207 error = config_parse_refusefile(coll, collstem); 208 if (error) { 209 free(collstem); 210 return (error); 211 } 212 213 /* Finally, the per-release and per-tag refuse file. */ 214 suffix = coll_statussuffix(coll); 215 if (suffix != NULL) { 216 xasprintf(&path, "%s%s", collstem, suffix); 217 free(suffix); 218 error = config_parse_refusefile(coll, path); 219 free(path); 220 } 221 free(collstem); 222 return (error); 223} 224 225/* 226 * Parses a "refuse" file, and records the relevant information in 227 * coll->co_refusals. If the file does not exist, it is silently 228 * ignored. 229 */ 230static int 231config_parse_refusefile(struct coll *coll, char *path) 232{ 233 struct stream *rd; 234 char *cp, *line, *pat; 235 236 rd = stream_open_file(path, O_RDONLY); 237 if (rd == NULL) 238 return (0); 239 while ((line = stream_getln(rd, NULL)) != NULL) { 240 pat = line; 241 for (;;) { 242 /* Trim leading whitespace. */ 243 pat += strspn(pat, " \t"); 244 if (pat[0] == '\0') 245 break; 246 cp = strpbrk(pat, " \t"); 247 if (cp != NULL) 248 *cp = '\0'; 249 pattlist_add(coll->co_refusals, pat); 250 if (cp == NULL) 251 break; 252 pat = cp + 1; 253 } 254 } 255 if (!stream_eof(rd)) { 256 stream_close(rd); 257 lprintf(-1, "Read failure from \"%s\": %s\n", path, 258 strerror(errno)); 259 return (-1); 260 } 261 stream_close(rd); 262 return (0); 263} 264 265void 266config_free(struct config *config) 267{ 268 struct coll *coll; 269 270 while (!STAILQ_EMPTY(&config->colls)) { 271 coll = STAILQ_FIRST(&config->colls); 272 STAILQ_REMOVE_HEAD(&config->colls, co_next); 273 coll_free(coll); 274 } 275 if (config->server != NULL) 276 stream_close(config->server); 277 if (config->laddr != NULL) 278 free(config->laddr); 279 free(config); 280} 281 282/* Create a new collection, inheriting options from the default collection. */ 283struct coll * 284coll_new(struct coll *def) 285{ 286 struct coll *new; 287 288 new = xmalloc(sizeof(struct coll)); 289 memset(new, 0, sizeof(struct coll)); 290 if (def != NULL) { 291 new->co_options = def->co_options; 292 new->co_umask = def->co_umask; 293 if (def->co_host != NULL) 294 new->co_host = xstrdup(def->co_host); 295 if (def->co_base != NULL) 296 new->co_base = xstrdup(def->co_base); 297 if (def->co_date != NULL) 298 new->co_date = xstrdup(def->co_date); 299 if (def->co_prefix != NULL) 300 new->co_prefix = xstrdup(def->co_prefix); 301 if (def->co_release != NULL) 302 new->co_release = xstrdup(def->co_release); 303 if (def->co_tag != NULL) 304 new->co_tag = xstrdup(def->co_tag); 305 if (def->co_listsuffix != NULL) 306 new->co_listsuffix = xstrdup(def->co_listsuffix); 307 } else { 308 new->co_tag = xstrdup("."); 309 new->co_date = xstrdup("."); 310 } 311 new->co_keyword = keyword_new(); 312 new->co_accepts = pattlist_new(); 313 new->co_refusals = pattlist_new(); 314 new->co_attrignore = FA_DEV | FA_INODE; 315 return (new); 316} 317 318void 319coll_override(struct coll *coll, struct coll *from, int mask) 320{ 321 size_t i; 322 int newoptions, oldoptions; 323 324 newoptions = from->co_options & mask; 325 oldoptions = coll->co_options & (CO_MASK & ~mask); 326 327 if (from->co_release != NULL) { 328 if (coll->co_release != NULL) 329 free(coll->co_release); 330 coll->co_release = xstrdup(from->co_release); 331 } 332 if (from->co_host != NULL) { 333 if (coll->co_host != NULL) 334 free(coll->co_host); 335 coll->co_host = xstrdup(from->co_host); 336 } 337 if (from->co_base != NULL) { 338 if (coll->co_base != NULL) 339 free(coll->co_base); 340 coll->co_base = xstrdup(from->co_base); 341 } 342 if (from->co_colldir != NULL) 343 coll->co_colldir = from->co_colldir; 344 if (from->co_prefix != NULL) { 345 if (coll->co_prefix != NULL) 346 free(coll->co_prefix); 347 coll->co_prefix = xstrdup(from->co_prefix); 348 } 349 if (newoptions & CO_CHECKOUTMODE) { 350 if (from->co_tag != NULL) { 351 if (coll->co_tag != NULL) 352 free(coll->co_tag); 353 coll->co_tag = xstrdup(from->co_tag); 354 } 355 if (from->co_date != NULL) { 356 if (coll->co_date != NULL) 357 free(coll->co_date); 358 coll->co_date = xstrdup(from->co_date); 359 } 360 } 361 if (from->co_listsuffix != NULL) { 362 if (coll->co_listsuffix != NULL) 363 free(coll->co_listsuffix); 364 coll->co_listsuffix = xstrdup(from->co_listsuffix); 365 } 366 for (i = 0; i < pattlist_size(from->co_accepts); i++) { 367 pattlist_add(coll->co_accepts, 368 pattlist_get(from->co_accepts, i)); 369 } 370 for (i = 0; i < pattlist_size(from->co_refusals); i++) { 371 pattlist_add(coll->co_refusals, 372 pattlist_get(from->co_refusals, i)); 373 } 374 coll->co_options = oldoptions | newoptions; 375} 376 377char * 378coll_statussuffix(struct coll *coll) 379{ 380 const char *tag; 381 char *suffix; 382 383 if (coll->co_listsuffix != NULL) { 384 xasprintf(&suffix, ".%s", coll->co_listsuffix); 385 } else if (coll->co_options & CO_USERELSUFFIX) { 386 if (coll->co_tag == NULL) 387 tag = "."; 388 else 389 tag = coll->co_tag; 390 if (coll->co_release != NULL) { 391 if (coll->co_options & CO_CHECKOUTMODE) { 392 xasprintf(&suffix, ".%s:%s", 393 coll->co_release, tag); 394 } else { 395 xasprintf(&suffix, ".%s", coll->co_release); 396 } 397 } else if (coll->co_options & CO_CHECKOUTMODE) { 398 xasprintf(&suffix, ":%s", tag); 399 } 400 } else 401 suffix = NULL; 402 return (suffix); 403} 404 405char * 406coll_statuspath(struct coll *coll) 407{ 408 char *path, *suffix; 409 410 suffix = coll_statussuffix(coll); 411 if (suffix != NULL) { 412 if (coll->co_colldir[0] == '/') 413 xasprintf(&path, "%s/%s/checkouts%s", coll->co_colldir, 414 coll->co_name, suffix); 415 else 416 xasprintf(&path, "%s/%s/%s/checkouts%s", coll->co_base, 417 coll->co_colldir, coll->co_name, suffix); 418 } else { 419 if (coll->co_colldir[0] == '/') 420 xasprintf(&path, "%s/%s/checkouts", coll->co_colldir, 421 coll->co_name); 422 else 423 xasprintf(&path, "%s/%s/%s/checkouts", coll->co_base, 424 coll->co_colldir, coll->co_name); 425 } 426 free(suffix); 427 return (path); 428} 429 430void 431coll_add(char *name) 432{ 433 struct coll *coll; 434 435 cur_coll->co_name = name; 436 coll_override(cur_coll, ovcoll, ovmask); 437 if (cur_coll->co_release == NULL) { 438 lprintf(-1, "Release not specified for collection " 439 "\"%s\"\n", cur_coll->co_name); 440 exit(1); 441 } 442 if (cur_coll->co_host == NULL) { 443 lprintf(-1, "Host not specified for collection " 444 "\"%s\"\n", cur_coll->co_name); 445 exit(1); 446 } 447 if (!(cur_coll->co_options & CO_CHECKOUTMODE)) { 448 lprintf(-1, "Client only supports checkout mode\n"); 449 exit(1); 450 } 451 if (!STAILQ_EMPTY(&colls)) { 452 coll = STAILQ_LAST(&colls, coll, co_next); 453 if (strcmp(coll->co_host, cur_coll->co_host) != 0) { 454 lprintf(-1, "All \"host\" fields in the supfile " 455 "must be the same\n"); 456 exit(1); 457 } 458 } 459 STAILQ_INSERT_TAIL(&colls, cur_coll, co_next); 460 cur_coll = coll_new(defaults); 461} 462 463void 464coll_free(struct coll *coll) 465{ 466 467 if (coll == NULL) 468 return; 469 if (coll->co_host != NULL) 470 free(coll->co_host); 471 if (coll->co_base != NULL) 472 free(coll->co_base); 473 if (coll->co_date != NULL) 474 free(coll->co_date); 475 if (coll->co_prefix != NULL) 476 free(coll->co_prefix); 477 if (coll->co_release != NULL) 478 free(coll->co_release); 479 if (coll->co_tag != NULL) 480 free(coll->co_tag); 481 if (coll->co_cvsroot != NULL) 482 free(coll->co_cvsroot); 483 if (coll->co_name != NULL) 484 free(coll->co_name); 485 if (coll->co_listsuffix != NULL) 486 free(coll->co_listsuffix); 487 keyword_free(coll->co_keyword); 488 if (coll->co_dirfilter != NULL) 489 globtree_free(coll->co_dirfilter); 490 if (coll->co_dirfilter != NULL) 491 globtree_free(coll->co_filefilter); 492 if (coll->co_norsync != NULL) 493 globtree_free(coll->co_norsync); 494 if (coll->co_accepts != NULL) 495 pattlist_free(coll->co_accepts); 496 if (coll->co_refusals != NULL) 497 pattlist_free(coll->co_refusals); 498 free(coll); 499} 500 501void 502coll_setopt(int opt, char *value) 503{ 504 struct coll *coll; 505 int error, mask; 506 507 coll = cur_coll; 508 switch (opt) { 509 case PT_HOST: 510 if (coll->co_host != NULL) 511 free(coll->co_host); 512 coll->co_host = value; 513 break; 514 case PT_BASE: 515 if (coll->co_base != NULL) 516 free(coll->co_base); 517 coll->co_base = value; 518 break; 519 case PT_DATE: 520 if (coll->co_date != NULL) 521 free(coll->co_date); 522 coll->co_date = value; 523 coll->co_options |= CO_CHECKOUTMODE; 524 break; 525 case PT_PREFIX: 526 if (coll->co_prefix != NULL) 527 free(coll->co_prefix); 528 coll->co_prefix = value; 529 break; 530 case PT_RELEASE: 531 if (coll->co_release != NULL) 532 free(coll->co_release); 533 coll->co_release = value; 534 break; 535 case PT_TAG: 536 if (coll->co_tag != NULL) 537 free(coll->co_tag); 538 coll->co_tag = value; 539 coll->co_options |= CO_CHECKOUTMODE; 540 break; 541 case PT_LIST: 542 if (strchr(value, '/') != NULL) { 543 lprintf(-1, "Parse error in \"%s\": \"list\" suffix " 544 "must not contain slashes\n", cfgfile); 545 exit(1); 546 } 547 if (coll->co_listsuffix != NULL) 548 free(coll->co_listsuffix); 549 coll->co_listsuffix = value; 550 break; 551 case PT_UMASK: 552 error = asciitoint(value, &mask, 8); 553 free(value); 554 if (error) { 555 lprintf(-1, "Parse error in \"%s\": Invalid " 556 "umask value\n", cfgfile); 557 exit(1); 558 } 559 coll->co_umask = mask; 560 break; 561 case PT_USE_REL_SUFFIX: 562 coll->co_options |= CO_USERELSUFFIX; 563 break; 564 case PT_DELETE: 565 coll->co_options |= CO_DELETE | CO_EXACTRCS; 566 break; 567 case PT_COMPRESS: 568 coll->co_options |= CO_COMPRESS; 569 break; 570 case PT_NORSYNC: 571 coll->co_options |= CO_NORSYNC; 572 break; 573 } 574} 575 576/* Set "coll" as being the default collection. */ 577void 578coll_setdef(void) 579{ 580 581 coll_free(defaults); 582 defaults = cur_coll; 583 cur_coll = coll_new(defaults); 584} 585