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

3 Commits
0.12 ... 0.13

Author SHA1 Message Date
390f1b1112 Drop Python2 support. 2020-04-26 09:16:59 +02:00
f7a6b145fd Added new option for trying su command. 2020-04-26 09:06:28 +02:00
9f1a51fdbf Added wrapper on adb shell commands 2020-04-26 09:04:28 +02:00
2 changed files with 60 additions and 43 deletions

View File

@@ -9,7 +9,7 @@ This is Midnight Commander extfs plugin for browsing Android device through
Rquirements Rquirements
=========== ===========
* Python 2.7 or 3.x (tested on 3.5.4) * Python 3.x (tested on 3.5.4, 3.6 and 3.7)
* ``adb`` installed and in ``$PATH`` or provided via the config file * ``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`` (``toolbox``, ``toybox``) installed and available in the path on * ``busybox`` (``toolbox``, ``toybox``) installed and available in the path on
@@ -90,12 +90,14 @@ where:
* ``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`` absolute or relative path to ``adb`` command. `~/` or * ``adb_command`` absolute or relative path to ``adb`` command. ``~/`` or
environment variables are allowed. environment variables are allowed.
* ``adb_connect`` specifies if connection to specific device needs to be * ``adb_connect`` specifies if connection to specific device needs to be
performed before accessing shell. It is useful for *adb over network* performed before accessing shell. It is useful for *adb over network*
feature. Typical value here is a device IP address with optional port, which feature. Typical value here is a device IP address with optional port, which
defaults to 5555. defaults to 5555.
* ``try_su`` specifies whether or not to try to detect if ``su`` command is
available and usable.
Contribution Contribution
@@ -103,6 +105,9 @@ Contribution
There is a ``Makefile`` in the top directory, which is basic helper for running There is a ``Makefile`` in the top directory, which is basic helper for running
the tests. Please use it, and adapt/add tests for provided fixes/functionality. the tests. Please use it, and adapt/add tests for provided fixes/functionality.
The reason why `tox`_ wasn't used is, that there is no ``setup.py`` file, and
it's difficult to install simple script, which isn't a python module (python
interpreter will refuse to import module without ``.py`` extension).
It requires GNU ``make`` program, and also ``virtualenv`` besides Python in It requires GNU ``make`` program, and also ``virtualenv`` besides Python in
version 2 and 3. Using it is simple as running following command: version 2 and 3. Using it is simple as running following command:
@@ -150,3 +155,5 @@ License
This software is licensed under 3-clause BSD license. See LICENSE file for This software is licensed under 3-clause BSD license. See LICENSE file for
details. details.
.. _tox: https://tox.readthedocs.io

88
adbfs
View File

@@ -5,10 +5,7 @@ adbfs Virtual filesystem for Midnight Commander
* Copyright (c) 2016, Roman Dobosz, * Copyright (c) 2016, Roman Dobosz,
* Published under 3-clause BSD-style license (see LICENSE file) * Published under 3-clause BSD-style license (see LICENSE file)
""" """
try: import configparser
import ConfigParser as configparser
except ImportError:
import configparser
import argparse import argparse
from datetime import datetime from datetime import datetime
import json import json
@@ -16,8 +13,9 @@ import os
import re import re
import subprocess import subprocess
import sys import sys
import shlex
__version__ = 0.12 __version__ = 0.13
XDG_CONFIG_HOME = os.getenv('XDG_CONFIG_HOME', os.path.expanduser('~/.config')) XDG_CONFIG_HOME = os.getenv('XDG_CONFIG_HOME', os.path.expanduser('~/.config'))
@@ -195,7 +193,8 @@ class Conf(object):
'suppress_colors': (cfg.get, 'suppress_colors'), 'suppress_colors': (cfg.get, 'suppress_colors'),
'root': (cfg.get, 'root'), 'root': (cfg.get, 'root'),
'adb_command': (cfg.get, 'adb_command'), 'adb_command': (cfg.get, 'adb_command'),
'adb_connect': (cfg.get, 'adb_connect')} 'adb_connect': (cfg.get, 'adb_connect'),
'try_su': (cfg.getboolean, 'try_su')}
cfg.read(conf_fname) cfg.read(conf_fname)
for key, (function, attr) in cfg_map.items(): for key, (function, attr) in cfg_map.items():
@@ -335,11 +334,23 @@ class Adb(object):
self._links = {} self._links = {}
self._got_root = False self._got_root = False
if self.conf.try_su:
self.__su_check() self.__su_check()
def _shell_cmd(self, with_root, *args):
cmd = [self.conf.adb_command, 'shell']
if with_root and self._got_root:
_args = [shlex.quote(x) for x in args]
cmd += ['su', '-c', shlex.quote(' '.join(_args))]
else:
cmd += args
return cmd
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() cmd = self._shell_cmd(False, 'su -c whoami')
try: try:
with open(os.devnull, 'w') as fnull: with open(os.devnull, 'w') as fnull:
result = check_output(cmd, stderr=fnull) result = check_output(cmd, stderr=fnull)
@@ -389,10 +400,8 @@ 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"""
lscmd = self.conf.box['rls'].format(dir_) lscmd = self.conf.box['rls'].format(shlex.quote(dir_))
if self._got_root: command = self._shell_cmd(True, *shlex.split(lscmd))
lscmd = 'su -c "{}"'.format(lscmd)
command = [self.conf.adb_command, 'shell', lscmd]
try: try:
if self.conf.debug: if self.conf.debug:
@@ -449,12 +458,9 @@ class Adb(object):
if not root: if not root:
lscmd = self.conf.box['ls'] lscmd = self.conf.box['ls']
else: else:
lscmd = self.conf.box['rls'].format(root.filepath) lscmd = self.conf.box['rls'].format(shlex.quite(root.filepath))
if self._got_root: command = self._shell_cmd(True, *shlex.split(lscmd))
lscmd = 'su -c "{}"'.format(lscmd)
command = [self.conf.adb_command, 'shell', lscmd]
try: try:
if self.conf.debug: if self.conf.debug:
@@ -463,7 +469,7 @@ class Adb(object):
lines = check_output(command) lines = 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 2
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'):
@@ -499,13 +505,13 @@ class Adb(object):
"""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 3
def list(self): def list(self):
"""Output list contents directory""" """Output list contents directory"""
if self.error: if self.error:
sys.stderr.write(self.error) sys.stderr.write(self.error)
return 1 return 4
if self.conf.root: if self.conf.root:
self._retrieve_single_dir_list(self.conf.root) self._retrieve_single_dir_list(self.conf.root)
@@ -520,7 +526,7 @@ class Adb(object):
"""Copy file form the device using adb.""" """Copy file form the device using adb."""
if self.error: if self.error:
sys.stderr.write(self.error) sys.stderr.write(self.error)
return 1 return 5
cmd = [self.conf.adb_command, 'pull', src, dst] cmd = [self.conf.adb_command, 'pull', src, dst]
if self.conf.debug: if self.conf.debug:
@@ -531,7 +537,7 @@ class Adb(object):
err = subprocess.call(cmd, stdout=fnull, stderr=fnull) err = subprocess.call(cmd, stdout=fnull, stderr=fnull)
except subprocess.CalledProcessError: except subprocess.CalledProcessError:
sys.stderr.write('Error executing adb shell') sys.stderr.write('Error executing adb shell')
return 1 return 6
return err return err
@@ -539,7 +545,7 @@ class Adb(object):
"""Copy file to the device through adb.""" """Copy file to the device through adb."""
if self.error: if self.error:
sys.stderr.write(self.error) sys.stderr.write(self.error)
return 1 return 7
if not dst.startswith('/'): if not dst.startswith('/'):
dst = '/' + dst dst = '/' + dst
@@ -552,69 +558,69 @@ class Adb(object):
err = subprocess.call(cmd, stdout=fnull, stderr=fnull) err = subprocess.call(cmd, stdout=fnull, stderr=fnull)
except subprocess.CalledProcessError: except subprocess.CalledProcessError:
sys.stderr.write('Error executing adb shell') sys.stderr.write('Error executing adb shell')
return 1 return 8
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 9
return 0 return 0
def rm(self, dst): def rm(self, dst):
"""Remove file from device.""" """Remove file from device."""
if self.error: if self.error:
sys.stderr.write(self.error) sys.stderr.write(self.error)
return 1 return 10
cmd = [self.conf.adb_command, 'shell', 'rm', dst] cmd = self._shell_cmd(False, 'rm', dst)
try: try:
err = check_output(cmd) err = check_output(cmd).strip()
except subprocess.CalledProcessError: except subprocess.CalledProcessError:
sys.stderr.write('Error executing adb shell') sys.stderr.write('Error executing adb shell')
return 1 return 11
if err != '': if err != '':
sys.stderr.write(err) sys.stderr.write(err)
return 1 return 12
return 0 return 0
def rmdir(self, dst): def rmdir(self, dst):
"""Remove directory from device.""" """Remove directory from device."""
if self.error: if self.error:
sys.stderr.write(self.error) sys.stderr.write(self.error)
return 1 return 13
cmd = [self.conf.adb_command, 'shell', 'rm', '-r', dst] cmd = self._shell_cmd(False, 'rm -r %s' % shlex.quote(dst))
try: try:
err = check_output(cmd) err = check_output(cmd).strip()
except subprocess.CalledProcessError: except subprocess.CalledProcessError:
sys.stderr.write('Error executing adb shell') sys.stderr.write('Error executing adb shell')
return 1 return 14
if err != '': if err != '':
sys.stderr.write(err) sys.stderr.write(err)
return 1 return 15
return 0 return 0
def mkdir(self, dst): def mkdir(self, dst):
"""Make directory on the device through adb.""" """Make directory on the device through adb."""
if self.error: if self.error:
sys.stderr.write(self.error) sys.stderr.write(self.error)
return 1 return 16
if not dst.startswith('/'): if not dst.startswith('/'):
dst = '/' + dst dst = '/' + dst
cmd = [self.conf.adb_command, 'shell', 'mkdir', dst] cmd = self._shell_cmd(False, 'mkdir %s' % shlex.quote(dst))
try: try:
err = check_output(cmd) err = check_output(cmd).strip()
except subprocess.CalledProcessError: except subprocess.CalledProcessError:
sys.stderr.write('Error executing adb shell') sys.stderr.write('Error executing adb shell')
return 1 return 17
if err != '': if err != '':
sys.stderr.write(err) sys.stderr.write(err)
return 1 return 18
return 0 return 0
@@ -673,7 +679,11 @@ def main():
args = parser.parse_args() args = parser.parse_args()
try:
return args.func(args) return args.func(args)
except AttributeError:
parser.print_help()
parser.exit()
if __name__ == '__main__': if __name__ == '__main__':