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