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