fileattr.c revision 17721
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   You should have received a copy of the GNU General Public License
14   along with this program; if not, write to the Free Software
15   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.  */
16
17#include "cvs.h"
18#include "getline.h"
19#include "fileattr.h"
20#include <assert.h>
21
22static void fileattr_read PROTO ((void));
23static int writeattr_proc PROTO ((Node *, void *));
24
25/* Where to look for CVSREP_FILEATTR.  */
26static char *fileattr_stored_repos;
27
28/* The in-memory attributes.  */
29static List *attrlist;
30static char *fileattr_default_attrs;
31/* We have already tried to read attributes and failed in this directory
32   (for example, there is no CVSREP_FILEATTR file).  */
33static int attr_read_attempted;
34
35/* Have the in-memory attributes been modified since we read them?  */
36static int attrs_modified;
37
38/* Note that if noone calls fileattr_get, this is very cheap.  No stat(),
39   no open(), no nothing.  */
40void
41fileattr_startdir (repos)
42    char *repos;
43{
44    assert (fileattr_stored_repos == NULL);
45    fileattr_stored_repos = xstrdup (repos);
46    assert (attrlist == NULL);
47    attr_read_attempted = 0;
48}
49
50static void
51fileattr_delproc (node)
52    Node *node;
53{
54    assert (node->data != NULL);
55    free (node->data);
56    node->data = NULL;
57}
58
59/* Read all the attributes for the current directory into memory.  */
60static void
61fileattr_read ()
62{
63    char *fname;
64    FILE *fp;
65    char *line = NULL;
66    size_t line_len = 0;
67
68    /* If there are no attributes, don't waste time repeatedly looking
69       for the CVSREP_FILEATTR file.  */
70    if (attr_read_attempted)
71	return;
72
73    /* If NULL was passed to fileattr_startdir, then it isn't kosher to look
74       at attributes.  */
75    assert (fileattr_stored_repos != NULL);
76
77    fname = xmalloc (strlen (fileattr_stored_repos)
78		     + 1
79		     + sizeof (CVSREP_FILEATTR)
80		     + 1);
81
82    strcpy (fname, fileattr_stored_repos);
83    strcat (fname, "/");
84    strcat (fname, CVSREP_FILEATTR);
85
86    attr_read_attempted = 1;
87    fp = fopen (fname, FOPEN_BINARY_READ);
88    if (fp == NULL)
89    {
90	if (!existence_error (errno))
91	    error (0, errno, "cannot read %s", fname);
92	free (fname);
93	return;
94    }
95    attrlist = getlist ();
96    while (1) {
97	int nread;
98	nread = getline (&line, &line_len, fp);
99	if (nread < 0)
100	    break;
101	/* Remove trailing newline.  */
102	line[nread - 1] = '\0';
103	if (line[0] == 'F')
104	{
105	    char *p;
106	    Node *newnode;
107
108	    p = strchr (line, '\t');
109	    *p++ = '\0';
110	    newnode = getnode ();
111	    newnode->type = FILEATTR;
112	    newnode->delproc = fileattr_delproc;
113	    newnode->key = xstrdup (line + 1);
114	    newnode->data = xstrdup (p);
115	    addnode (attrlist, newnode);
116	}
117	else if (line[0] == 'D')
118	{
119	    char *p;
120	    /* Currently nothing to skip here, but for future expansion,
121	       ignore anything located here.  */
122	    p = strchr (line, '\t');
123	    ++p;
124	    fileattr_default_attrs = xstrdup (p);
125	}
126	/* else just ignore the line, for future expansion.  */
127    }
128    if (ferror (fp))
129	error (0, errno, "cannot read %s", fname);
130    if (line != NULL)
131	free (line);
132    if (fclose (fp) < 0)
133	error (0, errno, "cannot close %s", fname);
134    attrs_modified = 0;
135    free (fname);
136}
137
138char *
139fileattr_get (filename, attrname)
140    char *filename;
141    char *attrname;
142{
143    Node *node;
144    size_t attrname_len = strlen (attrname);
145    char *p;
146
147    if (attrlist == NULL)
148	fileattr_read ();
149    if (attrlist == NULL)
150	/* Either nothing has any attributes, or fileattr_read already printed
151	   an error message.  */
152	return NULL;
153
154    if (filename == NULL)
155	p = fileattr_default_attrs;
156    else
157    {
158	node = findnode (attrlist, filename);
159	if (node == NULL)
160	    /* A file not mentioned has no attributes.  */
161	    return NULL;
162	p = node->data;
163    }
164    while (p)
165    {
166	if (strncmp (attrname, p, attrname_len) == 0
167	    && p[attrname_len] == '=')
168	{
169	    /* Found it.  */
170	    return p + attrname_len + 1;
171	}
172	p = strchr (p, ';');
173	if (p == NULL)
174	    break;
175	++p;
176    }
177    /* The file doesn't have this attribute.  */
178    return NULL;
179}
180
181char *
182fileattr_get0 (filename, attrname)
183    char *filename;
184    char *attrname;
185{
186    char *cp;
187    char *cpend;
188    char *retval;
189
190    cp = fileattr_get (filename, attrname);
191    if (cp == NULL)
192	return NULL;
193    cpend = strchr (cp, ';');
194    if (cpend == NULL)
195	cpend = cp + strlen (cp);
196    retval = xmalloc (cpend - cp + 1);
197    strncpy (retval, cp, cpend - cp);
198    retval[cpend - cp] = '\0';
199    return retval;
200}
201
202char *
203fileattr_modify (list, attrname, attrval, namevalsep, entsep)
204    char *list;
205    char *attrname;
206    char *attrval;
207    int namevalsep;
208    int entsep;
209{
210    char *retval;
211    char *rp;
212    size_t attrname_len = strlen (attrname);
213
214    /* Portion of list before the attribute to be replaced.  */
215    char *pre;
216    char *preend;
217    /* Portion of list after the attribute to be replaced.  */
218    char *post;
219
220    char *p;
221    char *p2;
222
223    p = list;
224    pre = list;
225    preend = NULL;
226    /* post is NULL unless set otherwise.  */
227    post = NULL;
228    p2 = NULL;
229    if (list != NULL)
230    {
231	while (1) {
232	    p2 = strchr (p, entsep);
233	    if (p2 == NULL)
234	    {
235		p2 = p + strlen (p);
236		if (preend == NULL)
237		    preend = p2;
238	    }
239	    else
240		++p2;
241	    if (strncmp (attrname, p, attrname_len) == 0
242		&& p[attrname_len] == namevalsep)
243	    {
244		/* Found it.  */
245		preend = p;
246		if (preend > list)
247		    /* Don't include the preceding entsep.  */
248		    --preend;
249
250		post = p2;
251	    }
252	    if (p2[0] == '\0')
253		break;
254	    p = p2;
255	}
256    }
257    if (post == NULL)
258	post = p2;
259
260    if (preend == pre && attrval == NULL && post == p2)
261	return NULL;
262
263    retval = xmalloc ((preend - pre)
264		      + 1
265		      + (attrval == NULL ? 0 : (attrname_len + 1
266						+ strlen (attrval)))
267		      + 1
268		      + (p2 - post)
269		      + 1);
270    if (preend != pre)
271    {
272	strncpy (retval, pre, preend - pre);
273	rp = retval + (preend - pre);
274	if (attrval != NULL)
275	    *rp++ = entsep;
276	*rp = '\0';
277    }
278    else
279	retval[0] = '\0';
280    if (attrval != NULL)
281    {
282	strcat (retval, attrname);
283	rp = retval + strlen (retval);
284	*rp++ = namevalsep;
285	strcpy (rp, attrval);
286    }
287    if (post != p2)
288    {
289	rp = retval + strlen (retval);
290	if (preend != pre || attrval != NULL)
291	    *rp++ = entsep;
292	strncpy (rp, post, p2 - post);
293	rp += p2 - post;
294	*rp = '\0';
295    }
296    return retval;
297}
298
299void
300fileattr_set (filename, attrname, attrval)
301    char *filename;
302    char *attrname;
303    char *attrval;
304{
305    Node *node;
306    char *p;
307
308    attrs_modified = 1;
309
310    if (filename == NULL)
311    {
312	p = fileattr_modify (fileattr_default_attrs, attrname, attrval,
313			     '=', ';');
314	if (fileattr_default_attrs != NULL)
315	    free (fileattr_default_attrs);
316	fileattr_default_attrs = p;
317	return;
318    }
319    if (attrlist == NULL)
320	fileattr_read ();
321    if (attrlist == NULL)
322    {
323	/* Not sure this is a graceful way to handle things
324	   in the case where fileattr_read was unable to read the file.  */
325        /* No attributes existed previously.  */
326	attrlist = getlist ();
327    }
328
329    node = findnode (attrlist, filename);
330    if (node == NULL)
331    {
332	if (attrval == NULL)
333	    /* Attempt to remove an attribute which wasn't there.  */
334	    return;
335
336	/* First attribute for this file.  */
337	node = getnode ();
338	node->type = FILEATTR;
339	node->delproc = fileattr_delproc;
340	node->key = xstrdup (filename);
341	node->data = xmalloc (strlen (attrname) + 1 + strlen (attrval) + 1);
342	strcpy (node->data, attrname);
343	strcat (node->data, "=");
344	strcat (node->data, attrval);
345	addnode (attrlist, node);
346    }
347
348    p = fileattr_modify (node->data, attrname, attrval, '=', ';');
349    free (node->data);
350    node->data = NULL;
351    if (p == NULL)
352	delnode (node);
353    else
354	node->data = p;
355}
356
357void
358fileattr_newfile (filename)
359    char *filename;
360{
361    Node *node;
362
363    if (attrlist == NULL)
364	fileattr_read ();
365
366    if (fileattr_default_attrs == NULL)
367	return;
368
369    if (attrlist == NULL)
370    {
371	/* Not sure this is a graceful way to handle things
372	   in the case where fileattr_read was unable to read the file.  */
373        /* No attributes existed previously.  */
374	attrlist = getlist ();
375    }
376
377    node = getnode ();
378    node->type = FILEATTR;
379    node->delproc = fileattr_delproc;
380    node->key = xstrdup (filename);
381    node->data = xstrdup (fileattr_default_attrs);
382    addnode (attrlist, node);
383    attrs_modified = 1;
384}
385
386static int
387writeattr_proc (node, data)
388    Node *node;
389    void *data;
390{
391    FILE *fp = (FILE *)data;
392    fputs ("F", fp);
393    fputs (node->key, fp);
394    fputs ("\t", fp);
395    fputs (node->data, fp);
396    fputs ("\012", fp);
397    return 0;
398}
399
400void
401fileattr_write ()
402{
403    FILE *fp;
404    char *fname;
405    mode_t omask;
406
407    if (!attrs_modified)
408	return;
409
410    if (noexec)
411	return;
412
413    /* If NULL was passed to fileattr_startdir, then it isn't kosher to set
414       attributes.  */
415    assert (fileattr_stored_repos != NULL);
416
417    fname = xmalloc (strlen (fileattr_stored_repos)
418		     + 1
419		     + sizeof (CVSREP_FILEATTR)
420		     + 1);
421
422    strcpy (fname, fileattr_stored_repos);
423    strcat (fname, "/");
424    strcat (fname, CVSREP_FILEATTR);
425
426    if (list_isempty (attrlist) && fileattr_default_attrs == NULL)
427    {
428	/* There are no attributes.  */
429	if (unlink_file (fname) < 0)
430	{
431	    if (!existence_error (errno))
432	    {
433		error (0, errno, "cannot remove %s", fname);
434	    }
435	}
436
437	/* Now remove CVSREP directory, if empty.  The main reason we bother
438	   is that CVS 1.6 and earlier will choke if a CVSREP directory
439	   exists, so provide the user a graceful way to remove it.  */
440	strcpy (fname, fileattr_stored_repos);
441	strcat (fname, "/");
442	strcat (fname, CVSREP);
443	if (rmdir (fname) < 0)
444	{
445	    if (errno != ENOTEMPTY
446
447		/* Don't know why we would be here if there is no CVSREP
448		   directory, but it seemed to be happening anyway, so
449		   check for it.  */
450		&& !existence_error (errno))
451		error (0, errno, "cannot remove %s", fname);
452	}
453
454	free (fname);
455	return;
456    }
457
458    omask = umask (cvsumask);
459    fp = fopen (fname, FOPEN_BINARY_WRITE);
460    if (fp == NULL)
461    {
462	if (existence_error (errno))
463	{
464	    /* Maybe the CVSREP directory doesn't exist.  Try creating it.  */
465	    char *repname;
466
467	    repname = xmalloc (strlen (fileattr_stored_repos)
468			       + 1
469			       + sizeof (CVSREP)
470			       + 1);
471	    strcpy (repname, fileattr_stored_repos);
472	    strcat (repname, "/");
473	    strcat (repname, CVSREP);
474
475	    if (CVS_MKDIR (repname, 0777) < 0 && errno != EEXIST)
476	    {
477		error (0, errno, "cannot make directory %s", repname);
478		(void) umask (omask);
479		free (repname);
480		return;
481	    }
482	    free (repname);
483
484	    fp = fopen (fname, FOPEN_BINARY_WRITE);
485	}
486	if (fp == NULL)
487	{
488	    error (0, errno, "cannot write %s", fname);
489	    (void) umask (omask);
490	    return;
491	}
492    }
493    (void) umask (omask);
494    walklist (attrlist, writeattr_proc, fp);
495    if (fileattr_default_attrs != NULL)
496    {
497	fputs ("D\t", fp);
498	fputs (fileattr_default_attrs, fp);
499	fputs ("\012", fp);
500    }
501    if (fclose (fp) < 0)
502	error (0, errno, "cannot close %s", fname);
503    attrs_modified = 0;
504    free (fname);
505}
506
507void
508fileattr_free ()
509{
510    dellist (&attrlist);
511    if (fileattr_stored_repos != NULL)
512	free (fileattr_stored_repos);
513    fileattr_stored_repos = NULL;
514    if (fileattr_default_attrs != NULL)
515	free (fileattr_default_attrs);
516    fileattr_default_attrs = NULL;
517}
518