1
0
mirror of https://github.com/gryf/mc_adbfs.git synced 2026-03-27 13:53:35 +01:00

7 Commits
0.9 ... 0.10

Author SHA1 Message Date
1c6a6cfdf8 Added new option for selecting adb executable 2017-04-29 17:49:19 +02:00
7ce2dd2568 Fix for new version of adb command
Previously it was possible (most probably unintentionally) to perform
command which gives listing of directory contents on the output:

$ adb shell su -c toolbox ls /
acct
cache
charger
config
...

Using such syntax in newer versions of adb, return an error:

$ adb shell su -c toolbox ls /
Unknown id: ls

It is needed to quote argument passed to the 'shell' parameter on adb,
like:

$ adb shell 'su -c "toolbox ls /"'
acct
cache
charger
config
...

This patch fixes this issue for both adb versions.
2017-04-29 17:44:57 +02:00
11f980beb1 Minor code style fixes 2017-04-29 17:30:18 +02:00
bc742ccdf6 Merge remote-tracking branch 'github/master' 2017-03-06 17:12:58 +01:00
7a6a8a499b Added basic support for not rooted devices 2017-03-06 17:10:15 +01:00
b2163e0fba Update README.rst 2016-10-06 18:51:52 +02:00
57509eaac0 Added option for suppressing colors for ls command from busybox 2016-10-06 14:14:28 +02:00
2 changed files with 115 additions and 100 deletions

View File

@@ -9,7 +9,7 @@ Rquirements
=========== ===========
* Python 2.7 * Python 2.7
* ``adb`` installed and in ``$PATH`` * ``adb`` installed and in ``$PATH`` or provided via the config file
* An Android device or emulator preferably rooted * An Android device or emulator preferably rooted
* Busybox installed and available in the path on the device * Busybox installed and available in the path on the device
@@ -18,6 +18,8 @@ Make sure, that issuing from command line:
.. code:: shell-session .. code:: shell-session
$ adb shell busybox ls $ adb shell busybox ls
$ # or in case of no PATH adb placement
$ /path/to/adb shell busybox ls
it should display files from root directory on the device. it should display files from root directory on the device.
@@ -55,7 +57,7 @@ your device and how fast it is :)
Configuration Configuration
============= =============
You can use configure behaviour of this plugin using ``.ini`` file located under You can configure behaviour of this plugin using ``.ini`` file located under
``$XDG_CONFIG_HOME/mc/adbfs.ini`` (which usually is located under ``$XDG_CONFIG_HOME/mc/adbfs.ini`` (which usually is located under
``~/.config/mc/adbfs.ini``), and have default values, like: ``~/.config/mc/adbfs.ini``), and have default values, like:
@@ -65,7 +67,9 @@ You can use configure behaviour of this plugin using ``.ini`` file located under
debug = false debug = false
skip_dirs = true skip_dirs = true
dirs_to_skip = ["acct", "charger", "d", "dev", "proc", "sys"] dirs_to_skip = ["acct", "charger", "d", "dev", "proc", "sys"]
suppress_colors = false
root = root =
adb_command = adb
where: where:
@@ -74,9 +78,13 @@ where:
* ``dirs_to_skip`` list of paths to directories which will be skipped during * ``dirs_to_skip`` list of paths to directories which will be skipped during
reading. If leaved empty, or setted to empty list (``[]``) will read reading. If leaved empty, or setted to empty list (``[]``) will read
everything (slow!) everything (slow!)
* ``suppress_colors`` this option will make ``busybox`` not to display colors,
helpful, if ``busybox ls`` is configured to display colors by default. Does
not affect ``toolbox``.
* ``root`` root directory to read. Everything outside of that directory will be * ``root`` root directory to read. Everything outside of that directory will be
omitted. That would be the fastest way to access certain location on the omitted. That would be the fastest way to access certain location on the
device. Note, that ``dirs_to_skip`` still apply inside this directory. device. Note, that ``dirs_to_skip`` still apply inside this directory.
* ``adb_command`` = path to ``adb`` command.
Limitations Limitations
=========== ===========

203
adbfs
View File

@@ -16,9 +16,9 @@ import re
import subprocess import subprocess
import sys import sys
__version__ = 0.8 __version__ = 0.10
XDG_CONFIG_HOME = os.getenv("XDG_CONFIG_HOME", os.path.expanduser("~/.config")) XDG_CONFIG_HOME = os.getenv('XDG_CONFIG_HOME', os.path.expanduser('~/.config'))
class NoBoxFoundException(OSError): class NoBoxFoundException(OSError):
@@ -28,6 +28,7 @@ class NoBoxFoundException(OSError):
""" """
pass pass
class Conf(object): class Conf(object):
"""Simple config parser""" """Simple config parser"""
boxes = {'busybox': {'ls': 'busybox ls -anel', boxes = {'busybox': {'ls': 'busybox ls -anel',
@@ -48,37 +49,40 @@ class Conf(object):
r'(?P<size>\d+)?\s' r'(?P<size>\d+)?\s'
r'(?P<date>\d{4}-\d{2}-\d{2}\s' r'(?P<date>\d{4}-\d{2}-\d{2}\s'
r'\d{2}:\d{2})\s' r'\d{2}:\d{2})\s'
r'(?P<name>.*)'} r'(?P<name>.*)'}}
}
def __init__(self): def __init__(self):
self.box = None self.box = None
self.debug = False self.debug = False
self.dirs_to_skip = ["acct", "charger", "d", "dev", "proc", "sys"] self.dirs_to_skip = ['acct', 'charger', 'd', 'dev', 'proc', 'sys']
self.root = None self.root = None
self.suppress_colors = False
self.adb_command = 'adb'
self.get_the_box()
self.read() self.read()
self.get_the_box()
def get_the_box(self): def get_the_box(self):
"""Detect if we dealing with busybox or toolbox""" """Detect if we dealing with busybox or toolbox"""
cmd = [self.adb_command] + 'shell which'.split()
try: try:
with open(os.devnull, "w") as fnull: with open(os.devnull, 'w') as fnull:
result = subprocess.check_output('adb shell which ' result = subprocess.check_output(cmd + ['busybox'],
'busybox'.split(),
stderr=fnull) stderr=fnull)
if 'busybox' in result: if 'busybox' in result:
self.box = Conf.boxes['busybox'] self.box = Conf.boxes['busybox']
if self.suppress_colors:
self.box.update({'ls': 'busybox ls --color=none -anel',
'rls': 'busybox ls --color=none '
'-Ranel {}'})
Adb.file_re = re.compile(self.box['file_re']) Adb.file_re = re.compile(self.box['file_re'])
return return
except subprocess.CalledProcessError: except subprocess.CalledProcessError:
pass pass
try: try:
with open(os.devnull, "w") as fnull: with open(os.devnull, 'w') as fnull:
result = subprocess.check_output('adb shell which ' result = subprocess.check_output(cmd + ['toolbox'],
'toolbox'.split(),
stderr=fnull) stderr=fnull)
if 'toolbox' in result: if 'toolbox' in result:
@@ -89,7 +93,7 @@ class Conf(object):
pass pass
raise NoBoxFoundException(errno.ENOENT, raise NoBoxFoundException(errno.ENOENT,
"There is no toolbox or busybox available") 'There is no toolbox or busybox available')
def read(self): def read(self):
""" """
@@ -106,7 +110,9 @@ class Conf(object):
cfg = ConfigParser.SafeConfigParser() cfg = ConfigParser.SafeConfigParser()
cfg_map = {'debug': (cfg.getboolean, 'debug'), cfg_map = {'debug': (cfg.getboolean, 'debug'),
'dirs_to_skip': (cfg.get, 'dirs_to_skip'), 'dirs_to_skip': (cfg.get, 'dirs_to_skip'),
'root': (cfg.get, 'root')} 'suppress_colors': (cfg.get, 'suppress_colors'),
'root': (cfg.get, 'root'),
'adb_command': (cfg.get, 'adb')}
cfg.read(conf_fname) cfg.read(conf_fname)
for key, (function, attr) in cfg_map.items(): for key, (function, attr) in cfg_map.items():
@@ -116,11 +122,15 @@ class Conf(object):
pass pass
if self.dirs_to_skip and isinstance(self.dirs_to_skip, str): if self.dirs_to_skip and isinstance(self.dirs_to_skip, str):
self.dirs_to_skip = json.loads(self.dirs_to_skip, encoding="ascii") self.dirs_to_skip = json.loads(self.dirs_to_skip, encoding='ascii')
self.dirs_to_skip = [x.encode('utf-8') for x in self.dirs_to_skip] self.dirs_to_skip = [x.encode('utf-8') for x in self.dirs_to_skip]
else: else:
self.dirs_to_skip = [] self.dirs_to_skip = []
if self.adb_command:
self.adb_command = os.path.expandvars(self.adb_command)
self.adb_command = os.path.expanduser(self.adb_command)
class File(object): class File(object):
"""Item in filesystem representation""" """Item in filesystem representation"""
@@ -136,7 +146,7 @@ class File(object):
self.name = name self.name = name
self.date = date # as string self.date = date # as string
self.dirname = "" self.dirname = ''
self.type = None self.type = None
self.string = None self.string = None
self.link_target = None self.link_target = None
@@ -145,7 +155,7 @@ class File(object):
def _correct_link(self): def _correct_link(self):
"""Canonize filename and fill the link attr""" """Canonize filename and fill the link attr"""
try: try:
name, target = self.name.split(" -> ") name, target = self.name.split(' -> ')
except ValueError: except ValueError:
return return
@@ -154,7 +164,7 @@ class File(object):
if not self.size: if not self.size:
self.size = 0 self.size = 0
if target.startswith("/"): if target.startswith('/'):
self.link_target = target self.link_target = target
else: else:
self.link_target = os.path.abspath(os.path.join(self.dirname, self.link_target = os.path.abspath(os.path.join(self.dirname,
@@ -162,34 +172,34 @@ class File(object):
def update(self, dirname): def update(self, dirname):
"""update object fields""" """update object fields"""
month_num = {"Jan": 1, month_num = {'Jan': 1,
"Feb": 2, 'Feb': 2,
"Mar": 3, 'Mar': 3,
"Apr": 4, 'Apr': 4,
"May": 5, 'May': 5,
"Jun": 6, 'Jun': 6,
"Jul": 7, 'Jul': 7,
"Aug": 8, 'Aug': 8,
"Sep": 9, 'Sep': 9,
"Oct": 10, 'Oct': 10,
"Nov": 11, 'Nov': 11,
"Dec": 12} 'Dec': 12}
self.dirname = dirname self.dirname = dirname
if self.date_time: if self.date_time:
date = self.date_time.split() date = self.date_time.split()
date = "%s-%02d-%s %s" % (date[1], date = '%s-%02d-%s %s' % (date[1],
month_num[date[0]], month_num[date[0]],
date[3], date[3],
date[2]) date[2])
date = datetime.strptime(date, "%d-%m-%Y %H:%M:%S") date = datetime.strptime(date, '%d-%m-%Y %H:%M:%S')
elif self.date: elif self.date:
date = datetime.strptime(self.date, "%Y-%m-%d %H:%M") date = datetime.strptime(self.date, '%Y-%m-%d %H:%M')
self.date_time = date.strftime("%m/%d/%Y %H:%M:01") self.date_time = date.strftime('%m/%d/%Y %H:%M:01')
self.type = self.perms[0] if self.perms else None self.type = self.perms[0] if self.perms else None
if self.type == "l" and " -> " in self.name: if self.type == 'l' and ' -> ' in self.name:
self._correct_link() self._correct_link()
self.filepath = os.path.join(self.dirname, self.name) self.filepath = os.path.join(self.dirname, self.name)
@@ -202,22 +212,22 @@ class File(object):
"""represent the file/entire node""" """represent the file/entire node"""
fullname = os.path.join(self.dirname, self.name) fullname = os.path.join(self.dirname, self.name)
if self.link_target: if self.link_target:
fullname += " -> " + self.link_target fullname += ' -> ' + self.link_target
return "<File {type} {name} {id}>".format(type=self.type, return '<File {type} {name} {id}>'.format(type=self.type,
name=fullname, name=fullname,
id=hex(id(self))) id=hex(id(self)))
def __str__(self): def __str__(self):
"""display the file/entire node""" """display the file/entire node"""
template = ("{perms} {links:>4} {uid:<8} {gid:<8} {size:>8} " template = ('{perms} {links:>4} {uid:<8} {gid:<8} {size:>8} '
"{date_time} {fullname}\n") '{date_time} {fullname}\n')
if not self.name: if not self.name:
return "" return ''
fullname = os.path.join(self.dirname, self.name) fullname = os.path.join(self.dirname, self.name)
if self.link_target: if self.link_target:
fullname += " -> " + self.link_target fullname += ' -> ' + self.link_target
return template.format(perms=self.perms, return template.format(perms=self.perms,
links=self.links, links=self.links,
@@ -231,7 +241,7 @@ class File(object):
class Adb(object): class Adb(object):
"""Class for interact with android rooted device through adb""" """Class for interact with android rooted device through adb"""
file_re = None file_re = None
current_re = re.compile(r"^(\./)?(?P<dir>.+):$") current_re = re.compile(r'^(\./)?(?P<dir>.+):$')
def __init__(self): def __init__(self):
"""Prepare archive content for operations""" """Prepare archive content for operations"""
@@ -246,18 +256,16 @@ class Adb(object):
def __su_check(self): def __su_check(self):
"""Check if we are able to get elevated privileges""" """Check if we are able to get elevated privileges"""
cmd = [self.conf.adb_command] + 'shell su -c whoami'.split()
try: try:
with open(os.devnull, "w") as fnull: with open(os.devnull, 'w') as fnull:
result = subprocess.check_output('adb shell su -c ' result = subprocess.check_output(cmd, stderr=fnull)
'whoami'.split(),
stderr=fnull)
except subprocess.CalledProcessError: except subprocess.CalledProcessError:
return return
if 'root' in result: if 'root' in result:
self._got_root = True self._got_root = True
return
def _find_target(self, needle): def _find_target(self, needle):
"""Find link target""" """Find link target"""
@@ -298,16 +306,18 @@ class Adb(object):
def _retrieve_single_dir_list(self, dir_): def _retrieve_single_dir_list(self, dir_):
"""Retrieve file list using adb""" """Retrieve file list using adb"""
command = ["adb", "shell", "su", "-c"] lscmd = self.conf.box['rls'].format(dir_)
command.append(self.conf.box['rls'].format(dir_)) if self._got_root:
lscmd = 'su -c "{}"'.format(lscmd)
command = [self.conf.adb_command, 'shell', lscmd]
try: try:
if self.conf.debug: if self.conf.debug:
print "executing", " ".join(command) print 'executing', ' '.join(command)
lines = subprocess.check_output(command) lines = subprocess.check_output(command)
except subprocess.CalledProcessError: except subprocess.CalledProcessError:
sys.stderr.write("Cannot read directory. Is device connected?\n") sys.stderr.write('Cannot read directory. Is device connected?\n')
return 1 return 1
lines = [l.strip() for l in lines.split('\n') if l.strip()] lines = [l.strip() for l in lines.split('\n') if l.strip()]
@@ -320,16 +330,16 @@ class Adb(object):
return return
self._entries.append(entry) self._entries.append(entry)
if entry.type == "l": if entry.type == 'l':
self._links[entry.filepath] = entry self._links[entry.filepath] = entry
self._retrieve_single_dir_list(entry.link_target) self._retrieve_single_dir_list(entry.link_target)
else: else:
for line in lines: for line in lines:
current_dir_re = self.current_re.match(line) current_dir_re = self.current_re.match(line)
if current_dir_re: if current_dir_re:
current_dir = current_dir_re.groupdict()["dir"] current_dir = current_dir_re.groupdict()['dir']
if not current_dir: if not current_dir:
current_dir = "/" current_dir = '/'
continue continue
reg_match = self.file_re.match(line) reg_match = self.file_re.match(line)
@@ -337,7 +347,7 @@ class Adb(object):
continue continue
entry = File(**reg_match.groupdict()) entry = File(**reg_match.groupdict())
if entry.name in (".", ".."): if entry.name in ('.', '..'):
continue continue
entry.update(current_dir) entry.update(current_dir)
@@ -347,35 +357,39 @@ class Adb(object):
self._entries.append(entry) self._entries.append(entry)
if entry.type == "l": if entry.type == 'l':
self._links[entry.filepath] = entry self._links[entry.filepath] = entry
def _retrieve_file_list(self, root=None): def _retrieve_file_list(self, root=None):
"""Retrieve file list using adb""" """Retrieve file list using adb"""
command = ["adb", "shell", "su", "-c"]
if not root: if not root:
command.append(self.conf.box['ls']) lscmd = self.conf.box['ls']
else: else:
command.append(self.conf.box['rls'].format(root.filepath)) lscmd = self.conf.box['rls'].format(root.filepath)
if self._got_root:
lscmd = 'su -c "{}"'.format(lscmd)
command = [self.conf.adb_command, 'shell', lscmd]
try: try:
if self.conf.debug: if self.conf.debug:
print "executing", " ".join(command) print 'executing', ' '.join(command)
lines = subprocess.check_output(command) lines = subprocess.check_output(command)
except subprocess.CalledProcessError: except subprocess.CalledProcessError:
sys.stderr.write("Cannot read directory. Is device connected?\n") sys.stderr.write('Cannot read directory. Is device connected?\n')
return 1 return 1
current_dir = root.dirname if root else "/" current_dir = root.dirname if root else '/'
for line in lines.split("\n"): for line in lines.split('\n'):
line = line.strip() line = line.strip()
current_dir_re = self.current_re.match(line) current_dir_re = self.current_re.match(line)
if current_dir_re: if current_dir_re:
current_dir = current_dir_re.groupdict()["dir"] current_dir = current_dir_re.groupdict()['dir']
if not current_dir: if not current_dir:
current_dir = "/" current_dir = '/'
continue continue
reg_match = self.file_re.match(line) reg_match = self.file_re.match(line)
@@ -383,7 +397,7 @@ class Adb(object):
continue continue
entry = File(**reg_match.groupdict()) entry = File(**reg_match.groupdict())
if entry.name in (".", ".."): if entry.name in ('.', '..'):
continue continue
entry.update(current_dir) entry.update(current_dir)
@@ -392,16 +406,16 @@ class Adb(object):
continue continue
self._entries.append(entry) self._entries.append(entry)
if root is None and entry.type == "d": if root is None and entry.type == 'd':
self._retrieve_file_list(entry) self._retrieve_file_list(entry)
if entry.type == "l": if entry.type == 'l':
self._links[entry.filepath] = entry self._links[entry.filepath] = entry
def run(self, fname): def run(self, fname):
"""Not supported""" """Not supported"""
sys.stderr.write("Not supported - or maybe you are on compatible " sys.stderr.write('Not supported - or maybe you are on compatible '
"architecture?\n") 'architecture?\n')
return 1 return 1
def list(self): def list(self):
@@ -416,13 +430,7 @@ class Adb(object):
self._retrieve_file_list() self._retrieve_file_list()
self._normalize_links() self._normalize_links()
# with open(os.path.join(os.path.dirname(os.path.realpath(__file__)), sys.stdout.write(''.join([str(entry) for entry in self._entries]))
# # "list.pcl"), "w") as fob:
# "list.pcl")) as fob:
# import cPickle
# # cPickle.dump(self._entries, fob)
# self._entries = cPickle.load(fob)
sys.stdout.write("".join([str(entry) for entry in self._entries]))
return 0 return 0
def copyout(self, src, dst): def copyout(self, src, dst):
@@ -431,11 +439,11 @@ class Adb(object):
sys.stderr.write(self.error) sys.stderr.write(self.error)
return 1 return 1
cmd = ["adb", "pull", src, dst] cmd = [self.conf.adb_command, 'pull', src, dst]
if self.conf.debug: if self.conf.debug:
sys.stderr.write(" ".join(cmd) + "\n") sys.stderr.write(' '.join(cmd) + '\n')
with open(os.devnull, "w") as fnull: with open(os.devnull, 'w') as fnull:
try: try:
err = subprocess.call(cmd, stdout=fnull, stderr=fnull) err = subprocess.call(cmd, stdout=fnull, stderr=fnull)
except subprocess.CalledProcessError: except subprocess.CalledProcessError:
@@ -449,15 +457,14 @@ class Adb(object):
if self.error: if self.error:
sys.stderr.write(self.error) sys.stderr.write(self.error)
return 1 return 1
if not dst.startswith("/"): if not dst.startswith('/'):
dst = "/" + dst dst = '/' + dst
# cmd = ["adb", "push", pipes.quote(src), pipes.quote(dst)] cmd = [self.conf.adb_command, 'push', src, dst]
cmd = ["adb", "push", src, dst]
if self.conf.debug: if self.conf.debug:
sys.stderr.write(" ".join(cmd) + "\n") sys.stderr.write(' '.join(cmd) + '\n')
with open(os.devnull, "w") as fnull: with open(os.devnull, 'w') as fnull:
try: try:
err = subprocess.call(cmd, stdout=fnull, stderr=fnull) err = subprocess.call(cmd, stdout=fnull, stderr=fnull)
except subprocess.CalledProcessError: except subprocess.CalledProcessError:
@@ -465,8 +472,8 @@ class Adb(object):
return 1 return 1
if err != 0: if err != 0:
sys.stderr.write("Cannot push the file, " sys.stderr.write('Cannot push the file, '
"%s, error %d" % (dst, err)) '%s, error %d' % (dst, err))
return 1 return 1
return 0 return 0
@@ -476,14 +483,14 @@ class Adb(object):
sys.stderr.write(self.error) sys.stderr.write(self.error)
return 1 return 1
cmd = ["adb", "shell", "rm", dst] cmd = [self.conf.adb_command, 'shell', 'rm', dst]
try: try:
err = subprocess.check_output(cmd) err = subprocess.check_output(cmd)
except subprocess.CalledProcessError: except subprocess.CalledProcessError:
sys.stderr.write('Error executing adb shell') sys.stderr.write('Error executing adb shell')
return 1 return 1
if err != "": if err != '':
sys.stderr.write(err) sys.stderr.write(err)
return 1 return 1
return 0 return 0
@@ -494,14 +501,14 @@ class Adb(object):
sys.stderr.write(self.error) sys.stderr.write(self.error)
return 1 return 1
cmd = ["adb", "shell", "rm", "-r", dst] cmd = [self.conf.adb_command, 'shell', 'rm', '-r', dst]
try: try:
err = subprocess.check_output(cmd) err = subprocess.check_output(cmd)
except subprocess.CalledProcessError: except subprocess.CalledProcessError:
sys.stderr.write('Error executing adb shell') sys.stderr.write('Error executing adb shell')
return 1 return 1
if err != "": if err != '':
sys.stderr.write(err) sys.stderr.write(err)
return 1 return 1
return 0 return 0
@@ -512,17 +519,17 @@ class Adb(object):
sys.stderr.write(self.error) sys.stderr.write(self.error)
return 1 return 1
if not dst.startswith("/"): if not dst.startswith('/'):
dst = "/" + dst dst = '/' + dst
cmd = ["adb", "shell", "mkdir", dst] cmd = [self.conf.adb_command, 'shell', 'mkdir', dst]
try: try:
err = subprocess.check_output(cmd) err = subprocess.check_output(cmd)
except subprocess.CalledProcessError: except subprocess.CalledProcessError:
sys.stderr.write('Error executing adb shell') sys.stderr.write('Error executing adb shell')
return 1 return 1
if err != "": if err != '':
sys.stderr.write(err) sys.stderr.write(err)
return 1 return 1
return 0 return 0
@@ -586,5 +593,5 @@ def main():
return args.func(args) return args.func(args)
if __name__ == "__main__": if __name__ == '__main__':
sys.exit(main()) sys.exit(main())