co.c revision 1.53
1/* $OpenBSD: co.c,v 1.53 2006/02/14 13:28:38 xsa Exp $ */ 2/* 3 * Copyright (c) 2005 Joris Vink <joris@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 "includes.h" 28 29#include "rcsprog.h" 30 31#define CO_OPTSTRING "d:f::k:l::M::p::q::r::s:Tu::Vw::x:" 32 33static void checkout_err_nobranch(RCSFILE *, const char *, const char *, 34 const char *, int); 35 36int 37checkout_main(int argc, char **argv) 38{ 39 int i, ch, flags, kflag; 40 RCSNUM *frev, *rev; 41 RCSFILE *file; 42 char fpath[MAXPATHLEN]; 43 char *author, *username, *date; 44 const char *state; 45 time_t rcs_mtime = -1; 46 47 flags = 0; 48 kflag = RCS_KWEXP_ERR; 49 rev = RCS_HEAD_REV; 50 frev = NULL; 51 state = NULL; 52 author = NULL; 53 date = NULL; 54 55 while ((ch = rcs_getopt(argc, argv, CO_OPTSTRING)) != -1) { 56 switch (ch) { 57 case 'd': 58 date = xstrdup(rcs_optarg); 59 break; 60 case 'f': 61 rcs_set_rev(rcs_optarg, &rev); 62 flags |= FORCE; 63 break; 64 case 'k': 65 kflag = rcs_kflag_get(rcs_optarg); 66 if (RCS_KWEXP_INVAL(kflag)) { 67 cvs_log(LP_ERR, 68 "invalid RCS keyword expansion mode"); 69 (usage)(); 70 exit(1); 71 } 72 break; 73 case 'l': 74 rcs_set_rev(rcs_optarg, &rev); 75 if (flags & CO_UNLOCK) { 76 cvs_log(LP_ERR, "warning: -u overridden by -l"); 77 flags &= ~CO_UNLOCK; 78 } 79 flags |= CO_LOCK; 80 break; 81 case 'M': 82 rcs_set_rev(rcs_optarg, &rev); 83 flags |= CO_REVDATE; 84 break; 85 case 'p': 86 rcs_set_rev(rcs_optarg, &rev); 87 pipeout = 1; 88 break; 89 case 'q': 90 rcs_set_rev(rcs_optarg, &rev); 91 verbose = 0; 92 break; 93 case 'r': 94 rcs_set_rev(rcs_optarg, &rev); 95 break; 96 case 's': 97 state = xstrdup(rcs_optarg); 98 flags |= CO_STATE; 99 break; 100 case 'T': 101 flags |= PRESERVETIME; 102 break; 103 case 'u': 104 rcs_set_rev(rcs_optarg, &rev); 105 if (flags & CO_LOCK) { 106 cvs_log(LP_ERR, "warning: -l overridden by -u"); 107 flags &= ~CO_LOCK; 108 } 109 flags |= CO_UNLOCK; 110 break; 111 case 'V': 112 printf("%s\n", rcs_version); 113 exit(0); 114 case 'w': 115 /* if no argument, assume current user */ 116 if (rcs_optarg == NULL) { 117 if ((author = getlogin()) == NULL) 118 fatal("getlogin failed"); 119 } else 120 author = xstrdup(rcs_optarg); 121 flags |= CO_AUTHOR; 122 break; 123 case 'x': 124 rcs_suffixes = rcs_optarg; 125 break; 126 default: 127 (usage)(); 128 exit(1); 129 } 130 } 131 132 argc -= rcs_optind; 133 argv += rcs_optind; 134 135 if (argc == 0) { 136 cvs_log(LP_ERR, "no input file"); 137 (usage)(); 138 exit (1); 139 } 140 141 if ((username = getlogin()) == NULL) { 142 cvs_log(LP_ERRNO, "failed to get username"); 143 exit (1); 144 } 145 146 for (i = 0; i < argc; i++) { 147 if (rcs_statfile(argv[i], fpath, sizeof(fpath)) < 0) 148 continue; 149 150 if (verbose == 1) 151 printf("%s --> %s\n", fpath, 152 (pipeout == 1) ? "standard output" : argv[i]); 153 154 if ((flags & CO_LOCK) && (kflag & RCS_KWEXP_VAL)) { 155 cvs_log(LP_ERR, "%s: cannot combine -kv and -l", fpath); 156 continue; 157 } 158 159 if ((file = rcs_open(fpath, RCS_RDWR|RCS_PARSE_FULLY)) == NULL) 160 continue; 161 162 if (flags & PRESERVETIME) 163 rcs_mtime = rcs_get_mtime(file->rf_path); 164 165 if (kflag != RCS_KWEXP_ERR) 166 rcs_kwexp_set(file, kflag); 167 168 if (rev == RCS_HEAD_REV) 169 frev = file->rf_head; 170 else 171 frev = rev; 172 173 if (checkout_rev(file, frev, argv[i], flags, 174 username, author, state, date) < 0) { 175 rcs_close(file); 176 continue; 177 } 178 179 rcs_close(file); 180 181 if (flags & PRESERVETIME) 182 rcs_set_mtime(fpath, rcs_mtime); 183 } 184 185 if (rev != RCS_HEAD_REV) 186 rcsnum_free(frev); 187 188 return (0); 189} 190 191void 192checkout_usage(void) 193{ 194 fprintf(stderr, 195 "usage: co [-TV] [-ddate] [-f[rev]] [-I[rev]] [-kmode] [-l[rev]]\n" 196 " [-M[rev]] [-p[rev]] [-q[rev]] [-r[rev]] [-sstate]\n" 197 " [-u[rev]] [-w[user]] [-xsuffixes] [-ztz] file ...\n"); 198} 199 200/* 201 * Checkout revision <rev> from RCSFILE <file>, writing it to the path <dst> 202 * Currenly recognised <flags> are CO_LOCK, CO_UNLOCK and CO_REVDATE. 203 * 204 * Looks up revision based upon <lockname>, <author>, <state> and <date> 205 * 206 * Returns 0 on success, -1 on failure. 207 */ 208int 209checkout_rev(RCSFILE *file, RCSNUM *frev, const char *dst, int flags, 210 const char *lockname, const char *author, const char *state, 211 const char *date) 212{ 213 BUF *bp; 214 int lcount; 215 char buf[16], yn; 216 mode_t mode = 0444; 217 struct stat st; 218 struct rcs_delta *rdp; 219 struct rcs_lock *lkp; 220 char *content, msg[128], *fdate; 221 time_t rcsdate, givendate; 222 223 rcsdate = givendate = -1; 224 if (date != NULL) 225 givendate = cvs_date_parse(date); 226 227 /* Check out the latest revision if <frev> is greater than HEAD */ 228 if (rcsnum_cmp(frev, file->rf_head, 0) == -1) 229 frev = file->rf_head; 230 231 lcount = 0; 232 TAILQ_FOREACH(lkp, &(file->rf_locks), rl_list) { 233 if (!strcmp(lkp->rl_name, lockname)) 234 lcount++; 235 } 236 237 /* 238 * If the user didn't specify any revision, we cycle through 239 * revisions to lookup the first one that matches what he specified. 240 * 241 * If we cannot find one, we return an error. 242 */ 243 rdp = NULL; 244 if (frev == file->rf_head) { 245 if (lcount > 1) { 246 cvs_log(LP_WARN, 247 "multiple revisions locked by %s; " 248 "please specify one", lockname); 249 return (-1); 250 } 251 252 TAILQ_FOREACH(rdp, &file->rf_delta, rd_list) { 253 if (date != NULL) { 254 fdate = asctime(&rdp->rd_date); 255 rcsdate = cvs_date_parse(fdate); 256 if (givendate <= rcsdate) 257 continue; 258 } 259 260 if ((author != NULL) && 261 (strcmp(rdp->rd_author, author))) 262 continue; 263 264 if ((state != NULL) && 265 (strcmp(rdp->rd_state, state))) 266 continue; 267 268 frev = rdp->rd_num; 269 break; 270 } 271 } else { 272 rdp = rcs_findrev(file, frev); 273 } 274 275 if (rdp == NULL) { 276 checkout_err_nobranch(file, author, date, state, flags); 277 return (-1); 278 } 279 280 rcsnum_tostr(frev, buf, sizeof(buf)); 281 282 if (rdp->rd_locker != NULL) { 283 if (strcmp(lockname, rdp->rd_locker)) { 284 strlcpy(msg, "Revision %s is already locked by %s; ", 285 sizeof(msg)); 286 if (flags & CO_UNLOCK) 287 strlcat(msg, "use co -r or rcs -u", sizeof(msg)); 288 cvs_log(LP_ERR, msg, buf, rdp->rd_locker); 289 return (-1); 290 } 291 } 292 293 if ((verbose == 1) && !(flags & NEWFILE)) 294 printf("revision %s", buf); 295 296 297 if ((bp = rcs_getrev(file, frev)) == NULL) { 298 cvs_log(LP_ERR, "cannot find revision `%s'", buf); 299 return (-1); 300 } 301 302 if (flags & CO_LOCK) { 303 if ((lockname != NULL) 304 && (rcs_lock_add(file, lockname, frev) < 0)) { 305 if (rcs_errno != RCS_ERR_DUPENT) 306 return (-1); 307 } 308 309 mode = 0644; 310 if ((verbose == 1) && !(flags & NEWFILE)) 311 printf(" (locked)\n"); 312 } else if (flags & CO_UNLOCK) { 313 if (rcs_lock_remove(file, lockname, frev) < 0) { 314 if (rcs_errno != RCS_ERR_NOENT) 315 return (-1); 316 } 317 318 mode = 0444; 319 if ((verbose == 1) && !(flags & NEWFILE)) 320 printf(" (unlocked)\n"); 321 } 322 323 if (flags & CO_LOCK) { 324 lcount++; 325 if (lcount > 1) 326 cvs_log(LP_WARN, "You now have %d locks.", lcount); 327 } 328 329 if ((pipeout == 0) && (stat(dst, &st) == 0) && !(flags & FORCE)) { 330 if (st.st_mode & S_IWUSR) { 331 yn = 0; 332 if (verbose == 0) { 333 cvs_log(LP_ERR, 334 "writable %s exists; checkout aborted", 335 dst); 336 return (-1); 337 } 338 339 while ((yn != 'y') && (yn != 'n')) { 340 printf("writable %s exists%s; ", dst, 341 ((uid_t)getuid() == st.st_uid) ? "" : 342 ", and you do not own it"); 343 printf("remove it? [ny](n): "); 344 fflush(stdout); 345 yn = getchar(); 346 } 347 348 if (yn == 'n') { 349 cvs_log(LP_ERR, "checkout aborted"); 350 return (-1); 351 } 352 } 353 } 354 355 if (pipeout == 1) { 356 cvs_buf_putc(bp, '\0'); 357 content = cvs_buf_release(bp); 358 printf("%s", content); 359 xfree(content); 360 } else { 361 if (cvs_buf_write(bp, dst, mode) < 0) { 362 cvs_log(LP_ERR, "failed to write revision to file"); 363 cvs_buf_free(bp); 364 return (-1); 365 } 366 cvs_buf_free(bp); 367 if (flags & CO_REVDATE) { 368 struct timeval tv[2]; 369 memset(&tv, 0, sizeof(tv)); 370 tv[0].tv_sec = (long)rcs_rev_getdate(file, frev); 371 tv[1].tv_sec = tv[0].tv_sec; 372 if (utimes(dst, (const struct timeval *)&tv) < 0) 373 cvs_log(LP_ERRNO, "error setting utimes"); 374 } 375 376 if ((verbose == 1) && !(flags & NEWFILE)) 377 printf("done\n"); 378 } 379 380 return (0); 381} 382 383/* 384 * checkout_err_nobranch() 385 * 386 * XXX - should handle the dates too. 387 */ 388static void 389checkout_err_nobranch(RCSFILE *file, const char *author, const char *date, 390 const char *state, int flags) 391{ 392 if (!(flags & CO_AUTHOR)) 393 author = NULL; 394 if (!(flags & CO_STATE)) 395 state = NULL; 396 397 cvs_log(LP_ERR, "%s: No revision on branch has%s%s%s%s%s%s.", 398 file->rf_path, 399 date ? " a date before " : "", 400 date ? date : "", 401 author ? " and author " + (date ? 0:4 ) : "", 402 author ? author : "", 403 state ? " and state " + (date || author ? 0:4) : "", 404 state ? state : ""); 405} 406