summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGravatar Martin Szulecki2010-01-26 02:03:21 +0100
committerGravatar Martin Szulecki2010-01-26 02:03:21 +0100
commitb369efa426307bb6e9828c755ccc50c4f213c2e8 (patch)
treee9fa13ca2662961af57095d38fe4231faba09fa4
parent90af94845ba841c693e80ac0eec317130c1c416e (diff)
downloadlibimobiledevice-b369efa426307bb6e9828c755ccc50c4f213c2e8.tar.gz
libimobiledevice-b369efa426307bb6e9828c755ccc50c4f213c2e8.tar.bz2
Refactor iphonebackup and implement incremental backup support
-rw-r--r--tools/iphonebackup.c293
1 files changed, 237 insertions, 56 deletions
diff --git a/tools/iphonebackup.c b/tools/iphonebackup.c
index 19acc4e..f0cfa7a 100644
--- a/tools/iphonebackup.c
+++ b/tools/iphonebackup.c
@@ -44,6 +44,17 @@ enum cmd_mode {
44 CMD_LEAVE 44 CMD_LEAVE
45}; 45};
46 46
47enum plist_format_t {
48 PLIST_FORMAT_XML,
49 PLIST_FORMAT_BINARY
50};
51
52enum device_link_file_status_t {
53 DEVICE_LINK_FILE_STATUS_NONE = 0,
54 DEVICE_LINK_FILE_STATUS_HUNK,
55 DEVICE_LINK_FILE_STATUS_LAST_HUNK
56};
57
47static plist_t mobilebackup_factory_info_plist_new() 58static plist_t mobilebackup_factory_info_plist_new()
48{ 59{
49 /* gather data from lockdown */ 60 /* gather data from lockdown */
@@ -95,20 +106,50 @@ static plist_t mobilebackup_factory_info_plist_new()
95 free(uuid_uppercase); 106 free(uuid_uppercase);
96 free(uuid); 107 free(uuid);
97 108
98 plist_t files = plist_new_dict();
99 /* FIXME: Embed files as <data> nodes */ 109 /* FIXME: Embed files as <data> nodes */
110 plist_t files = plist_new_dict();
100 plist_dict_insert_item(ret, "iTunes Files", files); 111 plist_dict_insert_item(ret, "iTunes Files", files);
101 plist_dict_insert_item(ret, "iTunes Version", plist_new_string("9.0.2")); 112 plist_dict_insert_item(ret, "iTunes Version", plist_new_string("9.0.2"));
102 113
114 plist_free(root_node);
115
103 return ret; 116 return ret;
104} 117}
105 118
106enum plist_format_t { 119static void mobilebackup_info_update_last_backup_date(plist_t info_plist)
107 PLIST_FORMAT_XML, 120{
108 PLIST_FORMAT_BINARY 121 GTimeVal tv = {0, 0};
109}; 122 plist_t node = NULL;
110 123
111static void buffer_to_filename(char *filename, char *buffer, uint32_t length) 124 if (!info_plist)
125 return;
126
127 g_get_current_time(&tv);
128 node = plist_dict_get_item(info_plist, "Last Backup Date");
129 plist_set_date_val(node, tv.tv_sec, tv.tv_usec);
130
131 node = NULL;
132}
133
134static void buffer_read_from_filename(const char *filename, char **buffer, uint32_t *length)
135{
136 FILE *f;
137 uint64_t size;
138
139 f = fopen(filename, "rb");
140
141 fseek(f, 0, SEEK_END);
142 size = ftell(f);
143 rewind(f);
144
145 *buffer = (char*)malloc(sizeof(char)*size);
146 fread(*buffer, sizeof(char), size, f);
147 fclose(f);
148
149 *length = size;
150}
151
152static void buffer_write_to_filename(const char *filename, const char *buffer, uint32_t length)
112{ 153{
113 FILE *f; 154 FILE *f;
114 155
@@ -117,7 +158,32 @@ static void buffer_to_filename(char *filename, char *buffer, uint32_t length)
117 fclose(f); 158 fclose(f);
118} 159}
119 160
120static int plist_write_to_filename(plist_t plist, char *filename, enum plist_format_t format) 161static int plist_read_from_filename(plist_t *plist, const char *filename)
162{
163 char *buffer = NULL;
164 uint32_t length;
165
166 if (!filename)
167 return 0;
168
169 buffer_read_from_filename(filename, &buffer, &length);
170
171 if (!buffer) {
172 return 0;
173 }
174
175 if (memcmp(buffer, "bplist00", 8) == 0) {
176 plist_from_bin(buffer, length, plist);
177 } else {
178 plist_from_xml(buffer, length, plist);
179 }
180
181 free(buffer);
182
183 return 1;
184}
185
186static int plist_write_to_filename(plist_t plist, const char *filename, enum plist_format_t format)
121{ 187{
122 char *buffer = NULL; 188 char *buffer = NULL;
123 uint32_t length; 189 uint32_t length;
@@ -132,7 +198,7 @@ static int plist_write_to_filename(plist_t plist, char *filename, enum plist_for
132 else 198 else
133 return 0; 199 return 0;
134 200
135 buffer_to_filename(filename, buffer, length); 201 buffer_write_to_filename(filename, buffer, length);
136 202
137 free(buffer); 203 free(buffer);
138 204
@@ -183,17 +249,100 @@ static plist_t mobilebackup_factory_backup_file_received_new()
183 return device_link_message_factory_process_message_new(node); 249 return device_link_message_factory_process_message_new(node);
184} 250}
185 251
186static void mobilebackup_write_status(char *path, int status) 252static gchar *mobilebackup_build_path(const char *backup_directory, const char *name, const char *extension)
253{
254 gchar *filename = g_strconcat(name, extension, NULL);
255 gchar *path = g_build_path(G_DIR_SEPARATOR_S, backup_directory, filename, NULL);
256 g_free(filename);
257 return path;
258}
259
260static void mobilebackup_write_status(const char *path, int status)
187{ 261{
188 struct stat st; 262 struct stat st;
189 plist_t status_plist = plist_new_dict(); 263 plist_t status_plist = plist_new_dict();
190 plist_dict_insert_item(status_plist, "Backup Success", plist_new_bool(status)); 264 plist_dict_insert_item(status_plist, "Backup Success", plist_new_bool(status));
191 char *file_path = g_build_path(G_DIR_SEPARATOR_S, path, "Status.plist", NULL); 265 gchar *file_path = mobilebackup_build_path(path, "Status", ".plist");
266
192 if (stat(file_path, &st) == 0) 267 if (stat(file_path, &st) == 0)
193 remove(file_path); 268 remove(file_path);
269
194 plist_write_to_filename(status_plist, file_path, PLIST_FORMAT_XML); 270 plist_write_to_filename(status_plist, file_path, PLIST_FORMAT_XML);
195 g_free(file_path); 271
196 plist_free(status_plist); 272 plist_free(status_plist);
273 status_plist = NULL;
274
275 g_free(file_path);
276}
277
278static int mobilebackup_info_is_current_device(plist_t info)
279{
280 plist_t value_node = NULL;
281 plist_t node = NULL;
282 plist_t root_node = NULL;
283 int ret = 0;
284
285 if (!info)
286 return ret;
287
288 if (plist_get_node_type(info) != PLIST_DICT)
289 return ret;
290
291 /* get basic device information in one go */
292 lockdownd_get_value(client, NULL, NULL, &root_node);
293
294 /* verify UUID */
295 value_node = plist_dict_get_item(root_node, "UniqueDeviceID");
296 node = plist_dict_get_item(info, "Target Identifier");
297
298 if(plist_compare_node_value(value_node, node))
299 ret = 1;
300
301 /* verify SerialNumber */
302 if (ret == 1) {
303 value_node = plist_dict_get_item(root_node, "SerialNumber");
304 node = plist_dict_get_item(info, "Serial Number");
305
306 if(plist_compare_node_value(value_node, node))
307 ret = 1;
308 else
309 ret = 0;
310 }
311
312 plist_free(root_node);
313 root_node = NULL;
314
315 value_node = NULL;
316 node = NULL;
317
318 return ret;
319}
320
321static int mobilebackup_delete_backup_file_by_hash(const char *backup_directory, const char *hash)
322{
323 int ret = 0;
324 gchar *path = mobilebackup_build_path(backup_directory, hash, ".mddata");
325 printf("Removing \"%s\"... ", path);
326 if (!remove( path ))
327 ret = 1;
328 else
329 ret = 0;
330
331 g_free(path);
332
333 if (!ret)
334 return ret;
335
336 path = mobilebackup_build_path(backup_directory, hash, ".mdinfo");
337 printf("Removing \"%s\"... ", path);
338 if (!remove( path ))
339 ret = 1;
340 else
341 ret = 0;
342
343 g_free(path);
344
345 return ret;
197} 346}
198 347
199/** 348/**
@@ -229,13 +378,17 @@ int main(int argc, char *argv[])
229 uint16_t port = 0; 378 uint16_t port = 0;
230 uuid[0] = 0; 379 uuid[0] = 0;
231 int cmd = -1; 380 int cmd = -1;
381 int is_full_backup = 0;
232 char *backup_directory = NULL; 382 char *backup_directory = NULL;
233 struct stat st; 383 struct stat st;
234 plist_t node = NULL; 384 plist_t node = NULL;
235 plist_t node_tmp = NULL; 385 plist_t node_tmp = NULL;
386 plist_t manifest_plist = NULL;
387 plist_t info_plist = NULL;
236 char *buffer = NULL; 388 char *buffer = NULL;
237 uint64_t length = 0; 389 uint64_t length = 0;
238 uint64_t backup_total_size = 0; 390 uint64_t backup_total_size = 0;
391 enum device_link_file_status_t file_status;
239 uint64_t c = 0; 392 uint64_t c = 0;
240 393
241 /* we need to exit cleanly on running backups and restores or we cause havok */ 394 /* we need to exit cleanly on running backups and restores or we cause havok */
@@ -298,7 +451,7 @@ int main(int argc, char *argv[])
298 } 451 }
299 452
300 /* restore directory must contain an Info.plist */ 453 /* restore directory must contain an Info.plist */
301 char *info_path = g_build_path(G_DIR_SEPARATOR_S, backup_directory, "Info.plist", NULL); 454 char *info_path = mobilebackup_build_path(backup_directory, "Info", ".plist");
302 if (cmd == CMD_RESTORE) { 455 if (cmd == CMD_RESTORE) {
303 if (stat(info_path, &st) != 0) { 456 if (stat(info_path, &st) != 0) {
304 g_free(info_path); 457 g_free(info_path);
@@ -347,27 +500,45 @@ int main(int argc, char *argv[])
347 /* TODO: check domain com.apple.mobile.backup key RequiresEncrypt and WillEncrypt with lockdown */ 500 /* TODO: check domain com.apple.mobile.backup key RequiresEncrypt and WillEncrypt with lockdown */
348 /* TODO: verify battery on AC enough battery remaining */ 501 /* TODO: verify battery on AC enough battery remaining */
349 502
350 /* create Info.plist (Device infos, IC-Info.sidb, photos, app_ids, iTunesPrefs) */ 503 /* Info.plist (Device infos, IC-Info.sidb, photos, app_ids, iTunesPrefs) */
351 printf("Creating Info.plist.\n"); 504
352 plist_t info_plist = mobilebackup_factory_info_plist_new(); 505 /* read existing Info.plist or create new one */
353 if (stat(info_path, &st) == 0) 506 if (stat(info_path, &st) == 0) {
354 remove(info_path); 507 printf("Reading Info.plist from existing backup.\n");
355 plist_write_to_filename(info_plist, info_path, PLIST_FORMAT_XML); 508 plist_read_from_filename(&info_plist, info_path);
509
510 if(!is_full_backup) {
511 /* update the last backup time within Info.plist */
512 mobilebackup_info_update_last_backup_date(info_plist);
513 remove(info_path);
514 plist_write_to_filename(info_plist, info_path, PLIST_FORMAT_XML);
515 }
516 } else {
517 printf("Creating Info.plist for new backup.\n");
518 info_plist = mobilebackup_factory_info_plist_new();
519 plist_write_to_filename(info_plist, info_path, PLIST_FORMAT_XML);
520 is_full_backup = 1;
521 }
522
356 g_free(info_path); 523 g_free(info_path);
357 524
525 /* Manifest.plist (backup manifest (backup state)) */
526 char *manifest_path = mobilebackup_build_path(backup_directory, "Manifest", ".plist");
527 /* read the last Manifest.plist if the current backup is for this device */
528 if (!is_full_backup && mobilebackup_info_is_current_device(info_plist)) {
529 printf("Reading existing Manifest.\n");
530 plist_read_from_filename(&manifest_plist, manifest_path);
531 }
532
533 plist_free(info_plist);
534 info_plist = NULL;
535
358 /* close down the lockdown connection as it is no longer needed */ 536 /* close down the lockdown connection as it is no longer needed */
359 if (client) { 537 if (client) {
360 lockdownd_client_free(client); 538 lockdownd_client_free(client);
361 client = NULL; 539 client = NULL;
362 } 540 }
363 541
364 /* create Manifest.plist (backup manifest (backup state)) */
365 char *manifest_path = g_build_path(G_DIR_SEPARATOR_S, backup_directory, "Manifest.plist", NULL);
366 /* FIXME: We should read the last Manifest.plist and send it to the device */
367 plist_t manifest_plist = NULL;
368 if (stat(manifest_path, &st) == 0)
369 remove(manifest_path);
370
371 /* create Status.plist with failed status for now */ 542 /* create Status.plist with failed status for now */
372 mobilebackup_write_status(backup_directory, 0); 543 mobilebackup_write_status(backup_directory, 0);
373 544
@@ -375,8 +546,10 @@ int main(int argc, char *argv[])
375 printf("Requesting backup from device...\n"); 546 printf("Requesting backup from device...\n");
376 547
377 node = plist_new_dict(); 548 node = plist_new_dict();
549
378 if (manifest_plist) 550 if (manifest_plist)
379 plist_dict_insert_item(node, "BackupManifestKey", manifest_plist); 551 plist_dict_insert_item(node, "BackupManifestKey", manifest_plist);
552
380 plist_dict_insert_item(node, "BackupComputerBasePathKey", plist_new_string("/")); 553 plist_dict_insert_item(node, "BackupComputerBasePathKey", plist_new_string("/"));
381 plist_dict_insert_item(node, "BackupMessageTypeKey", plist_new_string("BackupMessageBackupRequest")); 554 plist_dict_insert_item(node, "BackupMessageTypeKey", plist_new_string("BackupMessageBackupRequest"));
382 plist_dict_insert_item(node, "BackupProtocolVersion", plist_new_string("1.6")); 555 plist_dict_insert_item(node, "BackupProtocolVersion", plist_new_string("1.6"));
@@ -389,6 +562,7 @@ int main(int argc, char *argv[])
389 /* get response */ 562 /* get response */
390 int backup_ok = 0; 563 int backup_ok = 0;
391 mobilebackup_receive(mobilebackup, &message); 564 mobilebackup_receive(mobilebackup, &message);
565
392 node = plist_array_get_item(message, 0); 566 node = plist_array_get_item(message, 0);
393 if (!plist_strcmp(node, "DLMessageProcessMessage")) { 567 if (!plist_strcmp(node, "DLMessageProcessMessage")) {
394 node = plist_array_get_item(message, 1); 568 node = plist_array_get_item(message, 1);
@@ -397,6 +571,10 @@ int main(int argc, char *argv[])
397 printf("Device accepts manifest and will send backup data now...\n"); 571 printf("Device accepts manifest and will send backup data now...\n");
398 backup_ok = 1; 572 backup_ok = 1;
399 printf("Acknowledging...\n"); 573 printf("Acknowledging...\n");
574 if (is_full_backup)
575 printf("Full backup mode.\n");
576 else
577 printf("Incremental backup mode.\n");
400 printf("Please wait. Device prepares backup data...\n"); 578 printf("Please wait. Device prepares backup data...\n");
401 /* send it back for ACK */ 579 /* send it back for ACK */
402 mobilebackup_send(mobilebackup, message); 580 mobilebackup_send(mobilebackup, message);
@@ -427,9 +605,13 @@ int main(int argc, char *argv[])
427 char *format_size = NULL; 605 char *format_size = NULL;
428 gboolean is_manifest = FALSE; 606 gboolean is_manifest = FALSE;
429 uint8_t b = 0; 607 uint8_t b = 0;
608
609 /* process series of DLSendFile messages */
430 do { 610 do {
431 mobilebackup_receive(mobilebackup, &message); 611 mobilebackup_receive(mobilebackup, &message);
432 node = plist_array_get_item(message, 0); 612 node = plist_array_get_item(message, 0);
613
614 /* get out if we don't get a DLSendFile */
433 if (plist_strcmp(node, "DLSendFile")) 615 if (plist_strcmp(node, "DLSendFile"))
434 break; 616 break;
435 617
@@ -446,9 +628,10 @@ int main(int argc, char *argv[])
446 } 628 }
447 } 629 }
448 630
449 /* print out "received" if DLFileStatusKey is 2 (last file piece) */ 631 /* check DLFileStatusKey (codes: 1 = Hunk, 2 = Last Hunk) */
450 node = plist_dict_get_item(node_tmp, "DLFileStatusKey"); 632 node = plist_dict_get_item(node_tmp, "DLFileStatusKey");
451 plist_get_uint_val(node, &c); 633 plist_get_uint_val(node, &c);
634 file_status = c;
452 635
453 /* get source filename */ 636 /* get source filename */
454 node = plist_dict_get_item(node_tmp, "BackupManifestKey"); 637 node = plist_dict_get_item(node_tmp, "BackupManifestKey");
@@ -458,25 +641,26 @@ int main(int argc, char *argv[])
458 } 641 }
459 is_manifest = (b == 1) ? TRUE: FALSE; 642 is_manifest = (b == 1) ? TRUE: FALSE;
460 643
461 /* increased received size for each completed file */ 644 /* check if we completed a file */
462 if ((c == 2) && (!is_manifest)) { 645 if ((file_status == DEVICE_LINK_FILE_STATUS_LAST_HUNK) && (!is_manifest)) {
463 /* get source filename */ 646 /* get source filename */
464 node = plist_dict_get_item(node_tmp, "DLFileSource"); 647 node = plist_dict_get_item(node_tmp, "DLFileSource");
465 plist_get_string_val(node, &filename_source); 648 plist_get_string_val(node, &filename_source);
466 649
650 /* increase received size */
467 node = plist_dict_get_item(node_tmp, "DLFileAttributesKey"); 651 node = plist_dict_get_item(node_tmp, "DLFileAttributesKey");
468 node = plist_dict_get_item(node, "FileSize"); 652 node = plist_dict_get_item(node, "FileSize");
469 plist_get_uint_val(node, &length); 653 plist_get_uint_val(node, &length);
470
471 backup_real_size += length; 654 backup_real_size += length;
472 file_index++;
473 655
474 format_size = g_format_size_for_display(backup_real_size); 656 format_size = g_format_size_for_display(backup_real_size);
475 printf("(%s", format_size); 657 printf("(%s", format_size);
476 g_free(format_size); 658 g_free(format_size);
659
477 format_size = g_format_size_for_display(backup_total_size); 660 format_size = g_format_size_for_display(backup_total_size);
478 printf("/%s): ", format_size); 661 printf("/%s): ", format_size);
479 g_free(format_size); 662 g_free(format_size);
663
480 printf("Received file %s... ", filename_source); 664 printf("Received file %s... ", filename_source);
481 665
482 if (filename_source) 666 if (filename_source)
@@ -487,13 +671,20 @@ int main(int argc, char *argv[])
487 if (node) { 671 if (node) {
488 node = plist_dict_get_item(node_tmp, "DLFileDest"); 672 node = plist_dict_get_item(node_tmp, "DLFileDest");
489 plist_get_string_val(node, &file_path); 673 plist_get_string_val(node, &file_path);
490 file_ext = (char *)g_strconcat(file_path, ".mdinfo", NULL); 674
491 filename_mdinfo = g_build_path(G_DIR_SEPARATOR_S, backup_directory, file_ext, NULL); 675 filename_mdinfo = mobilebackup_build_path(backup_directory, file_path, ".mdinfo");
676
677 /* remove any existing file */
678 if (stat(filename_mdinfo, &st) != 0)
679 remove(filename_mdinfo);
680
492 node = plist_dict_get_item(node_tmp, "BackupFileInfo"); 681 node = plist_dict_get_item(node_tmp, "BackupFileInfo");
493 plist_write_to_filename(node, filename_mdinfo, PLIST_FORMAT_BINARY); 682 plist_write_to_filename(node, filename_mdinfo, PLIST_FORMAT_BINARY);
494 g_free(file_ext); 683
495 g_free(filename_mdinfo); 684 g_free(filename_mdinfo);
496 } 685 }
686
687 file_index++;
497 } 688 }
498 689
499 /* save <hash>.mddata */ 690 /* save <hash>.mddata */
@@ -502,23 +693,26 @@ int main(int argc, char *argv[])
502 node = plist_dict_get_item(node_tmp, "DLFileDest"); 693 node = plist_dict_get_item(node_tmp, "DLFileDest");
503 plist_get_string_val(node, &file_path); 694 plist_get_string_val(node, &file_path);
504 695
505 if (!is_manifest) 696 filename_mddata = mobilebackup_build_path(backup_directory, file_path, is_manifest ? NULL: ".mddata");
506 file_ext = (char *)g_strconcat(file_path, ".mddata", NULL); 697
507 else 698 /* if this is the first hunk, remove any existing file */
508 file_ext = g_strdup(file_path); 699 if (stat(filename_mddata, &st) != 0)
700 remove(filename_mddata);
509 701
510 filename_mddata = g_build_path(G_DIR_SEPARATOR_S, backup_directory, file_ext, NULL); 702 /* get file data hunk */
511 node_tmp = plist_array_get_item(message, 1); 703 node_tmp = plist_array_get_item(message, 1);
512 plist_get_data_val(node_tmp, &buffer, &length); 704 plist_get_data_val(node_tmp, &buffer, &length);
513 705
514 buffer_to_filename(filename_mddata, buffer, length); 706 buffer_write_to_filename(filename_mddata, buffer, length);
515 707
516 /* activate currently sent manifest */ 708 /* activate currently sent manifest */
517 if ((c == 2) && (is_manifest)) { 709 if ((file_status == DEVICE_LINK_FILE_STATUS_LAST_HUNK) && (is_manifest)) {
518 rename(filename_mddata, manifest_path); 710 rename(filename_mddata, manifest_path);
519 } 711 }
712
520 free(buffer); 713 free(buffer);
521 buffer = NULL; 714 buffer = NULL;
715
522 g_free(filename_mddata); 716 g_free(filename_mddata);
523 } 717 }
524 718
@@ -531,7 +725,7 @@ int main(int argc, char *argv[])
531 plist_free(message); 725 plist_free(message);
532 message = NULL; 726 message = NULL;
533 727
534 if (c == 2) { 728 if (file_status == DEVICE_LINK_FILE_STATUS_LAST_HUNK) {
535 if (!is_manifest) 729 if (!is_manifest)
536 printf("DONE\n"); 730 printf("DONE\n");
537 731
@@ -566,20 +760,7 @@ int main(int argc, char *argv[])
566 while ((node_tmp = plist_array_get_item(node, i++)) != NULL) { 760 while ((node_tmp = plist_array_get_item(node, i++)) != NULL) {
567 plist_get_string_val(node_tmp, &file_path); 761 plist_get_string_val(node_tmp, &file_path);
568 762
569 file_ext = (char *)g_strconcat(file_path, ".mddata", NULL); 763 if (mobilebackup_delete_backup_file_by_hash(backup_directory, file_path)) {
570 filename_mddata = g_build_path(G_DIR_SEPARATOR_S, backup_directory, file_ext, NULL);
571 g_free(file_ext);
572 printf("Removing \"%s\"... ", filename_mddata);
573 if (!remove( filename_mddata )) {
574 printf("DONE\n");
575 } else
576 printf("FAILED\n");
577
578 file_ext = (char *)g_strconcat(file_path, ".mdinfo", NULL);
579 filename_mdinfo = g_build_path(G_DIR_SEPARATOR_S, backup_directory, file_ext, NULL);
580 g_free(file_ext);
581 printf("Removing \"%s\"... ", filename_mdinfo);
582 if (!remove( filename_mdinfo )) {
583 printf("DONE\n"); 764 printf("DONE\n");
584 } else 765 } else
585 printf("FAILED\n"); 766 printf("FAILED\n");
@@ -600,7 +781,7 @@ int main(int argc, char *argv[])
600 } 781 }
601 782
602 if (backup_ok) { 783 if (backup_ok) {
603 /* create: Status.plist (Info on how the backup process turned out) */ 784 /* Status.plist (Info on how the backup process turned out) */
604 printf("Backup Successful.\n"); 785 printf("Backup Successful.\n");
605 mobilebackup_write_status(backup_directory, 1); 786 mobilebackup_write_status(backup_directory, 1);
606 } else { 787 } else {