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