1 /* $OpenBSD: entries.c,v 1.15 2004/08/13 13:24:13 jfb 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/param.h> 28 #include <sys/stat.h> 29 30 #include <stdio.h> 31 #include <fcntl.h> 32 #include <stdlib.h> 33 #include <unistd.h> 34 #include <string.h> 35 36 #include "log.h" 37 #include "cvs.h" 38 39 40 #define CVS_ENTRIES_NFIELDS 6 41 #define CVS_ENTRIES_DELIM '/' 42 43 44 45 /* 46 * cvs_ent_open() 47 * 48 * Open the CVS Entries file for the directory <dir>. 49 * Returns a pointer to the CVSENTRIES file structure on success, or NULL 50 * on failure. 51 */ 52 53 CVSENTRIES* 54 cvs_ent_open(const char *dir, int flags) 55 { 56 size_t len; 57 int exists; 58 char entpath[MAXPATHLEN], ebuf[128], mode[4]; 59 FILE *fp; 60 struct stat st; 61 struct cvs_ent *ent; 62 CVSENTRIES *ep; 63 64 exists = 0; 65 memset(mode, 0, sizeof(mode)); 66 67 snprintf(entpath, sizeof(entpath), "%s/" CVS_PATH_ENTRIES, dir); 68 69 switch (flags & O_ACCMODE) { 70 case O_WRONLY: 71 case O_RDWR: 72 /* we have to use append otherwise the file gets truncated */ 73 mode[0] = 'w'; 74 mode[1] = '+'; 75 break; 76 case O_RDONLY: 77 mode[0] = 'r'; 78 break; 79 } 80 81 /* we can use 'r' if the file already exists */ 82 if (stat(entpath, &st) == 0) { 83 exists = 1; 84 mode[0] = 'r'; 85 } 86 87 fp = fopen(entpath, mode); 88 if (fp == NULL) { 89 cvs_log(LP_ERRNO, "cannot open %s for %s", entpath, 90 mode[1] == '+' ? "writing" : "reading"); 91 return (NULL); 92 } 93 94 ep = (CVSENTRIES *)malloc(sizeof(CVSENTRIES)); 95 if (ep == NULL) { 96 cvs_log(LP_ERRNO, "failed to allocate Entries data"); 97 (void)fclose(fp); 98 return (NULL); 99 } 100 memset(ep, 0, sizeof(*ep)); 101 102 ep->cef_path = strdup(dir); 103 if (ep->cef_path == NULL) { 104 cvs_log(LP_ERRNO, "failed to copy Entries path"); 105 free(ep); 106 (void)fclose(fp); 107 return (NULL); 108 } 109 110 ep->cef_cur = NULL; 111 TAILQ_INIT(&(ep->cef_ent)); 112 113 while (fgets(ebuf, sizeof(ebuf), fp) != NULL) { 114 len = strlen(ebuf); 115 if ((len > 0) && (ebuf[len - 1] == '\n')) 116 ebuf[--len] = '\0'; 117 if (strcmp(ebuf, "D") == 0) 118 break; 119 ent = cvs_ent_parse(ebuf); 120 if (ent == NULL) 121 continue; 122 123 TAILQ_INSERT_TAIL(&(ep->cef_ent), ent, ce_list); 124 } 125 if (ferror(fp)) { 126 cvs_ent_close(ep); 127 return (NULL); 128 } 129 130 /* only keep a pointer to the open file if we're in writing mode */ 131 if ((flags & O_WRONLY) || (flags & O_RDWR)) { 132 ep->cef_flags |= CVS_ENTF_WR; 133 ep->cef_file = fp; 134 } 135 else 136 (void)fclose(fp); 137 138 if (exists) 139 ep->cef_flags |= CVS_ENTF_SYNC; 140 141 return (ep); 142 } 143 144 145 /* 146 * cvs_ent_close() 147 * 148 * Close the Entries file <ep> and free all data. Any reference to entries 149 * structure within that file become invalid. 150 */ 151 152 void 153 cvs_ent_close(CVSENTRIES *ep) 154 { 155 struct cvs_ent *ent; 156 157 if ((ep->cef_flags & CVS_ENTF_WR) && 158 !(ep->cef_flags & CVS_ENTF_SYNC)) { 159 /* implicit sync with disk */ 160 (void)cvs_ent_write(ep); 161 } 162 163 if (ep->cef_file != NULL) 164 (void)fclose(ep->cef_file); 165 if (ep->cef_path != NULL) 166 free(ep->cef_path); 167 168 while (!TAILQ_EMPTY(&(ep->cef_ent))) { 169 ent = TAILQ_FIRST(&(ep->cef_ent)); 170 TAILQ_REMOVE(&(ep->cef_ent), ent, ce_list); 171 cvs_ent_free(ent); 172 } 173 174 free(ep); 175 } 176 177 178 /* 179 * cvs_ent_add() 180 * 181 * Add the entry <ent> to the Entries file <ef>. The disk contents are not 182 * modified until a call to cvs_ent_write() is performed. This is done 183 * implicitly on a call to cvs_ent_close() on an Entries file that has been 184 * opened for writing. 185 * Returns 0 on success, or -1 on failure. 186 */ 187 188 int 189 cvs_ent_add(CVSENTRIES *ef, struct cvs_ent *ent) 190 { 191 if (ef->cef_file == NULL) { 192 cvs_log(LP_ERR, "Entries file is opened in read-only mode"); 193 return (-1); 194 } 195 196 if (cvs_ent_get(ef, ent->ce_name) != NULL) 197 return (-1); 198 199 TAILQ_INSERT_TAIL(&(ef->cef_ent), ent, ce_list); 200 201 ef->cef_flags &= ~CVS_ENTF_SYNC; 202 203 return (0); 204 } 205 206 207 /* 208 * cvs_ent_addln() 209 * 210 * Add a line to the Entries file. 211 */ 212 213 int 214 cvs_ent_addln(CVSENTRIES *ef, const char *line) 215 { 216 struct cvs_ent *ent; 217 218 if (ef->cef_file == NULL) { 219 cvs_log(LP_ERR, "Entries file is opened in read-only mode"); 220 return (-1); 221 } 222 223 ent = cvs_ent_parse(line); 224 if (ent == NULL) 225 return (-1); 226 227 if (cvs_ent_get(ef, ent->ce_name) != NULL) 228 return (-1); 229 230 TAILQ_INSERT_TAIL(&(ef->cef_ent), ent, ce_list); 231 ef->cef_flags &= ~CVS_ENTF_SYNC; 232 233 return (0); 234 } 235 236 237 /* 238 * cvs_ent_remove() 239 * 240 * Remove an entry from the Entries file <ef>. The entry's name is given 241 * by <name>. 242 */ 243 244 int 245 cvs_ent_remove(CVSENTRIES *ef, const char *name) 246 { 247 struct cvs_ent *ent; 248 249 ent = cvs_ent_get(ef, name); 250 if (ent == NULL) 251 return (-1); 252 253 TAILQ_REMOVE(&(ef->cef_ent), ent, ce_list); 254 cvs_ent_free(ent); 255 256 ef->cef_flags &= ~CVS_ENTF_SYNC; 257 258 return (0); 259 } 260 261 262 /* 263 * cvs_ent_get() 264 * 265 * Get the CVS entry from the Entries file <ef> whose 'name' portion matches 266 * <file>. 267 * Returns a pointer to the cvs entry structure on success, or NULL on failure. 268 */ 269 270 struct cvs_ent* 271 cvs_ent_get(CVSENTRIES *ef, const char *file) 272 { 273 struct cvs_ent *ep; 274 275 TAILQ_FOREACH(ep, &(ef->cef_ent), ce_list) 276 if (strcmp(ep->ce_name, file) == 0) 277 return (ep); 278 279 return (NULL); 280 } 281 282 283 /* 284 * cvs_ent_next() 285 * 286 * This function is used to iterate over the entries in an Entries file. The 287 * first call will return the first entry of the file and each subsequent call 288 * will return the entry following the last one returned. 289 * Returns a pointer to the cvs entry structure on success, or NULL on failure. 290 */ 291 292 struct cvs_ent* 293 cvs_ent_next(CVSENTRIES *ef) 294 { 295 if (ef->cef_cur == NULL) 296 ef->cef_cur = TAILQ_FIRST(&(ef->cef_ent)); 297 else 298 ef->cef_cur = TAILQ_NEXT(ef->cef_cur, ce_list); 299 return (ef->cef_cur); 300 } 301 302 303 /* 304 * cvs_ent_parse() 305 * 306 * Parse a single line from a CVS/Entries file and return a cvs_entry structure 307 * containing all the parsed information. 308 */ 309 310 struct cvs_ent* 311 cvs_ent_parse(const char *entry) 312 { 313 int i; 314 char *fields[CVS_ENTRIES_NFIELDS], *buf, *sp, *dp; 315 struct cvs_ent *entp; 316 317 buf = strdup(entry); 318 if (buf == NULL) { 319 cvs_log(LP_ERRNO, "failed to allocate entry copy"); 320 return (NULL); 321 } 322 323 sp = buf; 324 i = 0; 325 do { 326 dp = strchr(sp, CVS_ENTRIES_DELIM); 327 if (dp != NULL) 328 *(dp++) = '\0'; 329 fields[i++] = sp; 330 sp = dp; 331 } while ((dp != NULL) && (i < CVS_ENTRIES_NFIELDS)); 332 333 if (i < CVS_ENTRIES_NFIELDS) { 334 cvs_log(LP_ERR, "missing fields in entry line `%s'", entry); 335 return (NULL); 336 } 337 338 entp = (struct cvs_ent *)malloc(sizeof(*entp)); 339 if (entp == NULL) { 340 cvs_log(LP_ERRNO, "failed to allocate CVS entry"); 341 return (NULL); 342 } 343 memset(entp, 0, sizeof(*entp)); 344 entp->ce_buf = buf; 345 346 entp->ce_rev = rcsnum_alloc(); 347 if (entp->ce_rev == NULL) { 348 cvs_ent_free(entp); 349 return (NULL); 350 } 351 352 entp->ce_line = strdup(entry); 353 if (entp->ce_line == NULL) { 354 cvs_ent_free(entp); 355 return (NULL); 356 } 357 358 if (*fields[0] == '\0') 359 entp->ce_type = CVS_ENT_FILE; 360 else if (*fields[0] == 'D') 361 entp->ce_type = CVS_ENT_DIR; 362 else 363 entp->ce_type = CVS_ENT_NONE; 364 365 entp->ce_name = fields[1]; 366 367 if (entp->ce_type == CVS_ENT_FILE) { 368 rcsnum_aton(fields[2], NULL, entp->ce_rev); 369 entp->ce_mtime = cvs_datesec(fields[3], CVS_DATE_CTIME, 0); 370 entp->ce_opts = fields[4]; 371 entp->ce_tag = fields[5]; 372 } 373 374 return (entp); 375 } 376 377 378 /* 379 * cvs_ent_free() 380 * 381 * Free a single CVS entries structure. 382 */ 383 384 void 385 cvs_ent_free(struct cvs_ent *ent) 386 { 387 if (ent->ce_rev != NULL) 388 rcsnum_free(ent->ce_rev); 389 if (ent->ce_line != NULL) 390 free(ent->ce_line); 391 if (ent->ce_buf != NULL) 392 free(ent->ce_buf); 393 free(ent); 394 } 395 396 397 /* 398 * cvs_ent_getent() 399 * 400 * Get a single entry from the CVS/Entries file of the basename portion of 401 * path <path> and return that entry. That entry must later be freed using 402 * cvs_ent_free(). 403 */ 404 405 struct cvs_ent* 406 cvs_ent_getent(const char *path) 407 { 408 char base[MAXPATHLEN], *file; 409 CVSENTRIES *entf; 410 struct cvs_ent *ep; 411 412 cvs_splitpath(path, base, sizeof(base), &file); 413 414 entf = cvs_ent_open(base, O_RDONLY); 415 if (entf == NULL) 416 return (NULL); 417 418 ep = cvs_ent_get(entf, file); 419 if (ep != NULL) { 420 /* take it out of the queue so it doesn't get freed */ 421 TAILQ_REMOVE(&(entf->cef_ent), ep, ce_list); 422 } 423 424 cvs_ent_close(entf); 425 return (ep); 426 } 427 428 429 /* 430 * cvs_ent_write() 431 * 432 * Explicitly write the contents of the Entries file <ef> to disk. 433 * Returns 0 on success, or -1 on failure. 434 */ 435 436 int 437 cvs_ent_write(CVSENTRIES *ef) 438 { 439 size_t len; 440 char revbuf[64], timebuf[32]; 441 struct cvs_ent *ent; 442 443 if (ef->cef_file == NULL) 444 return (-1); 445 446 if (ef->cef_flags & CVS_ENTF_SYNC) 447 return (0); 448 449 /* reposition ourself at beginning of file */ 450 rewind(ef->cef_file); 451 TAILQ_FOREACH(ent, &(ef->cef_ent), ce_list) { 452 if (ent->ce_type == CVS_ENT_DIR) { 453 putc('D', ef->cef_file); 454 timebuf[0] = '\0'; 455 revbuf[0] = '\0'; 456 } 457 else { 458 rcsnum_tostr(ent->ce_rev, revbuf, sizeof(revbuf)); 459 if (ent->ce_mtime == CVS_DATE_DMSEC) 460 strlcpy(timebuf, CVS_DATE_DUMMY, 461 sizeof(timebuf)); 462 else { 463 ctime_r(&(ent->ce_mtime), timebuf); 464 len = strlen(timebuf); 465 if ((len > 0) && (timebuf[len - 1] == '\n')) 466 timebuf[--len] = '\0'; 467 } 468 } 469 470 fprintf(ef->cef_file, "/%s/%s/%s/%s/%s\n", ent->ce_name, 471 revbuf, timebuf, "", ""); 472 } 473 474 /* terminating line */ 475 fprintf(ef->cef_file, "D\n"); 476 477 ef->cef_flags |= CVS_ENTF_SYNC; 478 479 return (0); 480 } 481