1/* Clean up working files.  */
2
3/* Copyright 1991, 1992, 1993, 1994, 1995 Paul Eggert
4   Distributed under license by the Free Software Foundation, Inc.
5
6This file is part of RCS.
7
8RCS is free software; you can redistribute it and/or modify
9it under the terms of the GNU General Public License as published by
10the Free Software Foundation; either version 2, or (at your option)
11any later version.
12
13RCS is distributed in the hope that it will be useful,
14but WITHOUT ANY WARRANTY; without even the implied warranty of
15MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16GNU General Public License for more details.
17
18You should have received a copy of the GNU General Public License
19along with RCS; see the file COPYING.
20If not, write to the Free Software Foundation,
2159 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
22
23Report problems and direct all questions to:
24
25	rcs-bugs@cs.purdue.edu
26
27*/
28
29#include "rcsbase.h"
30
31#if has_dirent
32	static int get_directory P((char const*,char***));
33#endif
34
35static int unlock P((struct hshentry *));
36static void cleanup P((void));
37
38static RILE *workptr;
39static int exitstatus;
40
41mainProg(rcscleanId, "rcsclean", "$FreeBSD$")
42{
43	static char const usage[] =
44		"\nrcsclean: usage: rcsclean -ksubst -{nqru}[rev] -T -Vn -xsuff -zzone file ...";
45
46	static struct buf revision;
47
48	char *a, **newargv;
49	char const *rev, *p;
50	int dounlock, expmode, perform, unlocked, unlockflag, waslocked;
51	int Ttimeflag;
52	struct hshentries *deltas;
53	struct hshentry *delta;
54	struct stat workstat;
55
56	setrid();
57
58	expmode = -1;
59	rev = 0;
60	suffixes = X_DEFAULT;
61	perform = true;
62	unlockflag = false;
63	Ttimeflag = false;
64
65	argc = getRCSINIT(argc, argv, &newargv);
66	argv = newargv;
67	for (;;) {
68		if (--argc < 1) {
69#			if has_dirent
70				argc = get_directory(".", &newargv);
71				argv = newargv;
72				break;
73#			else
74				faterror("no pathnames specified");
75#			endif
76		}
77		a = *++argv;
78		if (!*a  ||  *a++ != '-')
79			break;
80		switch (*a++) {
81			case 'k':
82				if (0 <= expmode)
83					redefined('k');
84				if ((expmode = str2expmode(a))  <  0)
85					goto unknown;
86				break;
87
88			case 'n':
89				perform = false;
90				goto handle_revision;
91
92			case 'q':
93				quietflag = true;
94				/* fall into */
95			case 'r':
96			handle_revision:
97				if (*a) {
98					if (rev)
99						warn("redefinition of revision number");
100					rev = a;
101				}
102				break;
103
104			case 'T':
105				if (*a)
106					goto unknown;
107				Ttimeflag = true;
108				break;
109
110			case 'u':
111				unlockflag = true;
112				goto handle_revision;
113
114			case 'V':
115				setRCSversion(*argv);
116				break;
117
118			case 'x':
119				suffixes = a;
120				break;
121
122			case 'z':
123				zone_set(a);
124				break;
125
126			default:
127			unknown:
128				error("unknown option: %s%s", *argv, usage);
129		}
130	}
131
132	dounlock = perform & unlockflag;
133
134	if (nerror)
135	  cleanup();
136	else
137	  for (;  0 < argc;  cleanup(), ++argv, --argc) {
138
139		ffree();
140
141		if (!(
142			0 < pairnames(
143				argc, argv,
144				dounlock ? rcswriteopen : rcsreadopen,
145				true, true
146			) &&
147			(workptr = Iopen(workname, FOPEN_R_WORK, &workstat))
148		))
149			continue;
150
151		if (same_file(RCSstat, workstat, 0)) {
152			rcserror("RCS file is the same as working file %s.",
153				workname
154			);
155			continue;
156		}
157
158		gettree();
159
160		p = 0;
161		if (rev) {
162			if (!fexpandsym(rev, &revision, workptr))
163				continue;
164			p = revision.string;
165		} else if (Head)
166			switch (unlockflag ? findlock(false,&delta) : 0) {
167				default:
168					continue;
169				case 0:
170					p = Dbranch ? Dbranch : "";
171					break;
172				case 1:
173					p = delta->num;
174					break;
175			}
176		delta = 0;
177		deltas = 0;  /* Keep lint happy.  */
178		if (p  &&  !(delta = genrevs(p,(char*)0,(char*)0,(char*)0,&deltas)))
179			continue;
180
181		waslocked = delta && delta->lockedby;
182		locker_expansion = unlock(delta);
183		unlocked = locker_expansion & unlockflag;
184		if (unlocked<waslocked  &&  workstat.st_mode&(S_IWUSR|S_IWGRP|S_IWOTH))
185			continue;
186
187		if (unlocked && !checkaccesslist())
188			continue;
189
190		if (dorewrite(dounlock, unlocked) != 0)
191			continue;
192
193		if (0 <= expmode)
194			Expand = expmode;
195		else if (
196			waslocked  &&
197			Expand == KEYVAL_EXPAND  &&
198			WORKMODE(RCSstat.st_mode,true) == workstat.st_mode
199		)
200			Expand = KEYVALLOCK_EXPAND;
201
202		getdesc(false);
203
204		if (
205			!delta ? workstat.st_size!=0 :
206			0 < rcsfcmp(
207				workptr, &workstat,
208				buildrevision(deltas, delta, (FILE*)0, false),
209				delta
210			)
211		)
212			continue;
213
214		if (quietflag < unlocked)
215			aprintf(stdout, "rcs -u%s %s\n", delta->num, RCSname);
216
217		if (perform & unlocked) {
218			if_advise_access(deltas->first != delta, finptr, MADV_SEQUENTIAL);
219			if (donerewrite(true,
220				Ttimeflag ? RCSstat.st_mtime : (time_t)-1
221			) != 0)
222				continue;
223		}
224
225		if (!quietflag)
226			aprintf(stdout, "rm -f %s\n", workname);
227		Izclose(&workptr);
228		if (perform  &&  un_link(workname) != 0)
229			eerror(workname);
230
231	  }
232
233	tempunlink();
234	if (!quietflag)
235		Ofclose(stdout);
236	exitmain(exitstatus);
237}
238
239	static void
240cleanup()
241{
242	if (nerror) exitstatus = EXIT_FAILURE;
243	Izclose(&finptr);
244	Izclose(&workptr);
245	Ozclose(&fcopy);
246	ORCSclose();
247	dirtempunlink();
248}
249
250#if RCS_lint
251#	define exiterr rcscleanExit
252#endif
253	void
254exiterr()
255{
256	ORCSerror();
257	dirtempunlink();
258	tempunlink();
259	_exit(EXIT_FAILURE);
260}
261
262	static int
263unlock(delta)
264	struct hshentry *delta;
265{
266	register struct rcslock **al, *l;
267
268	if (delta && delta->lockedby && strcmp(getcaller(),delta->lockedby)==0)
269		for (al = &Locks;  (l = *al);  al = &l->nextlock)
270			if (l->delta == delta) {
271				*al = l->nextlock;
272				delta->lockedby = 0;
273				return true;
274			}
275	return false;
276}
277
278#if has_dirent
279	static int
280get_directory(dirname, aargv)
281	char const *dirname;
282	char ***aargv;
283/*
284 * Put a vector of all DIRNAME's directory entries names into *AARGV.
285 * Ignore names of RCS files.
286 * Yield the number of entries found.  Terminate the vector with 0.
287 * Allocate the storage for the vector and entry names.
288 * Do not sort the names.  Do not include '.' and '..'.
289 */
290{
291	int i, entries = 0, entries_max = 64;
292	size_t chars = 0, chars_max = 1024;
293	size_t *offset = tnalloc(size_t, entries_max);
294	char *a = tnalloc(char, chars_max), **p;
295	DIR *d;
296	struct dirent *e;
297
298	if (!(d = opendir(dirname)))
299		efaterror(dirname);
300	while ((errno = 0,  e = readdir(d))) {
301		char const *en = e->d_name;
302		size_t s = strlen(en) + 1;
303		if (en[0]=='.'   &&   (!en[1]  ||  (en[1]=='.' && !en[2])))
304			continue;
305		if (rcssuffix(en))
306			continue;
307		while (chars_max < s + chars)
308			a = trealloc(char, a, chars_max<<=1);
309		if (entries == entries_max)
310			offset = trealloc(size_t, offset, entries_max<<=1);
311		offset[entries++] = chars;
312		VOID strcpy(a+chars, en);
313		chars += s;
314	}
315#	if void_closedir
316#		define close_directory(d) (closedir(d), 0)
317#	else
318#		define close_directory(d) closedir(d)
319#	endif
320	if (errno  ||  close_directory(d) != 0)
321		efaterror(dirname);
322	if (chars)
323		a = trealloc(char, a, chars);
324	else
325		tfree(a);
326	*aargv = p = tnalloc(char*, entries+1);
327	for (i=0; i<entries; i++)
328		*p++ = a + offset[i];
329	*p = 0;
330	tfree(offset);
331	return entries;
332}
333#endif
334