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, "r"); 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 node = findnode (attrlist, filename); 153 if (node == NULL) 154 /* A file not mentioned has no attributes. */ 155 return NULL; 156 p = node->data; 157 while (1) { 158 if (strncmp (attrname, p, attrname_len) == 0 159 && p[attrname_len] == '=') 160 { 161 /* Found it. */ 162 return p + attrname_len + 1; 163 } 164 p = strchr (p, ';'); 165 if (p == NULL) 166 break; 167 ++p; 168 } 169 /* The file doesn't have this attribute. */ 170 return NULL; 171 } 172 173 char * 174 fileattr_get0 (filename, attrname) 175 char *filename; 176 char *attrname; 177 { 178 char *cp; 179 char *cpend; 180 char *retval; 181 182 cp = fileattr_get (filename, attrname); 183 if (cp == NULL) 184 return NULL; 185 cpend = strchr (cp, ';'); 186 if (cpend == NULL) 187 cpend = cp + strlen (cp); 188 retval = xmalloc (cpend - cp + 1); 189 strncpy (retval, cp, cpend - cp); 190 retval[cpend - cp] = '\0'; 191 return retval; 192 } 193 194 char * 195 fileattr_modify (list, attrname, attrval, namevalsep, entsep) 196 char *list; 197 char *attrname; 198 char *attrval; 199 int namevalsep; 200 int entsep; 201 { 202 char *retval; 203 char *rp; 204 size_t attrname_len = strlen (attrname); 205 206 /* Portion of list before the attribute to be replaced. */ 207 char *pre; 208 char *preend; 209 /* Portion of list after the attribute to be replaced. */ 210 char *post; 211 212 char *p; 213 char *p2; 214 215 p = list; 216 pre = list; 217 preend = NULL; 218 /* post is NULL unless set otherwise. */ 219 post = NULL; 220 p2 = NULL; 221 if (list != NULL) 222 { 223 while (1) { 224 p2 = strchr (p, entsep); 225 if (p2 == NULL) 226 { 227 p2 = p + strlen (p); 228 if (preend == NULL) 229 preend = p2; 230 } 231 else 232 ++p2; 233 if (strncmp (attrname, p, attrname_len) == 0 234 && p[attrname_len] == namevalsep) 235 { 236 /* Found it. */ 237 preend = p; 238 if (preend > list) 239 /* Don't include the preceding entsep. */ 240 --preend; 241 242 post = p2; 243 } 244 if (p2[0] == '\0') 245 break; 246 p = p2; 247 } 248 } 249 if (post == NULL) 250 post = p2; 251 252 if (preend == pre && attrval == NULL && post == p2) 253 return NULL; 254 255 retval = xmalloc ((preend - pre) 256 + 1 257 + (attrval == NULL ? 0 : (attrname_len + 1 258 + strlen (attrval))) 259 + 1 260 + (p2 - post) 261 + 1); 262 if (preend != pre) 263 { 264 strncpy (retval, pre, preend - pre); 265 rp = retval + (preend - pre); 266 if (attrval != NULL) 267 *rp++ = entsep; 268 *rp = '\0'; 269 } 270 else 271 retval[0] = '\0'; 272 if (attrval != NULL) 273 { 274 strcat (retval, attrname); 275 rp = retval + strlen (retval); 276 *rp++ = namevalsep; 277 strcpy (rp, attrval); 278 } 279 if (post != p2) 280 { 281 rp = retval + strlen (retval); 282 if (preend != pre || attrval != NULL) 283 *rp++ = entsep; 284 strncpy (rp, post, p2 - post); 285 rp += p2 - post; 286 *rp = '\0'; 287 } 288 return retval; 289 } 290 291 void 292 fileattr_set (filename, attrname, attrval) 293 char *filename; 294 char *attrname; 295 char *attrval; 296 { 297 Node *node; 298 char *p; 299 300 attrs_modified = 1; 301 302 if (filename == NULL) 303 { 304 p = fileattr_modify (fileattr_default_attrs, attrname, attrval, 305 '=', ';'); 306 if (fileattr_default_attrs != NULL) 307 free (fileattr_default_attrs); 308 fileattr_default_attrs = p; 309 return; 310 } 311 if (attrlist == NULL) 312 fileattr_read (); 313 if (attrlist == NULL) 314 { 315 /* Not sure this is a graceful way to handle things 316 in the case where fileattr_read was unable to read the file. */ 317 /* No attributes existed previously. */ 318 attrlist = getlist (); 319 } 320 321 node = findnode (attrlist, filename); 322 if (node == NULL) 323 { 324 if (attrval == NULL) 325 /* Attempt to remove an attribute which wasn't there. */ 326 return; 327 328 /* First attribute for this file. */ 329 node = getnode (); 330 node->type = FILEATTR; 331 node->delproc = fileattr_delproc; 332 node->key = xstrdup (filename); 333 node->data = xmalloc (strlen (attrname) + 1 + strlen (attrval) + 1); 334 strcpy (node->data, attrname); 335 strcat (node->data, "="); 336 strcat (node->data, attrval); 337 addnode (attrlist, node); 338 } 339 340 p = fileattr_modify (node->data, attrname, attrval, '=', ';'); 341 free (node->data); 342 if (p == NULL) 343 delnode (node); 344 else 345 node->data = p; 346 } 347 348 void 349 fileattr_newfile (filename) 350 char *filename; 351 { 352 Node *node; 353 354 if (attrlist == NULL) 355 fileattr_read (); 356 357 if (fileattr_default_attrs == NULL) 358 return; 359 360 if (attrlist == NULL) 361 { 362 /* Not sure this is a graceful way to handle things 363 in the case where fileattr_read was unable to read the file. */ 364 /* No attributes existed previously. */ 365 attrlist = getlist (); 366 } 367 368 node = getnode (); 369 node->type = FILEATTR; 370 node->delproc = fileattr_delproc; 371 node->key = xstrdup (filename); 372 node->data = xstrdup (fileattr_default_attrs); 373 addnode (attrlist, node); 374 attrs_modified = 1; 375 } 376 377 static int 378 writeattr_proc (node, data) 379 Node *node; 380 void *data; 381 { 382 FILE *fp = (FILE *)data; 383 fputs ("F", fp); 384 fputs (node->key, fp); 385 fputs ("\t", fp); 386 fputs (node->data, fp); 387 fputs ("\n", fp); 388 return 0; 389 } 390 391 void 392 fileattr_write () 393 { 394 FILE *fp; 395 char *fname; 396 mode_t omask; 397 398 if (!attrs_modified) 399 return; 400 401 if (noexec) 402 return; 403 404 /* If NULL was passed to fileattr_startdir, then it isn't kosher to set 405 attributes. */ 406 assert (fileattr_stored_repos != NULL); 407 408 fname = xmalloc (strlen (fileattr_stored_repos) 409 + 1 410 + sizeof (CVSREP_FILEATTR) 411 + 1); 412 413 strcpy (fname, fileattr_stored_repos); 414 strcat (fname, "/"); 415 strcat (fname, CVSREP_FILEATTR); 416 417 if (list_isempty (attrlist) && fileattr_default_attrs == NULL) 418 { 419 /* There are no attributes. */ 420 if (unlink_file (fname) < 0) 421 { 422 if (!existence_error (errno)) 423 { 424 error (0, errno, "cannot remove %s", fname); 425 } 426 } 427 428 /* Now remove CVSREP directory, if empty. The main reason we bother 429 is that CVS 1.6 and earlier will choke if a CVSREP directory 430 exists, so provide the user a graceful way to remove it. */ 431 strcpy (fname, fileattr_stored_repos); 432 strcat (fname, "/"); 433 strcat (fname, CVSREP); 434 if (rmdir (fname) < 0) 435 { 436 if (errno != ENOTEMPTY 437 438 /* Don't know why we would be here if there is no CVSREP 439 directory, but it seemed to be happening anyway, so 440 check for it. */ 441 && !existence_error (errno)) 442 error (0, errno, "cannot remove %s", fname); 443 } 444 445 free (fname); 446 return; 447 } 448 449 omask = umask (cvsumask); 450 fp = fopen (fname, "w"); 451 if (fp == NULL) 452 { 453 if (existence_error (errno)) 454 { 455 /* Maybe the CVSREP directory doesn't exist. Try creating it. */ 456 char *repname; 457 458 repname = xmalloc (strlen (fileattr_stored_repos) 459 + 1 460 + sizeof (CVSREP) 461 + 1); 462 strcpy (repname, fileattr_stored_repos); 463 strcat (repname, "/"); 464 strcat (repname, CVSREP); 465 466 if (CVS_MKDIR (repname, 0777) < 0 && errno != EEXIST) 467 { 468 error (0, errno, "cannot make directory %s", repname); 469 (void) umask (omask); 470 free (repname); 471 return; 472 } 473 free (repname); 474 475 fp = fopen (fname, "w"); 476 } 477 if (fp == NULL) 478 { 479 error (0, errno, "cannot write %s", fname); 480 (void) umask (omask); 481 return; 482 } 483 } 484 (void) umask (omask); 485 walklist (attrlist, writeattr_proc, fp); 486 if (fileattr_default_attrs != NULL) 487 { 488 fputs ("D\t", fp); 489 fputs (fileattr_default_attrs, fp); 490 fputs ("\n", fp); 491 } 492 if (fclose (fp) < 0) 493 error (0, errno, "cannot close %s", fname); 494 attrs_modified = 0; 495 free (fname); 496 } 497 498 void 499 fileattr_free () 500 { 501 dellist (&attrlist); 502 if (fileattr_stored_repos != NULL) 503 free (fileattr_stored_repos); 504 fileattr_stored_repos = NULL; 505 if (fileattr_default_attrs != NULL) 506 free (fileattr_default_attrs); 507 fileattr_default_attrs = NULL; 508 } 509