99import os , sys , datetime , socket , time , argparse , re
1010from sys import stderr
1111from base64 import standard_b64decode as b64dec
12- from progressbar import ProgressBar , Percentage , ETA , FileTransferSpeed , DataSize
12+ from progressbar import ProgressBar , Percentage , FileTransferSpeed , DataSize
1313from tabulate import tabulate
1414from enum import Enum
1515from hashlib import md5
1616from collections import namedtuple , OrderedDict as odict
1717
18+ from .version import __version__
1819from .adb_wrapper import AdbWrapper
1920from .adb_stuff import *
2021
2930
3031def parse_args (args = None ):
3132 p = argparse .ArgumentParser (description = '''Tool to create TWRP and nandroid-style backups of an Android device running TWRP recovery, using adb-over-USB, without touching the device's internal storage or SD card.''' )
33+ p .version = __version__
34+ p .add_argument ('--version' , action = 'version' )
3235 p .add_argument ('-s' , dest = 'specific' , metavar = 'DEVICE_ID' , default = None , help = "Specific device ID (shown by adb devices). Default is sole USB-connected device." )
3336 p .add_argument ('-o' , '--output-path' , default = "." , help = "Set optional output path for backup files." )
3437 p .add_argument ('-N' , '--nandroid' , action = 'store_true' , help = "Make nandroid backup; raw images rather than tarballs for /system and /data partitions (default is TWRP backup)" )
@@ -55,7 +58,8 @@ def parse_args(args=None):
5558 g .add_argument ('-U' , '--no-userdata' , dest = 'userdata' , action = 'store_false' , default = True , help = "Omit /data partition from backup" )
5659 g .add_argument ('-S' , '--no-system' , dest = 'system' , action = 'store_false' , default = True , help = "Omit /system partition from backup" )
5760 g .add_argument ('-B' , '--no-boot' , dest = 'boot' , action = 'store_false' , default = True , help = "Omit boot partition from backup" )
58- g .add_argument ('-X' , '--extra' , action = 'append' , dest = 'extra' , metavar = 'NAME' , default = [], help = "Include extra partition as raw image" )
61+ g .add_argument ('-X' , '--extra' , action = 'append' , metavar = 'NAME' , default = [], help = "Include extra partition (as a tarball if this partition is mountable and TWRP backup type is chosen, otherwise as raw image)" )
62+ g .add_argument ('--extra-raw' , action = 'append' , metavar = 'NAME' , default = [], help = "Include extra partition (always as raw image)" )
5963 return p , p .parse_args (args )
6064
6165def check_adb_version (p , adb ):
@@ -119,49 +123,59 @@ def sensible_transport(transport, adbversion):
119123 return adbxp .tcp
120124 return transport
121125
122- def build_partmap (adb , mmcblk = 'mmcblk0' , fstab = '/etc/fstab' ):
126+ def build_partmap (adb , mmcblks = None , fstab = '/etc/fstab' ):
123127 # build partition map
124128 partmap = odict ()
125129 fstab = fstab_dict (adb , fstab )
126- d = uevent_dict (adb , '/sys/block/%s/uevent' % mmcblk )
127- nparts = int (d ['NPARTS' ])
128- print ("Reading partition map for %s (%d partitions)..." % (mmcblk , nparts ), file = stderr )
129- pbar = ProgressBar (max_value = nparts , widgets = [' partition map: ' , Percentage (), ' ' , ETA ()]).start ()
130- for ii in range (1 , nparts + 1 ):
131- d = uevent_dict (adb , '/sys/block/%s/%sp%d/uevent' % (mmcblk , mmcblk , ii ))
132- devname , partn = d ['DEVNAME' ], int (d ['PARTN' ])
133- size = int (adb .check_output (('shell' ,'cat /sys/block/%s/%sp%d/size' % (mmcblk , mmcblk , ii ))))
134- mountpoint , fstype = fstab .get ('/dev/block/%s' % d ['DEVNAME' ], (None , None ))
135-
136- # some devices have uppercase names, see #14
137- partname = d ['PARTNAME' ].lower ()
138-
139- # some devices apparently use non-standard partition names, though standard mount points, see #18
140- if partname == 'system' or mountpoint == '/system' :
141- standard = 'system'
142- elif partname == 'userdata' or mountpoint == '/data' :
143- standard = 'userdata'
144- elif partname == 'cache' or mountpoint == '/cache' :
145- standard = 'cache'
130+ if mmcblks is None :
131+ mmcblks = adb .check_output (('shell' ,'cd /sys/block; ls -1d mmcblk*' )).splitlines ()
132+ for mmcblk in mmcblks :
133+ d = uevent_dict (adb , '/sys/block/%s/uevent' % mmcblk )
134+ nparts = int (d .get ('NPARTS' ,0 ))
135+ print ("Reading partition map for %s (%d partitions)..." % (mmcblk , nparts ), file = stderr )
136+ pbar = ProgressBar (max_value = nparts , widgets = [' partition map: ' , Percentage ()]).start ()
137+ for ii in range (1 , nparts + 1 ):
138+ d = uevent_dict (adb , '/sys/block/%s/%sp%d/uevent' % (mmcblk , mmcblk , ii ))
139+ devname , partn = d ['DEVNAME' ], int (d ['PARTN' ])
140+ size = int (adb .check_output (('shell' ,'cat /sys/block/%s/%sp%d/size' % (mmcblk , mmcblk , ii ))))
141+ mountpoint , fstype = fstab .get ('/dev/block/%s' % d ['DEVNAME' ], (None , None ))
142+
143+ # some devices have uppercase names, see #14
144+ partname = d ['PARTNAME' ].lower ()
145+
146+ # some devices apparently use non-standard partition names, though standard mount points, see #18
147+ if partname == 'system' or mountpoint == '/system' :
148+ standard = 'system'
149+ elif partname == 'userdata' or mountpoint == '/data' :
150+ standard = 'userdata'
151+ elif partname == 'cache' or mountpoint == '/cache' :
152+ standard = 'cache'
153+ else :
154+ standard = partname
155+
156+ partmap [standard ] = PartInfo (partname , devname , partn , size , mountpoint , fstype )
157+ pbar .update (ii )
146158 else :
147- standard = partname
148-
149- partmap [standard ] = PartInfo (partname , devname , partn , size , mountpoint , fstype )
150- pbar .update (ii )
159+ pbar .finish ()
151160 else :
152- pbar .finish ()
153161 return partmap
154162
155- def plan_backup (args ):
163+ def plan_backup (args , partmap ):
156164 # Build table of partitions requested for backup
157165 if args .nandroid :
158- rp = args .extra + [x for x in ('boot' ,'recovery' ,'system' ,'userdata' ,'cache' ) if getattr (args , x )]
166+ rp = args .extra + args . extra_raw + [x for x in ('boot' ,'recovery' ,'system' ,'userdata' ,'cache' ) if getattr (args , x )]
159167 plan = odict ((p ,BackupPlan ('%s.emmc.gz' % p , None )) for p in rp )
160168 else :
161- rp = args .extra + [x for x in ('boot' ,'recovery' ) if getattr (args , x )]
169+ # Figure out which of the --extra partitions can't actually be mounted and exile them to --extra-raw
170+ extra_raw = args .extra_raw
171+ extra_mount = []
172+ for p in args .extra :
173+ (extra_mount if p in partmap and partmap [p ].fstype else extra_raw ).append (p )
174+
175+ rp = extra_raw + [x for x in ('boot' ,'recovery' ) if getattr (args , x )]
162176 plan = odict ((p ,BackupPlan ('%s.emmc.win' % p , None )) for p in rp )
163- mp = [x for x in ('cache' ,'system' ) if getattr (args , x )]
164- plan .update ((p ,BackupPlan ('%s.ext4 .win' % p , '-p' )) for p in mp )
177+ mp = extra_mount + [x for x in ('cache' ,'system' ) if getattr (args , x )]
178+ plan .update ((p ,BackupPlan ('%s.%s .win' % ( p , partmap [ p ]. fstype ) , '-p' )) for p in mp )
165179
166180 if args .userdata :
167181 data_omit = []
@@ -244,7 +258,7 @@ def backup_partition(adb, pi, bp, transport, verify=True):
244258 s .connect (('localhost' , port ))
245259 block_iter = iter (lambda : s .recv (65536 ), b'' )
246260
247- pbwidgets = [' %s: ' % bp .fn , Percentage (), ' ' , ETA (), ' ' , FileTransferSpeed (), ' ' , DataSize () ]
261+ pbwidgets = [' %s: ' % bp .fn , Percentage (), ' ' , FileTransferSpeed (), ' ' , DataSize () ]
248262 pbar = ProgressBar (max_value = pi .size * 512 , widgets = pbwidgets ).start ()
249263
250264 with open (bp .fn , 'wb' ) as out :
@@ -275,7 +289,9 @@ def backup_partition(adb, pi, bp, transport, verify=True):
275289
276290def main (args = None ):
277291 p , args = parse_args (args )
278- adb = AdbWrapper ('adb' , ('-s' ,args .specific ) if args .specific else ('-d' ,))
292+ adb = AdbWrapper ('adb' , ('-s' ,args .specific ) if args .specific else ('-d' ,), debug = (args .verbose > 1 ))
293+
294+ print ('%s v%s' % (p .prog , p .version ), file = stderr )
279295
280296 # check adb version, and TWRP recovery
281297 adbversion = check_adb_version (p , adb )
@@ -284,7 +300,7 @@ def main(args=None):
284300
285301 # build partition map and backup plan
286302 partmap = build_partmap (adb )
287- plan = plan_backup (args )
303+ plan = plan_backup (args , partmap )
288304 missing = set (plan ) - set (partmap )
289305
290306 # print partition map and backup explanation
0 commit comments