fe2f24e23252bcaa3f2914e104d6ac5b8fe105f4
[wimlib] / src / ntfs-3g_apply.c
1 /*
2  * ntfs-3g_apply.c
3  *
4  * Apply a WIM image directly to a NTFS volume using libntfs-3g.  Restore as
5  * much information as possible, including security data, file attributes, DOS
6  * names, and alternate data streams.
7  */
8
9 /*
10  * Copyright (C) 2012, 2013 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 General Public License as published by the Free
16  * Software Foundation; either version 3 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 General Public License for more
22  * details.
23  *
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/.
26  */
27
28 #ifdef HAVE_CONFIG_H
29 #  include "config.h"
30 #endif
31
32 #ifdef WITH_NTFS_3G
33
34 #include <locale.h>
35 #include <string.h>
36 #include <time.h> /* NTFS-3g headers are missing <time.h> include */
37
38 #include <ntfs-3g/attrib.h>
39 #include <ntfs-3g/reparse.h>
40 #include <ntfs-3g/security.h>
41 #include <errno.h>
42
43 #ifdef HAVE_ALLOCA_H
44 #  include <alloca.h>
45 #else
46 #  include <stdlib.h>
47 #endif
48
49 #include "wimlib/apply.h"
50 #include "wimlib/encoding.h"
51 #include "wimlib/error.h"
52 #include "wimlib/lookup_table.h"
53 #include "wimlib/ntfs_3g.h"
54 #include "wimlib/paths.h"
55 #include "wimlib/resource.h"
56
57 static ntfs_volume *
58 ntfs_3g_apply_ctx_get_volume(struct apply_ctx *ctx)
59 {
60         return (ntfs_volume*)ctx->private[0];
61 }
62
63 static void
64 ntfs_3g_apply_ctx_set_volume(struct apply_ctx *ctx, ntfs_volume *vol)
65 {
66         ctx->private[0] = (intptr_t)vol;
67 }
68
69 static ntfs_inode *
70 ntfs_3g_apply_pathname_to_inode(const char *path, struct apply_ctx *ctx)
71 {
72         ntfs_volume *vol = ntfs_3g_apply_ctx_get_volume(ctx);
73         return ntfs_pathname_to_inode(vol, NULL, path);
74 }
75
76 struct ntfs_attr_extract_ctx {
77         u64 offset;
78         ntfs_attr *na;
79 };
80
81 static int
82 ntfs_3g_extract_wim_chunk(const void *buf, size_t len, void *_ctx)
83 {
84         struct ntfs_attr_extract_ctx *ctx = _ctx;
85
86         if (ntfs_attr_pwrite(ctx->na, ctx->offset, len, buf) != len)
87                 return WIMLIB_ERR_WRITE;
88         ctx->offset += len;
89         return 0;
90 }
91
92 static ntfs_inode *
93 ntfs_3g_open_parent_inode(const char *path, ntfs_volume *vol)
94 {
95         char *p;
96         ntfs_inode *dir_ni;
97
98         p = strrchr(path, '/');
99         *p = '\0';
100         dir_ni = ntfs_pathname_to_inode(vol, NULL, path);
101         *p = '/';
102         return dir_ni;
103 }
104
105 static int
106 ntfs_3g_create(const char *path, struct apply_ctx *ctx, mode_t mode)
107 {
108         ntfs_volume *vol;
109         ntfs_inode *dir_ni, *ni;
110         const char *name;
111         utf16lechar *name_utf16le;
112         size_t name_utf16le_nbytes;
113         int ret;
114
115         vol = ntfs_3g_apply_ctx_get_volume(ctx);
116
117         ret = WIMLIB_ERR_NTFS_3G;
118         dir_ni = ntfs_3g_open_parent_inode(path, vol);
119         if (!dir_ni)
120                 goto out;
121
122         name = path_basename(path);
123         ret = tstr_to_utf16le(name, strlen(name),
124                               &name_utf16le, &name_utf16le_nbytes);
125         if (ret)
126                 goto out_close_dir_ni;
127
128         ret = 0;
129         ni = ntfs_create(dir_ni, 0, name_utf16le,
130                          name_utf16le_nbytes / 2, mode);
131         if (!ni || ntfs_inode_close_in_dir(ni, dir_ni))
132                 ret = WIMLIB_ERR_NTFS_3G;
133         FREE(name_utf16le);
134 out_close_dir_ni:
135         if (ntfs_inode_close(dir_ni))
136                 ret = WIMLIB_ERR_NTFS_3G;
137 out:
138         return ret;
139 }
140
141 static int
142 ntfs_3g_create_file(const char *path, struct apply_ctx *ctx)
143 {
144         return ntfs_3g_create(path, ctx, S_IFREG);
145 }
146
147 static int
148 ntfs_3g_create_directory(const char *path, struct apply_ctx *ctx)
149 {
150         return ntfs_3g_create(path, ctx, S_IFDIR);
151 }
152
153 static int
154 ntfs_3g_create_hardlink(const char *oldpath, const char *newpath,
155                         struct apply_ctx *ctx)
156 {
157         ntfs_volume *vol;
158         ntfs_inode *dir_ni, *ni;
159         const char *name;
160         utf16lechar *name_utf16le;
161         size_t name_utf16le_nbytes;
162         int ret;
163
164         vol = ntfs_3g_apply_ctx_get_volume(ctx);
165
166         ret = WIMLIB_ERR_NTFS_3G;
167         ni = ntfs_pathname_to_inode(vol, NULL, oldpath);
168         if (!ni)
169                 goto out;
170
171         ret = WIMLIB_ERR_NTFS_3G;
172         dir_ni = ntfs_3g_open_parent_inode(newpath, vol);
173         if (!dir_ni)
174                 goto out_close_ni;
175
176         name = path_basename(newpath);
177         ret = tstr_to_utf16le(name, strlen(name),
178                               &name_utf16le, &name_utf16le_nbytes);
179         if (ret)
180                 goto out_close_dir_ni;
181         ret = 0;
182         if (ntfs_link(ni, dir_ni, name_utf16le, name_utf16le_nbytes / 2))
183                 ret = WIMLIB_ERR_NTFS_3G;
184         FREE(name_utf16le);
185 out_close_dir_ni:
186         if (ntfs_inode_close(dir_ni))
187                 ret = WIMLIB_ERR_NTFS_3G;
188 out_close_ni:
189         if (ntfs_inode_close(ni))
190                 ret = WIMLIB_ERR_NTFS_3G;
191 out:
192         return ret;
193 }
194
195 /*
196  * Extract a stream (default or alternate data) to an attribute of a NTFS file.
197  */
198 static int
199 ntfs_3g_extract_stream(const char *path, const utf16lechar *raw_stream_name,
200                        size_t stream_name_nchars,
201                        struct wim_lookup_table_entry *lte, struct apply_ctx *ctx)
202 {
203         ntfs_inode *ni;
204         ntfs_attr *na;
205         int ret;
206         struct ntfs_attr_extract_ctx extract_ctx;
207         utf16lechar *stream_name;
208
209         if (stream_name_nchars == 0) {
210                 stream_name = AT_UNNAMED;
211         } else {
212                 stream_name = alloca((stream_name_nchars + 1) * sizeof(utf16lechar));
213                 memcpy(stream_name, raw_stream_name,
214                        stream_name_nchars * sizeof(utf16lechar));
215                 stream_name[stream_name_nchars] = 0;
216         }
217
218         ret = 0;
219         if (!stream_name_nchars && !lte)
220                 goto out;
221
222         /* Look up NTFS inode to which to extract the stream.  */
223         ret = WIMLIB_ERR_NTFS_3G;
224         ni = ntfs_3g_apply_pathname_to_inode(path, ctx);
225         if (!ni)
226                 goto out;
227
228         /* Add the stream if it's not the default (unnamed) stream.  */
229         ret = WIMLIB_ERR_NTFS_3G;
230         if (stream_name_nchars)
231                 if (ntfs_attr_add(ni, AT_DATA, stream_name,
232                                   stream_name_nchars, NULL, 0))
233                         goto out_close;
234
235         /* If stream is empty, no need to open and extract it.  */
236         ret = 0;
237         if (!lte)
238                 goto out_close;
239
240         /* Open the stream (NTFS attribute).  */
241         ret = WIMLIB_ERR_NTFS_3G;
242         na = ntfs_attr_open(ni, AT_DATA, stream_name, stream_name_nchars);
243         if (!na)
244                 goto out_close;
245
246         /* (Optional) Immediately resize attribute to size of stream.  */
247         ret = WIMLIB_ERR_NTFS_3G;
248         if (ntfs_attr_truncate_solid(na, wim_resource_size(lte)))
249                 goto out_attr_close;
250
251         /* Extract stream data to the NTFS attribute.  */
252         extract_ctx.na = na;
253         extract_ctx.offset = 0;
254         ret = extract_wim_resource(lte, wim_resource_size(lte),
255                                    ntfs_3g_extract_wim_chunk, &extract_ctx);
256         /* Clean up and return.  */
257 out_attr_close:
258         ntfs_attr_close(na);
259 out_close:
260         if (ntfs_inode_close(ni))
261                 ret = WIMLIB_ERR_NTFS_3G;
262 out:
263         if (ret && !errno)
264                 errno = -1;
265         return ret;
266 }
267
268 static int
269 ntfs_3g_extract_unnamed_stream(const char *path,
270                                struct wim_lookup_table_entry *lte,
271                                struct apply_ctx *ctx)
272 {
273         return ntfs_3g_extract_stream(path, NULL, 0, lte, ctx);
274 }
275
276 static int
277 ntfs_3g_extract_named_stream(const char *path, const utf16lechar *stream_name,
278                              size_t stream_name_nchars,
279                              struct wim_lookup_table_entry *lte, struct apply_ctx *ctx)
280 {
281         return ntfs_3g_extract_stream(path, stream_name,
282                                       stream_name_nchars, lte, ctx);
283 }
284
285 static int
286 ntfs_3g_set_file_attributes(const char *path, u32 attributes,
287                             struct apply_ctx *ctx)
288 {
289         ntfs_inode *ni;
290         int ret = 0;
291
292         ni = ntfs_3g_apply_pathname_to_inode(path, ctx);
293         if (!ni)
294                 return WIMLIB_ERR_NTFS_3G;
295         if (ntfs_set_ntfs_attrib(ni, (const char*)&attributes, sizeof(u32), 0))
296                 ret = WIMLIB_ERR_NTFS_3G;
297         if (ntfs_inode_close(ni))
298                 ret = WIMLIB_ERR_NTFS_3G;
299         return ret;
300 }
301
302 static int
303 ntfs_3g_set_reparse_data(const char *path, const u8 *rpbuf, u16 rpbuflen,
304                          struct apply_ctx *ctx)
305 {
306         ntfs_inode *ni;
307         int ret = 0;
308
309         ni = ntfs_3g_apply_pathname_to_inode(path, ctx);
310         if (!ni)
311                 return WIMLIB_ERR_NTFS_3G;
312         if (ntfs_set_ntfs_reparse_data(ni, rpbuf, rpbuflen, 0))
313                 ret = WIMLIB_ERR_NTFS_3G;
314         if (ntfs_inode_close(ni))
315                 ret = WIMLIB_ERR_NTFS_3G;
316         return ret;
317 }
318
319 static int
320 ntfs_3g_set_short_name(const char *path, const utf16lechar *short_name,
321                        size_t short_name_nchars, struct apply_ctx *ctx)
322 {
323         ntfs_inode *ni, *dir_ni;
324         ntfs_volume *vol;
325         int ret;
326         char *dosname = NULL;
327         size_t dosname_nbytes;
328
329         ret = 0;
330         if (short_name_nchars == 0)
331                 goto out;
332
333         vol = ntfs_3g_apply_ctx_get_volume(ctx);
334
335         ret = WIMLIB_ERR_NTFS_3G;
336         dir_ni = ntfs_3g_open_parent_inode(path, vol);
337         if (!dir_ni)
338                 goto out;
339
340         ret = WIMLIB_ERR_NTFS_3G;
341         ni = ntfs_pathname_to_inode(vol, NULL, path);
342         if (!ni)
343                 goto out_close_dir_ni;
344
345         ret = utf16le_to_tstr(short_name, short_name_nchars * 2,
346                               &dosname, &dosname_nbytes);
347         if (ret)
348                 goto out_close_ni;
349
350         ret = 0;
351         if (ntfs_set_ntfs_dos_name(ni, dir_ni, dosname,
352                                    dosname_nbytes, 0))
353                 ret = WIMLIB_ERR_NTFS_3G;
354         /* ntfs_set_ntfs_dos_name() always closes the inodes.  */
355         FREE(dosname);
356         goto out;
357 out_close_ni:
358         if (ntfs_inode_close_in_dir(ni, dir_ni))
359                 ret = WIMLIB_ERR_NTFS_3G;
360 out_close_dir_ni:
361         if (ntfs_inode_close(dir_ni))
362                 ret = WIMLIB_ERR_NTFS_3G;
363 out:
364         return ret;
365 }
366
367 static int
368 ntfs_3g_set_security_descriptor(const char *path, const u8 *desc, size_t desc_size,
369                                 struct apply_ctx *ctx)
370 {
371         ntfs_volume *vol;
372         ntfs_inode *ni;
373         struct SECURITY_CONTEXT sec_ctx;
374         int ret = 0;
375
376         vol = ntfs_3g_apply_ctx_get_volume(ctx);
377
378         ni = ntfs_pathname_to_inode(vol, NULL, path);
379         if (!ni)
380                 return WIMLIB_ERR_NTFS_3G;
381
382         memset(&sec_ctx, 0, sizeof(sec_ctx));
383         sec_ctx.vol = vol;
384
385         if (ntfs_set_ntfs_acl(&sec_ctx, ni, desc, desc_size, 0))
386                 ret = WIMLIB_ERR_NTFS_3G;
387         if (ntfs_inode_close(ni))
388                 ret = WIMLIB_ERR_NTFS_3G;
389         return ret;
390 }
391
392 static int
393 ntfs_3g_set_timestamps(const char *path, u64 creation_time,
394                        u64 last_write_time, u64 last_access_time,
395                        struct apply_ctx *ctx)
396 {
397         u64 ntfs_timestamps[3];
398         ntfs_inode *ni;
399         int ret = 0;
400
401         ni = ntfs_3g_apply_pathname_to_inode(path, ctx);
402         if (!ni)
403                 return WIMLIB_ERR_NTFS_3G;
404
405         /* Note: ntfs_inode_set_times() expects the times in native byte order,
406          * not little endian. */
407         ntfs_timestamps[0] = creation_time;
408         ntfs_timestamps[1] = last_write_time;
409         ntfs_timestamps[2] = last_access_time;
410
411         if (ntfs_inode_set_times(ni, (const char*)ntfs_timestamps,
412                                  sizeof(ntfs_timestamps), 0))
413                 ret = WIMLIB_ERR_NTFS_3G;
414         if (ntfs_inode_close(ni))
415                 ret = WIMLIB_ERR_NTFS_3G;
416         return ret;
417 }
418
419 static bool
420 ntfs_3g_target_is_root(const char *target)
421 {
422         /* We always extract to the root of the NTFS volume.  */
423         return true;
424 }
425
426 static int
427 ntfs_3g_start_extract(const char *path, struct apply_ctx *ctx)
428 {
429         ntfs_volume *vol;
430
431         vol = ntfs_mount(ctx->target, 0);
432         if (!vol) {
433                 ERROR_WITH_ERRNO("Failed to mount \"%"TS"\" with NTFS-3g", ctx->target);
434                 return WIMLIB_ERR_NTFS_3G;
435         }
436         ntfs_3g_apply_ctx_set_volume(ctx, vol);
437
438         ctx->supported_features.archive_files             = 1;
439         ctx->supported_features.hidden_files              = 1;
440         ctx->supported_features.system_files              = 1;
441         ctx->supported_features.compressed_files          = 1;
442         ctx->supported_features.encrypted_files           = 0;
443         ctx->supported_features.not_context_indexed_files = 1;
444         ctx->supported_features.sparse_files              = 1;
445         ctx->supported_features.named_data_streams        = 1;
446         ctx->supported_features.hard_links                = 1;
447         ctx->supported_features.reparse_points            = 1;
448         ctx->supported_features.security_descriptors      = 1;
449         ctx->supported_features.short_names               = 1;
450         return 0;
451 }
452
453 static int
454 ntfs_3g_finish_or_abort_extract(struct apply_ctx *ctx)
455 {
456         ntfs_volume *vol;
457
458         vol = ntfs_3g_apply_ctx_get_volume(ctx);
459         if (ntfs_umount(vol, FALSE)) {
460                 ERROR_WITH_ERRNO("Failed to unmount \"%"TS"\" with NTFS-3g",
461                                  ctx->target);
462                 return WIMLIB_ERR_NTFS_3G;
463         }
464         return 0;
465 }
466
467 void
468 libntfs3g_global_init(void)
469 {
470         ntfs_set_char_encoding(setlocale(LC_ALL, ""));
471 }
472
473 const struct apply_operations ntfs_3g_apply_ops = {
474         .name = "NTFS-3g",
475
476         .target_is_root          = ntfs_3g_target_is_root,
477         .start_extract           = ntfs_3g_start_extract,
478         .create_file             = ntfs_3g_create_file,
479         .create_directory        = ntfs_3g_create_directory,
480         .create_hardlink         = ntfs_3g_create_hardlink,
481         .extract_unnamed_stream  = ntfs_3g_extract_unnamed_stream,
482         .extract_named_stream    = ntfs_3g_extract_named_stream,
483         .set_file_attributes     = ntfs_3g_set_file_attributes,
484         .set_reparse_data        = ntfs_3g_set_reparse_data,
485         .set_short_name          = ntfs_3g_set_short_name,
486         .set_security_descriptor = ntfs_3g_set_security_descriptor,
487         .set_timestamps          = ntfs_3g_set_timestamps,
488         .abort_extract           = ntfs_3g_finish_or_abort_extract,
489         .finish_extract          = ntfs_3g_finish_or_abort_extract,
490
491         .path_prefix = "/",
492         .path_prefix_nchars = 1,
493         .path_separator = '/',
494         .path_max = 32768,
495
496         .supports_case_sensitive_filenames = 1,
497         .root_directory_is_special = 1,
498 };
499
500 #endif /* WITH_NTFS_3G */