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
43typedef struct {
44    char *wildCard;
45    char *tocvsFilter;
46    char *fromcvsFilter;
47    char *rcsOption;
48    WrapMergeMethod mergeMethod;
49} WrapperEntry;
50
51static WrapperEntry **wrap_list=NULL;
52static WrapperEntry **wrap_saved_list=NULL;
53
54static int wrap_size=0;
55static int wrap_count=0;
56static 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
70static int wrap_saved_count=0;
71
72static int wrap_saved_tempcount=0;
73
74#define WRAPPER_GROW	8
75
76void wrap_add_entry (WrapperEntry *e,int temp);
77void wrap_kill (void);
78void wrap_kill_temp (void);
79void wrap_free_entry (WrapperEntry *e);
80void wrap_free_entry_internal (WrapperEntry *e);
81void wrap_restore_saved (void);
82
83void 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    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    /* Then add entries found in home dir, (if user has one) and file
111       exists.  */
112    homedir = get_homedir ();
113    /* If we can't find a home directory, ignore ~/.cvswrappers.  This may
114       make tracking down problems a bit of a pain, but on the other
115       hand it might be obnoxious to complain when CVS will function
116       just fine without .cvswrappers (and many users won't even know what
117       .cvswrappers is).  */
118    if (homedir != NULL)
119    {
120	char *file = strcat_filename_onto_homedir (homedir, CVSDOTWRAPPER);
121	if (isfile (file))
122	{
123	    wrap_add_file (file, 0);
124	}
125	free (file);
126    }
127
128    /* FIXME: calling wrap_add() below implies that the CVSWRAPPERS
129     * environment variable contains exactly one "wrapper" -- a line
130     * of the form
131     *
132     *    FILENAME_PATTERN	FLAG  OPTS [ FLAG OPTS ...]
133     *
134     * This may disagree with the documentation, which states:
135     *
136     *   `$CVSWRAPPERS'
137     *      A whitespace-separated list of file name patterns that CVS
138     *      should treat as wrappers. *Note Wrappers::.
139     *
140     * Does this mean the environment variable can hold multiple
141     * wrappers lines?  If so, a single call to wrap_add() is
142     * insufficient.
143     */
144
145    /* Then add entries found in CVSWRAPPERS environment variable. */
146    wrap_add (getenv (WRAPPER_ENV), 0);
147}
148
149#ifdef CLIENT_SUPPORT
150/* Send -W arguments for the wrappers to the server.  The command must
151   be one that accepts them (e.g. update, import).  */
152void
153wrap_send (void)
154{
155    int i;
156
157    for (i = 0; i < wrap_count + wrap_tempcount; ++i)
158    {
159	if (wrap_list[i]->tocvsFilter != NULL
160	    || wrap_list[i]->fromcvsFilter != NULL)
161	    /* For greater studliness we would print the offending option
162	       and (more importantly) where we found it.  */
163	    error (0, 0, "\
164-t and -f wrapper options are not supported remotely; ignored");
165	if (wrap_list[i]->mergeMethod == WRAP_COPY)
166	    /* For greater studliness we would print the offending option
167	       and (more importantly) where we found it.  */
168	    error (0, 0, "\
169-m wrapper option is not supported remotely; ignored");
170	send_to_server ("Argument -W\012Argument ", 0);
171	send_to_server (wrap_list[i]->wildCard, 0);
172	send_to_server (" -k '", 0);
173	if (wrap_list[i]->rcsOption != NULL)
174	    send_to_server (wrap_list[i]->rcsOption, 0);
175	else
176	    send_to_server ("kv", 0);
177	send_to_server ("'\012", 0);
178    }
179}
180#endif /* CLIENT_SUPPORT */
181
182#if defined(SERVER_SUPPORT) || defined(CLIENT_SUPPORT)
183/* Output wrapper entries in the format of cvswrappers lines.
184 *
185 * This is useful when one side of a client/server connection wants to
186 * send its wrappers to the other; since the receiving side would like
187 * to use wrap_add() to incorporate the wrapper, it's best if the
188 * entry arrives in this format.
189 *
190 * The entries are stored in `line', which is allocated here.  Caller
191 * can free() it.
192 *
193 * If first_call_p is nonzero, then start afresh.  */
194void
195wrap_unparse_rcs_options (char **line, int first_call_p)
196{
197    /* FIXME-reentrancy: we should design a reentrant interface, like
198       a callback which gets handed each wrapper (a multithreaded
199       server being the most concrete reason for this, but the
200       non-reentrant interface is fairly unnecessary/ugly).  */
201    static int i;
202
203    if (first_call_p)
204        i = 0;
205
206    if (i >= wrap_count + wrap_tempcount) {
207        *line = NULL;
208        return;
209    }
210
211    *line = Xasprintf ("%s -k '%s'",
212		       wrap_list[i]->wildCard,
213		       wrap_list[i]->rcsOption
214		       ? wrap_list[i]->rcsOption : "kv");
215    ++i;
216}
217#endif /* SERVER_SUPPORT || CLIENT_SUPPORT */
218
219/*
220 * Remove fmt str specifier other than %% or %s. And allow
221 * only max_s %s specifiers
222 */
223static void
224wrap_clean_fmt_str(char *fmt, int max_s)
225{
226    while (*fmt) {
227	if (fmt[0] == '%' && fmt[1])
228	{
229	    if (fmt[1] == '%')
230		fmt++;
231	    else
232		if (fmt[1] == 's' && max_s > 0)
233		{
234		    max_s--;
235		    fmt++;
236		} else
237		    *fmt = ' ';
238	}
239	fmt++;
240    }
241}
242
243/*
244 * Open a file and read lines, feeding each line to a line parser. Arrange
245 * for keeping a temporary list of wrappers at the end, if the "temp"
246 * argument is set.
247 */
248void
249wrap_add_file (const char *file, int temp)
250{
251    FILE *fp;
252    char *line = NULL;
253    size_t line_allocated = 0;
254
255    wrap_restore_saved ();
256    wrap_kill_temp ();
257
258    /* Load the file.  */
259    fp = CVS_FOPEN (file, "r");
260    if (fp == NULL)
261    {
262	if (!existence_error (errno))
263	    error (0, errno, "cannot open %s", file);
264	return;
265    }
266    while (getline (&line, &line_allocated, fp) >= 0)
267	wrap_add (line, temp);
268    if (line)
269        free (line);
270    if (ferror (fp))
271	error (0, errno, "cannot read %s", file);
272    if (fclose (fp) == EOF)
273	error (0, errno, "cannot close %s", file);
274}
275
276void
277wrap_kill(void)
278{
279    wrap_kill_temp();
280    while(wrap_count)
281	wrap_free_entry(wrap_list[--wrap_count]);
282}
283
284void
285wrap_kill_temp(void)
286{
287    WrapperEntry **temps=wrap_list+wrap_count;
288
289    while(wrap_tempcount)
290	wrap_free_entry(temps[--wrap_tempcount]);
291}
292
293void
294wrap_free_entry(WrapperEntry *e)
295{
296    wrap_free_entry_internal(e);
297    free(e);
298}
299
300void
301wrap_free_entry_internal(WrapperEntry *e)
302{
303    free (e->wildCard);
304    if (e->tocvsFilter)
305	free (e->tocvsFilter);
306    if (e->fromcvsFilter)
307	free (e->fromcvsFilter);
308    if (e->rcsOption)
309	free (e->rcsOption);
310}
311
312void
313wrap_restore_saved(void)
314{
315    if(!wrap_saved_list)
316	return;
317
318    wrap_kill();
319
320    free(wrap_list);
321
322    wrap_list=wrap_saved_list;
323    wrap_count=wrap_saved_count;
324    wrap_tempcount=wrap_saved_tempcount;
325
326    wrap_saved_list=NULL;
327    wrap_saved_count=0;
328    wrap_saved_tempcount=0;
329}
330
331void
332wrap_add (char *line, int isTemp)
333{
334    char *temp;
335    char ctemp;
336    WrapperEntry e;
337    char opt;
338
339    if (!line || line[0] == '#')
340	return;
341
342    memset (&e, 0, sizeof(e));
343
344	/* Search for the wild card */
345    while (*line && isspace ((unsigned char) *line))
346	++line;
347    for (temp = line;
348	 *line && !isspace ((unsigned char) *line);
349	 ++line)
350	;
351    if(temp==line)
352	return;
353
354    ctemp=*line;
355    *line='\0';
356
357    e.wildCard=xstrdup(temp);
358    *line=ctemp;
359
360    while(*line){
361	    /* Search for the option */
362	while(*line && *line!='-')
363	    ++line;
364	if(!*line)
365	    break;
366	++line;
367	if(!*line)
368	    break;
369	opt=*line;
370
371	    /* Search for the filter commandline */
372	for(++line;*line && *line!='\'';++line);
373	if(!*line)
374	    break;
375
376	for(temp=++line;*line && (*line!='\'' || line[-1]=='\\');++line)
377	    ;
378
379	/* This used to "break;" (ignore the option) if there was a
380	   single character between the single quotes (I'm guessing
381	   that was accidental).  Now it "break;"s if there are no
382	   characters.  I'm not sure either behavior is particularly
383	   necessary--the current options might not require ''
384	   arguments, but surely some future option legitimately
385	   might.  Also I'm not sure that ignoring the option is a
386	   swift way to handle syntax errors in general.  */
387	if (line==temp)
388	    break;
389
390	ctemp=*line;
391	*line='\0';
392	switch(opt){
393	case 'f':
394	    /* Before this is reenabled, need to address the problem in
395	       commit.c (see
396	       <http://ximbiot.com/cvs/cvshome/docs/infowrapper.html>).  */
397	    error (1, 0,
398		   "-t/-f wrappers not supported by this version of CVS");
399
400	    if(e.fromcvsFilter)
401		free(e.fromcvsFilter);
402	    /* FIXME: error message should say where the bad value
403	       came from.  */
404	    e.fromcvsFilter =
405	      expand_path (temp, current_parsed_root->directory, false,
406			   "<wrapper>", 0);
407            if (!e.fromcvsFilter)
408		error (1, 0, "Correct above errors first");
409	    break;
410	case 't':
411	    /* Before this is reenabled, need to address the problem in
412	       commit.c (see
413	       <http://ximbiot.com/cvs/cvshome/docs/infowrapper.html>).  */
414	    error (1, 0,
415		   "-t/-f wrappers not supported by this version of CVS");
416
417	    if(e.tocvsFilter)
418		free(e.tocvsFilter);
419	    /* FIXME: error message should say where the bad value
420	       came from.  */
421	    e.tocvsFilter = expand_path (temp, current_parsed_root->directory,
422					 false, "<wrapper>", 0);
423            if (!e.tocvsFilter)
424		error (1, 0, "Correct above errors first");
425	    break;
426	case 'm':
427	    if(*temp=='C' || *temp=='c')
428		e.mergeMethod=WRAP_COPY;
429	    else
430		e.mergeMethod=WRAP_MERGE;
431	    break;
432	case 'k':
433	    if (e.rcsOption)
434		free (e.rcsOption);
435	    e.rcsOption = strcmp (temp, "kv") ? xstrdup (temp) : NULL;
436	    break;
437	default:
438	    break;
439	}
440	*line=ctemp;
441	if(!*line)break;
442	++line;
443    }
444
445    wrap_add_entry(&e, isTemp);
446}
447
448void
449wrap_add_entry (WrapperEntry *e, int temp)
450{
451    int x;
452    if (wrap_count + wrap_tempcount >= wrap_size)
453    {
454	wrap_size += WRAPPER_GROW;
455	wrap_list = xnrealloc (wrap_list, wrap_size, sizeof (WrapperEntry *));
456    }
457
458    if (!temp && wrap_tempcount)
459    {
460	for (x = wrap_count + wrap_tempcount - 1; x >= wrap_count; --x)
461	    wrap_list[x + 1] = wrap_list[x];
462    }
463
464    x = (temp ? wrap_count + (wrap_tempcount++) : (wrap_count++));
465    wrap_list[x] = xmalloc (sizeof (WrapperEntry));
466    *wrap_list[x] = *e;
467}
468
469/* Return 1 if the given filename is a wrapper filename */
470int
471wrap_name_has (const char *name, WrapMergeHas has)
472{
473    int x,count=wrap_count+wrap_tempcount;
474    char *temp;
475
476    for(x=0;x<count;++x)
477	if (CVS_FNMATCH (wrap_list[x]->wildCard, name, 0) == 0){
478	    switch(has){
479	    case WRAP_TOCVS:
480		temp=wrap_list[x]->tocvsFilter;
481		break;
482	    case WRAP_FROMCVS:
483		temp=wrap_list[x]->fromcvsFilter;
484		break;
485	    case WRAP_RCSOPTION:
486		temp = wrap_list[x]->rcsOption;
487		break;
488	    default:
489	        abort ();
490	    }
491	    if(temp==NULL)
492		return (0);
493	    else
494		return (1);
495	}
496    return (0);
497}
498
499static WrapperEntry *wrap_matching_entry (const char *);
500
501static WrapperEntry *
502wrap_matching_entry (const char *name)
503{
504    int x,count=wrap_count+wrap_tempcount;
505
506    for(x=0;x<count;++x)
507	if (CVS_FNMATCH (wrap_list[x]->wildCard, name, 0) == 0)
508	    return wrap_list[x];
509    return NULL;
510}
511
512/* Return the RCS options for FILENAME in a newly malloc'd string.  If
513   ASFLAG, then include "-k" at the beginning (e.g. "-kb"), otherwise
514   just give the option itself (e.g. "b").  */
515char *
516wrap_rcsoption (const char *filename, int asflag)
517{
518    WrapperEntry *e = wrap_matching_entry (filename);
519
520    if (e == NULL || e->rcsOption == NULL || (*e->rcsOption == '\0'))
521	return NULL;
522
523    return Xasprintf ("%s%s", asflag ? "-k" : "", e->rcsOption);
524}
525
526char *
527wrap_tocvs_process_file(const char *fileName)
528{
529    WrapperEntry *e=wrap_matching_entry(fileName);
530    static char *buf = NULL;
531    char *args;
532
533    if(e==NULL || e->tocvsFilter==NULL)
534	return NULL;
535
536    if (buf != NULL)
537	free (buf);
538    buf = cvs_temp_name ();
539
540    wrap_clean_fmt_str (e->tocvsFilter, 2);
541    args = Xasprintf (e->tocvsFilter, fileName, buf);
542    run_setup (args);
543    run_exec (RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL | RUN_REALLY );
544    free (args);
545
546    return buf;
547}
548
549int
550wrap_merge_is_copy (const char *fileName)
551{
552    WrapperEntry *e=wrap_matching_entry(fileName);
553    if(e==NULL || e->mergeMethod==WRAP_MERGE)
554	return 0;
555
556    return 1;
557}
558
559void
560wrap_fromcvs_process_file(const char *fileName)
561{
562    char *args;
563    WrapperEntry *e = wrap_matching_entry(fileName);
564
565    if (e != NULL && e->fromcvsFilter != NULL)
566    {
567	wrap_clean_fmt_str (e->fromcvsFilter, 1);
568	args = Xasprintf (e->fromcvsFilter, fileName);
569	run_setup (args);
570	run_exec (RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL);
571	free (args);
572    }
573    return;
574}
575