1 /* Implementation for file attribute munging features. 2 3 This program is free software; you can redistribute it and/or modify 4 it under the terms of the GNU General Public License as published by 5 the Free Software Foundation; either version 2, or (at your option) 6 any later version. 7 8 This program is distributed in the hope that it will be useful, 9 but WITHOUT ANY WARRANTY; without even the implied warranty of 10 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 GNU General Public License for more details. */ 12 13 #include "cvs.h" 14 #include "getline.h" 15 #include "fileattr.h" 16 #include <assert.h> 17 18 static void fileattr_read PROTO ((void)); 19 static int writeattr_proc PROTO ((Node *, void *)); 20 21 /* Where to look for CVSREP_FILEATTR. */ 22 static char *fileattr_stored_repos; 23 24 /* The in-memory attributes. */ 25 static List *attrlist; 26 static char *fileattr_default_attrs; 27 /* We have already tried to read attributes and failed in this directory 28 (for example, there is no CVSREP_FILEATTR file). */ 29 static int attr_read_attempted; 30 31 /* Have the in-memory attributes been modified since we read them? */ 32 static int attrs_modified; 33 34 /* More in-memory attributes: linked list of unrecognized 35 fileattr lines. We pass these on unchanged. */ 36 struct unrecog { 37 char *line; 38 struct unrecog *next; 39 }; 40 static struct unrecog *unrecog_head; 41 42 /* Note that if noone calls fileattr_get, this is very cheap. No stat(), 43 no open(), no nothing. */ 44 void 45 fileattr_startdir (repos) 46 char *repos; 47 { 48 assert (fileattr_stored_repos == NULL); 49 fileattr_stored_repos = xstrdup (repos); 50 assert (attrlist == NULL); 51 attr_read_attempted = 0; 52 assert (unrecog_head == NULL); 53 } 54 55 static void 56 fileattr_delproc (node) 57 Node *node; 58 { 59 assert (node->data != NULL); 60 free (node->data); 61 node->data = NULL; 62 } 63 64 /* Read all the attributes for the current directory into memory. */ 65 static void 66 fileattr_read () 67 { 68 char *fname; 69 FILE *fp; 70 char *line = NULL; 71 size_t line_len = 0; 72 73 /* If there are no attributes, don't waste time repeatedly looking 74 for the CVSREP_FILEATTR file. */ 75 if (attr_read_attempted) 76 return; 77 78 /* If NULL was passed to fileattr_startdir, then it isn't kosher to look 79 at attributes. */ 80 assert (fileattr_stored_repos != NULL); 81 82 fname = xmalloc (strlen (fileattr_stored_repos) 83 + 1 84 + sizeof (CVSREP_FILEATTR) 85 + 1); 86 87 strcpy (fname, fileattr_stored_repos); 88 strcat (fname, "/"); 89 strcat (fname, CVSREP_FILEATTR); 90 91 attr_read_attempted = 1; 92 fp = CVS_FOPEN (fname, FOPEN_BINARY_READ); 93 if (fp == NULL) 94 { 95 if (!existence_error (errno)) 96 error (0, errno, "cannot read %s", fname); 97 free (fname); 98 return; 99 } 100 attrlist = getlist (); 101 while (1) { 102 int nread; 103 nread = get_line (&line, &line_len, fp); 104 if (nread < 0) 105 break; 106 /* Remove trailing newline. */ 107 line[nread - 1] = '\0'; 108 if (line[0] == 'F') 109 { 110 char *p; 111 Node *newnode; 112 113 p = strchr (line, '\t'); 114 if (p == NULL) 115 error (1, 0, 116 "file attribute database corruption: tab missing in %s", 117 fname); 118 *p++ = '\0'; 119 newnode = getnode (); 120 newnode->type = FILEATTR; 121 newnode->delproc = fileattr_delproc; 122 newnode->key = xstrdup (line + 1); 123 newnode->data = xstrdup (p); 124 if (addnode (attrlist, newnode) != 0) 125 /* If the same filename appears twice in the file, discard 126 any line other than the first for that filename. This 127 is the way that CVS has behaved since file attributes 128 were first introduced. */ 129 freenode (newnode); 130 } 131 else if (line[0] == 'D') 132 { 133 char *p; 134 /* Currently nothing to skip here, but for future expansion, 135 ignore anything located here. */ 136 p = strchr (line, '\t'); 137 if (p == NULL) 138 error (1, 0, 139 "file attribute database corruption: tab missing in %s", 140 fname); 141 ++p; 142 fileattr_default_attrs = xstrdup (p); 143 } 144 else 145 { 146 /* Unrecognized type, we want to just preserve the line without 147 changing it, for future expansion. */ 148 struct unrecog *new; 149 150 new = (struct unrecog *) xmalloc (sizeof (struct unrecog)); 151 new->line = xstrdup (line); 152 new->next = unrecog_head; 153 unrecog_head = new; 154 } 155 } 156 if (ferror (fp)) 157 error (0, errno, "cannot read %s", fname); 158 if (line != NULL) 159 free (line); 160 if (fclose (fp) < 0) 161 error (0, errno, "cannot close %s", fname); 162 attrs_modified = 0; 163 free (fname); 164 } 165 166 char * 167 fileattr_get (filename, attrname) 168 const char *filename; 169 const char *attrname; 170 { 171 Node *node; 172 size_t attrname_len = strlen (attrname); 173 char *p; 174 175 if (attrlist == NULL) 176 fileattr_read (); 177 if (attrlist == NULL) 178 /* Either nothing has any attributes, or fileattr_read already printed 179 an error message. */ 180 return NULL; 181 182 if (filename == NULL) 183 p = fileattr_default_attrs; 184 else 185 { 186 node = findnode (attrlist, filename); 187 if (node == NULL) 188 /* A file not mentioned has no attributes. */ 189 return NULL; 190 p = node->data; 191 } 192 while (p) 193 { 194 if (strncmp (attrname, p, attrname_len) == 0 195 && p[attrname_len] == '=') 196 { 197 /* Found it. */ 198 return p + attrname_len + 1; 199 } 200 p = strchr (p, ';'); 201 if (p == NULL) 202 break; 203 ++p; 204 } 205 /* The file doesn't have this attribute. */ 206 return NULL; 207 } 208 209 char * 210 fileattr_get0 (filename, attrname) 211 const char *filename; 212 const char *attrname; 213 { 214 char *cp; 215 char *cpend; 216 char *retval; 217 218 cp = fileattr_get (filename, attrname); 219 if (cp == NULL) 220 return NULL; 221 cpend = strchr (cp, ';'); 222 if (cpend == NULL) 223 cpend = cp + strlen (cp); 224 retval = xmalloc (cpend - cp + 1); 225 strncpy (retval, cp, cpend - cp); 226 retval[cpend - cp] = '\0'; 227 return retval; 228 } 229 230 char * 231 fileattr_modify (list, attrname, attrval, namevalsep, entsep) 232 char *list; 233 const char *attrname; 234 const char *attrval; 235 int namevalsep; 236 int entsep; 237 { 238 char *retval; 239 char *rp; 240 size_t attrname_len = strlen (attrname); 241 242 /* Portion of list before the attribute to be replaced. */ 243 char *pre; 244 char *preend; 245 /* Portion of list after the attribute to be replaced. */ 246 char *post; 247 248 char *p; 249 char *p2; 250 251 p = list; 252 pre = list; 253 preend = NULL; 254 /* post is NULL unless set otherwise. */ 255 post = NULL; 256 p2 = NULL; 257 if (list != NULL) 258 { 259 while (1) { 260 p2 = strchr (p, entsep); 261 if (p2 == NULL) 262 { 263 p2 = p + strlen (p); 264 if (preend == NULL) 265 preend = p2; 266 } 267 else 268 ++p2; 269 if (strncmp (attrname, p, attrname_len) == 0 270 && p[attrname_len] == namevalsep) 271 { 272 /* Found it. */ 273 preend = p; 274 if (preend > list) 275 /* Don't include the preceding entsep. */ 276 --preend; 277 278 post = p2; 279 } 280 if (p2[0] == '\0') 281 break; 282 p = p2; 283 } 284 } 285 if (post == NULL) 286 post = p2; 287 288 if (preend == pre && attrval == NULL && post == p2) 289 return NULL; 290 291 retval = xmalloc ((preend - pre) 292 + 1 293 + (attrval == NULL ? 0 : (attrname_len + 1 294 + strlen (attrval))) 295 + 1 296 + (p2 - post) 297 + 1); 298 if (preend != pre) 299 { 300 strncpy (retval, pre, preend - pre); 301 rp = retval + (preend - pre); 302 if (attrval != NULL) 303 *rp++ = entsep; 304 *rp = '\0'; 305 } 306 else 307 retval[0] = '\0'; 308 if (attrval != NULL) 309 { 310 strcat (retval, attrname); 311 rp = retval + strlen (retval); 312 *rp++ = namevalsep; 313 strcpy (rp, attrval); 314 } 315 if (post != p2) 316 { 317 rp = retval + strlen (retval); 318 if (preend != pre || attrval != NULL) 319 *rp++ = entsep; 320 strncpy (rp, post, p2 - post); 321 rp += p2 - post; 322 *rp = '\0'; 323 } 324 return retval; 325 } 326 327 void 328 fileattr_set (filename, attrname, attrval) 329 const char *filename; 330 const char *attrname; 331 const char *attrval; 332 { 333 Node *node; 334 char *p; 335 336 if (filename == NULL) 337 { 338 p = fileattr_modify (fileattr_default_attrs, attrname, attrval, 339 '=', ';'); 340 if (fileattr_default_attrs != NULL) 341 free (fileattr_default_attrs); 342 fileattr_default_attrs = p; 343 attrs_modified = 1; 344 return; 345 } 346 if (attrlist == NULL) 347 fileattr_read (); 348 if (attrlist == NULL) 349 { 350 /* Not sure this is a graceful way to handle things 351 in the case where fileattr_read was unable to read the file. */ 352 /* No attributes existed previously. */ 353 attrlist = getlist (); 354 } 355 356 node = findnode (attrlist, filename); 357 if (node == NULL) 358 { 359 if (attrval == NULL) 360 /* Attempt to remove an attribute which wasn't there. */ 361 return; 362 363 /* First attribute for this file. */ 364 node = getnode (); 365 node->type = FILEATTR; 366 node->delproc = fileattr_delproc; 367 node->key = xstrdup (filename); 368 node->data = xmalloc (strlen (attrname) + 1 + strlen (attrval) + 1); 369 strcpy (node->data, attrname); 370 strcat (node->data, "="); 371 strcat (node->data, attrval); 372 addnode (attrlist, node); 373 } 374 375 p = fileattr_modify (node->data, attrname, attrval, '=', ';'); 376 if (p == NULL) 377 delnode (node); 378 else 379 { 380 free (node->data); 381 node->data = p; 382 } 383 384 attrs_modified = 1; 385 } 386 387 char * 388 fileattr_getall (filename) 389 const char *filename; 390 { 391 Node *node; 392 char *p; 393 394 if (attrlist == NULL) 395 fileattr_read (); 396 if (attrlist == NULL) 397 /* Either nothing has any attributes, or fileattr_read already printed 398 an error message. */ 399 return NULL; 400 401 if (filename == NULL) 402 p = fileattr_default_attrs; 403 else 404 { 405 node = findnode (attrlist, filename); 406 if (node == NULL) 407 /* A file not mentioned has no attributes. */ 408 return NULL; 409 p = node->data; 410 } 411 return xstrdup (p); 412 } 413 414 void 415 fileattr_setall (filename, attrs) 416 const char *filename; 417 const char *attrs; 418 { 419 Node *node; 420 421 if (filename == NULL) 422 { 423 if (fileattr_default_attrs != NULL) 424 free (fileattr_default_attrs); 425 fileattr_default_attrs = xstrdup (attrs); 426 attrs_modified = 1; 427 return; 428 } 429 if (attrlist == NULL) 430 fileattr_read (); 431 if (attrlist == NULL) 432 { 433 /* Not sure this is a graceful way to handle things 434 in the case where fileattr_read was unable to read the file. */ 435 /* No attributes existed previously. */ 436 attrlist = getlist (); 437 } 438 439 node = findnode (attrlist, filename); 440 if (node == NULL) 441 { 442 /* The file had no attributes. Add them if we have any to add. */ 443 if (attrs != NULL) 444 { 445 node = getnode (); 446 node->type = FILEATTR; 447 node->delproc = fileattr_delproc; 448 node->key = xstrdup (filename); 449 node->data = xstrdup (attrs); 450 addnode (attrlist, node); 451 } 452 } 453 else 454 { 455 if (attrs == NULL) 456 delnode (node); 457 else 458 { 459 free (node->data); 460 node->data = xstrdup (attrs); 461 } 462 } 463 464 attrs_modified = 1; 465 } 466 467 void 468 fileattr_newfile (filename) 469 const char *filename; 470 { 471 Node *node; 472 473 if (attrlist == NULL) 474 fileattr_read (); 475 476 if (fileattr_default_attrs == NULL) 477 return; 478 479 if (attrlist == NULL) 480 { 481 /* Not sure this is a graceful way to handle things 482 in the case where fileattr_read was unable to read the file. */ 483 /* No attributes existed previously. */ 484 attrlist = getlist (); 485 } 486 487 node = getnode (); 488 node->type = FILEATTR; 489 node->delproc = fileattr_delproc; 490 node->key = xstrdup (filename); 491 node->data = xstrdup (fileattr_default_attrs); 492 addnode (attrlist, node); 493 attrs_modified = 1; 494 } 495 496 static int 497 writeattr_proc (node, data) 498 Node *node; 499 void *data; 500 { 501 FILE *fp = (FILE *)data; 502 fputs ("F", fp); 503 fputs (node->key, fp); 504 fputs ("\t", fp); 505 fputs (node->data, fp); 506 fputs ("\012", fp); 507 return 0; 508 } 509 510 void 511 fileattr_write () 512 { 513 FILE *fp; 514 char *fname; 515 mode_t omask; 516 struct unrecog *p; 517 518 if (!attrs_modified) 519 return; 520 521 if (noexec) 522 return; 523 524 /* If NULL was passed to fileattr_startdir, then it isn't kosher to set 525 attributes. */ 526 assert (fileattr_stored_repos != NULL); 527 528 fname = xmalloc (strlen (fileattr_stored_repos) 529 + 1 530 + sizeof (CVSREP_FILEATTR) 531 + 1); 532 533 strcpy (fname, fileattr_stored_repos); 534 strcat (fname, "/"); 535 strcat (fname, CVSREP_FILEATTR); 536 537 if (list_isempty (attrlist) 538 && fileattr_default_attrs == NULL 539 && unrecog_head == NULL) 540 { 541 /* There are no attributes. */ 542 if (unlink_file (fname) < 0) 543 { 544 if (!existence_error (errno)) 545 { 546 error (0, errno, "cannot remove %s", fname); 547 } 548 } 549 550 /* Now remove CVSREP directory, if empty. The main reason we bother 551 is that CVS 1.6 and earlier will choke if a CVSREP directory 552 exists, so provide the user a graceful way to remove it. */ 553 strcpy (fname, fileattr_stored_repos); 554 strcat (fname, "/"); 555 strcat (fname, CVSREP); 556 if (CVS_RMDIR (fname) < 0) 557 { 558 if (errno != ENOTEMPTY 559 560 /* Don't know why we would be here if there is no CVSREP 561 directory, but it seemed to be happening anyway, so 562 check for it. */ 563 && !existence_error (errno)) 564 error (0, errno, "cannot remove %s", fname); 565 } 566 567 free (fname); 568 return; 569 } 570 571 omask = umask (cvsumask); 572 fp = CVS_FOPEN (fname, FOPEN_BINARY_WRITE); 573 if (fp == NULL) 574 { 575 if (existence_error (errno)) 576 { 577 /* Maybe the CVSREP directory doesn't exist. Try creating it. */ 578 char *repname; 579 580 repname = xmalloc (strlen (fileattr_stored_repos) 581 + 1 582 + sizeof (CVSREP) 583 + 1); 584 strcpy (repname, fileattr_stored_repos); 585 strcat (repname, "/"); 586 strcat (repname, CVSREP); 587 588 if (CVS_MKDIR (repname, 0777) < 0 && errno != EEXIST) 589 { 590 error (0, errno, "cannot make directory %s", repname); 591 (void) umask (omask); 592 free (repname); 593 return; 594 } 595 free (repname); 596 597 fp = CVS_FOPEN (fname, FOPEN_BINARY_WRITE); 598 } 599 if (fp == NULL) 600 { 601 error (0, errno, "cannot write %s", fname); 602 (void) umask (omask); 603 return; 604 } 605 } 606 (void) umask (omask); 607 608 /* First write the "F" attributes. */ 609 walklist (attrlist, writeattr_proc, fp); 610 611 /* Then the "D" attribute. */ 612 if (fileattr_default_attrs != NULL) 613 { 614 fputs ("D\t", fp); 615 fputs (fileattr_default_attrs, fp); 616 fputs ("\012", fp); 617 } 618 619 /* Then any other attributes. */ 620 for (p = unrecog_head; p != NULL; p = p->next) 621 { 622 fputs (p->line, fp); 623 fputs ("\012", fp); 624 } 625 626 if (fclose (fp) < 0) 627 error (0, errno, "cannot close %s", fname); 628 attrs_modified = 0; 629 free (fname); 630 } 631 632 void 633 fileattr_free () 634 { 635 /* Note that attrs_modified will ordinarily be zero, but there are 636 a few cases in which fileattr_write will fail to zero it (if 637 noexec is set, or error conditions). This probably is the way 638 it should be. */ 639 dellist (&attrlist); 640 if (fileattr_stored_repos != NULL) 641 free (fileattr_stored_repos); 642 fileattr_stored_repos = NULL; 643 if (fileattr_default_attrs != NULL) 644 free (fileattr_default_attrs); 645 fileattr_default_attrs = NULL; 646 while (unrecog_head) 647 { 648 struct unrecog *p = unrecog_head; 649 unrecog_head = p->next; 650 free (p->line); 651 free (p); 652 } 653 } 654