├── .gitignore ├── Makefile ├── README.md └── imgclone.c /.gitignore: -------------------------------------------------------------------------------- 1 | # Windows image file caches 2 | Thumbs.db 3 | ehthumbs.db 4 | 5 | # Folder config file 6 | Desktop.ini 7 | 8 | # Recycle Bin used on file shares 9 | $RECYCLE.BIN/ 10 | 11 | # Windows Installer files 12 | *.cab 13 | *.msi 14 | *.msm 15 | *.msp 16 | 17 | # Windows shortcuts 18 | *.lnk 19 | 20 | # ========================= 21 | # Operating System Files 22 | # ========================= 23 | 24 | # OSX 25 | # ========================= 26 | 27 | .DS_Store 28 | .AppleDouble 29 | .LSOverride 30 | 31 | # Thumbnails 32 | ._* 33 | 34 | # Files that might appear on external disk 35 | .Spotlight-V100 36 | .Trashes 37 | 38 | # Directories potentially created on remote AFP share 39 | .AppleDB 40 | .AppleDesktop 41 | Network Trash Folder 42 | Temporary Items 43 | .apdisk 44 | 45 | #executable / objects: 46 | *.o 47 | *.elf 48 | ws2812svr -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all: imgclone 2 | 3 | INCL=-I/usr/include 4 | LINK=-L/usr/lib -L/usr/local/lib -I/usr/lib/arm-linux-gnueabihf -pthread 5 | CC=gcc -g $(INCL) 6 | 7 | imgclone: imgclone.c 8 | $(CC) $(LINK) $^ -o $@ 9 | 10 | clean: 11 | rm imgclone 12 | 13 | install: 14 | chmod 777 imgclone 15 | cp imgclone /usr/local/bin 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # imgclone 2 | This program can be used to create a backup of your Raspberry Pi SD card while it's active. It does this in the same way as the official piclone program (https://github.com/raspberrypi-ui/piclone). 3 | Main differences are: 4 | * Command line based, so it can be used to automatically create backups 5 | * It creates a backup to a disk image file (.img). So you can create multiple backups on 1 external SD card, USB drive, USB hard disk or even a network shared drive. 6 | * Result backup will only be a few percent larger than the used disk space of your Raspberry SD card! 7 | 8 | # Installation 9 | On the raspberry you open a terminal window and type following commands: 10 | * `sudo apt-get update` 11 | * `sudo apt-get install gcc make git` 12 | * `git clone https://github.com/tom-2015/imgclone.git` 13 | * `cd imgclone` 14 | * `make` 15 | * `sudo make install` 16 | 17 | # Usage 18 | Attach external storage device (USB, Hard disk or mount a Samba share). cd to the drive: 19 | * `cd /media/pi/` 20 | Start backup: 21 | * `imgclone -d mybackup.img` 22 | 23 | To compress the image AFTER it is made use the -gzip or -bzip2 arguments. 24 | Show copy progress with the -p argument. 25 | 26 | # backup to network drive 27 | Make sure you have a NAS or other Samba shared drive in your network, then just mount it: 28 | * `mkdir /tmp/backup` 29 | * `sudo mount -t cifs /// /tmp/backup` 30 | * `imgclone -d /tmp/backup/mybackup.img` 31 | 32 | # restore backup 33 | You can use standard procedure (dd or win32diskimager) to write the .img file to a (new) SD card. 34 | Insert to your Raspberry and start it! Run sudo raspi-config to expand the file system of the root partition to fill the entire SD card. 35 | See https://www.raspberrypi.org/documentation/installation/installing-images/README.md -------------------------------------------------------------------------------- /imgclone.c: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2018 Raspberry Pi (Trading) Ltd. 3 | All rights reserved. 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | * Redistributions of source code must retain the above copyright 7 | notice, this list of conditions and the following disclaimer. 8 | * Redistributions in binary form must reproduce the above copyright 9 | notice, this list of conditions and the following disclaimer in the 10 | documentation and/or other materials provided with the distribution. 11 | * Neither the name of the copyright holder nor the 12 | names of its contributors may be used to endorse or promote products 13 | derived from this software without specific prior written permission. 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 15 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 16 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY 18 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 19 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 20 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 21 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 23 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | 25 | This program was adjusted to be able to clone the Raspberry Pi SD card to a .img file 26 | 27 | see: 28 | https://github.com/tom-2015/imgclone.git 29 | 30 | */ 31 | 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | 42 | /*---------------------------------------------------------------------------*/ 43 | /* Variable and macro definitions */ 44 | /*---------------------------------------------------------------------------*/ 45 | 46 | /* struct to store partition data */ 47 | 48 | #define MAXPART 9 49 | 50 | typedef struct 51 | { 52 | int pnum; 53 | long long int start; 54 | long long int end; 55 | char ptype[10]; 56 | char ftype[20]; 57 | char flags[10]; 58 | } partition_t; 59 | 60 | typedef struct 61 | { 62 | char * src; 63 | char * dst; 64 | } copy_args; 65 | 66 | partition_t parts[MAXPART]; 67 | 68 | volatile char copying=0; 69 | 70 | /*---------------------------------------------------------------------------*/ 71 | /* System helpers */ 72 | 73 | /* Call a system command and read the first string returned */ 74 | 75 | static int get_string2 (char *cmd, char *name, int print_cmd_line) 76 | { 77 | FILE *fp; 78 | char buf[64]; 79 | int res; 80 | 81 | name[0] = 0; 82 | if (print_cmd_line) printf("%s\n", cmd); 83 | fp = popen (cmd, "r"); 84 | if (fp == NULL) return 0; 85 | if (fgets (buf, sizeof (buf) - 1, fp) == NULL) 86 | { 87 | pclose (fp); 88 | return 0; 89 | } 90 | else 91 | { 92 | pclose (fp); 93 | res = sscanf (buf, "%s", name); 94 | if (res != 1) return 0; 95 | return 1; 96 | } 97 | } 98 | 99 | static int get_string (char *cmd, char *name) 100 | { 101 | return get_string2(cmd, name, 1); 102 | } 103 | 104 | 105 | 106 | /* System function with printf formatting */ 107 | 108 | static int sys_printf (const char * format, ...) 109 | { 110 | char buffer[256]; 111 | char output[256]; 112 | va_list args; 113 | FILE *fp; 114 | 115 | va_start (args, format); 116 | vsprintf (buffer, format, args); 117 | printf ("%s\n",buffer); 118 | fp = popen (buffer, "r"); 119 | while (fgets(output, sizeof(output), fp) != NULL) { 120 | printf("%s", output); 121 | } 122 | va_end (args); 123 | return pclose (fp); 124 | } 125 | 126 | 127 | 128 | /* Get a partition name - format is different on mmcblk from sd */ 129 | 130 | static char *partition_name (char *device, char *buffer) 131 | { 132 | if (!strncmp (device, "/dev/mmcblk", 11) || !strncmp(device, "/dev/loop", 9)) 133 | sprintf (buffer, "%sp", device); 134 | else 135 | sprintf (buffer, "%s", device); 136 | return buffer; 137 | } 138 | 139 | 140 | static void escape_shell_arg(char * dst_buffer, const char * src_buffer){ 141 | while (*src_buffer!='\0'){ 142 | if (*src_buffer=='"' || *src_buffer=='\\'){ 143 | *dst_buffer='\\'; 144 | dst_buffer++; 145 | } 146 | *dst_buffer=*src_buffer; 147 | src_buffer++; 148 | dst_buffer++; 149 | } 150 | *dst_buffer='\0'; 151 | } 152 | 153 | void * copy_thread_func(copy_args * src_dst){ 154 | copying=1; 155 | sys_printf ("cp -ax %s/. %s/.", src_dst->src, src_dst->dst); 156 | copying=0; 157 | return NULL; 158 | } 159 | 160 | /* clone_to_img 161 | This function starts clone to img file 162 | @param src_dev the source device to clone 163 | @param dst_file the destination disk file to clone to (.IMG) 164 | @param new_uuid if 1, new uuid will be generated for destination 165 | @param extra_space add extra free space to the image file for future expansion 166 | @param show_progress if 1 will show copy progress 167 | @param compress if 1 will run bzip2 command to compress image when finished, 2 will run gzip to compress 168 | */ 169 | int clone_to_img (char * src_dev, char * dst_file, char new_uuid, long long int extra_space, char show_progress, char compress) 170 | { 171 | char buffer[1024], res[256], dev[16], uuid[64], puuid[64], npuuid[64], dst_dev[64], src_mnt[64], dst_mnt[64], dst_file_escaped[512]; 172 | int n, p, lbl, uid, puid; 173 | long long int srcsz, dstsz, stime, total_blocks=0, blocks_available=0, file_size_needed=0,available_free_space=0; 174 | double prog; 175 | FILE *fp; 176 | 177 | escape_shell_arg(dst_file_escaped, dst_file); 178 | 179 | // get a new partition UUID 180 | get_string ("uuid | cut -f1 -d-", npuuid); 181 | 182 | // check the source has an msdos partition table 183 | sprintf (buffer, "parted %s unit s print | tail -n +4 | head -n 1", src_dev); 184 | fp = popen (buffer, "r"); 185 | if (fp == NULL) return 1; 186 | if (fgets (buffer, sizeof (buffer) - 1, fp) == NULL) 187 | { 188 | pclose (fp); 189 | fprintf(stderr,"Unable to read source.\n"); 190 | return 2; 191 | } 192 | pclose (fp); 193 | if (strncmp (buffer, "Partition Table: msdos", 22)) 194 | { 195 | fprintf(stderr,"Non-MSDOS partition table on source.\n"); 196 | return 3; 197 | } 198 | 199 | // prepare temp mount points 200 | get_string ("mktemp -d", src_mnt); 201 | get_string ("mktemp -d", dst_mnt); 202 | 203 | printf ("-----------------------------------------------\n"); 204 | printf ("---- READING PARTITIONS ------\n"); 205 | printf ("-----------------------------------------------\n"); 206 | 207 | // read in the source partition table 208 | n = 0; 209 | sprintf (buffer, "parted %s unit s print | sed '/^ /!d'", src_dev); 210 | fp = popen (buffer, "r"); 211 | if (fp != NULL) 212 | { 213 | while (1) 214 | { 215 | if (fgets (buffer, sizeof (buffer) - 1, fp) == NULL) break; 216 | if (n >= MAXPART) 217 | { 218 | pclose (fp); 219 | fprintf(stderr,"Too many partitions on source.\n"); 220 | return 4; 221 | } 222 | 223 | sscanf (buffer, "%d %llds %llds %*ds %s %s %s", &(parts[n].pnum), &(parts[n].start), 224 | &(parts[n].end), (char *) &(parts[n].ptype), (char *) &(parts[n].ftype), (char *) &(parts[n].flags)); 225 | 226 | printf("Partition %d start: %lld end: %lld ptype:%s ftype:%s flags: %s\n.", n+1, parts[n].start, parts[n].end, parts[n].ptype, parts[n].ftype, parts[n].flags); 227 | n++; 228 | 229 | } 230 | pclose (fp); 231 | } 232 | 233 | //get the needed size of the destination image_file 234 | //this is the start of the last partition + used space on last partition 235 | 236 | // belt-and-braces call to partprobe to make sure devices are found... 237 | get_string ("partprobe", res); 238 | 239 | file_size_needed=parts[n-1].start*(long long int)512; //need at least the start of last partition as image size (blocks * block_size) in bytes 240 | printf("Last partition starts at %lld bytes.\n", file_size_needed); 241 | 242 | //mount last partition to get used disk space 243 | if (sys_printf ("mount %s%d %s", partition_name (src_dev, dev), parts[n-1].pnum, src_mnt)) 244 | { 245 | fprintf(stderr,"Could not mount partition %s\n", partition_name (src_dev, dev)); 246 | return 5; 247 | } 248 | 249 | sprintf (buffer, "df %s | tail -n 1 | tr -s \" \" \" \" | cut -d ' ' -f 2", src_mnt); 250 | get_string (buffer, res); 251 | sscanf (res, "%lld", &total_blocks); 252 | sprintf (buffer, "df %s | tail -n 1 | tr -s \" \" \" \" | cut -d ' ' -f 4", src_mnt); 253 | get_string (buffer, res); 254 | sscanf (res, "%lld", &blocks_available); 255 | 256 | long long int partition_size_used = (total_blocks - blocks_available) * 1024; //blocks used reported by df is not including all reserved space for filesystem, seems better to take the blocks available 257 | printf("Used size of last partition %s is %lld bytes.\n", src_mnt, partition_size_used); 258 | 259 | if (sys_printf ("umount %s", src_mnt)) 260 | { 261 | fputs("Could not unmount partition.\n",stderr); 262 | return 6; 263 | } 264 | 265 | file_size_needed+=partition_size_used; 266 | file_size_needed+=file_size_needed*(long long int)2/(long long int)100; //add 2% extra space 267 | file_size_needed+=extra_space; //add extra free space if required 268 | if ((file_size_needed%512)!=0) file_size_needed+=(long long int)(512-(file_size_needed%512)); //align at 512 byte block size 269 | printf("Required size for destination image: %lld bytes\n", file_size_needed); 270 | 271 | printf ("-----------------------------------------------\n"); 272 | printf ("---- ALLOCATING SPACE FOR .IMG FILE ------\n"); 273 | printf ("-----------------------------------------------\n"); 274 | 275 | //create file 276 | if (sys_printf("touch \"%s\"", dst_file_escaped)){ 277 | fprintf(stderr,"Could not create destination file %s.\n", dst_file); 278 | return 24; 279 | } 280 | 281 | //check if file is on a different disk device 282 | sprintf (buffer, "df \"%s\" | tail -n 1 | tr -s \" \" \" \" | cut -d ' ' -f 1", dst_file_escaped); 283 | get_string (buffer, res); 284 | printf("%s\n", res); 285 | if (strncmp(src_dev, buffer, strlen(src_dev))==0){ 286 | fprintf(stderr, "Destination file is located on the disk to clone. Destination file must be on external drive.\n"); 287 | return 25; 288 | } 289 | 290 | //check if there is enough space on the destination disk 291 | sprintf (buffer, "df --output=avail -B 1 \"%s\" | tail -n 1", dst_file_escaped); 292 | get_string (buffer, res); 293 | printf("%s\n", res); 294 | sscanf (res, "%lld", &available_free_space); 295 | if (available_free_space < file_size_needed){ 296 | //sys_printf("rm %s", dst_file); 297 | fprintf(stderr, "Not enough free space to create destination image file %lld free, required %lld bytes.\n", available_free_space, file_size_needed); 298 | return 26; 299 | } 300 | 301 | //make file size big enough 302 | if (sys_printf("truncate --size %lld \"%s\"", file_size_needed, dst_file_escaped)){ 303 | fprintf(stderr,"Could not create file large enough on destination disk.\n"); 304 | return 23; 305 | } 306 | 307 | printf ("-----------------------------------------------\n"); 308 | printf ("---- CREATING DISK DEVICE FOR .IMG FILE ------\n"); 309 | printf ("-----------------------------------------------\n"); 310 | 311 | //create device, returns the /dev/loopX interface, which is dst_dev 312 | sprintf(buffer, "losetup --show -f \"%s\"", dst_file_escaped); 313 | get_string(buffer, dst_dev); 314 | 315 | //printf("Unmounting partitions on target\n"); 316 | // unmount any partitions on the target device 317 | //for (n = 9; n >= 1; n--) 318 | //{ 319 | // sys_printf ("umount %s%d", partition_name (dst_dev, dev), n); 320 | //} 321 | 322 | // wipe the FAT on the target 323 | if (sys_printf ("dd if=/dev/zero of=%s bs=512 count=1", dst_dev)) 324 | { 325 | fprintf(stderr,"Could not write to destination device %s\n", dst_dev); 326 | return 7; 327 | } 328 | 329 | printf("Creating FAT on %s.\n", dst_dev); 330 | 331 | // prepare the new FAT 332 | if (sys_printf ("parted -s %s mklabel msdos", dst_dev)) 333 | { 334 | fprintf(stderr,"Could not create FAT.\n"); 335 | return 8; 336 | } 337 | 338 | printf ("-----------------------------------------------\n"); 339 | printf ("---- CREATING PARTITIONS PLEASE WAIT ------\n"); 340 | printf ("-----------------------------------------------\n"); 341 | 342 | // recreate the partitions on the target 343 | for (p = 0; p < n; p++) 344 | { 345 | // create the partition 346 | if (!strcmp (parts[p].ptype, "extended")) 347 | { 348 | if (sys_printf ("parted -s %s -- mkpart extended %llds -1s", dst_dev, parts[p].start)) 349 | { 350 | fprintf(stderr,"Could not create partition.\n"); 351 | return 9; 352 | } 353 | } 354 | else 355 | { 356 | if (p == (n - 1)) 357 | { 358 | if (sys_printf ("parted -s %s -- mkpart %s %s %llds -1s", dst_dev, 359 | parts[p].ptype, parts[p].ftype, parts[p].start)) 360 | { 361 | fprintf(stderr,"Could not create partition.\n"); 362 | return 10; 363 | } 364 | } 365 | else 366 | { 367 | if (sys_printf ("parted -s %s mkpart %s %s %llds %llds", dst_dev, 368 | parts[p].ptype, parts[p].ftype, parts[p].start, parts[p].end)) 369 | { 370 | fprintf(stderr,"Could not create partition.\n"); 371 | return 11; 372 | } 373 | } 374 | } 375 | 376 | // refresh the kernel partion table 377 | sys_printf ("partprobe"); 378 | 379 | // get the UUID 380 | sprintf (buffer, "lsblk -o name,uuid %s | grep %s%d | tr -s \" \" | cut -d \" \" -f 2", src_dev, partition_name (src_dev, dev) + 5, parts[p].pnum); 381 | uid = get_string (buffer, uuid); 382 | if (uid) 383 | { 384 | // sanity check the ID 385 | if (strlen (uuid) == 9) 386 | { 387 | if (uuid[4] == '-') 388 | { 389 | // remove the hyphen from the middle of a FAT volume ID 390 | uuid[4] = uuid[5]; 391 | uuid[5] = uuid[6]; 392 | uuid[6] = uuid[7]; 393 | uuid[7] = uuid[8]; 394 | uuid[8] = 0; 395 | } 396 | else uid = 0; 397 | } 398 | else if (strlen (uuid) == 36) 399 | { 400 | // check there are hyphens in the right places in a UUID 401 | if (uuid[8] != '-') uid = 0; 402 | if (uuid[13] != '-') uid = 0; 403 | if (uuid[18] != '-') uid = 0; 404 | if (uuid[23] != '-') uid = 0; 405 | } 406 | else uid = 0; 407 | } 408 | 409 | // get the label 410 | sprintf (buffer, "lsblk -o name,label %s | grep %s%d | tr -s \" \" | cut -d \" \" -f 2", src_dev, partition_name (src_dev, dev) + 5, parts[p].pnum); 411 | lbl = get_string (buffer, res); 412 | if (!strlen (res)) lbl = 0; 413 | 414 | // get the partition UUID 415 | sprintf (buffer, "blkid %s | rev | cut -f 2 -d ' ' | rev | cut -f 2 -d \\\"", src_dev); 416 | puid = get_string (buffer, puuid); 417 | if (!strlen (puuid)) puid = 0; 418 | 419 | // create file systems 420 | if (!strncmp (parts[p].ftype, "fat", 3)) 421 | { 422 | if (uid) sprintf (buffer, "mkfs.fat -F 32 -i %s %s%d", uuid, partition_name (dst_dev, dev), parts[p].pnum); 423 | else sprintf (buffer, "mkfs.fat -F 32 %s%d", partition_name (dst_dev, dev), parts[p].pnum); 424 | 425 | if (sys_printf (buffer)) 426 | { 427 | if (uid) 428 | { 429 | // second try just in case the only problem was a corrupt UUID 430 | sprintf (buffer, "mkfs.fat -F 32 %s%d", partition_name (dst_dev, dev), parts[p].pnum); 431 | if (sys_printf (buffer)) 432 | { 433 | fprintf(stderr, "Could not create file system on uid %s: %s\n", uuid, buffer); 434 | return 12; 435 | } 436 | } 437 | else 438 | { 439 | fprintf(stderr, "Could not create file system: %s\n", buffer); 440 | return 13; 441 | } 442 | } 443 | 444 | if (lbl) sys_printf ("fatlabel %s%d %s", partition_name (dst_dev, dev), parts[p].pnum, res); 445 | } 446 | 447 | if (!strcmp (parts[p].ftype, "ext4")) 448 | { 449 | if (uid) sprintf (buffer, "mkfs.ext4 -F -U %s %s%d", uuid, partition_name (dst_dev, dev), parts[p].pnum); 450 | else sprintf (buffer, "mkfs.ext4 -F %s%d", partition_name (dst_dev, dev), parts[p].pnum); 451 | 452 | if (sys_printf (buffer)) 453 | { 454 | if (uid) 455 | { 456 | // second try just in case the only problem was a corrupt UUID 457 | sprintf (buffer, "mkfs.ext4 -F %s%d", partition_name (dst_dev, dev), parts[p].pnum); 458 | if (sys_printf (buffer)) 459 | { 460 | fprintf(stderr,"Could not create file system.\n"); 461 | return 14; 462 | } 463 | } 464 | else 465 | { 466 | fprintf(stderr,"Could not create file system.\n"); 467 | return 15; 468 | } 469 | } 470 | 471 | if (lbl) sys_printf ("e2label %s%d %s", partition_name (dst_dev, dev), parts[p].pnum, res); 472 | } 473 | 474 | // write the partition UUID 475 | if (puid) sys_printf ("echo \"x\ni\n0x%s\nr\nw\n\" | fdisk %s", new_uuid ? npuuid : puuid, dst_dev); 476 | 477 | prog = p + 1; 478 | prog /= n; 479 | //gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (progress), prog); 480 | } 481 | 482 | printf("%d partitions created, now copy files.\n", n); 483 | 484 | // do the copy for each partition 485 | for (p = 0; p < n; p++) 486 | { 487 | // don't try to copy extended partitions 488 | if (strcmp (parts[p].ptype, "extended")) 489 | { 490 | printf ("Copying partition %d of %d...\n", p + 1, n); 491 | printf ("Copy from %s to %s\n", src_mnt, dst_mnt); 492 | 493 | // belt-and-braces call to partprobe to make sure devices are found... 494 | get_string ("partprobe", res); 495 | 496 | // mount partitions 497 | if (sys_printf ("mount %s%d %s", partition_name (dst_dev, dev), parts[p].pnum, dst_mnt)) 498 | { 499 | fprintf(stderr,"Could not mount partition.\n"); 500 | return 16; 501 | } 502 | 503 | if (sys_printf ("mount %s%d %s", partition_name (src_dev, dev), parts[p].pnum, src_mnt)) 504 | { 505 | fprintf(stderr,"Could not mount partition.\n"); 506 | return 17; 507 | } 508 | 509 | // check there is enough space... 510 | sprintf (buffer, "df %s | tail -n 1 | tr -s \" \" \" \" | cut -d ' ' -f 3", src_mnt); 511 | get_string (buffer, res); 512 | sscanf (res, "%lld", &srcsz); 513 | 514 | sprintf (buffer, "df %s | tail -n 1 | tr -s \" \" \" \" | cut -d ' ' -f 4", dst_mnt); 515 | get_string (buffer, res); 516 | sscanf (res, "%lld", &dstsz); 517 | 518 | if (srcsz >= dstsz) 519 | { 520 | sys_printf ("umount %s", dst_mnt); 521 | sys_printf ("umount %s", src_mnt); 522 | fprintf(stderr,"Insufficient space. Backup aborted.\n"); 523 | return 18; 524 | } 525 | 526 | printf ("-----------------------------------------------\n"); 527 | printf ("---- COPYING FILES PLEASE WAIT ------\n"); 528 | printf ("-----------------------------------------------\n"); 529 | 530 | if (show_progress){ 531 | long long int progress_max; 532 | long long int progress_done; 533 | copy_args src_dst; 534 | pthread_t copy_thread; 535 | 536 | src_dst.src=src_mnt; 537 | src_dst.dst=dst_mnt; 538 | copying=1; 539 | if(pthread_create(©_thread, NULL, (void* (*)(void*)) & copy_thread_func, &src_dst)) { 540 | fprintf(stderr, "Error creating copy thread\n"); 541 | return 27; 542 | } 543 | 544 | // get the size to be copied 545 | /*sprintf (buffer, "du -s %s", src_mnt); 546 | get_string (buffer, res); 547 | sscanf (res, "%ld", &progress_max);*/ 548 | progress_max = srcsz; 549 | if (progress_max < 50000) stime = 1; 550 | else if (progress_max < 500000) stime = 5; 551 | else stime = 10; 552 | 553 | // wait for the copy to complete, while updating the progress bar... 554 | //sprintf (buffer, "du -s %s", dst_mnt); 555 | sprintf (buffer, "df %s | tail -n 1 | tr -s \" \" \" \" | cut -d ' ' -f 3", dst_mnt); 556 | sleep (5); 557 | printf("0%%"); 558 | while (copying) 559 | { 560 | get_string2 (buffer, res, 0); 561 | sscanf (res, "%lld", &progress_done); 562 | prog = 100.0 * progress_done / progress_max; 563 | if (prog > 100) prog=100; 564 | printf("\r%d%%", (int)prog); 565 | fflush(stdout); 566 | sleep (stime); 567 | } 568 | printf ("\r100%%\n"); 569 | /* wait for the second thread to finish */ 570 | if(pthread_join(copy_thread, NULL)) { 571 | fprintf(stderr, "Error joining thread\n"); 572 | return 28; 573 | } 574 | 575 | }else{ 576 | sys_printf ("cp -ax %s/. %s/.", src_mnt, dst_mnt); 577 | } 578 | 579 | // fix up relevant files if changing partition UUID 580 | if (puid && new_uuid) 581 | { 582 | // relevant files are dst_mnt/etc/fstab and dst_mnt/boot/cmdline.txt 583 | sys_printf ("if [ -e /%s/etc/fstab ] ; then sed -i s/%s/%s/g /%s/etc/fstab ; fi", dst_mnt, puuid, npuuid, dst_mnt); 584 | sys_printf ("if [ -e /%s/cmdline.txt ] ; then sed -i s/%s/%s/g /%s/cmdline.txt ; fi", dst_mnt, puuid, npuuid, dst_mnt); 585 | } 586 | 587 | // unmount partitions 588 | int timeout=30; 589 | while (sys_printf ("umount %s", dst_mnt) && timeout>0) 590 | { 591 | sleep(10); 592 | timeout--; 593 | if (timeout==0){ 594 | fprintf(stderr,"Could not unmount partition %s.\n", dst_mnt); 595 | return 19; 596 | } 597 | } 598 | 599 | if (sys_printf ("umount %s", src_mnt)) 600 | { 601 | fprintf(stderr,"Warning: could not unmount partition source partition %s.\n", src_mnt); 602 | //return 20; 603 | } 604 | 605 | //if (sys_printf ("umount %s%d", partition_name (dst_dev, dev), parts[p].pnum)){ 606 | 607 | //} 608 | } 609 | 610 | // set the flags 611 | if (!strcmp (parts[p].flags, "lba")) 612 | { 613 | if (sys_printf ("parted -s %s set %d lba on", dst_dev, parts[p].pnum)) 614 | { 615 | fprintf(stderr,"Could not set flags.\n"); 616 | return 21; 617 | } 618 | } 619 | else 620 | { 621 | if (sys_printf ("parted -s %s set %d lba off", dst_dev, parts[p].pnum)) 622 | { 623 | fprintf(stderr,"Could not set flags.\n"); 624 | return 22; 625 | } 626 | } 627 | } 628 | 629 | //release the image file 630 | if (sys_printf("losetup -d %s", dst_dev)){ 631 | fprintf(stderr,"Error releasing device %s.\n", dst_dev); 632 | return 24; 633 | }else{ 634 | printf("Backup completed!\n"); 635 | } 636 | 637 | //delete partitions from devices 638 | for (p = 0; p < n; p++){ 639 | sys_printf ("rm %s%d", partition_name (dst_dev, dev), parts[p].pnum); 640 | } 641 | 642 | if (compress==1){ 643 | printf("Compressing image.\n"); 644 | sys_printf("bzip2 -f \"%s\"", dst_file_escaped); 645 | } 646 | 647 | if (compress==2){ 648 | printf("Compressing image.\n"); 649 | sys_printf("gzip -f \"%s\"", dst_file_escaped); 650 | } 651 | 652 | return 0; 653 | } 654 | 655 | 656 | /*---------------------------------------------------------------------------*/ 657 | /* Main function */ 658 | int main (int argc, char *argv[]) 659 | { 660 | char dst_file[64]; 661 | char src_dev[64]; 662 | char new_uuid=0; 663 | char show_progress=0; 664 | char compress=0; 665 | long long int extra_space=(long long int)512*(long long int)20480; //10MB extra space 666 | int i; 667 | 668 | printf ("---- Raspberry Pi clone to image V1.8 ---\n"); 669 | printf ("-----------------------------------------------\n"); 670 | printf ("---- DO NOT CHANGE FILES ON YOUR SD CARD ---\n"); 671 | printf ("---- WHILE THE BACKUP PROGRAM IS RUNNING ---\n"); 672 | printf ("---- THE DESTINATION .IMG FILE MUST BE ---\n"); 673 | printf ("---- ON AN EXTERNAL STORAGE / NETWORK SHARE ---\n"); 674 | printf ("-----------------------------------------------\n"); 675 | 676 | sprintf(src_dev, "/dev/mmcblk0"); 677 | dst_file[0]=0; 678 | 679 | for (i=1;i image file must be located on an external drive, you cannot backup to a file on the SD card!\n"); 721 | printf("Usage:\n"); 722 | printf("imgclone [-u 1] [-s ] -d \n"); 723 | printf(" -u 1 optional creates new UUID for the partitions.\n"); 724 | printf(" -s creates a backup of source_device, optional default is /dev/mmcblk0.\n"); 725 | printf(" -d backup to destination_file.\n"); 726 | printf(" -x add extra bytes of free space to the last partition.\n"); 727 | printf(" -p write copy progress to output.\n"); 728 | printf(" -bzip2 use bzip2 command to compress the image after cloning.\n"); 729 | printf(" -gzip use gzip command to compress the image after cloning.\n"); 730 | return 0; 731 | }else{ 732 | fprintf(stderr,"Invalid argument %s\n", argv[i]); 733 | return 1; 734 | } 735 | } 736 | 737 | if (strlen(dst_file)==0){ 738 | fprintf(stderr,"Missing destination file argument (-d ).\n"); 739 | return 1; 740 | } 741 | 742 | 743 | printf("Cloning %s to %s\n", src_dev, dst_file); 744 | switch (compress){ 745 | case 1: 746 | printf("bzip2 compress is on.\n"); 747 | break; 748 | case 2: 749 | printf("gzip compress in on.\n"); 750 | break; 751 | } 752 | if (show_progress){ 753 | printf("Show progress is on.\n"); 754 | } 755 | return clone_to_img(src_dev, dst_file, new_uuid, extra_space, show_progress, compress); 756 | } 757 | --------------------------------------------------------------------------------