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 <assert.h>
30156230Smux#include <errno.h>
31156230Smux#include <limits.h>
32156230Smux#include <stdio.h>
33156230Smux#include <stdlib.h>
34156230Smux#include <string.h>
35156230Smux
36156230Smux#include "attrstack.h"
37156230Smux#include "config.h"
38156230Smux#include "fattr.h"
39156230Smux#include "globtree.h"
40156230Smux#include "lister.h"
41156230Smux#include "misc.h"
42156230Smux#include "mux.h"
43156230Smux#include "proto.h"
44156230Smux#include "status.h"
45156230Smux#include "stream.h"
46156230Smux
47156230Smux/* Internal error codes. */
48156230Smux#define	LISTER_ERR_WRITE	(-1)	/* Error writing to server. */
49156230Smux#define	LISTER_ERR_STATUS	(-2)	/* Status file error in lstr->errmsg. */
50156230Smux
51156230Smuxstruct lister {
52156230Smux	struct config *config;
53156230Smux	struct stream *wr;
54156230Smux	char *errmsg;
55156230Smux};
56156230Smux
57156230Smuxstatic int	lister_batch(struct lister *);
58156230Smuxstatic int	lister_coll(struct lister *, struct coll *, struct status *);
59156230Smuxstatic int	lister_dodirdown(struct lister *, struct coll *,
60156230Smux		    struct statusrec *, struct attrstack *as);
61156230Smuxstatic int	lister_dodirup(struct lister *, struct coll *,
62156230Smux    		    struct statusrec *, struct attrstack *as);
63156230Smuxstatic int	lister_dofile(struct lister *, struct coll *,
64156230Smux		    struct statusrec *);
65156230Smuxstatic int	lister_dodead(struct lister *, struct coll *,
66156230Smux		    struct statusrec *);
67186781Slulfstatic int	lister_dorcsfile(struct lister *, struct coll *,
68186781Slulf		    struct statusrec *);
69186781Slulfstatic int	lister_dorcsdead(struct lister *, struct coll *,
70186781Slulf		    struct statusrec *);
71156230Smux
72156230Smuxvoid *
73156230Smuxlister(void *arg)
74156230Smux{
75156230Smux	struct thread_args *args;
76156230Smux	struct lister lbuf, *l;
77156230Smux	int error;
78156230Smux
79156230Smux	args = arg;
80156230Smux	l = &lbuf;
81156230Smux	l->config = args->config;
82156230Smux	l->wr = args->wr;
83156230Smux	l->errmsg = NULL;
84156230Smux	error = lister_batch(l);
85156230Smux	switch (error) {
86156230Smux	case LISTER_ERR_WRITE:
87156230Smux		xasprintf(&args->errmsg,
88156230Smux		    "TreeList failed: Network write failure: %s",
89156230Smux		    strerror(errno));
90156230Smux		args->status = STATUS_TRANSIENTFAILURE;
91156230Smux		break;
92156230Smux	case LISTER_ERR_STATUS:
93156230Smux		xasprintf(&args->errmsg,
94156230Smux		    "TreeList failed: %s.  Delete it and try again.",
95156230Smux		    l->errmsg);
96156230Smux		free(l->errmsg);
97156230Smux		args->status = STATUS_FAILURE;
98156230Smux		break;
99156230Smux	default:
100156230Smux		assert(error == 0);
101156230Smux		args->status = STATUS_SUCCESS;
102156230Smux	};
103156230Smux	return (NULL);
104156230Smux}
105156230Smux
106156230Smuxstatic int
107156230Smuxlister_batch(struct lister *l)
108156230Smux{
109156230Smux	struct config *config;
110156230Smux	struct stream *wr;
111156230Smux	struct status *st;
112156230Smux	struct coll *coll;
113156230Smux	int error;
114156230Smux
115156230Smux	config = l->config;
116156230Smux	wr = l->wr;
117156230Smux	STAILQ_FOREACH(coll, &config->colls, co_next) {
118156230Smux		if (coll->co_options & CO_SKIP)
119156230Smux			continue;
120156230Smux		st = status_open(coll, -1, &l->errmsg);
121156230Smux		if (st == NULL)
122156230Smux			return (LISTER_ERR_STATUS);
123156230Smux		error = proto_printf(wr, "COLL %s %s\n", coll->co_name,
124156230Smux		    coll->co_release);
125156230Smux		if (error)
126156230Smux			return (LISTER_ERR_WRITE);
127156230Smux		stream_flush(wr);
128156230Smux		if (coll->co_options & CO_COMPRESS)
129156230Smux			stream_filter_start(wr, STREAM_FILTER_ZLIB, NULL);
130156230Smux		error = lister_coll(l, coll, st);
131156230Smux		status_close(st, NULL);
132156230Smux		if (error)
133156230Smux			return (error);
134156230Smux		if (coll->co_options & CO_COMPRESS)
135156230Smux			stream_filter_stop(wr);
136156230Smux		stream_flush(wr);
137156230Smux	}
138156230Smux	error = proto_printf(wr, ".\n");
139156230Smux	if (error)
140156230Smux		return (LISTER_ERR_WRITE);
141156230Smux	return (0);
142156230Smux}
143156230Smux
144156230Smux/* List a single collection based on the status file. */
145156230Smuxstatic int
146156230Smuxlister_coll(struct lister *l, struct coll *coll, struct status *st)
147156230Smux{
148156230Smux	struct stream *wr;
149156230Smux	struct attrstack *as;
150156230Smux	struct statusrec *sr;
151156230Smux	struct fattr *fa;
152156701Smux	size_t i;
153156230Smux	int depth, error, ret, prunedepth;
154156230Smux
155156230Smux	wr = l->wr;
156156230Smux	depth = 0;
157156230Smux	prunedepth = INT_MAX;
158156230Smux	as = attrstack_new();
159156230Smux	while ((ret = status_get(st, NULL, 0, 0, &sr)) == 1) {
160156230Smux		switch (sr->sr_type) {
161156230Smux		case SR_DIRDOWN:
162156230Smux			depth++;
163156230Smux			if (depth < prunedepth) {
164156230Smux				error = lister_dodirdown(l, coll, sr, as);
165156230Smux				if (error < 0)
166156230Smux					goto bad;
167156230Smux				if (error)
168156230Smux					prunedepth = depth;
169156230Smux			}
170156230Smux			break;
171156230Smux		case SR_DIRUP:
172156230Smux			if (depth < prunedepth) {
173156230Smux				error = lister_dodirup(l, coll, sr, as);
174156230Smux				if (error)
175156230Smux					goto bad;
176156230Smux			} else if (depth == prunedepth) {
177156230Smux				/* Finished pruning. */
178156230Smux				prunedepth = INT_MAX;
179156230Smux			}
180156230Smux			depth--;
181156230Smux			continue;
182156230Smux		case SR_CHECKOUTLIVE:
183156230Smux			if (depth < prunedepth) {
184156230Smux				error = lister_dofile(l, coll, sr);
185156230Smux				if (error)
186156230Smux					goto bad;
187156230Smux			}
188156230Smux			break;
189156230Smux		case SR_CHECKOUTDEAD:
190156230Smux			if (depth < prunedepth) {
191156230Smux				error = lister_dodead(l, coll, sr);
192156230Smux				if (error)
193156230Smux					goto bad;
194156230Smux			}
195156230Smux			break;
196186781Slulf		case SR_FILEDEAD:
197186781Slulf			if (depth < prunedepth) {
198186781Slulf				if (!(coll->co_options & CO_CHECKOUTMODE)) {
199186781Slulf					error = lister_dorcsdead(l, coll, sr);
200186781Slulf					if (error)
201186781Slulf						goto bad;
202186781Slulf				}
203186781Slulf			}
204186781Slulf			break;
205186781Slulf		case SR_FILELIVE:
206186781Slulf			if (depth < prunedepth) {
207186781Slulf				if (!(coll->co_options & CO_CHECKOUTMODE)) {
208186781Slulf					error = lister_dorcsfile(l, coll, sr);
209186781Slulf					if (error)
210186781Slulf						goto bad;
211186781Slulf				}
212186781Slulf			}
213186781Slulf			break;
214156230Smux		}
215156230Smux	}
216156230Smux	if (ret == -1) {
217156230Smux		l->errmsg = status_errmsg(st);
218156230Smux		error = LISTER_ERR_STATUS;
219156230Smux		goto bad;
220156230Smux	}
221156230Smux	assert(status_eof(st));
222156230Smux	assert(depth == 0);
223156230Smux	error = proto_printf(wr, ".\n");
224156230Smux	attrstack_free(as);
225156230Smux	if (error)
226156230Smux		return (LISTER_ERR_WRITE);
227156230Smux	return (0);
228156230Smuxbad:
229156701Smux	for (i = 0; i < attrstack_size(as); i++) {
230156230Smux		fa = attrstack_pop(as);
231156230Smux		fattr_free(fa);
232156230Smux	}
233156230Smux	attrstack_free(as);
234156230Smux	return (error);
235156230Smux}
236156230Smux
237156230Smux/* Handle a directory up entry found in the status file. */
238156230Smuxstatic int
239156230Smuxlister_dodirdown(struct lister *l, struct coll *coll, struct statusrec *sr,
240156230Smux    struct attrstack *as)
241156230Smux{
242156230Smux	struct config *config;
243156230Smux	struct stream *wr;
244156230Smux	struct fattr *fa, *fa2;
245156230Smux	char *path;
246156230Smux	int error;
247156230Smux
248156230Smux	config = l->config;
249156230Smux	wr = l->wr;
250156230Smux	if (!globtree_test(coll->co_dirfilter, sr->sr_file))
251156230Smux		return (1);
252156230Smux	if (coll->co_options & CO_TRUSTSTATUSFILE) {
253156230Smux		fa = fattr_new(FT_DIRECTORY, -1);
254156230Smux	} else {
255156230Smux		xasprintf(&path, "%s/%s", coll->co_prefix, sr->sr_file);
256156230Smux		fa = fattr_frompath(path, FATTR_NOFOLLOW);
257156230Smux		if (fa == NULL) {
258156230Smux			/* The directory doesn't exist, prune
259156230Smux			 * everything below it. */
260156230Smux			free(path);
261156230Smux			return (1);
262156230Smux		}
263156230Smux		if (fattr_type(fa) == FT_SYMLINK) {
264156230Smux			fa2 = fattr_frompath(path, FATTR_FOLLOW);
265156230Smux			if (fa2 != NULL && fattr_type(fa2) == FT_DIRECTORY) {
266156230Smux				/* XXX - When not in checkout mode, CVSup warns
267156230Smux				 * here about the file being a symlink to a
268156230Smux				 * directory instead of a directory. */
269156230Smux				fattr_free(fa);
270156230Smux				fa = fa2;
271156230Smux			} else {
272156230Smux				fattr_free(fa2);
273156230Smux			}
274156230Smux		}
275156230Smux		free(path);
276156230Smux	}
277156230Smux
278156230Smux	if (fattr_type(fa) != FT_DIRECTORY) {
279156230Smux		fattr_free(fa);
280156230Smux		/* Report it as something bogus so
281156230Smux		 * that it will be replaced. */
282156230Smux		error = proto_printf(wr, "F %s %F\n", pathlast(sr->sr_file),
283156230Smux		    fattr_bogus, config->fasupport, coll->co_attrignore);
284156230Smux		if (error)
285156230Smux			return (LISTER_ERR_WRITE);
286156230Smux		return (1);
287156230Smux	}
288156230Smux
289156230Smux	/* It really is a directory. */
290156230Smux	attrstack_push(as, fa);
291156230Smux	error = proto_printf(wr, "D %s\n", pathlast(sr->sr_file));
292156230Smux	if (error)
293156230Smux		return (LISTER_ERR_WRITE);
294156230Smux	return (0);
295156230Smux}
296156230Smux
297156230Smux/* Handle a directory up entry found in the status file. */
298156230Smuxstatic int
299156230Smuxlister_dodirup(struct lister *l, struct coll *coll, struct statusrec *sr,
300156230Smux    struct attrstack *as)
301156230Smux{
302156230Smux	struct config *config;
303156230Smux	const struct fattr *sendattr;
304156230Smux	struct stream *wr;
305156230Smux	struct fattr *fa, *fa2;
306156230Smux	int error;
307156230Smux
308156230Smux	config = l->config;
309156230Smux	wr = l->wr;
310156230Smux	fa = attrstack_pop(as);
311156230Smux	if (coll->co_options & CO_TRUSTSTATUSFILE) {
312156230Smux		fattr_free(fa);
313156230Smux		fa = sr->sr_clientattr;
314156230Smux	}
315156230Smux
316156230Smux	fa2 = sr->sr_clientattr;
317156230Smux	if (fattr_equal(fa, fa2))
318156230Smux		sendattr = fa;
319156230Smux	else
320156230Smux		sendattr = fattr_bogus;
321156230Smux	error = proto_printf(wr, "U %F\n", sendattr, config->fasupport,
322156230Smux	    coll->co_attrignore);
323156230Smux	if (error)
324156230Smux		return (LISTER_ERR_WRITE);
325156230Smux	if (!(coll->co_options & CO_TRUSTSTATUSFILE))
326156230Smux		fattr_free(fa);
327156230Smux	/* XXX CVSup flushes here for some reason with a comment saying
328156230Smux	   "Be smarter".  We don't flush when listing other file types. */
329156230Smux	stream_flush(wr);
330156230Smux	return (0);
331156230Smux}
332156230Smux
333156230Smux/* Handle a checkout live entry found in the status file. */
334156230Smuxstatic int
335156230Smuxlister_dofile(struct lister *l, struct coll *coll, struct statusrec *sr)
336156230Smux{
337156230Smux	struct config *config;
338156230Smux	struct stream *wr;
339156230Smux	const struct fattr *sendattr, *fa;
340156230Smux	struct fattr *fa2, *rfa;
341156230Smux	char *path, *spath;
342156230Smux	int error;
343156230Smux
344156230Smux	if (!globtree_test(coll->co_filefilter, sr->sr_file))
345156230Smux		return (0);
346156230Smux	config = l->config;
347156230Smux	wr = l->wr;
348156230Smux	rfa = NULL;
349156230Smux	sendattr = NULL;
350156230Smux	error = 0;
351156230Smux	if (!(coll->co_options & CO_TRUSTSTATUSFILE)) {
352156230Smux		path = checkoutpath(coll->co_prefix, sr->sr_file);
353156230Smux		if (path == NULL) {
354156230Smux			spath = coll_statuspath(coll);
355156230Smux			xasprintf(&l->errmsg, "Error in \"%s\": "
356156230Smux			    "Invalid filename \"%s\"", spath, sr->sr_file);
357156230Smux			free(spath);
358156230Smux			return (LISTER_ERR_STATUS);
359156230Smux		}
360156230Smux		rfa = fattr_frompath(path, FATTR_NOFOLLOW);
361156230Smux		free(path);
362156230Smux		if (rfa == NULL) {
363156230Smux			/*
364156230Smux			 * According to the checkouts file we should have
365156230Smux			 * this file but we don't.  Maybe the user deleted
366156230Smux			 * the file, or maybe the checkouts file is wrong.
367156230Smux			 * List the file with bogus attributes to cause the
368156230Smux			 * server to get things back in sync again.
369156230Smux			 */
370156230Smux			sendattr = fattr_bogus;
371156230Smux			goto send;
372156230Smux		}
373156230Smux		fa = rfa;
374156230Smux	} else {
375156230Smux		fa = sr->sr_clientattr;
376156230Smux	}
377156230Smux	fa2 = fattr_forcheckout(sr->sr_serverattr, coll->co_umask);
378156230Smux	if (!fattr_equal(fa, sr->sr_clientattr) || !fattr_equal(fa, fa2) ||
379156230Smux	    strcmp(coll->co_tag, sr->sr_tag) != 0 ||
380156230Smux	    strcmp(coll->co_date, sr->sr_date) != 0) {
381156230Smux		/*
382156230Smux		 * The file corresponds to the information we have
383156230Smux		 * recorded about it, and its moded is correct for
384156230Smux		 * the requested umask setting.
385156230Smux		 */
386156230Smux		sendattr = fattr_bogus;
387156230Smux	} else {
388156230Smux		/*
389156230Smux		 * Either the file has been touched, or we are asking
390156230Smux		 * for a different revision than the one we recorded
391156230Smux		 * information about, or its mode isn't right (because
392156230Smux		 * it was last updated using a version of CVSup that
393156230Smux		 * wasn't so strict about modes).
394156230Smux		 */
395156230Smux		sendattr = sr->sr_serverattr;
396156230Smux	}
397156230Smux	fattr_free(fa2);
398156230Smux	if (rfa != NULL)
399156230Smux		fattr_free(rfa);
400156230Smuxsend:
401156230Smux	error = proto_printf(wr, "F %s %F\n", pathlast(sr->sr_file), sendattr,
402156230Smux	    config->fasupport, coll->co_attrignore);
403156230Smux	if (error)
404156230Smux		return (LISTER_ERR_WRITE);
405156230Smux	return (0);
406156230Smux}
407156230Smux
408186781Slulf/* Handle a rcs file live entry found in the status file. */
409186781Slulfstatic int
410186781Slulflister_dorcsfile(struct lister *l, struct coll *coll, struct statusrec *sr)
411186781Slulf{
412186781Slulf	struct config *config;
413186781Slulf	struct stream *wr;
414186781Slulf	const struct fattr *sendattr;
415186781Slulf	struct fattr *fa;
416186781Slulf	char *path, *spath;
417186781Slulf	size_t len;
418186781Slulf	int error;
419186781Slulf
420186781Slulf	if (!globtree_test(coll->co_filefilter, sr->sr_file))
421186781Slulf		return (0);
422186781Slulf	config = l->config;
423186781Slulf	wr = l->wr;
424186781Slulf	if (!(coll->co_options & CO_TRUSTSTATUSFILE)) {
425186781Slulf		path = cvspath(coll->co_prefix, sr->sr_file, 0);
426186781Slulf		if (path == NULL) {
427186781Slulf			spath = coll_statuspath(coll);
428186781Slulf			xasprintf(&l->errmsg, "Error in \"%s\": "
429186781Slulf			    "Invalid filename \"%s\"", spath, sr->sr_file);
430186781Slulf			free(spath);
431186781Slulf			return (LISTER_ERR_STATUS);
432186781Slulf		}
433186781Slulf		fa = fattr_frompath(path, FATTR_NOFOLLOW);
434186781Slulf		free(path);
435186781Slulf	} else
436186781Slulf		fa = sr->sr_clientattr;
437186781Slulf	if (fa != NULL && fattr_equal(fa, sr->sr_clientattr)) {
438186781Slulf		/*
439186781Slulf		 * If the file is an RCS file, we use "loose" equality, so sizes
440186781Slulf		 * may disagress because of differences in whitespace.
441186781Slulf		 */
442186781Slulf		if (isrcs(sr->sr_file, &len) &&
443186781Slulf		    !(coll->co_options & CO_NORCS) &&
444186781Slulf		    !(coll->co_options & CO_STRICTCHECKRCS)) {
445186781Slulf			fattr_maskout(fa, FA_SIZE);
446186781Slulf		}
447186781Slulf		sendattr = fa;
448186781Slulf	} else {
449186781Slulf		/*
450186781Slulf		 * If different, the user may have changed it, so we report
451186781Slulf		 * bogus attributes to force a full comparison.
452186781Slulf		 */
453186781Slulf		sendattr = fattr_bogus;
454186781Slulf	}
455186781Slulf	error = proto_printf(wr, "F %s %F\n", pathlast(sr->sr_file), sendattr,
456186781Slulf	    config->fasupport, coll->co_attrignore);
457186781Slulf	if (error)
458186781Slulf		return (LISTER_ERR_WRITE);
459186781Slulf	return (0);
460186781Slulf}
461186781Slulf
462156230Smux/* Handle a checkout dead entry found in the status file. */
463156230Smuxstatic int
464156230Smuxlister_dodead(struct lister *l, struct coll *coll, struct statusrec *sr)
465156230Smux{
466156230Smux	struct config *config;
467156230Smux	struct stream *wr;
468156230Smux	const struct fattr *sendattr;
469156230Smux	struct fattr *fa;
470156230Smux	char *path, *spath;
471156230Smux	int error;
472156230Smux
473156230Smux	if (!globtree_test(coll->co_filefilter, sr->sr_file))
474156230Smux		return (0);
475156230Smux	config = l->config;
476156230Smux	wr = l->wr;
477156230Smux	if (!(coll->co_options & CO_TRUSTSTATUSFILE)) {
478156230Smux		path = checkoutpath(coll->co_prefix, sr->sr_file);
479156230Smux		if (path == NULL) {
480156230Smux			spath = coll_statuspath(coll);
481156230Smux			xasprintf(&l->errmsg, "Error in \"%s\": "
482156230Smux			    "Invalid filename \"%s\"", spath, sr->sr_file);
483156230Smux			free(spath);
484156230Smux			return (LISTER_ERR_STATUS);
485156230Smux		}
486156230Smux		fa = fattr_frompath(path, FATTR_NOFOLLOW);
487156230Smux		free(path);
488156230Smux		if (fa != NULL && fattr_type(fa) != FT_DIRECTORY) {
489156230Smux			/*
490156230Smux			 * We shouldn't have this file but we do.  Report
491156230Smux			 * it to the server, which will either send a
492156230Smux			 * deletion request, of (if the file has come alive)
493156230Smux			 * sent the correct version.
494156230Smux			 */
495156230Smux			fattr_free(fa);
496156230Smux			error = proto_printf(wr, "F %s %F\n",
497156230Smux			    pathlast(sr->sr_file), fattr_bogus,
498156230Smux			    config->fasupport, coll->co_attrignore);
499156230Smux			if (error)
500156230Smux				return (LISTER_ERR_WRITE);
501156230Smux			return (0);
502156230Smux		}
503156230Smux		fattr_free(fa);
504156230Smux	}
505156230Smux	if (strcmp(coll->co_tag, sr->sr_tag) != 0 ||
506156230Smux	    strcmp(coll->co_date, sr->sr_date) != 0)
507156230Smux		sendattr = fattr_bogus;
508156230Smux	else
509156230Smux		sendattr = sr->sr_serverattr;
510156230Smux	error = proto_printf(wr, "f %s %F\n", pathlast(sr->sr_file), sendattr,
511156230Smux	    config->fasupport, coll->co_attrignore);
512156230Smux	if (error)
513156230Smux		return (LISTER_ERR_WRITE);
514156230Smux	return (0);
515156230Smux}
516186781Slulf
517186781Slulf/* Handle a rcs file dead entry found in the status file. */
518186781Slulfstatic int
519186781Slulflister_dorcsdead(struct lister *l, struct coll *coll, struct statusrec *sr)
520186781Slulf{
521186781Slulf	struct config *config;
522186781Slulf	struct stream *wr;
523186781Slulf	const struct fattr *sendattr;
524186781Slulf	struct fattr *fa;
525186781Slulf	char *path, *spath;
526186781Slulf	size_t len;
527186781Slulf	int error;
528186781Slulf
529186781Slulf	if (!globtree_test(coll->co_filefilter, sr->sr_file))
530186781Slulf		return (0);
531186781Slulf	config = l->config;
532186781Slulf	wr = l->wr;
533241835Seadler	if (!(coll->co_options & CO_TRUSTSTATUSFILE)) {
534186781Slulf		path = cvspath(coll->co_prefix, sr->sr_file, 1);
535186781Slulf		if (path == NULL) {
536186781Slulf			spath = coll_statuspath(coll);
537186781Slulf			xasprintf(&l->errmsg, "Error in \"%s\": "
538186781Slulf			    "Invalid filename \"%s\"", spath, sr->sr_file);
539186781Slulf			free(spath);
540186781Slulf			return (LISTER_ERR_STATUS);
541186781Slulf		}
542186781Slulf		fa = fattr_frompath(path, FATTR_NOFOLLOW);
543186781Slulf		free(path);
544186781Slulf	} else
545186781Slulf		fa = sr->sr_clientattr;
546186781Slulf	if (fattr_equal(fa, sr->sr_clientattr)) {
547186781Slulf		/*
548186781Slulf		 * If the file is an RCS file, we use "loose" equality, so sizes
549186781Slulf		 * may disagress because of differences in whitespace.
550186781Slulf		 */
551186781Slulf		if (isrcs(sr->sr_file, &len) &&
552186781Slulf		    !(coll->co_options & CO_NORCS) &&
553186781Slulf		    !(coll->co_options & CO_STRICTCHECKRCS)) {
554186781Slulf			fattr_maskout(fa, FA_SIZE);
555186781Slulf		}
556186781Slulf		sendattr = fa;
557186781Slulf	} else {
558186781Slulf		/*
559186781Slulf		 * If different, the user may have changed it, so we report
560186781Slulf		 * bogus attributes to force a full comparison.
561186781Slulf		 */
562186781Slulf		sendattr = fattr_bogus;
563186781Slulf	}
564186781Slulf	error = proto_printf(wr, "f %s %F\n", pathlast(sr->sr_file), sendattr,
565186781Slulf	    config->fasupport, coll->co_attrignore);
566186781Slulf	if (error)
567186781Slulf		return (LISTER_ERR_WRITE);
568186781Slulf	return (0);
569186781Slulf}
570