blob: 79f368e43ebf83e288dae5b34707f49cfac0d1c2 [file] [log] [blame]
'''
Copyright 2019 Google LLC
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
'''
import os
import sys
import time
from stat import S_ISDIR
from mdt import command
from mdt import config
from mdt import console
from mdt import discoverer
from mdt import sshclient
PROGRESS_WIDTH = 45
FILENAME_WIDTH = 30
def MakeProgressFunc(full_filename, width, char='>'):
def closure(bytes_xferred, total_bytes):
filename = full_filename
if len(filename) > FILENAME_WIDTH:
filename = filename[0:FILENAME_WIDTH - 3] + '...'
pcnt = bytes_xferred / total_bytes
left = char * round(pcnt * width)
right = ' ' * round((1 - pcnt) * width)
pcnt = '%3d' % (int(pcnt * 100))
sys.stdout.write('\r{0}% |{1}{2}| {3}'.format(
pcnt, left, right, filename))
sys.stdout.flush()
return closure
class InstallCommand(command.NetworkCommand):
'''Usage: mdt install <deb-package>
Installs a given Debian package file to the connected device via
mdt-install-package.
Variables used:
preferred-device - set this to your preferred device name to connect
to by default if no <devicename> is provided on the
command line. Can be set to an IPv4 address to bypass
the mDNS lookup.
username - set this to the username that should be used to
connect to a device with. Defaults to 'mendel'.
password - set this to the password to use to login to a new
device with. Defaults to 'mendel'. Only used
during the initial setup phase of pushing an SSH
key to the board.
Note: if the package provided has dependencies that are not already installed on
the device, this will require internet connectivity to fetch and install those
dependencies.
'''
def preConnectRun(self, args):
if len(args) < 2:
print("Usage: mdt install [<package-filename...>]")
return False
return True
def runWithClient(self, client, args):
package_to_install = args[1]
package_filename = os.path.basename(package_to_install)
remote_filename = os.path.join('/tmp', package_filename)
sftp_callback = MakeProgressFunc(package_filename, PROGRESS_WIDTH)
sftp = client.openSftp()
sftp.put(package_to_install, remote_filename, callback=sftp_callback)
sftp.close()
client.close()
print()
channel = client.shellExec("sudo /usr/sbin/mdt-install-package {0}; "
"rm -f {0}".format(remote_filename),
allocPty=True)
cons = console.Console(channel, sys.stdin)
return cons.run()
class PushCommand(command.NetworkCommand):
'''Usage: mdt push <local-path...> [<remote-path>]
If a directory name is provided as a local path, a directory will be created on
the device and the contents of that directory will be copied in place,
recursively.
If remote-path is omitted, push assumes you mean to push files to /home/mendel
instead.
Note that the last argument is considered to be the remote path to push to!
If remote-path is a path that exists locally, it will be considered to be a
local-path instead, and push will default to pushing to /home/mendel instead.
This can lead to surprising results, so check if your last argument exists or
not.
Variables used:
preferred-device - set this to your preferred device name to connect
to by default if no <devicename> is provided on the
command line. Can be set to an IPv4 address to bypass
the mDNS lookup.
username - set this to the username that should be used to
connect to a device with. Defaults to 'mendel'.
password - set this to the password to use to login to a new
device with. Defaults to 'mendel'. Only used
during the initial setup phase of pushing an SSH
key to the board.
'''
def preConnectRun(self, args):
if len(args) < 2:
print('Usage: mdt push <local-path...> [<remote-path>]')
return False
# If the last argument (remote-path) exists, assume it is a file to push
# and the remote-path should be /home/mendel
if os.path.exists(args[-1]):
self.files_to_push = args[1:]
self.destination = '/home/mendel'
elif len(args) == 2:
# Catch the case where the only argument given doesn't exist.
print('{0}: No such file or directory'.format(args[1]))
return False
else:
self.files_to_push = args[1:-1]
self.destination = args[-1]
# Convert relative paths into absolute ones for Paramiko
if self.destination[0] != '/':
self.destination = '/home/mendel/' + self.destination
return True
def maybeMkdir(self, sftp, dirname):
try:
sftp.stat(dirname)
except IOError as e:
sftp.mkdir(dirname)
def pushDir(self, sftp, dir, destination):
basename = os.path.basename(dir)
destination = os.path.normpath(os.path.join(destination, basename))
self.maybeMkdir(sftp, destination)
for path, subdirs, files in os.walk(dir):
relpath = os.path.relpath(path, start=dir)
remote_path = os.path.join(destination, relpath)
for name in subdirs:
remote_name = os.path.join(remote_path, name)
self.maybeMkdir(sftp, remote_name)
for name in files:
self.pushFile(sftp,
os.path.join(path, name),
remote_path)
def pushFile(self, sftp, file, destination):
destination = os.path.normpath(destination)
self.maybeMkdir(sftp, destination)
base_filename = os.path.basename(file)
remote_path = os.path.join(destination, base_filename)
sftp_callback = MakeProgressFunc(file, PROGRESS_WIDTH)
sftp_callback(0, 1)
sftp.put(file, remote_path, callback=sftp_callback)
sftp_callback(1, 1)
print()
def runWithClient(self, client, args):
sftp = client.openSftp()
try:
for file in self.files_to_push:
file = os.path.normpath(file)
if os.path.isdir(file):
self.pushDir(sftp, file, self.destination)
else:
self.pushFile(sftp, file, self.destination)
except IOError as e:
print("Couldn't upload file to device: {0}".format(e))
return 1
finally:
sftp.close()
return 0
class PullCommand(command.NetworkCommand):
'''Usage: mdt pull <filename...> <local-destination-path>
Variables used:
preferred-device - set this to your preferred device name to connect
to by default if no <devicename> is provided on the
command line. Can be set to an IPv4 address to bypass
the mDNS lookup.
username - set this to the username that should be used to
connect to a device with. Defaults to 'mendel'.
password - set this to the password to use to login to a new
device with. Defaults to 'mendel'. Only used
during the initial setup phase of pushing an SSH
key to the board.
Pulls (copies) a set of files from the remote device to a local path.
'''
def preConnectRun(self, args):
if len(args) < 3:
print("Usage: mdt pull <filename...> <local-destination-path>")
return False
return True
def sftpWalk(self, sftp, remote_dir):
dirs_to_walk = [remote_dir]
for dir in dirs_to_walk:
entries = sftp.listdir_attr(dir)
dirs = []
files = []
for entry in entries:
if S_ISDIR(entry.st_mode):
dirs.append(entry.filename)
dirs_to_walk.append(dir + '/' + entry.filename)
else:
files.append(entry.filename)
yield (dir, dirs, files)
def maybeMkdir(self, dir):
if not os.path.exists(dir):
os.mkdir(dir)
def pullDir(self, sftp, remote_dir, destination):
basename = os.path.basename(remote_dir)
destination = os.path.normpath(os.path.join(destination, basename))
self.maybeMkdir(destination)
for path, subdirs, files in self.sftpWalk(sftp, remote_dir):
relpath = os.path.relpath(path, start=remote_dir)
local_path = os.path.join(destination, relpath)
for name in subdirs:
local_name = os.path.join(local_path, name)
self.maybeMkdir(local_name)
for name in files:
self.pullFile(sftp, path + '/' + name, local_path)
def pullFile(self, sftp, remote_file, destination):
base_filename = os.path.basename(remote_file)
destination_filename = os.path.join(destination, base_filename)
sftp_callback = MakeProgressFunc(remote_file, PROGRESS_WIDTH, char='<')
sftp_callback(0, 1)
sftp.get(remote_file, destination_filename, callback=sftp_callback)
sftp_callback(1, 1)
print()
def runWithClient(self, client, args):
files_to_pull = args[1:-1]
destination = args[-1]
try:
sftp = client.openSftp()
for file in files_to_pull:
stat_result = sftp.stat(file)
if S_ISDIR(stat_result.st_mode):
self.pullDir(sftp, file, destination)
else:
self.pullFile(sftp, file, destination)
except IOError as e:
print("Couldn't download file from device: {0}".format(e))
return 1
finally:
sftp.close()
return 0