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