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