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