output_file.c revision 5a6181798de5c2d882c79b27406c330a6fa7da3e
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#define _LARGEFILE64_SOURCE
17
18#include <sys/types.h>
19#include <sys/stat.h>
20#include <sys/types.h>
21#include <sys/mman.h>
22#include <unistd.h>
23#include <fcntl.h>
24
25#include <zlib.h>
26
27#include "ext4_utils.h"
28#include "output_file.h"
29#include "sparse_format.h"
30#include "sparse_crc32.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	int chunk_cnt;
54	u32 crc32;
55	struct output_file_ops *ops;
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	/* Compute the CRC for all those zeroes.  Do it block_size bytes at a time. */
175	while (skip_len) {
176		chunk = (skip_len > info.block_size) ? info.block_size : skip_len;
177		out->crc32 = sparse_crc32(out->crc32, zero_buf, chunk);
178		skip_len -= chunk;
179	}
180
181	return 0;
182}
183
184static int write_chunk_raw(struct output_file *out, u64 off, u8 *data, int len)
185{
186	chunk_header_t chunk_header;
187	int rnd_up_len, zero_len;
188	int ret;
189
190	/* We can assume that all the chunks to be written are in
191	 * ascending order, block-size aligned, and non-overlapping.
192	 * So, if the offset is less than the current output pointer,
193	 * throw an error, and if there is a gap, emit a "don't care"
194	 * chunk.  The first write (of the super block) may not be
195	 * blocksize aligned, so we need to deal with that too.
196	 */
197	//DBG printf("write chunk: offset 0x%llx, length 0x%x bytes\n", off, len);
198
199	if (off < out->cur_out_ptr) {
200		error("offset %llu is less than the current output offset %llu",
201				off, out->cur_out_ptr);
202		return -1;
203	}
204
205	if (off > out->cur_out_ptr) {
206		emit_skip_chunk(out, off - out->cur_out_ptr);
207	}
208
209	if (off % info.block_size) {
210		error("write chunk offset %llu is not a multiple of the block size %u",
211				off, info.block_size);
212		return -1;
213	}
214
215	if (off != out->cur_out_ptr) {
216		error("internal error, offset accounting screwy in write_chunk_raw()");
217		return -1;
218	}
219
220	/* Round up the file length to a multiple of the block size */
221	rnd_up_len = (len + (info.block_size - 1)) & (~(info.block_size -1));
222	zero_len = rnd_up_len - len;
223
224	/* Finally we can safely emit a chunk of data */
225	chunk_header.chunk_type = CHUNK_TYPE_RAW;
226	chunk_header.reserved1 = 0;
227	chunk_header.chunk_sz = rnd_up_len / info.block_size;
228	chunk_header.total_sz = CHUNK_HEADER_LEN + rnd_up_len;
229	ret = out->ops->write(out, (u8 *)&chunk_header, sizeof(chunk_header));
230
231	if (ret < 0)
232		return -1;
233	ret = out->ops->write(out, data, len);
234	if (ret < 0)
235		return -1;
236	if (zero_len) {
237		ret = out->ops->write(out, zero_buf, zero_len);
238		if (ret < 0)
239			return -1;
240	}
241
242	out->crc32 = sparse_crc32(out->crc32, data, len);
243	if (zero_len)
244		out->crc32 = sparse_crc32(out->crc32, zero_buf, zero_len);
245	out->cur_out_ptr += rnd_up_len;
246	out->chunk_cnt++;
247
248	return 0;
249}
250
251void close_output_file(struct output_file *out)
252{
253	int ret;
254
255	if (out->sparse) {
256		/* we need to seek back to the beginning and update the file header */
257		sparse_header.total_chunks = out->chunk_cnt;
258		sparse_header.image_checksum = out->crc32;
259
260		ret = out->ops->seek(out, 0);
261		if (ret < 0)
262			error("failure seeking to start of sparse file");
263
264		ret = out->ops->write(out, (u8 *)&sparse_header, sizeof(sparse_header));
265		if (ret < 0)
266			error("failure updating sparse file header");
267	}
268	out->ops->close(out);
269}
270
271struct output_file *open_output_file(const char *filename, int gz, int sparse)
272{
273	int ret;
274	struct output_file *out = malloc(sizeof(struct output_file));
275	if (!out) {
276		error_errno("malloc struct out");
277		return NULL;
278	}
279	zero_buf = malloc(info.block_size);
280	if (!zero_buf) {
281		error_errno("malloc zero_buf");
282		return NULL;
283	}
284	memset(zero_buf, '\0', info.block_size);
285
286	if (gz) {
287		out->ops = &gz_file_ops;
288		out->gz_fd = gzopen(filename, "wb9");
289		if (!out->gz_fd) {
290			error_errno("gzopen");
291			free(out);
292			return NULL;
293		}
294	} else {
295		out->ops = &file_ops;
296		out->fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0644);
297		if (out->fd < 0) {
298			error_errno("open");
299			free(out);
300			return NULL;
301		}
302	}
303	out->sparse = sparse;
304	out->cur_out_ptr = 0ll;
305	out->chunk_cnt = 0;
306
307	/* Initialize the crc32 value */
308	out->crc32 = 0;
309
310	if (out->sparse) {
311		/* Write out the file header.  We'll update the unknown fields
312		 * when we close the file.
313		 */
314		sparse_header.blk_sz = info.block_size,
315		sparse_header.total_blks = info.len / info.block_size,
316		ret = out->ops->write(out, (u8 *)&sparse_header, sizeof(sparse_header));
317		if (ret < 0)
318			return NULL;
319	}
320
321	return out;
322}
323
324void pad_output_file(struct output_file *out, u64 len)
325{
326	int ret;
327
328	if (len > info.len) {
329		error("attempted to pad file %llu bytes past end of filesystem",
330				len - info.len);
331		return;
332	}
333	if (out->sparse) {
334		/* We need to emit a DONT_CARE chunk to pad out the file if the
335		 * cur_out_ptr is not already at the end of the filesystem.
336		 * We also need to compute the CRC for it.
337		 */
338		if (len < out->cur_out_ptr) {
339			error("attempted to pad file %llu bytes less than the current output pointer",
340					out->cur_out_ptr - len);
341			return;
342		}
343		if (len > out->cur_out_ptr) {
344			emit_skip_chunk(out, len - out->cur_out_ptr);
345		}
346	} else {
347		//KEN TODO: Fixme.  If the filesystem image needs no padding,
348		//          this will overwrite the last byte in the file with 0
349		//          The answer is to do accounting like the sparse image
350		//          code does and know if there is already data there.
351		ret = out->ops->seek(out, len - 1);
352		if (ret < 0)
353			return;
354
355		ret = out->ops->write(out, (u8*)"", 1);
356		if (ret < 0)
357			return;
358	}
359}
360
361/* Write a contiguous region of data blocks from a memory buffer */
362void write_data_block(struct output_file *out, u64 off, u8 *data, int len)
363{
364	int ret;
365
366	if (off + len > info.len) {
367		error("attempted to write block %llu past end of filesystem",
368				off + len - info.len);
369		return;
370	}
371
372	if (out->sparse) {
373		write_chunk_raw(out, off, data, len);
374	} else {
375		ret = out->ops->seek(out, off);
376		if (ret < 0)
377			return;
378
379		ret = out->ops->write(out, data, len);
380		if (ret < 0)
381			return;
382	}
383}
384
385/* Write a contiguous region of data blocks from a file */
386void write_data_file(struct output_file *out, u64 off, const char *file,
387		     off_t offset, int len)
388{
389	int ret;
390
391	if (off + len >= info.len) {
392		error("attempted to write block %llu past end of filesystem",
393				off + len - info.len);
394		return;
395	}
396
397	int file_fd = open(file, O_RDONLY);
398	if (file_fd < 0) {
399		error_errno("open");
400		return;
401	}
402
403	u8 *data = mmap(NULL, len, PROT_READ, MAP_SHARED, file_fd, offset);
404	if (data == MAP_FAILED) {
405		error_errno("mmap");
406		close(file_fd);
407		return;
408	}
409
410	if (out->sparse) {
411		write_chunk_raw(out, off, data, len);
412	} else {
413		ret = out->ops->seek(out, off);
414		if (ret < 0)
415			goto err;
416
417		ret = out->ops->write(out, data, len);
418		if (ret < 0)
419			goto err;
420	}
421
422	munmap(data, len);
423
424	close(file_fd);
425
426err:
427	munmap(data, len);
428	close(file_fd);
429}
430
431