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