1# Copyright 2019 The Chromium Authors. All rights reserved. 2# Use of this source code is governed by a BSD-style license that can be 3# found in the LICENSE file. 4 5 6from recipe_engine import recipe_api 7 8import default 9import ntpath 10import ssh 11import subprocess # TODO(borenet): No! Remove this. 12 13 14"""Win SSH flavor, used for running code on Windows via an SSH connection. 15 16Copied from chromebook.py and modified for Windows. 17""" 18 19 20class WinSSHFlavor(ssh.SSHFlavor): 21 22 def __init__(self, m): 23 super(WinSSHFlavor, self).__init__(m) 24 self.remote_homedir = 'C:\\Users\\chrome-bot\\botdata\\' 25 self.device_dirs = default.DeviceDirs( 26 bin_dir = self.device_path_join(self.remote_homedir, 'bin'), 27 dm_dir = self.device_path_join(self.remote_homedir, 'dm_out'), 28 perf_data_dir = self.device_path_join(self.remote_homedir, 'perf'), 29 resource_dir = self.device_path_join(self.remote_homedir, 'resources'), 30 images_dir = self.device_path_join(self.remote_homedir, 'images'), 31 lotties_dir = self.device_path_join(self.remote_homedir, 'lotties'), 32 skp_dir = self.device_path_join(self.remote_homedir, 'skps'), 33 svg_dir = self.device_path_join(self.remote_homedir, 'svgs'), 34 mskp_dir = self.device_path_join(self.remote_homedir, 'mskp'), 35 tmp_dir = self.remote_homedir, 36 texttraces_dir = '') 37 self._empty_dir = self.device_path_join(self.remote_homedir, 'empty') 38 39 40 def _cmd(self, title, cmd, infra_step=True, fail_errorlevel=1, **kwargs): 41 return self.m.run(self.m.python, title, 42 script=self.module.resource('win_ssh_cmd.py'), 43 args=[self.user_ip, cmd, fail_errorlevel], 44 infra_step=infra_step, **kwargs) 45 46 def ensure_device_dir(self, path): 47 self._cmd('mkdir %s' % path, 'if not exist "%s" md "%s"' % (path, path)) 48 49 def _rmdir(self, path): 50 self._cmd('rmdir %s' % path, 'if exist "%s" rd "%s"' % (path, path)) 51 52 def device_path_join(self, *args): 53 return ntpath.join(*args) 54 55 def install(self): 56 super(WinSSHFlavor, self).install() 57 58 # Ensure that our empty dir is actually empty. 59 self._rmdir(self._empty_dir) 60 self.ensure_device_dir(self._empty_dir) 61 62 self.create_clean_device_dir(self.device_dirs.bin_dir) 63 64 def create_clean_device_dir(self, path): 65 # Based on https://stackoverflow.com/a/98069 and 66 # https://superuser.com/a/346112. 67 self._cmd('clean %s' % path, 68 'robocopy /mir "%s" "%s"' % (self._empty_dir, path), 69 fail_errorlevel=8) 70 71 def read_file_on_device(self, path, **kwargs): 72 with self.m.step.nest('read %s' % path): 73 tmp = self.m.path.mkdtemp('read_file_on_device') 74 host_path = tmp.join(ntpath.basename(path)) 75 device_path = self.scp_device_path(path) 76 ok = self._run('scp %s %s' % (device_path, host_path), 77 cmd=['scp', device_path, host_path], 78 infra_step=True, **kwargs) 79 # TODO(dogben): Should readfile respect fail_build_on_failure and 80 # abort_on_failure? 81 if ok: 82 return self.m.run.readfile(host_path) 83 84 def remove_file_on_device(self, path): 85 self._cmd('rm %s' % path, 'if exist "%s" del "%s"' % (path, path)) 86 87 def _copy_dir(self, src, dest): 88 self._run('scp -r %s %s' % (src, dest), 89 cmd=['scp', '-r', src, dest], infra_step=True) 90 91 def copy_directory_contents_to_device(self, host_path, device_path): 92 # Callers expect that the destination directory is replaced, which is not 93 # how scp works when the destination is a directory. Instead scp to tmp_dir 94 # and then robocopy to the correct destination. 95 # Other flavors use a glob and subprocess with shell=True to copy the 96 # contents of host_path; however, there are a lot of ways POSIX shell 97 # interpretation could mess up Windows path names. 98 with self.m.step.nest('copy %s to device' % host_path): 99 tmp_pardir = self.device_path_join( 100 self.device_dirs.tmp_dir, 101 'tmp_copy_directory_contents_to_device') 102 self.create_clean_device_dir(tmp_pardir) 103 tmpdir = self.device_path_join(tmp_pardir, 104 self.m.path.basename(host_path)) 105 self._copy_dir(host_path, self.scp_device_path(tmpdir)) 106 self._cmd('copy %s to %s' % (tmpdir, device_path), 107 'robocopy /mir "%s" "%s"' % (tmpdir, device_path), 108 fail_errorlevel=8) 109 110 def copy_directory_contents_to_host(self, device_path, host_path): 111 # Note that the glob in src is interpreted by the remote shell. 112 src = self.scp_device_path(self.device_path_join(device_path, '*')) 113 self._copy_dir(src, host_path) 114 115 def step(self, name, cmd, infra_step=False, **kwargs): 116 # There may be DLLs in the same dir as the executable that must be loaded 117 # (yes, Windows allows overriding system DLLs with files in the local 118 # directory). For simplicity, just copy the entire dir to the device. 119 self.copy_directory_contents_to_device(self.host_dirs.bin_dir, 120 self.device_dirs.bin_dir) 121 device_bin = self.device_path_join(self.device_dirs.bin_dir, cmd[0]) 122 123 # Copy PowerShell script to device. 124 ps = 'win_run_and_check_log.ps1' 125 device_ps = self.device_path_join(self.device_dirs.bin_dir, ps) 126 self.copy_file_to_device(self.module.resource(ps), device_ps) 127 128 cmd = ['powershell', '-ExecutionPolicy', 'Unrestricted', '-File', device_ps, 129 device_bin] + cmd[1:] 130 self._cmd(name, subprocess.list2cmdline(map(str, cmd)), 131 infra_step=infra_step, **kwargs) 132