Skip to content

Commit

Permalink
pad_file: Write sparse files
Browse files Browse the repository at this point in the history
When writing the output to a regular file, only write the file extents
actually allocated in the input file. This utilized the FIEMAP ioctl on
supported filesystems with fallback to non-sparse output otherwise.

Signed-off-by: Stefan Sørensen <stefan.sorensen@spectralink.com>
  • Loading branch information
ssorensen committed Mar 11, 2019
1 parent abc1c32 commit 1b98d6f
Show file tree
Hide file tree
Showing 2 changed files with 148 additions and 30 deletions.
20 changes: 20 additions & 0 deletions test/basic-images.test
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,25 @@ check_size() {
fi
}

get_disk_usage() {
local file="${1}"
if [ ! -f "${file}" ]; then
echo "Failed to check file disk usage: '${file}' does not exist!"
return 1
fi
set -- $(du -B 1 "${file}")
usage="${1}"
}

check_disk_usage_range() {
local usage
get_disk_usage "${1}" || return
if [ "${usage}" -lt "${2}" -o "${usage}" -gt "${3}" ]; then
echo "Incorrect file disk usage for '${1}': expected min: ${2} max: ${3} found: ${usage}"
return 1
fi
}

exec_test_set_prereq() {
command -v "${1}" > /dev/null && test_set_prereq "${1/./_}"
}
Expand Down Expand Up @@ -194,6 +213,7 @@ test_expect_success fdisk,sfdisk "hdimage" "
setup_test_images &&
run_genimage hdimage.config test.hdimage &&
check_size images/test.hdimage 9442816 &&
check_disk_usage_range images/test.hdimage 40960 57344 &&
# check the this identifier
fdisk -l images/test.hdimage | grep identifier: > hdimage.fdisk &&
# check partitions; filter output to handle different sfdisk versions
Expand Down
158 changes: 128 additions & 30 deletions util.c
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
#include <sys/stat.h>
#include <sys/wait.h>
#include <linux/fs.h>
#include <linux/fiemap.h>
#include <unistd.h>
#include <fcntl.h>
#include <ctype.h>
Expand Down Expand Up @@ -312,13 +313,83 @@ static int open_file(struct image *image, const char *filename, int extra_flags)
return fd;
}

struct extent {
unsigned long long start, end;
};

/* Build a file extent covering the whole file */
static int whole_file_exent(size_t size, struct extent **extents,
size_t *extent_count)
{
*extents = xzalloc(sizeof(struct extent));
(*extents)[0].start = 0;
(*extents)[0].end = size;
*extent_count = 1;
return 0;
}

/* Build an file extent array for the file */
static int map_file_extents(struct image *image, const char *filename, int f,
size_t size, struct extent **extents,
size_t *extent_count)
{
struct fiemap *fiemap;
unsigned i;
int ret;

/* Get extent count */
fiemap = xzalloc(sizeof(struct fiemap));
fiemap->fm_length = size;
ret = ioctl(f, FS_IOC_FIEMAP, fiemap);
if (ret == -1)
goto err_out;

/* Get extents */
fiemap = realloc(fiemap, sizeof(struct fiemap) + fiemap->fm_mapped_extents * sizeof(struct fiemap_extent));
fiemap->fm_extent_count = fiemap->fm_mapped_extents;
ret = ioctl(f, FS_IOC_FIEMAP, fiemap);
if (ret == -1)
goto err_out;

/* Build extent array */
*extent_count = fiemap->fm_mapped_extents;
*extents = xzalloc(fiemap->fm_mapped_extents * sizeof(struct extent));
for (i = 0; i < fiemap->fm_mapped_extents; i++) {
(*extents)[i].start = fiemap->fm_extents[i].fe_logical;
(*extents)[i].end = fiemap->fm_extents[i].fe_logical + fiemap->fm_extents[i].fe_length;
}
free(fiemap);

/* The last extent may extend beyond the end of file, limit it to the actual end */
if ((*extents)[i-1].end > size)
(*extents)[i-1].end = size;

return 0;

err_out:
ret = -errno;

free(fiemap);

/* If failure is due to no filesystem support, return a single extent */
if (ret == -EOPNOTSUPP)
return whole_file_exent(size, extents, extent_count);

image_error(image, "fiemap %s: %d %s\n", filename, errno, strerror(errno));
return ret;
}

int pad_file(struct image *image, const char *infile,
size_t size, unsigned char fillpattern, enum pad_mode mode)
{
const char *outfile = imageoutfile(image);
int f = -1, outf = -1, flags = 0;
unsigned long f_offset = 0;
struct extent *extents;
size_t extent_count;
void *buf = NULL;
int now, r, w;
unsigned e;
struct stat s;
int ret = 0;

Expand Down Expand Up @@ -386,44 +457,71 @@ int pad_file(struct image *image, const char *infile,
goto fill;
}

while (size) {
now = min(size, 4096);

r = read(f, buf, now);
w = write(outf, buf, r);
if (w < r) {
ret = -errno;
image_error(image, "write %s: %s\n", outfile, strerror(errno));
if ((s.st_mode & S_IFMT) == S_IFREG) {
ret = map_file_extents(image, infile, f, size, &extents, &extent_count);
if (ret != 0)
goto err_out;
}
size -= r;

if (r < now)
goto fill;
}

now = read(f, buf, 1);
if (now == 1) {
image_error(image, "input file '%s' too large\n", infile);
ret = -EINVAL;
goto err_out;
else {
whole_file_exent(size, &extents, &extent_count);
}

fill:
memset(buf, fillpattern, 4096);
for (e = 0; e < extent_count && size > 0; e++) {
/* Ship over any holes in the input file */
if (f_offset != extents[e].start) {
unsigned long skip = extents[e].start - f_offset;
lseek(f, skip, SEEK_CUR);
lseek(outf, skip, SEEK_CUR);
size -= skip;
f_offset += skip;
}

while (size) {
now = min(size, 4096);
/* Copy the data in the extent */
while (f_offset < extents[e].end) {
now = min(extents[e].end - f_offset, 4096);

r = read(f, buf, now);
w = write(outf, buf, r);
if (w < r) {
ret = -errno;
image_error(image, "write %s: %s\n", outfile, strerror(errno));
goto err_out;
}
size -= r;
f_offset += r;

if (r < now)
goto fill;
}
}

r = write(outf, buf, now);
if (r < now) {
ret = -errno;
image_error(image, "write %s: %s\n", outfile, strerror(errno));
goto err_out;
fill:
if (fillpattern == 0 && (s.st_mode & S_IFMT) == S_IFREG) {
/* Truncate output to desired size */
image_info(image, "f_offset=%lu filesize=%llu\n", f_offset, (unsigned long long)lseek(outf, 0, SEEK_CUR));
image->last_offset = lseek(outf, 0, SEEK_CUR) + size;
ret = ftruncate(outf, image->last_offset);
if (ret == -1) {
image_error(image, "ftruncate %s: %s\n", outfile, strerror(errno));
goto err_out;
}
}
else {
memset(buf, fillpattern, 4096);

while (size) {
now = min(size, 4096);

r = write(outf, buf, now);
if (r < now) {
ret = -errno;
image_error(image, "write %s: %s\n", outfile, strerror(errno));
goto err_out;
}
size -= now;
}
size -= now;
image->last_offset = lseek(outf, 0, SEEK_CUR);
}
image->last_offset = lseek(outf, 0, SEEK_CUR);
err_out:
free(buf);
if (f >= 0)
Expand Down

0 comments on commit 1b98d6f

Please sign in to comment.