detailer.c revision 156230
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: vendor/csup/dist/contrib/csup/detailer.c 156230 2006-03-03 04:11:29Z mux $
27156230Smux */
28156230Smux
29156230Smux#include <assert.h>
30156230Smux#include <errno.h>
31156230Smux#include <stdlib.h>
32156230Smux#include <string.h>
33156230Smux
34156230Smux#include "config.h"
35156230Smux#include "detailer.h"
36156230Smux#include "fixups.h"
37156230Smux#include "misc.h"
38156230Smux#include "mux.h"
39156230Smux#include "proto.h"
40156230Smux#include "status.h"
41156230Smux#include "stream.h"
42156230Smux
43156230Smux/* Internal error codes. */
44156230Smux#define	DETAILER_ERR_PROTO	(-1)	/* Protocol error. */
45156230Smux#define	DETAILER_ERR_MSG	(-2)	/* Error is in detailer->errmsg. */
46156230Smux#define	DETAILER_ERR_READ	(-3)	/* Error reading from server. */
47156230Smux#define	DETAILER_ERR_WRITE	(-4)	/* Error writing to server. */
48156230Smux
49156230Smuxstruct detailer {
50156230Smux	struct config *config;
51156230Smux	struct stream *rd;
52156230Smux	struct stream *wr;
53156230Smux	char *errmsg;
54156230Smux};
55156230Smux
56156230Smuxstatic int	detailer_batch(struct detailer *);
57156230Smuxstatic int	detailer_coll(struct detailer *, struct coll *,
58156230Smux		    struct status *);
59156230Smuxstatic int	detailer_dofile(struct detailer *, struct coll *,
60156230Smux		    struct status *, char *);
61156230Smux
62156230Smuxvoid *
63156230Smuxdetailer(void *arg)
64156230Smux{
65156230Smux	struct thread_args *args;
66156230Smux	struct detailer dbuf, *d;
67156230Smux	int error;
68156230Smux
69156230Smux	args = arg;
70156230Smux
71156230Smux	d = &dbuf;
72156230Smux	d->config = args->config;
73156230Smux	d->rd = args->rd;
74156230Smux	d->wr = args->wr;
75156230Smux	d->errmsg = NULL;
76156230Smux
77156230Smux	error = detailer_batch(d);
78156230Smux	switch (error) {
79156230Smux	case DETAILER_ERR_PROTO:
80156230Smux		xasprintf(&args->errmsg, "Detailer failed: Protocol error");
81156230Smux		args->status = STATUS_FAILURE;
82156230Smux		break;
83156230Smux	case DETAILER_ERR_MSG:
84156230Smux		xasprintf(&args->errmsg, "Detailer failed: %s", d->errmsg);
85156230Smux		free(d->errmsg);
86156230Smux		args->status = STATUS_FAILURE;
87156230Smux		break;
88156230Smux	case DETAILER_ERR_READ:
89156230Smux		if (stream_eof(d->rd)) {
90156230Smux			xasprintf(&args->errmsg, "Detailer failed: "
91156230Smux			    "Premature EOF from server");
92156230Smux		} else {
93156230Smux			xasprintf(&args->errmsg, "Detailer failed: "
94156230Smux			    "Network read failure: %s", strerror(errno));
95156230Smux		}
96156230Smux		args->status = STATUS_TRANSIENTFAILURE;
97156230Smux		break;
98156230Smux	case DETAILER_ERR_WRITE:
99156230Smux		xasprintf(&args->errmsg, "Detailer failed: "
100156230Smux		    "Network write failure: %s", strerror(errno));
101156230Smux		args->status = STATUS_TRANSIENTFAILURE;
102156230Smux		break;
103156230Smux	default:
104156230Smux		assert(error == 0);
105156230Smux		args->status = STATUS_SUCCESS;
106156230Smux	}
107156230Smux	return (NULL);
108156230Smux}
109156230Smux
110156230Smuxstatic int
111156230Smuxdetailer_batch(struct detailer *d)
112156230Smux{
113156230Smux	struct config *config;
114156230Smux	struct stream *rd, *wr;
115156230Smux	struct coll *coll;
116156230Smux	struct status *st;
117156230Smux	struct fixup *fixup;
118156230Smux	char *cmd, *collname, *line, *release;
119156230Smux	int error, fixupseof;
120156230Smux
121156230Smux	config = d->config;
122156230Smux	rd = d->rd;
123156230Smux	wr = d->wr;
124156230Smux	STAILQ_FOREACH(coll, &config->colls, co_next) {
125156230Smux		if (coll->co_options & CO_SKIP)
126156230Smux			continue;
127156230Smux		line = stream_getln(rd, NULL);
128156230Smux		cmd = proto_get_ascii(&line);
129156230Smux		collname = proto_get_ascii(&line);
130156230Smux		release = proto_get_ascii(&line);
131156230Smux		error = proto_get_time(&line, &coll->co_scantime);
132156230Smux		if (error || line != NULL || strcmp(cmd, "COLL") != 0 ||
133156230Smux		    strcmp(collname, coll->co_name) != 0 ||
134156230Smux		    strcmp(release, coll->co_release) != 0)
135156230Smux			return (DETAILER_ERR_PROTO);
136156230Smux		error = proto_printf(wr, "COLL %s %s\n", coll->co_name,
137156230Smux		    coll->co_release);
138156230Smux		if (error)
139156230Smux			return (DETAILER_ERR_WRITE);
140156230Smux		stream_flush(wr);
141156230Smux		if (coll->co_options & CO_COMPRESS) {
142156230Smux			stream_filter_start(rd, STREAM_FILTER_ZLIB, NULL);
143156230Smux			stream_filter_start(wr, STREAM_FILTER_ZLIB, NULL);
144156230Smux		}
145156230Smux		st = status_open(coll, -1, &d->errmsg);
146156230Smux		if (st == NULL)
147156230Smux			return (DETAILER_ERR_MSG);
148156230Smux		error = detailer_coll(d, coll, st);
149156230Smux		status_close(st, NULL);
150156230Smux		if (error)
151156230Smux			return (error);
152156230Smux		if (coll->co_options & CO_COMPRESS) {
153156230Smux			stream_filter_stop(rd);
154156230Smux			stream_filter_stop(wr);
155156230Smux		}
156156230Smux		stream_flush(wr);
157156230Smux	}
158156230Smux	line = stream_getln(rd, NULL);
159156230Smux	if (line == NULL)
160156230Smux		return (DETAILER_ERR_READ);
161156230Smux	if (strcmp(line, ".") != 0)
162156230Smux		return (DETAILER_ERR_PROTO);
163156230Smux	error = proto_printf(wr, ".\n");
164156230Smux	if (error)
165156230Smux		return (DETAILER_ERR_WRITE);
166156230Smux	stream_flush(wr);
167156230Smux
168156230Smux	/* Now send fixups if needed. */
169156230Smux	fixup = NULL;
170156230Smux	fixupseof = 0;
171156230Smux	STAILQ_FOREACH(coll, &config->colls, co_next) {
172156230Smux		if (coll->co_options & CO_SKIP)
173156230Smux			continue;
174156230Smux		error = proto_printf(wr, "COLL %s %s\n", coll->co_name,
175156230Smux		    coll->co_release);
176156230Smux		if (error)
177156230Smux			return (DETAILER_ERR_WRITE);
178156230Smux		if (coll->co_options & CO_COMPRESS)
179156230Smux			stream_filter_start(wr, STREAM_FILTER_ZLIB, NULL);
180156230Smux		while (!fixupseof) {
181156230Smux			if (fixup == NULL)
182156230Smux				fixup = fixups_get(config->fixups);
183156230Smux			if (fixup == NULL) {
184156230Smux				fixupseof = 1;
185156230Smux				break;
186156230Smux			}
187156230Smux			if (fixup->f_coll != coll)
188156230Smux				break;
189156230Smux			error = proto_printf(wr, "Y %s %s %s\n", fixup->f_name,
190156230Smux			    coll->co_tag, coll->co_date);
191156230Smux			if (error)
192156230Smux				return (DETAILER_ERR_WRITE);
193156230Smux			fixup = NULL;
194156230Smux		}
195156230Smux		error = proto_printf(wr, ".\n");
196156230Smux		if (error)
197156230Smux			return (DETAILER_ERR_WRITE);
198156230Smux		if (coll->co_options & CO_COMPRESS)
199156230Smux			stream_filter_stop(wr);
200156230Smux		stream_flush(wr);
201156230Smux	}
202156230Smux	error = proto_printf(wr, ".\n");
203156230Smux	if (error)
204156230Smux		return (DETAILER_ERR_WRITE);
205156230Smux	return (0);
206156230Smux}
207156230Smux
208156230Smuxstatic int
209156230Smuxdetailer_coll(struct detailer *d, struct coll *coll, struct status *st)
210156230Smux{
211156230Smux	struct stream *rd, *wr;
212156230Smux	char *cmd, *file, *line, *msg;
213156230Smux	int error;
214156230Smux
215156230Smux	rd = d->rd;
216156230Smux	wr = d->wr;
217156230Smux	line = stream_getln(rd, NULL);
218156230Smux	if (line == NULL)
219156230Smux		return (DETAILER_ERR_READ);
220156230Smux	while (strcmp(line, ".") != 0) {
221156230Smux		cmd = proto_get_ascii(&line);
222156230Smux		if (cmd == NULL || strlen(cmd) != 1)
223156230Smux			return (DETAILER_ERR_PROTO);
224156230Smux		switch (cmd[0]) {
225156230Smux		case 'D':
226156230Smux			/* Delete file. */
227156230Smux			file = proto_get_ascii(&line);
228156230Smux			if (file == NULL || line != NULL)
229156230Smux				return (DETAILER_ERR_PROTO);
230156230Smux			error = proto_printf(wr, "D %s\n", file);
231156230Smux			if (error)
232156230Smux				return (DETAILER_ERR_WRITE);
233156230Smux			break;
234156230Smux		case 'U':
235156230Smux			/* Add or update file. */
236156230Smux			file = proto_get_ascii(&line);
237156230Smux			if (file == NULL || line != NULL)
238156230Smux				return (DETAILER_ERR_PROTO);
239156230Smux			error = detailer_dofile(d, coll, st, file);
240156230Smux			if (error)
241156230Smux				return (error);
242156230Smux			break;
243156230Smux		case '!':
244156230Smux			/* Warning from server. */
245156230Smux			msg = proto_get_rest(&line);
246156230Smux			if (msg == NULL)
247156230Smux				return (DETAILER_ERR_PROTO);
248156230Smux			lprintf(-1, "Server warning: %s\n", msg);
249156230Smux			break;
250156230Smux		default:
251156230Smux			return (DETAILER_ERR_PROTO);
252156230Smux		}
253156230Smux		stream_flush(wr);
254156230Smux		line = stream_getln(rd, NULL);
255156230Smux		if (line == NULL)
256156230Smux			return (DETAILER_ERR_READ);
257156230Smux	}
258156230Smux	error = proto_printf(wr, ".\n");
259156230Smux	if (error)
260156230Smux		return (DETAILER_ERR_WRITE);
261156230Smux	return (0);
262156230Smux}
263156230Smux
264156230Smuxstatic int
265156230Smuxdetailer_dofile(struct detailer *d, struct coll *coll, struct status *st,
266156230Smux    char *file)
267156230Smux{
268156230Smux	char md5[MD5_DIGEST_SIZE];
269156230Smux	struct stream *wr;
270156230Smux	struct fattr *fa;
271156230Smux	struct statusrec *sr;
272156230Smux	char *path;
273156230Smux	int error, ret;
274156230Smux
275156230Smux	wr = d->wr;
276156230Smux	path = checkoutpath(coll->co_prefix, file);
277156230Smux	if (path == NULL)
278156230Smux		return (DETAILER_ERR_PROTO);
279156230Smux	fa = fattr_frompath(path, FATTR_NOFOLLOW);
280156230Smux	if (fa == NULL) {
281156230Smux		/* We don't have the file, so the only option at this
282156230Smux		   point is to tell the server to send it.  The server
283156230Smux		   may figure out that the file is dead, in which case
284156230Smux		   it will tell us. */
285156230Smux		error = proto_printf(wr, "C %s %s %s\n",
286156230Smux		    file, coll->co_tag, coll->co_date);
287156230Smux		free(path);
288156230Smux		if (error)
289156230Smux			return (DETAILER_ERR_WRITE);
290156230Smux		return (0);
291156230Smux	}
292156230Smux	ret = status_get(st, file, 0, 0, &sr);
293156230Smux	if (ret == -1) {
294156230Smux		d->errmsg = status_errmsg(st);
295156230Smux		free(path);
296156230Smux		return (DETAILER_ERR_MSG);
297156230Smux	}
298156230Smux	if (ret == 0)
299156230Smux		sr = NULL;
300156230Smux
301156230Smux	/* If our recorded information doesn't match the file that the
302156230Smux	   client has, then ignore the recorded information. */
303156230Smux	if (sr != NULL && (sr->sr_type != SR_CHECKOUTLIVE ||
304156230Smux	    !fattr_equal(sr->sr_clientattr, fa)))
305156230Smux		sr = NULL;
306156230Smux	fattr_free(fa);
307156230Smux	if (sr != NULL && strcmp(sr->sr_revdate, ".") != 0) {
308156230Smux		error = proto_printf(wr, "U %s %s %s %s %s\n", file,
309156230Smux		    coll->co_tag, coll->co_date, sr->sr_revnum, sr->sr_revdate);
310156230Smux		free(path);
311156230Smux		if (error)
312156230Smux			return (DETAILER_ERR_WRITE);
313156230Smux		return (0);
314156230Smux	}
315156230Smux
316156230Smux	/*
317156230Smux	 * We don't have complete and/or accurate recorded information
318156230Smux	 * about what version of the file we have.  Compute the file's
319156230Smux	 * checksum as an aid toward identifying which version it is.
320156230Smux	 */
321156230Smux	error = MD5_File(path, md5);
322156230Smux	if (error) {
323156230Smux		xasprintf(&d->errmsg,
324156230Smux		    "Cannot calculate checksum for \"%s\": %s", path,
325156230Smux		    strerror(errno));
326156230Smux		return (DETAILER_ERR_MSG);
327156230Smux	}
328156230Smux	free(path);
329156230Smux	if (sr == NULL) {
330156230Smux		error = proto_printf(wr, "S %s %s %s %s\n", file,
331156230Smux		    coll->co_tag, coll->co_date, md5);
332156230Smux	} else {
333156230Smux		error = proto_printf(wr, "s %s %s %s %s %s\n", file,
334156230Smux		    coll->co_tag, coll->co_date, sr->sr_revnum, md5);
335156230Smux	}
336156230Smux	if (error)
337156230Smux		return (DETAILER_ERR_WRITE);
338156230Smux	return (0);
339156230Smux}
340