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