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