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