• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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