also add -fwrapv to CFLAGS (addresses #698908)
[alioth/cvs.git] / src / wrapper.c
1 /* This program is free software; you can redistribute it and/or modify
2    it under the terms of the GNU General Public License as published by
3    the Free Software Foundation; either version 2, or (at your option)
4    any later version.
5
6    This program is distributed in the hope that it will be useful,
7    but WITHOUT ANY WARRANTY; without even the implied warranty of
8    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
9    GNU General Public License for more details.  */
10
11 #include "cvs.h"
12 #include "getline.h"
13
14 /*
15   Original Author:  athan@morgan.com <Andrew C. Athan> 2/1/94
16   Modified By:      vdemarco@bou.shl.com
17
18   This package was written to support the NEXTSTEP concept of
19   "wrappers."  These are essentially directories that are to be
20   treated as "files."  This package allows such wrappers to be
21   "processed" on the way in and out of CVS.  The intended use is to
22   wrap up a wrapper into a single tar, such that that tar can be
23   treated as a single binary file in CVS.  To solve the problem
24   effectively, it was also necessary to be able to prevent rcsmerge
25   application at appropriate times.
26
27   ------------------
28   Format of wrapper file ($CVSROOT/CVSROOT/cvswrappers or .cvswrappers)
29
30   wildcard      [option value][option value]...
31
32   where option is one of
33   -m            update methodology      value: MERGE or COPY
34   -k            default -k rcs option to use on import or add
35
36   and value is a single-quote delimited value.
37
38   E.g:
39   *.nib         -f 'gunzipuntar' -t 'targzip' -m 'COPY'
40 */
41
42
43 typedef struct {
44     char *wildCard;
45     char *tocvsFilter;
46     char *fromcvsFilter;
47     char *rcsOption;
48     WrapMergeMethod mergeMethod;
49 } WrapperEntry;
50
51 static WrapperEntry **wrap_list=NULL;
52 static WrapperEntry **wrap_saved_list=NULL;
53
54 static int wrap_size=0;
55 static int wrap_count=0;
56 static int wrap_tempcount=0;
57
58 /* FIXME: the relationship between wrap_count, wrap_tempcount,
59  * wrap_saved_count, and wrap_saved_tempcount is not entirely clear;
60  * it is certainly suspicious that wrap_saved_count is never set to a
61  * value other than zero!  If the variable isn't being used, it should
62  * be removed.  And in general, we should describe how temporary
63  * vs. permanent wrappers are implemented, and then make sure the
64  * implementation is actually doing that.
65  *
66  * Right now things seem to be working, but that's no guarantee there
67  * isn't a bug lurking somewhere in the murk.
68  */
69
70 static int wrap_saved_count=0;
71
72 static int wrap_saved_tempcount=0;
73
74 #define WRAPPER_GROW    8
75
76 void wrap_add_entry (WrapperEntry *e,int temp);
77 void wrap_kill (void);
78 void wrap_kill_temp (void);
79 void wrap_free_entry (WrapperEntry *e);
80 void wrap_free_entry_internal (WrapperEntry *e);
81 void wrap_restore_saved (void);
82
83 void wrap_setup(void)
84 {
85     /* FIXME-reentrancy: if we do a multithreaded server, will need to
86        move this to a per-connection data structure, or better yet
87        think about a cleaner solution.  */
88     static int wrap_setup_already_done = 0;
89     char *homedir = NULL;
90
91     if (wrap_setup_already_done != 0)
92         return;
93     else
94         wrap_setup_already_done = 1;
95
96     if (!current_parsed_root->isremote)
97     {
98         char *file;
99
100         /* Then add entries found in repository, if it exists.  */
101         file = Xasprintf ("%s/%s/%s", current_parsed_root->directory,
102                           CVSROOTADM, CVSROOTADM_WRAPPER);
103         if (isfile (file))
104         {
105             wrap_add_file(file,0);
106         }
107         free (file);
108     }
109
110 #ifdef SERVER_SUPPORT
111     if (!server_active)
112 #endif
113     {
114
115     /* Then add entries found in home dir, (if user has one) and file
116        exists.  */
117     homedir = get_homedir ();
118     /* If we can't find a home directory, ignore ~/.cvswrappers.  This may
119        make tracking down problems a bit of a pain, but on the other
120        hand it might be obnoxious to complain when CVS will function
121        just fine without .cvswrappers (and many users won't even know what
122        .cvswrappers is).  */
123     }
124
125     if (homedir != NULL)
126     {
127         char *file = strcat_filename_onto_homedir (homedir, CVSDOTWRAPPER);
128         if (isfile (file))
129         {
130             wrap_add_file (file, 0);
131         }
132         free (file);
133     }
134
135     /* FIXME: calling wrap_add() below implies that the CVSWRAPPERS
136      * environment variable contains exactly one "wrapper" -- a line
137      * of the form
138      * 
139      *    FILENAME_PATTERN      FLAG  OPTS [ FLAG OPTS ...]
140      *
141      * This may disagree with the documentation, which states:
142      * 
143      *   `$CVSWRAPPERS'
144      *      A whitespace-separated list of file name patterns that CVS
145      *      should treat as wrappers. *Note Wrappers::.
146      *
147      * Does this mean the environment variable can hold multiple
148      * wrappers lines?  If so, a single call to wrap_add() is
149      * insufficient.
150      */
151
152     /* Then add entries found in CVSWRAPPERS environment variable. */
153     wrap_add (getenv (WRAPPER_ENV), 0);
154 }
155
156 #ifdef CLIENT_SUPPORT
157 /* Send -W arguments for the wrappers to the server.  The command must
158    be one that accepts them (e.g. update, import).  */
159 void
160 wrap_send (void)
161 {
162     int i;
163
164     for (i = 0; i < wrap_count + wrap_tempcount; ++i)
165     {
166         if (wrap_list[i]->tocvsFilter != NULL
167             || wrap_list[i]->fromcvsFilter != NULL)
168             /* For greater studliness we would print the offending option
169                and (more importantly) where we found it.  */
170             error (0, 0, "\
171 -t and -f wrapper options are not supported remotely; ignored");
172         if (wrap_list[i]->mergeMethod == WRAP_COPY)
173             /* For greater studliness we would print the offending option
174                and (more importantly) where we found it.  */
175             error (0, 0, "\
176 -m wrapper option is not supported remotely; ignored");
177         send_to_server ("Argument -W\012Argument ", 0);
178         send_to_server (wrap_list[i]->wildCard, 0);
179         send_to_server (" -k '", 0);
180         if (wrap_list[i]->rcsOption != NULL)
181             send_to_server (wrap_list[i]->rcsOption, 0);
182         else
183             send_to_server ("kv", 0);
184         send_to_server ("'\012", 0);
185     }
186 }
187 #endif /* CLIENT_SUPPORT */
188
189 #if defined(SERVER_SUPPORT) || defined(CLIENT_SUPPORT)
190 /* Output wrapper entries in the format of cvswrappers lines.
191  *
192  * This is useful when one side of a client/server connection wants to
193  * send its wrappers to the other; since the receiving side would like
194  * to use wrap_add() to incorporate the wrapper, it's best if the
195  * entry arrives in this format.
196  *
197  * The entries are stored in `line', which is allocated here.  Caller
198  * can free() it.
199  *
200  * If first_call_p is nonzero, then start afresh.  */
201 void
202 wrap_unparse_rcs_options (char **line, int first_call_p)
203 {
204     /* FIXME-reentrancy: we should design a reentrant interface, like
205        a callback which gets handed each wrapper (a multithreaded
206        server being the most concrete reason for this, but the
207        non-reentrant interface is fairly unnecessary/ugly).  */
208     static int i;
209
210     if (first_call_p)
211         i = 0;
212
213     if (i >= wrap_count + wrap_tempcount) {
214         *line = NULL;
215         return;
216     }
217
218     *line = Xasprintf ("%s -k '%s'",
219                        wrap_list[i]->wildCard,
220                        wrap_list[i]->rcsOption
221                        ? wrap_list[i]->rcsOption : "kv");
222     ++i;
223 }
224 #endif /* SERVER_SUPPORT || CLIENT_SUPPORT */
225
226 /*
227  * Remove fmt str specifier other than %% or %s. And allow
228  * only max_s %s specifiers
229  */
230 static void
231 wrap_clean_fmt_str(char *fmt, int max_s)
232 {
233     while (*fmt) {
234         if (fmt[0] == '%' && fmt[1])
235         {
236             if (fmt[1] == '%') 
237                 fmt++;
238             else
239                 if (fmt[1] == 's' && max_s > 0)
240                 {
241                     max_s--;
242                     fmt++;
243                 } else 
244                     *fmt = ' ';
245         }
246         fmt++;
247     }
248 }
249
250 /*
251  * Open a file and read lines, feeding each line to a line parser. Arrange
252  * for keeping a temporary list of wrappers at the end, if the "temp"
253  * argument is set.
254  */
255 void
256 wrap_add_file (const char *file, int temp)
257 {
258     FILE *fp;
259     char *line = NULL;
260     size_t line_allocated = 0;
261
262     wrap_restore_saved ();
263     wrap_kill_temp ();
264
265     /* Load the file.  */
266     fp = CVS_FOPEN (file, "r");
267     if (fp == NULL)
268     {
269         if (!existence_error (errno))
270             error (0, errno, "cannot open %s", file);
271         return;
272     }
273     while (getline (&line, &line_allocated, fp) >= 0)
274         wrap_add (line, temp);
275     if (line)
276         free (line);
277     if (ferror (fp))
278         error (0, errno, "cannot read %s", file);
279     if (fclose (fp) == EOF)
280         error (0, errno, "cannot close %s", file);
281 }
282
283 void
284 wrap_kill(void)
285 {
286     wrap_kill_temp();
287     while(wrap_count)
288         wrap_free_entry(wrap_list[--wrap_count]);
289 }
290
291 void
292 wrap_kill_temp(void)
293 {
294     WrapperEntry **temps=wrap_list+wrap_count;
295
296     while(wrap_tempcount)
297         wrap_free_entry(temps[--wrap_tempcount]);
298 }
299
300 void
301 wrap_free_entry(WrapperEntry *e)
302 {
303     wrap_free_entry_internal(e);
304     free(e);
305 }
306
307 void
308 wrap_free_entry_internal(WrapperEntry *e)
309 {
310     free (e->wildCard);
311     if (e->tocvsFilter)
312         free (e->tocvsFilter);
313     if (e->fromcvsFilter)
314         free (e->fromcvsFilter);
315     if (e->rcsOption)
316         free (e->rcsOption);
317 }
318
319 void
320 wrap_restore_saved(void)
321 {
322     if(!wrap_saved_list)
323         return;
324
325     wrap_kill();
326
327     free(wrap_list);
328
329     wrap_list=wrap_saved_list;
330     wrap_count=wrap_saved_count;
331     wrap_tempcount=wrap_saved_tempcount;
332
333     wrap_saved_list=NULL;
334     wrap_saved_count=0;
335     wrap_saved_tempcount=0;
336 }
337
338 void
339 wrap_add (char *line, int isTemp)
340 {
341     char *temp;
342     char ctemp;
343     WrapperEntry e;
344     char opt;
345
346     if (!line || line[0] == '#')
347         return;
348
349     /* Allows user to declare all wrappers null and void */
350     if (line[0] == '!') {
351       wrap_kill ();
352       return;
353     }
354
355     memset (&e, 0, sizeof(e));
356
357         /* Search for the wild card */
358     while (*line && isspace ((unsigned char) *line))
359         ++line;
360     for (temp = line;
361          *line && !isspace ((unsigned char) *line);
362          ++line)
363         ;
364     if(temp==line)
365         return;
366
367     ctemp=*line;
368     *line='\0';
369
370     e.wildCard=xstrdup(temp);
371     *line=ctemp;
372
373     while(*line){
374             /* Search for the option */
375         while(*line && *line!='-')
376             ++line;
377         if(!*line)
378             break;
379         ++line;
380         if(!*line)
381             break;
382         opt=*line;
383
384             /* Search for the filter commandline */
385         for(++line;*line && *line!='\'';++line);
386         if(!*line)
387             break;
388
389         for(temp=++line;*line && (*line!='\'' || line[-1]=='\\');++line)
390             ;
391
392         /* This used to "break;" (ignore the option) if there was a
393            single character between the single quotes (I'm guessing
394            that was accidental).  Now it "break;"s if there are no
395            characters.  I'm not sure either behavior is particularly
396            necessary--the current options might not require ''
397            arguments, but surely some future option legitimately
398            might.  Also I'm not sure that ignoring the option is a
399            swift way to handle syntax errors in general.  */
400         if (line==temp)
401             break;
402
403         ctemp=*line;
404         *line='\0';
405         switch(opt){
406         case 'f':
407             /* Before this is reenabled, need to address the problem in
408                commit.c (see
409                <http://ximbiot.com/cvs/cvshome/docs/infowrapper.html>).  */
410             error (1, 0,
411                    "-t/-f wrappers not supported by this version of CVS");
412
413             if(e.fromcvsFilter)
414                 free(e.fromcvsFilter);
415             /* FIXME: error message should say where the bad value
416                came from.  */
417             e.fromcvsFilter =
418               expand_path (temp, current_parsed_root->directory, false,
419                            "<wrapper>", 0);
420             if (!e.fromcvsFilter)
421                 error (1, 0, "Correct above errors first");
422             break;
423         case 't':
424             /* Before this is reenabled, need to address the problem in
425                commit.c (see
426                <http://ximbiot.com/cvs/cvshome/docs/infowrapper.html>).  */
427             error (1, 0,
428                    "-t/-f wrappers not supported by this version of CVS");
429
430             if(e.tocvsFilter)
431                 free(e.tocvsFilter);
432             /* FIXME: error message should say where the bad value
433                came from.  */
434             e.tocvsFilter = expand_path (temp, current_parsed_root->directory,
435                                          false, "<wrapper>", 0);
436             if (!e.tocvsFilter)
437                 error (1, 0, "Correct above errors first");
438             break;
439         case 'm':
440             if(*temp=='C' || *temp=='c')
441                 e.mergeMethod=WRAP_COPY;
442             else
443                 e.mergeMethod=WRAP_MERGE;
444             break;
445         case 'k':
446             if (e.rcsOption)
447                 free (e.rcsOption);
448             e.rcsOption = strcmp (temp, "kv") ? xstrdup (temp) : NULL;
449             break;
450         default:
451             break;
452         }
453         *line=ctemp;
454         if(!*line)break;
455         ++line;
456     }
457
458     wrap_add_entry(&e, isTemp);
459 }
460
461 void
462 wrap_add_entry (WrapperEntry *e, int temp)
463 {
464     int x;
465     if (wrap_count + wrap_tempcount >= wrap_size)
466     {
467         wrap_size += WRAPPER_GROW;
468         wrap_list = xnrealloc (wrap_list, wrap_size, sizeof (WrapperEntry *));
469     }
470
471     if (!temp && wrap_tempcount)
472     {
473         for (x = wrap_count + wrap_tempcount - 1; x >= wrap_count; --x)
474             wrap_list[x + 1] = wrap_list[x];
475     }
476
477     x = (temp ? wrap_count + (wrap_tempcount++) : (wrap_count++));
478     wrap_list[x] = xmalloc (sizeof (WrapperEntry));
479     *wrap_list[x] = *e;
480 }
481
482 /* Return 1 if the given filename is a wrapper filename */
483 int
484 wrap_name_has (const char *name, WrapMergeHas has)
485 {
486     int x,count=wrap_count+wrap_tempcount;
487     char *temp;
488
489     for(x=0;x<count;++x)
490         if (CVS_FNMATCH (wrap_list[x]->wildCard, name, 0) == 0){
491             switch(has){
492             case WRAP_TOCVS:
493                 temp=wrap_list[x]->tocvsFilter;
494                 break;
495             case WRAP_FROMCVS:
496                 temp=wrap_list[x]->fromcvsFilter;
497                 break;
498             case WRAP_RCSOPTION:
499                 temp = wrap_list[x]->rcsOption;
500                 break;
501             default:
502                 abort ();
503             }
504             if(temp==NULL)
505                 return (0);
506             else
507                 return (1);
508         }
509     return (0);
510 }
511
512 static WrapperEntry *wrap_matching_entry (const char *);
513
514 static WrapperEntry *
515 wrap_matching_entry (const char *name)
516 {
517     int x,count=wrap_count+wrap_tempcount;
518
519     for(x=0;x<count;++x)
520         if (CVS_FNMATCH (wrap_list[x]->wildCard, name, 0) == 0)
521             return wrap_list[x];
522     return NULL;
523 }
524
525 /* Return the RCS options for FILENAME in a newly malloc'd string.  If
526    ASFLAG, then include "-k" at the beginning (e.g. "-kb"), otherwise
527    just give the option itself (e.g. "b").  */
528 char *
529 wrap_rcsoption (const char *filename, int asflag)
530 {
531     WrapperEntry *e = wrap_matching_entry (filename);
532
533     if (e == NULL || e->rcsOption == NULL || (*e->rcsOption == '\0'))
534         return NULL;
535
536     return Xasprintf ("%s%s", asflag ? "-k" : "", e->rcsOption);
537 }
538
539 char *
540 wrap_tocvs_process_file(const char *fileName)
541 {
542     WrapperEntry *e=wrap_matching_entry(fileName);
543     static char *buf = NULL;
544     char *args;
545
546     if(e==NULL || e->tocvsFilter==NULL)
547         return NULL;
548
549     if (buf != NULL)
550         free (buf);
551     buf = cvs_temp_name ();
552
553     wrap_clean_fmt_str (e->tocvsFilter, 2);
554     args = Xasprintf (e->tocvsFilter, fileName, buf);
555     run_setup (args);
556     run_exec (RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL | RUN_REALLY );
557     free (args);
558
559     return buf;
560 }
561
562 int
563 wrap_merge_is_copy (const char *fileName)
564 {
565     WrapperEntry *e=wrap_matching_entry(fileName);
566     if(e==NULL || e->mergeMethod==WRAP_MERGE)
567         return 0;
568
569     return 1;
570 }
571
572 void
573 wrap_fromcvs_process_file(const char *fileName)
574 {
575     char *args;
576     WrapperEntry *e = wrap_matching_entry(fileName);
577
578     if (e != NULL && e->fromcvsFilter != NULL)
579     {
580         wrap_clean_fmt_str (e->fromcvsFilter, 1);
581         args = Xasprintf (e->fromcvsFilter, fileName);
582         run_setup (args);
583         run_exec (RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL);
584         free (args);
585     }
586     return;
587 }