1 /* $OpenBSD: rcs.c,v 1.322 2024/05/30 10:25:58 jsg Exp $ */
2 /*
3 * Copyright (c) 2004 Jean-Francois Brousseau <jfb@openbsd.org>
4 * All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 *
10 * 1. Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 * 2. The name of the author may not be used to endorse or promote products
13 * derived from this software without specific prior written permission.
14 *
15 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
16 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
17 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
18 * THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
19 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
20 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
21 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
22 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
23 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
24 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 */
26
27 #include <sys/stat.h>
28
29 #include <ctype.h>
30 #include <errno.h>
31 #include <libgen.h>
32 #include <pwd.h>
33 #include <stdlib.h>
34 #include <string.h>
35 #include <unistd.h>
36
37 #include "atomicio.h"
38 #include "cvs.h"
39 #include "diff.h"
40 #include "rcs.h"
41 #include "rcsparse.h"
42
43 #define MINIMUM(a, b) (((a) < (b)) ? (a) : (b))
44
45 #define RCS_KWEXP_SIZE 1024
46
47 #define ANNOTATE_NEVER 0
48 #define ANNOTATE_NOW 1
49 #define ANNOTATE_LATER 2
50
51 /* invalid characters in RCS symbol names */
52 static const char rcs_sym_invch[] = RCS_SYM_INVALCHAR;
53
54 /* comment leaders, depending on the file's suffix */
55 static const struct rcs_comment {
56 const char *rc_suffix;
57 const char *rc_cstr;
58 } rcs_comments[] = {
59 { "1", ".\\\" " },
60 { "2", ".\\\" " },
61 { "3", ".\\\" " },
62 { "4", ".\\\" " },
63 { "5", ".\\\" " },
64 { "6", ".\\\" " },
65 { "7", ".\\\" " },
66 { "8", ".\\\" " },
67 { "9", ".\\\" " },
68 { "a", "-- " }, /* Ada */
69 { "ada", "-- " },
70 { "adb", "-- " },
71 { "asm", ";; " }, /* assembler (MS-DOS) */
72 { "ads", "-- " }, /* Ada */
73 { "bat", ":: " }, /* batch (MS-DOS) */
74 { "body", "-- " }, /* Ada */
75 { "c", " * " }, /* C */
76 { "c++", "// " }, /* C++ */
77 { "cc", "// " },
78 { "cpp", "// " },
79 { "cxx", "// " },
80 { "m", "// " }, /* Objective-C */
81 { "cl", ";;; " }, /* Common Lisp */
82 { "cmd", ":: " }, /* command (OS/2) */
83 { "cmf", "c " }, /* CM Fortran */
84 { "csh", "# " }, /* shell */
85 { "e", "# " }, /* efl */
86 { "epsf", "% " }, /* encapsulated postscript */
87 { "epsi", "% " }, /* encapsulated postscript */
88 { "el", "; " }, /* Emacs Lisp */
89 { "f", "c " }, /* Fortran */
90 { "for", "c " },
91 { "h", " * " }, /* C-header */
92 { "hh", "// " }, /* C++ header */
93 { "hpp", "// " },
94 { "hxx", "// " },
95 { "in", "# " }, /* for Makefile.in */
96 { "l", " * " }, /* lex */
97 { "mac", ";; " }, /* macro (DEC-10, MS-DOS, PDP-11, VMS, etc) */
98 { "mak", "# " }, /* makefile, e.g. Visual C++ */
99 { "me", ".\\\" " }, /* me-macros t/nroff */
100 { "ml", "; " }, /* mocklisp */
101 { "mm", ".\\\" " }, /* mm-macros t/nroff */
102 { "ms", ".\\\" " }, /* ms-macros t/nroff */
103 { "man", ".\\\" " }, /* man-macros t/nroff */
104 { "p", " * " }, /* pascal */
105 { "pas", " * " },
106 { "pl", "# " }, /* Perl (conflict with Prolog) */
107 { "pm", "# " }, /* Perl module */
108 { "ps", "% " }, /* postscript */
109 { "psw", "% " }, /* postscript wrap */
110 { "pswm", "% " }, /* postscript wrap */
111 { "r", "# " }, /* ratfor */
112 { "rc", " * " }, /* Microsoft Windows resource file */
113 { "red", "% " }, /* psl/rlisp */
114 { "sh", "# " }, /* shell */
115 { "sl", "% " }, /* psl */
116 { "spec", "-- " }, /* Ada */
117 { "tex", "% " }, /* tex */
118 { "y", " * " }, /* yacc */
119 { "ye", " * " }, /* yacc-efl */
120 { "yr", " * " }, /* yacc-ratfor */
121 };
122
123 struct rcs_kw rcs_expkw[] = {
124 { "Author", RCS_KW_AUTHOR },
125 { "Date", RCS_KW_DATE },
126 { "Header", RCS_KW_HEADER },
127 { "Id", RCS_KW_ID },
128 { "Locker", RCS_KW_LOCKER },
129 { "Log", RCS_KW_LOG },
130 { "Name", RCS_KW_NAME },
131 { "RCSfile", RCS_KW_RCSFILE },
132 { "Revision", RCS_KW_REVISION },
133 { "Source", RCS_KW_SOURCE },
134 { "State", RCS_KW_STATE },
135 { "Mdocdate", RCS_KW_MDOCDATE },
136 };
137
138 #define NB_COMTYPES (sizeof(rcs_comments)/sizeof(rcs_comments[0]))
139
140 static RCSNUM *rcs_get_revision(const char *, RCSFILE *);
141 int rcs_patch_lines(struct rcs_lines *, struct rcs_lines *,
142 struct rcs_line **, struct rcs_delta *);
143 static void rcs_freedelta(struct rcs_delta *);
144 static void rcs_strprint(const u_char *, size_t, FILE *);
145
146 static void rcs_kwexp_line(char *, struct rcs_delta *, struct rcs_lines *,
147 struct rcs_line *, int mode);
148
149 /*
150 * Prepare RCSFILE for parsing. The given file descriptor (if any) must be
151 * read-only and is closed on rcs_close().
152 */
153 RCSFILE *
rcs_open(const char * path,int fd,int flags,...)154 rcs_open(const char *path, int fd, int flags, ...)
155 {
156 int mode;
157 mode_t fmode;
158 RCSFILE *rfp;
159 va_list vap;
160 struct stat st;
161 struct rcs_delta *rdp;
162 struct rcs_lock *lkr;
163
164 fmode = S_IRUSR|S_IRGRP|S_IROTH;
165 flags &= 0xffff; /* ditch any internal flags */
166
167 if (flags & RCS_CREATE) {
168 va_start(vap, flags);
169 mode = va_arg(vap, int);
170 va_end(vap);
171 fmode = (mode_t)mode;
172 } else {
173 if (fstat(fd, &st) == -1)
174 fatal("rcs_open: %s: fstat: %s", path, strerror(errno));
175 fmode = st.st_mode;
176 }
177
178 fmode &= ~cvs_umask;
179
180 rfp = xcalloc(1, sizeof(*rfp));
181
182 rfp->rf_path = xstrdup(path);
183 rfp->rf_flags = flags | RCS_SLOCK | RCS_SYNCED;
184 rfp->rf_mode = fmode;
185 if (fd == -1)
186 rfp->rf_file = NULL;
187 else if ((rfp->rf_file = fdopen(fd, "r")) == NULL)
188 fatal("rcs_open: %s: fdopen: %s", path, strerror(errno));
189 rfp->rf_dead = 0;
190
191 TAILQ_INIT(&(rfp->rf_delta));
192 TAILQ_INIT(&(rfp->rf_access));
193 TAILQ_INIT(&(rfp->rf_symbols));
194 TAILQ_INIT(&(rfp->rf_locks));
195
196 if (!(rfp->rf_flags & RCS_CREATE)) {
197 if (rcsparse_init(rfp))
198 fatal("could not parse admin data");
199 }
200
201 /* fill in rd_locker */
202 TAILQ_FOREACH(lkr, &(rfp->rf_locks), rl_list) {
203 if ((rdp = rcs_findrev(rfp, lkr->rl_num)) == NULL) {
204 rcs_close(rfp);
205 return (NULL);
206 }
207
208 rdp->rd_locker = xstrdup(lkr->rl_name);
209 }
210
211 return (rfp);
212 }
213
214 /*
215 * rcs_close()
216 *
217 * Close an RCS file handle.
218 */
219 void
rcs_close(RCSFILE * rfp)220 rcs_close(RCSFILE *rfp)
221 {
222 struct rcs_delta *rdp;
223 struct rcs_access *rap;
224 struct rcs_lock *rlp;
225 struct rcs_sym *rsp;
226
227 if ((rfp->rf_flags & RCS_WRITE) && !(rfp->rf_flags & RCS_SYNCED))
228 rcs_write(rfp);
229
230 while (!TAILQ_EMPTY(&(rfp->rf_delta))) {
231 rdp = TAILQ_FIRST(&(rfp->rf_delta));
232 TAILQ_REMOVE(&(rfp->rf_delta), rdp, rd_list);
233 rcs_freedelta(rdp);
234 }
235
236 while (!TAILQ_EMPTY(&(rfp->rf_access))) {
237 rap = TAILQ_FIRST(&(rfp->rf_access));
238 TAILQ_REMOVE(&(rfp->rf_access), rap, ra_list);
239 free(rap->ra_name);
240 free(rap);
241 }
242
243 while (!TAILQ_EMPTY(&(rfp->rf_symbols))) {
244 rsp = TAILQ_FIRST(&(rfp->rf_symbols));
245 TAILQ_REMOVE(&(rfp->rf_symbols), rsp, rs_list);
246 free(rsp->rs_num);
247 free(rsp->rs_name);
248 free(rsp);
249 }
250
251 while (!TAILQ_EMPTY(&(rfp->rf_locks))) {
252 rlp = TAILQ_FIRST(&(rfp->rf_locks));
253 TAILQ_REMOVE(&(rfp->rf_locks), rlp, rl_list);
254 free(rlp->rl_num);
255 free(rlp->rl_name);
256 free(rlp);
257 }
258
259 free(rfp->rf_head);
260 free(rfp->rf_branch);
261
262 if (rfp->rf_file != NULL)
263 fclose(rfp->rf_file);
264 free(rfp->rf_path);
265 free(rfp->rf_comment);
266 free(rfp->rf_expand);
267 free(rfp->rf_desc);
268 if (rfp->rf_pdata != NULL)
269 rcsparse_free(rfp);
270 free(rfp);
271 }
272
273 /*
274 * rcs_write()
275 *
276 * Write the contents of the RCS file handle <rfp> to disk in the file whose
277 * path is in <rf_path>.
278 */
279 void
rcs_write(RCSFILE * rfp)280 rcs_write(RCSFILE *rfp)
281 {
282 FILE *fp;
283 char numbuf[CVS_REV_BUFSZ], *fn, tmpdir[PATH_MAX];
284 struct rcs_access *ap;
285 struct rcs_sym *symp;
286 struct rcs_branch *brp;
287 struct rcs_delta *rdp;
288 struct rcs_lock *lkp;
289 size_t len;
290 int fd, saved_errno;
291
292 fd = -1;
293
294 if (rfp->rf_flags & RCS_SYNCED)
295 return;
296
297 if (cvs_noexec == 1)
298 return;
299
300 /* Write operations need the whole file parsed */
301 if (rcsparse_deltatexts(rfp, NULL))
302 fatal("rcs_write: rcsparse_deltatexts");
303
304 if (strlcpy(tmpdir, rfp->rf_path, sizeof(tmpdir)) >= sizeof(tmpdir))
305 fatal("rcs_write: truncation");
306 (void)xasprintf(&fn, "%s/rcs.XXXXXXXXXX", dirname(tmpdir));
307
308 if ((fd = mkstemp(fn)) == -1)
309 fatal("%s", fn);
310
311 if ((fp = fdopen(fd, "w")) == NULL) {
312 saved_errno = errno;
313 (void)unlink(fn);
314 fatal("fdopen %s: %s", fn, strerror(saved_errno));
315 }
316
317 worklist_add(fn, &temp_files);
318
319 if (rfp->rf_head != NULL)
320 rcsnum_tostr(rfp->rf_head, numbuf, sizeof(numbuf));
321 else
322 numbuf[0] = '\0';
323
324 fprintf(fp, "head\t%s;\n", numbuf);
325
326 if (rfp->rf_branch != NULL) {
327 rcsnum_tostr(rfp->rf_branch, numbuf, sizeof(numbuf));
328 fprintf(fp, "branch\t%s;\n", numbuf);
329 }
330
331 fputs("access", fp);
332 TAILQ_FOREACH(ap, &(rfp->rf_access), ra_list) {
333 fprintf(fp, "\n\t%s", ap->ra_name);
334 }
335 fputs(";\n", fp);
336
337 fprintf(fp, "symbols");
338 TAILQ_FOREACH(symp, &(rfp->rf_symbols), rs_list) {
339 if (RCSNUM_ISBRANCH(symp->rs_num))
340 rcsnum_addmagic(symp->rs_num);
341 rcsnum_tostr(symp->rs_num, numbuf, sizeof(numbuf));
342 fprintf(fp, "\n\t%s:%s", symp->rs_name, numbuf);
343 }
344 fprintf(fp, ";\n");
345
346 fprintf(fp, "locks");
347 TAILQ_FOREACH(lkp, &(rfp->rf_locks), rl_list) {
348 rcsnum_tostr(lkp->rl_num, numbuf, sizeof(numbuf));
349 fprintf(fp, "\n\t%s:%s", lkp->rl_name, numbuf);
350 }
351
352 fprintf(fp, ";");
353
354 if (rfp->rf_flags & RCS_SLOCK)
355 fprintf(fp, " strict;");
356 fputc('\n', fp);
357
358 fputs("comment\t@", fp);
359 if (rfp->rf_comment != NULL) {
360 rcs_strprint((const u_char *)rfp->rf_comment,
361 strlen(rfp->rf_comment), fp);
362 fputs("@;\n", fp);
363 } else
364 fputs("# @;\n", fp);
365
366 if (rfp->rf_expand != NULL) {
367 fputs("expand @", fp);
368 rcs_strprint((const u_char *)rfp->rf_expand,
369 strlen(rfp->rf_expand), fp);
370 fputs("@;\n", fp);
371 }
372
373 fputs("\n\n", fp);
374
375 TAILQ_FOREACH(rdp, &(rfp->rf_delta), rd_list) {
376 fprintf(fp, "%s\n", rcsnum_tostr(rdp->rd_num, numbuf,
377 sizeof(numbuf)));
378 fprintf(fp, "date\t%d.%02d.%02d.%02d.%02d.%02d;",
379 rdp->rd_date.tm_year + 1900, rdp->rd_date.tm_mon + 1,
380 rdp->rd_date.tm_mday, rdp->rd_date.tm_hour,
381 rdp->rd_date.tm_min, rdp->rd_date.tm_sec);
382 fprintf(fp, "\tauthor %s;\tstate %s;\n",
383 rdp->rd_author, rdp->rd_state);
384 fputs("branches", fp);
385 TAILQ_FOREACH(brp, &(rdp->rd_branches), rb_list) {
386 fprintf(fp, "\n\t%s", rcsnum_tostr(brp->rb_num, numbuf,
387 sizeof(numbuf)));
388 }
389 fputs(";\n", fp);
390 fprintf(fp, "next\t%s;\n\n", rcsnum_tostr(rdp->rd_next,
391 numbuf, sizeof(numbuf)));
392 }
393
394 fputs("\ndesc\n@", fp);
395 if (rfp->rf_desc != NULL && (len = strlen(rfp->rf_desc)) > 0) {
396 rcs_strprint((const u_char *)rfp->rf_desc, len, fp);
397 if (rfp->rf_desc[len-1] != '\n')
398 fputc('\n', fp);
399 }
400 fputs("@\n", fp);
401
402 /* deltatexts */
403 TAILQ_FOREACH(rdp, &(rfp->rf_delta), rd_list) {
404 fprintf(fp, "\n\n%s\n", rcsnum_tostr(rdp->rd_num, numbuf,
405 sizeof(numbuf)));
406 fputs("log\n@", fp);
407 if (rdp->rd_log != NULL) {
408 len = strlen(rdp->rd_log);
409 rcs_strprint((const u_char *)rdp->rd_log, len, fp);
410 if (len == 0 || rdp->rd_log[len-1] != '\n')
411 fputc('\n', fp);
412 }
413 fputs("@\ntext\n@", fp);
414 if (rdp->rd_text != NULL)
415 rcs_strprint(rdp->rd_text, rdp->rd_tlen, fp);
416 fputs("@\n", fp);
417 }
418
419 if (fchmod(fd, rfp->rf_mode) == -1) {
420 saved_errno = errno;
421 (void)unlink(fn);
422 fatal("fchmod %s: %s", fn, strerror(saved_errno));
423 }
424
425 (void)fclose(fp);
426
427 if (rename(fn, rfp->rf_path) == -1) {
428 saved_errno = errno;
429 (void)unlink(fn);
430 fatal("rename(%s, %s): %s", fn, rfp->rf_path,
431 strerror(saved_errno));
432 }
433
434 rfp->rf_flags |= RCS_SYNCED;
435 free(fn);
436 }
437
438 /*
439 * rcs_head_get()
440 *
441 * Retrieve the revision number of the head revision for the RCS file <file>.
442 */
443 RCSNUM *
rcs_head_get(RCSFILE * file)444 rcs_head_get(RCSFILE *file)
445 {
446 struct rcs_branch *brp;
447 struct rcs_delta *rdp;
448 RCSNUM *rev, *rootrev;
449
450 if (file->rf_head == NULL)
451 return NULL;
452
453 rev = rcsnum_alloc();
454 if (file->rf_branch != NULL) {
455 /* we have a default branch, use that to calculate the
456 * real HEAD*/
457 rootrev = rcsnum_alloc();
458 rcsnum_cpy(file->rf_branch, rootrev,
459 file->rf_branch->rn_len - 1);
460 if ((rdp = rcs_findrev(file, rootrev)) == NULL)
461 fatal("rcs_head_get: could not find root revision");
462
463 /* HEAD should be the last revision on the default branch */
464 TAILQ_FOREACH(brp, &(rdp->rd_branches), rb_list) {
465 if (rcsnum_cmp(brp->rb_num, file->rf_branch,
466 file->rf_branch->rn_len) == 0)
467 break;
468 }
469 free(rootrev);
470
471 if (brp == NULL)
472 fatal("rcs_head_get: could not find first default "
473 "branch revision");
474
475 if ((rdp = rcs_findrev(file, brp->rb_num)) == NULL)
476 fatal("rcs_head_get: could not find branch revision");
477 while (rdp->rd_next->rn_len != 0)
478 if ((rdp = rcs_findrev(file, rdp->rd_next)) == NULL)
479 fatal("rcs_head_get: could not find "
480 "next branch revision");
481
482 rcsnum_cpy(rdp->rd_num, rev, 0);
483 } else {
484 rcsnum_cpy(file->rf_head, rev, 0);
485 }
486
487 return (rev);
488 }
489
490 /*
491 * rcs_head_set()
492 *
493 * Set the revision number of the head revision for the RCS file <file> to
494 * <rev>, which must reference a valid revision within the file.
495 */
496 int
rcs_head_set(RCSFILE * file,RCSNUM * rev)497 rcs_head_set(RCSFILE *file, RCSNUM *rev)
498 {
499 if (rcs_findrev(file, rev) == NULL)
500 return (-1);
501
502 if (file->rf_head == NULL)
503 file->rf_head = rcsnum_alloc();
504
505 rcsnum_cpy(rev, file->rf_head, 0);
506 file->rf_flags &= ~RCS_SYNCED;
507 return (0);
508 }
509
510 /*
511 * rcs_branch_new()
512 *
513 * Create a new branch out of supplied revision for the RCS file <file>.
514 */
515 RCSNUM *
rcs_branch_new(RCSFILE * file,RCSNUM * rev)516 rcs_branch_new(RCSFILE *file, RCSNUM *rev)
517 {
518 RCSNUM *brev;
519 struct rcs_sym *sym;
520
521 if ((brev = rcsnum_new_branch(rev)) == NULL)
522 return (NULL);
523
524 for (;;) {
525 TAILQ_FOREACH(sym, &(file->rf_symbols), rs_list)
526 if (!rcsnum_cmp(sym->rs_num, brev, 0))
527 break;
528
529 if (sym == NULL)
530 break;
531
532 if (rcsnum_inc(brev) == NULL ||
533 rcsnum_inc(brev) == NULL) {
534 free(brev);
535 return (NULL);
536 }
537 }
538
539 return (brev);
540 }
541
542 /*
543 * rcs_branch_get()
544 *
545 * Retrieve the default branch number for the RCS file <file>.
546 * Returns the number on success. If NULL is returned, then there is no
547 * default branch for this file.
548 */
549 const RCSNUM *
rcs_branch_get(RCSFILE * file)550 rcs_branch_get(RCSFILE *file)
551 {
552 return (file->rf_branch);
553 }
554
555 /*
556 * rcs_branch_set()
557 *
558 * Set the default branch for the RCS file <file> to <bnum>.
559 * Returns 0 on success, -1 on failure.
560 */
561 int
rcs_branch_set(RCSFILE * file,const RCSNUM * bnum)562 rcs_branch_set(RCSFILE *file, const RCSNUM *bnum)
563 {
564 if (file->rf_branch == NULL)
565 file->rf_branch = rcsnum_alloc();
566
567 rcsnum_cpy(bnum, file->rf_branch, 0);
568 file->rf_flags &= ~RCS_SYNCED;
569 return (0);
570 }
571
572 /*
573 * rcs_access_add()
574 *
575 * Add the login name <login> to the access list for the RCS file <file>.
576 * Returns 0 on success, or -1 on failure.
577 */
578 int
rcs_access_add(RCSFILE * file,const char * login)579 rcs_access_add(RCSFILE *file, const char *login)
580 {
581 struct rcs_access *ap;
582
583 /* first look for duplication */
584 TAILQ_FOREACH(ap, &(file->rf_access), ra_list) {
585 if (strcmp(ap->ra_name, login) == 0)
586 return (-1);
587 }
588
589 ap = xmalloc(sizeof(*ap));
590 ap->ra_name = xstrdup(login);
591 TAILQ_INSERT_TAIL(&(file->rf_access), ap, ra_list);
592
593 /* not synced anymore */
594 file->rf_flags &= ~RCS_SYNCED;
595 return (0);
596 }
597
598 /*
599 * rcs_access_remove()
600 *
601 * Remove an entry with login name <login> from the access list of the RCS
602 * file <file>.
603 * Returns 0 on success, or -1 on failure.
604 */
605 int
rcs_access_remove(RCSFILE * file,const char * login)606 rcs_access_remove(RCSFILE *file, const char *login)
607 {
608 struct rcs_access *ap;
609
610 TAILQ_FOREACH(ap, &(file->rf_access), ra_list)
611 if (strcmp(ap->ra_name, login) == 0)
612 break;
613
614 if (ap == NULL)
615 return (-1);
616
617 TAILQ_REMOVE(&(file->rf_access), ap, ra_list);
618 free(ap->ra_name);
619 free(ap);
620
621 /* not synced anymore */
622 file->rf_flags &= ~RCS_SYNCED;
623 return (0);
624 }
625
626 /*
627 * rcs_sym_add()
628 *
629 * Add a symbol to the list of symbols for the RCS file <rfp>. The new symbol
630 * is named <sym> and is bound to the RCS revision <snum>.
631 */
632 int
rcs_sym_add(RCSFILE * rfp,const char * sym,RCSNUM * snum)633 rcs_sym_add(RCSFILE *rfp, const char *sym, RCSNUM *snum)
634 {
635 struct rcs_sym *symp;
636
637 if (!rcs_sym_check(sym))
638 return (-1);
639
640 /* first look for duplication */
641 TAILQ_FOREACH(symp, &(rfp->rf_symbols), rs_list) {
642 if (strcmp(symp->rs_name, sym) == 0)
643 return (1);
644 }
645
646 symp = xmalloc(sizeof(*symp));
647 symp->rs_name = xstrdup(sym);
648 symp->rs_num = rcsnum_alloc();
649 rcsnum_cpy(snum, symp->rs_num, 0);
650
651 TAILQ_INSERT_HEAD(&(rfp->rf_symbols), symp, rs_list);
652
653 /* not synced anymore */
654 rfp->rf_flags &= ~RCS_SYNCED;
655 return (0);
656 }
657
658 /*
659 * rcs_sym_remove()
660 *
661 * Remove the symbol with name <sym> from the symbol list for the RCS file
662 * <file>. If no such symbol is found, the call fails and returns with an
663 * error.
664 * Returns 0 on success, or -1 on failure.
665 */
666 int
rcs_sym_remove(RCSFILE * file,const char * sym)667 rcs_sym_remove(RCSFILE *file, const char *sym)
668 {
669 struct rcs_sym *symp;
670
671 if (!rcs_sym_check(sym))
672 return (-1);
673
674 TAILQ_FOREACH(symp, &(file->rf_symbols), rs_list)
675 if (strcmp(symp->rs_name, sym) == 0)
676 break;
677
678 if (symp == NULL)
679 return (-1);
680
681 TAILQ_REMOVE(&(file->rf_symbols), symp, rs_list);
682 free(symp->rs_name);
683 free(symp->rs_num);
684 free(symp);
685
686 /* not synced anymore */
687 file->rf_flags &= ~RCS_SYNCED;
688 return (0);
689 }
690
691 /*
692 * rcs_sym_get()
693 *
694 * Find a specific symbol <sym> entry in the tree of the RCS file <file>.
695 *
696 * Returns a pointer to the symbol on success, or NULL on failure.
697 */
698 struct rcs_sym *
rcs_sym_get(RCSFILE * file,const char * sym)699 rcs_sym_get(RCSFILE *file, const char *sym)
700 {
701 struct rcs_sym *symp;
702
703 TAILQ_FOREACH(symp, &(file->rf_symbols), rs_list)
704 if (strcmp(symp->rs_name, sym) == 0)
705 return (symp);
706
707 return (NULL);
708 }
709
710 /*
711 * rcs_sym_getrev()
712 *
713 * Retrieve the RCS revision number associated with the symbol <sym> for the
714 * RCS file <file>. The returned value is a dynamically-allocated copy and
715 * should be freed by the caller once they are done with it.
716 * Returns the RCSNUM on success, or NULL on failure.
717 */
718 RCSNUM *
rcs_sym_getrev(RCSFILE * file,const char * sym)719 rcs_sym_getrev(RCSFILE *file, const char *sym)
720 {
721 RCSNUM *num;
722 struct rcs_sym *symp;
723
724 if (!rcs_sym_check(sym) || file->rf_head == NULL)
725 return (NULL);
726
727 if (!strcmp(sym, RCS_HEAD_BRANCH)) {
728 num = rcsnum_alloc();
729 rcsnum_cpy(file->rf_head, num, 0);
730 return (num);
731 }
732
733 num = NULL;
734 TAILQ_FOREACH(symp, &(file->rf_symbols), rs_list)
735 if (strcmp(symp->rs_name, sym) == 0)
736 break;
737
738 if (symp != NULL) {
739 num = rcsnum_alloc();
740 rcsnum_cpy(symp->rs_num, num, 0);
741 }
742
743 return (num);
744 }
745
746 /*
747 * rcs_sym_check()
748 *
749 * Check the RCS symbol name <sym> for any unsupported characters.
750 * Returns 1 if the tag is correct, 0 if it isn't valid.
751 */
752 int
rcs_sym_check(const char * sym)753 rcs_sym_check(const char *sym)
754 {
755 int ret;
756 const unsigned char *cp;
757
758 ret = 1;
759 cp = sym;
760 if (!isalpha(*cp++))
761 return (0);
762
763 for (; *cp != '\0'; cp++)
764 if (!isgraph(*cp) || (strchr(rcs_sym_invch, *cp) != NULL)) {
765 ret = 0;
766 break;
767 }
768
769 return (ret);
770 }
771
772 /*
773 * rcs_lock_getmode()
774 *
775 * Retrieve the locking mode of the RCS file <file>.
776 */
777 int
rcs_lock_getmode(RCSFILE * file)778 rcs_lock_getmode(RCSFILE *file)
779 {
780 return (file->rf_flags & RCS_SLOCK) ? RCS_LOCK_STRICT : RCS_LOCK_LOOSE;
781 }
782
783 /*
784 * rcs_lock_setmode()
785 *
786 * Set the locking mode of the RCS file <file> to <mode>, which must either
787 * be RCS_LOCK_LOOSE or RCS_LOCK_STRICT.
788 * Returns the previous mode on success, or -1 on failure.
789 */
790 int
rcs_lock_setmode(RCSFILE * file,int mode)791 rcs_lock_setmode(RCSFILE *file, int mode)
792 {
793 int pmode;
794 pmode = rcs_lock_getmode(file);
795
796 if (mode == RCS_LOCK_STRICT)
797 file->rf_flags |= RCS_SLOCK;
798 else if (mode == RCS_LOCK_LOOSE)
799 file->rf_flags &= ~RCS_SLOCK;
800 else
801 fatal("rcs_lock_setmode: invalid mode `%d'", mode);
802
803 file->rf_flags &= ~RCS_SYNCED;
804 return (pmode);
805 }
806
807 /*
808 * rcs_lock_add()
809 *
810 * Add an RCS lock for the user <user> on revision <rev>.
811 * Returns 0 on success, or -1 on failure.
812 */
813 int
rcs_lock_add(RCSFILE * file,const char * user,RCSNUM * rev)814 rcs_lock_add(RCSFILE *file, const char *user, RCSNUM *rev)
815 {
816 struct rcs_lock *lkp;
817
818 /* first look for duplication */
819 TAILQ_FOREACH(lkp, &(file->rf_locks), rl_list) {
820 if (strcmp(lkp->rl_name, user) == 0 &&
821 rcsnum_cmp(rev, lkp->rl_num, 0) == 0)
822 return (-1);
823 }
824
825 lkp = xmalloc(sizeof(*lkp));
826 lkp->rl_name = xstrdup(user);
827 lkp->rl_num = rcsnum_alloc();
828 rcsnum_cpy(rev, lkp->rl_num, 0);
829
830 TAILQ_INSERT_TAIL(&(file->rf_locks), lkp, rl_list);
831
832 /* not synced anymore */
833 file->rf_flags &= ~RCS_SYNCED;
834 return (0);
835 }
836
837
838 /*
839 * rcs_lock_remove()
840 *
841 * Remove the RCS lock on revision <rev>.
842 * Returns 0 on success, or -1 on failure.
843 */
844 int
rcs_lock_remove(RCSFILE * file,const char * user,RCSNUM * rev)845 rcs_lock_remove(RCSFILE *file, const char *user, RCSNUM *rev)
846 {
847 struct rcs_lock *lkp;
848
849 TAILQ_FOREACH(lkp, &(file->rf_locks), rl_list) {
850 if (strcmp(lkp->rl_name, user) == 0 &&
851 rcsnum_cmp(lkp->rl_num, rev, 0) == 0)
852 break;
853 }
854
855 if (lkp == NULL)
856 return (-1);
857
858 TAILQ_REMOVE(&(file->rf_locks), lkp, rl_list);
859 free(lkp->rl_num);
860 free(lkp->rl_name);
861 free(lkp);
862
863 /* not synced anymore */
864 file->rf_flags &= ~RCS_SYNCED;
865 return (0);
866 }
867
868 /*
869 * rcs_desc_get()
870 *
871 * Retrieve the description for the RCS file <file>.
872 */
873 const char *
rcs_desc_get(RCSFILE * file)874 rcs_desc_get(RCSFILE *file)
875 {
876 return (file->rf_desc);
877 }
878
879 /*
880 * rcs_desc_set()
881 *
882 * Set the description for the RCS file <file>.
883 */
884 void
rcs_desc_set(RCSFILE * file,const char * desc)885 rcs_desc_set(RCSFILE *file, const char *desc)
886 {
887 char *tmp;
888
889 tmp = xstrdup(desc);
890 free(file->rf_desc);
891 file->rf_desc = tmp;
892 file->rf_flags &= ~RCS_SYNCED;
893 }
894
895 /*
896 * rcs_comment_lookup()
897 *
898 * Lookup the assumed comment leader based on a file's suffix.
899 * Returns a pointer to the string on success, or NULL on failure.
900 */
901 const char *
rcs_comment_lookup(const char * filename)902 rcs_comment_lookup(const char *filename)
903 {
904 int i;
905 const char *sp;
906
907 if ((sp = strrchr(filename, '.')) == NULL)
908 return (NULL);
909 sp++;
910
911 for (i = 0; i < (int)NB_COMTYPES; i++)
912 if (strcmp(rcs_comments[i].rc_suffix, sp) == 0)
913 return (rcs_comments[i].rc_cstr);
914 return (NULL);
915 }
916
917 /*
918 * rcs_comment_get()
919 *
920 * Retrieve the comment leader for the RCS file <file>.
921 */
922 const char *
rcs_comment_get(RCSFILE * file)923 rcs_comment_get(RCSFILE *file)
924 {
925 return (file->rf_comment);
926 }
927
928 /*
929 * rcs_comment_set()
930 *
931 * Set the comment leader for the RCS file <file>.
932 */
933 void
rcs_comment_set(RCSFILE * file,const char * comment)934 rcs_comment_set(RCSFILE *file, const char *comment)
935 {
936 char *tmp;
937
938 tmp = xstrdup(comment);
939 free(file->rf_comment);
940 file->rf_comment = tmp;
941 file->rf_flags &= ~RCS_SYNCED;
942 }
943
944 int
rcs_patch_lines(struct rcs_lines * dlines,struct rcs_lines * plines,struct rcs_line ** alines,struct rcs_delta * rdp)945 rcs_patch_lines(struct rcs_lines *dlines, struct rcs_lines *plines,
946 struct rcs_line **alines, struct rcs_delta *rdp)
947 {
948 u_char op;
949 char *ep;
950 struct rcs_line *lp, *dlp, *ndlp;
951 int i, lineno, nbln;
952 u_char tmp;
953
954 dlp = TAILQ_FIRST(&(dlines->l_lines));
955 lp = TAILQ_FIRST(&(plines->l_lines));
956
957 /* skip first bogus line */
958 for (lp = TAILQ_NEXT(lp, l_list); lp != NULL;
959 lp = TAILQ_NEXT(lp, l_list)) {
960 if (lp->l_len < 2)
961 fatal("line too short, RCS patch seems broken");
962 op = *(lp->l_line);
963 /* NUL-terminate line buffer for strtol() safety. */
964 tmp = lp->l_line[lp->l_len - 1];
965 lp->l_line[lp->l_len - 1] = '\0';
966 lineno = (int)strtol((char*)(lp->l_line + 1), &ep, 10);
967 if (lineno - 1 > dlines->l_nblines || lineno < 0) {
968 fatal("invalid line specification in RCS patch");
969 }
970 ep++;
971 nbln = (int)strtol(ep, &ep, 10);
972 /* Restore the last byte of the buffer */
973 lp->l_line[lp->l_len - 1] = tmp;
974 if (nbln < 0)
975 fatal("invalid line number specification in RCS patch");
976
977 /* find the appropriate line */
978 for (;;) {
979 if (dlp == NULL)
980 break;
981 if (dlp->l_lineno == lineno)
982 break;
983 if (dlp->l_lineno > lineno) {
984 dlp = TAILQ_PREV(dlp, tqh, l_list);
985 } else if (dlp->l_lineno < lineno) {
986 if (((ndlp = TAILQ_NEXT(dlp, l_list)) == NULL) ||
987 ndlp->l_lineno > lineno)
988 break;
989 dlp = ndlp;
990 }
991 }
992 if (dlp == NULL)
993 fatal("can't find referenced line in RCS patch");
994
995 if (op == 'd') {
996 for (i = 0; (i < nbln) && (dlp != NULL); i++) {
997 ndlp = TAILQ_NEXT(dlp, l_list);
998 TAILQ_REMOVE(&(dlines->l_lines), dlp, l_list);
999 if (alines != NULL && dlp->l_line != NULL) {
1000 dlp->l_delta = rdp;
1001 alines[dlp->l_lineno_orig - 1] =
1002 dlp;
1003 } else
1004 free(dlp);
1005 dlp = ndlp;
1006 /* last line is gone - reset dlp */
1007 if (dlp == NULL) {
1008 ndlp = TAILQ_LAST(&(dlines->l_lines),
1009 tqh);
1010 dlp = ndlp;
1011 }
1012 }
1013 } else if (op == 'a') {
1014 for (i = 0; i < nbln; i++) {
1015 ndlp = lp;
1016 lp = TAILQ_NEXT(lp, l_list);
1017 if (lp == NULL)
1018 fatal("truncated RCS patch");
1019 TAILQ_REMOVE(&(plines->l_lines), lp, l_list);
1020 if (alines != NULL) {
1021 if (lp->l_needsfree == 1)
1022 free(lp->l_line);
1023 lp->l_line = NULL;
1024 lp->l_needsfree = 0;
1025 }
1026 lp->l_delta = rdp;
1027 TAILQ_INSERT_AFTER(&(dlines->l_lines), dlp,
1028 lp, l_list);
1029 dlp = lp;
1030
1031 /* we don't want lookup to block on those */
1032 lp->l_lineno = lineno;
1033
1034 lp = ndlp;
1035 }
1036 } else
1037 fatal("unknown RCS patch operation `%c'", op);
1038
1039 /* last line of the patch, done */
1040 if (lp->l_lineno == plines->l_nblines)
1041 break;
1042 }
1043
1044 /* once we're done patching, rebuild the line numbers */
1045 lineno = 0;
1046 TAILQ_FOREACH(lp, &(dlines->l_lines), l_list)
1047 lp->l_lineno = lineno++;
1048 dlines->l_nblines = lineno - 1;
1049
1050 return (0);
1051 }
1052
1053 void
rcs_delta_stats(struct rcs_delta * rdp,int * ladded,int * lremoved)1054 rcs_delta_stats(struct rcs_delta *rdp, int *ladded, int *lremoved)
1055 {
1056 struct rcs_lines *plines;
1057 struct rcs_line *lp;
1058 int added, i, nbln, removed;
1059 char op, *ep;
1060 u_char tmp;
1061
1062 added = removed = 0;
1063
1064 plines = cvs_splitlines(rdp->rd_text, rdp->rd_tlen);
1065 lp = TAILQ_FIRST(&(plines->l_lines));
1066
1067 /* skip first bogus line */
1068 for (lp = TAILQ_NEXT(lp, l_list); lp != NULL;
1069 lp = TAILQ_NEXT(lp, l_list)) {
1070 if (lp->l_len < 2)
1071 fatal("line too short, RCS patch seems broken");
1072 op = *(lp->l_line);
1073 /* NUL-terminate line buffer for strtol() safety. */
1074 tmp = lp->l_line[lp->l_len - 1];
1075 lp->l_line[lp->l_len - 1] = '\0';
1076 (void)strtol((lp->l_line + 1), &ep, 10);
1077 ep++;
1078 nbln = (int)strtol(ep, &ep, 10);
1079 /* Restore the last byte of the buffer */
1080 lp->l_line[lp->l_len - 1] = tmp;
1081 if (nbln < 0)
1082 fatal("invalid line number specification in RCS patch");
1083
1084 if (op == 'a') {
1085 added += nbln;
1086 for (i = 0; i < nbln; i++) {
1087 lp = TAILQ_NEXT(lp, l_list);
1088 if (lp == NULL)
1089 fatal("truncated RCS patch");
1090 }
1091 }
1092 else if (op == 'd')
1093 removed += nbln;
1094 else
1095 fatal("unknown RCS patch operation '%c'", op);
1096 }
1097
1098 cvs_freelines(plines);
1099
1100 *ladded = added;
1101 *lremoved = removed;
1102 }
1103
1104 /*
1105 * rcs_rev_add()
1106 *
1107 * Add a revision to the RCS file <rf>. The new revision's number can be
1108 * specified in <rev> (which can also be RCS_HEAD_REV, in which case the
1109 * new revision will have a number equal to the previous head revision plus
1110 * one). The <msg> argument specifies the log message for that revision, and
1111 * <date> specifies the revision's date (a value of -1 is
1112 * equivalent to using the current time).
1113 * If <author> is NULL, set the author for this revision to the current user.
1114 * Returns 0 on success, or -1 on failure.
1115 */
1116 int
rcs_rev_add(RCSFILE * rf,RCSNUM * rev,const char * msg,time_t date,const char * author)1117 rcs_rev_add(RCSFILE *rf, RCSNUM *rev, const char *msg, time_t date,
1118 const char *author)
1119 {
1120 time_t now;
1121 RCSNUM *root = NULL;
1122 struct passwd *pw;
1123 struct rcs_branch *brp, *obrp;
1124 struct rcs_delta *ordp, *rdp;
1125
1126 if (rev == RCS_HEAD_REV) {
1127 if (rf->rf_flags & RCS_CREATE) {
1128 if ((rev = rcsnum_parse(RCS_HEAD_INIT)) == NULL)
1129 return (-1);
1130 free(rf->rf_head);
1131 rf->rf_head = rev;
1132 } else if (rf->rf_head == NULL) {
1133 return (-1);
1134 } else {
1135 rev = rcsnum_inc(rf->rf_head);
1136 }
1137 } else {
1138 if ((rdp = rcs_findrev(rf, rev)) != NULL)
1139 return (-1);
1140 }
1141
1142 rdp = xcalloc(1, sizeof(*rdp));
1143
1144 TAILQ_INIT(&(rdp->rd_branches));
1145
1146 rdp->rd_num = rcsnum_alloc();
1147 rcsnum_cpy(rev, rdp->rd_num, 0);
1148
1149 rdp->rd_next = rcsnum_alloc();
1150
1151 if (!author && !(author = getlogin())) {
1152 if (!(pw = getpwuid(getuid())))
1153 fatal("getpwuid failed");
1154 author = pw->pw_name;
1155 }
1156 rdp->rd_author = xstrdup(author);
1157 rdp->rd_state = xstrdup(RCS_STATE_EXP);
1158 rdp->rd_log = xstrdup(msg);
1159
1160 if (date != (time_t)(-1))
1161 now = date;
1162 else
1163 time(&now);
1164 gmtime_r(&now, &(rdp->rd_date));
1165
1166 if (RCSNUM_ISBRANCHREV(rev))
1167 TAILQ_INSERT_TAIL(&(rf->rf_delta), rdp, rd_list);
1168 else
1169 TAILQ_INSERT_HEAD(&(rf->rf_delta), rdp, rd_list);
1170 rf->rf_ndelta++;
1171
1172 if (!(rf->rf_flags & RCS_CREATE)) {
1173 if (RCSNUM_ISBRANCHREV(rev)) {
1174 if (rev->rn_id[rev->rn_len - 1] == 1) {
1175 /* a new branch */
1176 root = rcsnum_branch_root(rev);
1177 brp = xmalloc(sizeof(*brp));
1178 brp->rb_num = rcsnum_alloc();
1179 rcsnum_cpy(rdp->rd_num, brp->rb_num, 0);
1180
1181 if ((ordp = rcs_findrev(rf, root)) == NULL)
1182 fatal("root node not found");
1183
1184 TAILQ_FOREACH(obrp, &(ordp->rd_branches),
1185 rb_list) {
1186 if (!rcsnum_cmp(obrp->rb_num,
1187 brp->rb_num,
1188 brp->rb_num->rn_len - 1))
1189 break;
1190 }
1191
1192 if (obrp == NULL) {
1193 TAILQ_INSERT_TAIL(&(ordp->rd_branches),
1194 brp, rb_list);
1195 }
1196 } else {
1197 root = rcsnum_alloc();
1198 rcsnum_cpy(rev, root, 0);
1199 rcsnum_dec(root);
1200 if ((ordp = rcs_findrev(rf, root)) == NULL)
1201 fatal("previous revision not found");
1202 rcsnum_cpy(rdp->rd_num, ordp->rd_next, 0);
1203 }
1204 } else {
1205 ordp = TAILQ_NEXT(rdp, rd_list);
1206 rcsnum_cpy(ordp->rd_num, rdp->rd_next, 0);
1207 }
1208 }
1209
1210 free(root);
1211
1212 /* not synced anymore */
1213 rf->rf_flags &= ~RCS_SYNCED;
1214
1215 return (0);
1216 }
1217
1218 /*
1219 * rcs_rev_remove()
1220 *
1221 * Remove the revision whose number is <rev> from the RCS file <rf>.
1222 */
1223 int
rcs_rev_remove(RCSFILE * rf,RCSNUM * rev)1224 rcs_rev_remove(RCSFILE *rf, RCSNUM *rev)
1225 {
1226 int fd1, fd2;
1227 char *path_tmp1, *path_tmp2;
1228 struct rcs_delta *rdp, *prevrdp, *nextrdp;
1229 BUF *prevbuf, *newdiff, *newdeltatext;
1230
1231 if (rev == RCS_HEAD_REV)
1232 rev = rf->rf_head;
1233
1234 if (rev == NULL)
1235 return (-1);
1236
1237 /* do we actually have that revision? */
1238 if ((rdp = rcs_findrev(rf, rev)) == NULL)
1239 return (-1);
1240
1241 /*
1242 * This is confusing, the previous delta is next in the TAILQ list.
1243 * the next delta is the previous one in the TAILQ list.
1244 *
1245 * When the HEAD revision got specified, nextrdp will be NULL.
1246 * When the first revision got specified, prevrdp will be NULL.
1247 */
1248 prevrdp = (struct rcs_delta *)TAILQ_NEXT(rdp, rd_list);
1249 nextrdp = (struct rcs_delta *)TAILQ_PREV(rdp, tqh, rd_list);
1250
1251 newdeltatext = NULL;
1252 prevbuf = NULL;
1253 path_tmp1 = path_tmp2 = NULL;
1254
1255 if (prevrdp != NULL && nextrdp != NULL) {
1256 newdiff = buf_alloc(64);
1257
1258 /* calculate new diff */
1259 (void)xasprintf(&path_tmp1, "%s/diff1.XXXXXXXXXX", cvs_tmpdir);
1260 fd1 = rcs_rev_write_stmp(rf, nextrdp->rd_num, path_tmp1, 0);
1261
1262 (void)xasprintf(&path_tmp2, "%s/diff2.XXXXXXXXXX", cvs_tmpdir);
1263 fd2 = rcs_rev_write_stmp(rf, prevrdp->rd_num, path_tmp2, 0);
1264
1265 diff_format = D_RCSDIFF;
1266 if (diffreg(path_tmp1, path_tmp2,
1267 fd1, fd2, newdiff, D_FORCEASCII) == D_ERROR)
1268 fatal("rcs_diffreg failed");
1269
1270 close(fd1);
1271 close(fd2);
1272
1273 newdeltatext = newdiff;
1274 } else if (nextrdp == NULL && prevrdp != NULL) {
1275 newdeltatext = prevbuf;
1276 }
1277
1278 if (newdeltatext != NULL) {
1279 if (rcs_deltatext_set(rf, prevrdp->rd_num, newdeltatext) < 0)
1280 fatal("error setting new deltatext");
1281 }
1282
1283 TAILQ_REMOVE(&(rf->rf_delta), rdp, rd_list);
1284
1285 /* update pointers */
1286 if (prevrdp != NULL && nextrdp != NULL) {
1287 rcsnum_cpy(prevrdp->rd_num, nextrdp->rd_next, 0);
1288 } else if (prevrdp != NULL) {
1289 if (rcs_head_set(rf, prevrdp->rd_num) < 0)
1290 fatal("rcs_head_set failed");
1291 } else if (nextrdp != NULL) {
1292 free(nextrdp->rd_next);
1293 nextrdp->rd_next = rcsnum_alloc();
1294 } else {
1295 free(rf->rf_head);
1296 rf->rf_head = NULL;
1297 }
1298
1299 rf->rf_ndelta--;
1300 rf->rf_flags &= ~RCS_SYNCED;
1301
1302 rcs_freedelta(rdp);
1303 free(newdeltatext);
1304 free(path_tmp1);
1305 free(path_tmp2);
1306
1307 return (0);
1308 }
1309
1310 /*
1311 * rcs_findrev()
1312 *
1313 * Find a specific revision's delta entry in the tree of the RCS file <rfp>.
1314 * The revision number is given in <rev>.
1315 *
1316 * Returns a pointer to the delta on success, or NULL on failure.
1317 */
1318 struct rcs_delta *
rcs_findrev(RCSFILE * rfp,RCSNUM * rev)1319 rcs_findrev(RCSFILE *rfp, RCSNUM *rev)
1320 {
1321 int isbrev;
1322 struct rcs_delta *rdp;
1323
1324 if (rev == NULL)
1325 return NULL;
1326
1327 isbrev = RCSNUM_ISBRANCHREV(rev);
1328
1329 /*
1330 * We need to do more parsing if the last revision in the linked list
1331 * is greater than the requested revision.
1332 */
1333 rdp = TAILQ_LAST(&(rfp->rf_delta), rcs_dlist);
1334 if (rdp == NULL ||
1335 (!isbrev && rcsnum_cmp(rdp->rd_num, rev, 0) == -1) ||
1336 ((isbrev && rdp->rd_num->rn_len < 4) ||
1337 (isbrev && rcsnum_differ(rev, rdp->rd_num)))) {
1338 if (rcsparse_deltas(rfp, rev))
1339 fatal("error parsing deltas");
1340 }
1341
1342 TAILQ_FOREACH(rdp, &(rfp->rf_delta), rd_list) {
1343 if (rcsnum_differ(rdp->rd_num, rev))
1344 continue;
1345 else
1346 return (rdp);
1347 }
1348
1349 return (NULL);
1350 }
1351
1352 /*
1353 * rcs_kwexp_set()
1354 *
1355 * Set the keyword expansion mode to use on the RCS file <file> to <mode>.
1356 */
1357 void
rcs_kwexp_set(RCSFILE * file,int mode)1358 rcs_kwexp_set(RCSFILE *file, int mode)
1359 {
1360 int i;
1361 char *tmp, buf[8] = "";
1362
1363 if (RCS_KWEXP_INVAL(mode))
1364 return;
1365
1366 i = 0;
1367 if (mode == RCS_KWEXP_NONE)
1368 buf[0] = 'b';
1369 else if (mode == RCS_KWEXP_OLD)
1370 buf[0] = 'o';
1371 else {
1372 if (mode & RCS_KWEXP_NAME)
1373 buf[i++] = 'k';
1374 if (mode & RCS_KWEXP_VAL)
1375 buf[i++] = 'v';
1376 if (mode & RCS_KWEXP_LKR)
1377 buf[i++] = 'l';
1378 }
1379
1380 tmp = xstrdup(buf);
1381 free(file->rf_expand);
1382 file->rf_expand = tmp;
1383 /* not synced anymore */
1384 file->rf_flags &= ~RCS_SYNCED;
1385 }
1386
1387 /*
1388 * rcs_kwexp_get()
1389 *
1390 * Retrieve the keyword expansion mode to be used for the RCS file <file>.
1391 */
1392 int
rcs_kwexp_get(RCSFILE * file)1393 rcs_kwexp_get(RCSFILE *file)
1394 {
1395 if (file->rf_expand == NULL)
1396 return (RCS_KWEXP_DEFAULT);
1397
1398 return (rcs_kflag_get(file->rf_expand));
1399 }
1400
1401 /*
1402 * rcs_kflag_get()
1403 *
1404 * Get the keyword expansion mode from a set of character flags given in
1405 * <flags> and return the appropriate flag mask. In case of an error, the
1406 * returned mask will have the RCS_KWEXP_ERR bit set to 1.
1407 */
1408 int
rcs_kflag_get(const char * flags)1409 rcs_kflag_get(const char *flags)
1410 {
1411 int fl;
1412 size_t len;
1413 const char *fp;
1414
1415 if (flags == NULL || !(len = strlen(flags)))
1416 return (RCS_KWEXP_ERR);
1417
1418 fl = 0;
1419 for (fp = flags; *fp != '\0'; fp++) {
1420 if (*fp == 'k')
1421 fl |= RCS_KWEXP_NAME;
1422 else if (*fp == 'v')
1423 fl |= RCS_KWEXP_VAL;
1424 else if (*fp == 'l')
1425 fl |= RCS_KWEXP_LKR;
1426 else if (*fp == 'o') {
1427 if (len != 1)
1428 fl |= RCS_KWEXP_ERR;
1429 fl |= RCS_KWEXP_OLD;
1430 } else if (*fp == 'b') {
1431 if (len != 1)
1432 fl |= RCS_KWEXP_ERR;
1433 fl |= RCS_KWEXP_NONE;
1434 } else /* unknown letter */
1435 fl |= RCS_KWEXP_ERR;
1436 }
1437
1438 return (fl);
1439 }
1440
1441 /*
1442 * rcs_freedelta()
1443 *
1444 * Free the contents of a delta structure.
1445 */
1446 static void
rcs_freedelta(struct rcs_delta * rdp)1447 rcs_freedelta(struct rcs_delta *rdp)
1448 {
1449 struct rcs_branch *rb;
1450
1451 free(rdp->rd_num);
1452 free(rdp->rd_next);
1453 free(rdp->rd_author);
1454 free(rdp->rd_locker);
1455 free(rdp->rd_state);
1456 free(rdp->rd_log);
1457 free(rdp->rd_text);
1458
1459 while ((rb = TAILQ_FIRST(&(rdp->rd_branches))) != NULL) {
1460 TAILQ_REMOVE(&(rdp->rd_branches), rb, rb_list);
1461 free(rb->rb_num);
1462 free(rb);
1463 }
1464
1465 free(rdp);
1466 }
1467
1468 /*
1469 * rcs_strprint()
1470 *
1471 * Output an RCS string <str> of size <slen> to the stream <stream>. Any
1472 * '@' characters are escaped. Otherwise, the string can contain arbitrary
1473 * binary data.
1474 */
1475 static void
rcs_strprint(const u_char * str,size_t slen,FILE * stream)1476 rcs_strprint(const u_char *str, size_t slen, FILE *stream)
1477 {
1478 const u_char *ap, *ep, *sp;
1479
1480 if (slen == 0)
1481 return;
1482
1483 ep = str + slen - 1;
1484
1485 for (sp = str; sp <= ep;) {
1486 ap = memchr(sp, '@', ep - sp);
1487 if (ap == NULL)
1488 ap = ep;
1489 (void)fwrite(sp, sizeof(u_char), ap - sp + 1, stream);
1490
1491 if (*ap == '@')
1492 putc('@', stream);
1493 sp = ap + 1;
1494 }
1495 }
1496
1497 /*
1498 * rcs_deltatext_set()
1499 *
1500 * Set deltatext for <rev> in RCS file <rfp> to <dtext>
1501 * Returns -1 on error, 0 on success.
1502 */
1503 int
rcs_deltatext_set(RCSFILE * rfp,RCSNUM * rev,BUF * bp)1504 rcs_deltatext_set(RCSFILE *rfp, RCSNUM *rev, BUF *bp)
1505 {
1506 size_t len;
1507 u_char *dtext;
1508 struct rcs_delta *rdp;
1509
1510 /* Write operations require full parsing */
1511 if (rcsparse_deltatexts(rfp, NULL))
1512 return (-1);
1513
1514 if ((rdp = rcs_findrev(rfp, rev)) == NULL)
1515 return (-1);
1516
1517 free(rdp->rd_text);
1518
1519 len = buf_len(bp);
1520 dtext = buf_release(bp);
1521 bp = NULL;
1522
1523 if (len != 0) {
1524 rdp->rd_text = xmalloc(len);
1525 rdp->rd_tlen = len;
1526 (void)memcpy(rdp->rd_text, dtext, len);
1527 } else {
1528 rdp->rd_text = NULL;
1529 rdp->rd_tlen = 0;
1530 }
1531
1532 free(dtext);
1533 return (0);
1534 }
1535
1536 /*
1537 * rcs_rev_setlog()
1538 *
1539 * Sets the log message of revision <rev> to <logtext>.
1540 */
1541 int
rcs_rev_setlog(RCSFILE * rfp,RCSNUM * rev,const char * logtext)1542 rcs_rev_setlog(RCSFILE *rfp, RCSNUM *rev, const char *logtext)
1543 {
1544 struct rcs_delta *rdp;
1545
1546 if ((rdp = rcs_findrev(rfp, rev)) == NULL)
1547 return (-1);
1548
1549 free(rdp->rd_log);
1550
1551 rdp->rd_log = xstrdup(logtext);
1552 rfp->rf_flags &= ~RCS_SYNCED;
1553 return (0);
1554 }
1555 /*
1556 * rcs_rev_getdate()
1557 *
1558 * Get the date corresponding to a given revision.
1559 * Returns the date on success, -1 on failure.
1560 */
1561 time_t
rcs_rev_getdate(RCSFILE * rfp,RCSNUM * rev)1562 rcs_rev_getdate(RCSFILE *rfp, RCSNUM *rev)
1563 {
1564 struct rcs_delta *rdp;
1565
1566 if ((rdp = rcs_findrev(rfp, rev)) == NULL)
1567 return (-1);
1568
1569 return (timegm(&rdp->rd_date));
1570 }
1571
1572 /*
1573 * rcs_state_set()
1574 *
1575 * Sets the state of revision <rev> to <state>
1576 * NOTE: default state is 'Exp'. States may not contain spaces.
1577 *
1578 * Returns -1 on failure, 0 on success.
1579 */
1580 int
rcs_state_set(RCSFILE * rfp,RCSNUM * rev,const char * state)1581 rcs_state_set(RCSFILE *rfp, RCSNUM *rev, const char *state)
1582 {
1583 struct rcs_delta *rdp;
1584
1585 if ((rdp = rcs_findrev(rfp, rev)) == NULL)
1586 return (-1);
1587
1588 free(rdp->rd_state);
1589
1590 rdp->rd_state = xstrdup(state);
1591
1592 rfp->rf_flags &= ~RCS_SYNCED;
1593
1594 return (0);
1595 }
1596
1597 /*
1598 * rcs_state_check()
1599 *
1600 * Check if string <state> is valid.
1601 *
1602 * Returns 0 if the string is valid, -1 otherwise.
1603 */
1604 int
rcs_state_check(const char * state)1605 rcs_state_check(const char *state)
1606 {
1607 if (strcmp(state, RCS_STATE_DEAD) && strcmp(state, RCS_STATE_EXP))
1608 return (-1);
1609
1610 return (0);
1611 }
1612
1613 /*
1614 * rcs_state_get()
1615 *
1616 * Get the state for a given revision of a specified RCSFILE.
1617 *
1618 * Returns NULL on failure.
1619 */
1620 const char *
rcs_state_get(RCSFILE * rfp,RCSNUM * rev)1621 rcs_state_get(RCSFILE *rfp, RCSNUM *rev)
1622 {
1623 struct rcs_delta *rdp;
1624
1625 if ((rdp = rcs_findrev(rfp, rev)) == NULL)
1626 return (NULL);
1627
1628 return (rdp->rd_state);
1629 }
1630
1631 /* rcs_get_revision() */
1632 static RCSNUM *
rcs_get_revision(const char * revstr,RCSFILE * rfp)1633 rcs_get_revision(const char *revstr, RCSFILE *rfp)
1634 {
1635 RCSNUM *rev, *brev, *frev;
1636 struct rcs_branch *brp;
1637 struct rcs_delta *rdp;
1638 size_t i;
1639
1640 rdp = NULL;
1641
1642 if (!strcmp(revstr, RCS_HEAD_BRANCH)) {
1643 if (rfp->rf_head == NULL)
1644 return (NULL);
1645
1646 frev = rcsnum_alloc();
1647 rcsnum_cpy(rfp->rf_head, frev, 0);
1648 return (frev);
1649 }
1650
1651 /* Possibly we could be passed a version number */
1652 if ((rev = rcsnum_parse(revstr)) != NULL) {
1653 /* Do not return if it is not in RCS file */
1654 if ((rdp = rcs_findrev(rfp, rev)) != NULL)
1655 return (rev);
1656 } else {
1657 /* More likely we will be passed a symbol */
1658 rev = rcs_sym_getrev(rfp, revstr);
1659 }
1660
1661 if (rev == NULL)
1662 return (NULL);
1663
1664 /*
1665 * If it was not a branch, thats ok the symbolic
1666 * name referred to a revision, so return the resolved
1667 * revision for the given name. */
1668 if (!RCSNUM_ISBRANCH(rev)) {
1669 /* Sanity check: The first two elements of any
1670 * revision (be it on a branch or on trunk) cannot
1671 * be greater than HEAD.
1672 *
1673 * XXX: To avoid comparing to uninitialized memory,
1674 * the minimum of both revision lengths is taken
1675 * instead of just 2.
1676 */
1677 if (rfp->rf_head == NULL || rcsnum_cmp(rev, rfp->rf_head,
1678 MINIMUM(rfp->rf_head->rn_len, rev->rn_len)) < 0) {
1679 free(rev);
1680 return (NULL);
1681 }
1682 return (rev);
1683 }
1684
1685 brev = rcsnum_alloc();
1686 rcsnum_cpy(rev, brev, rev->rn_len - 1);
1687
1688 if ((rdp = rcs_findrev(rfp, brev)) == NULL)
1689 fatal("rcs_get_revision: tag `%s' does not exist", revstr);
1690 free(brev);
1691
1692 TAILQ_FOREACH(brp, &(rdp->rd_branches), rb_list) {
1693 for (i = 0; i < rev->rn_len; i++)
1694 if (brp->rb_num->rn_id[i] != rev->rn_id[i])
1695 break;
1696 if (i != rev->rn_len)
1697 continue;
1698 break;
1699 }
1700
1701 free(rev);
1702 frev = rcsnum_alloc();
1703 if (brp == NULL) {
1704 rcsnum_cpy(rdp->rd_num, frev, 0);
1705 return (frev);
1706 } else {
1707 /* Fetch the delta with the correct branch num */
1708 if ((rdp = rcs_findrev(rfp, brp->rb_num)) == NULL)
1709 fatal("rcs_get_revision: could not fetch branch "
1710 "delta");
1711 rcsnum_cpy(rdp->rd_num, frev, 0);
1712 return (frev);
1713 }
1714 }
1715
1716 /*
1717 * rcs_rev_getlines()
1718 *
1719 * Get the entire contents of revision <frev> from the RCSFILE <rfp> and
1720 * return it as a pointer to a struct rcs_lines.
1721 */
1722 struct rcs_lines *
rcs_rev_getlines(RCSFILE * rfp,RCSNUM * frev,struct rcs_line *** alines)1723 rcs_rev_getlines(RCSFILE *rfp, RCSNUM *frev, struct rcs_line ***alines)
1724 {
1725 size_t plen;
1726 int annotate, done, i, nextroot;
1727 RCSNUM *tnum, *bnum;
1728 struct rcs_branch *brp;
1729 struct rcs_delta *hrdp, *prdp, *rdp, *trdp;
1730 u_char *patch;
1731 struct rcs_line *line, *nline;
1732 struct rcs_lines *dlines, *plines;
1733
1734 hrdp = prdp = rdp = trdp = NULL;
1735
1736 if (rfp->rf_head == NULL ||
1737 (hrdp = rcs_findrev(rfp, rfp->rf_head)) == NULL)
1738 fatal("rcs_rev_getlines: no HEAD revision");
1739
1740 tnum = frev;
1741 if (rcsparse_deltatexts(rfp, hrdp->rd_num))
1742 fatal("rcs_rev_getlines: rcsparse_deltatexts");
1743
1744 /* revision on branch, get the branch root */
1745 nextroot = 2;
1746 bnum = rcsnum_alloc();
1747 if (RCSNUM_ISBRANCHREV(tnum))
1748 rcsnum_cpy(tnum, bnum, nextroot);
1749 else
1750 rcsnum_cpy(tnum, bnum, tnum->rn_len);
1751
1752 if (alines != NULL) {
1753 /* start with annotate first at requested revision */
1754 annotate = ANNOTATE_LATER;
1755 *alines = NULL;
1756 } else
1757 annotate = ANNOTATE_NEVER;
1758
1759 dlines = cvs_splitlines(hrdp->rd_text, hrdp->rd_tlen);
1760
1761 done = 0;
1762
1763 rdp = hrdp;
1764 if (!rcsnum_differ(rdp->rd_num, bnum)) {
1765 if (annotate == ANNOTATE_LATER) {
1766 /* found requested revision for annotate */
1767 i = 0;
1768 TAILQ_FOREACH(line, &(dlines->l_lines), l_list) {
1769 line->l_lineno_orig = line->l_lineno;
1770 i++;
1771 }
1772
1773 *alines = xcalloc(i + 1, sizeof(struct rcs_line *));
1774 (*alines)[i] = NULL;
1775 annotate = ANNOTATE_NOW;
1776
1777 /* annotate down to 1.1 from where we are */
1778 free(bnum);
1779 bnum = rcsnum_parse("1.1");
1780 if (!rcsnum_differ(rdp->rd_num, bnum)) {
1781 goto next;
1782 }
1783 } else
1784 goto next;
1785 }
1786
1787 prdp = hrdp;
1788 if ((rdp = rcs_findrev(rfp, hrdp->rd_next)) == NULL)
1789 goto done;
1790
1791 again:
1792 while (rdp != NULL) {
1793 if (rdp->rd_next->rn_len != 0) {
1794 trdp = rcs_findrev(rfp, rdp->rd_next);
1795 if (trdp == NULL)
1796 fatal("failed to grab next revision");
1797 }
1798
1799 if (rdp->rd_tlen == 0) {
1800 if (rcsparse_deltatexts(rfp, rdp->rd_num))
1801 fatal("rcs_rev_getlines: rcsparse_deltatexts");
1802 if (rdp->rd_tlen == 0) {
1803 if (!rcsnum_differ(rdp->rd_num, bnum))
1804 break;
1805 rdp = trdp;
1806 continue;
1807 }
1808 }
1809
1810 plen = rdp->rd_tlen;
1811 patch = rdp->rd_text;
1812 plines = cvs_splitlines(patch, plen);
1813 if (annotate == ANNOTATE_NOW)
1814 rcs_patch_lines(dlines, plines, *alines, prdp);
1815 else
1816 rcs_patch_lines(dlines, plines, NULL, NULL);
1817 cvs_freelines(plines);
1818
1819 if (!rcsnum_differ(rdp->rd_num, bnum)) {
1820 if (annotate != ANNOTATE_LATER)
1821 break;
1822
1823 /* found requested revision for annotate */
1824 i = 0;
1825 TAILQ_FOREACH(line, &(dlines->l_lines), l_list) {
1826 line->l_lineno_orig = line->l_lineno;
1827 i++;
1828 }
1829
1830 *alines = xcalloc(i + 1, sizeof(struct rcs_line *));
1831 (*alines)[i] = NULL;
1832 annotate = ANNOTATE_NOW;
1833
1834 /* annotate down to 1.1 from where we are */
1835 free(bnum);
1836 bnum = rcsnum_parse("1.1");
1837
1838 if (!rcsnum_differ(rdp->rd_num, bnum))
1839 break;
1840 }
1841
1842 prdp = rdp;
1843 rdp = trdp;
1844 }
1845
1846 next:
1847 if (rdp == NULL || !rcsnum_differ(rdp->rd_num, frev))
1848 done = 1;
1849
1850 if (RCSNUM_ISBRANCHREV(frev) && done != 1) {
1851 nextroot += 2;
1852 rcsnum_cpy(frev, bnum, nextroot);
1853
1854 TAILQ_FOREACH(brp, &(rdp->rd_branches), rb_list) {
1855 for (i = 0; i < nextroot - 1; i++)
1856 if (brp->rb_num->rn_id[i] != bnum->rn_id[i])
1857 break;
1858 if (i == nextroot - 1)
1859 break;
1860 }
1861
1862 if (brp == NULL) {
1863 if (annotate != ANNOTATE_NEVER) {
1864 free(*alines);
1865 *alines = NULL;
1866 cvs_freelines(dlines);
1867 free(bnum);
1868 return (NULL);
1869 }
1870 fatal("expected branch not found on branch list");
1871 }
1872
1873 if ((rdp = rcs_findrev(rfp, brp->rb_num)) == NULL)
1874 fatal("rcs_rev_getlines: failed to get delta for target rev");
1875
1876 goto again;
1877 }
1878 done:
1879 /* put remaining lines into annotate buffer */
1880 if (annotate == ANNOTATE_NOW) {
1881 for (line = TAILQ_FIRST(&(dlines->l_lines));
1882 line != NULL; line = nline) {
1883 nline = TAILQ_NEXT(line, l_list);
1884 TAILQ_REMOVE(&(dlines->l_lines), line, l_list);
1885 if (line->l_line == NULL) {
1886 free(line);
1887 continue;
1888 }
1889
1890 line->l_delta = rdp;
1891 (*alines)[line->l_lineno_orig - 1] = line;
1892 }
1893
1894 cvs_freelines(dlines);
1895 dlines = NULL;
1896 }
1897
1898 if (bnum != tnum)
1899 free(bnum);
1900
1901 return (dlines);
1902 }
1903
1904 void
rcs_annotate_getlines(RCSFILE * rfp,RCSNUM * frev,struct rcs_line *** alines)1905 rcs_annotate_getlines(RCSFILE *rfp, RCSNUM *frev, struct rcs_line ***alines)
1906 {
1907 size_t plen;
1908 int i, nextroot;
1909 RCSNUM *bnum;
1910 struct rcs_branch *brp;
1911 struct rcs_delta *rdp, *trdp;
1912 u_char *patch;
1913 struct rcs_line *line;
1914 struct rcs_lines *dlines, *plines;
1915
1916 rdp = trdp = NULL;
1917
1918 if (!RCSNUM_ISBRANCHREV(frev))
1919 fatal("rcs_annotate_getlines: branch revision expected");
1920
1921 /* revision on branch, get the branch root */
1922 nextroot = 2;
1923 bnum = rcsnum_alloc();
1924 rcsnum_cpy(frev, bnum, nextroot);
1925
1926 /*
1927 * Going from HEAD to 1.1 enables the use of an array, which is
1928 * much faster. Unfortunately this is not possible with branch
1929 * revisions, so copy over our alines (array) into dlines (tailq).
1930 */
1931 dlines = xcalloc(1, sizeof(*dlines));
1932 TAILQ_INIT(&(dlines->l_lines));
1933 line = xcalloc(1, sizeof(*line));
1934 TAILQ_INSERT_TAIL(&(dlines->l_lines), line, l_list);
1935
1936 for (i = 0; (*alines)[i] != NULL; i++) {
1937 line = (*alines)[i];
1938 line->l_lineno = i + 1;
1939 TAILQ_INSERT_TAIL(&(dlines->l_lines), line, l_list);
1940 }
1941
1942 rdp = rcs_findrev(rfp, bnum);
1943 if (rdp == NULL)
1944 fatal("failed to grab branch root revision");
1945
1946 do {
1947 nextroot += 2;
1948 rcsnum_cpy(frev, bnum, nextroot);
1949
1950 TAILQ_FOREACH(brp, &(rdp->rd_branches), rb_list) {
1951 for (i = 0; i < nextroot - 1; i++)
1952 if (brp->rb_num->rn_id[i] != bnum->rn_id[i])
1953 break;
1954 if (i == nextroot - 1)
1955 break;
1956 }
1957
1958 if (brp == NULL)
1959 fatal("expected branch not found on branch list");
1960
1961 if ((rdp = rcs_findrev(rfp, brp->rb_num)) == NULL)
1962 fatal("failed to get delta for target rev");
1963
1964 for (;;) {
1965 if (rdp->rd_next->rn_len != 0) {
1966 trdp = rcs_findrev(rfp, rdp->rd_next);
1967 if (trdp == NULL)
1968 fatal("failed to grab next revision");
1969 }
1970
1971 if (rdp->rd_tlen == 0) {
1972 if (rcsparse_deltatexts(rfp, rdp->rd_num))
1973 fatal("rcs_annotate_getlines: "
1974 "rcsparse_deltatexts");
1975 if (rdp->rd_tlen == 0) {
1976 if (!rcsnum_differ(rdp->rd_num, bnum))
1977 break;
1978 rdp = trdp;
1979 continue;
1980 }
1981 }
1982
1983 plen = rdp->rd_tlen;
1984 patch = rdp->rd_text;
1985 plines = cvs_splitlines(patch, plen);
1986 rcs_patch_lines(dlines, plines, NULL, rdp);
1987 cvs_freelines(plines);
1988
1989 if (!rcsnum_differ(rdp->rd_num, bnum))
1990 break;
1991
1992 rdp = trdp;
1993 }
1994 } while (rcsnum_differ(rdp->rd_num, frev));
1995
1996 if (bnum != frev)
1997 free(bnum);
1998
1999 /*
2000 * All lines have been parsed, now they must be copied over
2001 * into alines (array) again.
2002 */
2003 free(*alines);
2004
2005 i = 0;
2006 TAILQ_FOREACH(line, &(dlines->l_lines), l_list) {
2007 if (line->l_line != NULL)
2008 i++;
2009 }
2010 *alines = xcalloc(i + 1, sizeof(struct rcs_line *));
2011 (*alines)[i] = NULL;
2012
2013 i = 0;
2014 TAILQ_FOREACH(line, &(dlines->l_lines), l_list) {
2015 if (line->l_line != NULL)
2016 (*alines)[i++] = line;
2017 }
2018 }
2019
2020 /*
2021 * rcs_rev_getbuf()
2022 *
2023 * XXX: This is really really slow and should be avoided if at all possible!
2024 *
2025 * Get the entire contents of revision <rev> from the RCSFILE <rfp> and
2026 * return it as a BUF pointer.
2027 */
2028 BUF *
rcs_rev_getbuf(RCSFILE * rfp,RCSNUM * rev,int mode)2029 rcs_rev_getbuf(RCSFILE *rfp, RCSNUM *rev, int mode)
2030 {
2031 int expmode, expand;
2032 struct rcs_delta *rdp;
2033 struct rcs_lines *lines;
2034 struct rcs_line *lp, *nlp;
2035 BUF *bp;
2036
2037 rdp = NULL;
2038 expmode = RCS_KWEXP_NONE;
2039 expand = 0;
2040 lines = rcs_rev_getlines(rfp, rev, NULL);
2041 bp = buf_alloc(1024 * 16);
2042
2043 if (!(mode & RCS_KWEXP_NONE)) {
2044 expmode = rcs_kwexp_get(rfp);
2045
2046 if (!(expmode & RCS_KWEXP_NONE)) {
2047 if ((rdp = rcs_findrev(rfp, rev)) == NULL) {
2048 char version[RCSNUM_MAXSTR];
2049
2050 rcsnum_tostr(rev, version, sizeof(version));
2051 fatal("could not find desired version %s in %s",
2052 version, rfp->rf_path);
2053 }
2054
2055 expand = 1;
2056 }
2057 }
2058
2059 for (lp = TAILQ_FIRST(&lines->l_lines); lp != NULL;) {
2060 nlp = TAILQ_NEXT(lp, l_list);
2061
2062 if (lp->l_line == NULL) {
2063 lp = nlp;
2064 continue;
2065 }
2066
2067 if (expand)
2068 rcs_kwexp_line(rfp->rf_path, rdp, lines, lp, expmode);
2069
2070 do {
2071 buf_append(bp, lp->l_line, lp->l_len);
2072 } while ((lp = TAILQ_NEXT(lp, l_list)) != nlp);
2073 }
2074
2075 cvs_freelines(lines);
2076
2077 return (bp);
2078 }
2079
2080 /*
2081 * rcs_rev_write_fd()
2082 *
2083 * Write the entire contents of revision <frev> from the rcsfile <rfp> to
2084 * file descriptor <fd>.
2085 */
2086 void
rcs_rev_write_fd(RCSFILE * rfp,RCSNUM * rev,int _fd,int mode)2087 rcs_rev_write_fd(RCSFILE *rfp, RCSNUM *rev, int _fd, int mode)
2088 {
2089 int fd;
2090 FILE *fp;
2091 size_t ret;
2092 int expmode, expand;
2093 struct rcs_delta *rdp;
2094 struct rcs_lines *lines;
2095 struct rcs_line *lp, *nlp;
2096 extern int print_stdout;
2097
2098 rdp = NULL;
2099 expmode = RCS_KWEXP_NONE;
2100 expand = 0;
2101 lines = rcs_rev_getlines(rfp, rev, NULL);
2102
2103 if (!(mode & RCS_KWEXP_NONE)) {
2104 expmode = rcs_kwexp_get(rfp);
2105
2106 if (!(expmode & RCS_KWEXP_NONE)) {
2107 if ((rdp = rcs_findrev(rfp, rev)) == NULL)
2108 fatal("could not fetch revision");
2109 expand = 1;
2110 }
2111 }
2112
2113 fd = dup(_fd);
2114 if (fd == -1)
2115 fatal("rcs_rev_write_fd: dup: %s", strerror(errno));
2116
2117 if ((fp = fdopen(fd, "w")) == NULL)
2118 fatal("rcs_rev_write_fd: fdopen: %s", strerror(errno));
2119
2120 for (lp = TAILQ_FIRST(&lines->l_lines); lp != NULL;) {
2121 nlp = TAILQ_NEXT(lp, l_list);
2122
2123 if (lp->l_line == NULL) {
2124 lp = nlp;
2125 continue;
2126 }
2127
2128 if (expand)
2129 rcs_kwexp_line(rfp->rf_path, rdp, lines, lp, expmode);
2130
2131 do {
2132 /*
2133 * Solely for the checkout and update -p options.
2134 */
2135 if (cvs_server_active == 1 &&
2136 (cvs_cmdop == CVS_OP_CHECKOUT ||
2137 cvs_cmdop == CVS_OP_UPDATE) && print_stdout == 1) {
2138 ret = fwrite("M ", 1, 2, fp);
2139 if (ret != 2)
2140 fatal("rcs_rev_write_fd: %s",
2141 strerror(errno));
2142 }
2143
2144 ret = fwrite(lp->l_line, 1, lp->l_len, fp);
2145 if (ret != lp->l_len)
2146 fatal("rcs_rev_write_fd: %s", strerror(errno));
2147 } while ((lp = TAILQ_NEXT(lp, l_list)) != nlp);
2148 }
2149
2150 cvs_freelines(lines);
2151 (void)fclose(fp);
2152 }
2153
2154 /*
2155 * rcs_rev_write_stmp()
2156 *
2157 * Write the contents of the rev <rev> to a temporary file whose path is
2158 * specified using <template> (see mkstemp(3)). NB. This function will modify
2159 * <template>, as per mkstemp.
2160 */
2161 int
rcs_rev_write_stmp(RCSFILE * rfp,RCSNUM * rev,char * template,int mode)2162 rcs_rev_write_stmp(RCSFILE *rfp, RCSNUM *rev, char *template, int mode)
2163 {
2164 int fd;
2165
2166 if ((fd = mkstemp(template)) == -1)
2167 fatal("mkstemp: `%s': %s", template, strerror(errno));
2168
2169 worklist_add(template, &temp_files);
2170 rcs_rev_write_fd(rfp, rev, fd, mode);
2171
2172 if (lseek(fd, 0, SEEK_SET) == -1)
2173 fatal("rcs_rev_write_stmp: lseek: %s", strerror(errno));
2174
2175 return (fd);
2176 }
2177
2178 static void
rcs_kwexp_line(char * rcsfile,struct rcs_delta * rdp,struct rcs_lines * lines,struct rcs_line * line,int mode)2179 rcs_kwexp_line(char *rcsfile, struct rcs_delta *rdp, struct rcs_lines *lines,
2180 struct rcs_line *line, int mode)
2181 {
2182 BUF *tmpbuf;
2183 int kwtype;
2184 u_int j, found;
2185 const u_char *c, *start, *fin, *end;
2186 char *kwstr, *rcsfile_basename;
2187 char expbuf[256], buf[256], path[PATH_MAX];
2188 size_t clen, kwlen, len, tlen;
2189
2190 kwtype = 0;
2191 kwstr = NULL;
2192
2193 if (mode & RCS_KWEXP_OLD)
2194 return;
2195
2196 len = line->l_len;
2197 if (len == 0)
2198 return;
2199
2200 c = line->l_line;
2201 found = 0;
2202 /* Final character in buffer. */
2203 fin = c + len - 1;
2204
2205 if (strlcpy(path, rcsfile, sizeof(path)) >= sizeof(path))
2206 fatal("rcs_kwexp_line: truncation");
2207 rcsfile_basename = basename(path);
2208
2209 /*
2210 * Keyword formats:
2211 * $Keyword$
2212 * $Keyword: value$
2213 */
2214 for (; c < fin; c++) {
2215 if (*c != '$')
2216 continue;
2217
2218 /* remember start of this possible keyword */
2219 start = c;
2220
2221 /* first following character has to be alphanumeric */
2222 c++;
2223 if (!isalpha(*c)) {
2224 c = start;
2225 continue;
2226 }
2227
2228 /* Number of characters between c and fin, inclusive. */
2229 clen = fin - c + 1;
2230
2231 /* look for any matching keywords */
2232 found = 0;
2233 for (j = 0; j < RCS_NKWORDS; j++) {
2234 kwlen = strlen(rcs_expkw[j].kw_str);
2235 /*
2236 * kwlen must be less than clen since clen
2237 * includes either a terminating `$' or a `:'.
2238 */
2239 if (kwlen < clen &&
2240 memcmp(c, rcs_expkw[j].kw_str, kwlen) == 0 &&
2241 (c[kwlen] == '$' || c[kwlen] == ':')) {
2242 found = 1;
2243 kwstr = rcs_expkw[j].kw_str;
2244 kwtype = rcs_expkw[j].kw_type;
2245 c += kwlen;
2246 break;
2247 }
2248 }
2249
2250 if (found == 0 && cvs_tagname != NULL) {
2251 kwlen = strlen(cvs_tagname);
2252 if (kwlen < clen &&
2253 memcmp(c, cvs_tagname, kwlen) == 0 &&
2254 (c[kwlen] == '$' || c[kwlen] == ':')) {
2255 found = 1;
2256 kwstr = cvs_tagname;
2257 kwtype = RCS_KW_ID;
2258 c += kwlen;
2259 }
2260 }
2261
2262 /* unknown keyword, continue looking */
2263 if (found == 0) {
2264 c = start;
2265 continue;
2266 }
2267
2268 /*
2269 * if the next character was ':' we need to look for
2270 * an '$' before the end of the line to be sure it is
2271 * in fact a keyword.
2272 */
2273 if (*c == ':') {
2274 for (; c <= fin; ++c) {
2275 if (*c == '$' || *c == '\n')
2276 break;
2277 }
2278
2279 if (*c != '$') {
2280 c = start;
2281 continue;
2282 }
2283 }
2284 end = c + 1;
2285
2286 /* start constructing the expansion */
2287 expbuf[0] = '\0';
2288
2289 if (mode & RCS_KWEXP_NAME) {
2290 if (strlcat(expbuf, "$", sizeof(expbuf)) >=
2291 sizeof(expbuf) || strlcat(expbuf, kwstr,
2292 sizeof(expbuf)) >= sizeof(expbuf))
2293 fatal("rcs_kwexp_line: truncated");
2294 if ((mode & RCS_KWEXP_VAL) &&
2295 strlcat(expbuf, ": ", sizeof(expbuf)) >=
2296 sizeof(expbuf))
2297 fatal("rcs_kwexp_line: truncated");
2298 }
2299
2300 /*
2301 * order matters because of RCS_KW_ID and
2302 * RCS_KW_HEADER here
2303 */
2304 if (mode & RCS_KWEXP_VAL) {
2305 if (kwtype & RCS_KW_RCSFILE) {
2306 if (!(kwtype & RCS_KW_FULLPATH))
2307 (void)strlcat(expbuf, rcsfile_basename,
2308 sizeof(expbuf));
2309 else
2310 (void)strlcat(expbuf, rcsfile,
2311 sizeof(expbuf));
2312 if (strlcat(expbuf, " ", sizeof(expbuf)) >=
2313 sizeof(expbuf))
2314 fatal("rcs_kwexp_line: truncated");
2315 }
2316
2317 if (kwtype & RCS_KW_REVISION) {
2318 rcsnum_tostr(rdp->rd_num, buf, sizeof(buf));
2319 if (strlcat(buf, " ", sizeof(buf)) >=
2320 sizeof(buf) || strlcat(expbuf, buf,
2321 sizeof(expbuf)) >= sizeof(buf))
2322 fatal("rcs_kwexp_line: truncated");
2323 }
2324
2325 if (kwtype & RCS_KW_DATE) {
2326 if (strftime(buf, sizeof(buf),
2327 "%Y/%m/%d %H:%M:%S ",
2328 &rdp->rd_date) == 0)
2329 fatal("rcs_kwexp_line: strftime "
2330 "failure");
2331 if (strlcat(expbuf, buf, sizeof(expbuf)) >=
2332 sizeof(expbuf))
2333 fatal("rcs_kwexp_line: string "
2334 "truncated");
2335 }
2336
2337 if (kwtype & RCS_KW_MDOCDATE) {
2338 /*
2339 * Do not prepend ' ' for a single
2340 * digit, %e would do so and there is
2341 * no better format for strftime().
2342 */
2343 if (strftime(buf, sizeof(buf),
2344 (rdp->rd_date.tm_mday < 10) ?
2345 "%B%e %Y " : "%B %e %Y ",
2346 &rdp->rd_date) == 0)
2347 fatal("rcs_kwexp_line: strftime "
2348 "failure");
2349 if (strlcat(expbuf, buf, sizeof(expbuf)) >=
2350 sizeof(expbuf))
2351 fatal("rcs_kwexp_line: string "
2352 "truncated");
2353 }
2354
2355 if (kwtype & RCS_KW_AUTHOR) {
2356 if (strlcat(expbuf, rdp->rd_author,
2357 sizeof(expbuf)) >= sizeof(expbuf) ||
2358 strlcat(expbuf, " ", sizeof(expbuf)) >=
2359 sizeof(expbuf))
2360 fatal("rcs_kwexp_line: string "
2361 "truncated");
2362 }
2363
2364 if (kwtype & RCS_KW_STATE) {
2365 if (strlcat(expbuf, rdp->rd_state,
2366 sizeof(expbuf)) >= sizeof(expbuf) ||
2367 strlcat(expbuf, " ", sizeof(expbuf)) >=
2368 sizeof(expbuf))
2369 fatal("rcs_kwexp_line: string "
2370 "truncated");
2371 }
2372
2373 /* order does not matter anymore below */
2374 if (kwtype & RCS_KW_LOG) {
2375 char linebuf[256];
2376 struct rcs_line *cur, *lp;
2377 char *logp, *l_line, *prefix, *q, *sprefix;
2378 size_t i;
2379
2380 /* Log line */
2381 if (!(kwtype & RCS_KW_FULLPATH))
2382 (void)strlcat(expbuf,
2383 rcsfile_basename, sizeof(expbuf));
2384 else
2385 (void)strlcat(expbuf, rcsfile,
2386 sizeof(expbuf));
2387
2388 if (strlcat(expbuf, " ", sizeof(expbuf)) >=
2389 sizeof(expbuf))
2390 fatal("rcs_kwexp_line: string "
2391 "truncated");
2392
2393 cur = line;
2394
2395 /* copy rdp->rd_log for strsep */
2396 logp = xstrdup(rdp->rd_log);
2397
2398 /* copy our prefix for later processing */
2399 prefix = xmalloc(start - line->l_line + 1);
2400 memcpy(prefix, line->l_line,
2401 start - line->l_line);
2402 prefix[start - line->l_line] = '\0';
2403
2404 /* copy also prefix without trailing blanks. */
2405 sprefix = xstrdup(prefix);
2406 for (i = strlen(sprefix); i > 0 &&
2407 sprefix[i - 1] == ' '; i--)
2408 sprefix[i - 1] = '\0';
2409
2410 /* new line: revision + date + author */
2411 linebuf[0] = '\0';
2412 if (strlcat(linebuf, "Revision ",
2413 sizeof(linebuf)) >= sizeof(linebuf))
2414 fatal("rcs_kwexp_line: truncated");
2415 rcsnum_tostr(rdp->rd_num, buf, sizeof(buf));
2416 if (strlcat(linebuf, buf, sizeof(linebuf))
2417 >= sizeof(buf))
2418 fatal("rcs_kwexp_line: truncated");
2419 if (strftime(buf, sizeof(buf),
2420 " %Y/%m/%d %H:%M:%S ",
2421 &rdp->rd_date) == 0)
2422 fatal("rcs_kwexp_line: strftime "
2423 "failure");
2424 if (strlcat(linebuf, buf, sizeof(linebuf))
2425 >= sizeof(linebuf))
2426 fatal("rcs_kwexp_line: string "
2427 "truncated");
2428 if (strlcat(linebuf, rdp->rd_author,
2429 sizeof(linebuf)) >= sizeof(linebuf))
2430 fatal("rcs_kwexp_line: string "
2431 "truncated");
2432
2433 lp = xcalloc(1, sizeof(*lp));
2434 xasprintf((char **)&(lp->l_line), "%s%s\n",
2435 prefix, linebuf);
2436 lp->l_len = strlen(lp->l_line);
2437 TAILQ_INSERT_AFTER(&(lines->l_lines), cur, lp,
2438 l_list);
2439 cur = lp;
2440
2441 /* Log message */
2442 q = logp;
2443 while ((l_line = strsep(&q, "\n")) != NULL &&
2444 q != NULL) {
2445 lp = xcalloc(1, sizeof(*lp));
2446
2447 if (l_line[0] == '\0') {
2448 xasprintf((char **)&(lp->l_line),
2449 "%s\n", sprefix);
2450 } else {
2451 xasprintf((char **)&(lp->l_line),
2452 "%s%s\n", prefix, l_line);
2453 }
2454
2455 lp->l_len = strlen(lp->l_line);
2456 TAILQ_INSERT_AFTER(&(lines->l_lines),
2457 cur, lp, l_list);
2458 cur = lp;
2459 }
2460 free(logp);
2461
2462 /*
2463 * This is just another hairy mess, but it must
2464 * be done: All characters behind Log will be
2465 * written in a new line next to log messages.
2466 * But that's not enough, we have to strip all
2467 * trailing whitespaces of our prefix.
2468 */
2469 lp = xcalloc(1, sizeof(*lp));
2470 xasprintf((char **)&lp->l_line, "%s%s",
2471 sprefix, end);
2472 lp->l_len = strlen(lp->l_line);
2473 TAILQ_INSERT_AFTER(&(lines->l_lines), cur, lp,
2474 l_list);
2475 cur = lp;
2476
2477 end = line->l_line + line->l_len - 1;
2478
2479 free(prefix);
2480 free(sprefix);
2481
2482 }
2483
2484 if (kwtype & RCS_KW_SOURCE) {
2485 if (strlcat(expbuf, rcsfile, sizeof(expbuf)) >=
2486 sizeof(expbuf) || strlcat(expbuf, " ",
2487 sizeof(expbuf)) >= sizeof(expbuf))
2488 fatal("rcs_kwexp_line: string "
2489 "truncated");
2490 }
2491
2492 if (kwtype & RCS_KW_NAME)
2493 if (strlcat(expbuf, " ", sizeof(expbuf)) >=
2494 sizeof(expbuf))
2495 fatal("rcs_kwexp_line: string "
2496 "truncated");
2497
2498 if (kwtype & RCS_KW_LOCKER)
2499 if (strlcat(expbuf, " ", sizeof(expbuf)) >=
2500 sizeof(expbuf))
2501 fatal("rcs_kwexp_line: string "
2502 "truncated");
2503 }
2504
2505 /* end the expansion */
2506 if (mode & RCS_KWEXP_NAME)
2507 if (strlcat(expbuf, "$",
2508 sizeof(expbuf)) >= sizeof(expbuf))
2509 fatal("rcs_kwexp_line: truncated");
2510
2511 /* Concatenate everything together. */
2512 tmpbuf = buf_alloc(len + strlen(expbuf));
2513 /* Append everything before keyword. */
2514 buf_append(tmpbuf, line->l_line,
2515 start - line->l_line);
2516 /* Append keyword. */
2517 buf_puts(tmpbuf, expbuf);
2518 /* Point c to end of keyword. */
2519 tlen = buf_len(tmpbuf) - 1;
2520 /* Append everything after keyword. */
2521 buf_append(tmpbuf, end,
2522 line->l_line + line->l_len - end);
2523 c = buf_get(tmpbuf) + tlen;
2524 /* Point fin to end of data. */
2525 fin = buf_get(tmpbuf) + buf_len(tmpbuf) - 1;
2526 /* Recalculate new length. */
2527 len = buf_len(tmpbuf);
2528
2529 /* tmpbuf is now ready, convert to string */
2530 if (line->l_needsfree)
2531 free(line->l_line);
2532 line->l_len = len;
2533 line->l_line = buf_release(tmpbuf);
2534 line->l_needsfree = 1;
2535 }
2536 }
2537
2538 /* rcs_translate_tag() */
2539 RCSNUM *
rcs_translate_tag(const char * revstr,RCSFILE * rfp)2540 rcs_translate_tag(const char *revstr, RCSFILE *rfp)
2541 {
2542 int follow;
2543 time_t deltatime;
2544 char branch[CVS_REV_BUFSZ];
2545 RCSNUM *brev, *frev, *rev;
2546 struct rcs_delta *rdp, *trdp;
2547 time_t cdate;
2548
2549 brev = frev = NULL;
2550
2551 if (revstr == NULL) {
2552 if (rfp->rf_branch != NULL) {
2553 rcsnum_tostr(rfp->rf_branch, branch, sizeof(branch));
2554 revstr = branch;
2555 } else {
2556 revstr = RCS_HEAD_BRANCH;
2557 }
2558 }
2559
2560 if ((rev = rcs_get_revision(revstr, rfp)) == NULL)
2561 return (NULL);
2562
2563 if ((rdp = rcs_findrev(rfp, rev)) == NULL)
2564 return (NULL);
2565
2566 /* let's see if we must follow a branch */
2567 if (!strcmp(revstr, RCS_HEAD_BRANCH))
2568 follow = 1;
2569 else {
2570 frev = rcs_sym_getrev(rfp, revstr);
2571 if (frev == NULL)
2572 frev = rcsnum_parse(revstr);
2573
2574 brev = rcsnum_alloc();
2575 rcsnum_cpy(rev, brev, rev->rn_len - 1);
2576
2577 if (frev != NULL && RCSNUM_ISBRANCH(frev) &&
2578 !rcsnum_cmp(frev, brev, 0)) {
2579 follow = 1;
2580 } else
2581 follow = 0;
2582
2583 free(brev);
2584 brev = NULL;
2585 }
2586
2587 if (cvs_specified_date != -1)
2588 cdate = cvs_specified_date;
2589 else
2590 cdate = cvs_directory_date;
2591
2592 if (cdate == -1) {
2593 free(frev);
2594
2595 /* XXX */
2596 if (rev->rn_len < 4 || !follow) {
2597 return (rev);
2598 }
2599
2600 /* Find the latest delta on that branch */
2601 free(rev);
2602 for (;;) {
2603 if (rdp->rd_next->rn_len == 0)
2604 break;
2605 if ((rdp = rcs_findrev(rfp, rdp->rd_next)) == NULL)
2606 fatal("rcs_translate_tag: could not fetch "
2607 "branch delta");
2608 }
2609
2610 rev = rcsnum_alloc();
2611 rcsnum_cpy(rdp->rd_num, rev, 0);
2612 return (rev);
2613 }
2614
2615 if (frev != NULL) {
2616 brev = rcsnum_revtobr(frev);
2617 brev->rn_len = rev->rn_len - 1;
2618 free(frev);
2619 }
2620
2621 free(rev);
2622
2623 do {
2624 deltatime = timegm(&(rdp->rd_date));
2625
2626 if (RCSNUM_ISBRANCHREV(rdp->rd_num)) {
2627 if (deltatime > cdate) {
2628 trdp = TAILQ_PREV(rdp, rcs_dlist, rd_list);
2629 if (trdp == NULL)
2630 trdp = rdp;
2631
2632 if (trdp->rd_num->rn_len != rdp->rd_num->rn_len)
2633 return (NULL);
2634
2635 rev = rcsnum_alloc();
2636 rcsnum_cpy(trdp->rd_num, rev, 0);
2637 return (rev);
2638 }
2639
2640 if (rdp->rd_next->rn_len == 0) {
2641 rev = rcsnum_alloc();
2642 rcsnum_cpy(rdp->rd_num, rev, 0);
2643 return (rev);
2644 }
2645 } else {
2646 if (deltatime < cdate) {
2647 rev = rcsnum_alloc();
2648 rcsnum_cpy(rdp->rd_num, rev, 0);
2649 return (rev);
2650 }
2651 }
2652
2653 if (follow && rdp->rd_next->rn_len != 0) {
2654 if (brev != NULL && !rcsnum_cmp(brev, rdp->rd_num, 0))
2655 break;
2656
2657 trdp = rcs_findrev(rfp, rdp->rd_next);
2658 if (trdp == NULL)
2659 fatal("failed to grab next revision");
2660 rdp = trdp;
2661 } else
2662 follow = 0;
2663 } while (follow);
2664
2665 return (NULL);
2666 }
2667