1 /* $OpenBSD: entries.c,v 1.102 2009/03/19 09:53:16 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 if (*fields[2] == CVS_SERVER_QUESTIONABLE) { 160 sp = NULL; 161 ent->ce_status = CVS_ENT_UNKNOWN; 162 } else { 163 sp = fields[2]; 164 if (fields[2][0] == '0' && fields[2][1] == '\0') 165 ent->ce_status = CVS_ENT_ADDED; 166 } 167 168 if (sp != NULL) { 169 if ((ent->ce_rev = rcsnum_parse(sp)) == NULL) { 170 fatal("failed to parse entry revision '%s'", 171 entry); 172 } 173 } 174 175 if (fields[3][0] == '\0' || 176 strncmp(fields[3], CVS_DATE_DUMMY, 177 sizeof(CVS_DATE_DUMMY) - 1) == 0 || 178 strncmp(fields[3], "Initial ", 8) == 0 || 179 strcmp(fields[3], "Result of merge") == 0) { 180 ent->ce_mtime = CVS_DATE_DMSEC; 181 } else if (cvs_server_active == 1 && 182 strncmp(fields[3], CVS_SERVER_UNCHANGED, 183 strlen(CVS_SERVER_UNCHANGED)) == 0) { 184 ent->ce_mtime = CVS_SERVER_UPTODATE; 185 } else { 186 p = fields[3]; 187 if (strncmp(fields[3], "Result of merge+", 16) == 0) 188 p += 16; 189 190 /* Date field can be a '+=' with remote to indicate 191 * conflict. In this case do nothing. */ 192 if (strptime(p, "%a %b %d %T %Y", &t) != NULL) { 193 t.tm_isdst = -1; /* Figure out DST. */ 194 t.tm_gmtoff = 0; 195 ent->ce_mtime = mktime(&t); 196 ent->ce_mtime += t.tm_gmtoff; 197 } 198 } 199 } 200 201 ent->ce_conflict = fields[3]; 202 if ((dp = strchr(ent->ce_conflict, '+')) != NULL) 203 *dp = '\0'; 204 else 205 ent->ce_conflict = NULL; 206 207 if (strcmp(fields[4], "")) 208 ent->ce_opts = fields[4]; 209 else 210 ent->ce_opts = NULL; 211 212 if (strcmp(fields[5], "")) { 213 switch (*fields[5]) { 214 case 'D': 215 if (sscanf(fields[5] + 1, "%d.%d.%d.%d.%d.%d", 216 &dt.tm_year, &dt.tm_mon, &dt.tm_mday, 217 &dt.tm_hour, &dt.tm_min, &dt.tm_sec) != 6) 218 fatal("wrong date specification"); 219 dt.tm_year -= 1900; 220 dt.tm_mon -= 1; 221 ent->ce_date = timegm(&dt); 222 ent->ce_tag = NULL; 223 break; 224 case 'T': 225 ent->ce_tag = fields[5] + 1; 226 break; 227 default: 228 fatal("invalid sticky entry"); 229 } 230 } 231 232 return (ent); 233 } 234 235 struct cvs_ent * 236 cvs_ent_get(CVSENTRIES *ep, const char *name) 237 { 238 struct cvs_ent *ent; 239 struct cvs_ent_line *l; 240 241 l = ent_get_line(ep, name); 242 if (l == NULL) 243 return (NULL); 244 245 ent = cvs_ent_parse(l->buf); 246 return (ent); 247 } 248 249 void 250 cvs_ent_close(CVSENTRIES *ep, int writefile) 251 { 252 FILE *fp; 253 struct cvs_ent_line *l; 254 int dflag; 255 256 dflag = 1; 257 cvs_log(LP_TRACE, "cvs_ent_close(%s, %d)", ep->cef_bpath, writefile); 258 259 if (cvs_cmdop == CVS_OP_EXPORT) 260 writefile = 0; 261 262 fp = NULL; 263 if (writefile) 264 fp = fopen(ep->cef_bpath, "w"); 265 266 while ((l = TAILQ_FIRST(&(ep->cef_ent))) != NULL) { 267 if (fp != NULL) { 268 if (l->buf[0] == 'D') 269 dflag = 0; 270 271 fputs(l->buf, fp); 272 fputc('\n', fp); 273 } 274 275 TAILQ_REMOVE(&(ep->cef_ent), l, entries_list); 276 xfree(l->buf); 277 xfree(l); 278 } 279 280 if (fp != NULL) { 281 if (dflag) { 282 fputc('D', fp); 283 fputc('\n', fp); 284 } 285 (void)fclose(fp); 286 287 if (rename(ep->cef_bpath, ep->cef_path) == -1) 288 fatal("cvs_ent_close: rename: `%s'->`%s': %s", 289 ep->cef_bpath, ep->cef_path, strerror(errno)); 290 291 (void)unlink(ep->cef_lpath); 292 } 293 294 xfree(ep->cef_path); 295 xfree(ep->cef_bpath); 296 xfree(ep->cef_lpath); 297 xfree(ep); 298 } 299 300 void 301 cvs_ent_add(CVSENTRIES *ep, const char *line) 302 { 303 FILE *fp; 304 struct cvs_ent_line *l; 305 struct cvs_ent *ent; 306 307 if ((ent = cvs_ent_parse(line)) == NULL) 308 fatal("cvs_ent_add: parsing failed '%s'", line); 309 310 l = ent_get_line(ep, ent->ce_name); 311 if (l != NULL) 312 cvs_ent_remove(ep, ent->ce_name); 313 314 cvs_ent_free(ent); 315 316 if (cvs_server_active == 0) 317 cvs_log(LP_TRACE, "cvs_ent_add(%s, %s)", ep->cef_path, line); 318 319 if ((fp = fopen(ep->cef_lpath, "a")) == NULL) 320 fatal("cvs_ent_add: fopen: `%s': %s", 321 ep->cef_lpath, strerror(errno)); 322 323 fputs("A ", fp); 324 fputs(line, fp); 325 fputc('\n', fp); 326 327 (void)fclose(fp); 328 329 l = (struct cvs_ent_line *)xmalloc(sizeof(*l)); 330 l->buf = xstrdup(line); 331 TAILQ_INSERT_TAIL(&(ep->cef_ent), l, entries_list); 332 } 333 334 void 335 cvs_ent_remove(CVSENTRIES *ep, const char *name) 336 { 337 FILE *fp; 338 struct cvs_ent_line *l; 339 340 if (cvs_server_active == 0) 341 cvs_log(LP_TRACE, "cvs_ent_remove(%s, %s)", ep->cef_path, name); 342 343 l = ent_get_line(ep, name); 344 if (l == NULL) 345 return; 346 347 if ((fp = fopen(ep->cef_lpath, "a")) == NULL) 348 fatal("cvs_ent_remove: fopen: `%s': %s", ep->cef_lpath, 349 strerror(errno)); 350 351 fputs("R ", fp); 352 fputs(l->buf, fp); 353 fputc('\n', fp); 354 355 (void)fclose(fp); 356 357 TAILQ_REMOVE(&(ep->cef_ent), l, entries_list); 358 xfree(l->buf); 359 xfree(l); 360 } 361 362 /* 363 * cvs_ent_line_str() 364 * 365 * Build CVS/Entries line. 366 * 367 */ 368 void 369 cvs_ent_line_str(const char *name, char *rev, char *tstamp, char *opts, 370 char *sticky, int isdir, int isremoved, char *buf, size_t len) 371 { 372 if (isdir == 1) { 373 (void)xsnprintf(buf, len, "D/%s////", name); 374 return; 375 } 376 377 (void)xsnprintf(buf, len, "/%s/%s%s/%s/%s/%s", 378 name, isremoved == 1 ? "-" : "", rev, tstamp, opts, sticky); 379 } 380 381 void 382 cvs_ent_free(struct cvs_ent *ent) 383 { 384 if (ent->ce_rev != NULL) 385 rcsnum_free(ent->ce_rev); 386 xfree(ent->ce_buf); 387 xfree(ent); 388 } 389 390 static struct cvs_ent_line * 391 ent_get_line(CVSENTRIES *ep, const char *name) 392 { 393 char *p, *s; 394 struct cvs_ent_line *l; 395 396 TAILQ_FOREACH(l, &(ep->cef_ent), entries_list) { 397 if (l->buf[0] == 'D') 398 p = &(l->buf[2]); 399 else 400 p = &(l->buf[1]); 401 402 if ((s = strchr(p, '/')) == NULL) 403 fatal("ent_get_line: bad entry line '%s'", l->buf); 404 405 *s = '\0'; 406 407 if (!strcmp(p, name)) { 408 *s = '/'; 409 return (l); 410 } 411 412 *s = '/'; 413 } 414 415 return (NULL); 416 } 417 418 void 419 cvs_parse_tagfile(char *dir, char **tagp, char **datep, int *nbp) 420 { 421 FILE *fp; 422 int i, linenum; 423 size_t len; 424 struct tm datetm; 425 char linebuf[128], tagpath[MAXPATHLEN]; 426 427 cvs_directory_date = -1; 428 429 if (tagp != NULL) 430 *tagp = NULL; 431 432 if (datep != NULL) 433 *datep = NULL; 434 435 if (nbp != NULL) 436 *nbp = 0; 437 438 i = snprintf(tagpath, MAXPATHLEN, "%s/%s", dir, CVS_PATH_TAG); 439 if (i < 0 || i >= MAXPATHLEN) 440 return; 441 442 if ((fp = fopen(tagpath, "r")) == NULL) { 443 if (errno != ENOENT) 444 cvs_log(LP_NOTICE, "failed to open `%s' : %s", tagpath, 445 strerror(errno)); 446 return; 447 } 448 449 linenum = 0; 450 451 while (fgets(linebuf, (int)sizeof(linebuf), fp) != NULL) { 452 linenum++; 453 if ((len = strlen(linebuf)) == 0) 454 continue; 455 if (linebuf[len - 1] != '\n') { 456 cvs_log(LP_NOTICE, "line too long in `%s:%d'", 457 tagpath, linenum); 458 break; 459 } 460 linebuf[--len] = '\0'; 461 462 switch (*linebuf) { 463 case 'T': 464 if (tagp != NULL) 465 *tagp = xstrdup(linebuf + 1); 466 break; 467 case 'D': 468 if (sscanf(linebuf + 1, "%d.%d.%d.%d.%d.%d", 469 &datetm.tm_year, &datetm.tm_mon, &datetm.tm_mday, 470 &datetm.tm_hour, &datetm.tm_min, &datetm.tm_sec) != 471 6) 472 fatal("wrong date specification"); 473 datetm.tm_year -= 1900; 474 datetm.tm_mon -= 1; 475 476 cvs_directory_date = timegm(&datetm); 477 478 if (datep != NULL) 479 *datep = xstrdup(linebuf + 1); 480 break; 481 case 'N': 482 if (tagp != NULL) 483 *tagp = xstrdup(linebuf + 1); 484 if (nbp != NULL) 485 *nbp = 1; 486 break; 487 default: 488 break; 489 } 490 } 491 if (ferror(fp)) 492 cvs_log(LP_NOTICE, "failed to read line from `%s'", tagpath); 493 494 (void)fclose(fp); 495 } 496 497 void 498 cvs_write_tagfile(const char *dir, char *tag, char *date) 499 { 500 FILE *fp; 501 RCSNUM *rev; 502 char tagpath[MAXPATHLEN]; 503 char sticky[CVS_REV_BUFSZ]; 504 struct tm datetm; 505 int i; 506 507 cvs_log(LP_TRACE, "cvs_write_tagfile(%s, %s, %s)", dir, 508 tag != NULL ? tag : "", date != NULL ? date : ""); 509 510 if (cvs_noexec == 1) 511 return; 512 513 i = snprintf(tagpath, MAXPATHLEN, "%s/%s", dir, CVS_PATH_TAG); 514 if (i < 0 || i >= MAXPATHLEN) 515 return; 516 517 if (tag != NULL || cvs_specified_date != -1 || 518 cvs_directory_date != -1) { 519 if ((fp = fopen(tagpath, "w+")) == NULL) { 520 if (errno != ENOENT) { 521 cvs_log(LP_NOTICE, "failed to open `%s' : %s", 522 tagpath, strerror(errno)); 523 } 524 return; 525 } 526 527 if (tag != NULL) { 528 if ((rev = rcsnum_parse(tag)) != NULL) { 529 (void)xsnprintf(sticky, sizeof(sticky), 530 "N%s", tag); 531 rcsnum_free(rev); 532 } else { 533 (void)xsnprintf(sticky, sizeof(sticky), 534 "T%s", tag); 535 } 536 } else { 537 if (cvs_specified_date != -1) 538 gmtime_r(&cvs_specified_date, &datetm); 539 else 540 gmtime_r(&cvs_directory_date, &datetm); 541 (void)strftime(sticky, sizeof(sticky), 542 "D"CVS_DATE_FMT, &datetm); 543 } 544 545 (void)fprintf(fp, "%s\n", sticky); 546 (void)fclose(fp); 547 } 548 } 549