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