| /* |
| * 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; |
| } |