X-Git-Url: https://wimlib.net/git/?p=wimlib;a=blobdiff_plain;f=src%2Fntfs-3g_apply.c;h=c012c8e5b600188d76115949098763eb5b22d6b0;hp=a8292c7e2432c90da4b8d6c919e1a50fbf9c6dfa;hb=HEAD;hpb=00a775dc256d1fc8254d4f055e362e67f25b66d8 diff --git a/src/ntfs-3g_apply.c b/src/ntfs-3g_apply.c index a8292c7e..b0b376fa 100644 --- a/src/ntfs-3g_apply.c +++ b/src/ntfs-3g_apply.c @@ -1,678 +1,1050 @@ /* * ntfs-3g_apply.c * - * Apply a WIM image directly to a NTFS volume using libntfs-3g. Restore as + * Apply a WIM image directly to an NTFS volume using libntfs-3g. Restore as * much information as possible, including security data, file attributes, DOS - * names, and alternate data streams. + * names, alternate data streams, and object IDs. + * + * Note: because NTFS-3G offers inode-based interfaces, we actually don't need + * to deal with paths at all! (Other than for error messages.) */ /* - * Copyright (C) 2012, 2013 Eric Biggers - * - * This file is part of wimlib, a library for working with WIM files. + * Copyright (C) 2012-2017 Eric Biggers * - * wimlib is free software; you can redistribute it and/or modify it under the - * terms of the GNU General Public License as published by the Free - * Software Foundation; either version 3 of the License, or (at your option) - * any later version. + * 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 + * Software Foundation; either version 3 of the License, or (at your option) any + * later version. * - * wimlib is distributed in the hope that it will be useful, but WITHOUT ANY - * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR - * A PARTICULAR PURPOSE. See the GNU General Public License for more + * This file is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more * details. * - * You should have received a copy of the GNU General Public License - * along with wimlib; if not, see http://www.gnu.org/licenses/. + * You should have received a copy of the GNU Lesser General Public License + * along with this file; if not, see https://www.gnu.org/licenses/. */ #ifdef HAVE_CONFIG_H # include "config.h" #endif -#ifdef WITH_NTFS_3G - +#include #include #include -#include /* NTFS-3g headers are missing include */ #include -#include +#include #include #include -#include -#include +#include "wimlib/assert.h" #include "wimlib/apply.h" -#include "wimlib/compiler.h" +#include "wimlib/blob_table.h" #include "wimlib/dentry.h" #include "wimlib/encoding.h" #include "wimlib/error.h" -#include "wimlib/lookup_table.h" #include "wimlib/metadata.h" #include "wimlib/ntfs_3g.h" +#include "wimlib/object_id.h" #include "wimlib/reparse.h" #include "wimlib/security.h" -struct ntfs_attr_extract_ctx { - u64 offset; - ntfs_attr *na; -}; - static int -extract_wim_chunk_to_ntfs_attr(const void *buf, size_t len, void *_ctx) +ntfs_3g_get_supported_features(const char *target, + struct wim_features *supported_features) { - struct ntfs_attr_extract_ctx *ctx = _ctx; - if (ntfs_attr_pwrite(ctx->na, ctx->offset, len, buf) == len) { - ctx->offset += len; - return 0; - } else { - ERROR_WITH_ERRNO("Error extracting WIM resource to NTFS attribute"); - return WIMLIB_ERR_WRITE; - } + supported_features->readonly_files = 1; + supported_features->hidden_files = 1; + supported_features->system_files = 1; + supported_features->archive_files = 1; + supported_features->compressed_files = 1; + supported_features->not_context_indexed_files = 1; + supported_features->sparse_files = 1; + supported_features->named_data_streams = 1; + supported_features->hard_links = 1; + supported_features->reparse_points = 1; + supported_features->security_descriptors = 1; + supported_features->short_names = 1; + supported_features->object_ids = 1; + supported_features->timestamps = 1; + supported_features->case_sensitive_filenames = 1; + return 0; } -/* - * Extracts a WIM resource to a NTFS attribute. - */ +struct ntfs_3g_apply_ctx { + /* Extract flags, the pointer to the WIMStruct, etc. */ + struct apply_ctx common; + + /* Pointer to the open NTFS volume */ + ntfs_volume *vol; + + ntfs_attr *open_attrs[MAX_OPEN_FILES]; + unsigned num_open_attrs; + ntfs_inode *open_inodes[MAX_OPEN_FILES]; + unsigned num_open_inodes; + + /* For each currently open attribute, whether we're writing to it in + * "sparse" mode or not. */ + bool is_sparse_attr[MAX_OPEN_FILES]; + + /* Whether is_sparse_attr[] is true for any currently open attribute */ + bool any_sparse_attrs; + + struct reparse_buffer_disk rpbuf; + u8 *reparse_ptr; + + unsigned num_reparse_inodes; + ntfs_inode *ntfs_reparse_inodes[MAX_OPEN_FILES]; + struct wim_inode *wim_reparse_inodes[MAX_OPEN_FILES]; +}; + static int -extract_wim_resource_to_ntfs_attr(const struct wim_lookup_table_entry *lte, - ntfs_attr *na) +ntfs_3g_set_timestamps(ntfs_inode *ni, const struct wim_inode *inode) { - struct ntfs_attr_extract_ctx ctx; - ctx.na = na; - ctx.offset = 0; - return extract_wim_resource(lte, wim_resource_size(lte), - extract_wim_chunk_to_ntfs_attr, &ctx); + u64 times[3] = { + inode->i_creation_time, + inode->i_last_write_time, + inode->i_last_access_time, + }; + + if (ntfs_inode_set_times(ni, (const char *)times, sizeof(times), 0)) + return WIMLIB_ERR_SET_TIMESTAMPS; + return 0; } -/* Writes the data streams of a WIM inode to the data attributes of a NTFS - * inode. - * - * @ni: The NTFS inode to which the streams are to be extracted. - * - * @dentry: The WIM dentry being extracted. The @d_inode member points to the - * corresponding WIM inode that contains the streams being extracted. - * The WIM dentry itself is only needed to provide a file path for - * better error messages. - * - * @progress_info: Progress information for the image application. The number - * of extracted bytes will be incremented by the uncompressed - * size of each stream extracted. - * - * Returns 0 on success, nonzero on failure. - */ +/* Restore the timestamps on the NTFS inode corresponding to @inode. */ static int -write_ntfs_data_streams(ntfs_inode *ni, struct wim_dentry *dentry, - union wimlib_progress_info *progress_info) +ntfs_3g_restore_timestamps(ntfs_volume *vol, const struct wim_inode *inode) { - int ret = 0; - unsigned stream_idx = 0; - ntfschar *stream_name = AT_UNNAMED; - u32 stream_name_nbytes = 0; - const struct wim_inode *inode = dentry->d_inode; - struct wim_lookup_table_entry *lte; - - lte = inode->i_lte; - - /* For directories, skip unnamed streams; just extract alternate data - * streams. */ - if (inode->i_attributes & FILE_ATTRIBUTE_DIRECTORY) - goto cont; - - DEBUG("Writing %u NTFS data stream%s for `%s'", - inode->i_num_ads + 1, - (inode->i_num_ads == 0 ? "" : "s"), - dentry->_full_path); - - for (;;) { - if (stream_name_nbytes) { - /* Skip special UNIX data entries (see documentation for - * WIMLIB_ADD_FLAG_UNIX_DATA) */ - if (stream_name_nbytes == WIMLIB_UNIX_DATA_TAG_UTF16LE_NBYTES - && !memcmp(stream_name, - WIMLIB_UNIX_DATA_TAG_UTF16LE, - WIMLIB_UNIX_DATA_TAG_UTF16LE_NBYTES)) - goto cont; - - /* Create an empty named stream. */ - ret = ntfs_attr_add(ni, AT_DATA, stream_name, - stream_name_nbytes / 2, NULL, 0); - if (ret) { - ERROR_WITH_ERRNO("Failed to create named data " - "stream for extracted file " - "`%s'", - dentry->_full_path); - ret = WIMLIB_ERR_NTFS_3G; - break; + ntfs_inode *ni; + int res; - } - } + ni = ntfs_inode_open(vol, inode->i_mft_no); + if (!ni) + goto fail; - /* If there's no lookup table entry, it's an empty stream. - * Otherwise, open the attribute and extract the data. */ - if (lte) { - ntfs_attr *na; - - na = ntfs_attr_open(ni, AT_DATA, stream_name, - stream_name_nbytes / 2); - if (!na) { - ERROR_WITH_ERRNO("Failed to open a data stream of " - "extracted file `%s'", - dentry->_full_path); - ret = WIMLIB_ERR_NTFS_3G; - break; - } + res = ntfs_3g_set_timestamps(ni, inode); - /* The WIM lookup table entry provides the stream - * length, so the NTFS attribute should be resized to - * this length before starting to extract the data. */ - ret = ntfs_attr_truncate_solid(na, wim_resource_size(lte)); - if (ret) { - ntfs_attr_close(na); - break; - } + if (ntfs_inode_close(ni) || res) + goto fail; - /* Actually extract the stream */ - ret = extract_wim_resource_to_ntfs_attr(lte, na); + return 0; - /* Close the attribute */ - ntfs_attr_close(na); - if (ret) - break; +fail: + ERROR_WITH_ERRNO("Failed to update timestamps of \"%s\" in NTFS volume", + dentry_full_path(inode_first_extraction_dentry(inode))); + return WIMLIB_ERR_SET_TIMESTAMPS; +} - /* Record the number of bytes of uncompressed data that - * have been extracted. */ - progress_info->extract.completed_bytes += wim_resource_size(lte); - } - cont: - if (stream_idx == inode->i_num_ads) /* Has the last stream been extracted? */ - break; +/* Restore the DOS name of the @dentry. + * This closes both @ni and @dir_ni. + * If either is NULL, then they are opened temporarily. */ +static int +ntfs_3g_restore_dos_name(ntfs_inode *ni, ntfs_inode *dir_ni, + struct wim_dentry *dentry, ntfs_volume *vol) +{ + int ret; + const char *dos_name; + size_t dos_name_nbytes; - /* Get the name and lookup table entry for the next stream. */ - stream_name = inode->i_ads_entries[stream_idx].stream_name; - stream_name_nbytes = inode->i_ads_entries[stream_idx].stream_name_nbytes; - lte = inode->i_ads_entries[stream_idx].lte; - stream_idx++; + /* Note: ntfs_set_ntfs_dos_name() closes both inodes (even if it fails). + * And it takes in a multibyte string, even though it translates it to + * UTF-16LE internally... which is annoying because we currently have + * the UTF-16LE string but not the multibyte string. */ + + ret = utf16le_get_tstr(dentry->d_short_name, dentry->d_short_name_nbytes, + &dos_name, &dos_name_nbytes); + if (ret) + goto out_close; + + if (!dir_ni) + dir_ni = ntfs_inode_open(vol, dentry->d_parent->d_inode->i_mft_no); + if (!ni) + ni = ntfs_inode_open(vol, dentry->d_inode->i_mft_no); + if (dir_ni && ni) { + ret = ntfs_set_ntfs_dos_name(ni, dir_ni, + dos_name, dos_name_nbytes, 0); + dir_ni = NULL; + ni = NULL; + } else { + ret = -1; } + utf16le_put_tstr(dos_name); + if (unlikely(ret)) { + int err = errno; + ERROR_WITH_ERRNO("Failed to set DOS name of \"%s\" in NTFS " + "volume", dentry_full_path(dentry)); + if (err == EILSEQ) { + ERROR("This error may have been caused by a known " + "bug in libntfs-3g where it is unable to set " + "DOS names on files whose long names contain " + "unpaired surrogate characters. This bug " + "was fixed in NTFS-3G version 2017.3.23."); + } + if (err == EINVAL) { + utf16lechar c = + dentry->d_name[dentry->d_name_nbytes / 2 - 1]; + if (c == cpu_to_le16('.') || c == cpu_to_le16(' ')) { + ERROR("This error was probably caused by a " + "known bug in libntfs-3g where it is " + "unable to set DOS names on files whose " + "long names end with a dot or space " + "character. This bug was fixed in " + "NTFS-3G version 2017.3.23."); + } + } + ret = WIMLIB_ERR_SET_SHORT_NAME; + goto out_close; + } + + /* Unlike most other NTFS-3G functions, ntfs_set_ntfs_dos_name() + * changes the directory's last modification timestamp... + * Change it back. */ + return ntfs_3g_restore_timestamps(vol, dentry->d_parent->d_inode); + +out_close: + /* ntfs_inode_close() can take a NULL argument, but it's probably best + * not to rely on this behavior. */ + if (ni) + ntfs_inode_close(ni); + if (dir_ni) + ntfs_inode_close(dir_ni); return ret; } -/* Open the NTFS inode that corresponds to the parent of a WIM dentry. Returns - * the opened inode, or NULL on failure. */ -static ntfs_inode * -dentry_open_parent_ni(struct wim_dentry *dentry, ntfs_volume *vol) +static int +ntfs_3g_restore_reparse_point(ntfs_inode *ni, const struct wim_inode *inode, + unsigned blob_size, struct ntfs_3g_apply_ctx *ctx) { - char *p; - const char *dir_name; - ntfs_inode *dir_ni; - char orig; + complete_reparse_point(&ctx->rpbuf, inode, blob_size); + + if (ntfs_set_ntfs_reparse_data(ni, (const char *)&ctx->rpbuf, + REPARSE_DATA_OFFSET + blob_size, 0)) + { + int err = errno; + ERROR_WITH_ERRNO("Failed to set reparse data on \"%s\"", + dentry_full_path( + inode_first_extraction_dentry(inode))); + if (err == EINVAL && !(inode->i_reparse_tag & 0x80000000)) { + WARNING("This reparse point had a non-Microsoft reparse " + "tag. The preceding error may have been caused " + "by a known bug in libntfs-3g where it does not " + "correctly validate non-Microsoft reparse " + "points. This bug was fixed in NTFS-3G version " + "2016.2.22."); + } + return WIMLIB_ERR_SET_REPARSE_DATA; + } - p = dentry->_full_path + dentry->full_path_nbytes; - do { - p--; - } while (*p != '/'); + return 0; +} - orig = *p; - *p = '\0'; - dir_name = dentry->_full_path; - dir_ni = ntfs_pathname_to_inode(vol, NULL, dir_name); - if (!dir_ni) { - ERROR_WITH_ERRNO("Could not find NTFS inode for `%s'", - dir_name); +static bool +ntfs_3g_has_empty_attributes(const struct wim_inode *inode) +{ + for (unsigned i = 0; i < inode->i_num_streams; i++) { + const struct wim_inode_stream *strm = &inode->i_streams[i]; + + if (stream_blob_resolved(strm) == NULL && + (strm->stream_type == STREAM_TYPE_REPARSE_POINT || + stream_is_named_data_stream(strm))) + return true; } - *p = orig; - return dir_ni; + return false; } /* - * Makes a NTFS hard link. - * - * The hard link is named @from_dentry->file_name and is located under the - * directory specified by @dir_ni, and it is made to point to the previously - * extracted file located at @inode->i_extracted_file. - * - * Or, in other words, this adds a new name @from_dentry->full_path to an - * existing NTFS inode which already has a name @inode->i_extracted_file. + * Create empty attributes (named data streams and potentially a reparse point) + * for the specified file, if there are any. * - * The new name is made in the POSIX namespace (this is the behavior of - * ntfs_link()). + * Since these won't have blob descriptors, they won't show up in the call to + * extract_blob_list(). Hence the need for the special case. * - * Return 0 on success, nonzero on failure. dir_ni is closed either way. + * Keep this in sync with ntfs_3g_has_empty_attributes()! */ static int -apply_ntfs_hardlink(struct wim_dentry *from_dentry, - const struct wim_inode *inode, - ntfs_inode *dir_ni) +ntfs_3g_create_empty_attributes(ntfs_inode *ni, + const struct wim_inode *inode, + struct ntfs_3g_apply_ctx *ctx) { - int ret; - ntfs_inode *to_ni; - ntfs_volume *vol; + for (unsigned i = 0; i < inode->i_num_streams; i++) { - vol = dir_ni->vol; - ret = ntfs_inode_close(dir_ni); - if (ret != 0) { - ERROR_WITH_ERRNO("Error closing directory"); - return WIMLIB_ERR_NTFS_3G; + const struct wim_inode_stream *strm = &inode->i_streams[i]; + int ret; + + if (stream_blob_resolved(strm) != NULL) + continue; + + if (strm->stream_type == STREAM_TYPE_REPARSE_POINT) { + ret = ntfs_3g_restore_reparse_point(ni, inode, 0, ctx); + if (ret) + return ret; + } else if (stream_is_named_data_stream(strm)) { + if (ntfs_attr_add(ni, AT_DATA, strm->stream_name, + utf16le_len_chars(strm->stream_name), + NULL, 0)) + { + ERROR_WITH_ERRNO("Failed to create named data " + "stream of \"%s\"", + dentry_full_path( + inode_first_extraction_dentry(inode))); + return WIMLIB_ERR_NTFS_3G; + } + } } + return 0; +} - DEBUG("Extracting NTFS hard link `%s' => `%s'", - from_dentry->_full_path, inode->i_extracted_file); +/* Set attributes, security descriptor, and timestamps on the NTFS inode @ni. + */ +static int +ntfs_3g_set_metadata(ntfs_inode *ni, const struct wim_inode *inode, + const struct ntfs_3g_apply_ctx *ctx) +{ + int extract_flags; + const struct wim_security_data *sd; + struct wim_dentry *one_dentry; + int ret; - to_ni = ntfs_pathname_to_inode(vol, NULL, inode->i_extracted_file); - if (!to_ni) { - ERROR_WITH_ERRNO("Could not find NTFS inode for `%s'", - inode->i_extracted_file); - return WIMLIB_ERR_NTFS_3G; + extract_flags = ctx->common.extract_flags; + sd = wim_get_current_security_data(ctx->common.wim); + one_dentry = inode_first_extraction_dentry(inode); + + /* Object ID */ + { + u32 len; + const void *object_id = inode_get_object_id(inode, &len); + if (unlikely(object_id != NULL) && + ntfs_set_ntfs_object_id(ni, object_id, len, 0)) + { + if (errno == EEXIST) { + WARNING("Duplicate object ID on file \"%s\"", + dentry_full_path(one_dentry)); + } else { + ERROR_WITH_ERRNO("Failed to set object ID on " + "\"%s\" in NTFS volume", + dentry_full_path(one_dentry)); + return WIMLIB_ERR_NTFS_3G; + } + } } - dir_ni = dentry_open_parent_ni(from_dentry, vol); - if (!dir_ni) { - ntfs_inode_close(to_ni); - return WIMLIB_ERR_NTFS_3G; + /* Attributes */ + if (!(extract_flags & WIMLIB_EXTRACT_FLAG_NO_ATTRIBUTES)) { + u32 attrib = inode->i_attributes; + + if (ntfs_set_ntfs_attrib(ni, (const char *)&attrib, + sizeof(attrib), 0)) + { + ERROR_WITH_ERRNO("Failed to set attributes on \"%s\" " + "in NTFS volume", + dentry_full_path(one_dentry)); + return WIMLIB_ERR_SET_ATTRIBUTES; + } } - ret = ntfs_link(to_ni, dir_ni, - from_dentry->file_name, - from_dentry->file_name_nbytes / 2); - ret |= ntfs_inode_close(dir_ni); - ret |= ntfs_inode_close(to_ni); + /* Security descriptor */ + if (inode_has_security_descriptor(inode) + && !(extract_flags & WIMLIB_EXTRACT_FLAG_NO_ACLS)) + { + struct SECURITY_CONTEXT sec_ctx = { ctx->vol }; + const void *desc; + size_t desc_size; + + desc = sd->descriptors[inode->i_security_id]; + desc_size = sd->sizes[inode->i_security_id]; + + ret = ntfs_set_ntfs_acl(&sec_ctx, ni, desc, desc_size, 0); + + if (unlikely(ret)) { + int err = errno; + ERROR_WITH_ERRNO("Failed to set security descriptor on " + "\"%s\" in NTFS volume", + dentry_full_path(one_dentry)); + if (err == EINVAL && wimlib_print_errors) { + fprintf(wimlib_error_file, + "The security descriptor is: "); + print_byte_field(desc, desc_size, wimlib_error_file); + fprintf(wimlib_error_file, + "\n\nThis error occurred because libntfs-3g thinks " + "the security descriptor is invalid. There " + "are several known bugs with libntfs-3g's " + "security descriptor validation logic in older " + "versions. Please upgrade to NTFS-3G version " + "2016.2.22 or later if you haven't already.\n"); + } + return WIMLIB_ERR_SET_SECURITY; + } + } + + /* Timestamps */ + ret = ntfs_3g_set_timestamps(ni, inode); if (ret) { - ERROR_WITH_ERRNO("Could not create hard link `%s' => `%s'", - from_dentry->_full_path, - inode->i_extracted_file); - ret = WIMLIB_ERR_NTFS_3G; + ERROR_WITH_ERRNO("Failed to set timestamps on \"%s\" " + "in NTFS volume", + dentry_full_path(one_dentry)); + return ret; } - return ret; + return 0; } -/* Transfers file attributes and possibly a security descriptor from a WIM inode - * to a NTFS inode. - * - * @ni: The NTFS inode to apply the metadata to. - * @dir_ni: The NTFS inode for a directory containing @ni. - * @dentry: The WIM dentry whose inode contains the metadata to apply. - * @wim: The WIMStruct for the WIM, through which the table of security - * descriptors can be accessed. - * - * Returns 0 on success, nonzero on failure. - */ +/* Recursively creates all the subdirectories of @dir, which has been created as + * the NTFS inode @dir_ni. */ static int -apply_file_attributes_and_security_data(ntfs_inode *ni, - ntfs_inode *dir_ni, - struct wim_dentry *dentry, - const WIMStruct *wim, - int extract_flags) +ntfs_3g_create_dirs_recursive(ntfs_inode *dir_ni, struct wim_dentry *dir, + struct ntfs_3g_apply_ctx *ctx) { - int ret; - struct SECURITY_CONTEXT ctx; - le32 attributes; - const struct wim_inode *inode; - - inode = dentry->d_inode; - - DEBUG("Setting NTFS file attributes on `%s' to %#"PRIx32, - dentry->_full_path, inode->i_attributes); - - attributes = cpu_to_le32(inode->i_attributes); - memset(&ctx, 0, sizeof(ctx)); - ctx.vol = ni->vol; - ret = ntfs_xattr_system_setxattr(&ctx, XATTR_NTFS_ATTRIB, - ni, dir_ni, - (char*)&attributes, - sizeof(attributes), 0); - if (ret) { - ERROR("Failed to set NTFS file attributes on `%s'", - dentry->_full_path); - ret = WIMLIB_ERR_NTFS_3G; - } else if (inode->i_security_id != -1 && - !(extract_flags & WIMLIB_EXTRACT_FLAG_NO_ACLS)) - { - const char *desc; - const struct wim_security_data *sd; - - sd = wim_const_security_data(wim); - wimlib_assert(inode->i_security_id < sd->num_entries); - desc = (const char *)sd->descriptors[inode->i_security_id]; - DEBUG("Applying security descriptor %d to `%s'", - inode->i_security_id, dentry->_full_path); - - ret = ntfs_xattr_system_setxattr(&ctx, XATTR_NTFS_ACL, - ni, dir_ni, desc, - sd->sizes[inode->i_security_id], 0); - - if (ret) { - ERROR_WITH_ERRNO("Failed to set security data on `%s'", - dentry->_full_path); + struct wim_dentry *child; + + for_dentry_child(child, dir) { + ntfs_inode *ni; + int ret; + + if (!(child->d_inode->i_attributes & FILE_ATTRIBUTE_DIRECTORY)) + continue; + if (!will_extract_dentry(child)) + continue; + + ni = ntfs_create(dir_ni, 0, child->d_extraction_name, + child->d_extraction_name_nchars, S_IFDIR); + if (!ni) { + ERROR_WITH_ERRNO("Error creating \"%s\" in NTFS volume", + dentry_full_path(child)); + return WIMLIB_ERR_NTFS_3G; + } + + child->d_inode->i_mft_no = ni->mft_no; + + ret = report_file_created(&ctx->common); + if (!ret) + ret = ntfs_3g_set_metadata(ni, child->d_inode, ctx); + if (!ret) + ret = ntfs_3g_create_dirs_recursive(ni, child, ctx); + + if (ntfs_inode_close_in_dir(ni, dir_ni) && !ret) { + ERROR_WITH_ERRNO("Error closing \"%s\" in NTFS volume", + dentry_full_path(child)); ret = WIMLIB_ERR_NTFS_3G; } + if (ret) + return ret; } - return ret; + return 0; } -/* - * Transfers the reparse data from a WIM inode (which must represent a reparse - * point) to a NTFS inode. - */ +/* For each WIM dentry in the @root tree that represents a directory, create the + * corresponding directory in the NTFS volume @ctx->vol. */ static int -apply_reparse_data(ntfs_inode *ni, struct wim_dentry *dentry, - union wimlib_progress_info *progress_info) +ntfs_3g_create_directories(struct wim_dentry *root, + struct list_head *dentry_list, + struct ntfs_3g_apply_ctx *ctx) { + ntfs_inode *root_ni; int ret; - u8 rpbuf[REPARSE_POINT_MAX_SIZE] _aligned_attribute(8); - u16 rpbuflen; + struct wim_dentry *dentry; - DEBUG("Applying reparse data to `%s'", dentry->_full_path); + /* Create the directories using POSIX names. */ - ret = wim_inode_get_reparse_data(dentry->d_inode, rpbuf, &rpbuflen); + root_ni = ntfs_inode_open(ctx->vol, FILE_root); + if (!root_ni) { + ERROR_WITH_ERRNO("Can't open root of NTFS volume"); + return WIMLIB_ERR_NTFS_3G; + } + + root->d_inode->i_mft_no = FILE_root; + + ret = ntfs_3g_set_metadata(root_ni, root->d_inode, ctx); + if (!ret) + ret = ntfs_3g_create_dirs_recursive(root_ni, root, ctx); + + if (ntfs_inode_close(root_ni) && !ret) { + ERROR_WITH_ERRNO("Error closing root of NTFS volume"); + ret = WIMLIB_ERR_NTFS_3G; + } if (ret) return ret; - ret = ntfs_set_ntfs_reparse_data(ni, rpbuf, rpbuflen, 0); - if (ret) { - ERROR_WITH_ERRNO("Failed to set NTFS reparse data on `%s'", - dentry->_full_path); - return WIMLIB_ERR_NTFS_3G; - } + /* Set the DOS name of any directory that has one. In addition, create + * empty attributes for directories that have them. Note that creating + * an empty reparse point attribute must happen *after* setting the DOS + * name in order to work around a case where ntfs_set_ntfs_dos_name() + * fails with EOPNOTSUPP. This bug was fixed in NTFS-3G version + * 2016.2.22. */ + list_for_each_entry(dentry, dentry_list, d_extraction_list_node) { + const struct wim_inode *inode = dentry->d_inode; + + if (!(inode->i_attributes & FILE_ATTRIBUTE_DIRECTORY)) + continue; + if (dentry_has_short_name(dentry)) { + ret = ntfs_3g_restore_dos_name(NULL, NULL, dentry, + ctx->vol); + if (ret) + return ret; + ret = report_file_created(&ctx->common); + if (ret) + return ret; + } + if (ntfs_3g_has_empty_attributes(inode)) { + ntfs_inode *ni; - progress_info->extract.completed_bytes += rpbuflen - 8; + ret = WIMLIB_ERR_NTFS_3G; + ni = ntfs_inode_open(ctx->vol, inode->i_mft_no); + if (ni) { + ret = ntfs_3g_create_empty_attributes(ni, inode, + ctx); + if (ntfs_inode_close(ni) && !ret) + ret = WIMLIB_ERR_NTFS_3G; + } + if (ret) { + ERROR_WITH_ERRNO("Failed to create empty " + "attributes of directory " + "\"%s\" in NTFS volume", + dentry_full_path(dentry)); + return ret; + } + } + } return 0; } +/* When creating an inode that will have a short (DOS) name, we create it using + * the long name associated with the short name. This ensures that the short + * name gets associated with the correct long name. */ +static struct wim_dentry * +ntfs_3g_first_extraction_alias(struct wim_inode *inode) +{ + struct wim_dentry *dentry; + + inode_for_each_extraction_alias(dentry, inode) + if (dentry_has_short_name(dentry)) + return dentry; + return inode_first_extraction_dentry(inode); +} + /* - * Applies a WIM dentry to a NTFS filesystem. + * Add a hard link for the NTFS inode @ni at the location corresponding to the + * WIM dentry @dentry. * - * @dentry: The WIM dentry to apply - * @dir_ni: The NTFS inode for the parent directory + * The parent directory must have already been created on the NTFS volume. * - * @return: 0 on success; nonzero on failure. + * Returns 0 on success; returns WIMLIB_ERR_NTFS_3G and sets errno on failure. */ static int -do_apply_dentry_ntfs(struct wim_dentry *dentry, ntfs_inode *dir_ni, - struct apply_args *args) +ntfs_3g_add_link(ntfs_inode *ni, struct wim_dentry *dentry) +{ + ntfs_inode *dir_ni; + int res; + + /* Open the inode of the parent directory. */ + dir_ni = ntfs_inode_open(ni->vol, dentry->d_parent->d_inode->i_mft_no); + if (!dir_ni) + goto fail; + + /* Create the link. */ + res = ntfs_link(ni, dir_ni, dentry->d_extraction_name, + dentry->d_extraction_name_nchars); + + /* Close the parent directory. */ + if (ntfs_inode_close(dir_ni) || res) + goto fail; + + return 0; + +fail: + ERROR_WITH_ERRNO("Can't create link \"%s\" in NTFS volume", + dentry_full_path(dentry)); + return WIMLIB_ERR_NTFS_3G; +} + +static int +ntfs_3g_create_nondirectory(struct wim_inode *inode, + struct ntfs_3g_apply_ctx *ctx) { + struct wim_dentry *first_dentry; + ntfs_inode *dir_ni; + ntfs_inode *ni; + struct wim_dentry *dentry; int ret; - mode_t type; - ntfs_inode *ni = NULL; - struct wim_inode *inode = dentry->d_inode; - dentry->needs_extraction = 0; - if (inode->i_attributes & FILE_ATTRIBUTE_DIRECTORY) { - type = S_IFDIR; - } else { - type = S_IFREG; - if (inode->i_nlink > 1) { - /* Inode has multiple dentries referencing it. */ - if (inode->i_extracted_file) { - /* Already extracted another dentry in the hard - * link group. Make a hard link instead of - * extracting the file data. */ - ret = apply_ntfs_hardlink(dentry, inode, dir_ni); - /* dir_ni was closed */ - goto out; - } else { - /* None of the dentries of this inode have been - * extracted yet, so go ahead and extract the - * first one. */ - FREE(inode->i_extracted_file); - if (!(inode->i_extracted_file = STRDUP(dentry->_full_path))) - { - ret = WIMLIB_ERR_NOMEM; - goto out_close_dir_ni; - } - } - } + first_dentry = ntfs_3g_first_extraction_alias(inode); + + /* Create first link. */ + + dir_ni = ntfs_inode_open(ctx->vol, first_dentry->d_parent->d_inode->i_mft_no); + if (!dir_ni) { + ERROR_WITH_ERRNO("Can't open \"%s\" in NTFS volume", + dentry_full_path(first_dentry->d_parent)); + return WIMLIB_ERR_NTFS_3G; } - /* Create a NTFS directory or file. - * - * Note: For symbolic links that are not directory junctions, S_IFREG is - * passed here, since the reparse data and file attributes are set - * later. */ - ni = ntfs_create(dir_ni, 0, dentry->file_name, - dentry->file_name_nbytes / 2, type); + ni = ntfs_create(dir_ni, 0, first_dentry->d_extraction_name, + first_dentry->d_extraction_name_nchars, S_IFREG); if (!ni) { - ERROR_WITH_ERRNO("Could not create NTFS inode for `%s'", - dentry->_full_path); - ret = WIMLIB_ERR_NTFS_3G; - goto out_close_dir_ni; + ERROR_WITH_ERRNO("Can't create \"%s\" in NTFS volume", + dentry_full_path(first_dentry)); + ntfs_inode_close(dir_ni); + return WIMLIB_ERR_NTFS_3G; } - /* Write the data streams, unless this is reparse point. */ - if (!(inode->i_attributes & FILE_ATTRIBUTE_REPARSE_POINT)) { - ret = write_ntfs_data_streams(ni, dentry, &args->progress); + inode->i_mft_no = ni->mft_no; + + /* Set short name if present. */ + if (dentry_has_short_name(first_dentry)) { + + ret = ntfs_3g_restore_dos_name(ni, dir_ni, first_dentry, ctx->vol); + + /* ntfs_3g_restore_dos_name() closed both 'ni' and 'dir_ni'. */ + if (ret) - goto out_close_dir_ni; + return ret; + + /* Reopen the inode. */ + ni = ntfs_inode_open(ctx->vol, inode->i_mft_no); + if (!ni) { + ERROR_WITH_ERRNO("Failed to reopen \"%s\" " + "in NTFS volume", + dentry_full_path(first_dentry)); + return WIMLIB_ERR_NTFS_3G; + } + } else { + /* Close the directory in which the first link was created. */ + if (ntfs_inode_close(dir_ni)) { + ERROR_WITH_ERRNO("Failed to close \"%s\" in NTFS volume", + dentry_full_path(first_dentry->d_parent)); + ret = WIMLIB_ERR_NTFS_3G; + goto out_close_ni; + } + } + + /* Create additional links if present. */ + inode_for_each_extraction_alias(dentry, inode) { + if (dentry != first_dentry) { + ret = ntfs_3g_add_link(ni, dentry); + if (ret) + goto out_close_ni; + } } - ret = apply_file_attributes_and_security_data(ni, dir_ni, dentry, - args->wim, - args->extract_flags); + /* Set metadata. */ + ret = ntfs_3g_set_metadata(ni, inode, ctx); if (ret) - goto out_close_dir_ni; + goto out_close_ni; - if (inode->i_attributes & FILE_ATTR_REPARSE_POINT) { - ret = apply_reparse_data(ni, dentry, &args->progress); - if (ret) - goto out_close_dir_ni; + ret = ntfs_3g_create_empty_attributes(ni, inode, ctx); + +out_close_ni: + /* Close the inode. */ + if (ntfs_inode_close(ni) && !ret) { + ERROR_WITH_ERRNO("Error closing \"%s\" in NTFS volume", + dentry_full_path(first_dentry)); + ret = WIMLIB_ERR_NTFS_3G; } + return ret; +} - /* Set DOS (short) name if given */ - if (dentry_has_short_name(dentry) && !dentry->dos_name_invalid) - { - char *short_name_mbs; - size_t short_name_mbs_nbytes; - ret = utf16le_to_tstr(dentry->short_name, - dentry->short_name_nbytes, - &short_name_mbs, - &short_name_mbs_nbytes); +/* For each WIM dentry in the @dentry_list that represents a nondirectory file, + * create the corresponding nondirectory file in the NTFS volume. + * + * Directories must have already been created. */ +static int +ntfs_3g_create_nondirectories(struct list_head *dentry_list, + struct ntfs_3g_apply_ctx *ctx) +{ + struct wim_dentry *dentry; + struct wim_inode *inode; + int ret; + + list_for_each_entry(dentry, dentry_list, d_extraction_list_node) { + inode = dentry->d_inode; + if (inode->i_attributes & FILE_ATTRIBUTE_DIRECTORY) + continue; + if (dentry == inode_first_extraction_dentry(inode)) { + ret = ntfs_3g_create_nondirectory(inode, ctx); + if (ret) + return ret; + } + ret = report_file_created(&ctx->common); if (ret) - goto out_close_dir_ni; - - DEBUG("Setting short (DOS) name of `%s' to %s", - dentry->_full_path, short_name_mbs); - - ret = ntfs_set_ntfs_dos_name(ni, dir_ni, short_name_mbs, - short_name_mbs_nbytes, 0); - FREE(short_name_mbs); - if (ret) { - WARNING_WITH_ERRNO("Could not set DOS (short) name for `%s'", - dentry->_full_path); - ret = 0; + return ret; + } + return 0; +} + +static int +ntfs_3g_begin_extract_blob_instance(struct blob_descriptor *blob, + ntfs_inode *ni, + struct wim_inode *inode, + const struct wim_inode_stream *strm, + struct ntfs_3g_apply_ctx *ctx) +{ + struct wim_dentry *one_dentry = inode_first_extraction_dentry(inode); + ntfschar *stream_name; + size_t stream_name_nchars; + ntfs_attr *na; + + if (unlikely(strm->stream_type == STREAM_TYPE_REPARSE_POINT)) { + + if (blob->size > REPARSE_DATA_MAX_SIZE) { + ERROR("Reparse data of \"%s\" has size " + "%"PRIu64" bytes (exceeds %u bytes)", + dentry_full_path(one_dentry), + blob->size, REPARSE_DATA_MAX_SIZE); + return WIMLIB_ERR_INVALID_REPARSE_DATA; } - /* inodes have been closed by ntfs_set_ntfs_dos_name(). */ - goto out; + ctx->reparse_ptr = ctx->rpbuf.rpdata; + ctx->ntfs_reparse_inodes[ctx->num_reparse_inodes] = ni; + ctx->wim_reparse_inodes[ctx->num_reparse_inodes] = inode; + ctx->num_reparse_inodes++; + return 0; } -out_close_dir_ni: - if (dir_ni) { - if (ni) { - if (ntfs_inode_close_in_dir(ni, dir_ni)) { - if (ret == 0) - ret = WIMLIB_ERR_NTFS_3G; - ERROR_WITH_ERRNO("Failed to close inode for `%s'", - dentry->_full_path); - } + + /* It's a data stream (may be unnamed or named). */ + wimlib_assert(strm->stream_type == STREAM_TYPE_DATA); + + if (unlikely(stream_is_named(strm))) { + stream_name = strm->stream_name; + stream_name_nchars = utf16le_len_chars(stream_name); + + if (ntfs_attr_add(ni, AT_DATA, stream_name, + stream_name_nchars, NULL, 0)) + { + ERROR_WITH_ERRNO("Failed to create named data stream of \"%s\"", + dentry_full_path(one_dentry)); + return WIMLIB_ERR_NTFS_3G; } - if (ntfs_inode_close(dir_ni)) { - if (ret == 0) - ret = WIMLIB_ERR_NTFS_3G; - ERROR_WITH_ERRNO("Failed to close inode of directory " - "containing `%s'", - dentry->_full_path); + } else { + /* Don't pass an empty string other than AT_UNNAMED to + * ntfs_attr_open() --- it violates assumptions made by + * libntfs-3g. */ + stream_name = AT_UNNAMED; + stream_name_nchars = 0; + } + + /* This should be ensured by extract_blob_list() */ + wimlib_assert(ctx->num_open_attrs < MAX_OPEN_FILES); + + na = ntfs_attr_open(ni, AT_DATA, stream_name, stream_name_nchars); + if (!na) { + ERROR_WITH_ERRNO("Failed to open data stream of \"%s\"", + dentry_full_path(one_dentry)); + return WIMLIB_ERR_NTFS_3G; + } + + /* + * Note: there are problems with trying to combine compression with + * sparseness when extracting. For example, doing ntfs_attr_truncate() + * at the end to extend the attribute to its final size actually extends + * to a compression block size boundary rather than to the requested + * size. Until these problems are solved, we always write the full data + * to compressed attributes. We also don't attempt to preallocate space + * for compressed attributes, since we don't know how much space they + * are going to actually need. + */ + ctx->is_sparse_attr[ctx->num_open_attrs] = false; + if (!(na->data_flags & ATTR_COMPRESSION_MASK)) { + if (inode->i_attributes & FILE_ATTRIBUTE_SPARSE_FILE) { + ctx->is_sparse_attr[ctx->num_open_attrs] = true; + ctx->any_sparse_attrs = true; + } else { + ntfs_attr_truncate_solid(na, blob->size); } } -out: - return ret; + ctx->open_attrs[ctx->num_open_attrs++] = na; + return 0; } static int -apply_root_dentry_ntfs(struct wim_dentry *dentry, - ntfs_volume *vol, const WIMStruct *wim, - int extract_flags) +ntfs_3g_cleanup_blob_extract(struct ntfs_3g_apply_ctx *ctx) { - ntfs_inode *ni; int ret = 0; - ret = calculate_dentry_full_path(dentry); - if (ret) - return ret; + for (unsigned i = 0; i < ctx->num_open_attrs; i++) { + if (ntfs_attr_pclose(ctx->open_attrs[i])) + ret = -1; + ntfs_attr_close(ctx->open_attrs[i]); + } - ni = ntfs_pathname_to_inode(vol, NULL, "/"); - if (!ni) { - ERROR_WITH_ERRNO("Could not find root NTFS inode"); - return WIMLIB_ERR_NTFS_3G; + ctx->num_open_attrs = 0; + + for (unsigned i = 0; i < ctx->num_open_inodes; i++) { + if (ntfs_inode_close(ctx->open_inodes[i])) + ret = -1; } - ret = apply_file_attributes_and_security_data(ni, ni, dentry, wim, - extract_flags); - if (ntfs_inode_close(ni) != 0) { - ERROR_WITH_ERRNO("Failed to close NTFS inode for root " - "directory"); + ctx->num_open_inodes = 0; + + ctx->any_sparse_attrs = false; + ctx->reparse_ptr = NULL; + ctx->num_reparse_inodes = 0; + return ret; +} + +static ntfs_inode * +ntfs_3g_open_inode(struct wim_inode *inode, struct ntfs_3g_apply_ctx *ctx) +{ + ntfs_inode *ni; + + /* If the same blob is being extracted to multiple streams of the same + * inode, then we must only open the inode once. */ + if (unlikely(inode->i_num_streams > 1)) { + for (unsigned i = 0; i < ctx->num_open_inodes; i++) { + if (ctx->open_inodes[i]->mft_no == inode->i_mft_no) { + return ctx->open_inodes[i]; + } + } + } + + ni = ntfs_inode_open(ctx->vol, inode->i_mft_no); + if (unlikely(!ni)) { + ERROR_WITH_ERRNO("Can't open \"%s\" in NTFS volume", + dentry_full_path( + inode_first_extraction_dentry(inode))); + return NULL; + } + + ctx->open_inodes[ctx->num_open_inodes++] = ni; + return ni; +} + +static int +ntfs_3g_begin_extract_blob(struct blob_descriptor *blob, void *_ctx) +{ + struct ntfs_3g_apply_ctx *ctx = _ctx; + const struct blob_extraction_target *targets = blob_extraction_targets(blob); + int ret; + ntfs_inode *ni; + + for (u32 i = 0; i < blob->out_refcnt; i++) { ret = WIMLIB_ERR_NTFS_3G; + ni = ntfs_3g_open_inode(targets[i].inode, ctx); + if (!ni) + goto out_cleanup; + + ret = ntfs_3g_begin_extract_blob_instance(blob, ni, + targets[i].inode, + targets[i].stream, ctx); + if (ret) + goto out_cleanup; } + ret = 0; + goto out; + +out_cleanup: + ntfs_3g_cleanup_blob_extract(ctx); +out: return ret; } -/* Applies a WIM dentry to the NTFS volume */ -int -apply_dentry_ntfs(struct wim_dentry *dentry, void *arg) +/* + * Note: prior to NTFS-3G version 2016.2.22, ntfs_attr_pwrite() could return a + * short count in non-error cases, contrary to its documentation. Specifically, + * a short count could be returned when writing to a compressed attribute and + * the requested count exceeded the size of an NTFS "compression block". + * Therefore, we must continue calling ntfs_attr_pwrite() until all bytes have + * been written or a real error has occurred. + */ +static bool +ntfs_3g_full_pwrite(ntfs_attr *na, u64 offset, size_t size, const u8 *data) { - struct apply_args *args = arg; - ntfs_volume *vol = args->vol; - WIMStruct *wim = args->wim; - struct wim_dentry *orig_dentry; - struct wim_dentry *other; - int ret; + while (size) { + s64 res = ntfs_attr_pwrite(na, offset, size, data); + if (unlikely(res <= 0)) + return false; + wimlib_assert(res <= size); + offset += res; + size -= res; + data += res; + } + return true; +} - /* Treat the root dentry specially. */ - if (dentry_is_root(dentry)) - return apply_root_dentry_ntfs(dentry, vol, wim, - args->extract_flags); - - /* NTFS filename namespaces need careful consideration. A name for a - * NTFS file may be in either the POSIX, Win32, DOS, or Win32+DOS - * namespaces. A NTFS file (a.k.a. inode) may have multiple names in - * multiple directories (i.e. hard links); however, a NTFS file can have - * at most 1 DOS name total. Furthermore, a Win32 name is always - * associated with a DOS name (either as a Win32+DOS name, or a Win32 - * name and a DOS name separately), which implies that a NTFS file can - * have at most 1 Win32 name. - * - * A WIM dentry just contains a "long name", which wimlib makes sure is - * non-empty, and a "short name", which may be empty. So, wimlib must - * map these to the correct NTFS names. wimlib collects all WIM - * dentries that map to the same NTFS inode and factors out the common - * information into a 'struct wim_inode', so this should make the - * mapping a little more obvious. As a NTFS file can have at most 1 DOS - * name, a WIM inode cannot have more than 1 dentry with a non-empty - * short name, and this is checked in the verify_inode() function in - * verify.c. Furthermore, a WIM dentry, if any, that has a DOS name - * must have a long name that corresponds to a Win32 name or Win32+DOS - * name. - * - * WIM dentries that have a long name but no associated short name are - * assumed to be in the POSIX namespace. - * - * So, given a WIM inode that is to map to a NTFS inode, we must apply - * the Win32 and DOS or Win32+DOS names, if they exist, then any - * additional (POSIX) names. A caveat when actually doing this: as - * confirmed by the libntfs-3g authors, ntfs_set_ntfs_dos_name() is only - * guaranteed to associate a DOS name with the appropriate long name if - * it's called when that long name is the only one in existence for that - * file. So, this implies that the correct ordering of function calls - * to extract a NTFS file are: - * - * if (file has a DOS name) { - * - Call ntfs_create() to create long name associated with - * the DOS name (this initially creates a POSIX name) - * - Call ntfs_set_ntfs_dos_name() to associate a DOS name - * with the long name just created. This either changes - * the POSIX name to Win32+DOS, or changes the POSIX name - * to Win32 and creates a separate DOS name. - * } else { - * - Call ntfs_create() to create the first link to the - * file in the POSIX namespace - * } - * - Call ntfs_link() to create the other names of the file, in the - * POSIX namespace. +static int +ntfs_3g_extract_chunk(const struct blob_descriptor *blob, u64 offset, + const void *chunk, size_t size, void *_ctx) +{ + struct ntfs_3g_apply_ctx *ctx = _ctx; + const void * const end = chunk + size; + const void *p; + bool zeroes; + size_t len; + unsigned i; + + /* + * For sparse attributes, only write nonzero regions. This lets the + * filesystem use holes to represent zero regions. */ -again: - orig_dentry = NULL; - if (!dentry->d_inode->i_dos_name_extracted && - (!dentry_has_short_name(dentry) || dentry->dos_name_invalid)) - { - inode_for_each_dentry(other, dentry->d_inode) { - if (dentry_has_short_name(other) && !other->dos_name_invalid) { - orig_dentry = dentry; - dentry = other; - break; + for (p = chunk; p != end; p += len, offset += len) { + zeroes = maybe_detect_sparse_region(p, end - p, &len, + ctx->any_sparse_attrs); + for (i = 0; i < ctx->num_open_attrs; i++) { + if (!zeroes || !ctx->is_sparse_attr[i]) { + if (!ntfs_3g_full_pwrite(ctx->open_attrs[i], + offset, len, p)) + goto err; } } } - dentry->d_inode->i_dos_name_extracted = 1; - ret = calculate_dentry_full_path(dentry); - if (ret) - return ret; + if (ctx->reparse_ptr) + ctx->reparse_ptr = mempcpy(ctx->reparse_ptr, chunk, size); + return 0; + +err: + ERROR_WITH_ERRNO("Error writing data to NTFS volume"); + return WIMLIB_ERR_NTFS_3G; +} + +static int +ntfs_3g_end_extract_blob(struct blob_descriptor *blob, int status, void *_ctx) +{ + struct ntfs_3g_apply_ctx *ctx = _ctx; + int ret; - ntfs_inode *dir_ni = dentry_open_parent_ni(dentry, vol); - if (dir_ni) { - ret = do_apply_dentry_ntfs(dentry, dir_ni, arg); - if (ret == 0 && orig_dentry != NULL) { - dentry = orig_dentry; - goto again; + if (status) { + ret = status; + goto out; + } + + /* Extend sparse attributes to their final size. */ + if (ctx->any_sparse_attrs) { + for (unsigned i = 0; i < ctx->num_open_attrs; i++) { + if (!ctx->is_sparse_attr[i]) + continue; + if (ntfs_attr_truncate(ctx->open_attrs[i], blob->size)) + { + ERROR_WITH_ERRNO("Error extending attribute to " + "final size"); + ret = WIMLIB_ERR_WRITE; + goto out; + } } - } else { + } + + for (u32 i = 0; i < ctx->num_reparse_inodes; i++) { + ret = ntfs_3g_restore_reparse_point(ctx->ntfs_reparse_inodes[i], + ctx->wim_reparse_inodes[i], + blob->size, ctx); + if (ret) + goto out; + } + ret = 0; +out: + if (ntfs_3g_cleanup_blob_extract(ctx) && !ret) { + ERROR_WITH_ERRNO("Error writing data to NTFS volume"); ret = WIMLIB_ERR_NTFS_3G; } return ret; } -/* Transfers the 100-nanosecond precision timestamps from a WIM dentry to a NTFS - * inode */ -int -apply_dentry_timestamps_ntfs(struct wim_dentry *dentry, void *arg) +static u64 +ntfs_3g_count_dentries(const struct list_head *dentry_list) { - struct apply_args *args = arg; - ntfs_volume *vol = args->vol; - u64 ntfs_timestamps[3]; - ntfs_inode *ni; + const struct wim_dentry *dentry; + u64 count = 0; + + list_for_each_entry(dentry, dentry_list, d_extraction_list_node) { + count++; + if ((dentry->d_inode->i_attributes & FILE_ATTRIBUTE_DIRECTORY) && + dentry_has_short_name(dentry)) + { + count++; + } + } + + return count; +} + +static int +ntfs_3g_extract(struct list_head *dentry_list, struct apply_ctx *_ctx) +{ + struct ntfs_3g_apply_ctx *ctx = (struct ntfs_3g_apply_ctx *)_ctx; + ntfs_volume *vol; + struct wim_dentry *root; int ret; - DEBUG("Setting timestamps on `%s'", dentry->_full_path); + /* For NTFS-3G extraction mode we require that the dentries to extract + * form a single tree. */ + root = list_first_entry(dentry_list, struct wim_dentry, + d_extraction_list_node); - ni = ntfs_pathname_to_inode(vol, NULL, dentry->_full_path); - if (!ni) { - ERROR_WITH_ERRNO("Could not find NTFS inode for `%s'", - dentry->_full_path); + /* Mount the NTFS volume. */ + vol = ntfs_mount(ctx->common.target, 0); + if (!vol) { + ERROR_WITH_ERRNO("Failed to mount \"%s\" with NTFS-3G", + ctx->common.target); return WIMLIB_ERR_NTFS_3G; } - - /* Note: ntfs_inode_set_times() expects the times in native byte order, - * not little endian. */ - ntfs_timestamps[0] = dentry->d_inode->i_creation_time; - ntfs_timestamps[1] = dentry->d_inode->i_last_write_time; - ntfs_timestamps[2] = dentry->d_inode->i_last_access_time; - ret = ntfs_inode_set_times(ni, (const char*)ntfs_timestamps, - sizeof(ntfs_timestamps), 0); - if (ret != 0) { - ERROR_WITH_ERRNO("Failed to set NTFS timestamps on `%s'", - dentry->_full_path); + ctx->vol = vol; + + /* Opening $Secure is required to set security descriptors in NTFS v3.0 + * format, where security descriptors are stored in a per-volume index + * rather than being fully specified for each file. */ + if (ntfs_open_secure(vol) && vol->major_ver >= 3) { + ERROR_WITH_ERRNO("Unable to open security descriptor index of " + "NTFS volume \"%s\"", ctx->common.target); ret = WIMLIB_ERR_NTFS_3G; + goto out_unmount; } - if (ntfs_inode_close(ni) != 0) { - if (ret == 0) + /* Create all inodes and aliases, including short names, and set + * metadata (attributes, security descriptors, and timestamps). */ + + ret = start_file_structure_phase(&ctx->common, + ntfs_3g_count_dentries(dentry_list)); + if (ret) + goto out_unmount; + + ret = ntfs_3g_create_directories(root, dentry_list, ctx); + if (ret) + goto out_unmount; + + ret = ntfs_3g_create_nondirectories(dentry_list, ctx); + if (ret) + goto out_unmount; + + ret = end_file_structure_phase(&ctx->common); + if (ret) + goto out_unmount; + + /* Extract blobs. */ + struct read_blob_callbacks cbs = { + .begin_blob = ntfs_3g_begin_extract_blob, + .continue_blob = ntfs_3g_extract_chunk, + .end_blob = ntfs_3g_end_extract_blob, + .ctx = ctx, + }; + ret = extract_blob_list(&ctx->common, &cbs); + + /* We do not need a final pass to set timestamps because libntfs-3g does + * not update timestamps automatically (exception: + * ntfs_set_ntfs_dos_name() does, but we handle this elsewhere). */ + +out_unmount: + if (vol->secure_ni) { + ntfs_index_ctx_put(vol->secure_xsii); + ntfs_index_ctx_put(vol->secure_xsdh); + if (ntfs_inode_close(vol->secure_ni) && !ret) { + ERROR_WITH_ERRNO("Failed to close security descriptor " + "index of NTFS volume \"%s\"", + ctx->common.target); ret = WIMLIB_ERR_NTFS_3G; - ERROR_WITH_ERRNO("Failed to close NTFS inode for `%s'", - dentry->_full_path); + } + vol->secure_ni = NULL; + } + if (ntfs_umount(ctx->vol, FALSE) && !ret) { + ERROR_WITH_ERRNO("Failed to unmount \"%s\" with NTFS-3G", + ctx->common.target); + ret = WIMLIB_ERR_NTFS_3G; } return ret; } -void -libntfs3g_global_init(void) -{ - ntfs_set_char_encoding(setlocale(LC_ALL, "")); -} - -#endif /* WITH_NTFS_3G */ +const struct apply_operations ntfs_3g_apply_ops = { + .name = "NTFS-3G", + .get_supported_features = ntfs_3g_get_supported_features, + .extract = ntfs_3g_extract, + .context_size = sizeof(struct ntfs_3g_apply_ctx), + .single_tree_only = true, +};