OSDN Git Service

3785b06e0d4e4ce9f2c9b6c36672bb04a06c7037
[android-x86/system-extras.git] / ext4_utils / output_file.c
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
42 struct 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
48 struct 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
58 static 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
70 static 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
85 static void file_close(struct output_file *out)
86 {
87         close(out->fd);
88 }
89
90
91 static struct output_file_ops file_ops = {
92         .seek = file_seek,
93         .write = file_write,
94         .close = file_close,
95 };
96
97 static 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
109 static 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
124 static void gz_file_close(struct output_file *out)
125 {
126         gzclose(out->gz_fd);
127 }
128
129 static struct output_file_ops gz_file_ops = {
130         .seek = gz_file_seek,
131         .write = gz_file_write,
132         .close = gz_file_close,
133 };
134
135 static 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
147 static u8 *zero_buf;
148
149 static 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
184 static 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
251 void 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
271 struct 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
324 void 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 */
362 void 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 */
386 void 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
426 err:
427         munmap(data, len);
428         close(file_fd);
429 }
430