#include "wimlib/reparse.h"
#include "wimlib/security.h"
-static inline ntfschar *
-attr_record_name(ATTR_RECORD *ar)
+/* A reference-counted NTFS volume than is automatically unmounted when the
+ * reference count reaches 0 */
+struct ntfs_volume_wrapper {
+ ntfs_volume *vol;
+ size_t refcnt;
+};
+
+/* Description of where data is located in an NTFS volume */
+struct ntfs_location {
+ struct ntfs_volume_wrapper *volume;
+ u64 mft_no;
+ utf16lechar *attr_name;
+ unsigned attr_name_nchars;
+ unsigned attr_type;
+ u64 sort_key;
+};
+
+static struct ntfs_volume_wrapper *
+get_ntfs_volume(struct ntfs_volume_wrapper *volume)
+{
+ volume->refcnt++;
+ return volume;
+}
+
+static void
+put_ntfs_volume(struct ntfs_volume_wrapper *volume)
+{
+ if (--volume->refcnt == 0) {
+ ntfs_umount(volume->vol, FALSE);
+ FREE(volume);
+ }
+}
+
+static inline const ntfschar *
+attr_record_name(const ATTR_RECORD *record)
{
- return (ntfschar*)((u8*)ar + le16_to_cpu(ar->name_offset));
+ return (const ntfschar *)
+ ((const u8 *)record + le16_to_cpu(record->name_offset));
}
static ntfs_attr *
loc->attr_name,
loc->attr_name_nchars);
if (!na) {
- ERROR_WITH_ERRNO("Failed to open attribute of \"%"TS"\" in "
- "NTFS volume", loc->path);
+ ERROR_WITH_ERRNO("Failed to open attribute of NTFS inode %"PRIu64,
+ loc->mft_no);
}
return na;
}
consume_data_callback_t cb, void *cb_ctx)
{
const struct ntfs_location *loc = blob->ntfs_loc;
- ntfs_volume *vol = loc->ntfs_vol;
+ ntfs_volume *vol = loc->volume->vol;
ntfs_inode *ni;
ntfs_attr *na;
s64 pos;
int ret;
u8 buf[BUFFER_SIZE];
- ni = ntfs_pathname_to_inode(vol, NULL, loc->path);
+ ni = ntfs_inode_open(vol, loc->mft_no);
if (!ni) {
- ERROR_WITH_ERRNO("Can't find NTFS inode for \"%"TS"\"", loc->path);
+ ERROR_WITH_ERRNO("Failed to open NTFS inode %"PRIu64,
+ loc->mft_no);
ret = WIMLIB_ERR_NTFS_3G;
goto out;
}
while (bytes_remaining) {
s64 to_read = min(bytes_remaining, sizeof(buf));
if (ntfs_attr_pread(na, pos, to_read, buf) != to_read) {
- ERROR_WITH_ERRNO("Error reading \"%"TS"\"", loc->path);
+ ERROR_WITH_ERRNO("Error reading data from NTFS inode "
+ "%"PRIu64, loc->mft_no);
ret = WIMLIB_ERR_NTFS_3G;
goto out_close_ntfs_attr;
}
return ret;
}
+void
+free_ntfs_location(struct ntfs_location *loc)
+{
+ put_ntfs_volume(loc->volume);
+ FREE(loc->attr_name);
+ FREE(loc);
+}
+
+struct ntfs_location *
+clone_ntfs_location(const struct ntfs_location *loc)
+{
+ struct ntfs_location *new = memdup(loc, sizeof(*loc));
+ if (!new)
+ goto err0;
+ if (loc->attr_name) {
+ new->attr_name = utf16le_dup(loc->attr_name);
+ if (!new->attr_name)
+ goto err1;
+ }
+ new->volume = get_ntfs_volume(loc->volume);
+ return new;
+
+err1:
+ FREE(new);
+err0:
+ return NULL;
+}
+
+int
+cmp_ntfs_locations(const struct ntfs_location *loc1,
+ const struct ntfs_location *loc2)
+{
+ return cmp_u64(loc1->sort_key, loc2->sort_key);
+}
+
static int
read_reparse_tag(ntfs_inode *ni, struct ntfs_location *loc,
u32 *reparse_tag_ret)
}
}
-/* Load attributes of the specified type from a file in the NTFS volume */
+/* When sorting blobs located in NTFS volumes for sequential reading, we sort
+ * first by starting LCN of the attribute if available, otherwise no sort order
+ * is defined. This usually results in better sequential access to the volume.
+ */
+static int
+set_attr_sort_key(ntfs_inode *ni, struct ntfs_location *loc)
+{
+ ntfs_attr *na;
+ runlist_element *rl;
+
+ na = open_ntfs_attr(ni, loc);
+ if (!na)
+ return WIMLIB_ERR_NTFS_3G;
+
+ rl = ntfs_attr_find_vcn(na, 0);
+ if (rl && rl->lcn != LCN_HOLE)
+ loc->sort_key = rl->lcn;
+ else
+ loc->sort_key = 0;
+
+ ntfs_attr_close(na);
+ return 0;
+}
+
+/* Save information about an NTFS attribute (stream) to a WIM inode. */
static int
-load_ntfs_attrs_with_type(struct wim_inode *inode,
+scan_ntfs_attr(struct wim_inode *inode,
+ ntfs_inode *ni,
+ const char *path,
+ size_t path_len,
+ struct list_head *unhashed_blobs,
+ struct ntfs_volume_wrapper *volume,
+ ATTR_TYPES type,
+ const ATTR_RECORD *record)
+{
+ const u64 data_size = ntfs_get_attribute_value_length(record);
+ const size_t name_nchars = record->name_length;
+ struct blob_descriptor *blob = NULL;
+ utf16lechar *stream_name = NULL;
+ struct wim_inode_stream *strm;
+ int ret;
+
+ if (unlikely(name_nchars)) {
+ /* Named stream */
+ stream_name = utf16le_dupz(attr_record_name(record),
+ name_nchars * sizeof(ntfschar));
+ if (!stream_name) {
+ ret = WIMLIB_ERR_NOMEM;
+ goto out_cleanup;
+ }
+ }
+
+ /* If the stream is non-empty, set up a blob descriptor for it. */
+ if (data_size != 0) {
+ blob = new_blob_descriptor();
+ if (unlikely(!blob)) {
+ ret = WIMLIB_ERR_NOMEM;
+ goto out_cleanup;
+ }
+
+ blob->ntfs_loc = CALLOC(1, sizeof(struct ntfs_location));
+ if (unlikely(!blob->ntfs_loc)) {
+ ret = WIMLIB_ERR_NOMEM;
+ goto out_cleanup;
+ }
+
+ blob->blob_location = BLOB_IN_NTFS_VOLUME;
+ blob->size = data_size;
+ blob->ntfs_loc->volume = get_ntfs_volume(volume);
+ blob->ntfs_loc->attr_type = type;
+ blob->ntfs_loc->mft_no = ni->mft_no;
+
+ if (unlikely(name_nchars)) {
+ blob->ntfs_loc->attr_name = utf16le_dup(stream_name);
+ if (!blob->ntfs_loc->attr_name) {
+ ret = WIMLIB_ERR_NOMEM;
+ goto out_cleanup;
+ }
+ blob->ntfs_loc->attr_name_nchars = name_nchars;
+ }
+
+ ret = set_attr_sort_key(ni, blob->ntfs_loc);
+ if (ret)
+ goto out_cleanup;
+
+ if (unlikely(type == AT_REPARSE_POINT)) {
+ if (blob->size < REPARSE_DATA_OFFSET) {
+ ERROR("Reparse data of \"%s\" "
+ "is invalid (only %"PRIu64" bytes)!",
+ path, data_size);
+ ret = WIMLIB_ERR_INVALID_REPARSE_DATA;
+ goto out_cleanup;
+ }
+ blob->size -= REPARSE_DATA_OFFSET;
+ ret = read_reparse_tag(ni, blob->ntfs_loc,
+ &inode->i_reparse_tag);
+ if (ret)
+ goto out_cleanup;
+ }
+ }
+
+ strm = inode_add_stream(inode,
+ attr_type_to_wimlib_stream_type(type),
+ stream_name ? stream_name : NO_STREAM_NAME,
+ blob);
+ if (unlikely(!strm)) {
+ ret = WIMLIB_ERR_NOMEM;
+ goto out_cleanup;
+ }
+ prepare_unhashed_blob(blob, inode, strm->stream_id, unhashed_blobs);
+ blob = NULL;
+ ret = 0;
+out_cleanup:
+ free_blob_descriptor(blob);
+ FREE(stream_name);
+ return ret;
+}
+
+/* Scan attributes of the specified type from a file in the NTFS volume */
+static int
+scan_ntfs_attrs_with_type(struct wim_inode *inode,
ntfs_inode *ni,
char *path,
size_t path_len,
struct list_head *unhashed_blobs,
- ntfs_volume *vol,
+ struct ntfs_volume_wrapper *volume,
ATTR_TYPES type)
{
ntfs_attr_search_ctx *actx;
- struct ntfs_location *ntfs_loc;
int ret;
- struct blob_descriptor *blob;
- utf16lechar *stream_name;
- DEBUG("Loading NTFS attributes from \"%s\"", path);
+ DEBUG("Scanning NTFS attributes from \"%s\"", path);
- /* Get context to search the attributes of the NTFS file. */
actx = ntfs_attr_get_search_ctx(ni, NULL);
if (!actx) {
- ERROR_WITH_ERRNO("Cannot get NTFS attribute search "
+ ERROR_WITH_ERRNO("Failed to get NTFS attribute search "
"context for \"%s\"", path);
return WIMLIB_ERR_NTFS_3G;
}
- /* Save each attribute */
while (!ntfs_attr_lookup(type, NULL, 0,
CASE_SENSITIVE, 0, NULL, 0, actx))
{
- u64 data_size = ntfs_get_attribute_value_length(actx->attr);
- size_t name_nchars = actx->attr->name_length;
- struct wim_inode_stream *strm;
-
- if (name_nchars) {
- stream_name = utf16le_dupz(attr_record_name(actx->attr),
- name_nchars * sizeof(ntfschar));
- if (!stream_name) {
- ret = WIMLIB_ERR_NOMEM;
- goto out_put_actx;
- }
- } else {
- stream_name = NULL;
- }
-
- if (data_size == 0) {
- /* Empty attribute. No blob is needed. */
- blob = NULL;
- ntfs_loc = NULL;
- } else {
- ntfs_loc = CALLOC(1, sizeof(*ntfs_loc));
- if (!ntfs_loc) {
- ret = WIMLIB_ERR_NOMEM;
- goto out_free_stream_name;
- }
- ntfs_loc->ntfs_vol = vol;
- ntfs_loc->attr_type = type;
- ntfs_loc->path = memdup(path, path_len + 1);
- if (!ntfs_loc->path) {
- ret = WIMLIB_ERR_NOMEM;
- goto out_free_ntfs_loc;
- }
- if (name_nchars) {
- ntfs_loc->attr_name = utf16le_dup(stream_name);
- if (!ntfs_loc->attr_name) {
- ret = WIMLIB_ERR_NOMEM;
- goto out_free_ntfs_loc;
- }
- ntfs_loc->attr_name_nchars = name_nchars;
- }
-
- blob = new_blob_descriptor();
- if (!blob) {
- ret = WIMLIB_ERR_NOMEM;
- goto out_free_ntfs_loc;
- }
- blob->blob_location = BLOB_IN_NTFS_VOLUME;
- blob->ntfs_loc = ntfs_loc;
- blob->size = data_size;
- ntfs_loc = NULL;
- if (type == AT_REPARSE_POINT) {
- if (data_size < REPARSE_DATA_OFFSET) {
- ERROR("Reparse data of \"%s\" "
- "is invalid (only %u bytes)!",
- path, (unsigned)data_size);
- ret = WIMLIB_ERR_NTFS_3G;
- goto out_free_blob;
- }
- blob->size -= REPARSE_DATA_OFFSET;
- ret = read_reparse_tag(ni, blob->ntfs_loc,
- &inode->i_reparse_tag);
- if (ret)
- goto out_free_blob;
- }
- }
-
- strm = inode_add_stream(inode,
- attr_type_to_wimlib_stream_type(type),
- stream_name ? stream_name : NO_STREAM_NAME,
- blob);
- if (!strm) {
- ret = WIMLIB_ERR_NOMEM;
- goto out_free_blob;
- }
- prepare_unhashed_blob(blob, inode, strm->stream_id, unhashed_blobs);
-
- FREE(stream_name);
- stream_name = NULL;
+ ret = scan_ntfs_attr(inode,
+ ni,
+ path,
+ path_len,
+ unhashed_blobs,
+ volume,
+ type,
+ actx->attr);
+ if (ret)
+ goto out_put_actx;
}
- if (errno == ENOENT) {
- ret = 0;
- } else {
+ if (errno != ENOENT) {
ERROR_WITH_ERRNO("Error listing NTFS attributes of \"%s\"", path);
ret = WIMLIB_ERR_NTFS_3G;
+ goto out_put_actx;
}
- goto out_put_actx;
-out_free_blob:
- free_blob_descriptor(blob);
-out_free_ntfs_loc:
- if (ntfs_loc) {
- FREE(ntfs_loc->path);
- FREE(ntfs_loc->attr_name);
- FREE(ntfs_loc);
- }
-out_free_stream_name:
- FREE(stream_name);
+ ret = 0;
out_put_actx:
ntfs_attr_put_search_ctx(actx);
- if (ret == 0)
- DEBUG("Successfully loaded NTFS attributes from \"%s\"", path);
- else
- ERROR("Failed to load NTFS attributes from \"%s\"", path);
return ret;
}
char *path;
size_t path_len;
struct dos_name_map *dos_name_map;
- ntfs_volume *vol;
+ struct ntfs_volume_wrapper *volume;
struct capture_params *params;
int ret;
};
char *path,
size_t path_len,
int name_type,
- ntfs_volume *ntfs_vol,
+ struct ntfs_volume_wrapper *volume,
struct capture_params *params);
static int
/* Open the inode for this directory entry and recursively capture the
* directory tree rooted at it */
- ntfs_inode *ni = ntfs_inode_open(ctx->vol, mref);
+ ntfs_inode *ni = ntfs_inode_open(ctx->volume->vol, mref);
if (!ni) {
/* XXX This used to be treated as an error, but NTFS-3g seemed
* to be unable to read some inodes on a Windows 8 image for
child = NULL;
ret = build_dentry_tree_ntfs_recursive(&child, ni, ctx->path,
path_len, name_type,
- ctx->vol, ctx->params);
+ ctx->volume, ctx->params);
path_len -= mbs_name_nbytes + 1;
if (child)
dentry_add_child(ctx->parent, child);
char *path,
size_t path_len,
int name_type,
- ntfs_volume *vol,
+ struct ntfs_volume_wrapper *volume,
struct capture_params *params)
{
u32 attributes;
inode->i_attributes = attributes;
if (attributes & FILE_ATTRIBUTE_REPARSE_POINT) {
- /* Load the reparse point stream. */
- ret = load_ntfs_attrs_with_type(inode, ni, path, path_len,
+ /* Scan the reparse point stream. */
+ ret = scan_ntfs_attrs_with_type(inode, ni, path, path_len,
params->unhashed_blobs,
- vol, AT_REPARSE_POINT);
+ volume, AT_REPARSE_POINT);
if (ret)
goto out;
}
- /* Load the data streams.
+ /* Scan the data streams.
*
* Note: directories should not have an unnamed data stream, but they
* may have named data streams. Nondirectories (including reparse
* points) can have an unnamed data stream as well as named data
* streams. */
- ret = load_ntfs_attrs_with_type(inode, ni, path, path_len,
- params->unhashed_blobs, vol, AT_DATA);
+ ret = scan_ntfs_attrs_with_type(inode, ni, path, path_len,
+ params->unhashed_blobs,
+ volume, AT_DATA);
if (ret)
goto out;
.path = path,
.path_len = path_len,
.dos_name_map = &dos_name_map,
- .vol = vol,
+ .volume = volume,
.params = params,
.ret = 0,
};
/* Get security descriptor */
memset(&sec_ctx, 0, sizeof(sec_ctx));
- sec_ctx.vol = vol;
+ sec_ctx.vol = volume->vol;
errno = 0;
sd = _sd;
return ret;
}
-
-int
-do_ntfs_umount(struct _ntfs_volume *vol)
-{
- DEBUG("Unmounting NTFS volume");
- if (ntfs_umount(vol, FALSE))
- return WIMLIB_ERR_NTFS_3G;
- else
- return 0;
-}
-
int
build_dentry_tree_ntfs(struct wim_dentry **root_p,
const char *device,
struct capture_params *params)
{
+ struct ntfs_volume_wrapper *volume;
ntfs_volume *vol;
ntfs_inode *root_ni;
+ char *path;
int ret;
+ volume = MALLOC(sizeof(struct ntfs_volume_wrapper));
+ if (!volume)
+ return WIMLIB_ERR_NOMEM;
+
DEBUG("Mounting NTFS volume `%s' read-only", device);
-/* NTFS-3g 2013 renamed the "read-only" mount flag from MS_RDONLY to
- * NTFS_MNT_RDONLY.
- *
- * Unfortunately we can't check for defined(NTFS_MNT_RDONLY) because
- * NTFS_MNT_RDONLY is an enumerated constant. Also, the NTFS-3g headers don't
- * seem to contain any explicit version information. So we have to rely on a
- * test done at configure time to detect whether NTFS_MNT_RDONLY should be used.
- * */
+ /* NTFS-3g 2013 renamed the "read-only" mount flag from MS_RDONLY to
+ * NTFS_MNT_RDONLY.
+ *
+ * Unfortunately we can't check for defined(NTFS_MNT_RDONLY) because
+ * NTFS_MNT_RDONLY is an enumerated constant. Also, the NTFS-3g headers
+ * don't seem to contain any explicit version information. So we have
+ * to rely on a test done at configure time to detect whether
+ * NTFS_MNT_RDONLY should be used. */
#ifdef HAVE_NTFS_MNT_RDONLY
/* NTFS-3g 2013 */
vol = ntfs_mount(device, NTFS_MNT_RDONLY);
if (!vol) {
ERROR_WITH_ERRNO("Failed to mount NTFS volume `%s' read-only",
device);
+ FREE(volume);
return WIMLIB_ERR_NTFS_3G;
}
+
+ volume->vol = vol;
+ volume->refcnt = 1;
+
ntfs_open_secure(vol);
/* We don't want to capture the special NTFS files such as $Bitmap. Not
ERROR_WITH_ERRNO("Failed to open root inode of NTFS volume "
"`%s'", device);
ret = WIMLIB_ERR_NTFS_3G;
- goto out;
+ goto out_put_ntfs_volume;
}
/* Currently we assume that all the paths fit into this length and there
* is no check for overflow. */
- char *path = MALLOC(32768);
+ path = MALLOC(32768);
if (!path) {
- ERROR("Could not allocate memory for NTFS pathname");
ret = WIMLIB_ERR_NOMEM;
- goto out_cleanup;
+ goto out_close_root_ni;
}
path[0] = '/';
path[1] = '\0';
ret = build_dentry_tree_ntfs_recursive(root_p, root_ni, path, 1,
- FILE_NAME_POSIX, vol, params);
-out_cleanup:
+ FILE_NAME_POSIX, volume, params);
FREE(path);
+out_close_root_ni:
ntfs_inode_close(root_ni);
-out:
+out_put_ntfs_volume:
ntfs_index_ctx_put(vol->secure_xsii);
ntfs_index_ctx_put(vol->secure_xsdh);
ntfs_inode_close(vol->secure_ni);
-
- if (ret) {
- if (do_ntfs_umount(vol)) {
- ERROR_WITH_ERRNO("Failed to unmount NTFS volume `%s'",
- device);
- }
- } else {
- /* We need to leave the NTFS volume mounted so that we can read
- * the NTFS files again when we are actually writing the WIM */
- *(ntfs_volume**)params->extra_arg = vol;
- }
+ put_ntfs_volume(volume);
return ret;
}
#endif /* WITH_NTFS_3G */