xref: /dragonfly/contrib/cvs-1.12/src/fileattr.c (revision 86d7f5d305c6adaa56ff4582ece9859d73106103)
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
fileattr_startdir(const char * repos)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
fileattr_delproc(Node * node)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
fileattr_read(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 *
fileattr_get(const char * filename,const char * attrname)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 *
fileattr_get0(const char * filename,const char * attrname)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 *
fileattr_modify(char * list,const char * attrname,const char * attrval,int namevalsep,int entsep)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
fileattr_set(const char * filename,const char * attrname,const char * attrval)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 *
fileattr_getall(const char * filename)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
fileattr_setall(const char * filename,const char * attrs)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
fileattr_newfile(const char * filename)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
writeattr_proc(Node * node,void * data)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
postwatch_proc(const char * repository,const char * filter,void * closure)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
fileattr_write(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
fileattr_free(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