import cvs 2:1.12.13+real-5 (see mircvs://src/gnu/usr.bin/cvs/ for VCS history)
[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 __RCSID("$MirOS: ports/devel/cvs/patches/patch-src_fileattr_c,v 1.2 2010/09/15 20:57:00 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
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
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
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 *
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 *
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 *
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
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 *
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
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
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
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
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
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
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 }