cvs.c revision 1.69
1/* $OpenBSD: cvs.c,v 1.69 2005/06/01 17:44:34 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/* 83 * usage() 84 * 85 * Display usage information. 86 */ 87void 88usage(void) 89{ 90 fprintf(stderr, 91 "Usage: %s [-flnQqtv] [-d root] [-e editor] [-s var=val] [-z level] " 92 "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 103 TAILQ_INIT(&cvs_variables); 104 105 if (cvs_log_init(LD_STD, 0) < 0) 106 err(1, "failed to initialize logging"); 107 108 /* by default, be very verbose */ 109 (void)cvs_log_filter(LP_FILTER_UNSET, LP_INFO); 110 111#ifdef DEBUG 112 (void)cvs_log_filter(LP_FILTER_UNSET, LP_DEBUG); 113#endif 114 115 cvs_strtab_init(); 116 117 /* check environment so command-line options override it */ 118 if ((envstr = getenv("CVS_RSH")) != NULL) 119 cvs_rsh = envstr; 120 121 if (((envstr = getenv("CVSEDITOR")) != NULL) || 122 ((envstr = getenv("VISUAL")) != NULL) || 123 ((envstr = getenv("EDITOR")) != NULL)) 124 cvs_editor = envstr; 125 126 ret = cvs_getopt(argc, argv); 127 128 argc -= ret; 129 argv += ret; 130 if (argc == 0) { 131 usage(); 132 exit(1); 133 } 134 cvs_command = argv[0]; 135 136 if (cvs_readrc) { 137 cvs_read_rcfile(); 138 139 if (cvs_defargs != NULL) { 140 targv = cvs_makeargv(cvs_defargs, &i); 141 if (targv == NULL) { 142 cvs_log(LP_ERR, 143 "failed to load default arguments to %s", 144 __progname); 145 exit(1); 146 } 147 148 cvs_getopt(i, targv); 149 cvs_freeargv(targv, i); 150 free(targv); 151 } 152 } 153 154 /* setup signal handlers */ 155 signal(SIGPIPE, SIG_IGN); 156 157 if (cvs_file_init() < 0) { 158 cvs_log(LP_ERR, "failed to initialize file support"); 159 exit(1); 160 } 161 162 ret = -1; 163 164 cmdp = cvs_findcmd(cvs_command); 165 if (cmdp == NULL) { 166 fprintf(stderr, "Unknown command: `%s'\n\n", cvs_command); 167 fprintf(stderr, "CVS commands are:\n"); 168 for (i = 0; cvs_cdt[i] != NULL; i++) 169 fprintf(stderr, "\t%-16s%s\n", 170 cvs_cdt[i]->cmd_name, cvs_cdt[i]->cmd_descr); 171 exit(CVS_EX_USAGE); 172 } 173 174 cvs_cmdop = cmdp->cmd_op; 175 176 cmd_argc = 0; 177 memset(cmd_argv, 0, sizeof(cmd_argv)); 178 179 cmd_argv[cmd_argc++] = argv[0]; 180 if (cmdp->cmd_defargs != NULL) { 181 /* transform into a new argument vector */ 182 ret = cvs_getargv(cmdp->cmd_defargs, cmd_argv + 1, 183 CVS_CMD_MAXARG - 1); 184 if (ret < 0) { 185 cvs_log(LP_ERRNO, "failed to generate argument vector " 186 "from default arguments"); 187 exit(1); 188 } 189 cmd_argc += ret; 190 } 191 for (ret = 1; ret < argc; ret++) 192 cmd_argv[cmd_argc++] = argv[ret]; 193 194 ret = cvs_startcmd(cmdp, cmd_argc, cmd_argv); 195 switch (ret) { 196 case CVS_EX_USAGE: 197 fprintf(stderr, "Usage: %s %s %s\n", __progname, cvs_command, 198 cmdp->cmd_synopsis); 199 break; 200 case CVS_EX_DATA: 201 cvs_log(LP_ABORT, "internal data error"); 202 break; 203 case CVS_EX_PROTO: 204 cvs_log(LP_ABORT, "protocol error"); 205 break; 206 case CVS_EX_FILE: 207 cvs_log(LP_ABORT, "an operation on a file or directory failed"); 208 break; 209 case CVS_EX_BADROOT: 210 cvs_log(LP_ABORT, 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; 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 lp = strchr(linebuf, ' '); 368 if (lp == NULL) 369 continue; /* ignore lines with no arguments */ 370 *lp = '\0'; 371 if (strcmp(linebuf, "cvs") == 0) { 372 /* 373 * Global default options. In the case of cvs only, 374 * we keep the 'cvs' string as first argument because 375 * getopt() does not like starting at index 0 for 376 * argument processing. 377 */ 378 *lp = ' '; 379 cvs_defargs = strdup(linebuf); 380 if (cvs_defargs == NULL) 381 cvs_log(LP_ERRNO, 382 "failed to copy global arguments"); 383 } else { 384 lp++; 385 cmdp = cvs_findcmd(linebuf); 386 if (cmdp == NULL) { 387 cvs_log(LP_NOTICE, 388 "unknown command `%s' in `%s:%d'", 389 linebuf, rcpath, linenum); 390 continue; 391 } 392 393 cmdp->cmd_defargs = strdup(lp); 394 if (cmdp->cmd_defargs == NULL) 395 cvs_log(LP_ERRNO, 396 "failed to copy default arguments for %s", 397 cmdp->cmd_name); 398 } 399 } 400 if (ferror(fp)) { 401 cvs_log(LP_NOTICE, "failed to read line from `%s'", rcpath); 402 } 403 404 (void)fclose(fp); 405} 406 407 408/* 409 * cvs_var_set() 410 * 411 * Set the value of the variable <var> to <val>. If there is no such variable, 412 * a new entry is created, otherwise the old value is overwritten. 413 * Returns 0 on success, or -1 on failure. 414 */ 415int 416cvs_var_set(const char *var, const char *val) 417{ 418 char *valcp; 419 const char *cp; 420 struct cvs_var *vp; 421 422 if ((var == NULL) || (*var == '\0')) { 423 cvs_log(LP_ERR, "no variable name"); 424 return (-1); 425 } 426 427 /* sanity check on the name */ 428 for (cp = var; *cp != '\0'; cp++) 429 if (!isalnum(*cp) && (*cp != '_')) { 430 cvs_log(LP_ERR, 431 "variable name `%s' contains invalid characters", 432 var); 433 return (-1); 434 } 435 436 TAILQ_FOREACH(vp, &cvs_variables, cv_link) 437 if (strcmp(vp->cv_name, var) == 0) 438 break; 439 440 valcp = strdup(val); 441 if (valcp == NULL) { 442 cvs_log(LP_ERRNO, "failed to allocate variable"); 443 return (-1); 444 } 445 446 if (vp == NULL) { 447 vp = (struct cvs_var *)malloc(sizeof(*vp)); 448 if (vp == NULL) { 449 cvs_log(LP_ERRNO, "failed to allocate variable"); 450 free(valcp); 451 return (-1); 452 } 453 memset(vp, 0, sizeof(*vp)); 454 455 vp->cv_name = strdup(var); 456 if (vp->cv_name == NULL) { 457 cvs_log(LP_ERRNO, "failed to allocate variable"); 458 free(valcp); 459 free(vp); 460 return (-1); 461 } 462 463 TAILQ_INSERT_TAIL(&cvs_variables, vp, cv_link); 464 465 } else /* free the previous value */ 466 free(vp->cv_val); 467 468 vp->cv_val = valcp; 469 470 return (0); 471} 472 473 474/* 475 * cvs_var_set() 476 * 477 * Remove any entry for the variable <var>. 478 * Returns 0 on success, or -1 on failure. 479 */ 480int 481cvs_var_unset(const char *var) 482{ 483 struct cvs_var *vp; 484 485 TAILQ_FOREACH(vp, &cvs_variables, cv_link) 486 if (strcmp(vp->cv_name, var) == 0) { 487 TAILQ_REMOVE(&cvs_variables, vp, cv_link); 488 free(vp->cv_name); 489 free(vp->cv_val); 490 free(vp); 491 return (0); 492 } 493 494 return (-1); 495 496} 497 498 499/* 500 * cvs_var_get() 501 * 502 * Get the value associated with the variable <var>. Returns a pointer to the 503 * value string on success, or NULL if the variable does not exist. 504 */ 505 506const char* 507cvs_var_get(const char *var) 508{ 509 struct cvs_var *vp; 510 511 TAILQ_FOREACH(vp, &cvs_variables, cv_link) 512 if (strcmp(vp->cv_name, var) == 0) 513 return (vp->cv_val); 514 515 return (NULL); 516} 517