1/*-
2 * Copyright (c) 2008-2009, Ulf Lilleengen <lulf@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$
27 */
28
29#include <errno.h>
30#include <string.h>
31#include <stdlib.h>
32#include <stdio.h>
33
34#include <sys/types.h>
35#include <sys/stat.h>
36#include <sys/mman.h>
37#include <fcntl.h>
38#include <unistd.h>
39
40#include "misc.h"
41#include "rsyncfile.h"
42
43#define MINBLOCKSIZE 1024
44#define MAXBLOCKSIZE (16 * 1024)
45#define RECEIVEBUFFERSIZE (15 * 1024)
46#define BLOCKINFOSIZE 26
47#define SEARCHREGION 10
48#define MAXBLOCKS (RECEIVEBUFFERSIZE / BLOCKINFOSIZE)
49
50#define CHAR_OFFSET 3
51#define RSUM_SIZE 9
52
53struct rsyncfile {
54	char *start;
55	char *buf;
56	char *end;
57	size_t blocksize;
58	size_t fsize;
59	int fd;
60
61	char *blockptr;
62	int blocknum;
63	char blockmd5[MD5_DIGEST_SIZE];
64	char rsumstr[RSUM_SIZE];
65	uint32_t rsum;
66};
67
68static size_t		rsync_chooseblocksize(size_t);
69static uint32_t		rsync_rollsum(char *, size_t);
70
71/* Open a file and initialize variable for rsync operation. */
72struct rsyncfile *
73rsync_open(char *path, size_t blocksize, int rdonly)
74{
75	struct rsyncfile *rf;
76	struct stat st;
77	int error;
78
79	rf = xmalloc(sizeof(*rf));
80	error = stat(path, &st);
81	if (error) {
82		free(rf);
83		return (NULL);
84	}
85	rf->fsize = st.st_size;
86
87	rf->fd = open(path, rdonly ? O_RDONLY : O_RDWR);
88	if (rf->fd < 0) {
89		free(rf);
90		return (NULL);
91	}
92	rf->buf = mmap(0, rf->fsize, PROT_READ, MAP_SHARED, rf->fd, 0);
93	if (rf->buf == MAP_FAILED) {
94		free(rf);
95		return (NULL);
96	}
97	rf->start = rf->buf;
98	rf->end = rf->buf + rf->fsize;
99	rf->blocksize = (blocksize == 0 ? rsync_chooseblocksize(rf->fsize) :
100	    blocksize);
101	rf->blockptr = rf->buf;
102	rf->blocknum = 0;
103	return (rf);
104}
105
106/* Close and free all resources related to an rsync file transfer. */
107int
108rsync_close(struct rsyncfile *rf)
109{
110	int error;
111
112	error = munmap(rf->buf, rf->fsize);
113	if (error)
114		return (error);
115	close(rf->fd);
116	free(rf);
117	return (0);
118}
119
120/*
121 * Choose the most appropriate block size for an rsync transfer. Modeled
122 * algorithm after cvsup.
123 */
124static size_t
125rsync_chooseblocksize(size_t fsize)
126{
127	size_t bestrem, blocksize, bs, hisearch, losearch, rem;
128
129	blocksize = fsize / MAXBLOCKS;
130	losearch = blocksize - SEARCHREGION;
131	hisearch = blocksize + SEARCHREGION;
132
133	if (losearch < MINBLOCKSIZE) {
134		losearch = MINBLOCKSIZE;
135		hisearch = losearch + (2 * SEARCHREGION);
136	} else if (hisearch > MAXBLOCKSIZE) {
137		hisearch = MAXBLOCKSIZE;
138		losearch = hisearch - (2 * SEARCHREGION);
139	}
140
141	bestrem = MAXBLOCKSIZE;
142	for (bs = losearch; bs <= hisearch; bs++) {
143		rem = fsize % bs;
144		if (rem < bestrem) {
145			bestrem = rem;
146			blocksize = bs;
147		}
148	}
149	return (bestrem);
150}
151
152/* Get the next rsync block of a file. */
153int
154rsync_nextblock(struct rsyncfile *rf)
155{
156	MD5_CTX ctx;
157	size_t blocksize;
158
159	if (rf->blockptr >= rf->end)
160		return (0);
161	blocksize = min((size_t)(rf->end - rf->blockptr), rf->blocksize);
162	/* Calculate MD5 of the block. */
163	MD5_Init(&ctx);
164	MD5_Update(&ctx, rf->blockptr, blocksize);
165	MD5_End(rf->blockmd5, &ctx);
166
167	rf->rsum = rsync_rollsum(rf->blockptr, blocksize);
168	snprintf(rf->rsumstr, RSUM_SIZE, "%x", rf->rsum);
169	rf->blocknum++;
170	rf->blockptr += blocksize;
171	return (1);
172}
173
174/* Get the rolling checksum of a file. */
175static uint32_t
176rsync_rollsum(char *buf, size_t len)
177{
178	uint32_t a, b;
179	char *ptr, *limit;
180
181	a = b = 0;
182	ptr = buf;
183	limit = buf + len;
184
185	while (ptr < limit) {
186		a += *ptr + CHAR_OFFSET;
187		b += a;
188		ptr++;
189	}
190	return ((b << 16) | a);
191}
192
193/* Get running sum so far. */
194char *
195rsync_rsum(struct rsyncfile *rf)
196{
197
198	return (rf->rsumstr);
199}
200
201/* Get MD5 of current block. */
202char *
203rsync_blockmd5(struct rsyncfile *rf)
204{
205
206	return (rf->blockmd5);
207}
208
209/* Accessor for blocksize. */
210size_t
211rsync_blocksize(struct rsyncfile *rf)
212{
213
214	return (rf->blocksize);
215}
216
217/* Accessor for filesize. */
218size_t
219rsync_filesize(struct rsyncfile *rf)
220{
221
222	return (rf->fsize);
223}
224