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 __RCSID("$MirOS: src/gnu/usr.bin/cvs/src/fileattr.c,v 1.4 2010/09/19 19:43:03 tg Exp $");
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      * %I = commit ID
520      * %p = shortrepos
521      * %r = repository
522      */
523     /*
524      * Cast any NULL arguments as appropriate pointers as this is an
525      * stdarg function and we need to be certain the caller gets what
526      * is expected.
527      */
528     cmdline = format_cmdline (
529 #ifdef SUPPORT_OLD_INFO_FMT_STRINGS
530 	                      false, srepos,
531 #endif /* SUPPORT_OLD_INFO_FMT_STRINGS */
532 	                      filter,
533 	                      "c", "s", cvs_cmd_name,
534 			      "I", "s", global_session_id,
535 #ifdef SERVER_SUPPORT
536 	                      "R", "s", referrer ? referrer->original : "NONE",
537 #endif /* SERVER_SUPPORT */
538 	                      "p", "s", srepos,
539 	                      "r", "s", current_parsed_root->directory,
540 	                      (char *) NULL);
541 
542     if (!cmdline || !strlen (cmdline))
543     {
544 	if (cmdline) free (cmdline);
545 	error (0, 0, "postwatch proc resolved to the empty string!");
546 	return 1;
547     }
548 
549     run_setup (cmdline);
550 
551     free (cmdline);
552 
553     /* FIXME - read the comment in verifymsg_proc() about why we use abs()
554      * below() and shouldn't.
555      */
556     return abs (run_exec (RUN_TTY, RUN_TTY, RUN_TTY,
557 			  RUN_NORMAL | RUN_SIGIGNORE));
558 }
559 
560 
561 
562 void
fileattr_write(void)563 fileattr_write (void)
564 {
565     FILE *fp;
566     char *fname;
567     mode_t omask;
568     struct unrecog *p;
569 
570     if (!attrs_modified)
571 	return;
572 
573     if (noexec)
574 	return;
575 
576     /* If NULL was passed to fileattr_startdir, then it isn't kosher to set
577        attributes.  */
578     assert (fileattr_stored_repos != NULL);
579 
580     fname = Xasprintf ("%s/%s", fileattr_stored_repos, CVSREP_FILEATTR);
581 
582     if (list_isempty (attrlist)
583 	&& fileattr_default_attrs == NULL
584 	&& unrecog_head == NULL)
585     {
586 	/* There are no attributes.  */
587 	if (unlink_file (fname) < 0)
588 	{
589 	    if (!existence_error (errno))
590 	    {
591 		error (0, errno, "cannot remove %s", fname);
592 	    }
593 	}
594 
595 	/* Now remove CVSREP directory, if empty.  The main reason we bother
596 	   is that CVS 1.6 and earlier will choke if a CVSREP directory
597 	   exists, so provide the user a graceful way to remove it.  */
598 	strcpy (fname, fileattr_stored_repos);
599 	strcat (fname, "/");
600 	strcat (fname, CVSREP);
601 	if (CVS_RMDIR (fname) < 0)
602 	{
603 	    if (errno != ENOTEMPTY
604 
605 		/* Don't know why we would be here if there is no CVSREP
606 		   directory, but it seemed to be happening anyway, so
607 		   check for it.  */
608 		&& !existence_error (errno))
609 		error (0, errno, "cannot remove %s", fname);
610 	}
611 
612 	free (fname);
613 	return;
614     }
615 
616     omask = umask (cvsumask);
617     fp = CVS_FOPEN (fname, FOPEN_BINARY_WRITE);
618     if (fp == NULL)
619     {
620 	if (existence_error (errno))
621 	{
622 	    /* Maybe the CVSREP directory doesn't exist.  Try creating it.  */
623 	    char *repname;
624 
625 	    repname = Xasprintf ("%s/%s", fileattr_stored_repos, CVSREP);
626 
627 	    if (CVS_MKDIR (repname, 0777) < 0 && errno != EEXIST)
628 	    {
629 		error (0, errno, "cannot make directory %s", repname);
630 		(void) umask (omask);
631 		free (fname);
632 		free (repname);
633 		return;
634 	    }
635 	    free (repname);
636 
637 	    fp = CVS_FOPEN (fname, FOPEN_BINARY_WRITE);
638 	}
639 	if (fp == NULL)
640 	{
641 	    error (0, errno, "cannot write %s", fname);
642 	    (void) umask (omask);
643 	    free (fname);
644 	    return;
645 	}
646     }
647     (void) umask (omask);
648 
649     /* First write the "F" attributes.  */
650     walklist (attrlist, writeattr_proc, fp);
651 
652     /* Then the "D" attribute.  */
653     if (fileattr_default_attrs != NULL)
654     {
655 	fputs ("D\t", fp);
656 	fputs (fileattr_default_attrs, fp);
657 	fputs ("\012", fp);
658     }
659 
660     /* Then any other attributes.  */
661     for (p = unrecog_head; p != NULL; p = p->next)
662     {
663 	fputs (p->line, fp);
664 	fputs ("\012", fp);
665     }
666 
667     if (fclose (fp) < 0)
668 	error (0, errno, "cannot close %s", fname);
669     attrs_modified = 0;
670     free (fname);
671 
672     Parse_Info (CVSROOTADM_POSTWATCH, fileattr_stored_repos, postwatch_proc,
673 		PIOPT_ALL, NULL);
674 }
675 
676 
677 
678 void
fileattr_free(void)679 fileattr_free (void)
680 {
681     /* Note that attrs_modified will ordinarily be zero, but there are
682        a few cases in which fileattr_write will fail to zero it (if
683        noexec is set, or error conditions).  This probably is the way
684        it should be.  */
685     dellist (&attrlist);
686     if (fileattr_stored_repos != NULL)
687 	free (fileattr_stored_repos);
688     fileattr_stored_repos = NULL;
689     if (fileattr_default_attrs != NULL)
690 	free (fileattr_default_attrs);
691     fileattr_default_attrs = NULL;
692     while (unrecog_head)
693     {
694 	struct unrecog *p = unrecog_head;
695 	unrecog_head = p->next;
696 	free (p->line);
697 	free (p);
698     }
699 }
700