xref: /openbsd-src/gnu/usr.bin/cvs/src/fileattr.c (revision 50bf276cd1c7e20f1eda64a5e63e0fae39e12a95)
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 
22 static void fileattr_read PROTO ((void));
23 static int writeattr_proc PROTO ((Node *, void *));
24 
25 /* Where to look for CVSREP_FILEATTR.  */
26 static char *fileattr_stored_repos;
27 
28 /* The in-memory attributes.  */
29 static List *attrlist;
30 static 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).  */
33 static int attr_read_attempted;
34 
35 /* Have the in-memory attributes been modified since we read them?  */
36 static int attrs_modified;
37 
38 /* Note that if noone calls fileattr_get, this is very cheap.  No stat(),
39    no open(), no nothing.  */
40 void
41 fileattr_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 
50 static void
51 fileattr_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.  */
60 static void
61 fileattr_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 = CVS_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 
138 char *
139 fileattr_get (filename, attrname)
140     const char *filename;
141     const 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 
181 char *
182 fileattr_get0 (filename, attrname)
183     const char *filename;
184     const 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 
202 char *
203 fileattr_modify (list, attrname, attrval, namevalsep, entsep)
204     char *list;
205     const char *attrname;
206     const 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 
299 void
300 fileattr_set (filename, attrname, attrval)
301     const char *filename;
302     const char *attrname;
303     const 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     if (p == NULL)
350 	delnode (node);
351     else
352     {
353 	free (node->data);
354 	node->data = p;
355     }
356 }
357 
358 void
359 fileattr_newfile (filename)
360     const char *filename;
361 {
362     Node *node;
363 
364     if (attrlist == NULL)
365 	fileattr_read ();
366 
367     if (fileattr_default_attrs == NULL)
368 	return;
369 
370     if (attrlist == NULL)
371     {
372 	/* Not sure this is a graceful way to handle things
373 	   in the case where fileattr_read was unable to read the file.  */
374         /* No attributes existed previously.  */
375 	attrlist = getlist ();
376     }
377 
378     node = getnode ();
379     node->type = FILEATTR;
380     node->delproc = fileattr_delproc;
381     node->key = xstrdup (filename);
382     node->data = xstrdup (fileattr_default_attrs);
383     addnode (attrlist, node);
384     attrs_modified = 1;
385 }
386 
387 static int
388 writeattr_proc (node, data)
389     Node *node;
390     void *data;
391 {
392     FILE *fp = (FILE *)data;
393     fputs ("F", fp);
394     fputs (node->key, fp);
395     fputs ("\t", fp);
396     fputs (node->data, fp);
397     fputs ("\012", fp);
398     return 0;
399 }
400 
401 void
402 fileattr_write ()
403 {
404     FILE *fp;
405     char *fname;
406     mode_t omask;
407 
408     if (!attrs_modified)
409 	return;
410 
411     if (noexec)
412 	return;
413 
414     /* If NULL was passed to fileattr_startdir, then it isn't kosher to set
415        attributes.  */
416     assert (fileattr_stored_repos != NULL);
417 
418     fname = xmalloc (strlen (fileattr_stored_repos)
419 		     + 1
420 		     + sizeof (CVSREP_FILEATTR)
421 		     + 1);
422 
423     strcpy (fname, fileattr_stored_repos);
424     strcat (fname, "/");
425     strcat (fname, CVSREP_FILEATTR);
426 
427     if (list_isempty (attrlist) && fileattr_default_attrs == NULL)
428     {
429 	/* There are no attributes.  */
430 	if (unlink_file (fname) < 0)
431 	{
432 	    if (!existence_error (errno))
433 	    {
434 		error (0, errno, "cannot remove %s", fname);
435 	    }
436 	}
437 
438 	/* Now remove CVSREP directory, if empty.  The main reason we bother
439 	   is that CVS 1.6 and earlier will choke if a CVSREP directory
440 	   exists, so provide the user a graceful way to remove it.  */
441 	strcpy (fname, fileattr_stored_repos);
442 	strcat (fname, "/");
443 	strcat (fname, CVSREP);
444 	if (CVS_RMDIR (fname) < 0)
445 	{
446 	    if (errno != ENOTEMPTY
447 
448 		/* Don't know why we would be here if there is no CVSREP
449 		   directory, but it seemed to be happening anyway, so
450 		   check for it.  */
451 		&& !existence_error (errno))
452 		error (0, errno, "cannot remove %s", fname);
453 	}
454 
455 	free (fname);
456 	return;
457     }
458 
459     omask = umask (cvsumask);
460     fp = CVS_FOPEN (fname, FOPEN_BINARY_WRITE);
461     if (fp == NULL)
462     {
463 	if (existence_error (errno))
464 	{
465 	    /* Maybe the CVSREP directory doesn't exist.  Try creating it.  */
466 	    char *repname;
467 
468 	    repname = xmalloc (strlen (fileattr_stored_repos)
469 			       + 1
470 			       + sizeof (CVSREP)
471 			       + 1);
472 	    strcpy (repname, fileattr_stored_repos);
473 	    strcat (repname, "/");
474 	    strcat (repname, CVSREP);
475 
476 	    if (CVS_MKDIR (repname, 0777) < 0 && errno != EEXIST)
477 	    {
478 		error (0, errno, "cannot make directory %s", repname);
479 		(void) umask (omask);
480 		free (repname);
481 		return;
482 	    }
483 	    free (repname);
484 
485 	    fp = CVS_FOPEN (fname, FOPEN_BINARY_WRITE);
486 	}
487 	if (fp == NULL)
488 	{
489 	    error (0, errno, "cannot write %s", fname);
490 	    (void) umask (omask);
491 	    return;
492 	}
493     }
494     (void) umask (omask);
495     walklist (attrlist, writeattr_proc, fp);
496     if (fileattr_default_attrs != NULL)
497     {
498 	fputs ("D\t", fp);
499 	fputs (fileattr_default_attrs, fp);
500 	fputs ("\012", fp);
501     }
502     if (fclose (fp) < 0)
503 	error (0, errno, "cannot close %s", fname);
504     attrs_modified = 0;
505     free (fname);
506 }
507 
508 void
509 fileattr_free ()
510 {
511     dellist (&attrlist);
512     if (fileattr_stored_repos != NULL)
513 	free (fileattr_stored_repos);
514     fileattr_stored_repos = NULL;
515     if (fileattr_default_attrs != NULL)
516 	free (fileattr_default_attrs);
517     fileattr_default_attrs = NULL;
518 }
519