xref: /trueos/usr.bin/csup/lister.c (revision b972b67ed72b5687a023c92602aaef64163b2f59)
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$
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 
51 struct lister {
52 	struct config *config;
53 	struct stream *wr;
54 	char *errmsg;
55 };
56 
57 static int	lister_batch(struct lister *);
58 static int	lister_coll(struct lister *, struct coll *, struct status *);
59 static int	lister_dodirdown(struct lister *, struct coll *,
60 		    struct statusrec *, struct attrstack *as);
61 static int	lister_dodirup(struct lister *, struct coll *,
62     		    struct statusrec *, struct attrstack *as);
63 static int	lister_dofile(struct lister *, struct coll *,
64 		    struct statusrec *);
65 static int	lister_dodead(struct lister *, struct coll *,
66 		    struct statusrec *);
67 static int	lister_dorcsfile(struct lister *, struct coll *,
68 		    struct statusrec *);
69 static int	lister_dorcsdead(struct lister *, struct coll *,
70 		    struct statusrec *);
71 
72 void *
lister(void * arg)73 lister(void *arg)
74 {
75 	struct thread_args *args;
76 	struct lister lbuf, *l;
77 	int error;
78 
79 	args = arg;
80 	l = &lbuf;
81 	l->config = args->config;
82 	l->wr = args->wr;
83 	l->errmsg = NULL;
84 	error = lister_batch(l);
85 	switch (error) {
86 	case LISTER_ERR_WRITE:
87 		xasprintf(&args->errmsg,
88 		    "TreeList failed: Network write failure: %s",
89 		    strerror(errno));
90 		args->status = STATUS_TRANSIENTFAILURE;
91 		break;
92 	case LISTER_ERR_STATUS:
93 		xasprintf(&args->errmsg,
94 		    "TreeList failed: %s.  Delete it and try again.",
95 		    l->errmsg);
96 		free(l->errmsg);
97 		args->status = STATUS_FAILURE;
98 		break;
99 	default:
100 		assert(error == 0);
101 		args->status = STATUS_SUCCESS;
102 	};
103 	return (NULL);
104 }
105 
106 static int
lister_batch(struct lister * l)107 lister_batch(struct lister *l)
108 {
109 	struct config *config;
110 	struct stream *wr;
111 	struct status *st;
112 	struct coll *coll;
113 	int error;
114 
115 	config = l->config;
116 	wr = l->wr;
117 	STAILQ_FOREACH(coll, &config->colls, co_next) {
118 		if (coll->co_options & CO_SKIP)
119 			continue;
120 		st = status_open(coll, -1, &l->errmsg);
121 		if (st == NULL)
122 			return (LISTER_ERR_STATUS);
123 		error = proto_printf(wr, "COLL %s %s\n", coll->co_name,
124 		    coll->co_release);
125 		if (error)
126 			return (LISTER_ERR_WRITE);
127 		stream_flush(wr);
128 		if (coll->co_options & CO_COMPRESS)
129 			stream_filter_start(wr, STREAM_FILTER_ZLIB, NULL);
130 		error = lister_coll(l, coll, st);
131 		status_close(st, NULL);
132 		if (error)
133 			return (error);
134 		if (coll->co_options & CO_COMPRESS)
135 			stream_filter_stop(wr);
136 		stream_flush(wr);
137 	}
138 	error = proto_printf(wr, ".\n");
139 	if (error)
140 		return (LISTER_ERR_WRITE);
141 	return (0);
142 }
143 
144 /* List a single collection based on the status file. */
145 static int
lister_coll(struct lister * l,struct coll * coll,struct status * st)146 lister_coll(struct lister *l, struct coll *coll, struct status *st)
147 {
148 	struct stream *wr;
149 	struct attrstack *as;
150 	struct statusrec *sr;
151 	struct fattr *fa;
152 	size_t i;
153 	int depth, error, ret, prunedepth;
154 
155 	wr = l->wr;
156 	depth = 0;
157 	prunedepth = INT_MAX;
158 	as = attrstack_new();
159 	while ((ret = status_get(st, NULL, 0, 0, &sr)) == 1) {
160 		switch (sr->sr_type) {
161 		case SR_DIRDOWN:
162 			depth++;
163 			if (depth < prunedepth) {
164 				error = lister_dodirdown(l, coll, sr, as);
165 				if (error < 0)
166 					goto bad;
167 				if (error)
168 					prunedepth = depth;
169 			}
170 			break;
171 		case SR_DIRUP:
172 			if (depth < prunedepth) {
173 				error = lister_dodirup(l, coll, sr, as);
174 				if (error)
175 					goto bad;
176 			} else if (depth == prunedepth) {
177 				/* Finished pruning. */
178 				prunedepth = INT_MAX;
179 			}
180 			depth--;
181 			continue;
182 		case SR_CHECKOUTLIVE:
183 			if (depth < prunedepth) {
184 				error = lister_dofile(l, coll, sr);
185 				if (error)
186 					goto bad;
187 			}
188 			break;
189 		case SR_CHECKOUTDEAD:
190 			if (depth < prunedepth) {
191 				error = lister_dodead(l, coll, sr);
192 				if (error)
193 					goto bad;
194 			}
195 			break;
196 		case SR_FILEDEAD:
197 			if (depth < prunedepth) {
198 				if (!(coll->co_options & CO_CHECKOUTMODE)) {
199 					error = lister_dorcsdead(l, coll, sr);
200 					if (error)
201 						goto bad;
202 				}
203 			}
204 			break;
205 		case SR_FILELIVE:
206 			if (depth < prunedepth) {
207 				if (!(coll->co_options & CO_CHECKOUTMODE)) {
208 					error = lister_dorcsfile(l, coll, sr);
209 					if (error)
210 						goto bad;
211 				}
212 			}
213 			break;
214 		}
215 	}
216 	if (ret == -1) {
217 		l->errmsg = status_errmsg(st);
218 		error = LISTER_ERR_STATUS;
219 		goto bad;
220 	}
221 	assert(status_eof(st));
222 	assert(depth == 0);
223 	error = proto_printf(wr, ".\n");
224 	attrstack_free(as);
225 	if (error)
226 		return (LISTER_ERR_WRITE);
227 	return (0);
228 bad:
229 	for (i = 0; i < attrstack_size(as); i++) {
230 		fa = attrstack_pop(as);
231 		fattr_free(fa);
232 	}
233 	attrstack_free(as);
234 	return (error);
235 }
236 
237 /* Handle a directory up entry found in the status file. */
238 static int
lister_dodirdown(struct lister * l,struct coll * coll,struct statusrec * sr,struct attrstack * as)239 lister_dodirdown(struct lister *l, struct coll *coll, struct statusrec *sr,
240     struct attrstack *as)
241 {
242 	struct config *config;
243 	struct stream *wr;
244 	struct fattr *fa, *fa2;
245 	char *path;
246 	int error;
247 
248 	config = l->config;
249 	wr = l->wr;
250 	if (!globtree_test(coll->co_dirfilter, sr->sr_file))
251 		return (1);
252 	if (coll->co_options & CO_TRUSTSTATUSFILE) {
253 		fa = fattr_new(FT_DIRECTORY, -1);
254 	} else {
255 		xasprintf(&path, "%s/%s", coll->co_prefix, sr->sr_file);
256 		fa = fattr_frompath(path, FATTR_NOFOLLOW);
257 		if (fa == NULL) {
258 			/* The directory doesn't exist, prune
259 			 * everything below it. */
260 			free(path);
261 			return (1);
262 		}
263 		if (fattr_type(fa) == FT_SYMLINK) {
264 			fa2 = fattr_frompath(path, FATTR_FOLLOW);
265 			if (fa2 != NULL && fattr_type(fa2) == FT_DIRECTORY) {
266 				/* XXX - When not in checkout mode, CVSup warns
267 				 * here about the file being a symlink to a
268 				 * directory instead of a directory. */
269 				fattr_free(fa);
270 				fa = fa2;
271 			} else {
272 				fattr_free(fa2);
273 			}
274 		}
275 		free(path);
276 	}
277 
278 	if (fattr_type(fa) != FT_DIRECTORY) {
279 		fattr_free(fa);
280 		/* Report it as something bogus so
281 		 * that it will be replaced. */
282 		error = proto_printf(wr, "F %s %F\n", pathlast(sr->sr_file),
283 		    fattr_bogus, config->fasupport, coll->co_attrignore);
284 		if (error)
285 			return (LISTER_ERR_WRITE);
286 		return (1);
287 	}
288 
289 	/* It really is a directory. */
290 	attrstack_push(as, fa);
291 	error = proto_printf(wr, "D %s\n", pathlast(sr->sr_file));
292 	if (error)
293 		return (LISTER_ERR_WRITE);
294 	return (0);
295 }
296 
297 /* Handle a directory up entry found in the status file. */
298 static int
lister_dodirup(struct lister * l,struct coll * coll,struct statusrec * sr,struct attrstack * as)299 lister_dodirup(struct lister *l, struct coll *coll, struct statusrec *sr,
300     struct attrstack *as)
301 {
302 	struct config *config;
303 	const struct fattr *sendattr;
304 	struct stream *wr;
305 	struct fattr *fa, *fa2;
306 	int error;
307 
308 	config = l->config;
309 	wr = l->wr;
310 	fa = attrstack_pop(as);
311 	if (coll->co_options & CO_TRUSTSTATUSFILE) {
312 		fattr_free(fa);
313 		fa = sr->sr_clientattr;
314 	}
315 
316 	fa2 = sr->sr_clientattr;
317 	if (fattr_equal(fa, fa2))
318 		sendattr = fa;
319 	else
320 		sendattr = fattr_bogus;
321 	error = proto_printf(wr, "U %F\n", sendattr, config->fasupport,
322 	    coll->co_attrignore);
323 	if (error)
324 		return (LISTER_ERR_WRITE);
325 	if (!(coll->co_options & CO_TRUSTSTATUSFILE))
326 		fattr_free(fa);
327 	/* XXX CVSup flushes here for some reason with a comment saying
328 	   "Be smarter".  We don't flush when listing other file types. */
329 	stream_flush(wr);
330 	return (0);
331 }
332 
333 /* Handle a checkout live entry found in the status file. */
334 static int
lister_dofile(struct lister * l,struct coll * coll,struct statusrec * sr)335 lister_dofile(struct lister *l, struct coll *coll, struct statusrec *sr)
336 {
337 	struct config *config;
338 	struct stream *wr;
339 	const struct fattr *sendattr, *fa;
340 	struct fattr *fa2, *rfa;
341 	char *path, *spath;
342 	int error;
343 
344 	if (!globtree_test(coll->co_filefilter, sr->sr_file))
345 		return (0);
346 	config = l->config;
347 	wr = l->wr;
348 	rfa = NULL;
349 	sendattr = NULL;
350 	error = 0;
351 	if (!(coll->co_options & CO_TRUSTSTATUSFILE)) {
352 		path = checkoutpath(coll->co_prefix, sr->sr_file);
353 		if (path == NULL) {
354 			spath = coll_statuspath(coll);
355 			xasprintf(&l->errmsg, "Error in \"%s\": "
356 			    "Invalid filename \"%s\"", spath, sr->sr_file);
357 			free(spath);
358 			return (LISTER_ERR_STATUS);
359 		}
360 		rfa = fattr_frompath(path, FATTR_NOFOLLOW);
361 		free(path);
362 		if (rfa == NULL) {
363 			/*
364 			 * According to the checkouts file we should have
365 			 * this file but we don't.  Maybe the user deleted
366 			 * the file, or maybe the checkouts file is wrong.
367 			 * List the file with bogus attributes to cause the
368 			 * server to get things back in sync again.
369 			 */
370 			sendattr = fattr_bogus;
371 			goto send;
372 		}
373 		fa = rfa;
374 	} else {
375 		fa = sr->sr_clientattr;
376 	}
377 	fa2 = fattr_forcheckout(sr->sr_serverattr, coll->co_umask);
378 	if (!fattr_equal(fa, sr->sr_clientattr) || !fattr_equal(fa, fa2) ||
379 	    strcmp(coll->co_tag, sr->sr_tag) != 0 ||
380 	    strcmp(coll->co_date, sr->sr_date) != 0) {
381 		/*
382 		 * The file corresponds to the information we have
383 		 * recorded about it, and its moded is correct for
384 		 * the requested umask setting.
385 		 */
386 		sendattr = fattr_bogus;
387 	} else {
388 		/*
389 		 * Either the file has been touched, or we are asking
390 		 * for a different revision than the one we recorded
391 		 * information about, or its mode isn't right (because
392 		 * it was last updated using a version of CVSup that
393 		 * wasn't so strict about modes).
394 		 */
395 		sendattr = sr->sr_serverattr;
396 	}
397 	fattr_free(fa2);
398 	if (rfa != NULL)
399 		fattr_free(rfa);
400 send:
401 	error = proto_printf(wr, "F %s %F\n", pathlast(sr->sr_file), sendattr,
402 	    config->fasupport, coll->co_attrignore);
403 	if (error)
404 		return (LISTER_ERR_WRITE);
405 	return (0);
406 }
407 
408 /* Handle a rcs file live entry found in the status file. */
409 static int
lister_dorcsfile(struct lister * l,struct coll * coll,struct statusrec * sr)410 lister_dorcsfile(struct lister *l, struct coll *coll, struct statusrec *sr)
411 {
412 	struct config *config;
413 	struct stream *wr;
414 	const struct fattr *sendattr;
415 	struct fattr *fa;
416 	char *path, *spath;
417 	size_t len;
418 	int error;
419 
420 	if (!globtree_test(coll->co_filefilter, sr->sr_file))
421 		return (0);
422 	config = l->config;
423 	wr = l->wr;
424 	if (!(coll->co_options & CO_TRUSTSTATUSFILE)) {
425 		path = cvspath(coll->co_prefix, sr->sr_file, 0);
426 		if (path == NULL) {
427 			spath = coll_statuspath(coll);
428 			xasprintf(&l->errmsg, "Error in \"%s\": "
429 			    "Invalid filename \"%s\"", spath, sr->sr_file);
430 			free(spath);
431 			return (LISTER_ERR_STATUS);
432 		}
433 		fa = fattr_frompath(path, FATTR_NOFOLLOW);
434 		free(path);
435 	} else
436 		fa = sr->sr_clientattr;
437 	if (fa != NULL && fattr_equal(fa, sr->sr_clientattr)) {
438 		/*
439 		 * If the file is an RCS file, we use "loose" equality, so sizes
440 		 * may disagress because of differences in whitespace.
441 		 */
442 		if (isrcs(sr->sr_file, &len) &&
443 		    !(coll->co_options & CO_NORCS) &&
444 		    !(coll->co_options & CO_STRICTCHECKRCS)) {
445 			fattr_maskout(fa, FA_SIZE);
446 		}
447 		sendattr = fa;
448 	} else {
449 		/*
450 		 * If different, the user may have changed it, so we report
451 		 * bogus attributes to force a full comparison.
452 		 */
453 		sendattr = fattr_bogus;
454 	}
455 	error = proto_printf(wr, "F %s %F\n", pathlast(sr->sr_file), sendattr,
456 	    config->fasupport, coll->co_attrignore);
457 	if (error)
458 		return (LISTER_ERR_WRITE);
459 	return (0);
460 }
461 
462 /* Handle a checkout dead entry found in the status file. */
463 static int
lister_dodead(struct lister * l,struct coll * coll,struct statusrec * sr)464 lister_dodead(struct lister *l, struct coll *coll, struct statusrec *sr)
465 {
466 	struct config *config;
467 	struct stream *wr;
468 	const struct fattr *sendattr;
469 	struct fattr *fa;
470 	char *path, *spath;
471 	int error;
472 
473 	if (!globtree_test(coll->co_filefilter, sr->sr_file))
474 		return (0);
475 	config = l->config;
476 	wr = l->wr;
477 	if (!(coll->co_options & CO_TRUSTSTATUSFILE)) {
478 		path = checkoutpath(coll->co_prefix, sr->sr_file);
479 		if (path == NULL) {
480 			spath = coll_statuspath(coll);
481 			xasprintf(&l->errmsg, "Error in \"%s\": "
482 			    "Invalid filename \"%s\"", spath, sr->sr_file);
483 			free(spath);
484 			return (LISTER_ERR_STATUS);
485 		}
486 		fa = fattr_frompath(path, FATTR_NOFOLLOW);
487 		free(path);
488 		if (fa != NULL && fattr_type(fa) != FT_DIRECTORY) {
489 			/*
490 			 * We shouldn't have this file but we do.  Report
491 			 * it to the server, which will either send a
492 			 * deletion request, of (if the file has come alive)
493 			 * sent the correct version.
494 			 */
495 			fattr_free(fa);
496 			error = proto_printf(wr, "F %s %F\n",
497 			    pathlast(sr->sr_file), fattr_bogus,
498 			    config->fasupport, coll->co_attrignore);
499 			if (error)
500 				return (LISTER_ERR_WRITE);
501 			return (0);
502 		}
503 		fattr_free(fa);
504 	}
505 	if (strcmp(coll->co_tag, sr->sr_tag) != 0 ||
506 	    strcmp(coll->co_date, sr->sr_date) != 0)
507 		sendattr = fattr_bogus;
508 	else
509 		sendattr = sr->sr_serverattr;
510 	error = proto_printf(wr, "f %s %F\n", pathlast(sr->sr_file), sendattr,
511 	    config->fasupport, coll->co_attrignore);
512 	if (error)
513 		return (LISTER_ERR_WRITE);
514 	return (0);
515 }
516 
517 /* Handle a rcs file dead entry found in the status file. */
518 static int
lister_dorcsdead(struct lister * l,struct coll * coll,struct statusrec * sr)519 lister_dorcsdead(struct lister *l, struct coll *coll, struct statusrec *sr)
520 {
521 	struct config *config;
522 	struct stream *wr;
523 	const struct fattr *sendattr;
524 	struct fattr *fa;
525 	char *path, *spath;
526 	size_t len;
527 	int error;
528 
529 	if (!globtree_test(coll->co_filefilter, sr->sr_file))
530 		return (0);
531 	config = l->config;
532 	wr = l->wr;
533 	if (!(coll->co_options & CO_TRUSTSTATUSFILE)) {
534 		path = cvspath(coll->co_prefix, sr->sr_file, 1);
535 		if (path == NULL) {
536 			spath = coll_statuspath(coll);
537 			xasprintf(&l->errmsg, "Error in \"%s\": "
538 			    "Invalid filename \"%s\"", spath, sr->sr_file);
539 			free(spath);
540 			return (LISTER_ERR_STATUS);
541 		}
542 		fa = fattr_frompath(path, FATTR_NOFOLLOW);
543 		free(path);
544 	} else
545 		fa = sr->sr_clientattr;
546 	if (fattr_equal(fa, sr->sr_clientattr)) {
547 		/*
548 		 * If the file is an RCS file, we use "loose" equality, so sizes
549 		 * may disagress because of differences in whitespace.
550 		 */
551 		if (isrcs(sr->sr_file, &len) &&
552 		    !(coll->co_options & CO_NORCS) &&
553 		    !(coll->co_options & CO_STRICTCHECKRCS)) {
554 			fattr_maskout(fa, FA_SIZE);
555 		}
556 		sendattr = fa;
557 	} else {
558 		/*
559 		 * If different, the user may have changed it, so we report
560 		 * bogus attributes to force a full comparison.
561 		 */
562 		sendattr = fattr_bogus;
563 	}
564 	error = proto_printf(wr, "f %s %F\n", pathlast(sr->sr_file), sendattr,
565 	    config->fasupport, coll->co_attrignore);
566 	if (error)
567 		return (LISTER_ERR_WRITE);
568 	return (0);
569 }
570