1 /*
2  * Copyright (C) 1986-2005 The Free Software Foundation, Inc.
3  *
4  * Portions Copyright (C) 1998-2005 Derek Price, Ximbiot <http://ximbiot.com>,
5  *                                  and others.
6  *
7  * Portions Copyright (C) 1992, Brian Berliner and Jeff Polk
8  * Portions Copyright (C) 1989-1992, Brian Berliner
9  *
10  * You may distribute under the terms of the GNU General Public License as
11  * specified in the README file that comes with the CVS source distribution.
12  *
13  * The functions in this file provide an interface for performing
14  * operations directly on RCS files.
15  */
16 #include <sys/cdefs.h>
17 __RCSID("$NetBSD: rcscmds.c,v 1.2 2016/05/17 14:00:09 christos Exp $");
18 
19 #include "cvs.h"
20 #include <stdio.h>
21 #include "diffrun.h"
22 #include "quotearg.h"
23 
24 /* This file, rcs.h, and rcs.c, together sometimes known as the "RCS
25    library", are intended to define our interface to RCS files.
26 
27    Whether there will also be a version of RCS which uses this
28    library, or whether the library will be packaged for uses beyond
29    CVS or RCS (many people would like such a thing) is an open
30    question.  Some considerations:
31 
32    1.  An RCS library for CVS must have the capabilities of the
33    existing CVS code which accesses RCS files.  In particular, simple
34    approaches will often be slow.
35 
36    2.  An RCS library should not use code from the current RCS
37    (5.7 and its ancestors).  The code has many problems.  Too few
38    comments, too many layers of abstraction, too many global variables
39    (the correct number for a library is zero), too much intricately
40    interwoven functionality, and too many clever hacks.  Paul Eggert,
41    the current RCS maintainer, agrees.
42 
43    3.  More work needs to be done in terms of separating out the RCS
44    library from the rest of CVS (for example, cvs_output should be
45    replaced by a callback, and the declarations should be centralized
46    into rcs.h, and probably other such cleanups).
47 
48    4.  To be useful for RCS and perhaps for other uses, the library
49    may need features beyond those needed by CVS.
50 
51    5.  Any changes to the RCS file format *must* be compatible.  Many,
52    many tools (not just CVS and RCS) can at least import this format.
53    RCS and CVS must preserve the current ability to import/export it
54    (preferably improved--magic branches are currently a roadblock).
55    See doc/RCSFILES in the CVS distribution for documentation of this
56    file format.
57 
58    On a related note, see the comments at diff_exec, later in this file,
59    for more on the diff library.  */
60 
61 static void RCS_output_diff_options (int, char * const *, const char *,
62                                              const char *, const char *);
63 
64 
65 /* Stuff to deal with passing arguments the way libdiff.a wants to deal
66    with them.  This is a crufty interface; there is no good reason for it
67    to resemble a command line rather than something closer to "struct
68    log_data" in log.c.  */
69 
70 /* First call call_diff_setup to setup any initial arguments.  The
71    argument will be parsed into whitespace separated words and added
72    to the global call_diff_argv list.
73 
74    Then, optionally, call call_diff_add_arg for each additional argument
75    that you'd like to pass to the diff library.
76 
77    Finally, call call_diff or call_diff3 to produce the diffs.  */
78 
79 static char **call_diff_argv;
80 static int call_diff_argc;
81 static size_t call_diff_arg_allocated;
82 
83 static int call_diff (const char *out);
84 static int call_diff3 (char *out);
85 
86 static void call_diff_write_output (const char *, size_t);
87 static void call_diff_flush_output (void);
88 static void call_diff_write_stdout (const char *);
89 static void call_diff_error (const char *, const char *, const char *);
90 
91 
92 
93 /* VARARGS */
94 static void
call_diff_add_arg(const char * s)95 call_diff_add_arg (const char *s)
96 {
97     TRACE (TRACE_DATA, "call_diff_add_arg (%s)", s);
98     run_add_arg_p (&call_diff_argc, &call_diff_arg_allocated, &call_diff_argv,
99                        s);
100 }
101 
102 
103 
104 static void
call_diff_setup(const char * prog,int argc,char * const * argv)105 call_diff_setup (const char *prog, int argc, char * const *argv)
106 {
107     int i;
108 
109     /* clean out any malloc'ed values from call_diff_argv */
110     run_arg_free_p (call_diff_argc, call_diff_argv);
111     call_diff_argc = 0;
112 
113     /* put each word into call_diff_argv, allocating it as we go */
114     call_diff_add_arg (prog);
115     for (i = 0; i < argc; i++)
116           call_diff_add_arg (argv[i]);
117 }
118 
119 
120 
121 /* Callback function for the diff library to write data to the output
122    file.  This is used when we are producing output to stdout.  */
123 
124 static void
call_diff_write_output(const char * text,size_t len)125 call_diff_write_output (const char *text, size_t len)
126 {
127     if (len > 0)
128           cvs_output (text, len);
129 }
130 
131 /* Call back function for the diff library to flush the output file.
132    This is used when we are producing output to stdout.  */
133 
134 static void
call_diff_flush_output(void)135 call_diff_flush_output (void)
136 {
137     cvs_flushout ();
138 }
139 
140 /* Call back function for the diff library to write to stdout.  */
141 
142 static void
call_diff_write_stdout(const char * text)143 call_diff_write_stdout (const char *text)
144 {
145     cvs_output (text, 0);
146 }
147 
148 /* Call back function for the diff library to write to stderr.  */
149 
150 static void
call_diff_error(const char * format,const char * a1,const char * a2)151 call_diff_error (const char *format, const char *a1, const char *a2)
152 {
153     /* FIXME: Should we somehow indicate that this error is coming from
154        the diff library?  */
155     error (0, 0, format, a1, a2);
156 }
157 
158 /* This set of callback functions is used if we are sending the diff
159    to stdout.  */
160 
161 static struct diff_callbacks call_diff_stdout_callbacks =
162 {
163     call_diff_write_output,
164     call_diff_flush_output,
165     call_diff_write_stdout,
166     call_diff_error
167 };
168 
169 /* This set of callback functions is used if we are sending the diff
170    to a file.  */
171 
172 static struct diff_callbacks call_diff_file_callbacks =
173 {
174     NULL,
175     NULL,
176     call_diff_write_stdout,
177     call_diff_error
178 };
179 
180 
181 
182 static int
call_diff(const char * out)183 call_diff (const char *out)
184 {
185     call_diff_add_arg (NULL);
186 
187     if (out == RUN_TTY)
188           return diff_run( call_diff_argc, call_diff_argv, NULL,
189                                &call_diff_stdout_callbacks );
190     else
191           return diff_run( call_diff_argc, call_diff_argv, out,
192                                &call_diff_file_callbacks );
193 }
194 
195 
196 
197 static int
call_diff3(char * out)198 call_diff3 (char *out)
199 {
200     if (out == RUN_TTY)
201           return diff3_run (call_diff_argc, call_diff_argv, NULL,
202                                 &call_diff_stdout_callbacks);
203     else
204           return diff3_run (call_diff_argc, call_diff_argv, out,
205                                 &call_diff_file_callbacks);
206 }
207 
208 
209 
210 /* Merge revisions REV1 and REV2. */
211 
212 int
RCS_merge(RCSNode * rcs,const char * path,const char * workfile,const char * options,const char * rev1,const char * rev2)213 RCS_merge (RCSNode *rcs, const char *path, const char *workfile,
214            const char *options, const char *rev1, const char *rev2)
215 {
216     char *xrev1, *xrev2;
217     char *tmp1, *tmp2;
218     char *diffout = NULL;
219     int retval;
220 
221     if (options != NULL && options[0] != '\0')
222       assert (options[0] == '-' && options[1] == 'k');
223 
224     cvs_output ("RCS file: ", 0);
225     cvs_output (rcs->print_path, 0);
226     cvs_output ("\n", 1);
227 
228     /* Calculate numeric revision numbers from rev1 and rev2 (may be
229        symbolic).
230        FIXME - No they can't.  Both calls to RCS_merge are passing in
231        numeric revisions.  */
232     xrev1 = RCS_gettag (rcs, rev1, 0, NULL);
233     xrev2 = RCS_gettag (rcs, rev2, 0, NULL);
234     assert (xrev1 && xrev2);
235 
236     /* Check out chosen revisions.  The error message when RCS_checkout
237        fails is not very informative -- it is taken verbatim from RCS 5.7,
238        and relies on RCS_checkout saying something intelligent upon failure. */
239     cvs_output ("retrieving revision ", 0);
240     cvs_output (xrev1, 0);
241     cvs_output ("\n", 1);
242 
243     tmp1 = cvs_temp_name();
244     if (RCS_checkout (rcs, NULL, xrev1, rev1, options, tmp1, NULL, NULL))
245     {
246           cvs_outerr ("rcsmerge: co failed\n", 0);
247           exit (EXIT_FAILURE);
248     }
249 
250     cvs_output ("retrieving revision ", 0);
251     cvs_output (xrev2, 0);
252     cvs_output ("\n", 1);
253 
254     tmp2 = cvs_temp_name();
255     if (RCS_checkout (rcs, NULL, xrev2, rev2, options, tmp2, NULL, NULL))
256     {
257           cvs_outerr ("rcsmerge: co failed\n", 0);
258           exit (EXIT_FAILURE);
259     }
260 
261     /* Merge changes. */
262     cvs_output ("Merging differences between ", 0);
263     cvs_output (xrev1, 0);
264     cvs_output (" and ", 0);
265     cvs_output (xrev2, 0);
266     cvs_output (" into ", 0);
267     cvs_output (workfile, 0);
268     cvs_output ("\n", 1);
269 
270     /* Remember that the first word in the `call_diff_setup' string is used now
271        only for diagnostic messages -- CVS no longer forks to run diff3. */
272     diffout = cvs_temp_name();
273     call_diff_setup ("diff3", 0, NULL);
274     call_diff_add_arg ("-E");
275     call_diff_add_arg ("-am");
276 
277     call_diff_add_arg ("-L");
278     call_diff_add_arg (workfile);
279     call_diff_add_arg ("-L");
280     call_diff_add_arg (xrev1);
281     call_diff_add_arg ("-L");
282     call_diff_add_arg (xrev2);
283 
284     call_diff_add_arg ("--");
285     call_diff_add_arg (workfile);
286     call_diff_add_arg (tmp1);
287     call_diff_add_arg (tmp2);
288 
289     retval = call_diff3 (diffout);
290 
291     if (retval == 1)
292           cvs_outerr ("rcsmerge: warning: conflicts during merge\n", 0);
293     else if (retval == 2)
294           exit (EXIT_FAILURE);
295 
296     if (diffout)
297           copy_file (diffout, workfile);
298 
299     /* Clean up. */
300     {
301           int save_noexec = noexec;
302           noexec = 0;
303           if (unlink_file (tmp1) < 0)
304           {
305               if (!existence_error (errno))
306                     error (0, errno, "cannot remove temp file %s", tmp1);
307           }
308           free (tmp1);
309           if (unlink_file (tmp2) < 0)
310           {
311               if (!existence_error (errno))
312                     error (0, errno, "cannot remove temp file %s", tmp2);
313           }
314           free (tmp2);
315           if (diffout)
316           {
317               if (unlink_file (diffout) < 0)
318               {
319                     if (!existence_error (errno))
320                         error (0, errno, "cannot remove temp file %s", diffout);
321               }
322               free (diffout);
323           }
324           free (xrev1);
325           free (xrev2);
326           noexec = save_noexec;
327     }
328 
329     return retval;
330 }
331 
332 /* Diff revisions and/or files.  OPTS controls the format of the diff
333    (it contains options such as "-w -c", &c), or "" for the default.
334    OPTIONS controls keyword expansion, as a string starting with "-k",
335    or "" to use the default.  REV1 is the first revision to compare
336    against; it must be non-NULL.  If REV2 is non-NULL, compare REV1
337    and REV2; if REV2 is NULL compare REV1 with the file in the working
338    directory, whose name is WORKFILE.  LABEL1 and LABEL2 are default
339    file labels, and (if non-NULL) should be added as -L options
340    to diff.  Output goes to stdout.
341 
342    Return value is 0 for success, -1 for a failure which set errno,
343    or positive for a failure which printed a message on stderr.
344 
345    This used to exec rcsdiff, but now calls RCS_checkout and diff_exec.
346 
347    An issue is what timezone is used for the dates which appear in the
348    diff output.  rcsdiff uses the -z flag, which is not presently
349    processed by CVS diff, but I'm not sure exactly how hard to worry
350    about this--any such features are undocumented in the context of
351    CVS, and I'm not sure how important to users.  */
352 int
RCS_exec_rcsdiff(RCSNode * rcsfile,int diff_argc,char * const * diff_argv,const char * options,const char * rev1,const char * rev1_cache,const char * rev2,const char * label1,const char * label2,const char * workfile)353 RCS_exec_rcsdiff (RCSNode *rcsfile, int diff_argc,
354                       char * const *diff_argv, const char *options,
355                   const char *rev1, const char *rev1_cache, const char *rev2,
356                   const char *label1, const char *label2, const char *workfile)
357 {
358     char *tmpfile1 = NULL;
359     char *tmpfile2 = NULL;
360     const char *use_file1, *use_file2;
361     int status, retval;
362 
363 
364     cvs_output ("\
365 ===================================================================\n\
366 RCS file: ", 0);
367     cvs_output (rcsfile->print_path, 0);
368     cvs_output ("\n", 1);
369 
370     /* Historically, `cvs diff' has expanded the $Name keyword to the
371        empty string when checking out revisions.  This is an accident,
372        but no one has considered the issue thoroughly enough to determine
373        what the best behavior is.  Passing NULL for the `nametag' argument
374        preserves the existing behavior. */
375 
376     cvs_output ("retrieving revision ", 0);
377     cvs_output (rev1, 0);
378     cvs_output ("\n", 1);
379 
380     if (rev1_cache != NULL)
381           use_file1 = rev1_cache;
382     else
383     {
384           tmpfile1 = cvs_temp_name();
385           status = RCS_checkout (rcsfile, NULL, rev1, NULL, options, tmpfile1,
386                                  NULL, NULL);
387           if (status > 0)
388           {
389               retval = status;
390               goto error_return;
391           }
392           else if (status < 0)
393           {
394               error( 0, errno,
395                      "cannot check out revision %s of %s", rev1, rcsfile->path );
396               retval = 1;
397               goto error_return;
398           }
399           use_file1 = tmpfile1;
400     }
401 
402     if (rev2 == NULL)
403     {
404           assert (workfile != NULL);
405           use_file2 = workfile;
406     }
407     else
408     {
409           tmpfile2 = cvs_temp_name ();
410           cvs_output ("retrieving revision ", 0);
411           cvs_output (rev2, 0);
412           cvs_output ("\n", 1);
413           status = RCS_checkout (rcsfile, NULL, rev2, NULL, options,
414                                      tmpfile2, NULL, NULL);
415           if (status > 0)
416           {
417               retval = status;
418               goto error_return;
419           }
420           else if (status < 0)
421           {
422               error (0, errno,
423                        "cannot check out revision %s of %s", rev2, rcsfile->path);
424               return 1;
425           }
426           use_file2 = tmpfile2;
427     }
428 
429     RCS_output_diff_options (diff_argc, diff_argv, rev1, rev2, workfile);
430     status = diff_exec (use_file1, use_file2, label1, label2,
431                               diff_argc, diff_argv, RUN_TTY);
432     if (status >= 0)
433     {
434           retval = status;
435           goto error_return;
436     }
437     else if (status < 0)
438     {
439           error (0, errno,
440                  "cannot diff %s and %s", use_file1, use_file2);
441           retval = 1;
442           goto error_return;
443     }
444 
445  error_return:
446     {
447           /* Call CVS_UNLINK() below rather than unlink_file to avoid the check
448            * for noexec.
449            */
450           if( tmpfile1 != NULL )
451           {
452               if( CVS_UNLINK( tmpfile1 ) < 0 )
453               {
454                     if( !existence_error( errno ) )
455                         error( 0, errno, "cannot remove temp file %s", tmpfile1 );
456               }
457               free( tmpfile1 );
458           }
459           if( tmpfile2 != NULL )
460           {
461               if( CVS_UNLINK( tmpfile2 ) < 0 )
462               {
463                     if( !existence_error( errno ) )
464                         error( 0, errno, "cannot remove temp file %s", tmpfile2 );
465               }
466               free (tmpfile2);
467           }
468     }
469 
470     return retval;
471 }
472 
473 
474 
475 /* Show differences between two files.  This is the start of a diff library.
476 
477    Some issues:
478 
479    * Should option parsing be part of the library or the caller?  The
480    former allows the library to add options without changing the callers,
481    but it causes various problems.  One is that something like --brief really
482    wants special handling in CVS, and probably the caller should retain
483    some flexibility in this area.  Another is online help (the library could
484    have some feature for providing help, but how does that interact with
485    the help provided by the caller directly?).  Another is that as things
486    stand currently, there is no separate namespace for diff options versus
487    "cvs diff" options like -l (that is, if the library adds an option which
488    conflicts with a CVS option, it is trouble).
489 
490    * This isn't required for a first-cut diff library, but if there
491    would be a way for the caller to specify the timestamps that appear
492    in the diffs (rather than the library getting them from the files),
493    that would clean up the kludgy utime() calls in patch.c.
494 
495    Show differences between FILE1 and FILE2.  Either one can be
496    DEVNULL to indicate a nonexistent file (same as an empty file
497    currently, I suspect, but that may be an issue in and of itself).
498    OPTIONS is a list of diff options, or "" if none.  At a minimum,
499    CVS expects that -c (update.c, patch.c) and -n (update.c) will be
500    supported.  Other options, like -u, --speed-large-files, &c, will
501    be specified if the user specified them.
502 
503    OUT is a filename to send the diffs to, or RUN_TTY to send them to
504    stdout.  Error messages go to stderr.  Return value is 0 for
505    success, -1 for a failure which set errno, 1 for success (and some
506    differences were found), or >1 for a failure which printed a
507    message on stderr.  */
508 
509 int
diff_exec(const char * file1,const char * file2,const char * label1,const char * label2,int dargc,char * const * dargv,const char * out)510 diff_exec (const char *file1, const char *file2, const char *label1,
511            const char *label2, int dargc, char * const *dargv,
512              const char *out)
513 {
514     TRACE (TRACE_FUNCTION, "diff_exec (%s, %s, %s, %s, %s)",
515              file1, file2, label1, label2, out);
516 
517 #ifdef PRESERVE_PERMISSIONS_SUPPORT
518     /* If either file1 or file2 are special files, pretend they are
519        /dev/null.  Reason: suppose a file that represents a block
520        special device in one revision becomes a regular file.  CVS
521        must find the `difference' between these files, but a special
522        file contains no data useful for calculating this metric.  The
523        safe thing to do is to treat the special file as an empty file,
524        thus recording the regular file's full contents.  Doing so will
525        create extremely large deltas at the point of transition
526        between device files and regular files, but this is probably
527        very rare anyway.
528 
529        There may be ways around this, but I think they are fraught
530        with danger. -twp */
531 
532     if (preserve_perms &&
533           strcmp (file1, DEVNULL) != 0 &&
534           strcmp (file2, DEVNULL) != 0)
535     {
536           struct stat sb1, sb2;
537 
538           if (lstat (file1, &sb1) < 0)
539               error (1, errno, "cannot get file information for %s", file1);
540           if (lstat (file2, &sb2) < 0)
541               error (1, errno, "cannot get file information for %s", file2);
542 
543           if (!S_ISREG (sb1.st_mode) && !S_ISDIR (sb1.st_mode))
544               file1 = DEVNULL;
545           if (!S_ISREG (sb2.st_mode) && !S_ISDIR (sb2.st_mode))
546               file2 = DEVNULL;
547     }
548 #endif
549 
550     /* The first arg to call_diff_setup is used only for error reporting. */
551     call_diff_setup ("diff", dargc, dargv);
552     if (label1)
553           call_diff_add_arg (label1);
554     if (label2)
555           call_diff_add_arg (label2);
556     call_diff_add_arg ("--");
557     call_diff_add_arg (file1);
558     call_diff_add_arg (file2);
559 
560     return call_diff (out);
561 }
562 
563 /* Print the options passed to DIFF, in the format used by rcsdiff.
564    The rcsdiff code that produces this output is extremely hairy, and
565    it is not clear how rcsdiff decides which options to print and
566    which not to print.  The code below reproduces every rcsdiff run
567    that I have seen. */
568 
569 static void
RCS_output_diff_options(int diff_argc,char * const * diff_argv,const char * rev1,const char * rev2,const char * workfile)570 RCS_output_diff_options (int diff_argc, char * const *diff_argv,
571                                const char *rev1, const char *rev2,
572                          const char *workfile)
573 {
574     int i;
575 
576     cvs_output ("diff", 0);
577     for (i = 0; i < diff_argc; i++)
578     {
579         cvs_output (" ", 1);
580           cvs_output (quotearg_style (shell_quoting_style, diff_argv[i]), 0);
581     }
582     cvs_output (" -r", 3);
583     cvs_output (rev1, 0);
584 
585     if (rev2)
586     {
587           cvs_output (" -r", 3);
588           cvs_output (rev2, 0);
589     }
590     else
591     {
592           assert (workfile != NULL);
593           cvs_output (" ", 1);
594           cvs_output (workfile, 0);
595     }
596     cvs_output ("\n", 1);
597 }
598