detailer.c revision 156230
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: vendor/csup/dist/contrib/csup/detailer.c 156230 2006-03-03 04:11:29Z mux $
27 */
28
29#include <assert.h>
30#include <errno.h>
31#include <stdlib.h>
32#include <string.h>
33
34#include "config.h"
35#include "detailer.h"
36#include "fixups.h"
37#include "misc.h"
38#include "mux.h"
39#include "proto.h"
40#include "status.h"
41#include "stream.h"
42
43/* Internal error codes. */
44#define	DETAILER_ERR_PROTO	(-1)	/* Protocol error. */
45#define	DETAILER_ERR_MSG	(-2)	/* Error is in detailer->errmsg. */
46#define	DETAILER_ERR_READ	(-3)	/* Error reading from server. */
47#define	DETAILER_ERR_WRITE	(-4)	/* Error writing to server. */
48
49struct detailer {
50	struct config *config;
51	struct stream *rd;
52	struct stream *wr;
53	char *errmsg;
54};
55
56static int	detailer_batch(struct detailer *);
57static int	detailer_coll(struct detailer *, struct coll *,
58		    struct status *);
59static int	detailer_dofile(struct detailer *, struct coll *,
60		    struct status *, char *);
61
62void *
63detailer(void *arg)
64{
65	struct thread_args *args;
66	struct detailer dbuf, *d;
67	int error;
68
69	args = arg;
70
71	d = &dbuf;
72	d->config = args->config;
73	d->rd = args->rd;
74	d->wr = args->wr;
75	d->errmsg = NULL;
76
77	error = detailer_batch(d);
78	switch (error) {
79	case DETAILER_ERR_PROTO:
80		xasprintf(&args->errmsg, "Detailer failed: Protocol error");
81		args->status = STATUS_FAILURE;
82		break;
83	case DETAILER_ERR_MSG:
84		xasprintf(&args->errmsg, "Detailer failed: %s", d->errmsg);
85		free(d->errmsg);
86		args->status = STATUS_FAILURE;
87		break;
88	case DETAILER_ERR_READ:
89		if (stream_eof(d->rd)) {
90			xasprintf(&args->errmsg, "Detailer failed: "
91			    "Premature EOF from server");
92		} else {
93			xasprintf(&args->errmsg, "Detailer failed: "
94			    "Network read failure: %s", strerror(errno));
95		}
96		args->status = STATUS_TRANSIENTFAILURE;
97		break;
98	case DETAILER_ERR_WRITE:
99		xasprintf(&args->errmsg, "Detailer failed: "
100		    "Network write failure: %s", strerror(errno));
101		args->status = STATUS_TRANSIENTFAILURE;
102		break;
103	default:
104		assert(error == 0);
105		args->status = STATUS_SUCCESS;
106	}
107	return (NULL);
108}
109
110static int
111detailer_batch(struct detailer *d)
112{
113	struct config *config;
114	struct stream *rd, *wr;
115	struct coll *coll;
116	struct status *st;
117	struct fixup *fixup;
118	char *cmd, *collname, *line, *release;
119	int error, fixupseof;
120
121	config = d->config;
122	rd = d->rd;
123	wr = d->wr;
124	STAILQ_FOREACH(coll, &config->colls, co_next) {
125		if (coll->co_options & CO_SKIP)
126			continue;
127		line = stream_getln(rd, NULL);
128		cmd = proto_get_ascii(&line);
129		collname = proto_get_ascii(&line);
130		release = proto_get_ascii(&line);
131		error = proto_get_time(&line, &coll->co_scantime);
132		if (error || line != NULL || strcmp(cmd, "COLL") != 0 ||
133		    strcmp(collname, coll->co_name) != 0 ||
134		    strcmp(release, coll->co_release) != 0)
135			return (DETAILER_ERR_PROTO);
136		error = proto_printf(wr, "COLL %s %s\n", coll->co_name,
137		    coll->co_release);
138		if (error)
139			return (DETAILER_ERR_WRITE);
140		stream_flush(wr);
141		if (coll->co_options & CO_COMPRESS) {
142			stream_filter_start(rd, STREAM_FILTER_ZLIB, NULL);
143			stream_filter_start(wr, STREAM_FILTER_ZLIB, NULL);
144		}
145		st = status_open(coll, -1, &d->errmsg);
146		if (st == NULL)
147			return (DETAILER_ERR_MSG);
148		error = detailer_coll(d, coll, st);
149		status_close(st, NULL);
150		if (error)
151			return (error);
152		if (coll->co_options & CO_COMPRESS) {
153			stream_filter_stop(rd);
154			stream_filter_stop(wr);
155		}
156		stream_flush(wr);
157	}
158	line = stream_getln(rd, NULL);
159	if (line == NULL)
160		return (DETAILER_ERR_READ);
161	if (strcmp(line, ".") != 0)
162		return (DETAILER_ERR_PROTO);
163	error = proto_printf(wr, ".\n");
164	if (error)
165		return (DETAILER_ERR_WRITE);
166	stream_flush(wr);
167
168	/* Now send fixups if needed. */
169	fixup = NULL;
170	fixupseof = 0;
171	STAILQ_FOREACH(coll, &config->colls, co_next) {
172		if (coll->co_options & CO_SKIP)
173			continue;
174		error = proto_printf(wr, "COLL %s %s\n", coll->co_name,
175		    coll->co_release);
176		if (error)
177			return (DETAILER_ERR_WRITE);
178		if (coll->co_options & CO_COMPRESS)
179			stream_filter_start(wr, STREAM_FILTER_ZLIB, NULL);
180		while (!fixupseof) {
181			if (fixup == NULL)
182				fixup = fixups_get(config->fixups);
183			if (fixup == NULL) {
184				fixupseof = 1;
185				break;
186			}
187			if (fixup->f_coll != coll)
188				break;
189			error = proto_printf(wr, "Y %s %s %s\n", fixup->f_name,
190			    coll->co_tag, coll->co_date);
191			if (error)
192				return (DETAILER_ERR_WRITE);
193			fixup = NULL;
194		}
195		error = proto_printf(wr, ".\n");
196		if (error)
197			return (DETAILER_ERR_WRITE);
198		if (coll->co_options & CO_COMPRESS)
199			stream_filter_stop(wr);
200		stream_flush(wr);
201	}
202	error = proto_printf(wr, ".\n");
203	if (error)
204		return (DETAILER_ERR_WRITE);
205	return (0);
206}
207
208static int
209detailer_coll(struct detailer *d, struct coll *coll, struct status *st)
210{
211	struct stream *rd, *wr;
212	char *cmd, *file, *line, *msg;
213	int error;
214
215	rd = d->rd;
216	wr = d->wr;
217	line = stream_getln(rd, NULL);
218	if (line == NULL)
219		return (DETAILER_ERR_READ);
220	while (strcmp(line, ".") != 0) {
221		cmd = proto_get_ascii(&line);
222		if (cmd == NULL || strlen(cmd) != 1)
223			return (DETAILER_ERR_PROTO);
224		switch (cmd[0]) {
225		case 'D':
226			/* Delete file. */
227			file = proto_get_ascii(&line);
228			if (file == NULL || line != NULL)
229				return (DETAILER_ERR_PROTO);
230			error = proto_printf(wr, "D %s\n", file);
231			if (error)
232				return (DETAILER_ERR_WRITE);
233			break;
234		case 'U':
235			/* Add or update file. */
236			file = proto_get_ascii(&line);
237			if (file == NULL || line != NULL)
238				return (DETAILER_ERR_PROTO);
239			error = detailer_dofile(d, coll, st, file);
240			if (error)
241				return (error);
242			break;
243		case '!':
244			/* Warning from server. */
245			msg = proto_get_rest(&line);
246			if (msg == NULL)
247				return (DETAILER_ERR_PROTO);
248			lprintf(-1, "Server warning: %s\n", msg);
249			break;
250		default:
251			return (DETAILER_ERR_PROTO);
252		}
253		stream_flush(wr);
254		line = stream_getln(rd, NULL);
255		if (line == NULL)
256			return (DETAILER_ERR_READ);
257	}
258	error = proto_printf(wr, ".\n");
259	if (error)
260		return (DETAILER_ERR_WRITE);
261	return (0);
262}
263
264static int
265detailer_dofile(struct detailer *d, struct coll *coll, struct status *st,
266    char *file)
267{
268	char md5[MD5_DIGEST_SIZE];
269	struct stream *wr;
270	struct fattr *fa;
271	struct statusrec *sr;
272	char *path;
273	int error, ret;
274
275	wr = d->wr;
276	path = checkoutpath(coll->co_prefix, file);
277	if (path == NULL)
278		return (DETAILER_ERR_PROTO);
279	fa = fattr_frompath(path, FATTR_NOFOLLOW);
280	if (fa == NULL) {
281		/* We don't have the file, so the only option at this
282		   point is to tell the server to send it.  The server
283		   may figure out that the file is dead, in which case
284		   it will tell us. */
285		error = proto_printf(wr, "C %s %s %s\n",
286		    file, coll->co_tag, coll->co_date);
287		free(path);
288		if (error)
289			return (DETAILER_ERR_WRITE);
290		return (0);
291	}
292	ret = status_get(st, file, 0, 0, &sr);
293	if (ret == -1) {
294		d->errmsg = status_errmsg(st);
295		free(path);
296		return (DETAILER_ERR_MSG);
297	}
298	if (ret == 0)
299		sr = NULL;
300
301	/* If our recorded information doesn't match the file that the
302	   client has, then ignore the recorded information. */
303	if (sr != NULL && (sr->sr_type != SR_CHECKOUTLIVE ||
304	    !fattr_equal(sr->sr_clientattr, fa)))
305		sr = NULL;
306	fattr_free(fa);
307	if (sr != NULL && strcmp(sr->sr_revdate, ".") != 0) {
308		error = proto_printf(wr, "U %s %s %s %s %s\n", file,
309		    coll->co_tag, coll->co_date, sr->sr_revnum, sr->sr_revdate);
310		free(path);
311		if (error)
312			return (DETAILER_ERR_WRITE);
313		return (0);
314	}
315
316	/*
317	 * We don't have complete and/or accurate recorded information
318	 * about what version of the file we have.  Compute the file's
319	 * checksum as an aid toward identifying which version it is.
320	 */
321	error = MD5_File(path, md5);
322	if (error) {
323		xasprintf(&d->errmsg,
324		    "Cannot calculate checksum for \"%s\": %s", path,
325		    strerror(errno));
326		return (DETAILER_ERR_MSG);
327	}
328	free(path);
329	if (sr == NULL) {
330		error = proto_printf(wr, "S %s %s %s %s\n", file,
331		    coll->co_tag, coll->co_date, md5);
332	} else {
333		error = proto_printf(wr, "s %s %s %s %s %s\n", file,
334		    coll->co_tag, coll->co_date, sr->sr_revnum, md5);
335	}
336	if (error)
337		return (DETAILER_ERR_WRITE);
338	return (0);
339}
340