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