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