]> wimlib.net Git - wimlib/blobdiff - src/unix_apply.c
join.c: clean up verify_swm_set()
[wimlib] / src / unix_apply.c
index 122e1163580b3397ab1d733de984c877844a83ff..b17bd35101c39c48a078fea47b57a2bdcfc1e6fa 100644 (file)
@@ -3,7 +3,7 @@
  */
 
 /*
- * 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
@@ -54,6 +54,7 @@ static int
 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;
@@ -81,6 +82,13 @@ struct unix_apply_ctx {
         * 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];
 
@@ -156,7 +164,9 @@ unix_build_extraction_path(const struct wim_dentry *dentry,
        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));
@@ -181,6 +191,17 @@ unix_build_inode_extraction_path(const struct wim_inode *inode,
        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).
@@ -356,7 +377,7 @@ unix_create_if_directory(const struct wim_dentry *dentry,
        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);
@@ -391,7 +412,7 @@ unix_extract_if_empty_file(const struct wim_dentry *dentry,
 
        /* 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;
 
@@ -423,7 +444,7 @@ unix_extract_if_empty_file(const struct wim_dentry *dentry,
 
                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;
@@ -480,7 +501,7 @@ unix_count_dentries(const struct list_head *dentry_list,
 
                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) &&
@@ -528,6 +549,7 @@ unix_cleanup_open_fds(struct unix_apply_ctx *ctx, unsigned offset)
        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
@@ -564,13 +586,22 @@ unix_begin_extract_blob_instance(const struct blob_descriptor *blob,
        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);
 }
@@ -598,21 +629,41 @@ unix_begin_extract_blob(struct blob_descriptor *blob, void *_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  */
@@ -652,10 +703,17 @@ unix_end_extract_blob(struct blob_descriptor *blob, int status, void *_ctx)
                        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;
@@ -680,7 +738,7 @@ unix_set_dir_metadata(struct list_head *dentry_list, struct unix_apply_ctx *ctx)
        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;
@@ -752,7 +810,7 @@ unix_extract(struct list_head *dentry_list, struct apply_ctx *_ctx)
 
        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,
        };