detailer.c revision 204556
1279377Simp/*-
2279377Simp * Copyright (c) 2003-2006, Maxime Henrion <mux@FreeBSD.org>
3279377Simp * All rights reserved.
4279377Simp *
5279377Simp * Redistribution and use in source and binary forms, with or without
6279377Simp * modification, are permitted provided that the following conditions
7279377Simp * are met:
8279377Simp * 1. Redistributions of source code must retain the above copyright
9279377Simp *    notice, this list of conditions and the following disclaimer.
10279377Simp * 2. Redistributions in binary form must reproduce the above copyright
11279377Simp *    notice, this list of conditions and the following disclaimer in the
12279377Simp *    documentation and/or other materials provided with the distribution.
13279377Simp *
14279377Simp * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15279377Simp * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16279377Simp * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17279377Simp * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18279377Simp * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19279377Simp * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20279377Simp * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21279377Simp * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22279377Simp * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23279377Simp * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24279377Simp * SUCH DAMAGE.
25279377Simp *
26279377Simp * $FreeBSD: head/usr.bin/csup/detailer.c 204556 2010-03-02 07:26:07Z lulf $
27279377Simp */
28279377Simp
29279377Simp#include <assert.h>
30279377Simp#include <errno.h>
31279377Simp#include <stdlib.h>
32279377Simp#include <string.h>
33279377Simp#include <stdio.h>
34279377Simp
35279377Simp#include <sys/types.h>
36279377Simp#include <sys/stat.h>
37279377Simp#include <unistd.h>
38279377Simp
39279377Simp#include "config.h"
40279377Simp#include "detailer.h"
41279377Simp#include "fixups.h"
42279377Simp#include "globtree.h"
43279377Simp#include "misc.h"
44279377Simp#include "mux.h"
45279377Simp#include "proto.h"
46279377Simp#include "rcsfile.h"
47279377Simp#include "rsyncfile.h"
48279377Simp#include "status.h"
49279377Simp#include "stream.h"
50279377Simp
51279377Simp/* Internal error codes. */
52279377Simp#define	DETAILER_ERR_PROTO	(-1)	/* Protocol error. */
53279377Simp#define	DETAILER_ERR_MSG	(-2)	/* Error is in detailer->errmsg. */
54279377Simp#define	DETAILER_ERR_READ	(-3)	/* Error reading from server. */
55279377Simp#define	DETAILER_ERR_WRITE	(-4)	/* Error writing to server. */
56279377Simp
57279377Simpstruct detailer {
58279377Simp	struct config *config;
59279377Simp	struct stream *rd;
60279377Simp	struct stream *wr;
61279377Simp	char *errmsg;
62279377Simp};
63279377Simp
64279377Simpstatic int	detailer_batch(struct detailer *);
65279377Simpstatic int	detailer_coll(struct detailer *, struct coll *,
66279377Simp		    struct status *);
67279377Simpstatic int	detailer_dofile_co(struct detailer *, struct coll *,
68279377Simp		    struct status *, char *);
69279377Simpstatic int	detailer_dofile_rcs(struct detailer *, struct coll *,
70279377Simp		    char *, char *);
71279377Simpstatic int	detailer_dofile_regular(struct detailer *, char *, char *);
72279377Simpstatic int	detailer_dofile_rsync(struct detailer *, char *, char *);
73279377Simpstatic int	detailer_checkrcsattr(struct detailer *, struct coll *, char *,
74279377Simp		    struct fattr *, int);
75279377Simpint		detailer_send_details(struct detailer *, struct coll *, char *,
76279377Simp		    char *, struct fattr *);
77279377Simp
78279377Simpvoid *
79279377Simpdetailer(void *arg)
80279377Simp{
81279377Simp	struct thread_args *args;
82279377Simp	struct detailer dbuf, *d;
83279377Simp	int error;
84279377Simp
85279377Simp	args = arg;
86279377Simp
87279377Simp	d = &dbuf;
88279377Simp	d->config = args->config;
89279377Simp	d->rd = args->rd;
90279377Simp	d->wr = args->wr;
91279377Simp	d->errmsg = NULL;
92279377Simp
93279377Simp	error = detailer_batch(d);
94279377Simp	switch (error) {
95279377Simp	case DETAILER_ERR_PROTO:
96279377Simp		xasprintf(&args->errmsg, "Detailer failed: Protocol error");
97279377Simp		args->status = STATUS_FAILURE;
98279377Simp		break;
99279377Simp	case DETAILER_ERR_MSG:
100279377Simp		xasprintf(&args->errmsg, "Detailer failed: %s", d->errmsg);
101279377Simp		free(d->errmsg);
102279377Simp		args->status = STATUS_FAILURE;
103279377Simp		break;
104279377Simp	case DETAILER_ERR_READ:
105279377Simp		if (stream_eof(d->rd)) {
106279377Simp			xasprintf(&args->errmsg, "Detailer failed: "
107279377Simp			    "Premature EOF from server");
108279377Simp		} else {
109279377Simp			xasprintf(&args->errmsg, "Detailer failed: "
110279377Simp			    "Network read failure: %s", strerror(errno));
111279377Simp		}
112279377Simp		args->status = STATUS_TRANSIENTFAILURE;
113279377Simp		break;
114279377Simp	case DETAILER_ERR_WRITE:
115279377Simp		xasprintf(&args->errmsg, "Detailer failed: "
116279377Simp		    "Network write failure: %s", strerror(errno));
117279377Simp		args->status = STATUS_TRANSIENTFAILURE;
118279377Simp		break;
119279377Simp	default:
120279377Simp		assert(error == 0);
121279377Simp		args->status = STATUS_SUCCESS;
122279377Simp	}
123279377Simp	return (NULL);
124279377Simp}
125279377Simp
126279377Simpstatic int
127279377Simpdetailer_batch(struct detailer *d)
128279377Simp{
129279377Simp	struct config *config;
130279377Simp	struct stream *rd, *wr;
131279377Simp	struct coll *coll;
132279377Simp	struct status *st;
133279377Simp	struct fixup *fixup;
134279377Simp	char *cmd, *collname, *line, *release;
135279377Simp	int error, fixupseof;
136279377Simp
137279377Simp	config = d->config;
138279377Simp	rd = d->rd;
139279377Simp	wr = d->wr;
140279377Simp	STAILQ_FOREACH(coll, &config->colls, co_next) {
141279377Simp		if (coll->co_options & CO_SKIP)
142279377Simp			continue;
143279377Simp		line = stream_getln(rd, NULL);
144279377Simp		cmd = proto_get_ascii(&line);
145279377Simp		collname = proto_get_ascii(&line);
146279377Simp		release = proto_get_ascii(&line);
147279377Simp		error = proto_get_time(&line, &coll->co_scantime);
148279377Simp		if (error || line != NULL || strcmp(cmd, "COLL") != 0 ||
149279377Simp		    strcmp(collname, coll->co_name) != 0 ||
150279377Simp		    strcmp(release, coll->co_release) != 0)
151279377Simp			return (DETAILER_ERR_PROTO);
152279377Simp		error = proto_printf(wr, "COLL %s %s\n", coll->co_name,
153279377Simp		    coll->co_release);
154279377Simp		if (error)
155279377Simp			return (DETAILER_ERR_WRITE);
156279377Simp		stream_flush(wr);
157279377Simp		if (coll->co_options & CO_COMPRESS) {
158279377Simp			stream_filter_start(rd, STREAM_FILTER_ZLIB, NULL);
159279377Simp			stream_filter_start(wr, STREAM_FILTER_ZLIB, NULL);
160279377Simp		}
161279377Simp		st = status_open(coll, -1, &d->errmsg);
162279377Simp		if (st == NULL)
163279377Simp			return (DETAILER_ERR_MSG);
164279377Simp		error = detailer_coll(d, coll, st);
165279377Simp		status_close(st, NULL);
166279377Simp		if (error)
167279377Simp			return (error);
168279377Simp		if (coll->co_options & CO_COMPRESS) {
169279377Simp			stream_filter_stop(rd);
170279377Simp			stream_filter_stop(wr);
171279377Simp		}
172279377Simp		stream_flush(wr);
173279377Simp	}
174279377Simp	line = stream_getln(rd, NULL);
175279377Simp	if (line == NULL)
176279377Simp		return (DETAILER_ERR_READ);
177279377Simp	if (strcmp(line, ".") != 0)
178279377Simp		return (DETAILER_ERR_PROTO);
179279377Simp	error = proto_printf(wr, ".\n");
180279377Simp	if (error)
181279377Simp		return (DETAILER_ERR_WRITE);
182279377Simp	stream_flush(wr);
183279377Simp
184279377Simp	/* Now send fixups if needed. */
185279377Simp	fixup = NULL;
186279377Simp	fixupseof = 0;
187279377Simp	STAILQ_FOREACH(coll, &config->colls, co_next) {
188279377Simp		if (coll->co_options & CO_SKIP)
189279377Simp			continue;
190279377Simp		error = proto_printf(wr, "COLL %s %s\n", coll->co_name,
191279377Simp		    coll->co_release);
192279377Simp		if (error)
193279377Simp			return (DETAILER_ERR_WRITE);
194279377Simp		if (coll->co_options & CO_COMPRESS)
195279377Simp			stream_filter_start(wr, STREAM_FILTER_ZLIB, NULL);
196279377Simp		while (!fixupseof) {
197279377Simp			if (fixup == NULL)
198279377Simp				fixup = fixups_get(config->fixups);
199279377Simp			if (fixup == NULL) {
200279377Simp				fixupseof = 1;
201279377Simp				break;
202279377Simp			}
203279377Simp			if (fixup->f_coll != coll)
204279377Simp				break;
205279377Simp			if (coll->co_options & CO_CHECKOUTMODE)
206279377Simp				error = proto_printf(wr, "Y %s %s %s\n",
207279377Simp				    fixup->f_name, coll->co_tag, coll->co_date);
208279377Simp			else {
209279377Simp				error = proto_printf(wr, "A %s\n",
210279377Simp				    fixup->f_name);
211279377Simp			}
212279377Simp			if (error)
213279377Simp				return (DETAILER_ERR_WRITE);
214279377Simp			fixup = NULL;
215279377Simp		}
216279377Simp		error = proto_printf(wr, ".\n");
217279377Simp		if (error)
218279377Simp			return (DETAILER_ERR_WRITE);
219279377Simp		if (coll->co_options & CO_COMPRESS)
220279377Simp			stream_filter_stop(wr);
221279377Simp		stream_flush(wr);
222279377Simp	}
223279377Simp	error = proto_printf(wr, ".\n");
224279377Simp	if (error)
225279377Simp		return (DETAILER_ERR_WRITE);
226279377Simp	return (0);
227279377Simp}
228279377Simp
229279377Simpstatic int
230279377Simpdetailer_coll(struct detailer *d, struct coll *coll, struct status *st)
231279377Simp{
232279377Simp	struct fattr *rcsattr;
233279377Simp	struct stream *rd, *wr;
234279377Simp	char *attr, *cmd, *file, *line, *msg, *path, *target;
235279377Simp	int error, attic;
236279377Simp
237279377Simp	rd = d->rd;
238279377Simp	wr = d->wr;
239279377Simp	attic = 0;
240279377Simp	line = stream_getln(rd, NULL);
241279377Simp	if (line == NULL)
242279377Simp		return (DETAILER_ERR_READ);
243279377Simp	while (strcmp(line, ".") != 0) {
244279377Simp		cmd = proto_get_ascii(&line);
245279377Simp		if (cmd == NULL || strlen(cmd) != 1)
246279377Simp			return (DETAILER_ERR_PROTO);
247279377Simp		switch (cmd[0]) {
248279377Simp		case 'D':
249279377Simp			/* Delete file. */
250279377Simp			file = proto_get_ascii(&line);
251279377Simp			if (file == NULL || line != NULL)
252279377Simp				return (DETAILER_ERR_PROTO);
253279377Simp			error = proto_printf(wr, "D %s\n", file);
254279377Simp			if (error)
255279377Simp				return (DETAILER_ERR_WRITE);
256279377Simp			break;
257279377Simp		case 'I':
258279377Simp		case 'i':
259279377Simp		case 'j':
260279377Simp			/* Directory operations. */
261279377Simp			file = proto_get_ascii(&line);
262279377Simp			if (file == NULL || line != NULL)
263279377Simp				return (DETAILER_ERR_PROTO);
264279377Simp			error = proto_printf(wr, "%s %s\n", cmd, file);
265279377Simp			if (error)
266279377Simp				return (DETAILER_ERR_WRITE);
267279377Simp			break;
268279377Simp		case 'J':
269279377Simp			/* Set directory attributes. */
270279377Simp			file = proto_get_ascii(&line);
271279377Simp			attr = proto_get_ascii(&line);
272279377Simp			if (file == NULL || line != NULL || attr == NULL)
273279377Simp				return (DETAILER_ERR_PROTO);
274279377Simp			error = proto_printf(wr, "%s %s %s\n", cmd, file, attr);
275279377Simp			if (error)
276279377Simp				return (DETAILER_ERR_WRITE);
277279377Simp			break;
278279377Simp		case 'H':
279279377Simp		case 'h':
280279377Simp			/* Create a hard link. */
281279377Simp			file = proto_get_ascii(&line);
282279377Simp			target = proto_get_ascii(&line);
283279377Simp			if (file == NULL || target == NULL)
284279377Simp				return (DETAILER_ERR_PROTO);
285279377Simp			error = proto_printf(wr, "%s %s %s\n", cmd, file,
286279377Simp			    target);
287279377Simp			break;
288279377Simp		case 't':
289279377Simp			file = proto_get_ascii(&line);
290279377Simp			attr = proto_get_ascii(&line);
291279377Simp			if (file == NULL || attr == NULL || line != NULL) {
292279377Simp				return (DETAILER_ERR_PROTO);
293279377Simp			}
294279377Simp			rcsattr = fattr_decode(attr);
295279377Simp			if (rcsattr == NULL) {
296279377Simp				return (DETAILER_ERR_PROTO);
297279377Simp			}
298279377Simp			error = detailer_checkrcsattr(d, coll, file, rcsattr,
299279377Simp			    1);
300279377Simp			break;
301279377Simp
302279377Simp		case 'T':
303279377Simp			file = proto_get_ascii(&line);
304279377Simp			attr = proto_get_ascii(&line);
305279377Simp			if (file == NULL || attr == NULL || line != NULL)
306279377Simp				return (DETAILER_ERR_PROTO);
307279377Simp			rcsattr = fattr_decode(attr);
308279377Simp			if (rcsattr == NULL)
309279377Simp				return (DETAILER_ERR_PROTO);
310279377Simp			error = detailer_checkrcsattr(d, coll, file, rcsattr,
311279377Simp			    0);
312279377Simp			break;
313279377Simp
314279377Simp		case 'U':
315279377Simp			/* Add or update file. */
316279377Simp			file = proto_get_ascii(&line);
317279377Simp			if (file == NULL || line != NULL)
318279377Simp				return (DETAILER_ERR_PROTO);
319279377Simp			if (coll->co_options & CO_CHECKOUTMODE) {
320279377Simp				error = detailer_dofile_co(d, coll, st, file);
321279377Simp			} else {
322279377Simp				path = cvspath(coll->co_prefix, file, 0);
323279377Simp				rcsattr = fattr_frompath(path, FATTR_NOFOLLOW);
324279377Simp				error = detailer_send_details(d, coll, file,
325279377Simp				    path, rcsattr);
326279377Simp				if (rcsattr != NULL)
327279377Simp					fattr_free(rcsattr);
328279377Simp				free(path);
329279377Simp			}
330279377Simp			if (error)
331279377Simp				return (error);
332279377Simp			break;
333279377Simp		case '!':
334279377Simp			/* Warning from server. */
335279377Simp			msg = proto_get_rest(&line);
336279377Simp			if (msg == NULL)
337279377Simp				return (DETAILER_ERR_PROTO);
338279377Simp			lprintf(-1, "Server warning: %s\n", msg);
339279377Simp			break;
340279377Simp		default:
341279377Simp			return (DETAILER_ERR_PROTO);
342279377Simp		}
343279377Simp		stream_flush(wr);
344279377Simp		line = stream_getln(rd, NULL);
345279377Simp		if (line == NULL)
346279377Simp			return (DETAILER_ERR_READ);
347279377Simp	}
348279377Simp	error = proto_printf(wr, ".\n");
349279377Simp	if (error)
350279377Simp		return (DETAILER_ERR_WRITE);
351279377Simp	return (0);
352279377Simp}
353279377Simp
354279377Simp/*
355279377Simp * Tell the server to update a regular file.
356279377Simp */
357279377Simpstatic int
358279377Simpdetailer_dofile_regular(struct detailer *d, char *name, char *path)
359279377Simp{
360279377Simp	struct stream *wr;
361279377Simp	struct stat st;
362279377Simp	char md5[MD5_DIGEST_SIZE];
363279377Simp	int error;
364279377Simp
365279377Simp	wr = d->wr;
366279377Simp	error = stat(path, &st);
367279377Simp	/* If we don't have it or it's unaccessible, we want it again. */
368279377Simp	if (error) {
369279377Simp		proto_printf(wr, "A %s\n", name);
370279377Simp		return (0);
371279377Simp	}
372279377Simp
373279377Simp	/* If not, we want the file to be updated. */
374279377Simp	error = MD5_File(path, md5);
375279377Simp	if (error) {
376279377Simp		lprintf(-1, "Error reading \"%s\"\n", name);
377279377Simp		return (error);
378279377Simp	}
379279377Simp	error = proto_printf(wr, "R %s %O %s\n", name, st.st_size, md5);
380279377Simp	if (error)
381279377Simp		return (DETAILER_ERR_WRITE);
382279377Simp	return (0);
383279377Simp}
384279377Simp
385279377Simp/*
386279377Simp * Tell the server to update a file with the rsync algorithm.
387279377Simp */
388279377Simpstatic int
389279377Simpdetailer_dofile_rsync(struct detailer *d, char *name, char *path)
390279377Simp{
391279377Simp	struct stream *wr;
392279377Simp	struct rsyncfile *rf;
393279377Simp
394279377Simp	wr = d->wr;
395279377Simp	rf = rsync_open(path, 0, 1);
396279377Simp	if (rf == NULL) {
397279377Simp		/* Fallback if we fail in opening it. */
398279377Simp		proto_printf(wr, "A %s\n", name);
399279377Simp		return (0);
400279377Simp	}
401279377Simp	proto_printf(wr, "r %s %z %z\n", name, rsync_filesize(rf),
402279377Simp	    rsync_blocksize(rf));
403279377Simp	/* Detail the blocks. */
404279377Simp	while (rsync_nextblock(rf) != 0)
405279377Simp		proto_printf(wr, "%s %s\n", rsync_rsum(rf), rsync_blockmd5(rf));
406279377Simp	proto_printf(wr, ".\n");
407279377Simp	rsync_close(rf);
408279377Simp	return (0);
409279377Simp}
410279377Simp
411279377Simp/*
412279377Simp * Tell the server to update an RCS file that we have, or send it if we don't.
413279377Simp */
414279377Simpstatic int
415279377Simpdetailer_dofile_rcs(struct detailer *d, struct coll *coll, char *name,
416279377Simp    char *path)
417279377Simp{
418279377Simp	struct stream *wr;
419279377Simp	struct fattr *fa;
420279377Simp	struct rcsfile *rf;
421279377Simp	int error;
422279377Simp
423279377Simp	wr = d->wr;
424279377Simp	path = atticpath(coll->co_prefix, name);
425279377Simp	fa = fattr_frompath(path, FATTR_NOFOLLOW);
426279377Simp	if (fa == NULL) {
427279377Simp		/* We don't have it, so send request to get it. */
428279377Simp		error = proto_printf(wr, "A %s\n", name);
429279377Simp		if (error)
430279377Simp			return (DETAILER_ERR_WRITE);
431279377Simp		free(path);
432279377Simp		return (0);
433279377Simp	}
434279377Simp
435279377Simp	rf = rcsfile_frompath(path, name, coll->co_cvsroot, coll->co_tag, 1);
436279377Simp	free(path);
437279377Simp	if (rf == NULL) {
438279377Simp		error = proto_printf(wr, "A %s\n", name);
439279377Simp		if (error)
440279377Simp			return (DETAILER_ERR_WRITE);
441279377Simp		return (0);
442279377Simp	}
443279377Simp	/* Tell to update the RCS file. The client version details follow. */
444279377Simp	rcsfile_send_details(rf, wr);
445279377Simp	rcsfile_free(rf);
446279377Simp	fattr_free(fa);
447279377Simp	return (0);
448279377Simp}
449279377Simp
450279377Simpstatic int
451279377Simpdetailer_dofile_co(struct detailer *d, struct coll *coll, struct status *st,
452279377Simp    char *file)
453279377Simp{
454279377Simp	struct stream *wr;
455279377Simp	struct fattr *fa;
456279377Simp	struct statusrec *sr;
457279377Simp	char md5[MD5_DIGEST_SIZE];
458279377Simp	char *path;
459279377Simp	int error, ret;
460279377Simp
461279377Simp	wr = d->wr;
462279377Simp	path = checkoutpath(coll->co_prefix, file);
463279377Simp	if (path == NULL)
464279377Simp		return (DETAILER_ERR_PROTO);
465279377Simp	fa = fattr_frompath(path, FATTR_NOFOLLOW);
466279377Simp	if (fa == NULL) {
467279377Simp		/* We don't have the file, so the only option at this
468279377Simp		   point is to tell the server to send it.  The server
469279377Simp		   may figure out that the file is dead, in which case
470279377Simp		   it will tell us. */
471279377Simp		error = proto_printf(wr, "C %s %s %s\n",
472279377Simp		    file, coll->co_tag, coll->co_date);
473279377Simp		free(path);
474279377Simp		if (error)
475279377Simp			return (DETAILER_ERR_WRITE);
476279377Simp		return (0);
477279377Simp	}
478279377Simp	ret = status_get(st, file, 0, 0, &sr);
479279377Simp	if (ret == -1) {
480279377Simp		d->errmsg = status_errmsg(st);
481279377Simp		free(path);
482279377Simp		return (DETAILER_ERR_MSG);
483279377Simp	}
484279377Simp	if (ret == 0)
485279377Simp		sr = NULL;
486279377Simp
487279377Simp	/* If our recorded information doesn't match the file that the
488279377Simp	   client has, then ignore the recorded information. */
489279377Simp	if (sr != NULL && (sr->sr_type != SR_CHECKOUTLIVE ||
490279377Simp	    !fattr_equal(sr->sr_clientattr, fa)))
491279377Simp		sr = NULL;
492279377Simp	fattr_free(fa);
493279377Simp	if (sr != NULL && strcmp(sr->sr_revdate, ".") != 0) {
494279377Simp		error = proto_printf(wr, "U %s %s %s %s %s\n", file,
495279377Simp		    coll->co_tag, coll->co_date, sr->sr_revnum, sr->sr_revdate);
496279377Simp		free(path);
497279377Simp		if (error)
498279377Simp			return (DETAILER_ERR_WRITE);
499279377Simp		return (0);
500279377Simp	}
501279377Simp
502279377Simp	/*
503279377Simp	 * We don't have complete and/or accurate recorded information
504279377Simp	 * about what version of the file we have.  Compute the file's
505279377Simp	 * checksum as an aid toward identifying which version it is.
506279377Simp	 */
507279377Simp	error = MD5_File(path, md5);
508279377Simp	if (error) {
509279377Simp		xasprintf(&d->errmsg,
510279377Simp		    "Cannot calculate checksum for \"%s\": %s", path,
511279377Simp		    strerror(errno));
512279377Simp		return (DETAILER_ERR_MSG);
513279377Simp	}
514279377Simp	free(path);
515279377Simp	if (sr == NULL) {
516279377Simp		error = proto_printf(wr, "S %s %s %s %s\n", file,
517279377Simp		    coll->co_tag, coll->co_date, md5);
518279377Simp	} else {
519279377Simp		error = proto_printf(wr, "s %s %s %s %s %s\n", file,
520279377Simp		    coll->co_tag, coll->co_date, sr->sr_revnum, md5);
521279377Simp	}
522279377Simp	if (error)
523279377Simp		return (DETAILER_ERR_WRITE);
524279377Simp	return (0);
525279377Simp}
526279377Simp
527279377Simpint
528279377Simpdetailer_checkrcsattr(struct detailer *d, struct coll *coll, char *name,
529279377Simp    struct fattr *server_attr, int attic)
530279377Simp{
531279377Simp	struct fattr *client_attr;
532279377Simp	char *attr, *path;
533279377Simp	int error;
534279377Simp
535279377Simp	/*
536279377Simp	 * I don't think we can use the status file, since it only records file
537279377Simp	 * attributes in cvsmode.
538279377Simp	 */
539279377Simp	client_attr = NULL;
540279377Simp	path = cvspath(coll->co_prefix, name, attic);
541279377Simp	if (path == NULL) {
542279377Simp		return (DETAILER_ERR_PROTO);
543279377Simp	}
544279377Simp
545279377Simp	if (access(path, F_OK) == 0 &&
546279377Simp	    ((client_attr = fattr_frompath(path, FATTR_NOFOLLOW)) != NULL) &&
547279377Simp	    fattr_equal(client_attr, server_attr)) {
548279377Simp		attr = fattr_encode(client_attr, NULL, 0);
549279377Simp		if (attic) {
550279377Simp			error = proto_printf(d->wr, "l %s %s\n", name, attr);
551279377Simp		} else {
552279377Simp			error = proto_printf(d->wr, "L %s %s\n", name, attr);
553279377Simp		}
554279377Simp		free(attr);
555279377Simp		free(path);
556279377Simp		fattr_free(client_attr);
557279377Simp		if (error)
558279377Simp			return (DETAILER_ERR_WRITE);
559279377Simp		return (0);
560279377Simp	}
561279377Simp	/* We don't have it, so tell the server to send it. */
562279377Simp	error = detailer_send_details(d, coll, name, path, client_attr);
563279377Simp	fattr_free(client_attr);
564279377Simp	free(path);
565279377Simp	return (error);
566279377Simp}
567279377Simp
568279377Simpint
569279377Simpdetailer_send_details(struct detailer *d, struct coll *coll, char *name,
570279377Simp    char *path, struct fattr *fa)
571279377Simp{
572279377Simp	int error;
573279377Simp	size_t len;
574279377Simp
575279377Simp       /*
576279377Simp        * Try to check if the file exists either live or dead to see if we can
577279377Simp        * edit it and put it live or dead, rather than receiving the entire
578279377Simp        * file.
579279377Simp	*/
580279377Simp	if (fa == NULL) {
581279377Simp		path = atticpath(coll->co_prefix, name);
582279377Simp		fa = fattr_frompath(path, FATTR_NOFOLLOW);
583279377Simp	}
584279377Simp	if (fa == NULL) {
585279377Simp		error = proto_printf(d->wr, "A %s\n", name);
586279377Simp		if (error)
587279377Simp			return (DETAILER_ERR_WRITE);
588279377Simp	} else if (fattr_type(fa) == FT_FILE) {
589279377Simp		if (isrcs(name, &len) && !(coll->co_options & CO_NORCS)) {
590279377Simp			detailer_dofile_rcs(d, coll, name, path);
591279377Simp		} else if (!(coll->co_options & CO_NORSYNC) &&
592279377Simp		    !globtree_test(coll->co_norsync, name)) {
593279377Simp			detailer_dofile_rsync(d, name, path);
594279377Simp		} else {
595279377Simp			detailer_dofile_regular(d, name, path);
596279377Simp		}
597279377Simp	} else {
598279377Simp		error = proto_printf(d->wr, "N %s\n", name);
599279377Simp		if (error)
600279377Simp			return (DETAILER_ERR_WRITE);
601279377Simp	}
602279377Simp	return (0);
603279377Simp}
604279377Simp