lister.c revision 156701
1/*-
2 * Copyright (c) 2003-2006, Maxime Henrion <mux@FreeBSD.org>
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 *    notice, this list of conditions and the following disclaimer in the
12 *    documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24 * SUCH DAMAGE.
25 *
26 * $FreeBSD: vendor/csup/dist/contrib/csup/lister.c 156701 2006-03-14 03:51:13Z mux $
27 */
28
29#include <assert.h>
30#include <errno.h>
31#include <limits.h>
32#include <stdio.h>
33#include <stdlib.h>
34#include <string.h>
35
36#include "attrstack.h"
37#include "config.h"
38#include "fattr.h"
39#include "globtree.h"
40#include "lister.h"
41#include "misc.h"
42#include "mux.h"
43#include "proto.h"
44#include "status.h"
45#include "stream.h"
46
47/* Internal error codes. */
48#define	LISTER_ERR_WRITE	(-1)	/* Error writing to server. */
49#define	LISTER_ERR_STATUS	(-2)	/* Status file error in lstr->errmsg. */
50
51struct lister {
52	struct config *config;
53	struct stream *wr;
54	char *errmsg;
55};
56
57static int	lister_batch(struct lister *);
58static int	lister_coll(struct lister *, struct coll *, struct status *);
59static int	lister_dodirdown(struct lister *, struct coll *,
60		    struct statusrec *, struct attrstack *as);
61static int	lister_dodirup(struct lister *, struct coll *,
62    		    struct statusrec *, struct attrstack *as);
63static int	lister_dofile(struct lister *, struct coll *,
64		    struct statusrec *);
65static int	lister_dodead(struct lister *, struct coll *,
66		    struct statusrec *);
67
68void *
69lister(void *arg)
70{
71	struct thread_args *args;
72	struct lister lbuf, *l;
73	int error;
74
75	args = arg;
76	l = &lbuf;
77	l->config = args->config;
78	l->wr = args->wr;
79	l->errmsg = NULL;
80	error = lister_batch(l);
81	switch (error) {
82	case LISTER_ERR_WRITE:
83		xasprintf(&args->errmsg,
84		    "TreeList failed: Network write failure: %s",
85		    strerror(errno));
86		args->status = STATUS_TRANSIENTFAILURE;
87		break;
88	case LISTER_ERR_STATUS:
89		xasprintf(&args->errmsg,
90		    "TreeList failed: %s.  Delete it and try again.",
91		    l->errmsg);
92		free(l->errmsg);
93		args->status = STATUS_FAILURE;
94		break;
95	default:
96		assert(error == 0);
97		args->status = STATUS_SUCCESS;
98	};
99	return (NULL);
100}
101
102static int
103lister_batch(struct lister *l)
104{
105	struct config *config;
106	struct stream *wr;
107	struct status *st;
108	struct coll *coll;
109	int error;
110
111	config = l->config;
112	wr = l->wr;
113	STAILQ_FOREACH(coll, &config->colls, co_next) {
114		if (coll->co_options & CO_SKIP)
115			continue;
116		st = status_open(coll, -1, &l->errmsg);
117		if (st == NULL)
118			return (LISTER_ERR_STATUS);
119		error = proto_printf(wr, "COLL %s %s\n", coll->co_name,
120		    coll->co_release);
121		if (error)
122			return (LISTER_ERR_WRITE);
123		stream_flush(wr);
124		if (coll->co_options & CO_COMPRESS)
125			stream_filter_start(wr, STREAM_FILTER_ZLIB, NULL);
126		error = lister_coll(l, coll, st);
127		status_close(st, NULL);
128		if (error)
129			return (error);
130		if (coll->co_options & CO_COMPRESS)
131			stream_filter_stop(wr);
132		stream_flush(wr);
133	}
134	error = proto_printf(wr, ".\n");
135	if (error)
136		return (LISTER_ERR_WRITE);
137	return (0);
138}
139
140/* List a single collection based on the status file. */
141static int
142lister_coll(struct lister *l, struct coll *coll, struct status *st)
143{
144	struct stream *wr;
145	struct attrstack *as;
146	struct statusrec *sr;
147	struct fattr *fa;
148	size_t i;
149	int depth, error, ret, prunedepth;
150
151	wr = l->wr;
152	depth = 0;
153	prunedepth = INT_MAX;
154	as = attrstack_new();
155	while ((ret = status_get(st, NULL, 0, 0, &sr)) == 1) {
156		switch (sr->sr_type) {
157		case SR_DIRDOWN:
158			depth++;
159			if (depth < prunedepth) {
160				error = lister_dodirdown(l, coll, sr, as);
161				if (error < 0)
162					goto bad;
163				if (error)
164					prunedepth = depth;
165			}
166			break;
167		case SR_DIRUP:
168			if (depth < prunedepth) {
169				error = lister_dodirup(l, coll, sr, as);
170				if (error)
171					goto bad;
172			} else if (depth == prunedepth) {
173				/* Finished pruning. */
174				prunedepth = INT_MAX;
175			}
176			depth--;
177			continue;
178		case SR_CHECKOUTLIVE:
179			if (depth < prunedepth) {
180				error = lister_dofile(l, coll, sr);
181				if (error)
182					goto bad;
183			}
184			break;
185		case SR_CHECKOUTDEAD:
186			if (depth < prunedepth) {
187				error = lister_dodead(l, coll, sr);
188				if (error)
189					goto bad;
190			}
191			break;
192		}
193	}
194	if (ret == -1) {
195		l->errmsg = status_errmsg(st);
196		error = LISTER_ERR_STATUS;
197		goto bad;
198	}
199	assert(status_eof(st));
200	assert(depth == 0);
201	error = proto_printf(wr, ".\n");
202	attrstack_free(as);
203	if (error)
204		return (LISTER_ERR_WRITE);
205	return (0);
206bad:
207	for (i = 0; i < attrstack_size(as); i++) {
208		fa = attrstack_pop(as);
209		fattr_free(fa);
210	}
211	attrstack_free(as);
212	return (error);
213}
214
215/* Handle a directory up entry found in the status file. */
216static int
217lister_dodirdown(struct lister *l, struct coll *coll, struct statusrec *sr,
218    struct attrstack *as)
219{
220	struct config *config;
221	struct stream *wr;
222	struct fattr *fa, *fa2;
223	char *path;
224	int error;
225
226	config = l->config;
227	wr = l->wr;
228	if (!globtree_test(coll->co_dirfilter, sr->sr_file))
229		return (1);
230	if (coll->co_options & CO_TRUSTSTATUSFILE) {
231		fa = fattr_new(FT_DIRECTORY, -1);
232	} else {
233		xasprintf(&path, "%s/%s", coll->co_prefix, sr->sr_file);
234		fa = fattr_frompath(path, FATTR_NOFOLLOW);
235		if (fa == NULL) {
236			/* The directory doesn't exist, prune
237			 * everything below it. */
238			free(path);
239			return (1);
240		}
241		if (fattr_type(fa) == FT_SYMLINK) {
242			fa2 = fattr_frompath(path, FATTR_FOLLOW);
243			if (fa2 != NULL && fattr_type(fa2) == FT_DIRECTORY) {
244				/* XXX - When not in checkout mode, CVSup warns
245				 * here about the file being a symlink to a
246				 * directory instead of a directory. */
247				fattr_free(fa);
248				fa = fa2;
249			} else {
250				fattr_free(fa2);
251			}
252		}
253		free(path);
254	}
255
256	if (fattr_type(fa) != FT_DIRECTORY) {
257		fattr_free(fa);
258		/* Report it as something bogus so
259		 * that it will be replaced. */
260		error = proto_printf(wr, "F %s %F\n", pathlast(sr->sr_file),
261		    fattr_bogus, config->fasupport, coll->co_attrignore);
262		if (error)
263			return (LISTER_ERR_WRITE);
264		return (1);
265	}
266
267	/* It really is a directory. */
268	attrstack_push(as, fa);
269	error = proto_printf(wr, "D %s\n", pathlast(sr->sr_file));
270	if (error)
271		return (LISTER_ERR_WRITE);
272	return (0);
273}
274
275/* Handle a directory up entry found in the status file. */
276static int
277lister_dodirup(struct lister *l, struct coll *coll, struct statusrec *sr,
278    struct attrstack *as)
279{
280	struct config *config;
281	const struct fattr *sendattr;
282	struct stream *wr;
283	struct fattr *fa, *fa2;
284	int error;
285
286	config = l->config;
287	wr = l->wr;
288	fa = attrstack_pop(as);
289	if (coll->co_options & CO_TRUSTSTATUSFILE) {
290		fattr_free(fa);
291		fa = sr->sr_clientattr;
292	}
293
294	fa2 = sr->sr_clientattr;
295	if (fattr_equal(fa, fa2))
296		sendattr = fa;
297	else
298		sendattr = fattr_bogus;
299	error = proto_printf(wr, "U %F\n", sendattr, config->fasupport,
300	    coll->co_attrignore);
301	if (error)
302		return (LISTER_ERR_WRITE);
303	if (!(coll->co_options & CO_TRUSTSTATUSFILE))
304		fattr_free(fa);
305	/* XXX CVSup flushes here for some reason with a comment saying
306	   "Be smarter".  We don't flush when listing other file types. */
307	stream_flush(wr);
308	return (0);
309}
310
311/* Handle a checkout live entry found in the status file. */
312static int
313lister_dofile(struct lister *l, struct coll *coll, struct statusrec *sr)
314{
315	struct config *config;
316	struct stream *wr;
317	const struct fattr *sendattr, *fa;
318	struct fattr *fa2, *rfa;
319	char *path, *spath;
320	int error;
321
322	if (!globtree_test(coll->co_filefilter, sr->sr_file))
323		return (0);
324	config = l->config;
325	wr = l->wr;
326	rfa = NULL;
327	sendattr = NULL;
328	error = 0;
329	if (!(coll->co_options & CO_TRUSTSTATUSFILE)) {
330		path = checkoutpath(coll->co_prefix, sr->sr_file);
331		if (path == NULL) {
332			spath = coll_statuspath(coll);
333			xasprintf(&l->errmsg, "Error in \"%s\": "
334			    "Invalid filename \"%s\"", spath, sr->sr_file);
335			free(spath);
336			return (LISTER_ERR_STATUS);
337		}
338		rfa = fattr_frompath(path, FATTR_NOFOLLOW);
339		free(path);
340		if (rfa == NULL) {
341			/*
342			 * According to the checkouts file we should have
343			 * this file but we don't.  Maybe the user deleted
344			 * the file, or maybe the checkouts file is wrong.
345			 * List the file with bogus attributes to cause the
346			 * server to get things back in sync again.
347			 */
348			sendattr = fattr_bogus;
349			goto send;
350		}
351		fa = rfa;
352	} else {
353		fa = sr->sr_clientattr;
354	}
355	fa2 = fattr_forcheckout(sr->sr_serverattr, coll->co_umask);
356	if (!fattr_equal(fa, sr->sr_clientattr) || !fattr_equal(fa, fa2) ||
357	    strcmp(coll->co_tag, sr->sr_tag) != 0 ||
358	    strcmp(coll->co_date, sr->sr_date) != 0) {
359		/*
360		 * The file corresponds to the information we have
361		 * recorded about it, and its moded is correct for
362		 * the requested umask setting.
363		 */
364		sendattr = fattr_bogus;
365	} else {
366		/*
367		 * Either the file has been touched, or we are asking
368		 * for a different revision than the one we recorded
369		 * information about, or its mode isn't right (because
370		 * it was last updated using a version of CVSup that
371		 * wasn't so strict about modes).
372		 */
373		sendattr = sr->sr_serverattr;
374	}
375	fattr_free(fa2);
376	if (rfa != NULL)
377		fattr_free(rfa);
378send:
379	error = proto_printf(wr, "F %s %F\n", pathlast(sr->sr_file), sendattr,
380	    config->fasupport, coll->co_attrignore);
381	if (error)
382		return (LISTER_ERR_WRITE);
383	return (0);
384}
385
386/* Handle a checkout dead entry found in the status file. */
387static int
388lister_dodead(struct lister *l, struct coll *coll, struct statusrec *sr)
389{
390	struct config *config;
391	struct stream *wr;
392	const struct fattr *sendattr;
393	struct fattr *fa;
394	char *path, *spath;
395	int error;
396
397	if (!globtree_test(coll->co_filefilter, sr->sr_file))
398		return (0);
399	config = l->config;
400	wr = l->wr;
401	if (!(coll->co_options & CO_TRUSTSTATUSFILE)) {
402		path = checkoutpath(coll->co_prefix, sr->sr_file);
403		if (path == NULL) {
404			spath = coll_statuspath(coll);
405			xasprintf(&l->errmsg, "Error in \"%s\": "
406			    "Invalid filename \"%s\"", spath, sr->sr_file);
407			free(spath);
408			return (LISTER_ERR_STATUS);
409		}
410		fa = fattr_frompath(path, FATTR_NOFOLLOW);
411		free(path);
412		if (fa != NULL && fattr_type(fa) != FT_DIRECTORY) {
413			/*
414			 * We shouldn't have this file but we do.  Report
415			 * it to the server, which will either send a
416			 * deletion request, of (if the file has come alive)
417			 * sent the correct version.
418			 */
419			fattr_free(fa);
420			error = proto_printf(wr, "F %s %F\n",
421			    pathlast(sr->sr_file), fattr_bogus,
422			    config->fasupport, coll->co_attrignore);
423			if (error)
424				return (LISTER_ERR_WRITE);
425			return (0);
426		}
427		fattr_free(fa);
428	}
429	if (strcmp(coll->co_tag, sr->sr_tag) != 0 ||
430	    strcmp(coll->co_date, sr->sr_date) != 0)
431		sendattr = fattr_bogus;
432	else
433		sendattr = sr->sr_serverattr;
434	error = proto_printf(wr, "f %s %F\n", pathlast(sr->sr_file), sendattr,
435	    config->fasupport, coll->co_attrignore);
436	if (error)
437		return (LISTER_ERR_WRITE);
438	return (0);
439}
440