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