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