summaryrefslogtreecommitdiff
path: root/ubi-utils/src/libubimirror.c
blob: d06770edee5b0fe3905b43bb74fa4b6e09dd7b10 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
/*
 * Copyright (c) International Business Machines Corp., 2006
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
 * the GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#include <stdio.h>
#include <errno.h>
#include <unistd.h>
#include <memory.h>
#include <limits.h>
#include <fcntl.h>

#include <libubi.h>
#include "ubimirror.h"

#define COMPARE_BUF_SIZE    (128 * 1024)

#define DEFAULT_DEV_PATTERN    "/dev/ubi%d"
#define DEFAULT_VOL_PATTERN    "/dev/ubi%d_%d"

#define EBUF(fmt...) do {			\
	snprintf(err_buf, err_buf_size, fmt);	\
} while (0)

enum {
	compare_error = -1,
	seek_error = -2,
	write_error = -3,
	read_error = -4,
	update_error = -5,
	ubi_error = -6,
	open_error = -7,
	close_error = -8,
	compare_equal = 0,
	compare_different = 1
};

/*
 * Read len number of bytes from fd.
 * Return 0 on EOF, -1 on error.
 */
static ssize_t fill_buffer(int fd, unsigned char *buf, ssize_t len)
{
	ssize_t got, have = 0;

	do {
		got = read(fd, buf + have, len - have);
		if (got == -1 && errno != EINTR)
			return -1;
		have += got;
	} while (got > 0 && have < len);
	return have;
}

/*
 * Write len number of bytes to fd.
 * Return bytes written (>= 0), -1 on error.
 */
static ssize_t flush_buffer(int fd, unsigned char *buf, ssize_t len)
{
	ssize_t done, have = 0;

	do {
		done = write(fd, buf + have, len - have);
		if (done == -1 && errno != EINTR)
			return -1;
		have += done;
	} while (done > 0 && have < len);
	return have;
}

/*
 *  Compare two files.  Return 0, 1, or -1, depending on whether the
 *  files are equal, different, or an error occured.
 *  Return compare-different when target volume can not be read. Might be
 *  an interrupted volume update and then the target device returns -EIO but
 *  can be updated.
 *
 *  fd_a is source
 *  fd_b is destination
 */
static int compare_files(int fd_a, int fd_b)
{
	unsigned char buf_a[COMPARE_BUF_SIZE], buf_b[COMPARE_BUF_SIZE];
	ssize_t len_a, len_b;
	int rc;

	for (;;) {
		len_a = fill_buffer(fd_a, buf_a, sizeof(buf_a));
		if (len_a == -1) {
			rc = compare_error;
			break;
		}
		len_b = fill_buffer(fd_b, buf_b, sizeof(buf_b));
		if (len_b == -1) {
			rc = compare_different;
			break;
		}
		if (len_a != len_b) {
			rc = compare_different;
			break;
		}
		if (len_a == 0) {	/* Size on both files equal and EOF */
			rc = compare_equal;
			break;
		}
		if (memcmp(buf_a, buf_b, len_a) != 0 ) {
			rc = compare_different;
			break;
		}
	}
	/* Position both files at the beginning */
	if (lseek(fd_a, 0, SEEK_SET) == -1 ||
	   lseek(fd_b, 0, SEEK_SET) == -1)
		rc = seek_error;
	return rc;
}

int vol_get_used_bytes(int vol_fd, unsigned long long *bytes)
{
	off_t res;

	res = lseek(vol_fd, 0, SEEK_END);
	if (res == (off_t)-1)
		return -1;
	*bytes = (unsigned long long) res;
	res = lseek(vol_fd, 0, SEEK_SET);
	return res == (off_t)-1 ? -1 : 0;
}

static int copy_files(libubi_t ulib, int fd_in, int fd_out)
{
	unsigned char buf_a[COMPARE_BUF_SIZE];
	ssize_t len_a, len_b;
	unsigned long long update_size, copied;

	if (vol_get_used_bytes(fd_in, &update_size) == -1 ||
	    ubi_update_start(ulib, fd_out, update_size) == -1)
		return update_error;
	for (copied = 0; copied < update_size; copied += len_b ) {
		len_a = fill_buffer(fd_in, buf_a, sizeof(buf_a));
		if (len_a == -1)
			return read_error;
		if (len_a == 0)		/* Reach EOF */
			return 0;
		len_b = flush_buffer(fd_out, buf_a, len_a);
		if (len_b != len_a)
			return write_error;
	}
	return 0;
}

int ubimirror(uint32_t devno, int seqnum, uint32_t *ids, ssize_t ids_size,
		char *err_buf, size_t err_buf_size)
{
	int rc = 0;
	uint32_t src_id;
	char path[PATH_MAX];
	libubi_t ulib;
	int fd_in = -1, i = 0, fd_out = -1;

	if (ids_size == 0)
		return 0;
	else {
		if ((seqnum < 0) || (seqnum > (ids_size - 1))) {
			EBUF("volume id %d out of range", seqnum);
			return EUBIMIRROR_NO_SRC;
		}
		src_id = ids[seqnum];
	}

	ulib = libubi_open();
	if (ulib == NULL)
		return ubi_error;

	snprintf(path, PATH_MAX, DEFAULT_VOL_PATTERN, devno, src_id);

	fd_in = open(path, O_RDONLY);
	if (fd_in == -1) {
		EBUF("open error source volume %d", ids[i]);
		rc = open_error;
		goto err;
	}

	for (i = 0; i < ids_size; i++) {
		if (ids[i] == src_id)		/* skip self-mirror */
			continue;

		snprintf(path, PATH_MAX, DEFAULT_VOL_PATTERN, devno, ids[i]);

		fd_out = open(path, O_RDWR);
		if (fd_out < 0){
			EBUF("open error destination volume %d", ids[i]);
			rc = open_error;
			goto err;
		}
		rc = compare_files(fd_in, fd_out);
		if (rc < 0) {
			EBUF("compare error volume %d and %d", src_id, ids[i]);
			goto err;
		} else if (rc == compare_different) {
			rc = copy_files(ulib, fd_in, fd_out);
			if (rc != 0) {
				EBUF("mirror error volume %d to %d", src_id,
						ids[i]);
				goto err;
			}
		}
		if ((rc = close(fd_out)) == -1) {
			EBUF("close error volume %d", ids[i]);
			rc = close_error;
			goto err;
		} else
			fd_out = -1;
	}
err:
	if (fd_out != -1)
		close(fd_out);
	if (fd_in != -1)
		close(fd_in);
	if (ulib != NULL)
		libubi_close(ulib);
	return rc;
}