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 = 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 158 char * 159 fileattr_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 201 char * 202 fileattr_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 222 char * 223 fileattr_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 319 void 320 fileattr_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 379 void 380 fileattr_newfile (filename) 381 const char *filename; 382 { 383 Node *node; 384 385 if (attrlist == NULL) 386 fileattr_read (); 387 388 if (fileattr_default_attrs == NULL) 389 return; 390 391 if (attrlist == NULL) 392 { 393 /* Not sure this is a graceful way to handle things 394 in the case where fileattr_read was unable to read the file. */ 395 /* No attributes existed previously. */ 396 attrlist = getlist (); 397 } 398 399 node = getnode (); 400 node->type = FILEATTR; 401 node->delproc = fileattr_delproc; 402 node->key = xstrdup (filename); 403 node->data = xstrdup (fileattr_default_attrs); 404 addnode (attrlist, node); 405 attrs_modified = 1; 406 } 407 408 static int 409 writeattr_proc (node, data) 410 Node *node; 411 void *data; 412 { 413 FILE *fp = (FILE *)data; 414 fputs ("F", fp); 415 fputs (node->key, fp); 416 fputs ("\t", fp); 417 fputs (node->data, fp); 418 fputs ("\012", fp); 419 return 0; 420 } 421 422 void 423 fileattr_write () 424 { 425 FILE *fp; 426 char *fname; 427 mode_t omask; 428 429 if (!attrs_modified) 430 return; 431 432 if (noexec) 433 return; 434 435 /* If NULL was passed to fileattr_startdir, then it isn't kosher to set 436 attributes. */ 437 assert (fileattr_stored_repos != NULL); 438 439 fname = xmalloc (strlen (fileattr_stored_repos) 440 + 1 441 + sizeof (CVSREP_FILEATTR) 442 + 1); 443 444 strcpy (fname, fileattr_stored_repos); 445 strcat (fname, "/"); 446 strcat (fname, CVSREP_FILEATTR); 447 448 if (list_isempty (attrlist) 449 && fileattr_default_attrs == NULL 450 && unrecog_head == NULL) 451 { 452 /* There are no attributes. */ 453 if (unlink_file (fname) < 0) 454 { 455 if (!existence_error (errno)) 456 { 457 error (0, errno, "cannot remove %s", fname); 458 } 459 } 460 461 /* Now remove CVSREP directory, if empty. The main reason we bother 462 is that CVS 1.6 and earlier will choke if a CVSREP directory 463 exists, so provide the user a graceful way to remove it. */ 464 strcpy (fname, fileattr_stored_repos); 465 strcat (fname, "/"); 466 strcat (fname, CVSREP); 467 if (CVS_RMDIR (fname) < 0) 468 { 469 if (errno != ENOTEMPTY 470 471 /* Don't know why we would be here if there is no CVSREP 472 directory, but it seemed to be happening anyway, so 473 check for it. */ 474 && !existence_error (errno)) 475 error (0, errno, "cannot remove %s", fname); 476 } 477 478 free (fname); 479 return; 480 } 481 482 omask = umask (cvsumask); 483 fp = CVS_FOPEN (fname, FOPEN_BINARY_WRITE); 484 if (fp == NULL) 485 { 486 if (existence_error (errno)) 487 { 488 /* Maybe the CVSREP directory doesn't exist. Try creating it. */ 489 char *repname; 490 491 repname = xmalloc (strlen (fileattr_stored_repos) 492 + 1 493 + sizeof (CVSREP) 494 + 1); 495 strcpy (repname, fileattr_stored_repos); 496 strcat (repname, "/"); 497 strcat (repname, CVSREP); 498 499 if (CVS_MKDIR (repname, 0777) < 0 && errno != EEXIST) 500 { 501 error (0, errno, "cannot make directory %s", repname); 502 (void) umask (omask); 503 free (repname); 504 return; 505 } 506 free (repname); 507 508 fp = CVS_FOPEN (fname, FOPEN_BINARY_WRITE); 509 } 510 if (fp == NULL) 511 { 512 error (0, errno, "cannot write %s", fname); 513 (void) umask (omask); 514 return; 515 } 516 } 517 (void) umask (omask); 518 519 /* First write the "F" attributes. */ 520 walklist (attrlist, writeattr_proc, fp); 521 522 /* Then the "D" attribute. */ 523 if (fileattr_default_attrs != NULL) 524 { 525 fputs ("D\t", fp); 526 fputs (fileattr_default_attrs, fp); 527 fputs ("\012", fp); 528 } 529 530 /* Then any other attributes. */ 531 while (unrecog_head != NULL) 532 { 533 struct unrecog *p; 534 535 p = unrecog_head; 536 fputs (p->line, fp); 537 fputs ("\012", fp); 538 539 unrecog_head = p->next; 540 free (p->line); 541 free (p); 542 } 543 544 if (fclose (fp) < 0) 545 error (0, errno, "cannot close %s", fname); 546 attrs_modified = 0; 547 free (fname); 548 } 549 550 void 551 fileattr_free () 552 { 553 dellist (&attrlist); 554 if (fileattr_stored_repos != NULL) 555 free (fileattr_stored_repos); 556 fileattr_stored_repos = NULL; 557 if (fileattr_default_attrs != NULL) 558 free (fileattr_default_attrs); 559 fileattr_default_attrs = NULL; 560 } 561