1 /* 2 * Copyright (C) 2016 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 #include <libgen.h> 18 #include <unistd.h> 19 #include <sys/types.h> 20 #include <sys/stat.h> 21 #include <fcntl.h> 22 #include <ext2fs/ext2fs.h> 23 #include <et/com_err.h> 24 #include <sparse/sparse.h> 25 26 struct { 27 int crc; 28 int sparse; 29 int gzip; 30 char *in_file; 31 char *out_file; 32 bool overwrite_input; 33 } params = { 34 .crc = 0, 35 .sparse = 1, 36 .gzip = 0, 37 }; 38 39 #define ext2fs_fatal(Retval, Format, ...) \ 40 do { \ 41 com_err("error", Retval, Format, __VA_ARGS__); \ 42 exit(EXIT_FAILURE); \ 43 } while(0) 44 45 #define sparse_fatal(Format) \ 46 do { \ 47 fprintf(stderr, "sparse: "Format); \ 48 exit(EXIT_FAILURE); \ 49 } while(0) 50 51 static void usage(char *path) 52 { 53 char *progname = basename(path); 54 55 fprintf(stderr, "%s [ options ] <image or block device> <output image>\n" 56 " -c include CRC block\n" 57 " -z gzip output\n" 58 " -S don't use sparse output format\n", progname); 59 } 60 61 static struct buf_item { 62 struct buf_item *next; 63 void *buf[0]; 64 } *buf_list; 65 66 static void add_chunk(ext2_filsys fs, struct sparse_file *s, blk_t chunk_start, blk_t chunk_end) 67 { 68 int retval; 69 unsigned int nb_blk = chunk_end - chunk_start; 70 size_t len = nb_blk * fs->blocksize; 71 int64_t offset = (int64_t)chunk_start * (int64_t)fs->blocksize; 72 73 if (params.overwrite_input == false) { 74 if (sparse_file_add_file(s, params.in_file, offset, len, chunk_start) < 0) 75 sparse_fatal("adding data to the sparse file"); 76 } else { 77 /* 78 * The input file will be overwritten, make a copy of 79 * the blocks 80 */ 81 struct buf_item *bi = calloc(1, sizeof(struct buf_item) + len); 82 if (buf_list == NULL) 83 buf_list = bi; 84 else { 85 bi->next = buf_list; 86 buf_list = bi; 87 } 88 89 retval = io_channel_read_blk64(fs->io, chunk_start, nb_blk, bi->buf); 90 if (retval < 0) 91 ext2fs_fatal(retval, "reading block %u - %u", chunk_start, chunk_end); 92 93 if (sparse_file_add_data(s, bi->buf, len, chunk_start) < 0) 94 sparse_fatal("adding data to the sparse file"); 95 } 96 } 97 98 static void free_chunks(void) 99 { 100 struct buf_item *bi; 101 102 while (buf_list) { 103 bi = buf_list->next; 104 free(buf_list); 105 buf_list = bi; 106 } 107 } 108 109 static struct sparse_file *ext_to_sparse(const char *in_file) 110 { 111 errcode_t retval; 112 ext2_filsys fs; 113 struct sparse_file *s; 114 int64_t chunk_start = -1; 115 blk_t first_blk, last_blk, nb_blk, cur_blk; 116 117 retval = ext2fs_open(in_file, 0, 0, 0, unix_io_manager, &fs); 118 if (retval) 119 ext2fs_fatal(retval, "while reading %s", in_file); 120 121 retval = ext2fs_read_block_bitmap(fs); 122 if (retval) 123 ext2fs_fatal(retval, "while reading block bitmap of %s", in_file); 124 125 first_blk = ext2fs_get_block_bitmap_start2(fs->block_map); 126 last_blk = ext2fs_get_block_bitmap_end2(fs->block_map); 127 nb_blk = last_blk - first_blk + 1; 128 129 s = sparse_file_new(fs->blocksize, (uint64_t)fs->blocksize * (uint64_t)nb_blk); 130 if (!s) 131 sparse_fatal("creating sparse file"); 132 133 /* 134 * The sparse format encodes the size of a chunk (and its header) in a 135 * 32-bit unsigned integer (UINT32_MAX) 136 * When writing the chunk, the library uses a single call to write(). 137 * Linux's implementation of the 'write' syscall does not allow transfers 138 * larger than INT32_MAX (32-bit _and_ 64-bit systems). 139 * Make sure we do not create chunks larger than this limit. 140 */ 141 int64_t max_blk_per_chunk = (INT32_MAX - 12) / fs->blocksize; 142 143 /* Iter on the blocks to merge contiguous chunk */ 144 for (cur_blk = first_blk; cur_blk <= last_blk; ++cur_blk) { 145 if (ext2fs_test_block_bitmap2(fs->block_map, cur_blk)) { 146 if (chunk_start == -1) { 147 chunk_start = cur_blk; 148 } else if (cur_blk - chunk_start + 1 == max_blk_per_chunk) { 149 add_chunk(fs, s, chunk_start, cur_blk); 150 chunk_start = -1; 151 } 152 } else if (chunk_start != -1) { 153 add_chunk(fs, s, chunk_start, cur_blk); 154 chunk_start = -1; 155 } 156 } 157 if (chunk_start != -1) 158 add_chunk(fs, s, chunk_start, cur_blk - 1); 159 160 ext2fs_free(fs); 161 return s; 162 } 163 164 static bool same_file(const char *in, const char *out) 165 { 166 struct stat st1, st2; 167 168 if (access(out, F_OK) == -1) 169 return false; 170 171 if (lstat(in, &st1) == -1) 172 ext2fs_fatal(errno, "stat %s\n", in); 173 if (lstat(out, &st2) == -1) 174 ext2fs_fatal(errno, "stat %s\n", out); 175 return st1.st_ino == st2.st_ino; 176 } 177 178 int main(int argc, char *argv[]) 179 { 180 int opt; 181 int out_fd; 182 struct sparse_file *s; 183 184 while ((opt = getopt(argc, argv, "czS")) != -1) { 185 switch(opt) { 186 case 'c': 187 params.crc = 1; 188 break; 189 case 'z': 190 params.gzip = 1; 191 break; 192 case 'S': 193 params.sparse = 0; 194 break; 195 default: 196 usage(argv[0]); 197 exit(EXIT_FAILURE); 198 } 199 } 200 if (optind + 1 >= argc) { 201 usage(argv[0]); 202 exit(EXIT_FAILURE); 203 } 204 params.in_file = strdup(argv[optind++]); 205 params.out_file = strdup(argv[optind]); 206 params.overwrite_input = same_file(params.in_file, params.out_file); 207 208 s = ext_to_sparse(params.in_file); 209 210 out_fd = open(params.out_file, O_WRONLY | O_CREAT | O_TRUNC, 0664); 211 if (out_fd == -1) 212 ext2fs_fatal(errno, "opening %s\n", params.out_file); 213 if (sparse_file_write(s, out_fd, params.gzip, params.sparse, params.crc) < 0) 214 sparse_fatal("writing sparse file"); 215 216 sparse_file_destroy(s); 217 218 free(params.in_file); 219 free(params.out_file); 220 free_chunks(); 221 close(out_fd); 222 223 return 0; 224 } 225