xref: /openbsd-src/gnu/usr.bin/cvs/src/fileattr.c (revision c2c6168287231ca1f2357af7564f6bed5739dfd2)
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 = 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     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 
181 char *
182 fileattr_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 
202 char *
203 fileattr_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 
299 void
300 fileattr_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 
357 void
358 fileattr_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 
386 static int
387 writeattr_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 
400 void
401 fileattr_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 
507 void
508 fileattr_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