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 <sys/types.h>
30156230Smux#include <sys/stat.h>
31156230Smux
32156230Smux#include <assert.h>
33186781Slulf#include <err.h>
34156230Smux#include <errno.h>
35156230Smux#include <fcntl.h>
36156230Smux#include <stddef.h>
37156230Smux#include <stdio.h>
38156230Smux#include <stdlib.h>
39156230Smux#include <string.h>
40156230Smux#include <unistd.h>
41156230Smux
42156230Smux#include "config.h"
43156230Smux#include "diff.h"
44156230Smux#include "fattr.h"
45156230Smux#include "fixups.h"
46156230Smux#include "keyword.h"
47156230Smux#include "updater.h"
48156230Smux#include "misc.h"
49156230Smux#include "mux.h"
50156230Smux#include "proto.h"
51186781Slulf#include "rcsfile.h"
52156230Smux#include "status.h"
53156230Smux#include "stream.h"
54156230Smux
55156230Smux/* Internal error codes. */
56156230Smux#define	UPDATER_ERR_PROTO	(-1)	/* Protocol error. */
57156230Smux#define	UPDATER_ERR_MSG		(-2)	/* Error is in updater->errmsg. */
58156230Smux#define	UPDATER_ERR_READ	(-3)	/* Error reading from server. */
59156701Smux#define	UPDATER_ERR_DELETELIM	(-4)	/* File deletion limit exceeded. */
60156230Smux
61186781Slulf#define BUFSIZE 4096
62186781Slulf
63156230Smux/* Everything needed to update a file. */
64156230Smuxstruct file_update {
65156230Smux	struct statusrec srbuf;
66156230Smux	char *destpath;
67156701Smux	char *temppath;
68186781Slulf	char *origpath;
69156230Smux	char *coname;		/* Points somewhere in destpath. */
70156230Smux	char *wantmd5;
71156230Smux	struct coll *coll;
72156230Smux	struct status *st;
73156230Smux	/* Those are only used for diff updating. */
74156230Smux	char *author;
75156230Smux	struct stream *orig;
76156230Smux	struct stream *to;
77186781Slulf	int attic;
78156230Smux	int expand;
79156230Smux};
80156230Smux
81156230Smuxstruct updater {
82156230Smux	struct config *config;
83156230Smux	struct stream *rd;
84156230Smux	char *errmsg;
85156701Smux	int deletecount;
86156230Smux};
87156230Smux
88156230Smuxstatic struct file_update	*fup_new(struct coll *, struct status *);
89186781Slulfstatic int	 fup_prepare(struct file_update *, char *, int);
90156230Smuxstatic void	 fup_cleanup(struct file_update *);
91156230Smuxstatic void	 fup_free(struct file_update *);
92156230Smux
93156230Smuxstatic void	 updater_prunedirs(char *, char *);
94156230Smuxstatic int	 updater_batch(struct updater *, int);
95156230Smuxstatic int	 updater_docoll(struct updater *, struct file_update *, int);
96156701Smuxstatic int	 updater_delete(struct updater *, struct file_update *);
97156701Smuxstatic void	 updater_deletefile(const char *);
98156230Smuxstatic int	 updater_checkout(struct updater *, struct file_update *, int);
99186781Slulfstatic int	 updater_addfile(struct updater *, struct file_update *,
100186781Slulf		     char *, int);
101186781Slulfint		 updater_addelta(struct rcsfile *, struct stream *, char *);
102156230Smuxstatic int	 updater_setattrs(struct updater *, struct file_update *,
103156230Smux		     char *, char *, char *, char *, char *, struct fattr *);
104186781Slulfstatic int	updater_setdirattrs(struct updater *, struct coll *,
105186781Slulf		     struct file_update *, char *, char *);
106156701Smuxstatic int	 updater_updatefile(struct updater *, struct file_update *fup,
107156230Smux		     const char *, int);
108186781Slulfstatic int	 updater_updatenode(struct updater *, struct coll *,
109186781Slulf		     struct file_update *, char *, char *);
110156230Smuxstatic int	 updater_diff(struct updater *, struct file_update *);
111156230Smuxstatic int	 updater_diff_batch(struct updater *, struct file_update *);
112156230Smuxstatic int	 updater_diff_apply(struct updater *, struct file_update *,
113156230Smux		     char *);
114186781Slulfstatic int	 updater_rcsedit(struct updater *, struct file_update *, char *,
115186781Slulf		     char *);
116186781Slulfint		 updater_append_file(struct updater *, struct file_update *,
117186781Slulf		     off_t);
118186781Slulfstatic int	 updater_rsync(struct updater *, struct file_update *, size_t);
119186781Slulfstatic int	 updater_read_checkout(struct stream *, struct stream *);
120156230Smux
121156230Smuxstatic struct file_update *
122156230Smuxfup_new(struct coll *coll, struct status *st)
123156230Smux{
124156230Smux	struct file_update *fup;
125156230Smux
126156230Smux	fup = xmalloc(sizeof(struct file_update));
127156230Smux	memset(fup, 0, sizeof(*fup));
128156230Smux	fup->coll = coll;
129156230Smux	fup->st = st;
130156230Smux	return (fup);
131156230Smux}
132156230Smux
133156230Smuxstatic int
134186781Slulffup_prepare(struct file_update *fup, char *name, int attic)
135156230Smux{
136156230Smux	struct coll *coll;
137156230Smux
138156230Smux	coll = fup->coll;
139186781Slulf	fup->attic = 0;
140186781Slulf	fup->origpath = NULL;
141186781Slulf
142186781Slulf	if (coll->co_options & CO_CHECKOUTMODE)
143186781Slulf		fup->destpath = checkoutpath(coll->co_prefix, name);
144186781Slulf	else {
145186781Slulf		fup->destpath = cvspath(coll->co_prefix, name, attic);
146186781Slulf		fup->origpath = atticpath(coll->co_prefix, name);
147186781Slulf		/* If they're equal, we don't need special care. */
148186781Slulf		if (fup->origpath != NULL &&
149186781Slulf		    strcmp(fup->origpath, fup->destpath) == 0) {
150186781Slulf			free(fup->origpath);
151186781Slulf			fup->origpath = NULL;
152186781Slulf		}
153186781Slulf		fup->attic = attic;
154186781Slulf	}
155156230Smux	if (fup->destpath == NULL)
156156230Smux		return (-1);
157156230Smux	fup->coname = fup->destpath + coll->co_prefixlen + 1;
158156230Smux	return (0);
159156230Smux}
160156230Smux
161156230Smux/* Called after each file update to reinit the structure. */
162156230Smuxstatic void
163156230Smuxfup_cleanup(struct file_update *fup)
164156230Smux{
165156230Smux	struct statusrec *sr;
166156230Smux
167156230Smux	sr = &fup->srbuf;
168156230Smux
169156230Smux	if (fup->destpath != NULL) {
170156230Smux		free(fup->destpath);
171156230Smux		fup->destpath = NULL;
172156230Smux	}
173156701Smux	if (fup->temppath != NULL) {
174156701Smux		free(fup->temppath);
175156701Smux		fup->temppath = NULL;
176156701Smux	}
177186781Slulf	if (fup->origpath != NULL) {
178186781Slulf		free(fup->origpath);
179186781Slulf		fup->origpath = NULL;
180186781Slulf	}
181156230Smux	fup->coname = NULL;
182156230Smux	if (fup->author != NULL) {
183156230Smux		free(fup->author);
184156230Smux		fup->author = NULL;
185156230Smux	}
186156230Smux	fup->expand = 0;
187156230Smux	if (fup->wantmd5 != NULL) {
188156230Smux		free(fup->wantmd5);
189156230Smux		fup->wantmd5 = NULL;
190156230Smux	}
191156230Smux	if (fup->orig != NULL) {
192156230Smux		stream_close(fup->orig);
193156230Smux		fup->orig = NULL;
194156230Smux	}
195156230Smux	if (fup->to != NULL) {
196156230Smux		stream_close(fup->to);
197156230Smux		fup->to = NULL;
198156230Smux	}
199156230Smux	if (sr->sr_file != NULL)
200156230Smux		free(sr->sr_file);
201156230Smux	if (sr->sr_tag != NULL)
202156230Smux		free(sr->sr_tag);
203156230Smux	if (sr->sr_date != NULL)
204156230Smux		free(sr->sr_date);
205156230Smux	if (sr->sr_revnum != NULL)
206156230Smux		free(sr->sr_revnum);
207156230Smux	if (sr->sr_revdate != NULL)
208156230Smux		free(sr->sr_revdate);
209156230Smux	fattr_free(sr->sr_clientattr);
210156230Smux	fattr_free(sr->sr_serverattr);
211156230Smux	memset(sr, 0, sizeof(*sr));
212156230Smux}
213156230Smux
214156230Smuxstatic void
215156230Smuxfup_free(struct file_update *fup)
216156230Smux{
217156230Smux
218156230Smux	fup_cleanup(fup);
219156230Smux	free(fup);
220156230Smux}
221156230Smux
222156230Smuxvoid *
223156230Smuxupdater(void *arg)
224156230Smux{
225156230Smux	struct thread_args *args;
226156230Smux	struct updater upbuf, *up;
227156230Smux	int error;
228156230Smux
229156230Smux	args = arg;
230156230Smux
231156230Smux	up = &upbuf;
232156230Smux	up->config = args->config;
233156230Smux	up->rd = args->rd;
234156230Smux	up->errmsg = NULL;
235156701Smux	up->deletecount = 0;
236156230Smux
237156230Smux	error = updater_batch(up, 0);
238156230Smux
239156230Smux	/*
240156230Smux	 * Make sure to close the fixups even in case of an error,
241225979Sadrian	 * so that the detailer thread doesn't block indefinitely.
242156230Smux	 */
243156230Smux	fixups_close(up->config->fixups);
244156230Smux	if (!error)
245156230Smux		error = updater_batch(up, 1);
246156230Smux	switch (error) {
247156230Smux	case UPDATER_ERR_PROTO:
248156230Smux		xasprintf(&args->errmsg, "Updater failed: Protocol error");
249156230Smux		args->status = STATUS_FAILURE;
250156230Smux		break;
251156230Smux	case UPDATER_ERR_MSG:
252156230Smux		xasprintf(&args->errmsg, "Updater failed: %s", up->errmsg);
253156230Smux		free(up->errmsg);
254156230Smux		args->status = STATUS_FAILURE;
255156230Smux		break;
256156230Smux	case UPDATER_ERR_READ:
257156230Smux		if (stream_eof(up->rd)) {
258156230Smux			xasprintf(&args->errmsg, "Updater failed: "
259156230Smux			    "Premature EOF from server");
260156230Smux		} else {
261156230Smux			xasprintf(&args->errmsg, "Updater failed: "
262156230Smux			    "Network read failure: %s", strerror(errno));
263156230Smux		}
264156230Smux		args->status = STATUS_TRANSIENTFAILURE;
265156230Smux		break;
266156701Smux	case UPDATER_ERR_DELETELIM:
267156701Smux		xasprintf(&args->errmsg, "Updater failed: "
268156701Smux		    "File deletion limit exceeded");
269156701Smux		args->status = STATUS_FAILURE;
270156701Smux		break;
271156230Smux	default:
272156230Smux		assert(error == 0);
273156230Smux		args->status = STATUS_SUCCESS;
274156230Smux	};
275156230Smux	return (NULL);
276156230Smux}
277156230Smux
278156230Smuxstatic int
279156230Smuxupdater_batch(struct updater *up, int isfixups)
280156230Smux{
281156230Smux	struct stream *rd;
282156230Smux	struct coll *coll;
283156230Smux	struct status *st;
284156230Smux	struct file_update *fup;
285156230Smux	char *line, *cmd, *errmsg, *collname, *release;
286156230Smux	int error;
287156230Smux
288156230Smux	rd = up->rd;
289156230Smux	STAILQ_FOREACH(coll, &up->config->colls, co_next) {
290156230Smux		if (coll->co_options & CO_SKIP)
291156230Smux			continue;
292156230Smux		umask(coll->co_umask);
293156230Smux		line = stream_getln(rd, NULL);
294156230Smux		if (line == NULL)
295156230Smux			return (UPDATER_ERR_READ);
296156230Smux		cmd = proto_get_ascii(&line);
297156230Smux		collname = proto_get_ascii(&line);
298156230Smux		release = proto_get_ascii(&line);
299156230Smux		if (release == NULL || line != NULL)
300156230Smux			return (UPDATER_ERR_PROTO);
301156230Smux		if (strcmp(cmd, "COLL") != 0 ||
302156230Smux		    strcmp(collname, coll->co_name) != 0 ||
303156230Smux		    strcmp(release, coll->co_release) != 0)
304156230Smux			return (UPDATER_ERR_PROTO);
305156230Smux
306156230Smux		if (!isfixups)
307156230Smux			lprintf(1, "Updating collection %s/%s\n", coll->co_name,
308156230Smux			    coll->co_release);
309156230Smux
310156230Smux		if (coll->co_options & CO_COMPRESS)
311156230Smux			stream_filter_start(rd, STREAM_FILTER_ZLIB, NULL);
312156230Smux
313156230Smux		st = status_open(coll, coll->co_scantime, &errmsg);
314156230Smux		if (st == NULL) {
315156230Smux			up->errmsg = errmsg;
316156230Smux			return (UPDATER_ERR_MSG);
317156230Smux		}
318156701Smux		fup = fup_new(coll, st);
319156230Smux		error = updater_docoll(up, fup, isfixups);
320156230Smux		status_close(st, &errmsg);
321156230Smux		fup_free(fup);
322156230Smux		if (errmsg != NULL) {
323156230Smux			/* Discard previous error. */
324156230Smux			if (up->errmsg != NULL)
325156230Smux				free(up->errmsg);
326156230Smux			up->errmsg = errmsg;
327156230Smux			return (UPDATER_ERR_MSG);
328156230Smux		}
329156230Smux		if (error)
330156230Smux			return (error);
331156230Smux
332156230Smux		if (coll->co_options & CO_COMPRESS)
333156230Smux			stream_filter_stop(rd);
334156230Smux	}
335156230Smux	line = stream_getln(rd, NULL);
336156230Smux	if (line == NULL)
337156230Smux		return (UPDATER_ERR_READ);
338156230Smux	if (strcmp(line, ".") != 0)
339156230Smux		return (UPDATER_ERR_PROTO);
340156230Smux	return (0);
341156230Smux}
342156230Smux
343156230Smuxstatic int
344156230Smuxupdater_docoll(struct updater *up, struct file_update *fup, int isfixups)
345156230Smux{
346156230Smux	struct stream *rd;
347156230Smux	struct coll *coll;
348156230Smux	struct statusrec srbuf, *sr;
349156230Smux	struct fattr *rcsattr, *tmp;
350186781Slulf	char *attr, *cmd, *blocksize, *line, *msg;
351156230Smux	char *name, *tag, *date, *revdate;
352156230Smux	char *expand, *wantmd5, *revnum;
353186781Slulf	char *optstr, *rcsopt, *pos;
354156230Smux	time_t t;
355186781Slulf	off_t position;
356186781Slulf	int attic, error, needfixupmsg;
357156230Smux
358156230Smux	error = 0;
359156230Smux	rd = up->rd;
360156230Smux	coll = fup->coll;
361156230Smux	needfixupmsg = isfixups;
362156230Smux	while ((line = stream_getln(rd, NULL)) != NULL) {
363156230Smux		if (strcmp(line, ".") == 0)
364156230Smux			break;
365156230Smux		memset(&srbuf, 0, sizeof(srbuf));
366156230Smux		if (needfixupmsg) {
367156230Smux			lprintf(1, "Applying fixups for collection %s/%s\n",
368156230Smux			    coll->co_name, coll->co_release);
369156230Smux			needfixupmsg = 0;
370156230Smux		}
371156230Smux		cmd = proto_get_ascii(&line);
372156230Smux		if (cmd == NULL || strlen(cmd) != 1)
373156230Smux			return (UPDATER_ERR_PROTO);
374156230Smux		switch (cmd[0]) {
375156230Smux		case 'T':
376156230Smux			/* Update recorded information for checked-out file. */
377156230Smux			name = proto_get_ascii(&line);
378156230Smux			tag = proto_get_ascii(&line);
379156230Smux			date = proto_get_ascii(&line);
380156230Smux			revnum = proto_get_ascii(&line);
381156230Smux			revdate = proto_get_ascii(&line);
382156230Smux			attr = proto_get_ascii(&line);
383156230Smux			if (attr == NULL || line != NULL)
384156230Smux				return (UPDATER_ERR_PROTO);
385156230Smux
386156230Smux			rcsattr = fattr_decode(attr);
387156230Smux			if (rcsattr == NULL)
388156230Smux				return (UPDATER_ERR_PROTO);
389156230Smux
390186781Slulf			error = fup_prepare(fup, name, 0);
391156230Smux			if (error)
392156230Smux				return (UPDATER_ERR_PROTO);
393156230Smux			error = updater_setattrs(up, fup, name, tag, date,
394156230Smux			    revnum, revdate, rcsattr);
395156230Smux			fattr_free(rcsattr);
396156230Smux			if (error)
397156230Smux				return (error);
398156230Smux			break;
399156230Smux		case 'c':
400156230Smux			/* Checkout dead file. */
401156230Smux			name = proto_get_ascii(&line);
402156230Smux			tag = proto_get_ascii(&line);
403156230Smux			date = proto_get_ascii(&line);
404156230Smux			attr = proto_get_ascii(&line);
405156230Smux			if (attr == NULL || line != NULL)
406156230Smux				return (UPDATER_ERR_PROTO);
407156230Smux
408186781Slulf			error = fup_prepare(fup, name, 0);
409156230Smux			if (error)
410156230Smux				return (UPDATER_ERR_PROTO);
411156230Smux			/* Theoritically, the file does not exist on the client.
412156230Smux			   Just to make sure, we'll delete it here, if it
413156230Smux			   exists. */
414156701Smux			if (access(fup->destpath, F_OK) == 0) {
415156701Smux				error = updater_delete(up, fup);
416156701Smux				if (error)
417156701Smux					return (error);
418156701Smux			}
419156230Smux
420156230Smux			sr = &srbuf;
421156230Smux			sr->sr_type = SR_CHECKOUTDEAD;
422156230Smux			sr->sr_file = name;
423156230Smux			sr->sr_tag = tag;
424156230Smux			sr->sr_date = date;
425156230Smux			sr->sr_serverattr = fattr_decode(attr);
426156230Smux			if (sr->sr_serverattr == NULL)
427156230Smux				return (UPDATER_ERR_PROTO);
428156230Smux
429156230Smux			error = status_put(fup->st, sr);
430156230Smux			fattr_free(sr->sr_serverattr);
431156230Smux			if (error) {
432156230Smux				up->errmsg = status_errmsg(fup->st);
433156230Smux				return (UPDATER_ERR_MSG);
434156230Smux			}
435156230Smux			break;
436156230Smux		case 'U':
437156230Smux			/* Update live checked-out file. */
438156230Smux			name = proto_get_ascii(&line);
439156230Smux			tag = proto_get_ascii(&line);
440156230Smux			date = proto_get_ascii(&line);
441156230Smux			proto_get_ascii(&line);	/* XXX - oldRevNum */
442156230Smux			proto_get_ascii(&line);	/* XXX - fromAttic */
443156230Smux			proto_get_ascii(&line);	/* XXX - logLines */
444156230Smux			expand = proto_get_ascii(&line);
445156230Smux			attr = proto_get_ascii(&line);
446156230Smux			wantmd5 = proto_get_ascii(&line);
447156230Smux			if (wantmd5 == NULL || line != NULL)
448156230Smux				return (UPDATER_ERR_PROTO);
449156230Smux
450156230Smux			sr = &fup->srbuf;
451156230Smux			sr->sr_type = SR_CHECKOUTLIVE;
452156230Smux			sr->sr_file = xstrdup(name);
453156230Smux			sr->sr_date = xstrdup(date);
454156230Smux			sr->sr_tag = xstrdup(tag);
455156230Smux			sr->sr_serverattr = fattr_decode(attr);
456156230Smux			if (sr->sr_serverattr == NULL)
457156230Smux				return (UPDATER_ERR_PROTO);
458156230Smux
459156230Smux			fup->expand = keyword_decode_expand(expand);
460156230Smux			if (fup->expand == -1)
461156230Smux				return (UPDATER_ERR_PROTO);
462186781Slulf			error = fup_prepare(fup, name, 0);
463156230Smux			if (error)
464156230Smux				return (UPDATER_ERR_PROTO);
465156230Smux
466156230Smux			fup->wantmd5 = xstrdup(wantmd5);
467156701Smux			fup->temppath = tempname(fup->destpath);
468156230Smux			error = updater_diff(up, fup);
469156230Smux			if (error)
470156230Smux				return (error);
471156230Smux			break;
472156230Smux		case 'u':
473156230Smux			/* Update dead checked-out file. */
474156230Smux			name = proto_get_ascii(&line);
475156230Smux			tag = proto_get_ascii(&line);
476156230Smux			date = proto_get_ascii(&line);
477156230Smux			attr = proto_get_ascii(&line);
478156230Smux			if (attr == NULL || line != NULL)
479156230Smux				return (UPDATER_ERR_PROTO);
480156230Smux
481186781Slulf			error = fup_prepare(fup, name, 0);
482156230Smux			if (error)
483156230Smux				return (UPDATER_ERR_PROTO);
484156701Smux			error = updater_delete(up, fup);
485156701Smux			if (error)
486156701Smux				return (error);
487156230Smux			sr = &srbuf;
488156230Smux			sr->sr_type = SR_CHECKOUTDEAD;
489156230Smux			sr->sr_file = name;
490156230Smux			sr->sr_tag = tag;
491156230Smux			sr->sr_date = date;
492156230Smux			sr->sr_serverattr = fattr_decode(attr);
493156230Smux			if (sr->sr_serverattr == NULL)
494156230Smux				return (UPDATER_ERR_PROTO);
495156230Smux			error = status_put(fup->st, sr);
496156230Smux			fattr_free(sr->sr_serverattr);
497156230Smux			if (error) {
498156230Smux				up->errmsg = status_errmsg(fup->st);
499156230Smux				return (UPDATER_ERR_MSG);
500156230Smux			}
501156230Smux			break;
502156230Smux		case 'C':
503156230Smux		case 'Y':
504156230Smux			/* Checkout file. */
505156230Smux			name = proto_get_ascii(&line);
506156230Smux			tag = proto_get_ascii(&line);
507156230Smux			date = proto_get_ascii(&line);
508156230Smux			revnum = proto_get_ascii(&line);
509156230Smux			revdate = proto_get_ascii(&line);
510156230Smux			attr = proto_get_ascii(&line);
511156230Smux			if (attr == NULL || line != NULL)
512156230Smux				return (UPDATER_ERR_PROTO);
513156230Smux
514156230Smux			sr = &fup->srbuf;
515156230Smux			sr->sr_type = SR_CHECKOUTLIVE;
516156230Smux			sr->sr_file = xstrdup(name);
517156230Smux			sr->sr_tag = xstrdup(tag);
518156230Smux			sr->sr_date = xstrdup(date);
519156230Smux			sr->sr_revnum = xstrdup(revnum);
520156230Smux			sr->sr_revdate = xstrdup(revdate);
521156230Smux			sr->sr_serverattr = fattr_decode(attr);
522156230Smux			if (sr->sr_serverattr == NULL)
523156230Smux				return (UPDATER_ERR_PROTO);
524156230Smux
525156230Smux			t = rcsdatetotime(revdate);
526156230Smux			if (t == -1)
527156230Smux				return (UPDATER_ERR_PROTO);
528156230Smux
529156230Smux			sr->sr_clientattr = fattr_new(FT_FILE, t);
530156230Smux			tmp = fattr_forcheckout(sr->sr_serverattr,
531156230Smux			    coll->co_umask);
532156230Smux			fattr_override(sr->sr_clientattr, tmp, FA_MASK);
533156230Smux			fattr_free(tmp);
534156230Smux			fattr_mergedefault(sr->sr_clientattr);
535186781Slulf			error = fup_prepare(fup, name, 0);
536156230Smux			if (error)
537156230Smux				return (UPDATER_ERR_PROTO);
538156701Smux			fup->temppath = tempname(fup->destpath);
539156230Smux			if (*cmd == 'Y')
540156230Smux				error = updater_checkout(up, fup, 1);
541156230Smux			else
542156230Smux				error = updater_checkout(up, fup, 0);
543156230Smux			if (error)
544156230Smux				return (error);
545156230Smux			break;
546156230Smux		case 'D':
547156230Smux			/* Delete file. */
548156230Smux			name = proto_get_ascii(&line);
549156230Smux			if (name == NULL || line != NULL)
550156230Smux				return (UPDATER_ERR_PROTO);
551186781Slulf			error = fup_prepare(fup, name, 0);
552156230Smux			if (error)
553156230Smux				return (UPDATER_ERR_PROTO);
554156701Smux			error = updater_delete(up, fup);
555156701Smux			if (error)
556156701Smux				return (error);
557156230Smux			error = status_delete(fup->st, name, 0);
558156230Smux			if (error) {
559156230Smux				up->errmsg = status_errmsg(fup->st);
560156230Smux				return (UPDATER_ERR_MSG);
561156230Smux			}
562156230Smux			break;
563186781Slulf		case 'A':
564186781Slulf		case 'a':
565186781Slulf		case 'R':
566186781Slulf			name = proto_get_ascii(&line);
567186781Slulf			attr = proto_get_ascii(&line);
568186781Slulf			if (name == NULL || attr == NULL || line != NULL)
569186781Slulf				return (UPDATER_ERR_PROTO);
570186781Slulf			attic = (cmd[0] == 'a');
571186781Slulf			error = fup_prepare(fup, name, attic);
572186781Slulf			if (error)
573186781Slulf				return (UPDATER_ERR_PROTO);
574186781Slulf
575186781Slulf			fup->temppath = tempname(fup->destpath);
576186781Slulf			sr = &fup->srbuf;
577186781Slulf			sr->sr_type = attic ? SR_FILEDEAD : SR_FILELIVE;
578186781Slulf			sr->sr_file = xstrdup(name);
579186781Slulf			sr->sr_serverattr = fattr_decode(attr);
580186781Slulf			if (sr->sr_serverattr == NULL)
581186781Slulf				return (UPDATER_ERR_PROTO);
582186781Slulf			if (attic)
583186781Slulf				lprintf(1, " Create %s -> Attic\n", name);
584186781Slulf			else
585186781Slulf				lprintf(1, " Create %s\n", name);
586186781Slulf			error = updater_addfile(up, fup, attr, 0);
587186781Slulf			if (error)
588186781Slulf				return (error);
589186781Slulf			break;
590186781Slulf		case 'r':
591186781Slulf			name = proto_get_ascii(&line);
592186781Slulf			attr = proto_get_ascii(&line);
593186781Slulf			blocksize = proto_get_ascii(&line);
594186781Slulf			wantmd5 = proto_get_ascii(&line);
595186781Slulf			if (name == NULL || attr == NULL || blocksize == NULL ||
596186781Slulf			    wantmd5 == NULL) {
597186781Slulf				return (UPDATER_ERR_PROTO);
598186781Slulf			}
599186781Slulf			error = fup_prepare(fup, name, 0);
600186781Slulf			if (error)
601186781Slulf				return (UPDATER_ERR_PROTO);
602186781Slulf			fup->wantmd5 = xstrdup(wantmd5);
603186781Slulf			fup->temppath = tempname(fup->destpath);
604186781Slulf			sr = &fup->srbuf;
605186781Slulf			sr->sr_file = xstrdup(name);
606186781Slulf			sr->sr_serverattr = fattr_decode(attr);
607186781Slulf			sr->sr_type = SR_FILELIVE;
608186781Slulf			if (sr->sr_serverattr == NULL)
609186781Slulf				return (UPDATER_ERR_PROTO);
610186781Slulf			error = updater_rsync(up, fup, strtol(blocksize, NULL,
611186781Slulf			    10));
612186781Slulf			if (error)
613186781Slulf				return (error);
614186781Slulf			break;
615186781Slulf		case 'I':
616186781Slulf			/*
617186781Slulf			 * Create directory and add DirDown entry in status
618186781Slulf			 * file.
619186781Slulf			 */
620186781Slulf			name = proto_get_ascii(&line);
621186781Slulf			if (name == NULL || line != NULL)
622186781Slulf				return (UPDATER_ERR_PROTO);
623186781Slulf			error = fup_prepare(fup, name, 0);
624186781Slulf			if (error)
625186781Slulf				return (UPDATER_ERR_PROTO);
626186781Slulf			sr = &fup->srbuf;
627186781Slulf			sr->sr_type = SR_DIRDOWN;
628186781Slulf			sr->sr_file = xstrdup(name);
629186781Slulf			sr->sr_serverattr = NULL;
630186781Slulf			sr->sr_clientattr = fattr_new(FT_DIRECTORY, -1);
631186781Slulf			fattr_mergedefault(sr->sr_clientattr);
632186781Slulf
633186781Slulf			error = mkdirhier(fup->destpath, coll->co_umask);
634186781Slulf			if (error)
635186781Slulf				return (UPDATER_ERR_PROTO);
636186781Slulf			if (access(fup->destpath, F_OK) != 0) {
637186781Slulf				lprintf(1, " Mkdir %s\n", name);
638186781Slulf				error = fattr_makenode(sr->sr_clientattr,
639186781Slulf				    fup->destpath);
640186781Slulf				if (error)
641186781Slulf					return (UPDATER_ERR_PROTO);
642186781Slulf			}
643186781Slulf			error = status_put(fup->st, sr);
644186781Slulf			if (error) {
645186781Slulf				up->errmsg = status_errmsg(fup->st);
646186781Slulf				return (UPDATER_ERR_MSG);
647186781Slulf			}
648186781Slulf			break;
649186781Slulf		case 'i':
650186781Slulf			/* Remove DirDown entry in status file. */
651186781Slulf			name = proto_get_ascii(&line);
652186781Slulf			if (name == NULL || line != NULL)
653186781Slulf				return (UPDATER_ERR_PROTO);
654186781Slulf			error = fup_prepare(fup, name, 0);
655186781Slulf			if (error)
656186781Slulf				return (UPDATER_ERR_PROTO);
657186781Slulf			error = status_delete(fup->st, name, 0);
658186781Slulf			if (error) {
659186781Slulf				up->errmsg = status_errmsg(fup->st);
660186781Slulf				return (UPDATER_ERR_MSG);
661186781Slulf			}
662186781Slulf			break;
663186781Slulf		case 'J':
664186781Slulf			/*
665186781Slulf			 * Set attributes of directory and update DirUp entry in
666186781Slulf			 * status file.
667186781Slulf			 */
668186781Slulf			name = proto_get_ascii(&line);
669186781Slulf			if (name == NULL)
670186781Slulf				return (UPDATER_ERR_PROTO);
671186781Slulf			attr = proto_get_ascii(&line);
672186781Slulf			if (attr == NULL || line != NULL)
673186781Slulf				return (UPDATER_ERR_PROTO);
674186781Slulf			error = fup_prepare(fup, name, 0);
675186781Slulf			if (error)
676186781Slulf				return (UPDATER_ERR_PROTO);
677186781Slulf			error = updater_setdirattrs(up, coll, fup, name, attr);
678186781Slulf			if (error)
679186781Slulf				return (error);
680186781Slulf			break;
681186781Slulf		case 'j':
682186781Slulf			/*
683186781Slulf			 * Remove directory and delete its DirUp entry in status
684186781Slulf			 * file.
685186781Slulf			 */
686186781Slulf			name = proto_get_ascii(&line);
687186781Slulf			if (name == NULL || line != NULL)
688186781Slulf				return (UPDATER_ERR_PROTO);
689186781Slulf			error = fup_prepare(fup, name, 0);
690186781Slulf			if (error)
691186781Slulf				return (UPDATER_ERR_PROTO);
692186781Slulf			lprintf(1, " Rmdir %s\n", name);
693186781Slulf			updater_deletefile(fup->destpath);
694186781Slulf			error = status_delete(fup->st, name, 0);
695186781Slulf			if (error) {
696186781Slulf				up->errmsg = status_errmsg(fup->st);
697186781Slulf				return (UPDATER_ERR_MSG);
698186781Slulf			}
699186781Slulf			break;
700186781Slulf		case 'L':
701186781Slulf		case 'l':
702186781Slulf			name = proto_get_ascii(&line);
703186781Slulf			if (name == NULL)
704186781Slulf				return (UPDATER_ERR_PROTO);
705186781Slulf			attr = proto_get_ascii(&line);
706186781Slulf			if (attr == NULL || line != NULL)
707186781Slulf				return (UPDATER_ERR_PROTO);
708186781Slulf			attic = (cmd[0] == 'l');
709186781Slulf			sr = &fup->srbuf;
710186781Slulf			sr->sr_type = attic ? SR_FILEDEAD : SR_FILELIVE;
711186781Slulf			sr->sr_file = xstrdup(name);
712186781Slulf			sr->sr_serverattr = fattr_decode(attr);
713186781Slulf			sr->sr_clientattr = fattr_decode(attr);
714186781Slulf			if (sr->sr_serverattr == NULL ||
715186781Slulf			    sr->sr_clientattr == NULL)
716186781Slulf				return (UPDATER_ERR_PROTO);
717186781Slulf
718186781Slulf			/* Save space. Described in detail in updatefile. */
719186781Slulf			if (!(fattr_getmask(sr->sr_clientattr) & FA_LINKCOUNT)
720186781Slulf			    || fattr_getlinkcount(sr->sr_clientattr) <= 1)
721186781Slulf				fattr_maskout(sr->sr_clientattr,
722186781Slulf				    FA_DEV | FA_INODE);
723186781Slulf			fattr_maskout(sr->sr_clientattr, FA_FLAGS);
724186781Slulf			error = status_put(fup->st, sr);
725186781Slulf			if (error) {
726186781Slulf				up->errmsg = status_errmsg(fup->st);
727186781Slulf				return (UPDATER_ERR_MSG);
728186781Slulf			}
729186781Slulf			break;
730186781Slulf		case 'N':
731186781Slulf		case 'n':
732186781Slulf			name = proto_get_ascii(&line);
733186781Slulf			attr = proto_get_ascii(&line);
734186781Slulf			if (name == NULL || attr == NULL || line != NULL)
735186781Slulf				return (UPDATER_ERR_PROTO);
736186781Slulf			attic = (cmd[0] == 'n');
737186781Slulf			error = fup_prepare(fup, name, attic);
738186781Slulf			if (error)
739186781Slulf				return (UPDATER_ERR_PROTO);
740186781Slulf			sr = &fup->srbuf;
741186781Slulf			sr->sr_type = (attic ? SR_FILEDEAD : SR_FILELIVE);
742186781Slulf			sr->sr_file = xstrdup(name);
743186781Slulf			sr->sr_serverattr = fattr_decode(attr);
744186781Slulf			sr->sr_clientattr = fattr_new(FT_SYMLINK, -1);
745186781Slulf			fattr_mergedefault(sr->sr_clientattr);
746186781Slulf			fattr_maskout(sr->sr_clientattr, FA_FLAGS);
747186781Slulf			error = updater_updatenode(up, coll, fup, name, attr);
748186781Slulf			if (error)
749186781Slulf				return (error);
750186781Slulf			break;
751186781Slulf		case 'V':
752186781Slulf		case 'v':
753186781Slulf			name = proto_get_ascii(&line);
754186781Slulf			attr = proto_get_ascii(&line);
755186781Slulf			optstr = proto_get_ascii(&line);
756186781Slulf			wantmd5 = proto_get_ascii(&line);
757186781Slulf			rcsopt = NULL; /* XXX: Not supported. */
758186781Slulf			if (attr == NULL || line != NULL || wantmd5 == NULL)
759186781Slulf				return (UPDATER_ERR_PROTO);
760186781Slulf			attic = (cmd[0] == 'v');
761186781Slulf			error = fup_prepare(fup, name, attic);
762186781Slulf			if (error)
763186781Slulf				return (UPDATER_ERR_PROTO);
764186781Slulf			fup->temppath = tempname(fup->destpath);
765186781Slulf			fup->wantmd5 = xstrdup(wantmd5);
766186781Slulf			sr = &fup->srbuf;
767186781Slulf			sr->sr_type = attic ? SR_FILEDEAD : SR_FILELIVE;
768186781Slulf			sr->sr_file = xstrdup(name);
769186781Slulf			sr->sr_serverattr = fattr_decode(attr);
770186781Slulf			if (sr->sr_serverattr == NULL)
771186781Slulf				return (UPDATER_ERR_PROTO);
772186781Slulf
773186781Slulf			error = updater_rcsedit(up, fup, name, rcsopt);
774186781Slulf			if (error)
775186781Slulf				return (error);
776186781Slulf			break;
777186781Slulf		case 'X':
778186781Slulf		case 'x':
779186781Slulf			name = proto_get_ascii(&line);
780186781Slulf			attr = proto_get_ascii(&line);
781186781Slulf			if (name == NULL || attr == NULL || line != NULL)
782186781Slulf				return (UPDATER_ERR_PROTO);
783186781Slulf			attic = (cmd[0] == 'x');
784186781Slulf			error = fup_prepare(fup, name, attic);
785186781Slulf			if (error)
786186781Slulf				return (UPDATER_ERR_PROTO);
787186781Slulf
788186781Slulf			fup->temppath = tempname(fup->destpath);
789186781Slulf			sr = &fup->srbuf;
790186781Slulf			sr->sr_type = attic ? SR_FILEDEAD : SR_FILELIVE;
791186781Slulf			sr->sr_file = xstrdup(name);
792186781Slulf			sr->sr_serverattr = fattr_decode(attr);
793186781Slulf			if (sr->sr_serverattr == NULL)
794186781Slulf				return (UPDATER_ERR_PROTO);
795186781Slulf			lprintf(1, " Fixup %s\n", name);
796186781Slulf			error = updater_addfile(up, fup, attr, 1);
797186781Slulf			if (error)
798186781Slulf				return (error);
799186781Slulf			break;
800186781Slulf		case 'Z':
801186781Slulf			name = proto_get_ascii(&line);
802186781Slulf			attr = proto_get_ascii(&line);
803186781Slulf			pos  = proto_get_ascii(&line);
804186781Slulf			if (name == NULL || attr == NULL || pos == NULL ||
805186781Slulf			    line != NULL)
806186781Slulf				return (UPDATER_ERR_PROTO);
807186781Slulf			error = fup_prepare(fup, name, 0);
808186781Slulf			fup->temppath = tempname(fup->destpath);
809186781Slulf			sr = &fup->srbuf;
810186781Slulf			sr->sr_type = SR_FILELIVE;
811186781Slulf			sr->sr_file = xstrdup(name);
812186781Slulf			sr->sr_serverattr = fattr_decode(attr);
813186781Slulf			if (sr->sr_serverattr == NULL)
814186781Slulf				return (UPDATER_ERR_PROTO);
815186781Slulf			position = strtol(pos, NULL, 10);
816186781Slulf			lprintf(1, " Append to %s\n", name);
817186781Slulf			error = updater_append_file(up, fup, position);
818186781Slulf			if (error)
819186781Slulf				return (error);
820186781Slulf			break;
821156230Smux		case '!':
822156230Smux			/* Warning from server. */
823156230Smux			msg = proto_get_rest(&line);
824156230Smux			if (msg == NULL)
825156230Smux				return (UPDATER_ERR_PROTO);
826156230Smux			lprintf(-1, "Server warning: %s\n", msg);
827156230Smux			break;
828156230Smux		default:
829156230Smux			return (UPDATER_ERR_PROTO);
830156230Smux		}
831156230Smux		fup_cleanup(fup);
832156230Smux	}
833156230Smux	if (line == NULL)
834156230Smux		return (UPDATER_ERR_READ);
835156230Smux	return (0);
836156230Smux}
837156230Smux
838156230Smux/* Delete file. */
839156701Smuxstatic int
840156701Smuxupdater_delete(struct updater *up, struct file_update *fup)
841156230Smux{
842156701Smux	struct config *config;
843156230Smux	struct coll *coll;
844156230Smux
845156701Smux	config = up->config;
846156230Smux	coll = fup->coll;
847156230Smux	if (coll->co_options & CO_DELETE) {
848156230Smux		lprintf(1, " Delete %s\n", fup->coname);
849156701Smux		if (config->deletelim >= 0 &&
850156701Smux		    up->deletecount >= config->deletelim)
851156701Smux			return (UPDATER_ERR_DELETELIM);
852156701Smux		up->deletecount++;
853156701Smux		updater_deletefile(fup->destpath);
854156230Smux		if (coll->co_options & CO_CHECKOUTMODE)
855156230Smux			updater_prunedirs(coll->co_prefix, fup->destpath);
856156230Smux	} else {
857156230Smux		lprintf(1," NoDelete %s\n", fup->coname);
858156230Smux	}
859156701Smux	return (0);
860156230Smux}
861156230Smux
862156701Smuxstatic void
863156701Smuxupdater_deletefile(const char *path)
864156701Smux{
865156701Smux	int error;
866156701Smux
867156701Smux	error = fattr_delete(path);
868156701Smux	if (error && errno != ENOENT) {
869156701Smux		lprintf(-1, "Cannot delete \"%s\": %s\n",
870156701Smux		    path, strerror(errno));
871156701Smux	}
872156701Smux}
873156701Smux
874156230Smuxstatic int
875156230Smuxupdater_setattrs(struct updater *up, struct file_update *fup, char *name,
876156230Smux    char *tag, char *date, char *revnum, char *revdate, struct fattr *rcsattr)
877156230Smux{
878156230Smux	struct statusrec sr;
879156230Smux	struct status *st;
880156230Smux	struct coll *coll;
881156230Smux	struct fattr *fileattr, *fa;
882156230Smux	char *path;
883156230Smux	int error, rv;
884156230Smux
885156230Smux	coll = fup->coll;
886156230Smux	st = fup->st;
887156230Smux	path = fup->destpath;
888156230Smux
889156230Smux	fileattr = fattr_frompath(path, FATTR_NOFOLLOW);
890156230Smux	if (fileattr == NULL) {
891156230Smux		/* The file has vanished. */
892156230Smux		error = status_delete(st, name, 0);
893156230Smux		if (error) {
894156230Smux			up->errmsg = status_errmsg(st);
895156230Smux			return (UPDATER_ERR_MSG);
896156230Smux		}
897156230Smux		return (0);
898156230Smux	}
899156230Smux	fa = fattr_forcheckout(rcsattr, coll->co_umask);
900156230Smux	fattr_override(fileattr, fa, FA_MASK);
901156230Smux	fattr_free(fa);
902156230Smux
903156230Smux	rv = fattr_install(fileattr, path, NULL);
904156230Smux	if (rv == -1) {
905156230Smux		lprintf(1, " SetAttrs %s\n", fup->coname);
906156230Smux		fattr_free(fileattr);
907156230Smux		xasprintf(&up->errmsg, "Cannot set attributes for \"%s\": %s",
908156230Smux		    path, strerror(errno));
909156230Smux		return (UPDATER_ERR_MSG);
910156230Smux	}
911156230Smux	if (rv == 1) {
912156230Smux		lprintf(1, " SetAttrs %s\n", fup->coname);
913156230Smux		fattr_free(fileattr);
914156230Smux		fileattr = fattr_frompath(path, FATTR_NOFOLLOW);
915156230Smux		if (fileattr == NULL) {
916156230Smux			/* We're being very unlucky. */
917156230Smux			error = status_delete(st, name, 0);
918156230Smux			if (error) {
919156230Smux				up->errmsg = status_errmsg(st);
920156230Smux				return (UPDATER_ERR_MSG);
921156230Smux			}
922156230Smux			return (0);
923156230Smux		}
924156230Smux	}
925156230Smux
926156230Smux	fattr_maskout(fileattr, FA_COIGNORE);
927156230Smux
928156230Smux	sr.sr_type = SR_CHECKOUTLIVE;
929156230Smux	sr.sr_file = name;
930156230Smux	sr.sr_tag = tag;
931156230Smux	sr.sr_date = date;
932156230Smux	sr.sr_revnum = revnum;
933156230Smux	sr.sr_revdate = revdate;
934156230Smux	sr.sr_clientattr = fileattr;
935156230Smux	sr.sr_serverattr = rcsattr;
936156230Smux
937156230Smux	error = status_put(st, &sr);
938156230Smux	fattr_free(fileattr);
939156230Smux	if (error) {
940156230Smux		up->errmsg = status_errmsg(st);
941156230Smux		return (UPDATER_ERR_MSG);
942156230Smux	}
943156230Smux	return (0);
944156230Smux}
945156230Smux
946156230Smuxstatic int
947156701Smuxupdater_updatefile(struct updater *up, struct file_update *fup,
948156701Smux    const char *md5, int isfixup)
949156230Smux{
950156230Smux	struct coll *coll;
951156230Smux	struct status *st;
952156230Smux	struct statusrec *sr;
953156230Smux	struct fattr *fileattr;
954156230Smux	int error, rv;
955156230Smux
956156230Smux	coll = fup->coll;
957156230Smux	sr = &fup->srbuf;
958156230Smux	st = fup->st;
959156230Smux
960156701Smux	if (strcmp(fup->wantmd5, md5) != 0) {
961156701Smux		if (isfixup) {
962156701Smux			lprintf(-1, "%s: Checksum mismatch -- "
963156701Smux			    "file not updated\n", fup->destpath);
964156701Smux		} else {
965156701Smux			lprintf(-1, "%s: Checksum mismatch -- "
966156701Smux			    "will transfer entire file\n", fup->destpath);
967156701Smux			fixups_put(up->config->fixups, fup->coll, sr->sr_file);
968156701Smux		}
969156701Smux		if (coll->co_options & CO_KEEPBADFILES)
970156701Smux			lprintf(-1, "Bad version saved in %s\n", fup->temppath);
971156701Smux		else
972156701Smux			updater_deletefile(fup->temppath);
973156701Smux		return (0);
974156701Smux	}
975156701Smux
976156230Smux	fattr_umask(sr->sr_clientattr, coll->co_umask);
977156701Smux	rv = fattr_install(sr->sr_clientattr, fup->destpath, fup->temppath);
978156230Smux	if (rv == -1) {
979156701Smux		xasprintf(&up->errmsg, "Cannot install \"%s\" to \"%s\": %s",
980156701Smux		    fup->temppath, fup->destpath, strerror(errno));
981156230Smux		return (UPDATER_ERR_MSG);
982156230Smux	}
983156230Smux
984156230Smux	/* XXX Executes */
985156230Smux	/*
986156230Smux	 * We weren't necessarily able to set all the file attributes to the
987156230Smux	 * desired values, and any executes may have altered the attributes.
988156230Smux	 * To make sure we record the actual attribute values, we fetch
989156230Smux	 * them from the file.
990156230Smux	 *
991156230Smux	 * However, we preserve the link count as received from the
992156230Smux	 * server.  This is important for preserving hard links in mirror
993156230Smux	 * mode.
994156230Smux	 */
995156701Smux	fileattr = fattr_frompath(fup->destpath, FATTR_NOFOLLOW);
996156230Smux	if (fileattr == NULL) {
997156701Smux		xasprintf(&up->errmsg, "Cannot stat \"%s\": %s", fup->destpath,
998156230Smux		    strerror(errno));
999156230Smux		return (UPDATER_ERR_MSG);
1000156230Smux	}
1001156230Smux	fattr_override(fileattr, sr->sr_clientattr, FA_LINKCOUNT);
1002156230Smux	fattr_free(sr->sr_clientattr);
1003156230Smux	sr->sr_clientattr = fileattr;
1004156230Smux
1005156230Smux	/*
1006156230Smux	 * To save space, don't write out the device and inode unless
1007156230Smux	 * the link count is greater than 1.  These attributes are used
1008156230Smux	 * only for detecting hard links.  If the link count is 1 then we
1009156230Smux	 * know there aren't any hard links.
1010156230Smux	 */
1011156230Smux	if (!(fattr_getmask(sr->sr_clientattr) & FA_LINKCOUNT) ||
1012156230Smux	    fattr_getlinkcount(sr->sr_clientattr) <= 1)
1013156230Smux		fattr_maskout(sr->sr_clientattr, FA_DEV | FA_INODE);
1014156230Smux
1015156230Smux	if (coll->co_options & CO_CHECKOUTMODE)
1016156230Smux		fattr_maskout(sr->sr_clientattr, FA_COIGNORE);
1017156230Smux
1018156230Smux	error = status_put(st, sr);
1019156230Smux	if (error) {
1020156230Smux		up->errmsg = status_errmsg(st);
1021156230Smux		return (UPDATER_ERR_MSG);
1022156230Smux	}
1023156230Smux	return (0);
1024156230Smux}
1025156230Smux
1026186781Slulf/*
1027186781Slulf * Update attributes of a directory.
1028186781Slulf */
1029156230Smuxstatic int
1030186781Slulfupdater_setdirattrs(struct updater *up, struct coll *coll,
1031186781Slulf    struct file_update *fup, char *name, char *attr)
1032186781Slulf{
1033186781Slulf	struct statusrec *sr;
1034186781Slulf	struct fattr *fa;
1035186781Slulf	int error, rv;
1036186781Slulf
1037186781Slulf	sr = &fup->srbuf;
1038186781Slulf	sr->sr_type = SR_DIRUP;
1039186781Slulf	sr->sr_file = xstrdup(name);
1040186781Slulf	sr->sr_clientattr = fattr_decode(attr);
1041186781Slulf	sr->sr_serverattr = fattr_decode(attr);
1042186781Slulf	if (sr->sr_clientattr == NULL || sr->sr_serverattr == NULL)
1043186781Slulf		return (UPDATER_ERR_PROTO);
1044186781Slulf	fattr_mergedefault(sr->sr_clientattr);
1045186781Slulf	fattr_umask(sr->sr_clientattr, coll->co_umask);
1046186781Slulf	rv = fattr_install(sr->sr_clientattr, fup->destpath, NULL);
1047186781Slulf	lprintf(1, " SetAttrs %s\n", name);
1048186781Slulf	if (rv == -1) {
1049186781Slulf		xasprintf(&up->errmsg, "Cannot install \"%s\" to \"%s\": %s",
1050186781Slulf		    fup->temppath, fup->destpath, strerror(errno));
1051186781Slulf		return (UPDATER_ERR_MSG);
1052186781Slulf	}
1053186781Slulf	/*
1054186781Slulf	 * Now, make sure they were set and record what was set in the status
1055186781Slulf	 * file.
1056186781Slulf	 */
1057186781Slulf	fa = fattr_frompath(fup->destpath, FATTR_NOFOLLOW);
1058186781Slulf	if (fa == NULL) {
1059186781Slulf		xasprintf(&up->errmsg, "Cannot open \%s\": %s", fup->destpath,
1060186781Slulf		    strerror(errno));
1061186781Slulf		return (UPDATER_ERR_MSG);
1062186781Slulf	}
1063186781Slulf	fattr_free(sr->sr_clientattr);
1064186781Slulf	fattr_maskout(fa, FA_FLAGS);
1065186781Slulf	sr->sr_clientattr = fa;
1066186781Slulf	error = status_put(fup->st, sr);
1067186781Slulf	if (error) {
1068186781Slulf		up->errmsg = status_errmsg(fup->st);
1069186781Slulf		return (UPDATER_ERR_MSG);
1070186781Slulf	}
1071186781Slulf
1072186781Slulf	return (0);
1073186781Slulf}
1074186781Slulf
1075186781Slulfstatic int
1076156230Smuxupdater_diff(struct updater *up, struct file_update *fup)
1077156230Smux{
1078156230Smux	char md5[MD5_DIGEST_SIZE];
1079156230Smux	struct coll *coll;
1080156230Smux	struct statusrec *sr;
1081156230Smux	struct fattr *fa, *tmp;
1082156230Smux	char *author, *path, *revnum, *revdate;
1083156701Smux	char *line, *cmd;
1084156230Smux	int error;
1085156230Smux
1086156230Smux	coll = fup->coll;
1087156230Smux	sr = &fup->srbuf;
1088156230Smux	path = fup->destpath;
1089156230Smux
1090156230Smux	lprintf(1, " Edit %s\n", fup->coname);
1091156230Smux	while ((line = stream_getln(up->rd, NULL)) != NULL) {
1092156230Smux		if (strcmp(line, ".") == 0)
1093156230Smux			break;
1094156230Smux		cmd = proto_get_ascii(&line);
1095156701Smux		if (cmd == NULL || strcmp(cmd, "D") != 0)
1096156701Smux			return (UPDATER_ERR_PROTO);
1097156230Smux		revnum = proto_get_ascii(&line);
1098156230Smux		proto_get_ascii(&line); /* XXX - diffbase */
1099156230Smux		revdate = proto_get_ascii(&line);
1100156230Smux		author = proto_get_ascii(&line);
1101156701Smux		if (author == NULL || line != NULL)
1102156701Smux			return (UPDATER_ERR_PROTO);
1103156230Smux		if (sr->sr_revnum != NULL)
1104156230Smux			free(sr->sr_revnum);
1105156230Smux		if (sr->sr_revdate != NULL)
1106156230Smux			free(sr->sr_revdate);
1107156230Smux		if (fup->author != NULL)
1108156230Smux			free(fup->author);
1109156230Smux		sr->sr_revnum = xstrdup(revnum);
1110156230Smux		sr->sr_revdate = xstrdup(revdate);
1111156230Smux		fup->author = xstrdup(author);
1112156230Smux		if (fup->orig == NULL) {
1113156230Smux			/* First patch, the "origin" file is the one we have. */
1114156230Smux			fup->orig = stream_open_file(path, O_RDONLY);
1115156230Smux			if (fup->orig == NULL) {
1116156230Smux				xasprintf(&up->errmsg, "%s: Cannot open: %s",
1117156230Smux				    path, strerror(errno));
1118156701Smux				return (UPDATER_ERR_MSG);
1119156230Smux			}
1120156230Smux		} else {
1121156230Smux			/* Subsequent patches. */
1122156230Smux			stream_close(fup->orig);
1123156230Smux			fup->orig = fup->to;
1124156230Smux			stream_rewind(fup->orig);
1125156701Smux			unlink(fup->temppath);
1126156701Smux			free(fup->temppath);
1127156701Smux			fup->temppath = tempname(path);
1128156230Smux		}
1129156701Smux		fup->to = stream_open_file(fup->temppath,
1130156701Smux		    O_RDWR | O_CREAT | O_TRUNC, 0600);
1131156230Smux		if (fup->to == NULL) {
1132156230Smux			xasprintf(&up->errmsg, "%s: Cannot open: %s",
1133156701Smux			    fup->temppath, strerror(errno));
1134156701Smux			return (UPDATER_ERR_MSG);
1135156230Smux		}
1136156230Smux		lprintf(2, "  Add delta %s %s %s\n", sr->sr_revnum,
1137156230Smux		    sr->sr_revdate, fup->author);
1138156230Smux		error = updater_diff_batch(up, fup);
1139156230Smux		if (error)
1140156701Smux			return (error);
1141156230Smux	}
1142156701Smux	if (line == NULL)
1143156701Smux		return (UPDATER_ERR_READ);
1144156230Smux
1145156230Smux	fa = fattr_frompath(path, FATTR_FOLLOW);
1146156230Smux	tmp = fattr_forcheckout(sr->sr_serverattr, coll->co_umask);
1147156230Smux	fattr_override(fa, tmp, FA_MASK);
1148156230Smux	fattr_free(tmp);
1149156230Smux	fattr_maskout(fa, FA_MODTIME);
1150156230Smux	sr->sr_clientattr = fa;
1151156230Smux
1152156701Smux	if (MD5_File(fup->temppath, md5) == -1) {
1153156230Smux		xasprintf(&up->errmsg,
1154156230Smux		    "Cannot calculate checksum for \"%s\": %s",
1155156230Smux		    path, strerror(errno));
1156156701Smux		return (UPDATER_ERR_MSG);
1157156230Smux	}
1158156701Smux	error = updater_updatefile(up, fup, md5, 0);
1159156230Smux	return (error);
1160156230Smux}
1161156230Smux
1162186781Slulf/*
1163186781Slulf * Edit a file and add delta.
1164186781Slulf */
1165156230Smuxstatic int
1166156230Smuxupdater_diff_batch(struct updater *up, struct file_update *fup)
1167156230Smux{
1168156230Smux	struct stream *rd;
1169156230Smux	char *cmd, *line, *state, *tok;
1170156230Smux	int error;
1171156230Smux
1172156230Smux	state = NULL;
1173156230Smux	rd = up->rd;
1174156230Smux	while ((line = stream_getln(rd, NULL)) != NULL) {
1175156230Smux		if (strcmp(line, ".") == 0)
1176156230Smux			break;
1177156230Smux		cmd = proto_get_ascii(&line);
1178156230Smux		if (cmd == NULL || strlen(cmd) != 1) {
1179156230Smux			error = UPDATER_ERR_PROTO;
1180156230Smux			goto bad;
1181156230Smux		}
1182156230Smux		switch (cmd[0]) {
1183156230Smux		case 'L':
1184156230Smux			line = stream_getln(rd, NULL);
1185156230Smux			/* XXX - We're just eating the log for now. */
1186156230Smux			while (line != NULL && strcmp(line, ".") != 0 &&
1187156230Smux			    strcmp(line, ".+") != 0)
1188156230Smux				line = stream_getln(rd, NULL);
1189156230Smux			if (line == NULL) {
1190156230Smux				error = UPDATER_ERR_READ;
1191156230Smux				goto bad;
1192156230Smux			}
1193156230Smux			break;
1194156230Smux		case 'S':
1195156230Smux			tok = proto_get_ascii(&line);
1196156230Smux			if (tok == NULL || line != NULL) {
1197156230Smux				error = UPDATER_ERR_PROTO;
1198156230Smux				goto bad;
1199156230Smux			}
1200156230Smux			if (state != NULL)
1201156230Smux				free(state);
1202156230Smux			state = xstrdup(tok);
1203156230Smux			break;
1204156230Smux		case 'T':
1205156230Smux			error = updater_diff_apply(up, fup, state);
1206156230Smux			if (error)
1207156230Smux				goto bad;
1208156230Smux			break;
1209156230Smux		default:
1210156230Smux			error = UPDATER_ERR_PROTO;
1211156230Smux			goto bad;
1212156230Smux		}
1213156230Smux	}
1214156230Smux	if (line == NULL) {
1215156230Smux		error = UPDATER_ERR_READ;
1216156230Smux		goto bad;
1217156230Smux	}
1218156230Smux	if (state != NULL)
1219156230Smux		free(state);
1220156230Smux	return (0);
1221156230Smuxbad:
1222156230Smux	if (state != NULL)
1223156230Smux		free(state);
1224156230Smux	return (error);
1225156230Smux}
1226156230Smux
1227156230Smuxint
1228156230Smuxupdater_diff_apply(struct updater *up, struct file_update *fup, char *state)
1229156230Smux{
1230156230Smux	struct diffinfo dibuf, *di;
1231156230Smux	struct coll *coll;
1232156230Smux	struct statusrec *sr;
1233156230Smux	int error;
1234156230Smux
1235156230Smux	coll = fup->coll;
1236156230Smux	sr = &fup->srbuf;
1237156230Smux	di = &dibuf;
1238156230Smux
1239156230Smux	di->di_rcsfile = sr->sr_file;
1240156230Smux	di->di_cvsroot = coll->co_cvsroot;
1241156230Smux	di->di_revnum = sr->sr_revnum;
1242156230Smux	di->di_revdate = sr->sr_revdate;
1243156230Smux	di->di_author = fup->author;
1244156230Smux	di->di_tag = sr->sr_tag;
1245156230Smux	di->di_state = state;
1246156230Smux	di->di_expand = fup->expand;
1247156230Smux
1248186781Slulf	error = diff_apply(up->rd, fup->orig, fup->to, coll->co_keyword, di, 1);
1249156230Smux	if (error) {
1250156230Smux		/* XXX Bad error message */
1251156230Smux		xasprintf(&up->errmsg, "Bad diff from server");
1252156230Smux		return (UPDATER_ERR_MSG);
1253156230Smux	}
1254156230Smux	return (0);
1255156230Smux}
1256156230Smux
1257186781Slulf/* Update or create a node. */
1258156230Smuxstatic int
1259186781Slulfupdater_updatenode(struct updater *up, struct coll *coll,
1260186781Slulf    struct file_update *fup, char *name, char *attr)
1261186781Slulf{
1262186781Slulf	struct fattr *fa, *fileattr;
1263186781Slulf	struct status *st;
1264186781Slulf	struct statusrec *sr;
1265186781Slulf	int error, rv;
1266186781Slulf
1267186781Slulf	sr = &fup->srbuf;
1268186781Slulf	st = fup->st;
1269186781Slulf	fa = fattr_decode(attr);
1270186781Slulf
1271186781Slulf	if (fattr_type(fa) == FT_SYMLINK) {
1272186781Slulf		lprintf(1, " Symlink %s -> %s\n", name,
1273186781Slulf		    fattr_getlinktarget(fa));
1274186781Slulf	} else {
1275186781Slulf		lprintf(1, " Mknod %s\n", name);
1276186781Slulf	}
1277186781Slulf
1278186781Slulf	/* Create directory. */
1279186781Slulf	error = mkdirhier(fup->destpath, coll->co_umask);
1280186781Slulf	if (error)
1281186781Slulf		return (UPDATER_ERR_PROTO);
1282186781Slulf
1283186781Slulf	/* If it does not exist, create it. */
1284186781Slulf	if (access(fup->destpath, F_OK) != 0)
1285186781Slulf		fattr_makenode(fa, fup->destpath);
1286186781Slulf
1287186781Slulf	/*
1288186781Slulf	 * Coming from attic? I don't think this is a problem since we have
1289186781Slulf	 * determined attic before we call this function (Look at UpdateNode in
1290186781Slulf	 * cvsup).
1291186781Slulf	 */
1292186781Slulf	fattr_umask(fa, coll->co_umask);
1293186781Slulf	rv = fattr_install(fa, fup->destpath, fup->temppath);
1294186781Slulf	if (rv == -1) {
1295186781Slulf		xasprintf(&up->errmsg, "Cannot update attributes on "
1296186781Slulf	    "\"%s\": %s", fup->destpath, strerror(errno));
1297186781Slulf		return (UPDATER_ERR_MSG);
1298186781Slulf	}
1299186781Slulf	/*
1300186781Slulf	 * XXX: Executes not implemented. Have not encountered much use for it
1301186781Slulf	 * yet.
1302186781Slulf	 */
1303186781Slulf	/*
1304186781Slulf	 * We weren't necessarily able to set all the file attributes to the
1305186781Slulf	 * desired values, and any executes may have altered the attributes.
1306186781Slulf	 * To make sure we record the actual attribute values, we fetch
1307186781Slulf	 * them from the file.
1308186781Slulf	 *
1309186781Slulf	 * However, we preserve the link count as received from the
1310186781Slulf	 * server.  This is important for preserving hard links in mirror
1311186781Slulf	 * mode.
1312186781Slulf	 */
1313186781Slulf	fileattr = fattr_frompath(fup->destpath, FATTR_NOFOLLOW);
1314186781Slulf	if (fileattr == NULL) {
1315186781Slulf		xasprintf(&up->errmsg, "Cannot stat \"%s\": %s", fup->destpath,
1316186781Slulf		    strerror(errno));
1317186781Slulf		return (UPDATER_ERR_MSG);
1318186781Slulf	}
1319186781Slulf	fattr_override(fileattr, sr->sr_clientattr, FA_LINKCOUNT);
1320186781Slulf	fattr_free(sr->sr_clientattr);
1321186781Slulf	sr->sr_clientattr = fileattr;
1322186781Slulf
1323186781Slulf	/*
1324186781Slulf	 * To save space, don't write out the device and inode unless
1325186781Slulf	 * the link count is greater than 1.  These attributes are used
1326186781Slulf	 * only for detecting hard links.  If the link count is 1 then we
1327186781Slulf	 * know there aren't any hard links.
1328186781Slulf	 */
1329186781Slulf	if (!(fattr_getmask(sr->sr_clientattr) & FA_LINKCOUNT) ||
1330186781Slulf	    fattr_getlinkcount(sr->sr_clientattr) <= 1)
1331186781Slulf		fattr_maskout(sr->sr_clientattr, FA_DEV | FA_INODE);
1332186781Slulf
1333186781Slulf	/* If it is a symlink, write only out it's path. */
1334186781Slulf	if (fattr_type(fa) == FT_SYMLINK) {
1335186781Slulf		fattr_maskout(sr->sr_clientattr, ~(FA_FILETYPE |
1336186781Slulf		    FA_LINKTARGET));
1337186781Slulf	}
1338186781Slulf	fattr_maskout(sr->sr_clientattr, FA_FLAGS);
1339186781Slulf	error = status_put(st, sr);
1340186781Slulf	if (error) {
1341186781Slulf		up->errmsg = status_errmsg(st);
1342186781Slulf		return (UPDATER_ERR_MSG);
1343186781Slulf	}
1344186781Slulf	fattr_free(fa);
1345186781Slulf
1346186781Slulf	return (0);
1347186781Slulf}
1348186781Slulf
1349186781Slulf/*
1350186781Slulf * Fetches a new file in CVS mode.
1351186781Slulf */
1352186781Slulfstatic int
1353186781Slulfupdater_addfile(struct updater *up, struct file_update *fup, char *attr,
1354186781Slulf    int isfixup)
1355186781Slulf{
1356186781Slulf	struct coll *coll;
1357186781Slulf	struct stream *to;
1358186781Slulf	struct statusrec *sr;
1359186781Slulf	struct fattr *fa;
1360186781Slulf	char buf[BUFSIZE];
1361186781Slulf	char md5[MD5_DIGEST_SIZE];
1362186781Slulf	ssize_t nread;
1363186781Slulf	off_t fsize, remains;
1364186781Slulf	char *cmd, *line, *path;
1365186781Slulf	int error;
1366186781Slulf
1367186781Slulf	coll = fup->coll;
1368186781Slulf	path = fup->destpath;
1369186781Slulf	sr = &fup->srbuf;
1370186781Slulf	fa = fattr_decode(attr);
1371186781Slulf	fsize = fattr_filesize(fa);
1372186781Slulf
1373186781Slulf	error = mkdirhier(path, coll->co_umask);
1374186781Slulf	if (error)
1375186781Slulf		return (UPDATER_ERR_PROTO);
1376186781Slulf	to = stream_open_file(fup->temppath, O_WRONLY | O_CREAT | O_TRUNC, 0755);
1377186781Slulf	if (to == NULL) {
1378186781Slulf		xasprintf(&up->errmsg, "%s: Cannot create: %s",
1379186781Slulf		    fup->temppath, strerror(errno));
1380186781Slulf		return (UPDATER_ERR_MSG);
1381186781Slulf	}
1382186781Slulf	stream_filter_start(to, STREAM_FILTER_MD5, md5);
1383186781Slulf	remains = fsize;
1384186781Slulf	do {
1385186781Slulf		nread = stream_read(up->rd, buf, (BUFSIZE > remains ?
1386186781Slulf		    remains : BUFSIZE));
1387190422Slulf		if (nread == -1)
1388190422Slulf			return (UPDATER_ERR_PROTO);
1389186781Slulf		remains -= nread;
1390190422Slulf		if (stream_write(to, buf, nread) == -1)
1391190422Slulf			goto bad;
1392186781Slulf	} while (remains > 0);
1393186781Slulf	stream_close(to);
1394186781Slulf	line = stream_getln(up->rd, NULL);
1395186781Slulf	if (line == NULL)
1396186781Slulf		return (UPDATER_ERR_PROTO);
1397186781Slulf	/* Check for EOF. */
1398186781Slulf	if (!(*line == '.' || (strncmp(line, ".<", 2) != 0)))
1399186781Slulf		return (UPDATER_ERR_PROTO);
1400186781Slulf	line = stream_getln(up->rd, NULL);
1401186781Slulf	if (line == NULL)
1402186781Slulf		return (UPDATER_ERR_PROTO);
1403186781Slulf
1404186781Slulf	cmd = proto_get_ascii(&line);
1405186781Slulf	fup->wantmd5 = proto_get_ascii(&line);
1406186781Slulf	if (fup->wantmd5 == NULL || line != NULL || strcmp(cmd, "5") != 0)
1407186781Slulf		return (UPDATER_ERR_PROTO);
1408186781Slulf
1409186781Slulf	sr->sr_clientattr = fattr_frompath(fup->temppath, FATTR_NOFOLLOW);
1410186781Slulf	if (sr->sr_clientattr == NULL)
1411186781Slulf		return (UPDATER_ERR_PROTO);
1412186781Slulf	fattr_override(sr->sr_clientattr, sr->sr_serverattr,
1413186781Slulf	    FA_MODTIME | FA_MASK);
1414186781Slulf	error = updater_updatefile(up, fup, md5, isfixup);
1415186781Slulf	fup->wantmd5 = NULL;	/* So that it doesn't get freed. */
1416190422Slulf	return (error);
1417190422Slulfbad:
1418190422Slulf	xasprintf(&up->errmsg, "%s: Cannot write: %s", fup->temppath,
1419190422Slulf	    strerror(errno));
1420190422Slulf	return (UPDATER_ERR_MSG);
1421186781Slulf}
1422186781Slulf
1423186781Slulfstatic int
1424156230Smuxupdater_checkout(struct updater *up, struct file_update *fup, int isfixup)
1425156230Smux{
1426156230Smux	char md5[MD5_DIGEST_SIZE];
1427156230Smux	struct statusrec *sr;
1428156230Smux	struct coll *coll;
1429156230Smux	struct stream *to;
1430186781Slulf	ssize_t nbytes;
1431186781Slulf	size_t size;
1432156230Smux	char *cmd, *path, *line;
1433156230Smux	int error, first;
1434156230Smux
1435156230Smux	coll = fup->coll;
1436156230Smux	sr = &fup->srbuf;
1437156230Smux	path = fup->destpath;
1438156230Smux
1439156230Smux	if (isfixup)
1440156230Smux		lprintf(1, " Fixup %s\n", fup->coname);
1441156230Smux	else
1442156230Smux		lprintf(1, " Checkout %s\n", fup->coname);
1443156230Smux	error = mkdirhier(path, coll->co_umask);
1444156230Smux	if (error) {
1445156230Smux		xasprintf(&up->errmsg,
1446156230Smux		    "Cannot create directories leading to \"%s\": %s",
1447156230Smux		    path, strerror(errno));
1448156230Smux		return (UPDATER_ERR_MSG);
1449156230Smux	}
1450156230Smux
1451156701Smux	to = stream_open_file(fup->temppath,
1452156701Smux	    O_WRONLY | O_CREAT | O_TRUNC, 0600);
1453156230Smux	if (to == NULL) {
1454156230Smux		xasprintf(&up->errmsg, "%s: Cannot create: %s",
1455156701Smux		    fup->temppath, strerror(errno));
1456156230Smux		return (UPDATER_ERR_MSG);
1457156230Smux	}
1458156230Smux	stream_filter_start(to, STREAM_FILTER_MD5, md5);
1459156230Smux	line = stream_getln(up->rd, &size);
1460156230Smux	first = 1;
1461156230Smux	while (line != NULL) {
1462156230Smux		if (line[size - 1] == '\n')
1463156230Smux			size--;
1464156230Smux	       	if ((size == 1 && *line == '.') ||
1465156230Smux		    (size == 2 && memcmp(line, ".+", 2) == 0))
1466156230Smux			break;
1467156230Smux		if (size >= 2 && memcmp(line, "..", 2) == 0) {
1468156230Smux			size--;
1469156230Smux			line++;
1470156230Smux		}
1471156230Smux		if (!first) {
1472156230Smux			nbytes = stream_write(to, "\n", 1);
1473156230Smux			if (nbytes == -1)
1474156230Smux				goto bad;
1475156230Smux		}
1476190422Slulf		nbytes = stream_write(to, line, size);
1477190422Slulf		if (nbytes == -1)
1478190422Slulf			goto bad;
1479156230Smux		line = stream_getln(up->rd, &size);
1480156230Smux		first = 0;
1481156230Smux	}
1482156230Smux	if (line == NULL) {
1483156230Smux		stream_close(to);
1484156230Smux		return (UPDATER_ERR_READ);
1485156230Smux	}
1486156230Smux	if (size == 1 && *line == '.') {
1487156230Smux		nbytes = stream_write(to, "\n", 1);
1488156230Smux		if (nbytes == -1)
1489156230Smux			goto bad;
1490156230Smux	}
1491156230Smux	stream_close(to);
1492156230Smux	/* Get the checksum line. */
1493156230Smux	line = stream_getln(up->rd, NULL);
1494156230Smux	if (line == NULL)
1495156230Smux		return (UPDATER_ERR_READ);
1496156230Smux	cmd = proto_get_ascii(&line);
1497156230Smux	fup->wantmd5 = proto_get_ascii(&line);
1498156230Smux	if (fup->wantmd5 == NULL || line != NULL || strcmp(cmd, "5") != 0)
1499156230Smux		return (UPDATER_ERR_PROTO);
1500156701Smux	error = updater_updatefile(up, fup, md5, isfixup);
1501156230Smux	fup->wantmd5 = NULL;	/* So that it doesn't get freed. */
1502156230Smux	if (error)
1503156230Smux		return (error);
1504156230Smux	return (0);
1505156230Smuxbad:
1506156701Smux	xasprintf(&up->errmsg, "%s: Cannot write: %s", fup->temppath,
1507156701Smux	    strerror(errno));
1508156230Smux	return (UPDATER_ERR_MSG);
1509156230Smux}
1510156230Smux
1511156230Smux/*
1512156230Smux * Remove all empty directories below file.
1513156230Smux * This function will trash the path passed to it.
1514156230Smux */
1515156230Smuxstatic void
1516156230Smuxupdater_prunedirs(char *base, char *file)
1517156230Smux{
1518156230Smux	char *cp;
1519156230Smux	int error;
1520156230Smux
1521156230Smux	while ((cp = strrchr(file, '/')) != NULL) {
1522156230Smux		*cp = '\0';
1523156230Smux		if (strcmp(base, file) == 0)
1524156230Smux			return;
1525156230Smux		error = rmdir(file);
1526156230Smux		if (error)
1527156230Smux			return;
1528156230Smux	}
1529156230Smux}
1530186781Slulf
1531186781Slulf/*
1532186781Slulf * Edit an RCS file.
1533186781Slulf */
1534186781Slulfstatic int
1535186781Slulfupdater_rcsedit(struct updater *up, struct file_update *fup, char *name,
1536186781Slulf    char *rcsopt)
1537186781Slulf{
1538186781Slulf	struct coll *coll;
1539186781Slulf	struct stream *dest;
1540186781Slulf	struct statusrec *sr;
1541186781Slulf	struct status *st;
1542186781Slulf	struct rcsfile *rf;
1543186781Slulf	struct fattr *oldfattr;
1544186781Slulf	char md5[MD5_DIGEST_SIZE];
1545186781Slulf	char *branch, *cmd, *expand, *line, *path, *revnum, *tag, *temppath;
1546186781Slulf	int error;
1547186781Slulf
1548186781Slulf	coll = fup->coll;
1549186781Slulf	sr = &fup->srbuf;
1550186781Slulf	st = fup->st;
1551186781Slulf	temppath = fup->temppath;
1552186781Slulf	path = fup->origpath != NULL ? fup->origpath : fup->destpath;
1553186781Slulf	error = 0;
1554186781Slulf
1555186781Slulf	/* If the path is new, we must create the Attic dir if needed. */
1556186781Slulf	if (fup->origpath != NULL) {
1557186781Slulf		error = mkdirhier(fup->destpath, coll->co_umask);
1558186781Slulf		if (error) {
1559186781Slulf			xasprintf(&up->errmsg, "Unable to create Attic dir for "
1560186781Slulf			    "%s\n", fup->origpath);
1561186781Slulf			return (UPDATER_ERR_MSG);
1562186781Slulf		}
1563186781Slulf	}
1564186781Slulf	/*
1565186781Slulf	 * XXX: we could avoid parsing overhead if we're reading ahead before we
1566186781Slulf	 * parse the file.
1567186781Slulf	 */
1568186781Slulf	oldfattr = fattr_frompath(path, FATTR_NOFOLLOW);
1569186781Slulf	if (oldfattr == NULL) {
1570186781Slulf		xasprintf(&up->errmsg, "%s: Cannot get attributes: %s", path,
1571186781Slulf		    strerror(errno));
1572186781Slulf		return (UPDATER_ERR_MSG);
1573186781Slulf	}
1574186781Slulf	fattr_merge(sr->sr_serverattr, oldfattr);
1575186781Slulf	rf = NULL;
1576186781Slulf
1577186781Slulf	/* Macro for making touching an RCS file faster. */
1578186781Slulf#define UPDATER_OPENRCS(rf, up, path, name, cvsroot, tag) do {		\
1579186781Slulf	if ((rf) == NULL) {						\
1580186781Slulf		lprintf(1, " Edit %s", fup->coname);			\
1581186781Slulf		if (fup->attic)						\
1582186781Slulf			lprintf(1, " -> Attic");			\
1583186781Slulf		lprintf(1, "\n");					\
1584186781Slulf		(rf) = rcsfile_frompath((path), (name), (cvsroot),	\
1585186781Slulf		    (tag), 0);						\
1586186781Slulf		if ((rf) == NULL) {					\
1587186781Slulf			xasprintf(&(up)->errmsg,			\
1588186781Slulf			    "Error reading rcsfile %s\n", (name));	\
1589186781Slulf			return (UPDATER_ERR_MSG);			\
1590186781Slulf		}							\
1591186781Slulf	}								\
1592186781Slulf} while (0)
1593186781Slulf
1594186781Slulf	while ((line = stream_getln(up->rd, NULL)) != NULL) {
1595186781Slulf		if (strcmp(line, ".") == 0)
1596186781Slulf			break;
1597186781Slulf		cmd = proto_get_ascii(&line);
1598186781Slulf		if (cmd == NULL) {
1599186781Slulf			lprintf(-1, "Error editing %s\n", name);
1600186781Slulf			return (UPDATER_ERR_PROTO);
1601186781Slulf		}
1602186781Slulf		switch(cmd[0]) {
1603186781Slulf			case 'B':
1604186781Slulf				branch = proto_get_ascii(&line);
1605186781Slulf				if (branch == NULL || line != NULL)
1606186781Slulf					return (UPDATER_ERR_PROTO);
1607186781Slulf				UPDATER_OPENRCS(rf, up, path, name,
1608186781Slulf				    coll->co_cvsroot, coll->co_tag);
1609186781Slulf				break;
1610186781Slulf			case 'b':
1611186781Slulf				UPDATER_OPENRCS(rf, up, path, name,
1612186781Slulf				    coll->co_cvsroot, coll->co_tag);
1613186781Slulf				rcsfile_setval(rf, RCSFILE_BRANCH, NULL);
1614186781Slulf				break;
1615186781Slulf			case 'D':
1616186781Slulf				UPDATER_OPENRCS(rf, up, path, name,
1617186781Slulf				    coll->co_cvsroot, coll->co_tag);
1618186781Slulf				error = updater_addelta(rf, up->rd, line);
1619186781Slulf				if (error)
1620186781Slulf					return (error);
1621186781Slulf				break;
1622186781Slulf			case 'd':
1623186781Slulf				revnum = proto_get_ascii(&line);
1624186781Slulf				if (revnum == NULL || line != NULL)
1625186781Slulf					return (UPDATER_ERR_PROTO);
1626186781Slulf				UPDATER_OPENRCS(rf, up, path, name,
1627186781Slulf				    coll->co_cvsroot, coll->co_tag);
1628186781Slulf				rcsfile_deleterev(rf, revnum);
1629186781Slulf				break;
1630186781Slulf			case 'E':
1631186781Slulf				expand = proto_get_ascii(&line);
1632186781Slulf				if (expand == NULL || line != NULL)
1633186781Slulf					return (UPDATER_ERR_PROTO);
1634186781Slulf				UPDATER_OPENRCS(rf, up, path, name,
1635186781Slulf				    coll->co_cvsroot, coll->co_tag);
1636186781Slulf				rcsfile_setval(rf, RCSFILE_EXPAND, expand);
1637186781Slulf				break;
1638186781Slulf			case 'T':
1639186781Slulf				tag = proto_get_ascii(&line);
1640186781Slulf				revnum = proto_get_ascii(&line);
1641186781Slulf				if (tag == NULL || revnum == NULL ||
1642186781Slulf				    line != NULL)
1643186781Slulf					return (UPDATER_ERR_PROTO);
1644186781Slulf				UPDATER_OPENRCS(rf, up, path, name,
1645186781Slulf				    coll->co_cvsroot, coll->co_tag);
1646186781Slulf				rcsfile_addtag(rf, tag, revnum);
1647186781Slulf				break;
1648186781Slulf			case 't':
1649186781Slulf				tag = proto_get_ascii(&line);
1650186781Slulf				revnum = proto_get_ascii(&line);
1651186781Slulf				if (tag == NULL || revnum == NULL ||
1652186781Slulf				    line != NULL)
1653186781Slulf					return (UPDATER_ERR_PROTO);
1654186781Slulf				UPDATER_OPENRCS(rf, up, path, name,
1655186781Slulf				    coll->co_cvsroot, coll->co_tag);
1656186781Slulf				rcsfile_deletetag(rf, tag, revnum);
1657186781Slulf				break;
1658186781Slulf			default:
1659186781Slulf				return (UPDATER_ERR_PROTO);
1660186781Slulf		}
1661186781Slulf	}
1662186781Slulf
1663186781Slulf	if (rf == NULL) {
1664186781Slulf		fattr_maskout(oldfattr, ~FA_MODTIME);
1665188405Slulf		if (fattr_equal(oldfattr, sr->sr_serverattr))
1666186781Slulf		 	lprintf(1, " SetAttrs %s", fup->coname);
1667186781Slulf		else
1668186781Slulf			lprintf(1, " Touch %s", fup->coname);
1669188405Slulf		/* Install new attributes. */
1670190406Slulf		fattr_umask(sr->sr_serverattr, coll->co_umask);
1671188405Slulf		fattr_install(sr->sr_serverattr, fup->destpath, NULL);
1672186781Slulf		if (fup->attic)
1673186781Slulf			lprintf(1, " -> Attic");
1674186781Slulf		lprintf(1, "\n");
1675186781Slulf		fattr_free(oldfattr);
1676186781Slulf		goto finish;
1677186781Slulf	}
1678186781Slulf
1679186781Slulf	/* Write and rename temp file. */
1680186781Slulf	dest = stream_open_file(fup->temppath,
1681186781Slulf	    O_RDWR | O_CREAT | O_TRUNC, 0600);
1682186781Slulf	if (dest == NULL) {
1683186781Slulf		xasprintf(&up->errmsg, "Error opening file %s for writing: %s\n",
1684186781Slulf		    fup->temppath, strerror(errno));
1685186781Slulf		return (UPDATER_ERR_MSG);
1686186781Slulf	}
1687186781Slulf	stream_filter_start(dest, STREAM_FILTER_MD5RCS, md5);
1688186781Slulf	error = rcsfile_write(rf, dest);
1689186781Slulf	stream_close(dest);
1690186781Slulf	rcsfile_free(rf);
1691190422Slulf	if (error) {
1692190422Slulf		xasprintf(&up->errmsg, "%s: Cannot write: %s", fup->temppath,
1693190422Slulf		    strerror(errno));
1694190422Slulf		return (UPDATER_ERR_MSG);
1695190422Slulf	}
1696186781Slulf
1697186781Slulffinish:
1698186781Slulf	sr->sr_clientattr = fattr_frompath(path, FATTR_NOFOLLOW);
1699186781Slulf	if (sr->sr_clientattr == NULL) {
1700186781Slulf		xasprintf(&up->errmsg, "%s: Cannot get attributes: %s",
1701186781Slulf		    fup->destpath, strerror(errno));
1702186781Slulf		return (UPDATER_ERR_MSG);
1703186781Slulf	}
1704186781Slulf	fattr_override(sr->sr_clientattr, sr->sr_serverattr,
1705186781Slulf	    FA_MODTIME | FA_MASK);
1706186781Slulf	if (rf != NULL) {
1707186781Slulf		error = updater_updatefile(up, fup, md5, 0);
1708186781Slulf		fup->wantmd5 = NULL;	/* So that it doesn't get freed. */
1709186781Slulf		if (error)
1710186781Slulf			return (error);
1711186781Slulf	} else {
1712186781Slulf		/* Record its attributes since we touched it. */
1713186781Slulf		if (!(fattr_getmask(sr->sr_clientattr) & FA_LINKCOUNT) ||
1714186781Slulf		    fattr_getlinkcount(sr->sr_clientattr) <= 1)
1715186781Slulf		fattr_maskout(sr->sr_clientattr, FA_DEV | FA_INODE);
1716186781Slulf		error = status_put(st, sr);
1717186781Slulf		if (error) {
1718186781Slulf			up->errmsg = status_errmsg(st);
1719186781Slulf			return (UPDATER_ERR_MSG);
1720186781Slulf		}
1721186781Slulf	}
1722186781Slulf
1723186781Slulf	/* In this case, we need to remove the old file afterwards. */
1724186781Slulf	/* XXX: Can we be sure that a file not edited is moved? I don't think
1725186781Slulf	 * this is a problem, since if a file is moved, it should be edited to
1726186781Slulf	 * show if it's dead or not.
1727186781Slulf	 */
1728186781Slulf	if (fup->origpath != NULL)
1729186781Slulf		updater_deletefile(fup->origpath);
1730186781Slulf	return (0);
1731186781Slulf}
1732186781Slulf
1733186781Slulf/*
1734186781Slulf * Add a delta to a RCS file.
1735186781Slulf */
1736186781Slulfint
1737186781Slulfupdater_addelta(struct rcsfile *rf, struct stream *rd, char *cmdline)
1738186781Slulf{
1739186781Slulf	struct delta *d;
1740186781Slulf	size_t size;
1741186781Slulf	char *author, *cmd, *diffbase, *line, *logline;
1742186781Slulf	char *revdate, *revnum, *state, *textline;
1743186781Slulf
1744186781Slulf	revnum = proto_get_ascii(&cmdline);
1745186781Slulf	diffbase = proto_get_ascii(&cmdline);
1746186781Slulf	revdate = proto_get_ascii(&cmdline);
1747186781Slulf	author = proto_get_ascii(&cmdline);
1748186781Slulf	size = 0;
1749186781Slulf
1750186781Slulf	if (revnum == NULL || revdate == NULL || author == NULL)
1751186781Slulf		return (UPDATER_ERR_PROTO);
1752186781Slulf
1753186781Slulf	/* First add the delta so we have it. */
1754186781Slulf	d = rcsfile_addelta(rf, revnum, revdate, author, diffbase);
1755186781Slulf	if (d == NULL) {
1756186781Slulf		lprintf(-1, "Error adding delta %s\n", revnum);
1757186781Slulf		return (UPDATER_ERR_READ);
1758186781Slulf	}
1759186781Slulf	while ((line = stream_getln(rd, NULL)) != NULL) {
1760186781Slulf		if (strcmp(line, ".") == 0)
1761186781Slulf			break;
1762186781Slulf		cmd = proto_get_ascii(&line);
1763186781Slulf		switch (cmd[0]) {
1764186781Slulf			case 'L':
1765186781Slulf				/* Do the same as in 'C' command. */
1766186781Slulf				logline = stream_getln(rd, &size);
1767186781Slulf				while (logline != NULL) {
1768186781Slulf					if (size == 2 && *logline == '.')
1769186781Slulf						break;
1770186781Slulf					if (size == 3 &&
1771186781Slulf					    memcmp(logline, ".+", 2) == 0) {
1772186781Slulf						rcsdelta_truncatelog(d, -1);
1773186781Slulf						break;
1774186781Slulf					}
1775186781Slulf					if (size >= 3 &&
1776186781Slulf					    memcmp(logline, "..", 2) == 0) {
1777186781Slulf						size--;
1778186781Slulf						logline++;
1779186781Slulf					}
1780190422Slulf					if (rcsdelta_appendlog(d, logline, size)
1781190422Slulf					    < 0)
1782190422Slulf						return (-1);
1783186781Slulf					logline = stream_getln(rd, &size);
1784186781Slulf				}
1785186781Slulf			break;
1786186781Slulf			case 'N':
1787186781Slulf			case 'n':
1788186781Slulf				/* XXX: Not supported. */
1789186781Slulf			break;
1790186781Slulf			case 'S':
1791186781Slulf				state = proto_get_ascii(&line);
1792186781Slulf				if (state == NULL)
1793186781Slulf					return (UPDATER_ERR_PROTO);
1794186781Slulf				rcsdelta_setstate(d, state);
1795186781Slulf			break;
1796186781Slulf			case 'T':
1797186781Slulf				/* Do the same as in 'C' command. */
1798186781Slulf				textline = stream_getln(rd, &size);
1799186781Slulf				while (textline != NULL) {
1800186781Slulf					if (size == 2 && *textline == '.')
1801186781Slulf						break;
1802186781Slulf					if (size == 3 &&
1803186781Slulf					    memcmp(textline, ".+", 2) == 0) {
1804186781Slulf						/* Truncate newline. */
1805186781Slulf						rcsdelta_truncatetext(d, -1);
1806186781Slulf						break;
1807186781Slulf					}
1808186781Slulf					if (size >= 3 &&
1809186781Slulf					    memcmp(textline, "..", 2) == 0) {
1810186781Slulf						size--;
1811186781Slulf						textline++;
1812186781Slulf					}
1813190422Slulf					if (rcsdelta_appendtext(d, textline,
1814190422Slulf					    size) < 0)
1815190422Slulf						return (-1);
1816186781Slulf					textline = stream_getln(rd, &size);
1817186781Slulf				}
1818186781Slulf			break;
1819186781Slulf		}
1820186781Slulf	}
1821186781Slulf
1822186781Slulf	return (0);
1823186781Slulf}
1824186781Slulf
1825186781Slulfint
1826186781Slulfupdater_append_file(struct updater *up, struct file_update *fup, off_t pos)
1827186781Slulf{
1828186781Slulf	struct fattr *fa;
1829186781Slulf	struct stream *to;
1830186781Slulf	struct statusrec *sr;
1831186781Slulf	ssize_t nread;
1832186781Slulf	off_t bytes;
1833186781Slulf	char buf[BUFSIZE], md5[MD5_DIGEST_SIZE];
1834186781Slulf	char *line, *cmd;
1835186781Slulf	int error, fd;
1836186781Slulf
1837186781Slulf	sr = &fup->srbuf;
1838186781Slulf	fa = sr->sr_serverattr;
1839186781Slulf	to = stream_open_file(fup->temppath, O_WRONLY | O_CREAT | O_TRUNC,
1840186781Slulf	    0755);
1841186781Slulf	if (to == NULL) {
1842186781Slulf		xasprintf(&up->errmsg, "%s: Cannot open: %s", fup->temppath,
1843186781Slulf		    strerror(errno));
1844186781Slulf		return (UPDATER_ERR_MSG);
1845186781Slulf	}
1846186781Slulf	fd = open(fup->destpath, O_RDONLY);
1847186781Slulf	if (fd < 0) {
1848186781Slulf		xasprintf(&up->errmsg, "%s: Cannot open: %s", fup->destpath,
1849186781Slulf		    strerror(errno));
1850186781Slulf		return (UPDATER_ERR_MSG);
1851186781Slulf	}
1852186781Slulf
1853186781Slulf	stream_filter_start(to, STREAM_FILTER_MD5, md5);
1854186781Slulf	/* First write the existing content. */
1855190422Slulf	while ((nread = read(fd, buf, BUFSIZE)) > 0) {
1856190422Slulf		if (stream_write(to, buf, nread) == -1)
1857190422Slulf			goto bad;
1858190422Slulf	}
1859190422Slulf	if (nread == -1) {
1860193213Slulf		xasprintf(&up->errmsg, "%s: Error reading: %s", fup->destpath,
1861190422Slulf		    strerror(errno));
1862190422Slulf		return (UPDATER_ERR_MSG);
1863190422Slulf	}
1864186781Slulf	close(fd);
1865186781Slulf
1866186781Slulf	bytes = fattr_filesize(fa) - pos;
1867186781Slulf	/* Append the new data. */
1868186781Slulf	do {
1869186781Slulf		nread = stream_read(up->rd, buf,
1870186781Slulf		    (BUFSIZE > bytes) ? bytes : BUFSIZE);
1871190422Slulf		if (nread == -1)
1872190422Slulf			return (UPDATER_ERR_PROTO);
1873186781Slulf		bytes -= nread;
1874190422Slulf		if (stream_write(to, buf, nread) == -1)
1875190422Slulf			goto bad;
1876186781Slulf	} while (bytes > 0);
1877186781Slulf	stream_close(to);
1878186781Slulf
1879186781Slulf	line = stream_getln(up->rd, NULL);
1880186781Slulf	if (line == NULL)
1881186781Slulf		return (UPDATER_ERR_PROTO);
1882186781Slulf	/* Check for EOF. */
1883186781Slulf	if (!(*line == '.' || (strncmp(line, ".<", 2) != 0)))
1884186781Slulf		return (UPDATER_ERR_PROTO);
1885186781Slulf	line = stream_getln(up->rd, NULL);
1886186781Slulf	if (line == NULL)
1887186781Slulf		return (UPDATER_ERR_PROTO);
1888186781Slulf
1889186781Slulf	cmd = proto_get_ascii(&line);
1890186781Slulf	fup->wantmd5 = proto_get_ascii(&line);
1891186781Slulf	if (fup->wantmd5 == NULL || line != NULL || strcmp(cmd, "5") != 0)
1892186781Slulf		return (UPDATER_ERR_PROTO);
1893186781Slulf
1894186781Slulf	sr->sr_clientattr = fattr_frompath(fup->destpath, FATTR_NOFOLLOW);
1895186781Slulf	if (sr->sr_clientattr == NULL)
1896186781Slulf		return (UPDATER_ERR_PROTO);
1897186781Slulf	fattr_override(sr->sr_clientattr, sr->sr_serverattr,
1898186781Slulf	    FA_MODTIME | FA_MASK);
1899186781Slulf	error = updater_updatefile(up, fup, md5, 0);
1900186781Slulf	fup->wantmd5 = NULL;	/* So that it doesn't get freed. */
1901190422Slulf	return (error);
1902190422Slulfbad:
1903190422Slulf	xasprintf(&up->errmsg, "%s: Cannot write: %s", fup->temppath,
1904190422Slulf	    strerror(errno));
1905190422Slulf	return (UPDATER_ERR_MSG);
1906186781Slulf}
1907186781Slulf
1908186781Slulf/*
1909186781Slulf * Read file data from stream of checkout commands, and write it to the
1910186781Slulf * destination.
1911186781Slulf */
1912186781Slulfstatic int
1913186781Slulfupdater_read_checkout(struct stream *src, struct stream *dest)
1914186781Slulf{
1915186781Slulf	ssize_t nbytes;
1916186781Slulf	size_t size;
1917186781Slulf	char *line;
1918186781Slulf	int first;
1919186781Slulf
1920186781Slulf	first = 1;
1921186781Slulf	line = stream_getln(src, &size);
1922186781Slulf	while (line != NULL) {
1923186781Slulf		if (line[size - 1] == '\n')
1924186781Slulf			size--;
1925186781Slulf		if ((size == 1 && *line == '.') ||
1926186781Slulf		    (size == 2 && strncmp(line, ".+", 2) == 0))
1927186781Slulf			break;
1928186781Slulf		if (size >= 2 && strncmp(line, "..", 2) == 0) {
1929186781Slulf			size--;
1930186781Slulf			line++;
1931186781Slulf		}
1932186781Slulf		if (!first) {
1933186781Slulf			nbytes = stream_write(dest, "\n", 1);
1934186781Slulf			if (nbytes == -1)
1935186781Slulf				return (UPDATER_ERR_MSG);
1936186781Slulf		}
1937186781Slulf		nbytes = stream_write(dest, line, size);
1938186781Slulf		if (nbytes == -1)
1939186781Slulf			return (UPDATER_ERR_MSG);
1940186781Slulf		line = stream_getln(src, &size);
1941186781Slulf		first = 0;
1942186781Slulf	}
1943186781Slulf	if (line == NULL)
1944186781Slulf		return (UPDATER_ERR_READ);
1945186781Slulf	if (size == 1 && *line == '.') {
1946186781Slulf		nbytes = stream_write(dest, "\n", 1);
1947186781Slulf		if (nbytes == -1)
1948186781Slulf			return (UPDATER_ERR_MSG);
1949186781Slulf	}
1950186781Slulf	return (0);
1951186781Slulf}
1952186781Slulf
1953186781Slulf/* Update file using the rsync protocol. */
1954186781Slulfstatic int
1955186781Slulfupdater_rsync(struct updater *up, struct file_update *fup, size_t blocksize)
1956186781Slulf{
1957186781Slulf	struct statusrec *sr;
1958186781Slulf	struct stream *to;
1959186781Slulf	char md5[MD5_DIGEST_SIZE];
1960186781Slulf	ssize_t nbytes;
1961186781Slulf	size_t blocknum, blockstart, blockcount;
1962186781Slulf	char *buf, *line;
1963186781Slulf	int error, orig;
1964186781Slulf
1965186781Slulf	sr = &fup->srbuf;
1966186781Slulf
1967186781Slulf	lprintf(1, " Rsync %s\n", fup->coname);
1968186781Slulf	/* First open all files that we are going to work on. */
1969186781Slulf	to = stream_open_file(fup->temppath, O_WRONLY | O_CREAT | O_TRUNC,
1970186781Slulf	    0600);
1971186781Slulf	if (to == NULL) {
1972186781Slulf		xasprintf(&up->errmsg, "%s: Cannot create: %s",
1973186781Slulf		    fup->temppath, strerror(errno));
1974186781Slulf		return (UPDATER_ERR_MSG);
1975186781Slulf	}
1976186781Slulf	orig = open(fup->destpath, O_RDONLY);
1977186781Slulf	if (orig < 0) {
1978186781Slulf		xasprintf(&up->errmsg, "%s: Cannot open: %s",
1979186781Slulf		    fup->destpath, strerror(errno));
1980186781Slulf		return (UPDATER_ERR_MSG);
1981186781Slulf	}
1982186781Slulf	stream_filter_start(to, STREAM_FILTER_MD5, md5);
1983186781Slulf
1984186781Slulf	error = updater_read_checkout(up->rd, to);
1985186781Slulf	if (error) {
1986186781Slulf		xasprintf(&up->errmsg, "%s: Cannot write: %s", fup->temppath,
1987186781Slulf		    strerror(errno));
1988186781Slulf		return (error);
1989186781Slulf	}
1990186781Slulf
1991186781Slulf	/* Buffer must contain blocksize bytes. */
1992186781Slulf	buf = xmalloc(blocksize);
1993186781Slulf	/* Done with the initial text, read and write chunks. */
1994186781Slulf	line = stream_getln(up->rd, NULL);
1995186781Slulf	while (line != NULL) {
1996186781Slulf		if (strcmp(line, ".") == 0)
1997186781Slulf			break;
1998186781Slulf		error = UPDATER_ERR_PROTO;
1999186781Slulf		if (proto_get_sizet(&line, &blockstart, 10) != 0)
2000186781Slulf			goto bad;
2001186781Slulf		if (proto_get_sizet(&line, &blockcount, 10) != 0)
2002186781Slulf			goto bad;
2003186781Slulf		/* Read blocks from original file. */
2004186781Slulf		lseek(orig, (blocksize * blockstart), SEEK_SET);
2005186781Slulf		error = UPDATER_ERR_MSG;
2006186781Slulf		for (blocknum = 0; blocknum < blockcount; blocknum++) {
2007186781Slulf			nbytes = read(orig, buf, blocksize);
2008186781Slulf			if (nbytes < 0) {
2009186781Slulf				xasprintf(&up->errmsg, "%s: Cannot read: %s",
2010186781Slulf				    fup->destpath, strerror(errno));
2011186781Slulf				goto bad;
2012186781Slulf			}
2013186781Slulf			nbytes = stream_write(to, buf, nbytes);
2014186781Slulf			if (nbytes == -1) {
2015186781Slulf				xasprintf(&up->errmsg, "%s: Cannot write: %s",
2016186781Slulf				    fup->temppath, strerror(errno));
2017186781Slulf				goto bad;
2018186781Slulf			}
2019186781Slulf		}
2020186781Slulf		/* Get the remaining text from the server. */
2021186781Slulf		error = updater_read_checkout(up->rd, to);
2022186781Slulf		if (error) {
2023186781Slulf			xasprintf(&up->errmsg, "%s: Cannot write: %s",
2024186781Slulf			    fup->temppath, strerror(errno));
2025186781Slulf			goto bad;
2026186781Slulf		}
2027186781Slulf		line = stream_getln(up->rd, NULL);
2028186781Slulf	}
2029186781Slulf	stream_close(to);
2030186781Slulf	close(orig);
2031186781Slulf
2032186781Slulf	sr->sr_clientattr = fattr_frompath(fup->destpath, FATTR_NOFOLLOW);
2033186781Slulf	if (sr->sr_clientattr == NULL)
2034186781Slulf		return (UPDATER_ERR_PROTO);
2035186781Slulf	fattr_override(sr->sr_clientattr, sr->sr_serverattr,
2036186781Slulf	    FA_MODTIME | FA_MASK);
2037186781Slulf
2038186781Slulf	error = updater_updatefile(up, fup, md5, 0);
2039186781Slulf	fup->wantmd5 = NULL;	/* So that it doesn't get freed. */
2040186781Slulfbad:
2041186781Slulf	free(buf);
2042186781Slulf	return (error);
2043186781Slulf}
2044