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
fileattr_startdir(repos)45 fileattr_startdir (repos)
46 const 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
fileattr_delproc(node)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
fileattr_read()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 if (p == NULL)
115 error (1, 0,
116 "file attribute database corruption: tab missing in %s",
117 fname);
118 *p++ = '\0';
119 newnode = getnode ();
120 newnode->type = FILEATTR;
121 newnode->delproc = fileattr_delproc;
122 newnode->key = xstrdup (line + 1);
123 newnode->data = xstrdup (p);
124 if (addnode (attrlist, newnode) != 0)
125 /* If the same filename appears twice in the file, discard
126 any line other than the first for that filename. This
127 is the way that CVS has behaved since file attributes
128 were first introduced. */
129 freenode (newnode);
130 }
131 else if (line[0] == 'D')
132 {
133 char *p;
134 /* Currently nothing to skip here, but for future expansion,
135 ignore anything located here. */
136 p = strchr (line, '\t');
137 if (p == NULL)
138 error (1, 0,
139 "file attribute database corruption: tab missing in %s",
140 fname);
141 ++p;
142 if (fileattr_default_attrs) free (fileattr_default_attrs);
143 fileattr_default_attrs = xstrdup (p);
144 }
145 else
146 {
147 /* Unrecognized type, we want to just preserve the line without
148 changing it, for future expansion. */
149 struct unrecog *new;
150
151 new = (struct unrecog *) xmalloc (sizeof (struct unrecog));
152 new->line = xstrdup (line);
153 new->next = unrecog_head;
154 unrecog_head = new;
155 }
156 }
157 if (ferror (fp))
158 error (0, errno, "cannot read %s", fname);
159 if (line != NULL)
160 free (line);
161 if (fclose (fp) < 0)
162 error (0, errno, "cannot close %s", fname);
163 attrs_modified = 0;
164 free (fname);
165 }
166
167 char *
fileattr_get(filename,attrname)168 fileattr_get (filename, attrname)
169 const char *filename;
170 const char *attrname;
171 {
172 Node *node;
173 size_t attrname_len = strlen (attrname);
174 char *p;
175
176 if (attrlist == NULL)
177 fileattr_read ();
178 if (attrlist == NULL)
179 /* Either nothing has any attributes, or fileattr_read already printed
180 an error message. */
181 return NULL;
182
183 if (filename == NULL)
184 p = fileattr_default_attrs;
185 else
186 {
187 node = findnode (attrlist, filename);
188 if (node == NULL)
189 /* A file not mentioned has no attributes. */
190 return NULL;
191 p = node->data;
192 }
193 while (p)
194 {
195 if (strncmp (attrname, p, attrname_len) == 0
196 && p[attrname_len] == '=')
197 {
198 /* Found it. */
199 return p + attrname_len + 1;
200 }
201 p = strchr (p, ';');
202 if (p == NULL)
203 break;
204 ++p;
205 }
206 /* The file doesn't have this attribute. */
207 return NULL;
208 }
209
210 char *
fileattr_get0(filename,attrname)211 fileattr_get0 (filename, attrname)
212 const char *filename;
213 const char *attrname;
214 {
215 char *cp;
216 char *cpend;
217 char *retval;
218
219 cp = fileattr_get (filename, attrname);
220 if (cp == NULL)
221 return NULL;
222 cpend = strchr (cp, ';');
223 if (cpend == NULL)
224 cpend = cp + strlen (cp);
225 retval = xmalloc (cpend - cp + 1);
226 strncpy (retval, cp, cpend - cp);
227 retval[cpend - cp] = '\0';
228 return retval;
229 }
230
231 char *
fileattr_modify(list,attrname,attrval,namevalsep,entsep)232 fileattr_modify (list, attrname, attrval, namevalsep, entsep)
233 char *list;
234 const char *attrname;
235 const char *attrval;
236 int namevalsep;
237 int entsep;
238 {
239 char *retval;
240 char *rp;
241 size_t attrname_len = strlen (attrname);
242
243 /* Portion of list before the attribute to be replaced. */
244 char *pre;
245 char *preend;
246 /* Portion of list after the attribute to be replaced. */
247 char *post;
248
249 char *p;
250 char *p2;
251
252 p = list;
253 pre = list;
254 preend = NULL;
255 /* post is NULL unless set otherwise. */
256 post = NULL;
257 p2 = NULL;
258 if (list != NULL)
259 {
260 while (1) {
261 p2 = strchr (p, entsep);
262 if (p2 == NULL)
263 {
264 p2 = p + strlen (p);
265 if (preend == NULL)
266 preend = p2;
267 }
268 else
269 ++p2;
270 if (strncmp (attrname, p, attrname_len) == 0
271 && p[attrname_len] == namevalsep)
272 {
273 /* Found it. */
274 preend = p;
275 if (preend > list)
276 /* Don't include the preceding entsep. */
277 --preend;
278
279 post = p2;
280 }
281 if (p2[0] == '\0')
282 break;
283 p = p2;
284 }
285 }
286 if (post == NULL)
287 post = p2;
288
289 if (preend == pre && attrval == NULL && post == p2)
290 return NULL;
291
292 retval = xmalloc ((preend - pre)
293 + 1
294 + (attrval == NULL ? 0 : (attrname_len + 1
295 + strlen (attrval)))
296 + 1
297 + (p2 - post)
298 + 1);
299 if (preend != pre)
300 {
301 strncpy (retval, pre, preend - pre);
302 rp = retval + (preend - pre);
303 if (attrval != NULL)
304 *rp++ = entsep;
305 *rp = '\0';
306 }
307 else
308 retval[0] = '\0';
309 if (attrval != NULL)
310 {
311 strcat (retval, attrname);
312 rp = retval + strlen (retval);
313 *rp++ = namevalsep;
314 strcpy (rp, attrval);
315 }
316 if (post != p2)
317 {
318 rp = retval + strlen (retval);
319 if (preend != pre || attrval != NULL)
320 *rp++ = entsep;
321 strncpy (rp, post, p2 - post);
322 rp += p2 - post;
323 *rp = '\0';
324 }
325 return retval;
326 }
327
328 void
fileattr_set(filename,attrname,attrval)329 fileattr_set (filename, attrname, attrval)
330 const char *filename;
331 const char *attrname;
332 const char *attrval;
333 {
334 Node *node;
335 char *p;
336
337 if (filename == NULL)
338 {
339 p = fileattr_modify (fileattr_default_attrs, attrname, attrval,
340 '=', ';');
341 if (fileattr_default_attrs != NULL)
342 free (fileattr_default_attrs);
343 fileattr_default_attrs = p;
344 attrs_modified = 1;
345 return;
346 }
347 if (attrlist == NULL)
348 fileattr_read ();
349 if (attrlist == NULL)
350 {
351 /* Not sure this is a graceful way to handle things
352 in the case where fileattr_read was unable to read the file. */
353 /* No attributes existed previously. */
354 attrlist = getlist ();
355 }
356
357 node = findnode (attrlist, filename);
358 if (node == NULL)
359 {
360 if (attrval == NULL)
361 /* Attempt to remove an attribute which wasn't there. */
362 return;
363
364 /* First attribute for this file. */
365 node = getnode ();
366 node->type = FILEATTR;
367 node->delproc = fileattr_delproc;
368 node->key = xstrdup (filename);
369 node->data = xmalloc (strlen (attrname) + 1 + strlen (attrval) + 1);
370 strcpy (node->data, attrname);
371 strcat (node->data, "=");
372 strcat (node->data, attrval);
373 addnode (attrlist, node);
374 }
375
376 p = fileattr_modify (node->data, attrname, attrval, '=', ';');
377 if (p == NULL)
378 delnode (node);
379 else
380 {
381 free (node->data);
382 node->data = p;
383 }
384
385 attrs_modified = 1;
386 }
387
388 char *
fileattr_getall(filename)389 fileattr_getall (filename)
390 const char *filename;
391 {
392 Node *node;
393 char *p;
394
395 if (attrlist == NULL)
396 fileattr_read ();
397 if (attrlist == NULL)
398 /* Either nothing has any attributes, or fileattr_read already printed
399 an error message. */
400 return NULL;
401
402 if (filename == NULL)
403 p = fileattr_default_attrs;
404 else
405 {
406 node = findnode (attrlist, filename);
407 if (node == NULL)
408 /* A file not mentioned has no attributes. */
409 return NULL;
410 p = node->data;
411 }
412 return xstrdup (p);
413 }
414
415 void
fileattr_setall(filename,attrs)416 fileattr_setall (filename, attrs)
417 const char *filename;
418 const char *attrs;
419 {
420 Node *node;
421
422 if (filename == NULL)
423 {
424 if (fileattr_default_attrs != NULL)
425 free (fileattr_default_attrs);
426 fileattr_default_attrs = xstrdup (attrs);
427 attrs_modified = 1;
428 return;
429 }
430 if (attrlist == NULL)
431 fileattr_read ();
432 if (attrlist == NULL)
433 {
434 /* Not sure this is a graceful way to handle things
435 in the case where fileattr_read was unable to read the file. */
436 /* No attributes existed previously. */
437 attrlist = getlist ();
438 }
439
440 node = findnode (attrlist, filename);
441 if (node == NULL)
442 {
443 /* The file had no attributes. Add them if we have any to add. */
444 if (attrs != NULL)
445 {
446 node = getnode ();
447 node->type = FILEATTR;
448 node->delproc = fileattr_delproc;
449 node->key = xstrdup (filename);
450 node->data = xstrdup (attrs);
451 addnode (attrlist, node);
452 }
453 }
454 else
455 {
456 if (attrs == NULL)
457 delnode (node);
458 else
459 {
460 free (node->data);
461 node->data = xstrdup (attrs);
462 }
463 }
464
465 attrs_modified = 1;
466 }
467
468 void
fileattr_newfile(filename)469 fileattr_newfile (filename)
470 const char *filename;
471 {
472 Node *node;
473
474 if (attrlist == NULL)
475 fileattr_read ();
476
477 if (fileattr_default_attrs == NULL)
478 return;
479
480 if (attrlist == NULL)
481 {
482 /* Not sure this is a graceful way to handle things
483 in the case where fileattr_read was unable to read the file. */
484 /* No attributes existed previously. */
485 attrlist = getlist ();
486 }
487
488 node = getnode ();
489 node->type = FILEATTR;
490 node->delproc = fileattr_delproc;
491 node->key = xstrdup (filename);
492 node->data = xstrdup (fileattr_default_attrs);
493 addnode (attrlist, node);
494 attrs_modified = 1;
495 }
496
497 static int
writeattr_proc(node,data)498 writeattr_proc (node, data)
499 Node *node;
500 void *data;
501 {
502 FILE *fp = (FILE *)data;
503 fputs ("F", fp);
504 fputs (node->key, fp);
505 fputs ("\t", fp);
506 fputs (node->data, fp);
507 fputs ("\012", fp);
508 return 0;
509 }
510
511 void
fileattr_write()512 fileattr_write ()
513 {
514 FILE *fp;
515 char *fname;
516 mode_t omask;
517 struct unrecog *p;
518
519 if (!attrs_modified)
520 return;
521
522 if (noexec)
523 return;
524
525 /* If NULL was passed to fileattr_startdir, then it isn't kosher to set
526 attributes. */
527 assert (fileattr_stored_repos != NULL);
528
529 fname = xmalloc (strlen (fileattr_stored_repos)
530 + 1
531 + sizeof (CVSREP_FILEATTR)
532 + 1);
533
534 strcpy (fname, fileattr_stored_repos);
535 strcat (fname, "/");
536 strcat (fname, CVSREP_FILEATTR);
537
538 if (list_isempty (attrlist)
539 && fileattr_default_attrs == NULL
540 && unrecog_head == NULL)
541 {
542 /* There are no attributes. */
543 if (unlink_file (fname) < 0)
544 {
545 if (!existence_error (errno))
546 {
547 error (0, errno, "cannot remove %s", fname);
548 }
549 }
550
551 /* Now remove CVSREP directory, if empty. The main reason we bother
552 is that CVS 1.6 and earlier will choke if a CVSREP directory
553 exists, so provide the user a graceful way to remove it. */
554 strcpy (fname, fileattr_stored_repos);
555 strcat (fname, "/");
556 strcat (fname, CVSREP);
557 if (CVS_RMDIR (fname) < 0)
558 {
559 if (errno != ENOTEMPTY
560
561 /* Don't know why we would be here if there is no CVSREP
562 directory, but it seemed to be happening anyway, so
563 check for it. */
564 && !existence_error (errno))
565 error (0, errno, "cannot remove %s", fname);
566 }
567
568 free (fname);
569 return;
570 }
571
572 omask = umask (cvsumask);
573 fp = CVS_FOPEN (fname, FOPEN_BINARY_WRITE);
574 if (fp == NULL)
575 {
576 if (existence_error (errno))
577 {
578 /* Maybe the CVSREP directory doesn't exist. Try creating it. */
579 char *repname;
580
581 repname = xmalloc (strlen (fileattr_stored_repos)
582 + 1
583 + sizeof (CVSREP)
584 + 1);
585 strcpy (repname, fileattr_stored_repos);
586 strcat (repname, "/");
587 strcat (repname, CVSREP);
588
589 if (CVS_MKDIR (repname, 0777) < 0 && errno != EEXIST)
590 {
591 error (0, errno, "cannot make directory %s", repname);
592 (void) umask (omask);
593 free (fname);
594 free (repname);
595 return;
596 }
597 free (repname);
598
599 fp = CVS_FOPEN (fname, FOPEN_BINARY_WRITE);
600 }
601 if (fp == NULL)
602 {
603 error (0, errno, "cannot write %s", fname);
604 (void) umask (omask);
605 free (fname);
606 return;
607 }
608 }
609 (void) umask (omask);
610
611 /* First write the "F" attributes. */
612 walklist (attrlist, writeattr_proc, fp);
613
614 /* Then the "D" attribute. */
615 if (fileattr_default_attrs != NULL)
616 {
617 fputs ("D\t", fp);
618 fputs (fileattr_default_attrs, fp);
619 fputs ("\012", fp);
620 }
621
622 /* Then any other attributes. */
623 for (p = unrecog_head; p != NULL; p = p->next)
624 {
625 fputs (p->line, fp);
626 fputs ("\012", fp);
627 }
628
629 if (fclose (fp) < 0)
630 error (0, errno, "cannot close %s", fname);
631 attrs_modified = 0;
632 free (fname);
633 }
634
635 void
fileattr_free()636 fileattr_free ()
637 {
638 /* Note that attrs_modified will ordinarily be zero, but there are
639 a few cases in which fileattr_write will fail to zero it (if
640 noexec is set, or error conditions). This probably is the way
641 it should be. */
642 dellist (&attrlist);
643 if (fileattr_stored_repos != NULL)
644 free (fileattr_stored_repos);
645 fileattr_stored_repos = NULL;
646 if (fileattr_default_attrs != NULL)
647 free (fileattr_default_attrs);
648 fileattr_default_attrs = NULL;
649 while (unrecog_head)
650 {
651 struct unrecog *p = unrecog_head;
652 unrecog_head = p->next;
653 free (p->line);
654 free (p);
655 }
656 }
657