Merge from vendor branch CVS:
[dragonfly.git] / contrib / cvs-1.12.11 / 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;
90
91     if (wrap_setup_already_done != 0)
92         return;
93     else
94         wrap_setup_already_done = 1;
95
96 #ifdef CLIENT_SUPPORT
97     if (!current_parsed_root->isremote)
98 #endif
99     {
100         char *file;
101
102         file = xmalloc (strlen (current_parsed_root->directory)
103                         + sizeof (CVSROOTADM)
104                         + sizeof (CVSROOTADM_WRAPPER)
105                         + 3);
106         /* Then add entries found in repository, if it exists.  */
107         (void) sprintf (file, "%s/%s/%s", current_parsed_root->directory, CVSROOTADM,
108                         CVSROOTADM_WRAPPER);
109         if (isfile (file))
110         {
111             wrap_add_file(file,0);
112         }
113         free (file);
114     }
115
116     /* Then add entries found in home dir, (if user has one) and file
117        exists.  */
118     homedir = get_homedir ();
119     /* If we can't find a home directory, ignore ~/.cvswrappers.  This may
120        make tracking down problems a bit of a pain, but on the other
121        hand it might be obnoxious to complain when CVS will function
122        just fine without .cvswrappers (and many users won't even know what
123        .cvswrappers is).  */
124     if (homedir != NULL)
125     {
126         char *file = strcat_filename_onto_homedir (homedir, CVSDOTWRAPPER);
127         if (isfile (file))
128         {
129             wrap_add_file (file, 0);
130         }
131         free (file);
132     }
133
134     /* FIXME: calling wrap_add() below implies that the CVSWRAPPERS
135      * environment variable contains exactly one "wrapper" -- a line
136      * of the form
137      * 
138      *    FILENAME_PATTERN      FLAG  OPTS [ FLAG OPTS ...]
139      *
140      * This may disagree with the documentation, which states:
141      * 
142      *   `$CVSWRAPPERS'
143      *      A whitespace-separated list of file name patterns that CVS
144      *      should treat as wrappers. *Note Wrappers::.
145      *
146      * Does this mean the environment variable can hold multiple
147      * wrappers lines?  If so, a single call to wrap_add() is
148      * insufficient.
149      */
150
151     /* Then add entries found in CVSWRAPPERS environment variable. */
152     wrap_add (getenv (WRAPPER_ENV), 0);
153 }
154
155 #ifdef CLIENT_SUPPORT
156 /* Send -W arguments for the wrappers to the server.  The command must
157    be one that accepts them (e.g. update, import).  */
158 void
159 wrap_send (void)
160 {
161     int i;
162
163     for (i = 0; i < wrap_count + wrap_tempcount; ++i)
164     {
165         if (wrap_list[i]->tocvsFilter != NULL
166             || wrap_list[i]->fromcvsFilter != NULL)
167             /* For greater studliness we would print the offending option
168                and (more importantly) where we found it.  */
169             error (0, 0, "\
170 -t and -f wrapper options are not supported remotely; ignored");
171         if (wrap_list[i]->mergeMethod == WRAP_COPY)
172             /* For greater studliness we would print the offending option
173                and (more importantly) where we found it.  */
174             error (0, 0, "\
175 -m wrapper option is not supported remotely; ignored");
176         send_to_server ("Argument -W\012Argument ", 0);
177         send_to_server (wrap_list[i]->wildCard, 0);
178         send_to_server (" -k '", 0);
179         if (wrap_list[i]->rcsOption != NULL)
180             send_to_server (wrap_list[i]->rcsOption, 0);
181         else
182             send_to_server ("kv", 0);
183         send_to_server ("'\012", 0);
184     }
185 }
186 #endif /* CLIENT_SUPPORT */
187
188 #if defined(SERVER_SUPPORT) || defined(CLIENT_SUPPORT)
189 /* Output wrapper entries in the format of cvswrappers lines.
190  *
191  * This is useful when one side of a client/server connection wants to
192  * send its wrappers to the other; since the receiving side would like
193  * to use wrap_add() to incorporate the wrapper, it's best if the
194  * entry arrives in this format.
195  *
196  * The entries are stored in `line', which is allocated here.  Caller
197  * can free() it.
198  *
199  * If first_call_p is nonzero, then start afresh.  */
200 void
201 wrap_unparse_rcs_options (char **line, int first_call_p)
202 {
203     /* FIXME-reentrancy: we should design a reentrant interface, like
204        a callback which gets handed each wrapper (a multithreaded
205        server being the most concrete reason for this, but the
206        non-reentrant interface is fairly unnecessary/ugly).  */
207     static int i;
208
209     if (first_call_p)
210         i = 0;
211
212     if (i >= wrap_count + wrap_tempcount) {
213         *line = NULL;
214         return;
215     }
216
217     *line = xmalloc (strlen (wrap_list[i]->wildCard)
218                      + strlen ("\t")
219                      + strlen (" -k '")
220                      + (wrap_list[i]->rcsOption != NULL ? 
221                            strlen (wrap_list[i]->rcsOption) : 2)
222                      + strlen ("'")
223                      + 1);  /* leave room for '\0' */
224
225     strcpy (*line, wrap_list[i]->wildCard);
226     strcat (*line, " -k '");
227     if (wrap_list[i]->rcsOption != NULL)
228         strcat (*line, wrap_list[i]->rcsOption);
229     else
230         strcat (*line, "kv");
231     strcat (*line, "'");
232
233     ++i;
234 }
235 #endif /* SERVER_SUPPORT || CLIENT_SUPPORT */
236
237 /*
238  * Remove fmt str specifier other than %% or %s. And allow
239  * only max_s %s specifiers
240  */
241 static void
242 wrap_clean_fmt_str(char *fmt, int max_s)
243 {
244     while (*fmt) {
245         if (fmt[0] == '%' && fmt[1])
246         {
247             if (fmt[1] == '%') 
248                 fmt++;
249             else
250                 if (fmt[1] == 's' && max_s > 0)
251                 {
252                     max_s--;
253                     fmt++;
254                 } else 
255                     *fmt = ' ';
256         }
257         fmt++;
258     }
259 }
260
261 /*
262  * Open a file and read lines, feeding each line to a line parser. Arrange
263  * for keeping a temporary list of wrappers at the end, if the "temp"
264  * argument is set.
265  */
266 void
267 wrap_add_file (const char *file, int temp)
268 {
269     FILE *fp;
270     char *line = NULL;
271     size_t line_allocated = 0;
272
273     wrap_restore_saved ();
274     wrap_kill_temp ();
275
276     /* Load the file.  */
277     fp = CVS_FOPEN (file, "r");
278     if (fp == NULL)
279     {
280         if (!existence_error (errno))
281             error (0, errno, "cannot open %s", file);
282         return;
283     }
284     while (getline (&line, &line_allocated, fp) >= 0)
285         wrap_add (line, temp);
286     if (line)
287         free (line);
288     if (ferror (fp))
289         error (0, errno, "cannot read %s", file);
290     if (fclose (fp) == EOF)
291         error (0, errno, "cannot close %s", file);
292 }
293
294 void
295 wrap_kill(void)
296 {
297     wrap_kill_temp();
298     while(wrap_count)
299         wrap_free_entry(wrap_list[--wrap_count]);
300 }
301
302 void
303 wrap_kill_temp(void)
304 {
305     WrapperEntry **temps=wrap_list+wrap_count;
306
307     while(wrap_tempcount)
308         wrap_free_entry(temps[--wrap_tempcount]);
309 }
310
311 void
312 wrap_free_entry(WrapperEntry *e)
313 {
314     wrap_free_entry_internal(e);
315     free(e);
316 }
317
318 void
319 wrap_free_entry_internal(WrapperEntry *e)
320 {
321     free (e->wildCard);
322     if (e->tocvsFilter)
323         free (e->tocvsFilter);
324     if (e->fromcvsFilter)
325         free (e->fromcvsFilter);
326     if (e->rcsOption)
327         free (e->rcsOption);
328 }
329
330 void
331 wrap_restore_saved(void)
332 {
333     if(!wrap_saved_list)
334         return;
335
336     wrap_kill();
337
338     free(wrap_list);
339
340     wrap_list=wrap_saved_list;
341     wrap_count=wrap_saved_count;
342     wrap_tempcount=wrap_saved_tempcount;
343
344     wrap_saved_list=NULL;
345     wrap_saved_count=0;
346     wrap_saved_tempcount=0;
347 }
348
349 void
350 wrap_add (char *line, int isTemp)
351 {
352     char *temp;
353     char ctemp;
354     WrapperEntry e;
355     char opt;
356
357     if (!line || line[0] == '#')
358         return;
359
360     memset (&e, 0, sizeof(e));
361
362         /* Search for the wild card */
363     while (*line && isspace ((unsigned char) *line))
364         ++line;
365     for (temp = line;
366          *line && !isspace ((unsigned char) *line);
367          ++line)
368         ;
369     if(temp==line)
370         return;
371
372     ctemp=*line;
373     *line='\0';
374
375     e.wildCard=xstrdup(temp);
376     *line=ctemp;
377
378     while(*line){
379             /* Search for the option */
380         while(*line && *line!='-')
381             ++line;
382         if(!*line)
383             break;
384         ++line;
385         if(!*line)
386             break;
387         opt=*line;
388
389             /* Search for the filter commandline */
390         for(++line;*line && *line!='\'';++line);
391         if(!*line)
392             break;
393
394         for(temp=++line;*line && (*line!='\'' || line[-1]=='\\');++line)
395             ;
396
397         /* This used to "break;" (ignore the option) if there was a
398            single character between the single quotes (I'm guessing
399            that was accidental).  Now it "break;"s if there are no
400            characters.  I'm not sure either behavior is particularly
401            necessary--the current options might not require ''
402            arguments, but surely some future option legitimately
403            might.  Also I'm not sure that ignoring the option is a
404            swift way to handle syntax errors in general.  */
405         if (line==temp)
406             break;
407
408         ctemp=*line;
409         *line='\0';
410         switch(opt){
411         case 'f':
412             /* Before this is reenabled, need to address the problem in
413                commit.c (see http://www.cvshome.org/docs/infowrapper.html).  */
414             error (1, 0,
415                    "-t/-f wrappers not supported by this version of CVS");
416
417             if(e.fromcvsFilter)
418                 free(e.fromcvsFilter);
419             /* FIXME: error message should say where the bad value
420                came from.  */
421             e.fromcvsFilter=expand_path (temp, "<wrapper>", 0, 0);
422             if (!e.fromcvsFilter)
423                 error (1, 0, "Correct above errors first");
424             break;
425         case 't':
426             /* Before this is reenabled, need to address the problem in
427                commit.c (see http://www.cvshome.org/docs/infowrapper.html).  */
428             error (1, 0,
429                    "-t/-f wrappers not supported by this version of CVS");
430
431             if(e.tocvsFilter)
432                 free(e.tocvsFilter);
433             /* FIXME: error message should say where the bad value
434                came from.  */
435             e.tocvsFilter=expand_path (temp, "<wrapper>", 0, 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         wrap_size += WRAPPER_GROW;
467         wrap_list = (WrapperEntry **) xrealloc ((char *) wrap_list,
468                                                 wrap_size *
469                                                 sizeof (WrapperEntry *));
470     }
471
472     if(!temp && wrap_tempcount){
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]=(WrapperEntry *)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 (WrapperEntry *)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     char *buf;
533
534     if (e == NULL || e->rcsOption == NULL || (*e->rcsOption == '\0'))
535         return NULL;
536
537     buf = xmalloc (strlen (e->rcsOption) + 3);
538     if (asflag)
539     {
540         strcpy (buf, "-k");
541         strcat (buf, e->rcsOption);
542     }
543     else
544     {
545         strcpy (buf, e->rcsOption);
546     }
547     return buf;
548 }
549
550 char *
551 wrap_tocvs_process_file(const char *fileName)
552 {
553     WrapperEntry *e=wrap_matching_entry(fileName);
554     static char *buf = NULL;
555     char *args;
556
557     if(e==NULL || e->tocvsFilter==NULL)
558         return NULL;
559
560     if (buf != NULL)
561         free (buf);
562     buf = cvs_temp_name ();
563
564     args = xmalloc (strlen (e->tocvsFilter)
565                     + strlen (fileName)
566                     + strlen (buf));
567
568     wrap_clean_fmt_str(e->tocvsFilter, 2);
569     sprintf (args, e->tocvsFilter, fileName, buf);
570     run_setup (args);
571     run_exec(RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL|RUN_REALLY );
572     free (args);
573
574     return buf;
575 }
576
577 int
578 wrap_merge_is_copy (const char *fileName)
579 {
580     WrapperEntry *e=wrap_matching_entry(fileName);
581     if(e==NULL || e->mergeMethod==WRAP_MERGE)
582         return 0;
583
584     return 1;
585 }
586
587 void
588 wrap_fromcvs_process_file(const char *fileName)
589 {
590     char *args;
591     WrapperEntry *e=wrap_matching_entry(fileName);
592
593     if(e==NULL || e->fromcvsFilter==NULL)
594         return;
595
596     args = xmalloc (strlen (e->fromcvsFilter)
597                     + strlen (fileName));
598
599     wrap_clean_fmt_str(e->fromcvsFilter, 1);
600     sprintf (args, e->fromcvsFilter, fileName);
601     run_setup (args);
602     run_exec(RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL );
603     free (args);
604     return;
605 }