/*
* 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.
+ *
+ * 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
+ * Copyright (C) 2012, 2013, 2014 Eric Biggers
*
* This file is part of wimlib, a library for working with WIM files.
*
# include "config.h"
#endif
-#ifdef WITH_NTFS_3G
-
#include <locale.h>
#include <string.h>
-#include <time.h> /* NTFS-3g headers are missing <time.h> include */
#include <ntfs-3g/attrib.h>
-#include <ntfs-3g/endians.h>
#include <ntfs-3g/reparse.h>
#include <ntfs-3g/security.h>
-#include <ntfs-3g/types.h>
-#include <ntfs-3g/xattrs.h>
+#include "wimlib/assert.h"
#include "wimlib/apply.h"
-#include "wimlib/buffer_io.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/reparse.h"
#include "wimlib/security.h"
-
-struct ntfs_attr_extract_ctx {
- u64 offset;
- ntfs_attr *na;
-};
+#include "wimlib/security_descriptor.h"
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->archive_files = 1;
+ supported_features->hidden_files = 1;
+ supported_features->system_files = 1;
+ supported_features->compressed_files = 1;
+ supported_features->encrypted_directories = 1;
+ supported_features->not_context_indexed_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->timestamps = 1;
+ supported_features->case_sensitive_filenames = 1;
+ return 0;
}
-/*
- * Extracts a WIM resource to a NTFS attribute.
- */
-static int
-extract_wim_resource_to_ntfs_attr(const struct wim_lookup_table_entry *lte,
- ntfs_attr *na)
+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_STREAMS];
+ unsigned num_open_attrs;
+ ntfs_inode *open_inodes[MAX_OPEN_STREAMS];
+ unsigned num_open_inodes;
+
+ struct reparse_buffer_disk rpbuf;
+ u8 *reparse_ptr;
+
+ /* Offset in the stream currently being read */
+ u64 offset;
+
+ unsigned num_reparse_inodes;
+ ntfs_inode *ntfs_reparse_inodes[MAX_OPEN_STREAMS];
+ struct wim_inode *wim_reparse_inodes[MAX_OPEN_STREAMS];
+};
+
+static size_t
+sid_size(const wimlib_SID *sid)
{
- 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);
+ return offsetof(wimlib_SID, sub_authority) +
+ sizeof(le32) * sid->sub_authority_count;
}
-/* 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.
+/*
+ * sd_fixup - Fix up a Windows NT security descriptor for libntfs-3g.
*
- * @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.
+ * libntfs-3g validates security descriptors before setting them, but old
+ * versions contain bugs causing it to reject unusual but valid security
+ * descriptors:
*
- * @progress_info: Progress information for the image application. The number
- * of extracted bytes will be incremented by the uncompressed
- * size of each stream extracted.
+ * - Versions before 2013.1.13 reject security descriptors ending with an empty
+ * SACL (System Access Control List). This bug can be worked around either by
+ * moving the empty SACL earlier in the security descriptor or by removing the
+ * SACL entirely. The latter work-around is valid because an empty SACL is
+ * equivalent to a "null", or non-existent, SACL.
+ * - Versions up to and including 2013.1.13 reject security descriptors ending
+ * with an empty DACL (Discretionary Access Control List). This is very
+ * similar to the SACL bug and should be fixed in the next release after
+ * 2013.1.13. However, removing the DACL is not a valid workaround because
+ * this changes the meaning of the security descriptor--- an empty DACL allows
+ * no access, whereas a "null" DACL allows all access.
*
- * Returns 0 on success, nonzero on failure.
+ * If the security descriptor was fixed, this function returns an allocated
+ * buffer containing the fixed security descriptor, and its size is updated.
+ * Otherwise (or if no memory is available) the original descriptor is returned.
*/
+static u8 *
+sd_fixup(const u8 *_desc, size_t *size_p)
+{
+ u32 owner_offset, group_offset, dacl_offset, sacl_offset;
+ bool owner_valid, group_valid;
+ size_t size = *size_p;
+ const wimlib_SECURITY_DESCRIPTOR_RELATIVE *desc =
+ (const wimlib_SECURITY_DESCRIPTOR_RELATIVE*)_desc;
+ wimlib_SECURITY_DESCRIPTOR_RELATIVE *desc_new;
+ const wimlib_SID *owner, *group, *sid;
+
+ /* Don't attempt to fix clearly invalid security descriptors. */
+ if (size < sizeof(wimlib_SECURITY_DESCRIPTOR_RELATIVE))
+ return (u8*)_desc;
+
+ if (le16_to_cpu(desc->control) & wimlib_SE_DACL_PRESENT)
+ dacl_offset = le32_to_cpu(desc->dacl_offset);
+ else
+ dacl_offset = 0;
+
+ if (le16_to_cpu(desc->control) & wimlib_SE_SACL_PRESENT)
+ sacl_offset = le32_to_cpu(desc->sacl_offset);
+ else
+ sacl_offset = 0;
+
+ /* Check if the security descriptor will be affected by one of the bugs.
+ * If not, do nothing and return.
+ *
+ * Note: HAVE_NTFS_MNT_RDONLY is defined if libntfs-3g is
+ * version 2013.1.13 or later. */
+ if (!(
+ #if !defined(HAVE_NTFS_MNT_RDONLY)
+ (sacl_offset != 0 && sacl_offset == size - sizeof(wimlib_ACL)) ||
+ #endif
+ (dacl_offset != 0 && dacl_offset == size - sizeof(wimlib_ACL))))
+ return (u8*)_desc;
+
+ owner_offset = le32_to_cpu(desc->owner_offset);
+ group_offset = le32_to_cpu(desc->group_offset);
+ owner = (const wimlib_SID*)((const u8*)desc + owner_offset);
+ group = (const wimlib_SID*)((const u8*)desc + group_offset);
+
+ /* We'll try to move the owner or group SID to the end of the security
+ * descriptor to avoid the bug. This is only possible if at least one
+ * is valid. */
+ owner_valid = (owner_offset != 0) &&
+ (owner_offset % 4 == 0) &&
+ (owner_offset <= size - sizeof(SID)) &&
+ (owner_offset + sid_size(owner) <= size) &&
+ (owner_offset >= sizeof(wimlib_SECURITY_DESCRIPTOR_RELATIVE));
+ group_valid = (group_offset != 0) &&
+ (group_offset % 4 == 0) &&
+ (group_offset <= size - sizeof(SID)) &&
+ (group_offset + sid_size(group) <= size) &&
+ (group_offset >= sizeof(wimlib_SECURITY_DESCRIPTOR_RELATIVE));
+ if (owner_valid) {
+ sid = owner;
+ } else if (group_valid) {
+ sid = group;
+ } else {
+ return (u8*)_desc;
+ }
+
+ desc_new = MALLOC(size + sid_size(sid));
+ if (!desc_new)
+ return (u8*)_desc;
+
+ memcpy(desc_new, desc, size);
+ if (owner_valid)
+ desc_new->owner_offset = cpu_to_le32(size);
+ else if (group_valid)
+ desc_new->group_offset = cpu_to_le32(size);
+ memcpy((u8*)desc_new + size, sid, sid_size(sid));
+ *size_p = size + sid_size(sid);
+ return (u8*)desc_new;
+}
+
+/* Set the security descriptor @desc of size @desc_size on the NTFS inode @ni.
+ */
static int
-write_ntfs_data_streams(ntfs_inode *ni, struct wim_dentry *dentry,
- union wimlib_progress_info *progress_info)
+ntfs_3g_set_security_descriptor(ntfs_inode *ni, const void *desc, size_t desc_size)
{
+ struct SECURITY_CONTEXT sec_ctx;
+ u8 *desc_fixed;
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;
- }
- }
+ memset(&sec_ctx, 0, sizeof(sec_ctx));
+ sec_ctx.vol = ni->vol;
- /* 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;
- }
+ desc_fixed = sd_fixup(desc, &desc_size);
- /* 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_set_ntfs_acl(&sec_ctx, ni, desc_fixed, desc_size, 0))
+ ret = WIMLIB_ERR_SET_SECURITY;
- /* Actually extract the stream */
- ret = extract_wim_resource_to_ntfs_attr(lte, na);
+ if (desc_fixed != desc)
+ FREE(desc_fixed);
- /* Close the attribute */
- ntfs_attr_close(na);
- if (ret)
- break;
+ return ret;
+}
- /* 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;
+static int
+ntfs_3g_set_timestamps(ntfs_inode *ni, const struct wim_inode *inode)
+{
+ 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;
+}
- /* 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++;
- }
- return ret;
+/* Restore the timestamps on the NTFS inode corresponding to @inode. */
+static int
+ntfs_3g_restore_timestamps(ntfs_volume *vol, const struct wim_inode *inode)
+{
+ ntfs_inode *ni;
+ int res;
+
+ ni = ntfs_inode_open(vol, inode->i_mft_no);
+ if (!ni)
+ goto fail;
+
+ res = ntfs_3g_set_timestamps(ni, inode);
+
+ if (ntfs_inode_close(ni) || res)
+ goto fail;
+
+ return 0;
+
+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;
}
-/* 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)
+/* 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)
{
- char *p;
- const char *dir_name;
- ntfs_inode *dir_ni;
- char orig;
+ int ret;
+ const char *dos_name;
+ size_t dos_name_nbytes;
- p = dentry->_full_path + dentry->full_path_nbytes;
- do {
- p--;
- } while (*p != '/');
+ /* 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. */
- 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);
+ ret = utf16le_get_tstr(dentry->short_name, dentry->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 (ret) {
+ ERROR_WITH_ERRNO("Failed to set DOS name of \"%s\" in NTFS "
+ "volume", dentry_full_path(dentry));
+ ret = WIMLIB_ERR_SET_SHORT_NAME;
+ goto out_close;
}
- *p = orig;
- return dir_ni;
+
+ /* 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;
}
-/*
- * 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.
+/* Create empty named data streams.
*
- * 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.
- *
- * The new name is made in the POSIX namespace (this is the behavior of
- * ntfs_link()).
- *
- * Return 0 on success, nonzero on failure. dir_ni is closed either way.
+ * Since these won't have 'struct wim_lookup_table_entry's, they won't show up
+ * in the call to extract_stream_list(). Hence the need for the special case.
+ */
+static int
+ntfs_3g_create_any_empty_ads(ntfs_inode *ni, const struct wim_inode *inode,
+ const struct ntfs_3g_apply_ctx *ctx)
+{
+ for (u16 i = 0; i < inode->i_num_ads; i++) {
+ const struct wim_ads_entry *entry;
+
+ entry = &inode->i_ads_entries[i];
+
+ /* Not named? */
+ if (!entry->stream_name_nbytes)
+ continue;
+
+ /* Not empty? */
+ if (entry->lte)
+ continue;
+
+ if (ntfs_attr_add(ni, AT_DATA, entry->stream_name,
+ entry->stream_name_nbytes /
+ sizeof(utf16lechar),
+ 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;
+}
+
+/* Set attributes, security descriptor, and timestamps on the NTFS inode @ni.
*/
static int
-apply_ntfs_hardlink(struct wim_dentry *from_dentry,
- const struct wim_inode *inode,
- ntfs_inode *dir_ni)
+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;
- ntfs_inode *to_ni;
- ntfs_volume *vol;
- vol = dir_ni->vol;
- ret = ntfs_inode_close(dir_ni);
- if (ret != 0) {
- ERROR_WITH_ERRNO("Error closing directory");
- 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);
- DEBUG("Extracting NTFS hard link `%s' => `%s'",
- from_dentry->_full_path, inode->i_extracted_file);
+ /* Attributes */
+ if (!(extract_flags & WIMLIB_EXTRACT_FLAG_NO_ATTRIBUTES)) {
+ u32 attrib = inode->i_attributes;
- 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;
+ attrib &= ~(FILE_ATTRIBUTE_SPARSE_FILE |
+ FILE_ATTRIBUTE_ENCRYPTED);
+
+ 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;
+ }
}
- dir_ni = dentry_open_parent_ni(from_dentry, vol);
- if (!dir_ni) {
- ntfs_inode_close(to_ni);
- return WIMLIB_ERR_NTFS_3G;
+ /* Security descriptor */
+ if ((inode->i_security_id >= 0)
+ && !(extract_flags & WIMLIB_EXTRACT_FLAG_NO_ACLS))
+ {
+ const void *desc;
+ size_t desc_size;
+
+ desc = sd->descriptors[inode->i_security_id];
+ desc_size = sd->sizes[inode->i_security_id];
+
+ ret = ntfs_3g_set_security_descriptor(ni, desc, desc_size);
+ if (ret) {
+ if (wimlib_print_errors) {
+ ERROR_WITH_ERRNO("Failed to set security descriptor "
+ "on \"%s\" in NTFS volume",
+ dentry_full_path(one_dentry));
+ fprintf(wimlib_error_file,
+ "The security descriptor is: ");
+ print_byte_field(desc, desc_size, wimlib_error_file);
+ fprintf(wimlib_error_file, "\n");
+ }
+ return ret;
+ }
}
- 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);
+ /* 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.
- * @w: 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 *w,
- 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;
- u32 attributes_le32;
- 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_le32 = 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,
- (const char*)&attributes_le32,
- sizeof(u32), 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;
+ 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;
+ }
- sd = wim_const_security_data(w);
- 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);
+ child->d_inode->i_mft_no = ni->mft_no;
- ret = ntfs_xattr_system_setxattr(&ctx, XATTR_NTFS_ACL,
- ni, dir_ni, desc,
- sd->sizes[inode->i_security_id], 0);
+ ret = report_file_created(&ctx->common);
+ if (!ret)
+ ret = ntfs_3g_set_metadata(ni, child->d_inode, ctx);
+ if (!ret)
+ ret = ntfs_3g_create_any_empty_ads(ni, child->d_inode, ctx);
+ if (!ret)
+ ret = ntfs_3g_create_dirs_recursive(ni, child, ctx);
- if (ret) {
- ERROR_WITH_ERRNO("Failed to set security data on `%s'",
- dentry->_full_path);
+ 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)
{
- struct wim_lookup_table_entry *lte;
+ ntfs_inode *root_ni;
int ret;
+ struct wim_dentry *dentry;
- lte = inode_unnamed_lte_resolved(dentry->d_inode);
-
- DEBUG("Applying reparse data to `%s'", dentry->_full_path);
+ /* Create the directories using POSIX names. */
- if (!lte) {
- ERROR("Could not find reparse data for `%s'",
- dentry->_full_path);
- return WIMLIB_ERR_INVALID_DENTRY;
+ 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;
}
- /* "Reparse point data, including the tag and optional GUID, cannot
- * exceed 16 kilobytes." - MSDN */
- if (wim_resource_size(lte) > REPARSE_POINT_MAX_SIZE - 8) {
- ERROR("Reparse data of `%s' is too long (%"PRIu64" bytes)",
- dentry->_full_path, wim_resource_size(lte));
- return WIMLIB_ERR_INVALID_DENTRY;
- }
+ root->d_inode->i_mft_no = FILE_root;
- u8 reparse_data_buf[8 + wim_resource_size(lte)];
- u8 *p = reparse_data_buf;
- p = put_u32(p, dentry->d_inode->i_reparse_tag); /* ReparseTag */
- DEBUG("ReparseTag = %#x", dentry->d_inode->i_reparse_tag);
- p = put_u16(p, wim_resource_size(lte)); /* ReparseDataLength */
- p = put_u16(p, 0); /* Reserved */
+ ret = ntfs_3g_create_dirs_recursive(root_ni, root, ctx);
- ret = read_full_resource_into_buf(lte, p);
+ 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, (char*)reparse_data_buf,
- wim_resource_size(lte) + 8, 0);
- if (ret) {
- ERROR_WITH_ERRNO("Failed to set NTFS reparse data on `%s'",
- dentry->_full_path);
- return WIMLIB_ERR_NTFS_3G;
- } else {
- progress_info->extract.completed_bytes += wim_resource_size(lte);
+ /* Set the DOS name of any directory that has one. */
+ list_for_each_entry(dentry, dentry_list, d_extraction_list_node) {
+ if (!(dentry->d_inode->i_attributes & FILE_ATTRIBUTE_DIRECTORY))
+ continue;
+ if (!dentry_has_short_name(dentry))
+ continue;
+ 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;
}
- 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 list_head *next = inode->i_extraction_aliases.next;
+ struct wim_dentry *dentry;
+
+ do {
+ dentry = list_entry(next, struct wim_dentry,
+ d_extraction_alias_node);
+ if (dentry_has_short_name(dentry))
+ break;
+ next = next->next;
+ } while (next != &inode->i_extraction_aliases);
+ return dentry;
}
/*
- * 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,
+ const struct ntfs_3g_apply_ctx *ctx)
+{
+ struct wim_dentry *first_dentry;
+ ntfs_inode *dir_ni;
+ ntfs_inode *ni;
+ struct list_head *next;
+ 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;
+ }
}
- ret = apply_file_attributes_and_security_data(ni, dir_ni, dentry,
- args->w,
- args->extract_flags);
+ /* Create additional links if present. */
+ next = inode->i_extraction_aliases.next;
+ do {
+ dentry = list_entry(next, struct wim_dentry,
+ d_extraction_alias_node);
+ if (dentry != first_dentry) {
+ ret = ntfs_3g_add_link(ni, dentry);
+ if (ret)
+ goto out_close_ni;
+ }
+ next = next->next;
+ } while (next != &inode->i_extraction_aliases);
+
+ /* 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_any_empty_ads(ni, inode, ctx);
- /* Set DOS (short) name if given */
- if (dentry_has_short_name(dentry)) {
- 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);
- if (ret)
- goto out_close_dir_ni;
+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;
+}
- DEBUG("Setting short (DOS) name of `%s' to %s",
- dentry->_full_path, short_name_mbs);
+/* 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;
- ret = ntfs_set_ntfs_dos_name(ni, dir_ni, short_name_mbs,
- short_name_mbs_nbytes, 0);
- FREE(short_name_mbs);
- if (ret) {
- ERROR_WITH_ERRNO("Could not set DOS (short) name for `%s'",
- dentry->_full_path);
- ret = WIMLIB_ERR_NTFS_3G;
+ 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;
}
- /* inodes have been closed by ntfs_set_ntfs_dos_name(). */
- goto out;
+ ret = report_file_created(&ctx->common);
+ if (ret)
+ return ret;
}
-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);
- }
- }
- 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);
+ return 0;
+}
+
+static int
+ntfs_3g_begin_extract_stream_to_attr(struct wim_lookup_table_entry *stream,
+ ntfs_inode *ni,
+ struct wim_inode *inode,
+ ntfschar *stream_name,
+ struct ntfs_3g_apply_ctx *ctx)
+{
+ struct wim_dentry *one_dentry = inode_first_extraction_dentry(inode);
+ size_t stream_name_nchars = 0;
+ ntfs_attr *attr;
+
+ if (stream_name)
+ for (const ntfschar *p = stream_name; *p; p++)
+ stream_name_nchars++;
+
+ if (stream_name_nchars == 0)
+ stream_name = AT_UNNAMED;
+ if ((inode->i_attributes & FILE_ATTRIBUTE_REPARSE_POINT)
+ && (stream_name_nchars == 0))
+ {
+ if (stream->size > REPARSE_DATA_MAX_SIZE) {
+ ERROR("Reparse data of \"%s\" has size "
+ "%"PRIu64" bytes (exceeds %u bytes)",
+ dentry_full_path(one_dentry),
+ stream->size, REPARSE_DATA_MAX_SIZE);
+ return WIMLIB_ERR_INVALID_REPARSE_DATA;
}
+ ctx->reparse_ptr = ctx->rpbuf.rpdata;
+ ctx->rpbuf.rpdatalen = cpu_to_le16(stream->size);
+ ctx->rpbuf.rpreserved = cpu_to_le16(0);
+ 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:
- return ret;
+
+ if (stream_name_nchars &&
+ (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;
+ }
+
+ /* This should be ensured by extract_stream_list() */
+ wimlib_assert(ctx->num_open_attrs < MAX_OPEN_STREAMS);
+
+ attr = ntfs_attr_open(ni, AT_DATA, stream_name, stream_name_nchars);
+ if (!attr) {
+ ERROR_WITH_ERRNO("Failed to open data stream of \"%s\"",
+ dentry_full_path(one_dentry));
+ return WIMLIB_ERR_NTFS_3G;
+ }
+ ctx->open_attrs[ctx->num_open_attrs++] = attr;
+ ntfs_attr_truncate_solid(attr, stream->size);
+ return 0;
}
static int
-apply_root_dentry_ntfs(struct wim_dentry *dentry,
- ntfs_volume *vol, const WIMStruct *w,
- int extract_flags)
+ntfs_3g_cleanup_stream_extract(struct ntfs_3g_apply_ctx *ctx)
{
- ntfs_inode *ni;
int ret = 0;
- ni = ntfs_pathname_to_inode(vol, NULL, "/");
- if (!ni) {
- ERROR_WITH_ERRNO("Could not find root NTFS inode");
- return WIMLIB_ERR_NTFS_3G;
+ 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]);
}
- ret = apply_file_attributes_and_security_data(ni, ni, dentry, w,
- extract_flags);
- if (ntfs_inode_close(ni) != 0) {
- ERROR_WITH_ERRNO("Failed to close NTFS inode for root "
- "directory");
- ret = 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;
}
+ ctx->num_open_inodes = 0;
+
+ ctx->offset = 0;
+ ctx->reparse_ptr = NULL;
+ ctx->num_reparse_inodes = 0;
return ret;
}
-/* Applies a WIM dentry to the NTFS volume */
-int
-apply_dentry_ntfs(struct wim_dentry *dentry, void *arg)
+static ntfs_inode *
+ntfs_3g_open_inode(struct wim_inode *inode, struct ntfs_3g_apply_ctx *ctx)
{
- struct apply_args *args = arg;
- ntfs_volume *vol = args->vol;
- WIMStruct *w = args->w;
- struct wim_dentry *orig_dentry;
- struct wim_dentry *other;
- int ret;
+ ntfs_inode *ni = NULL;
- /* Treat the root dentry specially. */
- if (dentry_is_root(dentry))
- return apply_root_dentry_ntfs(dentry, vol, w,
- 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.
- */
-again:
- orig_dentry = NULL;
- if (!dentry->d_inode->i_dos_name_extracted &&
- !dentry_has_short_name(dentry))
- {
- inode_for_each_dentry(other, dentry->d_inode) {
- if (dentry_has_short_name(other)) {
- orig_dentry = dentry;
- dentry = other;
+ if (inode->i_visited) {
+ for (u32 i = 0; i < ctx->num_open_inodes; i++) {
+ if (ctx->open_inodes[i]->mft_no == inode->i_mft_no) {
+ ni = ctx->open_inodes[i];
break;
}
}
}
- dentry->d_inode->i_dos_name_extracted = 1;
- 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 (!ni) {
+ ni = ntfs_inode_open(ctx->vol, inode->i_mft_no);
+ ctx->open_inodes[ctx->num_open_inodes++] = ni;
+ inode->i_visited = 1;
+ }
+
+ if (!ni) {
+ ERROR_WITH_ERRNO("Can't open \"%s\" in NTFS volume",
+ dentry_full_path(
+ inode_first_extraction_dentry(inode)));
+ return NULL;
+ }
+ return ni;
+}
+
+static int
+ntfs_3g_begin_extract_stream(struct wim_lookup_table_entry *stream, void *_ctx)
+{
+ struct ntfs_3g_apply_ctx *ctx = _ctx;
+ const struct stream_owner *owners = stream_owners(stream);
+ int ret;
+
+ for (u32 i = 0; i < stream->out_refcnt; i++) {
+ struct wim_inode *inode = owners[i].inode;
+ ntfschar *stream_name = (ntfschar *)owners[i].stream_name;
+ ntfs_inode *ni;
+
+ ret = WIMLIB_ERR_NTFS_3G;
+ ni = ntfs_3g_open_inode(inode, ctx);
+ if (!ni)
+ goto out_cleanup;
+
+ ret = ntfs_3g_begin_extract_stream_to_attr(stream, ni, inode,
+ stream_name, ctx);
+ if (ret)
+ goto out_cleanup;
+ }
+ ret = 0;
+ goto out;
+
+out_cleanup:
+ ntfs_3g_cleanup_stream_extract(ctx);
+out:
+ for (u32 i = 0; i < stream->out_refcnt; i++)
+ owners[i].inode->i_visited = 0;
+ return ret;
+}
+
+static int
+ntfs_3g_extract_chunk(const void *chunk, size_t size, void *_ctx)
+{
+ struct ntfs_3g_apply_ctx *ctx = _ctx;
+ s64 res;
+
+ for (unsigned i = 0; i < ctx->num_open_attrs; i++) {
+ res = ntfs_attr_pwrite(ctx->open_attrs[i],
+ ctx->offset, size, chunk);
+ if (res != size) {
+ ERROR_WITH_ERRNO("Error writing data to NTFS volume");
+ return WIMLIB_ERR_NTFS_3G;
}
- } else {
+ }
+ if (ctx->reparse_ptr)
+ ctx->reparse_ptr = mempcpy(ctx->reparse_ptr, chunk, size);
+ ctx->offset += size;
+ return 0;
+}
+
+static int
+ntfs_3g_end_extract_stream(struct wim_lookup_table_entry *stream,
+ int status, void *_ctx)
+{
+ struct ntfs_3g_apply_ctx *ctx = _ctx;
+ int ret;
+
+ if (status) {
+ ret = status;
+ goto out;
+ }
+
+ for (u32 i = 0; i < ctx->num_reparse_inodes; i++) {
+ struct wim_inode *inode = ctx->wim_reparse_inodes[i];
+
+ ctx->rpbuf.rptag = cpu_to_le32(inode->i_reparse_tag);
+
+ if (ntfs_set_ntfs_reparse_data(ctx->ntfs_reparse_inodes[i],
+ (const char *)&ctx->rpbuf,
+ stream->size + REPARSE_DATA_OFFSET,
+ 0))
+ {
+ ERROR_WITH_ERRNO("Failed to set reparse "
+ "data on \"%s\"",
+ dentry_full_path(
+ inode_first_extraction_dentry(inode)));
+ ret = WIMLIB_ERR_NTFS_3G;
+ goto out;
+ }
+ }
+ ret = 0;
+out:
+ if (ntfs_3g_cleanup_stream_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 uint64_t
+ntfs_3g_count_dentries(const struct list_head *dentry_list)
{
- struct apply_args *args = arg;
- ntfs_volume *vol = args->vol;
- u8 *p;
- u8 buf[24];
- ntfs_inode *ni;
+ const struct wim_dentry *dentry;
+ uint64_t 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;
}
+ ctx->vol = vol;
- p = buf;
- p = put_u64(p, dentry->d_inode->i_creation_time);
- p = put_u64(p, dentry->d_inode->i_last_write_time);
- p = put_u64(p, dentry->d_inode->i_last_access_time);
- ret = ntfs_inode_set_times(ni, (const char*)buf, 3 * sizeof(u64), 0);
- if (ret != 0) {
- ERROR_WITH_ERRNO("Failed to set NTFS timestamps on `%s'",
- dentry->_full_path);
- ret = WIMLIB_ERR_NTFS_3G;
- }
+ /* Create all inodes and aliases, including short names, and set
+ * metadata (attributes, security descriptors, and timestamps). */
- if (ntfs_inode_close(ni) != 0) {
- if (ret == 0)
- ret = WIMLIB_ERR_NTFS_3G;
- ERROR_WITH_ERRNO("Failed to close NTFS inode for `%s'",
- dentry->_full_path);
+ 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 streams. */
+ struct read_stream_list_callbacks cbs = {
+ .begin_stream = ntfs_3g_begin_extract_stream,
+ .begin_stream_ctx = ctx,
+ .consume_chunk = ntfs_3g_extract_chunk,
+ .consume_chunk_ctx = ctx,
+ .end_stream = ntfs_3g_end_extract_stream,
+ .end_stream_ctx = ctx,
+ };
+ ret = extract_stream_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 (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;
}
+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,
+};
+
void
libntfs3g_global_init(void)
{
- ntfs_set_char_encoding(setlocale(LC_ALL, ""));
+ ntfs_set_char_encoding(setlocale(LC_ALL, ""));
}
-
-#endif /* WITH_NTFS_3G */