1313from tabulate import tabulate
1414from enum import Enum
1515from hashlib import md5
16+ from collections import OrderedDict as odict
1617
1718adbxp = Enum ('AdbTransport' , 'tcp pipe_xo pipe_b64 pipe_bin' )
1819
7576
7677if args .nandroid :
7778 rp = args .extra + [x for x in ('boot' ,'recovery' ,'system' ,'userdata' ,'cache' ) if getattr (args , x )]
78- backup_partitions = { p : ('%s.tar.gz' % p , None , None ) for p in rp }
79+ backup_partitions = odict (( p , ('%s.tar.gz' % p , None , None )) for p in rp )
7980else :
8081 rp = args .extra + [x for x in ('boot' ,'recovery' ) if getattr (args , x )]
81- backup_partitions = { p : ('%s.emmc.win' % p , None , None ) for p in rp }
82+ backup_partitions = odict (( p , ('%s.emmc.win' % p , None , None )) for p in rp )
8283 mp = [x for x in ('cache' ,'system' ) if getattr (args , x )]
83- backup_partitions .update (** { p : ('%s.ext4.win' % p , '/%s' % p , '-p' ) for p in mp } )
84+ backup_partitions .update (( p , ('%s.ext4.win' % p , '/%s' % p , '-p' )) for p in mp )
8485
8586 if args .userdata :
8687 data_omit = []
@@ -156,7 +157,7 @@ def uevent_dict(path):
156157 print ("Device reports TWRP version %s" % m .group (1 ), file = stderr )
157158
158159# build partition map
159- partmap = []
160+ partmap = odict ()
160161d = uevent_dict ('/sys/block/mmcblk0/uevent' )
161162nparts = int (d ['NPARTS' ])
162163print ("Reading partition map for mmcblk0 (%d partitions)..." % nparts , file = stderr )
@@ -165,18 +166,25 @@ def uevent_dict(path):
165166 d = uevent_dict ('/sys/block/mmcblk0/mmcblk0p%d/uevent' % ii )
166167 size = int (sp .check_output (adbcmd + ('shell' ,'cat /sys/block/mmcblk0/mmcblk0p%d/size' % ii )))
167168 # some devices have uppercase names, see #14
168- partmap . append (( d ['PARTNAME' ].lower (), d ['DEVNAME' ], int (d ['PARTN' ]), size ) )
169+ partmap [ d ['PARTNAME' ].lower ()] = ( d ['DEVNAME' ], int (d ['PARTN' ]), size )
169170 pbar .update (ii )
170171else :
171172 pbar .finish ()
172173
173- if args .dry_run or args .verbose > 0 :
174+ # check that all partitions intended for backup exist
175+ missing = set (backup_partitions ) - set (partmap )
176+
177+ # print partition map
178+ if args .dry_run or missing or args .verbose > 0 :
174179 print ()
175180 print (tabulate ( [[ devname , partname , size // 2 ] + backup_how (partname , backup_partitions )
176- for partname , devname , partn , size in partmap ],
181+ for partname , ( devname , partn , size ) in partmap . items () ],
177182 [ 'BLOCK DEVICE' ,'NAME' ,'SIZE (KiB)' ,'FILENAME' ,'FORMAT' ] ))
178183 print ()
179184
185+ if missing :
186+ p .error ("Partitions were requested for backup, but not found in the partition map: %s" % ', ' .join (missing ))
187+
180188if args .dry_run :
181189 p .exit ()
182190
@@ -194,79 +202,78 @@ def uevent_dict(path):
194202if args .verify :
195203 sp .check_call (adbcmd + ('shell' ,'rm -f /tmp/md5in; mknod /tmp/md5in p' ), stderr = sp .DEVNULL )
196204
197- for partname , devname , partn , size in partmap :
198- if partname in backup_partitions :
199- fn , mount , taropts = backup_partitions [partname ]
205+ for partname , (fn , mount , taropts ) in backup_partitions .items ():
206+ devname , partn , size = partmap [partname ]
207+
208+ if mount :
209+ print ("Saving tarball of %s (mounted at %s), %d MiB uncompressed..." % (devname , mount , size / 2048 ))
210+ fstype = really_mount ('/dev/block/' + devname , mount )
211+ if not fstype :
212+ raise RuntimeError ('%s: could not mount %s' % (partname , mount ))
213+ elif fstype != 'ext4' :
214+ raise RuntimeError ('%s: expected ext4 filesystem, but found %s' % (partname , fstype ))
215+ cmdline = 'tar -czC %s %s . 2> /dev/null' % (mount , taropts or '' )
216+ else :
217+ print ("Saving partition %s (%s), %d MiB uncompressed..." % (partname , devname , size / 2048 ))
218+ if not really_umount ('/dev/block/' + devname , mount ):
219+ raise RuntimeError ('%s: could not unmount %s' % (partname , mount ))
220+ cmdline = 'dd if=/dev/block/%s 2> /dev/null | gzip -f' % devname
221+
222+ if args .verify :
223+ cmdline = 'md5sum /tmp/md5in > /tmp/md5out & %s | tee /tmp/md5in' % cmdline
224+ localmd5 = md5 ()
225+
226+ if args .transport == adbxp .pipe_bin :
227+ # need stty -onlcr to make adb-shell an 8-bit-clean pipe: http://stackoverflow.com/a/20141481/20789
228+ child = sp .Popen (adbcmd + ('shell' ,'stty -onlcr && ' + cmdline ), stdout = sp .PIPE )
229+ block_iter = iter (lambda : child .stdout .read (65536 ), b'' )
230+ elif args .transport == adbxp .pipe_b64 :
231+ # pipe output through base64: excruciatingly slow
232+ child = sp .Popen (adbcmd + ('shell' ,cmdline + '| base64' ), stdout = sp .PIPE )
233+ block_iter = iter (lambda : b64dec (b'' .join (child .stdout .readlines (65536 ))), b'' )
234+ elif args .transport == adbxp .pipe_xo :
235+ # use adb exec-out, which is
236+ # (a) only available with newer versions of adb on the host, and
237+ # (b) only works with newer versions of TWRP (works with 2.8.0 for @kerlerm)
238+ # https://plus.google.com/110558071969009568835/posts/Ar3FdhknHo3
239+ # https://android.googlesource.com/platform/system/core/+/5d9d434efadf1c535c7fea634d5306e18c68ef1f/adb/commandline.c#1244
240+ child = sp .Popen (adbcmd + ('exec-out' ,cmdline ), stdout = sp .PIPE )
241+ block_iter = iter (lambda : child .stdout .read (65536 ), b'' )
242+ else :
243+ port = really_forward (5600 + partn , 5700 + partn )
244+ if not port :
245+ raise RuntimeError ('%s: could not ADB-forward a TCP port' )
246+ child = sp .Popen (adbcmd + ('shell' ,cmdline + '| nc -l -p%d -w3' % port ), stdout = sp .PIPE )
200247
201- if mount :
202- print ("Saving tarball of %s (mounted at %s), %d MiB uncompressed..." % (devname , mount , size / 2048 ))
203- fstype = really_mount ('/dev/block/' + devname , mount )
204- if not fstype :
205- raise RuntimeError ('%s: could not mount %s' % (partname , mount ))
206- elif fstype != 'ext4' :
207- raise RuntimeError ('%s: expected ext4 filesystem, but found %s' % (partname , fstype ))
208- cmdline = 'tar -czC %s %s . 2> /dev/null' % (mount , taropts or '' )
209- else :
210- print ("Saving partition %s (%s), %d MiB uncompressed..." % (partname , devname , size / 2048 ))
211- if not really_umount ('/dev/block/' + devname , mount ):
212- raise RuntimeError ('%s: could not unmount %s' % (partname , mount ))
213- cmdline = 'dd if=/dev/block/%s 2> /dev/null | gzip -f' % devname
214-
215- if args .verify :
216- cmdline = 'md5sum /tmp/md5in > /tmp/md5out & %s | tee /tmp/md5in' % cmdline
217- localmd5 = md5 ()
218-
219- if args .transport == adbxp .pipe_bin :
220- # need stty -onlcr to make adb-shell an 8-bit-clean pipe: http://stackoverflow.com/a/20141481/20789
221- child = sp .Popen (adbcmd + ('shell' ,'stty -onlcr && ' + cmdline ), stdout = sp .PIPE )
222- block_iter = iter (lambda : child .stdout .read (65536 ), b'' )
223- elif args .transport == adbxp .pipe_b64 :
224- # pipe output through base64: excruciatingly slow
225- child = sp .Popen (adbcmd + ('shell' ,cmdline + '| base64' ), stdout = sp .PIPE )
226- block_iter = iter (lambda : b64dec (b'' .join (child .stdout .readlines (65536 ))), b'' )
227- elif args .transport == adbxp .pipe_xo :
228- # use adb exec-out, which is
229- # (a) only available with newer versions of adb on the host, and
230- # (b) only works with newer versions of TWRP (works with 2.8.0 for @kerlerm)
231- # https://plus.google.com/110558071969009568835/posts/Ar3FdhknHo3
232- # https://android.googlesource.com/platform/system/core/+/5d9d434efadf1c535c7fea634d5306e18c68ef1f/adb/commandline.c#1244
233- child = sp .Popen (adbcmd + ('exec-out' ,cmdline ), stdout = sp .PIPE )
234- block_iter = iter (lambda : child .stdout .read (65536 ), b'' )
248+ # FIXME: need a better way to check that socket is ready to transmit
249+ time .sleep (1 )
250+ s = socket .socket (socket .AF_INET , socket .SOCK_STREAM )
251+ s .connect (('localhost' , port ))
252+ block_iter = iter (lambda : s .recv (65536 ), b'' )
253+
254+ pbwidgets = [' %s: ' % fn , Percentage (), ' ' , ETA (), ' ' , FileTransferSpeed (), ' ' , DataSize () ]
255+ pbar = ProgressBar (max_value = size * 512 , widgets = pbwidgets ).start ()
256+
257+ with open (fn , 'wb' ) as out :
258+ for block in block_iter :
259+ out .write (block )
260+ if args .verify :
261+ localmd5 .update (block )
262+ pbar .update (out .tell ())
235263 else :
236- port = really_forward (5600 + partn , 5700 + partn )
237- if not port :
238- raise RuntimeError ('%s: could not ADB-forward a TCP port' )
239- child = sp .Popen (adbcmd + ('shell' ,cmdline + '| nc -l -p%d -w3' % port ), stdout = sp .PIPE )
240-
241- # FIXME: need a better way to check that socket is ready to transmit
242- time .sleep (1 )
243- s = socket .socket (socket .AF_INET , socket .SOCK_STREAM )
244- s .connect (('localhost' , port ))
245- block_iter = iter (lambda : s .recv (65536 ), b'' )
246-
247- pbwidgets = [' %s: ' % fn , Percentage (), ' ' , ETA (), ' ' , FileTransferSpeed (), ' ' , DataSize () ]
248- pbar = ProgressBar (max_value = size * 512 , widgets = pbwidgets ).start ()
249-
250- with open (fn , 'wb' ) as out :
251- for block in block_iter :
252- out .write (block )
253- if args .verify :
254- localmd5 .update (block )
255- pbar .update (out .tell ())
256- else :
257- pbar .max_value = out .tell () or pbar .max_value # need to adjust for the smaller compressed size
258- pbar .finish ()
259-
260- if args .verify :
261- devicemd5 = sp .check_output (adbcmd + ('shell' ,'cat /tmp/md5out' )).strip ().decode ().split ()[0 ]
262- localmd5 = localmd5 .hexdigest ()
263- if devicemd5 != localmd5 :
264- raise RuntimeError ("md5sum mismatch (local %s, device %s)" % (localmd5 , devicemd5 ))
265- with open (fn + '.md5' , 'w' ) as md5out :
266- print ('%s *%s' % (localmd5 , fn ), file = md5out )
267-
268- if args .transport == adbxp .tcp :
269- s .close ()
270- if not really_unforward (port ):
271- raise RuntimeError ('could not remove ADB-forward for TCP port %d' % port )
272- child .terminate ()
264+ pbar .max_value = out .tell () or pbar .max_value # need to adjust for the smaller compressed size
265+ pbar .finish ()
266+
267+ if args .verify :
268+ devicemd5 = sp .check_output (adbcmd + ('shell' ,'cat /tmp/md5out' )).strip ().decode ().split ()[0 ]
269+ localmd5 = localmd5 .hexdigest ()
270+ if devicemd5 != localmd5 :
271+ raise RuntimeError ("md5sum mismatch (local %s, device %s)" % (localmd5 , devicemd5 ))
272+ with open (fn + '.md5' , 'w' ) as md5out :
273+ print ('%s *%s' % (localmd5 , fn ), file = md5out )
274+
275+ if args .transport == adbxp .tcp :
276+ s .close ()
277+ if not really_unforward (port ):
278+ raise RuntimeError ('could not remove ADB-forward for TCP port %d' % port )
279+ child .terminate ()
0 commit comments