fileattr.c revision 32785
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
18static void fileattr_read PROTO ((void));
19static int writeattr_proc PROTO ((Node *, void *));
20
21/* Where to look for CVSREP_FILEATTR.  */
22static char *fileattr_stored_repos;
23
24/* The in-memory attributes.  */
25static List *attrlist;
26static 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).  */
29static int attr_read_attempted;
30
31/* Have the in-memory attributes been modified since we read them?  */
32static int attrs_modified;
33
34/* More in-memory attributes: linked list of unrecognized
35   fileattr lines.  We pass these on unchanged.  */
36struct unrecog {
37    char *line;
38    struct unrecog *next;
39};
40static struct unrecog *unrecog_head;
41
42/* Note that if noone calls fileattr_get, this is very cheap.  No stat(),
43   no open(), no nothing.  */
44void
45fileattr_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
55static void
56fileattr_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.  */
65static void
66fileattr_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
158char *
159fileattr_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
201char *
202fileattr_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
222char *
223fileattr_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
319void
320fileattr_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
379char *
380fileattr_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
406void
407fileattr_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
459void
460fileattr_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
488static int
489writeattr_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
502void
503fileattr_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
630void
631fileattr_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