]> wimlib.net Git - wimlib/blob - src/ntfs-capture.c
0a27353ca614bc87951807e0ee1b6a259ec2c3ca
[wimlib] / src / ntfs-capture.c
1 /*
2  * ntfs-capture.c
3  *
4  * Capture a WIM image from a NTFS volume.  We capture everything we can,
5  * including security data and alternate data streams.  There should be no loss
6  * of information.
7  */
8
9 /*
10  * Copyright (C) 2012 Eric Biggers
11  *
12  * This file is part of wimlib, a library for working with WIM files.
13  *
14  * wimlib is free software; you can redistribute it and/or modify it under the
15  * terms of the GNU Lesser General Public License as published by the Free
16  * Software Foundation; either version 2.1 of the License, or (at your option)
17  * any later version.
18  *
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 Lesser General Public License for more
22  * details.
23  *
24  * You should have received a copy of the GNU Lesser General Public License
25  * along with wimlib; if not, see http://www.gnu.org/licenses/.
26  */
27
28 #include "config.h"
29 #include "wimlib_internal.h"
30
31
32 #ifdef WITH_NTFS_3G
33 #include "dentry.h"
34 #include "lookup_table.h"
35 #include "io.h"
36 #include <ntfs-3g/layout.h>
37 #include <ntfs-3g/acls.h>
38 #include <ntfs-3g/attrib.h>
39 #include <ntfs-3g/misc.h>
40 #include <ntfs-3g/reparse.h>
41 #include <ntfs-3g/security.h>
42 #include <ntfs-3g/volume.h>
43 #include <stdlib.h>
44 #include <unistd.h>
45
46 extern int ntfs_inode_get_security(ntfs_inode *ni, u32 selection, char *buf,
47                                    u32 buflen, u32 *psize);
48
49 extern int ntfs_inode_get_attributes(ntfs_inode *ni);
50
51 struct sd_tree {
52         u32 num_sds;
53         struct wim_security_data *sd;
54         struct sd_node *root;
55 };
56
57 struct sd_node {
58         int security_id;
59         u8 hash[SHA1_HASH_SIZE];
60         struct sd_node *left;
61         struct sd_node *right;
62 };
63
64 static void free_sd_tree(struct sd_node *root)
65 {
66         if (root) {
67                 free_sd_tree(root->left);
68                 free_sd_tree(root->right);
69                 FREE(root);
70         }
71 }
72
73 static void insert_sd_node(struct sd_node *new, struct sd_node *root)
74 {
75         int cmp = hashes_cmp(root->hash, new->hash);
76         if (cmp < 0) {
77                 if (root->left)
78                         insert_sd_node(new, root->left);
79                 else 
80                         root->left = new;
81         } else if (cmp > 0) {
82                 if (root->right)
83                         insert_sd_node(new, root->right);
84                 else 
85                         root->right = new;
86         } else {
87                 wimlib_assert(0);
88         }
89 }
90
91 static int lookup_sd(const u8 hash[SHA1_HASH_SIZE], struct sd_node *node)
92 {
93         int cmp;
94         if (!node)
95                 return -1;
96         cmp = hashes_cmp(hash, node->hash);
97         if (cmp < 0)
98                 return lookup_sd(hash, node->left);
99         else if (cmp > 0)
100                 return lookup_sd(hash, node->right);
101         else
102                 return node->security_id;
103 }
104
105 static int tree_add_sd(struct sd_tree *tree, const u8 *descriptor,
106                        size_t size)
107 {
108         u8 hash[SHA1_HASH_SIZE];
109         int security_id;
110         struct sd_node *new;
111         u8 **descriptors;
112         u64 *sizes;
113         u8 *descr_copy;
114         struct wim_security_data *sd = tree->sd;
115         sha1_buffer(descriptor, size, hash);
116
117         security_id = lookup_sd(hash, tree->root);
118         if (security_id >= 0)
119                 return security_id;
120
121         new = MALLOC(sizeof(struct sd_node));
122         if (!new)
123                 return -1;
124         descr_copy = MALLOC(size);
125         if (!descr_copy)
126                 goto out_free_node;
127         memcpy(descr_copy, descriptor, size);
128         new->security_id = tree->num_sds++;
129         new->left = NULL;
130         new->right = NULL;
131         copy_hash(new->hash, hash);
132
133         descriptors = REALLOC(sd->descriptors,
134                               (sd->num_entries + 1) * sizeof(sd->descriptors[0]));
135         if (!descriptors)
136                 goto out_free_descr;
137         sd->descriptors = descriptors;
138         sizes = REALLOC(sd->sizes,
139                         (sd->num_entries + 1) * sizeof(sd->sizes[0]));
140         if (!sizes)
141                 goto out_free_descr;
142         sd->sizes = sizes;
143         sd->descriptors[sd->num_entries] = descr_copy;
144         sd->sizes[sd->num_entries] = size;
145         sd->num_entries++;
146         sd->total_length += size + 8;
147
148         if (tree->root)
149                 insert_sd_node(tree->root, new);
150         else
151                 tree->root = new;
152         return new->security_id;
153 out_free_descr:
154         FREE(descr_copy);
155 out_free_node:
156         FREE(new);
157         return -1;
158 }
159
160 #if 0
161 static int build_sd_tree(struct wim_security_data *sd, struct sd_tree *tree)
162 {
163         int ret;
164         u32 orig_num_entries = sd->num_entries;
165         u32 orig_total_length = sd->total_length;
166
167         tree->num_sds = 0;
168         tree->sd = sd;
169         tree->root = NULL;
170
171         for (u32 i = 0; i < sd->num_entries; i++) {
172                 ret = tree_add_sd(tree, sd->descriptors[i], sd->sizes[i]);
173                 if (ret < 0)
174                         goto out_revert;
175         }
176         return 0;
177 out_revert:
178         sd->num_entries = orig_num_entries;
179         sd->total_length = orig_total_length;
180         free_sd_tree(tree->root);
181         return ret;
182 }
183 #endif
184
185 static int ntfs_attr_sha1sum(ntfs_inode *ni, ATTR_RECORD *ar,
186                              u8 md[SHA1_HASH_SIZE])
187 {
188         s64 pos = 0;
189         s64 bytes_remaining;
190         char buf[4096];
191         ntfs_attr *na;
192         SHA_CTX ctx;
193
194         na = ntfs_attr_open(ni, ar->type,
195                             (ntfschar*)((u8*)ar + le16_to_cpu(ar->name_offset)),
196                             ar->name_length);
197         if (!na) {
198                 ERROR_WITH_ERRNO("Failed to open NTFS attribute");
199                 return WIMLIB_ERR_NTFS_3G;
200         }
201
202         bytes_remaining = na->data_size;
203         sha1_init(&ctx);
204
205         while (bytes_remaining) {
206                 s64 to_read = min(bytes_remaining, sizeof(buf));
207                 if (ntfs_attr_pread(na, pos, to_read, buf) != to_read) {
208                         ERROR_WITH_ERRNO("Error reading NTFS attribute");
209                         return WIMLIB_ERR_NTFS_3G;
210                 }
211                 sha1_update(&ctx, buf, to_read);
212                 pos += to_read;
213                 bytes_remaining -= to_read;
214         }
215         sha1_final(md, &ctx);
216         ntfs_attr_close(na);
217         return 0;
218 }
219
220 /* Load the streams from a WIM file or reparse point in the NTFS volume into the
221  * WIM lookup table */
222 static int capture_ntfs_streams(struct dentry *dentry, ntfs_inode *ni,
223                                 char path[], size_t path_len,
224                                 struct lookup_table *lookup_table,
225                                 ntfs_volume **ntfs_vol_p,
226                                 ATTR_TYPES type)
227 {
228
229         ntfs_attr_search_ctx *actx;
230         u8 attr_hash[SHA1_HASH_SIZE];
231         struct ntfs_location *ntfs_loc;
232         struct lookup_table_entry *lte;
233         int ret = 0;
234
235         /* Get context to search the streams of the NTFS file. */
236         actx = ntfs_attr_get_search_ctx(ni, NULL);
237         if (!actx) {
238                 ERROR_WITH_ERRNO("Cannot get attribute search "
239                                  "context");
240                 return WIMLIB_ERR_NTFS_3G;
241         }
242
243         /* Capture each data stream or reparse data stream. */
244         while (!ntfs_attr_lookup(type, NULL, 0,
245                                  CASE_SENSITIVE, 0, NULL, 0, actx))
246         {
247                 char *stream_name_utf8;
248                 size_t stream_name_utf16_len;
249
250                 /* Checksum the stream. */
251                 ret = ntfs_attr_sha1sum(ni, actx->attr, attr_hash);
252                 if (ret != 0)
253                         goto out_put_actx;
254
255                 /* Make a lookup table entry for the stream, or use an existing
256                  * one if there's already an identical stream. */
257                 lte = __lookup_resource(lookup_table, attr_hash);
258                 ret = WIMLIB_ERR_NOMEM;
259                 if (lte) {
260                         lte->refcnt++;
261                 } else {
262                         struct ntfs_location *ntfs_loc;
263
264
265                         ntfs_loc = CALLOC(1, sizeof(*ntfs_loc));
266                         if (!ntfs_loc) {
267                                 goto out_put_actx;
268                         }
269                         ntfs_loc->ntfs_vol_p = ntfs_vol_p;
270                         ntfs_loc->path_utf8 = MALLOC(path_len + 1);
271                         if (!ntfs_loc->path_utf8)
272                                 goto out_put_actx;
273                         memcpy(ntfs_loc->path_utf8, path, path_len + 1);
274                         ntfs_loc->stream_name_utf16 = MALLOC(actx->attr->name_length * 2);
275                         if (!ntfs_loc->stream_name_utf16)
276                                 goto out_free_ntfs_loc;
277                         memcpy(ntfs_loc->stream_name_utf16,
278                                (u8*)actx->attr +
279                                         le16_to_cpu(actx->attr->name_offset),
280                                actx->attr->name_length * 2);
281
282                         ntfs_loc->stream_name_utf16_num_chars = actx->attr->name_length;
283                         lte = new_lookup_table_entry();
284                         if (!lte)
285                                 goto out_free_ntfs_loc;
286                         lte->ntfs_loc = ntfs_loc;
287                         lte->resource_location = RESOURCE_IN_NTFS_VOLUME;
288                         lte->resource_entry.original_size = actx->attr->data_size;
289                         lte->resource_entry.size = actx->attr->data_size;
290                         copy_hash(lte->hash, attr_hash);
291                         lookup_table_insert(lookup_table, lte);
292                 }
293                 if (actx->attr->name_length == 0) {
294                         wimlib_assert(!dentry->lte);
295                         dentry->lte = lte;
296                 } else {
297                         struct ads_entry *new_ads_entry;
298                         stream_name_utf8 = utf16_to_utf8((u8*)actx->attr +
299                                                          le16_to_cpu(actx->attr->name_offset),
300                                                          actx->attr->name_length,
301                                                          &stream_name_utf16_len);
302                         if (!stream_name_utf8)
303                                 goto out_free_lte;
304                         FREE(stream_name_utf8);
305                         new_ads_entry = dentry_add_ads(dentry, stream_name_utf8);
306                         if (!new_ads_entry)
307                                 goto out_free_lte;
308                                 
309                         new_ads_entry->lte = lte;
310                 }
311         }
312         ret = 0;
313         goto out_put_actx;
314 out_free_lte:
315         free_lookup_table_entry(lte);
316 out_free_ntfs_loc:
317         if (ntfs_loc) {
318                 FREE(ntfs_loc->path_utf8);
319                 FREE(ntfs_loc->stream_name_utf16);
320                 FREE(ntfs_loc);
321         }
322 out_put_actx:
323         ntfs_attr_put_search_ctx(actx);
324         return ret;
325 }
326
327 struct readdir_ctx {
328         struct dentry       *dentry;
329         ntfs_inode          *dir_ni;
330         char                *path;
331         size_t               path_len;
332         struct lookup_table *lookup_table;
333         struct sd_tree      *tree;
334         ntfs_volume        **ntfs_vol_p;
335 };
336
337 static int __build_dentry_tree_ntfs(struct dentry *dentry, ntfs_inode *ni,
338                                     char path[], size_t path_len,
339                                     struct lookup_table *lookup_table,
340                                     struct sd_tree *tree,
341                                     ntfs_volume **ntfs_vol_p);
342
343
344 static int filldir(void *dirent, const ntfschar *name,
345                    const int name_len, const int name_type, const s64 pos,
346                    const MFT_REF mref, const unsigned dt_type)
347 {
348         struct readdir_ctx *ctx;
349         size_t utf8_name_len;
350         char *utf8_name;
351         struct dentry *child;
352         int ret;
353         size_t path_len;
354
355         ret = -1;
356
357         utf8_name = utf16_to_utf8((const u8*)name, name_len * 2,
358                                   &utf8_name_len);
359         if (!utf8_name)
360                 goto out;
361
362         ctx = dirent;
363
364         ntfs_inode *ni = ntfs_inode_open(ctx->dir_ni->vol, mref);
365         if (!ni) {
366                 ERROR_WITH_ERRNO("Failed to open NTFS inode");
367                 ret = 1;
368         }
369         child = new_dentry(utf8_name);
370         if (!child)
371                 goto out_close_ni;
372
373         memcpy(ctx->path + ctx->path_len, utf8_name, utf8_name_len + 1);
374         path_len = ctx->path_len + utf8_name_len;
375         ret = __build_dentry_tree_ntfs(child, ni, ctx->path, path_len,
376                                        ctx->lookup_table, ctx->tree,
377                                        ctx->ntfs_vol_p);
378         link_dentry(child, ctx->dentry);
379 out_close_ni:
380         ntfs_inode_close(ni);
381 out_free_utf8_name:
382         FREE(utf8_name);
383 out:
384         return ret;
385 }
386
387 /* Recursively build a WIM dentry tree corresponding to a NTFS volume.
388  * At the same time, update the WIM lookup table with lookup table entries for
389  * the NTFS streams, and build an array of security descriptors.
390  */
391 static int __build_dentry_tree_ntfs(struct dentry *dentry, ntfs_inode *ni,
392                                     char path[], size_t path_len,
393                                     struct lookup_table *lookup_table,
394                                     struct sd_tree *tree,
395                                     ntfs_volume **ntfs_vol_p)
396 {
397         u32 attributes = ntfs_inode_get_attributes(ni);
398         int mrec_flags = ni->mrec->flags;
399         u32 sd_size;
400         int ret = 0;
401
402         dentry->creation_time    = le64_to_cpu(ni->creation_time);
403         dentry->last_write_time  = le64_to_cpu(ni->last_data_change_time);
404         dentry->last_access_time = le64_to_cpu(ni->last_access_time);
405         dentry->security_id      = le32_to_cpu(ni->security_id);
406         dentry->attributes       = le32_to_cpu(attributes);
407         dentry->hard_link        = ni->mft_no;
408         dentry->resolved = true;
409
410         if (attributes & FILE_ATTR_REPARSE_POINT) {
411                 /* Junction point, symbolic link, or other reparse point */
412                 ret = capture_ntfs_streams(dentry, ni, path, path_len,
413                                            lookup_table, ntfs_vol_p,
414                                            AT_REPARSE_POINT);
415         } else if (mrec_flags & MFT_RECORD_IS_DIRECTORY) {
416                 /* Normal directory */
417                 s64 pos = 0;
418                 struct readdir_ctx ctx = {
419                         .dentry       = dentry,
420                         .dir_ni       = ni,
421                         .path         = path,
422                         .path_len     = path_len,
423                         .lookup_table = lookup_table,
424                         .tree         = tree,
425                         .ntfs_vol_p   = ntfs_vol_p,
426                 };
427                 ret = ntfs_readdir(ni, &pos, &ctx, filldir);
428                 if (ret != 0)
429                         ret = WIMLIB_ERR_NTFS_3G;
430         } else {
431                 /* Normal file */
432                 ret = capture_ntfs_streams(dentry, ni, path, path_len,
433                                            lookup_table, ntfs_vol_p,
434                                            AT_DATA);
435         }
436         if (ret != 0)
437                 return ret;
438         ret = ntfs_inode_get_security(ni,
439                                       OWNER_SECURITY_INFORMATION |
440                                       GROUP_SECURITY_INFORMATION |
441                                       DACL_SECURITY_INFORMATION  |
442                                       SACL_SECURITY_INFORMATION,
443                                       NULL, 0, &sd_size);
444         u8 sd[sd_size];
445         ret = ntfs_inode_get_security(ni,
446                                       OWNER_SECURITY_INFORMATION |
447                                       GROUP_SECURITY_INFORMATION |
448                                       DACL_SECURITY_INFORMATION  |
449                                       SACL_SECURITY_INFORMATION,
450                                       sd, sd_size, &sd_size);
451         dentry->security_id = tree_add_sd(tree, sd, sd_size);
452         return 0;
453 }
454
455 static int build_dentry_tree_ntfs(struct dentry *root_dentry,
456                                   const char *device,
457                                   struct lookup_table *lookup_table,
458                                   struct wim_security_data *sd,
459                                   int flags,
460                                   void *extra_arg)
461 {
462         ntfs_volume *vol;
463         ntfs_inode *root_ni;
464         int ret = 0;
465         struct sd_tree tree;
466         tree.sd = sd;
467         tree.root = NULL;
468         ntfs_volume **ntfs_vol_p = extra_arg;
469         
470         vol = ntfs_mount(device, MS_RDONLY);
471         if (!vol) {
472                 ERROR_WITH_ERRNO("Failed to mount NTFS volume `%s' read-only",
473                                  device);
474                 return WIMLIB_ERR_NTFS_3G;
475         }
476         root_ni = ntfs_inode_open(vol, FILE_root);
477         if (!root_ni) {
478                 ERROR_WITH_ERRNO("Failed to open root inode of NTFS volume "
479                                  "`%s'", device);
480                 ret = WIMLIB_ERR_NTFS_3G;
481                 goto out;
482         }
483         char path[4096];
484         path[0] = '/';
485         path[1] = '\0';
486         ret = __build_dentry_tree_ntfs(root_dentry, root_ni, path, 1,
487                                        lookup_table, &tree, ntfs_vol_p);
488         ntfs_inode_close(root_ni);
489
490 out:
491         if (ntfs_umount(vol, FALSE) != 0) {
492                 ERROR_WITH_ERRNO("Failed to unmount NTFS volume `%s'", device);
493                 if (ret == 0)
494                         ret = WIMLIB_ERR_NTFS_3G;
495         }
496         return ret;
497 }
498
499 WIMLIBAPI int wimlib_add_image_from_ntfs_volume(WIMStruct *w,
500                                                 const char *device,
501                                                 const char *name,
502                                                 const char *description,
503                                                 const char *flags_element,
504                                                 int flags)
505 {
506         if (flags & (WIMLIB_ADD_IMAGE_FLAG_DEREFERENCE)) {
507                 ERROR("Cannot dereference files when capturing directly from NTFS");
508                 return WIMLIB_ERR_INVALID_PARAM;
509         }
510         return do_add_image(w, device, name, description, flags_element, flags,
511                             build_dentry_tree_ntfs,
512                             &w->ntfs_vol);
513 }
514
515 #else /* WITH_NTFS_3G */
516 WIMLIBAPI int wimlib_add_image_from_ntfs_volume(WIMStruct *w,
517                                                 const char *device,
518                                                 const char *name,
519                                                 const char *description,
520                                                 const char *flags_element,
521                                                 int flags)
522 {
523         ERROR("wimlib was compiled without support for NTFS-3g, so");
524         ERROR("we cannot capture a WIM image directly from a NTFS volume");
525         return WIMLIB_ERR_UNSUPPORTED;
526 }
527 #endif /* WITH_NTFS_3G */