]> wimlib.net Git - wimlib/blob - src/update_image.c
82aac7ffbf3d1a8f2ec6cb6e0ed609313affb580
[wimlib] / src / update_image.c
1 /*
2  * update_image.c - Update a WIM image.
3  */
4
5 /*
6  * Copyright (C) 2013, 2014 Eric Biggers
7  *
8  * This file is part of wimlib, a library for working with WIM files.
9  *
10  * wimlib is free software; you can redistribute it and/or modify it under the
11  * terms of the GNU General Public License as published by the Free
12  * Software Foundation; either version 3 of the License, or (at your option)
13  * any later version.
14  *
15  * wimlib is distributed in the hope that it will be useful, but WITHOUT ANY
16  * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
17  * A PARTICULAR PURPOSE. See the GNU General Public License for more
18  * details.
19  *
20  * You should have received a copy of the GNU General Public License
21  * along with wimlib; if not, see http://www.gnu.org/licenses/.
22  */
23
24 #ifdef HAVE_CONFIG_H
25 #  include "config.h"
26 #endif
27
28 #include "wimlib/capture.h"
29 #include "wimlib/dentry.h"
30 #include "wimlib/error.h"
31 #include "wimlib/lookup_table.h"
32 #include "wimlib/metadata.h"
33 #ifdef WITH_NTFS_3G
34 #  include "wimlib/ntfs_3g.h" /* for do_ntfs_umount() */
35 #endif
36 #include "wimlib/paths.h"
37 #include "wimlib/xml.h"
38
39 #include <errno.h>
40 #include <sys/stat.h>
41
42 /* Overlays @branch onto @target, both of which must be directories. */
43 static int
44 do_overlay(struct wim_dentry *target, struct wim_dentry *branch)
45 {
46         DEBUG("Doing overlay \"%"WS"\" => \"%"WS"\"",
47               branch->file_name, target->file_name);
48
49         if (!dentry_is_directory(branch) || !dentry_is_directory(target)) {
50                 ERROR("Cannot overlay \"%"WS"\" onto existing dentry: "
51                       "is not directory-on-directory!", branch->file_name);
52                 return WIMLIB_ERR_INVALID_OVERLAY;
53         }
54
55         LIST_HEAD(moved_children);
56         while (dentry_has_children(branch)) {
57                 struct wim_dentry *child = dentry_any_child(branch);
58                 struct wim_dentry *existing;
59
60                 /* Move @child to the directory @target */
61                 unlink_dentry(child);
62                 existing = dentry_add_child(target, child);
63
64                 /* File or directory with same name already exists */
65                 if (existing) {
66                         int ret;
67                         ret = do_overlay(existing, child);
68                         if (ret) {
69                                 /* Overlay failed.  Revert the changes. */
70                                 dentry_add_child(branch, child);
71                                 list_for_each_entry(child, &moved_children, tmp_list)
72                                 {
73                                         unlink_dentry(child);
74                                         dentry_add_child(branch, child);
75                                 }
76                                 return ret;
77                         }
78                 } else {
79                         list_add(&child->tmp_list, &moved_children);
80                 }
81         }
82         free_dentry(branch);
83         return 0;
84 }
85
86 /* Attach or overlay a branch onto the WIM image.
87  *
88  * @root_p:
89  *      Pointer to the root of the WIM image, or pointer to NULL if it has not
90  *      been created yet.
91  * @branch
92  *      Branch to add.
93  * @target_path:
94  *      Path in the WIM image to add the branch, with leading and trailing
95  *      slashes stripped.
96  */
97 static int
98 attach_branch(struct wim_dentry **root_p, struct wim_dentry *branch,
99               tchar *target_path, CASE_SENSITIVITY_TYPE case_type)
100 {
101         tchar *slash;
102         struct wim_dentry *dentry, *parent, *target;
103         int ret;
104
105         DEBUG("Attaching branch \"%"WS"\" => \"%"TS"\"",
106               branch->file_name, target_path);
107
108         if (*target_path == T('\0')) {
109                 /* Target: root directory */
110                 if (*root_p) {
111                         /* Overlay on existing root */
112                         return do_overlay(*root_p, branch);
113                 } else {
114                         if (!dentry_is_directory(branch)) {
115                                 ERROR("Cannot set non-directory as root of WIM image");
116                                 return WIMLIB_ERR_NOTDIR;
117                         }
118                         /* Set as root */
119                         *root_p = branch;
120                         return 0;
121                 }
122         }
123
124         /* Adding a non-root branch.  Create root if it hasn't been created
125          * already. */
126         if (!*root_p) {
127                 ret  = new_filler_directory(T(""), root_p);
128                 if (ret)
129                         return ret;
130         }
131
132         /* Walk the path to the branch, creating filler directories as needed.
133          * */
134         parent = *root_p;
135         while ((slash = tstrchr(target_path, WIM_PATH_SEPARATOR))) {
136                 *slash = T('\0');
137                 dentry = get_dentry_child_with_name(parent, target_path,
138                                                     case_type);
139                 if (!dentry) {
140                         ret = new_filler_directory(target_path, &dentry);
141                         if (ret)
142                                 return ret;
143                         dentry_add_child(parent, dentry);
144                 }
145                 parent = dentry;
146                 target_path = slash;
147                 /* Skip over slashes.  Note: this cannot overrun the length of
148                  * the string because the last character cannot be a slash, as
149                  * trailing slashes were tripped.  */
150                 do {
151                         ++target_path;
152                 } while (*target_path == WIM_PATH_SEPARATOR);
153         }
154
155         /* If the target path already existed, overlay the branch onto it.
156          * Otherwise, set the branch as the target path. */
157         target = get_dentry_child_with_utf16le_name(parent, branch->file_name,
158                                                     branch->file_name_nbytes,
159                                                     case_type);
160         if (target) {
161                 return do_overlay(target, branch);
162         } else {
163                 dentry_add_child(parent, branch);
164                 return 0;
165         }
166 }
167
168 const tchar wincfg[] =
169 T(
170 "[ExclusionList]\n"
171 "/$ntfs.log\n"
172 "/hiberfil.sys\n"
173 "/pagefile.sys\n"
174 "/System Volume Information\n"
175 "/RECYCLER\n"
176 "/Windows/CSC\n"
177 );
178
179 static int
180 get_capture_config(const tchar *config_file, struct capture_config *config,
181                    int add_flags, const tchar *fs_source_path)
182 {
183         int ret;
184         tchar *tmp_config_file = NULL;
185
186         memset(config, 0, sizeof(*config));
187
188         /* For WIMBoot capture, check for default capture configuration file
189          * unless one was explicitly specified.  */
190         if (!config_file && (add_flags & WIMLIB_ADD_FLAG_WIMBOOT)) {
191
192                 /* XXX: Handle loading file correctly when in NTFS volume.  */
193
194                 const tchar *wimboot_cfgfile =
195                         T("/Windows/System32/WimBootCompress.ini");
196                 size_t len = tstrlen(fs_source_path) +
197                              tstrlen(wimboot_cfgfile);
198                 tmp_config_file = MALLOC((len + 1) * sizeof(tchar));
199                 struct stat st;
200
201                 tsprintf(tmp_config_file, T("%"TS"%"TS),
202                          fs_source_path, wimboot_cfgfile);
203                 if (!tstat(tmp_config_file, &st)) {
204                         config_file = tmp_config_file;
205                         add_flags &= ~WIMLIB_ADD_FLAG_WINCONFIG;
206                 } else {
207                         WARNING("\"%"TS"\" does not exist.\n"
208                                 "          Using default capture configuration!",
209                                 tmp_config_file);
210                 }
211         }
212
213         if (add_flags & WIMLIB_ADD_FLAG_WINCONFIG) {
214
215                 /* Use Windows default.  */
216
217                 tchar *wincfg_copy;
218                 const size_t wincfg_len = ARRAY_LEN(wincfg) - 1;
219
220                 if (config_file)
221                         return WIMLIB_ERR_INVALID_PARAM;
222
223                 wincfg_copy = memdup(wincfg, wincfg_len * sizeof(wincfg[0]));
224                 if (!wincfg_copy)
225                         return WIMLIB_ERR_NOMEM;
226
227                 ret = do_read_capture_config_file(T("wincfg"), wincfg_copy,
228                                                   wincfg_len, config);
229                 if (ret)
230                         FREE(wincfg_copy);
231         } else if (config_file) {
232                 /* Use the specified configuration file.  */
233                 ret = do_read_capture_config_file(config_file, NULL, 0, config);
234         } else {
235                 /* ... Or don't use any configuration file at all.  No files
236                  * will be excluded from capture, all files will be compressed,
237                  * etc.  */
238                 ret = 0;
239         }
240         FREE(tmp_config_file);
241         return ret;
242 }
243
244 static int
245 execute_add_command(WIMStruct *wim,
246                     const struct wimlib_update_command *add_cmd,
247                     wimlib_progress_func_t progress_func)
248 {
249         int ret;
250         int add_flags;
251         tchar *fs_source_path;
252         tchar *wim_target_path;
253         struct wim_image_metadata *imd;
254         struct list_head unhashed_streams;
255         struct add_image_params params;
256         int (*capture_tree)(struct wim_dentry **,
257                             const tchar *,
258                             struct add_image_params *);
259         const tchar *config_file;
260         struct capture_config config;
261 #ifdef WITH_NTFS_3G
262         struct _ntfs_volume *ntfs_vol = NULL;
263 #endif
264         void *extra_arg;
265         struct wim_dentry *branch;
266         bool rollback_sd = true;
267
268         wimlib_assert(add_cmd->op == WIMLIB_UPDATE_OP_ADD);
269
270         add_flags = add_cmd->add.add_flags;
271         fs_source_path = add_cmd->add.fs_source_path;
272         wim_target_path = add_cmd->add.wim_target_path;
273         config_file = add_cmd->add.config_file;
274
275         DEBUG("fs_source_path=\"%"TS"\", wim_target_path=\"%"TS"\", add_flags=%#x",
276               fs_source_path, wim_target_path, add_flags);
277
278         memset(&params, 0, sizeof(params));
279
280         imd = wim->image_metadata[wim->current_image - 1];
281
282         if (add_flags & WIMLIB_ADD_FLAG_NTFS) {
283         #ifdef WITH_NTFS_3G
284                 capture_tree = build_dentry_tree_ntfs;
285                 extra_arg = &ntfs_vol;
286                 if (imd->ntfs_vol != NULL) {
287                         ERROR("NTFS volume already set");
288                         ret = WIMLIB_ERR_INVALID_PARAM;
289                         goto out;
290                 }
291         #else
292                 ret = WIMLIB_ERR_INVALID_PARAM;
293                 goto out;
294         #endif
295         } else {
296         #ifdef __WIN32__
297                 capture_tree = win32_build_dentry_tree;
298         #else
299                 capture_tree = unix_build_dentry_tree;
300         #endif
301                 extra_arg = NULL;
302         }
303
304         ret = get_capture_config(config_file, &config,
305                                  add_flags, fs_source_path);
306         if (ret)
307                 goto out;
308
309         ret = init_inode_table(&params.inode_table, 9001);
310         if (ret)
311                 goto out_destroy_config;
312
313         ret = init_sd_set(&params.sd_set, imd->security_data);
314         if (ret)
315                 goto out_destroy_inode_table;
316
317         INIT_LIST_HEAD(&unhashed_streams);
318         params.lookup_table = wim->lookup_table;
319         params.unhashed_streams = &unhashed_streams;
320         params.config = &config;
321         params.add_flags = add_flags;
322         params.extra_arg = extra_arg;
323
324         params.progress_func = progress_func;
325         params.progress.scan.source = fs_source_path;
326         params.progress.scan.wim_target_path = wim_target_path;
327         if (progress_func)
328                 progress_func(WIMLIB_PROGRESS_MSG_SCAN_BEGIN, &params.progress);
329
330         config.prefix = fs_source_path;
331         config.prefix_num_tchars = tstrlen(fs_source_path);
332
333         if (wim_target_path[0] == T('\0'))
334                 params.add_flags |= WIMLIB_ADD_FLAG_ROOT;
335         ret = (*capture_tree)(&branch, fs_source_path, &params);
336         if (ret)
337                 goto out_destroy_sd_set;
338
339         if (branch) {
340                 /* Use the target name, not the source name, for
341                  * the root of each branch from a capture
342                  * source.  (This will also set the root dentry
343                  * of the entire image to be unnamed.) */
344                 ret = dentry_set_name(branch,
345                                       path_basename(wim_target_path));
346                 if (ret)
347                         goto out_ntfs_umount;
348
349                 ret = attach_branch(&imd->root_dentry, branch, wim_target_path,
350                                     WIMLIB_CASE_PLATFORM_DEFAULT);
351                 if (ret)
352                         goto out_ntfs_umount;
353         }
354         if (progress_func)
355                 progress_func(WIMLIB_PROGRESS_MSG_SCAN_END, &params.progress);
356         list_splice_tail(&unhashed_streams, &imd->unhashed_streams);
357 #ifdef WITH_NTFS_3G
358         imd->ntfs_vol = ntfs_vol;
359 #endif
360         inode_table_prepare_inode_list(&params.inode_table, &imd->inode_list);
361         ret = 0;
362         rollback_sd = false;
363         if (add_flags & WIMLIB_ADD_FLAG_RPFIX)
364                 wim->hdr.flags |= WIM_HDR_FLAG_RP_FIX;
365         goto out_destroy_sd_set;
366 out_ntfs_umount:
367 #ifdef WITH_NTFS_3G
368         if (ntfs_vol)
369                 do_ntfs_umount(ntfs_vol);
370 #endif
371         free_dentry_tree(branch, wim->lookup_table);
372 out_destroy_sd_set:
373         destroy_sd_set(&params.sd_set, rollback_sd);
374 out_destroy_inode_table:
375         destroy_inode_table(&params.inode_table);
376 out_destroy_config:
377         destroy_capture_config(&config);
378 out:
379         return ret;
380 }
381
382 static int
383 execute_delete_command(WIMStruct *wim,
384                        const struct wimlib_update_command *delete_cmd)
385 {
386         int flags;
387         const tchar *wim_path;
388         struct wim_dentry *tree;
389         bool is_root;
390
391         wimlib_assert(delete_cmd->op == WIMLIB_UPDATE_OP_DELETE);
392         flags = delete_cmd->delete_.delete_flags;
393         wim_path = delete_cmd->delete_.wim_path;
394
395         DEBUG("Deleting WIM path \"%"TS"\" (flags=%#x)", wim_path, flags);
396
397         tree = get_dentry(wim, wim_path, WIMLIB_CASE_PLATFORM_DEFAULT);
398         if (!tree) {
399                 /* Path to delete does not exist in the WIM. */
400                 if (flags & WIMLIB_DELETE_FLAG_FORCE) {
401                         return 0;
402                 } else {
403                         ERROR("Path \"%"TS"\" does not exist in WIM image %d",
404                               wim_path, wim->current_image);
405                         return WIMLIB_ERR_PATH_DOES_NOT_EXIST;
406                 }
407         }
408
409         if (dentry_is_directory(tree) && !(flags & WIMLIB_DELETE_FLAG_RECURSIVE)) {
410                 ERROR("Path \"%"TS"\" in WIM image %d is a directory "
411                       "but a recursive delete was not requested",
412                       wim_path, wim->current_image);
413                 return WIMLIB_ERR_IS_DIRECTORY;
414         }
415
416         is_root = dentry_is_root(tree);
417         unlink_dentry(tree);
418         free_dentry_tree(tree, wim->lookup_table);
419         if (is_root)
420                 wim->image_metadata[wim->current_image - 1]->root_dentry = NULL;
421         return 0;
422 }
423
424 static int
425 execute_rename_command(WIMStruct *wim,
426                        const struct wimlib_update_command *rename_cmd)
427 {
428         int ret;
429
430         wimlib_assert(rename_cmd->op == WIMLIB_UPDATE_OP_RENAME);
431
432         ret = rename_wim_path(wim, rename_cmd->rename.wim_source_path,
433                               rename_cmd->rename.wim_target_path,
434                               WIMLIB_CASE_PLATFORM_DEFAULT);
435         if (ret) {
436                 ret = -ret;
437                 errno = ret;
438                 ERROR_WITH_ERRNO("Can't rename \"%"TS"\" to \"%"TS"\"",
439                                  rename_cmd->rename.wim_source_path,
440                                  rename_cmd->rename.wim_target_path);
441                 switch (ret) {
442                 case ENOMEM:
443                         ret = WIMLIB_ERR_NOMEM;
444                         break;
445                 case ENOTDIR:
446                         ret = WIMLIB_ERR_NOTDIR;
447                         break;
448                 case ENOTEMPTY:
449                         ret = WIMLIB_ERR_NOTEMPTY;
450                         break;
451                 case EISDIR:
452                         ret = WIMLIB_ERR_IS_DIRECTORY;
453                         break;
454                 case ENOENT:
455                 default:
456                         ret = WIMLIB_ERR_PATH_DOES_NOT_EXIST;
457                         break;
458                 }
459         }
460         return ret;
461 }
462
463 static inline const tchar *
464 update_op_to_str(int op)
465 {
466         switch (op) {
467         case WIMLIB_UPDATE_OP_ADD:
468                 return T("add");
469         case WIMLIB_UPDATE_OP_DELETE:
470                 return T("delete");
471         case WIMLIB_UPDATE_OP_RENAME:
472                 return T("rename");
473         default:
474                 wimlib_assert(0);
475                 return NULL;
476         }
477 }
478
479 static int
480 execute_update_commands(WIMStruct *wim,
481                         const struct wimlib_update_command *cmds,
482                         size_t num_cmds,
483                         int update_flags,
484                         wimlib_progress_func_t progress_func)
485 {
486         int ret = 0;
487         union wimlib_progress_info info;
488         info.update.completed_commands = 0;
489         info.update.total_commands = num_cmds;
490         for (size_t i = 0; i < num_cmds; i++) {
491                 DEBUG("Executing update command %zu of %zu (op=%"TS")",
492                       i + 1, num_cmds, update_op_to_str(cmds[i].op));
493                 if (update_flags & WIMLIB_UPDATE_FLAG_SEND_PROGRESS &&
494                     progress_func)
495                 {
496                         info.update.command = &cmds[i];
497                         (*progress_func)(WIMLIB_PROGRESS_MSG_UPDATE_BEGIN_COMMAND,
498                                          &info);
499                 }
500                 switch (cmds[i].op) {
501                 case WIMLIB_UPDATE_OP_ADD:
502                         ret = execute_add_command(wim, &cmds[i], progress_func);
503                         break;
504                 case WIMLIB_UPDATE_OP_DELETE:
505                         ret = execute_delete_command(wim, &cmds[i]);
506                         break;
507                 case WIMLIB_UPDATE_OP_RENAME:
508                         ret = execute_rename_command(wim, &cmds[i]);
509                         break;
510                 default:
511                         wimlib_assert(0);
512                 }
513                 if (ret)
514                         break;
515                 info.update.completed_commands++;
516                 if (update_flags & WIMLIB_UPDATE_FLAG_SEND_PROGRESS &&
517                     progress_func)
518                 {
519                         (*progress_func)(WIMLIB_PROGRESS_MSG_UPDATE_END_COMMAND,
520                                          &info);
521                 }
522         }
523         return ret;
524 }
525
526
527 static int
528 check_add_command(struct wimlib_update_command *cmd,
529                   const struct wim_header *hdr)
530 {
531         int add_flags = cmd->add.add_flags;
532
533         if (add_flags & ~(WIMLIB_ADD_FLAG_NTFS |
534                           WIMLIB_ADD_FLAG_DEREFERENCE |
535                           WIMLIB_ADD_FLAG_VERBOSE |
536                           /* BOOT doesn't make sense for wimlib_update_image().  */
537                           /*WIMLIB_ADD_FLAG_BOOT |*/
538                           WIMLIB_ADD_FLAG_WIMBOOT |
539                           WIMLIB_ADD_FLAG_UNIX_DATA |
540                           WIMLIB_ADD_FLAG_NO_ACLS |
541                           WIMLIB_ADD_FLAG_STRICT_ACLS |
542                           WIMLIB_ADD_FLAG_EXCLUDE_VERBOSE |
543                           WIMLIB_ADD_FLAG_RPFIX |
544                           WIMLIB_ADD_FLAG_NORPFIX |
545                           WIMLIB_ADD_FLAG_NO_UNSUPPORTED_EXCLUDE |
546                           WIMLIB_ADD_FLAG_WINCONFIG))
547                 return WIMLIB_ERR_INVALID_PARAM;
548
549         /* Are we adding the entire image or not?  An empty wim_target_path
550          * indicates that the tree we're adding is to be placed in the root of
551          * the image.  We consider this to be capturing the entire image,
552          * although it could potentially be an overlay on an existing root as
553          * well. */
554         bool is_entire_image = cmd->add.wim_target_path[0] == T('\0');
555
556 #ifdef __WIN32__
557         /* Check for flags not supported on Windows */
558         if (add_flags & WIMLIB_ADD_FLAG_NTFS) {
559                 ERROR("wimlib was compiled without support for NTFS-3g, so");
560                 ERROR("we cannot capture a WIM image directly from a NTFS volume");
561                 return WIMLIB_ERR_UNSUPPORTED;
562         }
563         if (add_flags & WIMLIB_ADD_FLAG_UNIX_DATA) {
564                 ERROR("Capturing UNIX-specific data is not supported on Windows");
565                 return WIMLIB_ERR_UNSUPPORTED;
566         }
567         if (add_flags & WIMLIB_ADD_FLAG_DEREFERENCE) {
568                 ERROR("Dereferencing symbolic links is not supported on Windows");
569                 return WIMLIB_ERR_UNSUPPORTED;
570         }
571 #endif
572
573         /* VERBOSE implies EXCLUDE_VERBOSE */
574         if (add_flags & WIMLIB_ADD_FLAG_VERBOSE)
575                 add_flags |= WIMLIB_ADD_FLAG_EXCLUDE_VERBOSE;
576
577         /* Check for contradictory reparse point fixup flags */
578         if ((add_flags & (WIMLIB_ADD_FLAG_RPFIX |
579                           WIMLIB_ADD_FLAG_NORPFIX)) ==
580                 (WIMLIB_ADD_FLAG_RPFIX |
581                  WIMLIB_ADD_FLAG_NORPFIX))
582         {
583                 ERROR("Cannot specify RPFIX and NORPFIX flags "
584                       "at the same time!");
585                 return WIMLIB_ERR_INVALID_PARAM;
586         }
587
588         /* Set default behavior on reparse point fixups if requested */
589         if ((add_flags & (WIMLIB_ADD_FLAG_RPFIX |
590                           WIMLIB_ADD_FLAG_NORPFIX)) == 0)
591         {
592                 /* Do reparse-point fixups by default if we are capturing an
593                  * entire image and either the header flag is set from previous
594                  * images, or if this is the first image being added. */
595                 if (is_entire_image &&
596                     ((hdr->flags & WIM_HDR_FLAG_RP_FIX) || hdr->image_count == 1))
597                         add_flags |= WIMLIB_ADD_FLAG_RPFIX;
598         }
599
600         if (!is_entire_image) {
601                 if (add_flags & WIMLIB_ADD_FLAG_NTFS) {
602                         ERROR("Cannot add directly from a NTFS volume "
603                               "when not capturing a full image!");
604                         return WIMLIB_ERR_INVALID_PARAM;
605                 }
606
607                 if (add_flags & WIMLIB_ADD_FLAG_RPFIX) {
608                         ERROR("Cannot do reparse point fixups when "
609                               "not capturing a full image!");
610                         return WIMLIB_ERR_INVALID_PARAM;
611                 }
612         }
613         /* We may have modified the add flags. */
614         cmd->add.add_flags = add_flags;
615         return 0;
616 }
617
618 static int
619 check_delete_command(const struct wimlib_update_command *cmd)
620 {
621         if (cmd->delete_.delete_flags & ~(WIMLIB_DELETE_FLAG_FORCE |
622                                           WIMLIB_DELETE_FLAG_RECURSIVE))
623                 return WIMLIB_ERR_INVALID_PARAM;
624         return 0;
625 }
626
627 static int
628 check_rename_command(const struct wimlib_update_command *cmd)
629 {
630         if (cmd->rename.rename_flags != 0)
631                 return WIMLIB_ERR_INVALID_PARAM;
632         return 0;
633 }
634
635 static int
636 check_update_command(struct wimlib_update_command *cmd,
637                      const struct wim_header *hdr)
638 {
639         switch (cmd->op) {
640         case WIMLIB_UPDATE_OP_ADD:
641                 return check_add_command(cmd, hdr);
642         case WIMLIB_UPDATE_OP_DELETE:
643                 return check_delete_command(cmd);
644         case WIMLIB_UPDATE_OP_RENAME:
645                 return check_rename_command(cmd);
646         }
647         return 0;
648 }
649
650 static int
651 check_update_commands(struct wimlib_update_command *cmds, size_t num_cmds,
652                       const struct wim_header *hdr)
653 {
654         int ret = 0;
655         for (size_t i = 0; i < num_cmds; i++) {
656                 ret = check_update_command(&cmds[i], hdr);
657                 if (ret)
658                         break;
659         }
660         return ret;
661 }
662
663
664 static void
665 free_update_commands(struct wimlib_update_command *cmds, size_t num_cmds)
666 {
667         if (cmds) {
668                 for (size_t i = 0; i < num_cmds; i++) {
669                         switch (cmds[i].op) {
670                         case WIMLIB_UPDATE_OP_ADD:
671                                 FREE(cmds[i].add.fs_source_path);
672                                 FREE(cmds[i].add.wim_target_path);
673                                 FREE(cmds[i].add.config_file);
674                                 break;
675                         case WIMLIB_UPDATE_OP_DELETE:
676                                 FREE(cmds[i].delete_.wim_path);
677                                 break;
678                         case WIMLIB_UPDATE_OP_RENAME:
679                                 FREE(cmds[i].rename.wim_source_path);
680                                 FREE(cmds[i].rename.wim_target_path);
681                                 break;
682                         }
683                 }
684                 FREE(cmds);
685         }
686 }
687
688 static int
689 copy_update_commands(const struct wimlib_update_command *cmds,
690                      size_t num_cmds,
691                      struct wimlib_update_command **cmds_copy_ret)
692 {
693         int ret;
694         struct wimlib_update_command *cmds_copy;
695
696         cmds_copy = CALLOC(num_cmds, sizeof(cmds[0]));
697         if (!cmds_copy)
698                 goto oom;
699
700         for (size_t i = 0; i < num_cmds; i++) {
701                 cmds_copy[i].op = cmds[i].op;
702                 switch (cmds[i].op) {
703                 case WIMLIB_UPDATE_OP_ADD:
704                         cmds_copy[i].add.fs_source_path =
705                                 canonicalize_fs_path(cmds[i].add.fs_source_path);
706                         cmds_copy[i].add.wim_target_path =
707                                 canonicalize_wim_path(cmds[i].add.wim_target_path);
708                         if (!cmds_copy[i].add.fs_source_path ||
709                             !cmds_copy[i].add.wim_target_path)
710                                 goto oom;
711                         if (cmds[i].add.config_file) {
712                                 cmds_copy[i].add.config_file = TSTRDUP(cmds[i].add.config_file);
713                                 if (!cmds_copy[i].add.config_file)
714                                         goto oom;
715                         }
716                         cmds_copy[i].add.add_flags = cmds[i].add.add_flags;
717                         break;
718                 case WIMLIB_UPDATE_OP_DELETE:
719                         cmds_copy[i].delete_.wim_path =
720                                 canonicalize_wim_path(cmds[i].delete_.wim_path);
721                         if (!cmds_copy[i].delete_.wim_path)
722                                 goto oom;
723                         cmds_copy[i].delete_.delete_flags = cmds[i].delete_.delete_flags;
724                         break;
725                 case WIMLIB_UPDATE_OP_RENAME:
726                         cmds_copy[i].rename.wim_source_path =
727                                 canonicalize_wim_path(cmds[i].rename.wim_source_path);
728                         cmds_copy[i].rename.wim_target_path =
729                                 canonicalize_wim_path(cmds[i].rename.wim_target_path);
730                         if (!cmds_copy[i].rename.wim_source_path ||
731                             !cmds_copy[i].rename.wim_target_path)
732                                 goto oom;
733                         break;
734                 default:
735                         ERROR("Unknown update operation %u", cmds[i].op);
736                         ret = WIMLIB_ERR_INVALID_PARAM;
737                         goto err;
738                 }
739         }
740         *cmds_copy_ret = cmds_copy;
741         ret = 0;
742 out:
743         return ret;
744 oom:
745         ret = WIMLIB_ERR_NOMEM;
746 err:
747         free_update_commands(cmds_copy, num_cmds);
748         goto out;
749 }
750
751 /* API function documented in wimlib.h  */
752 WIMLIBAPI int
753 wimlib_update_image(WIMStruct *wim,
754                     int image,
755                     const struct wimlib_update_command *cmds,
756                     size_t num_cmds,
757                     int update_flags,
758                     wimlib_progress_func_t progress_func)
759 {
760         int ret;
761         struct wimlib_update_command *cmds_copy;
762         bool deletion_requested = false;
763
764         if (update_flags & ~WIMLIB_UPDATE_FLAG_SEND_PROGRESS)
765                 return WIMLIB_ERR_INVALID_PARAM;
766
767         DEBUG("Updating image %d with %zu commands", image, num_cmds);
768
769         for (size_t i = 0; i < num_cmds; i++)
770                 if (cmds[i].op == WIMLIB_UPDATE_OP_DELETE)
771                         deletion_requested = true;
772
773         if (deletion_requested)
774                 ret = can_delete_from_wim(wim);
775         else
776                 ret = can_modify_wim(wim);
777
778         if (ret)
779                 goto out;
780
781         /* Load the metadata for the image to modify (if not loaded already) */
782         ret = select_wim_image(wim, image);
783         if (ret)
784                 goto out;
785
786         /* Short circuit a successful return if no commands were specified.
787          * Avoids problems with trying to allocate 0 bytes of memory. */
788         if (num_cmds == 0)
789                 goto out;
790
791         DEBUG("Preparing %zu update commands", num_cmds);
792
793         /* Make a copy of the update commands, in the process doing certain
794          * canonicalizations on paths (e.g. translating backslashes to forward
795          * slashes).  This is done to avoid modifying the caller's copy of the
796          * commands. */
797         ret = copy_update_commands(cmds, num_cmds, &cmds_copy);
798         if (ret)
799                 goto out;
800
801         /* Perform additional checks on the update commands before we execute
802          * them. */
803         ret = check_update_commands(cmds_copy, num_cmds, &wim->hdr);
804         if (ret)
805                 goto out_free_cmds_copy;
806
807         /* Actually execute the update commands. */
808         DEBUG("Executing %zu update commands", num_cmds);
809         ret = execute_update_commands(wim, cmds_copy, num_cmds, update_flags,
810                                       progress_func);
811         if (ret)
812                 goto out_free_cmds_copy;
813
814         wim->image_metadata[image - 1]->modified = 1;
815
816         /* Statistics about the WIM image, such as the numbers of files and
817          * directories, may have changed.  Call xml_update_image_info() to
818          * recalculate these statistics. */
819         xml_update_image_info(wim, image);
820 out_free_cmds_copy:
821         free_update_commands(cmds_copy, num_cmds);
822 out:
823         return ret;
824 }