summaryrefslogtreecommitdiffstats
path: root/tools/idevicebackup2.c
diff options
context:
space:
mode:
Diffstat (limited to 'tools/idevicebackup2.c')
-rw-r--r--tools/idevicebackup2.c2684
1 files changed, 2684 insertions, 0 deletions
diff --git a/tools/idevicebackup2.c b/tools/idevicebackup2.c
new file mode 100644
index 0000000..c73b269
--- /dev/null
+++ b/tools/idevicebackup2.c
@@ -0,0 +1,2684 @@
+/*
+ * idevicebackup2.c
+ * Command line interface to use the device's backup and restore service
+ *
+ * Copyright (c) 2010-2022 Nikias Bassen, All Rights Reserved.
+ * Copyright (c) 2009-2010 Martin Szulecki, All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#define TOOL_NAME "idevicebackup2"
+
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <signal.h>
+#include <unistd.h>
+#include <dirent.h>
+#include <libgen.h>
+#include <ctype.h>
+#include <time.h>
+#include <getopt.h>
+
+#include <libimobiledevice/libimobiledevice.h>
+#include <libimobiledevice/lockdown.h>
+#include <libimobiledevice/mobilebackup2.h>
+#include <libimobiledevice/notification_proxy.h>
+#include <libimobiledevice/afc.h>
+#include <libimobiledevice/installation_proxy.h>
+#include <libimobiledevice/sbservices.h>
+#include <libimobiledevice/diagnostics_relay.h>
+#include <libimobiledevice-glue/utils.h>
+#include <plist/plist.h>
+
+#include <endianness.h>
+
+#define LOCK_ATTEMPTS 50
+#define LOCK_WAIT 200000
+
+#ifdef WIN32
+#include <windows.h>
+#include <conio.h>
+#define sleep(x) Sleep(x*1000)
+#ifndef ELOOP
+#define ELOOP 114
+#endif
+#else
+#include <termios.h>
+#include <sys/statvfs.h>
+#endif
+#include <sys/stat.h>
+
+#define CODE_SUCCESS 0x00
+#define CODE_ERROR_LOCAL 0x06
+#define CODE_ERROR_REMOTE 0x0b
+#define CODE_FILE_DATA 0x0c
+
+static int verbose = 1;
+static int quit_flag = 0;
+
+#define PRINT_VERBOSE(min_level, ...) if (verbose >= min_level) { printf(__VA_ARGS__); };
+
+enum cmd_mode {
+ CMD_BACKUP,
+ CMD_RESTORE,
+ CMD_INFO,
+ CMD_LIST,
+ CMD_UNBACK,
+ CMD_CHANGEPW,
+ CMD_LEAVE,
+ CMD_CLOUD
+};
+
+enum cmd_flags {
+ CMD_FLAG_RESTORE_SYSTEM_FILES = (1 << 1),
+ CMD_FLAG_RESTORE_NO_REBOOT = (1 << 2),
+ CMD_FLAG_RESTORE_COPY_BACKUP = (1 << 3),
+ CMD_FLAG_RESTORE_SETTINGS = (1 << 4),
+ CMD_FLAG_RESTORE_REMOVE_ITEMS = (1 << 5),
+ CMD_FLAG_ENCRYPTION_ENABLE = (1 << 6),
+ CMD_FLAG_ENCRYPTION_DISABLE = (1 << 7),
+ CMD_FLAG_ENCRYPTION_CHANGEPW = (1 << 8),
+ CMD_FLAG_FORCE_FULL_BACKUP = (1 << 9),
+ CMD_FLAG_CLOUD_ENABLE = (1 << 10),
+ CMD_FLAG_CLOUD_DISABLE = (1 << 11),
+ CMD_FLAG_RESTORE_SKIP_APPS = (1 << 12)
+};
+
+static int backup_domain_changed = 0;
+
+static void notify_cb(const char *notification, void *userdata)
+{
+ if (strlen(notification) == 0) {
+ return;
+ }
+ if (!strcmp(notification, NP_SYNC_CANCEL_REQUEST)) {
+ PRINT_VERBOSE(1, "User has cancelled the backup process on the device.\n");
+ quit_flag++;
+ } else if (!strcmp(notification, NP_BACKUP_DOMAIN_CHANGED)) {
+ backup_domain_changed = 1;
+ } else {
+ PRINT_VERBOSE(1, "Unhandled notification '%s' (TODO: implement)\n", notification);
+ }
+}
+
+static void mobilebackup_afc_get_file_contents(afc_client_t afc, const char *filename, char **data, uint64_t *size)
+{
+ if (!afc || !data || !size) {
+ return;
+ }
+
+ char **fileinfo = NULL;
+ uint32_t fsize = 0;
+
+ afc_get_file_info(afc, filename, &fileinfo);
+ if (!fileinfo) {
+ return;
+ }
+ int i;
+ for (i = 0; fileinfo[i]; i+=2) {
+ if (!strcmp(fileinfo[i], "st_size")) {
+ fsize = atol(fileinfo[i+1]);
+ break;
+ }
+ }
+ afc_dictionary_free(fileinfo);
+
+ if (fsize == 0) {
+ return;
+ }
+
+ uint64_t f = 0;
+ afc_file_open(afc, filename, AFC_FOPEN_RDONLY, &f);
+ if (!f) {
+ return;
+ }
+ char *buf = (char*)malloc((uint32_t)fsize);
+ uint32_t done = 0;
+ while (done < fsize) {
+ uint32_t bread = 0;
+ afc_file_read(afc, f, buf+done, 65536, &bread);
+ if (bread > 0) {
+ done += bread;
+ } else {
+ break;
+ }
+ }
+ if (done == fsize) {
+ *size = fsize;
+ *data = buf;
+ } else {
+ free(buf);
+ }
+ afc_file_close(afc, f);
+}
+
+static int __mkdir(const char* path, int mode)
+{
+#ifdef WIN32
+ return mkdir(path);
+#else
+ return mkdir(path, mode);
+#endif
+}
+
+static int mkdir_with_parents(const char *dir, int mode)
+{
+ if (!dir) return -1;
+ if (__mkdir(dir, mode) == 0) {
+ return 0;
+ }
+ if (errno == EEXIST) return 0;
+ int res;
+ char *parent = strdup(dir);
+ char *parentdir = dirname(parent);
+ if (parentdir) {
+ res = mkdir_with_parents(parentdir, mode);
+ } else {
+ res = -1;
+ }
+ free(parent);
+ if (res == 0) {
+ mkdir_with_parents(dir, mode);
+ }
+ return res;
+}
+
+#ifdef WIN32
+static int win32err_to_errno(int err_value)
+{
+ switch (err_value) {
+ case ERROR_FILE_NOT_FOUND:
+ return ENOENT;
+ case ERROR_ALREADY_EXISTS:
+ return EEXIST;
+ default:
+ return EFAULT;
+ }
+}
+#endif
+
+static int remove_file(const char* path)
+{
+ int e = 0;
+#ifdef WIN32
+ if (!DeleteFile(path)) {
+ e = win32err_to_errno(GetLastError());
+ }
+#else
+ if (remove(path) < 0) {
+ e = errno;
+ }
+#endif
+ return e;
+}
+
+static int remove_directory(const char* path)
+{
+ int e = 0;
+#ifdef WIN32
+ if (!RemoveDirectory(path)) {
+ e = win32err_to_errno(GetLastError());
+ }
+#else
+ if (remove(path) < 0) {
+ e = errno;
+ }
+#endif
+ return e;
+}
+
+struct entry {
+ char *name;
+ struct entry *next;
+};
+
+static void scan_directory(const char *path, struct entry **files, struct entry **directories)
+{
+ DIR* cur_dir = opendir(path);
+ if (cur_dir) {
+ struct dirent* ep;
+ while ((ep = readdir(cur_dir))) {
+ if ((strcmp(ep->d_name, ".") == 0) || (strcmp(ep->d_name, "..") == 0)) {
+ continue;
+ }
+ char *fpath = string_build_path(path, ep->d_name, NULL);
+ if (fpath) {
+#ifdef HAVE_DIRENT_D_TYPE
+ if (ep->d_type & DT_DIR) {
+#else
+ struct stat st;
+ if (stat(fpath, &st) != 0) return;
+ if (S_ISDIR(st.st_mode)) {
+#endif
+ struct entry *ent = malloc(sizeof(struct entry));
+ if (!ent) return;
+ ent->name = fpath;
+ ent->next = *directories;
+ *directories = ent;
+ scan_directory(fpath, files, directories);
+ fpath = NULL;
+ } else {
+ struct entry *ent = malloc(sizeof(struct entry));
+ if (!ent) return;
+ ent->name = fpath;
+ ent->next = *files;
+ *files = ent;
+ fpath = NULL;
+ }
+ }
+ }
+ closedir(cur_dir);
+ }
+}
+
+static int rmdir_recursive(const char* path)
+{
+ int res = 0;
+ struct entry *files = NULL;
+ struct entry *directories = NULL;
+ struct entry *ent;
+
+ ent = malloc(sizeof(struct entry));
+ if (!ent) return ENOMEM;
+ ent->name = strdup(path);
+ ent->next = NULL;
+ directories = ent;
+
+ scan_directory(path, &files, &directories);
+
+ ent = files;
+ while (ent) {
+ struct entry *del = ent;
+ res = remove_file(ent->name);
+ free(ent->name);
+ ent = ent->next;
+ free(del);
+ }
+ ent = directories;
+ while (ent) {
+ struct entry *del = ent;
+ res = remove_directory(ent->name);
+ free(ent->name);
+ ent = ent->next;
+ free(del);
+ }
+
+ return res;
+}
+
+static char* get_uuid()
+{
+ const char *chars = "ABCDEF0123456789";
+ int i = 0;
+ char *uuid = (char*)malloc(sizeof(char) * 33);
+
+ srand(time(NULL));
+
+ for (i = 0; i < 32; i++) {
+ uuid[i] = chars[rand() % 16];
+ }
+
+ uuid[32] = '\0';
+
+ return uuid;
+}
+
+static plist_t mobilebackup_factory_info_plist_new(const char* udid, idevice_t device, afc_client_t afc)
+{
+ /* gather data from lockdown */
+ plist_t value_node = NULL;
+ plist_t root_node = NULL;
+ plist_t itunes_settings = NULL;
+ plist_t min_itunes_version = NULL;
+ char *udid_uppercase = NULL;
+
+ lockdownd_client_t lockdown = NULL;
+ if (lockdownd_client_new_with_handshake(device, &lockdown, TOOL_NAME) != LOCKDOWN_E_SUCCESS) {
+ return NULL;
+ }
+
+ plist_t ret = plist_new_dict();
+
+ /* get basic device information in one go */
+ lockdownd_get_value(lockdown, NULL, NULL, &root_node);
+
+ /* get iTunes settings */
+ lockdownd_get_value(lockdown, "com.apple.iTunes", NULL, &itunes_settings);
+
+ /* get minimum iTunes version */
+ lockdownd_get_value(lockdown, "com.apple.mobile.iTunes", "MinITunesVersion", &min_itunes_version);
+
+ lockdownd_client_free(lockdown);
+
+ /* get a list of installed user applications */
+ plist_t app_dict = plist_new_dict();
+ plist_t installed_apps = plist_new_array();
+ instproxy_client_t ip = NULL;
+ if (instproxy_client_start_service(device, &ip, TOOL_NAME) == INSTPROXY_E_SUCCESS) {
+ plist_t client_opts = instproxy_client_options_new();
+ instproxy_client_options_add(client_opts, "ApplicationType", "User", NULL);
+ instproxy_client_options_set_return_attributes(client_opts, "CFBundleIdentifier", "ApplicationSINF", "iTunesMetadata", NULL);
+
+ plist_t apps = NULL;
+ instproxy_browse(ip, client_opts, &apps);
+
+ sbservices_client_t sbs = NULL;
+ if (sbservices_client_start_service(device, &sbs, TOOL_NAME) != SBSERVICES_E_SUCCESS) {
+ printf("Couldn't establish sbservices connection. Continuing anyway.\n");
+ }
+
+ if (apps && (plist_get_node_type(apps) == PLIST_ARRAY)) {
+ uint32_t app_count = plist_array_get_size(apps);
+ uint32_t i;
+ for (i = 0; i < app_count; i++) {
+ plist_t app_entry = plist_array_get_item(apps, i);
+ plist_t bundle_id = plist_dict_get_item(app_entry, "CFBundleIdentifier");
+ if (bundle_id) {
+ char *bundle_id_str = NULL;
+ plist_array_append_item(installed_apps, plist_copy(bundle_id));
+
+ plist_get_string_val(bundle_id, &bundle_id_str);
+ plist_t sinf = plist_dict_get_item(app_entry, "ApplicationSINF");
+ plist_t meta = plist_dict_get_item(app_entry, "iTunesMetadata");
+ if (sinf && meta) {
+ plist_t adict = plist_new_dict();
+ plist_dict_set_item(adict, "ApplicationSINF", plist_copy(sinf));
+ if (sbs) {
+ char *pngdata = NULL;
+ uint64_t pngsize = 0;
+ sbservices_get_icon_pngdata(sbs, bundle_id_str, &pngdata, &pngsize);
+ if (pngdata) {
+ plist_dict_set_item(adict, "PlaceholderIcon", plist_new_data(pngdata, pngsize));
+ free(pngdata);
+ }
+ }
+ plist_dict_set_item(adict, "iTunesMetadata", plist_copy(meta));
+ plist_dict_set_item(app_dict, bundle_id_str, adict);
+ }
+ free(bundle_id_str);
+ }
+ }
+ }
+ plist_free(apps);
+
+ if (sbs) {
+ sbservices_client_free(sbs);
+ }
+
+ instproxy_client_options_free(client_opts);
+
+ instproxy_client_free(ip);
+ }
+
+ /* Applications */
+ plist_dict_set_item(ret, "Applications", app_dict);
+
+ /* set fields we understand */
+ value_node = plist_dict_get_item(root_node, "BuildVersion");
+ plist_dict_set_item(ret, "Build Version", plist_copy(value_node));
+
+ value_node = plist_dict_get_item(root_node, "DeviceName");
+ plist_dict_set_item(ret, "Device Name", plist_copy(value_node));
+ plist_dict_set_item(ret, "Display Name", plist_copy(value_node));
+
+ char *uuid = get_uuid();
+ plist_dict_set_item(ret, "GUID", plist_new_string(uuid));
+ free(uuid);
+
+ value_node = plist_dict_get_item(root_node, "IntegratedCircuitCardIdentity");
+ if (value_node)
+ plist_dict_set_item(ret, "ICCID", plist_copy(value_node));
+
+ value_node = plist_dict_get_item(root_node, "InternationalMobileEquipmentIdentity");
+ if (value_node)
+ plist_dict_set_item(ret, "IMEI", plist_copy(value_node));
+
+ /* Installed Applications */
+ plist_dict_set_item(ret, "Installed Applications", installed_apps);
+
+ plist_dict_set_item(ret, "Last Backup Date", plist_new_date(time(NULL) - MAC_EPOCH, 0));
+
+ value_node = plist_dict_get_item(root_node, "MobileEquipmentIdentifier");
+ if (value_node)
+ plist_dict_set_item(ret, "MEID", plist_copy(value_node));
+
+ value_node = plist_dict_get_item(root_node, "PhoneNumber");
+ if (value_node && (plist_get_node_type(value_node) == PLIST_STRING)) {
+ plist_dict_set_item(ret, "Phone Number", plist_copy(value_node));
+ }
+
+ /* FIXME Product Name */
+
+ value_node = plist_dict_get_item(root_node, "ProductType");
+ plist_dict_set_item(ret, "Product Type", plist_copy(value_node));
+
+ value_node = plist_dict_get_item(root_node, "ProductVersion");
+ plist_dict_set_item(ret, "Product Version", plist_copy(value_node));
+
+ value_node = plist_dict_get_item(root_node, "SerialNumber");
+ plist_dict_set_item(ret, "Serial Number", plist_copy(value_node));
+
+ /* FIXME Sync Settings? */
+
+ value_node = plist_dict_get_item(root_node, "UniqueDeviceID");
+ plist_dict_set_item(ret, "Target Identifier", plist_new_string(udid));
+
+ plist_dict_set_item(ret, "Target Type", plist_new_string("Device"));
+
+ /* uppercase */
+ udid_uppercase = string_toupper((char*)udid);
+ plist_dict_set_item(ret, "Unique Identifier", plist_new_string(udid_uppercase));
+ free(udid_uppercase);
+
+ char *data_buf = NULL;
+ uint64_t data_size = 0;
+ mobilebackup_afc_get_file_contents(afc, "/Books/iBooksData2.plist", &data_buf, &data_size);
+ if (data_buf) {
+ plist_dict_set_item(ret, "iBooks Data 2", plist_new_data(data_buf, data_size));
+ free(data_buf);
+ }
+
+ plist_t files = plist_new_dict();
+ const char *itunesfiles[] = {
+ "ApertureAlbumPrefs",
+ "IC-Info.sidb",
+ "IC-Info.sidv",
+ "PhotosFolderAlbums",
+ "PhotosFolderName",
+ "PhotosFolderPrefs",
+ "VoiceMemos.plist",
+ "iPhotoAlbumPrefs",
+ "iTunesApplicationIDs",
+ "iTunesPrefs",
+ "iTunesPrefs.plist",
+ NULL
+ };
+ int i = 0;
+ for (i = 0; itunesfiles[i]; i++) {
+ data_buf = NULL;
+ data_size = 0;
+ char *fname = (char*)malloc(strlen("/iTunes_Control/iTunes/") + strlen(itunesfiles[i]) + 1);
+ strcpy(fname, "/iTunes_Control/iTunes/");
+ strcat(fname, itunesfiles[i]);
+ mobilebackup_afc_get_file_contents(afc, fname, &data_buf, &data_size);
+ free(fname);
+ if (data_buf) {
+ plist_dict_set_item(files, itunesfiles[i], plist_new_data(data_buf, data_size));
+ free(data_buf);
+ }
+ }
+ plist_dict_set_item(ret, "iTunes Files", files);
+
+ plist_dict_set_item(ret, "iTunes Settings", itunes_settings ? plist_copy(itunes_settings) : plist_new_dict());
+
+ /* since we usually don't have iTunes, let's get the minimum required iTunes version from the device */
+ if (min_itunes_version) {
+ plist_dict_set_item(ret, "iTunes Version", plist_copy(min_itunes_version));
+ } else {
+ plist_dict_set_item(ret, "iTunes Version", plist_new_string("10.0.1"));
+ }
+
+ plist_free(itunes_settings);
+ plist_free(min_itunes_version);
+ plist_free(root_node);
+
+ return ret;
+}
+
+static int write_restore_applications(plist_t info_plist, afc_client_t afc)
+{
+ int res = -1;
+ uint64_t restore_applications_file = 0;
+ char * applications_plist_xml = NULL;
+ uint32_t applications_plist_xml_length = 0;
+
+ plist_t applications_plist = plist_dict_get_item(info_plist, "Applications");
+ if (!applications_plist) {
+ printf("No Applications in Info.plist, skipping creation of RestoreApplications.plist\n");
+ return 0;
+ }
+ plist_to_xml(applications_plist, &applications_plist_xml, &applications_plist_xml_length);
+ if (!applications_plist_xml) {
+ printf("Error preparing RestoreApplications.plist\n");
+ goto leave;
+ }
+
+ afc_error_t afc_err = 0;
+ afc_err = afc_make_directory(afc, "/iTunesRestore");
+ if (afc_err != AFC_E_SUCCESS) {
+ printf("Error creating directory /iTunesRestore, error code %d\n", afc_err);
+ goto leave;
+ }
+
+ afc_err = afc_file_open(afc, "/iTunesRestore/RestoreApplications.plist", AFC_FOPEN_WR, &restore_applications_file);
+ if (afc_err != AFC_E_SUCCESS || !restore_applications_file) {
+ printf("Error creating /iTunesRestore/RestoreApplications.plist, error code %d\n", afc_err);
+ goto leave;
+ }
+
+ uint32_t bytes_written = 0;
+ afc_err = afc_file_write(afc, restore_applications_file, applications_plist_xml, applications_plist_xml_length, &bytes_written);
+ if (afc_err != AFC_E_SUCCESS || bytes_written != applications_plist_xml_length) {
+ printf("Error writing /iTunesRestore/RestoreApplications.plist, error code %d, wrote %u of %u bytes\n", afc_err, bytes_written, applications_plist_xml_length);
+ goto leave;
+ }
+
+ afc_err = afc_file_close(afc, restore_applications_file);
+ restore_applications_file = 0;
+ if (afc_err != AFC_E_SUCCESS) {
+ goto leave;
+ }
+ /* success */
+ res = 0;
+
+leave:
+ free(applications_plist_xml);
+
+ if (restore_applications_file) {
+ afc_file_close(afc, restore_applications_file);
+ restore_applications_file = 0;
+ }
+
+ return res;
+}
+
+static int mb2_status_check_snapshot_state(const char *path, const char *udid, const char *matches)
+{
+ int ret = 0;
+ plist_t status_plist = NULL;
+ char *file_path = string_build_path(path, udid, "Status.plist", NULL);
+
+ plist_read_from_file(file_path, &status_plist, NULL);
+ free(file_path);
+ if (!status_plist) {
+ printf("Could not read Status.plist!\n");
+ return ret;
+ }
+ plist_t node = plist_dict_get_item(status_plist, "SnapshotState");
+ if (node && (plist_get_node_type(node) == PLIST_STRING)) {
+ char* sval = NULL;
+ plist_get_string_val(node, &sval);
+ if (sval) {
+ ret = (strcmp(sval, matches) == 0) ? 1 : 0;
+ free(sval);
+ }
+ } else {
+ printf("%s: ERROR could not get SnapshotState key from Status.plist!\n", __func__);
+ }
+ plist_free(status_plist);
+ return ret;
+}
+
+static void do_post_notification(idevice_t device, const char *notification)
+{
+ lockdownd_service_descriptor_t service = NULL;
+ np_client_t np;
+
+ lockdownd_client_t lockdown = NULL;
+
+ if (lockdownd_client_new_with_handshake(device, &lockdown, TOOL_NAME) != LOCKDOWN_E_SUCCESS) {
+ return;
+ }
+
+ lockdownd_error_t ldret = lockdownd_start_service(lockdown, NP_SERVICE_NAME, &service);
+ if (ldret == LOCKDOWN_E_SUCCESS) {
+ np_client_new(device, service, &np);
+ if (np) {
+ np_post_notification(np, notification);
+ np_client_free(np);
+ }
+ } else {
+ printf("ERROR: Could not start service %s: %s\n", NP_SERVICE_NAME, lockdownd_strerror(ldret));
+ }
+
+ if (service) {
+ lockdownd_service_descriptor_free(service);
+ service = NULL;
+ }
+ lockdownd_client_free(lockdown);
+}
+
+static void print_progress_real(double progress, int flush)
+{
+ int i = 0;
+ PRINT_VERBOSE(1, "\r[");
+ for(i = 0; i < 50; i++) {
+ if(i < progress / 2) {
+ PRINT_VERBOSE(1, "=");
+ } else {
+ PRINT_VERBOSE(1, " ");
+ }
+ }
+ PRINT_VERBOSE(1, "] %3.0f%%", progress);
+
+ if (flush > 0) {
+ fflush(stdout);
+ if (progress == 100)
+ PRINT_VERBOSE(1, "\n");
+ }
+}
+
+static void print_progress(uint64_t current, uint64_t total)
+{
+ char *format_size = NULL;
+ double progress = ((double)current/(double)total)*100;
+ if (progress < 0)
+ return;
+
+ if (progress > 100)
+ progress = 100;
+
+ print_progress_real((double)progress, 0);
+
+ format_size = string_format_size(current);
+ PRINT_VERBOSE(1, " (%s", format_size);
+ free(format_size);
+ format_size = string_format_size(total);
+ PRINT_VERBOSE(1, "/%s) ", format_size);
+ free(format_size);
+
+ fflush(stdout);
+ if (progress == 100)
+ PRINT_VERBOSE(1, "\n");
+}
+
+static double overall_progress = 0;
+
+static void mb2_set_overall_progress(double progress)
+{
+ if (progress > 0.0)
+ overall_progress = progress;
+}
+
+static void mb2_set_overall_progress_from_message(plist_t message, char* identifier)
+{
+ plist_t node = NULL;
+ double progress = 0.0;
+
+ if (!strcmp(identifier, "DLMessageDownloadFiles")) {
+ node = plist_array_get_item(message, 3);
+ } else if (!strcmp(identifier, "DLMessageUploadFiles")) {
+ node = plist_array_get_item(message, 2);
+ } else if (!strcmp(identifier, "DLMessageMoveFiles") || !strcmp(identifier, "DLMessageMoveItems")) {
+ node = plist_array_get_item(message, 3);
+ } else if (!strcmp(identifier, "DLMessageRemoveFiles") || !strcmp(identifier, "DLMessageRemoveItems")) {
+ node = plist_array_get_item(message, 3);
+ }
+
+ if (node != NULL) {
+ plist_get_real_val(node, &progress);
+ mb2_set_overall_progress(progress);
+ }
+}
+
+static void mb2_multi_status_add_file_error(plist_t status_dict, const char *path, int error_code, const char *error_message)
+{
+ if (!status_dict) return;
+ plist_t filedict = plist_new_dict();
+ plist_dict_set_item(filedict, "DLFileErrorString", plist_new_string(error_message));
+ plist_dict_set_item(filedict, "DLFileErrorCode", plist_new_uint(error_code));
+ plist_dict_set_item(status_dict, path, filedict);
+}
+
+static int errno_to_device_error(int errno_value)
+{
+ switch (errno_value) {
+ case ENOENT:
+ return -6;
+ case EEXIST:
+ return -7;
+ case ENOTDIR:
+ return -8;
+ case EISDIR:
+ return -9;
+ case ELOOP:
+ return -10;
+ case EIO:
+ return -11;
+ case ENOSPC:
+ return -15;
+ default:
+ return -1;
+ }
+}
+
+static int mb2_handle_send_file(mobilebackup2_client_t mobilebackup2, const char *backup_dir, const char *path, plist_t *errplist)
+{
+ uint32_t nlen = 0;
+ uint32_t pathlen = strlen(path);
+ uint32_t bytes = 0;
+ char *localfile = string_build_path(backup_dir, path, NULL);
+ char buf[32768];
+#ifdef WIN32
+ struct _stati64 fst;
+#else
+ struct stat fst;
+#endif
+
+ FILE *f = NULL;
+ uint32_t slen = 0;
+ int errcode = -1;
+ int result = -1;
+ uint32_t length;
+#ifdef WIN32
+ uint64_t total;
+ uint64_t sent;
+#else
+ off_t total;
+ off_t sent;
+#endif
+
+ mobilebackup2_error_t err;
+
+ /* send path length */
+ nlen = htobe32(pathlen);
+ err = mobilebackup2_send_raw(mobilebackup2, (const char*)&nlen, sizeof(nlen), &bytes);
+ if (err != MOBILEBACKUP2_E_SUCCESS) {
+ goto leave_proto_err;
+ }
+ if (bytes != (uint32_t)sizeof(nlen)) {
+ err = MOBILEBACKUP2_E_MUX_ERROR;
+ goto leave_proto_err;
+ }
+
+ /* send path */
+ err = mobilebackup2_send_raw(mobilebackup2, path, pathlen, &bytes);
+ if (err != MOBILEBACKUP2_E_SUCCESS) {
+ goto leave_proto_err;
+ }
+ if (bytes != pathlen) {
+ err = MOBILEBACKUP2_E_MUX_ERROR;
+ goto leave_proto_err;
+ }
+
+#ifdef WIN32
+ if (_stati64(localfile, &fst) < 0)
+#else
+ if (stat(localfile, &fst) < 0)
+#endif
+ {
+ if (errno != ENOENT)
+ printf("%s: stat failed on '%s': %d\n", __func__, localfile, errno);
+ errcode = errno;
+ goto leave;
+ }
+
+ total = fst.st_size;
+
+ char *format_size = string_format_size(total);
+ PRINT_VERBOSE(1, "Sending '%s' (%s)\n", path, format_size);
+ free(format_size);
+
+ if (total == 0) {
+ errcode = 0;
+ goto leave;
+ }
+
+ f = fopen(localfile, "rb");
+ if (!f) {
+ printf("%s: Error opening local file '%s': %d\n", __func__, localfile, errno);
+ errcode = errno;
+ goto leave;
+ }
+
+ sent = 0;
+ do {
+ length = ((total-sent) < (long long)sizeof(buf)) ? (uint32_t)total-sent : (uint32_t)sizeof(buf);
+ /* send data size (file size + 1) */
+ nlen = htobe32(length+1);
+ memcpy(buf, &nlen, sizeof(nlen));
+ buf[4] = CODE_FILE_DATA;
+ err = mobilebackup2_send_raw(mobilebackup2, (const char*)buf, 5, &bytes);
+ if (err != MOBILEBACKUP2_E_SUCCESS) {
+ goto leave_proto_err;
+ }
+ if (bytes != 5) {
+ goto leave_proto_err;
+ }
+
+ /* send file contents */
+ size_t r = fread(buf, 1, sizeof(buf), f);
+ if (r <= 0) {
+ printf("%s: read error\n", __func__);
+ errcode = errno;
+ goto leave;
+ }
+ err = mobilebackup2_send_raw(mobilebackup2, buf, r, &bytes);
+ if (err != MOBILEBACKUP2_E_SUCCESS) {
+ goto leave_proto_err;
+ }
+ if (bytes != (uint32_t)r) {
+ printf("Error: sent only %d of %d bytes\n", bytes, (int)r);
+ goto leave_proto_err;
+ }
+ sent += r;
+ } while (sent < total);
+ fclose(f);
+ f = NULL;
+ errcode = 0;
+
+leave:
+ if (errcode == 0) {
+ result = 0;
+ nlen = 1;
+ nlen = htobe32(nlen);
+ memcpy(buf, &nlen, 4);
+ buf[4] = CODE_SUCCESS;
+ mobilebackup2_send_raw(mobilebackup2, buf, 5, &bytes);
+ } else {
+ if (!*errplist) {
+ *errplist = plist_new_dict();
+ }
+ char *errdesc = strerror(errcode);
+ mb2_multi_status_add_file_error(*errplist, path, errno_to_device_error(errcode), errdesc);
+
+ length = strlen(errdesc);
+ nlen = htobe32(length+1);
+ memcpy(buf, &nlen, 4);
+ buf[4] = CODE_ERROR_LOCAL;
+ slen = 5;
+ memcpy(buf+slen, errdesc, length);
+ slen += length;
+ err = mobilebackup2_send_raw(mobilebackup2, (const char*)buf, slen, &bytes);
+ if (err != MOBILEBACKUP2_E_SUCCESS) {
+ printf("could not send message\n");
+ }
+ if (bytes != slen) {
+ printf("could only send %d from %d\n", bytes, slen);
+ }
+ }
+
+leave_proto_err:
+ if (f)
+ fclose(f);
+ free(localfile);
+ return result;
+}
+
+static void mb2_handle_send_files(mobilebackup2_client_t mobilebackup2, plist_t message, const char *backup_dir)
+{
+ uint32_t cnt;
+ uint32_t i = 0;
+ uint32_t sent;
+ plist_t errplist = NULL;
+
+ if (!message || (plist_get_node_type(message) != PLIST_ARRAY) || (plist_array_get_size(message) < 2) || !backup_dir) return;
+
+ plist_t files = plist_array_get_item(message, 1);
+ cnt = plist_array_get_size(files);
+
+ for (i = 0; i < cnt; i++) {
+ plist_t val = plist_array_get_item(files, i);
+ if (plist_get_node_type(val) != PLIST_STRING) {
+ continue;
+ }
+ char *str = NULL;
+ plist_get_string_val(val, &str);
+ if (!str)
+ continue;
+
+ if (mb2_handle_send_file(mobilebackup2, backup_dir, str, &errplist) < 0) {
+ free(str);
+ //printf("Error when sending file '%s' to device\n", str);
+ // TODO: perhaps we can continue, we've got a multi status response?!
+ break;
+ }
+ free(str);
+ }
+
+ /* send terminating 0 dword */
+ uint32_t zero = 0;
+ mobilebackup2_send_raw(mobilebackup2, (char*)&zero, 4, &sent);
+
+ if (!errplist) {
+ plist_t emptydict = plist_new_dict();
+ mobilebackup2_send_status_response(mobilebackup2, 0, NULL, emptydict);
+ plist_free(emptydict);
+ } else {
+ mobilebackup2_send_status_response(mobilebackup2, -13, "Multi status", errplist);
+ plist_free(errplist);
+ }
+}
+
+static int mb2_receive_filename(mobilebackup2_client_t mobilebackup2, char** filename)
+{
+ uint32_t nlen = 0;
+ uint32_t rlen = 0;
+
+ do {
+ nlen = 0;
+ rlen = 0;
+ mobilebackup2_receive_raw(mobilebackup2, (char*)&nlen, 4, &rlen);
+ nlen = be32toh(nlen);
+
+ if ((nlen == 0) && (rlen == 4)) {
+ // a zero length means no more files to receive
+ return 0;
+ }
+ if (rlen == 0) {
+ // device needs more time, waiting...
+ continue;
+ }
+ if (nlen > 4096) {
+ // filename length is too large
+ printf("ERROR: %s: too large filename length (%d)!\n", __func__, nlen);
+ return 0;
+ }
+
+ if (*filename != NULL) {
+ free(*filename);
+ *filename = NULL;
+ }
+
+ *filename = (char*)malloc(nlen+1);
+
+ rlen = 0;
+ mobilebackup2_receive_raw(mobilebackup2, *filename, nlen, &rlen);
+ if (rlen != nlen) {
+ printf("ERROR: %s: could not read filename\n", __func__);
+ return 0;
+ }
+
+ char* p = *filename;
+ p[rlen] = 0;
+
+ break;
+ } while(1 && !quit_flag);
+
+ return nlen;
+}
+
+static int mb2_handle_receive_files(mobilebackup2_client_t mobilebackup2, plist_t message, const char *backup_dir)
+{
+ uint64_t backup_real_size = 0;
+ uint64_t backup_total_size = 0;
+ uint32_t blocksize;
+ uint32_t bdone;
+ uint32_t rlen;
+ uint32_t nlen = 0;
+ uint32_t r;
+ char buf[32768];
+ char *fname = NULL;
+ char *dname = NULL;
+ char *bname = NULL;
+ char code = 0;
+ char last_code = 0;
+ plist_t node = NULL;
+ FILE *f = NULL;
+ unsigned int file_count = 0;
+ int errcode = 0;
+ char *errdesc = NULL;
+
+ if (!message || (plist_get_node_type(message) != PLIST_ARRAY) || plist_array_get_size(message) < 4 || !backup_dir) return 0;
+
+ node = plist_array_get_item(message, 3);
+ if (plist_get_node_type(node) == PLIST_UINT) {
+ plist_get_uint_val(node, &backup_total_size);
+ }
+ if (backup_total_size > 0) {
+ PRINT_VERBOSE(1, "Receiving files\n");
+ }
+
+ do {
+ if (quit_flag)
+ break;
+
+ nlen = mb2_receive_filename(mobilebackup2, &dname);
+ if (nlen == 0) {
+ break;
+ }
+
+ nlen = mb2_receive_filename(mobilebackup2, &fname);
+ if (!nlen) {
+ break;
+ }
+
+ if (bname != NULL) {
+ free(bname);
+ bname = NULL;
+ }
+
+ bname = string_build_path(backup_dir, fname, NULL);
+
+ if (fname != NULL) {
+ free(fname);
+ fname = NULL;
+ }
+
+ r = 0;
+ nlen = 0;
+ mobilebackup2_receive_raw(mobilebackup2, (char*)&nlen, 4, &r);
+ if (r != 4) {
+ printf("ERROR: %s: could not receive code length!\n", __func__);
+ break;
+ }
+ nlen = be32toh(nlen);
+
+ last_code = code;
+ code = 0;
+
+ mobilebackup2_receive_raw(mobilebackup2, &code, 1, &r);
+ if (r != 1) {
+ printf("ERROR: %s: could not receive code!\n", __func__);
+ break;
+ }
+
+ /* TODO remove this */
+ if ((code != CODE_SUCCESS) && (code != CODE_FILE_DATA) && (code != CODE_ERROR_REMOTE)) {
+ PRINT_VERBOSE(1, "Found new flag %02x\n", code);
+ }
+
+ remove_file(bname);
+ f = fopen(bname, "wb");
+ while (f && (code == CODE_FILE_DATA)) {
+ blocksize = nlen-1;
+ bdone = 0;
+ rlen = 0;
+ while (bdone < blocksize) {
+ if ((blocksize - bdone) < sizeof(buf)) {
+ rlen = blocksize - bdone;
+ } else {
+ rlen = sizeof(buf);
+ }
+ mobilebackup2_receive_raw(mobilebackup2, buf, rlen, &r);
+ if ((int)r <= 0) {
+ break;
+ }
+ fwrite(buf, 1, r, f);
+ bdone += r;
+ }
+ if (bdone == blocksize) {
+ backup_real_size += blocksize;
+ }
+ if (backup_total_size > 0) {
+ print_progress(backup_real_size, backup_total_size);
+ }
+ if (quit_flag)
+ break;
+ nlen = 0;
+ mobilebackup2_receive_raw(mobilebackup2, (char*)&nlen, 4, &r);
+ nlen = be32toh(nlen);
+ if (nlen > 0) {
+ last_code = code;
+ mobilebackup2_receive_raw(mobilebackup2, &code, 1, &r);
+ } else {
+ break;
+ }
+ }
+ if (f) {
+ fclose(f);
+ file_count++;
+ } else {
+ errcode = errno_to_device_error(errno);
+ errdesc = strerror(errno);
+ printf("Error opening '%s' for writing: %s\n", bname, errdesc);
+ break;
+ }
+ if (nlen == 0) {
+ break;
+ }
+
+ /* check if an error message was received */
+ if (code == CODE_ERROR_REMOTE) {
+ /* error message */
+ char *msg = (char*)malloc(nlen);
+ mobilebackup2_receive_raw(mobilebackup2, msg, nlen-1, &r);
+ msg[r] = 0;
+ /* If sent using CODE_FILE_DATA, end marker will be CODE_ERROR_REMOTE which is not an error! */
+ if (last_code != CODE_FILE_DATA) {
+ fprintf(stdout, "\nReceived an error message from device: %s\n", msg);
+ }
+ free(msg);
+ }
+ } while (1);
+
+ if (fname != NULL)
+ free(fname);
+
+ /* if there are leftovers to read, finish up cleanly */
+ if ((int)nlen-1 > 0) {
+ PRINT_VERBOSE(1, "\nDiscarding current data hunk.\n");
+ fname = (char*)malloc(nlen-1);
+ mobilebackup2_receive_raw(mobilebackup2, fname, nlen-1, &r);
+ free(fname);
+ remove_file(bname);
+ }
+
+ /* clean up */
+ if (bname != NULL)
+ free(bname);
+
+ if (dname != NULL)
+ free(dname);
+
+ plist_t empty_plist = plist_new_dict();
+ mobilebackup2_send_status_response(mobilebackup2, errcode, errdesc, empty_plist);
+ plist_free(empty_plist);
+
+ return file_count;
+}
+
+static void mb2_handle_list_directory(mobilebackup2_client_t mobilebackup2, plist_t message, const char *backup_dir)
+{
+ if (!message || (plist_get_node_type(message) != PLIST_ARRAY) || plist_array_get_size(message) < 2 || !backup_dir) return;
+
+ plist_t node = plist_array_get_item(message, 1);
+ char *str = NULL;
+ if (plist_get_node_type(node) == PLIST_STRING) {
+ plist_get_string_val(node, &str);
+ }
+ if (!str) {
+ printf("ERROR: Malformed DLContentsOfDirectory message\n");
+ // TODO error handling
+ return;
+ }
+
+ char *path = string_build_path(backup_dir, str, NULL);
+ free(str);
+
+ plist_t dirlist = plist_new_dict();
+
+ DIR* cur_dir = opendir(path);
+ if (cur_dir) {
+ struct dirent* ep;
+ while ((ep = readdir(cur_dir))) {
+ if ((strcmp(ep->d_name, ".") == 0) || (strcmp(ep->d_name, "..") == 0)) {
+ continue;
+ }
+ char *fpath = string_build_path(path, ep->d_name, NULL);
+ if (fpath) {
+ plist_t fdict = plist_new_dict();
+ struct stat st;
+ stat(fpath, &st);
+ const char *ftype = "DLFileTypeUnknown";
+ if (S_ISDIR(st.st_mode)) {
+ ftype = "DLFileTypeDirectory";
+ } else if (S_ISREG(st.st_mode)) {
+ ftype = "DLFileTypeRegular";
+ }
+ plist_dict_set_item(fdict, "DLFileType", plist_new_string(ftype));
+ plist_dict_set_item(fdict, "DLFileSize", plist_new_uint(st.st_size));
+ plist_dict_set_item(fdict, "DLFileModificationDate",
+ plist_new_date(st.st_mtime - MAC_EPOCH, 0));
+
+ plist_dict_set_item(dirlist, ep->d_name, fdict);
+ free(fpath);
+ }
+ }
+ closedir(cur_dir);
+ }
+ free(path);
+
+ /* TODO error handling */
+ mobilebackup2_error_t err = mobilebackup2_send_status_response(mobilebackup2, 0, NULL, dirlist);
+ plist_free(dirlist);
+ if (err != MOBILEBACKUP2_E_SUCCESS) {
+ printf("Could not send status response, error %d\n", err);
+ }
+}
+
+static void mb2_handle_make_directory(mobilebackup2_client_t mobilebackup2, plist_t message, const char *backup_dir)
+{
+ if (!message || (plist_get_node_type(message) != PLIST_ARRAY) || plist_array_get_size(message) < 2 || !backup_dir) return;
+
+ plist_t dir = plist_array_get_item(message, 1);
+ char *str = NULL;
+ int errcode = 0;
+ char *errdesc = NULL;
+ plist_get_string_val(dir, &str);
+
+ char *newpath = string_build_path(backup_dir, str, NULL);
+ free(str);
+
+ if (mkdir_with_parents(newpath, 0755) < 0) {
+ errdesc = strerror(errno);
+ if (errno != EEXIST) {
+ printf("mkdir: %s (%d)\n", errdesc, errno);
+ }
+ errcode = errno_to_device_error(errno);
+ }
+ free(newpath);
+ mobilebackup2_error_t err = mobilebackup2_send_status_response(mobilebackup2, errcode, errdesc, NULL);
+ if (err != MOBILEBACKUP2_E_SUCCESS) {
+ printf("Could not send status response, error %d\n", err);
+ }
+}
+
+static void mb2_copy_file_by_path(const char *src, const char *dst)
+{
+ FILE *from, *to;
+ char buf[BUFSIZ];
+ size_t length;
+
+ /* open source file */
+ if ((from = fopen(src, "rb")) == NULL) {
+ printf("Cannot open source path '%s'.\n", src);
+ return;
+ }
+
+ /* open destination file */
+ if ((to = fopen(dst, "wb")) == NULL) {
+ printf("Cannot open destination file '%s'.\n", dst);
+ fclose(from);
+ return;
+ }
+
+ /* copy the file */
+ while ((length = fread(buf, 1, BUFSIZ, from)) != 0) {
+ fwrite(buf, 1, length, to);
+ }
+
+ if(fclose(from) == EOF) {
+ printf("Error closing source file.\n");
+ }
+
+ if(fclose(to) == EOF) {
+ printf("Error closing destination file.\n");
+ }
+}
+
+static void mb2_copy_directory_by_path(const char *src, const char *dst)
+{
+ if (!src || !dst) {
+ return;
+ }
+
+ struct stat st;
+
+ /* if src does not exist */
+ if ((stat(src, &st) < 0) || !S_ISDIR(st.st_mode)) {
+ printf("ERROR: Source directory does not exist '%s': %s (%d)\n", src, strerror(errno), errno);
+ return;
+ }
+
+ /* if dst directory does not exist */
+ if ((stat(dst, &st) < 0) || !S_ISDIR(st.st_mode)) {
+ /* create it */
+ if (mkdir_with_parents(dst, 0755) < 0) {
+ printf("ERROR: Unable to create destination directory '%s': %s (%d)\n", dst, strerror(errno), errno);
+ return;
+ }
+ }
+
+ /* loop over src directory contents */
+ DIR *cur_dir = opendir(src);
+ if (cur_dir) {
+ struct dirent* ep;
+ while ((ep = readdir(cur_dir))) {
+ if ((strcmp(ep->d_name, ".") == 0) || (strcmp(ep->d_name, "..") == 0)) {
+ continue;
+ }
+ char *srcpath = string_build_path(src, ep->d_name, NULL);
+ char *dstpath = string_build_path(dst, ep->d_name, NULL);
+ if (srcpath && dstpath) {
+ /* copy file */
+ mb2_copy_file_by_path(srcpath, dstpath);
+ }
+
+ if (srcpath)
+ free(srcpath);
+ if (dstpath)
+ free(dstpath);
+ }
+ closedir(cur_dir);
+ }
+}
+
+#ifdef WIN32
+#define BS_CC '\b'
+#define my_getch getch
+#else
+#define BS_CC 0x7f
+static int my_getch(void)
+{
+ struct termios oldt, newt;
+ int ch;
+ tcgetattr(STDIN_FILENO, &oldt);
+ newt = oldt;
+ newt.c_lflag &= ~(ICANON | ECHO);
+ tcsetattr(STDIN_FILENO, TCSANOW, &newt);
+ ch = getchar();
+ tcsetattr(STDIN_FILENO, TCSANOW, &oldt);
+ return ch;
+}
+#endif
+
+static void get_hidden_input(char *buf, int maxlen)
+{
+ int pwlen = 0;
+ int c;
+
+ while ((c = my_getch())) {
+ if ((c == '\r') || (c == '\n')) {
+ break;
+ }
+ if (isprint(c)) {
+ if (pwlen < maxlen-1)
+ buf[pwlen++] = c;
+ fputc('*', stderr);
+ } else if (c == BS_CC) {
+ if (pwlen > 0) {
+ fputs("\b \b", stderr);
+ pwlen--;
+ }
+ }
+ }
+ buf[pwlen] = 0;
+}
+
+static char* ask_for_password(const char* msg, int type_again)
+{
+ char pwbuf[256];
+
+ fprintf(stderr, "%s: ", msg);
+ fflush(stderr);
+ get_hidden_input(pwbuf, 256);
+ fputc('\n', stderr);
+
+ if (type_again) {
+ char pwrep[256];
+
+ fprintf(stderr, "%s (repeat): ", msg);
+ fflush(stderr);
+ get_hidden_input(pwrep, 256);
+ fputc('\n', stderr);
+
+ if (strcmp(pwbuf, pwrep) != 0) {
+ printf("ERROR: passwords don't match\n");
+ return NULL;
+ }
+ }
+ return strdup(pwbuf);
+}
+
+/**
+ * signal handler function for cleaning up properly
+ */
+static void clean_exit(int sig)
+{
+ fprintf(stderr, "Exiting...\n");
+ quit_flag++;
+}
+
+static void print_usage(int argc, char **argv, int is_error)
+{
+ char *name = strrchr(argv[0], '/');
+ fprintf(is_error ? stderr : stdout, "Usage: %s [OPTIONS] CMD [CMDOPTIONS] DIRECTORY\n", (name ? name + 1: argv[0]));
+ fprintf(is_error ? stderr : stdout,
+ "\n"
+ "Create or restore backup in/from the specified directory.\n"
+ "\n"
+ "CMD:\n"
+ " backup create backup for the device\n"
+ " --full force full backup from device.\n"
+ " restore restore last backup to the device\n"
+ " --system restore system files, too.\n"
+ " --no-reboot do NOT reboot the device when done (default: yes).\n"
+ " --copy create a copy of backup folder before restoring.\n"
+ " --settings restore device settings from the backup.\n"
+ " --remove remove items which are not being restored\n"
+ " --skip-apps do not trigger re-installation of apps after restore\n"
+ " --password PWD supply the password for the encrypted source backup\n"
+ " info show details about last completed backup of device\n"
+ " list list files of last completed backup in CSV format\n"
+ " unback unpack a completed backup in DIRECTORY/_unback_/\n"
+ " encryption on|off [PWD] enable or disable backup encryption\n"
+ " changepw [OLD NEW] change backup password on target device\n"
+ " cloud on|off enable or disable cloud use (requires iCloud account)\n"
+ "\n"
+ "NOTE: Passwords will be requested in interactive mode (-i) if omitted, or can\n"
+ "be passed via environment variable BACKUP_PASSWORD/BACKUP_PASSWORD_NEW.\n"
+ "See man page for further details.\n"
+ "\n"
+ "OPTIONS:\n"
+ " -u, --udid UDID target specific device by UDID\n"
+ " -s, --source UDID use backup data from device specified by UDID\n"
+ " -n, --network connect to network device\n"
+ " -i, --interactive request passwords interactively\n"
+ " -d, --debug enable communication debugging\n"
+ " -h, --help prints usage information\n"
+ " -v, --version prints version information\n"
+ "\n"
+ "Homepage: <" PACKAGE_URL ">\n"
+ "Bug Reports: <" PACKAGE_BUGREPORT ">\n"
+ );
+}
+
+#define DEVICE_VERSION(maj, min, patch) ((((maj) & 0xFF) << 16) | (((min) & 0xFF) << 8) | ((patch) & 0xFF))
+
+int main(int argc, char *argv[])
+{
+ idevice_error_t ret = IDEVICE_E_UNKNOWN_ERROR;
+ lockdownd_error_t ldret = LOCKDOWN_E_UNKNOWN_ERROR;
+ int i = 0;
+ char* udid = NULL;
+ char* source_udid = NULL;
+ int use_network = 0;
+ lockdownd_service_descriptor_t service = NULL;
+ int cmd = -1;
+ int cmd_flags = 0;
+ int is_full_backup = 0;
+ int result_code = -1;
+ char* backup_directory = NULL;
+ int interactive_mode = 0;
+ char* backup_password = NULL;
+ char* newpw = NULL;
+ struct stat st;
+ plist_t node_tmp = NULL;
+ plist_t info_plist = NULL;
+ plist_t opts = NULL;
+
+ idevice_t device = NULL;
+ afc_client_t afc = NULL;
+ np_client_t np = NULL;
+ lockdownd_client_t lockdown = NULL;
+ mobilebackup2_client_t mobilebackup2 = NULL;
+ mobilebackup2_error_t err;
+ uint64_t lockfile = 0;
+
+#define OPT_SYSTEM 1
+#define OPT_REBOOT 2
+#define OPT_NO_REBOOT 3
+#define OPT_COPY 4
+#define OPT_SETTINGS 5
+#define OPT_REMOVE 6
+#define OPT_SKIP_APPS 7
+#define OPT_PASSWORD 8
+#define OPT_FULL 9
+
+ int c = 0;
+ const struct option longopts[] = {
+ { "debug", no_argument, NULL, 'd' },
+ { "help", no_argument, NULL, 'h' },
+ { "udid", required_argument, NULL, 'u' },
+ { "source", required_argument, NULL, 's' },
+ { "interactive", no_argument, NULL, 'i' },
+ { "network", no_argument, NULL, 'n' },
+ { "version", no_argument, NULL, 'v' },
+ // command options:
+ { "system", no_argument, NULL, OPT_SYSTEM },
+ { "reboot", no_argument, NULL, OPT_REBOOT },
+ { "no-reboot", no_argument, NULL, OPT_NO_REBOOT },
+ { "copy", no_argument, NULL, OPT_COPY },
+ { "settings", no_argument, NULL, OPT_SETTINGS },
+ { "remove", no_argument, NULL, OPT_REMOVE },
+ { "skip-apps", no_argument, NULL, OPT_SKIP_APPS },
+ { "password", required_argument, NULL, OPT_PASSWORD },
+ { "full", no_argument, NULL, OPT_FULL },
+ { NULL, 0, NULL, 0}
+ };
+
+ /* we need to exit cleanly on running backups and restores or we cause havok */
+ signal(SIGINT, clean_exit);
+ signal(SIGTERM, clean_exit);
+#ifndef WIN32
+ signal(SIGQUIT, clean_exit);
+ signal(SIGPIPE, SIG_IGN);
+#endif
+
+ /* parse cmdline args */
+ while ((c = getopt_long(argc, argv, "dhu:s:inv", longopts, NULL)) != -1) {
+ switch (c) {
+ case 'd':
+ idevice_set_debug_level(1);
+ break;
+ case 'u':
+ if (!*optarg) {
+ fprintf(stderr, "ERROR: UDID argument must not be empty!\n");
+ print_usage(argc, argv, 1);
+ return 2;
+ }
+ udid = strdup(optarg);
+ break;
+ case 's':
+ if (!*optarg) {
+ fprintf(stderr, "ERROR: SOURCE argument must not be empty!\n");
+ print_usage(argc, argv, 1);
+ return 2;
+ }
+ source_udid = strdup(optarg);
+ break;
+ case 'i':
+ interactive_mode = 1;
+ break;
+ case 'n':
+ use_network = 1;
+ break;
+ case 'h':
+ print_usage(argc, argv, 0);
+ return 0;
+ case 'v':
+ printf("%s %s\n", TOOL_NAME, PACKAGE_VERSION);
+ return 0;
+ case OPT_SYSTEM:
+ cmd_flags |= CMD_FLAG_RESTORE_SYSTEM_FILES;
+ break;
+ case OPT_REBOOT:
+ cmd_flags &= ~CMD_FLAG_RESTORE_NO_REBOOT;
+ break;
+ case OPT_NO_REBOOT:
+ cmd_flags |= CMD_FLAG_RESTORE_NO_REBOOT;
+ break;
+ case OPT_COPY:
+ cmd_flags |= CMD_FLAG_RESTORE_COPY_BACKUP;
+ break;
+ case OPT_SETTINGS:
+ cmd_flags |= CMD_FLAG_RESTORE_SETTINGS;
+ break;
+ case OPT_REMOVE:
+ cmd_flags |= CMD_FLAG_RESTORE_REMOVE_ITEMS;
+ break;
+ case OPT_SKIP_APPS:
+ cmd_flags |= CMD_FLAG_RESTORE_SKIP_APPS;
+ break;
+ case OPT_PASSWORD:
+ free(backup_password);
+ backup_password = strdup(optarg);
+ break;
+ case OPT_FULL:
+ cmd_flags |= CMD_FLAG_FORCE_FULL_BACKUP;
+ break;
+ default:
+ print_usage(argc, argv, 1);
+ return 2;
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (!argv[0]) {
+ fprintf(stderr, "ERROR: No command specified.\n");
+ print_usage(argc+optind, argv-optind, 1);
+ return 2;
+ }
+
+ if (!strcmp(argv[0], "backup")) {
+ cmd = CMD_BACKUP;
+ }
+ else if (!strcmp(argv[0], "restore")) {
+ cmd = CMD_RESTORE;
+ }
+ else if (!strcmp(argv[0], "cloud")) {
+ cmd = CMD_CLOUD;
+ i = 1;
+ if (!argv[i]) {
+ fprintf(stderr, "ERROR: No argument given for cloud command; requires either 'on' or 'off'.\n");
+ print_usage(argc+optind, argv-optind, 1);
+ return 2;
+ }
+ if (!strcmp(argv[i], "on")) {
+ cmd_flags |= CMD_FLAG_CLOUD_ENABLE;
+ } else if (!strcmp(argv[i], "off")) {
+ cmd_flags |= CMD_FLAG_CLOUD_DISABLE;
+ } else {
+ fprintf(stderr, "ERROR: Invalid argument '%s' for cloud command; must be either 'on' or 'off'.\n", argv[i]);
+ print_usage(argc+optind, argv-optind, 1);
+ return 2;
+ }
+ }
+ else if (!strcmp(argv[0], "info")) {
+ cmd = CMD_INFO;
+ verbose = 0;
+ }
+ else if (!strcmp(argv[0], "list")) {
+ cmd = CMD_LIST;
+ verbose = 0;
+ }
+ else if (!strcmp(argv[0], "unback")) {
+ cmd = CMD_UNBACK;
+ }
+ else if (!strcmp(argv[0], "encryption")) {
+ cmd = CMD_CHANGEPW;
+ i = 1;
+ if (!argv[i]) {
+ fprintf(stderr, "ERROR: No argument given for encryption command; requires either 'on' or 'off'.\n");
+ print_usage(argc+optind, argv-optind, 1);
+ return 2;
+ }
+ if (!strcmp(argv[i], "on")) {
+ cmd_flags |= CMD_FLAG_ENCRYPTION_ENABLE;
+ } else if (!strcmp(argv[i], "off")) {
+ cmd_flags |= CMD_FLAG_ENCRYPTION_DISABLE;
+ } else {
+ fprintf(stderr, "ERROR: Invalid argument '%s' for encryption command; must be either 'on' or 'off'.\n", argv[i]);
+ print_usage(argc+optind, argv-optind, 1);
+ return 2;
+ }
+ // check if a password was given on the command line
+ free(newpw);
+ newpw = NULL;
+ free(backup_password);
+ backup_password = NULL;
+ i++;
+ if (argv[i]) {
+ if (cmd_flags & CMD_FLAG_ENCRYPTION_ENABLE) {
+ newpw = strdup(argv[i]);
+ } else if (cmd_flags & CMD_FLAG_ENCRYPTION_DISABLE) {
+ backup_password = strdup(argv[i]);
+ }
+ }
+ }
+ else if (!strcmp(argv[0], "changepw")) {
+ cmd = CMD_CHANGEPW;
+ cmd_flags |= CMD_FLAG_ENCRYPTION_CHANGEPW;
+ // check if passwords were given on command line
+ free(newpw);
+ newpw = NULL;
+ free(backup_password);
+ backup_password = NULL;
+ i = 1;
+ if (argv[i]) {
+ backup_password = strdup(argv[i]);
+ i++;
+ if (!argv[i]) {
+ fprintf(stderr, "ERROR: Old and new passwords have to be passed as arguments for the changepw command\n");
+ print_usage(argc+optind, argv-optind, 1);
+ return 2;
+ }
+ newpw = strdup(argv[i]);
+ }
+ }
+
+ i++;
+ if (argv[i]) {
+ backup_directory = argv[i];
+ }
+
+ /* verify options */
+ if (cmd == -1) {
+ fprintf(stderr, "ERROR: Unsupported command '%s'.\n", argv[0]);
+ print_usage(argc+optind, argv-optind, 1);
+ return 2;
+ }
+
+ if (cmd == CMD_CHANGEPW || cmd == CMD_CLOUD) {
+ backup_directory = (char*)".this_folder_is_not_present_on_purpose";
+ } else {
+ if (backup_directory == NULL) {
+ fprintf(stderr, "ERROR: No target backup directory specified.\n");
+ print_usage(argc+optind, argv-optind, 1);
+ return 2;
+ }
+
+ /* verify if passed backup directory exists */
+ if (stat(backup_directory, &st) != 0) {
+ fprintf(stderr, "ERROR: Backup directory \"%s\" does not exist!\n", backup_directory);
+ return -1;
+ }
+ }
+
+ ret = idevice_new_with_options(&device, udid, (use_network) ? IDEVICE_LOOKUP_NETWORK : IDEVICE_LOOKUP_USBMUX);
+ if (ret != IDEVICE_E_SUCCESS) {
+ if (udid) {
+ printf("No device found with udid %s.\n", udid);
+ } else {
+ printf("No device found.\n");
+ }
+ return -1;
+ }
+
+ if (!udid) {
+ idevice_get_udid(device, &udid);
+ }
+
+ if (!source_udid) {
+ source_udid = strdup(udid);
+ }
+
+ uint8_t is_encrypted = 0;
+ char *info_path = NULL;
+ if (cmd == CMD_CHANGEPW) {
+ if (!interactive_mode) {
+ if (!newpw) {
+ newpw = getenv("BACKUP_PASSWORD_NEW");
+ if (newpw) {
+ newpw = strdup(newpw);
+ }
+ }
+ if (!backup_password) {
+ backup_password = getenv("BACKUP_PASSWORD");
+ if (backup_password) {
+ backup_password = strdup(backup_password);
+ }
+ }
+ }
+ if (!interactive_mode && !backup_password && !newpw) {
+ idevice_free(device);
+ printf("ERROR: Can't get password input in non-interactive mode. Either pass password(s) on the command line, or enable interactive mode with -i or --interactive.\n");
+ return -1;
+ }
+ } else if (cmd != CMD_CLOUD) {
+ /* backup directory must contain an Info.plist */
+ info_path = string_build_path(backup_directory, source_udid, "Info.plist", NULL);
+ if (cmd == CMD_RESTORE || cmd == CMD_UNBACK) {
+ if (stat(info_path, &st) != 0) {
+ idevice_free(device);
+ free(info_path);
+ printf("ERROR: Backup directory \"%s\" is invalid. No Info.plist found for UDID %s.\n", backup_directory, source_udid);
+ return -1;
+ }
+ char* manifest_path = string_build_path(backup_directory, source_udid, "Manifest.plist", NULL);
+ if (stat(manifest_path, &st) != 0) {
+ free(info_path);
+ }
+ plist_t manifest_plist = NULL;
+ plist_read_from_file(manifest_path, &manifest_plist, NULL);
+ if (!manifest_plist) {
+ idevice_free(device);
+ free(info_path);
+ free(manifest_path);
+ printf("ERROR: Backup directory \"%s\" is invalid. No Manifest.plist found for UDID %s.\n", backup_directory, source_udid);
+ return -1;
+ }
+ node_tmp = plist_dict_get_item(manifest_plist, "IsEncrypted");
+ if (node_tmp && (plist_get_node_type(node_tmp) == PLIST_BOOLEAN)) {
+ plist_get_bool_val(node_tmp, &is_encrypted);
+ }
+ plist_free(manifest_plist);
+ free(manifest_path);
+ }
+ PRINT_VERBOSE(1, "Backup directory is \"%s\"\n", backup_directory);
+ }
+
+ if (cmd != CMD_CLOUD && is_encrypted) {
+ PRINT_VERBOSE(1, "This is an encrypted backup.\n");
+ if (backup_password == NULL) {
+ backup_password = getenv("BACKUP_PASSWORD");
+ if (backup_password) {
+ backup_password = strdup(backup_password);
+ }
+ }
+ if (backup_password == NULL) {
+ if (interactive_mode) {
+ backup_password = ask_for_password("Enter backup password", 0);
+ }
+ if (!backup_password || (strlen(backup_password) == 0)) {
+ if (backup_password) {
+ free(backup_password);
+ }
+ idevice_free(device);
+ if (cmd == CMD_RESTORE) {
+ printf("ERROR: a backup password is required to restore an encrypted backup. Cannot continue.\n");
+ } else if (cmd == CMD_UNBACK) {
+ printf("ERROR: a backup password is required to unback an encrypted backup. Cannot continue.\n");
+ }
+ return -1;
+ }
+ }
+ }
+
+ if (LOCKDOWN_E_SUCCESS != (ldret = lockdownd_client_new_with_handshake(device, &lockdown, TOOL_NAME))) {
+ printf("ERROR: Could not connect to lockdownd, error code %d\n", ldret);
+ idevice_free(device);
+ return -1;
+ }
+
+ uint8_t willEncrypt = 0;
+ node_tmp = NULL;
+ lockdownd_get_value(lockdown, "com.apple.mobile.backup", "WillEncrypt", &node_tmp);
+ if (node_tmp) {
+ if (plist_get_node_type(node_tmp) == PLIST_BOOLEAN) {
+ plist_get_bool_val(node_tmp, &willEncrypt);
+ }
+ plist_free(node_tmp);
+ node_tmp = NULL;
+ }
+
+ /* get ProductVersion */
+ char *product_version = NULL;
+ int device_version = 0;
+ node_tmp = NULL;
+ lockdownd_get_value(lockdown, NULL, "ProductVersion", &node_tmp);
+ if (node_tmp) {
+ if (plist_get_node_type(node_tmp) == PLIST_STRING) {
+ plist_get_string_val(node_tmp, &product_version);
+ }
+ plist_free(node_tmp);
+ node_tmp = NULL;
+ }
+ if (product_version) {
+ int vers[3] = { 0, 0, 0 };
+ if (sscanf(product_version, "%d.%d.%d", &vers[0], &vers[1], &vers[2]) >= 2) {
+ device_version = DEVICE_VERSION(vers[0], vers[1], vers[2]);
+ }
+ }
+
+ /* start notification_proxy */
+ ldret = lockdownd_start_service(lockdown, NP_SERVICE_NAME, &service);
+ if ((ldret == LOCKDOWN_E_SUCCESS) && service && service->port) {
+ np_client_new(device, service, &np);
+ np_set_notify_callback(np, notify_cb, NULL);
+ const char *noties[5] = {
+ NP_SYNC_CANCEL_REQUEST,
+ NP_SYNC_SUSPEND_REQUEST,
+ NP_SYNC_RESUME_REQUEST,
+ NP_BACKUP_DOMAIN_CHANGED,
+ NULL
+ };
+ np_observe_notifications(np, noties);
+ } else {
+ printf("ERROR: Could not start service %s: %s\n", NP_SERVICE_NAME, lockdownd_strerror(ldret));
+ cmd = CMD_LEAVE;
+ goto checkpoint;
+ }
+ if (service) {
+ lockdownd_service_descriptor_free(service);
+ service = NULL;
+ }
+
+ if (cmd == CMD_BACKUP || cmd == CMD_RESTORE) {
+ /* start AFC, we need this for the lock file */
+ ldret = lockdownd_start_service(lockdown, AFC_SERVICE_NAME, &service);
+ if ((ldret == LOCKDOWN_E_SUCCESS) && service->port) {
+ afc_client_new(device, service, &afc);
+ } else {
+ printf("ERROR: Could not start service %s: %s\n", AFC_SERVICE_NAME, lockdownd_strerror(ldret));
+ cmd = CMD_LEAVE;
+ goto checkpoint;
+ }
+ }
+
+ if (service) {
+ lockdownd_service_descriptor_free(service);
+ service = NULL;
+ }
+
+ /* start mobilebackup service and retrieve port */
+ ldret = lockdownd_start_service_with_escrow_bag(lockdown, MOBILEBACKUP2_SERVICE_NAME, &service);
+ lockdownd_client_free(lockdown);
+ lockdown = NULL;
+ if ((ldret == LOCKDOWN_E_SUCCESS) && service && service->port) {
+ PRINT_VERBOSE(1, "Started \"%s\" service on port %d.\n", MOBILEBACKUP2_SERVICE_NAME, service->port);
+ mobilebackup2_client_new(device, service, &mobilebackup2);
+
+ if (service) {
+ lockdownd_service_descriptor_free(service);
+ service = NULL;
+ }
+
+ /* send Hello message */
+ double local_versions[2] = {2.0, 2.1};
+ double remote_version = 0.0;
+ err = mobilebackup2_version_exchange(mobilebackup2, local_versions, 2, &remote_version);
+ if (err != MOBILEBACKUP2_E_SUCCESS) {
+ printf("Could not perform backup protocol version exchange, error code %d\n", err);
+ cmd = CMD_LEAVE;
+ goto checkpoint;
+ }
+
+ PRINT_VERBOSE(1, "Negotiated Protocol Version %.1f\n", remote_version);
+
+ /* check abort conditions */
+ if (quit_flag > 0) {
+ PRINT_VERBOSE(1, "Aborting as requested by user...\n");
+ cmd = CMD_LEAVE;
+ goto checkpoint;
+ }
+
+ /* verify existing Info.plist */
+ if (info_path && (stat(info_path, &st) == 0) && cmd != CMD_CLOUD) {
+ PRINT_VERBOSE(1, "Reading Info.plist from backup.\n");
+ plist_read_from_file(info_path, &info_plist, NULL);
+
+ if (!info_plist) {
+ printf("Could not read Info.plist\n");
+ is_full_backup = 1;
+ }
+ } else {
+ if (cmd == CMD_RESTORE) {
+ printf("Aborting restore. Info.plist is missing.\n");
+ cmd = CMD_LEAVE;
+ } else {
+ is_full_backup = 1;
+ }
+ }
+
+ if (cmd == CMD_BACKUP || cmd == CMD_RESTORE) {
+ do_post_notification(device, NP_SYNC_WILL_START);
+ afc_file_open(afc, "/com.apple.itunes.lock_sync", AFC_FOPEN_RW, &lockfile);
+ }
+ if (lockfile) {
+ afc_error_t aerr;
+ do_post_notification(device, NP_SYNC_LOCK_REQUEST);
+ for (i = 0; i < LOCK_ATTEMPTS; i++) {
+ aerr = afc_file_lock(afc, lockfile, AFC_LOCK_EX);
+ if (aerr == AFC_E_SUCCESS) {
+ do_post_notification(device, NP_SYNC_DID_START);
+ break;
+ }
+ if (aerr == AFC_E_OP_WOULD_BLOCK) {
+ usleep(LOCK_WAIT);
+ continue;
+ }
+
+ fprintf(stderr, "ERROR: could not lock file! error code: %d\n", aerr);
+ afc_file_close(afc, lockfile);
+ lockfile = 0;
+ cmd = CMD_LEAVE;
+ }
+ if (i == LOCK_ATTEMPTS) {
+ fprintf(stderr, "ERROR: timeout while locking for sync\n");
+ afc_file_close(afc, lockfile);
+ lockfile = 0;
+ cmd = CMD_LEAVE;
+ }
+ }
+
+checkpoint:
+
+ switch(cmd) {
+ case CMD_CLOUD:
+ opts = plist_new_dict();
+ plist_dict_set_item(opts, "CloudBackupState", plist_new_bool(cmd_flags & CMD_FLAG_CLOUD_ENABLE ? 1: 0));
+ err = mobilebackup2_send_request(mobilebackup2, "EnableCloudBackup", udid, source_udid, opts);
+ plist_free(opts);
+ opts = NULL;
+ if (err != MOBILEBACKUP2_E_SUCCESS) {
+ printf("Error setting cloud backup state on device, error code %d\n", err);
+ cmd = CMD_LEAVE;
+ }
+ break;
+ case CMD_BACKUP:
+ PRINT_VERBOSE(1, "Starting backup...\n");
+
+ /* make sure backup device sub-directory exists */
+ char* devbackupdir = string_build_path(backup_directory, source_udid, NULL);
+ __mkdir(devbackupdir, 0755);
+ free(devbackupdir);
+
+ if (strcmp(source_udid, udid) != 0) {
+ /* handle different source backup directory */
+ // make sure target backup device sub-directory exists
+ devbackupdir = string_build_path(backup_directory, udid, NULL);
+ __mkdir(devbackupdir, 0755);
+ free(devbackupdir);
+
+ // use Info.plist path in target backup folder */
+ free(info_path);
+ info_path = string_build_path(backup_directory, udid, "Info.plist", NULL);
+ }
+
+ /* TODO: check domain com.apple.mobile.backup key RequiresEncrypt and WillEncrypt with lockdown */
+ /* TODO: verify battery on AC enough battery remaining */
+
+ /* re-create Info.plist (Device infos, IC-Info.sidb, photos, app_ids, iTunesPrefs) */
+ if (info_plist) {
+ plist_free(info_plist);
+ info_plist = NULL;
+ }
+ info_plist = mobilebackup_factory_info_plist_new(udid, device, afc);
+ if (!info_plist) {
+ fprintf(stderr, "Failed to generate Info.plist - aborting\n");
+ cmd = CMD_LEAVE;
+ }
+ remove_file(info_path);
+ plist_write_to_file(info_plist, info_path, PLIST_FORMAT_XML, 0);
+ free(info_path);
+
+ plist_free(info_plist);
+ info_plist = NULL;
+
+ if (cmd_flags & CMD_FLAG_FORCE_FULL_BACKUP) {
+ PRINT_VERBOSE(1, "Enforcing full backup from device.\n");
+ opts = plist_new_dict();
+ plist_dict_set_item(opts, "ForceFullBackup", plist_new_bool(1));
+ }
+ /* request backup from device with manifest from last backup */
+ if (willEncrypt) {
+ PRINT_VERBOSE(1, "Backup will be encrypted.\n");
+ } else {
+ PRINT_VERBOSE(1, "Backup will be unencrypted.\n");
+ }
+ PRINT_VERBOSE(1, "Requesting backup from device...\n");
+ err = mobilebackup2_send_request(mobilebackup2, "Backup", udid, source_udid, opts);
+ if (opts)
+ plist_free(opts);
+ if (err == MOBILEBACKUP2_E_SUCCESS) {
+ if (is_full_backup) {
+ PRINT_VERBOSE(1, "Full backup mode.\n");
+ } else {
+ PRINT_VERBOSE(1, "Incremental backup mode.\n");
+ }
+ } else {
+ if (err == MOBILEBACKUP2_E_BAD_VERSION) {
+ printf("ERROR: Could not start backup process: backup protocol version mismatch!\n");
+ } else if (err == MOBILEBACKUP2_E_REPLY_NOT_OK) {
+ printf("ERROR: Could not start backup process: device refused to start the backup process.\n");
+ } else {
+ printf("ERROR: Could not start backup process: unspecified error occurred\n");
+ }
+ cmd = CMD_LEAVE;
+ }
+ break;
+ case CMD_RESTORE:
+ /* TODO: verify battery on AC enough battery remaining */
+
+ /* verify if Status.plist says we read from an successful backup */
+ if (!mb2_status_check_snapshot_state(backup_directory, source_udid, "finished")) {
+ printf("ERROR: Cannot ensure we restore from a successful backup. Aborting.\n");
+ cmd = CMD_LEAVE;
+ break;
+ }
+
+ PRINT_VERBOSE(1, "Starting Restore...\n");
+
+ opts = plist_new_dict();
+ plist_dict_set_item(opts, "RestoreSystemFiles", plist_new_bool(cmd_flags & CMD_FLAG_RESTORE_SYSTEM_FILES));
+ PRINT_VERBOSE(1, "Restoring system files: %s\n", (cmd_flags & CMD_FLAG_RESTORE_SYSTEM_FILES ? "Yes":"No"));
+ if (cmd_flags & CMD_FLAG_RESTORE_NO_REBOOT)
+ plist_dict_set_item(opts, "RestoreShouldReboot", plist_new_bool(0));
+ PRINT_VERBOSE(1, "Rebooting after restore: %s\n", (cmd_flags & CMD_FLAG_RESTORE_NO_REBOOT ? "No":"Yes"));
+ if ((cmd_flags & CMD_FLAG_RESTORE_COPY_BACKUP) == 0)
+ plist_dict_set_item(opts, "RestoreDontCopyBackup", plist_new_bool(1));
+ PRINT_VERBOSE(1, "Don't copy backup: %s\n", ((cmd_flags & CMD_FLAG_RESTORE_COPY_BACKUP) == 0 ? "Yes":"No"));
+ plist_dict_set_item(opts, "RestorePreserveSettings", plist_new_bool((cmd_flags & CMD_FLAG_RESTORE_SETTINGS) == 0));
+ PRINT_VERBOSE(1, "Preserve settings of device: %s\n", ((cmd_flags & CMD_FLAG_RESTORE_SETTINGS) == 0 ? "Yes":"No"));
+ plist_dict_set_item(opts, "RemoveItemsNotRestored", plist_new_bool(cmd_flags & CMD_FLAG_RESTORE_REMOVE_ITEMS));
+ PRINT_VERBOSE(1, "Remove items that are not restored: %s\n", ((cmd_flags & CMD_FLAG_RESTORE_REMOVE_ITEMS) ? "Yes":"No"));
+ if (backup_password != NULL) {
+ plist_dict_set_item(opts, "Password", plist_new_string(backup_password));
+ }
+ PRINT_VERBOSE(1, "Backup password: %s\n", (backup_password == NULL ? "No":"Yes"));
+
+ if (cmd_flags & CMD_FLAG_RESTORE_SKIP_APPS) {
+ PRINT_VERBOSE(1, "Not writing RestoreApplications.plist - apps will not be re-installed after restore\n");
+ } else {
+ /* Write /iTunesRestore/RestoreApplications.plist so that the device will start
+ * restoring applications once the rest of the restore process is finished */
+ if (write_restore_applications(info_plist, afc) < 0) {
+ cmd = CMD_LEAVE;
+ break;
+ }
+ PRINT_VERBOSE(1, "Wrote RestoreApplications.plist\n");
+ }
+
+ /* Start restore */
+ err = mobilebackup2_send_request(mobilebackup2, "Restore", udid, source_udid, opts);
+ plist_free(opts);
+ if (err != MOBILEBACKUP2_E_SUCCESS) {
+ if (err == MOBILEBACKUP2_E_BAD_VERSION) {
+ printf("ERROR: Could not start restore process: backup protocol version mismatch!\n");
+ } else if (err == MOBILEBACKUP2_E_REPLY_NOT_OK) {
+ printf("ERROR: Could not start restore process: device refused to start the restore process.\n");
+ } else {
+ printf("ERROR: Could not start restore process: unspecified error occurred\n");
+ }
+ cmd = CMD_LEAVE;
+ }
+ break;
+ case CMD_INFO:
+ PRINT_VERBOSE(1, "Requesting backup info from device...\n");
+ err = mobilebackup2_send_request(mobilebackup2, "Info", udid, source_udid, NULL);
+ if (err != MOBILEBACKUP2_E_SUCCESS) {
+ printf("Error requesting backup info from device, error code %d\n", err);
+ cmd = CMD_LEAVE;
+ }
+ break;
+ case CMD_LIST:
+ PRINT_VERBOSE(1, "Requesting backup list from device...\n");
+ err = mobilebackup2_send_request(mobilebackup2, "List", udid, source_udid, NULL);
+ if (err != MOBILEBACKUP2_E_SUCCESS) {
+ printf("Error requesting backup list from device, error code %d\n", err);
+ cmd = CMD_LEAVE;
+ }
+ break;
+ case CMD_UNBACK:
+ PRINT_VERBOSE(1, "Starting to unpack backup...\n");
+ if (backup_password != NULL) {
+ opts = plist_new_dict();
+ plist_dict_set_item(opts, "Password", plist_new_string(backup_password));
+ }
+ PRINT_VERBOSE(1, "Backup password: %s\n", (backup_password == NULL ? "No":"Yes"));
+ err = mobilebackup2_send_request(mobilebackup2, "Unback", udid, source_udid, opts);
+ if (backup_password !=NULL) {
+ plist_free(opts);
+ }
+ if (err != MOBILEBACKUP2_E_SUCCESS) {
+ printf("Error requesting unback operation from device, error code %d\n", err);
+ cmd = CMD_LEAVE;
+ }
+ break;
+ case CMD_CHANGEPW:
+ opts = plist_new_dict();
+ plist_dict_set_item(opts, "TargetIdentifier", plist_new_string(udid));
+ if (cmd_flags & CMD_FLAG_ENCRYPTION_ENABLE) {
+ if (!willEncrypt) {
+ if (!newpw) {
+ newpw = getenv("BACKUP_PASSWORD");
+ if (newpw) {
+ newpw = strdup(newpw);
+ }
+ }
+ if (!newpw) {
+ newpw = ask_for_password("Enter new backup password", 1);
+ }
+ if (!newpw) {
+ printf("No backup password given. Aborting.\n");
+ }
+ } else {
+ printf("ERROR: Backup encryption is already enabled. Aborting.\n");
+ cmd = CMD_LEAVE;
+ if (newpw) {
+ free(newpw);
+ newpw = NULL;
+ }
+ }
+ } else if (cmd_flags & CMD_FLAG_ENCRYPTION_DISABLE) {
+ if (willEncrypt) {
+ if (!backup_password) {
+ backup_password = getenv("BACKUP_PASSWORD");
+ if (backup_password) {
+ backup_password = strdup(backup_password);
+ }
+ }
+ if (!backup_password) {
+ backup_password = ask_for_password("Enter current backup password", 0);
+ }
+ } else {
+ printf("ERROR: Backup encryption is not enabled. Aborting.\n");
+ cmd = CMD_LEAVE;
+ if (backup_password) {
+ free(backup_password);
+ backup_password = NULL;
+ }
+ }
+ } else if (cmd_flags & CMD_FLAG_ENCRYPTION_CHANGEPW) {
+ if (willEncrypt) {
+ if (!backup_password) {
+ backup_password = ask_for_password("Enter old backup password", 0);
+ newpw = ask_for_password("Enter new backup password", 1);
+ }
+ } else {
+ printf("ERROR: Backup encryption is not enabled so can't change password. Aborting.\n");
+ cmd = CMD_LEAVE;
+ if (newpw) {
+ free(newpw);
+ newpw = NULL;
+ }
+ if (backup_password) {
+ free(backup_password);
+ backup_password = NULL;
+ }
+ }
+ }
+ if (newpw) {
+ plist_dict_set_item(opts, "NewPassword", plist_new_string(newpw));
+ }
+ if (backup_password) {
+ plist_dict_set_item(opts, "OldPassword", plist_new_string(backup_password));
+ }
+ if (newpw || backup_password) {
+ mobilebackup2_send_message(mobilebackup2, "ChangePassword", opts);
+ uint8_t passcode_hint = 0;
+ if (device_version >= DEVICE_VERSION(13,0,0)) {
+ diagnostics_relay_client_t diag = NULL;
+ if (diagnostics_relay_client_start_service(device, &diag, TOOL_NAME) == DIAGNOSTICS_RELAY_E_SUCCESS) {
+ plist_t dict = NULL;
+ plist_t keys = plist_new_array();
+ plist_array_append_item(keys, plist_new_string("PasswordConfigured"));
+ if (diagnostics_relay_query_mobilegestalt(diag, keys, &dict) == DIAGNOSTICS_RELAY_E_SUCCESS) {
+ plist_t node = plist_access_path(dict, 2, "MobileGestalt", "PasswordConfigured");
+ plist_get_bool_val(node, &passcode_hint);
+ }
+ plist_free(keys);
+ plist_free(dict);
+ diagnostics_relay_goodbye(diag);
+ diagnostics_relay_client_free(diag);
+ }
+ }
+ if (passcode_hint) {
+ if (cmd_flags & CMD_FLAG_ENCRYPTION_CHANGEPW) {
+ PRINT_VERBOSE(1, "Please confirm changing the backup password by entering the passcode on the device.\n");
+ } else if (cmd_flags & CMD_FLAG_ENCRYPTION_ENABLE) {
+ PRINT_VERBOSE(1, "Please confirm enabling the backup encryption by entering the passcode on the device.\n");
+ } else if (cmd_flags & CMD_FLAG_ENCRYPTION_DISABLE) {
+ PRINT_VERBOSE(1, "Please confirm disabling the backup encryption by entering the passcode on the device.\n");
+ }
+ }
+ /*if (cmd_flags & CMD_FLAG_ENCRYPTION_ENABLE) {
+ int retr = 10;
+ while ((retr-- >= 0) && !backup_domain_changed) {
+ sleep(1);
+ }
+ }*/
+ } else {
+ cmd = CMD_LEAVE;
+ }
+ plist_free(opts);
+ break;
+ default:
+ break;
+ }
+
+ if (cmd != CMD_LEAVE) {
+ /* reset operation success status */
+ int operation_ok = 0;
+ plist_t message = NULL;
+
+ mobilebackup2_error_t mberr;
+ char *dlmsg = NULL;
+ int file_count = 0;
+ int errcode = 0;
+ const char *errdesc = NULL;
+ int progress_finished = 0;
+
+ /* process series of DLMessage* operations */
+ do {
+ free(dlmsg);
+ dlmsg = NULL;
+ mberr = mobilebackup2_receive_message(mobilebackup2, &message, &dlmsg);
+ if (mberr == MOBILEBACKUP2_E_RECEIVE_TIMEOUT) {
+ PRINT_VERBOSE(2, "Device is not ready yet, retrying...\n");
+ goto files_out;
+ } else if (mberr != MOBILEBACKUP2_E_SUCCESS) {
+ PRINT_VERBOSE(0, "ERROR: Could not receive from mobilebackup2 (%d)\n", mberr);
+ quit_flag++;
+ goto files_out;
+ }
+
+ if (!strcmp(dlmsg, "DLMessageDownloadFiles")) {
+ /* device wants to download files from the computer */
+ mb2_set_overall_progress_from_message(message, dlmsg);
+ mb2_handle_send_files(mobilebackup2, message, backup_directory);
+ } else if (!strcmp(dlmsg, "DLMessageUploadFiles")) {
+ /* device wants to send files to the computer */
+ mb2_set_overall_progress_from_message(message, dlmsg);
+ file_count += mb2_handle_receive_files(mobilebackup2, message, backup_directory);
+ } else if (!strcmp(dlmsg, "DLMessageGetFreeDiskSpace")) {
+ /* device wants to know how much disk space is available on the computer */
+ uint64_t freespace = 0;
+ int res = -1;
+#ifdef WIN32
+ if (GetDiskFreeSpaceEx(backup_directory, (PULARGE_INTEGER)&freespace, NULL, NULL)) {
+ res = 0;
+ }
+#else
+ struct statvfs fs;
+ memset(&fs, '\0', sizeof(fs));
+ res = statvfs(backup_directory, &fs);
+ if (res == 0) {
+ freespace = (uint64_t)fs.f_bavail * (uint64_t)fs.f_bsize;
+ }
+#endif
+ plist_t freespace_item = plist_new_uint(freespace);
+ mobilebackup2_send_status_response(mobilebackup2, res, NULL, freespace_item);
+ plist_free(freespace_item);
+ } else if (!strcmp(dlmsg, "DLMessagePurgeDiskSpace")) {
+ /* device wants to purge disk space on the host - not supported */
+ plist_t empty_dict = plist_new_dict();
+ err = mobilebackup2_send_status_response(mobilebackup2, -1, "Operation not supported", empty_dict);
+ plist_free(empty_dict);
+ } else if (!strcmp(dlmsg, "DLContentsOfDirectory")) {
+ /* list directory contents */
+ mb2_handle_list_directory(mobilebackup2, message, backup_directory);
+ } else if (!strcmp(dlmsg, "DLMessageCreateDirectory")) {
+ /* make a directory */
+ mb2_handle_make_directory(mobilebackup2, message, backup_directory);
+ } else if (!strcmp(dlmsg, "DLMessageMoveFiles") || !strcmp(dlmsg, "DLMessageMoveItems")) {
+ /* perform a series of rename operations */
+ mb2_set_overall_progress_from_message(message, dlmsg);
+ plist_t moves = plist_array_get_item(message, 1);
+ uint32_t cnt = plist_dict_get_size(moves);
+ PRINT_VERBOSE(1, "Moving %d file%s\n", cnt, (cnt == 1) ? "" : "s");
+ plist_dict_iter iter = NULL;
+ plist_dict_new_iter(moves, &iter);
+ errcode = 0;
+ errdesc = NULL;
+ if (iter) {
+ char *key = NULL;
+ plist_t val = NULL;
+ do {
+ plist_dict_next_item(moves, iter, &key, &val);
+ if (key && (plist_get_node_type(val) == PLIST_STRING)) {
+ char *str = NULL;
+ plist_get_string_val(val, &str);
+ if (str) {
+ char *newpath = string_build_path(backup_directory, str, NULL);
+ free(str);
+ char *oldpath = string_build_path(backup_directory, key, NULL);
+
+ if ((stat(newpath, &st) == 0) && S_ISDIR(st.st_mode))
+ rmdir_recursive(newpath);
+ else
+ remove_file(newpath);
+ if (rename(oldpath, newpath) < 0) {
+ printf("Renameing '%s' to '%s' failed: %s (%d)\n", oldpath, newpath, strerror(errno), errno);
+ errcode = errno_to_device_error(errno);
+ errdesc = strerror(errno);
+ break;
+ }
+ free(oldpath);
+ free(newpath);
+ }
+ free(key);
+ key = NULL;
+ }
+ } while (val);
+ free(iter);
+ } else {
+ errcode = -1;
+ errdesc = "Could not create dict iterator";
+ printf("Could not create dict iterator\n");
+ }
+ plist_t empty_dict = plist_new_dict();
+ err = mobilebackup2_send_status_response(mobilebackup2, errcode, errdesc, empty_dict);
+ plist_free(empty_dict);
+ if (err != MOBILEBACKUP2_E_SUCCESS) {
+ printf("Could not send status response, error %d\n", err);
+ }
+ } else if (!strcmp(dlmsg, "DLMessageRemoveFiles") || !strcmp(dlmsg, "DLMessageRemoveItems")) {
+ mb2_set_overall_progress_from_message(message, dlmsg);
+ plist_t removes = plist_array_get_item(message, 1);
+ uint32_t cnt = plist_array_get_size(removes);
+ PRINT_VERBOSE(1, "Removing %d file%s\n", cnt, (cnt == 1) ? "" : "s");
+ uint32_t ii = 0;
+ errcode = 0;
+ errdesc = NULL;
+ for (ii = 0; ii < cnt; ii++) {
+ plist_t val = plist_array_get_item(removes, ii);
+ if (plist_get_node_type(val) == PLIST_STRING) {
+ char *str = NULL;
+ plist_get_string_val(val, &str);
+ if (str) {
+ const char *checkfile = strchr(str, '/');
+ int suppress_warning = 0;
+ if (checkfile) {
+ if (strcmp(checkfile+1, "Manifest.mbdx") == 0) {
+ suppress_warning = 1;
+ }
+ }
+ char *newpath = string_build_path(backup_directory, str, NULL);
+ free(str);
+ int res = 0;
+ if ((stat(newpath, &st) == 0) && S_ISDIR(st.st_mode)) {
+ res = rmdir_recursive(newpath);
+ } else {
+ res = remove_file(newpath);
+ }
+ if (res != 0 && res != ENOENT) {
+ if (!suppress_warning)
+ printf("Could not remove '%s': %s (%d)\n", newpath, strerror(res), res);
+ errcode = errno_to_device_error(res);
+ errdesc = strerror(res);
+ }
+ free(newpath);
+ }
+ }
+ }
+ plist_t empty_dict = plist_new_dict();
+ err = mobilebackup2_send_status_response(mobilebackup2, errcode, errdesc, empty_dict);
+ plist_free(empty_dict);
+ if (err != MOBILEBACKUP2_E_SUCCESS) {
+ printf("Could not send status response, error %d\n", err);
+ }
+ } else if (!strcmp(dlmsg, "DLMessageCopyItem")) {
+ plist_t srcpath = plist_array_get_item(message, 1);
+ plist_t dstpath = plist_array_get_item(message, 2);
+ errcode = 0;
+ errdesc = NULL;
+ if ((plist_get_node_type(srcpath) == PLIST_STRING) && (plist_get_node_type(dstpath) == PLIST_STRING)) {
+ char *src = NULL;
+ char *dst = NULL;
+ plist_get_string_val(srcpath, &src);
+ plist_get_string_val(dstpath, &dst);
+ if (src && dst) {
+ char *oldpath = string_build_path(backup_directory, src, NULL);
+ char *newpath = string_build_path(backup_directory, dst, NULL);
+
+ PRINT_VERBOSE(1, "Copying '%s' to '%s'\n", src, dst);
+
+ /* check that src exists */
+ if ((stat(oldpath, &st) == 0) && S_ISDIR(st.st_mode)) {
+ mb2_copy_directory_by_path(oldpath, newpath);
+ } else if ((stat(oldpath, &st) == 0) && S_ISREG(st.st_mode)) {
+ mb2_copy_file_by_path(oldpath, newpath);
+ }
+
+ free(newpath);
+ free(oldpath);
+ }
+ free(src);
+ free(dst);
+ }
+ plist_t empty_dict = plist_new_dict();
+ err = mobilebackup2_send_status_response(mobilebackup2, errcode, errdesc, empty_dict);
+ plist_free(empty_dict);
+ if (err != MOBILEBACKUP2_E_SUCCESS) {
+ printf("Could not send status response, error %d\n", err);
+ }
+ } else if (!strcmp(dlmsg, "DLMessageDisconnect")) {
+ break;
+ } else if (!strcmp(dlmsg, "DLMessageProcessMessage")) {
+ node_tmp = plist_array_get_item(message, 1);
+ if (plist_get_node_type(node_tmp) != PLIST_DICT) {
+ printf("Unknown message received!\n");
+ }
+ plist_t nn;
+ int error_code = -1;
+ nn = plist_dict_get_item(node_tmp, "ErrorCode");
+ if (nn && (plist_get_node_type(nn) == PLIST_UINT)) {
+ uint64_t ec = 0;
+ plist_get_uint_val(nn, &ec);
+ error_code = (uint32_t)ec;
+ if (error_code == 0) {
+ operation_ok = 1;
+ result_code = 0;
+ } else {
+ result_code = -error_code;
+ }
+ }
+ nn = plist_dict_get_item(node_tmp, "ErrorDescription");
+ char *str = NULL;
+ if (nn && (plist_get_node_type(nn) == PLIST_STRING)) {
+ plist_get_string_val(nn, &str);
+ }
+ if (error_code != 0) {
+ if (str) {
+ printf("ErrorCode %d: %s\n", error_code, str);
+ } else {
+ printf("ErrorCode %d: (Unknown)\n", error_code);
+ }
+ }
+ if (str) {
+ free(str);
+ }
+ nn = plist_dict_get_item(node_tmp, "Content");
+ if (nn && (plist_get_node_type(nn) == PLIST_STRING)) {
+ str = NULL;
+ plist_get_string_val(nn, &str);
+ PRINT_VERBOSE(1, "Content:\n");
+ printf("%s", str);
+ free(str);
+ }
+ break;
+ }
+
+ /* print status */
+ if ((overall_progress > 0) && !progress_finished) {
+ if (overall_progress >= 100.0F) {
+ progress_finished = 1;
+ }
+ print_progress_real(overall_progress, 0);
+ PRINT_VERBOSE(1, " Finished\n");
+ }
+
+files_out:
+ plist_free(message);
+ message = NULL;
+ free(dlmsg);
+ dlmsg = NULL;
+
+ if (quit_flag > 0) {
+ /* need to cancel the backup here */
+ //mobilebackup_send_error(mobilebackup, "Cancelling DLSendFile");
+
+ /* remove any atomic Manifest.plist.tmp */
+
+ /*manifest_path = mobilebackup_build_path(backup_directory, "Manifest", ".plist.tmp");
+ if (stat(manifest_path, &st) == 0)
+ remove(manifest_path);*/
+ break;
+ }
+ } while (1);
+
+ plist_free(message);
+ free(dlmsg);
+
+ /* report operation status to user */
+ switch (cmd) {
+ case CMD_CLOUD:
+ if (cmd_flags & CMD_FLAG_CLOUD_ENABLE) {
+ if (operation_ok) {
+ PRINT_VERBOSE(1, "Cloud backup has been enabled successfully.\n");
+ } else {
+ PRINT_VERBOSE(1, "Could not enable cloud backup.\n");
+ }
+ } else if (cmd_flags & CMD_FLAG_CLOUD_DISABLE) {
+ if (operation_ok) {
+ PRINT_VERBOSE(1, "Cloud backup has been disabled successfully.\n");
+ } else {
+ PRINT_VERBOSE(1, "Could not disable cloud backup.\n");
+ }
+ }
+ break;
+ case CMD_BACKUP:
+ PRINT_VERBOSE(1, "Received %d files from device.\n", file_count);
+ if (operation_ok && mb2_status_check_snapshot_state(backup_directory, udid, "finished")) {
+ PRINT_VERBOSE(1, "Backup Successful.\n");
+ } else {
+ if (quit_flag) {
+ PRINT_VERBOSE(1, "Backup Aborted.\n");
+ } else {
+ PRINT_VERBOSE(1, "Backup Failed (Error Code %d).\n", -result_code);
+ }
+ }
+ break;
+ case CMD_UNBACK:
+ if (quit_flag) {
+ PRINT_VERBOSE(1, "Unback Aborted.\n");
+ } else {
+ PRINT_VERBOSE(1, "The files can now be found in the \"_unback_\" directory.\n");
+ PRINT_VERBOSE(1, "Unback Successful.\n");
+ }
+ break;
+ case CMD_CHANGEPW:
+ if (cmd_flags & CMD_FLAG_ENCRYPTION_ENABLE) {
+ if (operation_ok) {
+ PRINT_VERBOSE(1, "Backup encryption has been enabled successfully.\n");
+ } else {
+ PRINT_VERBOSE(1, "Could not enable backup encryption.\n");
+ }
+ } else if (cmd_flags & CMD_FLAG_ENCRYPTION_DISABLE) {
+ if (operation_ok) {
+ PRINT_VERBOSE(1, "Backup encryption has been disabled successfully.\n");
+ } else {
+ PRINT_VERBOSE(1, "Could not disable backup encryption.\n");
+ }
+ } else if (cmd_flags & CMD_FLAG_ENCRYPTION_CHANGEPW) {
+ if (operation_ok) {
+ PRINT_VERBOSE(1, "Backup encryption password has been changed successfully.\n");
+ } else {
+ PRINT_VERBOSE(1, "Could not change backup encryption password.\n");
+ }
+ }
+ break;
+ case CMD_RESTORE:
+ if (operation_ok) {
+ if ((cmd_flags & CMD_FLAG_RESTORE_NO_REBOOT) == 0)
+ PRINT_VERBOSE(1, "The device should reboot now.\n");
+ PRINT_VERBOSE(1, "Restore Successful.\n");
+ } else {
+ afc_remove_path(afc, "/iTunesRestore/RestoreApplications.plist");
+ afc_remove_path(afc, "/iTunesRestore");
+ if (quit_flag) {
+ PRINT_VERBOSE(1, "Restore Aborted.\n");
+ } else {
+ PRINT_VERBOSE(1, "Restore Failed (Error Code %d).\n", -result_code);
+ }
+ }
+ break;
+ case CMD_INFO:
+ case CMD_LIST:
+ case CMD_LEAVE:
+ default:
+ if (quit_flag) {
+ PRINT_VERBOSE(1, "Operation Aborted.\n");
+ } else if (cmd == CMD_LEAVE) {
+ PRINT_VERBOSE(1, "Operation Failed.\n");
+ } else {
+ PRINT_VERBOSE(1, "Operation Successful.\n");
+ }
+ break;
+ }
+ }
+ if (lockfile) {
+ afc_file_lock(afc, lockfile, AFC_LOCK_UN);
+ afc_file_close(afc, lockfile);
+ lockfile = 0;
+ if (cmd == CMD_BACKUP || cmd == CMD_RESTORE)
+ do_post_notification(device, NP_SYNC_DID_FINISH);
+ }
+ } else {
+ printf("ERROR: Could not start service %s: %s\n", MOBILEBACKUP2_SERVICE_NAME, lockdownd_strerror(ldret));
+ lockdownd_client_free(lockdown);
+ lockdown = NULL;
+ }
+
+ if (lockdown) {
+ lockdownd_client_free(lockdown);
+ lockdown = NULL;
+ }
+
+ if (mobilebackup2) {
+ mobilebackup2_client_free(mobilebackup2);
+ mobilebackup2 = NULL;
+ }
+
+ if (afc) {
+ afc_client_free(afc);
+ afc = NULL;
+ }
+
+ if (np) {
+ np_client_free(np);
+ np = NULL;
+ }
+
+ idevice_free(device);
+ device = NULL;
+
+ if (backup_password) {
+ free(backup_password);
+ }
+
+ if (udid) {
+ free(udid);
+ udid = NULL;
+ }
+ if (source_udid) {
+ free(source_udid);
+ source_udid = NULL;
+ }
+
+ return result_code;
+}
+