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