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