cvs.c revision 1.78
1/* $OpenBSD: cvs.c,v 1.78 2005/08/01 19:48:18 xsa Exp $ */ 2/* 3 * Copyright (c) 2004 Jean-Francois Brousseau <jfb@openbsd.org> 4 * All rights reserved. 5 * 6 * Redistribution and use in source and binary forms, with or without 7 * modification, are permitted provided that the following conditions 8 * are met: 9 * 10 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * 2. The name of the author may not be used to endorse or promote products 13 * derived from this software without specific prior written permission. 14 * 15 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, 16 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY 17 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL 18 * THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 19 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 20 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 21 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 22 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 23 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 24 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 */ 26 27#include <sys/types.h> 28#include <sys/wait.h> 29 30#include <ctype.h> 31#include <err.h> 32#include <errno.h> 33#include <pwd.h> 34#include <signal.h> 35#include <stdio.h> 36#include <stdlib.h> 37#include <string.h> 38#include <unistd.h> 39 40#include "cvs.h" 41#include "log.h" 42#include "file.h" 43#include "strtab.h" 44 45 46extern char *__progname; 47 48 49/* verbosity level: 0 = really quiet, 1 = quiet, 2 = verbose */ 50int verbosity = 2; 51 52/* compression level used with zlib, 0 meaning no compression taking place */ 53int cvs_compress = 0; 54int cvs_readrc = 1; /* read .cvsrc on startup */ 55int cvs_trace = 0; 56int cvs_nolog = 0; 57int cvs_readonly = 0; 58int cvs_nocase = 0; /* set to 1 to disable filename case sensitivity */ 59int cvs_noexec = 0; /* set to 1 to disable disk operations (-n option) */ 60int cvs_error = -1; /* set to the correct error code on failure */ 61char *cvs_defargs; /* default global arguments from .cvsrc */ 62char *cvs_command; /* name of the command we are running */ 63int cvs_cmdop; 64char *cvs_rootstr; 65char *cvs_rsh = CVS_RSH_DEFAULT; 66char *cvs_editor = CVS_EDITOR_DEFAULT; 67char *cvs_repo_base = NULL; 68char *cvs_msg = NULL; 69 70/* hierarchy of all the files affected by the command */ 71CVSFILE *cvs_files; 72 73static TAILQ_HEAD(, cvs_var) cvs_variables; 74 75 76void usage(void); 77static void cvs_read_rcfile(void); 78int cvs_getopt(int, char **); 79 80/* 81 * usage() 82 * 83 * Display usage information. 84 */ 85void 86usage(void) 87{ 88 fprintf(stderr, 89 "Usage: %s [-flnQqrtv] [-d root] [-e editor] " 90 "[-s var=val] [-z level] command [...]\n", __progname); 91} 92 93 94int 95main(int argc, char **argv) 96{ 97 char *envstr, *cmd_argv[CVS_CMD_MAXARG], **targv; 98 int i, ret, cmd_argc; 99 struct cvs_cmd *cmdp; 100 101 TAILQ_INIT(&cvs_variables); 102 103 if (cvs_log_init(LD_STD, 0) < 0) 104 err(1, "failed to initialize logging"); 105 106 /* by default, be very verbose */ 107 (void)cvs_log_filter(LP_FILTER_UNSET, LP_INFO); 108 109#ifdef DEBUG 110 (void)cvs_log_filter(LP_FILTER_UNSET, LP_DEBUG); 111#endif 112 113 cvs_strtab_init(); 114 115 /* check environment so command-line options override it */ 116 if ((envstr = getenv("CVS_RSH")) != NULL) 117 cvs_rsh = envstr; 118 119 if (((envstr = getenv("CVSEDITOR")) != NULL) || 120 ((envstr = getenv("VISUAL")) != NULL) || 121 ((envstr = getenv("EDITOR")) != NULL)) 122 cvs_editor = envstr; 123 124 if ((envstr = getenv("CVSREAD")) != NULL) 125 cvs_readonly = 1; 126 127 ret = cvs_getopt(argc, argv); 128 129 argc -= ret; 130 argv += ret; 131 if (argc == 0) { 132 usage(); 133 exit(1); 134 } 135 cvs_command = argv[0]; 136 137 if (cvs_readrc == 1) { 138 cvs_read_rcfile(); 139 140 if (cvs_defargs != NULL) { 141 targv = cvs_makeargv(cvs_defargs, &i); 142 if (targv == NULL) { 143 cvs_log(LP_ERR, 144 "failed to load default arguments to %s", 145 __progname); 146 exit(1); 147 } 148 149 cvs_getopt(i, targv); 150 cvs_freeargv(targv, i); 151 free(targv); 152 } 153 } 154 155 /* setup signal handlers */ 156 signal(SIGPIPE, SIG_IGN); 157 158 if (cvs_file_init() < 0) { 159 cvs_log(LP_ERR, "failed to initialize file support"); 160 exit(1); 161 } 162 163 ret = -1; 164 165 cmdp = cvs_findcmd(cvs_command); 166 if (cmdp == NULL) { 167 fprintf(stderr, "Unknown command: `%s'\n\n", cvs_command); 168 fprintf(stderr, "CVS commands are:\n"); 169 for (i = 0; cvs_cdt[i] != NULL; i++) 170 fprintf(stderr, "\t%-16s%s\n", 171 cvs_cdt[i]->cmd_name, cvs_cdt[i]->cmd_descr); 172 exit(CVS_EX_USAGE); 173 } 174 175 cvs_cmdop = cmdp->cmd_op; 176 177 cmd_argc = 0; 178 memset(cmd_argv, 0, sizeof(cmd_argv)); 179 180 cmd_argv[cmd_argc++] = argv[0]; 181 if (cmdp->cmd_defargs != NULL) { 182 /* transform into a new argument vector */ 183 ret = cvs_getargv(cmdp->cmd_defargs, cmd_argv + 1, 184 CVS_CMD_MAXARG - 1); 185 if (ret < 0) { 186 cvs_log(LP_ERRNO, "failed to generate argument vector " 187 "from default arguments"); 188 exit(1); 189 } 190 cmd_argc += ret; 191 } 192 for (ret = 1; ret < argc; ret++) 193 cmd_argv[cmd_argc++] = argv[ret]; 194 195 ret = cvs_startcmd(cmdp, cmd_argc, cmd_argv); 196 switch (ret) { 197 case CVS_EX_USAGE: 198 fprintf(stderr, "Usage: %s %s %s\n", __progname, cvs_command, 199 cmdp->cmd_synopsis); 200 break; 201 case CVS_EX_DATA: 202 cvs_log(LP_ABORT, "internal data error"); 203 break; 204 case CVS_EX_PROTO: 205 cvs_log(LP_ABORT, "protocol error"); 206 break; 207 case CVS_EX_FILE: 208 cvs_log(LP_ABORT, "an operation on a file or directory failed"); 209 break; 210 case CVS_EX_BADROOT: 211 /* match GNU CVS output, thus the LP_ERR and LP_ABORT codes. */ 212 cvs_log(LP_ERR, 213 "No CVSROOT specified! Please use the `-d' option"); 214 cvs_log(LP_ABORT, 215 "or set the CVSROOT enviroment variable."); 216 break; 217 case CVS_EX_ERR: 218 cvs_log(LP_ABORT, "yeah, we failed, and we don't know why"); 219 break; 220 default: 221 break; 222 } 223 224 if (cvs_files != NULL) 225 cvs_file_free(cvs_files); 226 if (cvs_msg != NULL) 227 free(cvs_msg); 228 229 cvs_strtab_cleanup(); 230 231 return (ret); 232} 233 234 235int 236cvs_getopt(int argc, char **argv) 237{ 238 int ret; 239 char *ep; 240 241 while ((ret = getopt(argc, argv, "b:d:e:fHlnQqrs:tvz:")) != -1) { 242 switch (ret) { 243 case 'b': 244 /* 245 * We do not care about the bin directory for RCS files 246 * as this program has no dependencies on RCS programs, 247 * so it is only here for backwards compatibility. 248 */ 249 cvs_log(LP_NOTICE, "the -b argument is obsolete"); 250 break; 251 case 'd': 252 cvs_rootstr = optarg; 253 break; 254 case 'e': 255 cvs_editor = optarg; 256 break; 257 case 'f': 258 cvs_readrc = 0; 259 break; 260 case 'l': 261 cvs_nolog = 1; 262 break; 263 case 'n': 264 cvs_noexec = 1; 265 break; 266 case 'Q': 267 verbosity = 0; 268 break; 269 case 'q': 270 /* don't override -Q */ 271 if (verbosity > 1) 272 verbosity = 1; 273 break; 274 case 'r': 275 cvs_readonly = 1; 276 break; 277 case 's': 278 ep = strchr(optarg, '='); 279 if (ep == NULL) { 280 cvs_log(LP_ERR, "no = in variable assignment"); 281 exit(1); 282 } 283 *(ep++) = '\0'; 284 if (cvs_var_set(optarg, ep) < 0) 285 exit(1); 286 break; 287 case 't': 288 (void)cvs_log_filter(LP_FILTER_UNSET, LP_TRACE); 289 cvs_trace = 1; 290 break; 291 case 'v': 292 printf("%s\n", CVS_VERSION); 293 exit(0); 294 /* NOTREACHED */ 295 break; 296 case 'x': 297 /* 298 * Kerberos encryption support, kept for compatibility 299 */ 300 break; 301 case 'z': 302 cvs_compress = (int)strtol(optarg, &ep, 10); 303 if (*ep != '\0') 304 errx(1, "error parsing compression level"); 305 if (cvs_compress < 0 || cvs_compress > 9) 306 errx(1, "gzip compression level must be " 307 "between 0 and 9"); 308 break; 309 default: 310 usage(); 311 exit(1); 312 } 313 } 314 315 ret = optind; 316 optind = 1; 317 optreset = 1; /* for next call */ 318 319 return (ret); 320} 321 322 323/* 324 * cvs_read_rcfile() 325 * 326 * Read the CVS `.cvsrc' file in the user's home directory. If the file 327 * exists, it should contain a list of arguments that should always be given 328 * implicitly to the specified commands. 329 */ 330static void 331cvs_read_rcfile(void) 332{ 333 char rcpath[MAXPATHLEN], linebuf[128], *envstr, *home, *lp, *p; 334 int l, linenum = 0; 335 size_t len; 336 struct cvs_cmd *cmdp; 337 struct passwd *pw; 338 FILE *fp; 339 340 pw = getpwuid(getuid()); 341 if (pw == NULL) { 342 cvs_log(LP_NOTICE, "failed to get user's password entry"); 343 return; 344 } 345 346 if ((envstr = getenv("HOME")) != NULL) 347 home = envstr; 348 else 349 home = pw->pw_dir; 350 351 l = snprintf(rcpath, sizeof(rcpath), "%s/%s", home, CVS_PATH_RC); 352 if (l == -1 || l >= (int)sizeof(rcpath)) { 353 errno = ENAMETOOLONG; 354 cvs_log(LP_ERRNO, "%s", rcpath); 355 return; 356 } 357 358 fp = fopen(rcpath, "r"); 359 if (fp == NULL) { 360 if (errno != ENOENT) 361 cvs_log(LP_NOTICE, "failed to open `%s': %s", rcpath, 362 strerror(errno)); 363 return; 364 } 365 366 while (fgets(linebuf, sizeof(linebuf), fp) != NULL) { 367 linenum++; 368 if ((len = strlen(linebuf)) == 0) 369 continue; 370 if (linebuf[len - 1] != '\n') { 371 cvs_log(LP_WARN, "line too long in `%s:%d'", rcpath, 372 linenum); 373 break; 374 } 375 linebuf[--len] = '\0'; 376 377 /* skip any whitespaces */ 378 p = linebuf; 379 while (*p == ' ') 380 *p++; 381 382 /* allow comments */ 383 if (*p == '#') 384 continue; 385 386 lp = strchr(p, ' '); 387 if (lp == NULL) 388 continue; /* ignore lines with no arguments */ 389 *lp = '\0'; 390 if (strcmp(p, "cvs") == 0) { 391 /* 392 * Global default options. In the case of cvs only, 393 * we keep the 'cvs' string as first argument because 394 * getopt() does not like starting at index 0 for 395 * argument processing. 396 */ 397 *lp = ' '; 398 cvs_defargs = strdup(p); 399 if (cvs_defargs == NULL) 400 cvs_log(LP_ERRNO, 401 "failed to copy global arguments"); 402 } else { 403 lp++; 404 cmdp = cvs_findcmd(p); 405 if (cmdp == NULL) { 406 cvs_log(LP_NOTICE, 407 "unknown command `%s' in `%s:%d'", 408 p, rcpath, linenum); 409 continue; 410 } 411 412 cmdp->cmd_defargs = strdup(lp); 413 if (cmdp->cmd_defargs == NULL) 414 cvs_log(LP_ERRNO, 415 "failed to copy default arguments for %s", 416 cmdp->cmd_name); 417 } 418 } 419 if (ferror(fp)) { 420 cvs_log(LP_NOTICE, "failed to read line from `%s'", rcpath); 421 } 422 423 (void)fclose(fp); 424} 425 426 427/* 428 * cvs_var_set() 429 * 430 * Set the value of the variable <var> to <val>. If there is no such variable, 431 * a new entry is created, otherwise the old value is overwritten. 432 * Returns 0 on success, or -1 on failure. 433 */ 434int 435cvs_var_set(const char *var, const char *val) 436{ 437 char *valcp; 438 const char *cp; 439 struct cvs_var *vp; 440 441 if ((var == NULL) || (*var == '\0')) { 442 cvs_log(LP_ERR, "no variable name"); 443 return (-1); 444 } 445 446 /* sanity check on the name */ 447 for (cp = var; *cp != '\0'; cp++) 448 if (!isalnum(*cp) && (*cp != '_')) { 449 cvs_log(LP_ERR, 450 "variable name `%s' contains invalid characters", 451 var); 452 return (-1); 453 } 454 455 TAILQ_FOREACH(vp, &cvs_variables, cv_link) 456 if (strcmp(vp->cv_name, var) == 0) 457 break; 458 459 valcp = strdup(val); 460 if (valcp == NULL) { 461 cvs_log(LP_ERRNO, "failed to allocate variable"); 462 return (-1); 463 } 464 465 if (vp == NULL) { 466 vp = (struct cvs_var *)malloc(sizeof(*vp)); 467 if (vp == NULL) { 468 cvs_log(LP_ERRNO, "failed to allocate variable"); 469 free(valcp); 470 return (-1); 471 } 472 memset(vp, 0, sizeof(*vp)); 473 474 vp->cv_name = strdup(var); 475 if (vp->cv_name == NULL) { 476 cvs_log(LP_ERRNO, "failed to allocate variable"); 477 free(valcp); 478 free(vp); 479 return (-1); 480 } 481 482 TAILQ_INSERT_TAIL(&cvs_variables, vp, cv_link); 483 484 } else /* free the previous value */ 485 free(vp->cv_val); 486 487 vp->cv_val = valcp; 488 489 return (0); 490} 491 492 493/* 494 * cvs_var_set() 495 * 496 * Remove any entry for the variable <var>. 497 * Returns 0 on success, or -1 on failure. 498 */ 499int 500cvs_var_unset(const char *var) 501{ 502 struct cvs_var *vp; 503 504 TAILQ_FOREACH(vp, &cvs_variables, cv_link) 505 if (strcmp(vp->cv_name, var) == 0) { 506 TAILQ_REMOVE(&cvs_variables, vp, cv_link); 507 free(vp->cv_name); 508 free(vp->cv_val); 509 free(vp); 510 return (0); 511 } 512 513 return (-1); 514 515} 516 517 518/* 519 * cvs_var_get() 520 * 521 * Get the value associated with the variable <var>. Returns a pointer to the 522 * value string on success, or NULL if the variable does not exist. 523 */ 524 525const char * 526cvs_var_get(const char *var) 527{ 528 struct cvs_var *vp; 529 530 TAILQ_FOREACH(vp, &cvs_variables, cv_link) 531 if (strcmp(vp->cv_name, var) == 0) 532 return (vp->cv_val); 533 534 return (NULL); 535} 536