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