1156230Smux/*-
2156230Smux * Copyright (c) 2003-2006, Maxime Henrion <mux@FreeBSD.org>
3156230Smux * All rights reserved.
4156230Smux *
5156230Smux * Redistribution and use in source and binary forms, with or without
6156230Smux * modification, are permitted provided that the following conditions
7156230Smux * are met:
8156230Smux * 1. Redistributions of source code must retain the above copyright
9156230Smux *    notice, this list of conditions and the following disclaimer.
10156230Smux * 2. Redistributions in binary form must reproduce the above copyright
11156230Smux *    notice, this list of conditions and the following disclaimer in the
12156230Smux *    documentation and/or other materials provided with the distribution.
13156230Smux *
14156230Smux * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15156230Smux * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16156230Smux * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17156230Smux * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18156230Smux * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19156230Smux * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20156230Smux * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21156230Smux * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22156230Smux * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23156230Smux * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24156230Smux * SUCH DAMAGE.
25156230Smux *
26156230Smux * $FreeBSD$
27156230Smux */
28156230Smux
29156230Smux#include <sys/types.h>
30156230Smux#include <sys/stat.h>
31156230Smux
32156230Smux#include <assert.h>
33156230Smux#include <err.h>
34156230Smux#include <errno.h>
35156230Smux#include <fcntl.h>
36156701Smux#include <limits.h>
37156230Smux#include <pthread.h>
38156230Smux#include <stdarg.h>
39156230Smux#include <stdio.h>
40156230Smux#include <stdlib.h>
41156230Smux#include <string.h>
42156230Smux#include <time.h>
43156230Smux#include <unistd.h>
44156230Smux
45156230Smux#include "fattr.h"
46156230Smux#include "main.h"
47156230Smux#include "misc.h"
48156230Smux
49156230Smuxstruct pattlist {
50156230Smux	char **patterns;
51156230Smux	size_t size;
52156230Smux	size_t in;
53156230Smux};
54156230Smux
55156230Smuxstruct backoff_timer {
56156230Smux	time_t min;
57156230Smux	time_t max;
58156230Smux	time_t interval;
59156230Smux	float backoff;
60156230Smux	float jitter;
61156230Smux};
62156230Smux
63156230Smuxstatic void	bt_update(struct backoff_timer *);
64156230Smuxstatic void	bt_addjitter(struct backoff_timer *);
65156230Smux
66156230Smuxint
67156701Smuxasciitoint(const char *s, int *val, int base)
68156701Smux{
69156701Smux	char *end;
70156701Smux	long longval;
71156701Smux
72156701Smux	errno = 0;
73156701Smux	longval = strtol(s, &end, base);
74156701Smux	if (errno || *end != '\0')
75156701Smux		return (-1);
76156701Smux	if (longval > INT_MAX || longval < INT_MIN) {
77156701Smux		errno = ERANGE;
78156701Smux		return (-1);
79156701Smux	}
80156701Smux	*val = longval;
81156701Smux	return (0);
82156701Smux}
83156701Smux
84156701Smuxint
85156230Smuxlprintf(int level, const char *fmt, ...)
86156230Smux{
87156230Smux	FILE *to;
88156230Smux	va_list ap;
89156230Smux	int ret;
90156230Smux
91156230Smux	if (level > verbose)
92156230Smux		return (0);
93156230Smux	if (level == -1)
94156230Smux		to = stderr;
95156230Smux	else
96156230Smux		to = stdout;
97156230Smux	va_start(ap, fmt);
98156230Smux	ret = vfprintf(to, fmt, ap);
99156230Smux	va_end(ap);
100156230Smux	fflush(to);
101156230Smux	return (ret);
102156230Smux}
103156230Smux
104156230Smux/*
105156230Smux * Compute the MD5 checksum of a file.  The md parameter must
106156230Smux * point to a buffer containing at least MD5_DIGEST_SIZE bytes.
107156230Smux *
108156230Smux * Do not confuse OpenSSL's MD5_DIGEST_LENGTH with our own
109156230Smux * MD5_DIGEST_SIZE macro.
110156230Smux */
111156230Smuxint
112156230SmuxMD5_File(char *path, char *md)
113156230Smux{
114156230Smux	char buf[1024];
115156230Smux	MD5_CTX ctx;
116156230Smux	ssize_t n;
117156230Smux	int fd;
118156230Smux
119156230Smux	fd = open(path, O_RDONLY);
120156230Smux	if (fd == -1)
121156230Smux		return (-1);
122156230Smux	MD5_Init(&ctx);
123156230Smux	while ((n = read(fd, buf, sizeof(buf))) > 0)
124156230Smux		MD5_Update(&ctx, buf, n);
125156230Smux	close(fd);
126156230Smux	if (n == -1)
127156230Smux		return (-1);
128156230Smux	MD5_End(md, &ctx);
129156230Smux	return (0);
130156230Smux}
131156230Smux
132156230Smux/*
133156230Smux * Wrapper around MD5_Final() that converts the 128 bits MD5 hash
134156230Smux * to an ASCII string representing this value in hexadecimal.
135156230Smux */
136156230Smuxvoid
137156230SmuxMD5_End(char *md, MD5_CTX *c)
138156230Smux{
139156230Smux	unsigned char md5[MD5_DIGEST_LENGTH];
140156230Smux	const char hex[] = "0123456789abcdef";
141156230Smux	int i, j;
142156230Smux
143156230Smux	MD5_Final(md5, c);
144156230Smux	j = 0;
145156230Smux	for (i = 0; i < MD5_DIGEST_LENGTH; i++) {
146156230Smux		md[j++] = hex[md5[i] >> 4];
147156230Smux		md[j++] = hex[md5[i] & 0xf];
148156230Smux	}
149156230Smux	md[j] = '\0';
150156230Smux}
151156230Smux
152156230Smuxint
153156230Smuxpathcmp(const char *s1, const char *s2)
154156230Smux{
155156230Smux	char c1, c2;
156156230Smux
157156230Smux	do {
158156230Smux		c1 = *s1++;
159156230Smux		if (c1 == '/')
160156230Smux			c1 = 1;
161156230Smux		c2 = *s2++;
162156230Smux		if (c2 == '/')
163156230Smux			c2 = 1;
164156230Smux	} while (c1 == c2 && c1 != '\0');
165156230Smux
166156230Smux	return (c1 - c2);
167156230Smux}
168156230Smux
169156230Smuxsize_t
170156230Smuxcommonpathlength(const char *a, size_t alen, const char *b, size_t blen)
171156230Smux{
172156230Smux	size_t i, minlen, lastslash;
173156230Smux
174156230Smux	minlen = min(alen, blen);
175156230Smux	lastslash = 0;
176156230Smux	for (i = 0; i < minlen; i++) {
177156230Smux		if (a[i] != b[i])
178156230Smux			return (lastslash);
179156230Smux		if (a[i] == '/') {
180156230Smux			if (i == 0)	/* Include the leading slash. */
181156230Smux				lastslash = 1;
182156230Smux			else
183156230Smux				lastslash = i;
184156230Smux		}
185156230Smux	}
186156230Smux
187156230Smux	/* One path is a prefix of the other/ */
188156230Smux	if (alen > minlen) {		/* Path "b" is a prefix of "a". */
189156230Smux		if (a[minlen] == '/')
190156230Smux			return (minlen);
191156230Smux		else
192156230Smux			return (lastslash);
193156230Smux	} else if (blen > minlen) {	/* Path "a" is a prefix of "b". */
194156230Smux		if (b[minlen] == '/')
195156230Smux			return (minlen);
196156230Smux		else
197156230Smux			return (lastslash);
198156230Smux	}
199156230Smux
200156230Smux	/* The paths are identical. */
201156230Smux	return (minlen);
202156230Smux}
203156230Smux
204186781Slulfconst char *
205186781Slulfpathlast(const char *path)
206156230Smux{
207186781Slulf	const char *s;
208156230Smux
209156230Smux	s = strrchr(path, '/');
210156230Smux	if (s == NULL)
211156230Smux		return (path);
212156230Smux	return (++s);
213156230Smux}
214156230Smux
215156230Smuxint
216156230Smuxrcsdatetotm(const char *revdate, struct tm *tm)
217156230Smux{
218156230Smux	char *cp;
219156230Smux	size_t len;
220156230Smux
221156230Smux	cp = strchr(revdate, '.');
222156230Smux	if (cp == NULL)
223156230Smux		return (-1);
224156230Smux	len = cp - revdate;
225156251Smux	if (len >= 4)
226156230Smux		cp = strptime(revdate, "%Y.%m.%d.%H.%M.%S", tm);
227156230Smux	else if (len == 2)
228156230Smux		cp = strptime(revdate, "%y.%m.%d.%H.%M.%S", tm);
229156230Smux	else
230156230Smux		return (-1);
231156230Smux	if (cp == NULL || *cp != '\0')
232156230Smux		return (-1);
233156230Smux	return (0);
234156230Smux}
235156230Smux
236156230Smuxtime_t
237156230Smuxrcsdatetotime(const char *revdate)
238156230Smux{
239156230Smux	struct tm tm;
240156230Smux	time_t t;
241156230Smux	int error;
242156230Smux
243156230Smux	error = rcsdatetotm(revdate, &tm);
244156230Smux	if (error)
245156230Smux		return (error);
246156230Smux	t = timegm(&tm);
247156230Smux	return (t);
248156230Smux}
249156230Smux
250156230Smux/*
251186781Slulf * Checks if a file is an RCS file.
252186781Slulf */
253186781Slulfint
254186781Slulfisrcs(const char *file, size_t *len)
255186781Slulf{
256186781Slulf	const char *cp;
257186781Slulf
258186781Slulf	if (file[0] == '/')
259186781Slulf		return (0);
260186781Slulf	cp = file;
261186781Slulf	while ((cp = strstr(cp, "..")) != NULL) {
262186781Slulf		if (cp == file || cp[2] == '\0' ||
263186781Slulf		    (cp[-1] == '/' && cp[2] == '/'))
264186781Slulf			return (0);
265186781Slulf		cp += 2;
266186781Slulf	}
267186781Slulf	*len = strlen(file);
268186781Slulf	if (*len < 2 || file[*len - 1] != 'v' || file[*len - 2] != ',') {
269186781Slulf		return (0);
270186781Slulf	}
271186781Slulf
272186781Slulf	return (1);
273186781Slulf}
274186781Slulf
275186781Slulf/*
276156230Smux * Returns a buffer allocated with malloc() containing the absolute
277156230Smux * pathname to the checkout file made from the prefix and the path
278156230Smux * of the corresponding RCS file relatively to the prefix.  If the
279156230Smux * filename is not an RCS filename, NULL will be returned.
280156230Smux */
281156230Smuxchar *
282156230Smuxcheckoutpath(const char *prefix, const char *file)
283156230Smux{
284156230Smux	char *path;
285156230Smux	size_t len;
286156230Smux
287186781Slulf	if (!isrcs(file, &len))
288156230Smux		return (NULL);
289156230Smux	xasprintf(&path, "%s/%.*s", prefix, (int)len - 2, file);
290156230Smux	return (path);
291156230Smux}
292156230Smux
293186781Slulf/*
294186781Slulf * Returns a cvs path allocated with malloc() containing absolute pathname to a
295186781Slulf * file in cvs mode which can reside in the attic. XXX: filename has really no
296186781Slulf * restrictions.
297186781Slulf */
298186781Slulfchar *
299186781Slulfcvspath(const char *prefix, const char *file, int attic)
300186781Slulf{
301186781Slulf	const char *last;
302186781Slulf	char *path;
303186781Slulf
304186781Slulf	last = pathlast(file);
305186781Slulf	if (attic)
306186781Slulf		xasprintf(&path, "%s/%.*sAttic/%s", prefix, (int)(last - file),
307186781Slulf		    file, last);
308186781Slulf	else
309186781Slulf		xasprintf(&path, "%s/%s", prefix, file);
310186781Slulf
311186781Slulf	return (path);
312186781Slulf}
313186781Slulf
314186781Slulf/*
315186781Slulf * Regular or attic path if regular fails.
316186781Slulf * XXX: This should perhaps also check if the Attic file exists too, and return
317186781Slulf * NULL if not.
318186781Slulf */
319186781Slulfchar *
320186781Slulfatticpath(const char *prefix, const char *file)
321186781Slulf{
322186781Slulf	char *path;
323186781Slulf
324186781Slulf	path = cvspath(prefix, file, 0);
325186781Slulf	if (access(path, F_OK) != 0) {
326186781Slulf		free(path);
327186781Slulf		path = cvspath(prefix, file, 1);
328186781Slulf	}
329186781Slulf	return (path);
330186781Slulf}
331186781Slulf
332156230Smuxint
333156230Smuxmkdirhier(char *path, mode_t mask)
334156230Smux{
335156230Smux	struct fattr *fa;
336156230Smux	size_t i, last, len;
337156230Smux	int error, finish, rv;
338156230Smux
339156230Smux	finish = 0;
340156230Smux	last = 0;
341156230Smux	len = strlen(path);
342156230Smux	for (i = len - 1; i > 0; i--) {
343156230Smux		if (path[i] == '/') {
344156230Smux			path[i] = '\0';
345156230Smux			if (access(path, F_OK) == 0) {
346156230Smux				path[i] = '/';
347156230Smux				break;
348156230Smux			}
349156230Smux			if (errno != ENOENT) {
350156230Smux				path[i] = '/';
351156230Smux				if (last == 0)
352156230Smux					return (-1);
353156230Smux				finish = 1;
354156230Smux				break;
355156230Smux			}
356156230Smux			last = i;
357156230Smux		}
358156230Smux	}
359156230Smux	if (last == 0)
360156230Smux		return (0);
361156230Smux
362156230Smux	i = strlen(path);
363156230Smux	fa = fattr_new(FT_DIRECTORY, -1);
364156230Smux	fattr_mergedefault(fa);
365156230Smux	fattr_umask(fa, mask);
366156230Smux	while (i < len) {
367156230Smux		if (!finish) {
368156230Smux			rv = 0;
369156230Smux			error = fattr_makenode(fa, path);
370156230Smux			if (!error)
371156230Smux				rv = fattr_install(fa, path, NULL);
372156230Smux			if (error || rv == -1)
373156230Smux				finish = 1;
374156230Smux		}
375156230Smux		path[i] = '/';
376156230Smux		i += strlen(path + i);
377156230Smux        }
378156230Smux	assert(i == len);
379156230Smux	if (finish)
380156230Smux		return (-1);
381156230Smux        return (0);
382156230Smux}
383156230Smux
384156230Smux/*
385156230Smux * Compute temporary pathnames.
386156230Smux * This can look a bit like overkill but we mimic CVSup's behaviour.
387156230Smux */
388156230Smux#define	TEMPNAME_PREFIX		"#cvs.csup"
389156230Smux
390156230Smuxstatic pthread_mutex_t tempname_mtx = PTHREAD_MUTEX_INITIALIZER;
391156230Smuxstatic pid_t tempname_pid = -1;
392156230Smuxstatic int tempname_count;
393156230Smux
394156230Smuxchar *
395156230Smuxtempname(const char *path)
396156230Smux{
397156230Smux	char *cp, *temp;
398156230Smux	int count, error;
399156230Smux
400156230Smux	error = pthread_mutex_lock(&tempname_mtx);
401156230Smux	assert(!error);
402156230Smux	if (tempname_pid == -1) {
403156230Smux		tempname_pid = getpid();
404156230Smux		tempname_count = 0;
405156230Smux	}
406156230Smux	count = tempname_count++;
407156230Smux	error = pthread_mutex_unlock(&tempname_mtx);
408156230Smux	assert(!error);
409156230Smux	cp = strrchr(path, '/');
410156230Smux	if (cp == NULL)
411156230Smux		xasprintf(&temp, "%s-%ld.%d", TEMPNAME_PREFIX,
412156230Smux		    (long)tempname_pid, count);
413156230Smux	else
414156230Smux		xasprintf(&temp, "%.*s%s-%ld.%d", (int)(cp - path + 1), path,
415156230Smux		    TEMPNAME_PREFIX, (long)tempname_pid, count);
416156230Smux	return (temp);
417156230Smux}
418156230Smux
419156230Smuxvoid *
420156230Smuxxmalloc(size_t size)
421156230Smux{
422156230Smux	void *buf;
423156230Smux
424156230Smux	buf = malloc(size);
425156230Smux	if (buf == NULL)
426156230Smux		err(1, "malloc");
427156230Smux	return (buf);
428156230Smux}
429156230Smux
430156230Smuxvoid *
431156230Smuxxrealloc(void *buf, size_t size)
432156230Smux{
433156230Smux
434156230Smux	buf = realloc(buf, size);
435156230Smux	if (buf == NULL)
436156230Smux		err(1, "realloc");
437156230Smux	return (buf);
438156230Smux}
439156230Smux
440156230Smuxchar *
441156230Smuxxstrdup(const char *str)
442156230Smux{
443156230Smux	char *buf;
444156230Smux
445156230Smux	buf = strdup(str);
446156230Smux	if (buf == NULL)
447156230Smux		err(1, "strdup");
448156230Smux	return (buf);
449156230Smux}
450156230Smux
451156230Smuxint
452156230Smuxxasprintf(char **ret, const char *format, ...)
453156230Smux{
454156230Smux	va_list ap;
455156230Smux	int rv;
456156230Smux
457156230Smux	va_start(ap, format);
458156230Smux	rv = vasprintf(ret, format, ap);
459156230Smux	va_end(ap);
460156230Smux	if (*ret == NULL)
461156230Smux		err(1, "asprintf");
462156230Smux	return (rv);
463156230Smux}
464156230Smux
465156230Smuxstruct pattlist *
466156230Smuxpattlist_new(void)
467156230Smux{
468156230Smux	struct pattlist *p;
469156230Smux
470156230Smux	p = xmalloc(sizeof(struct pattlist));
471156230Smux	p->size = 4;		/* Initial size. */
472156230Smux	p->patterns = xmalloc(p->size * sizeof(char *));
473156230Smux	p->in = 0;
474156230Smux	return (p);
475156230Smux}
476156230Smux
477156230Smuxvoid
478156230Smuxpattlist_add(struct pattlist *p, const char *pattern)
479156230Smux{
480156230Smux
481156230Smux	if (p->in == p->size) {
482156230Smux		p->size *= 2;
483156230Smux		p->patterns = xrealloc(p->patterns, p->size * sizeof(char *));
484156230Smux	}
485156230Smux	assert(p->in < p->size);
486156230Smux	p->patterns[p->in++] = xstrdup(pattern);
487156230Smux}
488156230Smux
489156230Smuxchar *
490156230Smuxpattlist_get(struct pattlist *p, size_t i)
491156230Smux{
492156230Smux
493156230Smux	assert(i < p->in);
494156230Smux	return (p->patterns[i]);
495156230Smux}
496156230Smux
497156230Smuxsize_t
498156230Smuxpattlist_size(struct pattlist *p)
499156230Smux{
500156230Smux
501156230Smux	return (p->in);
502156230Smux}
503156230Smux
504156230Smuxvoid
505156230Smuxpattlist_free(struct pattlist *p)
506156230Smux{
507156230Smux	size_t i;
508156230Smux
509156230Smux	for (i = 0; i < p->in; i++)
510156230Smux		free(p->patterns[i]);
511156230Smux	free(p->patterns);
512156230Smux	free(p);
513156230Smux}
514156230Smux
515156230Smux/* Creates a backoff timer. */
516156230Smuxstruct backoff_timer *
517156230Smuxbt_new(time_t min, time_t max, float backoff, float jitter)
518156230Smux{
519156230Smux	struct backoff_timer *bt;
520156230Smux
521156230Smux	bt = xmalloc(sizeof(struct backoff_timer));
522156230Smux	bt->min = min;
523156230Smux	bt->max = max;
524156230Smux	bt->backoff = backoff;
525156230Smux	bt->jitter = jitter;
526156230Smux	bt->interval = min;
527156230Smux	bt_addjitter(bt);
528156230Smux	srandom(time(0));
529156230Smux	return (bt);
530156230Smux}
531156230Smux
532156230Smux/* Updates the backoff timer. */
533156230Smuxstatic void
534156230Smuxbt_update(struct backoff_timer *bt)
535156230Smux{
536156230Smux
537156230Smux	bt->interval = (time_t)min(bt->interval * bt->backoff, bt->max);
538156230Smux	bt_addjitter(bt);
539156230Smux}
540156230Smux
541156230Smux/* Adds some jitter. */
542156230Smuxstatic void
543156230Smuxbt_addjitter(struct backoff_timer *bt)
544156230Smux{
545156230Smux	long mag;
546156230Smux
547156230Smux	mag = (long)(bt->jitter * bt->interval);
548156230Smux	/* We want a random number between -mag and mag. */
549156230Smux	bt->interval += (time_t)(random() % (2 * mag) - mag);
550156230Smux}
551156230Smux
552156230Smux/* Returns the current timer value. */
553156230Smuxtime_t
554156230Smuxbt_get(struct backoff_timer *bt)
555156230Smux{
556156230Smux
557156230Smux	return (bt->interval);
558156230Smux}
559156230Smux
560156230Smux/* Times out for bt->interval seconds. */
561156230Smuxvoid
562156230Smuxbt_pause(struct backoff_timer *bt)
563156230Smux{
564156230Smux
565156230Smux	sleep(bt->interval);
566156230Smux	bt_update(bt);
567156230Smux}
568156230Smux
569156230Smuxvoid
570156230Smuxbt_free(struct backoff_timer *bt)
571156230Smux{
572156230Smux
573156230Smux	free(bt);
574156230Smux}
575186781Slulf
576186781Slulf/* Compare two revisions. */
577186781Slulfint
578186781Slulfrcsnum_cmp(char *revision1, char *revision2)
579186781Slulf{
580186781Slulf        char *ptr1, *ptr2, *dot1, *dot2;
581186781Slulf	int num1len, num2len, ret;
582186781Slulf
583186781Slulf        ptr1 = revision1;
584186781Slulf        ptr2 = revision2;
585186781Slulf        while (*ptr1 != '\0' && *ptr2 != '\0') {
586186781Slulf                dot1 = strchr(ptr1, '.');
587186781Slulf                dot2 = strchr(ptr2, '.');
588186781Slulf                if (dot1 == NULL)
589186781Slulf                        dot1 = strchr(ptr1, '\0');
590186781Slulf                if (dot2 == NULL)
591186781Slulf                        dot2 = strchr(ptr2, '\0');
592186781Slulf
593186781Slulf		num1len = dot1 - ptr1;
594186781Slulf		num2len = dot2 - ptr2;
595186781Slulf                /* Check the distance between each, showing how many digits */
596186781Slulf                if (num1len > num2len)
597186781Slulf                        return (1);
598186781Slulf                else if (num1len < num2len)
599186781Slulf                        return (-1);
600186781Slulf
601186781Slulf                /* Equal distance means we must check each character. */
602186781Slulf		ret = strncmp(ptr1, ptr2, num1len);
603186781Slulf		if (ret != 0)
604186781Slulf			return (ret);
605186781Slulf		ptr1 = (*dot1 == '.') ? (dot1 + 1) : dot1;
606186781Slulf		ptr2 = (*dot2 == '.') ? (dot2 + 1) : dot2;
607186781Slulf        }
608186781Slulf
609186781Slulf        if (*ptr1 != '\0' && *ptr2 == '\0')
610186781Slulf                return (1);
611186781Slulf        if (*ptr1 == '\0' && *ptr2 != '\0')
612186781Slulf                return (-1);
613186781Slulf        return (0);
614186781Slulf
615186781Slulf}
616186781Slulf
617186781Slulf/* Returns 0 if a rcsrev is not a trunk revision number. */
618186781Slulfint
619186781Slulfrcsrev_istrunk(char *revnum)
620186781Slulf{
621186781Slulf	char *tmp;
622186781Slulf
623186781Slulf	tmp = strchr(revnum, '.');
624186781Slulf	tmp++;
625186781Slulf	if (strchr(tmp, '.') != NULL)
626186781Slulf		return (0);
627186781Slulf	return (1);
628186781Slulf}
629186781Slulf
630186781Slulf/* Return prefix of rcsfile. */
631186781Slulfchar *
632186781Slulfrcsrev_prefix(char *revnum)
633186781Slulf{
634186781Slulf	char *modrev, *pos;
635186781Slulf
636186781Slulf	modrev = xstrdup(revnum);
637186781Slulf	pos = strrchr(modrev, '.');
638186781Slulf	if (pos == NULL) {
639186781Slulf		free(modrev);
640186781Slulf		return (NULL);
641186781Slulf	}
642186781Slulf	*pos = '\0';
643186781Slulf	return (modrev);
644186781Slulf}
645