xref: /dragonfly/contrib/cvs-1.12/src/fileattr.c (revision 0db87cb7)
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 
17 static void fileattr_read (void);
18 static int writeattr_proc (Node *, void *);
19 
20 /* Where to look for CVSREP_FILEATTR.  */
21 static char *fileattr_stored_repos;
22 
23 /* The in-memory attributes.  */
24 static List *attrlist;
25 static char *fileattr_default_attrs;
26 /* We have already tried to read attributes and failed in this directory
27    (for example, there is no CVSREP_FILEATTR file).  */
28 static int attr_read_attempted;
29 
30 /* Have the in-memory attributes been modified since we read them?  */
31 static int attrs_modified;
32 
33 /* More in-memory attributes: linked list of unrecognized
34    fileattr lines.  We pass these on unchanged.  */
35 struct unrecog {
36     char *line;
37     struct unrecog *next;
38 };
39 static struct unrecog *unrecog_head;
40 
41 
42 
43 /* Note that if noone calls fileattr_get, this is very cheap.  No stat(),
44    no open(), no nothing.  */
45 void
46 fileattr_startdir (const 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 
55 
56 
57 static void
58 fileattr_delproc (Node *node)
59 {
60     assert (node->data != NULL);
61     free (node->data);
62     node->data = NULL;
63 }
64 
65 /* Read all the attributes for the current directory into memory.  */
66 static void
67 fileattr_read (void)
68 {
69     char *fname;
70     FILE *fp;
71     char *line = NULL;
72     size_t line_len = 0;
73 
74     /* If there are no attributes, don't waste time repeatedly looking
75        for the CVSREP_FILEATTR file.  */
76     if (attr_read_attempted)
77 	return;
78 
79     /* If NULL was passed to fileattr_startdir, then it isn't kosher to look
80        at attributes.  */
81     assert (fileattr_stored_repos != NULL);
82 
83     fname = Xasprintf ("%s/%s", fileattr_stored_repos, CVSREP_FILEATTR);
84 
85     attr_read_attempted = 1;
86     fp = CVS_FOPEN (fname, FOPEN_BINARY_READ);
87     if (fp == NULL)
88     {
89 	if (!existence_error (errno))
90 	    error (0, errno, "cannot read %s", fname);
91 	free (fname);
92 	return;
93     }
94     attrlist = getlist ();
95     while (1) {
96 	int nread;
97 	nread = getline (&line, &line_len, fp);
98 	if (nread < 0)
99 	    break;
100 	/* Remove trailing newline.
101 	 * It is okay to reference line[nread - 1] here, since getline must
102 	 * always return 1 character or EOF, but we need to verify that the
103 	 * character we eat is the newline, since getline can return a line
104 	 * w/o a newline just before returning EOF.
105 	 */
106 	if (line[nread - 1] == '\n') line[nread - 1] = '\0';
107 	if (line[0] == 'F')
108 	{
109 	    char *p;
110 	    Node *newnode;
111 
112 	    p = strchr (line, '\t');
113 	    if (p == NULL)
114 		error (1, 0,
115 		       "file attribute database corruption: tab missing in %s",
116 		       primary_root_inverse_translate (fname));
117 	    *p++ = '\0';
118 	    newnode = getnode ();
119 	    newnode->type = FILEATTR;
120 	    newnode->delproc = fileattr_delproc;
121 	    newnode->key = xstrdup (line + 1);
122 	    newnode->data = xstrdup (p);
123 	    if (addnode (attrlist, newnode) != 0)
124 		/* If the same filename appears twice in the file, discard
125 		   any line other than the first for that filename.  This
126 		   is the way that CVS has behaved since file attributes
127 		   were first introduced.  */
128 		freenode (newnode);
129 	}
130 	else if (line[0] == 'D')
131 	{
132 	    char *p;
133 	    /* Currently nothing to skip here, but for future expansion,
134 	       ignore anything located here.  */
135 	    p = strchr (line, '\t');
136 	    if (p == NULL)
137 		error (1, 0,
138 		       "file attribute database corruption: tab missing in %s",
139 		       fname);
140 	    ++p;
141 	    if (fileattr_default_attrs) free (fileattr_default_attrs);
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 = 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 
166 
167 
168 char *
169 fileattr_get (const char *filename, 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 
209 
210 
211 char *
212 fileattr_get0 (const char *filename, 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 
230 
231 
232 char *
233 fileattr_modify (char *list, const char *attrname, const char *attrval, int namevalsep, int entsep)
234 {
235     char *retval;
236     char *rp;
237     size_t attrname_len = strlen (attrname);
238 
239     /* Portion of list before the attribute to be replaced.  */
240     char *pre;
241     char *preend;
242     /* Portion of list after the attribute to be replaced.  */
243     char *post;
244 
245     char *p;
246     char *p2;
247 
248     p = list;
249     pre = list;
250     preend = NULL;
251     /* post is NULL unless set otherwise.  */
252     post = NULL;
253     p2 = NULL;
254     if (list != NULL)
255     {
256 	while (1) {
257 	    p2 = strchr (p, entsep);
258 	    if (p2 == NULL)
259 	    {
260 		p2 = p + strlen (p);
261 		if (preend == NULL)
262 		    preend = p2;
263 	    }
264 	    else
265 		++p2;
266 	    if (strncmp (attrname, p, attrname_len) == 0
267 		&& p[attrname_len] == namevalsep)
268 	    {
269 		/* Found it.  */
270 		preend = p;
271 		if (preend > list)
272 		    /* Don't include the preceding entsep.  */
273 		    --preend;
274 
275 		post = p2;
276 	    }
277 	    if (p2[0] == '\0')
278 		break;
279 	    p = p2;
280 	}
281     }
282     if (post == NULL)
283 	post = p2;
284 
285     if (preend == pre && attrval == NULL && post == p2)
286 	return NULL;
287 
288     retval = xmalloc ((preend - pre)
289 		      + 1
290 		      + (attrval == NULL ? 0 : (attrname_len + 1
291 						+ strlen (attrval)))
292 		      + 1
293 		      + (p2 - post)
294 		      + 1);
295     if (preend != pre)
296     {
297 	strncpy (retval, pre, preend - pre);
298 	rp = retval + (preend - pre);
299 	if (attrval != NULL)
300 	    *rp++ = entsep;
301 	*rp = '\0';
302     }
303     else
304 	retval[0] = '\0';
305     if (attrval != NULL)
306     {
307 	strcat (retval, attrname);
308 	rp = retval + strlen (retval);
309 	*rp++ = namevalsep;
310 	strcpy (rp, attrval);
311     }
312     if (post != p2)
313     {
314 	rp = retval + strlen (retval);
315 	if (preend != pre || attrval != NULL)
316 	    *rp++ = entsep;
317 	strncpy (rp, post, p2 - post);
318 	rp += p2 - post;
319 	*rp = '\0';
320     }
321     return retval;
322 }
323 
324 void
325 fileattr_set (const char *filename, const char *attrname, const char *attrval)
326 {
327     Node *node;
328     char *p;
329 
330     if (filename == NULL)
331     {
332 	p = fileattr_modify (fileattr_default_attrs, attrname, attrval,
333 			     '=', ';');
334 	if (fileattr_default_attrs != NULL)
335 	    free (fileattr_default_attrs);
336 	fileattr_default_attrs = p;
337 	attrs_modified = 1;
338 	return;
339     }
340     if (attrlist == NULL)
341 	fileattr_read ();
342     if (attrlist == NULL)
343     {
344 	/* Not sure this is a graceful way to handle things
345 	   in the case where fileattr_read was unable to read the file.  */
346         /* No attributes existed previously.  */
347 	attrlist = getlist ();
348     }
349 
350     node = findnode (attrlist, filename);
351     if (node == NULL)
352     {
353 	if (attrval == NULL)
354 	    /* Attempt to remove an attribute which wasn't there.  */
355 	    return;
356 
357 	/* First attribute for this file.  */
358 	node = getnode ();
359 	node->type = FILEATTR;
360 	node->delproc = fileattr_delproc;
361 	node->key = xstrdup (filename);
362 	node->data = Xasprintf ("%s=%s", attrname, attrval);
363 	addnode (attrlist, node);
364     }
365 
366     p = fileattr_modify (node->data, attrname, attrval, '=', ';');
367     if (p == NULL)
368 	delnode (node);
369     else
370     {
371 	free (node->data);
372 	node->data = p;
373     }
374 
375     attrs_modified = 1;
376 }
377 
378 
379 
380 char *
381 fileattr_getall (const char *filename)
382 {
383     Node *node;
384     char *p;
385 
386     if (attrlist == NULL)
387 	fileattr_read ();
388     if (attrlist == NULL)
389 	/* Either nothing has any attributes, or fileattr_read already printed
390 	   an error message.  */
391 	return NULL;
392 
393     if (filename == NULL)
394 	p = fileattr_default_attrs;
395     else
396     {
397 	node = findnode (attrlist, filename);
398 	if (node == NULL)
399 	    /* A file not mentioned has no attributes.  */
400 	    return NULL;
401 	p = node->data;
402     }
403     return xstrdup (p);
404 }
405 
406 
407 
408 void
409 fileattr_setall (const char *filename, const char *attrs)
410 {
411     Node *node;
412 
413     if (filename == NULL)
414     {
415 	if (fileattr_default_attrs != NULL)
416 	    free (fileattr_default_attrs);
417 	fileattr_default_attrs = xstrdup (attrs);
418 	attrs_modified = 1;
419 	return;
420     }
421     if (attrlist == NULL)
422 	fileattr_read ();
423     if (attrlist == NULL)
424     {
425 	/* Not sure this is a graceful way to handle things
426 	   in the case where fileattr_read was unable to read the file.  */
427         /* No attributes existed previously.  */
428 	attrlist = getlist ();
429     }
430 
431     node = findnode (attrlist, filename);
432     if (node == NULL)
433     {
434 	/* The file had no attributes.  Add them if we have any to add.  */
435 	if (attrs != NULL)
436 	{
437 	    node = getnode ();
438 	    node->type = FILEATTR;
439 	    node->delproc = fileattr_delproc;
440 	    node->key = xstrdup (filename);
441 	    node->data = xstrdup (attrs);
442 	    addnode (attrlist, node);
443 	}
444     }
445     else
446     {
447 	if (attrs == NULL)
448 	    delnode (node);
449 	else
450 	{
451 	    free (node->data);
452 	    node->data = xstrdup (attrs);
453 	}
454     }
455 
456     attrs_modified = 1;
457 }
458 
459 
460 
461 void
462 fileattr_newfile (const char *filename)
463 {
464     Node *node;
465 
466     if (attrlist == NULL)
467 	fileattr_read ();
468 
469     if (fileattr_default_attrs == NULL)
470 	return;
471 
472     if (attrlist == NULL)
473     {
474 	/* Not sure this is a graceful way to handle things
475 	   in the case where fileattr_read was unable to read the file.  */
476         /* No attributes existed previously.  */
477 	attrlist = getlist ();
478     }
479 
480     node = getnode ();
481     node->type = FILEATTR;
482     node->delproc = fileattr_delproc;
483     node->key = xstrdup (filename);
484     node->data = xstrdup (fileattr_default_attrs);
485     addnode (attrlist, node);
486     attrs_modified = 1;
487 }
488 
489 
490 
491 static int
492 writeattr_proc (Node *node, void *data)
493 {
494     FILE *fp = (FILE *)data;
495     fputs ("F", fp);
496     fputs (node->key, fp);
497     fputs ("\t", fp);
498     fputs (node->data, fp);
499     fputs ("\012", fp);
500     return 0;
501 }
502 
503 
504 
505 /*
506  * callback proc to run a script when fileattrs are updated.
507  */
508 static int
509 postwatch_proc (const char *repository, const char *filter, void *closure)
510 {
511     char *cmdline;
512     const char *srepos = Short_Repository (repository);
513 
514     TRACE (TRACE_FUNCTION, "postwatch_proc (%s, %s)", repository, filter);
515 
516     /* %c = command name
517      * %p = shortrepos
518      * %r = repository
519      */
520     /*
521      * Cast any NULL arguments as appropriate pointers as this is an
522      * stdarg function and we need to be certain the caller gets what
523      * is expected.
524      */
525     cmdline = format_cmdline (
526 #ifdef SUPPORT_OLD_INFO_FMT_STRINGS
527 	                      false, srepos,
528 #endif /* SUPPORT_OLD_INFO_FMT_STRINGS */
529 	                      filter,
530 	                      "c", "s", cvs_cmd_name,
531 #ifdef SERVER_SUPPORT
532 	                      "R", "s", referrer ? referrer->original : "NONE",
533 #endif /* SERVER_SUPPORT */
534 	                      "p", "s", srepos,
535 	                      "r", "s", current_parsed_root->directory,
536 	                      (char *) NULL);
537 
538     if (!cmdline || !strlen (cmdline))
539     {
540 	if (cmdline) free (cmdline);
541 	error (0, 0, "postwatch proc resolved to the empty string!");
542 	return 1;
543     }
544 
545     run_setup (cmdline);
546 
547     free (cmdline);
548 
549     /* FIXME - read the comment in verifymsg_proc() about why we use abs()
550      * below() and shouldn't.
551      */
552     return abs (run_exec (RUN_TTY, RUN_TTY, RUN_TTY,
553 			  RUN_NORMAL | RUN_SIGIGNORE));
554 }
555 
556 
557 
558 void
559 fileattr_write (void)
560 {
561     FILE *fp;
562     char *fname;
563     mode_t omask;
564     struct unrecog *p;
565 
566     if (!attrs_modified)
567 	return;
568 
569     if (noexec)
570 	return;
571 
572     /* If NULL was passed to fileattr_startdir, then it isn't kosher to set
573        attributes.  */
574     assert (fileattr_stored_repos != NULL);
575 
576     fname = Xasprintf ("%s/%s", fileattr_stored_repos, CVSREP_FILEATTR);
577 
578     if (list_isempty (attrlist)
579 	&& fileattr_default_attrs == NULL
580 	&& unrecog_head == NULL)
581     {
582 	/* There are no attributes.  */
583 	if (unlink_file (fname) < 0)
584 	{
585 	    if (!existence_error (errno))
586 	    {
587 		error (0, errno, "cannot remove %s", fname);
588 	    }
589 	}
590 
591 	/* Now remove CVSREP directory, if empty.  The main reason we bother
592 	   is that CVS 1.6 and earlier will choke if a CVSREP directory
593 	   exists, so provide the user a graceful way to remove it.  */
594 	strcpy (fname, fileattr_stored_repos);
595 	strcat (fname, "/");
596 	strcat (fname, CVSREP);
597 	if (CVS_RMDIR (fname) < 0)
598 	{
599 	    if (errno != ENOTEMPTY
600 
601 		/* Don't know why we would be here if there is no CVSREP
602 		   directory, but it seemed to be happening anyway, so
603 		   check for it.  */
604 		&& !existence_error (errno))
605 		error (0, errno, "cannot remove %s", fname);
606 	}
607 
608 	free (fname);
609 	return;
610     }
611 
612     omask = umask (cvsumask);
613     fp = CVS_FOPEN (fname, FOPEN_BINARY_WRITE);
614     if (fp == NULL)
615     {
616 	if (existence_error (errno))
617 	{
618 	    /* Maybe the CVSREP directory doesn't exist.  Try creating it.  */
619 	    char *repname;
620 
621 	    repname = Xasprintf ("%s/%s", fileattr_stored_repos, CVSREP);
622 
623 	    if (CVS_MKDIR (repname, 0777) < 0 && errno != EEXIST)
624 	    {
625 		error (0, errno, "cannot make directory %s", repname);
626 		(void) umask (omask);
627 		free (fname);
628 		free (repname);
629 		return;
630 	    }
631 	    free (repname);
632 
633 	    fp = CVS_FOPEN (fname, FOPEN_BINARY_WRITE);
634 	}
635 	if (fp == NULL)
636 	{
637 	    error (0, errno, "cannot write %s", fname);
638 	    (void) umask (omask);
639 	    free (fname);
640 	    return;
641 	}
642     }
643     (void) umask (omask);
644 
645     /* First write the "F" attributes.  */
646     walklist (attrlist, writeattr_proc, fp);
647 
648     /* Then the "D" attribute.  */
649     if (fileattr_default_attrs != NULL)
650     {
651 	fputs ("D\t", fp);
652 	fputs (fileattr_default_attrs, fp);
653 	fputs ("\012", fp);
654     }
655 
656     /* Then any other attributes.  */
657     for (p = unrecog_head; p != NULL; p = p->next)
658     {
659 	fputs (p->line, fp);
660 	fputs ("\012", fp);
661     }
662 
663     if (fclose (fp) < 0)
664 	error (0, errno, "cannot close %s", fname);
665     attrs_modified = 0;
666     free (fname);
667 
668     Parse_Info (CVSROOTADM_POSTWATCH, fileattr_stored_repos, postwatch_proc,
669 		PIOPT_ALL, NULL);
670 }
671 
672 
673 
674 void
675 fileattr_free (void)
676 {
677     /* Note that attrs_modified will ordinarily be zero, but there are
678        a few cases in which fileattr_write will fail to zero it (if
679        noexec is set, or error conditions).  This probably is the way
680        it should be.  */
681     dellist (&attrlist);
682     if (fileattr_stored_repos != NULL)
683 	free (fileattr_stored_repos);
684     fileattr_stored_repos = NULL;
685     if (fileattr_default_attrs != NULL)
686 	free (fileattr_default_attrs);
687     fileattr_default_attrs = NULL;
688     while (unrecog_head)
689     {
690 	struct unrecog *p = unrecog_head;
691 	unrecog_head = p->next;
692 	free (p->line);
693 	free (p);
694     }
695 }
696