X-Git-Url: https://wimlib.net/git/?p=wimlib;a=blobdiff_plain;f=src%2Fntfs-3g_apply.c;h=86a28c10e2aa31e2be7182e5ff0a521901cfe6cc;hp=4e5926bcc71f21ea0fad433e043b33095b8e0abc;hb=335e7f84a6ea46ea13e333c7294be0594fc859ca;hpb=fe328582e4ba43a639944067c3232358069c2e01 diff --git a/src/ntfs-3g_apply.c b/src/ntfs-3g_apply.c index 4e5926bc..86a28c10 100644 --- a/src/ntfs-3g_apply.c +++ b/src/ntfs-3g_apply.c @@ -31,642 +31,570 @@ #ifdef WITH_NTFS_3G +#include #include +#include #include -#include /* NTFS-3g headers are missing include */ +#ifdef HAVE_ALLOCA_H +# include +#endif #include -#include #include #include -#include -#include #include "wimlib/apply.h" -#include "wimlib/compiler.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" +#include "wimlib/paths.h" +#include "wimlib/resource.h" +#include "wimlib/security_descriptor.h" -struct ntfs_attr_extract_ctx { - u64 offset; - ntfs_attr *na; -}; +static ntfs_volume * +ntfs_3g_apply_ctx_get_volume(struct apply_ctx *ctx) +{ + return (ntfs_volume*)ctx->private[0]; +} -static int -extract_wim_chunk_to_ntfs_attr(const void *buf, size_t len, void *_ctx) +static void +ntfs_3g_apply_ctx_set_volume(struct apply_ctx *ctx, ntfs_volume *vol) { - 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; - } + ctx->private[0] = (intptr_t)vol; } -/* - * 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) +static ntfs_inode * +ntfs_3g_apply_pathname_to_inode(const char *path, struct apply_ctx *ctx) { - 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); + ntfs_volume *vol = ntfs_3g_apply_ctx_get_volume(ctx); + return ntfs_pathname_to_inode(vol, NULL, path); } -/* 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. - */ +struct ntfs_attr_extract_ctx { + u64 offset; + ntfs_attr *na; +}; + static int -write_ntfs_data_streams(ntfs_inode *ni, struct wim_dentry *dentry, - union wimlib_progress_info *progress_info) +ntfs_3g_extract_wim_chunk(const void *buf, size_t len, void *_ctx) { - 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; - - } - } - - /* 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; - } - - /* 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; - } - - /* Actually extract the stream */ - ret = extract_wim_resource_to_ntfs_attr(lte, na); - - /* Close the attribute */ - ntfs_attr_close(na); - if (ret) - break; - - /* 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; - - /* 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; + struct ntfs_attr_extract_ctx *ctx = _ctx; + + if (ntfs_attr_pwrite(ctx->na, ctx->offset, len, buf) != len) + return WIMLIB_ERR_WRITE; + ctx->offset += len; + return 0; } -/* 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) +ntfs_3g_open_parent_inode(const char *path, ntfs_volume *vol) { char *p; - const char *dir_name; ntfs_inode *dir_ni; - char orig; - p = dentry->_full_path + dentry->full_path_nbytes; - do { - p--; - } while (*p != '/'); - - orig = *p; + p = strrchr(path, '/'); *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); - } - *p = orig; + dir_ni = ntfs_pathname_to_inode(vol, NULL, path); + *p = '/'; return dir_ni; } -/* - * 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. - * - * 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. - */ static int -apply_ntfs_hardlink(struct wim_dentry *from_dentry, - const struct wim_inode *inode, - ntfs_inode *dir_ni) +ntfs_3g_create(const char *path, struct apply_ctx *ctx, u64 *cookie_ret, + mode_t mode) { - int ret; - ntfs_inode *to_ni; ntfs_volume *vol; + ntfs_inode *dir_ni, *ni; + const char *name; + utf16lechar *name_utf16le; + size_t name_utf16le_nbytes; + int ret; - vol = dir_ni->vol; - ret = ntfs_inode_close(dir_ni); - if (ret != 0) { - ERROR_WITH_ERRNO("Error closing directory"); - return WIMLIB_ERR_NTFS_3G; - } - - DEBUG("Extracting NTFS hard link `%s' => `%s'", - from_dentry->_full_path, inode->i_extracted_file); + vol = ntfs_3g_apply_ctx_get_volume(ctx); - 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; - } + ret = WIMLIB_ERR_OPEN; + dir_ni = ntfs_3g_open_parent_inode(path, vol); + if (!dir_ni) + goto out; - dir_ni = dentry_open_parent_ni(from_dentry, vol); - if (!dir_ni) { - ntfs_inode_close(to_ni); - return WIMLIB_ERR_NTFS_3G; - } + name = path_basename(path); + ret = tstr_to_utf16le(name, strlen(name), + &name_utf16le, &name_utf16le_nbytes); + if (ret) + goto out_close_dir_ni; - 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); - 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; - } + ret = WIMLIB_ERR_OPEN; + ni = ntfs_create(dir_ni, 0, name_utf16le, + name_utf16le_nbytes / 2, mode); + if (!ni) + goto out_free_name_utf16le; + *cookie_ret = MK_MREF(ni->mft_no, le16_to_cpu(ni->mrec->sequence_number)); + if (ntfs_inode_close_in_dir(ni, dir_ni)) + goto out_free_name_utf16le; + ret = 0; +out_free_name_utf16le: + FREE(name_utf16le); +out_close_dir_ni: + if (ntfs_inode_close(dir_ni)) + ret = WIMLIB_ERR_WRITE; +out: return ret; } -/* 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. - */ 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_file(const char *path, struct apply_ctx *ctx, + u64 *cookie_ret) { - 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(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); - - 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); - ret = WIMLIB_ERR_NTFS_3G; - } - } - return ret; + return ntfs_3g_create(path, ctx, cookie_ret, S_IFREG); +} + +static int +ntfs_3g_create_directory(const char *path, struct apply_ctx *ctx, + u64 *cookie_ret) +{ + return ntfs_3g_create(path, ctx, cookie_ret, S_IFDIR); } -/* - * Transfers the reparse data from a WIM inode (which must represent a reparse - * point) to a NTFS inode. - */ static int -apply_reparse_data(ntfs_inode *ni, struct wim_dentry *dentry, - union wimlib_progress_info *progress_info) +ntfs_3g_create_hardlink(const char *oldpath, const char *newpath, + struct apply_ctx *ctx) { + ntfs_volume *vol; + ntfs_inode *dir_ni, *ni; + const char *name; + utf16lechar *name_utf16le; + size_t name_utf16le_nbytes; int ret; - u8 rpbuf[REPARSE_POINT_MAX_SIZE] _aligned_attribute(8); - u16 rpbuflen; - DEBUG("Applying reparse data to `%s'", dentry->_full_path); + vol = ntfs_3g_apply_ctx_get_volume(ctx); - ret = wim_inode_get_reparse_data(dentry->d_inode, rpbuf, &rpbuflen); - if (ret) - return ret; + ret = WIMLIB_ERR_OPEN; + ni = ntfs_pathname_to_inode(vol, NULL, oldpath); + if (!ni) + goto out; - 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; - } + ret = WIMLIB_ERR_OPEN; + dir_ni = ntfs_3g_open_parent_inode(newpath, vol); + if (!dir_ni) + goto out_close_ni; - progress_info->extract.completed_bytes += rpbuflen - 8; - return 0; + name = path_basename(newpath); + ret = tstr_to_utf16le(name, strlen(name), + &name_utf16le, &name_utf16le_nbytes); + if (ret) + goto out_close_dir_ni; + ret = 0; + if (ntfs_link(ni, dir_ni, name_utf16le, name_utf16le_nbytes / 2)) + ret = WIMLIB_ERR_LINK; + FREE(name_utf16le); +out_close_dir_ni: + if (ntfs_inode_close(dir_ni)) + ret = WIMLIB_ERR_WRITE; +out_close_ni: + if (ntfs_inode_close(ni)) + ret = WIMLIB_ERR_WRITE; +out: + return ret; } /* - * Applies a WIM dentry to a NTFS filesystem. - * - * @dentry: The WIM dentry to apply - * @dir_ni: The NTFS inode for the parent directory - * - * @return: 0 on success; nonzero on failure. + * Extract a stream (default or alternate data) to an attribute of a NTFS file. */ static int -do_apply_dentry_ntfs(struct wim_dentry *dentry, ntfs_inode *dir_ni, - struct apply_args *args) +ntfs_3g_extract_stream(file_spec_t file, const utf16lechar *raw_stream_name, + size_t stream_name_nchars, + struct wim_lookup_table_entry *lte, struct apply_ctx *ctx) { + ntfs_inode *ni; + ntfs_attr *na; int ret; - mode_t type; - ntfs_inode *ni = NULL; - struct wim_inode *inode = dentry->d_inode; - dentry->needs_extraction = 0; + struct ntfs_attr_extract_ctx extract_ctx; + utf16lechar *stream_name; - if (inode->i_attributes & FILE_ATTRIBUTE_DIRECTORY) { - type = S_IFDIR; + if (stream_name_nchars == 0) { + stream_name = AT_UNNAMED; } 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; - } - } - } + stream_name = alloca((stream_name_nchars + 1) * sizeof(utf16lechar)); + memcpy(stream_name, raw_stream_name, + stream_name_nchars * sizeof(utf16lechar)); + stream_name[stream_name_nchars] = 0; } - /* Create a NTFS directory or file. + ret = 0; + if (!stream_name_nchars && !lte) + goto out; + + /* Open NTFS inode to which to extract the stream. */ + ret = WIMLIB_ERR_OPEN; + ni = ntfs_inode_open(ntfs_3g_apply_ctx_get_volume(ctx), file.cookie); + if (!ni) + goto out; + + /* Add the stream if it's not the default (unnamed) stream. */ + ret = WIMLIB_ERR_OPEN; + if (stream_name_nchars) + if (ntfs_attr_add(ni, AT_DATA, stream_name, + stream_name_nchars, NULL, 0)) + goto out_close; + + /* If stream is empty, no need to open and extract it. */ + ret = 0; + if (!lte) + goto out_close; + + /* Open the stream (NTFS attribute). */ + ret = WIMLIB_ERR_OPEN; + na = ntfs_attr_open(ni, AT_DATA, stream_name, stream_name_nchars); + if (!na) + goto out_close; + + /* (Optional) Immediately resize attribute to size of stream. * - * 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); - - 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; - } + * This dramatically speeds up extraction, as demonstrated with the + * following timing results: + * + * 18 mins. 27 sec. to apply Windows 7 image (with resize) + * 32 mins. 45 sec. to apply Windows 7 image (no resize) + * + * It probably would speed things up even more if we could get NTFS-3g + * to skip even more useless work (for example it fills resized + * attributes with 0's, then we just override it.) */ + ret = WIMLIB_ERR_WRITE; + if (ntfs_attr_truncate_solid(na, lte->size)) + goto out_attr_close; + + /* Extract stream data to the NTFS attribute. */ + extract_ctx.na = na; + extract_ctx.offset = 0; + ret = extract_stream(lte, lte->size, + ntfs_3g_extract_wim_chunk, &extract_ctx); + /* Clean up and return. */ +out_attr_close: + ntfs_attr_close(na); +out_close: + if (ntfs_inode_close(ni)) + ret = WIMLIB_ERR_WRITE; +out: + if (ret && !errno) + errno = -1; + return ret; +} - /* 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); - if (ret) - goto out_close_dir_ni; - } +static int +ntfs_3g_extract_unnamed_stream(file_spec_t file, + struct wim_lookup_table_entry *lte, + struct apply_ctx *ctx) +{ + return ntfs_3g_extract_stream(file, NULL, 0, lte, ctx); +} - ret = apply_file_attributes_and_security_data(ni, dir_ni, dentry, - args->w, - args->extract_flags); - if (ret) - goto out_close_dir_ni; +static int +ntfs_3g_extract_named_stream(file_spec_t file, const utf16lechar *stream_name, + size_t stream_name_nchars, + struct wim_lookup_table_entry *lte, struct apply_ctx *ctx) +{ + return ntfs_3g_extract_stream(file, stream_name, + stream_name_nchars, lte, ctx); +} - if (inode->i_attributes & FILE_ATTR_REPARSE_POINT) { - ret = apply_reparse_data(ni, dentry, &args->progress); - if (ret) - goto out_close_dir_ni; - } +static int +ntfs_3g_set_file_attributes(const char *path, u32 attributes, + struct apply_ctx *ctx, unsigned pass) +{ + ntfs_inode *ni; + int ret = 0; - /* 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); - 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; - } - /* inodes have been closed by ntfs_set_ntfs_dos_name(). */ - goto out; - } -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); - } - } -out: + ni = ntfs_3g_apply_pathname_to_inode(path, ctx); + if (!ni) + return WIMLIB_ERR_OPEN; + if (ntfs_set_ntfs_attrib(ni, (const char*)&attributes, sizeof(u32), 0)) + ret = WIMLIB_ERR_SET_ATTRIBUTES; + if (ntfs_inode_close(ni)) + ret = WIMLIB_ERR_WRITE; return ret; } static int -apply_root_dentry_ntfs(struct wim_dentry *dentry, - ntfs_volume *vol, const WIMStruct *w, - int extract_flags) +ntfs_3g_set_reparse_data(const char *path, const u8 *rpbuf, u16 rpbuflen, + struct apply_ctx *ctx) { ntfs_inode *ni; int ret = 0; - ret = calculate_dentry_full_path(dentry); - if (ret) - return ret; - - ni = ntfs_pathname_to_inode(vol, NULL, "/"); - if (!ni) { - ERROR_WITH_ERRNO("Could not find root NTFS inode"); - return WIMLIB_ERR_NTFS_3G; - } - 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; - } + ni = ntfs_3g_apply_pathname_to_inode(path, ctx); + if (!ni) + return WIMLIB_ERR_OPEN; + if (ntfs_set_ntfs_reparse_data(ni, rpbuf, rpbuflen, 0)) + ret = WIMLIB_ERR_SET_REPARSE_DATA; + if (ntfs_inode_close(ni)) + ret = WIMLIB_ERR_WRITE; return ret; } -/* Applies a WIM dentry to the NTFS volume */ -int -apply_dentry_ntfs(struct wim_dentry *dentry, void *arg) +static int +ntfs_3g_set_short_name(const char *path, const utf16lechar *short_name, + size_t short_name_nchars, struct 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; + ntfs_inode *ni, *dir_ni; + ntfs_volume *vol; int ret; + char *dosname = NULL; + size_t dosname_nbytes; - /* 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) || 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; - } - } - } - dentry->d_inode->i_dos_name_extracted = 1; + ret = 0; + if (short_name_nchars == 0) + goto out; + + vol = ntfs_3g_apply_ctx_get_volume(ctx); + + ret = WIMLIB_ERR_OPEN; + dir_ni = ntfs_3g_open_parent_inode(path, vol); + if (!dir_ni) + goto out; - ret = calculate_dentry_full_path(dentry); + ret = WIMLIB_ERR_OPEN; + ni = ntfs_pathname_to_inode(vol, NULL, path); + if (!ni) + goto out_close_dir_ni; + + ret = utf16le_to_tstr(short_name, short_name_nchars * 2, + &dosname, &dosname_nbytes); if (ret) - return 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; - } + goto out_close_ni; + + ret = 0; + if (ntfs_set_ntfs_dos_name(ni, dir_ni, dosname, + dosname_nbytes, 0)) + ret = WIMLIB_ERR_SET_SHORT_NAME; + /* ntfs_set_ntfs_dos_name() always closes the inodes. */ + FREE(dosname); + goto out; +out_close_ni: + if (ntfs_inode_close_in_dir(ni, dir_ni)) + ret = WIMLIB_ERR_WRITE; +out_close_dir_ni: + if (ntfs_inode_close(dir_ni)) + ret = WIMLIB_ERR_WRITE; +out: + return ret; +} + +static size_t +sid_size(const wimlib_SID *sid) +{ + return offsetof(wimlib_SID, sub_authority) + + sizeof(le32) * sid->sub_authority_count; +} + +/* + * sd_fixup - Fix up a Windows NT security descriptor for libntfs-3g. + * + * libntfs-3g validates security descriptors before setting them, but old + * versions contain bugs causing it to reject unusual but valid security + * descriptors: + * + * - 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. + * + * 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 { - ret = WIMLIB_ERR_NTFS_3G; + return (u8*)_desc; } - return ret; + + desc_new = MALLOC(size + sid_size(sid)); + if (desc_new == NULL) + 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; } -/* 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 int +ntfs_3g_set_security_descriptor(const char *path, const u8 *desc, size_t desc_size, + struct apply_ctx *ctx) { - struct apply_args *args = arg; - ntfs_volume *vol = args->vol; - u64 ntfs_timestamps[3]; + ntfs_volume *vol; ntfs_inode *ni; + struct SECURITY_CONTEXT sec_ctx; + u8 *desc_fixed; int ret; - DEBUG("Setting timestamps on `%s'", dentry->_full_path); + vol = ntfs_3g_apply_ctx_get_volume(ctx); - 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); - return WIMLIB_ERR_NTFS_3G; - } + ni = ntfs_pathname_to_inode(vol, NULL, path); + if (!ni) + return WIMLIB_ERR_OPEN; + + memset(&sec_ctx, 0, sizeof(sec_ctx)); + sec_ctx.vol = vol; + + desc_fixed = sd_fixup(desc, &desc_size); + + ret = 0; + + if (ntfs_set_ntfs_acl(&sec_ctx, ni, desc_fixed, desc_size, 0)) + ret = WIMLIB_ERR_SET_SECURITY; + + if (desc_fixed != desc) + FREE(desc_fixed); + + if (ntfs_inode_close(ni)) + ret = WIMLIB_ERR_WRITE; + + return ret; +} + +static int +ntfs_3g_set_timestamps(const char *path, u64 creation_time, + u64 last_write_time, u64 last_access_time, + struct apply_ctx *ctx) +{ + u64 ntfs_timestamps[3]; + ntfs_inode *ni; + int ret = 0; + + ni = ntfs_3g_apply_pathname_to_inode(path, ctx); + if (!ni) + return WIMLIB_ERR_OPEN; /* 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); - ret = WIMLIB_ERR_NTFS_3G; + ntfs_timestamps[0] = creation_time; + ntfs_timestamps[1] = last_write_time; + ntfs_timestamps[2] = last_access_time; + + if (ntfs_inode_set_times(ni, (const char*)ntfs_timestamps, + sizeof(ntfs_timestamps), 0)) + ret = WIMLIB_ERR_SET_TIMESTAMPS; + if (ntfs_inode_close(ni)) + ret = WIMLIB_ERR_WRITE; + return ret; +} + +static bool +ntfs_3g_target_is_root(const char *target) +{ + /* We always extract to the root of the NTFS volume. */ + return true; +} + +static int +ntfs_3g_start_extract(const char *path, struct apply_ctx *ctx) +{ + ntfs_volume *vol; + + vol = ntfs_mount(ctx->target, 0); + if (!vol) { + ERROR_WITH_ERRNO("Failed to mount \"%"TS"\" with NTFS-3g", ctx->target); + return WIMLIB_ERR_OPEN; } + ntfs_3g_apply_ctx_set_volume(ctx, vol); + + ctx->supported_features.archive_files = 1; + ctx->supported_features.hidden_files = 1; + ctx->supported_features.system_files = 1; + ctx->supported_features.compressed_files = 1; + ctx->supported_features.encrypted_files = 0; + ctx->supported_features.not_context_indexed_files = 1; + ctx->supported_features.sparse_files = 1; + ctx->supported_features.named_data_streams = 1; + ctx->supported_features.hard_links = 1; + ctx->supported_features.reparse_points = 1; + ctx->supported_features.security_descriptors = 1; + ctx->supported_features.short_names = 1; + return 0; +} - 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); +static int +ntfs_3g_finish_or_abort_extract(struct apply_ctx *ctx) +{ + ntfs_volume *vol; + + vol = ntfs_3g_apply_ctx_get_volume(ctx); + if (ntfs_umount(vol, FALSE)) { + ERROR_WITH_ERRNO("Failed to unmount \"%"TS"\" with NTFS-3g", + ctx->target); + return WIMLIB_ERR_WRITE; } - return ret; + return 0; } void @@ -675,4 +603,65 @@ libntfs3g_global_init(void) ntfs_set_char_encoding(setlocale(LC_ALL, "")); } +const struct apply_operations ntfs_3g_apply_ops = { + .name = "NTFS-3g", + + .target_is_root = ntfs_3g_target_is_root, + .start_extract = ntfs_3g_start_extract, + .create_file = ntfs_3g_create_file, + .create_directory = ntfs_3g_create_directory, + .create_hardlink = ntfs_3g_create_hardlink, + .extract_unnamed_stream = ntfs_3g_extract_unnamed_stream, + .extract_named_stream = ntfs_3g_extract_named_stream, + .set_file_attributes = ntfs_3g_set_file_attributes, + .set_reparse_data = ntfs_3g_set_reparse_data, + .set_short_name = ntfs_3g_set_short_name, + .set_security_descriptor = ntfs_3g_set_security_descriptor, + .set_timestamps = ntfs_3g_set_timestamps, + .abort_extract = ntfs_3g_finish_or_abort_extract, + .finish_extract = ntfs_3g_finish_or_abort_extract, + + .path_prefix = "/", + .path_prefix_nchars = 1, + .path_separator = '/', + .path_max = 32768, + + /* By default, NTFS-3g creates names in the NTFS POSIX namespace, which + * is case-sensitive. */ + .supports_case_sensitive_filenames = 1, + + /* The root directory of the NTFS volume should not be created + * explicitly. */ + .root_directory_is_special = 1, + + /* NTFS-3g can open files by MFT reference. */ + .uses_cookies = 1, + + /* + * With NTFS-3g, the extraction order of the names of a file that has a + * short name needs to be: + * + * 1. Create file using the long name that has an associated short name. + * This long name is temporarily placed in the POSIX namespace. + * 2. Set the short name on the file. This will either change the POSIX + * name to Win32 and create a new DOS name, or replace the POSIX name + * with a Win32+DOS name. + * 3. Create additional long names (links) of the file, which are placed + * in the POSIX namespace. + * + * The reason for this is that two issues can come up when the + * extraction is done otherwise: + * + * - If a DOS name is set on a file in a directory with several long + * names, it is ambiguous which long name to use (at least with the + * exported ntfs_set_ntfs_dos_name() function). + * - NTFS-3g 2013.1.13 will no longer allow even setting the DOS name on + * a file with multiple existing long names, even if those long names + * are in different directories and the ntfs_set_ntfs_dos_name() call + * is therefore unambiguous. (This was apparently changed with the + * FUSE interface in mind.) + */ + .requires_short_name_reordering = 1, +}; + #endif /* WITH_NTFS_3G */