include/wimlib/lzx_common.h \
include/wimlib/lzx_constants.h \
include/wimlib/metadata.h \
+ include/wimlib/object_id.h \
include/wimlib/pathlist.h \
include/wimlib/paths.h \
include/wimlib/pattern.h \
.IP \[bu]
All names of all files, including names in the Win32 namespace, DOS namespace,
Win32+DOS namespace, and POSIX namespace. This includes hard links.
+.IP \[bu]
+Object IDs.
.PP
However, there are also several known limitations of the NTFS volume extraction
mode:
DOS names (8.3) names of files; however, the failure to set them is not
considered an error condition.
.IP \[bu]
-Hard links, if supported by the filesystem.
+Hard links, if supported by the target filesystem.
+.IP \[bu]
+Object IDs, if supported by the target filesystem.
.PP
Additional notes about extracting files on Windows:
.IP \[bu] 4
.IP \[bu]
All names of all files, including names in the Win32 namespace, DOS namespace,
Win32+DOS namespace, and POSIX namespace. This includes hard links.
+.IP \[bu]
+Object IDs.
.PP
However, the main limitations of this NTFS volume capture mode are:
.IP \[bu] 4
considered an error condition.
.IP \[bu]
Hard links, if supported by the source filesystem.
+.IP \[bu]
+Object IDs, if supported by the source filesystem.
.PP
-There is no support for storing NTFS extended attributes and object IDs.
+There is no support for storing NTFS extended attributes.
.PP
The capture process is reversible, since when \fBwimlib-imagex apply\fR (on
Windows) extracts the captured WIM image, it will extract all of the above
although existing values will be preserved on commit. New files are assigned
default attributes based on the UNIX file mode bits.
.IP \[bu]
+Object IDs. These are not exposed in the mounted filesystem, although existing
+values will be preserved on commit. New files are not given object IDs.
+.IP \[bu]
EFS-encrypted files. The files themselves will be visible in mounted WIM images
but their data will not be available.
.SH SPLIT WIMS
unsigned long security_descriptors;
unsigned long short_names;
unsigned long unix_data;
+ unsigned long object_ids;
unsigned long timestamps;
unsigned long case_sensitive_filenames;
};
--- /dev/null
+#ifndef _WIMLIB_OBJECT_ID_H
+#define _WIMLIB_OBJECT_ID_H
+
+#include "wimlib/types.h"
+
+extern bool
+inode_has_object_id(const struct wim_inode *inode);
+
+extern const void *
+inode_get_object_id(const struct wim_inode *inode, u32 *len_ret);
+
+extern bool
+inode_set_object_id(struct wim_inode *inode, const void *object_id, u32 len);
+
+#endif /* _WIMLIB_OBJECT_ID_H */
#include "wimlib/endianness.h"
#include "wimlib/error.h"
#include "wimlib/metadata.h"
+#include "wimlib/object_id.h"
#include "wimlib/pathlist.h"
#include "wimlib/paths.h"
#include "wimlib/pattern.h"
features->security_descriptors++;
if (inode_has_unix_data(inode))
features->unix_data++;
+ if (inode_has_object_id(inode))
+ features->object_ids++;
}
/* Tally features necessary to extract a dentry and the corresponding inode. */
required_features->unix_data);
}
+ /* Object IDs. */
+ if (required_features->object_ids && !supported_features->object_ids) {
+ WARNING("Ignoring object IDs of %lu files",
+ required_features->object_ids);
+ }
+
/* DOS Names. */
if (required_features->short_names &&
!supported_features->short_names)
*
* Apply a WIM image directly to an NTFS volume using libntfs-3g. Restore as
* much information as possible, including security data, file attributes, DOS
- * names, and alternate data streams.
+ * names, alternate data streams, and object IDs.
*
* Note: because NTFS-3g offers inode-based interfaces, we actually don't need
* to deal with paths at all! (Other than for error messages.)
*/
/*
- * Copyright (C) 2012, 2013, 2014, 2015 Eric Biggers
+ * Copyright (C) 2012-2016 Eric Biggers
*
* This file is free software; you can redistribute it and/or modify it under
* the terms of the GNU Lesser General Public License as published by the Free
#include <string.h>
#include <ntfs-3g/attrib.h>
+#include <ntfs-3g/object_id.h>
#include <ntfs-3g/reparse.h>
#include <ntfs-3g/security.h>
#include "wimlib/error.h"
#include "wimlib/metadata.h"
#include "wimlib/ntfs_3g.h"
+#include "wimlib/object_id.h"
#include "wimlib/reparse.h"
#include "wimlib/security.h"
#include "wimlib/security_descriptor.h"
supported_features->reparse_points = 1;
supported_features->security_descriptors = 1;
supported_features->short_names = 1;
+ supported_features->object_ids = 1;
supported_features->timestamps = 1;
supported_features->case_sensitive_filenames = 1;
return 0;
sd = wim_get_current_security_data(ctx->common.wim);
one_dentry = inode_first_extraction_dentry(inode);
+ /* Object ID */
+ {
+ u32 len;
+ const void *object_id = inode_get_object_id(inode, &len);
+ if (unlikely(object_id != NULL) &&
+ ntfs_set_ntfs_object_id(ni, object_id, len, 0))
+ {
+ if (errno == EEXIST) {
+ WARNING("Duplicate object ID on file \"%s\"",
+ dentry_full_path(one_dentry));
+ } else {
+ ERROR_WITH_ERRNO("Failed to set object ID on "
+ "\"%s\" in NTFS volume",
+ dentry_full_path(one_dentry));
+ return WIMLIB_ERR_NTFS_3G;
+ }
+ }
+ }
+
/* Attributes */
if (!(extract_flags & WIMLIB_EXTRACT_FLAG_NO_ATTRIBUTES)) {
u32 attrib = inode->i_attributes;
*/
/*
- * Copyright (C) 2012, 2013, 2014, 2015 Eric Biggers
+ * Copyright (C) 2012-2016 Eric Biggers
*
* This file is free software; you can redistribute it and/or modify it under
* the terms of the GNU Lesser General Public License as published by the Free
#include <errno.h>
#include <ntfs-3g/attrib.h>
+#include <ntfs-3g/object_id.h>
#include <ntfs-3g/reparse.h>
#include <ntfs-3g/security.h>
#include <ntfs-3g/volume.h>
#include "wimlib/endianness.h"
#include "wimlib/error.h"
#include "wimlib/ntfs_3g.h"
+#include "wimlib/object_id.h"
#include "wimlib/paths.h"
#include "wimlib/reparse.h"
#include "wimlib/security.h"
return ret;
}
+static noinline_for_stack int
+load_object_id(ntfs_inode *ni, struct wim_inode *inode)
+{
+ OBJECT_ID_ATTR attr;
+ int len;
+
+ len = ntfs_get_ntfs_object_id(ni, (char *)&attr, sizeof(attr));
+ if (likely(len == -ENODATA || len == 0))
+ return 0;
+ if (len < 0)
+ return WIMLIB_ERR_NTFS_3G;
+ if (!inode_set_object_id(inode, &attr, len))
+ return WIMLIB_ERR_NOMEM;
+ return 0;
+}
+
/* Load the security descriptor of an NTFS inode into the corresponding WIM
* inode and the WIM image's security descriptor set. */
static noinline_for_stack int
goto out;
}
+ /* Load the object ID. */
+ ret = load_object_id(ni, inode);
+ if (ret) {
+ ERROR_WITH_ERRNO("Error reading object ID of \"%s\"", path);
+ goto out;
+ }
+
/* Scan the data streams.
*
* Note: directories should not have an unnamed data stream, but they
*/
/*
- * Copyright (C) 2014 Eric Biggers
+ * Copyright (C) 2014-2016 Eric Biggers
*
* This file is free software; you can redistribute it and/or modify it under
* the terms of the GNU Lesser General Public License as published by the Free
#include "wimlib/endianness.h"
#include "wimlib/inode.h"
+#include "wimlib/object_id.h"
#include "wimlib/types.h"
#include "wimlib/unix_data.h"
-/* Used by the Microsoft implementation. */
+/* Object ID tag; this is also used by the Microsoft implementation. */
#define TAG_OBJECT_ID 0x00000001
/* Random number that we'll use for tagging our UNIX data items. */
u8 data[];
};
+/* Unconfirmed: are all 64 bytes of the object ID always present? Since NTFS-3g
+ * permits shorter object IDs, we'll do the same for now. */
+#define OBJECT_ID_MIN_LENGTH 16
+
struct object_id_disk {
u8 object_id[16];
u8 birth_volume_id[16];
* NULL. */
static void *
inode_get_tagged_item(const struct wim_inode *inode,
- u32 desired_tag, u32 min_data_len)
+ u32 desired_tag, u32 min_data_len, u32 *actual_len_ret)
{
size_t minlen_with_hdr = sizeof(struct tagged_item_header) + min_data_len;
size_t len_remaining;
return NULL;
/* Matches the item we wanted? */
- if (tag == desired_tag && len >= min_data_len)
+ if (tag == desired_tag && len >= min_data_len) {
+ if (actual_len_ret)
+ *actual_len_ret = len;
return hdr->data;
+ }
len_remaining -= sizeof(struct tagged_item_header) + len;
p += sizeof(struct tagged_item_header) + len;
inode_get_unix_data_disk(const struct wim_inode *inode)
{
return inode_get_tagged_item(inode, TAG_WIMLIB_UNIX_DATA,
- sizeof(struct wimlib_unix_data_disk));
+ sizeof(struct wimlib_unix_data_disk),
+ NULL);
}
static inline struct wimlib_unix_data_disk *
p->rdev = cpu_to_le32(unix_data->rdev);
return true;
}
+
+/* Return %true iff the specified inode has an object ID. */
+bool
+inode_has_object_id(const struct wim_inode *inode)
+{
+ return inode_get_object_id(inode, NULL) != NULL;
+}
+
+/* Retrieve a pointer to the object ID of the specified inode and write its
+ * length to @len_ret. Return NULL if the inode does not have an object ID. */
+const void *
+inode_get_object_id(const struct wim_inode *inode, u32 *len_ret)
+{
+ return inode_get_tagged_item(inode, TAG_OBJECT_ID,
+ OBJECT_ID_MIN_LENGTH, len_ret);
+}
+
+/* Set the inode's object ID to the value specified by @object_id and @len.
+ * Assumes the inode didn't already have an object ID set. Returns %true if
+ * successful, %false if failed (out of memory). */
+bool
+inode_set_object_id(struct wim_inode *inode, const void *object_id, u32 len)
+{
+ void *p;
+
+ p = inode_add_tagged_item(inode, TAG_OBJECT_ID, len);
+ if (!p)
+ return false;
+
+ memcpy(p, object_id, len);
+ return true;
+}
#include "wimlib/encoding.h"
#include "wimlib/error.h"
#include "wimlib/metadata.h"
+#include "wimlib/object_id.h"
#include "wimlib/paths.h"
#include "wimlib/pattern.h"
#include "wimlib/reparse.h"
* [PrepopulateList]. */
unsigned long num_system_compression_exclusions;
+ /* Number of files for which we couldn't set the object ID. */
+ unsigned long num_object_id_failures;
+
/* The Windows build number of the image being applied, or 0 if unknown.
*/
u64 windows_build_number;
if (short_names_supported)
supported_features->short_names = 1;
+ if (vol_flags & FILE_SUPPORTS_OBJECT_IDS)
+ supported_features->object_ids = 1;
+
supported_features->timestamps = 1;
/* Note: Windows does not support case sensitive filenames! At least
FILE_ATTRIBUTE_SPARSE_FILE | \
FILE_ATTRIBUTE_COMPRESSED)
+static void
+set_object_id(HANDLE h, const struct wim_inode *inode,
+ struct win32_apply_ctx *ctx)
+{
+ const void *object_id;
+ u32 len;
+ NTSTATUS status;
+
+ if (!ctx->common.supported_features.object_ids)
+ return;
+
+ object_id = inode_get_object_id(inode, &len);
+ if (likely(object_id == NULL)) /* No object ID? */
+ return;
+
+ status = winnt_fsctl(h, FSCTL_SET_OBJECT_ID,
+ object_id, len, NULL, 0, NULL);
+ if (NT_SUCCESS(status))
+ return;
+
+ /* Object IDs must be unique within the filesystem. A duplicate might
+ * occur if an image containing object IDs is applied twice to the same
+ * filesystem. Arguably, the user should be warned in this case; but
+ * the reality seems to be that nothing important cares about object IDs
+ * except the Distributed Link Tracking Service... so for now these
+ * failures are just ignored. */
+ if (status == STATUS_DUPLICATE_NAME ||
+ status == STATUS_OBJECT_NAME_COLLISION)
+ return;
+
+ ctx->num_object_id_failures++;
+ if (ctx->num_object_id_failures < 10) {
+ winnt_warning(status, L"Can't set object ID on \"%ls\"",
+ current_path(ctx));
+ } else if (ctx->num_object_id_failures == 10) {
+ WARNING("Suppressing further warnings about failure to set "
+ "object IDs.");
+ }
+}
+
/* Set the security descriptor @desc, of @desc_size bytes, on the file with open
* handle @h. */
static NTSTATUS
FILE_BASIC_INFORMATION info;
NTSTATUS status;
- /* Set security descriptor if present and not in NO_ACLS mode */
+ /* Set the file's object ID if present and object IDs are supported by
+ * the filesystem. */
+ set_object_id(h, inode, ctx);
+
+ /* Set the file's security descriptor if present and we're not in
+ * NO_ACLS mode */
if (inode_has_security_descriptor(inode) &&
!(ctx->common.extract_flags & WIMLIB_EXTRACT_FLAG_NO_ACLS))
{
#include "wimlib/encoding.h"
#include "wimlib/endianness.h"
#include "wimlib/error.h"
+#include "wimlib/object_id.h"
#include "wimlib/paths.h"
#include "wimlib/reparse.h"
#include "wimlib/win32_vss.h"
return 0;
}
+/* Load a file's object ID into the corresponding WIM inode. */
+static noinline_for_stack int
+winnt_load_object_id(HANDLE h, struct wim_inode *inode,
+ const wchar_t *full_path, struct winnt_scan_ctx *ctx)
+{
+ FILE_OBJECTID_BUFFER buffer;
+ NTSTATUS status;
+ u32 len;
+
+ if (!(ctx->vol_flags & FILE_SUPPORTS_OBJECT_IDS))
+ return 0;
+
+ status = winnt_fsctl(h, FSCTL_GET_OBJECT_ID, NULL, 0,
+ &buffer, sizeof(buffer), &len);
+
+ if (status == STATUS_OBJECTID_NOT_FOUND) /* No object ID */
+ return 0;
+
+ if (status == STATUS_INVALID_DEVICE_REQUEST) {
+ /* The filesystem claimed to support object IDs, but we can't
+ * actually read them. This happens with Samba. */
+ ctx->vol_flags &= ~FILE_SUPPORTS_OBJECT_IDS;
+ return 0;
+ }
+
+ if (!NT_SUCCESS(status)) {
+ winnt_error(status, L"\"%ls\": Can't read object ID",
+ printable_path(full_path));
+ return WIMLIB_ERR_STAT;
+ }
+
+ if (len == 0) /* No object ID (for directories) */
+ return 0;
+
+ if (!inode_set_object_id(inode, &buffer, len))
+ return WIMLIB_ERR_NOMEM;
+
+ return 0;
+}
+
static int
winnt_build_dentry_tree_recursive(struct wim_dentry **root_ret,
HANDLE cur_dir,
goto out;
}
+ /* Get the file's object ID. */
+ ret = winnt_load_object_id(h, inode, full_path, ctx);
+ if (ret)
+ goto out;
+
/* If this is a reparse point, load the reparse data. */
if (unlikely(inode->i_attributes & FILE_ATTRIBUTE_REPARSE_POINT)) {
ret = winnt_load_reparse_data(h, inode, full_path, ctx->params);
u32 attributes;
u32 security_id;
u32 num_aliases;
- u32 num_streams;
+ u32 num_streams : 31;
+ u32 have_object_id : 1;
u32 first_stream_offset;
struct ntfs_dentry *first_child;
wchar_t short_name[13];
stream->StreamIdentifierLength / 2);
}
+static bool
+is_object_id_stream(const STREAM_LAYOUT_ENTRY *stream)
+{
+ return stream->StreamIdentifierLength == 24 &&
+ !wmemcmp(stream->StreamIdentifier, L"::$OBJECT_ID", 12);
+}
+
/*
* If the specified STREAM_LAYOUT_ENTRY represents a DATA stream as opposed to
* some other type of NTFS stream such as a STANDARD_INFORMATION stream, return
/* Validate the STREAM_LAYOUT_ENTRYs of the specified file and compute the total
* length in bytes of the ntfs_stream structures needed to hold the stream
- * information. */
+ * information. In addition, set *have_object_id_ret=true if the file has an
+ * object ID stream. */
static int
validate_streams_and_compute_total_length(const FILE_LAYOUT_ENTRY *file,
- size_t *total_length_ret)
+ size_t *total_length_ret,
+ bool *have_object_id_ret)
{
const STREAM_LAYOUT_ENTRY *stream =
(const void *)file + file->FirstStreamOffset;
if (use_stream(file, stream, &name, &name_nchars)) {
total += ALIGN(sizeof(struct ntfs_stream) +
(name_nchars + 1) * sizeof(wchar_t), 8);
+ } else if (is_object_id_stream(stream)) {
+ *have_object_id_ret = true;
}
if (stream->NextStreamOffset == 0)
break;
size_t n;
int ret;
void *p;
+ bool have_object_id = false;
inode_size = ALIGN(sizeof(struct ntfs_inode), 8);
}
if (file_has_streams(file)) {
- ret = validate_streams_and_compute_total_length(file, &n);
+ ret = validate_streams_and_compute_total_length(file, &n,
+ &have_object_id);
if (ret)
return ret;
inode_size += n;
ni->last_write_time = info->BasicInformation.LastWriteTime;
ni->last_access_time = info->BasicInformation.LastAccessTime;
ni->security_id = info->SecurityId;
+ ni->have_object_id = have_object_id;
p = FIRST_DENTRY(ni);
* filter driver (WOF) hides reparse points from regular filesystem APIs
* but not from FSCTL_QUERY_FILE_LAYOUT. */
if (ni->attributes & (FILE_ATTRIBUTE_REPARSE_POINT |
- FILE_ATTRIBUTE_ENCRYPTED))
+ FILE_ATTRIBUTE_ENCRYPTED) ||
+ ni->have_object_id)
{
ret = winnt_build_dentry_tree_recursive(&root,
NULL,
do_test 'touch file;
setfattr -n system.ntfs_acl -v 0s`cat $srcdir/tests/security_descriptor_1.base64` file'
+msg "file with object ID"
+do_test 'touch file;
+ touch file2;
+ setfattr -n system.ntfs_object_id -v 0x15ac83a36dc6cf8ec459b8017dd8626f file
+ setfattr -n system.ntfs_object_id -v 0xf67394c12b17608e1d050d181ba8ffd27df80cbdf620f4c82c79b9e6799147b697621aff72915ade05abb96b15dea1a3e0bda4caa9e33cfd461c92c16be9713d file2'
+
msg "files with different security descriptors"
do_test 'touch file;
touch file2;
cmp_xattr(file1, file2, "system.ntfs_acl", 0, false);
cmp_xattr(file1, file2, "system.ntfs_attrib", 0, false);
cmp_xattr(file1, file2, "system.ntfs_dos_name", 0, true);
+ cmp_xattr(file1, file2, "system.ntfs_object_id", 64, true);
cmp_xattr(file1, file2, "system.ntfs_reparse_data", 0, true);
cmp_xattr(file1, file2, "system.ntfs_times", 16, false);
cmp_ads(file1, file2);
call :do_test\r
if %errorlevel% neq 0 goto :fail\r
\r
+REM Note: since object IDs must be unique per filesystem, we can't expect them\r
+REM to preserved using our testing scheme. Therefore, win32-tree-cmp doesn't\r
+REM compare them, and the below tests really just ensure the object ID code is\r
+REM run to some extent.\r
+\r
+call :msg "file with object ID"\r
+echo hello > file\r
+fsutil objectid create file > nul\r
+call :do_test\r
+if %errorlevel% neq 0 goto :fail\r
+\r
+call :msg "directory with object ID"\r
+md subdir\r
+fsutil objectid set f67394c12b17608e1d050d181ba8ffd2 7df80cbdf620f4c82c79b9e6799147b6 97621aff72915ade05abb96b15dea1a3 e0bda4caa9e33cfd461c92c16be9713d subdir\r
+call :do_test\r
+if %errorlevel% neq 0 goto :fail\r
+\r
:rpfix_tests\r
\r
echo Testing rpfix junction\r