#ifdef WITH_NTFS_3G
+#include <errno.h>
#include <locale.h>
+#include <stdlib.h>
#include <string.h>
-#include <time.h> /* NTFS-3g headers are missing <time.h> include */
+#ifdef HAVE_ALLOCA_H
+# include <alloca.h>
+#endif
#include <ntfs-3g/attrib.h>
#include <ntfs-3g/reparse.h>
#include <ntfs-3g/security.h>
-#include <errno.h>
-
-#ifdef HAVE_ALLOCA_H
-# include <alloca.h>
-#else
-# include <stdlib.h>
-#endif
#include "wimlib/apply.h"
#include "wimlib/encoding.h"
#include "wimlib/ntfs_3g.h"
#include "wimlib/paths.h"
#include "wimlib/resource.h"
+#include "wimlib/security_descriptor.h"
static ntfs_volume *
ntfs_3g_apply_ctx_get_volume(struct apply_ctx *ctx)
}
static int
-ntfs_3g_create(const char *path, struct apply_ctx *ctx, mode_t mode)
+ntfs_3g_create(const char *path, struct apply_ctx *ctx, u64 *cookie_ret,
+ mode_t mode)
{
ntfs_volume *vol;
ntfs_inode *dir_ni, *ni;
vol = ntfs_3g_apply_ctx_get_volume(ctx);
- ret = WIMLIB_ERR_NTFS_3G;
+ ret = WIMLIB_ERR_OPEN;
dir_ni = ntfs_3g_open_parent_inode(path, vol);
if (!dir_ni)
goto out;
if (ret)
goto out_close_dir_ni;
- ret = 0;
+ ret = WIMLIB_ERR_OPEN;
ni = ntfs_create(dir_ni, 0, name_utf16le,
name_utf16le_nbytes / 2, mode);
- if (!ni || ntfs_inode_close_in_dir(ni, dir_ni))
- ret = WIMLIB_ERR_NTFS_3G;
+ 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_NTFS_3G;
+ ret = WIMLIB_ERR_WRITE;
out:
return ret;
}
static int
-ntfs_3g_create_file(const char *path, struct apply_ctx *ctx)
+ntfs_3g_create_file(const char *path, struct apply_ctx *ctx,
+ u64 *cookie_ret)
{
- return ntfs_3g_create(path, ctx, S_IFREG);
+ return ntfs_3g_create(path, ctx, cookie_ret, S_IFREG);
}
static int
-ntfs_3g_create_directory(const char *path, struct apply_ctx *ctx)
+ntfs_3g_create_directory(const char *path, struct apply_ctx *ctx,
+ u64 *cookie_ret)
{
- return ntfs_3g_create(path, ctx, S_IFDIR);
+ return ntfs_3g_create(path, ctx, cookie_ret, S_IFDIR);
}
static int
vol = ntfs_3g_apply_ctx_get_volume(ctx);
- ret = WIMLIB_ERR_NTFS_3G;
+ ret = WIMLIB_ERR_OPEN;
ni = ntfs_pathname_to_inode(vol, NULL, oldpath);
if (!ni)
goto out;
- ret = WIMLIB_ERR_NTFS_3G;
+ ret = WIMLIB_ERR_OPEN;
dir_ni = ntfs_3g_open_parent_inode(newpath, vol);
if (!dir_ni)
goto out_close_ni;
goto out_close_dir_ni;
ret = 0;
if (ntfs_link(ni, dir_ni, name_utf16le, name_utf16le_nbytes / 2))
- ret = WIMLIB_ERR_NTFS_3G;
+ ret = WIMLIB_ERR_LINK;
FREE(name_utf16le);
out_close_dir_ni:
if (ntfs_inode_close(dir_ni))
- ret = WIMLIB_ERR_NTFS_3G;
+ ret = WIMLIB_ERR_WRITE;
out_close_ni:
if (ntfs_inode_close(ni))
- ret = WIMLIB_ERR_NTFS_3G;
+ ret = WIMLIB_ERR_WRITE;
out:
return ret;
}
* Extract a stream (default or alternate data) to an attribute of a NTFS file.
*/
static int
-ntfs_3g_extract_stream(const char *path, const utf16lechar *raw_stream_name,
+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)
{
if (!stream_name_nchars && !lte)
goto out;
- /* Look up NTFS inode to which to extract the stream. */
- ret = WIMLIB_ERR_NTFS_3G;
- ni = ntfs_3g_apply_pathname_to_inode(path, ctx);
+ /* 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_NTFS_3G;
+ 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;
/* Open the stream (NTFS attribute). */
- ret = WIMLIB_ERR_NTFS_3G;
+ 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. */
- ret = WIMLIB_ERR_NTFS_3G;
- if (ntfs_attr_truncate_solid(na, wim_resource_size(lte)))
+ /* (Optional) Immediately resize attribute to size of stream.
+ *
+ * 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_wim_resource(lte, wim_resource_size(lte),
- ntfs_3g_extract_wim_chunk, &extract_ctx);
+ 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_NTFS_3G;
+ ret = WIMLIB_ERR_WRITE;
out:
if (ret && !errno)
errno = -1;
}
static int
-ntfs_3g_extract_unnamed_stream(const char *path,
+ntfs_3g_extract_unnamed_stream(file_spec_t file,
struct wim_lookup_table_entry *lte,
struct apply_ctx *ctx)
{
- return ntfs_3g_extract_stream(path, NULL, 0, lte, ctx);
+ return ntfs_3g_extract_stream(file, NULL, 0, lte, ctx);
}
static int
-ntfs_3g_extract_named_stream(const char *path, const utf16lechar *stream_name,
+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(path, stream_name,
+ return ntfs_3g_extract_stream(file, stream_name,
stream_name_nchars, lte, ctx);
}
static int
ntfs_3g_set_file_attributes(const char *path, u32 attributes,
- struct apply_ctx *ctx)
+ struct apply_ctx *ctx, unsigned pass)
{
ntfs_inode *ni;
int ret = 0;
ni = ntfs_3g_apply_pathname_to_inode(path, ctx);
if (!ni)
- return WIMLIB_ERR_NTFS_3G;
+ return WIMLIB_ERR_OPEN;
if (ntfs_set_ntfs_attrib(ni, (const char*)&attributes, sizeof(u32), 0))
- ret = WIMLIB_ERR_NTFS_3G;
+ ret = WIMLIB_ERR_SET_ATTRIBUTES;
if (ntfs_inode_close(ni))
- ret = WIMLIB_ERR_NTFS_3G;
+ ret = WIMLIB_ERR_WRITE;
return ret;
}
ni = ntfs_3g_apply_pathname_to_inode(path, ctx);
if (!ni)
- return WIMLIB_ERR_NTFS_3G;
+ return WIMLIB_ERR_OPEN;
if (ntfs_set_ntfs_reparse_data(ni, rpbuf, rpbuflen, 0))
- ret = WIMLIB_ERR_NTFS_3G;
+ ret = WIMLIB_ERR_SET_REPARSE_DATA;
if (ntfs_inode_close(ni))
- ret = WIMLIB_ERR_NTFS_3G;
+ ret = WIMLIB_ERR_WRITE;
return ret;
}
vol = ntfs_3g_apply_ctx_get_volume(ctx);
- ret = WIMLIB_ERR_NTFS_3G;
+ ret = WIMLIB_ERR_OPEN;
dir_ni = ntfs_3g_open_parent_inode(path, vol);
if (!dir_ni)
goto out;
- ret = WIMLIB_ERR_NTFS_3G;
+ ret = WIMLIB_ERR_OPEN;
ni = ntfs_pathname_to_inode(vol, NULL, path);
if (!ni)
goto out_close_dir_ni;
ret = 0;
if (ntfs_set_ntfs_dos_name(ni, dir_ni, dosname,
dosname_nbytes, 0))
- ret = WIMLIB_ERR_NTFS_3G;
+ 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_NTFS_3G;
+ ret = WIMLIB_ERR_WRITE;
out_close_dir_ni:
if (ntfs_inode_close(dir_ni))
- ret = WIMLIB_ERR_NTFS_3G;
+ 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 {
+ return (u8*)_desc;
+ }
+
+ 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;
+}
+
static int
ntfs_3g_set_security_descriptor(const char *path, const u8 *desc, size_t desc_size,
struct apply_ctx *ctx)
ntfs_volume *vol;
ntfs_inode *ni;
struct SECURITY_CONTEXT sec_ctx;
- int ret = 0;
+ u8 *desc_fixed;
+ int ret;
vol = ntfs_3g_apply_ctx_get_volume(ctx);
ni = ntfs_pathname_to_inode(vol, NULL, path);
if (!ni)
- return WIMLIB_ERR_NTFS_3G;
+ return WIMLIB_ERR_OPEN;
memset(&sec_ctx, 0, sizeof(sec_ctx));
sec_ctx.vol = vol;
- if (ntfs_set_ntfs_acl(&sec_ctx, ni, desc, desc_size, 0))
- ret = WIMLIB_ERR_NTFS_3G;
+ 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_NTFS_3G;
+ ret = WIMLIB_ERR_WRITE;
+
return ret;
}
ni = ntfs_3g_apply_pathname_to_inode(path, ctx);
if (!ni)
- return WIMLIB_ERR_NTFS_3G;
+ return WIMLIB_ERR_OPEN;
/* Note: ntfs_inode_set_times() expects the times in native byte order,
* not little endian. */
if (ntfs_inode_set_times(ni, (const char*)ntfs_timestamps,
sizeof(ntfs_timestamps), 0))
- ret = WIMLIB_ERR_NTFS_3G;
+ ret = WIMLIB_ERR_SET_TIMESTAMPS;
if (ntfs_inode_close(ni))
- ret = WIMLIB_ERR_NTFS_3G;
+ ret = WIMLIB_ERR_WRITE;
return ret;
}
vol = ntfs_mount(ctx->target, 0);
if (!vol) {
ERROR_WITH_ERRNO("Failed to mount \"%"TS"\" with NTFS-3g", ctx->target);
- return WIMLIB_ERR_NTFS_3G;
+ return WIMLIB_ERR_OPEN;
}
ntfs_3g_apply_ctx_set_volume(ctx, vol);
if (ntfs_umount(vol, FALSE)) {
ERROR_WITH_ERRNO("Failed to unmount \"%"TS"\" with NTFS-3g",
ctx->target);
- return WIMLIB_ERR_NTFS_3G;
+ return WIMLIB_ERR_WRITE;
}
return 0;
}
.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 */