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 You should have received a copy of the GNU General Public License 14 along with this program; if not, write to the Free Software 15 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ 16 17 #include "cvs.h" 18 #include "getline.h" 19 #include "fileattr.h" 20 #include <assert.h> 21 22 static void fileattr_read PROTO ((void)); 23 static int writeattr_proc PROTO ((Node *, void *)); 24 25 /* Where to look for CVSREP_FILEATTR. */ 26 static char *fileattr_stored_repos; 27 28 /* The in-memory attributes. */ 29 static List *attrlist; 30 static char *fileattr_default_attrs; 31 /* We have already tried to read attributes and failed in this directory 32 (for example, there is no CVSREP_FILEATTR file). */ 33 static int attr_read_attempted; 34 35 /* Have the in-memory attributes been modified since we read them? */ 36 static int attrs_modified; 37 38 /* Note that if noone calls fileattr_get, this is very cheap. No stat(), 39 no open(), no nothing. */ 40 void 41 fileattr_startdir (repos) 42 char *repos; 43 { 44 assert (fileattr_stored_repos == NULL); 45 fileattr_stored_repos = xstrdup (repos); 46 assert (attrlist == NULL); 47 attr_read_attempted = 0; 48 } 49 50 static void 51 fileattr_delproc (node) 52 Node *node; 53 { 54 assert (node->data != NULL); 55 free (node->data); 56 node->data = NULL; 57 } 58 59 /* Read all the attributes for the current directory into memory. */ 60 static void 61 fileattr_read () 62 { 63 char *fname; 64 FILE *fp; 65 char *line = NULL; 66 size_t line_len = 0; 67 68 /* If there are no attributes, don't waste time repeatedly looking 69 for the CVSREP_FILEATTR file. */ 70 if (attr_read_attempted) 71 return; 72 73 /* If NULL was passed to fileattr_startdir, then it isn't kosher to look 74 at attributes. */ 75 assert (fileattr_stored_repos != NULL); 76 77 fname = xmalloc (strlen (fileattr_stored_repos) 78 + 1 79 + sizeof (CVSREP_FILEATTR) 80 + 1); 81 82 strcpy (fname, fileattr_stored_repos); 83 strcat (fname, "/"); 84 strcat (fname, CVSREP_FILEATTR); 85 86 attr_read_attempted = 1; 87 fp = CVS_FOPEN (fname, FOPEN_BINARY_READ); 88 if (fp == NULL) 89 { 90 if (!existence_error (errno)) 91 error (0, errno, "cannot read %s", fname); 92 free (fname); 93 return; 94 } 95 attrlist = getlist (); 96 while (1) { 97 int nread; 98 nread = getline (&line, &line_len, fp); 99 if (nread < 0) 100 break; 101 /* Remove trailing newline. */ 102 line[nread - 1] = '\0'; 103 if (line[0] == 'F') 104 { 105 char *p; 106 Node *newnode; 107 108 p = strchr (line, '\t'); 109 *p++ = '\0'; 110 newnode = getnode (); 111 newnode->type = FILEATTR; 112 newnode->delproc = fileattr_delproc; 113 newnode->key = xstrdup (line + 1); 114 newnode->data = xstrdup (p); 115 addnode (attrlist, newnode); 116 } 117 else if (line[0] == 'D') 118 { 119 char *p; 120 /* Currently nothing to skip here, but for future expansion, 121 ignore anything located here. */ 122 p = strchr (line, '\t'); 123 ++p; 124 fileattr_default_attrs = xstrdup (p); 125 } 126 /* else just ignore the line, for future expansion. */ 127 } 128 if (ferror (fp)) 129 error (0, errno, "cannot read %s", fname); 130 if (line != NULL) 131 free (line); 132 if (fclose (fp) < 0) 133 error (0, errno, "cannot close %s", fname); 134 attrs_modified = 0; 135 free (fname); 136 } 137 138 char * 139 fileattr_get (filename, attrname) 140 const char *filename; 141 const char *attrname; 142 { 143 Node *node; 144 size_t attrname_len = strlen (attrname); 145 char *p; 146 147 if (attrlist == NULL) 148 fileattr_read (); 149 if (attrlist == NULL) 150 /* Either nothing has any attributes, or fileattr_read already printed 151 an error message. */ 152 return NULL; 153 154 if (filename == NULL) 155 p = fileattr_default_attrs; 156 else 157 { 158 node = findnode (attrlist, filename); 159 if (node == NULL) 160 /* A file not mentioned has no attributes. */ 161 return NULL; 162 p = node->data; 163 } 164 while (p) 165 { 166 if (strncmp (attrname, p, attrname_len) == 0 167 && p[attrname_len] == '=') 168 { 169 /* Found it. */ 170 return p + attrname_len + 1; 171 } 172 p = strchr (p, ';'); 173 if (p == NULL) 174 break; 175 ++p; 176 } 177 /* The file doesn't have this attribute. */ 178 return NULL; 179 } 180 181 char * 182 fileattr_get0 (filename, attrname) 183 const char *filename; 184 const char *attrname; 185 { 186 char *cp; 187 char *cpend; 188 char *retval; 189 190 cp = fileattr_get (filename, attrname); 191 if (cp == NULL) 192 return NULL; 193 cpend = strchr (cp, ';'); 194 if (cpend == NULL) 195 cpend = cp + strlen (cp); 196 retval = xmalloc (cpend - cp + 1); 197 strncpy (retval, cp, cpend - cp); 198 retval[cpend - cp] = '\0'; 199 return retval; 200 } 201 202 char * 203 fileattr_modify (list, attrname, attrval, namevalsep, entsep) 204 char *list; 205 const char *attrname; 206 const char *attrval; 207 int namevalsep; 208 int entsep; 209 { 210 char *retval; 211 char *rp; 212 size_t attrname_len = strlen (attrname); 213 214 /* Portion of list before the attribute to be replaced. */ 215 char *pre; 216 char *preend; 217 /* Portion of list after the attribute to be replaced. */ 218 char *post; 219 220 char *p; 221 char *p2; 222 223 p = list; 224 pre = list; 225 preend = NULL; 226 /* post is NULL unless set otherwise. */ 227 post = NULL; 228 p2 = NULL; 229 if (list != NULL) 230 { 231 while (1) { 232 p2 = strchr (p, entsep); 233 if (p2 == NULL) 234 { 235 p2 = p + strlen (p); 236 if (preend == NULL) 237 preend = p2; 238 } 239 else 240 ++p2; 241 if (strncmp (attrname, p, attrname_len) == 0 242 && p[attrname_len] == namevalsep) 243 { 244 /* Found it. */ 245 preend = p; 246 if (preend > list) 247 /* Don't include the preceding entsep. */ 248 --preend; 249 250 post = p2; 251 } 252 if (p2[0] == '\0') 253 break; 254 p = p2; 255 } 256 } 257 if (post == NULL) 258 post = p2; 259 260 if (preend == pre && attrval == NULL && post == p2) 261 return NULL; 262 263 retval = xmalloc ((preend - pre) 264 + 1 265 + (attrval == NULL ? 0 : (attrname_len + 1 266 + strlen (attrval))) 267 + 1 268 + (p2 - post) 269 + 1); 270 if (preend != pre) 271 { 272 strncpy (retval, pre, preend - pre); 273 rp = retval + (preend - pre); 274 if (attrval != NULL) 275 *rp++ = entsep; 276 *rp = '\0'; 277 } 278 else 279 retval[0] = '\0'; 280 if (attrval != NULL) 281 { 282 strcat (retval, attrname); 283 rp = retval + strlen (retval); 284 *rp++ = namevalsep; 285 strcpy (rp, attrval); 286 } 287 if (post != p2) 288 { 289 rp = retval + strlen (retval); 290 if (preend != pre || attrval != NULL) 291 *rp++ = entsep; 292 strncpy (rp, post, p2 - post); 293 rp += p2 - post; 294 *rp = '\0'; 295 } 296 return retval; 297 } 298 299 void 300 fileattr_set (filename, attrname, attrval) 301 const char *filename; 302 const char *attrname; 303 const char *attrval; 304 { 305 Node *node; 306 char *p; 307 308 attrs_modified = 1; 309 310 if (filename == NULL) 311 { 312 p = fileattr_modify (fileattr_default_attrs, attrname, attrval, 313 '=', ';'); 314 if (fileattr_default_attrs != NULL) 315 free (fileattr_default_attrs); 316 fileattr_default_attrs = p; 317 return; 318 } 319 if (attrlist == NULL) 320 fileattr_read (); 321 if (attrlist == NULL) 322 { 323 /* Not sure this is a graceful way to handle things 324 in the case where fileattr_read was unable to read the file. */ 325 /* No attributes existed previously. */ 326 attrlist = getlist (); 327 } 328 329 node = findnode (attrlist, filename); 330 if (node == NULL) 331 { 332 if (attrval == NULL) 333 /* Attempt to remove an attribute which wasn't there. */ 334 return; 335 336 /* First attribute for this file. */ 337 node = getnode (); 338 node->type = FILEATTR; 339 node->delproc = fileattr_delproc; 340 node->key = xstrdup (filename); 341 node->data = xmalloc (strlen (attrname) + 1 + strlen (attrval) + 1); 342 strcpy (node->data, attrname); 343 strcat (node->data, "="); 344 strcat (node->data, attrval); 345 addnode (attrlist, node); 346 } 347 348 p = fileattr_modify (node->data, attrname, attrval, '=', ';'); 349 if (p == NULL) 350 delnode (node); 351 else 352 { 353 free (node->data); 354 node->data = p; 355 } 356 } 357 358 void 359 fileattr_newfile (filename) 360 const char *filename; 361 { 362 Node *node; 363 364 if (attrlist == NULL) 365 fileattr_read (); 366 367 if (fileattr_default_attrs == NULL) 368 return; 369 370 if (attrlist == NULL) 371 { 372 /* Not sure this is a graceful way to handle things 373 in the case where fileattr_read was unable to read the file. */ 374 /* No attributes existed previously. */ 375 attrlist = getlist (); 376 } 377 378 node = getnode (); 379 node->type = FILEATTR; 380 node->delproc = fileattr_delproc; 381 node->key = xstrdup (filename); 382 node->data = xstrdup (fileattr_default_attrs); 383 addnode (attrlist, node); 384 attrs_modified = 1; 385 } 386 387 static int 388 writeattr_proc (node, data) 389 Node *node; 390 void *data; 391 { 392 FILE *fp = (FILE *)data; 393 fputs ("F", fp); 394 fputs (node->key, fp); 395 fputs ("\t", fp); 396 fputs (node->data, fp); 397 fputs ("\012", fp); 398 return 0; 399 } 400 401 void 402 fileattr_write () 403 { 404 FILE *fp; 405 char *fname; 406 mode_t omask; 407 408 if (!attrs_modified) 409 return; 410 411 if (noexec) 412 return; 413 414 /* If NULL was passed to fileattr_startdir, then it isn't kosher to set 415 attributes. */ 416 assert (fileattr_stored_repos != NULL); 417 418 fname = xmalloc (strlen (fileattr_stored_repos) 419 + 1 420 + sizeof (CVSREP_FILEATTR) 421 + 1); 422 423 strcpy (fname, fileattr_stored_repos); 424 strcat (fname, "/"); 425 strcat (fname, CVSREP_FILEATTR); 426 427 if (list_isempty (attrlist) && fileattr_default_attrs == NULL) 428 { 429 /* There are no attributes. */ 430 if (unlink_file (fname) < 0) 431 { 432 if (!existence_error (errno)) 433 { 434 error (0, errno, "cannot remove %s", fname); 435 } 436 } 437 438 /* Now remove CVSREP directory, if empty. The main reason we bother 439 is that CVS 1.6 and earlier will choke if a CVSREP directory 440 exists, so provide the user a graceful way to remove it. */ 441 strcpy (fname, fileattr_stored_repos); 442 strcat (fname, "/"); 443 strcat (fname, CVSREP); 444 if (CVS_RMDIR (fname) < 0) 445 { 446 if (errno != ENOTEMPTY 447 448 /* Don't know why we would be here if there is no CVSREP 449 directory, but it seemed to be happening anyway, so 450 check for it. */ 451 && !existence_error (errno)) 452 error (0, errno, "cannot remove %s", fname); 453 } 454 455 free (fname); 456 return; 457 } 458 459 omask = umask (cvsumask); 460 fp = CVS_FOPEN (fname, FOPEN_BINARY_WRITE); 461 if (fp == NULL) 462 { 463 if (existence_error (errno)) 464 { 465 /* Maybe the CVSREP directory doesn't exist. Try creating it. */ 466 char *repname; 467 468 repname = xmalloc (strlen (fileattr_stored_repos) 469 + 1 470 + sizeof (CVSREP) 471 + 1); 472 strcpy (repname, fileattr_stored_repos); 473 strcat (repname, "/"); 474 strcat (repname, CVSREP); 475 476 if (CVS_MKDIR (repname, 0777) < 0 && errno != EEXIST) 477 { 478 error (0, errno, "cannot make directory %s", repname); 479 (void) umask (omask); 480 free (repname); 481 return; 482 } 483 free (repname); 484 485 fp = CVS_FOPEN (fname, FOPEN_BINARY_WRITE); 486 } 487 if (fp == NULL) 488 { 489 error (0, errno, "cannot write %s", fname); 490 (void) umask (omask); 491 return; 492 } 493 } 494 (void) umask (omask); 495 walklist (attrlist, writeattr_proc, fp); 496 if (fileattr_default_attrs != NULL) 497 { 498 fputs ("D\t", fp); 499 fputs (fileattr_default_attrs, fp); 500 fputs ("\012", fp); 501 } 502 if (fclose (fp) < 0) 503 error (0, errno, "cannot close %s", fname); 504 attrs_modified = 0; 505 free (fname); 506 } 507 508 void 509 fileattr_free () 510 { 511 dellist (&attrlist); 512 if (fileattr_stored_repos != NULL) 513 free (fileattr_stored_repos); 514 fileattr_stored_repos = NULL; 515 if (fileattr_default_attrs != NULL) 516 free (fileattr_default_attrs); 517 fileattr_default_attrs = NULL; 518 } 519