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 
usage(char * path)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 
add_chunk(ext2_filsys fs,struct sparse_file * s,blk_t chunk_start,blk_t chunk_end)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 
free_chunks(void)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 
ext_to_sparse(const char * in_file)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 
same_file(const char * in,const char * out)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 
main(int argc,char * argv[])178 int main(int argc, char *argv[])
179 {
180 	int opt;
181 	int out_fd;
182 	errcode_t retval;
183 	struct sparse_file *s;
184 
185 	while ((opt = getopt(argc, argv, "czS")) != -1) {
186 		switch(opt) {
187 		case 'c':
188 			params.crc = 1;
189 			break;
190 		case 'z':
191 			params.gzip = 1;
192 			break;
193 		case 'S':
194 			params.sparse = 0;
195 			break;
196 		default:
197 			usage(argv[0]);
198 			exit(EXIT_FAILURE);
199 		}
200 	}
201 	if (optind + 1 >= argc) {
202 		usage(argv[0]);
203 		exit(EXIT_FAILURE);
204 	}
205 	params.in_file = strdup(argv[optind++]);
206 	params.out_file = strdup(argv[optind]);
207 	params.overwrite_input = same_file(params.in_file, params.out_file);
208 
209 	s = ext_to_sparse(params.in_file);
210 
211 	out_fd = open(params.out_file, O_WRONLY | O_CREAT | O_TRUNC, 0664);
212 	if (out_fd == -1)
213 		ext2fs_fatal(errno, "opening %s\n", params.out_file);
214 	if (sparse_file_write(s, out_fd, params.gzip, params.sparse, params.crc) < 0)
215 		sparse_fatal("writing sparse file");
216 
217 	sparse_file_destroy(s);
218 
219 	free(params.in_file);
220 	free(params.out_file);
221 	free_chunks();
222 	close(out_fd);
223 
224 	return 0;
225 }
226