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 <stdlib.h>
32156230Smux#include <string.h>
33186781Slulf#include <stdio.h>
34156230Smux
35186781Slulf#include <sys/types.h>
36186781Slulf#include <sys/stat.h>
37186781Slulf#include <unistd.h>
38186781Slulf
39156230Smux#include "config.h"
40156230Smux#include "detailer.h"
41156230Smux#include "fixups.h"
42186781Slulf#include "globtree.h"
43156230Smux#include "misc.h"
44156230Smux#include "mux.h"
45156230Smux#include "proto.h"
46186781Slulf#include "rcsfile.h"
47186781Slulf#include "rsyncfile.h"
48156230Smux#include "status.h"
49156230Smux#include "stream.h"
50156230Smux
51156230Smux/* Internal error codes. */
52156230Smux#define	DETAILER_ERR_PROTO	(-1)	/* Protocol error. */
53156230Smux#define	DETAILER_ERR_MSG	(-2)	/* Error is in detailer->errmsg. */
54156230Smux#define	DETAILER_ERR_READ	(-3)	/* Error reading from server. */
55156230Smux#define	DETAILER_ERR_WRITE	(-4)	/* Error writing to server. */
56156230Smux
57156230Smuxstruct detailer {
58156230Smux	struct config *config;
59156230Smux	struct stream *rd;
60156230Smux	struct stream *wr;
61156230Smux	char *errmsg;
62156230Smux};
63156230Smux
64156230Smuxstatic int	detailer_batch(struct detailer *);
65156230Smuxstatic int	detailer_coll(struct detailer *, struct coll *,
66156230Smux		    struct status *);
67186781Slulfstatic int	detailer_dofile_co(struct detailer *, struct coll *,
68156230Smux		    struct status *, char *);
69186781Slulfstatic int	detailer_dofile_rcs(struct detailer *, struct coll *,
70186781Slulf		    char *, char *);
71186781Slulfstatic int	detailer_dofile_regular(struct detailer *, char *, char *);
72186781Slulfstatic int	detailer_dofile_rsync(struct detailer *, char *, char *);
73186781Slulfstatic int	detailer_checkrcsattr(struct detailer *, struct coll *, char *,
74186781Slulf		    struct fattr *, int);
75186781Slulfint		detailer_send_details(struct detailer *, struct coll *, char *,
76186781Slulf		    char *, struct fattr *);
77156230Smux
78156230Smuxvoid *
79156230Smuxdetailer(void *arg)
80156230Smux{
81156230Smux	struct thread_args *args;
82156230Smux	struct detailer dbuf, *d;
83156230Smux	int error;
84156230Smux
85156230Smux	args = arg;
86156230Smux
87156230Smux	d = &dbuf;
88156230Smux	d->config = args->config;
89156230Smux	d->rd = args->rd;
90156230Smux	d->wr = args->wr;
91156230Smux	d->errmsg = NULL;
92156230Smux
93156230Smux	error = detailer_batch(d);
94156230Smux	switch (error) {
95156230Smux	case DETAILER_ERR_PROTO:
96156230Smux		xasprintf(&args->errmsg, "Detailer failed: Protocol error");
97156230Smux		args->status = STATUS_FAILURE;
98156230Smux		break;
99156230Smux	case DETAILER_ERR_MSG:
100156230Smux		xasprintf(&args->errmsg, "Detailer failed: %s", d->errmsg);
101156230Smux		free(d->errmsg);
102156230Smux		args->status = STATUS_FAILURE;
103156230Smux		break;
104156230Smux	case DETAILER_ERR_READ:
105156230Smux		if (stream_eof(d->rd)) {
106156230Smux			xasprintf(&args->errmsg, "Detailer failed: "
107156230Smux			    "Premature EOF from server");
108156230Smux		} else {
109156230Smux			xasprintf(&args->errmsg, "Detailer failed: "
110156230Smux			    "Network read failure: %s", strerror(errno));
111156230Smux		}
112156230Smux		args->status = STATUS_TRANSIENTFAILURE;
113156230Smux		break;
114156230Smux	case DETAILER_ERR_WRITE:
115156230Smux		xasprintf(&args->errmsg, "Detailer failed: "
116156230Smux		    "Network write failure: %s", strerror(errno));
117156230Smux		args->status = STATUS_TRANSIENTFAILURE;
118156230Smux		break;
119156230Smux	default:
120156230Smux		assert(error == 0);
121156230Smux		args->status = STATUS_SUCCESS;
122156230Smux	}
123156230Smux	return (NULL);
124156230Smux}
125156230Smux
126156230Smuxstatic int
127156230Smuxdetailer_batch(struct detailer *d)
128156230Smux{
129156230Smux	struct config *config;
130156230Smux	struct stream *rd, *wr;
131156230Smux	struct coll *coll;
132156230Smux	struct status *st;
133156230Smux	struct fixup *fixup;
134156230Smux	char *cmd, *collname, *line, *release;
135156230Smux	int error, fixupseof;
136156230Smux
137156230Smux	config = d->config;
138156230Smux	rd = d->rd;
139156230Smux	wr = d->wr;
140156230Smux	STAILQ_FOREACH(coll, &config->colls, co_next) {
141156230Smux		if (coll->co_options & CO_SKIP)
142156230Smux			continue;
143156230Smux		line = stream_getln(rd, NULL);
144156230Smux		cmd = proto_get_ascii(&line);
145156230Smux		collname = proto_get_ascii(&line);
146156230Smux		release = proto_get_ascii(&line);
147156230Smux		error = proto_get_time(&line, &coll->co_scantime);
148156230Smux		if (error || line != NULL || strcmp(cmd, "COLL") != 0 ||
149156230Smux		    strcmp(collname, coll->co_name) != 0 ||
150156230Smux		    strcmp(release, coll->co_release) != 0)
151156230Smux			return (DETAILER_ERR_PROTO);
152156230Smux		error = proto_printf(wr, "COLL %s %s\n", coll->co_name,
153156230Smux		    coll->co_release);
154156230Smux		if (error)
155156230Smux			return (DETAILER_ERR_WRITE);
156156230Smux		stream_flush(wr);
157156230Smux		if (coll->co_options & CO_COMPRESS) {
158156230Smux			stream_filter_start(rd, STREAM_FILTER_ZLIB, NULL);
159156230Smux			stream_filter_start(wr, STREAM_FILTER_ZLIB, NULL);
160156230Smux		}
161156230Smux		st = status_open(coll, -1, &d->errmsg);
162156230Smux		if (st == NULL)
163156230Smux			return (DETAILER_ERR_MSG);
164156230Smux		error = detailer_coll(d, coll, st);
165156230Smux		status_close(st, NULL);
166156230Smux		if (error)
167156230Smux			return (error);
168156230Smux		if (coll->co_options & CO_COMPRESS) {
169156230Smux			stream_filter_stop(rd);
170156230Smux			stream_filter_stop(wr);
171156230Smux		}
172156230Smux		stream_flush(wr);
173156230Smux	}
174156230Smux	line = stream_getln(rd, NULL);
175156230Smux	if (line == NULL)
176156230Smux		return (DETAILER_ERR_READ);
177156230Smux	if (strcmp(line, ".") != 0)
178156230Smux		return (DETAILER_ERR_PROTO);
179156230Smux	error = proto_printf(wr, ".\n");
180156230Smux	if (error)
181156230Smux		return (DETAILER_ERR_WRITE);
182156230Smux	stream_flush(wr);
183156230Smux
184156230Smux	/* Now send fixups if needed. */
185156230Smux	fixup = NULL;
186156230Smux	fixupseof = 0;
187156230Smux	STAILQ_FOREACH(coll, &config->colls, co_next) {
188156230Smux		if (coll->co_options & CO_SKIP)
189156230Smux			continue;
190156230Smux		error = proto_printf(wr, "COLL %s %s\n", coll->co_name,
191156230Smux		    coll->co_release);
192156230Smux		if (error)
193156230Smux			return (DETAILER_ERR_WRITE);
194156230Smux		if (coll->co_options & CO_COMPRESS)
195156230Smux			stream_filter_start(wr, STREAM_FILTER_ZLIB, NULL);
196156230Smux		while (!fixupseof) {
197156230Smux			if (fixup == NULL)
198156230Smux				fixup = fixups_get(config->fixups);
199156230Smux			if (fixup == NULL) {
200156230Smux				fixupseof = 1;
201156230Smux				break;
202156230Smux			}
203156230Smux			if (fixup->f_coll != coll)
204156230Smux				break;
205186781Slulf			if (coll->co_options & CO_CHECKOUTMODE)
206186781Slulf				error = proto_printf(wr, "Y %s %s %s\n",
207186781Slulf				    fixup->f_name, coll->co_tag, coll->co_date);
208186781Slulf			else {
209186781Slulf				error = proto_printf(wr, "A %s\n",
210186781Slulf				    fixup->f_name);
211186781Slulf			}
212156230Smux			if (error)
213156230Smux				return (DETAILER_ERR_WRITE);
214156230Smux			fixup = NULL;
215156230Smux		}
216156230Smux		error = proto_printf(wr, ".\n");
217156230Smux		if (error)
218156230Smux			return (DETAILER_ERR_WRITE);
219156230Smux		if (coll->co_options & CO_COMPRESS)
220156230Smux			stream_filter_stop(wr);
221156230Smux		stream_flush(wr);
222156230Smux	}
223156230Smux	error = proto_printf(wr, ".\n");
224156230Smux	if (error)
225156230Smux		return (DETAILER_ERR_WRITE);
226156230Smux	return (0);
227156230Smux}
228156230Smux
229156230Smuxstatic int
230156230Smuxdetailer_coll(struct detailer *d, struct coll *coll, struct status *st)
231156230Smux{
232186781Slulf	struct fattr *rcsattr;
233156230Smux	struct stream *rd, *wr;
234186781Slulf	char *attr, *cmd, *file, *line, *msg, *path, *target;
235186781Slulf	int error, attic;
236156230Smux
237156230Smux	rd = d->rd;
238156230Smux	wr = d->wr;
239186781Slulf	attic = 0;
240156230Smux	line = stream_getln(rd, NULL);
241156230Smux	if (line == NULL)
242156230Smux		return (DETAILER_ERR_READ);
243156230Smux	while (strcmp(line, ".") != 0) {
244156230Smux		cmd = proto_get_ascii(&line);
245156230Smux		if (cmd == NULL || strlen(cmd) != 1)
246156230Smux			return (DETAILER_ERR_PROTO);
247156230Smux		switch (cmd[0]) {
248156230Smux		case 'D':
249156230Smux			/* Delete file. */
250156230Smux			file = proto_get_ascii(&line);
251156230Smux			if (file == NULL || line != NULL)
252186781Slulf				return (DETAILER_ERR_PROTO);
253156230Smux			error = proto_printf(wr, "D %s\n", file);
254156230Smux			if (error)
255156230Smux				return (DETAILER_ERR_WRITE);
256156230Smux			break;
257186781Slulf		case 'I':
258186781Slulf		case 'i':
259186781Slulf		case 'j':
260186781Slulf			/* Directory operations. */
261186781Slulf			file = proto_get_ascii(&line);
262186781Slulf			if (file == NULL || line != NULL)
263186781Slulf				return (DETAILER_ERR_PROTO);
264186781Slulf			error = proto_printf(wr, "%s %s\n", cmd, file);
265186781Slulf			if (error)
266186781Slulf				return (DETAILER_ERR_WRITE);
267186781Slulf			break;
268186781Slulf		case 'J':
269186781Slulf			/* Set directory attributes. */
270186781Slulf			file = proto_get_ascii(&line);
271186781Slulf			attr = proto_get_ascii(&line);
272186781Slulf			if (file == NULL || line != NULL || attr == NULL)
273186781Slulf				return (DETAILER_ERR_PROTO);
274186781Slulf			error = proto_printf(wr, "%s %s %s\n", cmd, file, attr);
275186781Slulf			if (error)
276186781Slulf				return (DETAILER_ERR_WRITE);
277186781Slulf			break;
278186781Slulf		case 'H':
279186781Slulf		case 'h':
280186781Slulf			/* Create a hard link. */
281186781Slulf			file = proto_get_ascii(&line);
282186781Slulf			target = proto_get_ascii(&line);
283186781Slulf			if (file == NULL || target == NULL)
284186781Slulf				return (DETAILER_ERR_PROTO);
285186781Slulf			error = proto_printf(wr, "%s %s %s\n", cmd, file,
286186781Slulf			    target);
287186781Slulf			break;
288186781Slulf		case 't':
289186781Slulf			file = proto_get_ascii(&line);
290186781Slulf			attr = proto_get_ascii(&line);
291186781Slulf			if (file == NULL || attr == NULL || line != NULL) {
292186781Slulf				return (DETAILER_ERR_PROTO);
293186781Slulf			}
294186781Slulf			rcsattr = fattr_decode(attr);
295186781Slulf			if (rcsattr == NULL) {
296186781Slulf				return (DETAILER_ERR_PROTO);
297186781Slulf			}
298186781Slulf			error = detailer_checkrcsattr(d, coll, file, rcsattr,
299186781Slulf			    1);
300186781Slulf			break;
301186781Slulf
302186781Slulf		case 'T':
303186781Slulf			file = proto_get_ascii(&line);
304186781Slulf			attr = proto_get_ascii(&line);
305186781Slulf			if (file == NULL || attr == NULL || line != NULL)
306186781Slulf				return (DETAILER_ERR_PROTO);
307186781Slulf			rcsattr = fattr_decode(attr);
308186781Slulf			if (rcsattr == NULL)
309186781Slulf				return (DETAILER_ERR_PROTO);
310186781Slulf			error = detailer_checkrcsattr(d, coll, file, rcsattr,
311186781Slulf			    0);
312186781Slulf			break;
313186781Slulf
314156230Smux		case 'U':
315156230Smux			/* Add or update file. */
316156230Smux			file = proto_get_ascii(&line);
317156230Smux			if (file == NULL || line != NULL)
318156230Smux				return (DETAILER_ERR_PROTO);
319186781Slulf			if (coll->co_options & CO_CHECKOUTMODE) {
320186781Slulf				error = detailer_dofile_co(d, coll, st, file);
321186781Slulf			} else {
322186781Slulf				path = cvspath(coll->co_prefix, file, 0);
323186781Slulf				rcsattr = fattr_frompath(path, FATTR_NOFOLLOW);
324186781Slulf				error = detailer_send_details(d, coll, file,
325186781Slulf				    path, rcsattr);
326186781Slulf				if (rcsattr != NULL)
327186781Slulf					fattr_free(rcsattr);
328186781Slulf				free(path);
329186781Slulf			}
330156230Smux			if (error)
331156230Smux				return (error);
332156230Smux			break;
333156230Smux		case '!':
334156230Smux			/* Warning from server. */
335156230Smux			msg = proto_get_rest(&line);
336156230Smux			if (msg == NULL)
337156230Smux				return (DETAILER_ERR_PROTO);
338156230Smux			lprintf(-1, "Server warning: %s\n", msg);
339156230Smux			break;
340156230Smux		default:
341156230Smux			return (DETAILER_ERR_PROTO);
342156230Smux		}
343156230Smux		stream_flush(wr);
344156230Smux		line = stream_getln(rd, NULL);
345156230Smux		if (line == NULL)
346156230Smux			return (DETAILER_ERR_READ);
347156230Smux	}
348156230Smux	error = proto_printf(wr, ".\n");
349156230Smux	if (error)
350156230Smux		return (DETAILER_ERR_WRITE);
351156230Smux	return (0);
352156230Smux}
353156230Smux
354186781Slulf/*
355186781Slulf * Tell the server to update a regular file.
356186781Slulf */
357156230Smuxstatic int
358186781Slulfdetailer_dofile_regular(struct detailer *d, char *name, char *path)
359156230Smux{
360186781Slulf	struct stream *wr;
361186781Slulf	struct stat st;
362156230Smux	char md5[MD5_DIGEST_SIZE];
363186781Slulf	int error;
364186781Slulf
365186781Slulf	wr = d->wr;
366186781Slulf	error = stat(path, &st);
367186781Slulf	/* If we don't have it or it's unaccessible, we want it again. */
368186781Slulf	if (error) {
369186781Slulf		proto_printf(wr, "A %s\n", name);
370186781Slulf		return (0);
371186781Slulf	}
372186781Slulf
373186781Slulf	/* If not, we want the file to be updated. */
374186781Slulf	error = MD5_File(path, md5);
375186781Slulf	if (error) {
376186781Slulf		lprintf(-1, "Error reading \"%s\"\n", name);
377186781Slulf		return (error);
378186781Slulf	}
379186781Slulf	error = proto_printf(wr, "R %s %O %s\n", name, st.st_size, md5);
380186781Slulf	if (error)
381186781Slulf		return (DETAILER_ERR_WRITE);
382186781Slulf	return (0);
383186781Slulf}
384186781Slulf
385186781Slulf/*
386186781Slulf * Tell the server to update a file with the rsync algorithm.
387186781Slulf */
388186781Slulfstatic int
389186781Slulfdetailer_dofile_rsync(struct detailer *d, char *name, char *path)
390186781Slulf{
391156230Smux	struct stream *wr;
392186781Slulf	struct rsyncfile *rf;
393186781Slulf
394186781Slulf	wr = d->wr;
395186781Slulf	rf = rsync_open(path, 0, 1);
396186781Slulf	if (rf == NULL) {
397186781Slulf		/* Fallback if we fail in opening it. */
398186781Slulf		proto_printf(wr, "A %s\n", name);
399186781Slulf		return (0);
400186781Slulf	}
401186781Slulf	proto_printf(wr, "r %s %z %z\n", name, rsync_filesize(rf),
402186781Slulf	    rsync_blocksize(rf));
403186781Slulf	/* Detail the blocks. */
404186781Slulf	while (rsync_nextblock(rf) != 0)
405186781Slulf		proto_printf(wr, "%s %s\n", rsync_rsum(rf), rsync_blockmd5(rf));
406186781Slulf	proto_printf(wr, ".\n");
407186781Slulf	rsync_close(rf);
408186781Slulf	return (0);
409186781Slulf}
410186781Slulf
411186781Slulf/*
412186781Slulf * Tell the server to update an RCS file that we have, or send it if we don't.
413186781Slulf */
414186781Slulfstatic int
415186781Slulfdetailer_dofile_rcs(struct detailer *d, struct coll *coll, char *name,
416186781Slulf    char *path)
417186781Slulf{
418186781Slulf	struct stream *wr;
419156230Smux	struct fattr *fa;
420186781Slulf	struct rcsfile *rf;
421186781Slulf	int error;
422186781Slulf
423186781Slulf	wr = d->wr;
424186781Slulf	path = atticpath(coll->co_prefix, name);
425186781Slulf	fa = fattr_frompath(path, FATTR_NOFOLLOW);
426186781Slulf	if (fa == NULL) {
427186781Slulf		/* We don't have it, so send request to get it. */
428186781Slulf		error = proto_printf(wr, "A %s\n", name);
429186781Slulf		if (error)
430186781Slulf			return (DETAILER_ERR_WRITE);
431186781Slulf		free(path);
432186781Slulf		return (0);
433186781Slulf	}
434186781Slulf
435186781Slulf	rf = rcsfile_frompath(path, name, coll->co_cvsroot, coll->co_tag, 1);
436186781Slulf	free(path);
437186781Slulf	if (rf == NULL) {
438186781Slulf		error = proto_printf(wr, "A %s\n", name);
439186781Slulf		if (error)
440186781Slulf			return (DETAILER_ERR_WRITE);
441186781Slulf		return (0);
442186781Slulf	}
443186781Slulf	/* Tell to update the RCS file. The client version details follow. */
444186781Slulf	rcsfile_send_details(rf, wr);
445186781Slulf	rcsfile_free(rf);
446186781Slulf	fattr_free(fa);
447186781Slulf	return (0);
448186781Slulf}
449186781Slulf
450186781Slulfstatic int
451186781Slulfdetailer_dofile_co(struct detailer *d, struct coll *coll, struct status *st,
452186781Slulf    char *file)
453186781Slulf{
454186781Slulf	struct stream *wr;
455186781Slulf	struct fattr *fa;
456156230Smux	struct statusrec *sr;
457186781Slulf	char md5[MD5_DIGEST_SIZE];
458156230Smux	char *path;
459156230Smux	int error, ret;
460156230Smux
461156230Smux	wr = d->wr;
462156230Smux	path = checkoutpath(coll->co_prefix, file);
463156230Smux	if (path == NULL)
464156230Smux		return (DETAILER_ERR_PROTO);
465156230Smux	fa = fattr_frompath(path, FATTR_NOFOLLOW);
466156230Smux	if (fa == NULL) {
467156230Smux		/* We don't have the file, so the only option at this
468156230Smux		   point is to tell the server to send it.  The server
469156230Smux		   may figure out that the file is dead, in which case
470156230Smux		   it will tell us. */
471156230Smux		error = proto_printf(wr, "C %s %s %s\n",
472156230Smux		    file, coll->co_tag, coll->co_date);
473156230Smux		free(path);
474156230Smux		if (error)
475156230Smux			return (DETAILER_ERR_WRITE);
476156230Smux		return (0);
477156230Smux	}
478156230Smux	ret = status_get(st, file, 0, 0, &sr);
479156230Smux	if (ret == -1) {
480156230Smux		d->errmsg = status_errmsg(st);
481156230Smux		free(path);
482156230Smux		return (DETAILER_ERR_MSG);
483156230Smux	}
484156230Smux	if (ret == 0)
485156230Smux		sr = NULL;
486156230Smux
487156230Smux	/* If our recorded information doesn't match the file that the
488156230Smux	   client has, then ignore the recorded information. */
489156230Smux	if (sr != NULL && (sr->sr_type != SR_CHECKOUTLIVE ||
490156230Smux	    !fattr_equal(sr->sr_clientattr, fa)))
491156230Smux		sr = NULL;
492156230Smux	fattr_free(fa);
493156230Smux	if (sr != NULL && strcmp(sr->sr_revdate, ".") != 0) {
494156230Smux		error = proto_printf(wr, "U %s %s %s %s %s\n", file,
495156230Smux		    coll->co_tag, coll->co_date, sr->sr_revnum, sr->sr_revdate);
496156230Smux		free(path);
497156230Smux		if (error)
498156230Smux			return (DETAILER_ERR_WRITE);
499156230Smux		return (0);
500156230Smux	}
501156230Smux
502156230Smux	/*
503156230Smux	 * We don't have complete and/or accurate recorded information
504156230Smux	 * about what version of the file we have.  Compute the file's
505156230Smux	 * checksum as an aid toward identifying which version it is.
506156230Smux	 */
507156230Smux	error = MD5_File(path, md5);
508156230Smux	if (error) {
509156230Smux		xasprintf(&d->errmsg,
510156230Smux		    "Cannot calculate checksum for \"%s\": %s", path,
511156230Smux		    strerror(errno));
512156230Smux		return (DETAILER_ERR_MSG);
513156230Smux	}
514156230Smux	free(path);
515156230Smux	if (sr == NULL) {
516156230Smux		error = proto_printf(wr, "S %s %s %s %s\n", file,
517156230Smux		    coll->co_tag, coll->co_date, md5);
518156230Smux	} else {
519156230Smux		error = proto_printf(wr, "s %s %s %s %s %s\n", file,
520156230Smux		    coll->co_tag, coll->co_date, sr->sr_revnum, md5);
521156230Smux	}
522156230Smux	if (error)
523156230Smux		return (DETAILER_ERR_WRITE);
524156230Smux	return (0);
525156230Smux}
526186781Slulf
527186781Slulfint
528186781Slulfdetailer_checkrcsattr(struct detailer *d, struct coll *coll, char *name,
529186781Slulf    struct fattr *server_attr, int attic)
530186781Slulf{
531186781Slulf	struct fattr *client_attr;
532186781Slulf	char *attr, *path;
533186781Slulf	int error;
534186781Slulf
535186781Slulf	/*
536186781Slulf	 * I don't think we can use the status file, since it only records file
537186781Slulf	 * attributes in cvsmode.
538186781Slulf	 */
539186781Slulf	client_attr = NULL;
540186781Slulf	path = cvspath(coll->co_prefix, name, attic);
541186781Slulf	if (path == NULL) {
542186781Slulf		return (DETAILER_ERR_PROTO);
543186781Slulf	}
544186781Slulf
545186781Slulf	if (access(path, F_OK) == 0 &&
546186781Slulf	    ((client_attr = fattr_frompath(path, FATTR_NOFOLLOW)) != NULL) &&
547186781Slulf	    fattr_equal(client_attr, server_attr)) {
548186781Slulf		attr = fattr_encode(client_attr, NULL, 0);
549186781Slulf		if (attic) {
550186781Slulf			error = proto_printf(d->wr, "l %s %s\n", name, attr);
551186781Slulf		} else {
552186781Slulf			error = proto_printf(d->wr, "L %s %s\n", name, attr);
553186781Slulf		}
554186781Slulf		free(attr);
555186781Slulf		free(path);
556186781Slulf		fattr_free(client_attr);
557186781Slulf		if (error)
558186781Slulf			return (DETAILER_ERR_WRITE);
559186781Slulf		return (0);
560186781Slulf	}
561186781Slulf	/* We don't have it, so tell the server to send it. */
562186781Slulf	error = detailer_send_details(d, coll, name, path, client_attr);
563186781Slulf	fattr_free(client_attr);
564186781Slulf	free(path);
565186781Slulf	return (error);
566186781Slulf}
567186781Slulf
568186781Slulfint
569186781Slulfdetailer_send_details(struct detailer *d, struct coll *coll, char *name,
570186781Slulf    char *path, struct fattr *fa)
571186781Slulf{
572186781Slulf	int error;
573186781Slulf	size_t len;
574186781Slulf
575186781Slulf       /*
576186781Slulf        * Try to check if the file exists either live or dead to see if we can
577186781Slulf        * edit it and put it live or dead, rather than receiving the entire
578186781Slulf        * file.
579186781Slulf	*/
580186781Slulf	if (fa == NULL) {
581186781Slulf		path = atticpath(coll->co_prefix, name);
582186781Slulf		fa = fattr_frompath(path, FATTR_NOFOLLOW);
583186781Slulf	}
584186781Slulf	if (fa == NULL) {
585186781Slulf		error = proto_printf(d->wr, "A %s\n", name);
586186781Slulf		if (error)
587186781Slulf			return (DETAILER_ERR_WRITE);
588186781Slulf	} else if (fattr_type(fa) == FT_FILE) {
589186781Slulf		if (isrcs(name, &len) && !(coll->co_options & CO_NORCS)) {
590186781Slulf			detailer_dofile_rcs(d, coll, name, path);
591186781Slulf		} else if (!(coll->co_options & CO_NORSYNC) &&
592186781Slulf		    !globtree_test(coll->co_norsync, name)) {
593186781Slulf			detailer_dofile_rsync(d, name, path);
594186781Slulf		} else {
595186781Slulf			detailer_dofile_regular(d, name, path);
596186781Slulf		}
597186781Slulf	} else {
598186781Slulf		error = proto_printf(d->wr, "N %s\n", name);
599186781Slulf		if (error)
600186781Slulf			return (DETAILER_ERR_WRITE);
601186781Slulf	}
602186781Slulf	return (0);
603186781Slulf}
604