wrap and sort CLEANFILES
[alioth/cvs.git] / 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      * %I = commit ID
518      * %p = shortrepos
519      * %r = repository
520      */
521     /*
522      * Cast any NULL arguments as appropriate pointers as this is an
523      * stdarg function and we need to be certain the caller gets what
524      * is expected.
525      */
526     cmdline = format_cmdline (
527 #ifdef SUPPORT_OLD_INFO_FMT_STRINGS
528                               false, srepos,
529 #endif /* SUPPORT_OLD_INFO_FMT_STRINGS */
530                               filter,
531                               "c", "s", cvs_cmd_name,
532                               "I", "s", global_session_id,
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
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
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 }