blob: 5e8a68c54ada8a09cb81b369db881b5904f19369 [file] [log] [blame]
Colin Cross28fa5bc2012-05-20 13:28:05 -07001/*
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#define _FILE_OFFSET_BITS 64
18#define _LARGEFILE64_SOURCE 1
19
20#include <fcntl.h>
21#include <stdbool.h>
22#include <stdlib.h>
23#include <string.h>
24#include <sys/stat.h>
25#include <sys/types.h>
26#include <unistd.h>
27#include <zlib.h>
28
29#include "output_file.h"
30#include "sparse_format.h"
31#include "sparse_crc32.h"
32
33#ifndef USE_MINGW
34#include <sys/mman.h>
35#define O_BINARY 0
36#endif
37
38#if defined(__APPLE__) && defined(__MACH__)
39#define lseek64 lseek
40#define ftruncate64 ftruncate
41#define mmap64 mmap
42#define off64_t off_t
43#endif
44
45#ifdef __BIONIC__
46extern void* __mmap2(void *, size_t, int, int, int, off_t);
47static inline void *mmap64(void *addr, size_t length, int prot, int flags,
48 int fd, off64_t offset)
49{
50 return __mmap2(addr, length, prot, flags, fd, offset >> 12);
51}
52#endif
53
Colin Crossb55dcee2012-04-24 23:07:49 -070054#define min(a, b) \
55 ({ typeof(a) _a = (a); typeof(b) _b = (b); (_a < _b) ? _a : _b; })
56
Colin Cross28fa5bc2012-05-20 13:28:05 -070057#define SPARSE_HEADER_MAJOR_VER 1
58#define SPARSE_HEADER_MINOR_VER 0
59#define SPARSE_HEADER_LEN (sizeof(sparse_header_t))
60#define CHUNK_HEADER_LEN (sizeof(chunk_header_t))
61
62struct output_file_ops {
Colin Crossb55dcee2012-04-24 23:07:49 -070063 int (*skip)(struct output_file *, int64_t);
64 int (*write)(struct output_file *, void *, int);
Colin Cross28fa5bc2012-05-20 13:28:05 -070065 void (*close)(struct output_file *);
66};
67
Colin Crossb55dcee2012-04-24 23:07:49 -070068struct sparse_file_ops {
69 int (*write_data_chunk)(struct output_file *out, unsigned int len,
70 void *data);
71 int (*write_fill_chunk)(struct output_file *out, unsigned int len,
72 uint32_t fill_val);
73 int (*write_skip_chunk)(struct output_file *out, int64_t len);
74 int (*write_end_chunk)(struct output_file *out);
75};
76
Colin Cross28fa5bc2012-05-20 13:28:05 -070077struct output_file {
78 int fd;
79 gzFile gz_fd;
80 bool close_fd;
Colin Cross28fa5bc2012-05-20 13:28:05 -070081 int64_t cur_out_ptr;
Colin Crossb55dcee2012-04-24 23:07:49 -070082 unsigned int chunk_cnt;
83 uint32_t crc32;
Colin Cross28fa5bc2012-05-20 13:28:05 -070084 struct output_file_ops *ops;
Colin Crossb55dcee2012-04-24 23:07:49 -070085 struct sparse_file_ops *sparse_ops;
Colin Cross28fa5bc2012-05-20 13:28:05 -070086 int use_crc;
87 unsigned int block_size;
88 int64_t len;
Colin Crossb55dcee2012-04-24 23:07:49 -070089 char *zero_buf;
90 uint32_t *fill_buf;
Colin Cross28fa5bc2012-05-20 13:28:05 -070091};
92
Colin Crossb55dcee2012-04-24 23:07:49 -070093static int file_skip(struct output_file *out, int64_t cnt)
Colin Cross28fa5bc2012-05-20 13:28:05 -070094{
95 off64_t ret;
96
Colin Crossb55dcee2012-04-24 23:07:49 -070097 ret = lseek64(out->fd, cnt, SEEK_CUR);
Colin Cross28fa5bc2012-05-20 13:28:05 -070098 if (ret < 0) {
99 error_errno("lseek64");
100 return -1;
101 }
102 return 0;
103}
104
Colin Crossb55dcee2012-04-24 23:07:49 -0700105static int file_write(struct output_file *out, void *data, int len)
Colin Cross28fa5bc2012-05-20 13:28:05 -0700106{
107 int ret;
108 ret = write(out->fd, data, len);
109 if (ret < 0) {
110 error_errno("write");
111 return -1;
112 } else if (ret < len) {
113 error("incomplete write");
114 return -1;
115 }
116
117 return 0;
118}
119
120static void file_close(struct output_file *out)
121{
122 if (out->close_fd) {
123 close(out->fd);
124 }
125}
126
Colin Cross28fa5bc2012-05-20 13:28:05 -0700127static struct output_file_ops file_ops = {
Colin Crossb55dcee2012-04-24 23:07:49 -0700128 .skip = file_skip,
Colin Cross28fa5bc2012-05-20 13:28:05 -0700129 .write = file_write,
130 .close = file_close,
131};
132
Colin Crossb55dcee2012-04-24 23:07:49 -0700133static int gz_file_skip(struct output_file *out, int64_t cnt)
Colin Cross28fa5bc2012-05-20 13:28:05 -0700134{
135 off64_t ret;
136
Colin Crossb55dcee2012-04-24 23:07:49 -0700137 ret = gzseek(out->gz_fd, cnt, SEEK_CUR);
Colin Cross28fa5bc2012-05-20 13:28:05 -0700138 if (ret < 0) {
139 error_errno("gzseek");
140 return -1;
141 }
142 return 0;
143}
144
Colin Crossb55dcee2012-04-24 23:07:49 -0700145static int gz_file_write(struct output_file *out, void *data, int len)
Colin Cross28fa5bc2012-05-20 13:28:05 -0700146{
147 int ret;
148 ret = gzwrite(out->gz_fd, data, len);
149 if (ret < 0) {
150 error_errno("gzwrite");
151 return -1;
152 } else if (ret < len) {
153 error("incomplete gzwrite");
154 return -1;
155 }
156
157 return 0;
158}
159
160static void gz_file_close(struct output_file *out)
161{
162 gzclose(out->gz_fd);
163}
164
165static struct output_file_ops gz_file_ops = {
Colin Crossb55dcee2012-04-24 23:07:49 -0700166 .skip = gz_file_skip,
Colin Cross28fa5bc2012-05-20 13:28:05 -0700167 .write = gz_file_write,
168 .close = gz_file_close,
169};
170
Colin Cross13a56062012-06-19 16:45:48 -0700171int read_all(int fd, void *buf, size_t len)
172{
173 size_t total = 0;
174 int ret;
175 char *ptr = buf;
176
177 while (total < len) {
178 ret = read(fd, ptr, len - total);
179
180 if (ret < 0)
181 return -errno;
182
183 if (ret == 0)
184 return -EINVAL;
185
186 ptr += ret;
187 total += ret;
188 }
189
190 return 0;
191}
192
Colin Crossb55dcee2012-04-24 23:07:49 -0700193static int write_sparse_skip_chunk(struct output_file *out, int64_t skip_len)
Colin Cross28fa5bc2012-05-20 13:28:05 -0700194{
195 chunk_header_t chunk_header;
196 int ret, chunk;
197
Colin Cross28fa5bc2012-05-20 13:28:05 -0700198 if (skip_len % out->block_size) {
199 error("don't care size %llu is not a multiple of the block size %u",
200 skip_len, out->block_size);
201 return -1;
202 }
203
204 /* We are skipping data, so emit a don't care chunk. */
205 chunk_header.chunk_type = CHUNK_TYPE_DONT_CARE;
206 chunk_header.reserved1 = 0;
207 chunk_header.chunk_sz = skip_len / out->block_size;
208 chunk_header.total_sz = CHUNK_HEADER_LEN;
Colin Crossb55dcee2012-04-24 23:07:49 -0700209 ret = out->ops->write(out, &chunk_header, sizeof(chunk_header));
Colin Cross28fa5bc2012-05-20 13:28:05 -0700210 if (ret < 0)
211 return -1;
212
213 out->cur_out_ptr += skip_len;
214 out->chunk_cnt++;
215
216 return 0;
217}
218
Colin Crossb55dcee2012-04-24 23:07:49 -0700219static int write_sparse_fill_chunk(struct output_file *out, unsigned int len,
220 uint32_t fill_val)
Colin Cross28fa5bc2012-05-20 13:28:05 -0700221{
222 chunk_header_t chunk_header;
223 int rnd_up_len, zero_len, count;
224 int ret;
225 unsigned int i;
Colin Cross28fa5bc2012-05-20 13:28:05 -0700226
Colin Crossb55dcee2012-04-24 23:07:49 -0700227 /* Round up the fill length to a multiple of the block size */
228 rnd_up_len = ALIGN(len, out->block_size);
Colin Cross28fa5bc2012-05-20 13:28:05 -0700229
230 /* Finally we can safely emit a chunk of data */
231 chunk_header.chunk_type = CHUNK_TYPE_FILL;
232 chunk_header.reserved1 = 0;
233 chunk_header.chunk_sz = rnd_up_len / out->block_size;
234 chunk_header.total_sz = CHUNK_HEADER_LEN + sizeof(fill_val);
Colin Crossb55dcee2012-04-24 23:07:49 -0700235 ret = out->ops->write(out, &chunk_header, sizeof(chunk_header));
Colin Cross28fa5bc2012-05-20 13:28:05 -0700236
237 if (ret < 0)
238 return -1;
Colin Crossb55dcee2012-04-24 23:07:49 -0700239 ret = out->ops->write(out, &fill_val, sizeof(fill_val));
Colin Cross28fa5bc2012-05-20 13:28:05 -0700240 if (ret < 0)
241 return -1;
242
243 if (out->use_crc) {
Colin Crossb55dcee2012-04-24 23:07:49 -0700244 count = out->block_size / sizeof(uint32_t);
245 while (count--)
246 out->crc32 = sparse_crc32(out->crc32, &fill_val, sizeof(uint32_t));
Colin Cross28fa5bc2012-05-20 13:28:05 -0700247 }
248
249 out->cur_out_ptr += rnd_up_len;
250 out->chunk_cnt++;
251
252 return 0;
253}
254
Colin Crossb55dcee2012-04-24 23:07:49 -0700255static int write_sparse_data_chunk(struct output_file *out, unsigned int len,
256 void *data)
Colin Cross28fa5bc2012-05-20 13:28:05 -0700257{
258 chunk_header_t chunk_header;
259 int rnd_up_len, zero_len;
260 int ret;
261
Colin Crossb55dcee2012-04-24 23:07:49 -0700262 /* Round up the data length to a multiple of the block size */
263 rnd_up_len = ALIGN(len, out->block_size);
Colin Cross28fa5bc2012-05-20 13:28:05 -0700264 zero_len = rnd_up_len - len;
265
266 /* Finally we can safely emit a chunk of data */
267 chunk_header.chunk_type = CHUNK_TYPE_RAW;
268 chunk_header.reserved1 = 0;
269 chunk_header.chunk_sz = rnd_up_len / out->block_size;
270 chunk_header.total_sz = CHUNK_HEADER_LEN + rnd_up_len;
Colin Crossb55dcee2012-04-24 23:07:49 -0700271 ret = out->ops->write(out, &chunk_header, sizeof(chunk_header));
Colin Cross28fa5bc2012-05-20 13:28:05 -0700272
273 if (ret < 0)
274 return -1;
275 ret = out->ops->write(out, data, len);
276 if (ret < 0)
277 return -1;
278 if (zero_len) {
Colin Crossb55dcee2012-04-24 23:07:49 -0700279 ret = out->ops->write(out, out->zero_buf, zero_len);
Colin Cross28fa5bc2012-05-20 13:28:05 -0700280 if (ret < 0)
281 return -1;
282 }
283
284 if (out->use_crc) {
285 out->crc32 = sparse_crc32(out->crc32, data, len);
286 if (zero_len)
Colin Crossb55dcee2012-04-24 23:07:49 -0700287 out->crc32 = sparse_crc32(out->crc32, out->zero_buf, zero_len);
Colin Cross28fa5bc2012-05-20 13:28:05 -0700288 }
289
290 out->cur_out_ptr += rnd_up_len;
291 out->chunk_cnt++;
292
293 return 0;
294}
295
Colin Crossb55dcee2012-04-24 23:07:49 -0700296int write_sparse_end_chunk(struct output_file *out)
297{
298 chunk_header_t chunk_header;
299 int ret;
300
301 if (out->use_crc) {
302 chunk_header.chunk_type = CHUNK_TYPE_CRC32;
303 chunk_header.reserved1 = 0;
304 chunk_header.chunk_sz = 0;
305 chunk_header.total_sz = CHUNK_HEADER_LEN + 4;
306
307 ret = out->ops->write(out, &chunk_header, sizeof(chunk_header));
308 if (ret < 0) {
309 return ret;
310 }
311 out->ops->write(out, &out->crc32, 4);
312 if (ret < 0) {
313 return ret;
314 }
315
316 out->chunk_cnt++;
317 }
318
319 return 0;
320}
321
322static struct sparse_file_ops sparse_file_ops = {
323 .write_data_chunk = write_sparse_data_chunk,
324 .write_fill_chunk = write_sparse_fill_chunk,
325 .write_skip_chunk = write_sparse_skip_chunk,
326 .write_end_chunk = write_sparse_end_chunk,
327};
328
329static int write_normal_data_chunk(struct output_file *out, unsigned int len,
330 void *data)
331{
332 int ret;
333 unsigned int rnd_up_len = ALIGN(len, out->block_size);
334
335 ret = out->ops->write(out, data, len);
336 if (ret < 0) {
337 return ret;
338 }
339
340 if (rnd_up_len > len) {
341 ret = out->ops->skip(out, rnd_up_len - len);
342 }
343
344 return ret;
345}
346
347static int write_normal_fill_chunk(struct output_file *out, unsigned int len,
348 uint32_t fill_val)
349{
350 int ret;
351 unsigned int i;
352 unsigned int write_len;
353
354 /* Initialize fill_buf with the fill_val */
355 for (i = 0; i < out->block_size / sizeof(uint32_t); i++) {
356 out->fill_buf[i] = fill_val;
357 }
358
359 while (len) {
360 write_len = min(len, out->block_size);
361 ret = out->ops->write(out, out->fill_buf, write_len);
362 if (ret < 0) {
363 return ret;
364 }
365
366 len -= write_len;
367 }
368
369 return 0;
370}
371
372static int write_normal_skip_chunk(struct output_file *out, int64_t len)
373{
374 int ret;
375
376 return out->ops->skip(out, len);
377}
378
379int write_normal_end_chunk(struct output_file *out)
380{
381 int ret;
382
383 ret = ftruncate64(out->fd, out->len);
384 if (ret < 0) {
385 return -errno;
386 }
387
388 return 0;
389}
390
391static struct sparse_file_ops normal_file_ops = {
392 .write_data_chunk = write_normal_data_chunk,
393 .write_fill_chunk = write_normal_fill_chunk,
394 .write_skip_chunk = write_normal_skip_chunk,
395 .write_end_chunk = write_normal_end_chunk,
396};
397
Colin Cross28fa5bc2012-05-20 13:28:05 -0700398void close_output_file(struct output_file *out)
399{
400 int ret;
Colin Cross28fa5bc2012-05-20 13:28:05 -0700401
Colin Crossb55dcee2012-04-24 23:07:49 -0700402 out->sparse_ops->write_end_chunk(out);
Colin Cross28fa5bc2012-05-20 13:28:05 -0700403 out->ops->close(out);
Colin Crossb55dcee2012-04-24 23:07:49 -0700404 free(out);
Colin Cross28fa5bc2012-05-20 13:28:05 -0700405}
406
407struct output_file *open_output_fd(int fd, unsigned int block_size, int64_t len,
408 int gz, int sparse, int chunks, int crc)
409{
410 int ret;
411 struct output_file *out = malloc(sizeof(struct output_file));
412 if (!out) {
413 error_errno("malloc struct out");
414 return NULL;
415 }
Colin Crossb55dcee2012-04-24 23:07:49 -0700416 out->zero_buf = calloc(block_size, 1);
417 if (!out->zero_buf) {
Colin Cross28fa5bc2012-05-20 13:28:05 -0700418 error_errno("malloc zero_buf");
Colin Crossb55dcee2012-04-24 23:07:49 -0700419 goto err_zero_buf;
Colin Cross28fa5bc2012-05-20 13:28:05 -0700420 }
Colin Crossb55dcee2012-04-24 23:07:49 -0700421
422 out->fill_buf = calloc(block_size, 1);
423 if (!out->fill_buf) {
424 error_errno("malloc fill_buf");
425 goto err_fill_buf;
426 }
Colin Cross28fa5bc2012-05-20 13:28:05 -0700427
428 if (gz) {
429 out->ops = &gz_file_ops;
430 out->gz_fd = gzdopen(fd, "wb9");
431 if (!out->gz_fd) {
432 error_errno("gzopen");
Colin Crossb55dcee2012-04-24 23:07:49 -0700433 goto err_gzopen;
Colin Cross28fa5bc2012-05-20 13:28:05 -0700434 }
435 } else {
436 out->fd = fd;
437 out->ops = &file_ops;
438 }
Colin Crossb55dcee2012-04-24 23:07:49 -0700439
440 if (sparse) {
441 out->sparse_ops = &sparse_file_ops;
442 } else {
443 out->sparse_ops = &normal_file_ops;
444 }
445
Colin Cross28fa5bc2012-05-20 13:28:05 -0700446 out->close_fd = false;
Colin Cross28fa5bc2012-05-20 13:28:05 -0700447 out->cur_out_ptr = 0ll;
448 out->chunk_cnt = 0;
449
450 /* Initialize the crc32 value */
451 out->crc32 = 0;
452 out->use_crc = crc;
453
454 out->len = len;
455 out->block_size = block_size;
456
Colin Crossb55dcee2012-04-24 23:07:49 -0700457 if (sparse) {
458 sparse_header_t sparse_header = {
459 .magic = SPARSE_HEADER_MAGIC,
460 .major_version = SPARSE_HEADER_MAJOR_VER,
461 .minor_version = SPARSE_HEADER_MINOR_VER,
462 .file_hdr_sz = SPARSE_HEADER_LEN,
463 .chunk_hdr_sz = CHUNK_HEADER_LEN,
464 .blk_sz = out->block_size,
465 .total_blks = out->len / out->block_size,
466 .total_chunks = chunks,
467 .image_checksum = 0
468 };
Colin Cross28fa5bc2012-05-20 13:28:05 -0700469
Colin Crossb55dcee2012-04-24 23:07:49 -0700470 if (out->use_crc) {
471 sparse_header.total_chunks++;
472 }
473
474 ret = out->ops->write(out, &sparse_header, sizeof(sparse_header));
475 if (ret < 0) {
476 goto err_write;
477 }
Colin Cross28fa5bc2012-05-20 13:28:05 -0700478 }
479
480 return out;
Colin Crossb55dcee2012-04-24 23:07:49 -0700481
482err_write:
483 if (gz) {
484 gzclose(out->gz_fd);
485 }
486err_gzopen:
487 free(out->fill_buf);
488err_fill_buf:
489 free(out->zero_buf);
490err_zero_buf:
491 free(out);
492 return NULL;
Colin Cross28fa5bc2012-05-20 13:28:05 -0700493}
494
495struct output_file *open_output_file(const char *filename,
496 unsigned int block_size, int64_t len,
497 int gz, int sparse, int chunks, int crc)
498{
499 int fd;
500 struct output_file *file;
501
502 if (strcmp(filename, "-")) {
503 fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, 0644);
504 if (fd < 0) {
505 error_errno("open");
506 return NULL;
507 }
508 } else {
509 fd = STDOUT_FILENO;
510 }
511
512 file = open_output_fd(fd, block_size, len, gz, sparse, chunks, crc);
513 if (!file) {
514 close(fd);
515 return NULL;
516 }
517
518 file->close_fd = true; // we opened descriptor thus we responsible for closing it
519
520 return file;
521}
522
Colin Cross28fa5bc2012-05-20 13:28:05 -0700523/* Write a contiguous region of data blocks from a memory buffer */
Colin Crossb55dcee2012-04-24 23:07:49 -0700524int write_data_chunk(struct output_file *out, unsigned int len, void *data)
Colin Cross28fa5bc2012-05-20 13:28:05 -0700525{
Colin Crossb55dcee2012-04-24 23:07:49 -0700526 return out->sparse_ops->write_data_chunk(out, len, data);
Colin Cross28fa5bc2012-05-20 13:28:05 -0700527}
528
529/* Write a contiguous region of data blocks with a fill value */
Colin Crossb55dcee2012-04-24 23:07:49 -0700530int write_fill_chunk(struct output_file *out, unsigned int len,
531 uint32_t fill_val)
Colin Cross28fa5bc2012-05-20 13:28:05 -0700532{
Colin Crossb55dcee2012-04-24 23:07:49 -0700533 return out->sparse_ops->write_fill_chunk(out, len, fill_val);
Colin Cross28fa5bc2012-05-20 13:28:05 -0700534}
535
Colin Cross9e1f17e2012-04-25 18:31:39 -0700536int write_fd_chunk(struct output_file *out, unsigned int len,
537 int fd, int64_t offset)
Colin Cross28fa5bc2012-05-20 13:28:05 -0700538{
539 int ret;
540 int64_t aligned_offset;
541 int aligned_diff;
542 int buffer_size;
Colin Cross13a56062012-06-19 16:45:48 -0700543 char *ptr;
Colin Cross28fa5bc2012-05-20 13:28:05 -0700544
Colin Cross28fa5bc2012-05-20 13:28:05 -0700545 aligned_offset = offset & ~(4096 - 1);
546 aligned_diff = offset - aligned_offset;
547 buffer_size = len + aligned_diff;
548
549#ifndef USE_MINGW
Colin Cross9e1f17e2012-04-25 18:31:39 -0700550 char *data = mmap64(NULL, buffer_size, PROT_READ, MAP_SHARED, fd,
Colin Cross28fa5bc2012-05-20 13:28:05 -0700551 aligned_offset);
552 if (data == MAP_FAILED) {
Colin Cross9e1f17e2012-04-25 18:31:39 -0700553 return -errno;
Colin Cross28fa5bc2012-05-20 13:28:05 -0700554 }
Colin Cross13a56062012-06-19 16:45:48 -0700555 ptr = data + aligned_diff;
Colin Cross28fa5bc2012-05-20 13:28:05 -0700556#else
Colin Cross13a56062012-06-19 16:45:48 -0700557 off64_t pos;
558 char *data = malloc(len);
Colin Cross28fa5bc2012-05-20 13:28:05 -0700559 if (!data) {
Colin Cross9e1f17e2012-04-25 18:31:39 -0700560 return -errno;
Colin Cross28fa5bc2012-05-20 13:28:05 -0700561 }
Colin Cross13a56062012-06-19 16:45:48 -0700562 pos = lseek64(fd, offset, SEEK_SET);
563 if (pos < 0) {
564 return -errno;
565 }
566 ret = read_all(fd, data, len);
567 if (ret < 0) {
568 return ret;
569 }
570 ptr = data;
Colin Cross28fa5bc2012-05-20 13:28:05 -0700571#endif
572
Colin Cross13a56062012-06-19 16:45:48 -0700573 ret = out->sparse_ops->write_data_chunk(out, len, ptr);
Colin Cross28fa5bc2012-05-20 13:28:05 -0700574
Colin Cross28fa5bc2012-05-20 13:28:05 -0700575#ifndef USE_MINGW
576 munmap(data, buffer_size);
577#else
Colin Cross28fa5bc2012-05-20 13:28:05 -0700578 free(data);
579#endif
Colin Cross9e1f17e2012-04-25 18:31:39 -0700580
581 return ret;
582}
583
584/* Write a contiguous region of data blocks from a file */
585int write_file_chunk(struct output_file *out, unsigned int len,
586 const char *file, int64_t offset)
587{
588 int ret;
589
590 int file_fd = open(file, O_RDONLY | O_BINARY);
591 if (file_fd < 0) {
592 return -errno;
593 }
594
595 ret = write_fd_chunk(out, len, file_fd, offset);
596
Colin Cross28fa5bc2012-05-20 13:28:05 -0700597 close(file_fd);
Colin Crossb55dcee2012-04-24 23:07:49 -0700598
599 return ret;
600}
601
602int write_skip_chunk(struct output_file *out, int64_t len)
603{
604 return out->sparse_ops->write_skip_chunk(out, len);
Colin Cross28fa5bc2012-05-20 13:28:05 -0700605}