xref: /openbsd-src/gnu/usr.bin/cvs/src/fileattr.c (revision 780d15dfff9934c79e6717020f46114c9b7d7d04)
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 void
380 fileattr_newfile (filename)
381     const char *filename;
382 {
383     Node *node;
384 
385     if (attrlist == NULL)
386 	fileattr_read ();
387 
388     if (fileattr_default_attrs == NULL)
389 	return;
390 
391     if (attrlist == NULL)
392     {
393 	/* Not sure this is a graceful way to handle things
394 	   in the case where fileattr_read was unable to read the file.  */
395         /* No attributes existed previously.  */
396 	attrlist = getlist ();
397     }
398 
399     node = getnode ();
400     node->type = FILEATTR;
401     node->delproc = fileattr_delproc;
402     node->key = xstrdup (filename);
403     node->data = xstrdup (fileattr_default_attrs);
404     addnode (attrlist, node);
405     attrs_modified = 1;
406 }
407 
408 static int
409 writeattr_proc (node, data)
410     Node *node;
411     void *data;
412 {
413     FILE *fp = (FILE *)data;
414     fputs ("F", fp);
415     fputs (node->key, fp);
416     fputs ("\t", fp);
417     fputs (node->data, fp);
418     fputs ("\012", fp);
419     return 0;
420 }
421 
422 void
423 fileattr_write ()
424 {
425     FILE *fp;
426     char *fname;
427     mode_t omask;
428 
429     if (!attrs_modified)
430 	return;
431 
432     if (noexec)
433 	return;
434 
435     /* If NULL was passed to fileattr_startdir, then it isn't kosher to set
436        attributes.  */
437     assert (fileattr_stored_repos != NULL);
438 
439     fname = xmalloc (strlen (fileattr_stored_repos)
440 		     + 1
441 		     + sizeof (CVSREP_FILEATTR)
442 		     + 1);
443 
444     strcpy (fname, fileattr_stored_repos);
445     strcat (fname, "/");
446     strcat (fname, CVSREP_FILEATTR);
447 
448     if (list_isempty (attrlist)
449 	&& fileattr_default_attrs == NULL
450 	&& unrecog_head == NULL)
451     {
452 	/* There are no attributes.  */
453 	if (unlink_file (fname) < 0)
454 	{
455 	    if (!existence_error (errno))
456 	    {
457 		error (0, errno, "cannot remove %s", fname);
458 	    }
459 	}
460 
461 	/* Now remove CVSREP directory, if empty.  The main reason we bother
462 	   is that CVS 1.6 and earlier will choke if a CVSREP directory
463 	   exists, so provide the user a graceful way to remove it.  */
464 	strcpy (fname, fileattr_stored_repos);
465 	strcat (fname, "/");
466 	strcat (fname, CVSREP);
467 	if (CVS_RMDIR (fname) < 0)
468 	{
469 	    if (errno != ENOTEMPTY
470 
471 		/* Don't know why we would be here if there is no CVSREP
472 		   directory, but it seemed to be happening anyway, so
473 		   check for it.  */
474 		&& !existence_error (errno))
475 		error (0, errno, "cannot remove %s", fname);
476 	}
477 
478 	free (fname);
479 	return;
480     }
481 
482     omask = umask (cvsumask);
483     fp = CVS_FOPEN (fname, FOPEN_BINARY_WRITE);
484     if (fp == NULL)
485     {
486 	if (existence_error (errno))
487 	{
488 	    /* Maybe the CVSREP directory doesn't exist.  Try creating it.  */
489 	    char *repname;
490 
491 	    repname = xmalloc (strlen (fileattr_stored_repos)
492 			       + 1
493 			       + sizeof (CVSREP)
494 			       + 1);
495 	    strcpy (repname, fileattr_stored_repos);
496 	    strcat (repname, "/");
497 	    strcat (repname, CVSREP);
498 
499 	    if (CVS_MKDIR (repname, 0777) < 0 && errno != EEXIST)
500 	    {
501 		error (0, errno, "cannot make directory %s", repname);
502 		(void) umask (omask);
503 		free (repname);
504 		return;
505 	    }
506 	    free (repname);
507 
508 	    fp = CVS_FOPEN (fname, FOPEN_BINARY_WRITE);
509 	}
510 	if (fp == NULL)
511 	{
512 	    error (0, errno, "cannot write %s", fname);
513 	    (void) umask (omask);
514 	    return;
515 	}
516     }
517     (void) umask (omask);
518 
519     /* First write the "F" attributes.  */
520     walklist (attrlist, writeattr_proc, fp);
521 
522     /* Then the "D" attribute.  */
523     if (fileattr_default_attrs != NULL)
524     {
525 	fputs ("D\t", fp);
526 	fputs (fileattr_default_attrs, fp);
527 	fputs ("\012", fp);
528     }
529 
530     /* Then any other attributes.  */
531     while (unrecog_head != NULL)
532     {
533 	struct unrecog *p;
534 
535 	p = unrecog_head;
536 	fputs (p->line, fp);
537 	fputs ("\012", fp);
538 
539 	unrecog_head = p->next;
540 	free (p->line);
541 	free (p);
542     }
543 
544     if (fclose (fp) < 0)
545 	error (0, errno, "cannot close %s", fname);
546     attrs_modified = 0;
547     free (fname);
548 }
549 
550 void
551 fileattr_free ()
552 {
553     dellist (&attrlist);
554     if (fileattr_stored_repos != NULL)
555 	free (fileattr_stored_repos);
556     fileattr_stored_repos = NULL;
557     if (fileattr_default_attrs != NULL)
558 	free (fileattr_default_attrs);
559     fileattr_default_attrs = NULL;
560 }
561