*/
/*
- * Copyright (C) 2012, 2013, 2014 Eric Biggers
+ * Copyright (C) 2012-2016 Eric Biggers
*
* This file is free software; you can redistribute it and/or modify it under
* the terms of the GNU Lesser General Public License as published by the Free
unix_get_supported_features(const char *target,
struct wim_features *supported_features)
{
+ supported_features->sparse_files = 1;
supported_features->hard_links = 1;
supported_features->symlink_reparse_points = 1;
supported_features->unix_data = 1;
* the beginning of the array. */
unsigned num_open_fds;
+ /* For each currently open file, whether we're writing to it in "sparse"
+ * mode or not. */
+ bool is_sparse_file[MAX_OPEN_FILES];
+
+ /* Whether is_sparse_file[] is true for any currently open file */
+ bool any_sparse_files;
+
/* Buffer for reading reparse point data into memory */
u8 reparse_data[REPARSE_DATA_MAX_SIZE];
d = dentry;
do {
p -= d->d_extraction_name_nchars;
- memcpy(p, d->d_extraction_name, d->d_extraction_name_nchars);
+ if (d->d_extraction_name_nchars)
+ memcpy(p, d->d_extraction_name,
+ d->d_extraction_name_nchars);
*--p = '/';
d = d->d_parent;
} while (!dentry_is_root(d) && will_extract_dentry(d));
return unix_build_extraction_path(inode_first_extraction_dentry(inode), ctx);
}
+/* Should the specified file be extracted as a directory on UNIX? We extract
+ * the file as a directory if FILE_ATTRIBUTE_DIRECTORY is set and the file does
+ * not have a symlink or junction reparse point. It *may* have a different type
+ * of reparse point. */
+static inline bool
+should_extract_as_directory(const struct wim_inode *inode)
+{
+ return (inode->i_attributes & FILE_ATTRIBUTE_DIRECTORY) &&
+ !inode_is_symlink(inode);
+}
+
/* Sets the timestamps on a file being extracted.
*
* Either @fd or @path must be specified (not -1 and not NULL, respectively).
const char *path;
struct stat stbuf;
- if (!dentry_is_directory(dentry))
+ if (!should_extract_as_directory(dentry->d_inode))
return 0;
path = unix_build_extraction_path(dentry, ctx);
/* Is this a directory, a symbolic link, or any type of nonempty file?
*/
- if (inode_is_directory(inode) || inode_is_symlink(inode) ||
+ if (should_extract_as_directory(inode) || inode_is_symlink(inode) ||
inode_get_blob_for_unnamed_data_stream_resolved(inode))
return 0;
path = unix_build_extraction_path(dentry, ctx);
retry_create:
- fd = open(path, O_TRUNC | O_CREAT | O_WRONLY | O_NOFOLLOW, 0644);
+ fd = open(path, O_EXCL | O_CREAT | O_WRONLY | O_NOFOLLOW, 0644);
if (fd < 0) {
if (errno == EEXIST && !unlink(path))
goto retry_create;
const struct wim_inode *inode = dentry->d_inode;
- if (inode_is_directory(inode))
+ if (should_extract_as_directory(inode))
dir_count++;
else if ((dentry == inode_first_extraction_dentry(inode)) &&
!inode_is_symlink(inode) &&
for (unsigned i = offset; i < ctx->num_open_fds; i++)
filedes_close(&ctx->open_fds[i]);
ctx->num_open_fds = 0;
+ ctx->any_sparse_files = false;
}
static int
first_dentry = inode_first_extraction_dentry(inode);
first_path = unix_build_extraction_path(first_dentry, ctx);
retry_create:
- fd = open(first_path, O_TRUNC | O_CREAT | O_WRONLY | O_NOFOLLOW, 0644);
+ fd = open(first_path, O_EXCL | O_CREAT | O_WRONLY | O_NOFOLLOW, 0644);
if (fd < 0) {
if (errno == EEXIST && !unlink(first_path))
goto retry_create;
ERROR_WITH_ERRNO("Can't create regular file \"%s\"", first_path);
return WIMLIB_ERR_OPEN;
}
+ if (inode->i_attributes & FILE_ATTRIBUTE_SPARSE_FILE) {
+ ctx->is_sparse_file[ctx->num_open_fds] = true;
+ ctx->any_sparse_files = true;
+ } else {
+ ctx->is_sparse_file[ctx->num_open_fds] = false;
+#ifdef HAVE_POSIX_FALLOCATE
+ posix_fallocate(fd, 0, blob->size);
+#endif
+ }
filedes_init(&ctx->open_fds[ctx->num_open_fds++], fd);
return unix_create_hardlinks(inode, first_dentry, first_path, ctx);
}
/* Called when the next chunk of a blob has been read for extraction */
static int
-unix_extract_chunk(const void *chunk, size_t size, void *_ctx)
+unix_extract_chunk(const struct blob_descriptor *blob, u64 offset,
+ const void *chunk, size_t size, void *_ctx)
{
struct unix_apply_ctx *ctx = _ctx;
+ const void * const end = chunk + size;
+ const void *p;
+ bool zeroes;
+ size_t len;
+ unsigned i;
int ret;
- for (unsigned i = 0; i < ctx->num_open_fds; i++) {
- ret = full_write(&ctx->open_fds[i], chunk, size);
- if (ret) {
- ERROR_WITH_ERRNO("Error writing data to filesystem");
- return ret;
+ /*
+ * For sparse files, only write nonzero regions. This lets the
+ * filesystem use holes to represent zero regions.
+ */
+ for (p = chunk; p != end; p += len, offset += len) {
+ zeroes = maybe_detect_sparse_region(p, end - p, &len,
+ ctx->any_sparse_files);
+ for (i = 0; i < ctx->num_open_fds; i++) {
+ if (!zeroes || !ctx->is_sparse_file[i]) {
+ ret = full_pwrite(&ctx->open_fds[i],
+ p, len, offset);
+ if (ret)
+ goto err;
+ }
}
}
+
if (ctx->reparse_ptr)
ctx->reparse_ptr = mempcpy(ctx->reparse_ptr, chunk, size);
return 0;
+
+err:
+ ERROR_WITH_ERRNO("Error writing data to filesystem");
+ return ret;
}
/* Called when a blob has been fully read for extraction */
if (ret)
break;
} else {
- /* Set metadata on regular file just before closing it.
- */
struct filedes *fd = &ctx->open_fds[j];
+ /* If the file is sparse, extend it to its final size. */
+ if (ctx->is_sparse_file[j] && ftruncate(fd->fd, blob->size)) {
+ ERROR_WITH_ERRNO("Error extending \"%s\" to final size",
+ unix_build_inode_extraction_path(inode, ctx));
+ ret = WIMLIB_ERR_WRITE;
+ break;
+ }
+
+ /* Set metadata on regular file just before closing. */
ret = unix_set_metadata(fd->fd, inode, NULL, ctx);
if (ret)
break;
int ret;
list_for_each_entry_reverse(dentry, dentry_list, d_extraction_list_node) {
- if (dentry_is_directory(dentry)) {
+ if (should_extract_as_directory(dentry->d_inode)) {
ret = unix_set_metadata(-1, dentry->d_inode, NULL, ctx);
if (ret)
return ret;
struct read_blob_callbacks cbs = {
.begin_blob = unix_begin_extract_blob,
- .consume_chunk = unix_extract_chunk,
+ .continue_blob = unix_extract_chunk,
.end_blob = unix_end_extract_blob,
.ctx = ctx,
};