Skip to content

Commit 2eb2321

Browse files
committed
Merge branch 'develop'
2 parents 22cbb93 + 57fa8f9 commit 2eb2321

File tree

6 files changed

+125
-84
lines changed

6 files changed

+125
-84
lines changed

setup.py

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
#!/usr/bin/env python
1+
#!/usr/bin/env python3
22

3+
from __future__ import print_function
34
import sys, os, re, subprocess as sp
4-
from distutils.core import setup
5+
from setuptools import setup
56

67
if not sys.version_info[0] == 3:
78
sys.exit("Python 2.x is not supported; Python 3.x is required.")
@@ -14,19 +15,21 @@
1415

1516
# Fetch version from git tags, and write to version.py.
1617
# Also, when git is not available (PyPi package), use stored version.py.
17-
version_py = os.path.join(os.path.dirname(__file__), 'version.py')
18+
version_py = os.path.join(os.path.dirname(__file__), 'tetherback', 'version.py')
1819

1920
try:
20-
version_git = sp.check_output(["git", "describe", "--tags"]).strip().decode('ascii')
21-
final, dev, blob = re.match(r'v?((?:\d+\.)*\d+)(?:-(\d+)-(g[a-z0-9]+))?', version_git).groups()
22-
version_pep = final+('.dev%s+%s'%(dev,blob) if dev else '')
23-
except sp.CalledProcessError:
21+
version_git = sp.check_output(["git", "describe", "--tags", "--dirty=_dirty"]).strip().decode('ascii')
22+
final, dev, blob, dirty = re.match(r'v?((?:\d+\.)*\d+)(?:-(\d+)-(g[a-z0-9]+))?(_dirty)?', version_git).groups()
23+
version_pep = final+('.dev%s+%s'%(dev,blob) if dev else '')+(dirty if dirty else '')
24+
except:
25+
d = {}
2426
with open(version_py, 'r') as fh:
25-
version_pep = open(version_py).read().strip().split('=')[-1][1:-1]
27+
exec(fh.read(), d)
28+
version_pep = d['__version__']
2629
else:
2730
with open(version_py, 'w') as fh:
2831
print("# Do not edit this file, tetherback versioning is governed by git tags", file=fh)
29-
print('__version__="%s"\n' % version_pep, file=fh)
32+
print('__version__="%s"' % version_pep, file=fh)
3033

3134
########################################
3235

tetherback/README.md

Lines changed: 27 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -60,43 +60,55 @@ List of devices attached
6060
and `data.ext4.win`:
6161

6262
```bash
63-
$ tetherback
64-
Device reports TWRP kernel (3.4.0-bricked-hammerhead-twrp-g7b77eb4).
63+
Found ADB version 1.0.32
64+
Using default transfer method: adb exec-out pipe (--exec-out)
65+
Device reports kernel 3.4.0-bricked-hammerhead-twrp-g7b77eb4
66+
Device reports TWRP version 3.0.0-0
6567
Reading partition map for mmcblk0 (29 partitions)...
66-
partition map: 100% Time: 0:00:03
67-
Saving backup images in twrp-backup-2016-03-17--17-44-04/ ...
68+
partition map: 100%
69+
Reading partition map for mmcblk0rpmb (0 partitions)...
70+
partition map: 100%
71+
Saving backup images in ./twrp-backup-2016-07-03--14-53-29/ ...
6872
Saving partition boot (mmcblk0p19), 22 MiB uncompressed...
69-
boot.emmc.win: 100% Time: 0:00:05 3.04 MB/s
73+
boot.emmc.win: 100% 4.0 MiB/s 16.3 MiB
7074
Saving tarball of mmcblk0p25 (mounted at /system), 1024 MiB uncompressed...
71-
system.ext4.win: 100% Time: 0:02:16 2.29 MB/s
75+
system.ext4.win: 100% 2.5 MiB/s 299.7 MiB
7276
Saving tarball of mmcblk0p28 (mounted at /data), 13089 MiB uncompressed...
73-
data.ext4.win: 100% Time: 0:05:38 2.60 MB/s
77+
data.ext4.win: 100% 2.0 MiB/s 804.0 MiB
7478
```
7579

7680
* Make a "nandroid"-style backup over ADB. This saves gzipped images
7781
of the partitions labeled `boot`, `system`, and `userdata` (named
7882
`<label>.img.gz`):
7983

8084
```bash
81-
$ tetherback -n
82-
Device reports TWRP kernel (3.4.0-bricked-hammerhead-twrp-g7b77eb4).
85+
$ tetherback -N
86+
Found ADB version 1.0.32
87+
Using default transfer method: adb exec-out pipe (--exec-out)
88+
Device reports kernel 3.4.0-bricked-hammerhead-twrp-g7b77eb4
89+
Device reports TWRP version 3.0.0-0
8390
Reading partition map for mmcblk0 (29 partitions)...
8491
partition map: 100% Time: 0:00:03
85-
Saving backup images in nandroid-backup-2016-03-17--18-15-03/ ...
92+
Reading partition map for mmcblk0rpmb (0 partitions)...
93+
partition map: 100%
94+
Saving backup images in nandroid-backup-2016-07-03--18-15-03/ ...
8695
Saving partition boot (mmcblk0p19), 22 MiB uncompressed...
87-
mmcblk0p19: 100% Time: 0:00:05 3.07 MB/s
96+
mmcblk0p19: 100% 3.07 MB/s 16.3 MiB
8897
Saving partition system (mmcblk0p25), 1024 MiB uncompressed...
89-
mmcblk0p25: 100% Time: 0:03:05 1.76 MB/s
98+
mmcblk0p25: 100% 1.76 MB/s 343.7 MiB
9099
Saving partition userdata (mmcblk0p28), 13089 MiB uncompressed...
91-
mmcblk0p28: 100% Time: 0:40:04 1.80 MB/s
100+
mmcblk0p28: 100% 1.80 MB/s 6.4 GiB
92101
```
93102

94103
### Additional options
95104

96-
* Extra partitions can be included (as raw images) with the `-X`/`--extra`
97-
option; for example, `-X modemst1 -X modemst2` to backup the
105+
* Extra partitions can be included with the `-X`/`--extra` and `--extra-raw`
106+
options; for example, `-X modemst1 -X modemst2` to backup the
98107
[Nexus 5 EFS partitions](http://forum.xda-developers.com/google-nexus-5/development/modem-nexus-5-flashable-modems-efs-t2514095).
99108

109+
* With `--extra-raw`, the extra partition will *always* be saved as a raw image, rather than as a tarball, even if it is a
110+
mountable filesystem and tetherback is run in TWRP backup mode.
111+
100112
* The partition map and backup plan will be printed with
101113
`-v`/`--verbose` (or use `-0`/`--dry-run` to **only** print it, and
102114
skip the actual backup). For example, the following partition map

tetherback/adb_stuff.py

Lines changed: 22 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,40 @@
11
from sys import stderr
22
import time
33

4-
def really_mount(adb, dev, node, mode='ro'):
5-
for opts in (mode, 'remount,'+mode):
6-
if adb.check_output(('shell','mount -o %s %s %s 2>/dev/null && echo ok' % (opts, dev, node))).strip():
7-
break
4+
def find_mount(adb, dev, node):
85
for l in adb.check_output(('shell','mount')).splitlines():
96
f = l.split()
107
if not l:
118
pass
12-
elif len(f)<4:
13-
print( "WARNING: don't understand output from mount: %s" % (repr(l)), file=stderr )
149
else:
15-
mdev, mnode, mtype = (f[0], f[2], f[4]) if (f[1], f[3])==('on','type') else (f[0], f[1], f[2])
16-
if mdev==dev or mnode==node:
17-
return mtype
10+
# Some systems have `mount` output lines that look like:
11+
# /dev/node on /filesystem/mountpoint type fstype (options)
12+
# ... while others look like this:
13+
# /dev/node /filesystem/mountpoint fstype options
14+
if len(f)<3:
15+
print( "WARNING: don't understand output from mount: %s" % (repr(l)), file=stderr )
16+
else:
17+
mdev, mnode, mtype = (f[0], f[2], f[4]) if len(f)>=5 else f[:3]
18+
if mdev==dev or mnode==node:
19+
return (mdev, mnode, mtype)
20+
else:
21+
return (None, None, None)
22+
23+
def really_mount(adb, dev, node, mode='ro'):
24+
for opts in (mode, 'remount,'+mode):
25+
if adb.check_output(('shell','mount -o %s %s %s 2>/dev/null && echo ok' % (opts, dev, node))).strip():
26+
break
27+
mdev, mnode, mtype = find_mount(adb, dev, node)
28+
return mtype
1829

1930
def really_umount(adb, dev, node):
2031
for opts in ('','-f','-l','-r'):
2132
if adb.check_output(('shell','umount %s 2>/dev/null && echo ok' % dev)).strip():
2233
break
2334
if adb.check_output(('shell','umount %s 2>/dev/null && echo ok' % node)).strip():
2435
break
25-
for l in adb.check_output(('shell','mount')).splitlines():
26-
f = l.split()
27-
if not l:
28-
pass
29-
elif len(f)<4:
30-
print( "WARNING: don't understand output from mount: %s" % (repr(l)), file=stderr )
31-
else:
32-
mdev, mnode = (f[0], f[2]) if (f[1], f[3])==('on','type') else (f[0], f[1])
33-
if mdev==dev or mnode==node:
34-
return False
35-
return True
36+
mdev, mnode, mtype = find_mount(adb, dev, node)
37+
return (mtype==None)
3638

3739
def really_forward(adb, port1, port2):
3840
for port in range(port1, port2):

tetherback/adb_wrapper.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
1+
from sys import stderr
12
import subprocess as sp
23
import re
34

45
class AdbWrapper(object):
5-
def __init__(self, adbbin='adb', devsel=()):
6+
def __init__(self, adbbin='adb', devsel=(), debug=None):
67
self.adbbin = adbbin
78
self.devsel = tuple(devsel)
9+
self.debug = debug
810

911
def get_version(self):
1012
try:
11-
s, output = sp.getstatusoutput(self.adbcmd('version'))
13+
s, output = sp.getstatusoutput(self.adbcmd(('version',)))
1214
except FileNotFoundError:
1315
raise
1416
except sp.CalledProcessError:
@@ -23,12 +25,18 @@ def get_version(self):
2325
return adbversions, adbversion
2426

2527
def adbcmd(self, adbargs):
26-
return (self.adbbin,) + self.devsel + tuple(adbargs)
28+
args = (self.adbbin,) + self.devsel + tuple(adbargs)
29+
if self.debug:
30+
print("ADB: %s"%repr(args), file=stderr)
31+
return args
2732

2833
def check_output(self, adbargs, **kwargs):
2934
un = kwargs.pop('universal_newlines', True)
3035
return sp.check_output(self.adbcmd(adbargs), universal_newlines=un, **kwargs)
3136

37+
def pipe_in(self, adbargs, **kwargs):
38+
return sp.Popen(self.adbcmd(adbargs), stdin=sp.PIPE, **kwargs)
39+
3240
def pipe_out(self, adbargs, **kwargs):
3341
return sp.Popen(self.adbcmd(adbargs), stdout=sp.PIPE, **kwargs)
3442

tetherback/tetherback.py

Lines changed: 52 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,13 @@
99
import os, sys, datetime, socket, time, argparse, re
1010
from sys import stderr
1111
from base64 import standard_b64decode as b64dec
12-
from progressbar import ProgressBar, Percentage, ETA, FileTransferSpeed, DataSize
12+
from progressbar import ProgressBar, Percentage, FileTransferSpeed, DataSize
1313
from tabulate import tabulate
1414
from enum import Enum
1515
from hashlib import md5
1616
from collections import namedtuple, OrderedDict as odict
1717

18+
from .version import __version__
1819
from .adb_wrapper import AdbWrapper
1920
from .adb_stuff import *
2021

@@ -29,6 +30,8 @@
2930

3031
def 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

6165
def 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

276290
def 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
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
# Do not edit this file, tetherback versioning is governed by git tags
2-
__version__="0.6.3"
2+
__version__="0.7"

0 commit comments

Comments
 (0)