xref: /openbsd/gnu/usr.bin/cvs/src/fileattr.c (revision f9bbbf45)
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