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$ 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 coll->co_options |= CO_NORSYNC; 137 error = config_parse_refusefiles(coll); 138 if (error) 139 goto bad; 140 } 141 142 coll_free(cur_coll); 143 coll_free(defaults); 144 config->host = STAILQ_FIRST(&config->colls)->co_host; 145 return (config); 146bad: 147 coll_free(cur_coll); 148 coll_free(defaults); 149 config_free(config); 150 return (NULL); 151} 152 153int 154config_checkcolls(struct config *config) 155{ 156 char linkname[4]; 157 struct stat sb; 158 struct coll *coll; 159 int error, numvalid, ret; 160 161 numvalid = 0; 162 STAILQ_FOREACH(coll, &config->colls, co_next) { 163 error = stat(coll->co_prefix, &sb); 164 if (error || !S_ISDIR(sb.st_mode)) { 165 /* Skip this collection, and warn about it unless its 166 prefix is a symbolic link pointing to "SKIP". */ 167 coll->co_options |= CO_SKIP; 168 ret = readlink(coll->co_prefix, linkname, 169 sizeof(linkname)); 170 if (ret != 4 || memcmp(linkname, "SKIP", 4) != 0) { 171 lprintf(-1,"Nonexistent prefix \"%s\" for " 172 "%s/%s\n", coll->co_prefix, coll->co_name, 173 coll->co_release); 174 } 175 continue; 176 } 177 numvalid++; 178 } 179 return (numvalid); 180} 181 182static int 183config_parse_refusefiles(struct coll *coll) 184{ 185 char *collstem, *suffix, *supdir, *path; 186 int error; 187 188 if (coll->co_colldir[0] == '/') 189 supdir = xstrdup(coll->co_colldir); 190 else 191 xasprintf(&supdir, "%s/%s", coll->co_base, coll->co_colldir); 192 193 /* First, the global refuse file that applies to all collections. */ 194 xasprintf(&path, "%s/refuse", supdir); 195 error = config_parse_refusefile(coll, path); 196 free(path); 197 if (error) { 198 free(supdir); 199 return (error); 200 } 201 202 /* Next the per-collection refuse files that applies to all release/tag 203 combinations. */ 204 xasprintf(&collstem, "%s/%s/refuse", supdir, coll->co_name); 205 free(supdir); 206 error = config_parse_refusefile(coll, collstem); 207 if (error) { 208 free(collstem); 209 return (error); 210 } 211 212 /* Finally, the per-release and per-tag refuse file. */ 213 suffix = coll_statussuffix(coll); 214 if (suffix != NULL) { 215 xasprintf(&path, "%s%s", collstem, suffix); 216 free(suffix); 217 error = config_parse_refusefile(coll, path); 218 free(path); 219 } 220 free(collstem); 221 return (error); 222} 223 224/* 225 * Parses a "refuse" file, and records the relevant information in 226 * coll->co_refusals. If the file does not exist, it is silently 227 * ignored. 228 */ 229static int 230config_parse_refusefile(struct coll *coll, char *path) 231{ 232 struct stream *rd; 233 char *cp, *line, *pat; 234 235 rd = stream_open_file(path, O_RDONLY); 236 if (rd == NULL) 237 return (0); 238 while ((line = stream_getln(rd, NULL)) != NULL) { 239 pat = line; 240 for (;;) { 241 /* Trim leading whitespace. */ 242 pat += strspn(pat, " \t"); 243 if (pat[0] == '\0') 244 break; 245 cp = strpbrk(pat, " \t"); 246 if (cp != NULL) 247 *cp = '\0'; 248 pattlist_add(coll->co_refusals, pat); 249 if (cp == NULL) 250 break; 251 pat = cp + 1; 252 } 253 } 254 if (!stream_eof(rd)) { 255 stream_close(rd); 256 lprintf(-1, "Read failure from \"%s\": %s\n", path, 257 strerror(errno)); 258 return (-1); 259 } 260 stream_close(rd); 261 return (0); 262} 263 264void 265config_free(struct config *config) 266{ 267 struct coll *coll; 268 269 while (!STAILQ_EMPTY(&config->colls)) { 270 coll = STAILQ_FIRST(&config->colls); 271 STAILQ_REMOVE_HEAD(&config->colls, co_next); 272 coll_free(coll); 273 } 274 if (config->server != NULL) 275 stream_close(config->server); 276 if (config->laddr != NULL) 277 free(config->laddr); 278 free(config); 279} 280 281/* Create a new collection, inheriting options from the default collection. */ 282struct coll * 283coll_new(struct coll *def) 284{ 285 struct coll *new; 286 287 new = xmalloc(sizeof(struct coll)); 288 memset(new, 0, sizeof(struct coll)); 289 if (def != NULL) { 290 new->co_options = def->co_options; 291 new->co_umask = def->co_umask; 292 if (def->co_host != NULL) 293 new->co_host = xstrdup(def->co_host); 294 if (def->co_base != NULL) 295 new->co_base = xstrdup(def->co_base); 296 if (def->co_date != NULL) 297 new->co_date = xstrdup(def->co_date); 298 if (def->co_prefix != NULL) 299 new->co_prefix = xstrdup(def->co_prefix); 300 if (def->co_release != NULL) 301 new->co_release = xstrdup(def->co_release); 302 if (def->co_tag != NULL) 303 new->co_tag = xstrdup(def->co_tag); 304 if (def->co_listsuffix != NULL) 305 new->co_listsuffix = xstrdup(def->co_listsuffix); 306 } else { 307 new->co_tag = xstrdup("."); 308 new->co_date = xstrdup("."); 309 } 310 new->co_keyword = keyword_new(); 311 new->co_accepts = pattlist_new(); 312 new->co_refusals = pattlist_new(); 313 new->co_attrignore = FA_DEV | FA_INODE; 314 return (new); 315} 316 317void 318coll_override(struct coll *coll, struct coll *from, int mask) 319{ 320 size_t i; 321 int newoptions, oldoptions; 322 323 newoptions = from->co_options & mask; 324 oldoptions = coll->co_options & (CO_MASK & ~mask); 325 326 if (from->co_release != NULL) { 327 if (coll->co_release != NULL) 328 free(coll->co_release); 329 coll->co_release = xstrdup(from->co_release); 330 } 331 if (from->co_host != NULL) { 332 if (coll->co_host != NULL) 333 free(coll->co_host); 334 coll->co_host = xstrdup(from->co_host); 335 } 336 if (from->co_base != NULL) { 337 if (coll->co_base != NULL) 338 free(coll->co_base); 339 coll->co_base = xstrdup(from->co_base); 340 } 341 if (from->co_colldir != NULL) 342 coll->co_colldir = from->co_colldir; 343 if (from->co_prefix != NULL) { 344 if (coll->co_prefix != NULL) 345 free(coll->co_prefix); 346 coll->co_prefix = xstrdup(from->co_prefix); 347 } 348 if (newoptions & CO_CHECKOUTMODE) { 349 if (from->co_tag != NULL) { 350 if (coll->co_tag != NULL) 351 free(coll->co_tag); 352 coll->co_tag = xstrdup(from->co_tag); 353 } 354 if (from->co_date != NULL) { 355 if (coll->co_date != NULL) 356 free(coll->co_date); 357 coll->co_date = xstrdup(from->co_date); 358 } 359 } 360 if (from->co_listsuffix != NULL) { 361 if (coll->co_listsuffix != NULL) 362 free(coll->co_listsuffix); 363 coll->co_listsuffix = xstrdup(from->co_listsuffix); 364 } 365 for (i = 0; i < pattlist_size(from->co_accepts); i++) { 366 pattlist_add(coll->co_accepts, 367 pattlist_get(from->co_accepts, i)); 368 } 369 for (i = 0; i < pattlist_size(from->co_refusals); i++) { 370 pattlist_add(coll->co_refusals, 371 pattlist_get(from->co_refusals, i)); 372 } 373 coll->co_options = oldoptions | newoptions; 374} 375 376char * 377coll_statussuffix(struct coll *coll) 378{ 379 const char *tag; 380 char *suffix; 381 382 if (coll->co_listsuffix != NULL) { 383 xasprintf(&suffix, ".%s", coll->co_listsuffix); 384 } else if (coll->co_options & CO_USERELSUFFIX) { 385 if (coll->co_tag == NULL) 386 tag = "."; 387 else 388 tag = coll->co_tag; 389 if (coll->co_release != NULL) { 390 if (coll->co_options & CO_CHECKOUTMODE) { 391 xasprintf(&suffix, ".%s:%s", 392 coll->co_release, tag); 393 } else { 394 xasprintf(&suffix, ".%s", coll->co_release); 395 } 396 } else if (coll->co_options & CO_CHECKOUTMODE) { 397 xasprintf(&suffix, ":%s", tag); 398 } 399 } else 400 suffix = NULL; 401 return (suffix); 402} 403 404char * 405coll_statuspath(struct coll *coll) 406{ 407 char *path, *suffix; 408 409 suffix = coll_statussuffix(coll); 410 if (suffix != NULL) { 411 if (coll->co_colldir[0] == '/') 412 xasprintf(&path, "%s/%s/checkouts%s", coll->co_colldir, 413 coll->co_name, suffix); 414 else 415 xasprintf(&path, "%s/%s/%s/checkouts%s", coll->co_base, 416 coll->co_colldir, coll->co_name, suffix); 417 } else { 418 if (coll->co_colldir[0] == '/') 419 xasprintf(&path, "%s/%s/checkouts", coll->co_colldir, 420 coll->co_name); 421 else 422 xasprintf(&path, "%s/%s/%s/checkouts", coll->co_base, 423 coll->co_colldir, coll->co_name); 424 } 425 free(suffix); 426 return (path); 427} 428 429void 430coll_add(char *name) 431{ 432 struct coll *coll; 433 434 cur_coll->co_name = name; 435 coll_override(cur_coll, ovcoll, ovmask); 436 if (cur_coll->co_release == NULL) { 437 lprintf(-1, "Release not specified for collection " 438 "\"%s\"\n", cur_coll->co_name); 439 exit(1); 440 } 441 if (cur_coll->co_host == NULL) { 442 lprintf(-1, "Host not specified for collection " 443 "\"%s\"\n", cur_coll->co_name); 444 exit(1); 445 } 446 if (!STAILQ_EMPTY(&colls)) { 447 coll = STAILQ_LAST(&colls, coll, co_next); 448 if (strcmp(coll->co_host, cur_coll->co_host) != 0) { 449 lprintf(-1, "All \"host\" fields in the supfile " 450 "must be the same\n"); 451 exit(1); 452 } 453 } 454 STAILQ_INSERT_TAIL(&colls, cur_coll, co_next); 455 cur_coll = coll_new(defaults); 456} 457 458void 459coll_free(struct coll *coll) 460{ 461 462 if (coll == NULL) 463 return; 464 if (coll->co_host != NULL) 465 free(coll->co_host); 466 if (coll->co_base != NULL) 467 free(coll->co_base); 468 if (coll->co_date != NULL) 469 free(coll->co_date); 470 if (coll->co_prefix != NULL) 471 free(coll->co_prefix); 472 if (coll->co_release != NULL) 473 free(coll->co_release); 474 if (coll->co_tag != NULL) 475 free(coll->co_tag); 476 if (coll->co_cvsroot != NULL) 477 free(coll->co_cvsroot); 478 if (coll->co_name != NULL) 479 free(coll->co_name); 480 if (coll->co_listsuffix != NULL) 481 free(coll->co_listsuffix); 482 keyword_free(coll->co_keyword); 483 if (coll->co_dirfilter != NULL) 484 globtree_free(coll->co_dirfilter); 485 if (coll->co_dirfilter != NULL) 486 globtree_free(coll->co_filefilter); 487 if (coll->co_norsync != NULL) 488 globtree_free(coll->co_norsync); 489 if (coll->co_accepts != NULL) 490 pattlist_free(coll->co_accepts); 491 if (coll->co_refusals != NULL) 492 pattlist_free(coll->co_refusals); 493 free(coll); 494} 495 496void 497coll_setopt(int opt, char *value) 498{ 499 struct coll *coll; 500 int error, mask; 501 502 coll = cur_coll; 503 switch (opt) { 504 case PT_HOST: 505 if (coll->co_host != NULL) 506 free(coll->co_host); 507 coll->co_host = value; 508 break; 509 case PT_BASE: 510 if (coll->co_base != NULL) 511 free(coll->co_base); 512 coll->co_base = value; 513 break; 514 case PT_DATE: 515 if (coll->co_date != NULL) 516 free(coll->co_date); 517 coll->co_date = value; 518 coll->co_options |= CO_CHECKOUTMODE; 519 break; 520 case PT_PREFIX: 521 if (coll->co_prefix != NULL) 522 free(coll->co_prefix); 523 coll->co_prefix = value; 524 break; 525 case PT_RELEASE: 526 if (coll->co_release != NULL) 527 free(coll->co_release); 528 coll->co_release = value; 529 break; 530 case PT_TAG: 531 if (coll->co_tag != NULL) 532 free(coll->co_tag); 533 coll->co_tag = value; 534 coll->co_options |= CO_CHECKOUTMODE; 535 break; 536 case PT_LIST: 537 if (strchr(value, '/') != NULL) { 538 lprintf(-1, "Parse error in \"%s\": \"list\" suffix " 539 "must not contain slashes\n", cfgfile); 540 exit(1); 541 } 542 if (coll->co_listsuffix != NULL) 543 free(coll->co_listsuffix); 544 coll->co_listsuffix = value; 545 break; 546 case PT_UMASK: 547 error = asciitoint(value, &mask, 8); 548 free(value); 549 if (error) { 550 lprintf(-1, "Parse error in \"%s\": Invalid " 551 "umask value\n", cfgfile); 552 exit(1); 553 } 554 coll->co_umask = mask; 555 break; 556 case PT_USE_REL_SUFFIX: 557 coll->co_options |= CO_USERELSUFFIX; 558 break; 559 case PT_DELETE: 560 coll->co_options |= CO_DELETE | CO_EXACTRCS; 561 break; 562 case PT_COMPRESS: 563 coll->co_options |= CO_COMPRESS; 564 break; 565 case PT_NORSYNC: 566 coll->co_options |= CO_NORSYNC; 567 break; 568 } 569} 570 571/* Set "coll" as being the default collection. */ 572void 573coll_setdef(void) 574{ 575 576 coll_free(defaults); 577 defaults = cur_coll; 578 cur_coll = coll_new(defaults); 579} 580