1 /* $OpenBSD: entries.c,v 1.100 2008/06/14 20:04:14 joris Exp $ */ 2 /* 3 * Copyright (c) 2006 Joris Vink <joris@openbsd.org> 4 * 5 * Permission to use, copy, modify, and distribute this software for any 6 * purpose with or without fee is hereby granted, provided that the above 7 * copyright notice and this permission notice appear in all copies. 8 * 9 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 */ 17 18 #include <errno.h> 19 #include <string.h> 20 #include <time.h> 21 #include <unistd.h> 22 23 #include "cvs.h" 24 #include "remote.h" 25 26 #define CVS_ENTRIES_NFIELDS 6 27 #define CVS_ENTRIES_DELIM '/' 28 29 static struct cvs_ent_line *ent_get_line(CVSENTRIES *, const char *); 30 31 CVSENTRIES *current_list = NULL; 32 33 CVSENTRIES * 34 cvs_ent_open(const char *dir) 35 { 36 FILE *fp; 37 CVSENTRIES *ep; 38 char *p, buf[MAXPATHLEN]; 39 struct cvs_ent *ent; 40 struct cvs_ent_line *line; 41 42 cvs_log(LP_TRACE, "cvs_ent_open(%s)", dir); 43 44 (void)xsnprintf(buf, sizeof(buf), "%s/%s", dir, CVS_PATH_ENTRIES); 45 46 if (current_list != NULL && !strcmp(current_list->cef_path, buf)) 47 return (current_list); 48 49 if (current_list != NULL) { 50 cvs_ent_close(current_list, ENT_SYNC); 51 current_list = NULL; 52 } 53 54 ep = (CVSENTRIES *)xcalloc(1, sizeof(*ep)); 55 ep->cef_path = xstrdup(buf); 56 57 (void)xsnprintf(buf, sizeof(buf), "%s/%s", 58 dir, CVS_PATH_BACKUPENTRIES); 59 60 ep->cef_bpath = xstrdup(buf); 61 62 (void)xsnprintf(buf, sizeof(buf), "%s/%s", dir, CVS_PATH_LOGENTRIES); 63 64 ep->cef_lpath = xstrdup(buf); 65 66 TAILQ_INIT(&(ep->cef_ent)); 67 68 if ((fp = fopen(ep->cef_path, "r")) != NULL) { 69 while (fgets(buf, sizeof(buf), fp)) { 70 buf[strcspn(buf, "\n")] = '\0'; 71 72 if (buf[0] == 'D' && buf[1] == '\0') 73 break; 74 75 line = (struct cvs_ent_line *)xmalloc(sizeof(*line)); 76 line->buf = xstrdup(buf); 77 TAILQ_INSERT_TAIL(&(ep->cef_ent), line, entries_list); 78 } 79 80 (void)fclose(fp); 81 } 82 83 if ((fp = fopen(ep->cef_lpath, "r")) != NULL) { 84 while (fgets(buf, sizeof(buf), fp)) { 85 buf[strcspn(buf, "\n")] = '\0'; 86 87 if (strlen(buf) < 2) 88 fatal("cvs_ent_open: %s: malformed line %s", 89 ep->cef_lpath, buf); 90 91 p = &buf[2]; 92 93 if (buf[0] == 'A') { 94 line = xmalloc(sizeof(*line)); 95 line->buf = xstrdup(p); 96 TAILQ_INSERT_TAIL(&(ep->cef_ent), line, 97 entries_list); 98 } else if (buf[0] == 'R') { 99 ent = cvs_ent_parse(p); 100 line = ent_get_line(ep, ent->ce_name); 101 if (line != NULL) { 102 TAILQ_REMOVE(&(ep->cef_ent), line, 103 entries_list); 104 xfree(line->buf); 105 xfree(line); 106 } 107 cvs_ent_free(ent); 108 } 109 } 110 111 (void)fclose(fp); 112 } 113 114 current_list = ep; 115 return (ep); 116 } 117 118 struct cvs_ent * 119 cvs_ent_parse(const char *entry) 120 { 121 int i; 122 struct tm t, dt; 123 struct cvs_ent *ent; 124 char *fields[CVS_ENTRIES_NFIELDS], *buf, *sp, *dp, *p; 125 126 buf = sp = xstrdup(entry); 127 i = 0; 128 do { 129 dp = strchr(sp, CVS_ENTRIES_DELIM); 130 if (dp != NULL) 131 *(dp++) = '\0'; 132 fields[i++] = sp; 133 sp = dp; 134 } while (dp != NULL && i < CVS_ENTRIES_NFIELDS); 135 136 if (i < CVS_ENTRIES_NFIELDS) 137 fatal("missing fields in entry line '%s'", entry); 138 139 ent = xmalloc(sizeof(*ent)); 140 ent->ce_buf = buf; 141 142 if (*fields[0] == '\0') 143 ent->ce_type = CVS_ENT_FILE; 144 else if (*fields[0] == 'D') 145 ent->ce_type = CVS_ENT_DIR; 146 else 147 ent->ce_type = CVS_ENT_NONE; 148 149 ent->ce_status = CVS_ENT_REG; 150 ent->ce_name = fields[1]; 151 ent->ce_rev = NULL; 152 ent->ce_date = -1; 153 ent->ce_tag = NULL; 154 155 if (ent->ce_type == CVS_ENT_FILE) { 156 if (*fields[2] == '-') { 157 ent->ce_status = CVS_ENT_REMOVED; 158 sp = fields[2] + 1; 159 } else { 160 sp = fields[2]; 161 if (fields[2][0] == '0' && fields[2][1] == '\0') 162 ent->ce_status = CVS_ENT_ADDED; 163 } 164 165 if ((ent->ce_rev = rcsnum_parse(sp)) == NULL) 166 fatal("failed to parse entry revision '%s'", entry); 167 168 if (fields[3][0] == '\0' || 169 strncmp(fields[3], CVS_DATE_DUMMY, sizeof(CVS_DATE_DUMMY) - 1) == 0 || 170 strncmp(fields[3], "Initial ", 8) == 0 || 171 strcmp(fields[3], "Result of merge") == 0) { 172 ent->ce_mtime = CVS_DATE_DMSEC; 173 } else if (cvs_server_active == 1 && 174 strncmp(fields[3], CVS_SERVER_UNCHANGED, 175 strlen(CVS_SERVER_UNCHANGED)) == 0) { 176 ent->ce_mtime = CVS_SERVER_UPTODATE; 177 } else { 178 p = fields[3]; 179 if (strncmp(fields[3], "Result of merge+", 16) == 0) 180 p += 16; 181 182 /* Date field can be a '+=' with remote to indicate 183 * conflict. In this case do nothing. */ 184 if (strptime(p, "%a %b %d %T %Y", &t) != NULL) { 185 186 t.tm_isdst = -1; /* Figure out DST. */ 187 t.tm_gmtoff = 0; 188 ent->ce_mtime = mktime(&t); 189 ent->ce_mtime += t.tm_gmtoff; 190 } 191 } 192 } 193 194 ent->ce_conflict = fields[3]; 195 if ((dp = strchr(ent->ce_conflict, '+')) != NULL) 196 *dp = '\0'; 197 else 198 ent->ce_conflict = NULL; 199 200 if (strcmp(fields[4], "")) 201 ent->ce_opts = fields[4]; 202 else 203 ent->ce_opts = NULL; 204 205 if (strcmp(fields[5], "")) { 206 switch (*fields[5]) { 207 case 'D': 208 if (sscanf(fields[5] + 1, "%d.%d.%d.%d.%d.%d", 209 &dt.tm_year, &dt.tm_mon, &dt.tm_mday, 210 &dt.tm_hour, &dt.tm_min, &dt.tm_sec) != 6) 211 fatal("wrong date specification"); 212 dt.tm_year -= 1900; 213 dt.tm_mon -= 1; 214 ent->ce_date = timegm(&dt); 215 ent->ce_tag = NULL; 216 break; 217 case 'T': 218 ent->ce_tag = fields[5] + 1; 219 break; 220 default: 221 fatal("invalid sticky entry"); 222 } 223 } 224 225 return (ent); 226 } 227 228 struct cvs_ent * 229 cvs_ent_get(CVSENTRIES *ep, const char *name) 230 { 231 struct cvs_ent *ent; 232 struct cvs_ent_line *l; 233 234 l = ent_get_line(ep, name); 235 if (l == NULL) 236 return (NULL); 237 238 ent = cvs_ent_parse(l->buf); 239 return (ent); 240 } 241 242 void 243 cvs_ent_close(CVSENTRIES *ep, int writefile) 244 { 245 FILE *fp; 246 struct cvs_ent_line *l; 247 int dflag; 248 249 dflag = 1; 250 cvs_log(LP_TRACE, "cvs_ent_close(%s, %d)", ep->cef_bpath, writefile); 251 252 if (cvs_cmdop == CVS_OP_EXPORT) 253 writefile = 0; 254 255 fp = NULL; 256 if (writefile) 257 fp = fopen(ep->cef_bpath, "w"); 258 259 while ((l = TAILQ_FIRST(&(ep->cef_ent))) != NULL) { 260 if (fp != NULL) { 261 if (l->buf[0] == 'D') 262 dflag = 0; 263 264 fputs(l->buf, fp); 265 fputc('\n', fp); 266 } 267 268 TAILQ_REMOVE(&(ep->cef_ent), l, entries_list); 269 xfree(l->buf); 270 xfree(l); 271 } 272 273 if (fp != NULL) { 274 if (dflag) { 275 fputc('D', fp); 276 fputc('\n', fp); 277 } 278 (void)fclose(fp); 279 280 if (rename(ep->cef_bpath, ep->cef_path) == -1) 281 fatal("cvs_ent_close: rename: `%s'->`%s': %s", 282 ep->cef_bpath, ep->cef_path, strerror(errno)); 283 284 (void)unlink(ep->cef_lpath); 285 } 286 287 xfree(ep->cef_path); 288 xfree(ep->cef_bpath); 289 xfree(ep->cef_lpath); 290 xfree(ep); 291 } 292 293 void 294 cvs_ent_add(CVSENTRIES *ep, const char *line) 295 { 296 FILE *fp; 297 struct cvs_ent_line *l; 298 struct cvs_ent *ent; 299 300 if ((ent = cvs_ent_parse(line)) == NULL) 301 fatal("cvs_ent_add: parsing failed '%s'", line); 302 303 l = ent_get_line(ep, ent->ce_name); 304 if (l != NULL) 305 cvs_ent_remove(ep, ent->ce_name); 306 307 cvs_ent_free(ent); 308 309 if (cvs_server_active == 0) 310 cvs_log(LP_TRACE, "cvs_ent_add(%s, %s)", ep->cef_path, line); 311 312 if ((fp = fopen(ep->cef_lpath, "a")) == NULL) 313 fatal("cvs_ent_add: fopen: `%s': %s", 314 ep->cef_lpath, strerror(errno)); 315 316 fputs("A ", fp); 317 fputs(line, fp); 318 fputc('\n', fp); 319 320 (void)fclose(fp); 321 322 l = (struct cvs_ent_line *)xmalloc(sizeof(*l)); 323 l->buf = xstrdup(line); 324 TAILQ_INSERT_TAIL(&(ep->cef_ent), l, entries_list); 325 } 326 327 void 328 cvs_ent_remove(CVSENTRIES *ep, const char *name) 329 { 330 FILE *fp; 331 struct cvs_ent_line *l; 332 333 if (cvs_server_active == 0) 334 cvs_log(LP_TRACE, "cvs_ent_remove(%s, %s)", ep->cef_path, name); 335 336 l = ent_get_line(ep, name); 337 if (l == NULL) 338 return; 339 340 if ((fp = fopen(ep->cef_lpath, "a")) == NULL) 341 fatal("cvs_ent_remove: fopen: `%s': %s", ep->cef_lpath, 342 strerror(errno)); 343 344 fputs("R ", fp); 345 fputs(l->buf, fp); 346 fputc('\n', fp); 347 348 (void)fclose(fp); 349 350 TAILQ_REMOVE(&(ep->cef_ent), l, entries_list); 351 xfree(l->buf); 352 xfree(l); 353 } 354 355 /* 356 * cvs_ent_line_str() 357 * 358 * Build CVS/Entries line. 359 * 360 */ 361 void 362 cvs_ent_line_str(const char *name, char *rev, char *tstamp, char *opts, 363 char *sticky, int isdir, int isremoved, char *buf, size_t len) 364 { 365 if (isdir == 1) { 366 (void)xsnprintf(buf, len, "D/%s////", name); 367 return; 368 } 369 370 (void)xsnprintf(buf, len, "/%s/%s%s/%s/%s/%s", 371 name, isremoved == 1 ? "-" : "", rev, tstamp, opts, sticky); 372 } 373 374 void 375 cvs_ent_free(struct cvs_ent *ent) 376 { 377 if (ent->ce_rev != NULL) 378 rcsnum_free(ent->ce_rev); 379 xfree(ent->ce_buf); 380 xfree(ent); 381 } 382 383 static struct cvs_ent_line * 384 ent_get_line(CVSENTRIES *ep, const char *name) 385 { 386 char *p, *s; 387 struct cvs_ent_line *l; 388 389 TAILQ_FOREACH(l, &(ep->cef_ent), entries_list) { 390 if (l->buf[0] == 'D') 391 p = &(l->buf[2]); 392 else 393 p = &(l->buf[1]); 394 395 if ((s = strchr(p, '/')) == NULL) 396 fatal("ent_get_line: bad entry line '%s'", l->buf); 397 398 *s = '\0'; 399 400 if (!strcmp(p, name)) { 401 *s = '/'; 402 return (l); 403 } 404 405 *s = '/'; 406 } 407 408 return (NULL); 409 } 410 411 void 412 cvs_parse_tagfile(char *dir, char **tagp, char **datep, int *nbp) 413 { 414 FILE *fp; 415 int i, linenum; 416 size_t len; 417 struct tm datetm; 418 char linebuf[128], tagpath[MAXPATHLEN]; 419 420 cvs_directory_date = -1; 421 422 if (tagp != NULL) 423 *tagp = NULL; 424 425 if (datep != NULL) 426 *datep = NULL; 427 428 if (nbp != NULL) 429 *nbp = 0; 430 431 i = snprintf(tagpath, MAXPATHLEN, "%s/%s", dir, CVS_PATH_TAG); 432 if (i < 0 || i >= MAXPATHLEN) 433 return; 434 435 if ((fp = fopen(tagpath, "r")) == NULL) { 436 if (errno != ENOENT) 437 cvs_log(LP_NOTICE, "failed to open `%s' : %s", tagpath, 438 strerror(errno)); 439 return; 440 } 441 442 linenum = 0; 443 444 while (fgets(linebuf, (int)sizeof(linebuf), fp) != NULL) { 445 linenum++; 446 if ((len = strlen(linebuf)) == 0) 447 continue; 448 if (linebuf[len - 1] != '\n') { 449 cvs_log(LP_NOTICE, "line too long in `%s:%d'", 450 tagpath, linenum); 451 break; 452 } 453 linebuf[--len] = '\0'; 454 455 switch (*linebuf) { 456 case 'T': 457 if (tagp != NULL) 458 *tagp = xstrdup(linebuf + 1); 459 break; 460 case 'D': 461 if (sscanf(linebuf + 1, "%d.%d.%d.%d.%d.%d", 462 &datetm.tm_year, &datetm.tm_mon, &datetm.tm_mday, 463 &datetm.tm_hour, &datetm.tm_min, &datetm.tm_sec) != 464 6) 465 fatal("wrong date specification"); 466 datetm.tm_year -= 1900; 467 datetm.tm_mon -= 1; 468 469 cvs_directory_date = timegm(&datetm); 470 471 if (datep != NULL) 472 *datep = xstrdup(linebuf + 1); 473 break; 474 case 'N': 475 if (tagp != NULL) 476 *tagp = xstrdup(linebuf + 1); 477 if (nbp != NULL) 478 *nbp = 1; 479 break; 480 default: 481 break; 482 } 483 } 484 if (ferror(fp)) 485 cvs_log(LP_NOTICE, "failed to read line from `%s'", tagpath); 486 487 (void)fclose(fp); 488 } 489 490 void 491 cvs_write_tagfile(const char *dir, char *tag, char *date) 492 { 493 FILE *fp; 494 RCSNUM *rev; 495 char tagpath[MAXPATHLEN]; 496 char sticky[CVS_REV_BUFSZ]; 497 struct tm datetm; 498 int i; 499 500 cvs_log(LP_TRACE, "cvs_write_tagfile(%s, %s, %s)", dir, 501 tag != NULL ? tag : "", date != NULL ? date : ""); 502 503 if (cvs_noexec == 1) 504 return; 505 506 i = snprintf(tagpath, MAXPATHLEN, "%s/%s", dir, CVS_PATH_TAG); 507 if (i < 0 || i >= MAXPATHLEN) 508 return; 509 510 if (tag != NULL || cvs_specified_date != -1 || 511 cvs_directory_date != -1) { 512 if ((fp = fopen(tagpath, "w+")) == NULL) { 513 if (errno != ENOENT) { 514 cvs_log(LP_NOTICE, "failed to open `%s' : %s", 515 tagpath, strerror(errno)); 516 } 517 return; 518 } 519 520 if (tag != NULL) { 521 if ((rev = rcsnum_parse(tag)) != NULL) { 522 (void)xsnprintf(sticky, sizeof(sticky), 523 "N%s", tag); 524 rcsnum_free(rev); 525 } else { 526 (void)xsnprintf(sticky, sizeof(sticky), 527 "T%s", tag); 528 } 529 } else { 530 if (cvs_specified_date != -1) 531 gmtime_r(&cvs_specified_date, &datetm); 532 else 533 gmtime_r(&cvs_directory_date, &datetm); 534 (void)strftime(sticky, sizeof(sticky), 535 "D"CVS_DATE_FMT, &datetm); 536 } 537 538 if (cvs_server_active == 1) 539 cvs_server_set_sticky(dir, sticky); 540 541 (void)fprintf(fp, "%s\n", sticky); 542 (void)fclose(fp); 543 } 544 } 545