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