1 /* RCS filename handling
2 
3    Copyright (C) 2010-2020 Thien-Thi Nguyen
4    Copyright (C) 1990, 1991, 1992, 1993, 1994, 1995 Paul Eggert
5    Copyright (C) 1982, 1988, 1989 Walter Tichy
6 
7    This file is part of GNU RCS.
8 
9    GNU RCS is free software: you can redistribute it and/or modify it
10    under the terms of the GNU General Public License as published by
11    the Free Software Foundation, either version 3 of the License, or
12    (at your option) any later version.
13 
14    GNU RCS is distributed in the hope that it will be useful,
15    but WITHOUT ANY WARRANTY; without even the implied warranty
16    of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
17    See the GNU General Public License for more details.
18 
19    You should have received a copy of the GNU General Public License
20    along with this program.  If not, see <http://www.gnu.org/licenses/>.
21 */
22 
23 #include "base.h"
24 #include <string.h>
25 #include <errno.h>
26 #include <stdlib.h>
27 #include <unistd.h>
28 #include "same-inode.h"
29 #include "b-complain.h"
30 #include "b-divvy.h"
31 #include "b-feph.h"
32 #include "b-fro.h"
33 #include "b-grok.h"
34 
35 #define rcsdir     "RCS"
36 #define rcsdirlen  (sizeof rcsdir - 1)
37 
38 struct compair
39 {
40   char const *suffix, *comlead;
41 };
42 
43 /* This table is present only for backwards compatibility.  Normally we
44    ignore this table, and use the prefix of the ‘$Log’ line instead.  */
45 static struct compair const comtable[] = {
46   {"a",    "-- "},              /* Ada */
47   {"ada",  "-- "},
48   {"adb",  "-- "},
49   {"ads",  "-- "},
50   {"asm",  ";; "},              /* assembler (MS-DOS) */
51   {"bat",  ":: "},              /* batch (MS-DOS) */
52   {"body", "-- "},              /* Ada */
53   {"c",    " * "},              /* C */
54   {"c++",  "// "},              /* C++ in all its infinite guises */
55   {"cc",   "// "},
56   {"cpp",  "// "},
57   {"cxx",  "// "},
58   {"cl",   ";;; "},             /* Common Lisp */
59   {"cmd",  ":: "},              /* command (OS/2) */
60   {"cmf",  "c "},               /* CM Fortran */
61   {"cs",   " * "},              /* C* */
62   {"el",   "; "},               /* Emacs Lisp */
63   {"f",    "c "},               /* Fortran */
64   {"for",  "c "},
65   {"h",    " * "},              /* C-header */
66   {"hpp",  "// "},              /* C++ header */
67   {"hxx",  "// "},
68   {"l",    " * "},              /* lex (NOTE: franzlisp disagrees) */
69   {"lisp", ";;; "},             /* Lucid Lisp */
70   {"lsp",  ";; "},              /* Microsoft Lisp */
71   {"m",    "// "},              /* Objective C */
72   {"mac",  ";; "},              /* macro (DEC-10, MS-DOS, PDP-11, VMS, etc) */
73   {"me",   ".\\\" "},           /* troff -me */
74   {"ml",   "; "},               /* mocklisp */
75   {"mm",   ".\\\" "},           /* troff -mm */
76   {"ms",   ".\\\" "},           /* troff -ms */
77   {"p",    " * "},              /* Pascal */
78   {"pas",  " * "},
79   {"ps",   "% "},               /* PostScript */
80   {"spec", "-- "},              /* Ada */
81   {"sty",  "% "},               /* LaTeX style */
82   {"tex",  "% "},               /* TeX */
83   {"y",    " * "},              /* yacc */
84   {NULL,   "# "}                /* default for unknown suffix; must be last */
85 };
86 
87 static void
InitAdmin(void)88 InitAdmin (void)
89 /* Initialize an admin node.  */
90 {
91   register char const *ext;
92 
93   REPO (tip) = NULL;
94   BE (strictly_locking) = STRICT_LOCKING;
95   REPO (r) = empty_repo (SINGLE);
96 
97   /* Guess the comment leader from the suffix.  */
98   ext = (ext = strrchr (MANI (filename), '.'))
99     ? 1 + ext
100     /* Empty suffix; will get default.  */
101     : "";
102   for (struct compair const *ent = comtable; ; ent++)
103     if (!ent->suffix || !strcasecmp (ent->suffix, ext))
104       {
105         REPO (log_lead).string = ent->comlead;
106         REPO (log_lead).size = strlen (ent->comlead);
107         break;
108       }
109   BE (kws) = kwsub_kv;
110 }
111 
112 char const *
basefilename(char const * p)113 basefilename (char const *p)
114 /* Return the address of the base filename of the filename ‘p’.  */
115 {
116   register char const *b = p, *q = p;
117 
118   for (;;)
119     switch (*q++)
120       {
121       case SLASHes:
122         b = q;
123         break;
124       case 0:
125         return b;
126       }
127 }
128 
129 static size_t
suffixlen(char const * x)130 suffixlen (char const *x)
131 /* Return the length of ‘x’, an RCS filename suffix.  */
132 {
133   register char const *p;
134 
135   p = x;
136   for (;;)
137     switch (*p)
138       {
139       case 0:
140       case SLASHes:
141         return p - x;
142 
143       default:
144         ++p;
145         continue;
146       }
147 }
148 
149 char const *
rcssuffix(char const * name)150 rcssuffix (char const *name)
151 /* Return the suffix of ‘name’ if it is an RCS filename, NULL otherwise.  */
152 {
153   char const *x, *p, *nz;
154   size_t nl, xl;
155 
156   nl = strlen (name);
157   nz = name + nl;
158   x = BE (pe);
159   do
160     {
161       if ((xl = suffixlen (x)))
162         {
163           if (xl <= nl && MEM_SAME (xl, (p = nz - xl), x))
164             return p;
165         }
166       else
167         for (p = name; p < nz - rcsdirlen; p++)
168           if (isSLASH (p[rcsdirlen])
169               && (p == name || isSLASH (p[-1]))
170               && MEM_SAME (rcsdirlen, p, rcsdir))
171             return nz;
172       x += xl;
173     }
174   while (*x++);
175   return NULL;
176 }
177 
178 struct fro *
rcsreadopen(struct maybe * m)179 rcsreadopen (struct maybe *m)
180 /* Open ‘m->tentative’ for reading and return its ‘fro*’ descriptor.
181    If successful, set ‘*(m->status)’ to its status.
182    Pass this routine to ‘pairnames’ for read-only access to the file.  */
183 {
184   return fro_open (m->tentative.string, FOPEN_RB, m->status);
185 }
186 
187 static bool
finopen(struct maybe * m)188 finopen (struct maybe *m)
189 /* Use ‘m->open’ to open an RCS file; ‘m->mustread’ is set if the file must be
190    read.  Set ‘FLOW (from)’ to the result and return true if successful.
191    ‘m->tentative’ holds the file's name.  Set ‘m->bestfit’ to the best RCS name
192    found so far, and ‘m->eno’ to its errno.  Return true if successful or if
193    an unusual failure.  */
194 {
195   bool interesting, preferold;
196 
197   /* We prefer an old name to that of a nonexisting new RCS file,
198      unless we tried locking the old name and failed.  */
199   preferold = m->bestfit.string[0] && (m->mustread || 0 <= REPO (fd_lock));
200 
201   FLOW (from) = (m->open) (m);
202   interesting = FLOW (from) || errno != ENOENT;
203   if (interesting || !preferold)
204     {
205       /* Use the new name.  */
206       m->eno = errno;
207       m->bestfit = m->tentative;
208     }
209   return interesting;
210 }
211 
212 static bool
fin2open(char const * d,size_t dlen,char const * base,size_t baselen,char const * x,size_t xlen,struct maybe * m)213 fin2open (char const *d, size_t dlen,
214           char const *base, size_t baselen,
215           char const *x, size_t xlen,
216           struct maybe *m)
217 /* ‘d’ is a directory name with length ‘dlen’ (including trailing slash).
218    ‘base’ is a filename with length ‘baselen’.
219    ‘x’ is an RCS filename suffix with length ‘xlen’.
220    Use ‘m->open’ to open an RCS file; ‘m->mustread’ is set if the file
221    must be read.  Return true if successful.  Try "dRCS/basex" first; if
222    that fails and x is nonempty, try "dbasex".  Put these potential
223    names in ‘m->tentative’ for ‘finopen’ to wrangle.  */
224 {
225 #define ACC(start)  accumulate_nbytes (m->space, start, start ## len)
226 #define OK()  m->tentative.string = finish_string (m->space, &m->tentative.size)
227 
228   /* Try "dRCS/basex".  */
229   ACC (d);
230   ACC (rcsdir);
231   accumulate_byte (m->space, SLASH);
232   ACC (base);
233   ACC (x);
234   OK ();
235   if (xlen)
236     {
237       if (finopen (m))
238         return true;
239 
240       /* Try "dbasex".  Start from scratch, because
241          ‘finopen’ may have changed ‘m->filename’.  */
242       ACC (d);
243       ACC (base);
244       ACC (x);
245       OK ();
246     }
247   return finopen (m);
248 
249 #undef OK
250 #undef ACC
251 }
252 
253 int
pairnames(int argc,char ** argv,open_rcsfile_fn * rcsopen,bool mustread,bool quiet)254 pairnames (int argc, char **argv, open_rcsfile_fn *rcsopen,
255            bool mustread, bool quiet)
256 /* Pair the filenames pointed to by ‘argv’; ‘argc’ indicates how many there
257    are.  Place a pointer to the RCS filename into ‘REPO (filename)’, and a
258    pointer to the filename of the working file into ‘MANI (filename)’.  If
259    both are given, and ‘MANI (standard_output)’ is set, display a warning.
260 
261    If the RCS file exists, place its status into ‘REPO (stat)’, open it for
262    reading (using ‘rcsopen’), place the file pointer into ‘FLOW (from)’, read
263    in the admin-node, and return 1.
264 
265    If the RCS file does not exist and ‘mustread’, display an error unless
266    ‘quiet’ and return 0.  Otherwise, initialize the admin node and return -1.
267 
268    Return 0 on all errors, e.g. files that are not regular files.  */
269 {
270   register char *p, *arg, *RCS1;
271   char const *base, *RCSbase, *x;
272   char *mani_filename;
273   bool paired;
274   size_t arglen, dlen, baselen, xlen;
275   struct fro *from;
276   struct maybe maybe =
277     {
278       /* ‘.filename’ initialized by ‘fin2open’.  */
279       .open = rcsopen,
280       .mustread = mustread,
281       .status = &REPO (stat)
282     };
283 
284   REPO (fd_lock) = -1;
285 
286   if (!(arg = *argv))
287     return 0;                   /* already paired filename */
288   if (*arg == '-')
289     {
290       PERR ("%s option is ignored after filenames", arg);
291       return 0;
292     }
293 
294   base = basefilename (arg);
295   paired = false;
296 
297   /* First check suffix to see whether it is an RCS file or not.  */
298   if ((x = rcssuffix (arg)))
299     {
300       /* RCS filename given.  */
301       RCS1 = arg;
302       RCSbase = base;
303       baselen = x - base;
304       if (1 < argc
305           && !rcssuffix (mani_filename = p = argv[1])
306           && baselen <= (arglen = (size_t) strlen (p))
307           && ((p += arglen - baselen) == mani_filename || isSLASH (p[-1]))
308           && MEM_SAME (baselen, base, p))
309         {
310           argv[1] = NULL;
311           paired = true;
312         }
313       else
314         {
315           mani_filename = intern (SINGLE, base, baselen + 1);
316           mani_filename[baselen] = '\0';
317         }
318     }
319   else
320     {
321       /* Working file given; now try to find RCS file.  */
322       mani_filename = arg;
323       baselen = strlen (base);
324       /* Derive RCS filename.  */
325       if (1 < argc
326           && (x = rcssuffix (RCS1 = argv[1]))
327           && RCS1 + baselen <= x
328           && ((RCSbase = x - baselen) == RCS1 || isSLASH (RCSbase[-1]))
329           && MEM_SAME (baselen, base, RCSbase))
330         {
331           argv[1] = NULL;
332           paired = true;
333         }
334       else
335         RCSbase = RCS1 = NULL;
336     }
337   MANI (filename) = mani_filename;
338   /* Now we have a (tentative) RCS filename in ‘RCS1’ and ‘MANI (filename)’.
339      Next, try to find the right RCS file.  */
340   maybe.space = make_space (__func__);
341   if (RCSbase != RCS1)
342     {
343       /* A filename is given; single RCS file to look for.  */
344       maybe.bestfit.string = RCS1;
345       maybe.bestfit.size = strlen (RCS1);
346       maybe.tentative = maybe.bestfit;
347       FLOW (from) = (*rcsopen) (&maybe);
348       maybe.eno = errno;
349     }
350   else
351     {
352       maybe.bestfit.string = "";
353       maybe.bestfit.size = 0;
354       if (RCS1)
355         /* RCS filename was given without a directory component.  */
356         fin2open (arg, (size_t) 0, RCSbase, baselen,
357                   x, strlen (x), &maybe);
358       else
359         {
360           /* No RCS filename was given.
361              Try each suffix in turn.  */
362           dlen = base - arg;
363           x = BE (pe);
364           while (!fin2open (arg, dlen, base, baselen,
365                             x, xlen = suffixlen (x), &maybe))
366             {
367               x += xlen;
368               if (!*x++)
369                 break;
370             }
371         }
372     }
373   REPO (filename) = p = intern (SINGLE, maybe.bestfit.string,
374                                 maybe.bestfit.size);
375   FLOW (erroneous) = false;
376   BE (Oerrloop) = false;
377   if ((from = FLOW (from)))
378     {
379       if (!S_ISREG (maybe.status->st_mode))
380         {
381           PERR ("%s isn't a regular file -- ignored", p);
382           return 0;
383         }
384       REPO (r) = grok_all (SINGLE, from);
385       FLOW (to) = NULL;
386     }
387   else
388     {
389       if (maybe.eno != ENOENT || mustread || PROB (REPO (fd_lock)))
390         {
391           if (maybe.eno == EEXIST)
392             PERR ("RCS file %s is in use", p);
393           else if (!quiet || maybe.eno != ENOENT)
394             syserror (maybe.eno, p);
395           return 0;
396         }
397       InitAdmin ();
398     };
399 
400   if (paired && MANI (standard_output))
401     MWARN ("Working file ignored due to -p option");
402 
403   PREV (valid) = false;
404   close_space (maybe.space);
405   return from ? 1 : -1;
406 }
407 
408 #ifndef DOUBLE_SLASH_IS_DISTINCT_ROOT
409 #define DOUBLE_SLASH_IS_DISTINCT_ROOT 0
410 #endif
411 
412 static size_t
dir_useful_len(char const * d)413 dir_useful_len (char const *d)
414 /* ‘d’ names a directory; return the number of bytes of its useful part.  To
415    create a file in ‘d’, append a ‘SLASH’ and a file name to the useful part.
416    Ignore trailing slashes if possible; not only are they ugly, but some
417    non-POSIX systems misbehave unless the slashes are omitted.  */
418 {
419   size_t dlen = strlen (d);
420 
421   if (DOUBLE_SLASH_IS_DISTINCT_ROOT && dlen == 2
422       && isSLASH (d[0])
423       && isSLASH (d[1]))
424     --dlen;
425   else
426     while (dlen && isSLASH (d[dlen - 1]))
427       --dlen;
428   return dlen;
429 }
430 
431 char const *
getfullRCSname(void)432 getfullRCSname (void)
433 /* Return a pointer to the full filename of the RCS file.
434    Remove leading ‘./’.  */
435 {
436   char const *r = REPO (filename);
437 
438   if (ABSFNAME (r))
439     return r;
440   else
441     {
442       char *cwd;
443       char *rv;
444       size_t len;
445 
446       if (!(cwd = BE (cwd)))
447         {
448           /* Get working directory for the first time.  */
449           char *PWD = cgetenv ("PWD");
450           struct stat PWDstat, dotstat;
451 
452           if (!((cwd = PWD)
453                 && ABSFNAME (PWD)
454                 && !PROB (stat (PWD, &PWDstat))
455                 && !PROB (stat (".", &dotstat))
456                 && SAME_INODE (PWDstat, dotstat)))
457             {
458               size_t sz = 64;
459 
460               while (!(cwd = alloc (PLEXUS, sz),
461                        getcwd (cwd, sz)))
462                 {
463                   brush_off (PLEXUS, cwd);
464                   if (errno == ERANGE)
465                     sz <<= 1;
466                   else if ((cwd = PWD))
467                     break;
468                   else
469                     fatal_sys ("getcwd");
470                 }
471             }
472           cwd[dir_useful_len (cwd)] = '\0';
473           BE (cwd) = cwd;
474         }
475       /* Remove leading ‘./’s from ‘REPO (filename)’.
476          Do not try to handle ‘../’, since removing it may result
477          in the wrong answer in the presence of symbolic links.  */
478       for (; r[0] == '.' && isSLASH (r[1]); r += 2)
479         /* ‘.////’ is equivalent to ‘./’.  */
480         while (isSLASH (r[2]))
481           r++;
482       /* Build full filename.  */
483       accf (SINGLE, "%s%c%s", cwd, SLASH, r);
484       rv = finish_string (SINGLE, &len);
485       return rv;
486     }
487 }
488 
489 bool
isSLASH(int c)490 isSLASH (int c)
491 {
492   if (! WOE)
493     return (SLASH == c);
494 
495   switch (c)
496     {
497     case SLASHes:
498       return true;
499     default:
500       return false;
501     }
502 }
503 
504 /* rcsfnms.c ends here */
505