Whamcloud - gitweb
AOSP: ext2simg: clean up integer types and check for too-large fs
[tools/e2fsprogs.git] / contrib / android / ext2simg.c
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         bool    crc;
28         bool    sparse;
29         bool    gzip;
30         char    *in_file;
31         char    *out_file;
32         bool    overwrite_input;
33 } params = {
34         .sparse     = true,
35 };
36
37 #define ext2fs_fatal(Retval, Format, ...) \
38         do { \
39                 com_err("error", Retval, Format, __VA_ARGS__); \
40                 exit(EXIT_FAILURE); \
41         } while(0)
42
43 #define sparse_fatal(Format) \
44         do { \
45                 fprintf(stderr, "sparse: "Format); \
46                 exit(EXIT_FAILURE); \
47         } while(0)
48
49 static void usage(char *path)
50 {
51         char *progname = basename(path);
52
53         fprintf(stderr, "%s [ options ] <image or block device> <output image>\n"
54                         "  -c include CRC block\n"
55                         "  -z gzip output\n"
56                         "  -S don't use sparse output format\n", progname);
57 }
58
59 static struct buf_item {
60         struct buf_item     *next;
61         void                *buf[];
62 } *buf_list;
63
64 /*
65  * Add @num_blks blocks, starting at index @chunk_start, of the filesystem @fs
66  * to the sparse file @s.
67  */
68 static void add_chunk(ext2_filsys fs, struct sparse_file *s,
69                       blk_t chunk_start, int num_blks)
70 {
71         uint64_t len = (uint64_t)num_blks * fs->blocksize;
72         int64_t offset = (int64_t)chunk_start * fs->blocksize;
73         struct buf_item *bi;
74         int retval;
75
76         if (!params.overwrite_input) {
77                 if (sparse_file_add_file(s, params.in_file, offset, len, chunk_start) < 0)
78                         sparse_fatal("adding data to the sparse file");
79                 return;
80         }
81
82         /* The input file will be overwritten, so make a copy of the blocks. */
83         if (len > SIZE_MAX - sizeof(*bi))
84                 sparse_fatal("filesystem is too large");
85         bi = calloc(1, sizeof(*bi) + len);
86         if (!bi)
87                 sparse_fatal("out of memory");
88         bi->next = buf_list;
89         buf_list = bi;
90         retval = io_channel_read_blk64(fs->io, chunk_start, num_blks, bi->buf);
91         if (retval < 0)
92                 ext2fs_fatal(retval, "reading data from %s", params.in_file);
93
94         if (sparse_file_add_data(s, bi->buf, len, chunk_start) < 0)
95                 sparse_fatal("adding data to the sparse file");
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 blk_t fs_blocks_count(ext2_filsys fs)
110 {
111         blk64_t blks = ext2fs_blocks_count(fs->super);
112
113         /* libsparse assumes 32-bit block numbers. */
114         if ((blk_t)blks != blks)
115                 sparse_fatal("filesystem is too large");
116         return blks;
117 }
118
119 static struct sparse_file *ext_to_sparse(const char *in_file)
120 {
121         errcode_t retval;
122         ext2_filsys fs;
123         struct sparse_file *s;
124         int64_t chunk_start = -1;
125         blk_t fs_blks, cur_blk;
126
127         retval = ext2fs_open(in_file, 0, 0, 0, unix_io_manager, &fs);
128         if (retval)
129                 ext2fs_fatal(retval, "while reading %s", in_file);
130
131         retval = ext2fs_read_block_bitmap(fs);
132         if (retval)
133                 ext2fs_fatal(retval, "while reading block bitmap of %s", in_file);
134
135         fs_blks = fs_blocks_count(fs);
136
137         s = sparse_file_new(fs->blocksize, (uint64_t)fs_blks * fs->blocksize);
138         if (!s)
139                 sparse_fatal("creating sparse file");
140
141         /*
142          * The sparse format encodes the size of a chunk (and its header) in a
143          * 32-bit unsigned integer (UINT32_MAX)
144          * When writing the chunk, the library uses a single call to write().
145          * Linux's implementation of the 'write' syscall does not allow transfers
146          * larger than INT32_MAX (32-bit _and_ 64-bit systems).
147          * Make sure we do not create chunks larger than this limit.
148          */
149         int32_t max_blk_per_chunk = (INT32_MAX - 12) / fs->blocksize;
150
151         /*
152          * Iterate through the filesystem's blocks, identifying "chunks" that
153          * are contiguous ranges of blocks that are in-use by the filesystem.
154          * Add each chunk to the sparse_file.
155          */
156         for (cur_blk = ext2fs_get_block_bitmap_start2(fs->block_map);
157              cur_blk < fs_blks; ++cur_blk) {
158                 if (ext2fs_test_block_bitmap2(fs->block_map, cur_blk)) {
159                         /*
160                          * @cur_blk is in-use.  Append it to the pending chunk
161                          * if there is one, otherwise start a new chunk.
162                          */
163                         if (chunk_start == -1) {
164                                 chunk_start = cur_blk;
165                         } else if (cur_blk - chunk_start + 1 == max_blk_per_chunk) {
166                                 /*
167                                  * Appending @cur_blk to the pending chunk made
168                                  * it reach the maximum length, so end it.
169                                  */
170                                 add_chunk(fs, s, chunk_start, max_blk_per_chunk);
171                                 chunk_start = -1;
172                         }
173                 } else if (chunk_start != -1) {
174                         /* @cur_blk is not in-use, so end the pending chunk. */
175                         add_chunk(fs, s, chunk_start, cur_blk - chunk_start);
176                         chunk_start = -1;
177                 }
178         }
179         /* If there's still a pending chunk, end it. */
180         if (chunk_start != -1)
181                 add_chunk(fs, s, chunk_start, cur_blk - chunk_start);
182
183         ext2fs_free(fs);
184         return s;
185 }
186
187 static bool same_file(const char *in, const char *out)
188 {
189         struct stat st1, st2;
190
191         if (access(out, F_OK) == -1)
192                 return false;
193
194         if (lstat(in, &st1) == -1)
195                 ext2fs_fatal(errno, "stat %s\n", in);
196         if (lstat(out, &st2) == -1)
197                 ext2fs_fatal(errno, "stat %s\n", out);
198         return st1.st_dev == st2.st_dev && st1.st_ino == st2.st_ino;
199 }
200
201 int main(int argc, char *argv[])
202 {
203         int opt;
204         int out_fd;
205         struct sparse_file *s;
206
207         while ((opt = getopt(argc, argv, "czS")) != -1) {
208                 switch(opt) {
209                 case 'c':
210                         params.crc = true;
211                         break;
212                 case 'z':
213                         params.gzip = true;
214                         break;
215                 case 'S':
216                         params.sparse = false;
217                         break;
218                 default:
219                         usage(argv[0]);
220                         exit(EXIT_FAILURE);
221                 }
222         }
223         if (optind + 1 >= argc) {
224                 usage(argv[0]);
225                 exit(EXIT_FAILURE);
226         }
227         params.in_file = strdup(argv[optind++]);
228         params.out_file = strdup(argv[optind]);
229         params.overwrite_input = same_file(params.in_file, params.out_file);
230
231         s = ext_to_sparse(params.in_file);
232
233         out_fd = open(params.out_file, O_WRONLY | O_CREAT | O_TRUNC, 0664);
234         if (out_fd == -1)
235                 ext2fs_fatal(errno, "opening %s\n", params.out_file);
236         if (sparse_file_write(s, out_fd, params.gzip, params.sparse, params.crc) < 0)
237                 sparse_fatal("writing sparse file");
238
239         sparse_file_destroy(s);
240
241         free(params.in_file);
242         free(params.out_file);
243         free_chunks();
244         close(out_fd);
245
246         return 0;
247 }