output_file.c revision 435a8b61e925e3efb22fce08612efe210e83f791
1/*
2 * Copyright (C) 2010 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 "ext4_utils.h"
18#include "output_file.h"
19#include "sparse_format.h"
20#include "sparse_crc32.h"
21
22#include <sys/types.h>
23#include <sys/stat.h>
24#include <sys/types.h>
25#include <sys/mman.h>
26#include <unistd.h>
27#include <fcntl.h>
28
29#include <zlib.h>
30
31#if defined(__APPLE__) && defined(__MACH__)
32#define lseek64 lseek
33#define off64_t off_t
34#endif
35
36#define SPARSE_HEADER_MAJOR_VER 1
37#define SPARSE_HEADER_MINOR_VER 0
38#define SPARSE_HEADER_LEN       (sizeof(sparse_header_t))
39#define CHUNK_HEADER_LEN (sizeof(chunk_header_t))
40
41struct output_file_ops {
42	int (*seek)(struct output_file *, off64_t);
43	int (*write)(struct output_file *, u8 *, int);
44	void (*close)(struct output_file *);
45};
46
47struct output_file {
48	int fd;
49	gzFile gz_fd;
50	int sparse;
51	u64 cur_out_ptr;
52	u32 chunk_cnt;
53	u32 crc32;
54	struct output_file_ops *ops;
55	int use_crc;
56};
57
58static int file_seek(struct output_file *out, off64_t off)
59{
60	off64_t ret;
61
62	ret = lseek64(out->fd, off, SEEK_SET);
63	if (ret < 0) {
64		error_errno("lseek64");
65		return -1;
66	}
67	return 0;
68}
69
70static int file_write(struct output_file *out, u8 *data, int len)
71{
72	int ret;
73	ret = write(out->fd, data, len);
74	if (ret < 0) {
75		error_errno("write");
76		return -1;
77	} else if (ret < len) {
78		error("incomplete write");
79		return -1;
80	}
81
82	return 0;
83}
84
85static void file_close(struct output_file *out)
86{
87	close(out->fd);
88}
89
90
91static struct output_file_ops file_ops = {
92	.seek = file_seek,
93	.write = file_write,
94	.close = file_close,
95};
96
97static int gz_file_seek(struct output_file *out, off64_t off)
98{
99	off64_t ret;
100
101	ret = gzseek(out->gz_fd, off, SEEK_SET);
102	if (ret < 0) {
103		error_errno("gzseek");
104		return -1;
105	}
106	return 0;
107}
108
109static int gz_file_write(struct output_file *out, u8 *data, int len)
110{
111	int ret;
112	ret = gzwrite(out->gz_fd, data, len);
113	if (ret < 0) {
114		error_errno("gzwrite");
115		return -1;
116	} else if (ret < len) {
117		error("incomplete gzwrite");
118		return -1;
119	}
120
121	return 0;
122}
123
124static void gz_file_close(struct output_file *out)
125{
126	gzclose(out->gz_fd);
127}
128
129static struct output_file_ops gz_file_ops = {
130	.seek = gz_file_seek,
131	.write = gz_file_write,
132	.close = gz_file_close,
133};
134
135static sparse_header_t sparse_header = {
136	.magic = SPARSE_HEADER_MAGIC,
137	.major_version = SPARSE_HEADER_MAJOR_VER,
138	.minor_version = SPARSE_HEADER_MINOR_VER,
139	.file_hdr_sz = SPARSE_HEADER_LEN,
140	.chunk_hdr_sz = CHUNK_HEADER_LEN,
141	.blk_sz = 0,
142	.total_blks = 0,
143	.total_chunks = 0,
144	.image_checksum = 0
145};
146
147static u8 *zero_buf;
148
149static int emit_skip_chunk(struct output_file *out, u64 skip_len)
150{
151	chunk_header_t chunk_header;
152	int ret, chunk;
153
154	//DBG printf("skip chunk: 0x%llx bytes\n", skip_len);
155
156	if (skip_len % info.block_size) {
157		error("don't care size %llu is not a multiple of the block size %u",
158				skip_len, info.block_size);
159		return -1;
160	}
161
162	/* We are skipping data, so emit a don't care chunk. */
163	chunk_header.chunk_type = CHUNK_TYPE_DONT_CARE;
164	chunk_header.reserved1 = 0;
165	chunk_header.chunk_sz = skip_len / info.block_size;
166	chunk_header.total_sz = CHUNK_HEADER_LEN;
167	ret = out->ops->write(out, (u8 *)&chunk_header, sizeof(chunk_header));
168	if (ret < 0)
169		return -1;
170
171	out->cur_out_ptr += skip_len;
172	out->chunk_cnt++;
173
174	return 0;
175}
176
177static int write_chunk_raw(struct output_file *out, u64 off, u8 *data, int len)
178{
179	chunk_header_t chunk_header;
180	int rnd_up_len, zero_len;
181	int ret;
182
183	/* We can assume that all the chunks to be written are in
184	 * ascending order, block-size aligned, and non-overlapping.
185	 * So, if the offset is less than the current output pointer,
186	 * throw an error, and if there is a gap, emit a "don't care"
187	 * chunk.  The first write (of the super block) may not be
188	 * blocksize aligned, so we need to deal with that too.
189	 */
190	//DBG printf("write chunk: offset 0x%llx, length 0x%x bytes\n", off, len);
191
192	if (off < out->cur_out_ptr) {
193		error("offset %llu is less than the current output offset %llu",
194				off, out->cur_out_ptr);
195		return -1;
196	}
197
198	if (off > out->cur_out_ptr) {
199		emit_skip_chunk(out, off - out->cur_out_ptr);
200	}
201
202	if (off % info.block_size) {
203		error("write chunk offset %llu is not a multiple of the block size %u",
204				off, info.block_size);
205		return -1;
206	}
207
208	if (off != out->cur_out_ptr) {
209		error("internal error, offset accounting screwy in write_chunk_raw()");
210		return -1;
211	}
212
213	/* Round up the file length to a multiple of the block size */
214	rnd_up_len = (len + (info.block_size - 1)) & (~(info.block_size -1));
215	zero_len = rnd_up_len - len;
216
217	/* Finally we can safely emit a chunk of data */
218	chunk_header.chunk_type = CHUNK_TYPE_RAW;
219	chunk_header.reserved1 = 0;
220	chunk_header.chunk_sz = rnd_up_len / info.block_size;
221	chunk_header.total_sz = CHUNK_HEADER_LEN + rnd_up_len;
222	ret = out->ops->write(out, (u8 *)&chunk_header, sizeof(chunk_header));
223
224	if (ret < 0)
225		return -1;
226	ret = out->ops->write(out, data, len);
227	if (ret < 0)
228		return -1;
229	if (zero_len) {
230		ret = out->ops->write(out, zero_buf, zero_len);
231		if (ret < 0)
232			return -1;
233	}
234
235	if (out->use_crc) {
236		out->crc32 = sparse_crc32(out->crc32, data, len);
237		if (zero_len)
238			out->crc32 = sparse_crc32(out->crc32, zero_buf, zero_len);
239	}
240
241	out->cur_out_ptr += rnd_up_len;
242	out->chunk_cnt++;
243
244	return 0;
245}
246
247void close_output_file(struct output_file *out)
248{
249	int ret;
250	chunk_header_t chunk_header;
251
252	if (out->sparse) {
253		if (out->use_crc) {
254			chunk_header.chunk_type = CHUNK_TYPE_CRC32;
255			chunk_header.reserved1 = 0;
256			chunk_header.chunk_sz = 0;
257			chunk_header.total_sz = CHUNK_HEADER_LEN + 4;
258
259			out->ops->write(out, (u8 *)&chunk_header, sizeof(chunk_header));
260			out->ops->write(out, (u8 *)&out->crc32, 4);
261
262			out->chunk_cnt++;
263		}
264
265		if (out->chunk_cnt != sparse_header.total_chunks)
266			error("sparse chunk count did not match: %d %d", out->chunk_cnt,
267					sparse_header.total_chunks);
268	}
269	out->ops->close(out);
270}
271
272struct output_file *open_output_file(const char *filename, int gz, int sparse,
273        int chunks, int crc)
274{
275	int ret;
276	struct output_file *out = malloc(sizeof(struct output_file));
277	if (!out) {
278		error_errno("malloc struct out");
279		return NULL;
280	}
281	zero_buf = malloc(info.block_size);
282	if (!zero_buf) {
283		error_errno("malloc zero_buf");
284		return NULL;
285	}
286	memset(zero_buf, '\0', info.block_size);
287
288	if (gz) {
289		out->ops = &gz_file_ops;
290		out->gz_fd = gzopen(filename, "wb9");
291		if (!out->gz_fd) {
292			error_errno("gzopen");
293			free(out);
294			return NULL;
295		}
296	} else {
297		if (strcmp(filename, "-")) {
298			out->fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0644);
299			if (out->fd < 0) {
300				error_errno("open");
301				free(out);
302				return NULL;
303			}
304		} else {
305			out->fd = STDOUT_FILENO;
306		}
307		out->ops = &file_ops;
308	}
309	out->sparse = sparse;
310	out->cur_out_ptr = 0ll;
311	out->chunk_cnt = 0;
312
313	/* Initialize the crc32 value */
314	out->crc32 = 0;
315	out->use_crc = crc;
316
317	if (out->sparse) {
318		sparse_header.blk_sz = info.block_size,
319		sparse_header.total_blks = info.len / info.block_size,
320		sparse_header.total_chunks = chunks;
321		if (out->use_crc)
322			sparse_header.total_chunks++;
323
324		ret = out->ops->write(out, (u8 *)&sparse_header, sizeof(sparse_header));
325		if (ret < 0)
326			return NULL;
327	}
328
329	return out;
330}
331
332void pad_output_file(struct output_file *out, u64 len)
333{
334	int ret;
335
336	if (len > (u64) info.len) {
337		error("attempted to pad file %llu bytes past end of filesystem",
338				len - info.len);
339		return;
340	}
341	if (out->sparse) {
342		/* We need to emit a DONT_CARE chunk to pad out the file if the
343		 * cur_out_ptr is not already at the end of the filesystem.
344		 */
345		if (len < out->cur_out_ptr) {
346			error("attempted to pad file %llu bytes less than the current output pointer",
347					out->cur_out_ptr - len);
348			return;
349		}
350		if (len > out->cur_out_ptr) {
351			emit_skip_chunk(out, len - out->cur_out_ptr);
352		}
353	} else {
354		//KEN TODO: Fixme.  If the filesystem image needs no padding,
355		//          this will overwrite the last byte in the file with 0
356		//          The answer is to do accounting like the sparse image
357		//          code does and know if there is already data there.
358		ret = out->ops->seek(out, len - 1);
359		if (ret < 0)
360			return;
361
362		ret = out->ops->write(out, (u8*)"", 1);
363		if (ret < 0)
364			return;
365	}
366}
367
368/* Write a contiguous region of data blocks from a memory buffer */
369void write_data_block(struct output_file *out, u64 off, u8 *data, int len)
370{
371	int ret;
372
373	if (off + len > (u64) info.len) {
374		error("attempted to write block %llu past end of filesystem",
375				off + len - info.len);
376		return;
377	}
378
379	if (out->sparse) {
380		write_chunk_raw(out, off, data, len);
381	} else {
382		ret = out->ops->seek(out, off);
383		if (ret < 0)
384			return;
385
386		ret = out->ops->write(out, data, len);
387		if (ret < 0)
388			return;
389	}
390}
391
392/* Write a contiguous region of data blocks from a file */
393void write_data_file(struct output_file *out, u64 off, const char *file,
394		     off64_t offset, int len)
395{
396	int ret;
397	off64_t aligned_offset;
398	int aligned_diff;
399
400	if (off + len >= (u64) info.len) {
401		error("attempted to write block %llu past end of filesystem",
402				off + len - info.len);
403		return;
404	}
405
406	int file_fd = open(file, O_RDONLY);
407	if (file_fd < 0) {
408		error_errno("open");
409		return;
410	}
411
412	aligned_offset = offset & ~(4096 - 1);
413	aligned_diff = offset - aligned_offset;
414
415	u8 *data = mmap64(NULL, len + aligned_diff, PROT_READ, MAP_SHARED, file_fd,
416			aligned_offset);
417	if (data == MAP_FAILED) {
418		error_errno("mmap64");
419		close(file_fd);
420		return;
421	}
422
423	if (out->sparse) {
424		write_chunk_raw(out, off, data + aligned_diff, len);
425	} else {
426		ret = out->ops->seek(out, off);
427		if (ret < 0)
428			goto err;
429
430		ret = out->ops->write(out, data + aligned_diff, len);
431		if (ret < 0)
432			goto err;
433	}
434
435	munmap(data, len);
436
437	close(file_fd);
438
439err:
440	munmap(data, len);
441	close(file_fd);
442}
443