4 * Apply a WIM image to a NTFS volume. Restore as much information as possible,
5 * including security data, file attributes, DOS names, and alternate data
10 * Copyright (C) 2012 Eric Biggers
12 * This file is part of wimlib, a library for working with WIM files.
14 * wimlib is free software; you can redistribute it and/or modify it under the
15 * terms of the GNU General Public License as published by the Free
16 * Software Foundation; either version 3 of the License, or (at your option)
19 * wimlib is distributed in the hope that it will be useful, but WITHOUT ANY
20 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
21 * A PARTICULAR PURPOSE. See the GNU General Public License for more
24 * You should have received a copy of the GNU General Public License
25 * along with wimlib; if not, see http://www.gnu.org/licenses/.
32 #include <ntfs-3g/endians.h>
33 #include <ntfs-3g/types.h>
35 #include "wimlib_internal.h"
37 #include "lookup_table.h"
38 #include "buffer_io.h"
39 #include <ntfs-3g/layout.h>
40 #include <ntfs-3g/acls.h>
41 #include <ntfs-3g/attrib.h>
42 #include <ntfs-3g/security.h> /* security.h before xattrs.h */
43 #include <ntfs-3g/xattrs.h>
44 #include <ntfs-3g/reparse.h>
48 static int extract_wim_chunk_to_ntfs_attr(const u8 *buf, size_t len,
49 u64 offset, void *arg)
52 if (ntfs_attr_pwrite(na, offset, len, buf) == len) {
55 ERROR_WITH_ERRNO("Error extracting WIM resource to NTFS attribute");
56 return WIMLIB_ERR_WRITE;
61 * Extracts a WIM resource to a NTFS attribute.
64 extract_wim_resource_to_ntfs_attr(const struct lookup_table_entry *lte,
67 return extract_wim_resource(lte, wim_resource_size(lte),
68 extract_wim_chunk_to_ntfs_attr, na);
71 /* Writes the data streams to a NTFS file
73 * @ni: The NTFS inode for the file.
74 * @inode: The WIM dentry that has an inode containing the streams.
76 * Returns 0 on success, nonzero on failure.
78 static int write_ntfs_data_streams(ntfs_inode *ni, const struct dentry *dentry,
79 union wimlib_progress_info *progress_info)
82 unsigned stream_idx = 0;
83 ntfschar *stream_name = AT_UNNAMED;
84 u32 stream_name_len = 0;
85 const struct inode *inode = dentry->d_inode;
87 DEBUG("Writing %u NTFS data stream%s for `%s'",
89 (inode->num_ads == 0 ? "" : "s"),
90 dentry->full_path_utf8);
93 struct lookup_table_entry *lte;
95 lte = inode_stream_lte_resolved(inode, stream_idx);
97 if (stream_name_len) {
98 /* Create an empty named stream. */
99 ret = ntfs_attr_add(ni, AT_DATA, stream_name,
100 stream_name_len, NULL, 0);
102 ERROR_WITH_ERRNO("Failed to create name data "
103 "stream for extracted file "
105 dentry->full_path_utf8);
106 ret = WIMLIB_ERR_NTFS_3G;
111 /* If there's no lookup table entry, it's an empty stream.
112 * Otherwise, we must open the attribute and extract the data.
117 na = ntfs_attr_open(ni, AT_DATA, stream_name, stream_name_len);
119 ERROR_WITH_ERRNO("Failed to open a data stream of "
120 "extracted file `%s'",
121 dentry->full_path_utf8);
122 ret = WIMLIB_ERR_NTFS_3G;
125 ret = ntfs_attr_truncate_solid(na, wim_resource_size(lte));
131 ret = extract_wim_resource_to_ntfs_attr(lte, na);
135 progress_info->extract.completed_bytes += wim_resource_size(lte);
137 if (stream_idx == inode->num_ads)
139 stream_name = (ntfschar*)inode->ads_entries[stream_idx].stream_name;
140 stream_name_len = inode->ads_entries[stream_idx].stream_name_len / 2;
146 /* Open the NTFS inode that corresponds to the parent of a WIM dentry. */
147 static ntfs_inode *dentry_open_parent_ni(const struct dentry *dentry,
151 const char *dir_name;
155 p = dentry->full_path_utf8 + dentry->full_path_utf8_len;
162 dir_name = dentry->full_path_utf8;
163 dir_ni = ntfs_pathname_to_inode(vol, NULL, dir_name);
165 ERROR_WITH_ERRNO("Could not find NTFS inode for `%s'",
173 * Makes a NTFS hard link
175 * It is named @from_dentry->file_name and is located under the directory
176 * specified by @dir_ni, and it is made to point to the previously extracted
177 * file located at @inode->extracted_file.
179 * Return 0 on success, nonzero on failure.
181 static int apply_ntfs_hardlink(const struct dentry *from_dentry,
182 const struct inode *inode,
183 ntfs_inode **dir_ni_p)
192 ret = ntfs_inode_close(dir_ni);
195 ERROR_WITH_ERRNO("Error closing directory");
196 return WIMLIB_ERR_NTFS_3G;
199 DEBUG("Extracting NTFS hard link `%s' => `%s'",
200 from_dentry->full_path_utf8, inode->extracted_file);
202 to_ni = ntfs_pathname_to_inode(vol, NULL, inode->extracted_file);
204 ERROR_WITH_ERRNO("Could not find NTFS inode for `%s'",
205 inode->extracted_file);
206 return WIMLIB_ERR_NTFS_3G;
209 dir_ni = dentry_open_parent_ni(from_dentry, vol);
211 ntfs_inode_close(to_ni);
212 return WIMLIB_ERR_NTFS_3G;
217 ret = ntfs_link(to_ni, dir_ni,
218 (ntfschar*)from_dentry->file_name,
219 from_dentry->file_name_len / 2);
220 if (ntfs_inode_close_in_dir(to_ni, dir_ni) || ret != 0) {
221 ERROR_WITH_ERRNO("Could not create hard link `%s' => `%s'",
222 from_dentry->full_path_utf8,
223 inode->extracted_file);
224 ret = WIMLIB_ERR_NTFS_3G;
230 apply_file_attributes_and_security_data(ntfs_inode *ni,
232 const struct dentry *dentry,
235 DEBUG("Setting NTFS file attributes on `%s' to %#"PRIx32,
236 dentry->full_path_utf8, dentry->d_inode->attributes);
238 struct SECURITY_CONTEXT ctx;
240 attributes_le32 = cpu_to_le32(dentry->d_inode->attributes);
241 memset(&ctx, 0, sizeof(ctx));
243 ret = ntfs_xattr_system_setxattr(&ctx, XATTR_NTFS_ATTRIB,
245 (const char*)&attributes_le32,
248 ERROR("Failed to set NTFS file attributes on `%s'",
249 dentry->full_path_utf8);
250 return WIMLIB_ERR_NTFS_3G;
252 if (dentry->d_inode->security_id != -1) {
253 const struct wim_security_data *sd;
254 const char *descriptor;
256 sd = wim_const_security_data(w);
257 wimlib_assert(dentry->d_inode->security_id < sd->num_entries);
258 descriptor = (const char *)sd->descriptors[dentry->d_inode->security_id];
259 DEBUG("Applying security descriptor %d to `%s'",
260 dentry->d_inode->security_id, dentry->full_path_utf8);
262 ret = ntfs_xattr_system_setxattr(&ctx, XATTR_NTFS_ACL,
263 ni, dir_ni, descriptor,
264 sd->sizes[dentry->d_inode->security_id], 0);
267 ERROR_WITH_ERRNO("Failed to set security data on `%s'",
268 dentry->full_path_utf8);
269 return WIMLIB_ERR_NTFS_3G;
275 static int apply_reparse_data(ntfs_inode *ni, const struct dentry *dentry,
276 union wimlib_progress_info *progress_info)
278 struct lookup_table_entry *lte;
281 lte = inode_unnamed_lte_resolved(dentry->d_inode);
283 DEBUG("Applying reparse data to `%s'", dentry->full_path_utf8);
286 ERROR("Could not find reparse data for `%s'",
287 dentry->full_path_utf8);
288 return WIMLIB_ERR_INVALID_DENTRY;
291 if (wim_resource_size(lte) >= 0xffff) {
292 ERROR("Reparse data of `%s' is too long (%"PRIu64" bytes)",
293 dentry->full_path_utf8, wim_resource_size(lte));
294 return WIMLIB_ERR_INVALID_DENTRY;
297 u8 reparse_data_buf[8 + wim_resource_size(lte)];
298 u8 *p = reparse_data_buf;
299 p = put_u32(p, dentry->d_inode->reparse_tag); /* ReparseTag */
300 p = put_u16(p, wim_resource_size(lte)); /* ReparseDataLength */
301 p = put_u16(p, 0); /* Reserved */
303 ret = read_full_wim_resource(lte, p, 0);
307 ret = ntfs_set_ntfs_reparse_data(ni, (char*)reparse_data_buf,
308 wim_resource_size(lte) + 8, 0);
310 ERROR_WITH_ERRNO("Failed to set NTFS reparse data on `%s'",
311 dentry->full_path_utf8);
312 return WIMLIB_ERR_NTFS_3G;
314 progress_info->extract.completed_bytes += wim_resource_size(lte);
318 static int do_apply_dentry_ntfs(struct dentry *dentry, ntfs_inode *dir_ni,
319 struct apply_args *args);
322 * If @dentry is part of a hard link group, search for hard-linked dentries in
323 * the same directory that have a nonempty DOS (short) filename. There should
324 * be exactly 0 or 1 such dentries. If there is 1, extract that dentry first,
325 * so that the DOS name is correctly associated with the corresponding long name
326 * in the Win32 namespace, and not any of the additional names in the POSIX
327 * namespace created from hard links.
329 static int preapply_dentry_with_dos_name(struct dentry *dentry,
330 ntfs_inode **dir_ni_p,
331 struct apply_args *args)
333 struct dentry *other;
334 struct dentry *dentry_with_dos_name;
336 dentry_with_dos_name = NULL;
337 inode_for_each_dentry(other, dentry->d_inode) {
338 if (other != dentry && (dentry->parent == other->parent)
339 && other->short_name_len)
341 if (dentry_with_dos_name) {
342 ERROR("Found multiple DOS names for file `%s' "
343 "in the same directory",
344 dentry_with_dos_name->full_path_utf8);
345 return WIMLIB_ERR_INVALID_DENTRY;
347 dentry_with_dos_name = other;
350 /* If there's a dentry with a DOS name, extract it first */
351 if (dentry_with_dos_name && !dentry_with_dos_name->is_extracted) {
353 const char *dir_name;
356 ntfs_volume *vol = (*dir_ni_p)->vol;
358 DEBUG("pre-applying DOS name `%s'",
359 dentry_with_dos_name->full_path_utf8);
360 ret = do_apply_dentry_ntfs(dentry_with_dos_name,
365 *dir_ni_p = dentry_open_parent_ni(dentry, vol);
367 return WIMLIB_ERR_NTFS_3G;
373 * Applies a WIM dentry to a NTFS filesystem.
375 * @dentry: The WIM dentry to apply
376 * @dir_ni: The NTFS inode for the parent directory
378 * @return: 0 on success; nonzero on failure.
380 static int do_apply_dentry_ntfs(struct dentry *dentry, ntfs_inode *dir_ni,
381 struct apply_args *args)
385 ntfs_inode *ni = NULL;
386 bool is_hardlink = false;
387 ntfs_volume *vol = dir_ni->vol;
388 struct inode *inode = dentry->d_inode;
389 dentry->is_extracted = 1;
391 if (inode->attributes & FILE_ATTRIBUTE_DIRECTORY) {
394 /* If this dentry is hard-linked to any other dentries in the
395 * same directory, make sure to apply the one (if any) with a
396 * DOS name first. Otherwise, NTFS-3g might not assign the file
397 * names correctly. */
398 if (dentry->short_name_len == 0) {
399 ret = preapply_dentry_with_dos_name(dentry,
407 if (inode->link_count > 1) {
408 /* Already extracted another dentry in the hard link
409 * group. Make a hard link instead of extracting the
411 if (inode->extracted_file) {
412 ret = apply_ntfs_hardlink(dentry, inode,
416 goto out_close_dir_ni;
418 goto out_set_dos_name;
420 /* Can't make a hard link; extract the file itself */
421 FREE(inode->extracted_file);
422 inode->extracted_file = STRDUP(dentry->full_path_utf8);
423 if (!inode->extracted_file) {
424 ret = WIMLIB_ERR_NOMEM;
425 goto out_close_dir_ni;
431 * Create a directory or file.
433 * Note: For symbolic links that are not directory junctions, pass
434 * S_IFREG here, since we manually set the reparse data later.
436 ni = ntfs_create(dir_ni, 0, (ntfschar*)dentry->file_name,
437 dentry->file_name_len / 2, type);
440 ERROR_WITH_ERRNO("Could not create NTFS object for `%s'",
441 dentry->full_path_utf8);
442 ret = WIMLIB_ERR_NTFS_3G;
443 goto out_close_dir_ni;
446 /* Write the data streams, unless this is a directory or reparse point
448 if (!(inode->attributes & (FILE_ATTRIBUTE_REPARSE_POINT |
449 FILE_ATTRIBUTE_DIRECTORY))) {
450 ret = write_ntfs_data_streams(ni, dentry, &args->progress);
452 goto out_close_dir_ni;
456 ret = apply_file_attributes_and_security_data(ni, dir_ni,
459 goto out_close_dir_ni;
461 if (inode->attributes & FILE_ATTR_REPARSE_POINT) {
462 ret = apply_reparse_data(ni, dentry, &args->progress);
464 goto out_close_dir_ni;
468 /* Set DOS (short) name if given */
469 if (dentry->short_name_len != 0) {
471 char *short_name_utf8;
472 size_t short_name_utf8_len;
473 ret = utf16_to_utf8(dentry->short_name,
474 dentry->short_name_len,
476 &short_name_utf8_len);
478 goto out_close_dir_ni;
481 wimlib_assert(ni == NULL);
482 ni = ntfs_pathname_to_inode(vol, dir_ni,
483 dentry->file_name_utf8);
485 ERROR_WITH_ERRNO("Could not find NTFS inode for `%s'",
486 dentry->full_path_utf8);
487 ret = WIMLIB_ERR_NTFS_3G;
488 goto out_close_dir_ni;
492 wimlib_assert(ni != NULL);
494 DEBUG("Setting short (DOS) name of `%s' to %s",
495 dentry->full_path_utf8, short_name_utf8);
497 ret = ntfs_set_ntfs_dos_name(ni, dir_ni, short_name_utf8,
498 short_name_utf8_len, 0);
499 FREE(short_name_utf8);
501 ERROR_WITH_ERRNO("Could not set DOS (short) name for `%s'",
502 dentry->full_path_utf8);
503 ret = WIMLIB_ERR_NTFS_3G;
505 /* inodes have been closed by ntfs_set_ntfs_dos_name(). */
512 if (ntfs_inode_close_in_dir(ni, dir_ni)) {
514 ret = WIMLIB_ERR_NTFS_3G;
515 ERROR_WITH_ERRNO("Failed to close inode for `%s'",
516 dentry->full_path_utf8);
519 if (ntfs_inode_close(dir_ni)) {
521 ret = WIMLIB_ERR_NTFS_3G;
522 ERROR_WITH_ERRNO("Failed to close directory inode");
525 wimlib_assert(ni == NULL);
530 static int apply_root_dentry_ntfs(const struct dentry *dentry,
531 ntfs_volume *vol, const WIMStruct *w)
536 wimlib_assert(dentry_is_directory(dentry));
537 ni = ntfs_pathname_to_inode(vol, NULL, "/");
539 ERROR_WITH_ERRNO("Could not find root NTFS inode");
540 return WIMLIB_ERR_NTFS_3G;
542 ret = apply_file_attributes_and_security_data(ni, ni, dentry, w);
543 if (ntfs_inode_close(ni) != 0) {
544 ERROR_WITH_ERRNO("Failed to close NTFS inode for root "
546 ret = WIMLIB_ERR_NTFS_3G;
551 /* Applies a WIM dentry to the NTFS volume */
552 int apply_dentry_ntfs(struct dentry *dentry, void *arg)
554 struct apply_args *args = arg;
555 ntfs_volume *vol = args->vol;
556 WIMStruct *w = args->w;
559 if (dentry_is_root(dentry))
560 return apply_root_dentry_ntfs(dentry, vol, w);
562 dir_ni = dentry_open_parent_ni(dentry, vol);
564 return do_apply_dentry_ntfs(dentry, dir_ni, arg);
566 return WIMLIB_ERR_NTFS_3G;
569 int apply_dentry_timestamps_ntfs(struct dentry *dentry, void *arg)
571 struct apply_args *args = arg;
572 ntfs_volume *vol = args->vol;
578 DEBUG("Setting timestamps on `%s'", dentry->full_path_utf8);
580 ni = ntfs_pathname_to_inode(vol, NULL, dentry->full_path_utf8);
582 ERROR_WITH_ERRNO("Could not find NTFS inode for `%s'",
583 dentry->full_path_utf8);
584 return WIMLIB_ERR_NTFS_3G;
588 p = put_u64(p, dentry->d_inode->creation_time);
589 p = put_u64(p, dentry->d_inode->last_write_time);
590 p = put_u64(p, dentry->d_inode->last_access_time);
591 ret = ntfs_inode_set_times(ni, (const char*)buf, 3 * sizeof(u64), 0);
593 ERROR_WITH_ERRNO("Failed to set NTFS timestamps on `%s'",
594 dentry->full_path_utf8);
595 ret = WIMLIB_ERR_NTFS_3G;
598 if (ntfs_inode_close(ni) != 0) {
600 ret = WIMLIB_ERR_NTFS_3G;
601 ERROR_WITH_ERRNO("Failed to close NTFS inode for `%s'",
602 dentry->full_path_utf8);