1#!/usr/bin/env python3
2
3#
4# Sample run-on-target script
5# This is a script that can be used as cross-execute parameter to samba
6# configuration process, running the command on a remote target for which
7# the cross-compiled configure test was compiled.
8#
9# To use:
10# ./configure \
11# --cross-compile \
12# '--cross-execute=./buildtools/example/run_on_target.py --host=<host>'
13#
14# A more elaborate example:
15# ./configure \
16# --cross-compile \
17# '--cross-execute=./buildtools/example/run_on_target.py --host=<host> --user=<user> "--ssh=ssh -i <some key file>" --destdir=/path/to/dir'
18#
19# Typically this is to be used also with --cross-answers, so that the
20# cross answers file gets built and further builds can be made without
21# the help of a remote target.
22#
23# The following assumptions are made:
24# 1. rsync is available on build machine and target machine
25# 2. A running ssh service on target machine with password-less shell login
26# 3. A directory writable by the password-less login user
27# 4. The tests on the target can run and provide reliable results
28#    from the login account's home directory. This is significant
29#    for example in locking tests which
30#    create files in the current directory. As a workaround to this
31#    assumption, the TESTDIR environment variable can be set on the target
32#    (using ssh command line or server config) and the tests shall
33#    chdir to that directory.
34#
35
36import sys
37import os
38import subprocess
39from optparse import OptionParser
40
41# those are defaults, but can be overidden using command line
42SSH = 'ssh'
43USER = None
44HOST = 'localhost'
45
46
47def xfer_files(ssh, srcdir, host, user, targ_destdir):
48    """Transfer executable files to target
49
50    Use rsync to copy the directory containing program to run
51    INTO a destination directory on the target. An exact copy
52    of the source directory is created on the target machine,
53    possibly deleting files on the target machine which do not
54    exist on the source directory.
55
56    The idea is that the test may include files in addition to
57    the compiled binary, and all of those files reside alongside
58    the binary in a source directory.
59
60    For example, if the test to run is /foo/bar/test and the
61    destination directory on the target is /tbaz, then /tbaz/bar
62    on the target shall be an exact copy of /foo/bar on the source,
63    including deletion of files inside /tbaz/bar which do not exist
64    on the source.
65    """
66
67    userhost = host
68    if user:
69        userhost = '%s@%s' % (user, host)
70
71    cmd = 'rsync --verbose -rl --ignore-times --delete -e "%s" %s %s:%s/' % \
72          (ssh, srcdir, userhost, targ_destdir)
73    p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE,
74                         stderr=subprocess.PIPE)
75    (out, err) = p.communicate()
76    if p.returncode != 0:
77        raise Exception('failed syncing files\n stdout:\n%s\nstderr:%s\n'
78                        % (out, err))
79
80
81def exec_remote(ssh, host, user, destdir, targdir, prog, args):
82    """Run a test on the target
83
84    Using password-less ssh, run the compiled binary on the target.
85
86    An assumption is that there's no need to cd into the target dir,
87    same as there's no need to do it on a native build.
88    """
89    userhost = host
90    if user:
91        userhost = '%s@%s' % (user, host)
92
93    cmd = '%s %s %s/%s/%s' % (ssh, userhost, destdir, targdir, prog)
94    if args:
95        cmd = cmd + ' ' + ' '.join(args)
96    p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE,
97                         stderr=subprocess.PIPE)
98    (out, err) = p.communicate()
99    return (p.returncode, out)
100
101
102def main(argv):
103    usage = "usage: %prog [options] <prog> [args]"
104    parser = OptionParser(usage)
105
106    parser.add_option('--ssh', help="SSH client and additional flags",
107                      default=SSH)
108    parser.add_option('--host', help="target host name or IP address",
109                      default=HOST)
110    parser.add_option('--user', help="login user on target",
111                      default=USER)
112    parser.add_option('--destdir', help="work directory on target",
113                      default='~')
114
115    (options, args) = parser.parse_args(argv)
116    if len(args) < 1:
117        parser.error("please supply test program to run")
118
119    progpath = args[0]
120
121    # assume that a test that was not compiled fails (e.g. getconf)
122    if progpath[0] != '/':
123        return (1, "")
124
125    progdir = os.path.dirname(progpath)
126    prog = os.path.basename(progpath)
127    targ_progdir = os.path.basename(progdir)
128
129    xfer_files(
130        options.ssh,
131        progdir,
132        options.host,
133        options.user,
134        options.destdir)
135
136    (rc, out) = exec_remote(options.ssh,
137                            options.host,
138                            options.user,
139                            options.destdir,
140                            targ_progdir,
141                            prog, args[1:])
142    return (rc, out)
143
144
145if __name__ == '__main__':
146    (rc, out) = main(sys.argv[1:])
147    sys.stdout.write(out)
148    sys.exit(rc)
149