xref: /openbsd-src/gnu/usr.bin/cvs/src/fileattr.c (revision b2346922a76a50a89e33beab4ebbc0950de8a8df)
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
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
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
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 		free (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 *
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 *
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 *
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
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 *
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
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
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
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
511 fileattr_write ()
512 {
513     FILE *fp;
514     char *fname;
515     mode_t omask;
516 
517     if (!attrs_modified)
518 	return;
519 
520     if (noexec)
521 	return;
522 
523     /* If NULL was passed to fileattr_startdir, then it isn't kosher to set
524        attributes.  */
525     assert (fileattr_stored_repos != NULL);
526 
527     fname = xmalloc (strlen (fileattr_stored_repos)
528 		     + 1
529 		     + sizeof (CVSREP_FILEATTR)
530 		     + 1);
531 
532     strcpy (fname, fileattr_stored_repos);
533     strcat (fname, "/");
534     strcat (fname, CVSREP_FILEATTR);
535 
536     if (list_isempty (attrlist)
537 	&& fileattr_default_attrs == NULL
538 	&& unrecog_head == NULL)
539     {
540 	/* There are no attributes.  */
541 	if (unlink_file (fname) < 0)
542 	{
543 	    if (!existence_error (errno))
544 	    {
545 		error (0, errno, "cannot remove %s", fname);
546 	    }
547 	}
548 
549 	/* Now remove CVSREP directory, if empty.  The main reason we bother
550 	   is that CVS 1.6 and earlier will choke if a CVSREP directory
551 	   exists, so provide the user a graceful way to remove it.  */
552 	strcpy (fname, fileattr_stored_repos);
553 	strcat (fname, "/");
554 	strcat (fname, CVSREP);
555 	if (CVS_RMDIR (fname) < 0)
556 	{
557 	    if (errno != ENOTEMPTY
558 
559 		/* Don't know why we would be here if there is no CVSREP
560 		   directory, but it seemed to be happening anyway, so
561 		   check for it.  */
562 		&& !existence_error (errno))
563 		error (0, errno, "cannot remove %s", fname);
564 	}
565 
566 	free (fname);
567 	return;
568     }
569 
570     omask = umask (cvsumask);
571     fp = CVS_FOPEN (fname, FOPEN_BINARY_WRITE);
572     if (fp == NULL)
573     {
574 	if (existence_error (errno))
575 	{
576 	    /* Maybe the CVSREP directory doesn't exist.  Try creating it.  */
577 	    char *repname;
578 
579 	    repname = xmalloc (strlen (fileattr_stored_repos)
580 			       + 1
581 			       + sizeof (CVSREP)
582 			       + 1);
583 	    strcpy (repname, fileattr_stored_repos);
584 	    strcat (repname, "/");
585 	    strcat (repname, CVSREP);
586 
587 	    if (CVS_MKDIR (repname, 0777) < 0 && errno != EEXIST)
588 	    {
589 		error (0, errno, "cannot make directory %s", repname);
590 		(void) umask (omask);
591 		free (repname);
592 		return;
593 	    }
594 	    free (repname);
595 
596 	    fp = CVS_FOPEN (fname, FOPEN_BINARY_WRITE);
597 	}
598 	if (fp == NULL)
599 	{
600 	    error (0, errno, "cannot write %s", fname);
601 	    (void) umask (omask);
602 	    return;
603 	}
604     }
605     (void) umask (omask);
606 
607     /* First write the "F" attributes.  */
608     walklist (attrlist, writeattr_proc, fp);
609 
610     /* Then the "D" attribute.  */
611     if (fileattr_default_attrs != NULL)
612     {
613 	fputs ("D\t", fp);
614 	fputs (fileattr_default_attrs, fp);
615 	fputs ("\012", fp);
616     }
617 
618     /* Then any other attributes.  */
619     while (unrecog_head != NULL)
620     {
621 	struct unrecog *p;
622 
623 	p = unrecog_head;
624 	fputs (p->line, fp);
625 	fputs ("\012", fp);
626 
627 	unrecog_head = p->next;
628 	free (p->line);
629 	free (p);
630     }
631 
632     if (fclose (fp) < 0)
633 	error (0, errno, "cannot close %s", fname);
634     attrs_modified = 0;
635     free (fname);
636 }
637 
638 void
639 fileattr_free ()
640 {
641     /* Note that attrs_modified will ordinarily be zero, but there are
642        a few cases in which fileattr_write will fail to zero it (if
643        noexec is set, or error conditions).  This probably is the way
644        it should be.  */
645     dellist (&attrlist);
646     if (fileattr_stored_repos != NULL)
647 	free (fileattr_stored_repos);
648     fileattr_stored_repos = NULL;
649     if (fileattr_default_attrs != NULL)
650 	free (fileattr_default_attrs);
651     fileattr_default_attrs = NULL;
652 }
653