Sync with FreeBSD. This adds read-only support for zip and ISO9660.
[dragonfly.git] / contrib / cvs-1.12.12 / src / fileattr.c
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 }