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