1/*	$OpenBSD: rcsclean.c,v 1.57 2023/08/11 05:02:21 guenther Exp $	*/
2/*
3 * Copyright (c) 2005 Joris Vink <joris@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/types.h>
28#include <sys/stat.h>
29
30#include <dirent.h>
31#include <err.h>
32#include <stdio.h>
33#include <stdlib.h>
34#include <time.h>
35#include <unistd.h>
36
37#include "rcsprog.h"
38#include "diff.h"
39
40static void	rcsclean_file(char *, const char *);
41
42static int nflag = 0;
43static int kflag = RCS_KWEXP_ERR;
44static int uflag = 0;
45static int flags = 0;
46static char *locker = NULL;
47
48int
49rcsclean_main(int argc, char **argv)
50{
51	int i, ch;
52	char *rev_str;
53	DIR *dirp;
54	struct dirent *dp;
55
56	rev_str = NULL;
57
58	while ((ch = rcs_getopt(argc, argv, "k:n::q::r::Tu::Vx::")) != -1) {
59		switch (ch) {
60		case 'k':
61			kflag = rcs_kflag_get(rcs_optarg);
62			if (RCS_KWEXP_INVAL(kflag)) {
63				warnx("invalid RCS keyword substitution mode");
64				(usage)();
65			}
66			break;
67		case 'n':
68			rcs_setrevstr(&rev_str, rcs_optarg);
69			nflag = 1;
70			break;
71		case 'q':
72			rcs_setrevstr(&rev_str, rcs_optarg);
73			flags |= QUIET;
74			break;
75		case 'r':
76			rcs_setrevstr(&rev_str, rcs_optarg);
77			break;
78		case 'T':
79			flags |= PRESERVETIME;
80			break;
81		case 'u':
82			rcs_setrevstr(&rev_str, rcs_optarg);
83			uflag = 1;
84			break;
85		case 'V':
86			printf("%s\n", rcs_version);
87			exit(0);
88		case 'x':
89			/* Use blank extension if none given. */
90			rcs_suffixes = rcs_optarg ? rcs_optarg : "";
91			break;
92		default:
93			(usage)();
94		}
95	}
96
97	argc -= rcs_optind;
98	argv += rcs_optind;
99
100	if ((locker = getlogin()) == NULL)
101		err(1, "getlogin");
102
103	if (argc == 0) {
104		if ((dirp = opendir(".")) == NULL) {
105			warn("opendir");
106			(usage)();
107		}
108
109		while ((dp = readdir(dirp)) != NULL) {
110			if (dp->d_type == DT_DIR)
111				continue;
112			rcsclean_file(dp->d_name, rev_str);
113		}
114
115		(void)closedir(dirp);
116	} else
117		for (i = 0; i < argc; i++)
118			rcsclean_file(argv[i], rev_str);
119
120	return (0);
121}
122
123__dead void
124rcsclean_usage(void)
125{
126	fprintf(stderr,
127	    "usage: rcsclean [-TV] [-kmode] [-n[rev]] [-q[rev]] [-r[rev]]\n"
128	    "                [-u[rev]] [-xsuffixes] [-ztz] [file ...]\n");
129
130	exit(1);
131}
132
133static void
134rcsclean_file(char *fname, const char *rev_str)
135{
136	int fd, match;
137	RCSFILE *file;
138	char fpath[PATH_MAX], numb[RCS_REV_BUFSZ];
139	RCSNUM *rev;
140	BUF *b1, *b2;
141	struct timespec rcs_mtime = { .tv_sec = 0, .tv_nsec = UTIME_OMIT };
142
143	b1 = b2 = NULL;
144	file = NULL;
145	rev = NULL;
146
147	if ((fd = rcs_choosefile(fname, fpath, sizeof(fpath))) < 0)
148		goto out;
149
150	if ((file = rcs_open(fpath, fd, RCS_RDWR)) == NULL)
151		goto out;
152
153	if (flags & PRESERVETIME)
154		rcs_mtime = rcs_get_mtime(file);
155
156	rcs_kwexp_set(file, kflag);
157
158	if (rev_str == NULL)
159		rev = file->rf_head;
160	else if ((rev = rcs_getrevnum(rev_str, file)) == NULL) {
161		warnx("%s: Symbolic name `%s' is undefined.", fpath, rev_str);
162		goto out;
163	}
164
165	if ((b1 = rcs_getrev(file, rev)) == NULL) {
166		warnx("failed to get needed revision");
167		goto out;
168	}
169	if ((b2 = buf_load(fname)) == NULL) {
170		warnx("failed to load `%s'", fname);
171		goto out;
172	}
173
174	/* If buffer lengths are the same, compare contents as well. */
175	if (buf_len(b1) != buf_len(b2))
176		match = 0;
177	else {
178		size_t len, n;
179
180		len = buf_len(b1);
181
182		match = 1;
183		for (n = 0; n < len; ++n)
184			if (buf_getc(b1, n) != buf_getc(b2, n)) {
185				match = 0;
186				break;
187			}
188	}
189
190	if (match == 1) {
191		if (uflag == 1 && !TAILQ_EMPTY(&(file->rf_locks))) {
192			if (!(flags & QUIET) && nflag == 0) {
193				printf("rcs -u%s %s\n",
194				    rcsnum_tostr(rev, numb, sizeof(numb)),
195				    fpath);
196			}
197			(void)rcs_lock_remove(file, locker, rev);
198		}
199
200		if (TAILQ_EMPTY(&(file->rf_locks))) {
201			if (!(flags & QUIET))
202				printf("rm -f %s\n", fname);
203
204			if (nflag == 0)
205				(void)unlink(fname);
206		}
207	}
208
209	rcs_write(file);
210	if (flags & PRESERVETIME)
211		rcs_set_mtime(file, rcs_mtime);
212
213out:
214	buf_free(b1);
215	buf_free(b2);
216	if (file != NULL)
217		rcs_close(file);
218}
219