xref: /dflybsd-src/contrib/cvs-1.12/src/rcs.c (revision 86d7f5d305c6adaa56ff4582ece9859d73106103)
1*86d7f5d3SJohn Marino /*
2*86d7f5d3SJohn Marino  * Copyright (C) 1986-2005 The Free Software Foundation, Inc.
3*86d7f5d3SJohn Marino  *
4*86d7f5d3SJohn Marino  * Portions Copyright (C) 1998-2005 Derek Price, Ximbiot <http://ximbiot.com>,
5*86d7f5d3SJohn Marino  *                                  and others.
6*86d7f5d3SJohn Marino  *
7*86d7f5d3SJohn Marino  * Portions Copyright (C) 1992, Brian Berliner and Jeff Polk
8*86d7f5d3SJohn Marino  * Portions Copyright (C) 1989-1992, Brian Berliner
9*86d7f5d3SJohn Marino  *
10*86d7f5d3SJohn Marino  * You may distribute under the terms of the GNU General Public License as
11*86d7f5d3SJohn Marino  * specified in the README file that comes with the CVS source distribution.
12*86d7f5d3SJohn Marino  *
13*86d7f5d3SJohn Marino  * The routines contained in this file do all the rcs file parsing and
14*86d7f5d3SJohn Marino  * manipulation
15*86d7f5d3SJohn Marino  */
16*86d7f5d3SJohn Marino 
17*86d7f5d3SJohn Marino #include "cvs.h"
18*86d7f5d3SJohn Marino #include "edit.h"
19*86d7f5d3SJohn Marino #include "hardlink.h"
20*86d7f5d3SJohn Marino 
21*86d7f5d3SJohn Marino /* These need to be source after cvs.h or HAVE_MMAP won't be set... */
22*86d7f5d3SJohn Marino #ifdef HAVE_MMAP
23*86d7f5d3SJohn Marino # include "getpagesize.h"
24*86d7f5d3SJohn Marino # include <sys/mman.h>
25*86d7f5d3SJohn Marino 
26*86d7f5d3SJohn Marino /* Define MAP_FILE when it isn't otherwise.  */
27*86d7f5d3SJohn Marino # ifndef MAP_FILE
28*86d7f5d3SJohn Marino #  define MAP_FILE 0
29*86d7f5d3SJohn Marino # endif
30*86d7f5d3SJohn Marino /* Define MAP_FAILED for old systems which neglect to.  */
31*86d7f5d3SJohn Marino # ifndef MAP_FAILED
32*86d7f5d3SJohn Marino #  define MAP_FAILED ((void *)-1)
33*86d7f5d3SJohn Marino # endif
34*86d7f5d3SJohn Marino #endif
35*86d7f5d3SJohn Marino 
36*86d7f5d3SJohn Marino /* The RCS -k options, and a set of enums that must match the array.
37*86d7f5d3SJohn Marino    These come first so that we can use enum kflag in function
38*86d7f5d3SJohn Marino    prototypes.  */
39*86d7f5d3SJohn Marino static const char *const kflags[] =
40*86d7f5d3SJohn Marino   {"kv", "kvl", "k", "v", "o", "b", NULL};
41*86d7f5d3SJohn Marino enum kflag { KFLAG_KV = 0, KFLAG_KVL, KFLAG_K, KFLAG_V, KFLAG_O, KFLAG_B };
42*86d7f5d3SJohn Marino 
43*86d7f5d3SJohn Marino /* A structure we use to buffer the contents of an RCS file.  The
44*86d7f5d3SJohn Marino    various fields are only referenced directly by the rcsbuf_*
45*86d7f5d3SJohn Marino    functions.  We declare the struct here so that we can allocate it
46*86d7f5d3SJohn Marino    on the stack, rather than in memory.  */
47*86d7f5d3SJohn Marino 
48*86d7f5d3SJohn Marino struct rcsbuffer
49*86d7f5d3SJohn Marino {
50*86d7f5d3SJohn Marino     /* Points to the current position in the buffer.  */
51*86d7f5d3SJohn Marino     char *ptr;
52*86d7f5d3SJohn Marino     /* Points just after the last valid character in the buffer.  */
53*86d7f5d3SJohn Marino     char *ptrend;
54*86d7f5d3SJohn Marino     /* The file.  */
55*86d7f5d3SJohn Marino     FILE *fp;
56*86d7f5d3SJohn Marino     /* The name of the file, used for error messages.  */
57*86d7f5d3SJohn Marino     const char *filename;
58*86d7f5d3SJohn Marino     /* The starting file position of the data in the buffer.  */
59*86d7f5d3SJohn Marino     unsigned long pos;
60*86d7f5d3SJohn Marino     /* The length of the value.  */
61*86d7f5d3SJohn Marino     size_t vlen;
62*86d7f5d3SJohn Marino     /* Whether the value contains an '@' string.  If so, we can not
63*86d7f5d3SJohn Marino        compress whitespace characters.  */
64*86d7f5d3SJohn Marino     int at_string;
65*86d7f5d3SJohn Marino     /* The number of embedded '@' characters in an '@' string.  If
66*86d7f5d3SJohn Marino        this is non-zero, we must search the string for pairs of '@'
67*86d7f5d3SJohn Marino        and convert them to a single '@'.  */
68*86d7f5d3SJohn Marino     int embedded_at;
69*86d7f5d3SJohn Marino };
70*86d7f5d3SJohn Marino 
71*86d7f5d3SJohn Marino static RCSNode *RCS_parsercsfile_i (FILE * fp, const char *rcsfile);
72*86d7f5d3SJohn Marino static char *RCS_getdatebranch (RCSNode * rcs, const char *date,
73*86d7f5d3SJohn Marino                                 const char *branch);
74*86d7f5d3SJohn Marino static void rcsbuf_open (struct rcsbuffer *, FILE *fp,
75*86d7f5d3SJohn Marino                          const char *filename, unsigned long pos);
76*86d7f5d3SJohn Marino static void rcsbuf_close (struct rcsbuffer *);
77*86d7f5d3SJohn Marino static int rcsbuf_getkey (struct rcsbuffer *, char **keyp, char **valp);
78*86d7f5d3SJohn Marino static int rcsbuf_getrevnum (struct rcsbuffer *, char **revp);
79*86d7f5d3SJohn Marino static char *rcsbuf_fill (struct rcsbuffer *, char *ptr, char **keyp,
80*86d7f5d3SJohn Marino                           char **valp);
81*86d7f5d3SJohn Marino static int rcsbuf_valcmp (struct rcsbuffer *);
82*86d7f5d3SJohn Marino static char *rcsbuf_valcopy (struct rcsbuffer *, char *val, int polish,
83*86d7f5d3SJohn Marino                              size_t *lenp);
84*86d7f5d3SJohn Marino static void rcsbuf_valpolish (struct rcsbuffer *, char *val, int polish,
85*86d7f5d3SJohn Marino                               size_t *lenp);
86*86d7f5d3SJohn Marino static void rcsbuf_valpolish_internal (struct rcsbuffer *, char *to,
87*86d7f5d3SJohn Marino                                        const char *from, size_t *lenp);
88*86d7f5d3SJohn Marino static off_t rcsbuf_ftello (struct rcsbuffer *);
89*86d7f5d3SJohn Marino static void rcsbuf_get_buffered (struct rcsbuffer *, char **datap,
90*86d7f5d3SJohn Marino 				 size_t *lenp);
91*86d7f5d3SJohn Marino static void rcsbuf_cache (RCSNode *, struct rcsbuffer *);
92*86d7f5d3SJohn Marino static void rcsbuf_cache_close (void);
93*86d7f5d3SJohn Marino static void rcsbuf_cache_open (RCSNode *, off_t, FILE **, struct rcsbuffer *);
94*86d7f5d3SJohn Marino static int checkmagic_proc (Node *p, void *closure);
95*86d7f5d3SJohn Marino static void do_branches (List * list, char *val);
96*86d7f5d3SJohn Marino static void do_symbols (List * list, char *val);
97*86d7f5d3SJohn Marino static void do_locks (List * list, char *val);
98*86d7f5d3SJohn Marino static void free_rcsnode_contents (RCSNode *);
99*86d7f5d3SJohn Marino static void free_rcsvers_contents (RCSVers *);
100*86d7f5d3SJohn Marino static void rcsvers_delproc (Node * p);
101*86d7f5d3SJohn Marino static char *translate_symtag (RCSNode *, const char *);
102*86d7f5d3SJohn Marino static char *RCS_addbranch (RCSNode *, const char *);
103*86d7f5d3SJohn Marino static char *truncate_revnum_in_place (char *);
104*86d7f5d3SJohn Marino static char *truncate_revnum (const char *);
105*86d7f5d3SJohn Marino static char *printable_date (const char *);
106*86d7f5d3SJohn Marino static char *escape_keyword_value (const char *, int *);
107*86d7f5d3SJohn Marino static void expand_keywords (RCSNode *, RCSVers *, const char *,
108*86d7f5d3SJohn Marino                              const char *, size_t, enum kflag, char *,
109*86d7f5d3SJohn Marino                              size_t, char **, size_t *);
110*86d7f5d3SJohn Marino static void cmp_file_buffer (void *, const char *, size_t);
111*86d7f5d3SJohn Marino 
112*86d7f5d3SJohn Marino /* Routines for reading, parsing and writing RCS files. */
113*86d7f5d3SJohn Marino static RCSVers *getdelta (struct rcsbuffer *, char *, char **, char **);
114*86d7f5d3SJohn Marino static Deltatext *RCS_getdeltatext (RCSNode *, FILE *, struct rcsbuffer *);
115*86d7f5d3SJohn Marino static void freedeltatext (Deltatext *);
116*86d7f5d3SJohn Marino 
117*86d7f5d3SJohn Marino static void RCS_putadmin (RCSNode *, FILE *);
118*86d7f5d3SJohn Marino static void RCS_putdtree (RCSNode *, char *, FILE *);
119*86d7f5d3SJohn Marino static void RCS_putdesc (RCSNode *, FILE *);
120*86d7f5d3SJohn Marino static void putdelta (RCSVers *, FILE *);
121*86d7f5d3SJohn Marino static int putrcsfield_proc (Node *, void *);
122*86d7f5d3SJohn Marino static int putsymbol_proc (Node *, void *);
123*86d7f5d3SJohn Marino static void RCS_copydeltas (RCSNode *, FILE *, struct rcsbuffer *, FILE *,
124*86d7f5d3SJohn Marino 			    Deltatext *, char *);
125*86d7f5d3SJohn Marino static int count_delta_actions (Node *, void *);
126*86d7f5d3SJohn Marino static void putdeltatext (FILE *, Deltatext *);
127*86d7f5d3SJohn Marino 
128*86d7f5d3SJohn Marino static FILE *rcs_internal_lockfile (char *);
129*86d7f5d3SJohn Marino static void rcs_internal_unlockfile (FILE *, char *);
130*86d7f5d3SJohn Marino static char *rcs_lockfilename (const char *);
131*86d7f5d3SJohn Marino 
132*86d7f5d3SJohn Marino /* The RCS file reading functions are called a lot, and they do some
133*86d7f5d3SJohn Marino    string comparisons.  This macro speeds things up a bit by skipping
134*86d7f5d3SJohn Marino    the function call when the first characters are different.  It
135*86d7f5d3SJohn Marino    evaluates its arguments multiple times.  */
136*86d7f5d3SJohn Marino #define STREQ(a, b) (*(char *)(a) == *(char *)(b) && strcmp ((a), (b)) == 0)
137*86d7f5d3SJohn Marino 
138*86d7f5d3SJohn Marino static char * getfullCVSname (char *, char **);
139*86d7f5d3SJohn Marino 
140*86d7f5d3SJohn Marino /*
141*86d7f5d3SJohn Marino  * We don't want to use isspace() from the C library because:
142*86d7f5d3SJohn Marino  *
143*86d7f5d3SJohn Marino  * 1. The definition of "whitespace" in RCS files includes ASCII
144*86d7f5d3SJohn Marino  *    backspace, but the C locale doesn't.
145*86d7f5d3SJohn Marino  * 2. isspace is an very expensive function call in some implementations
146*86d7f5d3SJohn Marino  *    due to the addition of wide character support.
147*86d7f5d3SJohn Marino  */
148*86d7f5d3SJohn Marino static const char spacetab[] = {
149*86d7f5d3SJohn Marino         0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0,	/* 0x00 - 0x0f */
150*86d7f5d3SJohn Marino         0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x10 - 0x1f */
151*86d7f5d3SJohn Marino         1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x20 - 0x2f */
152*86d7f5d3SJohn Marino         0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x30 - 0x3f */
153*86d7f5d3SJohn Marino         0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x40 - 0x4f */
154*86d7f5d3SJohn Marino         0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x50 - 0x5f */
155*86d7f5d3SJohn Marino         0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x60 - 0x8f */
156*86d7f5d3SJohn Marino         0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x70 - 0x7f */
157*86d7f5d3SJohn Marino         0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x80 - 0x8f */
158*86d7f5d3SJohn Marino         0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x90 - 0x9f */
159*86d7f5d3SJohn Marino         0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xa0 - 0xaf */
160*86d7f5d3SJohn Marino         0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xb0 - 0xbf */
161*86d7f5d3SJohn Marino         0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xc0 - 0xcf */
162*86d7f5d3SJohn Marino         0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xd0 - 0xdf */
163*86d7f5d3SJohn Marino         0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xe0 - 0xef */
164*86d7f5d3SJohn Marino         0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0  /* 0xf0 - 0xff */
165*86d7f5d3SJohn Marino };
166*86d7f5d3SJohn Marino 
167*86d7f5d3SJohn Marino #define whitespace(c)	(spacetab[(unsigned char)c] != 0)
168*86d7f5d3SJohn Marino 
169*86d7f5d3SJohn Marino static char *rcs_lockfile = NULL;
170*86d7f5d3SJohn Marino static int rcs_lockfd = -1;
171*86d7f5d3SJohn Marino 
172*86d7f5d3SJohn Marino 
173*86d7f5d3SJohn Marino 
174*86d7f5d3SJohn Marino /*
175*86d7f5d3SJohn Marino  * char *
176*86d7f5d3SJohn Marino  * locate_rcs ( const char* file, const char *repository , int *inattic )
177*86d7f5d3SJohn Marino  *
178*86d7f5d3SJohn Marino  * Find an RCS file in the repository, case insensitively when the cased name
179*86d7f5d3SJohn Marino  * doesn't exist, we are running as the server, and a client has asked us to
180*86d7f5d3SJohn Marino  * ignore case.
181*86d7f5d3SJohn Marino  *
182*86d7f5d3SJohn Marino  * Most parts of CVS will want to rely instead on RCS_parse which calls this
183*86d7f5d3SJohn Marino  * function and is called by recurse.c which then puts the result in useful
184*86d7f5d3SJohn Marino  * places like the rcs field of struct file_info.
185*86d7f5d3SJohn Marino  *
186*86d7f5d3SJohn Marino  * INPUTS
187*86d7f5d3SJohn Marino  *
188*86d7f5d3SJohn Marino  *  repository		the repository (including the directory)
189*86d7f5d3SJohn Marino  *  file		the filename within that directory (without RCSEXT).
190*86d7f5d3SJohn Marino  *  inattic		NULL or a pointer to the output boolean
191*86d7f5d3SJohn Marino  *
192*86d7f5d3SJohn Marino  * OUTPUTS
193*86d7f5d3SJohn Marino  *
194*86d7f5d3SJohn Marino  *  inattic		If this input was non-null, the destination will be
195*86d7f5d3SJohn Marino  *  			set to true if the file was found in the attic or
196*86d7f5d3SJohn Marino  *  			false if not.  If no RCS file is found, this value
197*86d7f5d3SJohn Marino  *  			is undefined.
198*86d7f5d3SJohn Marino  *
199*86d7f5d3SJohn Marino  * RETURNS
200*86d7f5d3SJohn Marino  *
201*86d7f5d3SJohn Marino  *  a newly-malloc'd array containing the absolute pathname of the RCS
202*86d7f5d3SJohn Marino  *  file that was found or NULL when none was found.
203*86d7f5d3SJohn Marino  *
204*86d7f5d3SJohn Marino  * ERRORS
205*86d7f5d3SJohn Marino  *
206*86d7f5d3SJohn Marino  *  errno can be set by the return value of the final call to
207*86d7f5d3SJohn Marino  *  locate_file_in_dir().  This should resolve to the system's existence error
208*86d7f5d3SJohn Marino  *  value (sometime ENOENT) if the Attic directory did not exist and ENOENT if
209*86d7f5d3SJohn Marino  *  the Attic was found but no matching files were found in the Attic or its
210*86d7f5d3SJohn Marino  *  parent.
211*86d7f5d3SJohn Marino  */
212*86d7f5d3SJohn Marino static char *
locate_rcs(const char * repository,const char * file,int * inattic)213*86d7f5d3SJohn Marino locate_rcs (const char *repository, const char *file, int *inattic)
214*86d7f5d3SJohn Marino {
215*86d7f5d3SJohn Marino     char *retval;
216*86d7f5d3SJohn Marino 
217*86d7f5d3SJohn Marino     /* First, try to find the file as cased. */
218*86d7f5d3SJohn Marino     retval = xmalloc (strlen (repository)
219*86d7f5d3SJohn Marino                       + sizeof (CVSATTIC)
220*86d7f5d3SJohn Marino                       + strlen (file)
221*86d7f5d3SJohn Marino                       + sizeof (RCSEXT)
222*86d7f5d3SJohn Marino                       + 3);
223*86d7f5d3SJohn Marino     sprintf (retval, "%s/%s%s", repository, file, RCSEXT);
224*86d7f5d3SJohn Marino     if (isreadable (retval))
225*86d7f5d3SJohn Marino     {
226*86d7f5d3SJohn Marino 	if (inattic)
227*86d7f5d3SJohn Marino 	    *inattic = 0;
228*86d7f5d3SJohn Marino 	return retval;
229*86d7f5d3SJohn Marino     }
230*86d7f5d3SJohn Marino     sprintf (retval, "%s/%s/%s%s", repository, CVSATTIC, file, RCSEXT);
231*86d7f5d3SJohn Marino     if (isreadable (retval))
232*86d7f5d3SJohn Marino     {
233*86d7f5d3SJohn Marino 	if (inattic)
234*86d7f5d3SJohn Marino 	    *inattic = 1;
235*86d7f5d3SJohn Marino 	return retval;
236*86d7f5d3SJohn Marino     }
237*86d7f5d3SJohn Marino     free (retval);
238*86d7f5d3SJohn Marino 
239*86d7f5d3SJohn Marino     return NULL;
240*86d7f5d3SJohn Marino }
241*86d7f5d3SJohn Marino 
242*86d7f5d3SJohn Marino 
243*86d7f5d3SJohn Marino 
244*86d7f5d3SJohn Marino /* A few generic thoughts on error handling, in particular the
245*86d7f5d3SJohn Marino    printing of unexpected characters that we find in the RCS file
246*86d7f5d3SJohn Marino    (that is, why we use '\x%x' rather than %c or some such).
247*86d7f5d3SJohn Marino 
248*86d7f5d3SJohn Marino    * Avoiding %c means we don't have to worry about what is printable
249*86d7f5d3SJohn Marino    and other such stuff.  In error handling, often better to keep it
250*86d7f5d3SJohn Marino    simple.
251*86d7f5d3SJohn Marino 
252*86d7f5d3SJohn Marino    * Hex rather than decimal or octal because character set standards
253*86d7f5d3SJohn Marino    tend to use hex.
254*86d7f5d3SJohn Marino 
255*86d7f5d3SJohn Marino    * Saying "character 0x%x" might make it sound like we are printing
256*86d7f5d3SJohn Marino    a file offset.  So we use '\x%x'.
257*86d7f5d3SJohn Marino 
258*86d7f5d3SJohn Marino    * Would be nice to print the offset within the file, but I can
259*86d7f5d3SJohn Marino    imagine various portability hassles (in particular, whether
260*86d7f5d3SJohn Marino    unsigned long is always big enough to hold file offsets).  */
261*86d7f5d3SJohn Marino 
262*86d7f5d3SJohn Marino /* Parse an rcsfile given a user file name and a repository.  If there is
263*86d7f5d3SJohn Marino    an error, we print an error message and return NULL.  If the file
264*86d7f5d3SJohn Marino    does not exist, we return NULL without printing anything (I'm not
265*86d7f5d3SJohn Marino    sure this allows the caller to do anything reasonable, but it is
266*86d7f5d3SJohn Marino    the current behavior).  */
267*86d7f5d3SJohn Marino RCSNode *
RCS_parse(const char * file,const char * repos)268*86d7f5d3SJohn Marino RCS_parse (const char *file, const char *repos)
269*86d7f5d3SJohn Marino {
270*86d7f5d3SJohn Marino     RCSNode *rcs;
271*86d7f5d3SJohn Marino     FILE *fp;
272*86d7f5d3SJohn Marino     RCSNode *retval = NULL;
273*86d7f5d3SJohn Marino     char *rcsfile;
274*86d7f5d3SJohn Marino     int inattic;
275*86d7f5d3SJohn Marino 
276*86d7f5d3SJohn Marino     /* We're creating a new RCSNode, so there is no hope of finding it
277*86d7f5d3SJohn Marino        in the cache.  */
278*86d7f5d3SJohn Marino     rcsbuf_cache_close ();
279*86d7f5d3SJohn Marino 
280*86d7f5d3SJohn Marino     if (!(rcsfile = locate_rcs (repos, file, &inattic)))
281*86d7f5d3SJohn Marino     {
282*86d7f5d3SJohn Marino 	/* Handle the error cases */
283*86d7f5d3SJohn Marino     }
284*86d7f5d3SJohn Marino     else if ((fp = CVS_FOPEN (rcsfile, FOPEN_BINARY_READ)))
285*86d7f5d3SJohn Marino     {
286*86d7f5d3SJohn Marino 	rcs = RCS_parsercsfile_i (fp, rcsfile);
287*86d7f5d3SJohn Marino 	if (rcs)
288*86d7f5d3SJohn Marino 	{
289*86d7f5d3SJohn Marino 	    rcs->flags |= VALID;
290*86d7f5d3SJohn Marino 	    if (inattic)
291*86d7f5d3SJohn Marino 		rcs->flags |= INATTIC;
292*86d7f5d3SJohn Marino 	}
293*86d7f5d3SJohn Marino 
294*86d7f5d3SJohn Marino 	free (rcsfile);
295*86d7f5d3SJohn Marino 	retval = rcs;
296*86d7f5d3SJohn Marino     }
297*86d7f5d3SJohn Marino     else if (!existence_error (errno))
298*86d7f5d3SJohn Marino     {
299*86d7f5d3SJohn Marino 	error (0, errno, "cannot open `%s'", rcsfile);
300*86d7f5d3SJohn Marino 	free (rcsfile);
301*86d7f5d3SJohn Marino     }
302*86d7f5d3SJohn Marino 
303*86d7f5d3SJohn Marino     return retval;
304*86d7f5d3SJohn Marino }
305*86d7f5d3SJohn Marino 
306*86d7f5d3SJohn Marino 
307*86d7f5d3SJohn Marino 
308*86d7f5d3SJohn Marino /*
309*86d7f5d3SJohn Marino  * Parse a specific rcsfile.
310*86d7f5d3SJohn Marino  */
311*86d7f5d3SJohn Marino RCSNode *
RCS_parsercsfile(const char * rcsfile)312*86d7f5d3SJohn Marino RCS_parsercsfile (const char *rcsfile)
313*86d7f5d3SJohn Marino {
314*86d7f5d3SJohn Marino     FILE *fp;
315*86d7f5d3SJohn Marino     RCSNode *rcs;
316*86d7f5d3SJohn Marino 
317*86d7f5d3SJohn Marino     /* We're creating a new RCSNode, so there is no hope of finding it
318*86d7f5d3SJohn Marino        in the cache.  */
319*86d7f5d3SJohn Marino     rcsbuf_cache_close ();
320*86d7f5d3SJohn Marino 
321*86d7f5d3SJohn Marino     /* open the rcsfile */
322*86d7f5d3SJohn Marino     if ((fp = CVS_FOPEN (rcsfile, FOPEN_BINARY_READ)) == NULL)
323*86d7f5d3SJohn Marino     {
324*86d7f5d3SJohn Marino 	error (0, errno, "Couldn't open rcs file `%s'", rcsfile);
325*86d7f5d3SJohn Marino 	return NULL;
326*86d7f5d3SJohn Marino     }
327*86d7f5d3SJohn Marino 
328*86d7f5d3SJohn Marino     rcs = RCS_parsercsfile_i (fp, rcsfile);
329*86d7f5d3SJohn Marino 
330*86d7f5d3SJohn Marino     return rcs;
331*86d7f5d3SJohn Marino }
332*86d7f5d3SJohn Marino 
333*86d7f5d3SJohn Marino 
334*86d7f5d3SJohn Marino 
335*86d7f5d3SJohn Marino /*
336*86d7f5d3SJohn Marino  */
337*86d7f5d3SJohn Marino static RCSNode *
RCS_parsercsfile_i(FILE * fp,const char * rcsfile)338*86d7f5d3SJohn Marino RCS_parsercsfile_i (FILE *fp, const char *rcsfile)
339*86d7f5d3SJohn Marino {
340*86d7f5d3SJohn Marino     RCSNode *rdata;
341*86d7f5d3SJohn Marino     struct rcsbuffer rcsbuf;
342*86d7f5d3SJohn Marino     char *key, *value;
343*86d7f5d3SJohn Marino 
344*86d7f5d3SJohn Marino     /* make a node */
345*86d7f5d3SJohn Marino     rdata = xmalloc (sizeof (RCSNode));
346*86d7f5d3SJohn Marino     memset (rdata, 0, sizeof (RCSNode));
347*86d7f5d3SJohn Marino     rdata->refcount = 1;
348*86d7f5d3SJohn Marino     rdata->path = xstrdup (rcsfile);
349*86d7f5d3SJohn Marino     rdata->print_path = xstrdup (primary_root_inverse_translate (rcsfile));
350*86d7f5d3SJohn Marino 
351*86d7f5d3SJohn Marino     /* Process HEAD, BRANCH, and EXPAND keywords from the RCS header.
352*86d7f5d3SJohn Marino 
353*86d7f5d3SJohn Marino        Most cvs operations on the main branch don't need any more
354*86d7f5d3SJohn Marino        information.  Those that do call RCS_reparsercsfile to parse
355*86d7f5d3SJohn Marino        the rest of the header and the deltas.  */
356*86d7f5d3SJohn Marino 
357*86d7f5d3SJohn Marino     rcsbuf_open (&rcsbuf, fp, rcsfile, 0);
358*86d7f5d3SJohn Marino 
359*86d7f5d3SJohn Marino     if (! rcsbuf_getkey (&rcsbuf, &key, &value))
360*86d7f5d3SJohn Marino 	goto l_error;
361*86d7f5d3SJohn Marino     if (STREQ (key, RCSDESC))
362*86d7f5d3SJohn Marino 	goto l_error;
363*86d7f5d3SJohn Marino 
364*86d7f5d3SJohn Marino     if (STREQ (RCSHEAD, key) && value != NULL)
365*86d7f5d3SJohn Marino 	rdata->head = rcsbuf_valcopy (&rcsbuf, value, 0, NULL);
366*86d7f5d3SJohn Marino 
367*86d7f5d3SJohn Marino     if (! rcsbuf_getkey (&rcsbuf, &key, &value))
368*86d7f5d3SJohn Marino 	goto l_error;
369*86d7f5d3SJohn Marino     if (STREQ (key, RCSDESC))
370*86d7f5d3SJohn Marino 	goto l_error;
371*86d7f5d3SJohn Marino 
372*86d7f5d3SJohn Marino     if (STREQ (RCSBRANCH, key) && value != NULL)
373*86d7f5d3SJohn Marino     {
374*86d7f5d3SJohn Marino 	char *cp;
375*86d7f5d3SJohn Marino 
376*86d7f5d3SJohn Marino 	rdata->branch = rcsbuf_valcopy (&rcsbuf, value, 0, NULL);
377*86d7f5d3SJohn Marino 	if ((numdots (rdata->branch) & 1) != 0)
378*86d7f5d3SJohn Marino 	{
379*86d7f5d3SJohn Marino 	    /* turn it into a branch if it's a revision */
380*86d7f5d3SJohn Marino 	    cp = strrchr (rdata->branch, '.');
381*86d7f5d3SJohn Marino 	    *cp = '\0';
382*86d7f5d3SJohn Marino 	}
383*86d7f5d3SJohn Marino     }
384*86d7f5d3SJohn Marino 
385*86d7f5d3SJohn Marino     /* Look ahead for expand, stopping when we see desc or a revision
386*86d7f5d3SJohn Marino        number.  */
387*86d7f5d3SJohn Marino     while (1)
388*86d7f5d3SJohn Marino     {
389*86d7f5d3SJohn Marino 	char *cp;
390*86d7f5d3SJohn Marino 
391*86d7f5d3SJohn Marino 	if (STREQ (RCSEXPAND, key))
392*86d7f5d3SJohn Marino 	{
393*86d7f5d3SJohn Marino 	    rdata->expand = rcsbuf_valcopy (&rcsbuf, value, 0, NULL);
394*86d7f5d3SJohn Marino 	    break;
395*86d7f5d3SJohn Marino 	}
396*86d7f5d3SJohn Marino 
397*86d7f5d3SJohn Marino 	for (cp = key;
398*86d7f5d3SJohn Marino 	     (isdigit ((unsigned char)*cp) || *cp == '.') && *cp != '\0';
399*86d7f5d3SJohn Marino 	     cp++)
400*86d7f5d3SJohn Marino 	    /* do nothing */ ;
401*86d7f5d3SJohn Marino 	if (*cp == '\0')
402*86d7f5d3SJohn Marino 	    break;
403*86d7f5d3SJohn Marino 
404*86d7f5d3SJohn Marino 	if (STREQ (RCSDESC, key))
405*86d7f5d3SJohn Marino 	    break;
406*86d7f5d3SJohn Marino 
407*86d7f5d3SJohn Marino 	if (! rcsbuf_getkey (&rcsbuf, &key, &value))
408*86d7f5d3SJohn Marino 	    break;
409*86d7f5d3SJohn Marino     }
410*86d7f5d3SJohn Marino 
411*86d7f5d3SJohn Marino     rdata->flags |= PARTIAL;
412*86d7f5d3SJohn Marino 
413*86d7f5d3SJohn Marino     rcsbuf_cache (rdata, &rcsbuf);
414*86d7f5d3SJohn Marino 
415*86d7f5d3SJohn Marino     return rdata;
416*86d7f5d3SJohn Marino 
417*86d7f5d3SJohn Marino l_error:
418*86d7f5d3SJohn Marino     error (0, 0, "`%s' does not appear to be a valid rcs file",
419*86d7f5d3SJohn Marino 	   rcsfile);
420*86d7f5d3SJohn Marino     rcsbuf_close (&rcsbuf);
421*86d7f5d3SJohn Marino     freercsnode (&rdata);
422*86d7f5d3SJohn Marino     fclose (fp);
423*86d7f5d3SJohn Marino     return NULL;
424*86d7f5d3SJohn Marino }
425*86d7f5d3SJohn Marino 
426*86d7f5d3SJohn Marino 
427*86d7f5d3SJohn Marino 
428*86d7f5d3SJohn Marino /* Do the real work of parsing an RCS file.
429*86d7f5d3SJohn Marino 
430*86d7f5d3SJohn Marino    On error, die with a fatal error; if it returns at all it was successful.
431*86d7f5d3SJohn Marino 
432*86d7f5d3SJohn Marino    If PFP is NULL, close the file when done.  Otherwise, leave it open
433*86d7f5d3SJohn Marino    and store the FILE * in *PFP.  */
434*86d7f5d3SJohn Marino void
RCS_reparsercsfile(RCSNode * rdata,FILE ** pfp,struct rcsbuffer * rcsbufp)435*86d7f5d3SJohn Marino RCS_reparsercsfile (RCSNode *rdata, FILE **pfp, struct rcsbuffer *rcsbufp)
436*86d7f5d3SJohn Marino {
437*86d7f5d3SJohn Marino     FILE *fp;
438*86d7f5d3SJohn Marino     char *rcsfile;
439*86d7f5d3SJohn Marino     struct rcsbuffer rcsbuf;
440*86d7f5d3SJohn Marino     Node *q, *kv;
441*86d7f5d3SJohn Marino     RCSVers *vnode;
442*86d7f5d3SJohn Marino     int gotkey;
443*86d7f5d3SJohn Marino     char *cp;
444*86d7f5d3SJohn Marino     char *key, *value;
445*86d7f5d3SJohn Marino 
446*86d7f5d3SJohn Marino     assert (rdata != NULL);
447*86d7f5d3SJohn Marino     rcsfile = rdata->path;
448*86d7f5d3SJohn Marino 
449*86d7f5d3SJohn Marino     rcsbuf_cache_open (rdata, 0, &fp, &rcsbuf);
450*86d7f5d3SJohn Marino 
451*86d7f5d3SJohn Marino     /* make a node */
452*86d7f5d3SJohn Marino     /* This probably shouldn't be done until later: if a file has an
453*86d7f5d3SJohn Marino        empty revision tree (which is permissible), rdata->versions
454*86d7f5d3SJohn Marino        should be NULL. -twp */
455*86d7f5d3SJohn Marino     rdata->versions = getlist ();
456*86d7f5d3SJohn Marino 
457*86d7f5d3SJohn Marino     /*
458*86d7f5d3SJohn Marino      * process all the special header information, break out when we get to
459*86d7f5d3SJohn Marino      * the first revision delta
460*86d7f5d3SJohn Marino      */
461*86d7f5d3SJohn Marino     gotkey = 0;
462*86d7f5d3SJohn Marino     for (;;)
463*86d7f5d3SJohn Marino     {
464*86d7f5d3SJohn Marino 	/* get the next key/value pair */
465*86d7f5d3SJohn Marino 	if (!gotkey)
466*86d7f5d3SJohn Marino 	{
467*86d7f5d3SJohn Marino 	    if (! rcsbuf_getkey (&rcsbuf, &key, &value))
468*86d7f5d3SJohn Marino 	    {
469*86d7f5d3SJohn Marino 		error (1, 0, "`%s' does not appear to be a valid rcs file",
470*86d7f5d3SJohn Marino 		       rcsfile);
471*86d7f5d3SJohn Marino 	    }
472*86d7f5d3SJohn Marino 	}
473*86d7f5d3SJohn Marino 
474*86d7f5d3SJohn Marino 	gotkey = 0;
475*86d7f5d3SJohn Marino 
476*86d7f5d3SJohn Marino 	/* Skip head, branch and expand tags; we already have them. */
477*86d7f5d3SJohn Marino 	if (STREQ (key, RCSHEAD)
478*86d7f5d3SJohn Marino 	    || STREQ (key, RCSBRANCH)
479*86d7f5d3SJohn Marino 	    || STREQ (key, RCSEXPAND))
480*86d7f5d3SJohn Marino 	{
481*86d7f5d3SJohn Marino 	    continue;
482*86d7f5d3SJohn Marino 	}
483*86d7f5d3SJohn Marino 
484*86d7f5d3SJohn Marino 	if (STREQ (key, "access"))
485*86d7f5d3SJohn Marino 	{
486*86d7f5d3SJohn Marino 	    if (value != NULL)
487*86d7f5d3SJohn Marino 	    {
488*86d7f5d3SJohn Marino 		/* We pass the POLISH parameter as 1 because
489*86d7f5d3SJohn Marino                    RCS_addaccess expects nothing but spaces.  FIXME:
490*86d7f5d3SJohn Marino                    It would be easy and more efficient to change
491*86d7f5d3SJohn Marino                    RCS_addaccess.  */
492*86d7f5d3SJohn Marino 		if (rdata->access)
493*86d7f5d3SJohn Marino 		{
494*86d7f5d3SJohn Marino 		    error (0, 0,
495*86d7f5d3SJohn Marino 		           "Duplicate `access' keyword found in RCS file.");
496*86d7f5d3SJohn Marino 		    free (rdata->access);
497*86d7f5d3SJohn Marino 		}
498*86d7f5d3SJohn Marino 		rdata->access = rcsbuf_valcopy (&rcsbuf, value, 1, NULL);
499*86d7f5d3SJohn Marino 	    }
500*86d7f5d3SJohn Marino 	    continue;
501*86d7f5d3SJohn Marino 	}
502*86d7f5d3SJohn Marino 
503*86d7f5d3SJohn Marino 	/* We always save lock information, so that we can handle
504*86d7f5d3SJohn Marino            -kkvl correctly when checking out a file. */
505*86d7f5d3SJohn Marino 	if (STREQ (key, "locks"))
506*86d7f5d3SJohn Marino 	{
507*86d7f5d3SJohn Marino 	    if (value != NULL)
508*86d7f5d3SJohn Marino 	    {
509*86d7f5d3SJohn Marino 		if (rdata->locks_data)
510*86d7f5d3SJohn Marino 		{
511*86d7f5d3SJohn Marino 		    error (0, 0,
512*86d7f5d3SJohn Marino 		           "Duplicate `locks' keyword found in RCS file.");
513*86d7f5d3SJohn Marino 		    free (rdata->locks_data);
514*86d7f5d3SJohn Marino 		}
515*86d7f5d3SJohn Marino 		rdata->locks_data = rcsbuf_valcopy (&rcsbuf, value, 0, NULL);
516*86d7f5d3SJohn Marino 	    }
517*86d7f5d3SJohn Marino 	    if (! rcsbuf_getkey (&rcsbuf, &key, &value))
518*86d7f5d3SJohn Marino 	    {
519*86d7f5d3SJohn Marino 		error (1, 0, "premature end of file reading %s", rcsfile);
520*86d7f5d3SJohn Marino 	    }
521*86d7f5d3SJohn Marino 	    if (STREQ (key, "strict") && value == NULL)
522*86d7f5d3SJohn Marino 	    {
523*86d7f5d3SJohn Marino 		rdata->strict_locks = 1;
524*86d7f5d3SJohn Marino 	    }
525*86d7f5d3SJohn Marino 	    else
526*86d7f5d3SJohn Marino 		gotkey = 1;
527*86d7f5d3SJohn Marino 	    continue;
528*86d7f5d3SJohn Marino 	}
529*86d7f5d3SJohn Marino 
530*86d7f5d3SJohn Marino 	if (STREQ (RCSSYMBOLS, key))
531*86d7f5d3SJohn Marino 	{
532*86d7f5d3SJohn Marino 	    if (value != NULL)
533*86d7f5d3SJohn Marino 	    {
534*86d7f5d3SJohn Marino 		if (rdata->symbols_data)
535*86d7f5d3SJohn Marino 		{
536*86d7f5d3SJohn Marino 		    error (0, 0,
537*86d7f5d3SJohn Marino 		           "Duplicate `%s' keyword found in RCS file.",
538*86d7f5d3SJohn Marino 		           RCSSYMBOLS);
539*86d7f5d3SJohn Marino 		    free (rdata->symbols_data);
540*86d7f5d3SJohn Marino 		}
541*86d7f5d3SJohn Marino 		rdata->symbols_data = rcsbuf_valcopy (&rcsbuf, value, 0, NULL);
542*86d7f5d3SJohn Marino 	    }
543*86d7f5d3SJohn Marino 	    continue;
544*86d7f5d3SJohn Marino 	}
545*86d7f5d3SJohn Marino 
546*86d7f5d3SJohn Marino 	/*
547*86d7f5d3SJohn Marino 	 * check key for '.''s and digits (probably a rev) if it is a
548*86d7f5d3SJohn Marino 	 * revision or `desc', we are done with the headers and are down to the
549*86d7f5d3SJohn Marino 	 * revision deltas, so we break out of the loop
550*86d7f5d3SJohn Marino 	 */
551*86d7f5d3SJohn Marino 	for (cp = key;
552*86d7f5d3SJohn Marino 	     (isdigit ((unsigned char) *cp) || *cp == '.') && *cp != '\0';
553*86d7f5d3SJohn Marino 	     cp++)
554*86d7f5d3SJohn Marino 	     /* do nothing */ ;
555*86d7f5d3SJohn Marino 	/* Note that when comparing with RCSDATE, we are not massaging
556*86d7f5d3SJohn Marino            VALUE from the string found in the RCS file.  This is OK
557*86d7f5d3SJohn Marino            since we know exactly what to expect.  */
558*86d7f5d3SJohn Marino 	if (*cp == '\0' && strncmp (RCSDATE, value, (sizeof RCSDATE) - 1) == 0)
559*86d7f5d3SJohn Marino 	    break;
560*86d7f5d3SJohn Marino 
561*86d7f5d3SJohn Marino 	if (STREQ (key, RCSDESC))
562*86d7f5d3SJohn Marino 	    break;
563*86d7f5d3SJohn Marino 
564*86d7f5d3SJohn Marino 	if (STREQ (key, "comment"))
565*86d7f5d3SJohn Marino 	{
566*86d7f5d3SJohn Marino 	    if (rdata->comment)
567*86d7f5d3SJohn Marino 	    {
568*86d7f5d3SJohn Marino 		error (0, 0,
569*86d7f5d3SJohn Marino 		       "warning: duplicate key `%s' in RCS file `%s'",
570*86d7f5d3SJohn Marino 		       key, rcsfile);
571*86d7f5d3SJohn Marino 		free (rdata->comment);
572*86d7f5d3SJohn Marino 	    }
573*86d7f5d3SJohn Marino 	    rdata->comment = rcsbuf_valcopy (&rcsbuf, value, 0, NULL);
574*86d7f5d3SJohn Marino 	    continue;
575*86d7f5d3SJohn Marino 	}
576*86d7f5d3SJohn Marino 	if (rdata->other == NULL)
577*86d7f5d3SJohn Marino 	    rdata->other = getlist ();
578*86d7f5d3SJohn Marino 	kv = getnode ();
579*86d7f5d3SJohn Marino 	kv->type = rcsbuf_valcmp (&rcsbuf) ? RCSCMPFLD : RCSFIELD;
580*86d7f5d3SJohn Marino 	kv->key = xstrdup (key);
581*86d7f5d3SJohn Marino 	kv->data = rcsbuf_valcopy (&rcsbuf, value, kv->type == RCSFIELD, NULL);
582*86d7f5d3SJohn Marino 	if (addnode (rdata->other, kv) != 0)
583*86d7f5d3SJohn Marino 	{
584*86d7f5d3SJohn Marino 	    error (0, 0, "warning: duplicate key `%s' in RCS file `%s'",
585*86d7f5d3SJohn Marino 		   key, rcsfile);
586*86d7f5d3SJohn Marino 	    freenode (kv);
587*86d7f5d3SJohn Marino 	}
588*86d7f5d3SJohn Marino 
589*86d7f5d3SJohn Marino 	/* if we haven't grabbed it yet, we didn't want it */
590*86d7f5d3SJohn Marino     }
591*86d7f5d3SJohn Marino 
592*86d7f5d3SJohn Marino     /* We got out of the loop, so we have the first part of the first
593*86d7f5d3SJohn Marino        revision delta in KEY (the revision) and VALUE (the date key
594*86d7f5d3SJohn Marino        and its value).  This is what getdelta expects to receive.  */
595*86d7f5d3SJohn Marino 
596*86d7f5d3SJohn Marino     while ((vnode = getdelta (&rcsbuf, rcsfile, &key, &value)) != NULL)
597*86d7f5d3SJohn Marino     {
598*86d7f5d3SJohn Marino 	/* get the node */
599*86d7f5d3SJohn Marino 	q = getnode ();
600*86d7f5d3SJohn Marino 	q->type = RCSVERS;
601*86d7f5d3SJohn Marino 	q->delproc = rcsvers_delproc;
602*86d7f5d3SJohn Marino 	q->data = vnode;
603*86d7f5d3SJohn Marino 	q->key = vnode->version;
604*86d7f5d3SJohn Marino 
605*86d7f5d3SJohn Marino 	/* add the nodes to the list */
606*86d7f5d3SJohn Marino 	if (addnode (rdata->versions, q) != 0)
607*86d7f5d3SJohn Marino 	{
608*86d7f5d3SJohn Marino #if 0
609*86d7f5d3SJohn Marino 		purify_printf("WARNING: Adding duplicate version: %s (%s)\n",
610*86d7f5d3SJohn Marino 			 q->key, rcsfile);
611*86d7f5d3SJohn Marino 		freenode (q);
612*86d7f5d3SJohn Marino #endif
613*86d7f5d3SJohn Marino 	}
614*86d7f5d3SJohn Marino     }
615*86d7f5d3SJohn Marino 
616*86d7f5d3SJohn Marino     /* Here KEY and VALUE are whatever caused getdelta to return NULL.  */
617*86d7f5d3SJohn Marino 
618*86d7f5d3SJohn Marino     if (STREQ (key, RCSDESC))
619*86d7f5d3SJohn Marino     {
620*86d7f5d3SJohn Marino 	if (rdata->desc != NULL)
621*86d7f5d3SJohn Marino 	{
622*86d7f5d3SJohn Marino 	    error (0, 0,
623*86d7f5d3SJohn Marino 		   "warning: duplicate key `%s' in RCS file `%s'",
624*86d7f5d3SJohn Marino 		   key, rcsfile);
625*86d7f5d3SJohn Marino 	    free (rdata->desc);
626*86d7f5d3SJohn Marino 	}
627*86d7f5d3SJohn Marino 	rdata->desc = rcsbuf_valcopy (&rcsbuf, value, 1, NULL);
628*86d7f5d3SJohn Marino     }
629*86d7f5d3SJohn Marino 
630*86d7f5d3SJohn Marino     rdata->delta_pos = rcsbuf_ftello (&rcsbuf);
631*86d7f5d3SJohn Marino 
632*86d7f5d3SJohn Marino     if (pfp == NULL)
633*86d7f5d3SJohn Marino 	rcsbuf_cache (rdata, &rcsbuf);
634*86d7f5d3SJohn Marino     else
635*86d7f5d3SJohn Marino     {
636*86d7f5d3SJohn Marino 	*pfp = fp;
637*86d7f5d3SJohn Marino 	*rcsbufp = rcsbuf;
638*86d7f5d3SJohn Marino     }
639*86d7f5d3SJohn Marino     rdata->flags &= ~PARTIAL;
640*86d7f5d3SJohn Marino }
641*86d7f5d3SJohn Marino 
642*86d7f5d3SJohn Marino 
643*86d7f5d3SJohn Marino 
644*86d7f5d3SJohn Marino /* Move RCS into or out of the Attic, depending on TOATTIC.  If the
645*86d7f5d3SJohn Marino    file is already in the desired place, return without doing
646*86d7f5d3SJohn Marino    anything.  At some point may want to think about how this relates
647*86d7f5d3SJohn Marino    to RCS_rewrite but that is a bit hairy (if one wants renames to be
648*86d7f5d3SJohn Marino    atomic, or that kind of thing).  If there is an error, print a message
649*86d7f5d3SJohn Marino    and return 1.  On success, return 0.  */
650*86d7f5d3SJohn Marino int
RCS_setattic(RCSNode * rcs,int toattic)651*86d7f5d3SJohn Marino RCS_setattic (RCSNode *rcs, int toattic)
652*86d7f5d3SJohn Marino {
653*86d7f5d3SJohn Marino     char *newpath;
654*86d7f5d3SJohn Marino     const char *p;
655*86d7f5d3SJohn Marino     char *q;
656*86d7f5d3SJohn Marino 
657*86d7f5d3SJohn Marino     /* Some systems aren't going to let us rename an open file.  */
658*86d7f5d3SJohn Marino     rcsbuf_cache_close ();
659*86d7f5d3SJohn Marino 
660*86d7f5d3SJohn Marino     /* Could make the pathname computations in this file, and probably
661*86d7f5d3SJohn Marino        in other parts of rcs.c too, easier if the REPOS and FILE
662*86d7f5d3SJohn Marino        arguments to RCS_parse got stashed in the RCSNode.  */
663*86d7f5d3SJohn Marino 
664*86d7f5d3SJohn Marino     if (toattic)
665*86d7f5d3SJohn Marino     {
666*86d7f5d3SJohn Marino 	mode_t omask;
667*86d7f5d3SJohn Marino 
668*86d7f5d3SJohn Marino 	if (rcs->flags & INATTIC)
669*86d7f5d3SJohn Marino 	    return 0;
670*86d7f5d3SJohn Marino 
671*86d7f5d3SJohn Marino 	/* Example: rcs->path is "/foo/bar/baz,v".  */
672*86d7f5d3SJohn Marino 	newpath = xmalloc (strlen (rcs->path) + sizeof CVSATTIC + 5);
673*86d7f5d3SJohn Marino 	p = last_component (rcs->path);
674*86d7f5d3SJohn Marino 	strncpy (newpath, rcs->path, p - rcs->path);
675*86d7f5d3SJohn Marino 	strcpy (newpath + (p - rcs->path), CVSATTIC);
676*86d7f5d3SJohn Marino 
677*86d7f5d3SJohn Marino 	/* Create the Attic directory if it doesn't exist.  */
678*86d7f5d3SJohn Marino 	omask = umask (cvsumask);
679*86d7f5d3SJohn Marino 	if (CVS_MKDIR (newpath, 0777) < 0 && errno != EEXIST)
680*86d7f5d3SJohn Marino 	    error (0, errno, "cannot make directory %s", newpath);
681*86d7f5d3SJohn Marino 	(void) umask (omask);
682*86d7f5d3SJohn Marino 
683*86d7f5d3SJohn Marino 	strcat (newpath, "/");
684*86d7f5d3SJohn Marino 	strcat (newpath, p);
685*86d7f5d3SJohn Marino 
686*86d7f5d3SJohn Marino 	if (CVS_RENAME (rcs->path, newpath) < 0)
687*86d7f5d3SJohn Marino 	{
688*86d7f5d3SJohn Marino 	    int save_errno = errno;
689*86d7f5d3SJohn Marino 
690*86d7f5d3SJohn Marino 	    /* The checks for isreadable look awfully fishy, but
691*86d7f5d3SJohn Marino 	       I'm going to leave them here for now until I
692*86d7f5d3SJohn Marino 	       can think harder about whether they take care of
693*86d7f5d3SJohn Marino 	       some cases which should be handled somehow.  */
694*86d7f5d3SJohn Marino 
695*86d7f5d3SJohn Marino 	    if (isreadable (rcs->path) || !isreadable (newpath))
696*86d7f5d3SJohn Marino 	    {
697*86d7f5d3SJohn Marino 		error (0, save_errno, "cannot rename %s to %s",
698*86d7f5d3SJohn Marino 		       rcs->path, newpath);
699*86d7f5d3SJohn Marino 		free (newpath);
700*86d7f5d3SJohn Marino 		return 1;
701*86d7f5d3SJohn Marino 	    }
702*86d7f5d3SJohn Marino 	}
703*86d7f5d3SJohn Marino     }
704*86d7f5d3SJohn Marino     else
705*86d7f5d3SJohn Marino     {
706*86d7f5d3SJohn Marino 	if (!(rcs->flags & INATTIC))
707*86d7f5d3SJohn Marino 	    return 0;
708*86d7f5d3SJohn Marino 
709*86d7f5d3SJohn Marino 	newpath = xmalloc (strlen (rcs->path));
710*86d7f5d3SJohn Marino 
711*86d7f5d3SJohn Marino 	/* Example: rcs->path is "/foo/bar/Attic/baz,v".  */
712*86d7f5d3SJohn Marino 	p = last_component (rcs->path);
713*86d7f5d3SJohn Marino 	strncpy (newpath, rcs->path, p - rcs->path - 1);
714*86d7f5d3SJohn Marino 	newpath[p - rcs->path - 1] = '\0';
715*86d7f5d3SJohn Marino 	q = newpath + (p - rcs->path - 1) - (sizeof CVSATTIC - 1);
716*86d7f5d3SJohn Marino 	assert (strncmp (q, CVSATTIC, sizeof CVSATTIC - 1) == 0);
717*86d7f5d3SJohn Marino 	strcpy (q, p);
718*86d7f5d3SJohn Marino 
719*86d7f5d3SJohn Marino 	if (CVS_RENAME (rcs->path, newpath) < 0)
720*86d7f5d3SJohn Marino 	{
721*86d7f5d3SJohn Marino 	    error (0, errno, "failed to move `%s' out of the attic",
722*86d7f5d3SJohn Marino 		   rcs->path);
723*86d7f5d3SJohn Marino 	    free (newpath);
724*86d7f5d3SJohn Marino 	    return 1;
725*86d7f5d3SJohn Marino 	}
726*86d7f5d3SJohn Marino     }
727*86d7f5d3SJohn Marino 
728*86d7f5d3SJohn Marino     free (rcs->path);
729*86d7f5d3SJohn Marino     rcs->path = newpath;
730*86d7f5d3SJohn Marino 
731*86d7f5d3SJohn Marino     return 0;
732*86d7f5d3SJohn Marino }
733*86d7f5d3SJohn Marino 
734*86d7f5d3SJohn Marino 
735*86d7f5d3SJohn Marino 
736*86d7f5d3SJohn Marino /*
737*86d7f5d3SJohn Marino  * Fully parse the RCS file.  Store all keyword/value pairs, fetch the
738*86d7f5d3SJohn Marino  * log messages for each revision, and fetch add and delete counts for
739*86d7f5d3SJohn Marino  * each revision (we could fetch the entire text for each revision,
740*86d7f5d3SJohn Marino  * but the only caller, log_fileproc, doesn't need that information,
741*86d7f5d3SJohn Marino  * so we don't waste the memory required to store it).  The add and
742*86d7f5d3SJohn Marino  * delete counts are stored on the OTHER field of the RCSVERSNODE
743*86d7f5d3SJohn Marino  * structure, under the names ";add" and ";delete", so that we don't
744*86d7f5d3SJohn Marino  * waste the memory space of extra fields in RCSVERSNODE for code
745*86d7f5d3SJohn Marino  * which doesn't need this information.
746*86d7f5d3SJohn Marino  */
747*86d7f5d3SJohn Marino void
RCS_fully_parse(RCSNode * rcs)748*86d7f5d3SJohn Marino RCS_fully_parse (RCSNode *rcs)
749*86d7f5d3SJohn Marino {
750*86d7f5d3SJohn Marino     FILE *fp;
751*86d7f5d3SJohn Marino     struct rcsbuffer rcsbuf;
752*86d7f5d3SJohn Marino 
753*86d7f5d3SJohn Marino     RCS_reparsercsfile (rcs, &fp, &rcsbuf);
754*86d7f5d3SJohn Marino 
755*86d7f5d3SJohn Marino     while (1)
756*86d7f5d3SJohn Marino     {
757*86d7f5d3SJohn Marino 	char *key, *value;
758*86d7f5d3SJohn Marino 	Node *vers;
759*86d7f5d3SJohn Marino 	RCSVers *vnode;
760*86d7f5d3SJohn Marino 
761*86d7f5d3SJohn Marino 	/* Rather than try to keep track of how much information we
762*86d7f5d3SJohn Marino            have read, just read to the end of the file.  */
763*86d7f5d3SJohn Marino 	if (!rcsbuf_getrevnum (&rcsbuf, &key))
764*86d7f5d3SJohn Marino 	    break;
765*86d7f5d3SJohn Marino 
766*86d7f5d3SJohn Marino 	vers = findnode (rcs->versions, key);
767*86d7f5d3SJohn Marino 	if (vers == NULL)
768*86d7f5d3SJohn Marino 	    error (1, 0,
769*86d7f5d3SJohn Marino 		   "mismatch in rcs file %s between deltas and deltatexts (%s)",
770*86d7f5d3SJohn Marino 		   rcs->print_path, key);
771*86d7f5d3SJohn Marino 
772*86d7f5d3SJohn Marino 	vnode = vers->data;
773*86d7f5d3SJohn Marino 
774*86d7f5d3SJohn Marino 	while (rcsbuf_getkey (&rcsbuf, &key, &value))
775*86d7f5d3SJohn Marino 	{
776*86d7f5d3SJohn Marino 	    if (!STREQ (key, "text"))
777*86d7f5d3SJohn Marino 	    {
778*86d7f5d3SJohn Marino 		Node *kv;
779*86d7f5d3SJohn Marino 
780*86d7f5d3SJohn Marino 		if (vnode->other == NULL)
781*86d7f5d3SJohn Marino 		    vnode->other = getlist ();
782*86d7f5d3SJohn Marino 		kv = getnode ();
783*86d7f5d3SJohn Marino 		kv->type = rcsbuf_valcmp (&rcsbuf) ? RCSCMPFLD : RCSFIELD;
784*86d7f5d3SJohn Marino 		kv->key = xstrdup (key);
785*86d7f5d3SJohn Marino 		kv->data = rcsbuf_valcopy (&rcsbuf, value, kv->type == RCSFIELD,
786*86d7f5d3SJohn Marino 					   NULL);
787*86d7f5d3SJohn Marino 		if (addnode (vnode->other, kv) != 0)
788*86d7f5d3SJohn Marino 		{
789*86d7f5d3SJohn Marino 		    error (0, 0,
790*86d7f5d3SJohn Marino 			   "\
791*86d7f5d3SJohn Marino warning: duplicate key `%s' in version `%s' of RCS file `%s'",
792*86d7f5d3SJohn Marino 			   key, vnode->version, rcs->print_path);
793*86d7f5d3SJohn Marino 		    freenode (kv);
794*86d7f5d3SJohn Marino 		}
795*86d7f5d3SJohn Marino 
796*86d7f5d3SJohn Marino 		continue;
797*86d7f5d3SJohn Marino 	    }
798*86d7f5d3SJohn Marino 
799*86d7f5d3SJohn Marino 	    if (!STREQ (vnode->version, rcs->head))
800*86d7f5d3SJohn Marino 	    {
801*86d7f5d3SJohn Marino 		unsigned long add, del;
802*86d7f5d3SJohn Marino 		char buf[50];
803*86d7f5d3SJohn Marino 		Node *kv;
804*86d7f5d3SJohn Marino 
805*86d7f5d3SJohn Marino 		/* This is a change text.  Store the add and delete
806*86d7f5d3SJohn Marino                    counts.  */
807*86d7f5d3SJohn Marino 		add = 0;
808*86d7f5d3SJohn Marino 		del = 0;
809*86d7f5d3SJohn Marino 		if (value != NULL)
810*86d7f5d3SJohn Marino 		{
811*86d7f5d3SJohn Marino 		    size_t vallen;
812*86d7f5d3SJohn Marino 		    const char *cp;
813*86d7f5d3SJohn Marino 
814*86d7f5d3SJohn Marino 		    rcsbuf_valpolish (&rcsbuf, value, 0, &vallen);
815*86d7f5d3SJohn Marino 		    cp = value;
816*86d7f5d3SJohn Marino 		    while (cp < value + vallen)
817*86d7f5d3SJohn Marino 		    {
818*86d7f5d3SJohn Marino 			char op;
819*86d7f5d3SJohn Marino 			unsigned long count;
820*86d7f5d3SJohn Marino 
821*86d7f5d3SJohn Marino 			op = *cp++;
822*86d7f5d3SJohn Marino 			if (op != 'a' && op  != 'd')
823*86d7f5d3SJohn Marino 			    error (1, 0, "\
824*86d7f5d3SJohn Marino unrecognized operation '\\x%x' in %s",
825*86d7f5d3SJohn Marino 				   op, rcs->print_path);
826*86d7f5d3SJohn Marino 			(void) strtoul (cp, (char **) &cp, 10);
827*86d7f5d3SJohn Marino 			if (*cp++ != ' ')
828*86d7f5d3SJohn Marino 			    error (1, 0, "space expected in %s revision %s",
829*86d7f5d3SJohn Marino 				   rcs->print_path, vnode->version);
830*86d7f5d3SJohn Marino 			count = strtoul (cp, (char **) &cp, 10);
831*86d7f5d3SJohn Marino 			if (*cp++ != '\012')
832*86d7f5d3SJohn Marino 			    error (1, 0, "linefeed expected in %s revision %s",
833*86d7f5d3SJohn Marino 				   rcs->print_path, vnode->version);
834*86d7f5d3SJohn Marino 
835*86d7f5d3SJohn Marino 			if (op == 'd')
836*86d7f5d3SJohn Marino 			    del += count;
837*86d7f5d3SJohn Marino 			else
838*86d7f5d3SJohn Marino 			{
839*86d7f5d3SJohn Marino 			    add += count;
840*86d7f5d3SJohn Marino 			    while (count != 0)
841*86d7f5d3SJohn Marino 			    {
842*86d7f5d3SJohn Marino 				if (*cp == '\012')
843*86d7f5d3SJohn Marino 				    --count;
844*86d7f5d3SJohn Marino 				else if (cp == value + vallen)
845*86d7f5d3SJohn Marino 				{
846*86d7f5d3SJohn Marino 				    if (count != 1)
847*86d7f5d3SJohn Marino 					error (1, 0, "\
848*86d7f5d3SJohn Marino premature end of value in %s revision %s",
849*86d7f5d3SJohn Marino 					       rcs->print_path, vnode->version);
850*86d7f5d3SJohn Marino 				    else
851*86d7f5d3SJohn Marino 					break;
852*86d7f5d3SJohn Marino 				}
853*86d7f5d3SJohn Marino 				++cp;
854*86d7f5d3SJohn Marino 			    }
855*86d7f5d3SJohn Marino 			}
856*86d7f5d3SJohn Marino 		    }
857*86d7f5d3SJohn Marino 		}
858*86d7f5d3SJohn Marino 
859*86d7f5d3SJohn Marino 		sprintf (buf, "%lu", add);
860*86d7f5d3SJohn Marino 		kv = getnode ();
861*86d7f5d3SJohn Marino 		kv->type = RCSFIELD;
862*86d7f5d3SJohn Marino 		kv->key = xstrdup (";add");
863*86d7f5d3SJohn Marino 		kv->data = xstrdup (buf);
864*86d7f5d3SJohn Marino 		if (addnode (vnode->other, kv) != 0)
865*86d7f5d3SJohn Marino 		{
866*86d7f5d3SJohn Marino 		    error (0, 0,
867*86d7f5d3SJohn Marino 			   "\
868*86d7f5d3SJohn Marino warning: duplicate key `%s' in version `%s' of RCS file `%s'",
869*86d7f5d3SJohn Marino 			   key, vnode->version, rcs->print_path);
870*86d7f5d3SJohn Marino 		    freenode (kv);
871*86d7f5d3SJohn Marino 		}
872*86d7f5d3SJohn Marino 
873*86d7f5d3SJohn Marino 		sprintf (buf, "%lu", del);
874*86d7f5d3SJohn Marino 		kv = getnode ();
875*86d7f5d3SJohn Marino 		kv->type = RCSFIELD;
876*86d7f5d3SJohn Marino 		kv->key = xstrdup (";delete");
877*86d7f5d3SJohn Marino 		kv->data = xstrdup (buf);
878*86d7f5d3SJohn Marino 		if (addnode (vnode->other, kv) != 0)
879*86d7f5d3SJohn Marino 		{
880*86d7f5d3SJohn Marino 		    error (0, 0,
881*86d7f5d3SJohn Marino 			   "\
882*86d7f5d3SJohn Marino warning: duplicate key `%s' in version `%s' of RCS file `%s'",
883*86d7f5d3SJohn Marino 			   key, vnode->version, rcs->print_path);
884*86d7f5d3SJohn Marino 		    freenode (kv);
885*86d7f5d3SJohn Marino 		}
886*86d7f5d3SJohn Marino 	    }
887*86d7f5d3SJohn Marino 
888*86d7f5d3SJohn Marino 	    /* We have found the "text" key which ends the data for
889*86d7f5d3SJohn Marino                this revision.  Break out of the loop and go on to the
890*86d7f5d3SJohn Marino                next revision.  */
891*86d7f5d3SJohn Marino 	    break;
892*86d7f5d3SJohn Marino 	}
893*86d7f5d3SJohn Marino     }
894*86d7f5d3SJohn Marino 
895*86d7f5d3SJohn Marino     rcsbuf_cache (rcs, &rcsbuf);
896*86d7f5d3SJohn Marino }
897*86d7f5d3SJohn Marino 
898*86d7f5d3SJohn Marino 
899*86d7f5d3SJohn Marino 
900*86d7f5d3SJohn Marino /*
901*86d7f5d3SJohn Marino  * freercsnode - free up the info for an RCSNode
902*86d7f5d3SJohn Marino  */
903*86d7f5d3SJohn Marino void
freercsnode(RCSNode ** rnodep)904*86d7f5d3SJohn Marino freercsnode (RCSNode **rnodep)
905*86d7f5d3SJohn Marino {
906*86d7f5d3SJohn Marino     if (rnodep == NULL || *rnodep == NULL)
907*86d7f5d3SJohn Marino 	return;
908*86d7f5d3SJohn Marino 
909*86d7f5d3SJohn Marino     ((*rnodep)->refcount)--;
910*86d7f5d3SJohn Marino     if ((*rnodep)->refcount != 0)
911*86d7f5d3SJohn Marino     {
912*86d7f5d3SJohn Marino 	*rnodep = NULL;
913*86d7f5d3SJohn Marino 	return;
914*86d7f5d3SJohn Marino     }
915*86d7f5d3SJohn Marino     free ((*rnodep)->path);
916*86d7f5d3SJohn Marino     free ((*rnodep)->print_path);
917*86d7f5d3SJohn Marino     if ((*rnodep)->head != NULL)
918*86d7f5d3SJohn Marino 	free ((*rnodep)->head);
919*86d7f5d3SJohn Marino     if ((*rnodep)->branch != NULL)
920*86d7f5d3SJohn Marino 	free ((*rnodep)->branch);
921*86d7f5d3SJohn Marino     free_rcsnode_contents (*rnodep);
922*86d7f5d3SJohn Marino     free (*rnodep);
923*86d7f5d3SJohn Marino     *rnodep = NULL;
924*86d7f5d3SJohn Marino }
925*86d7f5d3SJohn Marino 
926*86d7f5d3SJohn Marino 
927*86d7f5d3SJohn Marino 
928*86d7f5d3SJohn Marino /*
929*86d7f5d3SJohn Marino  * free_rcsnode_contents - free up the contents of an RCSNode without
930*86d7f5d3SJohn Marino  * freeing the node itself, or the file name, or the head, or the
931*86d7f5d3SJohn Marino  * path.  This returns the RCSNode to the state it is in immediately
932*86d7f5d3SJohn Marino  * after a call to RCS_parse.
933*86d7f5d3SJohn Marino  */
934*86d7f5d3SJohn Marino static void
free_rcsnode_contents(RCSNode * rnode)935*86d7f5d3SJohn Marino free_rcsnode_contents (RCSNode *rnode)
936*86d7f5d3SJohn Marino {
937*86d7f5d3SJohn Marino     dellist (&rnode->versions);
938*86d7f5d3SJohn Marino     if (rnode->symbols != NULL)
939*86d7f5d3SJohn Marino 	dellist (&rnode->symbols);
940*86d7f5d3SJohn Marino     if (rnode->symbols_data != NULL)
941*86d7f5d3SJohn Marino 	free (rnode->symbols_data);
942*86d7f5d3SJohn Marino     if (rnode->expand != NULL)
943*86d7f5d3SJohn Marino 	free (rnode->expand);
944*86d7f5d3SJohn Marino     if (rnode->other != NULL)
945*86d7f5d3SJohn Marino 	dellist (&rnode->other);
946*86d7f5d3SJohn Marino     if (rnode->access != NULL)
947*86d7f5d3SJohn Marino 	free (rnode->access);
948*86d7f5d3SJohn Marino     if (rnode->locks_data != NULL)
949*86d7f5d3SJohn Marino 	free (rnode->locks_data);
950*86d7f5d3SJohn Marino     if (rnode->locks != NULL)
951*86d7f5d3SJohn Marino 	dellist (&rnode->locks);
952*86d7f5d3SJohn Marino     if (rnode->comment != NULL)
953*86d7f5d3SJohn Marino 	free (rnode->comment);
954*86d7f5d3SJohn Marino     if (rnode->desc != NULL)
955*86d7f5d3SJohn Marino 	free (rnode->desc);
956*86d7f5d3SJohn Marino }
957*86d7f5d3SJohn Marino 
958*86d7f5d3SJohn Marino 
959*86d7f5d3SJohn Marino 
960*86d7f5d3SJohn Marino /* free_rcsvers_contents -- free up the contents of an RCSVers node,
961*86d7f5d3SJohn Marino    but also free the pointer to the node itself. */
962*86d7f5d3SJohn Marino /* Note: The `hardlinks' list is *not* freed, since it is merely a
963*86d7f5d3SJohn Marino    pointer into the `hardlist' structure (defined in hardlink.c), and
964*86d7f5d3SJohn Marino    that structure is freed elsewhere in the program. */
965*86d7f5d3SJohn Marino static void
free_rcsvers_contents(RCSVers * rnode)966*86d7f5d3SJohn Marino free_rcsvers_contents (RCSVers *rnode)
967*86d7f5d3SJohn Marino {
968*86d7f5d3SJohn Marino     if (rnode->branches != NULL)
969*86d7f5d3SJohn Marino 	dellist (&rnode->branches);
970*86d7f5d3SJohn Marino     if (rnode->date != NULL)
971*86d7f5d3SJohn Marino 	free (rnode->date);
972*86d7f5d3SJohn Marino     if (rnode->next != NULL)
973*86d7f5d3SJohn Marino 	free (rnode->next);
974*86d7f5d3SJohn Marino     if (rnode->author != NULL)
975*86d7f5d3SJohn Marino 	free (rnode->author);
976*86d7f5d3SJohn Marino     if (rnode->state != NULL)
977*86d7f5d3SJohn Marino 	free (rnode->state);
978*86d7f5d3SJohn Marino     if (rnode->other != NULL)
979*86d7f5d3SJohn Marino 	dellist (&rnode->other);
980*86d7f5d3SJohn Marino     if (rnode->other_delta != NULL)
981*86d7f5d3SJohn Marino 	dellist (&rnode->other_delta);
982*86d7f5d3SJohn Marino     if (rnode->text != NULL)
983*86d7f5d3SJohn Marino 	freedeltatext (rnode->text);
984*86d7f5d3SJohn Marino     free (rnode);
985*86d7f5d3SJohn Marino }
986*86d7f5d3SJohn Marino 
987*86d7f5d3SJohn Marino 
988*86d7f5d3SJohn Marino 
989*86d7f5d3SJohn Marino /*
990*86d7f5d3SJohn Marino  * rcsvers_delproc - free up an RCSVers type node
991*86d7f5d3SJohn Marino  */
992*86d7f5d3SJohn Marino static void
rcsvers_delproc(Node * p)993*86d7f5d3SJohn Marino rcsvers_delproc (Node *p)
994*86d7f5d3SJohn Marino {
995*86d7f5d3SJohn Marino     free_rcsvers_contents (p->data);
996*86d7f5d3SJohn Marino }
997*86d7f5d3SJohn Marino 
998*86d7f5d3SJohn Marino 
999*86d7f5d3SJohn Marino 
1000*86d7f5d3SJohn Marino /* These functions retrieve keys and values from an RCS file using a
1001*86d7f5d3SJohn Marino    buffer.  We use this somewhat complex approach because it turns out
1002*86d7f5d3SJohn Marino    that for many common operations, CVS spends most of its time
1003*86d7f5d3SJohn Marino    reading keys, so it's worth doing some fairly hairy optimization.  */
1004*86d7f5d3SJohn Marino 
1005*86d7f5d3SJohn Marino /* The number of bytes we try to read each time we need more data.  */
1006*86d7f5d3SJohn Marino 
1007*86d7f5d3SJohn Marino #define RCSBUF_BUFSIZE (8192)
1008*86d7f5d3SJohn Marino 
1009*86d7f5d3SJohn Marino /* The buffer we use to store data.  This grows as needed.  */
1010*86d7f5d3SJohn Marino 
1011*86d7f5d3SJohn Marino static char *rcsbuf_buffer = NULL;
1012*86d7f5d3SJohn Marino static size_t rcsbuf_buffer_size = 0;
1013*86d7f5d3SJohn Marino 
1014*86d7f5d3SJohn Marino /* Whether rcsbuf_buffer is in use.  This is used as a sanity check.  */
1015*86d7f5d3SJohn Marino 
1016*86d7f5d3SJohn Marino static int rcsbuf_inuse;
1017*86d7f5d3SJohn Marino 
1018*86d7f5d3SJohn Marino /* Set up to start gathering keys and values from an RCS file.  This
1019*86d7f5d3SJohn Marino    initializes RCSBUF.  */
1020*86d7f5d3SJohn Marino 
1021*86d7f5d3SJohn Marino static void
rcsbuf_open(struct rcsbuffer * rcsbuf,FILE * fp,const char * filename,long unsigned int pos)1022*86d7f5d3SJohn Marino rcsbuf_open (struct rcsbuffer *rcsbuf, FILE *fp, const char *filename,
1023*86d7f5d3SJohn Marino 	     long unsigned int pos)
1024*86d7f5d3SJohn Marino {
1025*86d7f5d3SJohn Marino     if (rcsbuf_inuse)
1026*86d7f5d3SJohn Marino 	error (1, 0, "rcsbuf_open: internal error");
1027*86d7f5d3SJohn Marino     rcsbuf_inuse = 1;
1028*86d7f5d3SJohn Marino 
1029*86d7f5d3SJohn Marino #ifdef HAVE_MMAP
1030*86d7f5d3SJohn Marino     {
1031*86d7f5d3SJohn Marino 	/* When we have mmap, it is much more efficient to let the system do the
1032*86d7f5d3SJohn Marino 	 * buffering and caching for us
1033*86d7f5d3SJohn Marino 	 */
1034*86d7f5d3SJohn Marino 	struct stat fs;
1035*86d7f5d3SJohn Marino 	size_t mmap_off = 0;
1036*86d7f5d3SJohn Marino 
1037*86d7f5d3SJohn Marino 	if ( fstat (fileno(fp), &fs) < 0 )
1038*86d7f5d3SJohn Marino 	    error ( 1, errno, "Could not stat RCS archive %s for mapping", filename );
1039*86d7f5d3SJohn Marino 
1040*86d7f5d3SJohn Marino 	if (pos)
1041*86d7f5d3SJohn Marino 	{
1042*86d7f5d3SJohn Marino 	    size_t ps = getpagesize ();
1043*86d7f5d3SJohn Marino 	    mmap_off = ( pos / ps ) * ps;
1044*86d7f5d3SJohn Marino 	}
1045*86d7f5d3SJohn Marino 
1046*86d7f5d3SJohn Marino 	/* Map private here since this particular buffer is read only */
1047*86d7f5d3SJohn Marino 	rcsbuf_buffer = mmap ( NULL, fs.st_size - mmap_off,
1048*86d7f5d3SJohn Marino 				PROT_READ | PROT_WRITE,
1049*86d7f5d3SJohn Marino 				MAP_PRIVATE, fileno(fp), mmap_off );
1050*86d7f5d3SJohn Marino 	if ( rcsbuf_buffer == NULL || rcsbuf_buffer == MAP_FAILED )
1051*86d7f5d3SJohn Marino 	    error ( 1, errno, "Could not map memory to RCS archive %s", filename );
1052*86d7f5d3SJohn Marino 
1053*86d7f5d3SJohn Marino 	rcsbuf_buffer_size = fs.st_size - mmap_off;
1054*86d7f5d3SJohn Marino 	rcsbuf->ptr = rcsbuf_buffer + pos - mmap_off;
1055*86d7f5d3SJohn Marino 	rcsbuf->ptrend = rcsbuf_buffer + fs.st_size - mmap_off;
1056*86d7f5d3SJohn Marino 	rcsbuf->pos = mmap_off;
1057*86d7f5d3SJohn Marino     }
1058*86d7f5d3SJohn Marino #else /* !HAVE_MMAP */
1059*86d7f5d3SJohn Marino     if (rcsbuf_buffer_size < RCSBUF_BUFSIZE)
1060*86d7f5d3SJohn Marino 	expand_string (&rcsbuf_buffer, &rcsbuf_buffer_size, RCSBUF_BUFSIZE);
1061*86d7f5d3SJohn Marino 
1062*86d7f5d3SJohn Marino     rcsbuf->ptr = rcsbuf_buffer;
1063*86d7f5d3SJohn Marino     rcsbuf->ptrend = rcsbuf_buffer;
1064*86d7f5d3SJohn Marino     rcsbuf->pos = pos;
1065*86d7f5d3SJohn Marino #endif /* HAVE_MMAP */
1066*86d7f5d3SJohn Marino     rcsbuf->fp = fp;
1067*86d7f5d3SJohn Marino     rcsbuf->filename = filename;
1068*86d7f5d3SJohn Marino     rcsbuf->vlen = 0;
1069*86d7f5d3SJohn Marino     rcsbuf->at_string = 0;
1070*86d7f5d3SJohn Marino     rcsbuf->embedded_at = 0;
1071*86d7f5d3SJohn Marino }
1072*86d7f5d3SJohn Marino 
1073*86d7f5d3SJohn Marino 
1074*86d7f5d3SJohn Marino 
1075*86d7f5d3SJohn Marino /* Stop gathering keys from an RCS file.  */
1076*86d7f5d3SJohn Marino static void
rcsbuf_close(struct rcsbuffer * rcsbuf)1077*86d7f5d3SJohn Marino rcsbuf_close (struct rcsbuffer *rcsbuf)
1078*86d7f5d3SJohn Marino {
1079*86d7f5d3SJohn Marino     if (! rcsbuf_inuse)
1080*86d7f5d3SJohn Marino 	error (1, 0, "rcsbuf_close: internal error");
1081*86d7f5d3SJohn Marino #ifdef HAVE_MMAP
1082*86d7f5d3SJohn Marino     munmap ( rcsbuf_buffer, rcsbuf_buffer_size );
1083*86d7f5d3SJohn Marino #endif
1084*86d7f5d3SJohn Marino     rcsbuf_inuse = 0;
1085*86d7f5d3SJohn Marino }
1086*86d7f5d3SJohn Marino 
1087*86d7f5d3SJohn Marino 
1088*86d7f5d3SJohn Marino 
1089*86d7f5d3SJohn Marino /* Read a key/value pair from an RCS file.  This sets *KEYP to point
1090*86d7f5d3SJohn Marino    to the key, and *VALUEP to point to the value.  A missing or empty
1091*86d7f5d3SJohn Marino    value is indicated by setting *VALUEP to NULL.
1092*86d7f5d3SJohn Marino 
1093*86d7f5d3SJohn Marino    This function returns 1 on success, or 0 on EOF.  If there is an
1094*86d7f5d3SJohn Marino    error reading the file, or an EOF in an unexpected location, it
1095*86d7f5d3SJohn Marino    gives a fatal error.
1096*86d7f5d3SJohn Marino 
1097*86d7f5d3SJohn Marino    This sets *KEYP and *VALUEP to point to storage managed by
1098*86d7f5d3SJohn Marino    rcsbuf_getkey.  Moreover, *VALUEP has not been massaged from the
1099*86d7f5d3SJohn Marino    RCS format: it may contain embedded whitespace and embedded '@'
1100*86d7f5d3SJohn Marino    characters.  Call rcsbuf_valcopy or rcsbuf_valpolish to do
1101*86d7f5d3SJohn Marino    appropriate massaging.  */
1102*86d7f5d3SJohn Marino 
1103*86d7f5d3SJohn Marino /* Note that the extreme hair in rcsbuf_getkey is because profiling
1104*86d7f5d3SJohn Marino    statistics show that it was worth it. */
1105*86d7f5d3SJohn Marino static int
rcsbuf_getkey(struct rcsbuffer * rcsbuf,char ** keyp,char ** valp)1106*86d7f5d3SJohn Marino rcsbuf_getkey (struct rcsbuffer *rcsbuf, char **keyp, char **valp)
1107*86d7f5d3SJohn Marino {
1108*86d7f5d3SJohn Marino     register const char * const my_spacetab = spacetab;
1109*86d7f5d3SJohn Marino     register char *ptr, *ptrend;
1110*86d7f5d3SJohn Marino     char c;
1111*86d7f5d3SJohn Marino 
1112*86d7f5d3SJohn Marino #define my_whitespace(c)	(my_spacetab[(unsigned char)c] != 0)
1113*86d7f5d3SJohn Marino 
1114*86d7f5d3SJohn Marino     rcsbuf->vlen = 0;
1115*86d7f5d3SJohn Marino     rcsbuf->at_string = 0;
1116*86d7f5d3SJohn Marino     rcsbuf->embedded_at = 0;
1117*86d7f5d3SJohn Marino 
1118*86d7f5d3SJohn Marino     ptr = rcsbuf->ptr;
1119*86d7f5d3SJohn Marino     ptrend = rcsbuf->ptrend;
1120*86d7f5d3SJohn Marino 
1121*86d7f5d3SJohn Marino     /* Sanity check.  */
1122*86d7f5d3SJohn Marino     assert (ptr >= rcsbuf_buffer && ptr <= rcsbuf_buffer + rcsbuf_buffer_size);
1123*86d7f5d3SJohn Marino     assert (ptrend >= rcsbuf_buffer && ptrend <= rcsbuf_buffer + rcsbuf_buffer_size);
1124*86d7f5d3SJohn Marino 
1125*86d7f5d3SJohn Marino #ifndef HAVE_MMAP
1126*86d7f5d3SJohn Marino     /* If the pointer is more than RCSBUF_BUFSIZE bytes into the
1127*86d7f5d3SJohn Marino        buffer, move back to the start of the buffer.  This keeps the
1128*86d7f5d3SJohn Marino        buffer from growing indefinitely.  */
1129*86d7f5d3SJohn Marino     if (ptr - rcsbuf_buffer >= RCSBUF_BUFSIZE)
1130*86d7f5d3SJohn Marino     {
1131*86d7f5d3SJohn Marino 	int len;
1132*86d7f5d3SJohn Marino 
1133*86d7f5d3SJohn Marino 	len = ptrend - ptr;
1134*86d7f5d3SJohn Marino 
1135*86d7f5d3SJohn Marino 	/* Sanity check: we don't read more than RCSBUF_BUFSIZE bytes
1136*86d7f5d3SJohn Marino            at a time, so we can't have more bytes than that past PTR.  */
1137*86d7f5d3SJohn Marino 	assert (len <= RCSBUF_BUFSIZE);
1138*86d7f5d3SJohn Marino 
1139*86d7f5d3SJohn Marino 	/* Update the POS field, which holds the file offset of the
1140*86d7f5d3SJohn Marino            first byte in the RCSBUF_BUFFER buffer.  */
1141*86d7f5d3SJohn Marino 	rcsbuf->pos += ptr - rcsbuf_buffer;
1142*86d7f5d3SJohn Marino 
1143*86d7f5d3SJohn Marino 	memcpy (rcsbuf_buffer, ptr, len);
1144*86d7f5d3SJohn Marino 	ptr = rcsbuf_buffer;
1145*86d7f5d3SJohn Marino 	ptrend = ptr + len;
1146*86d7f5d3SJohn Marino 	rcsbuf->ptrend = ptrend;
1147*86d7f5d3SJohn Marino     }
1148*86d7f5d3SJohn Marino #endif /* HAVE_MMAP */
1149*86d7f5d3SJohn Marino 
1150*86d7f5d3SJohn Marino     /* Skip leading whitespace.  */
1151*86d7f5d3SJohn Marino 
1152*86d7f5d3SJohn Marino     while (1)
1153*86d7f5d3SJohn Marino     {
1154*86d7f5d3SJohn Marino 	if (ptr >= ptrend)
1155*86d7f5d3SJohn Marino 	{
1156*86d7f5d3SJohn Marino 	    ptr = rcsbuf_fill (rcsbuf, ptr, NULL, NULL);
1157*86d7f5d3SJohn Marino 	    if (ptr == NULL)
1158*86d7f5d3SJohn Marino 		return 0;
1159*86d7f5d3SJohn Marino 	    ptrend = rcsbuf->ptrend;
1160*86d7f5d3SJohn Marino 	}
1161*86d7f5d3SJohn Marino 
1162*86d7f5d3SJohn Marino 	c = *ptr;
1163*86d7f5d3SJohn Marino 	if (! my_whitespace (c))
1164*86d7f5d3SJohn Marino 	    break;
1165*86d7f5d3SJohn Marino 
1166*86d7f5d3SJohn Marino 	++ptr;
1167*86d7f5d3SJohn Marino     }
1168*86d7f5d3SJohn Marino 
1169*86d7f5d3SJohn Marino     /* We've found the start of the key.  */
1170*86d7f5d3SJohn Marino 
1171*86d7f5d3SJohn Marino     *keyp = ptr;
1172*86d7f5d3SJohn Marino 
1173*86d7f5d3SJohn Marino     if (c != ';')
1174*86d7f5d3SJohn Marino     {
1175*86d7f5d3SJohn Marino 	while (1)
1176*86d7f5d3SJohn Marino 	{
1177*86d7f5d3SJohn Marino 	    ++ptr;
1178*86d7f5d3SJohn Marino 	    if (ptr >= ptrend)
1179*86d7f5d3SJohn Marino 	    {
1180*86d7f5d3SJohn Marino 		ptr = rcsbuf_fill (rcsbuf, ptr, keyp, NULL);
1181*86d7f5d3SJohn Marino 		if (ptr == NULL)
1182*86d7f5d3SJohn Marino 		    error (1, 0, "EOF in key in RCS file %s",
1183*86d7f5d3SJohn Marino 			   primary_root_inverse_translate (rcsbuf->filename));
1184*86d7f5d3SJohn Marino 		ptrend = rcsbuf->ptrend;
1185*86d7f5d3SJohn Marino 	    }
1186*86d7f5d3SJohn Marino 	    c = *ptr;
1187*86d7f5d3SJohn Marino 	    if (c == ';' || my_whitespace (c))
1188*86d7f5d3SJohn Marino 		break;
1189*86d7f5d3SJohn Marino 	}
1190*86d7f5d3SJohn Marino     }
1191*86d7f5d3SJohn Marino 
1192*86d7f5d3SJohn Marino     /* Here *KEYP points to the key in the buffer, C is the character
1193*86d7f5d3SJohn Marino        we found at the of the key, and PTR points to the location in
1194*86d7f5d3SJohn Marino        the buffer where we found C.  We must set *PTR to \0 in order
1195*86d7f5d3SJohn Marino        to terminate the key.  If the key ended with ';', then there is
1196*86d7f5d3SJohn Marino        no value.  */
1197*86d7f5d3SJohn Marino 
1198*86d7f5d3SJohn Marino     *ptr = '\0';
1199*86d7f5d3SJohn Marino     ++ptr;
1200*86d7f5d3SJohn Marino 
1201*86d7f5d3SJohn Marino     if (c == ';')
1202*86d7f5d3SJohn Marino     {
1203*86d7f5d3SJohn Marino 	*valp = NULL;
1204*86d7f5d3SJohn Marino 	rcsbuf->ptr = ptr;
1205*86d7f5d3SJohn Marino 	return 1;
1206*86d7f5d3SJohn Marino     }
1207*86d7f5d3SJohn Marino 
1208*86d7f5d3SJohn Marino     /* C must be whitespace.  Skip whitespace between the key and the
1209*86d7f5d3SJohn Marino        value.  If we find ';' now, there is no value.  */
1210*86d7f5d3SJohn Marino 
1211*86d7f5d3SJohn Marino     while (1)
1212*86d7f5d3SJohn Marino     {
1213*86d7f5d3SJohn Marino 	if (ptr >= ptrend)
1214*86d7f5d3SJohn Marino 	{
1215*86d7f5d3SJohn Marino 	    ptr = rcsbuf_fill (rcsbuf, ptr, keyp, NULL);
1216*86d7f5d3SJohn Marino 	    if (ptr == NULL)
1217*86d7f5d3SJohn Marino 		error (1, 0, "EOF while looking for value in RCS file %s",
1218*86d7f5d3SJohn Marino 		       primary_root_inverse_translate (rcsbuf->filename));
1219*86d7f5d3SJohn Marino 	    ptrend = rcsbuf->ptrend;
1220*86d7f5d3SJohn Marino 	}
1221*86d7f5d3SJohn Marino 	c = *ptr;
1222*86d7f5d3SJohn Marino 	if (c == ';')
1223*86d7f5d3SJohn Marino 	{
1224*86d7f5d3SJohn Marino 	    *valp = NULL;
1225*86d7f5d3SJohn Marino 	    rcsbuf->ptr = ptr + 1;
1226*86d7f5d3SJohn Marino 	    return 1;
1227*86d7f5d3SJohn Marino 	}
1228*86d7f5d3SJohn Marino 	if (! my_whitespace (c))
1229*86d7f5d3SJohn Marino 	    break;
1230*86d7f5d3SJohn Marino 	++ptr;
1231*86d7f5d3SJohn Marino     }
1232*86d7f5d3SJohn Marino 
1233*86d7f5d3SJohn Marino     /* Now PTR points to the start of the value, and C is the first
1234*86d7f5d3SJohn Marino        character of the value.  */
1235*86d7f5d3SJohn Marino 
1236*86d7f5d3SJohn Marino     if (c != '@')
1237*86d7f5d3SJohn Marino 	*valp = ptr;
1238*86d7f5d3SJohn Marino     else
1239*86d7f5d3SJohn Marino     {
1240*86d7f5d3SJohn Marino 	char *pat;
1241*86d7f5d3SJohn Marino 	size_t vlen;
1242*86d7f5d3SJohn Marino 
1243*86d7f5d3SJohn Marino 	/* Optimize the common case of a value composed of a single
1244*86d7f5d3SJohn Marino 	   '@' string.  */
1245*86d7f5d3SJohn Marino 
1246*86d7f5d3SJohn Marino 	rcsbuf->at_string = 1;
1247*86d7f5d3SJohn Marino 
1248*86d7f5d3SJohn Marino 	++ptr;
1249*86d7f5d3SJohn Marino 
1250*86d7f5d3SJohn Marino 	*valp = ptr;
1251*86d7f5d3SJohn Marino 
1252*86d7f5d3SJohn Marino 	while (1)
1253*86d7f5d3SJohn Marino 	{
1254*86d7f5d3SJohn Marino 	    while ((pat = memchr (ptr, '@', ptrend - ptr)) == NULL)
1255*86d7f5d3SJohn Marino 	    {
1256*86d7f5d3SJohn Marino 		/* Note that we pass PTREND as the PTR value to
1257*86d7f5d3SJohn Marino                    rcsbuf_fill, so that we will wind up setting PTR to
1258*86d7f5d3SJohn Marino                    the location corresponding to the old PTREND, so
1259*86d7f5d3SJohn Marino                    that we don't search the same bytes again.  */
1260*86d7f5d3SJohn Marino 		ptr = rcsbuf_fill (rcsbuf, ptrend, keyp, valp);
1261*86d7f5d3SJohn Marino 		if (ptr == NULL)
1262*86d7f5d3SJohn Marino 		    error (1, 0,
1263*86d7f5d3SJohn Marino 			   "EOF while looking for end of string in RCS file %s",
1264*86d7f5d3SJohn Marino 			   primary_root_inverse_translate (rcsbuf->filename));
1265*86d7f5d3SJohn Marino 		ptrend = rcsbuf->ptrend;
1266*86d7f5d3SJohn Marino 	    }
1267*86d7f5d3SJohn Marino 
1268*86d7f5d3SJohn Marino 	    /* Handle the special case of an '@' right at the end of
1269*86d7f5d3SJohn Marino                the known bytes.  */
1270*86d7f5d3SJohn Marino 	    if (pat + 1 >= ptrend)
1271*86d7f5d3SJohn Marino 	    {
1272*86d7f5d3SJohn Marino 		/* Note that we pass PAT, not PTR, here.  */
1273*86d7f5d3SJohn Marino 		pat = rcsbuf_fill (rcsbuf, pat, keyp, valp);
1274*86d7f5d3SJohn Marino 		if (pat == NULL)
1275*86d7f5d3SJohn Marino 		{
1276*86d7f5d3SJohn Marino 		    /* EOF here is OK; it just means that the last
1277*86d7f5d3SJohn Marino 		       character of the file was an '@' terminating a
1278*86d7f5d3SJohn Marino 		       value for a key type which does not require a
1279*86d7f5d3SJohn Marino 		       trailing ';'.  */
1280*86d7f5d3SJohn Marino 		    pat = rcsbuf->ptrend - 1;
1281*86d7f5d3SJohn Marino 
1282*86d7f5d3SJohn Marino 		}
1283*86d7f5d3SJohn Marino 		ptrend = rcsbuf->ptrend;
1284*86d7f5d3SJohn Marino 
1285*86d7f5d3SJohn Marino 		/* Note that the value of PTR is bogus here.  This is
1286*86d7f5d3SJohn Marino 		   OK, because we don't use it.  */
1287*86d7f5d3SJohn Marino 	    }
1288*86d7f5d3SJohn Marino 
1289*86d7f5d3SJohn Marino 	    if (pat + 1 >= ptrend || pat[1] != '@')
1290*86d7f5d3SJohn Marino 		break;
1291*86d7f5d3SJohn Marino 
1292*86d7f5d3SJohn Marino 	    /* We found an '@' pair in the string.  Keep looking.  */
1293*86d7f5d3SJohn Marino 	    ++rcsbuf->embedded_at;
1294*86d7f5d3SJohn Marino 	    ptr = pat + 2;
1295*86d7f5d3SJohn Marino 	}
1296*86d7f5d3SJohn Marino 
1297*86d7f5d3SJohn Marino 	/* Here PAT points to the final '@' in the string.  */
1298*86d7f5d3SJohn Marino 
1299*86d7f5d3SJohn Marino 	*pat = '\0';
1300*86d7f5d3SJohn Marino 
1301*86d7f5d3SJohn Marino 	vlen = pat - *valp;
1302*86d7f5d3SJohn Marino 	if (vlen == 0)
1303*86d7f5d3SJohn Marino 	    *valp = NULL;
1304*86d7f5d3SJohn Marino 	rcsbuf->vlen = vlen;
1305*86d7f5d3SJohn Marino 
1306*86d7f5d3SJohn Marino 	ptr = pat + 1;
1307*86d7f5d3SJohn Marino     }
1308*86d7f5d3SJohn Marino 
1309*86d7f5d3SJohn Marino     /* Certain keywords only have a '@' string.  If there is no '@'
1310*86d7f5d3SJohn Marino        string, then the old getrcskey function assumed that they had
1311*86d7f5d3SJohn Marino        no value, and we do the same.  */
1312*86d7f5d3SJohn Marino 
1313*86d7f5d3SJohn Marino     {
1314*86d7f5d3SJohn Marino 	char *k;
1315*86d7f5d3SJohn Marino 
1316*86d7f5d3SJohn Marino 	k = *keyp;
1317*86d7f5d3SJohn Marino 	if (STREQ (k, RCSDESC)
1318*86d7f5d3SJohn Marino 	    || STREQ (k, "text")
1319*86d7f5d3SJohn Marino 	    || STREQ (k, "log"))
1320*86d7f5d3SJohn Marino 	{
1321*86d7f5d3SJohn Marino 	    if (c != '@')
1322*86d7f5d3SJohn Marino 		*valp = NULL;
1323*86d7f5d3SJohn Marino 	    rcsbuf->ptr = ptr;
1324*86d7f5d3SJohn Marino 	    return 1;
1325*86d7f5d3SJohn Marino 	}
1326*86d7f5d3SJohn Marino     }
1327*86d7f5d3SJohn Marino 
1328*86d7f5d3SJohn Marino     /* If we've already gathered a '@' string, try to skip whitespace
1329*86d7f5d3SJohn Marino        and find a ';'.  */
1330*86d7f5d3SJohn Marino     if (c == '@')
1331*86d7f5d3SJohn Marino     {
1332*86d7f5d3SJohn Marino 	while (1)
1333*86d7f5d3SJohn Marino 	{
1334*86d7f5d3SJohn Marino 	    char n;
1335*86d7f5d3SJohn Marino 
1336*86d7f5d3SJohn Marino 	    if (ptr >= ptrend)
1337*86d7f5d3SJohn Marino 	    {
1338*86d7f5d3SJohn Marino 		ptr = rcsbuf_fill (rcsbuf, ptr, keyp, valp);
1339*86d7f5d3SJohn Marino 		if (ptr == NULL)
1340*86d7f5d3SJohn Marino 		    error (1, 0, "EOF in value in RCS file %s",
1341*86d7f5d3SJohn Marino 			   primary_root_inverse_translate (rcsbuf->filename));
1342*86d7f5d3SJohn Marino 		ptrend = rcsbuf->ptrend;
1343*86d7f5d3SJohn Marino 	    }
1344*86d7f5d3SJohn Marino 	    n = *ptr;
1345*86d7f5d3SJohn Marino 	    if (n == ';')
1346*86d7f5d3SJohn Marino 	    {
1347*86d7f5d3SJohn Marino 		/* We're done.  We already set everything up for this
1348*86d7f5d3SJohn Marino                    case above.  */
1349*86d7f5d3SJohn Marino 		rcsbuf->ptr = ptr + 1;
1350*86d7f5d3SJohn Marino 		return 1;
1351*86d7f5d3SJohn Marino 	    }
1352*86d7f5d3SJohn Marino 	    if (! my_whitespace (n))
1353*86d7f5d3SJohn Marino 		break;
1354*86d7f5d3SJohn Marino 	    ++ptr;
1355*86d7f5d3SJohn Marino 	}
1356*86d7f5d3SJohn Marino 
1357*86d7f5d3SJohn Marino 	/* The value extends past the '@' string.  We need to undo the
1358*86d7f5d3SJohn Marino            '@' stripping done in the default case above.  This
1359*86d7f5d3SJohn Marino            case never happens in a plain RCS file, but it can happen
1360*86d7f5d3SJohn Marino            if user defined phrases are used.  */
1361*86d7f5d3SJohn Marino 	((*valp)--)[rcsbuf->vlen++] = '@';
1362*86d7f5d3SJohn Marino     }
1363*86d7f5d3SJohn Marino 
1364*86d7f5d3SJohn Marino     /* Here we have a value which is not a simple '@' string.  We need
1365*86d7f5d3SJohn Marino        to gather up everything until the next ';', including any '@'
1366*86d7f5d3SJohn Marino        strings.  *VALP points to the start of the value.  If
1367*86d7f5d3SJohn Marino        RCSBUF->VLEN is not zero, then we have already read an '@'
1368*86d7f5d3SJohn Marino        string, and PTR points to the data following the '@' string.
1369*86d7f5d3SJohn Marino        Otherwise, PTR points to the start of the value.  */
1370*86d7f5d3SJohn Marino 
1371*86d7f5d3SJohn Marino     while (1)
1372*86d7f5d3SJohn Marino     {
1373*86d7f5d3SJohn Marino 	char *start, *psemi, *pat;
1374*86d7f5d3SJohn Marino 
1375*86d7f5d3SJohn Marino 	/* Find the ';' which must end the value.  */
1376*86d7f5d3SJohn Marino 	start = ptr;
1377*86d7f5d3SJohn Marino 	while ((psemi = memchr (ptr, ';', ptrend - ptr)) == NULL)
1378*86d7f5d3SJohn Marino 	{
1379*86d7f5d3SJohn Marino 	    int slen;
1380*86d7f5d3SJohn Marino 
1381*86d7f5d3SJohn Marino 	    /* Note that we pass PTREND as the PTR value to
1382*86d7f5d3SJohn Marino 	       rcsbuf_fill, so that we will wind up setting PTR to the
1383*86d7f5d3SJohn Marino 	       location corresponding to the old PTREND, so that we
1384*86d7f5d3SJohn Marino 	       don't search the same bytes again.  */
1385*86d7f5d3SJohn Marino 	    slen = start - *valp;
1386*86d7f5d3SJohn Marino 	    ptr = rcsbuf_fill (rcsbuf, ptrend, keyp, valp);
1387*86d7f5d3SJohn Marino 	    if (ptr == NULL)
1388*86d7f5d3SJohn Marino 		error (1, 0, "EOF in value in RCS file %s",
1389*86d7f5d3SJohn Marino 		       primary_root_inverse_translate (rcsbuf->filename));
1390*86d7f5d3SJohn Marino 	    start = *valp + slen;
1391*86d7f5d3SJohn Marino 	    ptrend = rcsbuf->ptrend;
1392*86d7f5d3SJohn Marino 	}
1393*86d7f5d3SJohn Marino 
1394*86d7f5d3SJohn Marino 	/* See if there are any '@' strings in the value.  */
1395*86d7f5d3SJohn Marino 	pat = memchr (start, '@', psemi - start);
1396*86d7f5d3SJohn Marino 
1397*86d7f5d3SJohn Marino 	if (pat == NULL)
1398*86d7f5d3SJohn Marino 	{
1399*86d7f5d3SJohn Marino 	    size_t vlen;
1400*86d7f5d3SJohn Marino 
1401*86d7f5d3SJohn Marino 	    /* We're done with the value.  Trim any trailing
1402*86d7f5d3SJohn Marino                whitespace.  */
1403*86d7f5d3SJohn Marino 
1404*86d7f5d3SJohn Marino 	    rcsbuf->ptr = psemi + 1;
1405*86d7f5d3SJohn Marino 
1406*86d7f5d3SJohn Marino 	    start = *valp;
1407*86d7f5d3SJohn Marino 	    while (psemi > start && my_whitespace (psemi[-1]))
1408*86d7f5d3SJohn Marino 		--psemi;
1409*86d7f5d3SJohn Marino 	    *psemi = '\0';
1410*86d7f5d3SJohn Marino 
1411*86d7f5d3SJohn Marino 	    vlen = psemi - start;
1412*86d7f5d3SJohn Marino 	    if (vlen == 0)
1413*86d7f5d3SJohn Marino 		*valp = NULL;
1414*86d7f5d3SJohn Marino 	    rcsbuf->vlen = vlen;
1415*86d7f5d3SJohn Marino 
1416*86d7f5d3SJohn Marino 	    return 1;
1417*86d7f5d3SJohn Marino 	}
1418*86d7f5d3SJohn Marino 
1419*86d7f5d3SJohn Marino 	/* We found an '@' string in the value.  We set RCSBUF->AT_STRING
1420*86d7f5d3SJohn Marino 	   and RCSBUF->EMBEDDED_AT to indicate that we won't be able to
1421*86d7f5d3SJohn Marino 	   compress whitespace correctly for this type of value.
1422*86d7f5d3SJohn Marino 	   Since this type of value never arises in a normal RCS file,
1423*86d7f5d3SJohn Marino 	   this should not be a big deal.  It means that if anybody
1424*86d7f5d3SJohn Marino 	   adds a phrase which can have both an '@' string and regular
1425*86d7f5d3SJohn Marino 	   text, they will have to handle whitespace compression
1426*86d7f5d3SJohn Marino 	   themselves.  */
1427*86d7f5d3SJohn Marino 
1428*86d7f5d3SJohn Marino 	rcsbuf->at_string = 1;
1429*86d7f5d3SJohn Marino 	rcsbuf->embedded_at = -1;
1430*86d7f5d3SJohn Marino 
1431*86d7f5d3SJohn Marino 	ptr = pat + 1;
1432*86d7f5d3SJohn Marino 
1433*86d7f5d3SJohn Marino 	while (1)
1434*86d7f5d3SJohn Marino 	{
1435*86d7f5d3SJohn Marino 	    while ((pat = memchr (ptr, '@', ptrend - ptr)) == NULL)
1436*86d7f5d3SJohn Marino 	    {
1437*86d7f5d3SJohn Marino 		/* Note that we pass PTREND as the PTR value to
1438*86d7f5d3SJohn Marino                    rcsbuff_fill, so that we will wind up setting PTR
1439*86d7f5d3SJohn Marino                    to the location corresponding to the old PTREND, so
1440*86d7f5d3SJohn Marino                    that we don't search the same bytes again.  */
1441*86d7f5d3SJohn Marino 		ptr = rcsbuf_fill (rcsbuf, ptrend, keyp, valp);
1442*86d7f5d3SJohn Marino 		if (ptr == NULL)
1443*86d7f5d3SJohn Marino 		    error (1, 0,
1444*86d7f5d3SJohn Marino 			   "EOF while looking for end of string in RCS file %s",
1445*86d7f5d3SJohn Marino 			   primary_root_inverse_translate (rcsbuf->filename));
1446*86d7f5d3SJohn Marino 		ptrend = rcsbuf->ptrend;
1447*86d7f5d3SJohn Marino 	    }
1448*86d7f5d3SJohn Marino 
1449*86d7f5d3SJohn Marino 	    /* Handle the special case of an '@' right at the end of
1450*86d7f5d3SJohn Marino                the known bytes.  */
1451*86d7f5d3SJohn Marino 	    if (pat + 1 >= ptrend)
1452*86d7f5d3SJohn Marino 	    {
1453*86d7f5d3SJohn Marino 		ptr = rcsbuf_fill (rcsbuf, ptr, keyp, valp);
1454*86d7f5d3SJohn Marino 		if (ptr == NULL)
1455*86d7f5d3SJohn Marino 		    error (1, 0, "EOF in value in RCS file %s",
1456*86d7f5d3SJohn Marino 			   primary_root_inverse_translate (rcsbuf->filename));
1457*86d7f5d3SJohn Marino 		ptrend = rcsbuf->ptrend;
1458*86d7f5d3SJohn Marino 	    }
1459*86d7f5d3SJohn Marino 
1460*86d7f5d3SJohn Marino 	    if (pat[1] != '@')
1461*86d7f5d3SJohn Marino 		break;
1462*86d7f5d3SJohn Marino 
1463*86d7f5d3SJohn Marino 	    /* We found an '@' pair in the string.  Keep looking.  */
1464*86d7f5d3SJohn Marino 	    ptr = pat + 2;
1465*86d7f5d3SJohn Marino 	}
1466*86d7f5d3SJohn Marino 
1467*86d7f5d3SJohn Marino 	/* Here PAT points to the final '@' in the string.  */
1468*86d7f5d3SJohn Marino 	ptr = pat + 1;
1469*86d7f5d3SJohn Marino     }
1470*86d7f5d3SJohn Marino 
1471*86d7f5d3SJohn Marino #undef my_whitespace
1472*86d7f5d3SJohn Marino }
1473*86d7f5d3SJohn Marino 
1474*86d7f5d3SJohn Marino 
1475*86d7f5d3SJohn Marino 
1476*86d7f5d3SJohn Marino /* Read an RCS revision number from an RCS file.  This sets *REVP to
1477*86d7f5d3SJohn Marino    point to the revision number; it will point to space that is
1478*86d7f5d3SJohn Marino    managed by the rcsbuf functions, and is only good until the next
1479*86d7f5d3SJohn Marino    call to rcsbuf_getkey or rcsbuf_getrevnum.
1480*86d7f5d3SJohn Marino 
1481*86d7f5d3SJohn Marino    This function returns 1 on success, or 0 on EOF.  If there is an
1482*86d7f5d3SJohn Marino    error reading the file, or an EOF in an unexpected location, it
1483*86d7f5d3SJohn Marino    gives a fatal error.  */
1484*86d7f5d3SJohn Marino static int
rcsbuf_getrevnum(struct rcsbuffer * rcsbuf,char ** revp)1485*86d7f5d3SJohn Marino rcsbuf_getrevnum (struct rcsbuffer *rcsbuf, char **revp)
1486*86d7f5d3SJohn Marino {
1487*86d7f5d3SJohn Marino     char *ptr, *ptrend;
1488*86d7f5d3SJohn Marino     char c;
1489*86d7f5d3SJohn Marino 
1490*86d7f5d3SJohn Marino     ptr = rcsbuf->ptr;
1491*86d7f5d3SJohn Marino     ptrend = rcsbuf->ptrend;
1492*86d7f5d3SJohn Marino 
1493*86d7f5d3SJohn Marino     *revp = NULL;
1494*86d7f5d3SJohn Marino 
1495*86d7f5d3SJohn Marino     /* Skip leading whitespace.  */
1496*86d7f5d3SJohn Marino 
1497*86d7f5d3SJohn Marino     while (1)
1498*86d7f5d3SJohn Marino     {
1499*86d7f5d3SJohn Marino 	if (ptr >= ptrend)
1500*86d7f5d3SJohn Marino 	{
1501*86d7f5d3SJohn Marino 	    ptr = rcsbuf_fill (rcsbuf, ptr, NULL, NULL);
1502*86d7f5d3SJohn Marino 	    if (ptr == NULL)
1503*86d7f5d3SJohn Marino 		return 0;
1504*86d7f5d3SJohn Marino 	    ptrend = rcsbuf->ptrend;
1505*86d7f5d3SJohn Marino 	}
1506*86d7f5d3SJohn Marino 
1507*86d7f5d3SJohn Marino 	c = *ptr;
1508*86d7f5d3SJohn Marino 	if (! whitespace (c))
1509*86d7f5d3SJohn Marino 	    break;
1510*86d7f5d3SJohn Marino 
1511*86d7f5d3SJohn Marino 	++ptr;
1512*86d7f5d3SJohn Marino     }
1513*86d7f5d3SJohn Marino 
1514*86d7f5d3SJohn Marino     if (! isdigit ((unsigned char) c) && c != '.')
1515*86d7f5d3SJohn Marino 	error (1, 0,
1516*86d7f5d3SJohn Marino 	       "\
1517*86d7f5d3SJohn Marino unexpected '\\x%x' reading revision number in RCS file %s",
1518*86d7f5d3SJohn Marino 	       c, primary_root_inverse_translate (rcsbuf->filename));
1519*86d7f5d3SJohn Marino 
1520*86d7f5d3SJohn Marino     *revp = ptr;
1521*86d7f5d3SJohn Marino 
1522*86d7f5d3SJohn Marino     do
1523*86d7f5d3SJohn Marino     {
1524*86d7f5d3SJohn Marino 	++ptr;
1525*86d7f5d3SJohn Marino 	if (ptr >= ptrend)
1526*86d7f5d3SJohn Marino 	{
1527*86d7f5d3SJohn Marino 	    ptr = rcsbuf_fill (rcsbuf, ptr, revp, NULL);
1528*86d7f5d3SJohn Marino 	    if (ptr == NULL)
1529*86d7f5d3SJohn Marino 		error (1, 0,
1530*86d7f5d3SJohn Marino 		       "unexpected EOF reading revision number in RCS file %s",
1531*86d7f5d3SJohn Marino 		       primary_root_inverse_translate (rcsbuf->filename));
1532*86d7f5d3SJohn Marino 	    ptrend = rcsbuf->ptrend;
1533*86d7f5d3SJohn Marino 	}
1534*86d7f5d3SJohn Marino 
1535*86d7f5d3SJohn Marino 	c = *ptr;
1536*86d7f5d3SJohn Marino     }
1537*86d7f5d3SJohn Marino     while (isdigit ((unsigned char) c) || c == '.');
1538*86d7f5d3SJohn Marino 
1539*86d7f5d3SJohn Marino     if (! whitespace (c))
1540*86d7f5d3SJohn Marino 	error (1, 0, "\
1541*86d7f5d3SJohn Marino unexpected '\\x%x' reading revision number in RCS file %s",
1542*86d7f5d3SJohn Marino 	       c, primary_root_inverse_translate (rcsbuf->filename));
1543*86d7f5d3SJohn Marino 
1544*86d7f5d3SJohn Marino     *ptr = '\0';
1545*86d7f5d3SJohn Marino 
1546*86d7f5d3SJohn Marino     rcsbuf->ptr = ptr + 1;
1547*86d7f5d3SJohn Marino 
1548*86d7f5d3SJohn Marino     return 1;
1549*86d7f5d3SJohn Marino }
1550*86d7f5d3SJohn Marino 
1551*86d7f5d3SJohn Marino 
1552*86d7f5d3SJohn Marino 
1553*86d7f5d3SJohn Marino /* Fill RCSBUF_BUFFER with bytes from the file associated with RCSBUF,
1554*86d7f5d3SJohn Marino    updating PTR and the PTREND field.  If KEYP and *KEYP are not NULL,
1555*86d7f5d3SJohn Marino    then *KEYP points into the buffer, and must be adjusted if the
1556*86d7f5d3SJohn Marino    buffer is changed.  Likewise for VALP.  Returns the new value of
1557*86d7f5d3SJohn Marino    PTR, or NULL on error.  */
1558*86d7f5d3SJohn Marino static char *
rcsbuf_fill(struct rcsbuffer * rcsbuf,char * ptr,char ** keyp,char ** valp)1559*86d7f5d3SJohn Marino rcsbuf_fill (struct rcsbuffer *rcsbuf, char *ptr, char **keyp, char **valp)
1560*86d7f5d3SJohn Marino {
1561*86d7f5d3SJohn Marino #ifdef HAVE_MMAP
1562*86d7f5d3SJohn Marino     return NULL;
1563*86d7f5d3SJohn Marino #else /* HAVE_MMAP */
1564*86d7f5d3SJohn Marino     int got;
1565*86d7f5d3SJohn Marino 
1566*86d7f5d3SJohn Marino     if (rcsbuf->ptrend - rcsbuf_buffer + RCSBUF_BUFSIZE > rcsbuf_buffer_size)
1567*86d7f5d3SJohn Marino     {
1568*86d7f5d3SJohn Marino 	int poff, peoff, koff, voff;
1569*86d7f5d3SJohn Marino 
1570*86d7f5d3SJohn Marino 	poff = ptr - rcsbuf_buffer;
1571*86d7f5d3SJohn Marino 	peoff = rcsbuf->ptrend - rcsbuf_buffer;
1572*86d7f5d3SJohn Marino 	koff = keyp == NULL ? 0 : *keyp - rcsbuf_buffer;
1573*86d7f5d3SJohn Marino 	voff = valp == NULL ? 0 : *valp - rcsbuf_buffer;
1574*86d7f5d3SJohn Marino 
1575*86d7f5d3SJohn Marino 	expand_string (&rcsbuf_buffer, &rcsbuf_buffer_size,
1576*86d7f5d3SJohn Marino 		       rcsbuf_buffer_size + RCSBUF_BUFSIZE);
1577*86d7f5d3SJohn Marino 
1578*86d7f5d3SJohn Marino 	ptr = rcsbuf_buffer + poff;
1579*86d7f5d3SJohn Marino 	rcsbuf->ptrend = rcsbuf_buffer + peoff;
1580*86d7f5d3SJohn Marino 	if (keyp != NULL)
1581*86d7f5d3SJohn Marino 	    *keyp = rcsbuf_buffer + koff;
1582*86d7f5d3SJohn Marino 	if (valp != NULL)
1583*86d7f5d3SJohn Marino 	    *valp = rcsbuf_buffer + voff;
1584*86d7f5d3SJohn Marino     }
1585*86d7f5d3SJohn Marino 
1586*86d7f5d3SJohn Marino     got = fread (rcsbuf->ptrend, 1, RCSBUF_BUFSIZE, rcsbuf->fp);
1587*86d7f5d3SJohn Marino     if (got == 0)
1588*86d7f5d3SJohn Marino     {
1589*86d7f5d3SJohn Marino 	if (ferror (rcsbuf->fp))
1590*86d7f5d3SJohn Marino 	    error (1, errno, "cannot read %s", rcsbuf->filename);
1591*86d7f5d3SJohn Marino 	return NULL;
1592*86d7f5d3SJohn Marino     }
1593*86d7f5d3SJohn Marino 
1594*86d7f5d3SJohn Marino     rcsbuf->ptrend += got;
1595*86d7f5d3SJohn Marino 
1596*86d7f5d3SJohn Marino     return ptr;
1597*86d7f5d3SJohn Marino #endif /* HAVE_MMAP */
1598*86d7f5d3SJohn Marino }
1599*86d7f5d3SJohn Marino 
1600*86d7f5d3SJohn Marino 
1601*86d7f5d3SJohn Marino 
1602*86d7f5d3SJohn Marino /* Test whether the last value returned by rcsbuf_getkey is a composite
1603*86d7f5d3SJohn Marino    value or not. */
1604*86d7f5d3SJohn Marino static int
rcsbuf_valcmp(struct rcsbuffer * rcsbuf)1605*86d7f5d3SJohn Marino rcsbuf_valcmp (struct rcsbuffer *rcsbuf)
1606*86d7f5d3SJohn Marino {
1607*86d7f5d3SJohn Marino     return rcsbuf->at_string && rcsbuf->embedded_at < 0;
1608*86d7f5d3SJohn Marino }
1609*86d7f5d3SJohn Marino 
1610*86d7f5d3SJohn Marino 
1611*86d7f5d3SJohn Marino 
1612*86d7f5d3SJohn Marino /* Copy the value VAL returned by rcsbuf_getkey into a memory buffer,
1613*86d7f5d3SJohn Marino    returning the memory buffer.  Polish the value like
1614*86d7f5d3SJohn Marino    rcsbuf_valpolish, q.v.  */
1615*86d7f5d3SJohn Marino static char *
rcsbuf_valcopy(struct rcsbuffer * rcsbuf,char * val,int polish,size_t * lenp)1616*86d7f5d3SJohn Marino rcsbuf_valcopy (struct rcsbuffer *rcsbuf, char *val, int polish, size_t *lenp)
1617*86d7f5d3SJohn Marino {
1618*86d7f5d3SJohn Marino     size_t vlen;
1619*86d7f5d3SJohn Marino     int embedded_at;
1620*86d7f5d3SJohn Marino     char *ret;
1621*86d7f5d3SJohn Marino 
1622*86d7f5d3SJohn Marino     if (val == NULL)
1623*86d7f5d3SJohn Marino     {
1624*86d7f5d3SJohn Marino 	if (lenp != NULL)
1625*86d7f5d3SJohn Marino 	    *lenp = 0;
1626*86d7f5d3SJohn Marino 	return NULL;
1627*86d7f5d3SJohn Marino     }
1628*86d7f5d3SJohn Marino 
1629*86d7f5d3SJohn Marino     vlen = rcsbuf->vlen;
1630*86d7f5d3SJohn Marino     embedded_at = rcsbuf->embedded_at < 0 ? 0 : rcsbuf->embedded_at;
1631*86d7f5d3SJohn Marino 
1632*86d7f5d3SJohn Marino     ret = xmalloc (vlen - embedded_at + 1);
1633*86d7f5d3SJohn Marino 
1634*86d7f5d3SJohn Marino     if (rcsbuf->at_string ? embedded_at == 0 : ! polish)
1635*86d7f5d3SJohn Marino     {
1636*86d7f5d3SJohn Marino 	/* No special action to take.  */
1637*86d7f5d3SJohn Marino 	memcpy (ret, val, vlen + 1);
1638*86d7f5d3SJohn Marino 	if (lenp != NULL)
1639*86d7f5d3SJohn Marino 	    *lenp = vlen;
1640*86d7f5d3SJohn Marino 	return ret;
1641*86d7f5d3SJohn Marino     }
1642*86d7f5d3SJohn Marino 
1643*86d7f5d3SJohn Marino     rcsbuf_valpolish_internal (rcsbuf, ret, val, lenp);
1644*86d7f5d3SJohn Marino     return ret;
1645*86d7f5d3SJohn Marino }
1646*86d7f5d3SJohn Marino 
1647*86d7f5d3SJohn Marino 
1648*86d7f5d3SJohn Marino 
1649*86d7f5d3SJohn Marino /* Polish the value VAL returned by rcsbuf_getkey.  The POLISH
1650*86d7f5d3SJohn Marino    parameter is non-zero if multiple embedded whitespace characters
1651*86d7f5d3SJohn Marino    should be compressed into a single whitespace character.  Note that
1652*86d7f5d3SJohn Marino    leading and trailing whitespace was already removed by
1653*86d7f5d3SJohn Marino    rcsbuf_getkey.  Within an '@' string, pairs of '@' characters are
1654*86d7f5d3SJohn Marino    compressed into a single '@' character regardless of the value of
1655*86d7f5d3SJohn Marino    POLISH.  If LENP is not NULL, set *LENP to the length of the value.  */
1656*86d7f5d3SJohn Marino static void
rcsbuf_valpolish(struct rcsbuffer * rcsbuf,char * val,int polish,size_t * lenp)1657*86d7f5d3SJohn Marino rcsbuf_valpolish (struct rcsbuffer *rcsbuf, char *val, int polish,
1658*86d7f5d3SJohn Marino 		  size_t *lenp)
1659*86d7f5d3SJohn Marino {
1660*86d7f5d3SJohn Marino     if (val == NULL)
1661*86d7f5d3SJohn Marino     {
1662*86d7f5d3SJohn Marino 	if (lenp != NULL)
1663*86d7f5d3SJohn Marino 	    *lenp= 0;
1664*86d7f5d3SJohn Marino 	return;
1665*86d7f5d3SJohn Marino     }
1666*86d7f5d3SJohn Marino 
1667*86d7f5d3SJohn Marino     if (rcsbuf->at_string ? rcsbuf->embedded_at == 0 : ! polish)
1668*86d7f5d3SJohn Marino     {
1669*86d7f5d3SJohn Marino 	/* No special action to take.  */
1670*86d7f5d3SJohn Marino 	if (lenp != NULL)
1671*86d7f5d3SJohn Marino 	    *lenp = rcsbuf->vlen;
1672*86d7f5d3SJohn Marino 	return;
1673*86d7f5d3SJohn Marino     }
1674*86d7f5d3SJohn Marino 
1675*86d7f5d3SJohn Marino     rcsbuf_valpolish_internal (rcsbuf, val, val, lenp);
1676*86d7f5d3SJohn Marino }
1677*86d7f5d3SJohn Marino 
1678*86d7f5d3SJohn Marino 
1679*86d7f5d3SJohn Marino 
1680*86d7f5d3SJohn Marino /* Internal polishing routine, called from rcsbuf_valcopy and
1681*86d7f5d3SJohn Marino    rcsbuf_valpolish.  */
1682*86d7f5d3SJohn Marino static void
rcsbuf_valpolish_internal(struct rcsbuffer * rcsbuf,char * to,const char * from,size_t * lenp)1683*86d7f5d3SJohn Marino rcsbuf_valpolish_internal (struct rcsbuffer *rcsbuf, char *to,
1684*86d7f5d3SJohn Marino 			   const char *from, size_t *lenp)
1685*86d7f5d3SJohn Marino {
1686*86d7f5d3SJohn Marino     size_t len;
1687*86d7f5d3SJohn Marino 
1688*86d7f5d3SJohn Marino     len = rcsbuf->vlen;
1689*86d7f5d3SJohn Marino 
1690*86d7f5d3SJohn Marino     if (! rcsbuf->at_string)
1691*86d7f5d3SJohn Marino     {
1692*86d7f5d3SJohn Marino 	char *orig_to;
1693*86d7f5d3SJohn Marino 	size_t clen;
1694*86d7f5d3SJohn Marino 
1695*86d7f5d3SJohn Marino 	orig_to = to;
1696*86d7f5d3SJohn Marino 
1697*86d7f5d3SJohn Marino 	for (clen = len; clen > 0; ++from, --clen)
1698*86d7f5d3SJohn Marino 	{
1699*86d7f5d3SJohn Marino 	    char c;
1700*86d7f5d3SJohn Marino 
1701*86d7f5d3SJohn Marino 	    c = *from;
1702*86d7f5d3SJohn Marino 	    if (whitespace (c))
1703*86d7f5d3SJohn Marino 	    {
1704*86d7f5d3SJohn Marino 		/* Note that we know that clen can not drop to zero
1705*86d7f5d3SJohn Marino                    while we have whitespace, because we know there is
1706*86d7f5d3SJohn Marino                    no trailing whitespace.  */
1707*86d7f5d3SJohn Marino 		while (whitespace (from[1]))
1708*86d7f5d3SJohn Marino 		{
1709*86d7f5d3SJohn Marino 		    ++from;
1710*86d7f5d3SJohn Marino 		    --clen;
1711*86d7f5d3SJohn Marino 		}
1712*86d7f5d3SJohn Marino 		c = ' ';
1713*86d7f5d3SJohn Marino 	    }
1714*86d7f5d3SJohn Marino 	    *to++ = c;
1715*86d7f5d3SJohn Marino 	}
1716*86d7f5d3SJohn Marino 
1717*86d7f5d3SJohn Marino 	*to = '\0';
1718*86d7f5d3SJohn Marino 
1719*86d7f5d3SJohn Marino 	if (lenp != NULL)
1720*86d7f5d3SJohn Marino 	    *lenp = to - orig_to;
1721*86d7f5d3SJohn Marino     }
1722*86d7f5d3SJohn Marino     else
1723*86d7f5d3SJohn Marino     {
1724*86d7f5d3SJohn Marino 	const char *orig_from;
1725*86d7f5d3SJohn Marino 	char *orig_to;
1726*86d7f5d3SJohn Marino 	int embedded_at;
1727*86d7f5d3SJohn Marino 	size_t clen;
1728*86d7f5d3SJohn Marino 
1729*86d7f5d3SJohn Marino 	orig_from = from;
1730*86d7f5d3SJohn Marino 	orig_to = to;
1731*86d7f5d3SJohn Marino 
1732*86d7f5d3SJohn Marino 	embedded_at = rcsbuf->embedded_at;
1733*86d7f5d3SJohn Marino 	assert (embedded_at > 0);
1734*86d7f5d3SJohn Marino 
1735*86d7f5d3SJohn Marino 	if (lenp != NULL)
1736*86d7f5d3SJohn Marino 	    *lenp = len - embedded_at;
1737*86d7f5d3SJohn Marino 
1738*86d7f5d3SJohn Marino 	for (clen = len; clen > 0; ++from, --clen)
1739*86d7f5d3SJohn Marino 	{
1740*86d7f5d3SJohn Marino 	    char c;
1741*86d7f5d3SJohn Marino 
1742*86d7f5d3SJohn Marino 	    c = *from;
1743*86d7f5d3SJohn Marino 	    *to++ = c;
1744*86d7f5d3SJohn Marino 	    if (c == '@')
1745*86d7f5d3SJohn Marino 	    {
1746*86d7f5d3SJohn Marino 		++from;
1747*86d7f5d3SJohn Marino 
1748*86d7f5d3SJohn Marino 		/* Sanity check.
1749*86d7f5d3SJohn Marino 		 *
1750*86d7f5d3SJohn Marino 		 * FIXME: I restored this to an abort from an assert based on
1751*86d7f5d3SJohn Marino 		 * advice from Larry Jones that asserts should not be used to
1752*86d7f5d3SJohn Marino 		 * confirm the validity of an RCS file...  This leaves two
1753*86d7f5d3SJohn Marino 		 * issues here: 1) I am uncertain that the fact that we will
1754*86d7f5d3SJohn Marino 		 * only find double '@'s hasn't already been confirmed; and:
1755*86d7f5d3SJohn Marino 		 * 2) If this is the proper place to spot the error in the RCS
1756*86d7f5d3SJohn Marino 		 * file, then we should print a much clearer error here for the
1757*86d7f5d3SJohn Marino 		 * user!!!!!!!
1758*86d7f5d3SJohn Marino 		 *
1759*86d7f5d3SJohn Marino 		 *	- DRP
1760*86d7f5d3SJohn Marino 		 */
1761*86d7f5d3SJohn Marino 		if (*from != '@' || clen == 0)
1762*86d7f5d3SJohn Marino 		    abort ();
1763*86d7f5d3SJohn Marino 
1764*86d7f5d3SJohn Marino 		--clen;
1765*86d7f5d3SJohn Marino 
1766*86d7f5d3SJohn Marino 		--embedded_at;
1767*86d7f5d3SJohn Marino 		if (embedded_at == 0)
1768*86d7f5d3SJohn Marino 		{
1769*86d7f5d3SJohn Marino 		    /* We've found all the embedded '@' characters.
1770*86d7f5d3SJohn Marino                        We can just memcpy the rest of the buffer after
1771*86d7f5d3SJohn Marino                        this '@' character.  */
1772*86d7f5d3SJohn Marino 		    if (orig_to != orig_from)
1773*86d7f5d3SJohn Marino 			memcpy (to, from + 1, clen - 1);
1774*86d7f5d3SJohn Marino 		    else
1775*86d7f5d3SJohn Marino 			memmove (to, from + 1, clen - 1);
1776*86d7f5d3SJohn Marino 		    from += clen;
1777*86d7f5d3SJohn Marino 		    to += clen - 1;
1778*86d7f5d3SJohn Marino 		    break;
1779*86d7f5d3SJohn Marino 		}
1780*86d7f5d3SJohn Marino 	    }
1781*86d7f5d3SJohn Marino 	}
1782*86d7f5d3SJohn Marino 
1783*86d7f5d3SJohn Marino 	/* Sanity check.  */
1784*86d7f5d3SJohn Marino 	assert (from == orig_from + len
1785*86d7f5d3SJohn Marino 	    && to == orig_to + (len - rcsbuf->embedded_at));
1786*86d7f5d3SJohn Marino 
1787*86d7f5d3SJohn Marino 	*to = '\0';
1788*86d7f5d3SJohn Marino     }
1789*86d7f5d3SJohn Marino }
1790*86d7f5d3SJohn Marino 
1791*86d7f5d3SJohn Marino 
1792*86d7f5d3SJohn Marino 
1793*86d7f5d3SJohn Marino #ifdef PRESERVE_PERMISSIONS_SUPPORT
1794*86d7f5d3SJohn Marino 
1795*86d7f5d3SJohn Marino /* Copy the next word from the value VALP returned by rcsbuf_getkey into a
1796*86d7f5d3SJohn Marino    memory buffer, updating VALP and returning the memory buffer.  Return
1797*86d7f5d3SJohn Marino    NULL when there are no more words. */
1798*86d7f5d3SJohn Marino 
1799*86d7f5d3SJohn Marino static char *
rcsbuf_valword(struct rcsbuffer * rcsbuf,char ** valp)1800*86d7f5d3SJohn Marino rcsbuf_valword (struct rcsbuffer *rcsbuf, char **valp)
1801*86d7f5d3SJohn Marino {
1802*86d7f5d3SJohn Marino     register const char * const my_spacetab = spacetab;
1803*86d7f5d3SJohn Marino     register char *ptr, *pat;
1804*86d7f5d3SJohn Marino     char c;
1805*86d7f5d3SJohn Marino 
1806*86d7f5d3SJohn Marino # define my_whitespace(c)	(my_spacetab[(unsigned char)c] != 0)
1807*86d7f5d3SJohn Marino 
1808*86d7f5d3SJohn Marino     if (*valp == NULL)
1809*86d7f5d3SJohn Marino 	return NULL;
1810*86d7f5d3SJohn Marino 
1811*86d7f5d3SJohn Marino     for (ptr = *valp; my_whitespace (*ptr); ++ptr) ;
1812*86d7f5d3SJohn Marino     if (*ptr == '\0')
1813*86d7f5d3SJohn Marino     {
1814*86d7f5d3SJohn Marino 	assert (ptr - *valp == rcsbuf->vlen);
1815*86d7f5d3SJohn Marino 	*valp = NULL;
1816*86d7f5d3SJohn Marino 	rcsbuf->vlen = 0;
1817*86d7f5d3SJohn Marino 	return NULL;
1818*86d7f5d3SJohn Marino     }
1819*86d7f5d3SJohn Marino 
1820*86d7f5d3SJohn Marino     /* PTR now points to the start of a value.  Find out whether it is
1821*86d7f5d3SJohn Marino        a num, an id, a string or a colon. */
1822*86d7f5d3SJohn Marino     c = *ptr;
1823*86d7f5d3SJohn Marino     if (c == ':')
1824*86d7f5d3SJohn Marino     {
1825*86d7f5d3SJohn Marino 	rcsbuf->vlen -= ++ptr - *valp;
1826*86d7f5d3SJohn Marino 	*valp = ptr;
1827*86d7f5d3SJohn Marino 	return xstrdup (":");
1828*86d7f5d3SJohn Marino     }
1829*86d7f5d3SJohn Marino 
1830*86d7f5d3SJohn Marino     if (c == '@')
1831*86d7f5d3SJohn Marino     {
1832*86d7f5d3SJohn Marino 	int embedded_at = 0;
1833*86d7f5d3SJohn Marino 	size_t vlen;
1834*86d7f5d3SJohn Marino 
1835*86d7f5d3SJohn Marino 	pat = ++ptr;
1836*86d7f5d3SJohn Marino 	while ((pat = strchr (pat, '@')) != NULL)
1837*86d7f5d3SJohn Marino 	{
1838*86d7f5d3SJohn Marino 	    if (pat[1] != '@')
1839*86d7f5d3SJohn Marino 		break;
1840*86d7f5d3SJohn Marino 	    ++embedded_at;
1841*86d7f5d3SJohn Marino 	    pat += 2;
1842*86d7f5d3SJohn Marino 	}
1843*86d7f5d3SJohn Marino 
1844*86d7f5d3SJohn Marino 	/* Here PAT points to the final '@' in the string.  */
1845*86d7f5d3SJohn Marino 	*pat++ = '\0';
1846*86d7f5d3SJohn Marino 	assert (rcsbuf->at_string);
1847*86d7f5d3SJohn Marino 	vlen = rcsbuf->vlen - (pat - *valp);
1848*86d7f5d3SJohn Marino 	rcsbuf->vlen = pat - ptr - 1;
1849*86d7f5d3SJohn Marino 	rcsbuf->embedded_at = embedded_at;
1850*86d7f5d3SJohn Marino 	ptr = rcsbuf_valcopy (rcsbuf, ptr, 0, NULL);
1851*86d7f5d3SJohn Marino 	*valp = pat;
1852*86d7f5d3SJohn Marino 	rcsbuf->vlen = vlen;
1853*86d7f5d3SJohn Marino 	if (strchr (pat, '@') == NULL)
1854*86d7f5d3SJohn Marino 	    rcsbuf->at_string = 0;
1855*86d7f5d3SJohn Marino 	else
1856*86d7f5d3SJohn Marino 	    rcsbuf->embedded_at = -1;
1857*86d7f5d3SJohn Marino 	return ptr;
1858*86d7f5d3SJohn Marino     }
1859*86d7f5d3SJohn Marino 
1860*86d7f5d3SJohn Marino     /* *PTR is neither `:', `;' nor `@', so it should be the start of a num
1861*86d7f5d3SJohn Marino        or an id.  Make sure it is not another special character. */
1862*86d7f5d3SJohn Marino     if (c == '$' || c == '.' || c == ',')
1863*86d7f5d3SJohn Marino 	error (1, 0, "invalid special character in RCS field in %s",
1864*86d7f5d3SJohn Marino 	       primary_root_inverse_translate (rcsbuf->filename));
1865*86d7f5d3SJohn Marino 
1866*86d7f5d3SJohn Marino     pat = ptr;
1867*86d7f5d3SJohn Marino     while (1)
1868*86d7f5d3SJohn Marino     {
1869*86d7f5d3SJohn Marino 	/* Legitimate ID characters are digits, dots and any `graphic
1870*86d7f5d3SJohn Marino            printing character that is not a special.' This test ought
1871*86d7f5d3SJohn Marino 	   to do the trick. */
1872*86d7f5d3SJohn Marino 	c = *++pat;
1873*86d7f5d3SJohn Marino 	if (!isprint ((unsigned char) c) ||
1874*86d7f5d3SJohn Marino 	    c == ';' || c == '$' || c == ',' || c == '@' || c == ':')
1875*86d7f5d3SJohn Marino 	    break;
1876*86d7f5d3SJohn Marino     }
1877*86d7f5d3SJohn Marino 
1878*86d7f5d3SJohn Marino     /* PAT points to the last non-id character in this word, and C is
1879*86d7f5d3SJohn Marino        the character in its memory cell.  Check to make sure that it
1880*86d7f5d3SJohn Marino        is a legitimate word delimiter -- whitespace or end. */
1881*86d7f5d3SJohn Marino     if (c != '\0' && !my_whitespace (c))
1882*86d7f5d3SJohn Marino 	error (1, 0, "invalid special character in RCS field in %s",
1883*86d7f5d3SJohn Marino 	       primary_root_inverse_translate (rcsbuf->filename));
1884*86d7f5d3SJohn Marino 
1885*86d7f5d3SJohn Marino     *pat = '\0';
1886*86d7f5d3SJohn Marino     rcsbuf->vlen -= pat - *valp;
1887*86d7f5d3SJohn Marino     *valp = pat;
1888*86d7f5d3SJohn Marino     return xstrdup (ptr);
1889*86d7f5d3SJohn Marino 
1890*86d7f5d3SJohn Marino # undef my_whitespace
1891*86d7f5d3SJohn Marino }
1892*86d7f5d3SJohn Marino 
1893*86d7f5d3SJohn Marino #endif /* PRESERVE_PERMISSIONS_SUPPORT */
1894*86d7f5d3SJohn Marino 
1895*86d7f5d3SJohn Marino 
1896*86d7f5d3SJohn Marino 
1897*86d7f5d3SJohn Marino /* Return the current position of an rcsbuf.  */
1898*86d7f5d3SJohn Marino static off_t
rcsbuf_ftello(struct rcsbuffer * rcsbuf)1899*86d7f5d3SJohn Marino rcsbuf_ftello (struct rcsbuffer *rcsbuf)
1900*86d7f5d3SJohn Marino {
1901*86d7f5d3SJohn Marino     return rcsbuf->pos + rcsbuf->ptr - rcsbuf_buffer;
1902*86d7f5d3SJohn Marino }
1903*86d7f5d3SJohn Marino 
1904*86d7f5d3SJohn Marino 
1905*86d7f5d3SJohn Marino 
1906*86d7f5d3SJohn Marino /* Return a pointer to any data buffered for RCSBUF, along with the
1907*86d7f5d3SJohn Marino    length.  */
1908*86d7f5d3SJohn Marino static void
rcsbuf_get_buffered(struct rcsbuffer * rcsbuf,char ** datap,size_t * lenp)1909*86d7f5d3SJohn Marino rcsbuf_get_buffered (struct rcsbuffer *rcsbuf, char **datap, size_t *lenp)
1910*86d7f5d3SJohn Marino {
1911*86d7f5d3SJohn Marino     *datap = rcsbuf->ptr;
1912*86d7f5d3SJohn Marino     *lenp = rcsbuf->ptrend - rcsbuf->ptr;
1913*86d7f5d3SJohn Marino }
1914*86d7f5d3SJohn Marino 
1915*86d7f5d3SJohn Marino 
1916*86d7f5d3SJohn Marino 
1917*86d7f5d3SJohn Marino /* CVS optimizes by quickly reading some header information from a
1918*86d7f5d3SJohn Marino    file.  If it decides it needs to do more with the file, it reopens
1919*86d7f5d3SJohn Marino    it.  We speed that up here by maintaining a cache of a single open
1920*86d7f5d3SJohn Marino    file, to save the time it takes to reopen the file in the common
1921*86d7f5d3SJohn Marino    case.  */
1922*86d7f5d3SJohn Marino static RCSNode *cached_rcs;
1923*86d7f5d3SJohn Marino static struct rcsbuffer cached_rcsbuf;
1924*86d7f5d3SJohn Marino 
1925*86d7f5d3SJohn Marino /* Cache RCS and RCSBUF.  This takes responsibility for closing
1926*86d7f5d3SJohn Marino    RCSBUF->FP.  */
1927*86d7f5d3SJohn Marino static void
rcsbuf_cache(RCSNode * rcs,struct rcsbuffer * rcsbuf)1928*86d7f5d3SJohn Marino rcsbuf_cache (RCSNode *rcs, struct rcsbuffer *rcsbuf)
1929*86d7f5d3SJohn Marino {
1930*86d7f5d3SJohn Marino     if (cached_rcs != NULL)
1931*86d7f5d3SJohn Marino 	rcsbuf_cache_close ();
1932*86d7f5d3SJohn Marino     cached_rcs = rcs;
1933*86d7f5d3SJohn Marino     ++rcs->refcount;
1934*86d7f5d3SJohn Marino     cached_rcsbuf = *rcsbuf;
1935*86d7f5d3SJohn Marino }
1936*86d7f5d3SJohn Marino 
1937*86d7f5d3SJohn Marino 
1938*86d7f5d3SJohn Marino 
1939*86d7f5d3SJohn Marino /* If there is anything in the cache, close it.  */
1940*86d7f5d3SJohn Marino static void
rcsbuf_cache_close(void)1941*86d7f5d3SJohn Marino rcsbuf_cache_close (void)
1942*86d7f5d3SJohn Marino {
1943*86d7f5d3SJohn Marino     if (cached_rcs != NULL)
1944*86d7f5d3SJohn Marino     {
1945*86d7f5d3SJohn Marino 	rcsbuf_close (&cached_rcsbuf);
1946*86d7f5d3SJohn Marino 	if (fclose (cached_rcsbuf.fp) != 0)
1947*86d7f5d3SJohn Marino 	    error (0, errno, "cannot close %s", cached_rcsbuf.filename);
1948*86d7f5d3SJohn Marino 	freercsnode (&cached_rcs);
1949*86d7f5d3SJohn Marino 	cached_rcs = NULL;
1950*86d7f5d3SJohn Marino     }
1951*86d7f5d3SJohn Marino }
1952*86d7f5d3SJohn Marino 
1953*86d7f5d3SJohn Marino 
1954*86d7f5d3SJohn Marino 
1955*86d7f5d3SJohn Marino /* Open an rcsbuffer for RCS, getting it from the cache if possible.
1956*86d7f5d3SJohn Marino    Set *FPP to the file, and *RCSBUFP to the rcsbuf.  The file should
1957*86d7f5d3SJohn Marino    be put at position POS.  */
1958*86d7f5d3SJohn Marino static void
rcsbuf_cache_open(RCSNode * rcs,off_t pos,FILE ** pfp,struct rcsbuffer * prcsbuf)1959*86d7f5d3SJohn Marino rcsbuf_cache_open (RCSNode *rcs, off_t pos, FILE **pfp,
1960*86d7f5d3SJohn Marino 		   struct rcsbuffer *prcsbuf)
1961*86d7f5d3SJohn Marino {
1962*86d7f5d3SJohn Marino #ifndef HAVE_MMAP
1963*86d7f5d3SJohn Marino     if (cached_rcs == rcs)
1964*86d7f5d3SJohn Marino     {
1965*86d7f5d3SJohn Marino 	if (rcsbuf_ftello (&cached_rcsbuf) != pos)
1966*86d7f5d3SJohn Marino 	{
1967*86d7f5d3SJohn Marino 	    if (fseeko (cached_rcsbuf.fp, pos, SEEK_SET) != 0)
1968*86d7f5d3SJohn Marino 		error (1, 0, "cannot fseeko RCS file %s",
1969*86d7f5d3SJohn Marino 		       cached_rcsbuf.filename);
1970*86d7f5d3SJohn Marino 	    cached_rcsbuf.ptr = rcsbuf_buffer;
1971*86d7f5d3SJohn Marino 	    cached_rcsbuf.ptrend = rcsbuf_buffer;
1972*86d7f5d3SJohn Marino 	    cached_rcsbuf.pos = pos;
1973*86d7f5d3SJohn Marino 	}
1974*86d7f5d3SJohn Marino 	*pfp = cached_rcsbuf.fp;
1975*86d7f5d3SJohn Marino 
1976*86d7f5d3SJohn Marino 	/* When RCS_parse opens a file using fopen_case, it frees the
1977*86d7f5d3SJohn Marino            filename which we cached in CACHED_RCSBUF and stores a new
1978*86d7f5d3SJohn Marino            file name in RCS->PATH.  We avoid problems here by always
1979*86d7f5d3SJohn Marino            copying the filename over.  FIXME: This is hackish.  */
1980*86d7f5d3SJohn Marino 	cached_rcsbuf.filename = rcs->path;
1981*86d7f5d3SJohn Marino 
1982*86d7f5d3SJohn Marino 	*prcsbuf = cached_rcsbuf;
1983*86d7f5d3SJohn Marino 
1984*86d7f5d3SJohn Marino 	cached_rcs = NULL;
1985*86d7f5d3SJohn Marino 
1986*86d7f5d3SJohn Marino 	/* Removing RCS from the cache removes a reference to it.  */
1987*86d7f5d3SJohn Marino 	--rcs->refcount;
1988*86d7f5d3SJohn Marino 	if (rcs->refcount <= 0)
1989*86d7f5d3SJohn Marino 	    error (1, 0, "rcsbuf_cache_open: internal error");
1990*86d7f5d3SJohn Marino     }
1991*86d7f5d3SJohn Marino     else
1992*86d7f5d3SJohn Marino     {
1993*86d7f5d3SJohn Marino #endif /* ifndef HAVE_MMAP */
1994*86d7f5d3SJohn Marino 	/* FIXME:  If these routines can be rewritten to not write to the
1995*86d7f5d3SJohn Marino 	 * rcs file buffer, there would be a considerably larger memory savings
1996*86d7f5d3SJohn Marino 	 * from using mmap since the shared file would never need be copied to
1997*86d7f5d3SJohn Marino 	 * process memory.
1998*86d7f5d3SJohn Marino 	 *
1999*86d7f5d3SJohn Marino 	 * If this happens, cached mmapped buffers would be usable, but don't
2000*86d7f5d3SJohn Marino 	 * forget to make sure rcs->pos < pos here...
2001*86d7f5d3SJohn Marino 	 */
2002*86d7f5d3SJohn Marino 	if (cached_rcs != NULL)
2003*86d7f5d3SJohn Marino 	    rcsbuf_cache_close ();
2004*86d7f5d3SJohn Marino 
2005*86d7f5d3SJohn Marino 	*pfp = CVS_FOPEN (rcs->path, FOPEN_BINARY_READ);
2006*86d7f5d3SJohn Marino 	if (*pfp == NULL)
2007*86d7f5d3SJohn Marino 	    error (1, 0, "unable to reopen `%s'", rcs->path);
2008*86d7f5d3SJohn Marino #ifndef HAVE_MMAP
2009*86d7f5d3SJohn Marino 	if (pos != 0)
2010*86d7f5d3SJohn Marino 	{
2011*86d7f5d3SJohn Marino 	    if (fseeko (*pfp, pos, SEEK_SET) != 0)
2012*86d7f5d3SJohn Marino 		error (1, 0, "cannot fseeko RCS file %s", rcs->path);
2013*86d7f5d3SJohn Marino 	}
2014*86d7f5d3SJohn Marino #endif /* ifndef HAVE_MMAP */
2015*86d7f5d3SJohn Marino 	rcsbuf_open (prcsbuf, *pfp, rcs->path, pos);
2016*86d7f5d3SJohn Marino #ifndef HAVE_MMAP
2017*86d7f5d3SJohn Marino     }
2018*86d7f5d3SJohn Marino #endif /* ifndef HAVE_MMAP */
2019*86d7f5d3SJohn Marino }
2020*86d7f5d3SJohn Marino 
2021*86d7f5d3SJohn Marino 
2022*86d7f5d3SJohn Marino 
2023*86d7f5d3SJohn Marino /*
2024*86d7f5d3SJohn Marino  * process the symbols list of the rcs file
2025*86d7f5d3SJohn Marino  */
2026*86d7f5d3SJohn Marino static void
do_symbols(List * list,char * val)2027*86d7f5d3SJohn Marino do_symbols (List *list, char *val)
2028*86d7f5d3SJohn Marino {
2029*86d7f5d3SJohn Marino     Node *p;
2030*86d7f5d3SJohn Marino     char *cp = val;
2031*86d7f5d3SJohn Marino     char *tag, *rev;
2032*86d7f5d3SJohn Marino 
2033*86d7f5d3SJohn Marino     assert (cp);
2034*86d7f5d3SJohn Marino 
2035*86d7f5d3SJohn Marino     for (;;)
2036*86d7f5d3SJohn Marino     {
2037*86d7f5d3SJohn Marino 	/* skip leading whitespace */
2038*86d7f5d3SJohn Marino 	while (whitespace (*cp))
2039*86d7f5d3SJohn Marino 	    cp++;
2040*86d7f5d3SJohn Marino 
2041*86d7f5d3SJohn Marino 	/* if we got to the end, we are done */
2042*86d7f5d3SJohn Marino 	if (*cp == '\0')
2043*86d7f5d3SJohn Marino 	    break;
2044*86d7f5d3SJohn Marino 
2045*86d7f5d3SJohn Marino 	/* split it up into tag and rev */
2046*86d7f5d3SJohn Marino 	tag = cp;
2047*86d7f5d3SJohn Marino 	cp = strchr (cp, ':');
2048*86d7f5d3SJohn Marino 	*cp++ = '\0';
2049*86d7f5d3SJohn Marino 	rev = cp;
2050*86d7f5d3SJohn Marino 	while (!whitespace (*cp) && *cp != '\0')
2051*86d7f5d3SJohn Marino 	    cp++;
2052*86d7f5d3SJohn Marino 	if (*cp != '\0')
2053*86d7f5d3SJohn Marino 	    *cp++ = '\0';
2054*86d7f5d3SJohn Marino 
2055*86d7f5d3SJohn Marino 	/* make a new node and add it to the list */
2056*86d7f5d3SJohn Marino 	p = getnode ();
2057*86d7f5d3SJohn Marino 	p->key = xstrdup (tag);
2058*86d7f5d3SJohn Marino 	p->data = xstrdup (rev);
2059*86d7f5d3SJohn Marino 	(void) addnode (list, p);
2060*86d7f5d3SJohn Marino     }
2061*86d7f5d3SJohn Marino }
2062*86d7f5d3SJohn Marino 
2063*86d7f5d3SJohn Marino 
2064*86d7f5d3SJohn Marino 
2065*86d7f5d3SJohn Marino /*
2066*86d7f5d3SJohn Marino  * process the locks list of the rcs file
2067*86d7f5d3SJohn Marino  * Like do_symbols, but hash entries are keyed backwards: i.e.
2068*86d7f5d3SJohn Marino  * an entry like `user:rev' is keyed on REV rather than on USER.
2069*86d7f5d3SJohn Marino  */
2070*86d7f5d3SJohn Marino static void
do_locks(List * list,char * val)2071*86d7f5d3SJohn Marino do_locks (List *list, char *val)
2072*86d7f5d3SJohn Marino {
2073*86d7f5d3SJohn Marino     Node *p;
2074*86d7f5d3SJohn Marino     char *cp = val;
2075*86d7f5d3SJohn Marino     char *user, *rev;
2076*86d7f5d3SJohn Marino 
2077*86d7f5d3SJohn Marino     assert (cp);
2078*86d7f5d3SJohn Marino 
2079*86d7f5d3SJohn Marino     for (;;)
2080*86d7f5d3SJohn Marino     {
2081*86d7f5d3SJohn Marino 	/* skip leading whitespace */
2082*86d7f5d3SJohn Marino 	while (whitespace (*cp))
2083*86d7f5d3SJohn Marino 	    cp++;
2084*86d7f5d3SJohn Marino 
2085*86d7f5d3SJohn Marino 	/* if we got to the end, we are done */
2086*86d7f5d3SJohn Marino 	if (*cp == '\0')
2087*86d7f5d3SJohn Marino 	    break;
2088*86d7f5d3SJohn Marino 
2089*86d7f5d3SJohn Marino 	/* split it up into user and rev */
2090*86d7f5d3SJohn Marino 	user = cp;
2091*86d7f5d3SJohn Marino 	cp = strchr (cp, ':');
2092*86d7f5d3SJohn Marino 	*cp++ = '\0';
2093*86d7f5d3SJohn Marino 	rev = cp;
2094*86d7f5d3SJohn Marino 	while (!whitespace (*cp) && *cp != '\0')
2095*86d7f5d3SJohn Marino 	    cp++;
2096*86d7f5d3SJohn Marino 	if (*cp != '\0')
2097*86d7f5d3SJohn Marino 	    *cp++ = '\0';
2098*86d7f5d3SJohn Marino 
2099*86d7f5d3SJohn Marino 	/* make a new node and add it to the list */
2100*86d7f5d3SJohn Marino 	p = getnode ();
2101*86d7f5d3SJohn Marino 	p->key = xstrdup (rev);
2102*86d7f5d3SJohn Marino 	p->data = xstrdup (user);
2103*86d7f5d3SJohn Marino 	(void) addnode (list, p);
2104*86d7f5d3SJohn Marino     }
2105*86d7f5d3SJohn Marino }
2106*86d7f5d3SJohn Marino 
2107*86d7f5d3SJohn Marino 
2108*86d7f5d3SJohn Marino 
2109*86d7f5d3SJohn Marino /*
2110*86d7f5d3SJohn Marino  * process the branches list of a revision delta
2111*86d7f5d3SJohn Marino  */
2112*86d7f5d3SJohn Marino static void
do_branches(List * list,char * val)2113*86d7f5d3SJohn Marino do_branches (List *list, char *val)
2114*86d7f5d3SJohn Marino {
2115*86d7f5d3SJohn Marino     Node *p;
2116*86d7f5d3SJohn Marino     char *cp = val;
2117*86d7f5d3SJohn Marino     char *branch;
2118*86d7f5d3SJohn Marino 
2119*86d7f5d3SJohn Marino     for (;;)
2120*86d7f5d3SJohn Marino     {
2121*86d7f5d3SJohn Marino 	/* skip leading whitespace */
2122*86d7f5d3SJohn Marino 	while (whitespace (*cp))
2123*86d7f5d3SJohn Marino 	    cp++;
2124*86d7f5d3SJohn Marino 
2125*86d7f5d3SJohn Marino 	/* if we got to the end, we are done */
2126*86d7f5d3SJohn Marino 	if (*cp == '\0')
2127*86d7f5d3SJohn Marino 	    break;
2128*86d7f5d3SJohn Marino 
2129*86d7f5d3SJohn Marino 	/* find the end of this branch */
2130*86d7f5d3SJohn Marino 	branch = cp;
2131*86d7f5d3SJohn Marino 	while (!whitespace (*cp) && *cp != '\0')
2132*86d7f5d3SJohn Marino 	    cp++;
2133*86d7f5d3SJohn Marino 	if (*cp != '\0')
2134*86d7f5d3SJohn Marino 	    *cp++ = '\0';
2135*86d7f5d3SJohn Marino 
2136*86d7f5d3SJohn Marino 	/* make a new node and add it to the list */
2137*86d7f5d3SJohn Marino 	p = getnode ();
2138*86d7f5d3SJohn Marino 	p->key = xstrdup (branch);
2139*86d7f5d3SJohn Marino 	(void) addnode (list, p);
2140*86d7f5d3SJohn Marino     }
2141*86d7f5d3SJohn Marino }
2142*86d7f5d3SJohn Marino 
2143*86d7f5d3SJohn Marino 
2144*86d7f5d3SJohn Marino 
2145*86d7f5d3SJohn Marino /*
2146*86d7f5d3SJohn Marino  * Version Number
2147*86d7f5d3SJohn Marino  *
2148*86d7f5d3SJohn Marino  * Returns the requested version number of the RCS file, satisfying tags and/or
2149*86d7f5d3SJohn Marino  * dates, and walking branches, if necessary.
2150*86d7f5d3SJohn Marino  *
2151*86d7f5d3SJohn Marino  * The result is returned; null-string if error.
2152*86d7f5d3SJohn Marino  */
2153*86d7f5d3SJohn Marino char *
RCS_getversion(RCSNode * rcs,const char * tag,const char * date,int force_tag_match,int * simple_tag)2154*86d7f5d3SJohn Marino RCS_getversion (RCSNode *rcs, const char *tag, const char *date,
2155*86d7f5d3SJohn Marino                 int force_tag_match, int *simple_tag)
2156*86d7f5d3SJohn Marino {
2157*86d7f5d3SJohn Marino     if (simple_tag != NULL)
2158*86d7f5d3SJohn Marino 	*simple_tag = 0;
2159*86d7f5d3SJohn Marino 
2160*86d7f5d3SJohn Marino     /* make sure we have something to look at... */
2161*86d7f5d3SJohn Marino     assert (rcs != NULL);
2162*86d7f5d3SJohn Marino 
2163*86d7f5d3SJohn Marino     if (tag && date)
2164*86d7f5d3SJohn Marino     {
2165*86d7f5d3SJohn Marino 	char *branch, *rev;
2166*86d7f5d3SJohn Marino 
2167*86d7f5d3SJohn Marino 	if (! RCS_nodeisbranch (rcs, tag))
2168*86d7f5d3SJohn Marino 	{
2169*86d7f5d3SJohn Marino 	    /* We can't get a particular date if the tag is not a
2170*86d7f5d3SJohn Marino                branch.  */
2171*86d7f5d3SJohn Marino 	    return NULL;
2172*86d7f5d3SJohn Marino 	}
2173*86d7f5d3SJohn Marino 
2174*86d7f5d3SJohn Marino 	/* Work out the branch.  */
2175*86d7f5d3SJohn Marino 	if (! isdigit ((unsigned char) tag[0]))
2176*86d7f5d3SJohn Marino 	    branch = RCS_whatbranch (rcs, tag);
2177*86d7f5d3SJohn Marino 	else
2178*86d7f5d3SJohn Marino 	    branch = xstrdup (tag);
2179*86d7f5d3SJohn Marino 
2180*86d7f5d3SJohn Marino 	/* Fetch the revision of branch as of date.  */
2181*86d7f5d3SJohn Marino 	rev = RCS_getdatebranch (rcs, date, branch);
2182*86d7f5d3SJohn Marino 	free (branch);
2183*86d7f5d3SJohn Marino 	return rev;
2184*86d7f5d3SJohn Marino     }
2185*86d7f5d3SJohn Marino     else if (tag)
2186*86d7f5d3SJohn Marino 	return RCS_gettag (rcs, tag, force_tag_match, simple_tag);
2187*86d7f5d3SJohn Marino     else if (date)
2188*86d7f5d3SJohn Marino 	return RCS_getdate (rcs, date, force_tag_match);
2189*86d7f5d3SJohn Marino     else
2190*86d7f5d3SJohn Marino 	return RCS_head (rcs);
2191*86d7f5d3SJohn Marino 
2192*86d7f5d3SJohn Marino }
2193*86d7f5d3SJohn Marino 
2194*86d7f5d3SJohn Marino 
2195*86d7f5d3SJohn Marino 
2196*86d7f5d3SJohn Marino /*
2197*86d7f5d3SJohn Marino  * Get existing revision number corresponding to tag or revision.
2198*86d7f5d3SJohn Marino  * Similar to RCS_gettag but less interpretation imposed.
2199*86d7f5d3SJohn Marino  * For example:
2200*86d7f5d3SJohn Marino  * -- If tag designates a magic branch, RCS_tag2rev
2201*86d7f5d3SJohn Marino  *    returns the magic branch number.
2202*86d7f5d3SJohn Marino  * -- If tag is a branch tag, returns the branch number, not
2203*86d7f5d3SJohn Marino  *    the revision of the head of the branch.
2204*86d7f5d3SJohn Marino  * If tag or revision is not valid or does not exist in file,
2205*86d7f5d3SJohn Marino  * return NULL.
2206*86d7f5d3SJohn Marino  */
2207*86d7f5d3SJohn Marino char *
RCS_tag2rev(RCSNode * rcs,char * tag)2208*86d7f5d3SJohn Marino RCS_tag2rev (RCSNode *rcs, char *tag)
2209*86d7f5d3SJohn Marino {
2210*86d7f5d3SJohn Marino     char *rev, *pa, *pb;
2211*86d7f5d3SJohn Marino     int i;
2212*86d7f5d3SJohn Marino 
2213*86d7f5d3SJohn Marino     assert (rcs != NULL);
2214*86d7f5d3SJohn Marino 
2215*86d7f5d3SJohn Marino     if (rcs->flags & PARTIAL)
2216*86d7f5d3SJohn Marino 	RCS_reparsercsfile (rcs, NULL, NULL);
2217*86d7f5d3SJohn Marino 
2218*86d7f5d3SJohn Marino     /* If a valid revision, try to look it up */
2219*86d7f5d3SJohn Marino     if ( RCS_valid_rev (tag) )
2220*86d7f5d3SJohn Marino     {
2221*86d7f5d3SJohn Marino 	/* Make a copy so we can scribble on it */
2222*86d7f5d3SJohn Marino 	rev =  xstrdup (tag);
2223*86d7f5d3SJohn Marino 
2224*86d7f5d3SJohn Marino 	/* If revision exists, return the copy */
2225*86d7f5d3SJohn Marino 	if (RCS_exist_rev (rcs, tag))
2226*86d7f5d3SJohn Marino 	    return rev;
2227*86d7f5d3SJohn Marino 
2228*86d7f5d3SJohn Marino 	/* Nope, none such. If tag is not a branch we're done. */
2229*86d7f5d3SJohn Marino 	i = numdots (rev);
2230*86d7f5d3SJohn Marino 	if ((i & 1) == 1 )
2231*86d7f5d3SJohn Marino 	{
2232*86d7f5d3SJohn Marino 	    pa = strrchr (rev, '.');
2233*86d7f5d3SJohn Marino 	    if (i == 1 || *(pa-1) != RCS_MAGIC_BRANCH || *(pa-2) != '.')
2234*86d7f5d3SJohn Marino 	    {
2235*86d7f5d3SJohn Marino 		free (rev);
2236*86d7f5d3SJohn Marino 		error (1, 0, "revision `%s' does not exist", tag);
2237*86d7f5d3SJohn Marino 	    }
2238*86d7f5d3SJohn Marino 	}
2239*86d7f5d3SJohn Marino 
2240*86d7f5d3SJohn Marino 	/* Try for a real (that is, exists in the RCS deltas) branch
2241*86d7f5d3SJohn Marino 	   (RCS_exist_rev just checks for real revisions and revisions
2242*86d7f5d3SJohn Marino 	   which have tags pointing to them).  */
2243*86d7f5d3SJohn Marino 	pa = RCS_getbranch (rcs, rev, 1);
2244*86d7f5d3SJohn Marino 	if (pa != NULL)
2245*86d7f5d3SJohn Marino 	{
2246*86d7f5d3SJohn Marino 	    free (pa);
2247*86d7f5d3SJohn Marino 	    return rev;
2248*86d7f5d3SJohn Marino 	}
2249*86d7f5d3SJohn Marino 
2250*86d7f5d3SJohn Marino        /* Tag is branch, but does not exist, try corresponding
2251*86d7f5d3SJohn Marino 	* magic branch tag.
2252*86d7f5d3SJohn Marino 	*
2253*86d7f5d3SJohn Marino 	* FIXME: assumes all magic branches are of
2254*86d7f5d3SJohn Marino 	* form "n.n.n ... .0.n".  I'll fix if somebody can
2255*86d7f5d3SJohn Marino 	* send me a method to get a magic branch tag with
2256*86d7f5d3SJohn Marino 	* the 0 in some other position -- <dan@gasboy.com>
2257*86d7f5d3SJohn Marino 	*/
2258*86d7f5d3SJohn Marino 	pa = strrchr (rev, '.');
2259*86d7f5d3SJohn Marino 	if (!pa)
2260*86d7f5d3SJohn Marino 	    /* This might happen, for instance, if an RCS file only contained
2261*86d7f5d3SJohn Marino 	     * revisions 2.x and higher, and REV == "1".
2262*86d7f5d3SJohn Marino 	     */
2263*86d7f5d3SJohn Marino 	    error (1, 0, "revision `%s' does not exist", tag);
2264*86d7f5d3SJohn Marino 
2265*86d7f5d3SJohn Marino 	*pa++ = 0;
2266*86d7f5d3SJohn Marino 	pb = Xasprintf ("%s.%d.%s", rev, RCS_MAGIC_BRANCH, pa);
2267*86d7f5d3SJohn Marino 	free (rev);
2268*86d7f5d3SJohn Marino 	rev = pb;
2269*86d7f5d3SJohn Marino 	if (RCS_exist_rev (rcs, rev))
2270*86d7f5d3SJohn Marino 	    return rev;
2271*86d7f5d3SJohn Marino 	error (1, 0, "revision `%s' does not exist", tag);
2272*86d7f5d3SJohn Marino     }
2273*86d7f5d3SJohn Marino 
2274*86d7f5d3SJohn Marino 
2275*86d7f5d3SJohn Marino     RCS_check_tag (tag); /* exit if not a valid tag */
2276*86d7f5d3SJohn Marino 
2277*86d7f5d3SJohn Marino     /* If tag is "HEAD", special case to get head RCS revision */
2278*86d7f5d3SJohn Marino     if (tag && STREQ (tag, TAG_HEAD))
2279*86d7f5d3SJohn Marino         return RCS_head (rcs);
2280*86d7f5d3SJohn Marino 
2281*86d7f5d3SJohn Marino     /* If valid tag let translate_symtag say yea or nay. */
2282*86d7f5d3SJohn Marino     rev = translate_symtag (rcs, tag);
2283*86d7f5d3SJohn Marino 
2284*86d7f5d3SJohn Marino     if (rev)
2285*86d7f5d3SJohn Marino         return rev;
2286*86d7f5d3SJohn Marino 
2287*86d7f5d3SJohn Marino     /* Trust the caller to print warnings. */
2288*86d7f5d3SJohn Marino     return NULL;
2289*86d7f5d3SJohn Marino }
2290*86d7f5d3SJohn Marino 
2291*86d7f5d3SJohn Marino 
2292*86d7f5d3SJohn Marino 
2293*86d7f5d3SJohn Marino /*
2294*86d7f5d3SJohn Marino  * Find the revision for a specific tag.
2295*86d7f5d3SJohn Marino  * If force_tag_match is set, return NULL if an exact match is not
2296*86d7f5d3SJohn Marino  * possible otherwise return RCS_head ().  We are careful to look for
2297*86d7f5d3SJohn Marino  * and handle "magic" revisions specially.
2298*86d7f5d3SJohn Marino  *
2299*86d7f5d3SJohn Marino  * If the matched tag is a branch tag, find the head of the branch.
2300*86d7f5d3SJohn Marino  *
2301*86d7f5d3SJohn Marino  * Returns pointer to newly malloc'd string, or NULL.
2302*86d7f5d3SJohn Marino  */
2303*86d7f5d3SJohn Marino char *
RCS_gettag(RCSNode * rcs,const char * symtag,int force_tag_match,int * simple_tag)2304*86d7f5d3SJohn Marino RCS_gettag (RCSNode *rcs, const char *symtag, int force_tag_match,
2305*86d7f5d3SJohn Marino             int *simple_tag)
2306*86d7f5d3SJohn Marino {
2307*86d7f5d3SJohn Marino     char *tag;
2308*86d7f5d3SJohn Marino 
2309*86d7f5d3SJohn Marino     if (simple_tag != NULL)
2310*86d7f5d3SJohn Marino 	*simple_tag = 0;
2311*86d7f5d3SJohn Marino 
2312*86d7f5d3SJohn Marino     /* make sure we have something to look at... */
2313*86d7f5d3SJohn Marino     assert (rcs != NULL);
2314*86d7f5d3SJohn Marino 
2315*86d7f5d3SJohn Marino     /* XXX this is probably not necessary, --jtc */
2316*86d7f5d3SJohn Marino     if (rcs->flags & PARTIAL)
2317*86d7f5d3SJohn Marino 	RCS_reparsercsfile (rcs, NULL, NULL);
2318*86d7f5d3SJohn Marino 
2319*86d7f5d3SJohn Marino     /* If symtag is "HEAD", special case to get head RCS revision */
2320*86d7f5d3SJohn Marino     if (symtag && STREQ (symtag, TAG_HEAD))
2321*86d7f5d3SJohn Marino #if 0 /* This #if 0 is only in the Cygnus code.  Why?  Death support?  */
2322*86d7f5d3SJohn Marino 	if (force_tag_match && (rcs->flags & VALID) && (rcs->flags & INATTIC))
2323*86d7f5d3SJohn Marino 	    return NULL;	/* head request for removed file */
2324*86d7f5d3SJohn Marino 	else
2325*86d7f5d3SJohn Marino #endif
2326*86d7f5d3SJohn Marino 	    return RCS_head (rcs);
2327*86d7f5d3SJohn Marino 
2328*86d7f5d3SJohn Marino     if (!isdigit ((unsigned char) symtag[0]))
2329*86d7f5d3SJohn Marino     {
2330*86d7f5d3SJohn Marino 	char *version;
2331*86d7f5d3SJohn Marino 
2332*86d7f5d3SJohn Marino 	/* If we got a symbolic tag, resolve it to a numeric */
2333*86d7f5d3SJohn Marino 	version = translate_symtag (rcs, symtag);
2334*86d7f5d3SJohn Marino 	if (version != NULL)
2335*86d7f5d3SJohn Marino 	{
2336*86d7f5d3SJohn Marino 	    int dots;
2337*86d7f5d3SJohn Marino 	    char *magic, *branch, *cp;
2338*86d7f5d3SJohn Marino 
2339*86d7f5d3SJohn Marino 	    tag = version;
2340*86d7f5d3SJohn Marino 
2341*86d7f5d3SJohn Marino 	    /*
2342*86d7f5d3SJohn Marino 	     * If this is a magic revision, we turn it into either its
2343*86d7f5d3SJohn Marino 	     * physical branch equivalent (if one exists) or into
2344*86d7f5d3SJohn Marino 	     * its base revision, which we assume exists.
2345*86d7f5d3SJohn Marino 	     */
2346*86d7f5d3SJohn Marino 	    dots = numdots (tag);
2347*86d7f5d3SJohn Marino 	    if (dots > 2 && (dots & 1) != 0)
2348*86d7f5d3SJohn Marino 	    {
2349*86d7f5d3SJohn Marino 		branch = strrchr (tag, '.');
2350*86d7f5d3SJohn Marino 		cp = branch++ - 1;
2351*86d7f5d3SJohn Marino 		while (*cp != '.')
2352*86d7f5d3SJohn Marino 		    cp--;
2353*86d7f5d3SJohn Marino 
2354*86d7f5d3SJohn Marino 		/* see if we have .magic-branch. (".0.") */
2355*86d7f5d3SJohn Marino 		magic = xmalloc (strlen (tag) + 1);
2356*86d7f5d3SJohn Marino 		(void) sprintf (magic, ".%d.", RCS_MAGIC_BRANCH);
2357*86d7f5d3SJohn Marino 		if (strncmp (magic, cp, strlen (magic)) == 0)
2358*86d7f5d3SJohn Marino 		{
2359*86d7f5d3SJohn Marino 		    /* it's magic.  See if the branch exists */
2360*86d7f5d3SJohn Marino 		    *cp = '\0';		/* turn it into a revision */
2361*86d7f5d3SJohn Marino 		    (void) sprintf (magic, "%s.%s", tag, branch);
2362*86d7f5d3SJohn Marino 		    branch = RCS_getbranch (rcs, magic, 1);
2363*86d7f5d3SJohn Marino 		    free (magic);
2364*86d7f5d3SJohn Marino 		    if (branch != NULL)
2365*86d7f5d3SJohn Marino 		    {
2366*86d7f5d3SJohn Marino 			free (tag);
2367*86d7f5d3SJohn Marino 			return branch;
2368*86d7f5d3SJohn Marino 		    }
2369*86d7f5d3SJohn Marino 		    return tag;
2370*86d7f5d3SJohn Marino 		}
2371*86d7f5d3SJohn Marino 		free (magic);
2372*86d7f5d3SJohn Marino 	    }
2373*86d7f5d3SJohn Marino 	}
2374*86d7f5d3SJohn Marino 	else
2375*86d7f5d3SJohn Marino 	{
2376*86d7f5d3SJohn Marino 	    /* The tag wasn't there, so return the head or NULL */
2377*86d7f5d3SJohn Marino 	    if (force_tag_match)
2378*86d7f5d3SJohn Marino 		return NULL;
2379*86d7f5d3SJohn Marino 	    else
2380*86d7f5d3SJohn Marino 		return RCS_head (rcs);
2381*86d7f5d3SJohn Marino 	}
2382*86d7f5d3SJohn Marino     }
2383*86d7f5d3SJohn Marino     else
2384*86d7f5d3SJohn Marino 	tag = xstrdup (symtag);
2385*86d7f5d3SJohn Marino 
2386*86d7f5d3SJohn Marino     /* tag is always allocated and numeric now.  */
2387*86d7f5d3SJohn Marino 
2388*86d7f5d3SJohn Marino     /*
2389*86d7f5d3SJohn Marino      * numeric tag processing:
2390*86d7f5d3SJohn Marino      *		1) revision number - just return it
2391*86d7f5d3SJohn Marino      *		2) branch number   - find head of branch
2392*86d7f5d3SJohn Marino      */
2393*86d7f5d3SJohn Marino 
2394*86d7f5d3SJohn Marino     /* strip trailing dots */
2395*86d7f5d3SJohn Marino     while (tag[strlen (tag) - 1] == '.')
2396*86d7f5d3SJohn Marino 	tag[strlen (tag) - 1] = '\0';
2397*86d7f5d3SJohn Marino 
2398*86d7f5d3SJohn Marino     if ((numdots (tag) & 1) == 0)
2399*86d7f5d3SJohn Marino     {
2400*86d7f5d3SJohn Marino 	char *branch;
2401*86d7f5d3SJohn Marino 
2402*86d7f5d3SJohn Marino 	/* we have a branch tag, so we need to walk the branch */
2403*86d7f5d3SJohn Marino 	branch = RCS_getbranch (rcs, tag, force_tag_match);
2404*86d7f5d3SJohn Marino 	free (tag);
2405*86d7f5d3SJohn Marino 	return branch;
2406*86d7f5d3SJohn Marino     }
2407*86d7f5d3SJohn Marino     else
2408*86d7f5d3SJohn Marino     {
2409*86d7f5d3SJohn Marino 	Node *p;
2410*86d7f5d3SJohn Marino 
2411*86d7f5d3SJohn Marino 	/* we have a revision tag, so make sure it exists */
2412*86d7f5d3SJohn Marino 	p = findnode (rcs->versions, tag);
2413*86d7f5d3SJohn Marino 	if (p != NULL)
2414*86d7f5d3SJohn Marino 	{
2415*86d7f5d3SJohn Marino 	    /* We have found a numeric revision for the revision tag.
2416*86d7f5d3SJohn Marino 	       To support expanding the RCS keyword Name, if
2417*86d7f5d3SJohn Marino 	       SIMPLE_TAG is not NULL, tell the the caller that this
2418*86d7f5d3SJohn Marino 	       is a simple tag which co will recognize.  FIXME: Are
2419*86d7f5d3SJohn Marino 	       there other cases in which we should set this?  In
2420*86d7f5d3SJohn Marino 	       particular, what if we expand RCS keywords internally
2421*86d7f5d3SJohn Marino 	       without calling co?  */
2422*86d7f5d3SJohn Marino 	    if (simple_tag != NULL)
2423*86d7f5d3SJohn Marino 		*simple_tag = 1;
2424*86d7f5d3SJohn Marino 	    return tag;
2425*86d7f5d3SJohn Marino 	}
2426*86d7f5d3SJohn Marino 	else
2427*86d7f5d3SJohn Marino 	{
2428*86d7f5d3SJohn Marino 	    /* The revision wasn't there, so return the head or NULL */
2429*86d7f5d3SJohn Marino 	    free (tag);
2430*86d7f5d3SJohn Marino 	    if (force_tag_match)
2431*86d7f5d3SJohn Marino 		return NULL;
2432*86d7f5d3SJohn Marino 	    else
2433*86d7f5d3SJohn Marino 		return RCS_head (rcs);
2434*86d7f5d3SJohn Marino 	}
2435*86d7f5d3SJohn Marino     }
2436*86d7f5d3SJohn Marino }
2437*86d7f5d3SJohn Marino 
2438*86d7f5d3SJohn Marino 
2439*86d7f5d3SJohn Marino 
2440*86d7f5d3SJohn Marino /*
2441*86d7f5d3SJohn Marino  * Return a "magic" revision as a virtual branch off of REV for the RCS file.
2442*86d7f5d3SJohn Marino  * A "magic" revision is one which is unique in the RCS file.  By unique, I
2443*86d7f5d3SJohn Marino  * mean we return a revision which:
2444*86d7f5d3SJohn Marino  *	- has a branch of 0 (see rcs.h RCS_MAGIC_BRANCH)
2445*86d7f5d3SJohn Marino  *	- has a revision component which is not an existing branch off REV
2446*86d7f5d3SJohn Marino  *	- has a revision component which is not an existing magic revision
2447*86d7f5d3SJohn Marino  *	- is an even-numbered revision, to avoid conflicts with vendor branches
2448*86d7f5d3SJohn Marino  * The first point is what makes it "magic".
2449*86d7f5d3SJohn Marino  *
2450*86d7f5d3SJohn Marino  * As an example, if we pass in 1.37 as REV, we will look for an existing
2451*86d7f5d3SJohn Marino  * branch called 1.37.2.  If it did not exist, we would look for an
2452*86d7f5d3SJohn Marino  * existing symbolic tag with a numeric part equal to 1.37.0.2.  If that
2453*86d7f5d3SJohn Marino  * didn't exist, then we know that the 1.37.2 branch can be reserved by
2454*86d7f5d3SJohn Marino  * creating a symbolic tag with 1.37.0.2 as the numeric part.
2455*86d7f5d3SJohn Marino  *
2456*86d7f5d3SJohn Marino  * This allows us to fork development with very little overhead -- just a
2457*86d7f5d3SJohn Marino  * symbolic tag is used in the RCS file.  When a commit is done, a physical
2458*86d7f5d3SJohn Marino  * branch is dynamically created to hold the new revision.
2459*86d7f5d3SJohn Marino  *
2460*86d7f5d3SJohn Marino  * Note: We assume that REV is an RCS revision and not a branch number.
2461*86d7f5d3SJohn Marino  */
2462*86d7f5d3SJohn Marino static char *check_rev;
2463*86d7f5d3SJohn Marino char *
RCS_magicrev(RCSNode * rcs,char * rev)2464*86d7f5d3SJohn Marino RCS_magicrev (RCSNode *rcs, char *rev)
2465*86d7f5d3SJohn Marino {
2466*86d7f5d3SJohn Marino     int rev_num;
2467*86d7f5d3SJohn Marino     char *xrev, *test_branch, *local_branch_num;
2468*86d7f5d3SJohn Marino 
2469*86d7f5d3SJohn Marino     xrev = xmalloc (strlen (rev) + 14); /* enough for .0.number */
2470*86d7f5d3SJohn Marino     check_rev = xrev;
2471*86d7f5d3SJohn Marino 
2472*86d7f5d3SJohn Marino     local_branch_num = getenv("CVS_LOCAL_BRANCH_NUM");
2473*86d7f5d3SJohn Marino     if (local_branch_num)
2474*86d7f5d3SJohn Marino     {
2475*86d7f5d3SJohn Marino       rev_num = atoi(local_branch_num);
2476*86d7f5d3SJohn Marino       if (rev_num < 2)
2477*86d7f5d3SJohn Marino 	rev_num = 2;
2478*86d7f5d3SJohn Marino       else
2479*86d7f5d3SJohn Marino 	rev_num &= ~1;
2480*86d7f5d3SJohn Marino     }
2481*86d7f5d3SJohn Marino     else
2482*86d7f5d3SJohn Marino       rev_num = 2;
2483*86d7f5d3SJohn Marino 
2484*86d7f5d3SJohn Marino     /* only look at even numbered branches */
2485*86d7f5d3SJohn Marino     for ( ; ; rev_num += 2)
2486*86d7f5d3SJohn Marino     {
2487*86d7f5d3SJohn Marino 	/* see if the physical branch exists */
2488*86d7f5d3SJohn Marino 	(void) sprintf (xrev, "%s.%d", rev, rev_num);
2489*86d7f5d3SJohn Marino 	test_branch = RCS_getbranch (rcs, xrev, 1);
2490*86d7f5d3SJohn Marino 	if (test_branch != NULL)	/* it did, so keep looking */
2491*86d7f5d3SJohn Marino 	{
2492*86d7f5d3SJohn Marino 	    free (test_branch);
2493*86d7f5d3SJohn Marino 	    continue;
2494*86d7f5d3SJohn Marino 	}
2495*86d7f5d3SJohn Marino 
2496*86d7f5d3SJohn Marino 	/* now, create a "magic" revision */
2497*86d7f5d3SJohn Marino 	(void) sprintf (xrev, "%s.%d.%d", rev, RCS_MAGIC_BRANCH, rev_num);
2498*86d7f5d3SJohn Marino 
2499*86d7f5d3SJohn Marino 	/* walk the symbols list to see if a magic one already exists */
2500*86d7f5d3SJohn Marino 	if (walklist (RCS_symbols(rcs), checkmagic_proc, NULL) != 0)
2501*86d7f5d3SJohn Marino 	    continue;
2502*86d7f5d3SJohn Marino 
2503*86d7f5d3SJohn Marino 	/* we found a free magic branch.  Claim it as ours */
2504*86d7f5d3SJohn Marino 	return xrev;
2505*86d7f5d3SJohn Marino     }
2506*86d7f5d3SJohn Marino }
2507*86d7f5d3SJohn Marino 
2508*86d7f5d3SJohn Marino 
2509*86d7f5d3SJohn Marino 
2510*86d7f5d3SJohn Marino /*
2511*86d7f5d3SJohn Marino  * walklist proc to look for a match in the symbols list.
2512*86d7f5d3SJohn Marino  * Returns 0 if the symbol does not match, 1 if it does.
2513*86d7f5d3SJohn Marino  */
2514*86d7f5d3SJohn Marino static int
checkmagic_proc(Node * p,void * closure)2515*86d7f5d3SJohn Marino checkmagic_proc (Node *p, void *closure)
2516*86d7f5d3SJohn Marino {
2517*86d7f5d3SJohn Marino     if (STREQ (check_rev, p->data))
2518*86d7f5d3SJohn Marino 	return 1;
2519*86d7f5d3SJohn Marino     else
2520*86d7f5d3SJohn Marino 	return 0;
2521*86d7f5d3SJohn Marino }
2522*86d7f5d3SJohn Marino 
2523*86d7f5d3SJohn Marino 
2524*86d7f5d3SJohn Marino 
2525*86d7f5d3SJohn Marino /*
2526*86d7f5d3SJohn Marino  * Given an RCSNode, returns non-zero if the specified revision number
2527*86d7f5d3SJohn Marino  * or symbolic tag resolves to a "branch" within the rcs file.
2528*86d7f5d3SJohn Marino  *
2529*86d7f5d3SJohn Marino  * FIXME: this is the same as RCS_nodeisbranch except for the special
2530*86d7f5d3SJohn Marino  *        case for handling a null rcsnode.
2531*86d7f5d3SJohn Marino  */
2532*86d7f5d3SJohn Marino int
RCS_isbranch(RCSNode * rcs,const char * rev)2533*86d7f5d3SJohn Marino RCS_isbranch (RCSNode *rcs, const char *rev)
2534*86d7f5d3SJohn Marino {
2535*86d7f5d3SJohn Marino     /* numeric revisions are easy -- even number of dots is a branch */
2536*86d7f5d3SJohn Marino     if (isdigit ((unsigned char) *rev))
2537*86d7f5d3SJohn Marino 	return (numdots (rev) & 1) == 0;
2538*86d7f5d3SJohn Marino 
2539*86d7f5d3SJohn Marino     /* assume a revision if you can't find the RCS info */
2540*86d7f5d3SJohn Marino     if (rcs == NULL)
2541*86d7f5d3SJohn Marino 	return 0;
2542*86d7f5d3SJohn Marino 
2543*86d7f5d3SJohn Marino     /* now, look for a match in the symbols list */
2544*86d7f5d3SJohn Marino     return RCS_nodeisbranch (rcs, rev);
2545*86d7f5d3SJohn Marino }
2546*86d7f5d3SJohn Marino 
2547*86d7f5d3SJohn Marino 
2548*86d7f5d3SJohn Marino 
2549*86d7f5d3SJohn Marino /*
2550*86d7f5d3SJohn Marino  * Given an RCSNode, returns non-zero if the specified revision number
2551*86d7f5d3SJohn Marino  * or symbolic tag resolves to a "branch" within the rcs file.  We do
2552*86d7f5d3SJohn Marino  * take into account any magic branches as well.
2553*86d7f5d3SJohn Marino  */
2554*86d7f5d3SJohn Marino int
RCS_nodeisbranch(RCSNode * rcs,const char * rev)2555*86d7f5d3SJohn Marino RCS_nodeisbranch (RCSNode *rcs, const char *rev)
2556*86d7f5d3SJohn Marino {
2557*86d7f5d3SJohn Marino     int dots;
2558*86d7f5d3SJohn Marino     char *version;
2559*86d7f5d3SJohn Marino 
2560*86d7f5d3SJohn Marino     assert (rcs != NULL);
2561*86d7f5d3SJohn Marino 
2562*86d7f5d3SJohn Marino     /* numeric revisions are easy -- even number of dots is a branch */
2563*86d7f5d3SJohn Marino     if (isdigit ((unsigned char) *rev))
2564*86d7f5d3SJohn Marino 	return (numdots (rev) & 1) == 0;
2565*86d7f5d3SJohn Marino 
2566*86d7f5d3SJohn Marino     version = translate_symtag (rcs, rev);
2567*86d7f5d3SJohn Marino     if (version == NULL)
2568*86d7f5d3SJohn Marino 	return 0;
2569*86d7f5d3SJohn Marino     dots = numdots (version);
2570*86d7f5d3SJohn Marino     if ((dots & 1) == 0)
2571*86d7f5d3SJohn Marino     {
2572*86d7f5d3SJohn Marino 	free (version);
2573*86d7f5d3SJohn Marino 	return 1;
2574*86d7f5d3SJohn Marino     }
2575*86d7f5d3SJohn Marino 
2576*86d7f5d3SJohn Marino     /* got a symbolic tag match, but it's not a branch; see if it's magic */
2577*86d7f5d3SJohn Marino     if (dots > 2)
2578*86d7f5d3SJohn Marino     {
2579*86d7f5d3SJohn Marino 	char *magic;
2580*86d7f5d3SJohn Marino 	char *branch = strrchr (version, '.');
2581*86d7f5d3SJohn Marino 	char *cp = branch - 1;
2582*86d7f5d3SJohn Marino 	while (*cp != '.')
2583*86d7f5d3SJohn Marino 	    cp--;
2584*86d7f5d3SJohn Marino 
2585*86d7f5d3SJohn Marino 	/* see if we have .magic-branch. (".0.") */
2586*86d7f5d3SJohn Marino 	magic = Xasprintf (".%d.", RCS_MAGIC_BRANCH);
2587*86d7f5d3SJohn Marino 	if (strncmp (magic, cp, strlen (magic)) == 0)
2588*86d7f5d3SJohn Marino 	{
2589*86d7f5d3SJohn Marino 	    free (magic);
2590*86d7f5d3SJohn Marino 	    free (version);
2591*86d7f5d3SJohn Marino 	    return 1;
2592*86d7f5d3SJohn Marino 	}
2593*86d7f5d3SJohn Marino 	free (magic);
2594*86d7f5d3SJohn Marino     }
2595*86d7f5d3SJohn Marino     free (version);
2596*86d7f5d3SJohn Marino     return 0;
2597*86d7f5d3SJohn Marino }
2598*86d7f5d3SJohn Marino 
2599*86d7f5d3SJohn Marino 
2600*86d7f5d3SJohn Marino 
2601*86d7f5d3SJohn Marino /*
2602*86d7f5d3SJohn Marino  * Returns a pointer to malloc'ed memory which contains the branch
2603*86d7f5d3SJohn Marino  * for the specified *symbolic* tag.  Magic branches are handled correctly.
2604*86d7f5d3SJohn Marino  */
2605*86d7f5d3SJohn Marino char *
RCS_whatbranch(RCSNode * rcs,const char * rev)2606*86d7f5d3SJohn Marino RCS_whatbranch (RCSNode *rcs, const char *rev)
2607*86d7f5d3SJohn Marino {
2608*86d7f5d3SJohn Marino     char *version;
2609*86d7f5d3SJohn Marino     int dots;
2610*86d7f5d3SJohn Marino 
2611*86d7f5d3SJohn Marino     /* assume no branch if you can't find the RCS info */
2612*86d7f5d3SJohn Marino     if (rcs == NULL)
2613*86d7f5d3SJohn Marino 	return NULL;
2614*86d7f5d3SJohn Marino 
2615*86d7f5d3SJohn Marino     /* now, look for a match in the symbols list */
2616*86d7f5d3SJohn Marino     version = translate_symtag (rcs, rev);
2617*86d7f5d3SJohn Marino     if (version == NULL)
2618*86d7f5d3SJohn Marino 	return NULL;
2619*86d7f5d3SJohn Marino     dots = numdots (version);
2620*86d7f5d3SJohn Marino     if ((dots & 1) == 0)
2621*86d7f5d3SJohn Marino 	return version;
2622*86d7f5d3SJohn Marino 
2623*86d7f5d3SJohn Marino     /* got a symbolic tag match, but it's not a branch; see if it's magic */
2624*86d7f5d3SJohn Marino     if (dots > 2)
2625*86d7f5d3SJohn Marino     {
2626*86d7f5d3SJohn Marino 	char *magic;
2627*86d7f5d3SJohn Marino 	char *branch = strrchr (version, '.');
2628*86d7f5d3SJohn Marino 	char *cp = branch++ - 1;
2629*86d7f5d3SJohn Marino 	while (*cp != '.')
2630*86d7f5d3SJohn Marino 	    cp--;
2631*86d7f5d3SJohn Marino 
2632*86d7f5d3SJohn Marino 	/* see if we have .magic-branch. (".0.") */
2633*86d7f5d3SJohn Marino 	magic = xmalloc (strlen (version) + 1);
2634*86d7f5d3SJohn Marino 	(void) sprintf (magic, ".%d.", RCS_MAGIC_BRANCH);
2635*86d7f5d3SJohn Marino 	if (strncmp (magic, cp, strlen (magic)) == 0)
2636*86d7f5d3SJohn Marino 	{
2637*86d7f5d3SJohn Marino 	    /* yep.  it's magic.  now, construct the real branch */
2638*86d7f5d3SJohn Marino 	    *cp = '\0';			/* turn it into a revision */
2639*86d7f5d3SJohn Marino 	    (void) sprintf (magic, "%s.%s", version, branch);
2640*86d7f5d3SJohn Marino 	    free (version);
2641*86d7f5d3SJohn Marino 	    return magic;
2642*86d7f5d3SJohn Marino 	}
2643*86d7f5d3SJohn Marino 	free (magic);
2644*86d7f5d3SJohn Marino     }
2645*86d7f5d3SJohn Marino     free (version);
2646*86d7f5d3SJohn Marino     return NULL;
2647*86d7f5d3SJohn Marino }
2648*86d7f5d3SJohn Marino 
2649*86d7f5d3SJohn Marino 
2650*86d7f5d3SJohn Marino 
2651*86d7f5d3SJohn Marino /*
2652*86d7f5d3SJohn Marino  * Get the head of the specified branch.  If the branch does not exist,
2653*86d7f5d3SJohn Marino  * return NULL or RCS_head depending on force_tag_match.
2654*86d7f5d3SJohn Marino  * Returns NULL or a newly malloc'd string.
2655*86d7f5d3SJohn Marino  */
2656*86d7f5d3SJohn Marino char *
RCS_getbranch(RCSNode * rcs,const char * tag,int force_tag_match)2657*86d7f5d3SJohn Marino RCS_getbranch (RCSNode *rcs, const char *tag, int force_tag_match)
2658*86d7f5d3SJohn Marino {
2659*86d7f5d3SJohn Marino     Node *p, *head;
2660*86d7f5d3SJohn Marino     RCSVers *vn;
2661*86d7f5d3SJohn Marino     char *xtag;
2662*86d7f5d3SJohn Marino     char *nextvers;
2663*86d7f5d3SJohn Marino     char *cp;
2664*86d7f5d3SJohn Marino 
2665*86d7f5d3SJohn Marino     /* make sure we have something to look at... */
2666*86d7f5d3SJohn Marino     assert (rcs != NULL);
2667*86d7f5d3SJohn Marino 
2668*86d7f5d3SJohn Marino     if (rcs->flags & PARTIAL)
2669*86d7f5d3SJohn Marino 	RCS_reparsercsfile (rcs, NULL, NULL);
2670*86d7f5d3SJohn Marino 
2671*86d7f5d3SJohn Marino     /* find out if the tag contains a dot, or is on the trunk */
2672*86d7f5d3SJohn Marino     cp = strrchr (tag, '.');
2673*86d7f5d3SJohn Marino 
2674*86d7f5d3SJohn Marino     /* trunk processing is the special case */
2675*86d7f5d3SJohn Marino     if (cp == NULL)
2676*86d7f5d3SJohn Marino     {
2677*86d7f5d3SJohn Marino 	xtag = Xasprintf ("%s.", tag);
2678*86d7f5d3SJohn Marino 	for (cp = rcs->head; cp != NULL;)
2679*86d7f5d3SJohn Marino 	{
2680*86d7f5d3SJohn Marino 	    if (strncmp (xtag, cp, strlen (xtag)) == 0)
2681*86d7f5d3SJohn Marino 		break;
2682*86d7f5d3SJohn Marino 	    p = findnode (rcs->versions, cp);
2683*86d7f5d3SJohn Marino 	    if (p == NULL)
2684*86d7f5d3SJohn Marino 	    {
2685*86d7f5d3SJohn Marino 		free (xtag);
2686*86d7f5d3SJohn Marino 		if (force_tag_match)
2687*86d7f5d3SJohn Marino 		    return NULL;
2688*86d7f5d3SJohn Marino 		else
2689*86d7f5d3SJohn Marino 		    return RCS_head (rcs);
2690*86d7f5d3SJohn Marino 	    }
2691*86d7f5d3SJohn Marino 	    vn = p->data;
2692*86d7f5d3SJohn Marino 	    cp = vn->next;
2693*86d7f5d3SJohn Marino 	}
2694*86d7f5d3SJohn Marino 	free (xtag);
2695*86d7f5d3SJohn Marino 	if (cp == NULL)
2696*86d7f5d3SJohn Marino 	{
2697*86d7f5d3SJohn Marino 	    if (force_tag_match)
2698*86d7f5d3SJohn Marino 		return NULL;
2699*86d7f5d3SJohn Marino 	    else
2700*86d7f5d3SJohn Marino 		return RCS_head (rcs);
2701*86d7f5d3SJohn Marino 	}
2702*86d7f5d3SJohn Marino 	return xstrdup (cp);
2703*86d7f5d3SJohn Marino     }
2704*86d7f5d3SJohn Marino 
2705*86d7f5d3SJohn Marino     /* if it had a `.', terminate the string so we have the base revision */
2706*86d7f5d3SJohn Marino     *cp = '\0';
2707*86d7f5d3SJohn Marino 
2708*86d7f5d3SJohn Marino     /* look up the revision this branch is based on */
2709*86d7f5d3SJohn Marino     p = findnode (rcs->versions, tag);
2710*86d7f5d3SJohn Marino 
2711*86d7f5d3SJohn Marino     /* put the . back so we have the branch again */
2712*86d7f5d3SJohn Marino     *cp = '.';
2713*86d7f5d3SJohn Marino 
2714*86d7f5d3SJohn Marino     if (p == NULL)
2715*86d7f5d3SJohn Marino     {
2716*86d7f5d3SJohn Marino 	/* if the base revision didn't exist, return head or NULL */
2717*86d7f5d3SJohn Marino 	if (force_tag_match)
2718*86d7f5d3SJohn Marino 	    return NULL;
2719*86d7f5d3SJohn Marino 	else
2720*86d7f5d3SJohn Marino 	    return RCS_head (rcs);
2721*86d7f5d3SJohn Marino     }
2722*86d7f5d3SJohn Marino 
2723*86d7f5d3SJohn Marino     /* find the first element of the branch we are looking for */
2724*86d7f5d3SJohn Marino     vn = p->data;
2725*86d7f5d3SJohn Marino     if (vn->branches == NULL)
2726*86d7f5d3SJohn Marino 	return NULL;
2727*86d7f5d3SJohn Marino     xtag = Xasprintf ("%s.", tag);
2728*86d7f5d3SJohn Marino     head = vn->branches->list;
2729*86d7f5d3SJohn Marino     for (p = head->next; p != head; p = p->next)
2730*86d7f5d3SJohn Marino 	if (strncmp (p->key, xtag, strlen (xtag)) == 0)
2731*86d7f5d3SJohn Marino 	    break;
2732*86d7f5d3SJohn Marino     free (xtag);
2733*86d7f5d3SJohn Marino 
2734*86d7f5d3SJohn Marino     if (p == head)
2735*86d7f5d3SJohn Marino     {
2736*86d7f5d3SJohn Marino 	/* we didn't find a match so return head or NULL */
2737*86d7f5d3SJohn Marino 	if (force_tag_match)
2738*86d7f5d3SJohn Marino 	    return NULL;
2739*86d7f5d3SJohn Marino 	else
2740*86d7f5d3SJohn Marino 	    return RCS_head (rcs);
2741*86d7f5d3SJohn Marino     }
2742*86d7f5d3SJohn Marino 
2743*86d7f5d3SJohn Marino     /* now walk the next pointers of the branch */
2744*86d7f5d3SJohn Marino     nextvers = p->key;
2745*86d7f5d3SJohn Marino     do
2746*86d7f5d3SJohn Marino     {
2747*86d7f5d3SJohn Marino 	p = findnode (rcs->versions, nextvers);
2748*86d7f5d3SJohn Marino 	if (p == NULL)
2749*86d7f5d3SJohn Marino 	{
2750*86d7f5d3SJohn Marino 	    /* a link in the chain is missing - return head or NULL */
2751*86d7f5d3SJohn Marino 	    if (force_tag_match)
2752*86d7f5d3SJohn Marino 		return NULL;
2753*86d7f5d3SJohn Marino 	    else
2754*86d7f5d3SJohn Marino 		return RCS_head (rcs);
2755*86d7f5d3SJohn Marino 	}
2756*86d7f5d3SJohn Marino 	vn = p->data;
2757*86d7f5d3SJohn Marino 	nextvers = vn->next;
2758*86d7f5d3SJohn Marino     } while (nextvers != NULL);
2759*86d7f5d3SJohn Marino 
2760*86d7f5d3SJohn Marino     /* we have the version in our hand, so go for it */
2761*86d7f5d3SJohn Marino     return xstrdup (vn->version);
2762*86d7f5d3SJohn Marino }
2763*86d7f5d3SJohn Marino 
2764*86d7f5d3SJohn Marino 
2765*86d7f5d3SJohn Marino 
2766*86d7f5d3SJohn Marino /* Returns the head of the branch which REV is on.  REV can be a
2767*86d7f5d3SJohn Marino    branch tag or non-branch tag; symbolic or numeric.
2768*86d7f5d3SJohn Marino 
2769*86d7f5d3SJohn Marino    Returns a newly malloc'd string.  Returns NULL if a symbolic name
2770*86d7f5d3SJohn Marino    isn't found.  */
2771*86d7f5d3SJohn Marino char *
RCS_branch_head(RCSNode * rcs,char * rev)2772*86d7f5d3SJohn Marino RCS_branch_head (RCSNode *rcs, char *rev)
2773*86d7f5d3SJohn Marino {
2774*86d7f5d3SJohn Marino     char *num;
2775*86d7f5d3SJohn Marino     char *br;
2776*86d7f5d3SJohn Marino     char *retval;
2777*86d7f5d3SJohn Marino 
2778*86d7f5d3SJohn Marino     assert (rcs != NULL);
2779*86d7f5d3SJohn Marino 
2780*86d7f5d3SJohn Marino     if (RCS_nodeisbranch (rcs, rev))
2781*86d7f5d3SJohn Marino 	return RCS_getbranch (rcs, rev, 1);
2782*86d7f5d3SJohn Marino 
2783*86d7f5d3SJohn Marino     if (isdigit ((unsigned char) *rev))
2784*86d7f5d3SJohn Marino 	num = xstrdup (rev);
2785*86d7f5d3SJohn Marino     else
2786*86d7f5d3SJohn Marino     {
2787*86d7f5d3SJohn Marino 	num = translate_symtag (rcs, rev);
2788*86d7f5d3SJohn Marino 	if (num == NULL)
2789*86d7f5d3SJohn Marino 	    return NULL;
2790*86d7f5d3SJohn Marino     }
2791*86d7f5d3SJohn Marino     br = truncate_revnum (num);
2792*86d7f5d3SJohn Marino     retval = RCS_getbranch (rcs, br, 1);
2793*86d7f5d3SJohn Marino     free (br);
2794*86d7f5d3SJohn Marino     free (num);
2795*86d7f5d3SJohn Marino     return retval;
2796*86d7f5d3SJohn Marino }
2797*86d7f5d3SJohn Marino 
2798*86d7f5d3SJohn Marino 
2799*86d7f5d3SJohn Marino 
2800*86d7f5d3SJohn Marino /* Get the branch point for a particular branch, that is the first
2801*86d7f5d3SJohn Marino    revision on that branch.  For example, RCS_getbranchpoint (rcs,
2802*86d7f5d3SJohn Marino    "1.3.2") will normally return "1.3.2.1".  TARGET may be either a
2803*86d7f5d3SJohn Marino    branch number or a revision number; if a revnum, find the
2804*86d7f5d3SJohn Marino    branchpoint of the branch to which TARGET belongs.
2805*86d7f5d3SJohn Marino 
2806*86d7f5d3SJohn Marino    Return RCS_head if TARGET is on the trunk or if the root node could
2807*86d7f5d3SJohn Marino    not be found (this is sort of backwards from our behavior on a branch;
2808*86d7f5d3SJohn Marino    the rationale is that the return value is a revision from which you
2809*86d7f5d3SJohn Marino    can start walking the next fields and end up at TARGET).
2810*86d7f5d3SJohn Marino    Return NULL on error.  */
2811*86d7f5d3SJohn Marino static char *
RCS_getbranchpoint(RCSNode * rcs,char * target)2812*86d7f5d3SJohn Marino RCS_getbranchpoint (RCSNode *rcs, char *target)
2813*86d7f5d3SJohn Marino {
2814*86d7f5d3SJohn Marino     char *branch, *bp;
2815*86d7f5d3SJohn Marino     Node *vp;
2816*86d7f5d3SJohn Marino     RCSVers *rev;
2817*86d7f5d3SJohn Marino     int dots, isrevnum, brlen;
2818*86d7f5d3SJohn Marino 
2819*86d7f5d3SJohn Marino     dots = numdots (target);
2820*86d7f5d3SJohn Marino     isrevnum = dots & 1;
2821*86d7f5d3SJohn Marino 
2822*86d7f5d3SJohn Marino     if (dots == 1)
2823*86d7f5d3SJohn Marino 	/* TARGET is a trunk revision; return rcs->head. */
2824*86d7f5d3SJohn Marino 	return RCS_head (rcs);
2825*86d7f5d3SJohn Marino 
2826*86d7f5d3SJohn Marino     /* Get the revision number of the node at which TARGET's branch is
2827*86d7f5d3SJohn Marino        rooted.  If TARGET is a branch number, lop off the last field;
2828*86d7f5d3SJohn Marino        if it's a revision number, lop off the last *two* fields. */
2829*86d7f5d3SJohn Marino     branch = xstrdup (target);
2830*86d7f5d3SJohn Marino     bp = strrchr (branch, '.');
2831*86d7f5d3SJohn Marino     if (bp == NULL)
2832*86d7f5d3SJohn Marino 	error (1, 0, "%s: confused revision number %s",
2833*86d7f5d3SJohn Marino 	       rcs->print_path, target);
2834*86d7f5d3SJohn Marino     if (isrevnum)
2835*86d7f5d3SJohn Marino 	while (*--bp != '.')
2836*86d7f5d3SJohn Marino 	    ;
2837*86d7f5d3SJohn Marino     *bp = '\0';
2838*86d7f5d3SJohn Marino 
2839*86d7f5d3SJohn Marino     vp = findnode (rcs->versions, branch);
2840*86d7f5d3SJohn Marino     if (vp == NULL)
2841*86d7f5d3SJohn Marino     {
2842*86d7f5d3SJohn Marino 	error (0, 0, "%s: can't find branch point %s", rcs->print_path, target);
2843*86d7f5d3SJohn Marino 	free (branch);
2844*86d7f5d3SJohn Marino 	return NULL;
2845*86d7f5d3SJohn Marino     }
2846*86d7f5d3SJohn Marino     rev = vp->data;
2847*86d7f5d3SJohn Marino 
2848*86d7f5d3SJohn Marino     *bp++ = '.';
2849*86d7f5d3SJohn Marino     while (*bp && *bp != '.')
2850*86d7f5d3SJohn Marino 	++bp;
2851*86d7f5d3SJohn Marino     brlen = bp - branch;
2852*86d7f5d3SJohn Marino 
2853*86d7f5d3SJohn Marino     vp = rev->branches->list->next;
2854*86d7f5d3SJohn Marino     while (vp != rev->branches->list)
2855*86d7f5d3SJohn Marino     {
2856*86d7f5d3SJohn Marino 	/* BRANCH may be a genuine branch number, e.g. `1.1.3', or
2857*86d7f5d3SJohn Marino 	   maybe a full revision number, e.g. `1.1.3.6'.  We have
2858*86d7f5d3SJohn Marino 	   found our branch point if the first BRANCHLEN characters
2859*86d7f5d3SJohn Marino 	   of the revision number match, *and* if the following
2860*86d7f5d3SJohn Marino 	   character is a dot. */
2861*86d7f5d3SJohn Marino 	if (strncmp (vp->key, branch, brlen) == 0 && vp->key[brlen] == '.')
2862*86d7f5d3SJohn Marino 	    break;
2863*86d7f5d3SJohn Marino 	vp = vp->next;
2864*86d7f5d3SJohn Marino     }
2865*86d7f5d3SJohn Marino 
2866*86d7f5d3SJohn Marino     free (branch);
2867*86d7f5d3SJohn Marino     if (vp == rev->branches->list)
2868*86d7f5d3SJohn Marino     {
2869*86d7f5d3SJohn Marino 	error (0, 0, "%s: can't find branch point %s", rcs->print_path, target);
2870*86d7f5d3SJohn Marino 	return NULL;
2871*86d7f5d3SJohn Marino     }
2872*86d7f5d3SJohn Marino     else
2873*86d7f5d3SJohn Marino 	return xstrdup (vp->key);
2874*86d7f5d3SJohn Marino }
2875*86d7f5d3SJohn Marino 
2876*86d7f5d3SJohn Marino 
2877*86d7f5d3SJohn Marino 
2878*86d7f5d3SJohn Marino /*
2879*86d7f5d3SJohn Marino  * Get the head of the RCS file.  If branch is set, this is the head of the
2880*86d7f5d3SJohn Marino  * branch, otherwise the real head.
2881*86d7f5d3SJohn Marino  *
2882*86d7f5d3SJohn Marino  * INPUTS
2883*86d7f5d3SJohn Marino  *   rcs	The parsed rcs node information.
2884*86d7f5d3SJohn Marino  *
2885*86d7f5d3SJohn Marino  * RETURNS
2886*86d7f5d3SJohn Marino  *   NULL when rcs->branch exists and cannot be found.
2887*86d7f5d3SJohn Marino  *   A newly malloc'd string, otherwise.
2888*86d7f5d3SJohn Marino  */
2889*86d7f5d3SJohn Marino char *
RCS_head(RCSNode * rcs)2890*86d7f5d3SJohn Marino RCS_head (RCSNode *rcs)
2891*86d7f5d3SJohn Marino {
2892*86d7f5d3SJohn Marino     /* make sure we have something to look at... */
2893*86d7f5d3SJohn Marino     assert (rcs);
2894*86d7f5d3SJohn Marino 
2895*86d7f5d3SJohn Marino     /*
2896*86d7f5d3SJohn Marino      * NOTE: we call getbranch with force_tag_match set to avoid any
2897*86d7f5d3SJohn Marino      * possibility of recursion
2898*86d7f5d3SJohn Marino      */
2899*86d7f5d3SJohn Marino     if (rcs->branch)
2900*86d7f5d3SJohn Marino 	return RCS_getbranch (rcs, rcs->branch, 1);
2901*86d7f5d3SJohn Marino     else
2902*86d7f5d3SJohn Marino 	return xstrdup (rcs->head);
2903*86d7f5d3SJohn Marino }
2904*86d7f5d3SJohn Marino 
2905*86d7f5d3SJohn Marino 
2906*86d7f5d3SJohn Marino 
2907*86d7f5d3SJohn Marino /*
2908*86d7f5d3SJohn Marino  * Get the most recent revision, based on the supplied date, but use some
2909*86d7f5d3SJohn Marino  * funky stuff and follow the vendor branch maybe
2910*86d7f5d3SJohn Marino  */
2911*86d7f5d3SJohn Marino char *
RCS_getdate(RCSNode * rcs,const char * date,int force_tag_match)2912*86d7f5d3SJohn Marino RCS_getdate (RCSNode *rcs, const char *date, int force_tag_match)
2913*86d7f5d3SJohn Marino {
2914*86d7f5d3SJohn Marino     char *cur_rev = NULL;
2915*86d7f5d3SJohn Marino     char *retval = NULL;
2916*86d7f5d3SJohn Marino     Node *p;
2917*86d7f5d3SJohn Marino     RCSVers *vers = NULL;
2918*86d7f5d3SJohn Marino 
2919*86d7f5d3SJohn Marino     /* make sure we have something to look at... */
2920*86d7f5d3SJohn Marino     assert (rcs != NULL);
2921*86d7f5d3SJohn Marino 
2922*86d7f5d3SJohn Marino     if (rcs->flags & PARTIAL)
2923*86d7f5d3SJohn Marino 	RCS_reparsercsfile (rcs, NULL, NULL);
2924*86d7f5d3SJohn Marino 
2925*86d7f5d3SJohn Marino     /* if the head is on a branch, try the branch first */
2926*86d7f5d3SJohn Marino     if (rcs->branch != NULL)
2927*86d7f5d3SJohn Marino     {
2928*86d7f5d3SJohn Marino 	retval = RCS_getdatebranch (rcs, date, rcs->branch);
2929*86d7f5d3SJohn Marino 	if (retval != NULL)
2930*86d7f5d3SJohn Marino 	    return retval;
2931*86d7f5d3SJohn Marino     }
2932*86d7f5d3SJohn Marino 
2933*86d7f5d3SJohn Marino     /* otherwise if we have a trunk, try it */
2934*86d7f5d3SJohn Marino     if (rcs->head)
2935*86d7f5d3SJohn Marino     {
2936*86d7f5d3SJohn Marino 	p = findnode (rcs->versions, rcs->head);
2937*86d7f5d3SJohn Marino 	if (p == NULL)
2938*86d7f5d3SJohn Marino 	{
2939*86d7f5d3SJohn Marino 	    error (0, 0, "%s: head revision %s doesn't exist", rcs->print_path,
2940*86d7f5d3SJohn Marino 		   rcs->head);
2941*86d7f5d3SJohn Marino 	}
2942*86d7f5d3SJohn Marino 	while (p != NULL)
2943*86d7f5d3SJohn Marino 	{
2944*86d7f5d3SJohn Marino 	    /* if the date of this one is before date, take it */
2945*86d7f5d3SJohn Marino 	    vers = p->data;
2946*86d7f5d3SJohn Marino 	    if (RCS_datecmp (vers->date, date) <= 0)
2947*86d7f5d3SJohn Marino 	    {
2948*86d7f5d3SJohn Marino 		cur_rev = vers->version;
2949*86d7f5d3SJohn Marino 		break;
2950*86d7f5d3SJohn Marino 	    }
2951*86d7f5d3SJohn Marino 
2952*86d7f5d3SJohn Marino 	    /* if there is a next version, find the node */
2953*86d7f5d3SJohn Marino 	    if (vers->next != NULL)
2954*86d7f5d3SJohn Marino 		p = findnode (rcs->versions, vers->next);
2955*86d7f5d3SJohn Marino 	    else
2956*86d7f5d3SJohn Marino 		p = NULL;
2957*86d7f5d3SJohn Marino 	}
2958*86d7f5d3SJohn Marino     }
2959*86d7f5d3SJohn Marino     else
2960*86d7f5d3SJohn Marino 	error (0, 0, "%s: no head revision", rcs->print_path);
2961*86d7f5d3SJohn Marino 
2962*86d7f5d3SJohn Marino     /*
2963*86d7f5d3SJohn Marino      * at this point, either we have the revision we want, or we have the
2964*86d7f5d3SJohn Marino      * first revision on the trunk (1.1?) in our hands, or we've come up
2965*86d7f5d3SJohn Marino      * completely empty
2966*86d7f5d3SJohn Marino      */
2967*86d7f5d3SJohn Marino 
2968*86d7f5d3SJohn Marino     /* if we found what we're looking for, and it's not 1.1 return it */
2969*86d7f5d3SJohn Marino     if (cur_rev != NULL)
2970*86d7f5d3SJohn Marino     {
2971*86d7f5d3SJohn Marino 	if (! STREQ (cur_rev, "1.1"))
2972*86d7f5d3SJohn Marino 	    return xstrdup (cur_rev);
2973*86d7f5d3SJohn Marino 
2974*86d7f5d3SJohn Marino 	/* This is 1.1;  if the date of 1.1 is not the same as that for the
2975*86d7f5d3SJohn Marino 	   1.1.1.1 version, then return 1.1.  This happens when the first
2976*86d7f5d3SJohn Marino 	   version of a file is created by a regular cvs add and commit,
2977*86d7f5d3SJohn Marino 	   and there is a subsequent cvs import of the same file.  */
2978*86d7f5d3SJohn Marino 	p = findnode (rcs->versions, "1.1.1.1");
2979*86d7f5d3SJohn Marino 	if (p)
2980*86d7f5d3SJohn Marino 	{
2981*86d7f5d3SJohn Marino 	    char *date_1_1 = vers->date;
2982*86d7f5d3SJohn Marino 
2983*86d7f5d3SJohn Marino 	    vers = p->data;
2984*86d7f5d3SJohn Marino 	    if (RCS_datecmp (vers->date, date_1_1) != 0)
2985*86d7f5d3SJohn Marino 		return xstrdup ("1.1");
2986*86d7f5d3SJohn Marino 	}
2987*86d7f5d3SJohn Marino     }
2988*86d7f5d3SJohn Marino 
2989*86d7f5d3SJohn Marino     /* look on the vendor branch */
2990*86d7f5d3SJohn Marino     retval = RCS_getdatebranch (rcs, date, CVSBRANCH);
2991*86d7f5d3SJohn Marino 
2992*86d7f5d3SJohn Marino     /*
2993*86d7f5d3SJohn Marino      * if we found a match, return it; otherwise, we return the first
2994*86d7f5d3SJohn Marino      * revision on the trunk or NULL depending on force_tag_match and the
2995*86d7f5d3SJohn Marino      * date of the first rev
2996*86d7f5d3SJohn Marino      */
2997*86d7f5d3SJohn Marino     if (retval != NULL)
2998*86d7f5d3SJohn Marino 	return retval;
2999*86d7f5d3SJohn Marino 
3000*86d7f5d3SJohn Marino     if (vers && (!force_tag_match || RCS_datecmp (vers->date, date) <= 0))
3001*86d7f5d3SJohn Marino 	return xstrdup (vers->version);
3002*86d7f5d3SJohn Marino     else
3003*86d7f5d3SJohn Marino 	return NULL;
3004*86d7f5d3SJohn Marino }
3005*86d7f5d3SJohn Marino 
3006*86d7f5d3SJohn Marino 
3007*86d7f5d3SJohn Marino 
3008*86d7f5d3SJohn Marino /*
3009*86d7f5d3SJohn Marino  * Look up the last element on a branch that was put in before or on
3010*86d7f5d3SJohn Marino  * the specified date and time (return the rev or NULL)
3011*86d7f5d3SJohn Marino  */
3012*86d7f5d3SJohn Marino static char *
RCS_getdatebranch(RCSNode * rcs,const char * date,const char * branch)3013*86d7f5d3SJohn Marino RCS_getdatebranch (RCSNode *rcs, const char *date, const char *branch)
3014*86d7f5d3SJohn Marino {
3015*86d7f5d3SJohn Marino     char *cur_rev = NULL;
3016*86d7f5d3SJohn Marino     char *cp;
3017*86d7f5d3SJohn Marino     char *xbranch, *xrev;
3018*86d7f5d3SJohn Marino     Node *p;
3019*86d7f5d3SJohn Marino     RCSVers *vers;
3020*86d7f5d3SJohn Marino 
3021*86d7f5d3SJohn Marino     /* look up the first revision on the branch */
3022*86d7f5d3SJohn Marino     xrev = xstrdup (branch);
3023*86d7f5d3SJohn Marino     cp = strrchr (xrev, '.');
3024*86d7f5d3SJohn Marino     if (cp == NULL)
3025*86d7f5d3SJohn Marino     {
3026*86d7f5d3SJohn Marino 	free (xrev);
3027*86d7f5d3SJohn Marino 	return NULL;
3028*86d7f5d3SJohn Marino     }
3029*86d7f5d3SJohn Marino     *cp = '\0';				/* turn it into a revision */
3030*86d7f5d3SJohn Marino 
3031*86d7f5d3SJohn Marino     assert (rcs != NULL);
3032*86d7f5d3SJohn Marino 
3033*86d7f5d3SJohn Marino     if (rcs->flags & PARTIAL)
3034*86d7f5d3SJohn Marino 	RCS_reparsercsfile (rcs, NULL, NULL);
3035*86d7f5d3SJohn Marino 
3036*86d7f5d3SJohn Marino     p = findnode (rcs->versions, xrev);
3037*86d7f5d3SJohn Marino     free (xrev);
3038*86d7f5d3SJohn Marino     if (p == NULL)
3039*86d7f5d3SJohn Marino 	return NULL;
3040*86d7f5d3SJohn Marino     vers = p->data;
3041*86d7f5d3SJohn Marino 
3042*86d7f5d3SJohn Marino     /* Tentatively use this revision, if it is early enough.  */
3043*86d7f5d3SJohn Marino     if (RCS_datecmp (vers->date, date) <= 0)
3044*86d7f5d3SJohn Marino 	cur_rev = vers->version;
3045*86d7f5d3SJohn Marino 
3046*86d7f5d3SJohn Marino     /* If no branches list, return now.  This is what happens if the branch
3047*86d7f5d3SJohn Marino        is a (magic) branch with no revisions yet.  */
3048*86d7f5d3SJohn Marino     if (vers->branches == NULL)
3049*86d7f5d3SJohn Marino 	return xstrdup (cur_rev);
3050*86d7f5d3SJohn Marino 
3051*86d7f5d3SJohn Marino     /* walk the branches list looking for the branch number */
3052*86d7f5d3SJohn Marino     xbranch = Xasprintf ("%s.", branch);
3053*86d7f5d3SJohn Marino     for (p = vers->branches->list->next; p != vers->branches->list; p = p->next)
3054*86d7f5d3SJohn Marino 	if (strncmp (p->key, xbranch, strlen (xbranch)) == 0)
3055*86d7f5d3SJohn Marino 	    break;
3056*86d7f5d3SJohn Marino     free (xbranch);
3057*86d7f5d3SJohn Marino     if (p == vers->branches->list)
3058*86d7f5d3SJohn Marino     {
3059*86d7f5d3SJohn Marino 	/* This is what happens if the branch is a (magic) branch with
3060*86d7f5d3SJohn Marino 	   no revisions yet.  Similar to the case where vers->branches ==
3061*86d7f5d3SJohn Marino 	   NULL, except here there was a another branch off the same
3062*86d7f5d3SJohn Marino 	   branchpoint.  */
3063*86d7f5d3SJohn Marino 	return xstrdup (cur_rev);
3064*86d7f5d3SJohn Marino     }
3065*86d7f5d3SJohn Marino 
3066*86d7f5d3SJohn Marino     p = findnode (rcs->versions, p->key);
3067*86d7f5d3SJohn Marino 
3068*86d7f5d3SJohn Marino     /* walk the next pointers until you find the end, or the date is too late */
3069*86d7f5d3SJohn Marino     while (p != NULL)
3070*86d7f5d3SJohn Marino     {
3071*86d7f5d3SJohn Marino 	vers = p->data;
3072*86d7f5d3SJohn Marino 	if (RCS_datecmp (vers->date, date) <= 0)
3073*86d7f5d3SJohn Marino 	    cur_rev = vers->version;
3074*86d7f5d3SJohn Marino 	else
3075*86d7f5d3SJohn Marino 	    break;
3076*86d7f5d3SJohn Marino 
3077*86d7f5d3SJohn Marino 	/* if there is a next version, find the node */
3078*86d7f5d3SJohn Marino 	if (vers->next != NULL)
3079*86d7f5d3SJohn Marino 	    p = findnode (rcs->versions, vers->next);
3080*86d7f5d3SJohn Marino 	else
3081*86d7f5d3SJohn Marino 	    p = NULL;
3082*86d7f5d3SJohn Marino     }
3083*86d7f5d3SJohn Marino 
3084*86d7f5d3SJohn Marino     /* Return whatever we found, which may be NULL.  */
3085*86d7f5d3SJohn Marino     return xstrdup (cur_rev);
3086*86d7f5d3SJohn Marino }
3087*86d7f5d3SJohn Marino 
3088*86d7f5d3SJohn Marino 
3089*86d7f5d3SJohn Marino 
3090*86d7f5d3SJohn Marino /*
3091*86d7f5d3SJohn Marino  * Compare two dates in RCS format. Beware the change in format on January 1,
3092*86d7f5d3SJohn Marino  * 2000, when years go from 2-digit to full format.
3093*86d7f5d3SJohn Marino  */
3094*86d7f5d3SJohn Marino int
RCS_datecmp(const char * date1,const char * date2)3095*86d7f5d3SJohn Marino RCS_datecmp (const char *date1, const char *date2)
3096*86d7f5d3SJohn Marino {
3097*86d7f5d3SJohn Marino     int length_diff = strlen (date1) - strlen (date2);
3098*86d7f5d3SJohn Marino 
3099*86d7f5d3SJohn Marino     return length_diff ? length_diff : strcmp (date1, date2);
3100*86d7f5d3SJohn Marino }
3101*86d7f5d3SJohn Marino 
3102*86d7f5d3SJohn Marino 
3103*86d7f5d3SJohn Marino 
3104*86d7f5d3SJohn Marino /* Look up revision REV in RCS and return the date specified for the
3105*86d7f5d3SJohn Marino    revision minus FUDGE seconds (FUDGE will generally be one, so that the
3106*86d7f5d3SJohn Marino    logically previous revision will be found later, or zero, if we want
3107*86d7f5d3SJohn Marino    the exact date).
3108*86d7f5d3SJohn Marino 
3109*86d7f5d3SJohn Marino    The return value is the date being returned as a time_t, or (time_t)-1
3110*86d7f5d3SJohn Marino    on error (previously was documented as zero on error; I haven't checked
3111*86d7f5d3SJohn Marino    the callers to make sure that they really check for (time_t)-1, but
3112*86d7f5d3SJohn Marino    the latter is what this function really returns).  If DATE is non-NULL,
3113*86d7f5d3SJohn Marino    then it must point to MAXDATELEN characters, and we store the same
3114*86d7f5d3SJohn Marino    return value there in DATEFORM format.  */
3115*86d7f5d3SJohn Marino time_t
RCS_getrevtime(RCSNode * rcs,const char * rev,char * date,int fudge)3116*86d7f5d3SJohn Marino RCS_getrevtime (RCSNode *rcs, const char *rev, char *date, int fudge)
3117*86d7f5d3SJohn Marino {
3118*86d7f5d3SJohn Marino     char *tdate;
3119*86d7f5d3SJohn Marino     struct tm xtm, *ftm;
3120*86d7f5d3SJohn Marino     struct timespec revdate;
3121*86d7f5d3SJohn Marino     Node *p;
3122*86d7f5d3SJohn Marino     RCSVers *vers;
3123*86d7f5d3SJohn Marino 
3124*86d7f5d3SJohn Marino     /* make sure we have something to look at... */
3125*86d7f5d3SJohn Marino     assert (rcs != NULL);
3126*86d7f5d3SJohn Marino 
3127*86d7f5d3SJohn Marino     if (rcs->flags & PARTIAL)
3128*86d7f5d3SJohn Marino 	RCS_reparsercsfile (rcs, NULL, NULL);
3129*86d7f5d3SJohn Marino 
3130*86d7f5d3SJohn Marino     /* look up the revision */
3131*86d7f5d3SJohn Marino     p = findnode (rcs->versions, rev);
3132*86d7f5d3SJohn Marino     if (p == NULL)
3133*86d7f5d3SJohn Marino 	return -1;
3134*86d7f5d3SJohn Marino     vers = p->data;
3135*86d7f5d3SJohn Marino 
3136*86d7f5d3SJohn Marino     /* split up the date */
3137*86d7f5d3SJohn Marino     if (sscanf (vers->date, SDATEFORM, &xtm.tm_year, &xtm.tm_mon,
3138*86d7f5d3SJohn Marino 		&xtm.tm_mday, &xtm.tm_hour, &xtm.tm_min, &xtm.tm_sec) != 6)
3139*86d7f5d3SJohn Marino 	error (1, 0, "%s: invalid date for revision %s (%s)", rcs->print_path,
3140*86d7f5d3SJohn Marino 	       rev, vers->date);
3141*86d7f5d3SJohn Marino 
3142*86d7f5d3SJohn Marino     /* If the year is from 1900 to 1999, RCS files contain only two
3143*86d7f5d3SJohn Marino        digits, and sscanf gives us a year from 0-99.  If the year is
3144*86d7f5d3SJohn Marino        2000+, RCS files contain all four digits and we subtract 1900,
3145*86d7f5d3SJohn Marino        because the tm_year field should contain years since 1900.  */
3146*86d7f5d3SJohn Marino 
3147*86d7f5d3SJohn Marino     if (xtm.tm_year >= 100 && xtm.tm_year < 2000)
3148*86d7f5d3SJohn Marino 	error (0, 0, "%s: non-standard date format for revision %s (%s)",
3149*86d7f5d3SJohn Marino 	       rcs->print_path, rev, vers->date);
3150*86d7f5d3SJohn Marino     if (xtm.tm_year >= 1900)
3151*86d7f5d3SJohn Marino 	xtm.tm_year -= 1900;
3152*86d7f5d3SJohn Marino 
3153*86d7f5d3SJohn Marino     /* put the date in a form getdate can grok */
3154*86d7f5d3SJohn Marino     tdate = Xasprintf ("%d-%d-%d %d:%d:%d -0000",
3155*86d7f5d3SJohn Marino 		       xtm.tm_year + 1900, xtm.tm_mon, xtm.tm_mday,
3156*86d7f5d3SJohn Marino 		       xtm.tm_hour, xtm.tm_min, xtm.tm_sec);
3157*86d7f5d3SJohn Marino 
3158*86d7f5d3SJohn Marino     /* Turn it into seconds since the epoch.
3159*86d7f5d3SJohn Marino      *
3160*86d7f5d3SJohn Marino      * We use a struct timespec since that is what getdate requires, then
3161*86d7f5d3SJohn Marino      * truncate the nanoseconds.
3162*86d7f5d3SJohn Marino      */
3163*86d7f5d3SJohn Marino     if (!get_date (&revdate, tdate, NULL))
3164*86d7f5d3SJohn Marino     {
3165*86d7f5d3SJohn Marino 	free (tdate);
3166*86d7f5d3SJohn Marino 	return (time_t)-1;
3167*86d7f5d3SJohn Marino     }
3168*86d7f5d3SJohn Marino     free (tdate);
3169*86d7f5d3SJohn Marino 
3170*86d7f5d3SJohn Marino     revdate.tv_sec -= fudge;	/* remove "fudge" seconds */
3171*86d7f5d3SJohn Marino     if (date)
3172*86d7f5d3SJohn Marino     {
3173*86d7f5d3SJohn Marino 	/* Put an appropriate string into `date', if we were given one. */
3174*86d7f5d3SJohn Marino 	ftm = gmtime (&revdate.tv_sec);
3175*86d7f5d3SJohn Marino 	(void) sprintf (date, DATEFORM,
3176*86d7f5d3SJohn Marino 			ftm->tm_year + (ftm->tm_year < 100 ? 0 : 1900),
3177*86d7f5d3SJohn Marino 			ftm->tm_mon + 1, ftm->tm_mday, ftm->tm_hour,
3178*86d7f5d3SJohn Marino 			ftm->tm_min, ftm->tm_sec);
3179*86d7f5d3SJohn Marino     }
3180*86d7f5d3SJohn Marino 
3181*86d7f5d3SJohn Marino     return revdate.tv_sec;
3182*86d7f5d3SJohn Marino }
3183*86d7f5d3SJohn Marino 
3184*86d7f5d3SJohn Marino 
3185*86d7f5d3SJohn Marino 
3186*86d7f5d3SJohn Marino List *
RCS_getlocks(RCSNode * rcs)3187*86d7f5d3SJohn Marino RCS_getlocks (RCSNode *rcs)
3188*86d7f5d3SJohn Marino {
3189*86d7f5d3SJohn Marino     assert(rcs != NULL);
3190*86d7f5d3SJohn Marino 
3191*86d7f5d3SJohn Marino     if (rcs->flags & PARTIAL)
3192*86d7f5d3SJohn Marino 	RCS_reparsercsfile (rcs, NULL, NULL);
3193*86d7f5d3SJohn Marino 
3194*86d7f5d3SJohn Marino     if (rcs->locks_data) {
3195*86d7f5d3SJohn Marino 	rcs->locks = getlist ();
3196*86d7f5d3SJohn Marino 	do_locks (rcs->locks, rcs->locks_data);
3197*86d7f5d3SJohn Marino 	free(rcs->locks_data);
3198*86d7f5d3SJohn Marino 	rcs->locks_data = NULL;
3199*86d7f5d3SJohn Marino     }
3200*86d7f5d3SJohn Marino 
3201*86d7f5d3SJohn Marino     return rcs->locks;
3202*86d7f5d3SJohn Marino }
3203*86d7f5d3SJohn Marino 
3204*86d7f5d3SJohn Marino 
3205*86d7f5d3SJohn Marino 
3206*86d7f5d3SJohn Marino List *
RCS_symbols(RCSNode * rcs)3207*86d7f5d3SJohn Marino RCS_symbols(RCSNode *rcs)
3208*86d7f5d3SJohn Marino {
3209*86d7f5d3SJohn Marino     assert(rcs != NULL);
3210*86d7f5d3SJohn Marino 
3211*86d7f5d3SJohn Marino     if (rcs->flags & PARTIAL)
3212*86d7f5d3SJohn Marino 	RCS_reparsercsfile (rcs, NULL, NULL);
3213*86d7f5d3SJohn Marino 
3214*86d7f5d3SJohn Marino     if (rcs->symbols_data) {
3215*86d7f5d3SJohn Marino 	rcs->symbols = getlist ();
3216*86d7f5d3SJohn Marino 	do_symbols (rcs->symbols, rcs->symbols_data);
3217*86d7f5d3SJohn Marino 	free(rcs->symbols_data);
3218*86d7f5d3SJohn Marino 	rcs->symbols_data = NULL;
3219*86d7f5d3SJohn Marino     }
3220*86d7f5d3SJohn Marino 
3221*86d7f5d3SJohn Marino     return rcs->symbols;
3222*86d7f5d3SJohn Marino }
3223*86d7f5d3SJohn Marino 
3224*86d7f5d3SJohn Marino 
3225*86d7f5d3SJohn Marino 
3226*86d7f5d3SJohn Marino /*
3227*86d7f5d3SJohn Marino  * Return the version associated with a particular symbolic tag.
3228*86d7f5d3SJohn Marino  * Returns NULL or a newly malloc'd string.
3229*86d7f5d3SJohn Marino  */
3230*86d7f5d3SJohn Marino static char *
translate_symtag(RCSNode * rcs,const char * tag)3231*86d7f5d3SJohn Marino translate_symtag (RCSNode *rcs, const char *tag)
3232*86d7f5d3SJohn Marino {
3233*86d7f5d3SJohn Marino     if (rcs->flags & PARTIAL)
3234*86d7f5d3SJohn Marino 	RCS_reparsercsfile (rcs, NULL, NULL);
3235*86d7f5d3SJohn Marino 
3236*86d7f5d3SJohn Marino     if (rcs->symbols != NULL)
3237*86d7f5d3SJohn Marino     {
3238*86d7f5d3SJohn Marino 	Node *p;
3239*86d7f5d3SJohn Marino 
3240*86d7f5d3SJohn Marino 	/* The symbols have already been converted into a list.  */
3241*86d7f5d3SJohn Marino 	p = findnode (rcs->symbols, tag);
3242*86d7f5d3SJohn Marino 	if (p == NULL)
3243*86d7f5d3SJohn Marino 	    return NULL;
3244*86d7f5d3SJohn Marino 
3245*86d7f5d3SJohn Marino 	return xstrdup (p->data);
3246*86d7f5d3SJohn Marino     }
3247*86d7f5d3SJohn Marino 
3248*86d7f5d3SJohn Marino     if (rcs->symbols_data != NULL)
3249*86d7f5d3SJohn Marino     {
3250*86d7f5d3SJohn Marino 	size_t len;
3251*86d7f5d3SJohn Marino 	char *cp, *last;
3252*86d7f5d3SJohn Marino 
3253*86d7f5d3SJohn Marino 	/* Look through the RCS symbols information.  This is like
3254*86d7f5d3SJohn Marino            do_symbols, but we don't add the information to a list.  In
3255*86d7f5d3SJohn Marino            most cases, we will only be called once for this file, so
3256*86d7f5d3SJohn Marino            generating the list is unnecessary overhead.  */
3257*86d7f5d3SJohn Marino 
3258*86d7f5d3SJohn Marino 	len = strlen (tag);
3259*86d7f5d3SJohn Marino 	cp = rcs->symbols_data;
3260*86d7f5d3SJohn Marino 	/* Keeping track of LAST below isn't strictly necessary, now that tags
3261*86d7f5d3SJohn Marino 	 * should be parsed for validity before they are accepted, but tags
3262*86d7f5d3SJohn Marino 	 * with spaces used to cause the code below to loop indefintely, so
3263*86d7f5d3SJohn Marino 	 * I have corrected for that.  Now, in the event that I missed
3264*86d7f5d3SJohn Marino 	 * something, the server cannot be hung.  -DRP
3265*86d7f5d3SJohn Marino 	 */
3266*86d7f5d3SJohn Marino 	last = NULL;
3267*86d7f5d3SJohn Marino 	while ((cp = strchr (cp, tag[0])) != NULL)
3268*86d7f5d3SJohn Marino 	{
3269*86d7f5d3SJohn Marino 	    if (cp == last) break;
3270*86d7f5d3SJohn Marino 	    if ((cp == rcs->symbols_data || whitespace (cp[-1]))
3271*86d7f5d3SJohn Marino 		&& strncmp (cp, tag, len) == 0
3272*86d7f5d3SJohn Marino 		&& cp[len] == ':')
3273*86d7f5d3SJohn Marino 	    {
3274*86d7f5d3SJohn Marino 		char *v, *r;
3275*86d7f5d3SJohn Marino 
3276*86d7f5d3SJohn Marino 		/* We found the tag.  Return the version number.  */
3277*86d7f5d3SJohn Marino 
3278*86d7f5d3SJohn Marino 		cp += len + 1;
3279*86d7f5d3SJohn Marino 		v = cp;
3280*86d7f5d3SJohn Marino 		while (! whitespace (*cp) && *cp != '\0')
3281*86d7f5d3SJohn Marino 		    ++cp;
3282*86d7f5d3SJohn Marino 		r = xmalloc (cp - v + 1);
3283*86d7f5d3SJohn Marino 		strncpy (r, v, cp - v);
3284*86d7f5d3SJohn Marino 		r[cp - v] = '\0';
3285*86d7f5d3SJohn Marino 		return r;
3286*86d7f5d3SJohn Marino 	    }
3287*86d7f5d3SJohn Marino 
3288*86d7f5d3SJohn Marino 	    while (! whitespace (*cp) && *cp != '\0')
3289*86d7f5d3SJohn Marino 		++cp;
3290*86d7f5d3SJohn Marino 	    if (*cp == '\0')
3291*86d7f5d3SJohn Marino 		break;
3292*86d7f5d3SJohn Marino 	    last = cp;
3293*86d7f5d3SJohn Marino 	}
3294*86d7f5d3SJohn Marino     }
3295*86d7f5d3SJohn Marino 
3296*86d7f5d3SJohn Marino     return NULL;
3297*86d7f5d3SJohn Marino }
3298*86d7f5d3SJohn Marino 
3299*86d7f5d3SJohn Marino 
3300*86d7f5d3SJohn Marino 
3301*86d7f5d3SJohn Marino /*
3302*86d7f5d3SJohn Marino  * The argument ARG is the getopt remainder of the -k option specified on the
3303*86d7f5d3SJohn Marino  * command line.  This function returns malloc'ed space that can be used
3304*86d7f5d3SJohn Marino  * directly in calls to RCS V5, with the -k flag munged correctly.
3305*86d7f5d3SJohn Marino  */
3306*86d7f5d3SJohn Marino char *
RCS_check_kflag(const char * arg)3307*86d7f5d3SJohn Marino RCS_check_kflag (const char *arg)
3308*86d7f5d3SJohn Marino {
3309*86d7f5d3SJohn Marino     static const char *const  keyword_usage[] =
3310*86d7f5d3SJohn Marino     {
3311*86d7f5d3SJohn Marino       "%s %s: invalid RCS keyword expansion mode\n",
3312*86d7f5d3SJohn Marino       "Valid expansion modes include:\n",
3313*86d7f5d3SJohn Marino       "   -kkv\tGenerate keywords using the default form.\n",
3314*86d7f5d3SJohn Marino       "   -kkvl\tLike -kkv, except locker's name inserted.\n",
3315*86d7f5d3SJohn Marino       "   -kk\tGenerate only keyword names in keyword strings.\n",
3316*86d7f5d3SJohn Marino       "   -kv\tGenerate only keyword values in keyword strings.\n",
3317*86d7f5d3SJohn Marino       "   -ko\tGenerate the old keyword string (no changes from checked in file).\n",
3318*86d7f5d3SJohn Marino       "   -kb\tGenerate binary file unmodified (merges not allowed) (RCS 5.7).\n",
3319*86d7f5d3SJohn Marino       "(Specify the --help global option for a list of other help options)\n",
3320*86d7f5d3SJohn Marino       NULL,
3321*86d7f5d3SJohn Marino     };
3322*86d7f5d3SJohn Marino     char const *const *cpp = NULL;
3323*86d7f5d3SJohn Marino 
3324*86d7f5d3SJohn Marino     if (arg)
3325*86d7f5d3SJohn Marino     {
3326*86d7f5d3SJohn Marino 	for (cpp = kflags; *cpp != NULL; cpp++)
3327*86d7f5d3SJohn Marino 	{
3328*86d7f5d3SJohn Marino 	    if (STREQ (arg, *cpp))
3329*86d7f5d3SJohn Marino 		break;
3330*86d7f5d3SJohn Marino 	}
3331*86d7f5d3SJohn Marino     }
3332*86d7f5d3SJohn Marino 
3333*86d7f5d3SJohn Marino     if (arg == NULL || *cpp == NULL)
3334*86d7f5d3SJohn Marino     {
3335*86d7f5d3SJohn Marino 	usage (keyword_usage);
3336*86d7f5d3SJohn Marino     }
3337*86d7f5d3SJohn Marino 
3338*86d7f5d3SJohn Marino     return Xasprintf ("-k%s", *cpp);
3339*86d7f5d3SJohn Marino }
3340*86d7f5d3SJohn Marino 
3341*86d7f5d3SJohn Marino 
3342*86d7f5d3SJohn Marino 
3343*86d7f5d3SJohn Marino /*
3344*86d7f5d3SJohn Marino  * Do some consistency checks on the symbolic tag... These should equate
3345*86d7f5d3SJohn Marino  * pretty close to what RCS checks, though I don't know for certain.
3346*86d7f5d3SJohn Marino  */
3347*86d7f5d3SJohn Marino void
RCS_check_tag(const char * tag)3348*86d7f5d3SJohn Marino RCS_check_tag (const char *tag)
3349*86d7f5d3SJohn Marino {
3350*86d7f5d3SJohn Marino     char *invalid = "$,.:;@";		/* invalid RCS tag characters */
3351*86d7f5d3SJohn Marino     const char *cp;
3352*86d7f5d3SJohn Marino 
3353*86d7f5d3SJohn Marino     /*
3354*86d7f5d3SJohn Marino      * The first character must be an alphabetic letter. The remaining
3355*86d7f5d3SJohn Marino      * characters cannot be non-visible graphic characters, and must not be
3356*86d7f5d3SJohn Marino      * in the set of "invalid" RCS identifier characters.
3357*86d7f5d3SJohn Marino      */
3358*86d7f5d3SJohn Marino     if (isalpha ((unsigned char) *tag))
3359*86d7f5d3SJohn Marino     {
3360*86d7f5d3SJohn Marino 	for (cp = tag; *cp; cp++)
3361*86d7f5d3SJohn Marino 	{
3362*86d7f5d3SJohn Marino 	    if (!isgraph ((unsigned char) *cp))
3363*86d7f5d3SJohn Marino 		error (1, 0, "tag `%s' has non-visible graphic characters",
3364*86d7f5d3SJohn Marino 		       tag);
3365*86d7f5d3SJohn Marino 	    if (strchr (invalid, *cp))
3366*86d7f5d3SJohn Marino 		error (1, 0, "tag `%s' must not contain the characters `%s'",
3367*86d7f5d3SJohn Marino 		       tag, invalid);
3368*86d7f5d3SJohn Marino 	}
3369*86d7f5d3SJohn Marino     }
3370*86d7f5d3SJohn Marino     else
3371*86d7f5d3SJohn Marino 	error (1, 0, "tag `%s' must start with a letter", tag);
3372*86d7f5d3SJohn Marino }
3373*86d7f5d3SJohn Marino 
3374*86d7f5d3SJohn Marino 
3375*86d7f5d3SJohn Marino 
3376*86d7f5d3SJohn Marino /*
3377*86d7f5d3SJohn Marino  * TRUE if argument has valid syntax for an RCS revision or
3378*86d7f5d3SJohn Marino  * branch number.  All characters must be digits or dots, first
3379*86d7f5d3SJohn Marino  * and last characters must be digits, and no two consecutive
3380*86d7f5d3SJohn Marino  * characters may be dots.
3381*86d7f5d3SJohn Marino  *
3382*86d7f5d3SJohn Marino  * Intended for classifying things, so this function doesn't
3383*86d7f5d3SJohn Marino  * call error.
3384*86d7f5d3SJohn Marino  */
3385*86d7f5d3SJohn Marino int
RCS_valid_rev(const char * rev)3386*86d7f5d3SJohn Marino RCS_valid_rev (const char *rev)
3387*86d7f5d3SJohn Marino {
3388*86d7f5d3SJohn Marino    char last, c;
3389*86d7f5d3SJohn Marino    last = *rev++;
3390*86d7f5d3SJohn Marino    if (!isdigit ((unsigned char) last))
3391*86d7f5d3SJohn Marino        return 0;
3392*86d7f5d3SJohn Marino    while ((c = *rev++))   /* Extra parens placate -Wall gcc option */
3393*86d7f5d3SJohn Marino    {
3394*86d7f5d3SJohn Marino        if (c == '.')
3395*86d7f5d3SJohn Marino        {
3396*86d7f5d3SJohn Marino            if (last == '.')
3397*86d7f5d3SJohn Marino                return 0;
3398*86d7f5d3SJohn Marino            continue;
3399*86d7f5d3SJohn Marino        }
3400*86d7f5d3SJohn Marino        last = c;
3401*86d7f5d3SJohn Marino        if (!isdigit ((unsigned char) c))
3402*86d7f5d3SJohn Marino            return 0;
3403*86d7f5d3SJohn Marino    }
3404*86d7f5d3SJohn Marino    if (!isdigit ((unsigned char) last))
3405*86d7f5d3SJohn Marino        return 0;
3406*86d7f5d3SJohn Marino    return 1;
3407*86d7f5d3SJohn Marino }
3408*86d7f5d3SJohn Marino 
3409*86d7f5d3SJohn Marino 
3410*86d7f5d3SJohn Marino 
3411*86d7f5d3SJohn Marino /*
3412*86d7f5d3SJohn Marino  * Return true if RCS revision with TAG is a dead revision.
3413*86d7f5d3SJohn Marino  */
3414*86d7f5d3SJohn Marino int
RCS_isdead(RCSNode * rcs,const char * tag)3415*86d7f5d3SJohn Marino RCS_isdead (RCSNode *rcs, const char *tag)
3416*86d7f5d3SJohn Marino {
3417*86d7f5d3SJohn Marino     Node *p;
3418*86d7f5d3SJohn Marino     RCSVers *version;
3419*86d7f5d3SJohn Marino 
3420*86d7f5d3SJohn Marino     if (rcs->flags & PARTIAL)
3421*86d7f5d3SJohn Marino 	RCS_reparsercsfile (rcs, NULL, NULL);
3422*86d7f5d3SJohn Marino 
3423*86d7f5d3SJohn Marino     p = findnode (rcs->versions, tag);
3424*86d7f5d3SJohn Marino     if (p == NULL)
3425*86d7f5d3SJohn Marino 	return 0;
3426*86d7f5d3SJohn Marino 
3427*86d7f5d3SJohn Marino     version = p->data;
3428*86d7f5d3SJohn Marino     return version->dead;
3429*86d7f5d3SJohn Marino }
3430*86d7f5d3SJohn Marino 
3431*86d7f5d3SJohn Marino 
3432*86d7f5d3SJohn Marino 
3433*86d7f5d3SJohn Marino /* Return the RCS keyword expansion mode.  For example "b" for binary.
3434*86d7f5d3SJohn Marino    Returns a pointer into storage which is allocated and freed along with
3435*86d7f5d3SJohn Marino    the rest of the RCS information; the caller should not modify this
3436*86d7f5d3SJohn Marino    storage.  Returns NULL if the RCS file does not specify a keyword
3437*86d7f5d3SJohn Marino    expansion mode; for all other errors, die with a fatal error.  */
3438*86d7f5d3SJohn Marino char *
RCS_getexpand(RCSNode * rcs)3439*86d7f5d3SJohn Marino RCS_getexpand (RCSNode *rcs)
3440*86d7f5d3SJohn Marino {
3441*86d7f5d3SJohn Marino     /* Since RCS_parsercsfile_i now reads expand, don't need to worry
3442*86d7f5d3SJohn Marino        about RCS_reparsercsfile.  */
3443*86d7f5d3SJohn Marino     assert (rcs != NULL);
3444*86d7f5d3SJohn Marino     return rcs->expand;
3445*86d7f5d3SJohn Marino }
3446*86d7f5d3SJohn Marino 
3447*86d7f5d3SJohn Marino 
3448*86d7f5d3SJohn Marino 
3449*86d7f5d3SJohn Marino /* Set keyword expansion mode to EXPAND.  For example "b" for binary.  */
3450*86d7f5d3SJohn Marino void
RCS_setexpand(RCSNode * rcs,const char * expand)3451*86d7f5d3SJohn Marino RCS_setexpand (RCSNode *rcs, const char *expand)
3452*86d7f5d3SJohn Marino {
3453*86d7f5d3SJohn Marino     /* Since RCS_parsercsfile_i now reads expand, don't need to worry
3454*86d7f5d3SJohn Marino        about RCS_reparsercsfile.  */
3455*86d7f5d3SJohn Marino     assert (rcs != NULL);
3456*86d7f5d3SJohn Marino     if (rcs->expand != NULL)
3457*86d7f5d3SJohn Marino 	free (rcs->expand);
3458*86d7f5d3SJohn Marino     rcs->expand = xstrdup (expand);
3459*86d7f5d3SJohn Marino }
3460*86d7f5d3SJohn Marino 
3461*86d7f5d3SJohn Marino 
3462*86d7f5d3SJohn Marino 
3463*86d7f5d3SJohn Marino /* RCS keywords, and a matching enum.  */
3464*86d7f5d3SJohn Marino enum keyword
3465*86d7f5d3SJohn Marino {
3466*86d7f5d3SJohn Marino     KEYWORD_AUTHOR = 0,
3467*86d7f5d3SJohn Marino     KEYWORD_DATE,
3468*86d7f5d3SJohn Marino     KEYWORD_CVSHEADER,
3469*86d7f5d3SJohn Marino     KEYWORD_HEADER,
3470*86d7f5d3SJohn Marino     KEYWORD_ID,
3471*86d7f5d3SJohn Marino     KEYWORD_LOCKER,
3472*86d7f5d3SJohn Marino     KEYWORD_LOG,
3473*86d7f5d3SJohn Marino     KEYWORD_NAME,
3474*86d7f5d3SJohn Marino     KEYWORD_RCSFILE,
3475*86d7f5d3SJohn Marino     KEYWORD_REVISION,
3476*86d7f5d3SJohn Marino     KEYWORD_SOURCE,
3477*86d7f5d3SJohn Marino     KEYWORD_STATE,
3478*86d7f5d3SJohn Marino     KEYWORD_LOCALID
3479*86d7f5d3SJohn Marino };
3480*86d7f5d3SJohn Marino struct rcs_keyword
3481*86d7f5d3SJohn Marino {
3482*86d7f5d3SJohn Marino     const char *string;
3483*86d7f5d3SJohn Marino     size_t len;
3484*86d7f5d3SJohn Marino     enum keyword expandto;
3485*86d7f5d3SJohn Marino     bool expandit;
3486*86d7f5d3SJohn Marino };
3487*86d7f5d3SJohn Marino 
3488*86d7f5d3SJohn Marino 
3489*86d7f5d3SJohn Marino 
3490*86d7f5d3SJohn Marino static inline struct rcs_keyword *
new_keywords(void)3491*86d7f5d3SJohn Marino new_keywords (void)
3492*86d7f5d3SJohn Marino {
3493*86d7f5d3SJohn Marino     struct rcs_keyword *new;
3494*86d7f5d3SJohn Marino     new = xcalloc (KEYWORD_LOCALID + 2, sizeof (struct rcs_keyword));
3495*86d7f5d3SJohn Marino 
3496*86d7f5d3SJohn Marino #define KEYWORD_INIT(k, i, s) \
3497*86d7f5d3SJohn Marino 	k[i].string = s; \
3498*86d7f5d3SJohn Marino 	k[i].len = sizeof s - 1; \
3499*86d7f5d3SJohn Marino 	k[i].expandto = i; \
3500*86d7f5d3SJohn Marino 	k[i].expandit = true
3501*86d7f5d3SJohn Marino 
3502*86d7f5d3SJohn Marino     KEYWORD_INIT (new, KEYWORD_AUTHOR, "Author");
3503*86d7f5d3SJohn Marino     KEYWORD_INIT (new, KEYWORD_DATE, "Date");
3504*86d7f5d3SJohn Marino     KEYWORD_INIT (new, KEYWORD_CVSHEADER, "CVSHeader");
3505*86d7f5d3SJohn Marino     KEYWORD_INIT (new, KEYWORD_HEADER, "Header");
3506*86d7f5d3SJohn Marino     KEYWORD_INIT (new, KEYWORD_ID, "Id");
3507*86d7f5d3SJohn Marino     KEYWORD_INIT (new, KEYWORD_LOCKER, "Locker");
3508*86d7f5d3SJohn Marino     KEYWORD_INIT (new, KEYWORD_LOG, "Log");
3509*86d7f5d3SJohn Marino     KEYWORD_INIT (new, KEYWORD_NAME, "Name");
3510*86d7f5d3SJohn Marino     KEYWORD_INIT (new, KEYWORD_RCSFILE, "RCSfile");
3511*86d7f5d3SJohn Marino     KEYWORD_INIT (new, KEYWORD_REVISION, "Revision");
3512*86d7f5d3SJohn Marino     KEYWORD_INIT (new, KEYWORD_SOURCE, "Source");
3513*86d7f5d3SJohn Marino     KEYWORD_INIT (new, KEYWORD_STATE, "State");
3514*86d7f5d3SJohn Marino 
3515*86d7f5d3SJohn Marino     /* Per default, expand local keyword like Id */
3516*86d7f5d3SJohn Marino     new[KEYWORD_LOCALID].expandto = KEYWORD_ID;
3517*86d7f5d3SJohn Marino 
3518*86d7f5d3SJohn Marino     return new;
3519*86d7f5d3SJohn Marino }
3520*86d7f5d3SJohn Marino 
3521*86d7f5d3SJohn Marino 
3522*86d7f5d3SJohn Marino 
3523*86d7f5d3SJohn Marino void
free_keywords(void * keywords)3524*86d7f5d3SJohn Marino free_keywords (void *keywords)
3525*86d7f5d3SJohn Marino {
3526*86d7f5d3SJohn Marino     free (keywords);
3527*86d7f5d3SJohn Marino }
3528*86d7f5d3SJohn Marino 
3529*86d7f5d3SJohn Marino 
3530*86d7f5d3SJohn Marino 
3531*86d7f5d3SJohn Marino /* Convert an RCS date string into a readable string.  This is like
3532*86d7f5d3SJohn Marino    the RCS date2str function.  */
3533*86d7f5d3SJohn Marino static char *
printable_date(const char * rcs_date)3534*86d7f5d3SJohn Marino printable_date (const char *rcs_date)
3535*86d7f5d3SJohn Marino {
3536*86d7f5d3SJohn Marino     int year, mon, mday, hour, min, sec;
3537*86d7f5d3SJohn Marino     char buf[100];
3538*86d7f5d3SJohn Marino 
3539*86d7f5d3SJohn Marino     (void) sscanf (rcs_date, SDATEFORM, &year, &mon, &mday, &hour, &min,
3540*86d7f5d3SJohn Marino 		   &sec);
3541*86d7f5d3SJohn Marino     if (year < 1900)
3542*86d7f5d3SJohn Marino 	year += 1900;
3543*86d7f5d3SJohn Marino     sprintf (buf, "%04d/%02d/%02d %02d:%02d:%02d", year, mon, mday,
3544*86d7f5d3SJohn Marino 	     hour, min, sec);
3545*86d7f5d3SJohn Marino     return xstrdup (buf);
3546*86d7f5d3SJohn Marino }
3547*86d7f5d3SJohn Marino 
3548*86d7f5d3SJohn Marino 
3549*86d7f5d3SJohn Marino 
3550*86d7f5d3SJohn Marino /* Escape the characters in a string so that it can be included in an
3551*86d7f5d3SJohn Marino    RCS value.  */
3552*86d7f5d3SJohn Marino static char *
escape_keyword_value(const char * value,int * free_value)3553*86d7f5d3SJohn Marino escape_keyword_value (const char *value, int *free_value)
3554*86d7f5d3SJohn Marino {
3555*86d7f5d3SJohn Marino     char *ret, *t;
3556*86d7f5d3SJohn Marino     const char *s;
3557*86d7f5d3SJohn Marino 
3558*86d7f5d3SJohn Marino     for (s = value; *s != '\0'; s++)
3559*86d7f5d3SJohn Marino     {
3560*86d7f5d3SJohn Marino 	char c;
3561*86d7f5d3SJohn Marino 
3562*86d7f5d3SJohn Marino 	c = *s;
3563*86d7f5d3SJohn Marino 	if (c == '\t'
3564*86d7f5d3SJohn Marino 	    || c == '\n'
3565*86d7f5d3SJohn Marino 	    || c == '\\'
3566*86d7f5d3SJohn Marino 	    || c == ' '
3567*86d7f5d3SJohn Marino 	    || c == '$')
3568*86d7f5d3SJohn Marino 	{
3569*86d7f5d3SJohn Marino 	    break;
3570*86d7f5d3SJohn Marino 	}
3571*86d7f5d3SJohn Marino     }
3572*86d7f5d3SJohn Marino 
3573*86d7f5d3SJohn Marino     if (*s == '\0')
3574*86d7f5d3SJohn Marino     {
3575*86d7f5d3SJohn Marino 	*free_value = 0;
3576*86d7f5d3SJohn Marino 	return (char *) value;
3577*86d7f5d3SJohn Marino     }
3578*86d7f5d3SJohn Marino 
3579*86d7f5d3SJohn Marino     ret = xmalloc (strlen (value) * 4 + 1);
3580*86d7f5d3SJohn Marino     *free_value = 1;
3581*86d7f5d3SJohn Marino 
3582*86d7f5d3SJohn Marino     for (s = value, t = ret; *s != '\0'; s++, t++)
3583*86d7f5d3SJohn Marino     {
3584*86d7f5d3SJohn Marino 	switch (*s)
3585*86d7f5d3SJohn Marino 	{
3586*86d7f5d3SJohn Marino 	default:
3587*86d7f5d3SJohn Marino 	    *t = *s;
3588*86d7f5d3SJohn Marino 	    break;
3589*86d7f5d3SJohn Marino 	case '\t':
3590*86d7f5d3SJohn Marino 	    *t++ = '\\';
3591*86d7f5d3SJohn Marino 	    *t = 't';
3592*86d7f5d3SJohn Marino 	    break;
3593*86d7f5d3SJohn Marino 	case '\n':
3594*86d7f5d3SJohn Marino 	    *t++ = '\\';
3595*86d7f5d3SJohn Marino 	    *t = 'n';
3596*86d7f5d3SJohn Marino 	    break;
3597*86d7f5d3SJohn Marino 	case '\\':
3598*86d7f5d3SJohn Marino 	    *t++ = '\\';
3599*86d7f5d3SJohn Marino 	    *t = '\\';
3600*86d7f5d3SJohn Marino 	    break;
3601*86d7f5d3SJohn Marino 	case ' ':
3602*86d7f5d3SJohn Marino 	    *t++ = '\\';
3603*86d7f5d3SJohn Marino 	    *t++ = '0';
3604*86d7f5d3SJohn Marino 	    *t++ = '4';
3605*86d7f5d3SJohn Marino 	    *t = '0';
3606*86d7f5d3SJohn Marino 	    break;
3607*86d7f5d3SJohn Marino 	case '$':
3608*86d7f5d3SJohn Marino 	    *t++ = '\\';
3609*86d7f5d3SJohn Marino 	    *t++ = '0';
3610*86d7f5d3SJohn Marino 	    *t++ = '4';
3611*86d7f5d3SJohn Marino 	    *t = '4';
3612*86d7f5d3SJohn Marino 	    break;
3613*86d7f5d3SJohn Marino 	}
3614*86d7f5d3SJohn Marino     }
3615*86d7f5d3SJohn Marino 
3616*86d7f5d3SJohn Marino     *t = '\0';
3617*86d7f5d3SJohn Marino 
3618*86d7f5d3SJohn Marino     return ret;
3619*86d7f5d3SJohn Marino }
3620*86d7f5d3SJohn Marino 
3621*86d7f5d3SJohn Marino 
3622*86d7f5d3SJohn Marino 
3623*86d7f5d3SJohn Marino /* Expand RCS keywords in the memory buffer BUF of length LEN.  This
3624*86d7f5d3SJohn Marino    applies to file RCS and version VERS.  If NAME is not NULL, and is
3625*86d7f5d3SJohn Marino    not a numeric revision, then it is the symbolic tag used for the
3626*86d7f5d3SJohn Marino    checkout.  EXPAND indicates how to expand the keywords.  This
3627*86d7f5d3SJohn Marino    function sets *RETBUF and *RETLEN to the new buffer and length.
3628*86d7f5d3SJohn Marino    This function may modify the buffer BUF.  If BUF != *RETBUF, then
3629*86d7f5d3SJohn Marino    RETBUF is a newly allocated buffer.  */
3630*86d7f5d3SJohn Marino static void
expand_keywords(RCSNode * rcs,RCSVers * ver,const char * name,const char * log,size_t loglen,enum kflag expand,char * buf,size_t len,char ** retbuf,size_t * retlen)3631*86d7f5d3SJohn Marino expand_keywords (RCSNode *rcs, RCSVers *ver, const char *name, const char *log,
3632*86d7f5d3SJohn Marino 		 size_t loglen, enum kflag expand, char *buf, size_t len,
3633*86d7f5d3SJohn Marino 		 char **retbuf, size_t *retlen)
3634*86d7f5d3SJohn Marino {
3635*86d7f5d3SJohn Marino     struct expand_buffer
3636*86d7f5d3SJohn Marino     {
3637*86d7f5d3SJohn Marino 	struct expand_buffer *next;
3638*86d7f5d3SJohn Marino 	char *data;
3639*86d7f5d3SJohn Marino 	size_t len;
3640*86d7f5d3SJohn Marino 	int free_data;
3641*86d7f5d3SJohn Marino     } *ebufs = NULL;
3642*86d7f5d3SJohn Marino     struct expand_buffer *ebuf_last = NULL;
3643*86d7f5d3SJohn Marino     size_t ebuf_len = 0;
3644*86d7f5d3SJohn Marino     char *locker;
3645*86d7f5d3SJohn Marino     char *srch, *srch_next;
3646*86d7f5d3SJohn Marino     size_t srch_len;
3647*86d7f5d3SJohn Marino     const struct rcs_keyword *keywords;
3648*86d7f5d3SJohn Marino 
3649*86d7f5d3SJohn Marino     if (!config /* For `cvs init', config may not be set.  */
3650*86d7f5d3SJohn Marino 	||expand == KFLAG_O || expand == KFLAG_B)
3651*86d7f5d3SJohn Marino     {
3652*86d7f5d3SJohn Marino 	*retbuf = buf;
3653*86d7f5d3SJohn Marino 	*retlen = len;
3654*86d7f5d3SJohn Marino 	return;
3655*86d7f5d3SJohn Marino     }
3656*86d7f5d3SJohn Marino 
3657*86d7f5d3SJohn Marino     if (!config->keywords) config->keywords = new_keywords ();
3658*86d7f5d3SJohn Marino     keywords = config->keywords;
3659*86d7f5d3SJohn Marino 
3660*86d7f5d3SJohn Marino     /* If we are using -kkvl, dig out the locker information if any.  */
3661*86d7f5d3SJohn Marino     locker = NULL;
3662*86d7f5d3SJohn Marino     if (expand == KFLAG_KVL)
3663*86d7f5d3SJohn Marino     {
3664*86d7f5d3SJohn Marino 	Node *lock;
3665*86d7f5d3SJohn Marino 	lock = findnode (RCS_getlocks(rcs), ver->version);
3666*86d7f5d3SJohn Marino 	if (lock != NULL)
3667*86d7f5d3SJohn Marino 	    locker = xstrdup (lock->data);
3668*86d7f5d3SJohn Marino     }
3669*86d7f5d3SJohn Marino 
3670*86d7f5d3SJohn Marino     /* RCS keywords look like $STRING$ or $STRING: VALUE$.  */
3671*86d7f5d3SJohn Marino     srch = buf;
3672*86d7f5d3SJohn Marino     srch_len = len;
3673*86d7f5d3SJohn Marino     while ((srch_next = memchr (srch, '$', srch_len)) != NULL)
3674*86d7f5d3SJohn Marino     {
3675*86d7f5d3SJohn Marino 	char *s, *send;
3676*86d7f5d3SJohn Marino 	size_t slen;
3677*86d7f5d3SJohn Marino 	const struct rcs_keyword *keyword;
3678*86d7f5d3SJohn Marino 	char *value;
3679*86d7f5d3SJohn Marino 	int free_value;
3680*86d7f5d3SJohn Marino 	char *sub;
3681*86d7f5d3SJohn Marino 	size_t sublen;
3682*86d7f5d3SJohn Marino 
3683*86d7f5d3SJohn Marino 	srch_len -= (srch_next + 1) - srch;
3684*86d7f5d3SJohn Marino 	srch = srch_next + 1;
3685*86d7f5d3SJohn Marino 
3686*86d7f5d3SJohn Marino 	/* Look for the first non alphabetic character after the '$'.  */
3687*86d7f5d3SJohn Marino 	send = srch + srch_len;
3688*86d7f5d3SJohn Marino 	for (s = srch; s < send; s++)
3689*86d7f5d3SJohn Marino 	    if (! isalpha ((unsigned char) *s))
3690*86d7f5d3SJohn Marino 		break;
3691*86d7f5d3SJohn Marino 
3692*86d7f5d3SJohn Marino 	/* If the first non alphabetic character is not '$' or ':',
3693*86d7f5d3SJohn Marino            then this is not an RCS keyword.  */
3694*86d7f5d3SJohn Marino 	if (s == send || (*s != '$' && *s != ':'))
3695*86d7f5d3SJohn Marino 	    continue;
3696*86d7f5d3SJohn Marino 
3697*86d7f5d3SJohn Marino 	/* See if this is one of the keywords.  */
3698*86d7f5d3SJohn Marino 	slen = s - srch;
3699*86d7f5d3SJohn Marino 	for (keyword = keywords; keyword->string != NULL; keyword++)
3700*86d7f5d3SJohn Marino 	{
3701*86d7f5d3SJohn Marino 	    if (keyword->expandit
3702*86d7f5d3SJohn Marino 		&& keyword->len == slen
3703*86d7f5d3SJohn Marino 		&& strncmp (keyword->string, srch, slen) == 0)
3704*86d7f5d3SJohn Marino 	    {
3705*86d7f5d3SJohn Marino 		break;
3706*86d7f5d3SJohn Marino 	    }
3707*86d7f5d3SJohn Marino 	}
3708*86d7f5d3SJohn Marino 	if (keyword->string == NULL)
3709*86d7f5d3SJohn Marino 	    continue;
3710*86d7f5d3SJohn Marino 
3711*86d7f5d3SJohn Marino 	/* If the keyword ends with a ':', then the old value consists
3712*86d7f5d3SJohn Marino            of the characters up to the next '$'.  If there is no '$'
3713*86d7f5d3SJohn Marino            before the end of the line, though, then this wasn't an RCS
3714*86d7f5d3SJohn Marino            keyword after all.  */
3715*86d7f5d3SJohn Marino 	if (*s == ':')
3716*86d7f5d3SJohn Marino 	{
3717*86d7f5d3SJohn Marino 	    for (; s < send; s++)
3718*86d7f5d3SJohn Marino 		if (*s == '$' || *s == '\n')
3719*86d7f5d3SJohn Marino 		    break;
3720*86d7f5d3SJohn Marino 	    if (s == send || *s != '$')
3721*86d7f5d3SJohn Marino 		continue;
3722*86d7f5d3SJohn Marino 	}
3723*86d7f5d3SJohn Marino 
3724*86d7f5d3SJohn Marino 	/* At this point we must replace the string from SRCH to S
3725*86d7f5d3SJohn Marino            with the expansion of the keyword KW.  */
3726*86d7f5d3SJohn Marino 
3727*86d7f5d3SJohn Marino 	/* Get the value to use.  */
3728*86d7f5d3SJohn Marino 	free_value = 0;
3729*86d7f5d3SJohn Marino 	if (expand == KFLAG_K)
3730*86d7f5d3SJohn Marino 	    value = NULL;
3731*86d7f5d3SJohn Marino 	else
3732*86d7f5d3SJohn Marino 	{
3733*86d7f5d3SJohn Marino 	    switch (keyword->expandto)
3734*86d7f5d3SJohn Marino 	    {
3735*86d7f5d3SJohn Marino 	    default:
3736*86d7f5d3SJohn Marino 		assert (!"unreached");
3737*86d7f5d3SJohn Marino 
3738*86d7f5d3SJohn Marino 	    case KEYWORD_AUTHOR:
3739*86d7f5d3SJohn Marino 		value = ver->author;
3740*86d7f5d3SJohn Marino 		break;
3741*86d7f5d3SJohn Marino 
3742*86d7f5d3SJohn Marino 	    case KEYWORD_DATE:
3743*86d7f5d3SJohn Marino 		value = printable_date (ver->date);
3744*86d7f5d3SJohn Marino 		free_value = 1;
3745*86d7f5d3SJohn Marino 		break;
3746*86d7f5d3SJohn Marino 
3747*86d7f5d3SJohn Marino 	    case KEYWORD_CVSHEADER:
3748*86d7f5d3SJohn Marino 	    case KEYWORD_HEADER:
3749*86d7f5d3SJohn Marino 	    case KEYWORD_ID:
3750*86d7f5d3SJohn Marino 	    case KEYWORD_LOCALID:
3751*86d7f5d3SJohn Marino 		{
3752*86d7f5d3SJohn Marino 		    const char *path;
3753*86d7f5d3SJohn Marino 		    int free_path;
3754*86d7f5d3SJohn Marino 		    char *date;
3755*86d7f5d3SJohn Marino 		    char *old_path;
3756*86d7f5d3SJohn Marino 
3757*86d7f5d3SJohn Marino 		    old_path = NULL;
3758*86d7f5d3SJohn Marino 		    if (keyword->expandto == KEYWORD_HEADER)
3759*86d7f5d3SJohn Marino 			path = rcs->print_path;
3760*86d7f5d3SJohn Marino 		    else if (keyword->expandto == KEYWORD_CVSHEADER)
3761*86d7f5d3SJohn Marino 			path = getfullCVSname (rcs->print_path, &old_path);
3762*86d7f5d3SJohn Marino 		    else
3763*86d7f5d3SJohn Marino 			path = last_component (rcs->print_path);
3764*86d7f5d3SJohn Marino 		    path = escape_keyword_value (path, &free_path);
3765*86d7f5d3SJohn Marino 		    date = printable_date (ver->date);
3766*86d7f5d3SJohn Marino 		    value = Xasprintf ("%s %s %s %s %s%s%s",
3767*86d7f5d3SJohn Marino 				       path, ver->version, date, ver->author,
3768*86d7f5d3SJohn Marino 				       ver->state,
3769*86d7f5d3SJohn Marino 				       locker != NULL ? " " : "",
3770*86d7f5d3SJohn Marino 				       locker != NULL ? locker : "");
3771*86d7f5d3SJohn Marino 		    if (free_path)
3772*86d7f5d3SJohn Marino 			/* If free_path is set then we know we allocated path
3773*86d7f5d3SJohn Marino 			 * and we can discard the const.
3774*86d7f5d3SJohn Marino 			 */
3775*86d7f5d3SJohn Marino 			free ((char *)path);
3776*86d7f5d3SJohn Marino 		    if (old_path)
3777*86d7f5d3SJohn Marino 			free (old_path);
3778*86d7f5d3SJohn Marino 		    free (date);
3779*86d7f5d3SJohn Marino 		    free_value = 1;
3780*86d7f5d3SJohn Marino 		}
3781*86d7f5d3SJohn Marino 		break;
3782*86d7f5d3SJohn Marino 
3783*86d7f5d3SJohn Marino 	    case KEYWORD_LOCKER:
3784*86d7f5d3SJohn Marino 		value = locker;
3785*86d7f5d3SJohn Marino 		break;
3786*86d7f5d3SJohn Marino 
3787*86d7f5d3SJohn Marino 	    case KEYWORD_LOG:
3788*86d7f5d3SJohn Marino 	    case KEYWORD_RCSFILE:
3789*86d7f5d3SJohn Marino 		value = escape_keyword_value (last_component (rcs->print_path),
3790*86d7f5d3SJohn Marino 					      &free_value);
3791*86d7f5d3SJohn Marino 		break;
3792*86d7f5d3SJohn Marino 
3793*86d7f5d3SJohn Marino 	    case KEYWORD_NAME:
3794*86d7f5d3SJohn Marino 		if (name != NULL && ! isdigit ((unsigned char) *name))
3795*86d7f5d3SJohn Marino 		    value = (char *) name;
3796*86d7f5d3SJohn Marino 		else
3797*86d7f5d3SJohn Marino 		    value = NULL;
3798*86d7f5d3SJohn Marino 		break;
3799*86d7f5d3SJohn Marino 
3800*86d7f5d3SJohn Marino 	    case KEYWORD_REVISION:
3801*86d7f5d3SJohn Marino 		value = ver->version;
3802*86d7f5d3SJohn Marino 		break;
3803*86d7f5d3SJohn Marino 
3804*86d7f5d3SJohn Marino 	    case KEYWORD_SOURCE:
3805*86d7f5d3SJohn Marino 		value = escape_keyword_value (rcs->print_path, &free_value);
3806*86d7f5d3SJohn Marino 		break;
3807*86d7f5d3SJohn Marino 
3808*86d7f5d3SJohn Marino 	    case KEYWORD_STATE:
3809*86d7f5d3SJohn Marino 		value = ver->state;
3810*86d7f5d3SJohn Marino 		break;
3811*86d7f5d3SJohn Marino 	    }
3812*86d7f5d3SJohn Marino 	}
3813*86d7f5d3SJohn Marino 
3814*86d7f5d3SJohn Marino 	sub = xmalloc (keyword->len
3815*86d7f5d3SJohn Marino 		       + (value == NULL ? 0 : strlen (value))
3816*86d7f5d3SJohn Marino 		       + 10);
3817*86d7f5d3SJohn Marino 	if (expand == KFLAG_V)
3818*86d7f5d3SJohn Marino 	{
3819*86d7f5d3SJohn Marino 	    /* Decrement SRCH and increment S to remove the $
3820*86d7f5d3SJohn Marino                characters.  */
3821*86d7f5d3SJohn Marino 	    --srch;
3822*86d7f5d3SJohn Marino 	    ++srch_len;
3823*86d7f5d3SJohn Marino 	    ++s;
3824*86d7f5d3SJohn Marino 	    sublen = 0;
3825*86d7f5d3SJohn Marino 	}
3826*86d7f5d3SJohn Marino 	else
3827*86d7f5d3SJohn Marino 	{
3828*86d7f5d3SJohn Marino 	    strcpy (sub, keyword->string);
3829*86d7f5d3SJohn Marino 	    sublen = strlen (keyword->string);
3830*86d7f5d3SJohn Marino 	    if (expand != KFLAG_K)
3831*86d7f5d3SJohn Marino 	    {
3832*86d7f5d3SJohn Marino 		sub[sublen] = ':';
3833*86d7f5d3SJohn Marino 		sub[sublen + 1] = ' ';
3834*86d7f5d3SJohn Marino 		sublen += 2;
3835*86d7f5d3SJohn Marino 	    }
3836*86d7f5d3SJohn Marino 	}
3837*86d7f5d3SJohn Marino 	if (value != NULL)
3838*86d7f5d3SJohn Marino 	{
3839*86d7f5d3SJohn Marino 	    strcpy (sub + sublen, value);
3840*86d7f5d3SJohn Marino 	    sublen += strlen (value);
3841*86d7f5d3SJohn Marino 	}
3842*86d7f5d3SJohn Marino 	if (expand != KFLAG_V && expand != KFLAG_K)
3843*86d7f5d3SJohn Marino 	{
3844*86d7f5d3SJohn Marino 	    sub[sublen] = ' ';
3845*86d7f5d3SJohn Marino 	    ++sublen;
3846*86d7f5d3SJohn Marino 	    sub[sublen] = '\0';
3847*86d7f5d3SJohn Marino 	}
3848*86d7f5d3SJohn Marino 
3849*86d7f5d3SJohn Marino 	if (free_value)
3850*86d7f5d3SJohn Marino 	    free (value);
3851*86d7f5d3SJohn Marino 
3852*86d7f5d3SJohn Marino 	/* The Log keyword requires special handling.  This behaviour
3853*86d7f5d3SJohn Marino            is taken from RCS 5.7.  The special log message is what RCS
3854*86d7f5d3SJohn Marino            uses for ci -k.  */
3855*86d7f5d3SJohn Marino 	if (keyword->expandto == KEYWORD_LOG
3856*86d7f5d3SJohn Marino 	    && (sizeof "checked in with -k by " <= loglen
3857*86d7f5d3SJohn Marino 		|| log == NULL
3858*86d7f5d3SJohn Marino 		|| strncmp (log, "checked in with -k by ",
3859*86d7f5d3SJohn Marino 			    sizeof "checked in with -k by " - 1) != 0))
3860*86d7f5d3SJohn Marino 	{
3861*86d7f5d3SJohn Marino 	    char *start;
3862*86d7f5d3SJohn Marino 	    char *leader;
3863*86d7f5d3SJohn Marino 	    size_t leader_len, leader_sp_len;
3864*86d7f5d3SJohn Marino 	    const char *logend;
3865*86d7f5d3SJohn Marino 	    const char *snl;
3866*86d7f5d3SJohn Marino 	    int cnl;
3867*86d7f5d3SJohn Marino 	    char *date;
3868*86d7f5d3SJohn Marino 	    const char *sl;
3869*86d7f5d3SJohn Marino 
3870*86d7f5d3SJohn Marino 	    /* We are going to insert the trailing $ ourselves, before
3871*86d7f5d3SJohn Marino                the log message, so we must remove it from S, if we
3872*86d7f5d3SJohn Marino                haven't done so already.  */
3873*86d7f5d3SJohn Marino 	    if (expand != KFLAG_V)
3874*86d7f5d3SJohn Marino 		++s;
3875*86d7f5d3SJohn Marino 
3876*86d7f5d3SJohn Marino 	    /* CVS never has empty log messages, but old RCS files might.  */
3877*86d7f5d3SJohn Marino 	    if (log == NULL)
3878*86d7f5d3SJohn Marino 		log = "";
3879*86d7f5d3SJohn Marino 
3880*86d7f5d3SJohn Marino 	    /* Find the start of the line.  */
3881*86d7f5d3SJohn Marino 	    start = srch;
3882*86d7f5d3SJohn Marino 	    leader_len = 0;
3883*86d7f5d3SJohn Marino 	    while (start > buf && start[-1] != '\n'
3884*86d7f5d3SJohn Marino 		   && leader_len <= xsum (config->MaxCommentLeaderLength,
3885*86d7f5d3SJohn Marino 					  expand != KFLAG_V ? 1 : 0))
3886*86d7f5d3SJohn Marino 	    {
3887*86d7f5d3SJohn Marino 		--start;
3888*86d7f5d3SJohn Marino 		++leader_len;
3889*86d7f5d3SJohn Marino 	    }
3890*86d7f5d3SJohn Marino 
3891*86d7f5d3SJohn Marino 	    if (expand != KFLAG_V)
3892*86d7f5d3SJohn Marino 		/* When automagically determined and !KFLAG_V, we wish to avoid
3893*86d7f5d3SJohn Marino 		 * including the leading `$' of the Log keyword in our leader.
3894*86d7f5d3SJohn Marino 		 */
3895*86d7f5d3SJohn Marino 		--leader_len;
3896*86d7f5d3SJohn Marino 
3897*86d7f5d3SJohn Marino 	    /* If the automagically determined leader exceeds the limit set in
3898*86d7f5d3SJohn Marino 	     * CVSROOT/config, try to use a fallback.
3899*86d7f5d3SJohn Marino 	     */
3900*86d7f5d3SJohn Marino 	    if (leader_len > config->MaxCommentLeaderLength)
3901*86d7f5d3SJohn Marino 	    {
3902*86d7f5d3SJohn Marino 		if (config->UseArchiveCommentLeader && rcs->comment)
3903*86d7f5d3SJohn Marino 		{
3904*86d7f5d3SJohn Marino 		    leader = xstrdup (rcs->comment);
3905*86d7f5d3SJohn Marino 		    leader_len = strlen (rcs->comment);
3906*86d7f5d3SJohn Marino 		}
3907*86d7f5d3SJohn Marino 		else
3908*86d7f5d3SJohn Marino 		{
3909*86d7f5d3SJohn Marino 		    error (0, 0,
3910*86d7f5d3SJohn Marino "Skipping `$" "Log$' keyword due to excessive comment leader.");
3911*86d7f5d3SJohn Marino 		    continue;
3912*86d7f5d3SJohn Marino 		}
3913*86d7f5d3SJohn Marino 	    }
3914*86d7f5d3SJohn Marino 	    else /* leader_len <= config->MaxCommentLeaderLength */
3915*86d7f5d3SJohn Marino 	    {
3916*86d7f5d3SJohn Marino 		/* Copy the start of the line to use as a comment leader.  */
3917*86d7f5d3SJohn Marino 		leader = xmalloc (leader_len);
3918*86d7f5d3SJohn Marino 		memcpy (leader, start, leader_len);
3919*86d7f5d3SJohn Marino 	    }
3920*86d7f5d3SJohn Marino 
3921*86d7f5d3SJohn Marino 	    leader_sp_len = leader_len;
3922*86d7f5d3SJohn Marino 	    while (leader_sp_len > 0 && isspace (leader[leader_sp_len - 1]))
3923*86d7f5d3SJohn Marino 		--leader_sp_len;
3924*86d7f5d3SJohn Marino 
3925*86d7f5d3SJohn Marino 	    /* RCS does some checking for an old style of Log here,
3926*86d7f5d3SJohn Marino 	       but we don't bother.  RCS issues a warning if it
3927*86d7f5d3SJohn Marino 	       changes anything.  */
3928*86d7f5d3SJohn Marino 
3929*86d7f5d3SJohn Marino 	    /* Count the number of newlines in the log message so that
3930*86d7f5d3SJohn Marino 	       we know how many copies of the leader we will need.  */
3931*86d7f5d3SJohn Marino 	    cnl = 0;
3932*86d7f5d3SJohn Marino 	    logend = log + loglen;
3933*86d7f5d3SJohn Marino 	    for (snl = log; snl < logend; snl++)
3934*86d7f5d3SJohn Marino 		if (*snl == '\n')
3935*86d7f5d3SJohn Marino 		    ++cnl;
3936*86d7f5d3SJohn Marino 
3937*86d7f5d3SJohn Marino 	    /* If the log message did not end in a newline, increment
3938*86d7f5d3SJohn Marino 	     * the newline count so we have space for the extra leader.
3939*86d7f5d3SJohn Marino 	     * Failure to do so results in a buffer overrun.
3940*86d7f5d3SJohn Marino 	     */
3941*86d7f5d3SJohn Marino 	    if (loglen && snl[-1] != '\n')
3942*86d7f5d3SJohn Marino 		++cnl;
3943*86d7f5d3SJohn Marino 
3944*86d7f5d3SJohn Marino 	    date = printable_date (ver->date);
3945*86d7f5d3SJohn Marino 	    sub = xrealloc (sub,
3946*86d7f5d3SJohn Marino 			    (sublen
3947*86d7f5d3SJohn Marino 			     + sizeof "Revision"
3948*86d7f5d3SJohn Marino 			     + strlen (ver->version)
3949*86d7f5d3SJohn Marino 			     + strlen (date)
3950*86d7f5d3SJohn Marino 			     + strlen (ver->author)
3951*86d7f5d3SJohn Marino 			     + loglen
3952*86d7f5d3SJohn Marino 			       /* Use CNL + 2 below:  One leader for each log
3953*86d7f5d3SJohn Marino 				* line, plus the Revision/Author/Date line,
3954*86d7f5d3SJohn Marino 				* plus a trailing blank line.
3955*86d7f5d3SJohn Marino 				*/
3956*86d7f5d3SJohn Marino 			     + (cnl + 2) * leader_len
3957*86d7f5d3SJohn Marino 			     + 20));
3958*86d7f5d3SJohn Marino 	    if (expand != KFLAG_V)
3959*86d7f5d3SJohn Marino 	    {
3960*86d7f5d3SJohn Marino 		sub[sublen] = '$';
3961*86d7f5d3SJohn Marino 		++sublen;
3962*86d7f5d3SJohn Marino 	    }
3963*86d7f5d3SJohn Marino 	    sub[sublen] = '\n';
3964*86d7f5d3SJohn Marino 	    ++sublen;
3965*86d7f5d3SJohn Marino 	    memcpy (sub + sublen, leader, leader_len);
3966*86d7f5d3SJohn Marino 	    sublen += leader_len;
3967*86d7f5d3SJohn Marino 	    sprintf (sub + sublen, "Revision %s  %s  %s\n",
3968*86d7f5d3SJohn Marino 		     ver->version, date, ver->author);
3969*86d7f5d3SJohn Marino 	    sublen += strlen (sub + sublen);
3970*86d7f5d3SJohn Marino 	    free (date);
3971*86d7f5d3SJohn Marino 
3972*86d7f5d3SJohn Marino 	    sl = log;
3973*86d7f5d3SJohn Marino 	    while (sl < logend)
3974*86d7f5d3SJohn Marino 	    {
3975*86d7f5d3SJohn Marino 		if (*sl == '\n')
3976*86d7f5d3SJohn Marino 		{
3977*86d7f5d3SJohn Marino 		    memcpy (sub + sublen, leader, leader_sp_len);
3978*86d7f5d3SJohn Marino 		    sublen += leader_sp_len;
3979*86d7f5d3SJohn Marino 		    sub[sublen] = '\n';
3980*86d7f5d3SJohn Marino 		    ++sublen;
3981*86d7f5d3SJohn Marino 		    ++sl;
3982*86d7f5d3SJohn Marino 		}
3983*86d7f5d3SJohn Marino 		else
3984*86d7f5d3SJohn Marino 		{
3985*86d7f5d3SJohn Marino 		    const char *slnl;
3986*86d7f5d3SJohn Marino 
3987*86d7f5d3SJohn Marino 		    memcpy (sub + sublen, leader, leader_len);
3988*86d7f5d3SJohn Marino 		    sublen += leader_len;
3989*86d7f5d3SJohn Marino 		    for (slnl = sl; slnl < logend && *slnl != '\n'; ++slnl)
3990*86d7f5d3SJohn Marino 			;
3991*86d7f5d3SJohn Marino 		    if (slnl < logend)
3992*86d7f5d3SJohn Marino 			++slnl;
3993*86d7f5d3SJohn Marino 		    memcpy (sub + sublen, sl, slnl - sl);
3994*86d7f5d3SJohn Marino 		    sublen += slnl - sl;
3995*86d7f5d3SJohn Marino 		    if (slnl == logend && slnl[-1] != '\n')
3996*86d7f5d3SJohn Marino 		    {
3997*86d7f5d3SJohn Marino 			/* There was no EOL at the end of the log message.  Add
3998*86d7f5d3SJohn Marino 			 * one.
3999*86d7f5d3SJohn Marino 			 */
4000*86d7f5d3SJohn Marino 			sub[sublen] = '\n';
4001*86d7f5d3SJohn Marino 			++sublen;
4002*86d7f5d3SJohn Marino 		    }
4003*86d7f5d3SJohn Marino 		    sl = slnl;
4004*86d7f5d3SJohn Marino 		}
4005*86d7f5d3SJohn Marino 	    }
4006*86d7f5d3SJohn Marino 
4007*86d7f5d3SJohn Marino 	    memcpy (sub + sublen, leader, leader_sp_len);
4008*86d7f5d3SJohn Marino 	    sublen += leader_sp_len;
4009*86d7f5d3SJohn Marino 
4010*86d7f5d3SJohn Marino 	    free (leader);
4011*86d7f5d3SJohn Marino 	}
4012*86d7f5d3SJohn Marino 
4013*86d7f5d3SJohn Marino 	/* Now SUB contains a string which is to replace the string
4014*86d7f5d3SJohn Marino 	   from SRCH to S.  SUBLEN is the length of SUB.  */
4015*86d7f5d3SJohn Marino 
4016*86d7f5d3SJohn Marino 	if (srch + sublen == s)
4017*86d7f5d3SJohn Marino 	{
4018*86d7f5d3SJohn Marino 	    memcpy (srch, sub, sublen);
4019*86d7f5d3SJohn Marino 	    free (sub);
4020*86d7f5d3SJohn Marino 	}
4021*86d7f5d3SJohn Marino 	else
4022*86d7f5d3SJohn Marino 	{
4023*86d7f5d3SJohn Marino 	    struct expand_buffer *ebuf;
4024*86d7f5d3SJohn Marino 
4025*86d7f5d3SJohn Marino 	    /* We need to change the size of the buffer.  We build a
4026*86d7f5d3SJohn Marino                list of expand_buffer structures.  Each expand_buffer
4027*86d7f5d3SJohn Marino                structure represents a portion of the final output.  We
4028*86d7f5d3SJohn Marino                concatenate them back into a single buffer when we are
4029*86d7f5d3SJohn Marino                done.  This minimizes the number of potentially large
4030*86d7f5d3SJohn Marino                buffer copies we must do.  */
4031*86d7f5d3SJohn Marino 
4032*86d7f5d3SJohn Marino 	    if (ebufs == NULL)
4033*86d7f5d3SJohn Marino 	    {
4034*86d7f5d3SJohn Marino 		ebufs = xmalloc (sizeof *ebuf);
4035*86d7f5d3SJohn Marino 		ebufs->next = NULL;
4036*86d7f5d3SJohn Marino 		ebufs->data = buf;
4037*86d7f5d3SJohn Marino 		ebufs->free_data = 0;
4038*86d7f5d3SJohn Marino 		ebuf_len = srch - buf;
4039*86d7f5d3SJohn Marino 		ebufs->len = ebuf_len;
4040*86d7f5d3SJohn Marino 		ebuf_last = ebufs;
4041*86d7f5d3SJohn Marino 	    }
4042*86d7f5d3SJohn Marino 	    else
4043*86d7f5d3SJohn Marino 	    {
4044*86d7f5d3SJohn Marino 		assert (srch >= ebuf_last->data);
4045*86d7f5d3SJohn Marino 		assert (srch <= ebuf_last->data + ebuf_last->len);
4046*86d7f5d3SJohn Marino 		ebuf_len -= ebuf_last->len - (srch - ebuf_last->data);
4047*86d7f5d3SJohn Marino 		ebuf_last->len = srch - ebuf_last->data;
4048*86d7f5d3SJohn Marino 	    }
4049*86d7f5d3SJohn Marino 
4050*86d7f5d3SJohn Marino 	    ebuf = xmalloc (sizeof *ebuf);
4051*86d7f5d3SJohn Marino 	    ebuf->data = sub;
4052*86d7f5d3SJohn Marino 	    ebuf->len = sublen;
4053*86d7f5d3SJohn Marino 	    ebuf->free_data = 1;
4054*86d7f5d3SJohn Marino 	    ebuf->next = NULL;
4055*86d7f5d3SJohn Marino 	    ebuf_last->next = ebuf;
4056*86d7f5d3SJohn Marino 	    ebuf_last = ebuf;
4057*86d7f5d3SJohn Marino 	    ebuf_len += sublen;
4058*86d7f5d3SJohn Marino 
4059*86d7f5d3SJohn Marino 	    ebuf = xmalloc (sizeof *ebuf);
4060*86d7f5d3SJohn Marino 	    ebuf->data = s;
4061*86d7f5d3SJohn Marino 	    ebuf->len = srch_len - (s - srch);
4062*86d7f5d3SJohn Marino 	    ebuf->free_data = 0;
4063*86d7f5d3SJohn Marino 	    ebuf->next = NULL;
4064*86d7f5d3SJohn Marino 	    ebuf_last->next = ebuf;
4065*86d7f5d3SJohn Marino 	    ebuf_last = ebuf;
4066*86d7f5d3SJohn Marino 	    ebuf_len += srch_len - (s - srch);
4067*86d7f5d3SJohn Marino 	}
4068*86d7f5d3SJohn Marino 
4069*86d7f5d3SJohn Marino 	srch_len -= (s - srch);
4070*86d7f5d3SJohn Marino 	srch = s;
4071*86d7f5d3SJohn Marino     }
4072*86d7f5d3SJohn Marino 
4073*86d7f5d3SJohn Marino     if (locker != NULL)
4074*86d7f5d3SJohn Marino 	free (locker);
4075*86d7f5d3SJohn Marino 
4076*86d7f5d3SJohn Marino     if (ebufs == NULL)
4077*86d7f5d3SJohn Marino     {
4078*86d7f5d3SJohn Marino 	*retbuf = buf;
4079*86d7f5d3SJohn Marino 	*retlen = len;
4080*86d7f5d3SJohn Marino     }
4081*86d7f5d3SJohn Marino     else
4082*86d7f5d3SJohn Marino     {
4083*86d7f5d3SJohn Marino 	char *ret;
4084*86d7f5d3SJohn Marino 
4085*86d7f5d3SJohn Marino 	ret = xmalloc (ebuf_len);
4086*86d7f5d3SJohn Marino 	*retbuf = ret;
4087*86d7f5d3SJohn Marino 	*retlen = ebuf_len;
4088*86d7f5d3SJohn Marino 	while (ebufs != NULL)
4089*86d7f5d3SJohn Marino 	{
4090*86d7f5d3SJohn Marino 	    struct expand_buffer *next;
4091*86d7f5d3SJohn Marino 
4092*86d7f5d3SJohn Marino 	    memcpy (ret, ebufs->data, ebufs->len);
4093*86d7f5d3SJohn Marino 	    ret += ebufs->len;
4094*86d7f5d3SJohn Marino 	    if (ebufs->free_data)
4095*86d7f5d3SJohn Marino 		free (ebufs->data);
4096*86d7f5d3SJohn Marino 	    next = ebufs->next;
4097*86d7f5d3SJohn Marino 	    free (ebufs);
4098*86d7f5d3SJohn Marino 	    ebufs = next;
4099*86d7f5d3SJohn Marino 	}
4100*86d7f5d3SJohn Marino     }
4101*86d7f5d3SJohn Marino }
4102*86d7f5d3SJohn Marino 
4103*86d7f5d3SJohn Marino 
4104*86d7f5d3SJohn Marino 
4105*86d7f5d3SJohn Marino /* Check out a revision from an RCS file.
4106*86d7f5d3SJohn Marino 
4107*86d7f5d3SJohn Marino    If PFN is not NULL, then ignore WORKFILE and SOUT.  Call PFN zero
4108*86d7f5d3SJohn Marino    or more times with the contents of the file.  CALLERDAT is passed,
4109*86d7f5d3SJohn Marino    uninterpreted, to PFN.  (The current code will always call PFN
4110*86d7f5d3SJohn Marino    exactly once for a non empty file; however, the current code
4111*86d7f5d3SJohn Marino    assumes that it can hold the entire file contents in memory, which
4112*86d7f5d3SJohn Marino    is not a good assumption, and might change in the future).
4113*86d7f5d3SJohn Marino 
4114*86d7f5d3SJohn Marino    Otherwise, if WORKFILE is not NULL, check out the revision to
4115*86d7f5d3SJohn Marino    WORKFILE.  However, if WORKFILE is not NULL, and noexec is set,
4116*86d7f5d3SJohn Marino    then don't do anything.
4117*86d7f5d3SJohn Marino 
4118*86d7f5d3SJohn Marino    Otherwise, if WORKFILE is NULL, check out the revision to SOUT.  If
4119*86d7f5d3SJohn Marino    SOUT is RUN_TTY, then write the contents of the revision to
4120*86d7f5d3SJohn Marino    standard output.  When using SOUT, the output is generally a
4121*86d7f5d3SJohn Marino    temporary file; don't bother to get the file modes correct.  When
4122*86d7f5d3SJohn Marino    NOEXEC is set, WORKFILEs are not written but SOUTs are.
4123*86d7f5d3SJohn Marino 
4124*86d7f5d3SJohn Marino    REV is the numeric revision to check out.  It may be NULL, which
4125*86d7f5d3SJohn Marino    means to check out the head of the default branch.
4126*86d7f5d3SJohn Marino 
4127*86d7f5d3SJohn Marino    If NAMETAG is not NULL, and is not a numeric revision, then it is
4128*86d7f5d3SJohn Marino    the tag that should be used when expanding the RCS Name keyword.
4129*86d7f5d3SJohn Marino 
4130*86d7f5d3SJohn Marino    OPTIONS is a string such as "-kb" or "-kv" for keyword expansion
4131*86d7f5d3SJohn Marino    options.  It may be NULL to use the default expansion mode of the
4132*86d7f5d3SJohn Marino    file, typically "-kkv".
4133*86d7f5d3SJohn Marino 
4134*86d7f5d3SJohn Marino    On an error which prevented checking out the file, either print a
4135*86d7f5d3SJohn Marino    nonfatal error and return 1, or give a fatal error.  On success,
4136*86d7f5d3SJohn Marino    return 0.  */
4137*86d7f5d3SJohn Marino 
4138*86d7f5d3SJohn Marino /* This function mimics the behavior of `rcs co' almost exactly.  The
4139*86d7f5d3SJohn Marino    chief difference is in its support for preserving file ownership,
4140*86d7f5d3SJohn Marino    permissions, and special files across checkin and checkout -- see
4141*86d7f5d3SJohn Marino    comments in RCS_checkin for some issues about this. -twp */
4142*86d7f5d3SJohn Marino int
RCS_checkout(RCSNode * rcs,const char * workfile,const char * rev,const char * nametag,const char * options,const char * sout,RCSCHECKOUTPROC pfn,void * callerdat)4143*86d7f5d3SJohn Marino RCS_checkout (RCSNode *rcs, const char *workfile, const char *rev,
4144*86d7f5d3SJohn Marino               const char *nametag, const char *options, const char *sout,
4145*86d7f5d3SJohn Marino               RCSCHECKOUTPROC pfn, void *callerdat)
4146*86d7f5d3SJohn Marino {
4147*86d7f5d3SJohn Marino     int free_rev = 0;
4148*86d7f5d3SJohn Marino     enum kflag expand;
4149*86d7f5d3SJohn Marino     FILE *fp,
4150*86d7f5d3SJohn Marino 	 *ofp = NULL; /* Initialize since -Wall doesn't understand that
4151*86d7f5d3SJohn Marino 		       * error (1, ...) does not return.
4152*86d7f5d3SJohn Marino 		       */
4153*86d7f5d3SJohn Marino     struct stat sb;
4154*86d7f5d3SJohn Marino     struct rcsbuffer rcsbuf;
4155*86d7f5d3SJohn Marino     char *key;
4156*86d7f5d3SJohn Marino     char *value;
4157*86d7f5d3SJohn Marino     size_t len;
4158*86d7f5d3SJohn Marino     int free_value = 0;
4159*86d7f5d3SJohn Marino     char *log = NULL;
4160*86d7f5d3SJohn Marino     size_t loglen = 0;
4161*86d7f5d3SJohn Marino     Node *vp = NULL;
4162*86d7f5d3SJohn Marino #ifdef PRESERVE_PERMISSIONS_SUPPORT
4163*86d7f5d3SJohn Marino     uid_t rcs_owner = (uid_t) -1;
4164*86d7f5d3SJohn Marino     gid_t rcs_group = (gid_t) -1;
4165*86d7f5d3SJohn Marino     mode_t rcs_mode;
4166*86d7f5d3SJohn Marino     int change_rcs_owner_or_group = 0;
4167*86d7f5d3SJohn Marino     int change_rcs_mode = 0;
4168*86d7f5d3SJohn Marino     int special_file = 0;
4169*86d7f5d3SJohn Marino     unsigned long devnum_long;
4170*86d7f5d3SJohn Marino     dev_t devnum = 0;
4171*86d7f5d3SJohn Marino #endif
4172*86d7f5d3SJohn Marino 
4173*86d7f5d3SJohn Marino     TRACE (TRACE_FUNCTION, "RCS_checkout (%s, %s, %s, %s, %s)",
4174*86d7f5d3SJohn Marino 	   rcs->path,
4175*86d7f5d3SJohn Marino 	   rev != NULL ? rev : "",
4176*86d7f5d3SJohn Marino 	   nametag != NULL ? nametag : "",
4177*86d7f5d3SJohn Marino 	   options != NULL ? options : "",
4178*86d7f5d3SJohn Marino 	   (pfn != NULL ? "(function)"
4179*86d7f5d3SJohn Marino 	    : (workfile != NULL ? workfile
4180*86d7f5d3SJohn Marino 	       : (sout != RUN_TTY ? sout
4181*86d7f5d3SJohn Marino 		  : "(stdout)"))));
4182*86d7f5d3SJohn Marino 
4183*86d7f5d3SJohn Marino     assert (rev == NULL || isdigit ((unsigned char) *rev));
4184*86d7f5d3SJohn Marino 
4185*86d7f5d3SJohn Marino     if (noexec && !server_active && workfile != NULL)
4186*86d7f5d3SJohn Marino 	return 0;
4187*86d7f5d3SJohn Marino 
4188*86d7f5d3SJohn Marino     assert (sout == RUN_TTY || workfile == NULL);
4189*86d7f5d3SJohn Marino     assert (pfn == NULL || (sout == RUN_TTY && workfile == NULL));
4190*86d7f5d3SJohn Marino 
4191*86d7f5d3SJohn Marino     /* Some callers, such as Checkin or remove_file, will pass us a
4192*86d7f5d3SJohn Marino        branch.  */
4193*86d7f5d3SJohn Marino     if (rev != NULL && (numdots (rev) & 1) == 0)
4194*86d7f5d3SJohn Marino     {
4195*86d7f5d3SJohn Marino 	rev = RCS_getbranch (rcs, rev, 1);
4196*86d7f5d3SJohn Marino 	if (rev == NULL)
4197*86d7f5d3SJohn Marino 	    error (1, 0, "internal error: bad branch tag in checkout");
4198*86d7f5d3SJohn Marino 	free_rev = 1;
4199*86d7f5d3SJohn Marino     }
4200*86d7f5d3SJohn Marino 
4201*86d7f5d3SJohn Marino     if (rev == NULL || STREQ (rev, rcs->head))
4202*86d7f5d3SJohn Marino     {
4203*86d7f5d3SJohn Marino 	int gothead;
4204*86d7f5d3SJohn Marino 
4205*86d7f5d3SJohn Marino 	/* We want the head revision.  Try to read it directly.  */
4206*86d7f5d3SJohn Marino 
4207*86d7f5d3SJohn Marino 	if (rcs->flags & PARTIAL)
4208*86d7f5d3SJohn Marino 	    RCS_reparsercsfile (rcs, &fp, &rcsbuf);
4209*86d7f5d3SJohn Marino 	else
4210*86d7f5d3SJohn Marino 	    rcsbuf_cache_open (rcs, rcs->delta_pos, &fp, &rcsbuf);
4211*86d7f5d3SJohn Marino 
4212*86d7f5d3SJohn Marino 	gothead = 0;
4213*86d7f5d3SJohn Marino 	if (! rcsbuf_getrevnum (&rcsbuf, &key))
4214*86d7f5d3SJohn Marino 	    error (1, 0, "unexpected EOF reading %s", rcs->print_path);
4215*86d7f5d3SJohn Marino 	while (rcsbuf_getkey (&rcsbuf, &key, &value))
4216*86d7f5d3SJohn Marino 	{
4217*86d7f5d3SJohn Marino 	    if (STREQ (key, "log"))
4218*86d7f5d3SJohn Marino 	    {
4219*86d7f5d3SJohn Marino 		if (log)
4220*86d7f5d3SJohn Marino 		{
4221*86d7f5d3SJohn Marino 		    error (0, 0,
4222*86d7f5d3SJohn Marino "Duplicate log keyword found for head revision in RCS file.");
4223*86d7f5d3SJohn Marino 		    free (log);
4224*86d7f5d3SJohn Marino 		}
4225*86d7f5d3SJohn Marino 		log = rcsbuf_valcopy (&rcsbuf, value, 0, &loglen);
4226*86d7f5d3SJohn Marino 	    }
4227*86d7f5d3SJohn Marino 	    else if (STREQ (key, "text"))
4228*86d7f5d3SJohn Marino 	    {
4229*86d7f5d3SJohn Marino 		gothead = 1;
4230*86d7f5d3SJohn Marino 		break;
4231*86d7f5d3SJohn Marino 	    }
4232*86d7f5d3SJohn Marino 	}
4233*86d7f5d3SJohn Marino 
4234*86d7f5d3SJohn Marino 	if (! gothead)
4235*86d7f5d3SJohn Marino 	{
4236*86d7f5d3SJohn Marino 	    error (0, 0, "internal error: cannot find head text");
4237*86d7f5d3SJohn Marino 	    if (free_rev)
4238*86d7f5d3SJohn Marino 		/* It's okay to discard the const when free_rev is set, because
4239*86d7f5d3SJohn Marino 		 * we know we allocated it in this function.
4240*86d7f5d3SJohn Marino 		 */
4241*86d7f5d3SJohn Marino 		free ((char *)rev);
4242*86d7f5d3SJohn Marino 	    return 1;
4243*86d7f5d3SJohn Marino 	}
4244*86d7f5d3SJohn Marino 
4245*86d7f5d3SJohn Marino 	rcsbuf_valpolish (&rcsbuf, value, 0, &len);
4246*86d7f5d3SJohn Marino 
4247*86d7f5d3SJohn Marino 	if (fstat (fileno (fp), &sb) < 0)
4248*86d7f5d3SJohn Marino 	    error (1, errno, "cannot fstat %s", rcs->path);
4249*86d7f5d3SJohn Marino 
4250*86d7f5d3SJohn Marino 	rcsbuf_cache (rcs, &rcsbuf);
4251*86d7f5d3SJohn Marino     }
4252*86d7f5d3SJohn Marino     else
4253*86d7f5d3SJohn Marino     {
4254*86d7f5d3SJohn Marino 	struct rcsbuffer *rcsbufp;
4255*86d7f5d3SJohn Marino 
4256*86d7f5d3SJohn Marino 	/* It isn't the head revision of the trunk.  We'll need to
4257*86d7f5d3SJohn Marino 	   walk through the deltas.  */
4258*86d7f5d3SJohn Marino 
4259*86d7f5d3SJohn Marino 	fp = NULL;
4260*86d7f5d3SJohn Marino 	if (rcs->flags & PARTIAL)
4261*86d7f5d3SJohn Marino 	    RCS_reparsercsfile (rcs, &fp, &rcsbuf);
4262*86d7f5d3SJohn Marino 
4263*86d7f5d3SJohn Marino 	if (fp == NULL)
4264*86d7f5d3SJohn Marino 	{
4265*86d7f5d3SJohn Marino 	    /* If RCS_deltas didn't close the file, we could use fstat
4266*86d7f5d3SJohn Marino 	       here too.  Probably should change it thusly....  */
4267*86d7f5d3SJohn Marino 	    if (stat (rcs->path, &sb) < 0)
4268*86d7f5d3SJohn Marino 		error (1, errno, "cannot stat %s", rcs->path);
4269*86d7f5d3SJohn Marino 	    rcsbufp = NULL;
4270*86d7f5d3SJohn Marino 	}
4271*86d7f5d3SJohn Marino 	else
4272*86d7f5d3SJohn Marino 	{
4273*86d7f5d3SJohn Marino 	    if (fstat (fileno (fp), &sb) < 0)
4274*86d7f5d3SJohn Marino 		error (1, errno, "cannot fstat %s", rcs->path);
4275*86d7f5d3SJohn Marino 	    rcsbufp = &rcsbuf;
4276*86d7f5d3SJohn Marino 	}
4277*86d7f5d3SJohn Marino 
4278*86d7f5d3SJohn Marino 	RCS_deltas (rcs, fp, rcsbufp, rev, RCS_FETCH, &value, &len,
4279*86d7f5d3SJohn Marino 		    &log, &loglen);
4280*86d7f5d3SJohn Marino 	free_value = 1;
4281*86d7f5d3SJohn Marino     }
4282*86d7f5d3SJohn Marino 
4283*86d7f5d3SJohn Marino     /* If OPTIONS is NULL or the empty string, then the old code would
4284*86d7f5d3SJohn Marino        invoke the RCS co program with no -k option, which means that
4285*86d7f5d3SJohn Marino        co would use the string we have stored in rcs->expand.  */
4286*86d7f5d3SJohn Marino     if ((options == NULL || options[0] == '\0') && rcs->expand == NULL)
4287*86d7f5d3SJohn Marino 	expand = KFLAG_KV;
4288*86d7f5d3SJohn Marino     else
4289*86d7f5d3SJohn Marino     {
4290*86d7f5d3SJohn Marino 	const char *ouroptions;
4291*86d7f5d3SJohn Marino 	const char * const *cpp;
4292*86d7f5d3SJohn Marino 
4293*86d7f5d3SJohn Marino 	if (options != NULL && options[0] != '\0')
4294*86d7f5d3SJohn Marino 	{
4295*86d7f5d3SJohn Marino 	    assert (options[0] == '-' && options[1] == 'k');
4296*86d7f5d3SJohn Marino 	    ouroptions = options + 2;
4297*86d7f5d3SJohn Marino 	}
4298*86d7f5d3SJohn Marino 	else
4299*86d7f5d3SJohn Marino 	    ouroptions = rcs->expand;
4300*86d7f5d3SJohn Marino 
4301*86d7f5d3SJohn Marino 	for (cpp = kflags; *cpp != NULL; cpp++)
4302*86d7f5d3SJohn Marino 	    if (STREQ (*cpp, ouroptions))
4303*86d7f5d3SJohn Marino 		break;
4304*86d7f5d3SJohn Marino 
4305*86d7f5d3SJohn Marino 	if (*cpp != NULL)
4306*86d7f5d3SJohn Marino 	    expand = (enum kflag) (cpp - kflags);
4307*86d7f5d3SJohn Marino 	else
4308*86d7f5d3SJohn Marino 	{
4309*86d7f5d3SJohn Marino 	    error (0, 0,
4310*86d7f5d3SJohn Marino 		   "internal error: unsupported substitution string -k%s",
4311*86d7f5d3SJohn Marino 		   ouroptions);
4312*86d7f5d3SJohn Marino 	    expand = KFLAG_KV;
4313*86d7f5d3SJohn Marino 	}
4314*86d7f5d3SJohn Marino     }
4315*86d7f5d3SJohn Marino 
4316*86d7f5d3SJohn Marino #ifdef PRESERVE_PERMISSIONS_SUPPORT
4317*86d7f5d3SJohn Marino     /* Handle special files and permissions, if that is desired. */
4318*86d7f5d3SJohn Marino     if (preserve_perms)
4319*86d7f5d3SJohn Marino     {
4320*86d7f5d3SJohn Marino 	RCSVers *vers;
4321*86d7f5d3SJohn Marino 	Node *info;
4322*86d7f5d3SJohn Marino 
4323*86d7f5d3SJohn Marino 	vp = findnode (rcs->versions, rev == NULL ? rcs->head : rev);
4324*86d7f5d3SJohn Marino 	if (vp == NULL)
4325*86d7f5d3SJohn Marino 	    error (1, 0, "internal error: no revision information for %s",
4326*86d7f5d3SJohn Marino 		   rev == NULL ? rcs->head : rev);
4327*86d7f5d3SJohn Marino 	vers = vp->data;
4328*86d7f5d3SJohn Marino 
4329*86d7f5d3SJohn Marino 	/* First we look for symlinks, which are simplest to handle. */
4330*86d7f5d3SJohn Marino 	info = findnode (vers->other_delta, "symlink");
4331*86d7f5d3SJohn Marino 	if (info != NULL)
4332*86d7f5d3SJohn Marino 	{
4333*86d7f5d3SJohn Marino 	    char *dest;
4334*86d7f5d3SJohn Marino 
4335*86d7f5d3SJohn Marino 	    if (pfn != NULL || (workfile == NULL && sout == RUN_TTY))
4336*86d7f5d3SJohn Marino 		error (1, 0, "symbolic link %s:%s cannot be piped",
4337*86d7f5d3SJohn Marino 		       rcs->path, vers->version);
4338*86d7f5d3SJohn Marino 	    if (workfile == NULL)
4339*86d7f5d3SJohn Marino 		dest = sout;
4340*86d7f5d3SJohn Marino 	    else
4341*86d7f5d3SJohn Marino 		dest = workfile;
4342*86d7f5d3SJohn Marino 
4343*86d7f5d3SJohn Marino 	    /* Remove `dest', just in case.  It's okay to get ENOENT here,
4344*86d7f5d3SJohn Marino 	       since we just want the file not to be there.  (TODO: decide
4345*86d7f5d3SJohn Marino 	       whether it should be considered an error for `dest' to exist
4346*86d7f5d3SJohn Marino 	       at this point.  If so, the unlink call should be removed and
4347*86d7f5d3SJohn Marino 	       `symlink' should signal the error. -twp) */
4348*86d7f5d3SJohn Marino 	    if (CVS_UNLINK (dest) < 0 && !existence_error (errno))
4349*86d7f5d3SJohn Marino 		error (1, errno, "cannot remove %s", dest);
4350*86d7f5d3SJohn Marino 	    if (symlink (info->data, dest) < 0)
4351*86d7f5d3SJohn Marino 		error (1, errno, "cannot create symbolic link from %s to %s",
4352*86d7f5d3SJohn Marino 		       dest, (char *)info->data);
4353*86d7f5d3SJohn Marino 	    if (free_value)
4354*86d7f5d3SJohn Marino 		free (value);
4355*86d7f5d3SJohn Marino 	    if (free_rev)
4356*86d7f5d3SJohn Marino 		/* It's okay to discard the const when free_rev is set, because
4357*86d7f5d3SJohn Marino 		 * we know we allocated it in this function.
4358*86d7f5d3SJohn Marino 		 */
4359*86d7f5d3SJohn Marino 		free ((char *)rev);
4360*86d7f5d3SJohn Marino 	    return 0;
4361*86d7f5d3SJohn Marino 	}
4362*86d7f5d3SJohn Marino 
4363*86d7f5d3SJohn Marino 	/* Next, we look at this file's hardlinks field, and see whether
4364*86d7f5d3SJohn Marino 	   it is linked to any other file that has been checked out.
4365*86d7f5d3SJohn Marino 	   If so, we don't do anything else -- just link it to that file.
4366*86d7f5d3SJohn Marino 
4367*86d7f5d3SJohn Marino 	   If we are checking out a file to a pipe or temporary storage,
4368*86d7f5d3SJohn Marino 	   none of this should matter.  Hence the `workfile != NULL'
4369*86d7f5d3SJohn Marino 	   wrapper around the whole thing. -twp */
4370*86d7f5d3SJohn Marino 
4371*86d7f5d3SJohn Marino 	if (workfile != NULL)
4372*86d7f5d3SJohn Marino 	{
4373*86d7f5d3SJohn Marino 	    List *links = vers->hardlinks;
4374*86d7f5d3SJohn Marino 	    if (links != NULL)
4375*86d7f5d3SJohn Marino 	    {
4376*86d7f5d3SJohn Marino 		Node *uptodate_link;
4377*86d7f5d3SJohn Marino 
4378*86d7f5d3SJohn Marino 		/* For each file in the hardlinks field, check to see
4379*86d7f5d3SJohn Marino 		   if it exists, and if so, if it has been checked out
4380*86d7f5d3SJohn Marino 		   this iteration.  When walklist returns, uptodate_link
4381*86d7f5d3SJohn Marino 		   should point to a hardlist node representing a file
4382*86d7f5d3SJohn Marino 		   in `links' which has recently been checked out, or
4383*86d7f5d3SJohn Marino 		   NULL if no file in `links' has yet been checked out. */
4384*86d7f5d3SJohn Marino 
4385*86d7f5d3SJohn Marino 		uptodate_link = NULL;
4386*86d7f5d3SJohn Marino 		(void) walklist (links, find_checkedout_proc, &uptodate_link);
4387*86d7f5d3SJohn Marino 		dellist (&links);
4388*86d7f5d3SJohn Marino 
4389*86d7f5d3SJohn Marino 		/* If we've found a file that `workfile' is supposed to be
4390*86d7f5d3SJohn Marino 		   linked to, and it has been checked out since CVS was
4391*86d7f5d3SJohn Marino 		   invoked, then simply link workfile to that file and return.
4392*86d7f5d3SJohn Marino 
4393*86d7f5d3SJohn Marino 		   If one of these conditions is not met, then
4394*86d7f5d3SJohn Marino 		   workfile is the first one in its hardlink group to
4395*86d7f5d3SJohn Marino 		   be checked out, and we must continue with a full
4396*86d7f5d3SJohn Marino 		   checkout. */
4397*86d7f5d3SJohn Marino 
4398*86d7f5d3SJohn Marino 		if (uptodate_link != NULL)
4399*86d7f5d3SJohn Marino 		{
4400*86d7f5d3SJohn Marino 		    struct hardlink_info *hlinfo = uptodate_link->data;
4401*86d7f5d3SJohn Marino 
4402*86d7f5d3SJohn Marino 		    if (link (uptodate_link->key, workfile) < 0)
4403*86d7f5d3SJohn Marino 			error (1, errno, "cannot link %s to %s",
4404*86d7f5d3SJohn Marino 			       workfile, uptodate_link->key);
4405*86d7f5d3SJohn Marino 		    hlinfo->checked_out = 1;	/* probably unnecessary */
4406*86d7f5d3SJohn Marino 		    if (free_value)
4407*86d7f5d3SJohn Marino 			free (value);
4408*86d7f5d3SJohn Marino 		    if (free_rev)
4409*86d7f5d3SJohn Marino 			/* It's okay to discard the const when free_rev is set,
4410*86d7f5d3SJohn Marino 			 * because we know we allocated it in this function.
4411*86d7f5d3SJohn Marino 			 */
4412*86d7f5d3SJohn Marino 			free ((char *)rev);
4413*86d7f5d3SJohn Marino 		    return 0;
4414*86d7f5d3SJohn Marino 		}
4415*86d7f5d3SJohn Marino 	    }
4416*86d7f5d3SJohn Marino 	}
4417*86d7f5d3SJohn Marino 
4418*86d7f5d3SJohn Marino 	info = findnode (vers->other_delta, "owner");
4419*86d7f5d3SJohn Marino 	if (info != NULL)
4420*86d7f5d3SJohn Marino 	{
4421*86d7f5d3SJohn Marino 	    change_rcs_owner_or_group = 1;
4422*86d7f5d3SJohn Marino 	    rcs_owner = (uid_t) strtoul (info->data, NULL, 10);
4423*86d7f5d3SJohn Marino 	}
4424*86d7f5d3SJohn Marino 	info = findnode (vers->other_delta, "group");
4425*86d7f5d3SJohn Marino 	if (info != NULL)
4426*86d7f5d3SJohn Marino 	{
4427*86d7f5d3SJohn Marino 	    change_rcs_owner_or_group = 1;
4428*86d7f5d3SJohn Marino 	    rcs_group = (gid_t) strtoul (info->data, NULL, 10);
4429*86d7f5d3SJohn Marino 	}
4430*86d7f5d3SJohn Marino 	info = findnode (vers->other_delta, "permissions");
4431*86d7f5d3SJohn Marino 	if (info != NULL)
4432*86d7f5d3SJohn Marino 	{
4433*86d7f5d3SJohn Marino 	    change_rcs_mode = 1;
4434*86d7f5d3SJohn Marino 	    rcs_mode = (mode_t) strtoul (info->data, NULL, 8);
4435*86d7f5d3SJohn Marino 	}
4436*86d7f5d3SJohn Marino 	info = findnode (vers->other_delta, "special");
4437*86d7f5d3SJohn Marino 	if (info != NULL)
4438*86d7f5d3SJohn Marino 	{
4439*86d7f5d3SJohn Marino 	    /* If the size of `devtype' changes, fix the sscanf call also */
4440*86d7f5d3SJohn Marino 	    char devtype[16];
4441*86d7f5d3SJohn Marino 
4442*86d7f5d3SJohn Marino 	    if (sscanf (info->data, "%15s %lu",
4443*86d7f5d3SJohn Marino 			devtype, &devnum_long) < 2)
4444*86d7f5d3SJohn Marino 		error (1, 0, "%s:%s has bad `special' newphrase %s",
4445*86d7f5d3SJohn Marino 		       workfile, vers->version, (char *)info->data);
4446*86d7f5d3SJohn Marino 	    devnum = devnum_long;
4447*86d7f5d3SJohn Marino 	    if (STREQ (devtype, "character"))
4448*86d7f5d3SJohn Marino 		special_file = S_IFCHR;
4449*86d7f5d3SJohn Marino 	    else if (STREQ (devtype, "block"))
4450*86d7f5d3SJohn Marino 		special_file = S_IFBLK;
4451*86d7f5d3SJohn Marino 	    else
4452*86d7f5d3SJohn Marino 		error (0, 0, "%s is a special file of unsupported type `%s'",
4453*86d7f5d3SJohn Marino 		       workfile, (char *)info->data);
4454*86d7f5d3SJohn Marino 	}
4455*86d7f5d3SJohn Marino     }
4456*86d7f5d3SJohn Marino #endif /* PRESERVE_PERMISSIONS_SUPPORT */
4457*86d7f5d3SJohn Marino 
4458*86d7f5d3SJohn Marino     if (expand != KFLAG_O && expand != KFLAG_B)
4459*86d7f5d3SJohn Marino     {
4460*86d7f5d3SJohn Marino 	char *newvalue;
4461*86d7f5d3SJohn Marino 
4462*86d7f5d3SJohn Marino 	/* Don't fetch the delta node again if we already have it. */
4463*86d7f5d3SJohn Marino 	if (vp == NULL)
4464*86d7f5d3SJohn Marino 	{
4465*86d7f5d3SJohn Marino 	    vp = findnode (rcs->versions, rev == NULL ? rcs->head : rev);
4466*86d7f5d3SJohn Marino 	    if (vp == NULL)
4467*86d7f5d3SJohn Marino 		error (1, 0, "internal error: no revision information for %s",
4468*86d7f5d3SJohn Marino 		       rev == NULL ? rcs->head : rev);
4469*86d7f5d3SJohn Marino 	}
4470*86d7f5d3SJohn Marino 
4471*86d7f5d3SJohn Marino 	expand_keywords (rcs, vp->data, nametag, log, loglen,
4472*86d7f5d3SJohn Marino 			 expand, value, len, &newvalue, &len);
4473*86d7f5d3SJohn Marino 
4474*86d7f5d3SJohn Marino 	if (newvalue != value)
4475*86d7f5d3SJohn Marino 	{
4476*86d7f5d3SJohn Marino 	    if (free_value)
4477*86d7f5d3SJohn Marino 		free (value);
4478*86d7f5d3SJohn Marino 	    value = newvalue;
4479*86d7f5d3SJohn Marino 	    free_value = 1;
4480*86d7f5d3SJohn Marino 	}
4481*86d7f5d3SJohn Marino     }
4482*86d7f5d3SJohn Marino 
4483*86d7f5d3SJohn Marino     if (free_rev)
4484*86d7f5d3SJohn Marino 	/* It's okay to discard the const when free_rev is set, because
4485*86d7f5d3SJohn Marino 	 * we know we allocated it in this function.
4486*86d7f5d3SJohn Marino 	 */
4487*86d7f5d3SJohn Marino 	free ((char *)rev);
4488*86d7f5d3SJohn Marino 
4489*86d7f5d3SJohn Marino     if (log != NULL)
4490*86d7f5d3SJohn Marino     {
4491*86d7f5d3SJohn Marino 	free (log);
4492*86d7f5d3SJohn Marino 	log = NULL;
4493*86d7f5d3SJohn Marino     }
4494*86d7f5d3SJohn Marino 
4495*86d7f5d3SJohn Marino     if (pfn != NULL)
4496*86d7f5d3SJohn Marino     {
4497*86d7f5d3SJohn Marino #ifdef PRESERVE_PERMISSIONS_SUPPORT
4498*86d7f5d3SJohn Marino 	if (special_file)
4499*86d7f5d3SJohn Marino 	    error (1, 0, "special file %s cannot be piped to anything",
4500*86d7f5d3SJohn Marino 		   rcs->path);
4501*86d7f5d3SJohn Marino #endif
4502*86d7f5d3SJohn Marino 	/* The PFN interface is very simple to implement right now, as
4503*86d7f5d3SJohn Marino            we always have the entire file in memory.  */
4504*86d7f5d3SJohn Marino 	if (len != 0)
4505*86d7f5d3SJohn Marino 	    pfn (callerdat, value, len);
4506*86d7f5d3SJohn Marino     }
4507*86d7f5d3SJohn Marino #ifdef PRESERVE_PERMISSIONS_SUPPORT
4508*86d7f5d3SJohn Marino     else if (special_file)
4509*86d7f5d3SJohn Marino     {
4510*86d7f5d3SJohn Marino # ifdef HAVE_MKNOD
4511*86d7f5d3SJohn Marino 	char *dest;
4512*86d7f5d3SJohn Marino 
4513*86d7f5d3SJohn Marino 	/* Can send either to WORKFILE or to SOUT, as long as SOUT is
4514*86d7f5d3SJohn Marino 	   not RUN_TTY. */
4515*86d7f5d3SJohn Marino 	dest = workfile;
4516*86d7f5d3SJohn Marino 	if (dest == NULL)
4517*86d7f5d3SJohn Marino 	{
4518*86d7f5d3SJohn Marino 	    if (sout == RUN_TTY)
4519*86d7f5d3SJohn Marino 		error (1, 0, "special file %s cannot be written to stdout",
4520*86d7f5d3SJohn Marino 		       rcs->path);
4521*86d7f5d3SJohn Marino 	    dest = sout;
4522*86d7f5d3SJohn Marino 	}
4523*86d7f5d3SJohn Marino 
4524*86d7f5d3SJohn Marino 	/* Unlink `dest', just in case.  It's okay if this provokes a
4525*86d7f5d3SJohn Marino 	   ENOENT error. */
4526*86d7f5d3SJohn Marino 	if (CVS_UNLINK (dest) < 0 && existence_error (errno))
4527*86d7f5d3SJohn Marino 	    error (1, errno, "cannot remove %s", dest);
4528*86d7f5d3SJohn Marino 	if (mknod (dest, special_file, devnum) < 0)
4529*86d7f5d3SJohn Marino 	    error (1, errno, "could not create special file %s",
4530*86d7f5d3SJohn Marino 		   dest);
4531*86d7f5d3SJohn Marino # else
4532*86d7f5d3SJohn Marino 	error (1, 0,
4533*86d7f5d3SJohn Marino "cannot create %s: unable to create special files on this system",
4534*86d7f5d3SJohn Marino workfile);
4535*86d7f5d3SJohn Marino # endif
4536*86d7f5d3SJohn Marino     }
4537*86d7f5d3SJohn Marino #endif
4538*86d7f5d3SJohn Marino     else
4539*86d7f5d3SJohn Marino     {
4540*86d7f5d3SJohn Marino 	/* Not a special file: write to WORKFILE or SOUT. */
4541*86d7f5d3SJohn Marino 	if (workfile == NULL)
4542*86d7f5d3SJohn Marino 	{
4543*86d7f5d3SJohn Marino 	    if (sout == RUN_TTY)
4544*86d7f5d3SJohn Marino 		ofp = stdout;
4545*86d7f5d3SJohn Marino 	    else
4546*86d7f5d3SJohn Marino 	    {
4547*86d7f5d3SJohn Marino 		/* Symbolic links should be removed before replacement, so that
4548*86d7f5d3SJohn Marino 		   `fopen' doesn't follow the link and open the wrong file. */
4549*86d7f5d3SJohn Marino 		if (islink (sout))
4550*86d7f5d3SJohn Marino 		    if (unlink_file (sout) < 0)
4551*86d7f5d3SJohn Marino 			error (1, errno, "cannot remove %s", sout);
4552*86d7f5d3SJohn Marino 		ofp = CVS_FOPEN (sout, expand == KFLAG_B ? "wb" : "w");
4553*86d7f5d3SJohn Marino 		if (ofp == NULL)
4554*86d7f5d3SJohn Marino 		    error (1, errno, "cannot open %s", sout);
4555*86d7f5d3SJohn Marino 	    }
4556*86d7f5d3SJohn Marino 	}
4557*86d7f5d3SJohn Marino 	else
4558*86d7f5d3SJohn Marino 	{
4559*86d7f5d3SJohn Marino 	    /* Output is supposed to go to WORKFILE, so we should open that
4560*86d7f5d3SJohn Marino 	       file.  Symbolic links should be removed first (see above). */
4561*86d7f5d3SJohn Marino 	    if (islink (workfile))
4562*86d7f5d3SJohn Marino 		if (unlink_file (workfile) < 0)
4563*86d7f5d3SJohn Marino 		    error (1, errno, "cannot remove %s", workfile);
4564*86d7f5d3SJohn Marino 
4565*86d7f5d3SJohn Marino 	    ofp = CVS_FOPEN (workfile, expand == KFLAG_B ? "wb" : "w");
4566*86d7f5d3SJohn Marino 
4567*86d7f5d3SJohn Marino 	    /* If the open failed because the existing workfile was not
4568*86d7f5d3SJohn Marino 	       writable, try to chmod the file and retry the open.  */
4569*86d7f5d3SJohn Marino 	    if (ofp == NULL && errno == EACCES
4570*86d7f5d3SJohn Marino 		&& isfile (workfile) && !iswritable (workfile))
4571*86d7f5d3SJohn Marino 	    {
4572*86d7f5d3SJohn Marino 		xchmod (workfile, 1);
4573*86d7f5d3SJohn Marino 		ofp = CVS_FOPEN (workfile, expand == KFLAG_B ? "wb" : "w");
4574*86d7f5d3SJohn Marino 	    }
4575*86d7f5d3SJohn Marino 
4576*86d7f5d3SJohn Marino 	    if (ofp == NULL)
4577*86d7f5d3SJohn Marino 	    {
4578*86d7f5d3SJohn Marino 		error (0, errno, "cannot open %s", workfile);
4579*86d7f5d3SJohn Marino 		if (free_value)
4580*86d7f5d3SJohn Marino 		    free (value);
4581*86d7f5d3SJohn Marino 		return 1;
4582*86d7f5d3SJohn Marino 	    }
4583*86d7f5d3SJohn Marino 	}
4584*86d7f5d3SJohn Marino 
4585*86d7f5d3SJohn Marino 	if (workfile == NULL && sout == RUN_TTY)
4586*86d7f5d3SJohn Marino 	{
4587*86d7f5d3SJohn Marino 	    if (expand == KFLAG_B)
4588*86d7f5d3SJohn Marino 		cvs_output_binary (value, len);
4589*86d7f5d3SJohn Marino 	    else
4590*86d7f5d3SJohn Marino 	    {
4591*86d7f5d3SJohn Marino 		/* cvs_output requires the caller to check for zero
4592*86d7f5d3SJohn Marino 		   length.  */
4593*86d7f5d3SJohn Marino 		if (len > 0)
4594*86d7f5d3SJohn Marino 		    cvs_output (value, len);
4595*86d7f5d3SJohn Marino 	    }
4596*86d7f5d3SJohn Marino 	}
4597*86d7f5d3SJohn Marino 	else
4598*86d7f5d3SJohn Marino 	{
4599*86d7f5d3SJohn Marino 	    /* NT 4.0 is said to have trouble writing 2099999 bytes
4600*86d7f5d3SJohn Marino 	       (for example) in a single fwrite.  So break it down
4601*86d7f5d3SJohn Marino 	       (there is no need to be writing that much at once
4602*86d7f5d3SJohn Marino 	       anyway; it is possible that LARGEST_FWRITE should be
4603*86d7f5d3SJohn Marino 	       somewhat larger for good performance, but for testing I
4604*86d7f5d3SJohn Marino 	       want to start with a small value until/unless a bigger
4605*86d7f5d3SJohn Marino 	       one proves useful).  */
4606*86d7f5d3SJohn Marino #define LARGEST_FWRITE 8192
4607*86d7f5d3SJohn Marino 	    size_t nleft = len;
4608*86d7f5d3SJohn Marino 	    size_t nstep = (len < LARGEST_FWRITE ? len : LARGEST_FWRITE);
4609*86d7f5d3SJohn Marino 	    char *p = value;
4610*86d7f5d3SJohn Marino 
4611*86d7f5d3SJohn Marino 	    while (nleft > 0)
4612*86d7f5d3SJohn Marino 	    {
4613*86d7f5d3SJohn Marino 		if (fwrite (p, 1, nstep, ofp) != nstep)
4614*86d7f5d3SJohn Marino 		{
4615*86d7f5d3SJohn Marino 		    error (0, errno, "cannot write %s",
4616*86d7f5d3SJohn Marino 			   (workfile != NULL
4617*86d7f5d3SJohn Marino 			    ? workfile
4618*86d7f5d3SJohn Marino 			    : (sout != RUN_TTY ? sout : "stdout")));
4619*86d7f5d3SJohn Marino 		    if (free_value)
4620*86d7f5d3SJohn Marino 			free (value);
4621*86d7f5d3SJohn Marino 		    return 1;
4622*86d7f5d3SJohn Marino 		}
4623*86d7f5d3SJohn Marino 		p += nstep;
4624*86d7f5d3SJohn Marino 		nleft -= nstep;
4625*86d7f5d3SJohn Marino 		if (nleft < nstep)
4626*86d7f5d3SJohn Marino 		    nstep = nleft;
4627*86d7f5d3SJohn Marino 	    }
4628*86d7f5d3SJohn Marino 	}
4629*86d7f5d3SJohn Marino     }
4630*86d7f5d3SJohn Marino 
4631*86d7f5d3SJohn Marino     if (free_value)
4632*86d7f5d3SJohn Marino 	free (value);
4633*86d7f5d3SJohn Marino 
4634*86d7f5d3SJohn Marino     if (workfile != NULL)
4635*86d7f5d3SJohn Marino     {
4636*86d7f5d3SJohn Marino 	int ret;
4637*86d7f5d3SJohn Marino 
4638*86d7f5d3SJohn Marino #ifdef PRESERVE_PERMISSIONS_SUPPORT
4639*86d7f5d3SJohn Marino 	if (!special_file && fclose (ofp) < 0)
4640*86d7f5d3SJohn Marino 	{
4641*86d7f5d3SJohn Marino 	    error (0, errno, "cannot close %s", workfile);
4642*86d7f5d3SJohn Marino 	    return 1;
4643*86d7f5d3SJohn Marino 	}
4644*86d7f5d3SJohn Marino 
4645*86d7f5d3SJohn Marino 	if (change_rcs_owner_or_group)
4646*86d7f5d3SJohn Marino 	{
4647*86d7f5d3SJohn Marino 	    if (chown (workfile, rcs_owner, rcs_group) < 0)
4648*86d7f5d3SJohn Marino 		error (0, errno, "could not change owner or group of %s",
4649*86d7f5d3SJohn Marino 		       workfile);
4650*86d7f5d3SJohn Marino 	}
4651*86d7f5d3SJohn Marino 
4652*86d7f5d3SJohn Marino 	ret = chmod (workfile,
4653*86d7f5d3SJohn Marino 		     change_rcs_mode
4654*86d7f5d3SJohn Marino 		     ? rcs_mode
4655*86d7f5d3SJohn Marino 		     : sb.st_mode & ~(S_IWRITE | S_IWGRP | S_IWOTH));
4656*86d7f5d3SJohn Marino #else
4657*86d7f5d3SJohn Marino 	if (fclose (ofp) < 0)
4658*86d7f5d3SJohn Marino 	{
4659*86d7f5d3SJohn Marino 	    error (0, errno, "cannot close %s", workfile);
4660*86d7f5d3SJohn Marino 	    return 1;
4661*86d7f5d3SJohn Marino 	}
4662*86d7f5d3SJohn Marino 
4663*86d7f5d3SJohn Marino 	ret = chmod (workfile,
4664*86d7f5d3SJohn Marino 		     sb.st_mode & ~(S_IWRITE | S_IWGRP | S_IWOTH));
4665*86d7f5d3SJohn Marino #endif
4666*86d7f5d3SJohn Marino 	if (ret < 0)
4667*86d7f5d3SJohn Marino 	{
4668*86d7f5d3SJohn Marino 	    error (0, errno, "cannot change mode of file %s",
4669*86d7f5d3SJohn Marino 		   workfile);
4670*86d7f5d3SJohn Marino 	}
4671*86d7f5d3SJohn Marino     }
4672*86d7f5d3SJohn Marino     else if (sout != RUN_TTY)
4673*86d7f5d3SJohn Marino     {
4674*86d7f5d3SJohn Marino 	if (
4675*86d7f5d3SJohn Marino #ifdef PRESERVE_PERMISSIONS_SUPPORT
4676*86d7f5d3SJohn Marino 	    !special_file &&
4677*86d7f5d3SJohn Marino #endif
4678*86d7f5d3SJohn Marino 	    fclose (ofp) < 0)
4679*86d7f5d3SJohn Marino 	{
4680*86d7f5d3SJohn Marino 	    error (0, errno, "cannot close %s", sout);
4681*86d7f5d3SJohn Marino 	    return 1;
4682*86d7f5d3SJohn Marino 	}
4683*86d7f5d3SJohn Marino     }
4684*86d7f5d3SJohn Marino 
4685*86d7f5d3SJohn Marino #ifdef PRESERVE_PERMISSIONS_SUPPORT
4686*86d7f5d3SJohn Marino     /* If we are in the business of preserving hardlinks, then
4687*86d7f5d3SJohn Marino        mark this file as having been checked out. */
4688*86d7f5d3SJohn Marino     if (preserve_perms && workfile != NULL)
4689*86d7f5d3SJohn Marino 	update_hardlink_info (workfile);
4690*86d7f5d3SJohn Marino #endif
4691*86d7f5d3SJohn Marino 
4692*86d7f5d3SJohn Marino     return 0;
4693*86d7f5d3SJohn Marino }
4694*86d7f5d3SJohn Marino 
4695*86d7f5d3SJohn Marino 
4696*86d7f5d3SJohn Marino 
4697*86d7f5d3SJohn Marino /* Find the delta currently locked by the user.  From the `ci' man page:
4698*86d7f5d3SJohn Marino 
4699*86d7f5d3SJohn Marino 	"If rev is omitted, ci tries to  derive  the  new  revision
4700*86d7f5d3SJohn Marino 	 number  from  the  caller's  last lock.  If the caller has
4701*86d7f5d3SJohn Marino 	 locked the tip revision of a branch, the new  revision  is
4702*86d7f5d3SJohn Marino 	 appended  to  that  branch.   The  new  revision number is
4703*86d7f5d3SJohn Marino 	 obtained by incrementing the tip revision number.  If  the
4704*86d7f5d3SJohn Marino 	 caller  locked a non-tip revision, a new branch is started
4705*86d7f5d3SJohn Marino 	 at that revision by incrementing the highest branch number
4706*86d7f5d3SJohn Marino 	 at  that  revision.   The default initial branch and level
4707*86d7f5d3SJohn Marino 	 numbers are 1.
4708*86d7f5d3SJohn Marino 
4709*86d7f5d3SJohn Marino 	 If rev is omitted and the caller has no lock, but owns the
4710*86d7f5d3SJohn Marino 	 file  and  locking is not set to strict, then the revision
4711*86d7f5d3SJohn Marino 	 is appended to the default branch (normally the trunk; see
4712*86d7f5d3SJohn Marino 	 the -b option of rcs(1))."
4713*86d7f5d3SJohn Marino 
4714*86d7f5d3SJohn Marino    RCS_findlock_or_tip finds the unique revision locked by the caller
4715*86d7f5d3SJohn Marino    and returns its delta node.  If the caller has not locked any
4716*86d7f5d3SJohn Marino    revisions (and is permitted to commit to an unlocked delta, as
4717*86d7f5d3SJohn Marino    described above), return the tip of the default branch. */
4718*86d7f5d3SJohn Marino static RCSVers *
RCS_findlock_or_tip(RCSNode * rcs)4719*86d7f5d3SJohn Marino RCS_findlock_or_tip (RCSNode *rcs)
4720*86d7f5d3SJohn Marino {
4721*86d7f5d3SJohn Marino     char *user = getcaller();
4722*86d7f5d3SJohn Marino     Node *lock, *p;
4723*86d7f5d3SJohn Marino     List *locklist;
4724*86d7f5d3SJohn Marino 
4725*86d7f5d3SJohn Marino     /* Find unique delta locked by caller. This code is very similar
4726*86d7f5d3SJohn Marino        to the code in RCS_unlock -- perhaps it could be abstracted
4727*86d7f5d3SJohn Marino        into a RCS_findlock function. */
4728*86d7f5d3SJohn Marino     locklist = RCS_getlocks (rcs);
4729*86d7f5d3SJohn Marino     lock = NULL;
4730*86d7f5d3SJohn Marino     for (p = locklist->list->next; p != locklist->list; p = p->next)
4731*86d7f5d3SJohn Marino     {
4732*86d7f5d3SJohn Marino 	if (STREQ (p->data, user))
4733*86d7f5d3SJohn Marino 	{
4734*86d7f5d3SJohn Marino 	    if (lock != NULL)
4735*86d7f5d3SJohn Marino 	    {
4736*86d7f5d3SJohn Marino 		error (0, 0, "\
4737*86d7f5d3SJohn Marino %s: multiple revisions locked by %s; please specify one", rcs->print_path, user);
4738*86d7f5d3SJohn Marino 		return NULL;
4739*86d7f5d3SJohn Marino 	    }
4740*86d7f5d3SJohn Marino 	    lock = p;
4741*86d7f5d3SJohn Marino 	}
4742*86d7f5d3SJohn Marino     }
4743*86d7f5d3SJohn Marino 
4744*86d7f5d3SJohn Marino     if (lock != NULL)
4745*86d7f5d3SJohn Marino     {
4746*86d7f5d3SJohn Marino 	/* Found an old lock, but check that the revision still exists. */
4747*86d7f5d3SJohn Marino 	p = findnode (rcs->versions, lock->key);
4748*86d7f5d3SJohn Marino 	if (p == NULL)
4749*86d7f5d3SJohn Marino 	{
4750*86d7f5d3SJohn Marino 	    error (0, 0, "%s: can't unlock nonexistent revision %s",
4751*86d7f5d3SJohn Marino 		   rcs->print_path,
4752*86d7f5d3SJohn Marino 		   lock->key);
4753*86d7f5d3SJohn Marino 	    return NULL;
4754*86d7f5d3SJohn Marino 	}
4755*86d7f5d3SJohn Marino 	return p->data;
4756*86d7f5d3SJohn Marino     }
4757*86d7f5d3SJohn Marino 
4758*86d7f5d3SJohn Marino     /* No existing lock.  The RCS rule is that this is an error unless
4759*86d7f5d3SJohn Marino        locking is nonstrict AND the file is owned by the current
4760*86d7f5d3SJohn Marino        user.  Trying to determine the latter is a portability nightmare
4761*86d7f5d3SJohn Marino        in the face of NT, VMS, AFS, and other systems with non-unix-like
4762*86d7f5d3SJohn Marino        ideas of users and owners.  In the case of CVS, we should never get
4763*86d7f5d3SJohn Marino        here (as long as the traditional behavior of making sure to call
4764*86d7f5d3SJohn Marino        RCS_lock persists).  Anyway, we skip the RCS error checks
4765*86d7f5d3SJohn Marino        and just return the default branch or head.  The reasoning is that
4766*86d7f5d3SJohn Marino        those error checks are to make users lock before a checkin, and we do
4767*86d7f5d3SJohn Marino        that in other ways if at all anyway (e.g. rcslock.pl).  */
4768*86d7f5d3SJohn Marino 
4769*86d7f5d3SJohn Marino     p = findnode (rcs->versions, RCS_getbranch (rcs, rcs->branch, 0));
4770*86d7f5d3SJohn Marino     if (!p)
4771*86d7f5d3SJohn Marino     {
4772*86d7f5d3SJohn Marino 	error (0, 0, "RCS file `%s' does not contain its default revision.",
4773*86d7f5d3SJohn Marino 	       rcs->path);
4774*86d7f5d3SJohn Marino 	return NULL;
4775*86d7f5d3SJohn Marino     }
4776*86d7f5d3SJohn Marino 
4777*86d7f5d3SJohn Marino     return p->data;
4778*86d7f5d3SJohn Marino }
4779*86d7f5d3SJohn Marino 
4780*86d7f5d3SJohn Marino 
4781*86d7f5d3SJohn Marino 
4782*86d7f5d3SJohn Marino /* Revision number string, R, must contain a `.'.
4783*86d7f5d3SJohn Marino    Return a newly-malloc'd copy of the prefix of R up
4784*86d7f5d3SJohn Marino    to but not including the final `.'.  */
4785*86d7f5d3SJohn Marino static char *
truncate_revnum(const char * r)4786*86d7f5d3SJohn Marino truncate_revnum (const char *r)
4787*86d7f5d3SJohn Marino {
4788*86d7f5d3SJohn Marino     size_t len;
4789*86d7f5d3SJohn Marino     char *new_r;
4790*86d7f5d3SJohn Marino     char *dot = strrchr (r, '.');
4791*86d7f5d3SJohn Marino 
4792*86d7f5d3SJohn Marino     assert (dot);
4793*86d7f5d3SJohn Marino     len = dot - r;
4794*86d7f5d3SJohn Marino     new_r = xmalloc (len + 1);
4795*86d7f5d3SJohn Marino     memcpy (new_r, r, len);
4796*86d7f5d3SJohn Marino     *(new_r + len) = '\0';
4797*86d7f5d3SJohn Marino     return new_r;
4798*86d7f5d3SJohn Marino }
4799*86d7f5d3SJohn Marino 
4800*86d7f5d3SJohn Marino 
4801*86d7f5d3SJohn Marino 
4802*86d7f5d3SJohn Marino /* Revision number string, R, must contain a `.'.
4803*86d7f5d3SJohn Marino    R must be writable.  Replace the rightmost `.' in R with
4804*86d7f5d3SJohn Marino    the NUL byte and return a pointer to that NUL byte.  */
4805*86d7f5d3SJohn Marino static char *
truncate_revnum_in_place(char * r)4806*86d7f5d3SJohn Marino truncate_revnum_in_place (char *r)
4807*86d7f5d3SJohn Marino {
4808*86d7f5d3SJohn Marino     char *dot = strrchr (r, '.');
4809*86d7f5d3SJohn Marino     assert (dot);
4810*86d7f5d3SJohn Marino     *dot = '\0';
4811*86d7f5d3SJohn Marino     return dot;
4812*86d7f5d3SJohn Marino }
4813*86d7f5d3SJohn Marino 
4814*86d7f5d3SJohn Marino 
4815*86d7f5d3SJohn Marino 
4816*86d7f5d3SJohn Marino /* Revision number strings, R and S, must each contain a `.'.
4817*86d7f5d3SJohn Marino    R and S must be writable and must have the same number of dots.
4818*86d7f5d3SJohn Marino    Truncate R and S for the comparison, then restored them to their
4819*86d7f5d3SJohn Marino    original state.
4820*86d7f5d3SJohn Marino    Return the result (see compare_revnums) of comparing R and S
4821*86d7f5d3SJohn Marino    ignoring differences in any component after the rightmost `.'.  */
4822*86d7f5d3SJohn Marino static int
compare_truncated_revnums(char * r,char * s)4823*86d7f5d3SJohn Marino compare_truncated_revnums (char *r, char *s)
4824*86d7f5d3SJohn Marino {
4825*86d7f5d3SJohn Marino     char *r_dot = truncate_revnum_in_place (r);
4826*86d7f5d3SJohn Marino     char *s_dot = truncate_revnum_in_place (s);
4827*86d7f5d3SJohn Marino     int cmp;
4828*86d7f5d3SJohn Marino 
4829*86d7f5d3SJohn Marino     assert (numdots (r) == numdots (s));
4830*86d7f5d3SJohn Marino 
4831*86d7f5d3SJohn Marino     cmp = compare_revnums (r, s);
4832*86d7f5d3SJohn Marino 
4833*86d7f5d3SJohn Marino     *r_dot = '.';
4834*86d7f5d3SJohn Marino     *s_dot = '.';
4835*86d7f5d3SJohn Marino 
4836*86d7f5d3SJohn Marino     return cmp;
4837*86d7f5d3SJohn Marino }
4838*86d7f5d3SJohn Marino 
4839*86d7f5d3SJohn Marino 
4840*86d7f5d3SJohn Marino 
4841*86d7f5d3SJohn Marino /* Return a malloc'd copy of the string representing the highest branch
4842*86d7f5d3SJohn Marino    number on BRANCHNODE.  If there are no branches on BRANCHNODE, return NULL.
4843*86d7f5d3SJohn Marino    FIXME: isn't the max rev always the last one?
4844*86d7f5d3SJohn Marino    If so, we don't even need a loop.  */
4845*86d7f5d3SJohn Marino static char *
max_rev(const RCSVers * branchnode)4846*86d7f5d3SJohn Marino max_rev (const RCSVers *branchnode)
4847*86d7f5d3SJohn Marino {
4848*86d7f5d3SJohn Marino     Node *head;
4849*86d7f5d3SJohn Marino     Node *bp;
4850*86d7f5d3SJohn Marino     char *max;
4851*86d7f5d3SJohn Marino 
4852*86d7f5d3SJohn Marino     if (branchnode->branches == NULL)
4853*86d7f5d3SJohn Marino     {
4854*86d7f5d3SJohn Marino         return NULL;
4855*86d7f5d3SJohn Marino     }
4856*86d7f5d3SJohn Marino 
4857*86d7f5d3SJohn Marino     max = NULL;
4858*86d7f5d3SJohn Marino     head = branchnode->branches->list;
4859*86d7f5d3SJohn Marino     for (bp = head->next; bp != head; bp = bp->next)
4860*86d7f5d3SJohn Marino     {
4861*86d7f5d3SJohn Marino 	if (max == NULL || compare_truncated_revnums (max, bp->key) < 0)
4862*86d7f5d3SJohn Marino 	{
4863*86d7f5d3SJohn Marino 	    max = bp->key;
4864*86d7f5d3SJohn Marino 	}
4865*86d7f5d3SJohn Marino     }
4866*86d7f5d3SJohn Marino     assert (max);
4867*86d7f5d3SJohn Marino 
4868*86d7f5d3SJohn Marino     return truncate_revnum (max);
4869*86d7f5d3SJohn Marino }
4870*86d7f5d3SJohn Marino 
4871*86d7f5d3SJohn Marino 
4872*86d7f5d3SJohn Marino 
4873*86d7f5d3SJohn Marino /* Create BRANCH in RCS's delta tree.  BRANCH may be either a branch
4874*86d7f5d3SJohn Marino    number or a revision number.  In the former case, create the branch
4875*86d7f5d3SJohn Marino    with the specified number; in the latter case, create a new branch
4876*86d7f5d3SJohn Marino    rooted at node BRANCH with a higher branch number than any others.
4877*86d7f5d3SJohn Marino    Return the number of the tip node on the new branch. */
4878*86d7f5d3SJohn Marino static char *
RCS_addbranch(RCSNode * rcs,const char * branch)4879*86d7f5d3SJohn Marino RCS_addbranch (RCSNode *rcs, const char *branch)
4880*86d7f5d3SJohn Marino {
4881*86d7f5d3SJohn Marino     char *branchpoint, *newrevnum;
4882*86d7f5d3SJohn Marino     Node *nodep, *bp;
4883*86d7f5d3SJohn Marino     Node *marker;
4884*86d7f5d3SJohn Marino     RCSVers *branchnode;
4885*86d7f5d3SJohn Marino 
4886*86d7f5d3SJohn Marino     assert (branch);
4887*86d7f5d3SJohn Marino 
4888*86d7f5d3SJohn Marino     /* Append to end by default.  */
4889*86d7f5d3SJohn Marino     marker = NULL;
4890*86d7f5d3SJohn Marino 
4891*86d7f5d3SJohn Marino     branchpoint = xstrdup (branch);
4892*86d7f5d3SJohn Marino     if ((numdots (branchpoint) & 1) == 0)
4893*86d7f5d3SJohn Marino     {
4894*86d7f5d3SJohn Marino 	truncate_revnum_in_place (branchpoint);
4895*86d7f5d3SJohn Marino     }
4896*86d7f5d3SJohn Marino 
4897*86d7f5d3SJohn Marino     /* Find the branch rooted at BRANCHPOINT. */
4898*86d7f5d3SJohn Marino     nodep = findnode (rcs->versions, branchpoint);
4899*86d7f5d3SJohn Marino     if (nodep == NULL)
4900*86d7f5d3SJohn Marino     {
4901*86d7f5d3SJohn Marino 	error (0, 0, "%s: can't find branch point %s", rcs->print_path, branchpoint);
4902*86d7f5d3SJohn Marino 	free (branchpoint);
4903*86d7f5d3SJohn Marino 	return NULL;
4904*86d7f5d3SJohn Marino     }
4905*86d7f5d3SJohn Marino     free (branchpoint);
4906*86d7f5d3SJohn Marino     branchnode = nodep->data;
4907*86d7f5d3SJohn Marino 
4908*86d7f5d3SJohn Marino     /* If BRANCH was a full branch number, make sure it is higher than MAX. */
4909*86d7f5d3SJohn Marino     if ((numdots (branch) & 1) == 1)
4910*86d7f5d3SJohn Marino     {
4911*86d7f5d3SJohn Marino 	if (branchnode->branches == NULL)
4912*86d7f5d3SJohn Marino 	{
4913*86d7f5d3SJohn Marino 	    /* We have to create the first branch on this node, which means
4914*86d7f5d3SJohn Marino 	       appending ".2" to the revision number. */
4915*86d7f5d3SJohn Marino 	    newrevnum = Xasprintf ("%s.2", branch);
4916*86d7f5d3SJohn Marino 	}
4917*86d7f5d3SJohn Marino 	else
4918*86d7f5d3SJohn Marino 	{
4919*86d7f5d3SJohn Marino 	    char *max = max_rev (branchnode);
4920*86d7f5d3SJohn Marino 	    assert (max);
4921*86d7f5d3SJohn Marino 	    newrevnum = increment_revnum (max);
4922*86d7f5d3SJohn Marino 	    free (max);
4923*86d7f5d3SJohn Marino 	}
4924*86d7f5d3SJohn Marino     }
4925*86d7f5d3SJohn Marino     else
4926*86d7f5d3SJohn Marino     {
4927*86d7f5d3SJohn Marino 	newrevnum = xstrdup (branch);
4928*86d7f5d3SJohn Marino 
4929*86d7f5d3SJohn Marino 	if (branchnode->branches != NULL)
4930*86d7f5d3SJohn Marino 	{
4931*86d7f5d3SJohn Marino 	    Node *head;
4932*86d7f5d3SJohn Marino 	    Node *bp;
4933*86d7f5d3SJohn Marino 
4934*86d7f5d3SJohn Marino 	    /* Find the position of this new branch in the sorted list
4935*86d7f5d3SJohn Marino 	       of branches.  */
4936*86d7f5d3SJohn Marino 	    head = branchnode->branches->list;
4937*86d7f5d3SJohn Marino 	    for (bp = head->next; bp != head; bp = bp->next)
4938*86d7f5d3SJohn Marino 	    {
4939*86d7f5d3SJohn Marino 		char *dot;
4940*86d7f5d3SJohn Marino 		int found_pos;
4941*86d7f5d3SJohn Marino 
4942*86d7f5d3SJohn Marino 		/* The existing list must be sorted on increasing revnum.  */
4943*86d7f5d3SJohn Marino 		assert (bp->next == head
4944*86d7f5d3SJohn Marino 			|| compare_truncated_revnums (bp->key,
4945*86d7f5d3SJohn Marino 						      bp->next->key) < 0);
4946*86d7f5d3SJohn Marino 		dot = truncate_revnum_in_place (bp->key);
4947*86d7f5d3SJohn Marino 		found_pos = (compare_revnums (branch, bp->key) < 0);
4948*86d7f5d3SJohn Marino 		*dot = '.';
4949*86d7f5d3SJohn Marino 
4950*86d7f5d3SJohn Marino 		if (found_pos)
4951*86d7f5d3SJohn Marino 		{
4952*86d7f5d3SJohn Marino 		    break;
4953*86d7f5d3SJohn Marino 		}
4954*86d7f5d3SJohn Marino 	    }
4955*86d7f5d3SJohn Marino 	    marker = bp;
4956*86d7f5d3SJohn Marino 	}
4957*86d7f5d3SJohn Marino     }
4958*86d7f5d3SJohn Marino 
4959*86d7f5d3SJohn Marino     newrevnum = xrealloc (newrevnum, strlen (newrevnum) + 3);
4960*86d7f5d3SJohn Marino     strcat (newrevnum, ".1");
4961*86d7f5d3SJohn Marino 
4962*86d7f5d3SJohn Marino     /* Add this new revision number to BRANCHPOINT's branches list. */
4963*86d7f5d3SJohn Marino     if (branchnode->branches == NULL)
4964*86d7f5d3SJohn Marino 	branchnode->branches = getlist();
4965*86d7f5d3SJohn Marino     bp = getnode();
4966*86d7f5d3SJohn Marino     bp->key = xstrdup (newrevnum);
4967*86d7f5d3SJohn Marino 
4968*86d7f5d3SJohn Marino     /* Append to the end of the list by default, that is, just before
4969*86d7f5d3SJohn Marino        the header node, `list'.  */
4970*86d7f5d3SJohn Marino     if (marker == NULL)
4971*86d7f5d3SJohn Marino 	marker = branchnode->branches->list;
4972*86d7f5d3SJohn Marino 
4973*86d7f5d3SJohn Marino     {
4974*86d7f5d3SJohn Marino 	int fail;
4975*86d7f5d3SJohn Marino 	fail = insert_before (branchnode->branches, marker, bp);
4976*86d7f5d3SJohn Marino 	assert (!fail);
4977*86d7f5d3SJohn Marino     }
4978*86d7f5d3SJohn Marino 
4979*86d7f5d3SJohn Marino     return newrevnum;
4980*86d7f5d3SJohn Marino }
4981*86d7f5d3SJohn Marino 
4982*86d7f5d3SJohn Marino 
4983*86d7f5d3SJohn Marino 
4984*86d7f5d3SJohn Marino /* Check in to RCSFILE with revision REV (which must be greater than
4985*86d7f5d3SJohn Marino    the largest revision) and message MESSAGE (which is checked for
4986*86d7f5d3SJohn Marino    validity).  If FLAGS & RCS_FLAGS_DEAD, check in a dead revision.
4987*86d7f5d3SJohn Marino    If FLAGS & RCS_FLAGS_QUIET, tell ci to be quiet.  If FLAGS &
4988*86d7f5d3SJohn Marino    RCS_FLAGS_MODTIME, use the working file's modification time for the
4989*86d7f5d3SJohn Marino    checkin time.  WORKFILE is the working file to check in from, or
4990*86d7f5d3SJohn Marino    NULL to use the usual RCS rules for deriving it from the RCSFILE.
4991*86d7f5d3SJohn Marino    If FLAGS & RCS_FLAGS_KEEPFILE, don't unlink the working file;
4992*86d7f5d3SJohn Marino    unlinking the working file is standard RCS behavior, but is rarely
4993*86d7f5d3SJohn Marino    appropriate for CVS.
4994*86d7f5d3SJohn Marino 
4995*86d7f5d3SJohn Marino    UPDATE_DIR is used to print the path for the file.  This argument is
4996*86d7f5d3SJohn Marino    unnecessary when FLAGS & RCS_FLAGS_QUIET since the path won't be printed
4997*86d7f5d3SJohn Marino    anyhow.
4998*86d7f5d3SJohn Marino 
4999*86d7f5d3SJohn Marino    This function should almost exactly mimic the behavior of `rcs ci'.  The
5000*86d7f5d3SJohn Marino    principal point of difference is the support here for preserving file
5001*86d7f5d3SJohn Marino    ownership and permissions in the delta nodes.  This is not a clean
5002*86d7f5d3SJohn Marino    solution -- precisely because it diverges from RCS's behavior -- but
5003*86d7f5d3SJohn Marino    it doesn't seem feasible to do this anywhere else in the code. [-twp]
5004*86d7f5d3SJohn Marino 
5005*86d7f5d3SJohn Marino    Return value is -1 for error (and errno is set to indicate the
5006*86d7f5d3SJohn Marino    error), positive for error (and an error message has been printed),
5007*86d7f5d3SJohn Marino    or zero for success.  */
5008*86d7f5d3SJohn Marino int
RCS_checkin(RCSNode * rcs,const char * update_dir,const char * workfile_in,const char * message,const char * rev,time_t citime,int flags)5009*86d7f5d3SJohn Marino RCS_checkin (RCSNode *rcs, const char *update_dir, const char *workfile_in,
5010*86d7f5d3SJohn Marino 	     const char *message, const char *rev, time_t citime, int flags)
5011*86d7f5d3SJohn Marino {
5012*86d7f5d3SJohn Marino     RCSVers *delta, *commitpt;
5013*86d7f5d3SJohn Marino     Deltatext *dtext;
5014*86d7f5d3SJohn Marino     Node *nodep;
5015*86d7f5d3SJohn Marino     char *tmpfile, *changefile;
5016*86d7f5d3SJohn Marino     int dargc = 0;
5017*86d7f5d3SJohn Marino     size_t darg_allocated = 0;
5018*86d7f5d3SJohn Marino     char **dargv = NULL;
5019*86d7f5d3SJohn Marino     size_t bufsize;
5020*86d7f5d3SJohn Marino     int status, checkin_quiet;
5021*86d7f5d3SJohn Marino     struct tm *ftm;
5022*86d7f5d3SJohn Marino     time_t modtime;
5023*86d7f5d3SJohn Marino     int adding_branch = 0;
5024*86d7f5d3SJohn Marino     char *workfile = xstrdup (workfile_in);
5025*86d7f5d3SJohn Marino #ifdef PRESERVE_PERMISSIONS_SUPPORT
5026*86d7f5d3SJohn Marino     struct stat sb;
5027*86d7f5d3SJohn Marino #endif
5028*86d7f5d3SJohn Marino     Node *np;
5029*86d7f5d3SJohn Marino 
5030*86d7f5d3SJohn Marino     commitpt = NULL;
5031*86d7f5d3SJohn Marino 
5032*86d7f5d3SJohn Marino     if (rcs->flags & PARTIAL)
5033*86d7f5d3SJohn Marino 	RCS_reparsercsfile (rcs, NULL, NULL);
5034*86d7f5d3SJohn Marino 
5035*86d7f5d3SJohn Marino     /* Get basename of working file.  Is there a library function to
5036*86d7f5d3SJohn Marino        do this?  I couldn't find one. -twp */
5037*86d7f5d3SJohn Marino     if (workfile == NULL)
5038*86d7f5d3SJohn Marino     {
5039*86d7f5d3SJohn Marino 	char *p;
5040*86d7f5d3SJohn Marino 	int extlen = strlen (RCSEXT);
5041*86d7f5d3SJohn Marino 	assert (rcs->path);
5042*86d7f5d3SJohn Marino 	workfile = xstrdup (last_component (rcs->path));
5043*86d7f5d3SJohn Marino 	p = workfile + (strlen (workfile) - extlen);
5044*86d7f5d3SJohn Marino 	assert (strncmp (p, RCSEXT, extlen) == 0);
5045*86d7f5d3SJohn Marino 	*p = '\0';
5046*86d7f5d3SJohn Marino     }
5047*86d7f5d3SJohn Marino 
5048*86d7f5d3SJohn Marino     /* If the filename is a symbolic link, follow it and replace it
5049*86d7f5d3SJohn Marino        with the destination of the link.  We need to do this before
5050*86d7f5d3SJohn Marino        calling rcs_internal_lockfile, or else we won't put the lock in
5051*86d7f5d3SJohn Marino        the right place. */
5052*86d7f5d3SJohn Marino     resolve_symlink (&(rcs->path));
5053*86d7f5d3SJohn Marino 
5054*86d7f5d3SJohn Marino     checkin_quiet = flags & RCS_FLAGS_QUIET;
5055*86d7f5d3SJohn Marino     if (!(checkin_quiet || really_quiet))
5056*86d7f5d3SJohn Marino     {
5057*86d7f5d3SJohn Marino 	cvs_output (rcs->path, 0);
5058*86d7f5d3SJohn Marino 	cvs_output ("  <--  ", 7);
5059*86d7f5d3SJohn Marino 	if (update_dir && strlen (update_dir))
5060*86d7f5d3SJohn Marino 	{
5061*86d7f5d3SJohn Marino 	    cvs_output (update_dir, 0);
5062*86d7f5d3SJohn Marino 	    cvs_output ("/", 1);
5063*86d7f5d3SJohn Marino 	}
5064*86d7f5d3SJohn Marino 	cvs_output (workfile, 0);
5065*86d7f5d3SJohn Marino 	cvs_output ("\n", 1);
5066*86d7f5d3SJohn Marino     }
5067*86d7f5d3SJohn Marino 
5068*86d7f5d3SJohn Marino     /* Create new delta node. */
5069*86d7f5d3SJohn Marino     delta = xmalloc (sizeof (RCSVers));
5070*86d7f5d3SJohn Marino     memset (delta, 0, sizeof (RCSVers));
5071*86d7f5d3SJohn Marino     delta->author = xstrdup (getcaller ());
5072*86d7f5d3SJohn Marino     if (flags & RCS_FLAGS_MODTIME)
5073*86d7f5d3SJohn Marino     {
5074*86d7f5d3SJohn Marino 	struct stat ws;
5075*86d7f5d3SJohn Marino 	if (stat (workfile, &ws) < 0)
5076*86d7f5d3SJohn Marino 	{
5077*86d7f5d3SJohn Marino 	    error (1, errno, "cannot stat %s", workfile);
5078*86d7f5d3SJohn Marino 	}
5079*86d7f5d3SJohn Marino 	modtime = ws.st_mtime;
5080*86d7f5d3SJohn Marino     }
5081*86d7f5d3SJohn Marino     else if (flags & RCS_FLAGS_USETIME)
5082*86d7f5d3SJohn Marino 	modtime = citime;
5083*86d7f5d3SJohn Marino     else
5084*86d7f5d3SJohn Marino 	(void) time (&modtime);
5085*86d7f5d3SJohn Marino     ftm = gmtime (&modtime);
5086*86d7f5d3SJohn Marino     delta->date = Xasprintf (DATEFORM,
5087*86d7f5d3SJohn Marino 			     ftm->tm_year + (ftm->tm_year < 100 ? 0 : 1900),
5088*86d7f5d3SJohn Marino 			     ftm->tm_mon + 1, ftm->tm_mday, ftm->tm_hour,
5089*86d7f5d3SJohn Marino 			     ftm->tm_min, ftm->tm_sec);
5090*86d7f5d3SJohn Marino     if (flags & RCS_FLAGS_DEAD)
5091*86d7f5d3SJohn Marino     {
5092*86d7f5d3SJohn Marino 	delta->state = xstrdup (RCSDEAD);
5093*86d7f5d3SJohn Marino 	delta->dead = 1;
5094*86d7f5d3SJohn Marino     }
5095*86d7f5d3SJohn Marino     else
5096*86d7f5d3SJohn Marino 	delta->state = xstrdup ("Exp");
5097*86d7f5d3SJohn Marino 
5098*86d7f5d3SJohn Marino     delta->other_delta = getlist();
5099*86d7f5d3SJohn Marino 
5100*86d7f5d3SJohn Marino     /* save the commit ID */
5101*86d7f5d3SJohn Marino     np = getnode();
5102*86d7f5d3SJohn Marino     np->type = RCSFIELD;
5103*86d7f5d3SJohn Marino     np->key = xstrdup ("commitid");
5104*86d7f5d3SJohn Marino     np->data = xstrdup(global_session_id);
5105*86d7f5d3SJohn Marino     addnode (delta->other_delta, np);
5106*86d7f5d3SJohn Marino 
5107*86d7f5d3SJohn Marino 
5108*86d7f5d3SJohn Marino #ifdef PRESERVE_PERMISSIONS_SUPPORT
5109*86d7f5d3SJohn Marino     /* If permissions should be preserved on this project, then
5110*86d7f5d3SJohn Marino        save the permission info. */
5111*86d7f5d3SJohn Marino     if (preserve_perms)
5112*86d7f5d3SJohn Marino     {
5113*86d7f5d3SJohn Marino 	Node *np;
5114*86d7f5d3SJohn Marino 	char buf[64];	/* static buffer should be safe: see usage. -twp */
5115*86d7f5d3SJohn Marino 
5116*86d7f5d3SJohn Marino 	delta->other_delta = getlist();
5117*86d7f5d3SJohn Marino 
5118*86d7f5d3SJohn Marino 	if (lstat (workfile, &sb) < 0)
5119*86d7f5d3SJohn Marino 	    error (1, errno, "cannot lstat %s", workfile);
5120*86d7f5d3SJohn Marino 
5121*86d7f5d3SJohn Marino 	if (S_ISLNK (sb.st_mode))
5122*86d7f5d3SJohn Marino 	{
5123*86d7f5d3SJohn Marino 	    np = getnode();
5124*86d7f5d3SJohn Marino 	    np->type = RCSFIELD;
5125*86d7f5d3SJohn Marino 	    np->key = xstrdup ("symlink");
5126*86d7f5d3SJohn Marino 	    np->data = Xreadlink (workfile, sb.st_size);
5127*86d7f5d3SJohn Marino 	    addnode (delta->other_delta, np);
5128*86d7f5d3SJohn Marino 	}
5129*86d7f5d3SJohn Marino 	else
5130*86d7f5d3SJohn Marino 	{
5131*86d7f5d3SJohn Marino 	    (void) sprintf (buf, "%u", sb.st_uid);
5132*86d7f5d3SJohn Marino 	    np = getnode();
5133*86d7f5d3SJohn Marino 	    np->type = RCSFIELD;
5134*86d7f5d3SJohn Marino 	    np->key = xstrdup ("owner");
5135*86d7f5d3SJohn Marino 	    np->data = xstrdup (buf);
5136*86d7f5d3SJohn Marino 	    addnode (delta->other_delta, np);
5137*86d7f5d3SJohn Marino 
5138*86d7f5d3SJohn Marino 	    (void) sprintf (buf, "%u", sb.st_gid);
5139*86d7f5d3SJohn Marino 	    np = getnode();
5140*86d7f5d3SJohn Marino 	    np->type = RCSFIELD;
5141*86d7f5d3SJohn Marino 	    np->key = xstrdup ("group");
5142*86d7f5d3SJohn Marino 	    np->data = xstrdup (buf);
5143*86d7f5d3SJohn Marino 	    addnode (delta->other_delta, np);
5144*86d7f5d3SJohn Marino 
5145*86d7f5d3SJohn Marino 	    (void) sprintf (buf, "%o", sb.st_mode & 07777);
5146*86d7f5d3SJohn Marino 	    np = getnode();
5147*86d7f5d3SJohn Marino 	    np->type = RCSFIELD;
5148*86d7f5d3SJohn Marino 	    np->key = xstrdup ("permissions");
5149*86d7f5d3SJohn Marino 	    np->data = xstrdup (buf);
5150*86d7f5d3SJohn Marino 	    addnode (delta->other_delta, np);
5151*86d7f5d3SJohn Marino 
5152*86d7f5d3SJohn Marino 	    /* Save device number. */
5153*86d7f5d3SJohn Marino 	    switch (sb.st_mode & S_IFMT)
5154*86d7f5d3SJohn Marino 	    {
5155*86d7f5d3SJohn Marino 		case S_IFREG: break;
5156*86d7f5d3SJohn Marino 		case S_IFCHR:
5157*86d7f5d3SJohn Marino 		case S_IFBLK:
5158*86d7f5d3SJohn Marino # ifdef HAVE_STRUCT_STAT_ST_RDEV
5159*86d7f5d3SJohn Marino 		    np = getnode();
5160*86d7f5d3SJohn Marino 		    np->type = RCSFIELD;
5161*86d7f5d3SJohn Marino 		    np->key = xstrdup ("special");
5162*86d7f5d3SJohn Marino 		    sprintf (buf, "%s %lu",
5163*86d7f5d3SJohn Marino 			     ((sb.st_mode & S_IFMT) == S_IFCHR
5164*86d7f5d3SJohn Marino 			      ? "character" : "block"),
5165*86d7f5d3SJohn Marino 			     (unsigned long) sb.st_rdev);
5166*86d7f5d3SJohn Marino 		    np->data = xstrdup (buf);
5167*86d7f5d3SJohn Marino 		    addnode (delta->other_delta, np);
5168*86d7f5d3SJohn Marino # else
5169*86d7f5d3SJohn Marino 		    error (0, 0,
5170*86d7f5d3SJohn Marino "can't preserve %s: unable to save device files on this system",
5171*86d7f5d3SJohn Marino workfile);
5172*86d7f5d3SJohn Marino # endif
5173*86d7f5d3SJohn Marino 		    break;
5174*86d7f5d3SJohn Marino 
5175*86d7f5d3SJohn Marino 		default:
5176*86d7f5d3SJohn Marino 		    error (0, 0, "special file %s has unknown type", workfile);
5177*86d7f5d3SJohn Marino 	    }
5178*86d7f5d3SJohn Marino 
5179*86d7f5d3SJohn Marino 	    /* Save hardlinks. */
5180*86d7f5d3SJohn Marino 	    delta->hardlinks = list_linked_files_on_disk (workfile);
5181*86d7f5d3SJohn Marino 	}
5182*86d7f5d3SJohn Marino     }
5183*86d7f5d3SJohn Marino #endif
5184*86d7f5d3SJohn Marino 
5185*86d7f5d3SJohn Marino     /* Create a new deltatext node. */
5186*86d7f5d3SJohn Marino     dtext = xmalloc (sizeof (Deltatext));
5187*86d7f5d3SJohn Marino     memset (dtext, 0, sizeof (Deltatext));
5188*86d7f5d3SJohn Marino 
5189*86d7f5d3SJohn Marino     dtext->log = make_message_rcsvalid (message);
5190*86d7f5d3SJohn Marino 
5191*86d7f5d3SJohn Marino     /* If the delta tree is empty, then there's nothing to link the
5192*86d7f5d3SJohn Marino        new delta into.  So make a new delta tree, snarf the working
5193*86d7f5d3SJohn Marino        file contents, and just write the new RCS file. */
5194*86d7f5d3SJohn Marino     if (rcs->head == NULL)
5195*86d7f5d3SJohn Marino     {
5196*86d7f5d3SJohn Marino 	char *newrev;
5197*86d7f5d3SJohn Marino 	FILE *fout;
5198*86d7f5d3SJohn Marino 
5199*86d7f5d3SJohn Marino 	/* Figure out what the first revision number should be. */
5200*86d7f5d3SJohn Marino 	if (rev == NULL || *rev == '\0')
5201*86d7f5d3SJohn Marino 	    newrev = xstrdup ("1.1");
5202*86d7f5d3SJohn Marino 	else if (numdots (rev) == 0)
5203*86d7f5d3SJohn Marino 	{
5204*86d7f5d3SJohn Marino 	    newrev = Xasprintf ("%s.1", rev);
5205*86d7f5d3SJohn Marino 	}
5206*86d7f5d3SJohn Marino 	else
5207*86d7f5d3SJohn Marino 	    newrev = xstrdup (rev);
5208*86d7f5d3SJohn Marino 
5209*86d7f5d3SJohn Marino 	/* Don't need to xstrdup NEWREV because it's already dynamic, and
5210*86d7f5d3SJohn Marino 	   not used for anything else.  (Don't need to free it, either.) */
5211*86d7f5d3SJohn Marino 	rcs->head = newrev;
5212*86d7f5d3SJohn Marino 	delta->version = xstrdup (newrev);
5213*86d7f5d3SJohn Marino 	nodep = getnode();
5214*86d7f5d3SJohn Marino 	nodep->type = RCSVERS;
5215*86d7f5d3SJohn Marino 	nodep->delproc = rcsvers_delproc;
5216*86d7f5d3SJohn Marino 	nodep->data = delta;
5217*86d7f5d3SJohn Marino 	nodep->key = delta->version;
5218*86d7f5d3SJohn Marino 	(void) addnode (rcs->versions, nodep);
5219*86d7f5d3SJohn Marino 
5220*86d7f5d3SJohn Marino 	dtext->version = xstrdup (newrev);
5221*86d7f5d3SJohn Marino 	bufsize = 0;
5222*86d7f5d3SJohn Marino #ifdef PRESERVE_PERMISSIONS_SUPPORT
5223*86d7f5d3SJohn Marino 	if (preserve_perms && !S_ISREG (sb.st_mode))
5224*86d7f5d3SJohn Marino 	    /* Pretend file is empty.  */
5225*86d7f5d3SJohn Marino 	    bufsize = 0;
5226*86d7f5d3SJohn Marino 	else
5227*86d7f5d3SJohn Marino #endif
5228*86d7f5d3SJohn Marino 	get_file (workfile, workfile,
5229*86d7f5d3SJohn Marino 		  rcs->expand != NULL && STREQ (rcs->expand, "b") ? "rb" : "r",
5230*86d7f5d3SJohn Marino 		  &dtext->text, &bufsize, &dtext->len);
5231*86d7f5d3SJohn Marino 
5232*86d7f5d3SJohn Marino 	if (!(checkin_quiet || really_quiet))
5233*86d7f5d3SJohn Marino 	{
5234*86d7f5d3SJohn Marino 	    cvs_output ("initial revision: ", 0);
5235*86d7f5d3SJohn Marino 	    cvs_output (rcs->head, 0);
5236*86d7f5d3SJohn Marino 	    cvs_output ("\n", 1);
5237*86d7f5d3SJohn Marino 	}
5238*86d7f5d3SJohn Marino 
5239*86d7f5d3SJohn Marino 	/* We are probably about to invalidate any cached file.  */
5240*86d7f5d3SJohn Marino 	rcsbuf_cache_close ();
5241*86d7f5d3SJohn Marino 
5242*86d7f5d3SJohn Marino 	fout = rcs_internal_lockfile (rcs->path);
5243*86d7f5d3SJohn Marino 	RCS_putadmin (rcs, fout);
5244*86d7f5d3SJohn Marino 	RCS_putdtree (rcs, rcs->head, fout);
5245*86d7f5d3SJohn Marino 	RCS_putdesc (rcs, fout);
5246*86d7f5d3SJohn Marino 	rcs->delta_pos = ftello (fout);
5247*86d7f5d3SJohn Marino 	if (rcs->delta_pos == -1)
5248*86d7f5d3SJohn Marino 	    error (1, errno, "cannot ftello for %s", rcs->path);
5249*86d7f5d3SJohn Marino 	putdeltatext (fout, dtext);
5250*86d7f5d3SJohn Marino 	rcs_internal_unlockfile (fout, rcs->path);
5251*86d7f5d3SJohn Marino 
5252*86d7f5d3SJohn Marino 	if ((flags & RCS_FLAGS_KEEPFILE) == 0)
5253*86d7f5d3SJohn Marino 	{
5254*86d7f5d3SJohn Marino 	    if (unlink_file (workfile) < 0)
5255*86d7f5d3SJohn Marino 		/* FIXME-update-dir: message does not include update_dir.  */
5256*86d7f5d3SJohn Marino 		error (0, errno, "cannot remove %s", workfile);
5257*86d7f5d3SJohn Marino 	}
5258*86d7f5d3SJohn Marino 
5259*86d7f5d3SJohn Marino 	status = 0;
5260*86d7f5d3SJohn Marino 	goto checkin_done;
5261*86d7f5d3SJohn Marino     }
5262*86d7f5d3SJohn Marino 
5263*86d7f5d3SJohn Marino     /* Derive a new revision number.  From the `ci' man page:
5264*86d7f5d3SJohn Marino 
5265*86d7f5d3SJohn Marino 	 "If rev  is  a revision number, it must be higher than the
5266*86d7f5d3SJohn Marino 	 latest one on the branch to which  rev  belongs,  or  must
5267*86d7f5d3SJohn Marino 	 start a new branch.
5268*86d7f5d3SJohn Marino 
5269*86d7f5d3SJohn Marino 	 If  rev is a branch rather than a revision number, the new
5270*86d7f5d3SJohn Marino 	 revision is appended to that branch.  The level number  is
5271*86d7f5d3SJohn Marino 	 obtained  by  incrementing the tip revision number of that
5272*86d7f5d3SJohn Marino 	 branch.  If rev  indicates  a  non-existing  branch,  that
5273*86d7f5d3SJohn Marino 	 branch  is  created  with  the  initial  revision numbered
5274*86d7f5d3SJohn Marino 	 rev.1."
5275*86d7f5d3SJohn Marino 
5276*86d7f5d3SJohn Marino        RCS_findlock_or_tip handles the case where REV is omitted.
5277*86d7f5d3SJohn Marino        RCS 5.7 also permits REV to be "$" or to begin with a dot, but
5278*86d7f5d3SJohn Marino        we do not address those cases -- every routine that calls
5279*86d7f5d3SJohn Marino        RCS_checkin passes it a numeric revision. */
5280*86d7f5d3SJohn Marino 
5281*86d7f5d3SJohn Marino     if (rev == NULL || *rev == '\0')
5282*86d7f5d3SJohn Marino     {
5283*86d7f5d3SJohn Marino 	/* Figure out where the commit point is by looking for locks.
5284*86d7f5d3SJohn Marino 	   If the commit point is at the tip of a branch (or is the
5285*86d7f5d3SJohn Marino 	   head of the delta tree), then increment its revision number
5286*86d7f5d3SJohn Marino 	   to obtain the new revnum.  Otherwise, start a new
5287*86d7f5d3SJohn Marino 	   branch. */
5288*86d7f5d3SJohn Marino 	commitpt = RCS_findlock_or_tip (rcs);
5289*86d7f5d3SJohn Marino 	if (commitpt == NULL)
5290*86d7f5d3SJohn Marino 	{
5291*86d7f5d3SJohn Marino 	    status = 1;
5292*86d7f5d3SJohn Marino 	    goto checkin_done;
5293*86d7f5d3SJohn Marino 	}
5294*86d7f5d3SJohn Marino 	else if (commitpt->next == NULL
5295*86d7f5d3SJohn Marino 		 || STREQ (commitpt->version, rcs->head))
5296*86d7f5d3SJohn Marino 	    delta->version = increment_revnum (commitpt->version);
5297*86d7f5d3SJohn Marino 	else
5298*86d7f5d3SJohn Marino 	    delta->version = RCS_addbranch (rcs, commitpt->version);
5299*86d7f5d3SJohn Marino     }
5300*86d7f5d3SJohn Marino     else
5301*86d7f5d3SJohn Marino     {
5302*86d7f5d3SJohn Marino 	/* REV is either a revision number or a branch number.  Find the
5303*86d7f5d3SJohn Marino 	   tip of the target branch. */
5304*86d7f5d3SJohn Marino 	char *branch, *tip, *newrev, *p;
5305*86d7f5d3SJohn Marino 	int dots, isrevnum;
5306*86d7f5d3SJohn Marino 
5307*86d7f5d3SJohn Marino 	assert (isdigit ((unsigned char) *rev));
5308*86d7f5d3SJohn Marino 
5309*86d7f5d3SJohn Marino 	newrev = xstrdup (rev);
5310*86d7f5d3SJohn Marino 	dots = numdots (newrev);
5311*86d7f5d3SJohn Marino 	isrevnum = dots & 1;
5312*86d7f5d3SJohn Marino 
5313*86d7f5d3SJohn Marino 	branch = xstrdup (rev);
5314*86d7f5d3SJohn Marino 	if (isrevnum)
5315*86d7f5d3SJohn Marino 	{
5316*86d7f5d3SJohn Marino 	    p = strrchr (branch, '.');
5317*86d7f5d3SJohn Marino 	    *p = '\0';
5318*86d7f5d3SJohn Marino 	}
5319*86d7f5d3SJohn Marino 
5320*86d7f5d3SJohn Marino 	/* Find the tip of the target branch.  If we got a one- or two-digit
5321*86d7f5d3SJohn Marino 	   revision number, this will be the head of the tree.  Exception:
5322*86d7f5d3SJohn Marino 	   if rev is a single-field revision equal to the branch number of
5323*86d7f5d3SJohn Marino 	   the trunk (usually "1") then we want to treat it like an ordinary
5324*86d7f5d3SJohn Marino 	   branch revision. */
5325*86d7f5d3SJohn Marino 	if (dots == 0)
5326*86d7f5d3SJohn Marino 	{
5327*86d7f5d3SJohn Marino 	    tip = xstrdup (rcs->head);
5328*86d7f5d3SJohn Marino 	    if (atoi (tip) != atoi (branch))
5329*86d7f5d3SJohn Marino 	    {
5330*86d7f5d3SJohn Marino 		newrev = xrealloc (newrev, strlen (newrev) + 3);
5331*86d7f5d3SJohn Marino 		strcat (newrev, ".1");
5332*86d7f5d3SJohn Marino 		dots = isrevnum = 1;
5333*86d7f5d3SJohn Marino 	    }
5334*86d7f5d3SJohn Marino 	}
5335*86d7f5d3SJohn Marino 	else if (dots == 1)
5336*86d7f5d3SJohn Marino 	    tip = xstrdup (rcs->head);
5337*86d7f5d3SJohn Marino 	else
5338*86d7f5d3SJohn Marino 	    tip = RCS_getbranch (rcs, branch, 1);
5339*86d7f5d3SJohn Marino 
5340*86d7f5d3SJohn Marino 	/* If the branch does not exist, and we were supplied an exact
5341*86d7f5d3SJohn Marino 	   revision number, signal an error.  Otherwise, if we were
5342*86d7f5d3SJohn Marino 	   given only a branch number, create it and set COMMITPT to
5343*86d7f5d3SJohn Marino 	   the branch point. */
5344*86d7f5d3SJohn Marino 	if (tip == NULL)
5345*86d7f5d3SJohn Marino 	{
5346*86d7f5d3SJohn Marino 	    if (isrevnum)
5347*86d7f5d3SJohn Marino 	    {
5348*86d7f5d3SJohn Marino 		error (0, 0, "%s: can't find branch point %s",
5349*86d7f5d3SJohn Marino 		       rcs->print_path, branch);
5350*86d7f5d3SJohn Marino 		free (branch);
5351*86d7f5d3SJohn Marino 		free (newrev);
5352*86d7f5d3SJohn Marino 		status = 1;
5353*86d7f5d3SJohn Marino 		goto checkin_done;
5354*86d7f5d3SJohn Marino 	    }
5355*86d7f5d3SJohn Marino 	    delta->version = RCS_addbranch (rcs, branch);
5356*86d7f5d3SJohn Marino 	    if (!delta->version)
5357*86d7f5d3SJohn Marino 	    {
5358*86d7f5d3SJohn Marino 		free (branch);
5359*86d7f5d3SJohn Marino 		free (newrev);
5360*86d7f5d3SJohn Marino 		status = 1;
5361*86d7f5d3SJohn Marino 		goto checkin_done;
5362*86d7f5d3SJohn Marino 	    }
5363*86d7f5d3SJohn Marino 	    adding_branch = 1;
5364*86d7f5d3SJohn Marino 	    p = strrchr (branch, '.');
5365*86d7f5d3SJohn Marino 	    *p = '\0';
5366*86d7f5d3SJohn Marino 	    tip = xstrdup (branch);
5367*86d7f5d3SJohn Marino 	}
5368*86d7f5d3SJohn Marino 	else
5369*86d7f5d3SJohn Marino 	{
5370*86d7f5d3SJohn Marino 	    if (isrevnum)
5371*86d7f5d3SJohn Marino 	    {
5372*86d7f5d3SJohn Marino 		/* NEWREV must be higher than TIP. */
5373*86d7f5d3SJohn Marino 		if (compare_revnums (tip, newrev) >= 0)
5374*86d7f5d3SJohn Marino 		{
5375*86d7f5d3SJohn Marino 		    error (0, 0,
5376*86d7f5d3SJohn Marino 			   "%s: revision %s too low; must be higher than %s",
5377*86d7f5d3SJohn Marino 			   rcs->print_path,
5378*86d7f5d3SJohn Marino 			   newrev, tip);
5379*86d7f5d3SJohn Marino 		    free (branch);
5380*86d7f5d3SJohn Marino 		    free (newrev);
5381*86d7f5d3SJohn Marino 		    free (tip);
5382*86d7f5d3SJohn Marino 		    status = 1;
5383*86d7f5d3SJohn Marino 		    goto checkin_done;
5384*86d7f5d3SJohn Marino 		}
5385*86d7f5d3SJohn Marino 		delta->version = xstrdup (newrev);
5386*86d7f5d3SJohn Marino 	    }
5387*86d7f5d3SJohn Marino 	    else
5388*86d7f5d3SJohn Marino 		/* Just increment the tip number to get the new revision. */
5389*86d7f5d3SJohn Marino 		delta->version = increment_revnum (tip);
5390*86d7f5d3SJohn Marino 	}
5391*86d7f5d3SJohn Marino 
5392*86d7f5d3SJohn Marino 	nodep = findnode (rcs->versions, tip);
5393*86d7f5d3SJohn Marino 	commitpt = nodep->data;
5394*86d7f5d3SJohn Marino 
5395*86d7f5d3SJohn Marino 	free (branch);
5396*86d7f5d3SJohn Marino 	free (newrev);
5397*86d7f5d3SJohn Marino 	free (tip);
5398*86d7f5d3SJohn Marino     }
5399*86d7f5d3SJohn Marino 
5400*86d7f5d3SJohn Marino     assert (delta->version != NULL);
5401*86d7f5d3SJohn Marino 
5402*86d7f5d3SJohn Marino     /* If COMMITPT is locked by us, break the lock.  If it's locked
5403*86d7f5d3SJohn Marino        by someone else, signal an error. */
5404*86d7f5d3SJohn Marino     nodep = findnode (RCS_getlocks (rcs), commitpt->version);
5405*86d7f5d3SJohn Marino     if (nodep != NULL)
5406*86d7f5d3SJohn Marino     {
5407*86d7f5d3SJohn Marino 	if (! STREQ (nodep->data, delta->author))
5408*86d7f5d3SJohn Marino 	{
5409*86d7f5d3SJohn Marino 	    /* If we are adding a branch, then leave the old lock around.
5410*86d7f5d3SJohn Marino 	       That is sensible in the sense that when adding a branch,
5411*86d7f5d3SJohn Marino 	       we don't need to use the lock to tell us where to check
5412*86d7f5d3SJohn Marino 	       in.  It is fishy in the sense that if it is our own lock,
5413*86d7f5d3SJohn Marino 	       we break it.  However, this is the RCS 5.7 behavior (at
5414*86d7f5d3SJohn Marino 	       the end of addbranch in ci.c in RCS 5.7, it calls
5415*86d7f5d3SJohn Marino 	       removelock only if it is our own lock, not someone
5416*86d7f5d3SJohn Marino 	       else's).  */
5417*86d7f5d3SJohn Marino 
5418*86d7f5d3SJohn Marino 	    if (!adding_branch)
5419*86d7f5d3SJohn Marino 	    {
5420*86d7f5d3SJohn Marino 		error (0, 0, "%s: revision %s locked by %s",
5421*86d7f5d3SJohn Marino 		       rcs->print_path,
5422*86d7f5d3SJohn Marino 		       nodep->key, (char *)nodep->data);
5423*86d7f5d3SJohn Marino 		status = 1;
5424*86d7f5d3SJohn Marino 		goto checkin_done;
5425*86d7f5d3SJohn Marino 	    }
5426*86d7f5d3SJohn Marino 	}
5427*86d7f5d3SJohn Marino 	else
5428*86d7f5d3SJohn Marino 	    delnode (nodep);
5429*86d7f5d3SJohn Marino     }
5430*86d7f5d3SJohn Marino 
5431*86d7f5d3SJohn Marino     dtext->version = xstrdup (delta->version);
5432*86d7f5d3SJohn Marino 
5433*86d7f5d3SJohn Marino     /* Obtain the change text for the new delta.  If DELTA is to be the
5434*86d7f5d3SJohn Marino        new head of the tree, then its change text should be the contents
5435*86d7f5d3SJohn Marino        of the working file, and LEAFNODE's change text should be a diff.
5436*86d7f5d3SJohn Marino        Else, DELTA's change text should be a diff between LEAFNODE and
5437*86d7f5d3SJohn Marino        the working file. */
5438*86d7f5d3SJohn Marino 
5439*86d7f5d3SJohn Marino     tmpfile = cvs_temp_name();
5440*86d7f5d3SJohn Marino     status = RCS_checkout (rcs, NULL, commitpt->version, NULL,
5441*86d7f5d3SJohn Marino 			   ((rcs->expand != NULL
5442*86d7f5d3SJohn Marino 			     && STREQ (rcs->expand, "b"))
5443*86d7f5d3SJohn Marino 			    ? "-kb"
5444*86d7f5d3SJohn Marino 			    : "-ko"),
5445*86d7f5d3SJohn Marino 			   tmpfile,
5446*86d7f5d3SJohn Marino 			   NULL, NULL);
5447*86d7f5d3SJohn Marino     if (status != 0)
5448*86d7f5d3SJohn Marino 	error (1, 0,
5449*86d7f5d3SJohn Marino 	       "could not check out revision %s of `%s'",
5450*86d7f5d3SJohn Marino 	       commitpt->version, rcs->print_path);
5451*86d7f5d3SJohn Marino 
5452*86d7f5d3SJohn Marino     bufsize = 0;
5453*86d7f5d3SJohn Marino     changefile = cvs_temp_name();
5454*86d7f5d3SJohn Marino 
5455*86d7f5d3SJohn Marino     /* Diff options should include --binary if the RCS file has -kb set
5456*86d7f5d3SJohn Marino        in its `expand' field. */
5457*86d7f5d3SJohn Marino     run_add_arg_p (&dargc, &darg_allocated, &dargv, "-a");
5458*86d7f5d3SJohn Marino     run_add_arg_p (&dargc, &darg_allocated, &dargv, "-n");
5459*86d7f5d3SJohn Marino     if (rcs->expand != NULL && STREQ (rcs->expand, "b"))
5460*86d7f5d3SJohn Marino 	run_add_arg_p (&dargc, &darg_allocated, &dargv, "--binary");
5461*86d7f5d3SJohn Marino 
5462*86d7f5d3SJohn Marino     if (STREQ (commitpt->version, rcs->head) &&
5463*86d7f5d3SJohn Marino 	numdots (delta->version) == 1)
5464*86d7f5d3SJohn Marino     {
5465*86d7f5d3SJohn Marino 	/* If this revision is being inserted on the trunk, the change text
5466*86d7f5d3SJohn Marino 	   for the new delta should be the contents of the working file ... */
5467*86d7f5d3SJohn Marino 	bufsize = 0;
5468*86d7f5d3SJohn Marino #ifdef PRESERVE_PERMISSIONS_SUPPORT
5469*86d7f5d3SJohn Marino 	if (preserve_perms && !S_ISREG (sb.st_mode))
5470*86d7f5d3SJohn Marino 	    /* Pretend file is empty.  */
5471*86d7f5d3SJohn Marino 	    ;
5472*86d7f5d3SJohn Marino 	else
5473*86d7f5d3SJohn Marino #endif
5474*86d7f5d3SJohn Marino 	get_file (workfile, workfile,
5475*86d7f5d3SJohn Marino 		  rcs->expand != NULL && STREQ (rcs->expand, "b") ? "rb" : "r",
5476*86d7f5d3SJohn Marino 		  &dtext->text, &bufsize, &dtext->len);
5477*86d7f5d3SJohn Marino 
5478*86d7f5d3SJohn Marino 	/* ... and the change text for the old delta should be a diff. */
5479*86d7f5d3SJohn Marino 	commitpt->text = xmalloc (sizeof (Deltatext));
5480*86d7f5d3SJohn Marino 	memset (commitpt->text, 0, sizeof (Deltatext));
5481*86d7f5d3SJohn Marino 
5482*86d7f5d3SJohn Marino 	bufsize = 0;
5483*86d7f5d3SJohn Marino 	switch (diff_exec (workfile, tmpfile, NULL, NULL,
5484*86d7f5d3SJohn Marino 			   dargc, dargv, changefile))
5485*86d7f5d3SJohn Marino 	{
5486*86d7f5d3SJohn Marino 	    case 0:
5487*86d7f5d3SJohn Marino 	    case 1:
5488*86d7f5d3SJohn Marino 		break;
5489*86d7f5d3SJohn Marino 	    case -1:
5490*86d7f5d3SJohn Marino 		/* FIXME-update-dir: message does not include update_dir.  */
5491*86d7f5d3SJohn Marino 		error (1, errno, "error diffing %s", workfile);
5492*86d7f5d3SJohn Marino 		break;
5493*86d7f5d3SJohn Marino 	    default:
5494*86d7f5d3SJohn Marino 		/* FIXME-update-dir: message does not include update_dir.  */
5495*86d7f5d3SJohn Marino 		error (1, 0, "error diffing %s", workfile);
5496*86d7f5d3SJohn Marino 		break;
5497*86d7f5d3SJohn Marino 	}
5498*86d7f5d3SJohn Marino 
5499*86d7f5d3SJohn Marino 	/* OK, the text file case here is really dumb.  Logically
5500*86d7f5d3SJohn Marino 	   speaking we want diff to read the files in text mode,
5501*86d7f5d3SJohn Marino 	   convert them to the canonical form found in RCS files
5502*86d7f5d3SJohn Marino 	   (which, we hope at least, is independent of OS--always
5503*86d7f5d3SJohn Marino 	   bare linefeeds), and then work with change texts in that
5504*86d7f5d3SJohn Marino 	   format.  However, diff_exec both generates change
5505*86d7f5d3SJohn Marino 	   texts and produces output for user purposes (e.g. patch.c),
5506*86d7f5d3SJohn Marino 	   and there is no way to distinguish between the two cases.
5507*86d7f5d3SJohn Marino 	   So we actually implement the text file case by writing the
5508*86d7f5d3SJohn Marino 	   change text as a text file, then reading it as a text file.
5509*86d7f5d3SJohn Marino 	   This should cause no harm, but doesn't strike me as
5510*86d7f5d3SJohn Marino 	   immensely clean.  */
5511*86d7f5d3SJohn Marino 	get_file (changefile, changefile,
5512*86d7f5d3SJohn Marino 		  rcs->expand != NULL && STREQ (rcs->expand, "b") ? "rb" : "r",
5513*86d7f5d3SJohn Marino 		  &commitpt->text->text, &bufsize, &commitpt->text->len);
5514*86d7f5d3SJohn Marino 
5515*86d7f5d3SJohn Marino 	/* If COMMITPT->TEXT->TEXT is NULL, it means that CHANGEFILE
5516*86d7f5d3SJohn Marino 	   was empty and that there are no differences between revisions.
5517*86d7f5d3SJohn Marino 	   In that event, we want to force RCS_rewrite to write an empty
5518*86d7f5d3SJohn Marino 	   string for COMMITPT's change text.  Leaving the change text
5519*86d7f5d3SJohn Marino 	   field set NULL won't work, since that means "preserve the original
5520*86d7f5d3SJohn Marino 	   change text for this delta." */
5521*86d7f5d3SJohn Marino 	if (commitpt->text->text == NULL)
5522*86d7f5d3SJohn Marino 	{
5523*86d7f5d3SJohn Marino 	    commitpt->text->text = xstrdup ("");
5524*86d7f5d3SJohn Marino 	    commitpt->text->len = 0;
5525*86d7f5d3SJohn Marino 	}
5526*86d7f5d3SJohn Marino     }
5527*86d7f5d3SJohn Marino     else
5528*86d7f5d3SJohn Marino     {
5529*86d7f5d3SJohn Marino 	/* This file is not being inserted at the head, but on a side
5530*86d7f5d3SJohn Marino 	   branch somewhere.  Make a diff from the previous revision
5531*86d7f5d3SJohn Marino 	   to the working file. */
5532*86d7f5d3SJohn Marino 	switch (diff_exec (tmpfile, workfile, NULL, NULL,
5533*86d7f5d3SJohn Marino 			   dargc, dargv, changefile))
5534*86d7f5d3SJohn Marino 	{
5535*86d7f5d3SJohn Marino 	    case 0:
5536*86d7f5d3SJohn Marino 	    case 1:
5537*86d7f5d3SJohn Marino 		break;
5538*86d7f5d3SJohn Marino 	    case -1:
5539*86d7f5d3SJohn Marino 		/* FIXME-update-dir: message does not include update_dir.  */
5540*86d7f5d3SJohn Marino 		error (1, errno, "error diffing %s", workfile);
5541*86d7f5d3SJohn Marino 		break;
5542*86d7f5d3SJohn Marino 	    default:
5543*86d7f5d3SJohn Marino 		/* FIXME-update-dir: message does not include update_dir.  */
5544*86d7f5d3SJohn Marino 		error (1, 0, "error diffing %s", workfile);
5545*86d7f5d3SJohn Marino 		break;
5546*86d7f5d3SJohn Marino 	}
5547*86d7f5d3SJohn Marino 	/* See the comment above, at the other get_file invocation,
5548*86d7f5d3SJohn Marino 	   regarding binary vs. text.  */
5549*86d7f5d3SJohn Marino 	get_file (changefile, changefile,
5550*86d7f5d3SJohn Marino 		  rcs->expand != NULL && STREQ (rcs->expand, "b") ? "rb" : "r",
5551*86d7f5d3SJohn Marino 		  &dtext->text, &bufsize,
5552*86d7f5d3SJohn Marino 		  &dtext->len);
5553*86d7f5d3SJohn Marino 	if (dtext->text == NULL)
5554*86d7f5d3SJohn Marino 	{
5555*86d7f5d3SJohn Marino 	    dtext->text = xstrdup ("");
5556*86d7f5d3SJohn Marino 	    dtext->len = 0;
5557*86d7f5d3SJohn Marino 	}
5558*86d7f5d3SJohn Marino     }
5559*86d7f5d3SJohn Marino 
5560*86d7f5d3SJohn Marino     run_arg_free_p (dargc, dargv);
5561*86d7f5d3SJohn Marino     free (dargv);
5562*86d7f5d3SJohn Marino 
5563*86d7f5d3SJohn Marino     /* Update DELTA linkage.  It is important not to do this before
5564*86d7f5d3SJohn Marino        the very end of RCS_checkin; if an error arises that forces
5565*86d7f5d3SJohn Marino        us to abort checking in, we must not have malformed deltas
5566*86d7f5d3SJohn Marino        partially linked into the tree.
5567*86d7f5d3SJohn Marino 
5568*86d7f5d3SJohn Marino        If DELTA and COMMITPT are on different branches, do nothing --
5569*86d7f5d3SJohn Marino        DELTA is linked to the tree through COMMITPT->BRANCHES, and we
5570*86d7f5d3SJohn Marino        don't want to change `next' pointers.
5571*86d7f5d3SJohn Marino 
5572*86d7f5d3SJohn Marino        Otherwise, if the nodes are both on the trunk, link DELTA to
5573*86d7f5d3SJohn Marino        COMMITPT; otherwise, link COMMITPT to DELTA. */
5574*86d7f5d3SJohn Marino 
5575*86d7f5d3SJohn Marino     if (numdots (commitpt->version) == numdots (delta->version))
5576*86d7f5d3SJohn Marino     {
5577*86d7f5d3SJohn Marino 	if (STREQ (commitpt->version, rcs->head))
5578*86d7f5d3SJohn Marino 	{
5579*86d7f5d3SJohn Marino 	    delta->next = rcs->head;
5580*86d7f5d3SJohn Marino 	    rcs->head = xstrdup (delta->version);
5581*86d7f5d3SJohn Marino 	}
5582*86d7f5d3SJohn Marino 	else
5583*86d7f5d3SJohn Marino 	    commitpt->next = xstrdup (delta->version);
5584*86d7f5d3SJohn Marino     }
5585*86d7f5d3SJohn Marino 
5586*86d7f5d3SJohn Marino     /* Add DELTA to RCS->VERSIONS. */
5587*86d7f5d3SJohn Marino     if (rcs->versions == NULL)
5588*86d7f5d3SJohn Marino 	rcs->versions = getlist();
5589*86d7f5d3SJohn Marino     nodep = getnode();
5590*86d7f5d3SJohn Marino     nodep->type = RCSVERS;
5591*86d7f5d3SJohn Marino     nodep->delproc = rcsvers_delproc;
5592*86d7f5d3SJohn Marino     nodep->data = delta;
5593*86d7f5d3SJohn Marino     nodep->key = delta->version;
5594*86d7f5d3SJohn Marino     (void) addnode (rcs->versions, nodep);
5595*86d7f5d3SJohn Marino 
5596*86d7f5d3SJohn Marino     /* Write the new RCS file, inserting the new delta at COMMITPT. */
5597*86d7f5d3SJohn Marino     if (!(checkin_quiet || really_quiet))
5598*86d7f5d3SJohn Marino     {
5599*86d7f5d3SJohn Marino 	cvs_output ("new revision: ", 14);
5600*86d7f5d3SJohn Marino 	cvs_output (delta->version, 0);
5601*86d7f5d3SJohn Marino 	cvs_output ("; previous revision: ", 21);
5602*86d7f5d3SJohn Marino 	cvs_output (commitpt->version, 0);
5603*86d7f5d3SJohn Marino 	cvs_output ("\n", 1);
5604*86d7f5d3SJohn Marino     }
5605*86d7f5d3SJohn Marino 
5606*86d7f5d3SJohn Marino     RCS_rewrite (rcs, dtext, commitpt->version);
5607*86d7f5d3SJohn Marino 
5608*86d7f5d3SJohn Marino     if ((flags & RCS_FLAGS_KEEPFILE) == 0)
5609*86d7f5d3SJohn Marino     {
5610*86d7f5d3SJohn Marino 	if (unlink_file (workfile) < 0)
5611*86d7f5d3SJohn Marino 	    /* FIXME-update-dir: message does not include update_dir.  */
5612*86d7f5d3SJohn Marino 	    error (1, errno, "cannot remove %s", workfile);
5613*86d7f5d3SJohn Marino     }
5614*86d7f5d3SJohn Marino     if (unlink_file (tmpfile) < 0)
5615*86d7f5d3SJohn Marino 	error (0, errno, "cannot remove %s", tmpfile);
5616*86d7f5d3SJohn Marino     free (tmpfile);
5617*86d7f5d3SJohn Marino     if (unlink_file (changefile) < 0)
5618*86d7f5d3SJohn Marino 	error (0, errno, "cannot remove %s", changefile);
5619*86d7f5d3SJohn Marino     free (changefile);
5620*86d7f5d3SJohn Marino 
5621*86d7f5d3SJohn Marino  checkin_done:
5622*86d7f5d3SJohn Marino     free (workfile);
5623*86d7f5d3SJohn Marino 
5624*86d7f5d3SJohn Marino     if (commitpt != NULL && commitpt->text != NULL)
5625*86d7f5d3SJohn Marino     {
5626*86d7f5d3SJohn Marino 	freedeltatext (commitpt->text);
5627*86d7f5d3SJohn Marino 	commitpt->text = NULL;
5628*86d7f5d3SJohn Marino     }
5629*86d7f5d3SJohn Marino 
5630*86d7f5d3SJohn Marino     freedeltatext (dtext);
5631*86d7f5d3SJohn Marino     if (status != 0)
5632*86d7f5d3SJohn Marino     {
5633*86d7f5d3SJohn Marino 	/* If delta has not been added to a List, then freeing the Node key
5634*86d7f5d3SJohn Marino 	 * won't free delta->version.
5635*86d7f5d3SJohn Marino 	 */
5636*86d7f5d3SJohn Marino 	if (delta->version) free (delta->version);
5637*86d7f5d3SJohn Marino 	free_rcsvers_contents (delta);
5638*86d7f5d3SJohn Marino     }
5639*86d7f5d3SJohn Marino 
5640*86d7f5d3SJohn Marino     return status;
5641*86d7f5d3SJohn Marino }
5642*86d7f5d3SJohn Marino 
5643*86d7f5d3SJohn Marino 
5644*86d7f5d3SJohn Marino 
5645*86d7f5d3SJohn Marino /* This structure is passed between RCS_cmp_file and cmp_file_buffer.  */
5646*86d7f5d3SJohn Marino struct cmp_file_data
5647*86d7f5d3SJohn Marino {
5648*86d7f5d3SJohn Marino     const char *filename;
5649*86d7f5d3SJohn Marino     FILE *fp;
5650*86d7f5d3SJohn Marino     int different;
5651*86d7f5d3SJohn Marino };
5652*86d7f5d3SJohn Marino 
5653*86d7f5d3SJohn Marino /* Compare the contents of revision REV1 of RCS file RCS with the
5654*86d7f5d3SJohn Marino    contents of REV2 if given, otherwise, compare with the contents of
5655*86d7f5d3SJohn Marino    the file FILENAME.  OPTIONS is a string for the keyword
5656*86d7f5d3SJohn Marino    expansion options.  Return 0 if the contents of the revision are
5657*86d7f5d3SJohn Marino    the same as the contents of the file, 1 if they are different.  */
5658*86d7f5d3SJohn Marino int
RCS_cmp_file(RCSNode * rcs,const char * rev1,char ** rev1_cache,const char * rev2,const char * options,const char * filename)5659*86d7f5d3SJohn Marino RCS_cmp_file (RCSNode *rcs, const char *rev1, char **rev1_cache,
5660*86d7f5d3SJohn Marino               const char *rev2, const char *options, const char *filename)
5661*86d7f5d3SJohn Marino {
5662*86d7f5d3SJohn Marino     int binary;
5663*86d7f5d3SJohn Marino 
5664*86d7f5d3SJohn Marino     TRACE (TRACE_FUNCTION, "RCS_cmp_file( %s, %s, %s, %s, %s )",
5665*86d7f5d3SJohn Marino            rcs->path ? rcs->path : "(null)",
5666*86d7f5d3SJohn Marino 	   rev1 ? rev1 : "(null)", rev2 ? rev2 : "(null)",
5667*86d7f5d3SJohn Marino 	   options ? options : "(null)", filename ? filename : "(null)");
5668*86d7f5d3SJohn Marino 
5669*86d7f5d3SJohn Marino     if (options != NULL && options[0] != '\0')
5670*86d7f5d3SJohn Marino 	binary = STREQ (options, "-kb");
5671*86d7f5d3SJohn Marino     else
5672*86d7f5d3SJohn Marino     {
5673*86d7f5d3SJohn Marino 	char *expand;
5674*86d7f5d3SJohn Marino 
5675*86d7f5d3SJohn Marino 	expand = RCS_getexpand (rcs);
5676*86d7f5d3SJohn Marino 	if (expand != NULL && STREQ (expand, "b"))
5677*86d7f5d3SJohn Marino 	    binary = 1;
5678*86d7f5d3SJohn Marino 	else
5679*86d7f5d3SJohn Marino 	    binary = 0;
5680*86d7f5d3SJohn Marino     }
5681*86d7f5d3SJohn Marino 
5682*86d7f5d3SJohn Marino #ifdef PRESERVE_PERMISSIONS_SUPPORT
5683*86d7f5d3SJohn Marino     /* If CVS is to deal properly with special files (when
5684*86d7f5d3SJohn Marino        PreservePermissions is on), the best way is to check out the
5685*86d7f5d3SJohn Marino        revision to a temporary file and call `xcmp' on the two disk
5686*86d7f5d3SJohn Marino        files.  xcmp needs to handle non-regular files properly anyway,
5687*86d7f5d3SJohn Marino        so calling it simplifies RCS_cmp_file.  We *could* just yank
5688*86d7f5d3SJohn Marino        the delta node out of the version tree and look for device
5689*86d7f5d3SJohn Marino        numbers, but writing to disk and calling xcmp is a better
5690*86d7f5d3SJohn Marino        abstraction (therefore probably more robust). -twp */
5691*86d7f5d3SJohn Marino 
5692*86d7f5d3SJohn Marino     if (preserve_perms)
5693*86d7f5d3SJohn Marino     {
5694*86d7f5d3SJohn Marino 	char *tmp;
5695*86d7f5d3SJohn Marino 	int retcode;
5696*86d7f5d3SJohn Marino 
5697*86d7f5d3SJohn Marino 	tmp = cvs_temp_name();
5698*86d7f5d3SJohn Marino 	retcode = RCS_checkout(rcs, NULL, rev, NULL, options, tmp, NULL, NULL);
5699*86d7f5d3SJohn Marino 	if (retcode != 0)
5700*86d7f5d3SJohn Marino 	    return 1;
5701*86d7f5d3SJohn Marino 
5702*86d7f5d3SJohn Marino 	retcode = xcmp (tmp, filename);
5703*86d7f5d3SJohn Marino 	if (CVS_UNLINK (tmp) < 0)
5704*86d7f5d3SJohn Marino 	    error (0, errno, "cannot remove %s", tmp);
5705*86d7f5d3SJohn Marino 	free (tmp);
5706*86d7f5d3SJohn Marino 	return retcode;
5707*86d7f5d3SJohn Marino     }
5708*86d7f5d3SJohn Marino     else
5709*86d7f5d3SJohn Marino #endif
5710*86d7f5d3SJohn Marino     {
5711*86d7f5d3SJohn Marino 	FILE *fp;
5712*86d7f5d3SJohn Marino 	struct cmp_file_data data;
5713*86d7f5d3SJohn Marino 	const char *use_file1;
5714*86d7f5d3SJohn Marino 	char *tmpfile = NULL;
5715*86d7f5d3SJohn Marino 
5716*86d7f5d3SJohn Marino 	if (rev2 != NULL)
5717*86d7f5d3SJohn Marino 	{
5718*86d7f5d3SJohn Marino 	    /* Open & cache rev1 */
5719*86d7f5d3SJohn Marino 	    tmpfile = cvs_temp_name();
5720*86d7f5d3SJohn Marino 	    if (RCS_checkout (rcs, NULL, rev1, NULL, options, tmpfile,
5721*86d7f5d3SJohn Marino 	                      NULL, NULL))
5722*86d7f5d3SJohn Marino 		error (1, errno,
5723*86d7f5d3SJohn Marino 		       "cannot check out revision %s of %s",
5724*86d7f5d3SJohn Marino 		       rev1, rcs->print_path);
5725*86d7f5d3SJohn Marino 	    use_file1 = tmpfile;
5726*86d7f5d3SJohn Marino 	    if (rev1_cache != NULL)
5727*86d7f5d3SJohn Marino 		*rev1_cache = tmpfile;
5728*86d7f5d3SJohn Marino 	}
5729*86d7f5d3SJohn Marino 	else
5730*86d7f5d3SJohn Marino 	    use_file1 = filename;
5731*86d7f5d3SJohn Marino 
5732*86d7f5d3SJohn Marino         fp = CVS_FOPEN (use_file1, binary ? FOPEN_BINARY_READ : "r");
5733*86d7f5d3SJohn Marino 	if (fp == NULL)
5734*86d7f5d3SJohn Marino 	    /* FIXME-update-dir: should include update_dir in message.  */
5735*86d7f5d3SJohn Marino 	    error (1, errno, "cannot open file %s for comparing", use_file1);
5736*86d7f5d3SJohn Marino 
5737*86d7f5d3SJohn Marino         data.filename = use_file1;
5738*86d7f5d3SJohn Marino         data.fp = fp;
5739*86d7f5d3SJohn Marino         data.different = 0;
5740*86d7f5d3SJohn Marino 
5741*86d7f5d3SJohn Marino         if (RCS_checkout (rcs, NULL, rev2 ? rev2 : rev1, NULL, options,
5742*86d7f5d3SJohn Marino                           RUN_TTY, cmp_file_buffer, &data ))
5743*86d7f5d3SJohn Marino 		error (1, errno,
5744*86d7f5d3SJohn Marino 		       "cannot check out revision %s of %s",
5745*86d7f5d3SJohn Marino 		       rev2 ? rev2 : rev1, rcs->print_path);
5746*86d7f5d3SJohn Marino 
5747*86d7f5d3SJohn Marino         /* If we have not yet found a difference, make sure that we are at
5748*86d7f5d3SJohn Marino            the end of the file.  */
5749*86d7f5d3SJohn Marino         if (!data.different)
5750*86d7f5d3SJohn Marino         {
5751*86d7f5d3SJohn Marino 	    if (getc (fp) != EOF)
5752*86d7f5d3SJohn Marino 		data.different = 1;
5753*86d7f5d3SJohn Marino         }
5754*86d7f5d3SJohn Marino 
5755*86d7f5d3SJohn Marino         fclose (fp);
5756*86d7f5d3SJohn Marino 	if (rev1_cache == NULL && tmpfile)
5757*86d7f5d3SJohn Marino 	{
5758*86d7f5d3SJohn Marino 	    if (CVS_UNLINK (tmpfile ) < 0)
5759*86d7f5d3SJohn Marino 		error (0, errno, "cannot remove %s", tmpfile);
5760*86d7f5d3SJohn Marino 	    free (tmpfile);
5761*86d7f5d3SJohn Marino 	}
5762*86d7f5d3SJohn Marino 
5763*86d7f5d3SJohn Marino         return data.different;
5764*86d7f5d3SJohn Marino     }
5765*86d7f5d3SJohn Marino }
5766*86d7f5d3SJohn Marino 
5767*86d7f5d3SJohn Marino 
5768*86d7f5d3SJohn Marino 
5769*86d7f5d3SJohn Marino /* This is a subroutine of RCS_cmp_file.  It is passed to
5770*86d7f5d3SJohn Marino    RCS_checkout.  */
5771*86d7f5d3SJohn Marino #define CMP_BUF_SIZE (8 * 1024)
5772*86d7f5d3SJohn Marino 
5773*86d7f5d3SJohn Marino static void
cmp_file_buffer(void * callerdat,const char * buffer,size_t len)5774*86d7f5d3SJohn Marino cmp_file_buffer (void *callerdat, const char *buffer, size_t len)
5775*86d7f5d3SJohn Marino {
5776*86d7f5d3SJohn Marino     struct cmp_file_data *data = callerdat;
5777*86d7f5d3SJohn Marino     char *filebuf;
5778*86d7f5d3SJohn Marino 
5779*86d7f5d3SJohn Marino     /* If we've already found a difference, we don't need to check
5780*86d7f5d3SJohn Marino        further.  */
5781*86d7f5d3SJohn Marino     if (data->different)
5782*86d7f5d3SJohn Marino 	return;
5783*86d7f5d3SJohn Marino 
5784*86d7f5d3SJohn Marino     filebuf = xmalloc (len > CMP_BUF_SIZE ? CMP_BUF_SIZE : len);
5785*86d7f5d3SJohn Marino 
5786*86d7f5d3SJohn Marino     while (len > 0)
5787*86d7f5d3SJohn Marino     {
5788*86d7f5d3SJohn Marino 	size_t checklen;
5789*86d7f5d3SJohn Marino 
5790*86d7f5d3SJohn Marino 	checklen = len > CMP_BUF_SIZE ? CMP_BUF_SIZE : len;
5791*86d7f5d3SJohn Marino 	if (fread (filebuf, 1, checklen, data->fp) != checklen)
5792*86d7f5d3SJohn Marino 	{
5793*86d7f5d3SJohn Marino 	    if (ferror (data->fp))
5794*86d7f5d3SJohn Marino 		error (1, errno, "cannot read file %s for comparing",
5795*86d7f5d3SJohn Marino 		       data->filename);
5796*86d7f5d3SJohn Marino 	    data->different = 1;
5797*86d7f5d3SJohn Marino 	    free (filebuf);
5798*86d7f5d3SJohn Marino 	    return;
5799*86d7f5d3SJohn Marino 	}
5800*86d7f5d3SJohn Marino 
5801*86d7f5d3SJohn Marino 	if (memcmp (filebuf, buffer, checklen) != 0)
5802*86d7f5d3SJohn Marino 	{
5803*86d7f5d3SJohn Marino 	    data->different = 1;
5804*86d7f5d3SJohn Marino 	    free (filebuf);
5805*86d7f5d3SJohn Marino 	    return;
5806*86d7f5d3SJohn Marino 	}
5807*86d7f5d3SJohn Marino 
5808*86d7f5d3SJohn Marino 	buffer += checklen;
5809*86d7f5d3SJohn Marino 	len -= checklen;
5810*86d7f5d3SJohn Marino     }
5811*86d7f5d3SJohn Marino 
5812*86d7f5d3SJohn Marino     free (filebuf);
5813*86d7f5d3SJohn Marino }
5814*86d7f5d3SJohn Marino 
5815*86d7f5d3SJohn Marino 
5816*86d7f5d3SJohn Marino 
5817*86d7f5d3SJohn Marino /* For RCS file RCS, make symbolic tag TAG point to revision REV.
5818*86d7f5d3SJohn Marino    This validates that TAG is OK for a user to use.  Return value is
5819*86d7f5d3SJohn Marino    -1 for error (and errno is set to indicate the error), positive for
5820*86d7f5d3SJohn Marino    error (and an error message has been printed), or zero for success.  */
5821*86d7f5d3SJohn Marino int
RCS_settag(RCSNode * rcs,const char * tag,const char * rev)5822*86d7f5d3SJohn Marino RCS_settag (RCSNode *rcs, const char *tag, const char *rev)
5823*86d7f5d3SJohn Marino {
5824*86d7f5d3SJohn Marino     List *symbols;
5825*86d7f5d3SJohn Marino     Node *node;
5826*86d7f5d3SJohn Marino 
5827*86d7f5d3SJohn Marino     if (rcs->flags & PARTIAL)
5828*86d7f5d3SJohn Marino 	RCS_reparsercsfile (rcs, NULL, NULL);
5829*86d7f5d3SJohn Marino 
5830*86d7f5d3SJohn Marino     /* FIXME: This check should be moved to RCS_check_tag.  There is no
5831*86d7f5d3SJohn Marino        reason for it to be here.  */
5832*86d7f5d3SJohn Marino     if (STREQ (tag, TAG_BASE)
5833*86d7f5d3SJohn Marino 	|| STREQ (tag, TAG_HEAD))
5834*86d7f5d3SJohn Marino     {
5835*86d7f5d3SJohn Marino 	/* Print the name of the tag might be considered redundant
5836*86d7f5d3SJohn Marino 	   with the caller, which also prints it.  Perhaps this helps
5837*86d7f5d3SJohn Marino 	   clarify why the tag name is considered reserved, I don't
5838*86d7f5d3SJohn Marino 	   know.  */
5839*86d7f5d3SJohn Marino 	error (0, 0, "Attempt to add reserved tag name %s", tag);
5840*86d7f5d3SJohn Marino 	return 1;
5841*86d7f5d3SJohn Marino     }
5842*86d7f5d3SJohn Marino 
5843*86d7f5d3SJohn Marino     /* A revision number of NULL means use the head or default branch.
5844*86d7f5d3SJohn Marino        If rev is not NULL, it may be a symbolic tag or branch number;
5845*86d7f5d3SJohn Marino        expand it to the correct numeric revision or branch head. */
5846*86d7f5d3SJohn Marino     if (rev == NULL)
5847*86d7f5d3SJohn Marino 	rev = rcs->branch ? rcs->branch : rcs->head;
5848*86d7f5d3SJohn Marino 
5849*86d7f5d3SJohn Marino     /* At this point rcs->symbol_data may not have been parsed.
5850*86d7f5d3SJohn Marino        Calling RCS_symbols will force it to be parsed into a list
5851*86d7f5d3SJohn Marino        which we can easily manipulate.  */
5852*86d7f5d3SJohn Marino     symbols = RCS_symbols (rcs);
5853*86d7f5d3SJohn Marino     if (symbols == NULL)
5854*86d7f5d3SJohn Marino     {
5855*86d7f5d3SJohn Marino 	symbols = getlist ();
5856*86d7f5d3SJohn Marino 	rcs->symbols = symbols;
5857*86d7f5d3SJohn Marino     }
5858*86d7f5d3SJohn Marino     node = findnode (symbols, tag);
5859*86d7f5d3SJohn Marino     if (node != NULL)
5860*86d7f5d3SJohn Marino     {
5861*86d7f5d3SJohn Marino 	free (node->data);
5862*86d7f5d3SJohn Marino 	node->data = xstrdup (rev);
5863*86d7f5d3SJohn Marino     }
5864*86d7f5d3SJohn Marino     else
5865*86d7f5d3SJohn Marino     {
5866*86d7f5d3SJohn Marino 	node = getnode ();
5867*86d7f5d3SJohn Marino 	node->key = xstrdup (tag);
5868*86d7f5d3SJohn Marino 	node->data = xstrdup (rev);
5869*86d7f5d3SJohn Marino 	(void)addnode_at_front (symbols, node);
5870*86d7f5d3SJohn Marino     }
5871*86d7f5d3SJohn Marino 
5872*86d7f5d3SJohn Marino     return 0;
5873*86d7f5d3SJohn Marino }
5874*86d7f5d3SJohn Marino 
5875*86d7f5d3SJohn Marino 
5876*86d7f5d3SJohn Marino 
5877*86d7f5d3SJohn Marino /* Delete the symbolic tag TAG from the RCS file RCS.  Return 0 if
5878*86d7f5d3SJohn Marino    the tag was found (and removed), or 1 if it was not present.  (In
5879*86d7f5d3SJohn Marino    either case, the tag will no longer be in RCS->SYMBOLS.) */
5880*86d7f5d3SJohn Marino int
RCS_deltag(RCSNode * rcs,const char * tag)5881*86d7f5d3SJohn Marino RCS_deltag (RCSNode *rcs, const char *tag)
5882*86d7f5d3SJohn Marino {
5883*86d7f5d3SJohn Marino     List *symbols;
5884*86d7f5d3SJohn Marino     Node *node;
5885*86d7f5d3SJohn Marino     if (rcs->flags & PARTIAL)
5886*86d7f5d3SJohn Marino 	RCS_reparsercsfile (rcs, NULL, NULL);
5887*86d7f5d3SJohn Marino 
5888*86d7f5d3SJohn Marino     symbols = RCS_symbols (rcs);
5889*86d7f5d3SJohn Marino     if (symbols == NULL)
5890*86d7f5d3SJohn Marino 	return 1;
5891*86d7f5d3SJohn Marino 
5892*86d7f5d3SJohn Marino     node = findnode (symbols, tag);
5893*86d7f5d3SJohn Marino     if (node == NULL)
5894*86d7f5d3SJohn Marino 	return 1;
5895*86d7f5d3SJohn Marino 
5896*86d7f5d3SJohn Marino     delnode (node);
5897*86d7f5d3SJohn Marino 
5898*86d7f5d3SJohn Marino     return 0;
5899*86d7f5d3SJohn Marino }
5900*86d7f5d3SJohn Marino 
5901*86d7f5d3SJohn Marino 
5902*86d7f5d3SJohn Marino 
5903*86d7f5d3SJohn Marino /* Set the default branch of RCS to REV.  */
5904*86d7f5d3SJohn Marino int
RCS_setbranch(RCSNode * rcs,const char * rev)5905*86d7f5d3SJohn Marino RCS_setbranch (RCSNode *rcs, const char *rev)
5906*86d7f5d3SJohn Marino {
5907*86d7f5d3SJohn Marino     if (rcs->flags & PARTIAL)
5908*86d7f5d3SJohn Marino 	RCS_reparsercsfile (rcs, NULL, NULL);
5909*86d7f5d3SJohn Marino 
5910*86d7f5d3SJohn Marino     if (rev && ! *rev)
5911*86d7f5d3SJohn Marino 	rev = NULL;
5912*86d7f5d3SJohn Marino 
5913*86d7f5d3SJohn Marino     if (rev == NULL && rcs->branch == NULL)
5914*86d7f5d3SJohn Marino 	return 0;
5915*86d7f5d3SJohn Marino     if (rev != NULL && rcs->branch != NULL && STREQ (rev, rcs->branch))
5916*86d7f5d3SJohn Marino 	return 0;
5917*86d7f5d3SJohn Marino 
5918*86d7f5d3SJohn Marino     if (rcs->branch != NULL)
5919*86d7f5d3SJohn Marino 	free (rcs->branch);
5920*86d7f5d3SJohn Marino     rcs->branch = xstrdup (rev);
5921*86d7f5d3SJohn Marino 
5922*86d7f5d3SJohn Marino     return 0;
5923*86d7f5d3SJohn Marino }
5924*86d7f5d3SJohn Marino 
5925*86d7f5d3SJohn Marino 
5926*86d7f5d3SJohn Marino 
5927*86d7f5d3SJohn Marino /* Lock revision REV.  LOCK_QUIET is 1 to suppress output.  FIXME:
5928*86d7f5d3SJohn Marino    Most of the callers only call us because RCS_checkin still tends to
5929*86d7f5d3SJohn Marino    like a lock (a relic of old behavior inherited from the RCS ci
5930*86d7f5d3SJohn Marino    program).  If we clean this up, only "cvs admin -l" will still need
5931*86d7f5d3SJohn Marino    to call RCS_lock.  */
5932*86d7f5d3SJohn Marino 
5933*86d7f5d3SJohn Marino /* FIXME-twp: if a lock owned by someone else is broken, should this
5934*86d7f5d3SJohn Marino    send mail to the lock owner?  Prompt user?  It seems like such an
5935*86d7f5d3SJohn Marino    obscure situation for CVS as almost not worth worrying much
5936*86d7f5d3SJohn Marino    about. */
5937*86d7f5d3SJohn Marino int
RCS_lock(RCSNode * rcs,const char * rev,int lock_quiet)5938*86d7f5d3SJohn Marino RCS_lock (RCSNode *rcs, const char *rev, int lock_quiet)
5939*86d7f5d3SJohn Marino {
5940*86d7f5d3SJohn Marino     List *locks;
5941*86d7f5d3SJohn Marino     Node *p;
5942*86d7f5d3SJohn Marino     char *user;
5943*86d7f5d3SJohn Marino     char *xrev = NULL;
5944*86d7f5d3SJohn Marino 
5945*86d7f5d3SJohn Marino     if (rcs->flags & PARTIAL)
5946*86d7f5d3SJohn Marino 	RCS_reparsercsfile (rcs, NULL, NULL);
5947*86d7f5d3SJohn Marino 
5948*86d7f5d3SJohn Marino     locks = RCS_getlocks (rcs);
5949*86d7f5d3SJohn Marino     if (locks == NULL)
5950*86d7f5d3SJohn Marino 	locks = rcs->locks = getlist();
5951*86d7f5d3SJohn Marino     user = getcaller();
5952*86d7f5d3SJohn Marino 
5953*86d7f5d3SJohn Marino     /* A revision number of NULL means lock the head or default branch. */
5954*86d7f5d3SJohn Marino     if (rev == NULL)
5955*86d7f5d3SJohn Marino 	xrev = RCS_head (rcs);
5956*86d7f5d3SJohn Marino     else
5957*86d7f5d3SJohn Marino 	xrev = RCS_gettag (rcs, rev, 1, NULL);
5958*86d7f5d3SJohn Marino 
5959*86d7f5d3SJohn Marino     /* Make sure that the desired revision exists.  Technically,
5960*86d7f5d3SJohn Marino        we can update the locks list without even checking this,
5961*86d7f5d3SJohn Marino        but RCS 5.7 did this.  And it can't hurt. */
5962*86d7f5d3SJohn Marino     if (xrev == NULL || findnode (rcs->versions, xrev) == NULL)
5963*86d7f5d3SJohn Marino     {
5964*86d7f5d3SJohn Marino 	if (!lock_quiet)
5965*86d7f5d3SJohn Marino 	    error (0, 0, "%s: revision %s absent", rcs->print_path, rev);
5966*86d7f5d3SJohn Marino 	free (xrev);
5967*86d7f5d3SJohn Marino 	return 1;
5968*86d7f5d3SJohn Marino     }
5969*86d7f5d3SJohn Marino 
5970*86d7f5d3SJohn Marino     /* Is this rev already locked? */
5971*86d7f5d3SJohn Marino     p = findnode (locks, xrev);
5972*86d7f5d3SJohn Marino     if (p != NULL)
5973*86d7f5d3SJohn Marino     {
5974*86d7f5d3SJohn Marino 	if (STREQ (p->data, user))
5975*86d7f5d3SJohn Marino 	{
5976*86d7f5d3SJohn Marino 	    /* We already own the lock on this revision, so do nothing. */
5977*86d7f5d3SJohn Marino 	    free (xrev);
5978*86d7f5d3SJohn Marino 	    return 0;
5979*86d7f5d3SJohn Marino 	}
5980*86d7f5d3SJohn Marino 
5981*86d7f5d3SJohn Marino #if 0
5982*86d7f5d3SJohn Marino 	/* Well, first of all, "rev" below should be "xrev" to avoid
5983*86d7f5d3SJohn Marino 	   core dumps.  But more importantly, should we really be
5984*86d7f5d3SJohn Marino 	   breaking the lock unconditionally?  What CVS 1.9 does (via
5985*86d7f5d3SJohn Marino 	   RCS) is to prompt "Revision 1.1 is already locked by fred.
5986*86d7f5d3SJohn Marino 	   Do you want to break the lock? [ny](n): ".  Well, we don't
5987*86d7f5d3SJohn Marino 	   want to interact with the user (certainly not at the
5988*86d7f5d3SJohn Marino 	   server/protocol level, and probably not in the command-line
5989*86d7f5d3SJohn Marino 	   client), but isn't it more sensible to give an error and
5990*86d7f5d3SJohn Marino 	   let the user run "cvs admin -u" if they want to break the
5991*86d7f5d3SJohn Marino 	   lock?  */
5992*86d7f5d3SJohn Marino 
5993*86d7f5d3SJohn Marino 	/* Break the lock. */
5994*86d7f5d3SJohn Marino 	if (!lock_quiet)
5995*86d7f5d3SJohn Marino 	{
5996*86d7f5d3SJohn Marino 	    cvs_output (rev, 0);
5997*86d7f5d3SJohn Marino 	    cvs_output (" unlocked\n", 0);
5998*86d7f5d3SJohn Marino 	}
5999*86d7f5d3SJohn Marino 	delnode (p);
6000*86d7f5d3SJohn Marino #else
6001*86d7f5d3SJohn Marino 	error (1, 0, "Revision %s is already locked by %s",
6002*86d7f5d3SJohn Marino                xrev, (char *)p->data);
6003*86d7f5d3SJohn Marino #endif
6004*86d7f5d3SJohn Marino     }
6005*86d7f5d3SJohn Marino 
6006*86d7f5d3SJohn Marino     /* Create a new lock. */
6007*86d7f5d3SJohn Marino     p = getnode();
6008*86d7f5d3SJohn Marino     p->key = xrev;	/* already xstrdupped */
6009*86d7f5d3SJohn Marino     p->data = xstrdup (getcaller());
6010*86d7f5d3SJohn Marino     (void)addnode_at_front (locks, p);
6011*86d7f5d3SJohn Marino 
6012*86d7f5d3SJohn Marino     if (!lock_quiet)
6013*86d7f5d3SJohn Marino     {
6014*86d7f5d3SJohn Marino 	cvs_output (xrev, 0);
6015*86d7f5d3SJohn Marino 	cvs_output (" locked\n", 0);
6016*86d7f5d3SJohn Marino     }
6017*86d7f5d3SJohn Marino 
6018*86d7f5d3SJohn Marino     return 0;
6019*86d7f5d3SJohn Marino }
6020*86d7f5d3SJohn Marino 
6021*86d7f5d3SJohn Marino 
6022*86d7f5d3SJohn Marino 
6023*86d7f5d3SJohn Marino /* Unlock revision REV.  UNLOCK_QUIET is 1 to suppress output.  FIXME:
6024*86d7f5d3SJohn Marino    Like RCS_lock, this can become a no-op if we do the checkin
6025*86d7f5d3SJohn Marino    ourselves.
6026*86d7f5d3SJohn Marino 
6027*86d7f5d3SJohn Marino    If REV is not null and is locked by someone else, break their
6028*86d7f5d3SJohn Marino    lock and notify them.  It is an open issue whether RCS_unlock
6029*86d7f5d3SJohn Marino    queries the user about whether or not to break the lock. */
6030*86d7f5d3SJohn Marino int
RCS_unlock(RCSNode * rcs,char * rev,int unlock_quiet)6031*86d7f5d3SJohn Marino RCS_unlock (RCSNode *rcs, char *rev, int unlock_quiet)
6032*86d7f5d3SJohn Marino {
6033*86d7f5d3SJohn Marino     Node *lock;
6034*86d7f5d3SJohn Marino     List *locks;
6035*86d7f5d3SJohn Marino     char *user;
6036*86d7f5d3SJohn Marino     char *xrev = NULL;
6037*86d7f5d3SJohn Marino 
6038*86d7f5d3SJohn Marino     user = getcaller();
6039*86d7f5d3SJohn Marino     if (rcs->flags & PARTIAL)
6040*86d7f5d3SJohn Marino 	RCS_reparsercsfile (rcs, NULL, NULL);
6041*86d7f5d3SJohn Marino 
6042*86d7f5d3SJohn Marino     /* If rev is NULL, unlock the revision held by the caller; if more
6043*86d7f5d3SJohn Marino        than one, make the user specify the revision explicitly.  This
6044*86d7f5d3SJohn Marino        differs from RCS which unlocks the latest revision (first in
6045*86d7f5d3SJohn Marino        rcs->locks) held by the caller. */
6046*86d7f5d3SJohn Marino     if (rev == NULL)
6047*86d7f5d3SJohn Marino     {
6048*86d7f5d3SJohn Marino 	Node *p;
6049*86d7f5d3SJohn Marino 
6050*86d7f5d3SJohn Marino 	/* No-ops: attempts to unlock an empty tree or an unlocked file. */
6051*86d7f5d3SJohn Marino 	if (rcs->head == NULL)
6052*86d7f5d3SJohn Marino 	{
6053*86d7f5d3SJohn Marino 	    if (!unlock_quiet)
6054*86d7f5d3SJohn Marino 		cvs_outerr ("can't unlock an empty tree\n", 0);
6055*86d7f5d3SJohn Marino 	    return 0;
6056*86d7f5d3SJohn Marino 	}
6057*86d7f5d3SJohn Marino 
6058*86d7f5d3SJohn Marino 	locks = RCS_getlocks (rcs);
6059*86d7f5d3SJohn Marino 	if (locks == NULL)
6060*86d7f5d3SJohn Marino 	{
6061*86d7f5d3SJohn Marino 	    if (!unlock_quiet)
6062*86d7f5d3SJohn Marino 		cvs_outerr ("No locks are set.\n", 0);
6063*86d7f5d3SJohn Marino 	    return 0;
6064*86d7f5d3SJohn Marino 	}
6065*86d7f5d3SJohn Marino 
6066*86d7f5d3SJohn Marino 	lock = NULL;
6067*86d7f5d3SJohn Marino 	for (p = locks->list->next; p != locks->list; p = p->next)
6068*86d7f5d3SJohn Marino 	{
6069*86d7f5d3SJohn Marino 	    if (STREQ (p->data, user))
6070*86d7f5d3SJohn Marino 	    {
6071*86d7f5d3SJohn Marino 		if (lock != NULL)
6072*86d7f5d3SJohn Marino 		{
6073*86d7f5d3SJohn Marino 		    if (!unlock_quiet)
6074*86d7f5d3SJohn Marino 			error (0, 0, "\
6075*86d7f5d3SJohn Marino %s: multiple revisions locked by %s; please specify one", rcs->print_path, user);
6076*86d7f5d3SJohn Marino 		    return 1;
6077*86d7f5d3SJohn Marino 		}
6078*86d7f5d3SJohn Marino 		lock = p;
6079*86d7f5d3SJohn Marino 	    }
6080*86d7f5d3SJohn Marino 	}
6081*86d7f5d3SJohn Marino 	if (lock == NULL)
6082*86d7f5d3SJohn Marino 	{
6083*86d7f5d3SJohn Marino 	    if (!unlock_quiet)
6084*86d7f5d3SJohn Marino 		error (0, 0, "No locks are set for %s.\n", user);
6085*86d7f5d3SJohn Marino 	    return 0;	/* no lock found, ergo nothing to do */
6086*86d7f5d3SJohn Marino 	}
6087*86d7f5d3SJohn Marino 	xrev = xstrdup (lock->key);
6088*86d7f5d3SJohn Marino     }
6089*86d7f5d3SJohn Marino     else
6090*86d7f5d3SJohn Marino     {
6091*86d7f5d3SJohn Marino 	xrev = RCS_gettag (rcs, rev, 1, NULL);
6092*86d7f5d3SJohn Marino 	if (xrev == NULL)
6093*86d7f5d3SJohn Marino 	{
6094*86d7f5d3SJohn Marino 	    error (0, 0, "%s: revision %s absent", rcs->print_path, rev);
6095*86d7f5d3SJohn Marino 	    return 1;
6096*86d7f5d3SJohn Marino 	}
6097*86d7f5d3SJohn Marino     }
6098*86d7f5d3SJohn Marino 
6099*86d7f5d3SJohn Marino     lock = findnode (RCS_getlocks (rcs), xrev);
6100*86d7f5d3SJohn Marino     if (lock == NULL)
6101*86d7f5d3SJohn Marino     {
6102*86d7f5d3SJohn Marino 	/* This revision isn't locked. */
6103*86d7f5d3SJohn Marino 	free (xrev);
6104*86d7f5d3SJohn Marino 	return 0;
6105*86d7f5d3SJohn Marino     }
6106*86d7f5d3SJohn Marino 
6107*86d7f5d3SJohn Marino     if (! STREQ (lock->data, user))
6108*86d7f5d3SJohn Marino     {
6109*86d7f5d3SJohn Marino         /* If the revision is locked by someone else, notify
6110*86d7f5d3SJohn Marino 	   them.  Note that this shouldn't ever happen if RCS_unlock
6111*86d7f5d3SJohn Marino 	   is called with a NULL revision, since that means "whatever
6112*86d7f5d3SJohn Marino 	   revision is currently locked by the caller." */
6113*86d7f5d3SJohn Marino 	char *repos, *workfile;
6114*86d7f5d3SJohn Marino 	if (!unlock_quiet)
6115*86d7f5d3SJohn Marino 	    error (0, 0, "\
6116*86d7f5d3SJohn Marino %s: revision %s locked by %s; breaking lock", rcs->print_path, xrev,
6117*86d7f5d3SJohn Marino 		   (char *)lock->data);
6118*86d7f5d3SJohn Marino 	repos = xstrdup (rcs->path);
6119*86d7f5d3SJohn Marino 	workfile = strrchr (repos, '/');
6120*86d7f5d3SJohn Marino 	*workfile++ = '\0';
6121*86d7f5d3SJohn Marino 	notify_do ('C', workfile, NULL, user, NULL, NULL, repos);
6122*86d7f5d3SJohn Marino 	free (repos);
6123*86d7f5d3SJohn Marino     }
6124*86d7f5d3SJohn Marino 
6125*86d7f5d3SJohn Marino     delnode (lock);
6126*86d7f5d3SJohn Marino     if (!unlock_quiet)
6127*86d7f5d3SJohn Marino     {
6128*86d7f5d3SJohn Marino 	cvs_output (xrev, 0);
6129*86d7f5d3SJohn Marino 	cvs_output (" unlocked\n", 0);
6130*86d7f5d3SJohn Marino     }
6131*86d7f5d3SJohn Marino 
6132*86d7f5d3SJohn Marino     free (xrev);
6133*86d7f5d3SJohn Marino     return 0;
6134*86d7f5d3SJohn Marino }
6135*86d7f5d3SJohn Marino 
6136*86d7f5d3SJohn Marino 
6137*86d7f5d3SJohn Marino 
6138*86d7f5d3SJohn Marino /* Add USER to the access list of RCS.  Do nothing if already present.
6139*86d7f5d3SJohn Marino    FIXME-twp: check syntax of USER to make sure it's a valid id. */
6140*86d7f5d3SJohn Marino 
6141*86d7f5d3SJohn Marino void
RCS_addaccess(RCSNode * rcs,char * user)6142*86d7f5d3SJohn Marino RCS_addaccess (RCSNode *rcs, char *user)
6143*86d7f5d3SJohn Marino {
6144*86d7f5d3SJohn Marino     char *access, *a;
6145*86d7f5d3SJohn Marino 
6146*86d7f5d3SJohn Marino     if (rcs->flags & PARTIAL)
6147*86d7f5d3SJohn Marino 	RCS_reparsercsfile (rcs, NULL, NULL);
6148*86d7f5d3SJohn Marino 
6149*86d7f5d3SJohn Marino     if (rcs->access == NULL)
6150*86d7f5d3SJohn Marino 	rcs->access = xstrdup (user);
6151*86d7f5d3SJohn Marino     else
6152*86d7f5d3SJohn Marino     {
6153*86d7f5d3SJohn Marino 	access = xstrdup (rcs->access);
6154*86d7f5d3SJohn Marino 	for (a = strtok (access, " "); a != NULL; a = strtok (NULL, " "))
6155*86d7f5d3SJohn Marino 	{
6156*86d7f5d3SJohn Marino 	    if (STREQ (a, user))
6157*86d7f5d3SJohn Marino 	    {
6158*86d7f5d3SJohn Marino 		free (access);
6159*86d7f5d3SJohn Marino 		return;
6160*86d7f5d3SJohn Marino 	    }
6161*86d7f5d3SJohn Marino 	}
6162*86d7f5d3SJohn Marino 	free (access);
6163*86d7f5d3SJohn Marino 	rcs->access = xrealloc (rcs->access,
6164*86d7f5d3SJohn Marino 				strlen (rcs->access) + strlen (user) + 2);
6165*86d7f5d3SJohn Marino 	strcat (rcs->access, " ");
6166*86d7f5d3SJohn Marino 	strcat (rcs->access, user);
6167*86d7f5d3SJohn Marino     }
6168*86d7f5d3SJohn Marino }
6169*86d7f5d3SJohn Marino 
6170*86d7f5d3SJohn Marino 
6171*86d7f5d3SJohn Marino 
6172*86d7f5d3SJohn Marino /* Remove USER from the access list of RCS. */
6173*86d7f5d3SJohn Marino void
RCS_delaccess(RCSNode * rcs,char * user)6174*86d7f5d3SJohn Marino RCS_delaccess (RCSNode *rcs, char *user)
6175*86d7f5d3SJohn Marino {
6176*86d7f5d3SJohn Marino     char *p, *s;
6177*86d7f5d3SJohn Marino     int ulen;
6178*86d7f5d3SJohn Marino 
6179*86d7f5d3SJohn Marino     if (rcs->flags & PARTIAL)
6180*86d7f5d3SJohn Marino 	RCS_reparsercsfile (rcs, NULL, NULL);
6181*86d7f5d3SJohn Marino 
6182*86d7f5d3SJohn Marino     if (rcs->access == NULL)
6183*86d7f5d3SJohn Marino 	return;
6184*86d7f5d3SJohn Marino 
6185*86d7f5d3SJohn Marino     if (user == NULL)
6186*86d7f5d3SJohn Marino     {
6187*86d7f5d3SJohn Marino         free (rcs->access);
6188*86d7f5d3SJohn Marino         rcs->access = NULL;
6189*86d7f5d3SJohn Marino         return;
6190*86d7f5d3SJohn Marino     }
6191*86d7f5d3SJohn Marino 
6192*86d7f5d3SJohn Marino     p = rcs->access;
6193*86d7f5d3SJohn Marino     ulen = strlen (user);
6194*86d7f5d3SJohn Marino     while (p != NULL)
6195*86d7f5d3SJohn Marino     {
6196*86d7f5d3SJohn Marino 	if (strncmp (p, user, ulen) == 0 && (p[ulen] == '\0' || p[ulen] == ' '))
6197*86d7f5d3SJohn Marino 	    break;
6198*86d7f5d3SJohn Marino 	p = strchr (p, ' ');
6199*86d7f5d3SJohn Marino 	if (p != NULL)
6200*86d7f5d3SJohn Marino 	    ++p;
6201*86d7f5d3SJohn Marino     }
6202*86d7f5d3SJohn Marino 
6203*86d7f5d3SJohn Marino     if (p == NULL)
6204*86d7f5d3SJohn Marino 	return;
6205*86d7f5d3SJohn Marino 
6206*86d7f5d3SJohn Marino     s = p + ulen;
6207*86d7f5d3SJohn Marino     while (*s != '\0')
6208*86d7f5d3SJohn Marino 	*p++ = *s++;
6209*86d7f5d3SJohn Marino     *p = '\0';
6210*86d7f5d3SJohn Marino }
6211*86d7f5d3SJohn Marino 
6212*86d7f5d3SJohn Marino 
6213*86d7f5d3SJohn Marino 
6214*86d7f5d3SJohn Marino char *
RCS_getaccess(RCSNode * rcs)6215*86d7f5d3SJohn Marino RCS_getaccess (RCSNode *rcs)
6216*86d7f5d3SJohn Marino {
6217*86d7f5d3SJohn Marino     if (rcs->flags & PARTIAL)
6218*86d7f5d3SJohn Marino 	RCS_reparsercsfile (rcs, NULL, NULL);
6219*86d7f5d3SJohn Marino 
6220*86d7f5d3SJohn Marino     return rcs->access;
6221*86d7f5d3SJohn Marino }
6222*86d7f5d3SJohn Marino 
6223*86d7f5d3SJohn Marino 
6224*86d7f5d3SJohn Marino 
6225*86d7f5d3SJohn Marino /* Return a nonzero value if the revision specified by ARG is found.  */
6226*86d7f5d3SJohn Marino static int
findtag(Node * node,void * arg)6227*86d7f5d3SJohn Marino findtag (Node *node, void *arg)
6228*86d7f5d3SJohn Marino {
6229*86d7f5d3SJohn Marino     char *rev = arg;
6230*86d7f5d3SJohn Marino 
6231*86d7f5d3SJohn Marino     if (STREQ (node->data, rev))
6232*86d7f5d3SJohn Marino 	return 1;
6233*86d7f5d3SJohn Marino     else
6234*86d7f5d3SJohn Marino 	return 0;
6235*86d7f5d3SJohn Marino }
6236*86d7f5d3SJohn Marino 
6237*86d7f5d3SJohn Marino 
6238*86d7f5d3SJohn Marino 
6239*86d7f5d3SJohn Marino /* Delete revisions between REV1 and REV2.  The changes between the two
6240*86d7f5d3SJohn Marino    revisions must be collapsed, and the result stored in the revision
6241*86d7f5d3SJohn Marino    immediately preceding the lower one.  Return 0 for successful completion,
6242*86d7f5d3SJohn Marino    1 otherwise.
6243*86d7f5d3SJohn Marino 
6244*86d7f5d3SJohn Marino    Solution: check out the revision preceding REV1 and the revision
6245*86d7f5d3SJohn Marino    following REV2.  Use call_diff to find aggregate diffs between
6246*86d7f5d3SJohn Marino    these two revisions, and replace the delta text for the latter one
6247*86d7f5d3SJohn Marino    with the new aggregate diff.  Alternatively, we could write a
6248*86d7f5d3SJohn Marino    function that takes two change texts and combines them to produce a
6249*86d7f5d3SJohn Marino    new change text, without checking out any revs or calling diff.  It
6250*86d7f5d3SJohn Marino    would be hairy, but so, so cool.
6251*86d7f5d3SJohn Marino 
6252*86d7f5d3SJohn Marino    If INCLUSIVE is set, then TAG1 and TAG2, if non-NULL, tell us to
6253*86d7f5d3SJohn Marino    delete that revision as well (cvs admin -o tag1:tag2).  If clear,
6254*86d7f5d3SJohn Marino    delete up to but not including that revision (cvs admin -o tag1::tag2).
6255*86d7f5d3SJohn Marino    This does not affect TAG1 or TAG2 being NULL; the meaning of the start
6256*86d7f5d3SJohn Marino    point in ::tag2 and :tag2 is the same and likewise for end points.  */
6257*86d7f5d3SJohn Marino int
RCS_delete_revs(RCSNode * rcs,char * tag1,char * tag2,int inclusive)6258*86d7f5d3SJohn Marino RCS_delete_revs (RCSNode *rcs, char *tag1, char *tag2, int inclusive)
6259*86d7f5d3SJohn Marino {
6260*86d7f5d3SJohn Marino     char *next;
6261*86d7f5d3SJohn Marino     Node *nodep;
6262*86d7f5d3SJohn Marino     RCSVers *revp = NULL;
6263*86d7f5d3SJohn Marino     RCSVers *beforep;
6264*86d7f5d3SJohn Marino     int status, found;
6265*86d7f5d3SJohn Marino     int save_noexec;
6266*86d7f5d3SJohn Marino 
6267*86d7f5d3SJohn Marino     char *branchpoint = NULL;
6268*86d7f5d3SJohn Marino     char *rev1 = NULL;
6269*86d7f5d3SJohn Marino     char *rev2 = NULL;
6270*86d7f5d3SJohn Marino     int rev1_inclusive = inclusive;
6271*86d7f5d3SJohn Marino     int rev2_inclusive = inclusive;
6272*86d7f5d3SJohn Marino     char *before = NULL;
6273*86d7f5d3SJohn Marino     char *after = NULL;
6274*86d7f5d3SJohn Marino     char *beforefile = NULL;
6275*86d7f5d3SJohn Marino     char *afterfile = NULL;
6276*86d7f5d3SJohn Marino     char *outfile = NULL;
6277*86d7f5d3SJohn Marino 
6278*86d7f5d3SJohn Marino     if (tag1 == NULL && tag2 == NULL)
6279*86d7f5d3SJohn Marino 	return 0;
6280*86d7f5d3SJohn Marino 
6281*86d7f5d3SJohn Marino     /* Assume error status until everything is finished. */
6282*86d7f5d3SJohn Marino     status = 1;
6283*86d7f5d3SJohn Marino 
6284*86d7f5d3SJohn Marino     /* Make sure both revisions exist. */
6285*86d7f5d3SJohn Marino     if (tag1 != NULL)
6286*86d7f5d3SJohn Marino     {
6287*86d7f5d3SJohn Marino 	rev1 = RCS_gettag (rcs, tag1, 1, NULL);
6288*86d7f5d3SJohn Marino 	if (rev1 == NULL || (nodep = findnode (rcs->versions, rev1)) == NULL)
6289*86d7f5d3SJohn Marino 	{
6290*86d7f5d3SJohn Marino 	    error (0, 0, "%s: Revision %s doesn't exist.", rcs->print_path, tag1);
6291*86d7f5d3SJohn Marino 	    goto delrev_done;
6292*86d7f5d3SJohn Marino 	}
6293*86d7f5d3SJohn Marino     }
6294*86d7f5d3SJohn Marino     if (tag2 != NULL)
6295*86d7f5d3SJohn Marino     {
6296*86d7f5d3SJohn Marino 	rev2 = RCS_gettag (rcs, tag2, 1, NULL);
6297*86d7f5d3SJohn Marino 	if (rev2 == NULL || (nodep = findnode (rcs->versions, rev2)) == NULL)
6298*86d7f5d3SJohn Marino 	{
6299*86d7f5d3SJohn Marino 	    error (0, 0, "%s: Revision %s doesn't exist.", rcs->print_path, tag2);
6300*86d7f5d3SJohn Marino 	    goto delrev_done;
6301*86d7f5d3SJohn Marino 	}
6302*86d7f5d3SJohn Marino     }
6303*86d7f5d3SJohn Marino 
6304*86d7f5d3SJohn Marino     /* If rev1 is on the trunk and rev2 is NULL, rev2 should be
6305*86d7f5d3SJohn Marino        RCS->HEAD.  (*Not* RCS_head(rcs), which may return rcs->branch
6306*86d7f5d3SJohn Marino        instead.)  We need to check this special case early, in order
6307*86d7f5d3SJohn Marino        to make sure that rev1 and rev2 get ordered correctly. */
6308*86d7f5d3SJohn Marino     if (rev2 == NULL && numdots (rev1) == 1)
6309*86d7f5d3SJohn Marino     {
6310*86d7f5d3SJohn Marino 	rev2 = xstrdup (rcs->head);
6311*86d7f5d3SJohn Marino 	rev2_inclusive = 1;
6312*86d7f5d3SJohn Marino     }
6313*86d7f5d3SJohn Marino 
6314*86d7f5d3SJohn Marino     if (rev2 == NULL)
6315*86d7f5d3SJohn Marino 	rev2_inclusive = 1;
6316*86d7f5d3SJohn Marino 
6317*86d7f5d3SJohn Marino     if (rev1 != NULL && rev2 != NULL)
6318*86d7f5d3SJohn Marino     {
6319*86d7f5d3SJohn Marino 	/* A range consisting of a branch number means the latest revision
6320*86d7f5d3SJohn Marino 	   on that branch. */
6321*86d7f5d3SJohn Marino 	if (RCS_isbranch (rcs, rev1) && STREQ (rev1, rev2))
6322*86d7f5d3SJohn Marino 	{
6323*86d7f5d3SJohn Marino 	    char *tmp = RCS_getbranch (rcs, rev1, 0);
6324*86d7f5d3SJohn Marino 	    free (rev1);
6325*86d7f5d3SJohn Marino 	    free (rev2);
6326*86d7f5d3SJohn Marino 	    rev1 = rev2 = tmp;
6327*86d7f5d3SJohn Marino 	}
6328*86d7f5d3SJohn Marino 	else
6329*86d7f5d3SJohn Marino 	{
6330*86d7f5d3SJohn Marino 	    /* Make sure REV1 and REV2 are ordered correctly (in the
6331*86d7f5d3SJohn Marino 	       same order as the next field).  For revisions on the
6332*86d7f5d3SJohn Marino 	       trunk, REV1 should be higher than REV2; for branches,
6333*86d7f5d3SJohn Marino 	       REV1 should be lower.  */
6334*86d7f5d3SJohn Marino 	    /* Shouldn't we just be giving an error in the case where
6335*86d7f5d3SJohn Marino 	       the user specifies the revisions in the wrong order
6336*86d7f5d3SJohn Marino 	       (that is, always swap on the trunk, never swap on a
6337*86d7f5d3SJohn Marino 	       branch, in the non-error cases)?  It is not at all
6338*86d7f5d3SJohn Marino 	       clear to me that users who specify -o 1.4:1.2 really
6339*86d7f5d3SJohn Marino 	       meant to type -o 1.2:1.4, and the out of order usage
6340*86d7f5d3SJohn Marino 	       has never been documented, either by cvs.texinfo or
6341*86d7f5d3SJohn Marino 	       rcs(1).  */
6342*86d7f5d3SJohn Marino 	    char *temp;
6343*86d7f5d3SJohn Marino 	    int temp_inclusive;
6344*86d7f5d3SJohn Marino 	    if (numdots (rev1) == 1)
6345*86d7f5d3SJohn Marino 	    {
6346*86d7f5d3SJohn Marino 		if (compare_revnums (rev1, rev2) <= 0)
6347*86d7f5d3SJohn Marino 		{
6348*86d7f5d3SJohn Marino 		    temp = rev2;
6349*86d7f5d3SJohn Marino 		    rev2 = rev1;
6350*86d7f5d3SJohn Marino 		    rev1 = temp;
6351*86d7f5d3SJohn Marino 
6352*86d7f5d3SJohn Marino 		    temp_inclusive = rev2_inclusive;
6353*86d7f5d3SJohn Marino 		    rev2_inclusive = rev1_inclusive;
6354*86d7f5d3SJohn Marino 		    rev1_inclusive = temp_inclusive;
6355*86d7f5d3SJohn Marino 		}
6356*86d7f5d3SJohn Marino 	    }
6357*86d7f5d3SJohn Marino 	    else if (compare_revnums (rev1, rev2) > 0)
6358*86d7f5d3SJohn Marino 	    {
6359*86d7f5d3SJohn Marino 		temp = rev2;
6360*86d7f5d3SJohn Marino 		rev2 = rev1;
6361*86d7f5d3SJohn Marino 		rev1 = temp;
6362*86d7f5d3SJohn Marino 
6363*86d7f5d3SJohn Marino 		temp_inclusive = rev2_inclusive;
6364*86d7f5d3SJohn Marino 		rev2_inclusive = rev1_inclusive;
6365*86d7f5d3SJohn Marino 		rev1_inclusive = temp_inclusive;
6366*86d7f5d3SJohn Marino 	    }
6367*86d7f5d3SJohn Marino 	}
6368*86d7f5d3SJohn Marino     }
6369*86d7f5d3SJohn Marino 
6370*86d7f5d3SJohn Marino     /* Basically the same thing; make sure that the ordering is what we
6371*86d7f5d3SJohn Marino        need.  */
6372*86d7f5d3SJohn Marino     if (rev1 == NULL)
6373*86d7f5d3SJohn Marino     {
6374*86d7f5d3SJohn Marino 	assert (rev2 != NULL);
6375*86d7f5d3SJohn Marino 	if (numdots (rev2) == 1)
6376*86d7f5d3SJohn Marino 	{
6377*86d7f5d3SJohn Marino 	    /* Swap rev1 and rev2.  */
6378*86d7f5d3SJohn Marino 	    int temp_inclusive;
6379*86d7f5d3SJohn Marino 
6380*86d7f5d3SJohn Marino 	    rev1 = rev2;
6381*86d7f5d3SJohn Marino 	    rev2 = NULL;
6382*86d7f5d3SJohn Marino 
6383*86d7f5d3SJohn Marino 	    temp_inclusive = rev2_inclusive;
6384*86d7f5d3SJohn Marino 	    rev2_inclusive = rev1_inclusive;
6385*86d7f5d3SJohn Marino 	    rev1_inclusive = temp_inclusive;
6386*86d7f5d3SJohn Marino 	}
6387*86d7f5d3SJohn Marino     }
6388*86d7f5d3SJohn Marino 
6389*86d7f5d3SJohn Marino     /* Put the revision number preceding the first one to delete into
6390*86d7f5d3SJohn Marino        BEFORE (where "preceding" means according to the next field).
6391*86d7f5d3SJohn Marino        If the first revision to delete is the first revision on its
6392*86d7f5d3SJohn Marino        branch (e.g. 1.3.2.1), BEFORE should be the node on the trunk
6393*86d7f5d3SJohn Marino        at which the branch is rooted.  If the first revision to delete
6394*86d7f5d3SJohn Marino        is the head revision of the trunk, set BEFORE to NULL.
6395*86d7f5d3SJohn Marino 
6396*86d7f5d3SJohn Marino        Note that because BEFORE may not be on the same branch as REV1,
6397*86d7f5d3SJohn Marino        it is not very handy for navigating the revision tree.  It's
6398*86d7f5d3SJohn Marino        most useful just for checking out the revision preceding REV1. */
6399*86d7f5d3SJohn Marino     before = NULL;
6400*86d7f5d3SJohn Marino     branchpoint = RCS_getbranchpoint (rcs, rev1 != NULL ? rev1 : rev2);
6401*86d7f5d3SJohn Marino     if (rev1 == NULL)
6402*86d7f5d3SJohn Marino     {
6403*86d7f5d3SJohn Marino 	rev1 = xstrdup (branchpoint);
6404*86d7f5d3SJohn Marino 	if (numdots (branchpoint) > 1)
6405*86d7f5d3SJohn Marino 	{
6406*86d7f5d3SJohn Marino 	    char *bp;
6407*86d7f5d3SJohn Marino 	    bp = strrchr (branchpoint, '.');
6408*86d7f5d3SJohn Marino 	    while (*--bp != '.')
6409*86d7f5d3SJohn Marino 		;
6410*86d7f5d3SJohn Marino 	    *bp = '\0';
6411*86d7f5d3SJohn Marino 	    /* Note that this is exclusive, always, because the inclusive
6412*86d7f5d3SJohn Marino 	       flag doesn't affect the meaning when rev1 == NULL.  */
6413*86d7f5d3SJohn Marino 	    before = xstrdup (branchpoint);
6414*86d7f5d3SJohn Marino 	    *bp = '.';
6415*86d7f5d3SJohn Marino 	}
6416*86d7f5d3SJohn Marino     }
6417*86d7f5d3SJohn Marino     else if (! STREQ (rev1, branchpoint))
6418*86d7f5d3SJohn Marino     {
6419*86d7f5d3SJohn Marino 	/* Walk deltas from BRANCHPOINT on, looking for REV1. */
6420*86d7f5d3SJohn Marino 	nodep = findnode (rcs->versions, branchpoint);
6421*86d7f5d3SJohn Marino 	revp = nodep->data;
6422*86d7f5d3SJohn Marino 	while (revp->next != NULL && ! STREQ (revp->next, rev1))
6423*86d7f5d3SJohn Marino 	{
6424*86d7f5d3SJohn Marino 	    revp = nodep->data;
6425*86d7f5d3SJohn Marino 	    nodep = findnode (rcs->versions, revp->next);
6426*86d7f5d3SJohn Marino 	}
6427*86d7f5d3SJohn Marino 	if (revp->next == NULL)
6428*86d7f5d3SJohn Marino 	{
6429*86d7f5d3SJohn Marino 	    error (0, 0, "%s: Revision %s doesn't exist.", rcs->print_path, rev1);
6430*86d7f5d3SJohn Marino 	    goto delrev_done;
6431*86d7f5d3SJohn Marino 	}
6432*86d7f5d3SJohn Marino 	if (rev1_inclusive)
6433*86d7f5d3SJohn Marino 	    before = xstrdup (revp->version);
6434*86d7f5d3SJohn Marino 	else
6435*86d7f5d3SJohn Marino 	{
6436*86d7f5d3SJohn Marino 	    before = rev1;
6437*86d7f5d3SJohn Marino 	    nodep = findnode (rcs->versions, before);
6438*86d7f5d3SJohn Marino 	    rev1 = xstrdup (((RCSVers *)nodep->data)->next);
6439*86d7f5d3SJohn Marino 	}
6440*86d7f5d3SJohn Marino     }
6441*86d7f5d3SJohn Marino     else if (!rev1_inclusive)
6442*86d7f5d3SJohn Marino     {
6443*86d7f5d3SJohn Marino 	before = rev1;
6444*86d7f5d3SJohn Marino 	nodep = findnode (rcs->versions, before);
6445*86d7f5d3SJohn Marino 	rev1 = xstrdup (((RCSVers *)nodep->data)->next);
6446*86d7f5d3SJohn Marino     }
6447*86d7f5d3SJohn Marino     else if (numdots (branchpoint) > 1)
6448*86d7f5d3SJohn Marino     {
6449*86d7f5d3SJohn Marino 	/* Example: rev1 is "1.3.2.1", branchpoint is "1.3.2.1".
6450*86d7f5d3SJohn Marino 	   Set before to "1.3".  */
6451*86d7f5d3SJohn Marino 	char *bp;
6452*86d7f5d3SJohn Marino 	bp = strrchr (branchpoint, '.');
6453*86d7f5d3SJohn Marino 	while (*--bp != '.')
6454*86d7f5d3SJohn Marino 	    ;
6455*86d7f5d3SJohn Marino 	*bp = '\0';
6456*86d7f5d3SJohn Marino 	before = xstrdup (branchpoint);
6457*86d7f5d3SJohn Marino 	*bp = '.';
6458*86d7f5d3SJohn Marino     }
6459*86d7f5d3SJohn Marino 
6460*86d7f5d3SJohn Marino     /* If any revision between REV1 and REV2 is locked or is a branch point,
6461*86d7f5d3SJohn Marino        we can't delete that revision and must abort. */
6462*86d7f5d3SJohn Marino     after = NULL;
6463*86d7f5d3SJohn Marino     next = rev1;
6464*86d7f5d3SJohn Marino     found = 0;
6465*86d7f5d3SJohn Marino     while (!found && next != NULL)
6466*86d7f5d3SJohn Marino     {
6467*86d7f5d3SJohn Marino 	nodep = findnode (rcs->versions, next);
6468*86d7f5d3SJohn Marino 	revp = nodep->data;
6469*86d7f5d3SJohn Marino 
6470*86d7f5d3SJohn Marino 	if (rev2 != NULL)
6471*86d7f5d3SJohn Marino 	    found = STREQ (revp->version, rev2);
6472*86d7f5d3SJohn Marino 	next = revp->next;
6473*86d7f5d3SJohn Marino 
6474*86d7f5d3SJohn Marino 	if ((!found && next != NULL) || rev2_inclusive || rev2 == NULL)
6475*86d7f5d3SJohn Marino 	{
6476*86d7f5d3SJohn Marino 	    if (findnode (RCS_getlocks (rcs), revp->version))
6477*86d7f5d3SJohn Marino 	    {
6478*86d7f5d3SJohn Marino 		error (0, 0, "%s: can't remove locked revision %s",
6479*86d7f5d3SJohn Marino 		       rcs->print_path,
6480*86d7f5d3SJohn Marino 		       revp->version);
6481*86d7f5d3SJohn Marino 		goto delrev_done;
6482*86d7f5d3SJohn Marino 	    }
6483*86d7f5d3SJohn Marino 	    if (revp->branches != NULL)
6484*86d7f5d3SJohn Marino 	    {
6485*86d7f5d3SJohn Marino 		error (0, 0, "%s: can't remove branch point %s",
6486*86d7f5d3SJohn Marino 		       rcs->print_path,
6487*86d7f5d3SJohn Marino 		       revp->version);
6488*86d7f5d3SJohn Marino 		goto delrev_done;
6489*86d7f5d3SJohn Marino 	    }
6490*86d7f5d3SJohn Marino 
6491*86d7f5d3SJohn Marino 	    /* Doing this only for the :: syntax is for compatibility.
6492*86d7f5d3SJohn Marino 	       See cvs.texinfo for somewhat more discussion.  */
6493*86d7f5d3SJohn Marino 	    if (!inclusive
6494*86d7f5d3SJohn Marino 		&& walklist (RCS_symbols (rcs), findtag, revp->version))
6495*86d7f5d3SJohn Marino 	    {
6496*86d7f5d3SJohn Marino 		/* We don't print which file this happens to on the theory
6497*86d7f5d3SJohn Marino 		   that the caller will print the name of the file in a
6498*86d7f5d3SJohn Marino 		   more useful fashion (fullname not rcs->path).  */
6499*86d7f5d3SJohn Marino 		error (0, 0, "cannot remove revision %s because it has tags",
6500*86d7f5d3SJohn Marino 		       revp->version);
6501*86d7f5d3SJohn Marino 		goto delrev_done;
6502*86d7f5d3SJohn Marino 	    }
6503*86d7f5d3SJohn Marino 
6504*86d7f5d3SJohn Marino 	    /* It's misleading to print the `deleting revision' output
6505*86d7f5d3SJohn Marino 	       here, since we may not actually delete these revisions.
6506*86d7f5d3SJohn Marino 	       But that's how RCS does it.  Bleah.  Someday this should be
6507*86d7f5d3SJohn Marino 	       moved to the point where the revs are actually marked for
6508*86d7f5d3SJohn Marino 	       deletion. -twp */
6509*86d7f5d3SJohn Marino 	    cvs_output ("deleting revision ", 0);
6510*86d7f5d3SJohn Marino 	    cvs_output (revp->version, 0);
6511*86d7f5d3SJohn Marino 	    cvs_output ("\n", 1);
6512*86d7f5d3SJohn Marino 	}
6513*86d7f5d3SJohn Marino     }
6514*86d7f5d3SJohn Marino 
6515*86d7f5d3SJohn Marino     if (rev2 == NULL)
6516*86d7f5d3SJohn Marino 	;
6517*86d7f5d3SJohn Marino     else if (found)
6518*86d7f5d3SJohn Marino     {
6519*86d7f5d3SJohn Marino 	if (rev2_inclusive)
6520*86d7f5d3SJohn Marino 	    after = xstrdup (next);
6521*86d7f5d3SJohn Marino 	else
6522*86d7f5d3SJohn Marino 	    after = xstrdup (revp->version);
6523*86d7f5d3SJohn Marino     }
6524*86d7f5d3SJohn Marino     else if (!inclusive)
6525*86d7f5d3SJohn Marino     {
6526*86d7f5d3SJohn Marino 	/* In the case of an empty range, for example 1.2::1.2 or
6527*86d7f5d3SJohn Marino 	   1.2::1.3, we want to just do nothing.  */
6528*86d7f5d3SJohn Marino 	status = 0;
6529*86d7f5d3SJohn Marino 	goto delrev_done;
6530*86d7f5d3SJohn Marino     }
6531*86d7f5d3SJohn Marino     else
6532*86d7f5d3SJohn Marino     {
6533*86d7f5d3SJohn Marino 	/* This looks fishy in the cases where tag1 == NULL or tag2 == NULL.
6534*86d7f5d3SJohn Marino 	   Are those cases really impossible?  */
6535*86d7f5d3SJohn Marino 	assert (tag1 != NULL);
6536*86d7f5d3SJohn Marino 	assert (tag2 != NULL);
6537*86d7f5d3SJohn Marino 
6538*86d7f5d3SJohn Marino 	error (0, 0, "%s: invalid revision range %s:%s", rcs->print_path,
6539*86d7f5d3SJohn Marino 	       tag1, tag2);
6540*86d7f5d3SJohn Marino 	goto delrev_done;
6541*86d7f5d3SJohn Marino     }
6542*86d7f5d3SJohn Marino 
6543*86d7f5d3SJohn Marino     if (after == NULL && before == NULL)
6544*86d7f5d3SJohn Marino     {
6545*86d7f5d3SJohn Marino 	/* The user is trying to delete all revisions.  While an
6546*86d7f5d3SJohn Marino 	   RCS file without revisions makes sense to RCS (e.g. the
6547*86d7f5d3SJohn Marino 	   state after "rcs -i"), CVS has never been able to cope with
6548*86d7f5d3SJohn Marino 	   it.  So at least for now we just make this an error.
6549*86d7f5d3SJohn Marino 
6550*86d7f5d3SJohn Marino 	   We don't include rcs->path in the message since "cvs admin"
6551*86d7f5d3SJohn Marino 	   already printed "RCS file:" and the name.  */
6552*86d7f5d3SJohn Marino 	error (1, 0, "attempt to delete all revisions");
6553*86d7f5d3SJohn Marino     }
6554*86d7f5d3SJohn Marino 
6555*86d7f5d3SJohn Marino     /* The conditionals at this point get really hairy.  Here is the
6556*86d7f5d3SJohn Marino        general idea:
6557*86d7f5d3SJohn Marino 
6558*86d7f5d3SJohn Marino        IF before != NULL and after == NULL
6559*86d7f5d3SJohn Marino          THEN don't check out any revisions, just delete them
6560*86d7f5d3SJohn Marino        IF before == NULL and after != NULL
6561*86d7f5d3SJohn Marino          THEN only check out after's revision, and use it for the new deltatext
6562*86d7f5d3SJohn Marino        ELSE
6563*86d7f5d3SJohn Marino          check out both revisions and diff -n them.  This could use
6564*86d7f5d3SJohn Marino 	 RCS_exec_rcsdiff with some changes, like being able
6565*86d7f5d3SJohn Marino 	 to suppress diagnostic messages and to direct output. */
6566*86d7f5d3SJohn Marino 
6567*86d7f5d3SJohn Marino     if (after != NULL)
6568*86d7f5d3SJohn Marino     {
6569*86d7f5d3SJohn Marino 	char *diffbuf;
6570*86d7f5d3SJohn Marino 	size_t bufsize, len;
6571*86d7f5d3SJohn Marino 
6572*86d7f5d3SJohn Marino #if defined (WOE32) && !defined (__CYGWIN32__)
6573*86d7f5d3SJohn Marino 	/* FIXME: This is an awful kludge, but at least until I have
6574*86d7f5d3SJohn Marino 	   time to work on it a little more and test it, I'd rather
6575*86d7f5d3SJohn Marino 	   give a fatal error than corrupt the file.  I think that we
6576*86d7f5d3SJohn Marino 	   need to use "-kb" and "--binary" and "rb" to get_file
6577*86d7f5d3SJohn Marino 	   (probably can do it always, not just for binary files, if
6578*86d7f5d3SJohn Marino 	   we are consistent between the RCS_checkout and the diff).  */
6579*86d7f5d3SJohn Marino 	{
6580*86d7f5d3SJohn Marino 	    char *expand = RCS_getexpand (rcs);
6581*86d7f5d3SJohn Marino 	    if (expand != NULL && STREQ (expand, "b"))
6582*86d7f5d3SJohn Marino 		error (1, 0,
6583*86d7f5d3SJohn Marino 		   "admin -o not implemented yet for binary on this system");
6584*86d7f5d3SJohn Marino 	}
6585*86d7f5d3SJohn Marino #endif /* WOE32 */
6586*86d7f5d3SJohn Marino 
6587*86d7f5d3SJohn Marino 	afterfile = cvs_temp_name();
6588*86d7f5d3SJohn Marino 	status = RCS_checkout (rcs, NULL, after, NULL, "-ko", afterfile,
6589*86d7f5d3SJohn Marino 			       NULL, NULL);
6590*86d7f5d3SJohn Marino 	if (status > 0)
6591*86d7f5d3SJohn Marino 	    goto delrev_done;
6592*86d7f5d3SJohn Marino 
6593*86d7f5d3SJohn Marino 	if (before == NULL)
6594*86d7f5d3SJohn Marino 	{
6595*86d7f5d3SJohn Marino 	    /* We are deleting revisions from the head of the tree,
6596*86d7f5d3SJohn Marino 	       so must create a new head. */
6597*86d7f5d3SJohn Marino 	    diffbuf = NULL;
6598*86d7f5d3SJohn Marino 	    bufsize = 0;
6599*86d7f5d3SJohn Marino 	    get_file (afterfile, afterfile, "r", &diffbuf, &bufsize, &len);
6600*86d7f5d3SJohn Marino 
6601*86d7f5d3SJohn Marino 	    save_noexec = noexec;
6602*86d7f5d3SJohn Marino 	    noexec = 0;
6603*86d7f5d3SJohn Marino 	    if (unlink_file (afterfile) < 0)
6604*86d7f5d3SJohn Marino 		error (0, errno, "cannot remove %s", afterfile);
6605*86d7f5d3SJohn Marino 	    noexec = save_noexec;
6606*86d7f5d3SJohn Marino 
6607*86d7f5d3SJohn Marino 	    free (afterfile);
6608*86d7f5d3SJohn Marino 	    afterfile = NULL;
6609*86d7f5d3SJohn Marino 
6610*86d7f5d3SJohn Marino 	    free (rcs->head);
6611*86d7f5d3SJohn Marino 	    rcs->head = xstrdup (after);
6612*86d7f5d3SJohn Marino 	}
6613*86d7f5d3SJohn Marino 	else
6614*86d7f5d3SJohn Marino 	{
6615*86d7f5d3SJohn Marino 	    int dargc = 0;
6616*86d7f5d3SJohn Marino 	    size_t darg_allocated = 0;
6617*86d7f5d3SJohn Marino 	    char **dargv = NULL;
6618*86d7f5d3SJohn Marino 
6619*86d7f5d3SJohn Marino 	    beforefile = cvs_temp_name();
6620*86d7f5d3SJohn Marino 	    status = RCS_checkout (rcs, NULL, before, NULL, "-ko", beforefile,
6621*86d7f5d3SJohn Marino 				   NULL, NULL);
6622*86d7f5d3SJohn Marino 	    if (status > 0)
6623*86d7f5d3SJohn Marino 		goto delrev_done;
6624*86d7f5d3SJohn Marino 
6625*86d7f5d3SJohn Marino 	    outfile = cvs_temp_name();
6626*86d7f5d3SJohn Marino 	    run_add_arg_p (&dargc, &darg_allocated, &dargv, "-a");
6627*86d7f5d3SJohn Marino 	    run_add_arg_p (&dargc, &darg_allocated, &dargv, "-n");
6628*86d7f5d3SJohn Marino 	    status = diff_exec (beforefile, afterfile, NULL, NULL,
6629*86d7f5d3SJohn Marino 				dargc, dargv, outfile);
6630*86d7f5d3SJohn Marino 	    run_arg_free_p (dargc, dargv);
6631*86d7f5d3SJohn Marino 	    free (dargv);
6632*86d7f5d3SJohn Marino 
6633*86d7f5d3SJohn Marino 	    if (status == 2)
6634*86d7f5d3SJohn Marino 	    {
6635*86d7f5d3SJohn Marino 		/* Not sure we need this message; will diff_exec already
6636*86d7f5d3SJohn Marino 		   have printed an error?  */
6637*86d7f5d3SJohn Marino 		error (0, 0, "%s: could not diff", rcs->print_path);
6638*86d7f5d3SJohn Marino 		status = 1;
6639*86d7f5d3SJohn Marino 		goto delrev_done;
6640*86d7f5d3SJohn Marino 	    }
6641*86d7f5d3SJohn Marino 
6642*86d7f5d3SJohn Marino 	    diffbuf = NULL;
6643*86d7f5d3SJohn Marino 	    bufsize = 0;
6644*86d7f5d3SJohn Marino 	    get_file (outfile, outfile, "r", &diffbuf, &bufsize, &len);
6645*86d7f5d3SJohn Marino 	}
6646*86d7f5d3SJohn Marino 
6647*86d7f5d3SJohn Marino 	/* Save the new change text in after's delta node. */
6648*86d7f5d3SJohn Marino 	nodep = findnode (rcs->versions, after);
6649*86d7f5d3SJohn Marino 	revp = nodep->data;
6650*86d7f5d3SJohn Marino 
6651*86d7f5d3SJohn Marino 	assert (revp->text == NULL);
6652*86d7f5d3SJohn Marino 
6653*86d7f5d3SJohn Marino 	revp->text = xmalloc (sizeof (Deltatext));
6654*86d7f5d3SJohn Marino 	memset (revp->text, 0, sizeof (Deltatext));
6655*86d7f5d3SJohn Marino 	revp->text->version = xstrdup (revp->version);
6656*86d7f5d3SJohn Marino 	revp->text->text = diffbuf;
6657*86d7f5d3SJohn Marino 	revp->text->len = len;
6658*86d7f5d3SJohn Marino 
6659*86d7f5d3SJohn Marino 	/* If DIFFBUF is NULL, it means that OUTFILE is empty and that
6660*86d7f5d3SJohn Marino 	   there are no differences between the two revisions.  In that
6661*86d7f5d3SJohn Marino 	   case, we want to force RCS_copydeltas to write an empty string
6662*86d7f5d3SJohn Marino 	   for the new change text (leaving the text field set NULL
6663*86d7f5d3SJohn Marino 	   means "preserve the original change text for this delta," so
6664*86d7f5d3SJohn Marino 	   we don't want that). */
6665*86d7f5d3SJohn Marino 	if (revp->text->text == NULL)
6666*86d7f5d3SJohn Marino 	    revp->text->text = xstrdup ("");
6667*86d7f5d3SJohn Marino     }
6668*86d7f5d3SJohn Marino 
6669*86d7f5d3SJohn Marino     /* Walk through the revisions (again) to mark each one as
6670*86d7f5d3SJohn Marino        outdated.  (FIXME: would it be safe to use the `dead' field for
6671*86d7f5d3SJohn Marino        this?  Doubtful.) */
6672*86d7f5d3SJohn Marino     for (next = rev1;
6673*86d7f5d3SJohn Marino 	 next != NULL && (after == NULL || ! STREQ (next, after));
6674*86d7f5d3SJohn Marino 	 next = revp->next)
6675*86d7f5d3SJohn Marino     {
6676*86d7f5d3SJohn Marino 	nodep = findnode (rcs->versions, next);
6677*86d7f5d3SJohn Marino 	revp = nodep->data;
6678*86d7f5d3SJohn Marino 	revp->outdated = 1;
6679*86d7f5d3SJohn Marino     }
6680*86d7f5d3SJohn Marino 
6681*86d7f5d3SJohn Marino     /* Update delta links.  If BEFORE == NULL, we're changing the
6682*86d7f5d3SJohn Marino        head of the tree and don't need to update any `next' links. */
6683*86d7f5d3SJohn Marino     if (before != NULL)
6684*86d7f5d3SJohn Marino     {
6685*86d7f5d3SJohn Marino 	/* If REV1 is the first node on its branch, then BEFORE is its
6686*86d7f5d3SJohn Marino 	   root node (on the trunk) and we have to update its branches
6687*86d7f5d3SJohn Marino 	   list.  Otherwise, BEFORE is on the same branch as AFTER, and
6688*86d7f5d3SJohn Marino 	   we can just change BEFORE's `next' field to point to AFTER.
6689*86d7f5d3SJohn Marino 	   (This should be safe: since findnode manages its lists via
6690*86d7f5d3SJohn Marino 	   the `hashnext' and `hashprev' fields, rather than `next' and
6691*86d7f5d3SJohn Marino 	   `prev', mucking with `next' and `prev' should not corrupt the
6692*86d7f5d3SJohn Marino 	   delta tree's internal structure.  Much. -twp) */
6693*86d7f5d3SJohn Marino 
6694*86d7f5d3SJohn Marino 	if (rev1 == NULL)
6695*86d7f5d3SJohn Marino 	    /* beforep's ->next field already should be equal to after,
6696*86d7f5d3SJohn Marino 	       which I think is always NULL in this case.  */
6697*86d7f5d3SJohn Marino 	    ;
6698*86d7f5d3SJohn Marino 	else if (STREQ (rev1, branchpoint))
6699*86d7f5d3SJohn Marino 	{
6700*86d7f5d3SJohn Marino 	    nodep = findnode (rcs->versions, before);
6701*86d7f5d3SJohn Marino 	    revp = nodep->data;
6702*86d7f5d3SJohn Marino 	    nodep = revp->branches->list->next;
6703*86d7f5d3SJohn Marino 	    while (nodep != revp->branches->list &&
6704*86d7f5d3SJohn Marino 		   ! STREQ (nodep->key, rev1))
6705*86d7f5d3SJohn Marino 		nodep = nodep->next;
6706*86d7f5d3SJohn Marino 	    assert (nodep != revp->branches->list);
6707*86d7f5d3SJohn Marino 	    if (after == NULL)
6708*86d7f5d3SJohn Marino 		delnode (nodep);
6709*86d7f5d3SJohn Marino 	    else
6710*86d7f5d3SJohn Marino 	    {
6711*86d7f5d3SJohn Marino 		free (nodep->key);
6712*86d7f5d3SJohn Marino 		nodep->key = xstrdup (after);
6713*86d7f5d3SJohn Marino 	    }
6714*86d7f5d3SJohn Marino 	}
6715*86d7f5d3SJohn Marino 	else
6716*86d7f5d3SJohn Marino 	{
6717*86d7f5d3SJohn Marino 	    nodep = findnode (rcs->versions, before);
6718*86d7f5d3SJohn Marino 	    beforep = nodep->data;
6719*86d7f5d3SJohn Marino 	    free (beforep->next);
6720*86d7f5d3SJohn Marino 	    beforep->next = xstrdup (after);
6721*86d7f5d3SJohn Marino 	}
6722*86d7f5d3SJohn Marino     }
6723*86d7f5d3SJohn Marino 
6724*86d7f5d3SJohn Marino     status = 0;
6725*86d7f5d3SJohn Marino 
6726*86d7f5d3SJohn Marino  delrev_done:
6727*86d7f5d3SJohn Marino     if (rev1 != NULL)
6728*86d7f5d3SJohn Marino 	free (rev1);
6729*86d7f5d3SJohn Marino     if (rev2 && rev2 != rev1)
6730*86d7f5d3SJohn Marino 	free (rev2);
6731*86d7f5d3SJohn Marino     if (branchpoint != NULL)
6732*86d7f5d3SJohn Marino 	free (branchpoint);
6733*86d7f5d3SJohn Marino     if (before != NULL)
6734*86d7f5d3SJohn Marino 	free (before);
6735*86d7f5d3SJohn Marino     if (after != NULL)
6736*86d7f5d3SJohn Marino 	free (after);
6737*86d7f5d3SJohn Marino 
6738*86d7f5d3SJohn Marino     save_noexec = noexec;
6739*86d7f5d3SJohn Marino     noexec = 0;
6740*86d7f5d3SJohn Marino     if (beforefile != NULL)
6741*86d7f5d3SJohn Marino     {
6742*86d7f5d3SJohn Marino 	if (unlink_file (beforefile) < 0)
6743*86d7f5d3SJohn Marino 	    error (0, errno, "cannot remove %s", beforefile);
6744*86d7f5d3SJohn Marino 	free (beforefile);
6745*86d7f5d3SJohn Marino     }
6746*86d7f5d3SJohn Marino     if (afterfile != NULL)
6747*86d7f5d3SJohn Marino     {
6748*86d7f5d3SJohn Marino 	if (unlink_file (afterfile) < 0)
6749*86d7f5d3SJohn Marino 	    error (0, errno, "cannot remove %s", afterfile);
6750*86d7f5d3SJohn Marino 	free (afterfile);
6751*86d7f5d3SJohn Marino     }
6752*86d7f5d3SJohn Marino     if (outfile != NULL)
6753*86d7f5d3SJohn Marino     {
6754*86d7f5d3SJohn Marino 	if (unlink_file (outfile) < 0)
6755*86d7f5d3SJohn Marino 	    error (0, errno, "cannot remove %s", outfile);
6756*86d7f5d3SJohn Marino 	free (outfile);
6757*86d7f5d3SJohn Marino     }
6758*86d7f5d3SJohn Marino     noexec = save_noexec;
6759*86d7f5d3SJohn Marino 
6760*86d7f5d3SJohn Marino     return status;
6761*86d7f5d3SJohn Marino }
6762*86d7f5d3SJohn Marino 
6763*86d7f5d3SJohn Marino 
6764*86d7f5d3SJohn Marino 
6765*86d7f5d3SJohn Marino /*
6766*86d7f5d3SJohn Marino  * TRUE if there exists a symbolic tag "tag" in file.
6767*86d7f5d3SJohn Marino  */
6768*86d7f5d3SJohn Marino int
RCS_exist_tag(RCSNode * rcs,char * tag)6769*86d7f5d3SJohn Marino RCS_exist_tag (RCSNode *rcs, char *tag)
6770*86d7f5d3SJohn Marino {
6771*86d7f5d3SJohn Marino 
6772*86d7f5d3SJohn Marino     assert (rcs != NULL);
6773*86d7f5d3SJohn Marino 
6774*86d7f5d3SJohn Marino     if (findnode (RCS_symbols (rcs), tag))
6775*86d7f5d3SJohn Marino     return 1;
6776*86d7f5d3SJohn Marino     return 0;
6777*86d7f5d3SJohn Marino 
6778*86d7f5d3SJohn Marino }
6779*86d7f5d3SJohn Marino 
6780*86d7f5d3SJohn Marino 
6781*86d7f5d3SJohn Marino 
6782*86d7f5d3SJohn Marino /*
6783*86d7f5d3SJohn Marino  * TRUE if RCS revision number "rev" exists.
6784*86d7f5d3SJohn Marino  * This includes magic branch revisions, not found in rcs->versions,
6785*86d7f5d3SJohn Marino  * but only in rcs->symbols, requiring a list walk to find them.
6786*86d7f5d3SJohn Marino  * Take advantage of list walk callback function already used by
6787*86d7f5d3SJohn Marino  * RCS_delete_revs, above.
6788*86d7f5d3SJohn Marino  */
6789*86d7f5d3SJohn Marino int
RCS_exist_rev(RCSNode * rcs,char * rev)6790*86d7f5d3SJohn Marino RCS_exist_rev (RCSNode *rcs, char *rev)
6791*86d7f5d3SJohn Marino {
6792*86d7f5d3SJohn Marino 
6793*86d7f5d3SJohn Marino     assert (rcs != NULL);
6794*86d7f5d3SJohn Marino 
6795*86d7f5d3SJohn Marino     if (rcs->flags & PARTIAL)
6796*86d7f5d3SJohn Marino 	RCS_reparsercsfile (rcs, NULL, NULL);
6797*86d7f5d3SJohn Marino 
6798*86d7f5d3SJohn Marino     if (findnode(rcs->versions, rev) != 0)
6799*86d7f5d3SJohn Marino 	return 1;
6800*86d7f5d3SJohn Marino 
6801*86d7f5d3SJohn Marino     if (walklist (RCS_symbols(rcs), findtag, rev) != 0)
6802*86d7f5d3SJohn Marino 	return 1;
6803*86d7f5d3SJohn Marino 
6804*86d7f5d3SJohn Marino     return 0;
6805*86d7f5d3SJohn Marino 
6806*86d7f5d3SJohn Marino }
6807*86d7f5d3SJohn Marino 
6808*86d7f5d3SJohn Marino 
6809*86d7f5d3SJohn Marino 
6810*86d7f5d3SJohn Marino 
6811*86d7f5d3SJohn Marino /* RCS_deltas and friends.  Processing of the deltas in RCS files.  */
6812*86d7f5d3SJohn Marino struct line
6813*86d7f5d3SJohn Marino {
6814*86d7f5d3SJohn Marino     /* Text of this line.  Part of the same malloc'd block as the struct
6815*86d7f5d3SJohn Marino        line itself (we probably should use the "struct hack" (char text[1])
6816*86d7f5d3SJohn Marino        and save ourselves sizeof (char *) bytes).  Does not include \n;
6817*86d7f5d3SJohn Marino        instead has_newline indicates the presence or absence of \n.  */
6818*86d7f5d3SJohn Marino     char *text;
6819*86d7f5d3SJohn Marino     /* Length of this line, not counting \n if has_newline is true.  */
6820*86d7f5d3SJohn Marino     size_t len;
6821*86d7f5d3SJohn Marino     /* Version in which it was introduced.  */
6822*86d7f5d3SJohn Marino     RCSVers *vers;
6823*86d7f5d3SJohn Marino     /* Nonzero if this line ends with \n.  This will always be true
6824*86d7f5d3SJohn Marino        except possibly for the last line.  */
6825*86d7f5d3SJohn Marino     int has_newline;
6826*86d7f5d3SJohn Marino     /* Number of pointers to this struct line.  */
6827*86d7f5d3SJohn Marino     int refcount;
6828*86d7f5d3SJohn Marino };
6829*86d7f5d3SJohn Marino 
6830*86d7f5d3SJohn Marino struct linevector
6831*86d7f5d3SJohn Marino {
6832*86d7f5d3SJohn Marino     /* How many lines in use for this linevector?  */
6833*86d7f5d3SJohn Marino     unsigned int nlines;
6834*86d7f5d3SJohn Marino     /* How many lines allocated for this linevector?  */
6835*86d7f5d3SJohn Marino     unsigned int lines_alloced;
6836*86d7f5d3SJohn Marino     /* Pointer to array containing a pointer to each line.  */
6837*86d7f5d3SJohn Marino     struct line **vector;
6838*86d7f5d3SJohn Marino };
6839*86d7f5d3SJohn Marino 
6840*86d7f5d3SJohn Marino 
6841*86d7f5d3SJohn Marino 
6842*86d7f5d3SJohn Marino /* Initialize *VEC to be a linevector with no lines.  */
6843*86d7f5d3SJohn Marino static void
linevector_init(struct linevector * vec)6844*86d7f5d3SJohn Marino linevector_init (struct linevector *vec)
6845*86d7f5d3SJohn Marino {
6846*86d7f5d3SJohn Marino     vec->lines_alloced = 0;
6847*86d7f5d3SJohn Marino     vec->nlines = 0;
6848*86d7f5d3SJohn Marino     vec->vector = NULL;
6849*86d7f5d3SJohn Marino }
6850*86d7f5d3SJohn Marino 
6851*86d7f5d3SJohn Marino 
6852*86d7f5d3SJohn Marino 
6853*86d7f5d3SJohn Marino /* Given some text TEXT, add each of its lines to VEC before line POS
6854*86d7f5d3SJohn Marino    (where line 0 is the first line).  The last line in TEXT may or may
6855*86d7f5d3SJohn Marino    not be \n terminated.
6856*86d7f5d3SJohn Marino    Set the version for each of the new lines to VERS.  This
6857*86d7f5d3SJohn Marino    function returns non-zero for success.  It returns zero if the line
6858*86d7f5d3SJohn Marino    number is out of range.
6859*86d7f5d3SJohn Marino 
6860*86d7f5d3SJohn Marino    Each of the lines in TEXT are copied to space which is managed with
6861*86d7f5d3SJohn Marino    the linevector (and freed by linevector_free).  So the caller doesn't
6862*86d7f5d3SJohn Marino    need to keep TEXT around after the call to this function.  */
6863*86d7f5d3SJohn Marino static int
linevector_add(struct linevector * vec,const char * text,size_t len,RCSVers * vers,unsigned int pos)6864*86d7f5d3SJohn Marino linevector_add (struct linevector *vec, const char *text, size_t len,
6865*86d7f5d3SJohn Marino 		RCSVers *vers, unsigned int pos)
6866*86d7f5d3SJohn Marino {
6867*86d7f5d3SJohn Marino     const char *textend;
6868*86d7f5d3SJohn Marino     unsigned int i;
6869*86d7f5d3SJohn Marino     unsigned int nnew;
6870*86d7f5d3SJohn Marino     const char *p;
6871*86d7f5d3SJohn Marino     const char *nextline_text;
6872*86d7f5d3SJohn Marino     size_t nextline_len;
6873*86d7f5d3SJohn Marino     int nextline_newline;
6874*86d7f5d3SJohn Marino     struct line *q;
6875*86d7f5d3SJohn Marino 
6876*86d7f5d3SJohn Marino     if (len == 0)
6877*86d7f5d3SJohn Marino 	return 1;
6878*86d7f5d3SJohn Marino 
6879*86d7f5d3SJohn Marino     textend = text + len;
6880*86d7f5d3SJohn Marino 
6881*86d7f5d3SJohn Marino     /* Count the number of lines we will need to add.  */
6882*86d7f5d3SJohn Marino     nnew = 1;
6883*86d7f5d3SJohn Marino     for (p = text; p < textend; ++p)
6884*86d7f5d3SJohn Marino 	if (*p == '\n' && p + 1 < textend)
6885*86d7f5d3SJohn Marino 	    ++nnew;
6886*86d7f5d3SJohn Marino 
6887*86d7f5d3SJohn Marino     /* Expand VEC->VECTOR if needed.  */
6888*86d7f5d3SJohn Marino     if (vec->nlines + nnew >= vec->lines_alloced)
6889*86d7f5d3SJohn Marino     {
6890*86d7f5d3SJohn Marino 	if (vec->lines_alloced == 0)
6891*86d7f5d3SJohn Marino 	    vec->lines_alloced = 10;
6892*86d7f5d3SJohn Marino 	while (vec->nlines + nnew >= vec->lines_alloced)
6893*86d7f5d3SJohn Marino 	    vec->lines_alloced *= 2;
6894*86d7f5d3SJohn Marino 	vec->vector = xnrealloc (vec->vector,
6895*86d7f5d3SJohn Marino 				 vec->lines_alloced, sizeof (*vec->vector));
6896*86d7f5d3SJohn Marino     }
6897*86d7f5d3SJohn Marino 
6898*86d7f5d3SJohn Marino     /* Make room for the new lines in VEC->VECTOR.  */
6899*86d7f5d3SJohn Marino     for (i = vec->nlines + nnew - 1; i >= pos + nnew; --i)
6900*86d7f5d3SJohn Marino 	vec->vector[i] = vec->vector[i - nnew];
6901*86d7f5d3SJohn Marino 
6902*86d7f5d3SJohn Marino     if (pos > vec->nlines)
6903*86d7f5d3SJohn Marino 	return 0;
6904*86d7f5d3SJohn Marino 
6905*86d7f5d3SJohn Marino     /* Actually add the lines, to VEC->VECTOR.  */
6906*86d7f5d3SJohn Marino     i = pos;
6907*86d7f5d3SJohn Marino     nextline_text = text;
6908*86d7f5d3SJohn Marino     nextline_newline = 0;
6909*86d7f5d3SJohn Marino     for (p = text; p < textend; ++p)
6910*86d7f5d3SJohn Marino 	if (*p == '\n')
6911*86d7f5d3SJohn Marino 	{
6912*86d7f5d3SJohn Marino 	    nextline_newline = 1;
6913*86d7f5d3SJohn Marino 	    if (p + 1 == textend)
6914*86d7f5d3SJohn Marino 		/* If there are no characters beyond the last newline, we
6915*86d7f5d3SJohn Marino 		   don't consider it another line.  */
6916*86d7f5d3SJohn Marino 		break;
6917*86d7f5d3SJohn Marino 	    nextline_len = p - nextline_text;
6918*86d7f5d3SJohn Marino 	    q = xmalloc (sizeof (struct line) + nextline_len);
6919*86d7f5d3SJohn Marino 	    q->vers = vers;
6920*86d7f5d3SJohn Marino 	    q->text = (char *)q + sizeof (struct line);
6921*86d7f5d3SJohn Marino 	    q->len = nextline_len;
6922*86d7f5d3SJohn Marino 	    q->has_newline = nextline_newline;
6923*86d7f5d3SJohn Marino 	    q->refcount = 1;
6924*86d7f5d3SJohn Marino 	    memcpy (q->text, nextline_text, nextline_len);
6925*86d7f5d3SJohn Marino 	    vec->vector[i++] = q;
6926*86d7f5d3SJohn Marino 
6927*86d7f5d3SJohn Marino 	    nextline_text = (char *)p + 1;
6928*86d7f5d3SJohn Marino 	    nextline_newline = 0;
6929*86d7f5d3SJohn Marino 	}
6930*86d7f5d3SJohn Marino     nextline_len = p - nextline_text;
6931*86d7f5d3SJohn Marino     q = xmalloc (sizeof (struct line) + nextline_len);
6932*86d7f5d3SJohn Marino     q->vers = vers;
6933*86d7f5d3SJohn Marino     q->text = (char *)q + sizeof (struct line);
6934*86d7f5d3SJohn Marino     q->len = nextline_len;
6935*86d7f5d3SJohn Marino     q->has_newline = nextline_newline;
6936*86d7f5d3SJohn Marino     q->refcount = 1;
6937*86d7f5d3SJohn Marino     memcpy (q->text, nextline_text, nextline_len);
6938*86d7f5d3SJohn Marino     vec->vector[i] = q;
6939*86d7f5d3SJohn Marino 
6940*86d7f5d3SJohn Marino     vec->nlines += nnew;
6941*86d7f5d3SJohn Marino 
6942*86d7f5d3SJohn Marino     return 1;
6943*86d7f5d3SJohn Marino }
6944*86d7f5d3SJohn Marino 
6945*86d7f5d3SJohn Marino 
6946*86d7f5d3SJohn Marino 
6947*86d7f5d3SJohn Marino /* Remove NLINES lines from VEC at position POS (where line 0 is the
6948*86d7f5d3SJohn Marino    first line).  */
6949*86d7f5d3SJohn Marino static void
linevector_delete(struct linevector * vec,unsigned int pos,unsigned int nlines)6950*86d7f5d3SJohn Marino linevector_delete (struct linevector *vec, unsigned int pos,
6951*86d7f5d3SJohn Marino 		   unsigned int nlines)
6952*86d7f5d3SJohn Marino {
6953*86d7f5d3SJohn Marino     unsigned int i;
6954*86d7f5d3SJohn Marino     unsigned int last;
6955*86d7f5d3SJohn Marino 
6956*86d7f5d3SJohn Marino     last = vec->nlines - nlines;
6957*86d7f5d3SJohn Marino     for (i = pos; i < pos + nlines; ++i)
6958*86d7f5d3SJohn Marino     {
6959*86d7f5d3SJohn Marino 	if (--vec->vector[i]->refcount == 0)
6960*86d7f5d3SJohn Marino 	    free (vec->vector[i]);
6961*86d7f5d3SJohn Marino     }
6962*86d7f5d3SJohn Marino     for (i = pos; i < last; ++i)
6963*86d7f5d3SJohn Marino 	vec->vector[i] = vec->vector[i + nlines];
6964*86d7f5d3SJohn Marino     vec->nlines -= nlines;
6965*86d7f5d3SJohn Marino }
6966*86d7f5d3SJohn Marino 
6967*86d7f5d3SJohn Marino 
6968*86d7f5d3SJohn Marino 
6969*86d7f5d3SJohn Marino /* Copy FROM to TO, copying the vectors but not the lines pointed to.  */
6970*86d7f5d3SJohn Marino static void
linevector_copy(struct linevector * to,struct linevector * from)6971*86d7f5d3SJohn Marino linevector_copy (struct linevector *to, struct linevector *from)
6972*86d7f5d3SJohn Marino {
6973*86d7f5d3SJohn Marino     unsigned int ln;
6974*86d7f5d3SJohn Marino 
6975*86d7f5d3SJohn Marino     for (ln = 0; ln < to->nlines; ++ln)
6976*86d7f5d3SJohn Marino     {
6977*86d7f5d3SJohn Marino 	if (--to->vector[ln]->refcount == 0)
6978*86d7f5d3SJohn Marino 	    free (to->vector[ln]);
6979*86d7f5d3SJohn Marino     }
6980*86d7f5d3SJohn Marino     if (from->nlines > to->lines_alloced)
6981*86d7f5d3SJohn Marino     {
6982*86d7f5d3SJohn Marino 	if (to->lines_alloced == 0)
6983*86d7f5d3SJohn Marino 	    to->lines_alloced = 10;
6984*86d7f5d3SJohn Marino 	while (from->nlines > to->lines_alloced)
6985*86d7f5d3SJohn Marino 	    to->lines_alloced *= 2;
6986*86d7f5d3SJohn Marino 	to->vector = xnrealloc (to->vector,
6987*86d7f5d3SJohn Marino 				to->lines_alloced,
6988*86d7f5d3SJohn Marino 				sizeof (*to->vector));
6989*86d7f5d3SJohn Marino     }
6990*86d7f5d3SJohn Marino     memcpy (to->vector, from->vector,
6991*86d7f5d3SJohn Marino 	    xtimes (from->nlines, sizeof (*to->vector)));
6992*86d7f5d3SJohn Marino     to->nlines = from->nlines;
6993*86d7f5d3SJohn Marino     for (ln = 0; ln < to->nlines; ++ln)
6994*86d7f5d3SJohn Marino 	++to->vector[ln]->refcount;
6995*86d7f5d3SJohn Marino }
6996*86d7f5d3SJohn Marino 
6997*86d7f5d3SJohn Marino 
6998*86d7f5d3SJohn Marino 
6999*86d7f5d3SJohn Marino /* Free storage associated with linevector.  */
7000*86d7f5d3SJohn Marino static void
linevector_free(struct linevector * vec)7001*86d7f5d3SJohn Marino linevector_free (struct linevector *vec)
7002*86d7f5d3SJohn Marino {
7003*86d7f5d3SJohn Marino     unsigned int ln;
7004*86d7f5d3SJohn Marino 
7005*86d7f5d3SJohn Marino     if (vec->vector != NULL)
7006*86d7f5d3SJohn Marino     {
7007*86d7f5d3SJohn Marino 	for (ln = 0; ln < vec->nlines; ++ln)
7008*86d7f5d3SJohn Marino 	    if (--vec->vector[ln]->refcount == 0)
7009*86d7f5d3SJohn Marino 		free (vec->vector[ln]);
7010*86d7f5d3SJohn Marino 
7011*86d7f5d3SJohn Marino 	free (vec->vector);
7012*86d7f5d3SJohn Marino     }
7013*86d7f5d3SJohn Marino }
7014*86d7f5d3SJohn Marino 
7015*86d7f5d3SJohn Marino 
7016*86d7f5d3SJohn Marino 
7017*86d7f5d3SJohn Marino /* Given a textual string giving the month (1-12), terminated with any
7018*86d7f5d3SJohn Marino    character not recognized by atoi, return the 3 character name to
7019*86d7f5d3SJohn Marino    print it with.  I do not think it is a good idea to change these
7020*86d7f5d3SJohn Marino    strings based on the locale; they are standard abbreviations (for
7021*86d7f5d3SJohn Marino    example in rfc822 mail messages) which should be widely understood.
7022*86d7f5d3SJohn Marino    Returns a pointer into static readonly storage.  */
7023*86d7f5d3SJohn Marino static const char *
month_printname(const char * month)7024*86d7f5d3SJohn Marino month_printname (const char *month)
7025*86d7f5d3SJohn Marino {
7026*86d7f5d3SJohn Marino     static const char *const months[] =
7027*86d7f5d3SJohn Marino       {"Jan", "Feb", "Mar", "Apr", "May", "Jun",
7028*86d7f5d3SJohn Marino 	 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
7029*86d7f5d3SJohn Marino     int mnum;
7030*86d7f5d3SJohn Marino 
7031*86d7f5d3SJohn Marino     mnum = atoi (month);
7032*86d7f5d3SJohn Marino     if (mnum < 1 || mnum > 12)
7033*86d7f5d3SJohn Marino 	return "???";
7034*86d7f5d3SJohn Marino     return months[mnum - 1];
7035*86d7f5d3SJohn Marino }
7036*86d7f5d3SJohn Marino 
7037*86d7f5d3SJohn Marino 
7038*86d7f5d3SJohn Marino 
7039*86d7f5d3SJohn Marino /* Apply changes to the line vector LINES.  DIFFBUF is a buffer of
7040*86d7f5d3SJohn Marino    length DIFFLEN holding the change text from an RCS file (the output
7041*86d7f5d3SJohn Marino    of diff -n).  NAME is used in error messages.  The VERS field of
7042*86d7f5d3SJohn Marino    any line added is set to ADDVERS.  The VERS field of any line
7043*86d7f5d3SJohn Marino    deleted is set to DELVERS, unless DELVERS is NULL, in which case
7044*86d7f5d3SJohn Marino    the VERS field of deleted lines is unchanged.  The function returns
7045*86d7f5d3SJohn Marino    non-zero if the change text is applied successfully.  It returns
7046*86d7f5d3SJohn Marino    zero if the change text does not appear to apply to LINES (e.g., a
7047*86d7f5d3SJohn Marino    line number is invalid).  If the change text is improperly
7048*86d7f5d3SJohn Marino    formatted (e.g., it is not the output of diff -n), the function
7049*86d7f5d3SJohn Marino    calls error with a status of 1, causing the program to exit.  */
7050*86d7f5d3SJohn Marino static int
apply_rcs_changes(struct linevector * lines,const char * diffbuf,size_t difflen,const char * name,RCSVers * addvers,RCSVers * delvers)7051*86d7f5d3SJohn Marino apply_rcs_changes (struct linevector *lines, const char *diffbuf,
7052*86d7f5d3SJohn Marino 		   size_t difflen, const char *name, RCSVers *addvers,
7053*86d7f5d3SJohn Marino 		   RCSVers *delvers)
7054*86d7f5d3SJohn Marino {
7055*86d7f5d3SJohn Marino     const char *p;
7056*86d7f5d3SJohn Marino     const char *q;
7057*86d7f5d3SJohn Marino     int op;
7058*86d7f5d3SJohn Marino     /* The RCS format throws us for a loop in that the deltafrags (if
7059*86d7f5d3SJohn Marino        we define a deltafrag as an add or a delete) need to be applied
7060*86d7f5d3SJohn Marino        in reverse order.  So we stick them into a linked list.  */
7061*86d7f5d3SJohn Marino     struct deltafrag {
7062*86d7f5d3SJohn Marino 	enum {FRAG_ADD, FRAG_DELETE} type;
7063*86d7f5d3SJohn Marino 	unsigned long pos;
7064*86d7f5d3SJohn Marino 	unsigned long nlines;
7065*86d7f5d3SJohn Marino 	const char *new_lines;
7066*86d7f5d3SJohn Marino 	size_t len;
7067*86d7f5d3SJohn Marino 	struct deltafrag *next;
7068*86d7f5d3SJohn Marino     };
7069*86d7f5d3SJohn Marino     struct deltafrag *dfhead;
7070*86d7f5d3SJohn Marino     struct deltafrag *df;
7071*86d7f5d3SJohn Marino     int err;
7072*86d7f5d3SJohn Marino 
7073*86d7f5d3SJohn Marino     dfhead = NULL;
7074*86d7f5d3SJohn Marino     for (p = diffbuf; p != NULL && p < diffbuf + difflen; )
7075*86d7f5d3SJohn Marino     {
7076*86d7f5d3SJohn Marino 	op = *p++;
7077*86d7f5d3SJohn Marino 	if (op != 'a' && op != 'd')
7078*86d7f5d3SJohn Marino 	    /* Can't just skip over the deltafrag, because the value
7079*86d7f5d3SJohn Marino 	       of op determines the syntax.  */
7080*86d7f5d3SJohn Marino 	    error (1, 0, "unrecognized operation '\\x%x' in %s",
7081*86d7f5d3SJohn Marino 		   op, name);
7082*86d7f5d3SJohn Marino 	df = xmalloc (sizeof (struct deltafrag));
7083*86d7f5d3SJohn Marino 	df->next = dfhead;
7084*86d7f5d3SJohn Marino 	dfhead = df;
7085*86d7f5d3SJohn Marino 	df->pos = strtoul (p, (char **) &q, 10);
7086*86d7f5d3SJohn Marino 
7087*86d7f5d3SJohn Marino 	if (p == q)
7088*86d7f5d3SJohn Marino 	    error (1, 0, "number expected in %s", name);
7089*86d7f5d3SJohn Marino 	p = q;
7090*86d7f5d3SJohn Marino 	if (*p++ != ' ')
7091*86d7f5d3SJohn Marino 	    error (1, 0, "space expected in %s", name);
7092*86d7f5d3SJohn Marino 	df->nlines = strtoul (p, (char **) &q, 10);
7093*86d7f5d3SJohn Marino 	if (p == q)
7094*86d7f5d3SJohn Marino 	    error (1, 0, "number expected in %s", name);
7095*86d7f5d3SJohn Marino 	p = q;
7096*86d7f5d3SJohn Marino 	if (*p++ != '\012')
7097*86d7f5d3SJohn Marino 	    error (1, 0, "linefeed expected in %s", name);
7098*86d7f5d3SJohn Marino 
7099*86d7f5d3SJohn Marino 	if (op == 'a')
7100*86d7f5d3SJohn Marino 	{
7101*86d7f5d3SJohn Marino 	    unsigned int i;
7102*86d7f5d3SJohn Marino 
7103*86d7f5d3SJohn Marino 	    df->type = FRAG_ADD;
7104*86d7f5d3SJohn Marino 	    i = df->nlines;
7105*86d7f5d3SJohn Marino 	    /* The text we want is the number of lines specified, or
7106*86d7f5d3SJohn Marino 	       until the end of the value, whichever comes first (it
7107*86d7f5d3SJohn Marino 	       will be the former except in the case where we are
7108*86d7f5d3SJohn Marino 	       adding a line which does not end in newline).  */
7109*86d7f5d3SJohn Marino 	    for (q = p; i != 0; ++q)
7110*86d7f5d3SJohn Marino 		if (*q == '\n')
7111*86d7f5d3SJohn Marino 		    --i;
7112*86d7f5d3SJohn Marino 		else if (q == diffbuf + difflen)
7113*86d7f5d3SJohn Marino 		{
7114*86d7f5d3SJohn Marino 		    if (i != 1)
7115*86d7f5d3SJohn Marino 			error (1, 0, "premature end of change in %s", name);
7116*86d7f5d3SJohn Marino 		    else
7117*86d7f5d3SJohn Marino 			break;
7118*86d7f5d3SJohn Marino 		}
7119*86d7f5d3SJohn Marino 
7120*86d7f5d3SJohn Marino 	    /* Stash away a pointer to the text we are adding.  */
7121*86d7f5d3SJohn Marino 	    df->new_lines = p;
7122*86d7f5d3SJohn Marino 	    df->len = q - p;
7123*86d7f5d3SJohn Marino 
7124*86d7f5d3SJohn Marino 	    p = q;
7125*86d7f5d3SJohn Marino 	}
7126*86d7f5d3SJohn Marino 	else
7127*86d7f5d3SJohn Marino 	{
7128*86d7f5d3SJohn Marino 	    /* Correct for the fact that line numbers in RCS files
7129*86d7f5d3SJohn Marino 	       start with 1.  */
7130*86d7f5d3SJohn Marino 	    --df->pos;
7131*86d7f5d3SJohn Marino 
7132*86d7f5d3SJohn Marino 	    assert (op == 'd');
7133*86d7f5d3SJohn Marino 	    df->type = FRAG_DELETE;
7134*86d7f5d3SJohn Marino 	}
7135*86d7f5d3SJohn Marino     }
7136*86d7f5d3SJohn Marino 
7137*86d7f5d3SJohn Marino     err = 0;
7138*86d7f5d3SJohn Marino     for (df = dfhead; df != NULL;)
7139*86d7f5d3SJohn Marino     {
7140*86d7f5d3SJohn Marino 	unsigned int ln;
7141*86d7f5d3SJohn Marino 
7142*86d7f5d3SJohn Marino 	/* Once an error is encountered, just free the rest of the list and
7143*86d7f5d3SJohn Marino 	 * return.
7144*86d7f5d3SJohn Marino 	 */
7145*86d7f5d3SJohn Marino 	if (!err)
7146*86d7f5d3SJohn Marino 	    switch (df->type)
7147*86d7f5d3SJohn Marino 	    {
7148*86d7f5d3SJohn Marino 	    case FRAG_ADD:
7149*86d7f5d3SJohn Marino 		if (! linevector_add (lines, df->new_lines, df->len, addvers,
7150*86d7f5d3SJohn Marino 				      df->pos))
7151*86d7f5d3SJohn Marino 		    err = 1;
7152*86d7f5d3SJohn Marino 		break;
7153*86d7f5d3SJohn Marino 	    case FRAG_DELETE:
7154*86d7f5d3SJohn Marino 		if (df->pos > lines->nlines
7155*86d7f5d3SJohn Marino 		    || df->pos + df->nlines > lines->nlines)
7156*86d7f5d3SJohn Marino 		    return 0;
7157*86d7f5d3SJohn Marino 		if (delvers != NULL)
7158*86d7f5d3SJohn Marino 		    for (ln = df->pos; ln < df->pos + df->nlines; ++ln)
7159*86d7f5d3SJohn Marino 			lines->vector[ln]->vers = delvers;
7160*86d7f5d3SJohn Marino 		linevector_delete (lines, df->pos, df->nlines);
7161*86d7f5d3SJohn Marino 		break;
7162*86d7f5d3SJohn Marino 	    }
7163*86d7f5d3SJohn Marino 
7164*86d7f5d3SJohn Marino 	df = df->next;
7165*86d7f5d3SJohn Marino 	free (dfhead);
7166*86d7f5d3SJohn Marino 	dfhead = df;
7167*86d7f5d3SJohn Marino     }
7168*86d7f5d3SJohn Marino 
7169*86d7f5d3SJohn Marino     return !err;
7170*86d7f5d3SJohn Marino }
7171*86d7f5d3SJohn Marino 
7172*86d7f5d3SJohn Marino 
7173*86d7f5d3SJohn Marino 
7174*86d7f5d3SJohn Marino /* Apply an RCS change text to a buffer.  The function name starts
7175*86d7f5d3SJohn Marino    with rcs rather than RCS because this does not take an RCSNode
7176*86d7f5d3SJohn Marino    argument.  NAME is used in error messages.  TEXTBUF is the text
7177*86d7f5d3SJohn Marino    buffer to change, and TEXTLEN is the size.  DIFFBUF and DIFFLEN are
7178*86d7f5d3SJohn Marino    the change buffer and size.  The new buffer is returned in *RETBUF
7179*86d7f5d3SJohn Marino    and *RETLEN.  The new buffer is allocated by xmalloc.
7180*86d7f5d3SJohn Marino 
7181*86d7f5d3SJohn Marino    Return 1 for success.  On failure, call error and return 0.  */
7182*86d7f5d3SJohn Marino int
rcs_change_text(const char * name,char * textbuf,size_t textlen,const char * diffbuf,size_t difflen,char ** retbuf,size_t * retlen)7183*86d7f5d3SJohn Marino rcs_change_text (const char *name, char *textbuf, size_t textlen,
7184*86d7f5d3SJohn Marino 		 const char *diffbuf, size_t difflen, char **retbuf,
7185*86d7f5d3SJohn Marino 		 size_t *retlen)
7186*86d7f5d3SJohn Marino {
7187*86d7f5d3SJohn Marino     struct linevector lines;
7188*86d7f5d3SJohn Marino     int ret;
7189*86d7f5d3SJohn Marino 
7190*86d7f5d3SJohn Marino     *retbuf = NULL;
7191*86d7f5d3SJohn Marino     *retlen = 0;
7192*86d7f5d3SJohn Marino 
7193*86d7f5d3SJohn Marino     linevector_init (&lines);
7194*86d7f5d3SJohn Marino 
7195*86d7f5d3SJohn Marino     if (! linevector_add (&lines, textbuf, textlen, NULL, 0))
7196*86d7f5d3SJohn Marino 	error (1, 0, "cannot initialize line vector");
7197*86d7f5d3SJohn Marino 
7198*86d7f5d3SJohn Marino     if (! apply_rcs_changes (&lines, diffbuf, difflen, name, NULL, NULL))
7199*86d7f5d3SJohn Marino     {
7200*86d7f5d3SJohn Marino 	error (0, 0, "invalid change text in %s", name);
7201*86d7f5d3SJohn Marino 	ret = 0;
7202*86d7f5d3SJohn Marino     }
7203*86d7f5d3SJohn Marino     else
7204*86d7f5d3SJohn Marino     {
7205*86d7f5d3SJohn Marino 	char *p;
7206*86d7f5d3SJohn Marino 	size_t n;
7207*86d7f5d3SJohn Marino 	unsigned int ln;
7208*86d7f5d3SJohn Marino 
7209*86d7f5d3SJohn Marino 	n = 0;
7210*86d7f5d3SJohn Marino 	for (ln = 0; ln < lines.nlines; ++ln)
7211*86d7f5d3SJohn Marino 	    /* 1 for \n */
7212*86d7f5d3SJohn Marino 	    n += lines.vector[ln]->len + 1;
7213*86d7f5d3SJohn Marino 
7214*86d7f5d3SJohn Marino 	p = xmalloc (n);
7215*86d7f5d3SJohn Marino 	*retbuf = p;
7216*86d7f5d3SJohn Marino 
7217*86d7f5d3SJohn Marino 	for (ln = 0; ln < lines.nlines; ++ln)
7218*86d7f5d3SJohn Marino 	{
7219*86d7f5d3SJohn Marino 	    memcpy (p, lines.vector[ln]->text, lines.vector[ln]->len);
7220*86d7f5d3SJohn Marino 	    p += lines.vector[ln]->len;
7221*86d7f5d3SJohn Marino 	    if (lines.vector[ln]->has_newline)
7222*86d7f5d3SJohn Marino 		*p++ = '\n';
7223*86d7f5d3SJohn Marino 	}
7224*86d7f5d3SJohn Marino 
7225*86d7f5d3SJohn Marino 	*retlen = p - *retbuf;
7226*86d7f5d3SJohn Marino 	assert (*retlen <= n);
7227*86d7f5d3SJohn Marino 
7228*86d7f5d3SJohn Marino 	ret = 1;
7229*86d7f5d3SJohn Marino     }
7230*86d7f5d3SJohn Marino 
7231*86d7f5d3SJohn Marino     linevector_free (&lines);
7232*86d7f5d3SJohn Marino 
7233*86d7f5d3SJohn Marino     return ret;
7234*86d7f5d3SJohn Marino }
7235*86d7f5d3SJohn Marino 
7236*86d7f5d3SJohn Marino 
7237*86d7f5d3SJohn Marino 
7238*86d7f5d3SJohn Marino /* Walk the deltas in RCS to get to revision VERSION.
7239*86d7f5d3SJohn Marino 
7240*86d7f5d3SJohn Marino    If OP is RCS_ANNOTATE, then write annotations using cvs_output.
7241*86d7f5d3SJohn Marino 
7242*86d7f5d3SJohn Marino    If OP is RCS_FETCH, then put the contents of VERSION into a
7243*86d7f5d3SJohn Marino    newly-malloc'd array and put a pointer to it in *TEXT.  Each line
7244*86d7f5d3SJohn Marino    is \n terminated; the caller is responsible for converting text
7245*86d7f5d3SJohn Marino    files if desired.  The total length is put in *LEN.
7246*86d7f5d3SJohn Marino 
7247*86d7f5d3SJohn Marino    If FP is non-NULL, it should be a file descriptor open to the file
7248*86d7f5d3SJohn Marino    RCS with file position pointing to the deltas.  We close the file
7249*86d7f5d3SJohn Marino    when we are done.
7250*86d7f5d3SJohn Marino 
7251*86d7f5d3SJohn Marino    If LOG is non-NULL, then *LOG is set to the log message of VERSION,
7252*86d7f5d3SJohn Marino    and *LOGLEN is set to the length of the log message.
7253*86d7f5d3SJohn Marino 
7254*86d7f5d3SJohn Marino    On error, give a fatal error.  */
7255*86d7f5d3SJohn Marino void
RCS_deltas(RCSNode * rcs,FILE * fp,struct rcsbuffer * rcsbuf,const char * version,enum rcs_delta_op op,char ** text,size_t * len,char ** log,size_t * loglen)7256*86d7f5d3SJohn Marino RCS_deltas (RCSNode *rcs, FILE *fp, struct rcsbuffer *rcsbuf,
7257*86d7f5d3SJohn Marino             const char *version, enum rcs_delta_op op, char **text,
7258*86d7f5d3SJohn Marino             size_t *len, char **log, size_t *loglen)
7259*86d7f5d3SJohn Marino {
7260*86d7f5d3SJohn Marino     struct rcsbuffer rcsbuf_local;
7261*86d7f5d3SJohn Marino     char *branchversion;
7262*86d7f5d3SJohn Marino     char *cpversion;
7263*86d7f5d3SJohn Marino     char *key;
7264*86d7f5d3SJohn Marino     char *value;
7265*86d7f5d3SJohn Marino     size_t vallen;
7266*86d7f5d3SJohn Marino     RCSVers *vers;
7267*86d7f5d3SJohn Marino     RCSVers *prev_vers;
7268*86d7f5d3SJohn Marino     RCSVers *trunk_vers;
7269*86d7f5d3SJohn Marino     RCSVers *top_vers;
7270*86d7f5d3SJohn Marino     char *next;
7271*86d7f5d3SJohn Marino     int ishead, isnext, isversion, onbranch;
7272*86d7f5d3SJohn Marino     Node *node;
7273*86d7f5d3SJohn Marino     struct linevector headlines;
7274*86d7f5d3SJohn Marino     struct linevector curlines;
7275*86d7f5d3SJohn Marino     struct linevector trunklines;
7276*86d7f5d3SJohn Marino     int foundhead;
7277*86d7f5d3SJohn Marino     int backwards;
7278*86d7f5d3SJohn Marino 
7279*86d7f5d3SJohn Marino     assert (version);
7280*86d7f5d3SJohn Marino 
7281*86d7f5d3SJohn Marino     if (fp == NULL)
7282*86d7f5d3SJohn Marino     {
7283*86d7f5d3SJohn Marino 	rcsbuf_cache_open (rcs, rcs->delta_pos, &fp, &rcsbuf_local);
7284*86d7f5d3SJohn Marino 	rcsbuf = &rcsbuf_local;
7285*86d7f5d3SJohn Marino     }
7286*86d7f5d3SJohn Marino 
7287*86d7f5d3SJohn Marino    if (log) *log = NULL;
7288*86d7f5d3SJohn Marino 
7289*86d7f5d3SJohn Marino     ishead = 1;
7290*86d7f5d3SJohn Marino     vers = NULL;
7291*86d7f5d3SJohn Marino     prev_vers = NULL;
7292*86d7f5d3SJohn Marino     trunk_vers = NULL;
7293*86d7f5d3SJohn Marino     top_vers = NULL;
7294*86d7f5d3SJohn Marino     next = NULL;
7295*86d7f5d3SJohn Marino     onbranch = 0;
7296*86d7f5d3SJohn Marino     foundhead = 0;
7297*86d7f5d3SJohn Marino     backwards = 0;
7298*86d7f5d3SJohn Marino 
7299*86d7f5d3SJohn Marino     if (op == RCS_ANNOTATE_BACKWARDS) {
7300*86d7f5d3SJohn Marino 	backwards = 1;
7301*86d7f5d3SJohn Marino 	op = RCS_ANNOTATE;
7302*86d7f5d3SJohn Marino     }
7303*86d7f5d3SJohn Marino 
7304*86d7f5d3SJohn Marino     linevector_init (&curlines);
7305*86d7f5d3SJohn Marino     linevector_init (&headlines);
7306*86d7f5d3SJohn Marino     linevector_init (&trunklines);
7307*86d7f5d3SJohn Marino 
7308*86d7f5d3SJohn Marino     /* We set BRANCHVERSION to the version we are currently looking
7309*86d7f5d3SJohn Marino        for.  Initially, this is the version on the trunk from which
7310*86d7f5d3SJohn Marino        VERSION branches off.  If VERSION is not a branch, then
7311*86d7f5d3SJohn Marino        BRANCHVERSION is just VERSION.  */
7312*86d7f5d3SJohn Marino     branchversion = xstrdup (version);
7313*86d7f5d3SJohn Marino     cpversion = strchr (branchversion, '.');
7314*86d7f5d3SJohn Marino     if (cpversion != NULL)
7315*86d7f5d3SJohn Marino         cpversion = strchr (cpversion + 1, '.');
7316*86d7f5d3SJohn Marino     if (cpversion != NULL)
7317*86d7f5d3SJohn Marino         *cpversion = '\0';
7318*86d7f5d3SJohn Marino 
7319*86d7f5d3SJohn Marino     do {
7320*86d7f5d3SJohn Marino 	if (! rcsbuf_getrevnum (rcsbuf, &key))
7321*86d7f5d3SJohn Marino 	    error (1, 0, "unexpected EOF reading RCS file %s", rcs->print_path);
7322*86d7f5d3SJohn Marino 
7323*86d7f5d3SJohn Marino 	if (next != NULL && ! STREQ (next, key))
7324*86d7f5d3SJohn Marino 	{
7325*86d7f5d3SJohn Marino 	    /* This is not the next version we need.  It is a branch
7326*86d7f5d3SJohn Marino                version which we want to ignore.  */
7327*86d7f5d3SJohn Marino 	    isnext = 0;
7328*86d7f5d3SJohn Marino 	    isversion = 0;
7329*86d7f5d3SJohn Marino 	}
7330*86d7f5d3SJohn Marino 	else
7331*86d7f5d3SJohn Marino 	{
7332*86d7f5d3SJohn Marino 	    isnext = 1;
7333*86d7f5d3SJohn Marino 
7334*86d7f5d3SJohn Marino 	    /* look up the revision */
7335*86d7f5d3SJohn Marino 	    node = findnode (rcs->versions, key);
7336*86d7f5d3SJohn Marino 	    if (node == NULL)
7337*86d7f5d3SJohn Marino 	        error (1, 0,
7338*86d7f5d3SJohn Marino 		       "mismatch in rcs file %s between deltas and deltatexts (%s)",
7339*86d7f5d3SJohn Marino 		       rcs->print_path, key);
7340*86d7f5d3SJohn Marino 
7341*86d7f5d3SJohn Marino 	    /* Stash the previous version.  */
7342*86d7f5d3SJohn Marino 	    prev_vers = vers;
7343*86d7f5d3SJohn Marino 
7344*86d7f5d3SJohn Marino 	    vers = node->data;
7345*86d7f5d3SJohn Marino 	    next = vers->next;
7346*86d7f5d3SJohn Marino 
7347*86d7f5d3SJohn Marino 	    /* The top version is either HEAD or
7348*86d7f5d3SJohn Marino 	       the last version on the branch.  */
7349*86d7f5d3SJohn Marino 	    if (top_vers == NULL || onbranch && backwards)
7350*86d7f5d3SJohn Marino 		top_vers = vers;
7351*86d7f5d3SJohn Marino 
7352*86d7f5d3SJohn Marino 	    /* Compare key and trunkversion now, because key points to
7353*86d7f5d3SJohn Marino 	       storage controlled by rcsbuf_getkey.  */
7354*86d7f5d3SJohn Marino 	    if (STREQ (branchversion, key))
7355*86d7f5d3SJohn Marino 	        isversion = 1;
7356*86d7f5d3SJohn Marino 	    else
7357*86d7f5d3SJohn Marino 	        isversion = 0;
7358*86d7f5d3SJohn Marino 
7359*86d7f5d3SJohn Marino 	    if (backwards && STREQ (version, key)) {
7360*86d7f5d3SJohn Marino 		if (onbranch) {
7361*86d7f5d3SJohn Marino 		    unsigned int ln;
7362*86d7f5d3SJohn Marino 
7363*86d7f5d3SJohn Marino 		    for (ln = 0; ln < curlines.nlines; ++ln)
7364*86d7f5d3SJohn Marino 			curlines.vector[ln]->vers = NULL;
7365*86d7f5d3SJohn Marino 		} else {
7366*86d7f5d3SJohn Marino 		    foundhead = 1;
7367*86d7f5d3SJohn Marino 		    linevector_copy (&headlines, &curlines);
7368*86d7f5d3SJohn Marino 		    break;
7369*86d7f5d3SJohn Marino 		}
7370*86d7f5d3SJohn Marino 	    }
7371*86d7f5d3SJohn Marino 	}
7372*86d7f5d3SJohn Marino 
7373*86d7f5d3SJohn Marino 	while (1)
7374*86d7f5d3SJohn Marino 	{
7375*86d7f5d3SJohn Marino 	    if (! rcsbuf_getkey (rcsbuf, &key, &value))
7376*86d7f5d3SJohn Marino 		error (1, 0, "%s does not appear to be a valid rcs file",
7377*86d7f5d3SJohn Marino 		       rcs->print_path);
7378*86d7f5d3SJohn Marino 
7379*86d7f5d3SJohn Marino 	    if (log != NULL
7380*86d7f5d3SJohn Marino 		&& isversion
7381*86d7f5d3SJohn Marino 		&& STREQ (key, "log")
7382*86d7f5d3SJohn Marino 		&& STREQ (branchversion, version))
7383*86d7f5d3SJohn Marino 	    {
7384*86d7f5d3SJohn Marino 		if (*log != NULL)
7385*86d7f5d3SJohn Marino 		{
7386*86d7f5d3SJohn Marino 		    error (0, 0, "Duplicate `log' keyword in RCS file (`%s').",
7387*86d7f5d3SJohn Marino 		           rcs->print_path);
7388*86d7f5d3SJohn Marino 		    free (*log);
7389*86d7f5d3SJohn Marino 		}
7390*86d7f5d3SJohn Marino 		*log = rcsbuf_valcopy (rcsbuf, value, 0, loglen);
7391*86d7f5d3SJohn Marino 	    }
7392*86d7f5d3SJohn Marino 
7393*86d7f5d3SJohn Marino 	    if (STREQ (key, "text"))
7394*86d7f5d3SJohn Marino 	    {
7395*86d7f5d3SJohn Marino 		rcsbuf_valpolish (rcsbuf, value, 0, &vallen);
7396*86d7f5d3SJohn Marino 		if (ishead)
7397*86d7f5d3SJohn Marino 		{
7398*86d7f5d3SJohn Marino 		    if (! linevector_add (&curlines, value, vallen,
7399*86d7f5d3SJohn Marino 					  backwards ? vers : NULL, 0))
7400*86d7f5d3SJohn Marino 			error (1, 0, "invalid rcs file %s", rcs->print_path);
7401*86d7f5d3SJohn Marino 
7402*86d7f5d3SJohn Marino 		    ishead = 0;
7403*86d7f5d3SJohn Marino 		}
7404*86d7f5d3SJohn Marino 		else if (isnext)
7405*86d7f5d3SJohn Marino 		{
7406*86d7f5d3SJohn Marino 		    RCSVers *addv, *delv;
7407*86d7f5d3SJohn Marino 
7408*86d7f5d3SJohn Marino 		    if (backwards) {
7409*86d7f5d3SJohn Marino 			if (onbranch) {
7410*86d7f5d3SJohn Marino 			    addv = NULL;
7411*86d7f5d3SJohn Marino 			    delv = vers;
7412*86d7f5d3SJohn Marino 			} else {
7413*86d7f5d3SJohn Marino 			    addv = prev_vers;
7414*86d7f5d3SJohn Marino 			    delv = NULL;
7415*86d7f5d3SJohn Marino 			}
7416*86d7f5d3SJohn Marino 		    } else {
7417*86d7f5d3SJohn Marino 			if (onbranch) {
7418*86d7f5d3SJohn Marino 			    addv = vers;
7419*86d7f5d3SJohn Marino 			    delv = NULL;
7420*86d7f5d3SJohn Marino 			} else {
7421*86d7f5d3SJohn Marino 			    addv = NULL;
7422*86d7f5d3SJohn Marino 			    delv = prev_vers;
7423*86d7f5d3SJohn Marino 			}
7424*86d7f5d3SJohn Marino 		    }
7425*86d7f5d3SJohn Marino 		    if (! apply_rcs_changes (&curlines, value, vallen,
7426*86d7f5d3SJohn Marino 					     rcs->path,
7427*86d7f5d3SJohn Marino 					     addv, delv))
7428*86d7f5d3SJohn Marino 			error (1, 0, "invalid change text in %s", rcs->print_path);
7429*86d7f5d3SJohn Marino 		}
7430*86d7f5d3SJohn Marino 		break;
7431*86d7f5d3SJohn Marino 	    }
7432*86d7f5d3SJohn Marino 	}
7433*86d7f5d3SJohn Marino 
7434*86d7f5d3SJohn Marino 	if (isversion)
7435*86d7f5d3SJohn Marino 	{
7436*86d7f5d3SJohn Marino 	    /* This is either the version we want, or it is the
7437*86d7f5d3SJohn Marino                branchpoint to the version we want.  */
7438*86d7f5d3SJohn Marino 	    if (STREQ (branchversion, version))
7439*86d7f5d3SJohn Marino 	    {
7440*86d7f5d3SJohn Marino 	        /* This is the version we want.  */
7441*86d7f5d3SJohn Marino 		linevector_copy (&headlines, &curlines);
7442*86d7f5d3SJohn Marino 		foundhead = 1;
7443*86d7f5d3SJohn Marino 		/* If we are annotating backwards, we have to
7444*86d7f5d3SJohn Marino 		   continue tracking when we're tracking a branch.  */
7445*86d7f5d3SJohn Marino 		if (onbranch && !backwards)
7446*86d7f5d3SJohn Marino 		{
7447*86d7f5d3SJohn Marino 		    /* We have found this version by tracking up a
7448*86d7f5d3SJohn Marino                        branch.  Restore back to the lines we saved
7449*86d7f5d3SJohn Marino                        when we left the trunk, and continue tracking
7450*86d7f5d3SJohn Marino                        down the trunk.  */
7451*86d7f5d3SJohn Marino 		    onbranch = 0;
7452*86d7f5d3SJohn Marino 		    vers = trunk_vers;
7453*86d7f5d3SJohn Marino 		    next = vers->next;
7454*86d7f5d3SJohn Marino 		    linevector_copy (&curlines, &trunklines);
7455*86d7f5d3SJohn Marino 		}
7456*86d7f5d3SJohn Marino 	    }
7457*86d7f5d3SJohn Marino 	    else
7458*86d7f5d3SJohn Marino 	    {
7459*86d7f5d3SJohn Marino 	        Node *p;
7460*86d7f5d3SJohn Marino 
7461*86d7f5d3SJohn Marino 	        /* We need to look up the branch.  */
7462*86d7f5d3SJohn Marino 	        onbranch = 1;
7463*86d7f5d3SJohn Marino 
7464*86d7f5d3SJohn Marino 		if (numdots (branchversion) < 2)
7465*86d7f5d3SJohn Marino 		{
7466*86d7f5d3SJohn Marino 		    unsigned int ln;
7467*86d7f5d3SJohn Marino 
7468*86d7f5d3SJohn Marino 		    /* We are leaving the trunk; save the current
7469*86d7f5d3SJohn Marino                        lines so that we can restore them when we
7470*86d7f5d3SJohn Marino                        continue tracking down the trunk.  */
7471*86d7f5d3SJohn Marino 		    trunk_vers = vers;
7472*86d7f5d3SJohn Marino 		    linevector_copy (&trunklines, &curlines);
7473*86d7f5d3SJohn Marino 
7474*86d7f5d3SJohn Marino 		    /* Reset the version information we have
7475*86d7f5d3SJohn Marino                        accumulated so far.  It only applies to the
7476*86d7f5d3SJohn Marino                        changes from the head to this version.  */
7477*86d7f5d3SJohn Marino 		    for (ln = 0; ln < curlines.nlines; ++ln)
7478*86d7f5d3SJohn Marino 		        curlines.vector[ln]->vers = NULL;
7479*86d7f5d3SJohn Marino 		}
7480*86d7f5d3SJohn Marino 
7481*86d7f5d3SJohn Marino 		/* The next version we want is the entry on
7482*86d7f5d3SJohn Marino                    VERS->branches which matches this branch.  For
7483*86d7f5d3SJohn Marino                    example, suppose VERSION is 1.21.4.3 and
7484*86d7f5d3SJohn Marino                    BRANCHVERSION was 1.21.  Then we look for an entry
7485*86d7f5d3SJohn Marino                    starting with "1.21.4" and we'll put it (probably
7486*86d7f5d3SJohn Marino                    1.21.4.1) in NEXT.  We'll advance BRANCHVERSION by
7487*86d7f5d3SJohn Marino                    two dots (in this example, to 1.21.4.3).  */
7488*86d7f5d3SJohn Marino 
7489*86d7f5d3SJohn Marino 		if (vers->branches == NULL)
7490*86d7f5d3SJohn Marino 		    error (1, 0, "missing expected branches in %s",
7491*86d7f5d3SJohn Marino 			   rcs->print_path);
7492*86d7f5d3SJohn Marino 		if (!cpversion)
7493*86d7f5d3SJohn Marino 		    error (1, 0, "Invalid revision number in `%s'.",
7494*86d7f5d3SJohn Marino 		           rcs->print_path);
7495*86d7f5d3SJohn Marino 		*cpversion = '.';
7496*86d7f5d3SJohn Marino 		++cpversion;
7497*86d7f5d3SJohn Marino 		cpversion = strchr (cpversion, '.');
7498*86d7f5d3SJohn Marino 		if (cpversion == NULL)
7499*86d7f5d3SJohn Marino 		    error (1, 0, "version number confusion in %s",
7500*86d7f5d3SJohn Marino 			   rcs->print_path);
7501*86d7f5d3SJohn Marino 		for (p = vers->branches->list->next;
7502*86d7f5d3SJohn Marino 		     p != vers->branches->list;
7503*86d7f5d3SJohn Marino 		     p = p->next)
7504*86d7f5d3SJohn Marino 		    if (strncmp (p->key, branchversion,
7505*86d7f5d3SJohn Marino 				 cpversion - branchversion) == 0)
7506*86d7f5d3SJohn Marino 			break;
7507*86d7f5d3SJohn Marino 		if (p == vers->branches->list)
7508*86d7f5d3SJohn Marino 		    error (1, 0, "missing expected branch in %s",
7509*86d7f5d3SJohn Marino 			   rcs->print_path);
7510*86d7f5d3SJohn Marino 
7511*86d7f5d3SJohn Marino 		next = p->key;
7512*86d7f5d3SJohn Marino 
7513*86d7f5d3SJohn Marino 		cpversion = strchr (cpversion + 1, '.');
7514*86d7f5d3SJohn Marino 		if (cpversion != NULL)
7515*86d7f5d3SJohn Marino 		    *cpversion = '\0';
7516*86d7f5d3SJohn Marino 	    }
7517*86d7f5d3SJohn Marino 	}
7518*86d7f5d3SJohn Marino 	if (op == RCS_FETCH && foundhead)
7519*86d7f5d3SJohn Marino 	    break;
7520*86d7f5d3SJohn Marino     } while (next != NULL);
7521*86d7f5d3SJohn Marino 
7522*86d7f5d3SJohn Marino     free (branchversion);
7523*86d7f5d3SJohn Marino 
7524*86d7f5d3SJohn Marino     rcsbuf_cache (rcs, rcsbuf);
7525*86d7f5d3SJohn Marino 
7526*86d7f5d3SJohn Marino     if (! foundhead)
7527*86d7f5d3SJohn Marino         error (1, 0, "could not find desired version %s in %s",
7528*86d7f5d3SJohn Marino 	       version, rcs->print_path);
7529*86d7f5d3SJohn Marino 
7530*86d7f5d3SJohn Marino     /* Now print out or return the data we have just computed.  */
7531*86d7f5d3SJohn Marino     switch (op)
7532*86d7f5d3SJohn Marino     {
7533*86d7f5d3SJohn Marino 	case RCS_ANNOTATE:
7534*86d7f5d3SJohn Marino 	    {
7535*86d7f5d3SJohn Marino 		unsigned int ln;
7536*86d7f5d3SJohn Marino 
7537*86d7f5d3SJohn Marino 		for (ln = 0; ln < headlines.nlines; ++ln)
7538*86d7f5d3SJohn Marino 		{
7539*86d7f5d3SJohn Marino 		    char *buf;
7540*86d7f5d3SJohn Marino 		    /* Period which separates year from month in date.  */
7541*86d7f5d3SJohn Marino 		    char *ym;
7542*86d7f5d3SJohn Marino 		    /* Period which separates month from day in date.  */
7543*86d7f5d3SJohn Marino 		    char *md;
7544*86d7f5d3SJohn Marino 		    RCSVers *prvers;
7545*86d7f5d3SJohn Marino 
7546*86d7f5d3SJohn Marino 		    prvers = headlines.vector[ln]->vers;
7547*86d7f5d3SJohn Marino 		    if (prvers == NULL)
7548*86d7f5d3SJohn Marino 			prvers = vers;
7549*86d7f5d3SJohn Marino 
7550*86d7f5d3SJohn Marino 		    buf = xmalloc (strlen (prvers->version) + 24);
7551*86d7f5d3SJohn Marino 		    sprintf (buf, "%-12s (%-8.8s ",
7552*86d7f5d3SJohn Marino 			     prvers->version,
7553*86d7f5d3SJohn Marino 			     prvers->author);
7554*86d7f5d3SJohn Marino 		    cvs_output (buf, 0);
7555*86d7f5d3SJohn Marino 		    free (buf);
7556*86d7f5d3SJohn Marino 
7557*86d7f5d3SJohn Marino 		    /* Now output the date.  */
7558*86d7f5d3SJohn Marino 		    ym = strchr (prvers->date, '.');
7559*86d7f5d3SJohn Marino 		    if (ym == NULL)
7560*86d7f5d3SJohn Marino 		    {
7561*86d7f5d3SJohn Marino 			cvs_output ("??", 0);
7562*86d7f5d3SJohn Marino 			cvs_output ("-???", 0);
7563*86d7f5d3SJohn Marino 			cvs_output ("-??", 0);
7564*86d7f5d3SJohn Marino 		    }
7565*86d7f5d3SJohn Marino 		    else
7566*86d7f5d3SJohn Marino 		    {
7567*86d7f5d3SJohn Marino 			md = strchr (ym + 1, '.');
7568*86d7f5d3SJohn Marino 			if (md == NULL)
7569*86d7f5d3SJohn Marino 			    cvs_output ("??", 0);
7570*86d7f5d3SJohn Marino 			else
7571*86d7f5d3SJohn Marino 			    cvs_output (md + 1, 2);
7572*86d7f5d3SJohn Marino 
7573*86d7f5d3SJohn Marino 			cvs_output ("-", 1);
7574*86d7f5d3SJohn Marino 			cvs_output (month_printname (ym + 1), 0);
7575*86d7f5d3SJohn Marino 			cvs_output ("-", 1);
7576*86d7f5d3SJohn Marino 			/* Only output the last two digits of the year.  Our output
7577*86d7f5d3SJohn Marino 			   lines are long enough as it is without printing the
7578*86d7f5d3SJohn Marino 			   century.  */
7579*86d7f5d3SJohn Marino 			cvs_output (ym - 2, 2);
7580*86d7f5d3SJohn Marino 		    }
7581*86d7f5d3SJohn Marino 		    cvs_output ("): ", 0);
7582*86d7f5d3SJohn Marino 		    if (headlines.vector[ln]->len != 0)
7583*86d7f5d3SJohn Marino 			cvs_output (headlines.vector[ln]->text,
7584*86d7f5d3SJohn Marino 				    headlines.vector[ln]->len);
7585*86d7f5d3SJohn Marino 		    cvs_output ("\n", 1);
7586*86d7f5d3SJohn Marino 		}
7587*86d7f5d3SJohn Marino 	    }
7588*86d7f5d3SJohn Marino 	    break;
7589*86d7f5d3SJohn Marino 	case RCS_FETCH:
7590*86d7f5d3SJohn Marino 	    {
7591*86d7f5d3SJohn Marino 		char *p;
7592*86d7f5d3SJohn Marino 		size_t n;
7593*86d7f5d3SJohn Marino 		unsigned int ln;
7594*86d7f5d3SJohn Marino 
7595*86d7f5d3SJohn Marino 		assert (text != NULL);
7596*86d7f5d3SJohn Marino 		assert (len != NULL);
7597*86d7f5d3SJohn Marino 
7598*86d7f5d3SJohn Marino 		n = 0;
7599*86d7f5d3SJohn Marino 		for (ln = 0; ln < headlines.nlines; ++ln)
7600*86d7f5d3SJohn Marino 		    /* 1 for \n */
7601*86d7f5d3SJohn Marino 		    n += headlines.vector[ln]->len + 1;
7602*86d7f5d3SJohn Marino 		p = xmalloc (n);
7603*86d7f5d3SJohn Marino 		*text = p;
7604*86d7f5d3SJohn Marino 		for (ln = 0; ln < headlines.nlines; ++ln)
7605*86d7f5d3SJohn Marino 		{
7606*86d7f5d3SJohn Marino 		    memcpy (p, headlines.vector[ln]->text,
7607*86d7f5d3SJohn Marino 			    headlines.vector[ln]->len);
7608*86d7f5d3SJohn Marino 		    p += headlines.vector[ln]->len;
7609*86d7f5d3SJohn Marino 		    if (headlines.vector[ln]->has_newline)
7610*86d7f5d3SJohn Marino 			*p++ = '\n';
7611*86d7f5d3SJohn Marino 		}
7612*86d7f5d3SJohn Marino 		*len = p - *text;
7613*86d7f5d3SJohn Marino 		assert (*len <= n);
7614*86d7f5d3SJohn Marino 	    }
7615*86d7f5d3SJohn Marino 	    break;
7616*86d7f5d3SJohn Marino     }
7617*86d7f5d3SJohn Marino 
7618*86d7f5d3SJohn Marino     linevector_free (&curlines);
7619*86d7f5d3SJohn Marino     linevector_free (&headlines);
7620*86d7f5d3SJohn Marino     linevector_free (&trunklines);
7621*86d7f5d3SJohn Marino 
7622*86d7f5d3SJohn Marino     return;
7623*86d7f5d3SJohn Marino }
7624*86d7f5d3SJohn Marino 
7625*86d7f5d3SJohn Marino 
7626*86d7f5d3SJohn Marino 
7627*86d7f5d3SJohn Marino /* Read the information for a single delta from the RCS buffer RCSBUF,
7628*86d7f5d3SJohn Marino    whose name is RCSFILE.  *KEYP and *VALP are either NULL, or the
7629*86d7f5d3SJohn Marino    first key/value pair to read, as set by rcsbuf_getkey. Return NULL
7630*86d7f5d3SJohn Marino    if there are no more deltas.  Store the key/value pair which
7631*86d7f5d3SJohn Marino    terminated the read in *KEYP and *VALP.  */
7632*86d7f5d3SJohn Marino static RCSVers *
getdelta(struct rcsbuffer * rcsbuf,char * rcsfile,char ** keyp,char ** valp)7633*86d7f5d3SJohn Marino getdelta (struct rcsbuffer *rcsbuf, char *rcsfile, char **keyp, char **valp)
7634*86d7f5d3SJohn Marino {
7635*86d7f5d3SJohn Marino     RCSVers *vnode;
7636*86d7f5d3SJohn Marino     char *key, *value, *cp;
7637*86d7f5d3SJohn Marino     Node *kv;
7638*86d7f5d3SJohn Marino 
7639*86d7f5d3SJohn Marino     /* Get revision number if it wasn't passed in. This uses
7640*86d7f5d3SJohn Marino        rcsbuf_getkey because it doesn't croak when encountering
7641*86d7f5d3SJohn Marino        unexpected input.  As a result, we have to play unholy games
7642*86d7f5d3SJohn Marino        with `key' and `value'. */
7643*86d7f5d3SJohn Marino     if (*keyp != NULL)
7644*86d7f5d3SJohn Marino     {
7645*86d7f5d3SJohn Marino 	key = *keyp;
7646*86d7f5d3SJohn Marino 	value = *valp;
7647*86d7f5d3SJohn Marino     }
7648*86d7f5d3SJohn Marino     else
7649*86d7f5d3SJohn Marino     {
7650*86d7f5d3SJohn Marino 	if (! rcsbuf_getkey (rcsbuf, &key, &value))
7651*86d7f5d3SJohn Marino 	    error (1, 0, "%s: unexpected EOF", rcsfile);
7652*86d7f5d3SJohn Marino     }
7653*86d7f5d3SJohn Marino 
7654*86d7f5d3SJohn Marino     /* Make sure that it is a revision number and not a cabbage
7655*86d7f5d3SJohn Marino        or something. */
7656*86d7f5d3SJohn Marino     for (cp = key;
7657*86d7f5d3SJohn Marino 	 (isdigit ((unsigned char) *cp) || *cp == '.') && *cp != '\0';
7658*86d7f5d3SJohn Marino 	 cp++)
7659*86d7f5d3SJohn Marino 	/* do nothing */ ;
7660*86d7f5d3SJohn Marino     /* Note that when comparing with RCSDATE, we are not massaging
7661*86d7f5d3SJohn Marino        VALUE from the string found in the RCS file.  This is OK since
7662*86d7f5d3SJohn Marino        we know exactly what to expect.  */
7663*86d7f5d3SJohn Marino     if (*cp != '\0' || strncmp (RCSDATE, value, (sizeof RCSDATE) - 1) != 0)
7664*86d7f5d3SJohn Marino     {
7665*86d7f5d3SJohn Marino 	*keyp = key;
7666*86d7f5d3SJohn Marino 	*valp = value;
7667*86d7f5d3SJohn Marino 	return NULL;
7668*86d7f5d3SJohn Marino     }
7669*86d7f5d3SJohn Marino 
7670*86d7f5d3SJohn Marino     vnode = xmalloc (sizeof (RCSVers));
7671*86d7f5d3SJohn Marino     memset (vnode, 0, sizeof (RCSVers));
7672*86d7f5d3SJohn Marino 
7673*86d7f5d3SJohn Marino     vnode->version = xstrdup (key);
7674*86d7f5d3SJohn Marino 
7675*86d7f5d3SJohn Marino     /* Grab the value of the date from value.  Note that we are not
7676*86d7f5d3SJohn Marino        massaging VALUE from the string found in the RCS file.  */
7677*86d7f5d3SJohn Marino     cp = value + (sizeof RCSDATE) - 1;	/* skip the "date" keyword */
7678*86d7f5d3SJohn Marino     while (whitespace (*cp))		/* take space off front of value */
7679*86d7f5d3SJohn Marino 	cp++;
7680*86d7f5d3SJohn Marino 
7681*86d7f5d3SJohn Marino     vnode->date = xstrdup (cp);
7682*86d7f5d3SJohn Marino 
7683*86d7f5d3SJohn Marino     /* Get author field.  */
7684*86d7f5d3SJohn Marino     if (! rcsbuf_getkey (rcsbuf, &key, &value))
7685*86d7f5d3SJohn Marino     {
7686*86d7f5d3SJohn Marino 	error (1, 0, "unexpected end of file reading %s", rcsfile);
7687*86d7f5d3SJohn Marino     }
7688*86d7f5d3SJohn Marino     if (! STREQ (key, "author"))
7689*86d7f5d3SJohn Marino 	error (1, 0, "\
7690*86d7f5d3SJohn Marino unable to parse %s; `author' not in the expected place", rcsfile);
7691*86d7f5d3SJohn Marino     vnode->author = rcsbuf_valcopy (rcsbuf, value, 0, NULL);
7692*86d7f5d3SJohn Marino 
7693*86d7f5d3SJohn Marino     /* Get state field.  */
7694*86d7f5d3SJohn Marino     if (! rcsbuf_getkey (rcsbuf, &key, &value))
7695*86d7f5d3SJohn Marino     {
7696*86d7f5d3SJohn Marino 	error (1, 0, "unexpected end of file reading %s", rcsfile);
7697*86d7f5d3SJohn Marino     }
7698*86d7f5d3SJohn Marino     if (! STREQ (key, "state"))
7699*86d7f5d3SJohn Marino 	error (1, 0, "\
7700*86d7f5d3SJohn Marino unable to parse %s; `state' not in the expected place", rcsfile);
7701*86d7f5d3SJohn Marino     vnode->state = rcsbuf_valcopy (rcsbuf, value, 0, NULL);
7702*86d7f5d3SJohn Marino     /* The value is optional, according to rcsfile(5).  */
7703*86d7f5d3SJohn Marino     if (value != NULL && STREQ (value, RCSDEAD))
7704*86d7f5d3SJohn Marino     {
7705*86d7f5d3SJohn Marino 	vnode->dead = 1;
7706*86d7f5d3SJohn Marino     }
7707*86d7f5d3SJohn Marino 
7708*86d7f5d3SJohn Marino     /* Note that "branches" and "next" are in fact mandatory, according
7709*86d7f5d3SJohn Marino        to doc/RCSFILES.  */
7710*86d7f5d3SJohn Marino 
7711*86d7f5d3SJohn Marino     /* fill in the branch list (if any branches exist) */
7712*86d7f5d3SJohn Marino     if (! rcsbuf_getkey (rcsbuf, &key, &value))
7713*86d7f5d3SJohn Marino     {
7714*86d7f5d3SJohn Marino 	error (1, 0, "unexpected end of file reading %s", rcsfile);
7715*86d7f5d3SJohn Marino     }
7716*86d7f5d3SJohn Marino     if (STREQ (key, RCSDESC))
7717*86d7f5d3SJohn Marino     {
7718*86d7f5d3SJohn Marino 	*keyp = key;
7719*86d7f5d3SJohn Marino 	*valp = value;
7720*86d7f5d3SJohn Marino 	/* Probably could/should be a fatal error.  */
7721*86d7f5d3SJohn Marino 	error (0, 0, "warning: 'branches' keyword missing from %s", rcsfile);
7722*86d7f5d3SJohn Marino 	return vnode;
7723*86d7f5d3SJohn Marino     }
7724*86d7f5d3SJohn Marino     if (value != NULL)
7725*86d7f5d3SJohn Marino     {
7726*86d7f5d3SJohn Marino 	vnode->branches = getlist ();
7727*86d7f5d3SJohn Marino 	/* Note that we are not massaging VALUE from the string found
7728*86d7f5d3SJohn Marino            in the RCS file.  */
7729*86d7f5d3SJohn Marino 	do_branches (vnode->branches, value);
7730*86d7f5d3SJohn Marino     }
7731*86d7f5d3SJohn Marino 
7732*86d7f5d3SJohn Marino     /* fill in the next field if there is a next revision */
7733*86d7f5d3SJohn Marino     if (! rcsbuf_getkey (rcsbuf, &key, &value))
7734*86d7f5d3SJohn Marino     {
7735*86d7f5d3SJohn Marino 	error (1, 0, "unexpected end of file reading %s", rcsfile);
7736*86d7f5d3SJohn Marino     }
7737*86d7f5d3SJohn Marino     if (STREQ (key, RCSDESC))
7738*86d7f5d3SJohn Marino     {
7739*86d7f5d3SJohn Marino 	*keyp = key;
7740*86d7f5d3SJohn Marino 	*valp = value;
7741*86d7f5d3SJohn Marino 	/* Probably could/should be a fatal error.  */
7742*86d7f5d3SJohn Marino 	error (0, 0, "warning: 'next' keyword missing from %s", rcsfile);
7743*86d7f5d3SJohn Marino 	return vnode;
7744*86d7f5d3SJohn Marino     }
7745*86d7f5d3SJohn Marino     if (value != NULL)
7746*86d7f5d3SJohn Marino 	vnode->next = rcsbuf_valcopy (rcsbuf, value, 0, NULL);
7747*86d7f5d3SJohn Marino 
7748*86d7f5d3SJohn Marino     /*
7749*86d7f5d3SJohn Marino      * XXX - this is where we put the symbolic link stuff???
7750*86d7f5d3SJohn Marino      * (into newphrases in the deltas).
7751*86d7f5d3SJohn Marino      */
7752*86d7f5d3SJohn Marino     while (1)
7753*86d7f5d3SJohn Marino     {
7754*86d7f5d3SJohn Marino 	if (! rcsbuf_getkey (rcsbuf, &key, &value))
7755*86d7f5d3SJohn Marino 	    error (1, 0, "unexpected end of file reading %s", rcsfile);
7756*86d7f5d3SJohn Marino 
7757*86d7f5d3SJohn Marino 	/* The `desc' keyword is the end of the deltas. */
7758*86d7f5d3SJohn Marino 	if (strcmp (key, RCSDESC) == 0)
7759*86d7f5d3SJohn Marino 	    break;
7760*86d7f5d3SJohn Marino 
7761*86d7f5d3SJohn Marino #ifdef PRESERVE_PERMISSIONS_SUPPORT
7762*86d7f5d3SJohn Marino 
7763*86d7f5d3SJohn Marino 	/* The `hardlinks' value is a group of words, which must
7764*86d7f5d3SJohn Marino 	   be parsed separately and added as a list to vnode->hardlinks. */
7765*86d7f5d3SJohn Marino 	if (strcmp (key, "hardlinks") == 0)
7766*86d7f5d3SJohn Marino 	{
7767*86d7f5d3SJohn Marino 	    char *word;
7768*86d7f5d3SJohn Marino 
7769*86d7f5d3SJohn Marino 	    vnode->hardlinks = getlist();
7770*86d7f5d3SJohn Marino 	    while ((word = rcsbuf_valword (rcsbuf, &value)) != NULL)
7771*86d7f5d3SJohn Marino 	    {
7772*86d7f5d3SJohn Marino 		Node *n = getnode();
7773*86d7f5d3SJohn Marino 		n->key = word;
7774*86d7f5d3SJohn Marino 		addnode (vnode->hardlinks, n);
7775*86d7f5d3SJohn Marino 	    }
7776*86d7f5d3SJohn Marino 	    continue;
7777*86d7f5d3SJohn Marino 	}
7778*86d7f5d3SJohn Marino #endif
7779*86d7f5d3SJohn Marino 
7780*86d7f5d3SJohn Marino 	/* Enable use of repositories created by certain obsolete
7781*86d7f5d3SJohn Marino 	   versions of CVS.  This code should remain indefinately;
7782*86d7f5d3SJohn Marino 	   there is no procedure for converting old repositories, and
7783*86d7f5d3SJohn Marino 	   checking for it is harmless.  */
7784*86d7f5d3SJohn Marino 	if (STREQ (key, RCSDEAD))
7785*86d7f5d3SJohn Marino 	{
7786*86d7f5d3SJohn Marino 	    vnode->dead = 1;
7787*86d7f5d3SJohn Marino 	    if (vnode->state != NULL)
7788*86d7f5d3SJohn Marino 		free (vnode->state);
7789*86d7f5d3SJohn Marino 	    vnode->state = xstrdup (RCSDEAD);
7790*86d7f5d3SJohn Marino 	    continue;
7791*86d7f5d3SJohn Marino 	}
7792*86d7f5d3SJohn Marino 	/* if we have a new revision number, we're done with this delta */
7793*86d7f5d3SJohn Marino 	for (cp = key;
7794*86d7f5d3SJohn Marino 	     (isdigit ((unsigned char) *cp) || *cp == '.') && *cp != '\0';
7795*86d7f5d3SJohn Marino 	     cp++)
7796*86d7f5d3SJohn Marino 	    /* do nothing */ ;
7797*86d7f5d3SJohn Marino 	/* Note that when comparing with RCSDATE, we are not massaging
7798*86d7f5d3SJohn Marino 	   VALUE from the string found in the RCS file.  This is OK
7799*86d7f5d3SJohn Marino 	   since we know exactly what to expect.  */
7800*86d7f5d3SJohn Marino 	if (*cp == '\0' && strncmp (RCSDATE, value, strlen (RCSDATE)) == 0)
7801*86d7f5d3SJohn Marino 	    break;
7802*86d7f5d3SJohn Marino 
7803*86d7f5d3SJohn Marino 	/* At this point, key and value represent a user-defined field
7804*86d7f5d3SJohn Marino 	   in the delta node. */
7805*86d7f5d3SJohn Marino 	if (vnode->other_delta == NULL)
7806*86d7f5d3SJohn Marino 	    vnode->other_delta = getlist ();
7807*86d7f5d3SJohn Marino 	kv = getnode ();
7808*86d7f5d3SJohn Marino 	kv->type = rcsbuf_valcmp (rcsbuf) ? RCSCMPFLD : RCSFIELD;
7809*86d7f5d3SJohn Marino 	kv->key = xstrdup (key);
7810*86d7f5d3SJohn Marino 	kv->data = rcsbuf_valcopy (rcsbuf, value, kv->type == RCSFIELD, NULL);
7811*86d7f5d3SJohn Marino 	if (addnode (vnode->other_delta, kv) != 0)
7812*86d7f5d3SJohn Marino 	{
7813*86d7f5d3SJohn Marino 	    /* Complaining about duplicate keys in newphrases seems
7814*86d7f5d3SJohn Marino 	       questionable, in that we don't know what they mean and
7815*86d7f5d3SJohn Marino 	       doc/RCSFILES has no prohibition on several newphrases
7816*86d7f5d3SJohn Marino 	       with the same key.  But we can't store more than one as
7817*86d7f5d3SJohn Marino 	       long as we store them in a List *.  */
7818*86d7f5d3SJohn Marino 	    error (0, 0, "warning: duplicate key `%s' in RCS file `%s'",
7819*86d7f5d3SJohn Marino 		   key, rcsfile);
7820*86d7f5d3SJohn Marino 	    freenode (kv);
7821*86d7f5d3SJohn Marino 	}
7822*86d7f5d3SJohn Marino     }
7823*86d7f5d3SJohn Marino 
7824*86d7f5d3SJohn Marino     /* Return the key which caused us to fail back to the caller.  */
7825*86d7f5d3SJohn Marino     *keyp = key;
7826*86d7f5d3SJohn Marino     *valp = value;
7827*86d7f5d3SJohn Marino 
7828*86d7f5d3SJohn Marino     return vnode;
7829*86d7f5d3SJohn Marino }
7830*86d7f5d3SJohn Marino 
7831*86d7f5d3SJohn Marino 
7832*86d7f5d3SJohn Marino 
7833*86d7f5d3SJohn Marino static void
freedeltatext(Deltatext * d)7834*86d7f5d3SJohn Marino freedeltatext (Deltatext *d)
7835*86d7f5d3SJohn Marino {
7836*86d7f5d3SJohn Marino     if (d->version != NULL)
7837*86d7f5d3SJohn Marino 	free (d->version);
7838*86d7f5d3SJohn Marino     if (d->log != NULL)
7839*86d7f5d3SJohn Marino 	free (d->log);
7840*86d7f5d3SJohn Marino     if (d->text != NULL)
7841*86d7f5d3SJohn Marino 	free (d->text);
7842*86d7f5d3SJohn Marino     if (d->other != NULL)
7843*86d7f5d3SJohn Marino 	dellist (&d->other);
7844*86d7f5d3SJohn Marino     free (d);
7845*86d7f5d3SJohn Marino }
7846*86d7f5d3SJohn Marino 
7847*86d7f5d3SJohn Marino static Deltatext *
RCS_getdeltatext(RCSNode * rcs,FILE * fp,struct rcsbuffer * rcsbuf)7848*86d7f5d3SJohn Marino RCS_getdeltatext (RCSNode *rcs, FILE *fp, struct rcsbuffer *rcsbuf)
7849*86d7f5d3SJohn Marino {
7850*86d7f5d3SJohn Marino     char *num;
7851*86d7f5d3SJohn Marino     char *key, *value;
7852*86d7f5d3SJohn Marino     Node *p;
7853*86d7f5d3SJohn Marino     Deltatext *d;
7854*86d7f5d3SJohn Marino 
7855*86d7f5d3SJohn Marino     /* Get the revision number. */
7856*86d7f5d3SJohn Marino     if (! rcsbuf_getrevnum (rcsbuf, &num))
7857*86d7f5d3SJohn Marino     {
7858*86d7f5d3SJohn Marino 	/* If num == NULL, it means we reached EOF naturally.  That's
7859*86d7f5d3SJohn Marino 	   fine. */
7860*86d7f5d3SJohn Marino 	if (num == NULL)
7861*86d7f5d3SJohn Marino 	    return NULL;
7862*86d7f5d3SJohn Marino 	else
7863*86d7f5d3SJohn Marino 	    error (1, 0, "%s: unexpected EOF", rcs->print_path);
7864*86d7f5d3SJohn Marino     }
7865*86d7f5d3SJohn Marino 
7866*86d7f5d3SJohn Marino     p = findnode (rcs->versions, num);
7867*86d7f5d3SJohn Marino     if (p == NULL)
7868*86d7f5d3SJohn Marino 	error (1, 0, "mismatch in rcs file %s between deltas and deltatexts (%s)",
7869*86d7f5d3SJohn Marino 	       rcs->print_path, num);
7870*86d7f5d3SJohn Marino 
7871*86d7f5d3SJohn Marino     d = xmalloc (sizeof (Deltatext));
7872*86d7f5d3SJohn Marino     d->version = xstrdup (num);
7873*86d7f5d3SJohn Marino 
7874*86d7f5d3SJohn Marino     /* Get the log message. */
7875*86d7f5d3SJohn Marino     if (! rcsbuf_getkey (rcsbuf, &key, &value))
7876*86d7f5d3SJohn Marino 	error (1, 0, "%s, delta %s: unexpected EOF", rcs->print_path, num);
7877*86d7f5d3SJohn Marino     if (! STREQ (key, "log"))
7878*86d7f5d3SJohn Marino 	error (1, 0, "%s, delta %s: expected `log', got `%s'",
7879*86d7f5d3SJohn Marino 	       rcs->print_path, num, key);
7880*86d7f5d3SJohn Marino     d->log = rcsbuf_valcopy (rcsbuf, value, 0, NULL);
7881*86d7f5d3SJohn Marino 
7882*86d7f5d3SJohn Marino     /* Get random newphrases. */
7883*86d7f5d3SJohn Marino     d->other = getlist();
7884*86d7f5d3SJohn Marino     while (1)
7885*86d7f5d3SJohn Marino     {
7886*86d7f5d3SJohn Marino 	if (! rcsbuf_getkey (rcsbuf, &key, &value))
7887*86d7f5d3SJohn Marino 	    error (1, 0, "%s, delta %s: unexpected EOF", rcs->print_path, num);
7888*86d7f5d3SJohn Marino 
7889*86d7f5d3SJohn Marino 	if (STREQ (key, "text"))
7890*86d7f5d3SJohn Marino 	    break;
7891*86d7f5d3SJohn Marino 
7892*86d7f5d3SJohn Marino 	p = getnode();
7893*86d7f5d3SJohn Marino 	p->type = rcsbuf_valcmp (rcsbuf) ? RCSCMPFLD : RCSFIELD;
7894*86d7f5d3SJohn Marino 	p->key = xstrdup (key);
7895*86d7f5d3SJohn Marino 	p->data = rcsbuf_valcopy (rcsbuf, value, p->type == RCSFIELD, NULL);
7896*86d7f5d3SJohn Marino 	if (addnode (d->other, p) < 0)
7897*86d7f5d3SJohn Marino 	{
7898*86d7f5d3SJohn Marino 	    error (0, 0, "warning: %s, delta %s: duplicate field `%s'",
7899*86d7f5d3SJohn Marino 		   rcs->print_path, num, key);
7900*86d7f5d3SJohn Marino 	}
7901*86d7f5d3SJohn Marino     }
7902*86d7f5d3SJohn Marino 
7903*86d7f5d3SJohn Marino     /* Get the change text. We already know that this key is `text'. */
7904*86d7f5d3SJohn Marino     d->text = rcsbuf_valcopy (rcsbuf, value, 0, &d->len);
7905*86d7f5d3SJohn Marino 
7906*86d7f5d3SJohn Marino     return d;
7907*86d7f5d3SJohn Marino }
7908*86d7f5d3SJohn Marino 
7909*86d7f5d3SJohn Marino 
7910*86d7f5d3SJohn Marino 
7911*86d7f5d3SJohn Marino /* RCS output functions, for writing RCS format files from RCSNode
7912*86d7f5d3SJohn Marino    structures.
7913*86d7f5d3SJohn Marino 
7914*86d7f5d3SJohn Marino    For most of this work, RCS 5.7 uses an `aprintf' function which aborts
7915*86d7f5d3SJohn Marino    program upon error.  Instead, these functions check the output status
7916*86d7f5d3SJohn Marino    of the stream right before closing it, and aborts if an error condition
7917*86d7f5d3SJohn Marino    is found.  The RCS solution is probably the better one: it produces
7918*86d7f5d3SJohn Marino    more overhead, but will produce a clearer diagnostic in the case of
7919*86d7f5d3SJohn Marino    catastrophic error.  In either case, however, the repository will probably
7920*86d7f5d3SJohn Marino    not get corrupted. */
7921*86d7f5d3SJohn Marino static int
putsymbol_proc(Node * symnode,void * fparg)7922*86d7f5d3SJohn Marino putsymbol_proc (Node *symnode, void *fparg)
7923*86d7f5d3SJohn Marino {
7924*86d7f5d3SJohn Marino     FILE *fp = fparg;
7925*86d7f5d3SJohn Marino 
7926*86d7f5d3SJohn Marino     /* A fiddly optimization: this code used to just call fprintf, but
7927*86d7f5d3SJohn Marino        in an old repository with hundreds of tags this can get called
7928*86d7f5d3SJohn Marino        hundreds of thousands of times when doing a cvs tag.  Since
7929*86d7f5d3SJohn Marino        tagging is a relatively common operation, and using putc and
7930*86d7f5d3SJohn Marino        fputs is just as comprehensible, the change is worthwhile.  */
7931*86d7f5d3SJohn Marino     putc ('\n', fp);
7932*86d7f5d3SJohn Marino     putc ('\t', fp);
7933*86d7f5d3SJohn Marino     fputs (symnode->key, fp);
7934*86d7f5d3SJohn Marino     putc (':', fp);
7935*86d7f5d3SJohn Marino     fputs (symnode->data, fp);
7936*86d7f5d3SJohn Marino     return 0;
7937*86d7f5d3SJohn Marino }
7938*86d7f5d3SJohn Marino 
7939*86d7f5d3SJohn Marino 
7940*86d7f5d3SJohn Marino 
7941*86d7f5d3SJohn Marino /* putlock_proc is like putsymbol_proc, but key and data are reversed. */
7942*86d7f5d3SJohn Marino static int
putlock_proc(Node * symnode,void * fp)7943*86d7f5d3SJohn Marino putlock_proc (Node *symnode, void *fp)
7944*86d7f5d3SJohn Marino {
7945*86d7f5d3SJohn Marino     return fprintf (fp, "\n\t%s:%s", (char *)symnode->data, symnode->key);
7946*86d7f5d3SJohn Marino }
7947*86d7f5d3SJohn Marino 
7948*86d7f5d3SJohn Marino 
7949*86d7f5d3SJohn Marino 
7950*86d7f5d3SJohn Marino static int
putrcsfield_proc(Node * node,void * vfp)7951*86d7f5d3SJohn Marino putrcsfield_proc (Node *node, void *vfp)
7952*86d7f5d3SJohn Marino {
7953*86d7f5d3SJohn Marino     FILE *fp = vfp;
7954*86d7f5d3SJohn Marino 
7955*86d7f5d3SJohn Marino     /* Some magic keys used internally by CVS start with `;'. Skip them. */
7956*86d7f5d3SJohn Marino     if (node->key[0] == ';')
7957*86d7f5d3SJohn Marino 	return 0;
7958*86d7f5d3SJohn Marino 
7959*86d7f5d3SJohn Marino     fprintf (fp, "\n%s\t", node->key);
7960*86d7f5d3SJohn Marino     if (node->data != NULL)
7961*86d7f5d3SJohn Marino     {
7962*86d7f5d3SJohn Marino 	/* If the field's value contains evil characters,
7963*86d7f5d3SJohn Marino 	   it must be stringified. */
7964*86d7f5d3SJohn Marino 	/* FIXME: This does not quite get it right.  "7jk8f" is not a valid
7965*86d7f5d3SJohn Marino 	   value for a value in a newpharse, according to doc/RCSFILES,
7966*86d7f5d3SJohn Marino 	   because digits are not valid in an "id".  We might do OK by
7967*86d7f5d3SJohn Marino 	   always writing strings (enclosed in @@).  Would be nice to
7968*86d7f5d3SJohn Marino 	   explicitly mention this one way or another in doc/RCSFILES.
7969*86d7f5d3SJohn Marino 	   A case where we are wrong in a much more clear-cut way is that
7970*86d7f5d3SJohn Marino 	   we let through non-graphic characters such as whitespace and
7971*86d7f5d3SJohn Marino 	   control characters.  */
7972*86d7f5d3SJohn Marino 
7973*86d7f5d3SJohn Marino 	if (node->type == RCSCMPFLD || strpbrk (node->data, "$,.:;@") == NULL)
7974*86d7f5d3SJohn Marino 	    fputs (node->data, fp);
7975*86d7f5d3SJohn Marino 	else
7976*86d7f5d3SJohn Marino 	{
7977*86d7f5d3SJohn Marino 	    putc ('@', fp);
7978*86d7f5d3SJohn Marino 	    expand_at_signs (node->data, (off_t) strlen (node->data), fp);
7979*86d7f5d3SJohn Marino 	    putc ('@', fp);
7980*86d7f5d3SJohn Marino 	}
7981*86d7f5d3SJohn Marino     }
7982*86d7f5d3SJohn Marino 
7983*86d7f5d3SJohn Marino     /* desc, log and text fields should not be terminated with semicolon;
7984*86d7f5d3SJohn Marino        all other fields should be. */
7985*86d7f5d3SJohn Marino     if (! STREQ (node->key, "desc") &&
7986*86d7f5d3SJohn Marino 	! STREQ (node->key, "log") &&
7987*86d7f5d3SJohn Marino 	! STREQ (node->key, "text"))
7988*86d7f5d3SJohn Marino     {
7989*86d7f5d3SJohn Marino 	putc (';', fp);
7990*86d7f5d3SJohn Marino     }
7991*86d7f5d3SJohn Marino     return 0;
7992*86d7f5d3SJohn Marino }
7993*86d7f5d3SJohn Marino 
7994*86d7f5d3SJohn Marino 
7995*86d7f5d3SJohn Marino 
7996*86d7f5d3SJohn Marino #ifdef PRESERVE_PERMISSIONS_SUPPORT
7997*86d7f5d3SJohn Marino 
7998*86d7f5d3SJohn Marino /* Save a filename in a `hardlinks' RCS field.  NODE->KEY will contain
7999*86d7f5d3SJohn Marino    a full pathname, but currently only basenames are stored in the RCS
8000*86d7f5d3SJohn Marino    node.  Assume that the filename includes nasty characters and
8001*86d7f5d3SJohn Marino    @-escape it. */
8002*86d7f5d3SJohn Marino 
8003*86d7f5d3SJohn Marino static int
puthardlink_proc(node,vfp)8004*86d7f5d3SJohn Marino puthardlink_proc (node, vfp)
8005*86d7f5d3SJohn Marino     Node *node;
8006*86d7f5d3SJohn Marino     void *vfp;
8007*86d7f5d3SJohn Marino {
8008*86d7f5d3SJohn Marino     FILE *fp = vfp;
8009*86d7f5d3SJohn Marino     char *basename = strrchr (node->key, '/');
8010*86d7f5d3SJohn Marino 
8011*86d7f5d3SJohn Marino     if (basename == NULL)
8012*86d7f5d3SJohn Marino 	basename = node->key;
8013*86d7f5d3SJohn Marino     else
8014*86d7f5d3SJohn Marino 	++basename;
8015*86d7f5d3SJohn Marino 
8016*86d7f5d3SJohn Marino     putc ('\t', fp);
8017*86d7f5d3SJohn Marino     putc ('@', fp);
8018*86d7f5d3SJohn Marino     (void) expand_at_signs (basename, strlen (basename), fp);
8019*86d7f5d3SJohn Marino     putc ('@', fp);
8020*86d7f5d3SJohn Marino 
8021*86d7f5d3SJohn Marino     return 0;
8022*86d7f5d3SJohn Marino }
8023*86d7f5d3SJohn Marino 
8024*86d7f5d3SJohn Marino #endif /* PRESERVE_PERMISSIONS_SUPPORT */
8025*86d7f5d3SJohn Marino 
8026*86d7f5d3SJohn Marino 
8027*86d7f5d3SJohn Marino 
8028*86d7f5d3SJohn Marino /* Output the admin node for RCS into stream FP. */
8029*86d7f5d3SJohn Marino static void
RCS_putadmin(RCSNode * rcs,FILE * fp)8030*86d7f5d3SJohn Marino RCS_putadmin (RCSNode *rcs, FILE *fp)
8031*86d7f5d3SJohn Marino {
8032*86d7f5d3SJohn Marino     fprintf (fp, "%s\t%s;\n", RCSHEAD, rcs->head ? rcs->head : "");
8033*86d7f5d3SJohn Marino     if (rcs->branch)
8034*86d7f5d3SJohn Marino 	fprintf (fp, "%s\t%s;\n", RCSBRANCH, rcs->branch);
8035*86d7f5d3SJohn Marino 
8036*86d7f5d3SJohn Marino     fputs ("access", fp);
8037*86d7f5d3SJohn Marino     if (rcs->access)
8038*86d7f5d3SJohn Marino     {
8039*86d7f5d3SJohn Marino 	char *p, *s;
8040*86d7f5d3SJohn Marino 	s = xstrdup (rcs->access);
8041*86d7f5d3SJohn Marino 	for (p = strtok (s, " \n\t"); p != NULL; p = strtok (NULL, " \n\t"))
8042*86d7f5d3SJohn Marino 	    fprintf (fp, "\n\t%s", p);
8043*86d7f5d3SJohn Marino 	free (s);
8044*86d7f5d3SJohn Marino     }
8045*86d7f5d3SJohn Marino     fputs (";\n", fp);
8046*86d7f5d3SJohn Marino 
8047*86d7f5d3SJohn Marino     fputs (RCSSYMBOLS, fp);
8048*86d7f5d3SJohn Marino     /* If we haven't had to convert the symbols to a list yet, don't
8049*86d7f5d3SJohn Marino        force a conversion now; just write out the string.  */
8050*86d7f5d3SJohn Marino     if (rcs->symbols == NULL && rcs->symbols_data != NULL)
8051*86d7f5d3SJohn Marino     {
8052*86d7f5d3SJohn Marino 	fputs ("\n\t", fp);
8053*86d7f5d3SJohn Marino 	fputs (rcs->symbols_data, fp);
8054*86d7f5d3SJohn Marino     }
8055*86d7f5d3SJohn Marino     else
8056*86d7f5d3SJohn Marino 	walklist (RCS_symbols (rcs), putsymbol_proc, fp);
8057*86d7f5d3SJohn Marino     fputs (";\n", fp);
8058*86d7f5d3SJohn Marino 
8059*86d7f5d3SJohn Marino     fputs ("locks", fp);
8060*86d7f5d3SJohn Marino     if (rcs->locks_data)
8061*86d7f5d3SJohn Marino 	fprintf (fp, "\t%s", rcs->locks_data);
8062*86d7f5d3SJohn Marino     else if (rcs->locks)
8063*86d7f5d3SJohn Marino 	walklist (rcs->locks, putlock_proc, fp);
8064*86d7f5d3SJohn Marino     if (rcs->strict_locks)
8065*86d7f5d3SJohn Marino 	fprintf (fp, "; strict");
8066*86d7f5d3SJohn Marino     fputs (";\n", fp);
8067*86d7f5d3SJohn Marino 
8068*86d7f5d3SJohn Marino     if (rcs->comment)
8069*86d7f5d3SJohn Marino     {
8070*86d7f5d3SJohn Marino 	fprintf (fp, "comment\t@");
8071*86d7f5d3SJohn Marino 	expand_at_signs (rcs->comment, (off_t) strlen (rcs->comment), fp);
8072*86d7f5d3SJohn Marino 	fputs ("@;\n", fp);
8073*86d7f5d3SJohn Marino     }
8074*86d7f5d3SJohn Marino     if (rcs->expand && ! STREQ (rcs->expand, "kv"))
8075*86d7f5d3SJohn Marino 	fprintf (fp, "%s\t@%s@;\n", RCSEXPAND, rcs->expand);
8076*86d7f5d3SJohn Marino 
8077*86d7f5d3SJohn Marino     walklist (rcs->other, putrcsfield_proc, fp);
8078*86d7f5d3SJohn Marino 
8079*86d7f5d3SJohn Marino     putc ('\n', fp);
8080*86d7f5d3SJohn Marino }
8081*86d7f5d3SJohn Marino 
8082*86d7f5d3SJohn Marino 
8083*86d7f5d3SJohn Marino 
8084*86d7f5d3SJohn Marino static void
putdelta(RCSVers * vers,FILE * fp)8085*86d7f5d3SJohn Marino putdelta (RCSVers *vers, FILE *fp)
8086*86d7f5d3SJohn Marino {
8087*86d7f5d3SJohn Marino     Node *bp, *start;
8088*86d7f5d3SJohn Marino 
8089*86d7f5d3SJohn Marino     /* Skip if no revision was supplied, or if it is outdated (cvs admin -o) */
8090*86d7f5d3SJohn Marino     if (vers == NULL || vers->outdated)
8091*86d7f5d3SJohn Marino 	return;
8092*86d7f5d3SJohn Marino 
8093*86d7f5d3SJohn Marino     fprintf (fp, "\n%s\n%s\t%s;\t%s %s;\t%s %s;\nbranches",
8094*86d7f5d3SJohn Marino 	     vers->version,
8095*86d7f5d3SJohn Marino 	     RCSDATE, vers->date,
8096*86d7f5d3SJohn Marino 	     "author", vers->author,
8097*86d7f5d3SJohn Marino 	     "state", vers->state ? vers->state : "");
8098*86d7f5d3SJohn Marino 
8099*86d7f5d3SJohn Marino     if (vers->branches != NULL)
8100*86d7f5d3SJohn Marino     {
8101*86d7f5d3SJohn Marino 	start = vers->branches->list;
8102*86d7f5d3SJohn Marino 	for (bp = start->next; bp != start; bp = bp->next)
8103*86d7f5d3SJohn Marino 	    fprintf (fp, "\n\t%s", bp->key);
8104*86d7f5d3SJohn Marino     }
8105*86d7f5d3SJohn Marino 
8106*86d7f5d3SJohn Marino     fprintf (fp, ";\nnext\t%s;", vers->next ? vers->next : "");
8107*86d7f5d3SJohn Marino 
8108*86d7f5d3SJohn Marino     walklist (vers->other_delta, putrcsfield_proc, fp);
8109*86d7f5d3SJohn Marino 
8110*86d7f5d3SJohn Marino #ifdef PRESERVE_PERMISSIONS_SUPPORT
8111*86d7f5d3SJohn Marino     if (vers->hardlinks)
8112*86d7f5d3SJohn Marino     {
8113*86d7f5d3SJohn Marino 	fprintf (fp, "\nhardlinks");
8114*86d7f5d3SJohn Marino 	walklist (vers->hardlinks, puthardlink_proc, fp);
8115*86d7f5d3SJohn Marino 	putc (';', fp);
8116*86d7f5d3SJohn Marino     }
8117*86d7f5d3SJohn Marino #endif
8118*86d7f5d3SJohn Marino     putc ('\n', fp);
8119*86d7f5d3SJohn Marino }
8120*86d7f5d3SJohn Marino 
8121*86d7f5d3SJohn Marino 
8122*86d7f5d3SJohn Marino 
8123*86d7f5d3SJohn Marino static void
RCS_putdtree(RCSNode * rcs,char * rev,FILE * fp)8124*86d7f5d3SJohn Marino RCS_putdtree (RCSNode *rcs, char *rev, FILE *fp)
8125*86d7f5d3SJohn Marino {
8126*86d7f5d3SJohn Marino     RCSVers *versp;
8127*86d7f5d3SJohn Marino     Node *p, *branch;
8128*86d7f5d3SJohn Marino 
8129*86d7f5d3SJohn Marino     /* Previously, this function used a recursive implementation, but
8130*86d7f5d3SJohn Marino        if the trunk has a huge number of revisions and the program
8131*86d7f5d3SJohn Marino        stack is not big, a stack overflow could occur, so this
8132*86d7f5d3SJohn Marino        nonrecursive version was developed to be more safe. */
8133*86d7f5d3SJohn Marino     Node *branchlist, *onebranch;
8134*86d7f5d3SJohn Marino     List *branches;
8135*86d7f5d3SJohn Marino     List *onebranchlist;
8136*86d7f5d3SJohn Marino 
8137*86d7f5d3SJohn Marino     if (rev == NULL)
8138*86d7f5d3SJohn Marino 	return;
8139*86d7f5d3SJohn Marino 
8140*86d7f5d3SJohn Marino     branches = getlist();
8141*86d7f5d3SJohn Marino 
8142*86d7f5d3SJohn Marino     for (; rev != NULL;)
8143*86d7f5d3SJohn Marino     {
8144*86d7f5d3SJohn Marino 	/* Find the delta node for this revision. */
8145*86d7f5d3SJohn Marino 	p = findnode (rcs->versions, rev);
8146*86d7f5d3SJohn Marino 	if (p == NULL)
8147*86d7f5d3SJohn Marino 	{
8148*86d7f5d3SJohn Marino 	    error (1, 0,
8149*86d7f5d3SJohn Marino 		   "error parsing repository file %s, file may be corrupt.",
8150*86d7f5d3SJohn Marino 		   rcs->path);
8151*86d7f5d3SJohn Marino 	}
8152*86d7f5d3SJohn Marino 
8153*86d7f5d3SJohn Marino 	versp = p->data;
8154*86d7f5d3SJohn Marino 
8155*86d7f5d3SJohn Marino 	/* Print the delta node and go for its `next' node.  This
8156*86d7f5d3SJohn Marino 	   prints the trunk. If there are any branches printed on this
8157*86d7f5d3SJohn Marino 	   revision, mark we have some. */
8158*86d7f5d3SJohn Marino 	putdelta (versp, fp);
8159*86d7f5d3SJohn Marino 	/* Store branch information into branch list so to write its
8160*86d7f5d3SJohn Marino 	   trunk afterwards */
8161*86d7f5d3SJohn Marino 	if (versp->branches != NULL)
8162*86d7f5d3SJohn Marino 	{
8163*86d7f5d3SJohn Marino 	    branch = getnode();
8164*86d7f5d3SJohn Marino 	    branch->data = versp->branches;
8165*86d7f5d3SJohn Marino 
8166*86d7f5d3SJohn Marino 	    addnode(branches, branch);
8167*86d7f5d3SJohn Marino 	}
8168*86d7f5d3SJohn Marino 
8169*86d7f5d3SJohn Marino 	rev = versp->next;
8170*86d7f5d3SJohn Marino     }
8171*86d7f5d3SJohn Marino 
8172*86d7f5d3SJohn Marino     /* If there are any branches printed on this revision,
8173*86d7f5d3SJohn Marino        print those trunks as well. */
8174*86d7f5d3SJohn Marino     branchlist = branches->list;
8175*86d7f5d3SJohn Marino     for (branch = branchlist->next;
8176*86d7f5d3SJohn Marino 	 branch != branchlist;
8177*86d7f5d3SJohn Marino 	 branch = branch->next)
8178*86d7f5d3SJohn Marino     {
8179*86d7f5d3SJohn Marino 	onebranchlist = (List *)(branch->data);
8180*86d7f5d3SJohn Marino 	onebranch = onebranchlist->list;
8181*86d7f5d3SJohn Marino 	for (p = onebranch->next; p != onebranch; p = p->next)
8182*86d7f5d3SJohn Marino 	    RCS_putdtree (rcs, p->key, fp);
8183*86d7f5d3SJohn Marino 
8184*86d7f5d3SJohn Marino 	branch->data = NULL; /* so to prevent its freeing on dellist */
8185*86d7f5d3SJohn Marino     }
8186*86d7f5d3SJohn Marino 
8187*86d7f5d3SJohn Marino     dellist(&branches);
8188*86d7f5d3SJohn Marino }
8189*86d7f5d3SJohn Marino 
8190*86d7f5d3SJohn Marino 
8191*86d7f5d3SJohn Marino 
8192*86d7f5d3SJohn Marino static void
RCS_putdesc(RCSNode * rcs,FILE * fp)8193*86d7f5d3SJohn Marino RCS_putdesc (RCSNode *rcs, FILE *fp)
8194*86d7f5d3SJohn Marino {
8195*86d7f5d3SJohn Marino     fprintf (fp, "\n\n%s\n@", RCSDESC);
8196*86d7f5d3SJohn Marino     if (rcs->desc != NULL)
8197*86d7f5d3SJohn Marino     {
8198*86d7f5d3SJohn Marino 	off_t len = (off_t) strlen (rcs->desc);
8199*86d7f5d3SJohn Marino 	if (len > 0)
8200*86d7f5d3SJohn Marino 	{
8201*86d7f5d3SJohn Marino 	    expand_at_signs (rcs->desc, len, fp);
8202*86d7f5d3SJohn Marino 	    if (rcs->desc[len-1] != '\n')
8203*86d7f5d3SJohn Marino 		putc ('\n', fp);
8204*86d7f5d3SJohn Marino 	}
8205*86d7f5d3SJohn Marino     }
8206*86d7f5d3SJohn Marino     fputs ("@\n", fp);
8207*86d7f5d3SJohn Marino }
8208*86d7f5d3SJohn Marino 
8209*86d7f5d3SJohn Marino 
8210*86d7f5d3SJohn Marino 
8211*86d7f5d3SJohn Marino static void
putdeltatext(FILE * fp,Deltatext * d)8212*86d7f5d3SJohn Marino putdeltatext (FILE *fp, Deltatext *d)
8213*86d7f5d3SJohn Marino {
8214*86d7f5d3SJohn Marino     fprintf (fp, "\n\n%s\nlog\n@", d->version);
8215*86d7f5d3SJohn Marino     if (d->log != NULL)
8216*86d7f5d3SJohn Marino     {
8217*86d7f5d3SJohn Marino 	int loglen = strlen (d->log);
8218*86d7f5d3SJohn Marino 	expand_at_signs (d->log, (off_t) loglen, fp);
8219*86d7f5d3SJohn Marino 	if (d->log[loglen-1] != '\n')
8220*86d7f5d3SJohn Marino 	    putc ('\n', fp);
8221*86d7f5d3SJohn Marino     }
8222*86d7f5d3SJohn Marino     putc ('@', fp);
8223*86d7f5d3SJohn Marino 
8224*86d7f5d3SJohn Marino     walklist (d->other, putrcsfield_proc, fp);
8225*86d7f5d3SJohn Marino 
8226*86d7f5d3SJohn Marino     fputs ("\ntext\n@", fp);
8227*86d7f5d3SJohn Marino     if (d->text != NULL)
8228*86d7f5d3SJohn Marino 	expand_at_signs (d->text, (off_t) d->len, fp);
8229*86d7f5d3SJohn Marino     fputs ("@\n", fp);
8230*86d7f5d3SJohn Marino }
8231*86d7f5d3SJohn Marino 
8232*86d7f5d3SJohn Marino 
8233*86d7f5d3SJohn Marino 
8234*86d7f5d3SJohn Marino /* TODO: the whole mechanism for updating deltas is kludgey... more
8235*86d7f5d3SJohn Marino    sensible would be to supply all the necessary info in a `newdeltatext'
8236*86d7f5d3SJohn Marino    field for RCSVers nodes. -twp */
8237*86d7f5d3SJohn Marino 
8238*86d7f5d3SJohn Marino /* Copy delta text nodes from FIN to FOUT.  If NEWDTEXT is non-NULL, it
8239*86d7f5d3SJohn Marino    is a new delta text node, and should be added to the tree at the
8240*86d7f5d3SJohn Marino    node whose revision number is INSERTPT.  (Note that trunk nodes are
8241*86d7f5d3SJohn Marino    written in decreasing order, and branch nodes are written in
8242*86d7f5d3SJohn Marino    increasing order.) */
8243*86d7f5d3SJohn Marino static void
RCS_copydeltas(RCSNode * rcs,FILE * fin,struct rcsbuffer * rcsbufin,FILE * fout,Deltatext * newdtext,char * insertpt)8244*86d7f5d3SJohn Marino RCS_copydeltas (RCSNode *rcs, FILE *fin, struct rcsbuffer *rcsbufin,
8245*86d7f5d3SJohn Marino 		FILE *fout, Deltatext *newdtext, char *insertpt)
8246*86d7f5d3SJohn Marino {
8247*86d7f5d3SJohn Marino     int actions;
8248*86d7f5d3SJohn Marino     RCSVers *dadmin;
8249*86d7f5d3SJohn Marino     Node *np;
8250*86d7f5d3SJohn Marino     int insertbefore, found;
8251*86d7f5d3SJohn Marino     char *bufrest;
8252*86d7f5d3SJohn Marino     int nls;
8253*86d7f5d3SJohn Marino     size_t buflen;
8254*86d7f5d3SJohn Marino #ifndef HAVE_MMAP
8255*86d7f5d3SJohn Marino     char buf[8192];
8256*86d7f5d3SJohn Marino     int got;
8257*86d7f5d3SJohn Marino #endif
8258*86d7f5d3SJohn Marino 
8259*86d7f5d3SJohn Marino     /* Count the number of versions for which we have to do some
8260*86d7f5d3SJohn Marino        special operation.  */
8261*86d7f5d3SJohn Marino     actions = walklist (rcs->versions, count_delta_actions, NULL);
8262*86d7f5d3SJohn Marino 
8263*86d7f5d3SJohn Marino     /* Make a note of whether NEWDTEXT should be inserted
8264*86d7f5d3SJohn Marino        before or after its INSERTPT. */
8265*86d7f5d3SJohn Marino     insertbefore = (newdtext != NULL && numdots (newdtext->version) == 1);
8266*86d7f5d3SJohn Marino 
8267*86d7f5d3SJohn Marino     while (actions != 0 || newdtext != NULL)
8268*86d7f5d3SJohn Marino     {
8269*86d7f5d3SJohn Marino 	Deltatext *dtext;
8270*86d7f5d3SJohn Marino 
8271*86d7f5d3SJohn Marino 	dtext = RCS_getdeltatext (rcs, fin, rcsbufin);
8272*86d7f5d3SJohn Marino 
8273*86d7f5d3SJohn Marino 	/* We shouldn't hit EOF here, because that would imply that
8274*86d7f5d3SJohn Marino            some action was not taken, or that we could not insert
8275*86d7f5d3SJohn Marino            NEWDTEXT.  */
8276*86d7f5d3SJohn Marino 	if (dtext == NULL)
8277*86d7f5d3SJohn Marino 	    error (1, 0, "internal error: EOF too early in RCS_copydeltas");
8278*86d7f5d3SJohn Marino 
8279*86d7f5d3SJohn Marino 	found = (insertpt != NULL && STREQ (dtext->version, insertpt));
8280*86d7f5d3SJohn Marino 	if (found && insertbefore)
8281*86d7f5d3SJohn Marino 	{
8282*86d7f5d3SJohn Marino 	    putdeltatext (fout, newdtext);
8283*86d7f5d3SJohn Marino 	    newdtext = NULL;
8284*86d7f5d3SJohn Marino 	    insertpt = NULL;
8285*86d7f5d3SJohn Marino 	}
8286*86d7f5d3SJohn Marino 
8287*86d7f5d3SJohn Marino 	np = findnode (rcs->versions, dtext->version);
8288*86d7f5d3SJohn Marino 	dadmin = np->data;
8289*86d7f5d3SJohn Marino 
8290*86d7f5d3SJohn Marino 	/* If this revision has been outdated, just skip it. */
8291*86d7f5d3SJohn Marino 	if (dadmin->outdated)
8292*86d7f5d3SJohn Marino 	{
8293*86d7f5d3SJohn Marino 	    freedeltatext (dtext);
8294*86d7f5d3SJohn Marino 	    --actions;
8295*86d7f5d3SJohn Marino 	    continue;
8296*86d7f5d3SJohn Marino 	}
8297*86d7f5d3SJohn Marino 
8298*86d7f5d3SJohn Marino 	/* Update the change text for this delta.  New change text
8299*86d7f5d3SJohn Marino 	   data may come from cvs admin -m, cvs admin -o, or cvs ci. */
8300*86d7f5d3SJohn Marino 	if (dadmin->text != NULL)
8301*86d7f5d3SJohn Marino 	{
8302*86d7f5d3SJohn Marino 	    if (dadmin->text->log != NULL || dadmin->text->text != NULL)
8303*86d7f5d3SJohn Marino 		--actions;
8304*86d7f5d3SJohn Marino 	    if (dadmin->text->log != NULL)
8305*86d7f5d3SJohn Marino 	    {
8306*86d7f5d3SJohn Marino 		free (dtext->log);
8307*86d7f5d3SJohn Marino 		dtext->log = dadmin->text->log;
8308*86d7f5d3SJohn Marino 		dadmin->text->log = NULL;
8309*86d7f5d3SJohn Marino 	    }
8310*86d7f5d3SJohn Marino 	    if (dadmin->text->text != NULL)
8311*86d7f5d3SJohn Marino 	    {
8312*86d7f5d3SJohn Marino 		free (dtext->text);
8313*86d7f5d3SJohn Marino 		dtext->text = dadmin->text->text;
8314*86d7f5d3SJohn Marino 		dtext->len = dadmin->text->len;
8315*86d7f5d3SJohn Marino 		dadmin->text->text = NULL;
8316*86d7f5d3SJohn Marino 	    }
8317*86d7f5d3SJohn Marino 	}
8318*86d7f5d3SJohn Marino 	putdeltatext (fout, dtext);
8319*86d7f5d3SJohn Marino 	freedeltatext (dtext);
8320*86d7f5d3SJohn Marino 
8321*86d7f5d3SJohn Marino 	if (found && !insertbefore)
8322*86d7f5d3SJohn Marino 	{
8323*86d7f5d3SJohn Marino 	    putdeltatext (fout, newdtext);
8324*86d7f5d3SJohn Marino 	    newdtext = NULL;
8325*86d7f5d3SJohn Marino 	    insertpt = NULL;
8326*86d7f5d3SJohn Marino 	}
8327*86d7f5d3SJohn Marino     }
8328*86d7f5d3SJohn Marino 
8329*86d7f5d3SJohn Marino     /* Copy the rest of the file directly, without bothering to
8330*86d7f5d3SJohn Marino        interpret it.  The caller will handle error checking by calling
8331*86d7f5d3SJohn Marino        ferror.
8332*86d7f5d3SJohn Marino 
8333*86d7f5d3SJohn Marino        We just wrote a newline to the file, either in putdeltatext or
8334*86d7f5d3SJohn Marino        in the caller.  However, we may not have read the corresponding
8335*86d7f5d3SJohn Marino        newline from the file, because rcsbuf_getkey returns as soon as
8336*86d7f5d3SJohn Marino        it finds the end of the '@' string for the desc or text key.
8337*86d7f5d3SJohn Marino        Therefore, we may read three newlines when we should really
8338*86d7f5d3SJohn Marino        only write two, and we check for that case here.  This is not
8339*86d7f5d3SJohn Marino        an semantically important issue; we only do it to make our RCS
8340*86d7f5d3SJohn Marino        files look traditional.  */
8341*86d7f5d3SJohn Marino 
8342*86d7f5d3SJohn Marino     nls = 3;
8343*86d7f5d3SJohn Marino 
8344*86d7f5d3SJohn Marino     rcsbuf_get_buffered (rcsbufin, &bufrest, &buflen);
8345*86d7f5d3SJohn Marino     if (buflen > 0)
8346*86d7f5d3SJohn Marino     {
8347*86d7f5d3SJohn Marino 	if (bufrest[0] != '\n'
8348*86d7f5d3SJohn Marino 	    || strncmp (bufrest, "\n\n\n", buflen < 3 ? buflen : 3) != 0)
8349*86d7f5d3SJohn Marino 	{
8350*86d7f5d3SJohn Marino 	    nls = 0;
8351*86d7f5d3SJohn Marino 	}
8352*86d7f5d3SJohn Marino 	else
8353*86d7f5d3SJohn Marino 	{
8354*86d7f5d3SJohn Marino 	    if (buflen < 3)
8355*86d7f5d3SJohn Marino 		nls -= buflen;
8356*86d7f5d3SJohn Marino 	    else
8357*86d7f5d3SJohn Marino 	    {
8358*86d7f5d3SJohn Marino 		++bufrest;
8359*86d7f5d3SJohn Marino 		--buflen;
8360*86d7f5d3SJohn Marino 		nls = 0;
8361*86d7f5d3SJohn Marino 	    }
8362*86d7f5d3SJohn Marino 	}
8363*86d7f5d3SJohn Marino 
8364*86d7f5d3SJohn Marino 	fwrite (bufrest, 1, buflen, fout);
8365*86d7f5d3SJohn Marino     }
8366*86d7f5d3SJohn Marino #ifndef HAVE_MMAP
8367*86d7f5d3SJohn Marino     /* This bit isn't necessary when using mmap since the entire file
8368*86d7f5d3SJohn Marino      * will already be available via the RCS buffer.  Besides, the
8369*86d7f5d3SJohn Marino      * mmap code doesn't always keep the file pointer up to date, so
8370*86d7f5d3SJohn Marino      * this adds some data twice.
8371*86d7f5d3SJohn Marino      */
8372*86d7f5d3SJohn Marino     while ((got = fread (buf, 1, sizeof buf, fin)) != 0)
8373*86d7f5d3SJohn Marino     {
8374*86d7f5d3SJohn Marino 	if (nls > 0
8375*86d7f5d3SJohn Marino 	    && got >= nls
8376*86d7f5d3SJohn Marino 	    && buf[0] == '\n'
8377*86d7f5d3SJohn Marino 	    && strncmp (buf, "\n\n\n", nls) == 0)
8378*86d7f5d3SJohn Marino 	{
8379*86d7f5d3SJohn Marino 	    fwrite (buf + 1, 1, got - 1, fout);
8380*86d7f5d3SJohn Marino 	}
8381*86d7f5d3SJohn Marino 	else
8382*86d7f5d3SJohn Marino 	{
8383*86d7f5d3SJohn Marino 	    fwrite (buf, 1, got, fout);
8384*86d7f5d3SJohn Marino 	}
8385*86d7f5d3SJohn Marino 
8386*86d7f5d3SJohn Marino 	nls = 0;
8387*86d7f5d3SJohn Marino     }
8388*86d7f5d3SJohn Marino #endif /* HAVE_MMAP */
8389*86d7f5d3SJohn Marino }
8390*86d7f5d3SJohn Marino 
8391*86d7f5d3SJohn Marino 
8392*86d7f5d3SJohn Marino 
8393*86d7f5d3SJohn Marino /* A helper procedure for RCS_copydeltas.  This is called via walklist
8394*86d7f5d3SJohn Marino    to count the number of RCS revisions for which some special action
8395*86d7f5d3SJohn Marino    is required.  */
8396*86d7f5d3SJohn Marino static int
count_delta_actions(Node * np,void * ignore)8397*86d7f5d3SJohn Marino count_delta_actions (Node *np, void *ignore)
8398*86d7f5d3SJohn Marino {
8399*86d7f5d3SJohn Marino     RCSVers *dadmin = np->data;
8400*86d7f5d3SJohn Marino 
8401*86d7f5d3SJohn Marino     if (dadmin->outdated)
8402*86d7f5d3SJohn Marino 	return 1;
8403*86d7f5d3SJohn Marino 
8404*86d7f5d3SJohn Marino     if (dadmin->text != NULL
8405*86d7f5d3SJohn Marino 	&& (dadmin->text->log != NULL || dadmin->text->text != NULL))
8406*86d7f5d3SJohn Marino     {
8407*86d7f5d3SJohn Marino 	return 1;
8408*86d7f5d3SJohn Marino     }
8409*86d7f5d3SJohn Marino 
8410*86d7f5d3SJohn Marino     return 0;
8411*86d7f5d3SJohn Marino }
8412*86d7f5d3SJohn Marino 
8413*86d7f5d3SJohn Marino 
8414*86d7f5d3SJohn Marino 
8415*86d7f5d3SJohn Marino /*
8416*86d7f5d3SJohn Marino  * Clean up temporary files.
8417*86d7f5d3SJohn Marino  *
8418*86d7f5d3SJohn Marino  * NOTES
8419*86d7f5d3SJohn Marino  *   This function needs to be reentrant since a call to exit() can cause a
8420*86d7f5d3SJohn Marino  *   call to this function, which can then be interrupted by a signal, which
8421*86d7f5d3SJohn Marino  *   can cause a second call to this function.
8422*86d7f5d3SJohn Marino  *
8423*86d7f5d3SJohn Marino  * RETURNS
8424*86d7f5d3SJohn Marino  *   Nothing.
8425*86d7f5d3SJohn Marino  */
8426*86d7f5d3SJohn Marino static void
rcs_cleanup(void)8427*86d7f5d3SJohn Marino rcs_cleanup (void)
8428*86d7f5d3SJohn Marino {
8429*86d7f5d3SJohn Marino     TRACE (TRACE_FUNCTION, "rcs_cleanup()");
8430*86d7f5d3SJohn Marino 
8431*86d7f5d3SJohn Marino     /* FIXME: Do not perform buffered I/O from an interrupt handler like
8432*86d7f5d3SJohn Marino      * this (via error).  However, I'm leaving the error-calling code there
8433*86d7f5d3SJohn Marino      * in the hope that on the rare occasion the error call is actually made
8434*86d7f5d3SJohn Marino      * (e.g., a fluky I/O error or permissions problem prevents the deletion
8435*86d7f5d3SJohn Marino      * of a just-created file) reentrancy won't be an issue.
8436*86d7f5d3SJohn Marino      */
8437*86d7f5d3SJohn Marino 
8438*86d7f5d3SJohn Marino     /* We don't want to be interrupted during calls which set globals to NULL,
8439*86d7f5d3SJohn Marino      * but we know that by the time we reach this function, interrupts have
8440*86d7f5d3SJohn Marino      * already been blocked.
8441*86d7f5d3SJohn Marino      */
8442*86d7f5d3SJohn Marino     if (rcs_lockfile != NULL)
8443*86d7f5d3SJohn Marino     {
8444*86d7f5d3SJohn Marino 	/* Use a tmp var since any of these functions could call exit, causing
8445*86d7f5d3SJohn Marino 	 * us to be called a second time.
8446*86d7f5d3SJohn Marino 	 */
8447*86d7f5d3SJohn Marino 	char *tmp = rcs_lockfile;
8448*86d7f5d3SJohn Marino 	rcs_lockfile = NULL;
8449*86d7f5d3SJohn Marino 	if (rcs_lockfd >= 0)
8450*86d7f5d3SJohn Marino 	{
8451*86d7f5d3SJohn Marino 	    if (close (rcs_lockfd) != 0)
8452*86d7f5d3SJohn Marino 		error (0, errno, "error closing lock file %s", tmp);
8453*86d7f5d3SJohn Marino 	    rcs_lockfd = -1;
8454*86d7f5d3SJohn Marino 	}
8455*86d7f5d3SJohn Marino 
8456*86d7f5d3SJohn Marino 	/* Note that the checks for existence_error are because we can be
8457*86d7f5d3SJohn Marino 	 * called from a signal handler, so we don't know whether the
8458*86d7f5d3SJohn Marino 	 * files got created.
8459*86d7f5d3SJohn Marino 	 */
8460*86d7f5d3SJohn Marino 	if (unlink_file (tmp) < 0
8461*86d7f5d3SJohn Marino 	    && !existence_error (errno))
8462*86d7f5d3SJohn Marino 	    error (0, errno, "cannot remove %s", tmp);
8463*86d7f5d3SJohn Marino     }
8464*86d7f5d3SJohn Marino }
8465*86d7f5d3SJohn Marino 
8466*86d7f5d3SJohn Marino 
8467*86d7f5d3SJohn Marino 
8468*86d7f5d3SJohn Marino /* RCS_internal_lockfile and RCS_internal_unlockfile perform RCS-style
8469*86d7f5d3SJohn Marino    locking on the specified RCSFILE: for a file called `foo,v', open
8470*86d7f5d3SJohn Marino    for writing a file called `,foo,'.
8471*86d7f5d3SJohn Marino 
8472*86d7f5d3SJohn Marino    Note that we what do here is quite different from what RCS does.
8473*86d7f5d3SJohn Marino    RCS creates the ,foo, file before it reads the RCS file (if it
8474*86d7f5d3SJohn Marino    knows that it will be writing later), so that it actually serves as
8475*86d7f5d3SJohn Marino    a lock.  We don't; instead we rely on CVS writelocks.  This means
8476*86d7f5d3SJohn Marino    that if someone is running RCS on the file at the same time they
8477*86d7f5d3SJohn Marino    are running CVS on it, they might lose (we read the file,
8478*86d7f5d3SJohn Marino    then RCS writes it, then we write it, clobbering the
8479*86d7f5d3SJohn Marino    changes made by RCS).  I believe the current sentiment about this
8480*86d7f5d3SJohn Marino    is "well, don't do that".
8481*86d7f5d3SJohn Marino 
8482*86d7f5d3SJohn Marino    A concern has been expressed about whether adopting the RCS
8483*86d7f5d3SJohn Marino    strategy would slow us down.  I don't think so, since we need to
8484*86d7f5d3SJohn Marino    write the ,foo, file anyway (unless perhaps if O_EXCL is slower or
8485*86d7f5d3SJohn Marino    something).
8486*86d7f5d3SJohn Marino 
8487*86d7f5d3SJohn Marino    These do not perform quite the same function as the RCS -l option
8488*86d7f5d3SJohn Marino    for locking files: they are intended to prevent competing RCS
8489*86d7f5d3SJohn Marino    processes from stomping all over each other's laundry.  Hence,
8490*86d7f5d3SJohn Marino    they are `internal' locking functions.
8491*86d7f5d3SJohn Marino 
8492*86d7f5d3SJohn Marino    If there is an error, give a fatal error; if we return we always
8493*86d7f5d3SJohn Marino    return a non-NULL value.  */
8494*86d7f5d3SJohn Marino static FILE *
rcs_internal_lockfile(char * rcsfile)8495*86d7f5d3SJohn Marino rcs_internal_lockfile (char *rcsfile)
8496*86d7f5d3SJohn Marino {
8497*86d7f5d3SJohn Marino     struct stat rstat;
8498*86d7f5d3SJohn Marino     FILE *fp;
8499*86d7f5d3SJohn Marino     static int first_call = 1;
8500*86d7f5d3SJohn Marino 
8501*86d7f5d3SJohn Marino     if (first_call)
8502*86d7f5d3SJohn Marino     {
8503*86d7f5d3SJohn Marino 	first_call = 0;
8504*86d7f5d3SJohn Marino 	/* Clean up if we get a signal or exit.  */
8505*86d7f5d3SJohn Marino 	cleanup_register (rcs_cleanup);
8506*86d7f5d3SJohn Marino     }
8507*86d7f5d3SJohn Marino 
8508*86d7f5d3SJohn Marino     /* Get the lock file name: `,file,' for RCS file `file,v'. */
8509*86d7f5d3SJohn Marino     assert (rcs_lockfile == NULL);
8510*86d7f5d3SJohn Marino     assert (rcs_lockfd < 0);
8511*86d7f5d3SJohn Marino     rcs_lockfile = rcs_lockfilename (rcsfile);
8512*86d7f5d3SJohn Marino 
8513*86d7f5d3SJohn Marino     /* Use the existing RCS file mode, or read-only if this is a new
8514*86d7f5d3SJohn Marino        file.  (Really, this is a lie -- if this is a new file,
8515*86d7f5d3SJohn Marino        RCS_checkin uses the permissions from the working copy.  For
8516*86d7f5d3SJohn Marino        actually creating the file, we use 0444 as a safe default mode.) */
8517*86d7f5d3SJohn Marino     if (stat (rcsfile, &rstat) < 0)
8518*86d7f5d3SJohn Marino     {
8519*86d7f5d3SJohn Marino 	if (existence_error (errno))
8520*86d7f5d3SJohn Marino 	    rstat.st_mode = S_IRUSR | S_IRGRP | S_IROTH;
8521*86d7f5d3SJohn Marino 	else
8522*86d7f5d3SJohn Marino 	    error (1, errno, "cannot stat %s", rcsfile);
8523*86d7f5d3SJohn Marino     }
8524*86d7f5d3SJohn Marino 
8525*86d7f5d3SJohn Marino     /* Try to open exclusively.  POSIX.1 guarantees that O_EXCL|O_CREAT
8526*86d7f5d3SJohn Marino        guarantees an exclusive open.  According to the RCS source, with
8527*86d7f5d3SJohn Marino        NFS v2 we must also throw in O_TRUNC and use an open mask that makes
8528*86d7f5d3SJohn Marino        the file unwriteable.  For extensive justification, see the comments for
8529*86d7f5d3SJohn Marino        rcswriteopen() in rcsedit.c, in RCS 5.7.  This is kind of pointless
8530*86d7f5d3SJohn Marino        in the CVS case; see comment at the start of this file concerning
8531*86d7f5d3SJohn Marino        general ,foo, file strategy.
8532*86d7f5d3SJohn Marino 
8533*86d7f5d3SJohn Marino        There is some sentiment that with NFSv3 and such, that one can
8534*86d7f5d3SJohn Marino        rely on O_EXCL these days.  This might be true for unix (I
8535*86d7f5d3SJohn Marino        don't really know), but I am still pretty skeptical in the case
8536*86d7f5d3SJohn Marino        of the non-unix systems.  */
8537*86d7f5d3SJohn Marino     rcs_lockfd = open (rcs_lockfile,
8538*86d7f5d3SJohn Marino 		       OPEN_BINARY | O_WRONLY | O_CREAT | O_EXCL | O_TRUNC,
8539*86d7f5d3SJohn Marino 		       S_IRUSR | S_IRGRP | S_IROTH);
8540*86d7f5d3SJohn Marino 
8541*86d7f5d3SJohn Marino     if (rcs_lockfd < 0)
8542*86d7f5d3SJohn Marino     {
8543*86d7f5d3SJohn Marino 	error (1, errno, "could not open lock file `%s'", rcs_lockfile);
8544*86d7f5d3SJohn Marino     }
8545*86d7f5d3SJohn Marino 
8546*86d7f5d3SJohn Marino     /* Force the file permissions, and return a stream object. */
8547*86d7f5d3SJohn Marino     /* Because we change the modes later, we don't worry about
8548*86d7f5d3SJohn Marino        this in the non-HAVE_FCHMOD case.  */
8549*86d7f5d3SJohn Marino #ifdef HAVE_FCHMOD
8550*86d7f5d3SJohn Marino     if (fchmod (rcs_lockfd, rstat.st_mode) < 0)
8551*86d7f5d3SJohn Marino 	error (1, errno, "cannot change mode for %s", rcs_lockfile);
8552*86d7f5d3SJohn Marino #endif
8553*86d7f5d3SJohn Marino     fp = fdopen (rcs_lockfd, FOPEN_BINARY_WRITE);
8554*86d7f5d3SJohn Marino     if (fp == NULL)
8555*86d7f5d3SJohn Marino 	error (1, errno, "cannot fdopen %s", rcs_lockfile);
8556*86d7f5d3SJohn Marino 
8557*86d7f5d3SJohn Marino     return fp;
8558*86d7f5d3SJohn Marino }
8559*86d7f5d3SJohn Marino 
8560*86d7f5d3SJohn Marino 
8561*86d7f5d3SJohn Marino 
8562*86d7f5d3SJohn Marino static void
rcs_internal_unlockfile(FILE * fp,char * rcsfile)8563*86d7f5d3SJohn Marino rcs_internal_unlockfile (FILE *fp, char *rcsfile)
8564*86d7f5d3SJohn Marino {
8565*86d7f5d3SJohn Marino     assert (rcs_lockfile != NULL);
8566*86d7f5d3SJohn Marino     assert (rcs_lockfd >= 0);
8567*86d7f5d3SJohn Marino 
8568*86d7f5d3SJohn Marino     /* Abort if we could not write everything successfully to LOCKFILE.
8569*86d7f5d3SJohn Marino        This is not a great error-handling mechanism, but should prevent
8570*86d7f5d3SJohn Marino        corrupting the repository. */
8571*86d7f5d3SJohn Marino 
8572*86d7f5d3SJohn Marino     if (ferror (fp))
8573*86d7f5d3SJohn Marino 	/* Using errno here may well be misleanding since the most recent
8574*86d7f5d3SJohn Marino 	   call that set errno may not have anything whatsoever to do with
8575*86d7f5d3SJohn Marino 	   the error that set the flag, but it's better than nothing.  The
8576*86d7f5d3SJohn Marino 	   real solution is to check each call to fprintf rather than waiting
8577*86d7f5d3SJohn Marino 	   until the end like this.  */
8578*86d7f5d3SJohn Marino 	error (1, errno, "error writing to lock file %s", rcs_lockfile);
8579*86d7f5d3SJohn Marino 
8580*86d7f5d3SJohn Marino     /* Flush and sync the file, or the user may be told the commit completed,
8581*86d7f5d3SJohn Marino      * while a server crash/power failure could still cause the data to be
8582*86d7f5d3SJohn Marino      * lost.
8583*86d7f5d3SJohn Marino      *
8584*86d7f5d3SJohn Marino      * Invoking rename(",<file>," , "<file>,v") on Linux and almost all UNIXs
8585*86d7f5d3SJohn Marino      * only flushes the inode for the target file to disk, it does not
8586*86d7f5d3SJohn Marino      * guarantee flush of the kernel buffers allocated for the ,<file>,.
8587*86d7f5d3SJohn Marino      * Depending upon the load on the machine, the Linux kernel's flush daemon
8588*86d7f5d3SJohn Marino      * process may not flush for a while.  In the meantime the CVS transaction
8589*86d7f5d3SJohn Marino      * could have been declared committed to the end CVS user (CVS process has
8590*86d7f5d3SJohn Marino      * returned the final "OK").  If the machine crashes prior to syncing the
8591*86d7f5d3SJohn Marino      * changes to disk, the committed transaction can be lost.
8592*86d7f5d3SJohn Marino      */
8593*86d7f5d3SJohn Marino     if (fflush (fp) != 0)
8594*86d7f5d3SJohn Marino 	error (1, errno, "error flushing file `%s' to kernel buffers",
8595*86d7f5d3SJohn Marino 	       rcs_lockfile);
8596*86d7f5d3SJohn Marino #ifdef HAVE_FSYNC
8597*86d7f5d3SJohn Marino     if (fsync (rcs_lockfd) < 0)
8598*86d7f5d3SJohn Marino 	error (1, errno, "error fsyncing file `%s'", rcs_lockfile);
8599*86d7f5d3SJohn Marino #endif
8600*86d7f5d3SJohn Marino 
8601*86d7f5d3SJohn Marino     if (fclose (fp) == EOF)
8602*86d7f5d3SJohn Marino 	error (1, errno, "error closing lock file %s", rcs_lockfile);
8603*86d7f5d3SJohn Marino     rcs_lockfd = -1;
8604*86d7f5d3SJohn Marino 
8605*86d7f5d3SJohn Marino     rename_file (rcs_lockfile, rcsfile);
8606*86d7f5d3SJohn Marino 
8607*86d7f5d3SJohn Marino     {
8608*86d7f5d3SJohn Marino 	/* Use a temporary to make sure there's no interval
8609*86d7f5d3SJohn Marino 	   (after rcs_lockfile has been freed but before it's set to NULL)
8610*86d7f5d3SJohn Marino 	   during which the signal handler's use of rcs_lockfile would
8611*86d7f5d3SJohn Marino 	   reference freed memory.  */
8612*86d7f5d3SJohn Marino 	char *tmp = rcs_lockfile;
8613*86d7f5d3SJohn Marino 	rcs_lockfile = NULL;
8614*86d7f5d3SJohn Marino 	free (tmp);
8615*86d7f5d3SJohn Marino     }
8616*86d7f5d3SJohn Marino }
8617*86d7f5d3SJohn Marino 
8618*86d7f5d3SJohn Marino 
8619*86d7f5d3SJohn Marino 
8620*86d7f5d3SJohn Marino static char *
rcs_lockfilename(const char * rcsfile)8621*86d7f5d3SJohn Marino rcs_lockfilename (const char *rcsfile)
8622*86d7f5d3SJohn Marino {
8623*86d7f5d3SJohn Marino     char *lockfile, *lockp;
8624*86d7f5d3SJohn Marino     const char *rcsbase, *rcsp, *rcsend;
8625*86d7f5d3SJohn Marino     int rcslen;
8626*86d7f5d3SJohn Marino 
8627*86d7f5d3SJohn Marino     /* Create the lockfile name. */
8628*86d7f5d3SJohn Marino     rcslen = strlen (rcsfile);
8629*86d7f5d3SJohn Marino     lockfile = xmalloc (rcslen + 10);
8630*86d7f5d3SJohn Marino     rcsbase = last_component (rcsfile);
8631*86d7f5d3SJohn Marino     rcsend = rcsfile + rcslen - sizeof(RCSEXT);
8632*86d7f5d3SJohn Marino     for (lockp = lockfile, rcsp = rcsfile; rcsp < rcsbase; ++rcsp)
8633*86d7f5d3SJohn Marino 	*lockp++ = *rcsp;
8634*86d7f5d3SJohn Marino     *lockp++ = ',';
8635*86d7f5d3SJohn Marino     while (rcsp <= rcsend)
8636*86d7f5d3SJohn Marino 	*lockp++ = *rcsp++;
8637*86d7f5d3SJohn Marino     *lockp++ = ',';
8638*86d7f5d3SJohn Marino     *lockp = '\0';
8639*86d7f5d3SJohn Marino 
8640*86d7f5d3SJohn Marino     return lockfile;
8641*86d7f5d3SJohn Marino }
8642*86d7f5d3SJohn Marino 
8643*86d7f5d3SJohn Marino 
8644*86d7f5d3SJohn Marino 
8645*86d7f5d3SJohn Marino /* Rewrite an RCS file.  The basic idea here is that the caller should
8646*86d7f5d3SJohn Marino    first call RCS_reparsercsfile, then munge the data structures as
8647*86d7f5d3SJohn Marino    desired (via RCS_delete_revs, RCS_settag, &c), then call RCS_rewrite.  */
8648*86d7f5d3SJohn Marino void
RCS_rewrite(RCSNode * rcs,Deltatext * newdtext,char * insertpt)8649*86d7f5d3SJohn Marino RCS_rewrite (RCSNode *rcs, Deltatext *newdtext, char *insertpt)
8650*86d7f5d3SJohn Marino {
8651*86d7f5d3SJohn Marino     FILE *fin, *fout;
8652*86d7f5d3SJohn Marino     struct rcsbuffer rcsbufin;
8653*86d7f5d3SJohn Marino 
8654*86d7f5d3SJohn Marino     if (noexec)
8655*86d7f5d3SJohn Marino 	return;
8656*86d7f5d3SJohn Marino 
8657*86d7f5d3SJohn Marino     /* Make sure we're operating on an actual file and not a symlink.  */
8658*86d7f5d3SJohn Marino     resolve_symlink (&(rcs->path));
8659*86d7f5d3SJohn Marino 
8660*86d7f5d3SJohn Marino     fout = rcs_internal_lockfile (rcs->path);
8661*86d7f5d3SJohn Marino 
8662*86d7f5d3SJohn Marino     RCS_putadmin (rcs, fout);
8663*86d7f5d3SJohn Marino     RCS_putdtree (rcs, rcs->head, fout);
8664*86d7f5d3SJohn Marino     RCS_putdesc (rcs, fout);
8665*86d7f5d3SJohn Marino 
8666*86d7f5d3SJohn Marino     /* Open the original RCS file and seek to the first delta text. */
8667*86d7f5d3SJohn Marino     rcsbuf_cache_open (rcs, rcs->delta_pos, &fin, &rcsbufin);
8668*86d7f5d3SJohn Marino 
8669*86d7f5d3SJohn Marino     /* Update delta_pos to the current position in the output file.
8670*86d7f5d3SJohn Marino        Do NOT move these statements: they must be done after fin has
8671*86d7f5d3SJohn Marino        been positioned at the old delta_pos, but before any delta
8672*86d7f5d3SJohn Marino        texts have been written to fout.
8673*86d7f5d3SJohn Marino      */
8674*86d7f5d3SJohn Marino     rcs->delta_pos = ftello (fout);
8675*86d7f5d3SJohn Marino     if (rcs->delta_pos == -1)
8676*86d7f5d3SJohn Marino 	error (1, errno, "cannot ftello in RCS file %s", rcs->path);
8677*86d7f5d3SJohn Marino 
8678*86d7f5d3SJohn Marino     RCS_copydeltas (rcs, fin, &rcsbufin, fout, newdtext, insertpt);
8679*86d7f5d3SJohn Marino 
8680*86d7f5d3SJohn Marino     /* We don't want to call rcsbuf_cache here, since we're about to
8681*86d7f5d3SJohn Marino        delete the file.  */
8682*86d7f5d3SJohn Marino     rcsbuf_close (&rcsbufin);
8683*86d7f5d3SJohn Marino     if (ferror (fin))
8684*86d7f5d3SJohn Marino 	/* The only case in which using errno here would be meaningful
8685*86d7f5d3SJohn Marino 	   is if we happen to have left errno unmolested since the call
8686*86d7f5d3SJohn Marino 	   which produced the error (e.g. fread).  That is pretty
8687*86d7f5d3SJohn Marino 	   fragile even if it happens to sometimes be true.  The real
8688*86d7f5d3SJohn Marino 	   solution is to make sure that all the code which reads
8689*86d7f5d3SJohn Marino 	   from fin checks for errors itself (some does, some doesn't).  */
8690*86d7f5d3SJohn Marino 	error (0, 0, "warning: ferror set while rewriting RCS file `%s'", rcs->path);
8691*86d7f5d3SJohn Marino     if (fclose (fin) < 0)
8692*86d7f5d3SJohn Marino 	error (0, errno, "warning: closing RCS file `%s'", rcs->path);
8693*86d7f5d3SJohn Marino 
8694*86d7f5d3SJohn Marino     rcs_internal_unlockfile (fout, rcs->path);
8695*86d7f5d3SJohn Marino }
8696*86d7f5d3SJohn Marino 
8697*86d7f5d3SJohn Marino 
8698*86d7f5d3SJohn Marino 
8699*86d7f5d3SJohn Marino /* Abandon changes to an RCS file. */
8700*86d7f5d3SJohn Marino void
RCS_abandon(RCSNode * rcs)8701*86d7f5d3SJohn Marino RCS_abandon (RCSNode *rcs)
8702*86d7f5d3SJohn Marino {
8703*86d7f5d3SJohn Marino     free_rcsnode_contents (rcs);
8704*86d7f5d3SJohn Marino     rcs->symbols_data = NULL;
8705*86d7f5d3SJohn Marino     rcs->expand = NULL;
8706*86d7f5d3SJohn Marino     rcs->access = NULL;
8707*86d7f5d3SJohn Marino     rcs->locks_data = NULL;
8708*86d7f5d3SJohn Marino     rcs->comment = NULL;
8709*86d7f5d3SJohn Marino     rcs->desc = NULL;
8710*86d7f5d3SJohn Marino     rcs->flags |= PARTIAL;
8711*86d7f5d3SJohn Marino }
8712*86d7f5d3SJohn Marino 
8713*86d7f5d3SJohn Marino 
8714*86d7f5d3SJohn Marino 
8715*86d7f5d3SJohn Marino /*
8716*86d7f5d3SJohn Marino  * For a given file with full pathname PATH and revision number REV,
8717*86d7f5d3SJohn Marino  * produce a file label suitable for passing to diff.  The default
8718*86d7f5d3SJohn Marino  * file label as used by RCS 5.7 looks like this:
8719*86d7f5d3SJohn Marino  *
8720*86d7f5d3SJohn Marino  *	FILENAME <tab> YYYY/MM/DD <sp> HH:MM:SS <tab> REVNUM
8721*86d7f5d3SJohn Marino  *
8722*86d7f5d3SJohn Marino  * The date and time used are the revision's last checkin date and time.
8723*86d7f5d3SJohn Marino  * If REV is NULL, use the working copy's mtime instead.
8724*86d7f5d3SJohn Marino  *
8725*86d7f5d3SJohn Marino  * /dev/null is not statted but assumed to have been created on the Epoch.
8726*86d7f5d3SJohn Marino  * At least using the POSIX.2 definition of patch, this should cause creation
8727*86d7f5d3SJohn Marino  * of files on platforms such as Windoze where the null IO device isn't named
8728*86d7f5d3SJohn Marino  * /dev/null to be parsed by patch properly.
8729*86d7f5d3SJohn Marino  */
8730*86d7f5d3SJohn Marino char *
make_file_label(const char * path,const char * rev,RCSNode * rcs)8731*86d7f5d3SJohn Marino make_file_label (const char *path, const char *rev, RCSNode *rcs)
8732*86d7f5d3SJohn Marino {
8733*86d7f5d3SJohn Marino     char datebuf[MAXDATELEN + 1];
8734*86d7f5d3SJohn Marino     char *label;
8735*86d7f5d3SJohn Marino 
8736*86d7f5d3SJohn Marino     if (rev)
8737*86d7f5d3SJohn Marino     {
8738*86d7f5d3SJohn Marino 	char date[MAXDATELEN + 1];
8739*86d7f5d3SJohn Marino 	/* revs cannot be attached to /dev/null ... duh. */
8740*86d7f5d3SJohn Marino 	assert (strcmp(DEVNULL, path));
8741*86d7f5d3SJohn Marino 	RCS_getrevtime (rcs, rev, datebuf, 0);
8742*86d7f5d3SJohn Marino 	(void) date_to_internet (date, datebuf);
8743*86d7f5d3SJohn Marino 	label = Xasprintf ("-L%s\t%s\t%s", path, date, rev);
8744*86d7f5d3SJohn Marino     }
8745*86d7f5d3SJohn Marino     else
8746*86d7f5d3SJohn Marino     {
8747*86d7f5d3SJohn Marino 	struct stat sb;
8748*86d7f5d3SJohn Marino 	struct tm *wm;
8749*86d7f5d3SJohn Marino 
8750*86d7f5d3SJohn Marino 	if (strcmp(DEVNULL, path))
8751*86d7f5d3SJohn Marino 	{
8752*86d7f5d3SJohn Marino 	    const char *file = last_component (path);
8753*86d7f5d3SJohn Marino 	    if (stat (file, &sb) < 0)
8754*86d7f5d3SJohn Marino 		/* Assume that if the stat fails,then the later read for the
8755*86d7f5d3SJohn Marino 		 * diff will too.
8756*86d7f5d3SJohn Marino 		 */
8757*86d7f5d3SJohn Marino 		error (1, errno, "could not get info for `%s'", path);
8758*86d7f5d3SJohn Marino 	    wm = gmtime (&sb.st_mtime);
8759*86d7f5d3SJohn Marino 	}
8760*86d7f5d3SJohn Marino 	else
8761*86d7f5d3SJohn Marino 	{
8762*86d7f5d3SJohn Marino 	    time_t t = 0;
8763*86d7f5d3SJohn Marino 	    wm = gmtime(&t);
8764*86d7f5d3SJohn Marino 	}
8765*86d7f5d3SJohn Marino 
8766*86d7f5d3SJohn Marino 	(void) tm_to_internet (datebuf, wm);
8767*86d7f5d3SJohn Marino 	label = Xasprintf ("-L%s\t%s", path, datebuf);
8768*86d7f5d3SJohn Marino     }
8769*86d7f5d3SJohn Marino     return label;
8770*86d7f5d3SJohn Marino }
8771*86d7f5d3SJohn Marino 
8772*86d7f5d3SJohn Marino 
8773*86d7f5d3SJohn Marino 
8774*86d7f5d3SJohn Marino /*
8775*86d7f5d3SJohn Marino  * Set up a local/custom RCS keyword for expansion.
8776*86d7f5d3SJohn Marino  *
8777*86d7f5d3SJohn Marino  * INPUTS
8778*86d7f5d3SJohn Marino  *   infopath		Path to file being parsed, for error messages.
8779*86d7f5d3SJohn Marino  *   ln			Line number of INFOPATH being processed, for error
8780*86d7f5d3SJohn Marino  *			messages.
8781*86d7f5d3SJohn Marino  *   keywords_in
8782*86d7f5d3SJohn Marino  *   arg
8783*86d7f5d3SJohn Marino  *
8784*86d7f5d3SJohn Marino  * OUTPUTS
8785*86d7f5d3SJohn Marino  *   keywords_in
8786*86d7f5d3SJohn Marino  */
8787*86d7f5d3SJohn Marino void
RCS_setlocalid(const char * infopath,unsigned int ln,void ** keywords_in,const char * arg)8788*86d7f5d3SJohn Marino RCS_setlocalid (const char *infopath, unsigned int ln,
8789*86d7f5d3SJohn Marino 		void **keywords_in, const char *arg)
8790*86d7f5d3SJohn Marino {
8791*86d7f5d3SJohn Marino     char *copy, *next, *key, *s;
8792*86d7f5d3SJohn Marino     struct rcs_keyword *keywords;
8793*86d7f5d3SJohn Marino     enum keyword save_expandto;
8794*86d7f5d3SJohn Marino 
8795*86d7f5d3SJohn Marino     if (!*keywords_in)
8796*86d7f5d3SJohn Marino 	*keywords_in = new_keywords ();
8797*86d7f5d3SJohn Marino     keywords = *keywords_in;
8798*86d7f5d3SJohn Marino 
8799*86d7f5d3SJohn Marino     copy = xstrdup (arg);
8800*86d7f5d3SJohn Marino     next = copy;
8801*86d7f5d3SJohn Marino     key = strtok (next, "=");
8802*86d7f5d3SJohn Marino 
8803*86d7f5d3SJohn Marino     /*
8804*86d7f5d3SJohn Marino      * Validate key
8805*86d7f5d3SJohn Marino      */
8806*86d7f5d3SJohn Marino     for (s = key; *s != '\0'; s++)
8807*86d7f5d3SJohn Marino     {
8808*86d7f5d3SJohn Marino 	if (! isalpha ((unsigned char) *s))
8809*86d7f5d3SJohn Marino 	{
8810*86d7f5d3SJohn Marino 	    if (!parse_error (infopath, ln))
8811*86d7f5d3SJohn Marino 		    error (0, 0,
8812*86d7f5d3SJohn Marino "%s [%u]: LocalKeyword ignored: Bad character `%c' in key `%s'",
8813*86d7f5d3SJohn Marino 			   primary_root_inverse_translate (infopath),
8814*86d7f5d3SJohn Marino 			   ln, *s, key);
8815*86d7f5d3SJohn Marino 	    free (copy);
8816*86d7f5d3SJohn Marino 	    return;
8817*86d7f5d3SJohn Marino 	}
8818*86d7f5d3SJohn Marino     }
8819*86d7f5d3SJohn Marino 
8820*86d7f5d3SJohn Marino     save_expandto = keywords[KEYWORD_LOCALID].expandto;
8821*86d7f5d3SJohn Marino 
8822*86d7f5d3SJohn Marino     /* options? */
8823*86d7f5d3SJohn Marino     while ((key = strtok (NULL, ",")) != NULL) {
8824*86d7f5d3SJohn Marino 	if (!strcmp(key, keywords[KEYWORD_ID].string))
8825*86d7f5d3SJohn Marino 	    keywords[KEYWORD_LOCALID].expandto = KEYWORD_ID;
8826*86d7f5d3SJohn Marino 	else if (!strcmp(key, keywords[KEYWORD_HEADER].string))
8827*86d7f5d3SJohn Marino 	    keywords[KEYWORD_LOCALID].expandto = KEYWORD_HEADER;
8828*86d7f5d3SJohn Marino 	else if (!strcmp(key, keywords[KEYWORD_CVSHEADER].string))
8829*86d7f5d3SJohn Marino 	    keywords[KEYWORD_LOCALID].expandto = KEYWORD_CVSHEADER;
8830*86d7f5d3SJohn Marino 	else
8831*86d7f5d3SJohn Marino 	{
8832*86d7f5d3SJohn Marino 	    keywords[KEYWORD_LOCALID].expandto = save_expandto;
8833*86d7f5d3SJohn Marino 	    if (!parse_error (infopath, ln))
8834*86d7f5d3SJohn Marino 		error (0, 0,
8835*86d7f5d3SJohn Marino "%s [%u]: LocalKeyword ignored: Unknown LocalId mode: `%s'",
8836*86d7f5d3SJohn Marino 		       primary_root_inverse_translate (infopath),
8837*86d7f5d3SJohn Marino 		       ln, key);
8838*86d7f5d3SJohn Marino 	    free (copy);
8839*86d7f5d3SJohn Marino 	    return;
8840*86d7f5d3SJohn Marino 	}
8841*86d7f5d3SJohn Marino     }
8842*86d7f5d3SJohn Marino 
8843*86d7f5d3SJohn Marino     keywords[KEYWORD_LOCALID].string = xstrdup (next);
8844*86d7f5d3SJohn Marino     keywords[KEYWORD_LOCALID].len = strlen (next);
8845*86d7f5d3SJohn Marino     keywords[KEYWORD_LOCALID].expandit = 1;
8846*86d7f5d3SJohn Marino 
8847*86d7f5d3SJohn Marino     free (copy);
8848*86d7f5d3SJohn Marino }
8849*86d7f5d3SJohn Marino 
8850*86d7f5d3SJohn Marino 
8851*86d7f5d3SJohn Marino 
8852*86d7f5d3SJohn Marino void
RCS_setincexc(void ** keywords_in,const char * arg)8853*86d7f5d3SJohn Marino RCS_setincexc (void **keywords_in, const char *arg)
8854*86d7f5d3SJohn Marino {
8855*86d7f5d3SJohn Marino     char *key;
8856*86d7f5d3SJohn Marino     char *copy, *next;
8857*86d7f5d3SJohn Marino     bool include = false;
8858*86d7f5d3SJohn Marino     struct rcs_keyword *keyword;
8859*86d7f5d3SJohn Marino     struct rcs_keyword *keywords;
8860*86d7f5d3SJohn Marino 
8861*86d7f5d3SJohn Marino     if (!*keywords_in)
8862*86d7f5d3SJohn Marino 	*keywords_in = new_keywords ();
8863*86d7f5d3SJohn Marino     keywords = *keywords_in;
8864*86d7f5d3SJohn Marino 
8865*86d7f5d3SJohn Marino     copy = xstrdup(arg);
8866*86d7f5d3SJohn Marino     next = copy;
8867*86d7f5d3SJohn Marino     switch (*next++) {
8868*86d7f5d3SJohn Marino 	case 'e':
8869*86d7f5d3SJohn Marino 	    include = false;
8870*86d7f5d3SJohn Marino 	    break;
8871*86d7f5d3SJohn Marino 	case 'i':
8872*86d7f5d3SJohn Marino 	    include = true;
8873*86d7f5d3SJohn Marino 	    break;
8874*86d7f5d3SJohn Marino 	default:
8875*86d7f5d3SJohn Marino 	    free(copy);
8876*86d7f5d3SJohn Marino 	    return;
8877*86d7f5d3SJohn Marino     }
8878*86d7f5d3SJohn Marino 
8879*86d7f5d3SJohn Marino     if (include)
8880*86d7f5d3SJohn Marino 	for (keyword = keywords; keyword->string != NULL; keyword++)
8881*86d7f5d3SJohn Marino 	{
8882*86d7f5d3SJohn Marino 	    keyword->expandit = false;
8883*86d7f5d3SJohn Marino 	}
8884*86d7f5d3SJohn Marino 
8885*86d7f5d3SJohn Marino     key = strtok(next, ",");
8886*86d7f5d3SJohn Marino     while (key) {
8887*86d7f5d3SJohn Marino 	for (keyword = keywords; keyword->string != NULL; keyword++) {
8888*86d7f5d3SJohn Marino 	    if (strcmp (keyword->string, key) == 0)
8889*86d7f5d3SJohn Marino 		keyword->expandit = include;
8890*86d7f5d3SJohn Marino 	}
8891*86d7f5d3SJohn Marino 	key = strtok(NULL, ",");
8892*86d7f5d3SJohn Marino     }
8893*86d7f5d3SJohn Marino     free(copy);
8894*86d7f5d3SJohn Marino     return;
8895*86d7f5d3SJohn Marino }
8896*86d7f5d3SJohn Marino 
8897*86d7f5d3SJohn Marino 
8898*86d7f5d3SJohn Marino 
8899*86d7f5d3SJohn Marino #define ATTIC "/" CVSATTIC
8900*86d7f5d3SJohn Marino static char *
getfullCVSname(char * CVSname,char ** pathstore)8901*86d7f5d3SJohn Marino getfullCVSname(char *CVSname, char **pathstore)
8902*86d7f5d3SJohn Marino {
8903*86d7f5d3SJohn Marino     if (current_parsed_root->directory) {
8904*86d7f5d3SJohn Marino 	int rootlen;
8905*86d7f5d3SJohn Marino 	char *c = NULL;
8906*86d7f5d3SJohn Marino 	int alen = sizeof(ATTIC) - 1;
8907*86d7f5d3SJohn Marino 
8908*86d7f5d3SJohn Marino 	*pathstore = xstrdup(CVSname);
8909*86d7f5d3SJohn Marino 	if ((c = strrchr(*pathstore, '/')) != NULL) {
8910*86d7f5d3SJohn Marino 	    if (c - *pathstore >= alen) {
8911*86d7f5d3SJohn Marino 		if (!strncmp(c - alen, ATTIC, alen)) {
8912*86d7f5d3SJohn Marino 		    while (*c != '\0') {
8913*86d7f5d3SJohn Marino 			*(c - alen) = *c;
8914*86d7f5d3SJohn Marino 			c++;
8915*86d7f5d3SJohn Marino 		    }
8916*86d7f5d3SJohn Marino 		    *(c - alen) = '\0';
8917*86d7f5d3SJohn Marino 		}
8918*86d7f5d3SJohn Marino 	    }
8919*86d7f5d3SJohn Marino 	}
8920*86d7f5d3SJohn Marino 
8921*86d7f5d3SJohn Marino 	rootlen = strlen(current_parsed_root->directory);
8922*86d7f5d3SJohn Marino 	if (!strncmp(*pathstore, current_parsed_root->directory, rootlen) &&
8923*86d7f5d3SJohn Marino 	    (*pathstore)[rootlen] == '/')
8924*86d7f5d3SJohn Marino 	    CVSname = (*pathstore + rootlen + 1);
8925*86d7f5d3SJohn Marino 	else
8926*86d7f5d3SJohn Marino 	    CVSname = (*pathstore);
8927*86d7f5d3SJohn Marino     }
8928*86d7f5d3SJohn Marino     return CVSname;
8929*86d7f5d3SJohn Marino }
8930