| author | Fabian Huch <huch@in.tum.de> | 
| Fri, 01 Dec 2023 20:32:34 +0100 | |
| changeset 79101 | 4e47b34fbb8e | 
| parent 78924 | 0481c84f6919 | 
| child 79633 | c59231722f10 | 
| permissions | -rw-r--r-- | 
| 64123 | 1 | /* Title: Pure/General/ssh.scala | 
| 2 | Author: Makarius | |
| 3 | ||
| 76122 
b8f26c20d3b1
ssh client via regular OpenSSH tools, with authentic use of .ssh/config (notably proxy configuration);
 wenzelm parents: 
76119diff
changeset | 4 | SSH client on OpenSSH command-line tools, preferably with connection | 
| 
b8f26c20d3b1
ssh client via regular OpenSSH tools, with authentic use of .ssh/config (notably proxy configuration);
 wenzelm parents: 
76119diff
changeset | 5 | multiplexing, but this does not work on Windows. | 
| 64123 | 6 | */ | 
| 7 | ||
| 8 | package isabelle | |
| 9 | ||
| 10 | ||
| 73909 | 11 | import java.util.{Map => JMap}
 | 
| 76164 
5e8bc80df6b3
clarified run_sftp: avoid platform_path via careful use of tmp_dir, to support both Windows and Cygwin ssh;
 wenzelm parents: 
76163diff
changeset | 12 | import java.io.{File => JFile}
 | 
| 64123 | 13 | |
| 14 | ||
| 75393 | 15 | object SSH {
 | 
| 76170 | 16 | /* client command */ | 
| 17 | ||
| 18 | def client_command(port: Int = 0, control_path: String = ""): String = | |
| 19 |     if (control_path.isEmpty || control_path == Bash.string(control_path)) {
 | |
| 20 | "ssh" + | |
| 21 | (if (port > 0) " -p " + port else "") + | |
| 77369 | 22 | if_proper(control_path, " -o ControlPath=" + control_path) | 
| 76170 | 23 | } | 
| 24 |     else error ("Malformed SSH control socket: " + quote(control_path))
 | |
| 25 | ||
| 26 | ||
| 76122 
b8f26c20d3b1
ssh client via regular OpenSSH tools, with authentic use of .ssh/config (notably proxy configuration);
 wenzelm parents: 
76119diff
changeset | 27 | /* OpenSSH configuration and command-line */ | 
| 
b8f26c20d3b1
ssh client via regular OpenSSH tools, with authentic use of .ssh/config (notably proxy configuration);
 wenzelm parents: 
76119diff
changeset | 28 | |
| 
b8f26c20d3b1
ssh client via regular OpenSSH tools, with authentic use of .ssh/config (notably proxy configuration);
 wenzelm parents: 
76119diff
changeset | 29 | // see https://linux.die.net/man/5/ssh_config | 
| 
b8f26c20d3b1
ssh client via regular OpenSSH tools, with authentic use of .ssh/config (notably proxy configuration);
 wenzelm parents: 
76119diff
changeset | 30 |   object Config {
 | 
| 
b8f26c20d3b1
ssh client via regular OpenSSH tools, with authentic use of .ssh/config (notably proxy configuration);
 wenzelm parents: 
76119diff
changeset | 31 | def entry(x: String, y: String): String = x + "=" + y | 
| 
b8f26c20d3b1
ssh client via regular OpenSSH tools, with authentic use of .ssh/config (notably proxy configuration);
 wenzelm parents: 
76119diff
changeset | 32 | def entry(x: String, y: Int): String = entry(x, y.toString) | 
| 76165 | 33 | def entry(x: String, y: Long): String = entry(x, y.toString) | 
| 76122 
b8f26c20d3b1
ssh client via regular OpenSSH tools, with authentic use of .ssh/config (notably proxy configuration);
 wenzelm parents: 
76119diff
changeset | 34 | def entry(x: String, y: Boolean): String = entry(x, if (y) "yes" else "no") | 
| 64141 | 35 | |
| 76122 
b8f26c20d3b1
ssh client via regular OpenSSH tools, with authentic use of .ssh/config (notably proxy configuration);
 wenzelm parents: 
76119diff
changeset | 36 | def make(options: Options, | 
| 76131 
8b695e59db3f
clarified default: do not override port from ssh_config, which could be different from 22;
 wenzelm parents: 
76130diff
changeset | 37 | port: Int = 0, | 
| 76122 
b8f26c20d3b1
ssh client via regular OpenSSH tools, with authentic use of .ssh/config (notably proxy configuration);
 wenzelm parents: 
76119diff
changeset | 38 | user: String = "", | 
| 
b8f26c20d3b1
ssh client via regular OpenSSH tools, with authentic use of .ssh/config (notably proxy configuration);
 wenzelm parents: 
76119diff
changeset | 39 | control_master: Boolean = false, | 
| 
b8f26c20d3b1
ssh client via regular OpenSSH tools, with authentic use of .ssh/config (notably proxy configuration);
 wenzelm parents: 
76119diff
changeset | 40 | control_path: String = "" | 
| 
b8f26c20d3b1
ssh client via regular OpenSSH tools, with authentic use of .ssh/config (notably proxy configuration);
 wenzelm parents: 
76119diff
changeset | 41 |     ): List[String] = {
 | 
| 76168 | 42 |       val ssh_batch_mode = options.bool("ssh_batch_mode")
 | 
| 76167 | 43 |       val ssh_compression = options.bool("ssh_compression")
 | 
| 44 |       val ssh_alive_interval = options.real("ssh_alive_interval").round
 | |
| 45 |       val ssh_alive_count_max = options.int("ssh_alive_count_max")
 | |
| 46 | ||
| 76168 | 47 | List( | 
| 48 |         entry("BatchMode", ssh_batch_mode),
 | |
| 49 |         entry("Compression", ssh_compression)) :::
 | |
| 76167 | 50 |       (if (ssh_alive_interval >= 0) List(entry("ServerAliveInterval", ssh_alive_interval)) else Nil) :::
 | 
| 51 |       (if (ssh_alive_count_max >= 0) List(entry("ServerAliveCountMax", ssh_alive_count_max)) else Nil) :::
 | |
| 76131 
8b695e59db3f
clarified default: do not override port from ssh_config, which could be different from 22;
 wenzelm parents: 
76130diff
changeset | 52 |       (if (port > 0) List(entry("Port", port)) else Nil) :::
 | 
| 76122 
b8f26c20d3b1
ssh client via regular OpenSSH tools, with authentic use of .ssh/config (notably proxy configuration);
 wenzelm parents: 
76119diff
changeset | 53 |       (if (user.nonEmpty) List(entry("User", user)) else Nil) :::
 | 
| 
b8f26c20d3b1
ssh client via regular OpenSSH tools, with authentic use of .ssh/config (notably proxy configuration);
 wenzelm parents: 
76119diff
changeset | 54 |       (if (control_master) List("ControlMaster=yes", "ControlPersist=yes") else Nil) :::
 | 
| 
b8f26c20d3b1
ssh client via regular OpenSSH tools, with authentic use of .ssh/config (notably proxy configuration);
 wenzelm parents: 
76119diff
changeset | 55 |       (if (control_path.nonEmpty) List("ControlPath=" + control_path) else Nil)
 | 
| 
b8f26c20d3b1
ssh client via regular OpenSSH tools, with authentic use of .ssh/config (notably proxy configuration);
 wenzelm parents: 
76119diff
changeset | 56 | } | 
| 64325 | 57 | |
| 76147 | 58 | def option(entry: String): String = "-o " + Bash.string(entry) | 
| 59 | def option(x: String, y: String): String = option(entry(x, y)) | |
| 60 | def option(x: String, y: Int): String = option(entry(x, y)) | |
| 76165 | 61 | def option(x: String, y: Long): String = option(entry(x, y)) | 
| 76147 | 62 | def option(x: String, y: Boolean): String = option(entry(x, y)) | 
| 63 | ||
| 64 | def command(command: String, config: List[String]): String = | |
| 65 | Bash.string(command) + config.map(entry => " " + option(entry)).mkString | |
| 76122 
b8f26c20d3b1
ssh client via regular OpenSSH tools, with authentic use of .ssh/config (notably proxy configuration);
 wenzelm parents: 
76119diff
changeset | 66 | } | 
| 
b8f26c20d3b1
ssh client via regular OpenSSH tools, with authentic use of .ssh/config (notably proxy configuration);
 wenzelm parents: 
76119diff
changeset | 67 | |
| 
b8f26c20d3b1
ssh client via regular OpenSSH tools, with authentic use of .ssh/config (notably proxy configuration);
 wenzelm parents: 
76119diff
changeset | 68 |   def sftp_string(str: String): String = {
 | 
| 76150 | 69 |     val special = "[]?*\\{} \"'"
 | 
| 70 | if (str.isEmpty) "\"\"" | |
| 71 |     else if (str.exists(special.contains)) {
 | |
| 76122 
b8f26c20d3b1
ssh client via regular OpenSSH tools, with authentic use of .ssh/config (notably proxy configuration);
 wenzelm parents: 
76119diff
changeset | 72 | val res = new StringBuilder | 
| 
b8f26c20d3b1
ssh client via regular OpenSSH tools, with authentic use of .ssh/config (notably proxy configuration);
 wenzelm parents: 
76119diff
changeset | 73 |       for (c <- str) {
 | 
| 76150 | 74 | if (special.contains(c)) res += '\\' | 
| 76122 
b8f26c20d3b1
ssh client via regular OpenSSH tools, with authentic use of .ssh/config (notably proxy configuration);
 wenzelm parents: 
76119diff
changeset | 75 | res += c | 
| 
b8f26c20d3b1
ssh client via regular OpenSSH tools, with authentic use of .ssh/config (notably proxy configuration);
 wenzelm parents: 
76119diff
changeset | 76 | } | 
| 
b8f26c20d3b1
ssh client via regular OpenSSH tools, with authentic use of .ssh/config (notably proxy configuration);
 wenzelm parents: 
76119diff
changeset | 77 | res.toString() | 
| 
b8f26c20d3b1
ssh client via regular OpenSSH tools, with authentic use of .ssh/config (notably proxy configuration);
 wenzelm parents: 
76119diff
changeset | 78 | } | 
| 
b8f26c20d3b1
ssh client via regular OpenSSH tools, with authentic use of .ssh/config (notably proxy configuration);
 wenzelm parents: 
76119diff
changeset | 79 | else str | 
| 
b8f26c20d3b1
ssh client via regular OpenSSH tools, with authentic use of .ssh/config (notably proxy configuration);
 wenzelm parents: 
76119diff
changeset | 80 | } | 
| 67273 
c573cfb2c407
more robust connection: prefer ServerAliveCountMax=3 (ssh default) instead of 1 (jsch default);
 wenzelm parents: 
67067diff
changeset | 81 | |
| 64123 | 82 | |
| 78425 | 83 | /* local host (not "localhost") */ | 
| 84 | ||
| 85 | val LOCAL = "local" | |
| 86 | ||
| 87 | def is_local(host: String): Boolean = host.isEmpty || host == LOCAL | |
| 88 | ||
| 89 | def print_local(host: String): String = if (is_local(host)) LOCAL else host | |
| 90 | ||
| 91 | ||
| 76115 
f17393e21388
clarified signature: discontinue somewhat pointless SSH.Context;
 wenzelm parents: 
76100diff
changeset | 92 | /* open session */ | 
| 
f17393e21388
clarified signature: discontinue somewhat pointless SSH.Context;
 wenzelm parents: 
76100diff
changeset | 93 | |
| 
f17393e21388
clarified signature: discontinue somewhat pointless SSH.Context;
 wenzelm parents: 
76100diff
changeset | 94 | def open_session( | 
| 
f17393e21388
clarified signature: discontinue somewhat pointless SSH.Context;
 wenzelm parents: 
76100diff
changeset | 95 | options: Options, | 
| 
f17393e21388
clarified signature: discontinue somewhat pointless SSH.Context;
 wenzelm parents: 
76100diff
changeset | 96 | host: String, | 
| 76122 
b8f26c20d3b1
ssh client via regular OpenSSH tools, with authentic use of .ssh/config (notably proxy configuration);
 wenzelm parents: 
76119diff
changeset | 97 | port: Int = 0, | 
| 76148 
769ebb139a32
support port forwarding without multiplexing (for the sake of Windows);
 wenzelm parents: 
76147diff
changeset | 98 | user: String = "" | 
| 76115 
f17393e21388
clarified signature: discontinue somewhat pointless SSH.Context;
 wenzelm parents: 
76100diff
changeset | 99 |   ): Session = {
 | 
| 78583 | 100 |     if (is_local(host)) error("Illegal SSH host name " + quote(host))
 | 
| 78425 | 101 | |
| 76148 
769ebb139a32
support port forwarding without multiplexing (for the sake of Windows);
 wenzelm parents: 
76147diff
changeset | 102 |     val multiplex = options.bool("ssh_multiplexing") && !Platform.is_windows
 | 
| 76122 
b8f26c20d3b1
ssh client via regular OpenSSH tools, with authentic use of .ssh/config (notably proxy configuration);
 wenzelm parents: 
76119diff
changeset | 103 | val (control_master, control_path) = | 
| 76161 | 104 |       if (multiplex) (true, Isabelle_System.tmp_file("ssh", initialized = false).getPath)
 | 
| 76122 
b8f26c20d3b1
ssh client via regular OpenSSH tools, with authentic use of .ssh/config (notably proxy configuration);
 wenzelm parents: 
76119diff
changeset | 105 | else (false, "") | 
| 76131 
8b695e59db3f
clarified default: do not override port from ssh_config, which could be different from 22;
 wenzelm parents: 
76130diff
changeset | 106 | new Session(options, host, port, user, control_master, control_path) | 
| 64257 | 107 | } | 
| 64130 | 108 | |
| 76122 
b8f26c20d3b1
ssh client via regular OpenSSH tools, with authentic use of .ssh/config (notably proxy configuration);
 wenzelm parents: 
76119diff
changeset | 109 | class Session private[SSH]( | 
| 
b8f26c20d3b1
ssh client via regular OpenSSH tools, with authentic use of .ssh/config (notably proxy configuration);
 wenzelm parents: 
76119diff
changeset | 110 | val options: Options, | 
| 
b8f26c20d3b1
ssh client via regular OpenSSH tools, with authentic use of .ssh/config (notably proxy configuration);
 wenzelm parents: 
76119diff
changeset | 111 | val host: String, | 
| 
b8f26c20d3b1
ssh client via regular OpenSSH tools, with authentic use of .ssh/config (notably proxy configuration);
 wenzelm parents: 
76119diff
changeset | 112 | val port: Int, | 
| 
b8f26c20d3b1
ssh client via regular OpenSSH tools, with authentic use of .ssh/config (notably proxy configuration);
 wenzelm parents: 
76119diff
changeset | 113 | val user: String, | 
| 
b8f26c20d3b1
ssh client via regular OpenSSH tools, with authentic use of .ssh/config (notably proxy configuration);
 wenzelm parents: 
76119diff
changeset | 114 | control_master: Boolean, | 
| 76136 
1bb677cceea4
let rsync re-use ssh connection via control path;
 wenzelm parents: 
76133diff
changeset | 115 | val control_path: String | 
| 76122 
b8f26c20d3b1
ssh client via regular OpenSSH tools, with authentic use of .ssh/config (notably proxy configuration);
 wenzelm parents: 
76119diff
changeset | 116 |   ) extends System {
 | 
| 
b8f26c20d3b1
ssh client via regular OpenSSH tools, with authentic use of .ssh/config (notably proxy configuration);
 wenzelm parents: 
76119diff
changeset | 117 | ssh => | 
| 64128 | 118 | |
| 78346 | 119 | override def ssh_session: Option[Session] = Some(ssh) | 
| 77782 
127d077cccfe
clarified signature: avoid object-oriented "dispatch";
 wenzelm parents: 
77761diff
changeset | 120 | |
| 76133 | 121 | def port_suffix: String = if (port > 0) ":" + port else "" | 
| 122 | def user_prefix: String = if (user.nonEmpty) user + "@" else "" | |
| 123 | ||
| 124 | override def toString: String = user_prefix + host + port_suffix | |
| 77761 | 125 | override def print: String = " (ssh " + toString + ")" | 
| 76132 | 126 | override def hg_url: String = "ssh://" + toString + "/" | 
| 77783 
fb61887c069a
clarified underlying SSH session of "isabelle hg_sync" and "isabelle sync";
 wenzelm parents: 
77782diff
changeset | 127 | override def client_command: String = | 
| 
fb61887c069a
clarified underlying SSH session of "isabelle hg_sync" and "isabelle sync";
 wenzelm parents: 
77782diff
changeset | 128 | SSH.client_command(port = port, control_path = control_path) | 
| 76133 | 129 | override def rsync_prefix: String = user_prefix + host + ":" | 
| 64191 | 130 | |
| 131 | ||
| 76147 | 132 | /* local ssh commands */ | 
| 64256 
c3197aeae90b
simplified SSH.Session: sftp channel is always open and its operations provided by the main interface;
 wenzelm parents: 
64254diff
changeset | 133 | |
| 76122 
b8f26c20d3b1
ssh client via regular OpenSSH tools, with authentic use of .ssh/config (notably proxy configuration);
 wenzelm parents: 
76119diff
changeset | 134 | def run_command(command: String, | 
| 
b8f26c20d3b1
ssh client via regular OpenSSH tools, with authentic use of .ssh/config (notably proxy configuration);
 wenzelm parents: 
76119diff
changeset | 135 | master: Boolean = false, | 
| 
b8f26c20d3b1
ssh client via regular OpenSSH tools, with authentic use of .ssh/config (notably proxy configuration);
 wenzelm parents: 
76119diff
changeset | 136 | opts: String = "", | 
| 
b8f26c20d3b1
ssh client via regular OpenSSH tools, with authentic use of .ssh/config (notably proxy configuration);
 wenzelm parents: 
76119diff
changeset | 137 | args: String = "", | 
| 76164 
5e8bc80df6b3
clarified run_sftp: avoid platform_path via careful use of tmp_dir, to support both Windows and Cygwin ssh;
 wenzelm parents: 
76163diff
changeset | 138 | cwd: JFile = null, | 
| 77092 | 139 | redirect: Boolean = false, | 
| 64134 
57581e4026fe
proper support for exec channel (see also bash.scala);
 wenzelm parents: 
64133diff
changeset | 140 | progress_stdout: String => Unit = (_: String) => (), | 
| 
57581e4026fe
proper support for exec channel (see also bash.scala);
 wenzelm parents: 
64133diff
changeset | 141 | progress_stderr: String => Unit = (_: String) => (), | 
| 75393 | 142 | strict: Boolean = true | 
| 143 |     ): Process_Result = {
 | |
| 76122 
b8f26c20d3b1
ssh client via regular OpenSSH tools, with authentic use of .ssh/config (notably proxy configuration);
 wenzelm parents: 
76119diff
changeset | 144 | val config = | 
| 
b8f26c20d3b1
ssh client via regular OpenSSH tools, with authentic use of .ssh/config (notably proxy configuration);
 wenzelm parents: 
76119diff
changeset | 145 | Config.make(options, port = port, user = user, | 
| 
b8f26c20d3b1
ssh client via regular OpenSSH tools, with authentic use of .ssh/config (notably proxy configuration);
 wenzelm parents: 
76119diff
changeset | 146 | control_master = master, control_path = control_path) | 
| 
b8f26c20d3b1
ssh client via regular OpenSSH tools, with authentic use of .ssh/config (notably proxy configuration);
 wenzelm parents: 
76119diff
changeset | 147 | val cmd = | 
| 76147 | 148 | Config.command(command, config) + | 
| 77369 | 149 | if_proper(opts, " " + opts) + | 
| 150 | if_proper(args, " -- " + args) | |
| 77092 | 151 | Isabelle_System.bash(cmd, cwd = cwd, | 
| 152 | redirect = redirect, | |
| 153 | progress_stdout = progress_stdout, | |
| 154 | progress_stderr = progress_stderr, | |
| 155 | strict = strict) | |
| 76122 
b8f26c20d3b1
ssh client via regular OpenSSH tools, with authentic use of .ssh/config (notably proxy configuration);
 wenzelm parents: 
76119diff
changeset | 156 | } | 
| 64134 
57581e4026fe
proper support for exec channel (see also bash.scala);
 wenzelm parents: 
64133diff
changeset | 157 | |
| 76164 
5e8bc80df6b3
clarified run_sftp: avoid platform_path via careful use of tmp_dir, to support both Windows and Cygwin ssh;
 wenzelm parents: 
76163diff
changeset | 158 | def run_sftp( | 
| 
5e8bc80df6b3
clarified run_sftp: avoid platform_path via careful use of tmp_dir, to support both Windows and Cygwin ssh;
 wenzelm parents: 
76163diff
changeset | 159 | script: String, | 
| 
5e8bc80df6b3
clarified run_sftp: avoid platform_path via careful use of tmp_dir, to support both Windows and Cygwin ssh;
 wenzelm parents: 
76163diff
changeset | 160 | init: Path => Unit = _ => (), | 
| 
5e8bc80df6b3
clarified run_sftp: avoid platform_path via careful use of tmp_dir, to support both Windows and Cygwin ssh;
 wenzelm parents: 
76163diff
changeset | 161 | exit: Path => Unit = _ => () | 
| 
5e8bc80df6b3
clarified run_sftp: avoid platform_path via careful use of tmp_dir, to support both Windows and Cygwin ssh;
 wenzelm parents: 
76163diff
changeset | 162 |     ): Process_Result = {
 | 
| 
5e8bc80df6b3
clarified run_sftp: avoid platform_path via careful use of tmp_dir, to support both Windows and Cygwin ssh;
 wenzelm parents: 
76163diff
changeset | 163 |       Isabelle_System.with_tmp_dir("ssh") { dir =>
 | 
| 
5e8bc80df6b3
clarified run_sftp: avoid platform_path via careful use of tmp_dir, to support both Windows and Cygwin ssh;
 wenzelm parents: 
76163diff
changeset | 164 | init(dir) | 
| 
5e8bc80df6b3
clarified run_sftp: avoid platform_path via careful use of tmp_dir, to support both Windows and Cygwin ssh;
 wenzelm parents: 
76163diff
changeset | 165 |         File.write(dir + Path.explode("script"), script)
 | 
| 
5e8bc80df6b3
clarified run_sftp: avoid platform_path via careful use of tmp_dir, to support both Windows and Cygwin ssh;
 wenzelm parents: 
76163diff
changeset | 166 | val result = | 
| 
5e8bc80df6b3
clarified run_sftp: avoid platform_path via careful use of tmp_dir, to support both Windows and Cygwin ssh;
 wenzelm parents: 
76163diff
changeset | 167 |           run_command("sftp", opts = "-b script", args = Bash.string(host), cwd = dir.file).check
 | 
| 
5e8bc80df6b3
clarified run_sftp: avoid platform_path via careful use of tmp_dir, to support both Windows and Cygwin ssh;
 wenzelm parents: 
76163diff
changeset | 168 | exit(dir) | 
| 
5e8bc80df6b3
clarified run_sftp: avoid platform_path via careful use of tmp_dir, to support both Windows and Cygwin ssh;
 wenzelm parents: 
76163diff
changeset | 169 | result | 
| 76116 
c4dc343fdbcb
clarified signature: avoid exposure of JSch types;
 wenzelm parents: 
76115diff
changeset | 170 | } | 
| 
c4dc343fdbcb
clarified signature: avoid exposure of JSch types;
 wenzelm parents: 
76115diff
changeset | 171 | } | 
| 65009 | 172 | |
| 76122 
b8f26c20d3b1
ssh client via regular OpenSSH tools, with authentic use of .ssh/config (notably proxy configuration);
 wenzelm parents: 
76119diff
changeset | 173 |     def run_ssh(master: Boolean = false, opts: String = "", args: String = ""): Process_Result = {
 | 
| 77369 | 174 | val args1 = Bash.string(host) + if_proper(args, " " + args) | 
| 76122 
b8f26c20d3b1
ssh client via regular OpenSSH tools, with authentic use of .ssh/config (notably proxy configuration);
 wenzelm parents: 
76119diff
changeset | 175 |       run_command("ssh", master = master, opts = opts, args = args1)
 | 
| 
b8f26c20d3b1
ssh client via regular OpenSSH tools, with authentic use of .ssh/config (notably proxy configuration);
 wenzelm parents: 
76119diff
changeset | 176 | } | 
| 64256 
c3197aeae90b
simplified SSH.Session: sftp channel is always open and its operations provided by the main interface;
 wenzelm parents: 
64254diff
changeset | 177 | |
| 76122 
b8f26c20d3b1
ssh client via regular OpenSSH tools, with authentic use of .ssh/config (notably proxy configuration);
 wenzelm parents: 
76119diff
changeset | 178 | |
| 
b8f26c20d3b1
ssh client via regular OpenSSH tools, with authentic use of .ssh/config (notably proxy configuration);
 wenzelm parents: 
76119diff
changeset | 179 | /* init and exit */ | 
| 
b8f26c20d3b1
ssh client via regular OpenSSH tools, with authentic use of .ssh/config (notably proxy configuration);
 wenzelm parents: 
76119diff
changeset | 180 | |
| 76151 | 181 |     val user_home: String = {
 | 
| 76154 
dfddb80fc515
more robust: do not assume Bash syntax while testing for it;
 wenzelm parents: 
76151diff
changeset | 182 | run_ssh(master = control_master, args = "printenv HOME \";\" printenv SHELL").check.out_lines | 
| 
dfddb80fc515
more robust: do not assume Bash syntax while testing for it;
 wenzelm parents: 
76151diff
changeset | 183 |       match {
 | 
| 76149 
ccc748255342
more robust: Bash.string operations require remote bash;
 wenzelm parents: 
76148diff
changeset | 184 | case List(user_home, shell) => | 
| 
ccc748255342
more robust: Bash.string operations require remote bash;
 wenzelm parents: 
76148diff
changeset | 185 |           if (shell.endsWith("/bash")) user_home
 | 
| 
ccc748255342
more robust: Bash.string operations require remote bash;
 wenzelm parents: 
76148diff
changeset | 186 |           else {
 | 
| 
ccc748255342
more robust: Bash.string operations require remote bash;
 wenzelm parents: 
76148diff
changeset | 187 |             error("Bad SHELL for " + quote(toString) + " -- expected GNU bash, but found " + shell)
 | 
| 
ccc748255342
more robust: Bash.string operations require remote bash;
 wenzelm parents: 
76148diff
changeset | 188 | } | 
| 
ccc748255342
more robust: Bash.string operations require remote bash;
 wenzelm parents: 
76148diff
changeset | 189 |         case _ => error("Malformed remote environment for " + quote(toString))
 | 
| 
ccc748255342
more robust: Bash.string operations require remote bash;
 wenzelm parents: 
76148diff
changeset | 190 | } | 
| 76151 | 191 | } | 
| 76122 
b8f26c20d3b1
ssh client via regular OpenSSH tools, with authentic use of .ssh/config (notably proxy configuration);
 wenzelm parents: 
76119diff
changeset | 192 | |
| 77079 
395a0701a125
clarified signature: minimal interface for getenv/expand_env, instead of bulky java.util.Map;
 wenzelm parents: 
77077diff
changeset | 193 | val settings: Isabelle_System.Settings = | 
| 
395a0701a125
clarified signature: minimal interface for getenv/expand_env, instead of bulky java.util.Map;
 wenzelm parents: 
77077diff
changeset | 194 | (name: String) => if (name == "HOME" || name == "USER_HOME") user_home else "" | 
| 64256 
c3197aeae90b
simplified SSH.Session: sftp channel is always open and its operations provided by the main interface;
 wenzelm parents: 
64254diff
changeset | 195 | |
| 76122 
b8f26c20d3b1
ssh client via regular OpenSSH tools, with authentic use of .ssh/config (notably proxy configuration);
 wenzelm parents: 
76119diff
changeset | 196 |     override def close(): Unit = {
 | 
| 
b8f26c20d3b1
ssh client via regular OpenSSH tools, with authentic use of .ssh/config (notably proxy configuration);
 wenzelm parents: 
76119diff
changeset | 197 | if (control_path.nonEmpty) run_ssh(opts = "-O exit").check | 
| 
b8f26c20d3b1
ssh client via regular OpenSSH tools, with authentic use of .ssh/config (notably proxy configuration);
 wenzelm parents: 
76119diff
changeset | 198 | } | 
| 
b8f26c20d3b1
ssh client via regular OpenSSH tools, with authentic use of .ssh/config (notably proxy configuration);
 wenzelm parents: 
76119diff
changeset | 199 | |
| 
b8f26c20d3b1
ssh client via regular OpenSSH tools, with authentic use of .ssh/config (notably proxy configuration);
 wenzelm parents: 
76119diff
changeset | 200 | |
| 
b8f26c20d3b1
ssh client via regular OpenSSH tools, with authentic use of .ssh/config (notably proxy configuration);
 wenzelm parents: 
76119diff
changeset | 201 | /* remote commands */ | 
| 64256 
c3197aeae90b
simplified SSH.Session: sftp channel is always open and its operations provided by the main interface;
 wenzelm parents: 
64254diff
changeset | 202 | |
| 76122 
b8f26c20d3b1
ssh client via regular OpenSSH tools, with authentic use of .ssh/config (notably proxy configuration);
 wenzelm parents: 
76119diff
changeset | 203 | override def execute(cmd_line: String, | 
| 
b8f26c20d3b1
ssh client via regular OpenSSH tools, with authentic use of .ssh/config (notably proxy configuration);
 wenzelm parents: 
76119diff
changeset | 204 | progress_stdout: String => Unit = (_: String) => (), | 
| 
b8f26c20d3b1
ssh client via regular OpenSSH tools, with authentic use of .ssh/config (notably proxy configuration);
 wenzelm parents: 
76119diff
changeset | 205 | progress_stderr: String => Unit = (_: String) => (), | 
| 77092 | 206 | redirect: Boolean = false, | 
| 76122 
b8f26c20d3b1
ssh client via regular OpenSSH tools, with authentic use of .ssh/config (notably proxy configuration);
 wenzelm parents: 
76119diff
changeset | 207 | settings: Boolean = true, | 
| 
b8f26c20d3b1
ssh client via regular OpenSSH tools, with authentic use of .ssh/config (notably proxy configuration);
 wenzelm parents: 
76119diff
changeset | 208 | strict: Boolean = true | 
| 
b8f26c20d3b1
ssh client via regular OpenSSH tools, with authentic use of .ssh/config (notably proxy configuration);
 wenzelm parents: 
76119diff
changeset | 209 |     ): Process_Result = {
 | 
| 77077 
c2e8ba15a10a
discontinued adhoc change of environment (from c62b99e3ec07), which has been mostly superseded by expand_path / remote_path (from ef6f7e8a018c);
 wenzelm parents: 
77076diff
changeset | 210 |       run_command("ssh",
 | 
| 
c2e8ba15a10a
discontinued adhoc change of environment (from c62b99e3ec07), which has been mostly superseded by expand_path / remote_path (from ef6f7e8a018c);
 wenzelm parents: 
77076diff
changeset | 211 | args = Bash.string(host) + " " + Bash.string(cmd_line), | 
| 
c2e8ba15a10a
discontinued adhoc change of environment (from c62b99e3ec07), which has been mostly superseded by expand_path / remote_path (from ef6f7e8a018c);
 wenzelm parents: 
77076diff
changeset | 212 | progress_stdout = progress_stdout, | 
| 
c2e8ba15a10a
discontinued adhoc change of environment (from c62b99e3ec07), which has been mostly superseded by expand_path / remote_path (from ef6f7e8a018c);
 wenzelm parents: 
77076diff
changeset | 213 | progress_stderr = progress_stderr, | 
| 77092 | 214 | redirect = redirect, | 
| 77077 
c2e8ba15a10a
discontinued adhoc change of environment (from c62b99e3ec07), which has been mostly superseded by expand_path / remote_path (from ef6f7e8a018c);
 wenzelm parents: 
77076diff
changeset | 215 | strict = strict) | 
| 64256 
c3197aeae90b
simplified SSH.Session: sftp channel is always open and its operations provided by the main interface;
 wenzelm parents: 
64254diff
changeset | 216 | } | 
| 76122 
b8f26c20d3b1
ssh client via regular OpenSSH tools, with authentic use of .ssh/config (notably proxy configuration);
 wenzelm parents: 
76119diff
changeset | 217 | |
| 77054 | 218 | override def download_file( | 
| 219 | url_name: String, | |
| 220 | file: Path, | |
| 221 | progress: Progress = new Progress | |
| 222 |     ): Unit = {
 | |
| 223 | val cmd_line = | |
| 224 |         File.read(Path.explode("~~/lib/scripts/download_file")) + "\n" +
 | |
| 225 | "download_file " + Bash.string(url_name) + " " + bash_path(file) | |
| 226 | execute(cmd_line, | |
| 77509 
3bc49507bae5
clarified treatment of "verbose" messages, e.g. Progress.theory();
 wenzelm parents: 
77369diff
changeset | 227 | progress_stdout = progress.echo(_), | 
| 
3bc49507bae5
clarified treatment of "verbose" messages, e.g. Progress.theory();
 wenzelm parents: 
77369diff
changeset | 228 | progress_stderr = progress.echo(_)).check | 
| 77054 | 229 | } | 
| 230 | ||
| 76122 
b8f26c20d3b1
ssh client via regular OpenSSH tools, with authentic use of .ssh/config (notably proxy configuration);
 wenzelm parents: 
76119diff
changeset | 231 | override lazy val isabelle_platform: Isabelle_Platform = Isabelle_Platform(ssh = Some(ssh)) | 
| 
b8f26c20d3b1
ssh client via regular OpenSSH tools, with authentic use of .ssh/config (notably proxy configuration);
 wenzelm parents: 
76119diff
changeset | 232 | |
| 
b8f26c20d3b1
ssh client via regular OpenSSH tools, with authentic use of .ssh/config (notably proxy configuration);
 wenzelm parents: 
76119diff
changeset | 233 | |
| 
b8f26c20d3b1
ssh client via regular OpenSSH tools, with authentic use of .ssh/config (notably proxy configuration);
 wenzelm parents: 
76119diff
changeset | 234 | /* remote file-system */ | 
| 
b8f26c20d3b1
ssh client via regular OpenSSH tools, with authentic use of .ssh/config (notably proxy configuration);
 wenzelm parents: 
76119diff
changeset | 235 | |
| 66570 | 236 | override def expand_path(path: Path): Path = path.expand_env(settings) | 
| 77076 | 237 |     override def absolute_path(path: Path): Path = {
 | 
| 238 | val path1 = expand_path(path) | |
| 239 | if (path1.is_absolute) path1 else Path.explode(user_home) + path1 | |
| 240 | } | |
| 241 | ||
| 64256 
c3197aeae90b
simplified SSH.Session: sftp channel is always open and its operations provided by the main interface;
 wenzelm parents: 
64254diff
changeset | 242 | def remote_path(path: Path): String = expand_path(path).implode | 
| 76122 
b8f26c20d3b1
ssh client via regular OpenSSH tools, with authentic use of .ssh/config (notably proxy configuration);
 wenzelm parents: 
76119diff
changeset | 243 | |
| 67066 | 244 | override def bash_path(path: Path): String = Bash.string(remote_path(path)) | 
| 76122 
b8f26c20d3b1
ssh client via regular OpenSSH tools, with authentic use of .ssh/config (notably proxy configuration);
 wenzelm parents: 
76119diff
changeset | 245 | def sftp_path(path: Path): String = sftp_string(remote_path(path)) | 
| 64256 
c3197aeae90b
simplified SSH.Session: sftp channel is always open and its operations provided by the main interface;
 wenzelm parents: 
64254diff
changeset | 246 | |
| 76122 
b8f26c20d3b1
ssh client via regular OpenSSH tools, with authentic use of .ssh/config (notably proxy configuration);
 wenzelm parents: 
76119diff
changeset | 247 | override def is_dir(path: Path): Boolean = run_ssh(args = "test -d " + bash_path(path)).ok | 
| 
b8f26c20d3b1
ssh client via regular OpenSSH tools, with authentic use of .ssh/config (notably proxy configuration);
 wenzelm parents: 
76119diff
changeset | 248 | override def is_file(path: Path): Boolean = run_ssh(args = "test -f " + bash_path(path)).ok | 
| 69300 
8b6ab9989bcd
is_file/is_dir/read_dir: more uniform treatment of errors and boundary cases, notably for symlinks in ssh;
 wenzelm parents: 
67771diff
changeset | 249 | |
| 77092 | 250 |     override def delete(path: Path): Unit = {
 | 
| 251 | val cmd = if (is_dir(path)) "rmdir" else if (is_file(path)) "rm" else "" | |
| 252 | if (cmd.nonEmpty) run_sftp(cmd + " " + sftp_path(path)) | |
| 253 | } | |
| 254 | ||
| 78161 | 255 | override def restrict(path: Path): Unit = | 
| 256 |       if (!execute("chmod g-rwx,o-rwx " + bash_path(path)).ok) {
 | |
| 257 |         error("Failed to change permissions of " + quote(remote_path(path)))
 | |
| 258 | } | |
| 259 | ||
| 78298 
3b0f8f1010f2
clarified signature, with subtle change of semantics (amending 8b5a2e4b16d4);
 wenzelm parents: 
78161diff
changeset | 260 | override def set_executable(path: Path, reset: Boolean = false): Unit = | 
| 
3b0f8f1010f2
clarified signature, with subtle change of semantics (amending 8b5a2e4b16d4);
 wenzelm parents: 
78161diff
changeset | 261 |       if (!execute("chmod a" + (if (reset) "-" else "+") + "x " + bash_path(path)).ok) {
 | 
| 77092 | 262 |         error("Failed to change executable status of " + quote(remote_path(path)))
 | 
| 263 | } | |
| 264 | ||
| 75393 | 265 |     override def make_directory(path: Path): Path = {
 | 
| 76122 
b8f26c20d3b1
ssh client via regular OpenSSH tools, with authentic use of .ssh/config (notably proxy configuration);
 wenzelm parents: 
76119diff
changeset | 266 |       if (!execute("mkdir -p " + bash_path(path)).ok) {
 | 
| 76118 | 267 |         error("Failed to create directory: " + quote(remote_path(path)))
 | 
| 64256 
c3197aeae90b
simplified SSH.Session: sftp channel is always open and its operations provided by the main interface;
 wenzelm parents: 
64254diff
changeset | 268 | } | 
| 72376 | 269 | path | 
| 270 | } | |
| 64256 
c3197aeae90b
simplified SSH.Session: sftp channel is always open and its operations provided by the main interface;
 wenzelm parents: 
64254diff
changeset | 271 | |
| 77059 | 272 |     override def copy_file(src: Path, dst: Path): Unit = {
 | 
| 273 | val direct = if (is_dir(dst)) "/." else "" | |
| 274 |       if (!execute("cp -a " + bash_path(src) + " " + bash_path(dst) + direct).ok) {
 | |
| 275 |         error("Failed to copy file " + expand_path(src) + " to " +
 | |
| 276 | expand_path(dst) + " (ssh " + toString + ")") | |
| 277 | } | |
| 278 | } | |
| 279 | ||
| 77096 | 280 | override def read_dir(path: Path): List[String] = | 
| 76241 | 281 |       run_sftp("@cd " + sftp_path(path) + "\n@ls -1 -a").out_lines.flatMap(s =>
 | 
| 282 | if (s == "." || s == "..") None | |
| 76122 
b8f26c20d3b1
ssh client via regular OpenSSH tools, with authentic use of .ssh/config (notably proxy configuration);
 wenzelm parents: 
76119diff
changeset | 283 |         else Some(Library.perhaps_unprefix("./", s)))
 | 
| 64256 
c3197aeae90b
simplified SSH.Session: sftp channel is always open and its operations provided by the main interface;
 wenzelm parents: 
64254diff
changeset | 284 | |
| 76164 
5e8bc80df6b3
clarified run_sftp: avoid platform_path via careful use of tmp_dir, to support both Windows and Cygwin ssh;
 wenzelm parents: 
76163diff
changeset | 285 |     private def get_file[A](path: Path, f: Path => A): A = {
 | 
| 
5e8bc80df6b3
clarified run_sftp: avoid platform_path via careful use of tmp_dir, to support both Windows and Cygwin ssh;
 wenzelm parents: 
76163diff
changeset | 286 | var result: Option[A] = None | 
| 
5e8bc80df6b3
clarified run_sftp: avoid platform_path via careful use of tmp_dir, to support both Windows and Cygwin ssh;
 wenzelm parents: 
76163diff
changeset | 287 |       run_sftp("get -p " + sftp_path(path) + " local",
 | 
| 
5e8bc80df6b3
clarified run_sftp: avoid platform_path via careful use of tmp_dir, to support both Windows and Cygwin ssh;
 wenzelm parents: 
76163diff
changeset | 288 |         exit = dir => result = Some(f(dir + Path.explode("local"))))
 | 
| 
5e8bc80df6b3
clarified run_sftp: avoid platform_path via careful use of tmp_dir, to support both Windows and Cygwin ssh;
 wenzelm parents: 
76163diff
changeset | 289 | result.get | 
| 
5e8bc80df6b3
clarified run_sftp: avoid platform_path via careful use of tmp_dir, to support both Windows and Cygwin ssh;
 wenzelm parents: 
76163diff
changeset | 290 | } | 
| 
5e8bc80df6b3
clarified run_sftp: avoid platform_path via careful use of tmp_dir, to support both Windows and Cygwin ssh;
 wenzelm parents: 
76163diff
changeset | 291 | |
| 
5e8bc80df6b3
clarified run_sftp: avoid platform_path via careful use of tmp_dir, to support both Windows and Cygwin ssh;
 wenzelm parents: 
76163diff
changeset | 292 | private def put_file(path: Path, f: Path => Unit): Unit = | 
| 
5e8bc80df6b3
clarified run_sftp: avoid platform_path via careful use of tmp_dir, to support both Windows and Cygwin ssh;
 wenzelm parents: 
76163diff
changeset | 293 |       run_sftp("put -p local " + sftp_path(path),
 | 
| 
5e8bc80df6b3
clarified run_sftp: avoid platform_path via careful use of tmp_dir, to support both Windows and Cygwin ssh;
 wenzelm parents: 
76163diff
changeset | 294 |         init = dir => f(dir + Path.explode("local")))
 | 
| 
5e8bc80df6b3
clarified run_sftp: avoid platform_path via careful use of tmp_dir, to support both Windows and Cygwin ssh;
 wenzelm parents: 
76163diff
changeset | 295 | |
| 73634 | 296 | override def read_file(path: Path, local_path: Path): Unit = | 
| 76164 
5e8bc80df6b3
clarified run_sftp: avoid platform_path via careful use of tmp_dir, to support both Windows and Cygwin ssh;
 wenzelm parents: 
76163diff
changeset | 297 | get_file(path, Isabelle_System.copy_file(_, local_path)) | 
| 76122 
b8f26c20d3b1
ssh client via regular OpenSSH tools, with authentic use of .ssh/config (notably proxy configuration);
 wenzelm parents: 
76119diff
changeset | 298 | override def read_bytes(path: Path): Bytes = | 
| 76164 
5e8bc80df6b3
clarified run_sftp: avoid platform_path via careful use of tmp_dir, to support both Windows and Cygwin ssh;
 wenzelm parents: 
76163diff
changeset | 299 | get_file(path, Bytes.read) | 
| 
5e8bc80df6b3
clarified run_sftp: avoid platform_path via careful use of tmp_dir, to support both Windows and Cygwin ssh;
 wenzelm parents: 
76163diff
changeset | 300 | override def read(path: Path): String = | 
| 
5e8bc80df6b3
clarified run_sftp: avoid platform_path via careful use of tmp_dir, to support both Windows and Cygwin ssh;
 wenzelm parents: 
76163diff
changeset | 301 | get_file(path, File.read) | 
| 64256 
c3197aeae90b
simplified SSH.Session: sftp channel is always open and its operations provided by the main interface;
 wenzelm parents: 
64254diff
changeset | 302 | |
| 73634 | 303 | override def write_file(path: Path, local_path: Path): Unit = | 
| 76164 
5e8bc80df6b3
clarified run_sftp: avoid platform_path via careful use of tmp_dir, to support both Windows and Cygwin ssh;
 wenzelm parents: 
76163diff
changeset | 304 | put_file(path, Isabelle_System.copy_file(local_path, _)) | 
| 77092 | 305 | override def write_bytes(path: Path, bytes: Bytes): Unit = | 
| 76164 
5e8bc80df6b3
clarified run_sftp: avoid platform_path via careful use of tmp_dir, to support both Windows and Cygwin ssh;
 wenzelm parents: 
76163diff
changeset | 306 | put_file(path, Bytes.write(_, bytes)) | 
| 77092 | 307 | override def write(path: Path, text: String): Unit = | 
| 76164 
5e8bc80df6b3
clarified run_sftp: avoid platform_path via careful use of tmp_dir, to support both Windows and Cygwin ssh;
 wenzelm parents: 
76163diff
changeset | 308 | put_file(path, File.write(_, text)) | 
| 72338 | 309 | |
| 64137 | 310 | |
| 311 | /* tmp dirs */ | |
| 312 | ||
| 77092 | 313 | override def rm_tree(dir: Path): Unit = rm_tree(remote_path(dir)) | 
| 64306 
7b6dc1b36f20
tuned signature, in accordance to Isabelle_System;
 wenzelm parents: 
64304diff
changeset | 314 | |
| 64137 | 315 | def rm_tree(remote_dir: String): Unit = | 
| 64304 | 316 |       execute("rm -r -f " + Bash.string(remote_dir)).check
 | 
| 64137 | 317 | |
| 318 | def tmp_dir(): String = | |
| 76163 
9df6f51ebf45
more robust, notably for macOS (see also ff92d6edff2c);
 wenzelm parents: 
76161diff
changeset | 319 |       execute("mktemp -d /tmp/ssh-XXXXXXXXXXXX").check.out
 | 
| 64137 | 320 | |
| 75393 | 321 |     override def with_tmp_dir[A](body: Path => A): A = {
 | 
| 64137 | 322 | val remote_dir = tmp_dir() | 
| 76122 
b8f26c20d3b1
ssh client via regular OpenSSH tools, with authentic use of .ssh/config (notably proxy configuration);
 wenzelm parents: 
76119diff
changeset | 323 |       try { body(Path.explode(remote_dir)) }
 | 
| 
b8f26c20d3b1
ssh client via regular OpenSSH tools, with authentic use of .ssh/config (notably proxy configuration);
 wenzelm parents: 
76119diff
changeset | 324 |       finally { rm_tree(remote_dir) }
 | 
| 
b8f26c20d3b1
ssh client via regular OpenSSH tools, with authentic use of .ssh/config (notably proxy configuration);
 wenzelm parents: 
76119diff
changeset | 325 | } | 
| 
b8f26c20d3b1
ssh client via regular OpenSSH tools, with authentic use of .ssh/config (notably proxy configuration);
 wenzelm parents: 
76119diff
changeset | 326 | |
| 
b8f26c20d3b1
ssh client via regular OpenSSH tools, with authentic use of .ssh/config (notably proxy configuration);
 wenzelm parents: 
76119diff
changeset | 327 | |
| 78345 | 328 | /* open server on remote host */ | 
| 76122 
b8f26c20d3b1
ssh client via regular OpenSSH tools, with authentic use of .ssh/config (notably proxy configuration);
 wenzelm parents: 
76119diff
changeset | 329 | |
| 78345 | 330 | def open_server( | 
| 78339 | 331 | remote_port: Int = 0, | 
| 76122 
b8f26c20d3b1
ssh client via regular OpenSSH tools, with authentic use of .ssh/config (notably proxy configuration);
 wenzelm parents: 
76119diff
changeset | 332 | remote_host: String = "localhost", | 
| 
b8f26c20d3b1
ssh client via regular OpenSSH tools, with authentic use of .ssh/config (notably proxy configuration);
 wenzelm parents: 
76119diff
changeset | 333 | local_port: Int = 0, | 
| 
b8f26c20d3b1
ssh client via regular OpenSSH tools, with authentic use of .ssh/config (notably proxy configuration);
 wenzelm parents: 
76119diff
changeset | 334 | local_host: String = "localhost", | 
| 
b8f26c20d3b1
ssh client via regular OpenSSH tools, with authentic use of .ssh/config (notably proxy configuration);
 wenzelm parents: 
76119diff
changeset | 335 | ssh_close: Boolean = false | 
| 78345 | 336 |     ): Server = {
 | 
| 78340 
5790e48f7573
clarified signature: more uniform SSH.Port_Forwarding;
 wenzelm parents: 
78339diff
changeset | 337 | val forward_host = local_host | 
| 
5790e48f7573
clarified signature: more uniform SSH.Port_Forwarding;
 wenzelm parents: 
78339diff
changeset | 338 | val forward_port = if (local_port > 0) local_port else Isabelle_System.local_port() | 
| 
5790e48f7573
clarified signature: more uniform SSH.Port_Forwarding;
 wenzelm parents: 
78339diff
changeset | 339 |       val forward = List(forward_host, forward_port, remote_host, remote_port).mkString(":")
 | 
| 76147 | 340 | val forward_option = "-L " + Bash.string(forward) | 
| 341 | ||
| 342 | val cancel: () => Unit = | |
| 343 |         if (control_path.nonEmpty) {
 | |
| 344 | run_ssh(opts = forward_option + " -O forward").check | |
| 345 | () => run_ssh(opts = forward_option + " -O cancel") // permissive | |
| 346 | } | |
| 76148 
769ebb139a32
support port forwarding without multiplexing (for the sake of Windows);
 wenzelm parents: 
76147diff
changeset | 347 |         else {
 | 
| 
769ebb139a32
support port forwarding without multiplexing (for the sake of Windows);
 wenzelm parents: 
76147diff
changeset | 348 | val result = Synchronized[Exn.Result[Boolean]](Exn.Res(false)) | 
| 78345 | 349 |           val thread = Isabelle_Thread.fork("ssh_server") {
 | 
| 76148 
769ebb139a32
support port forwarding without multiplexing (for the sake of Windows);
 wenzelm parents: 
76147diff
changeset | 350 | val opts = | 
| 
769ebb139a32
support port forwarding without multiplexing (for the sake of Windows);
 wenzelm parents: 
76147diff
changeset | 351 | forward_option + | 
| 
769ebb139a32
support port forwarding without multiplexing (for the sake of Windows);
 wenzelm parents: 
76147diff
changeset | 352 |                 " " + Config.option("SessionType", "none") +
 | 
| 
769ebb139a32
support port forwarding without multiplexing (for the sake of Windows);
 wenzelm parents: 
76147diff
changeset | 353 |                 " " + Config.option("PermitLocalCommand", true) +
 | 
| 
769ebb139a32
support port forwarding without multiplexing (for the sake of Windows);
 wenzelm parents: 
76147diff
changeset | 354 |                 " " + Config.option("LocalCommand", "pwd")
 | 
| 
769ebb139a32
support port forwarding without multiplexing (for the sake of Windows);
 wenzelm parents: 
76147diff
changeset | 355 |             try {
 | 
| 
769ebb139a32
support port forwarding without multiplexing (for the sake of Windows);
 wenzelm parents: 
76147diff
changeset | 356 |               run_command("ssh", opts = opts, args = Bash.string(host),
 | 
| 
769ebb139a32
support port forwarding without multiplexing (for the sake of Windows);
 wenzelm parents: 
76147diff
changeset | 357 | progress_stdout = _ => result.change(_ => Exn.Res(true))).check | 
| 
769ebb139a32
support port forwarding without multiplexing (for the sake of Windows);
 wenzelm parents: 
76147diff
changeset | 358 | } | 
| 
769ebb139a32
support port forwarding without multiplexing (for the sake of Windows);
 wenzelm parents: 
76147diff
changeset | 359 |             catch { case exn: Throwable => result.change(_ => Exn.Exn(exn)) }
 | 
| 
769ebb139a32
support port forwarding without multiplexing (for the sake of Windows);
 wenzelm parents: 
76147diff
changeset | 360 | } | 
| 
769ebb139a32
support port forwarding without multiplexing (for the sake of Windows);
 wenzelm parents: 
76147diff
changeset | 361 |           result.guarded_access {
 | 
| 
769ebb139a32
support port forwarding without multiplexing (for the sake of Windows);
 wenzelm parents: 
76147diff
changeset | 362 | case res@Exn.Res(ok) => if (ok) Some((), res) else None | 
| 
769ebb139a32
support port forwarding without multiplexing (for the sake of Windows);
 wenzelm parents: 
76147diff
changeset | 363 | case Exn.Exn(exn) => throw exn | 
| 
769ebb139a32
support port forwarding without multiplexing (for the sake of Windows);
 wenzelm parents: 
76147diff
changeset | 364 | } | 
| 
769ebb139a32
support port forwarding without multiplexing (for the sake of Windows);
 wenzelm parents: 
76147diff
changeset | 365 | () => thread.interrupt() | 
| 
769ebb139a32
support port forwarding without multiplexing (for the sake of Windows);
 wenzelm parents: 
76147diff
changeset | 366 | } | 
| 76147 | 367 | |
| 368 | val shutdown_hook = | |
| 369 |         Isabelle_System.create_shutdown_hook { cancel() }
 | |
| 76122 
b8f26c20d3b1
ssh client via regular OpenSSH tools, with authentic use of .ssh/config (notably proxy configuration);
 wenzelm parents: 
76119diff
changeset | 370 | |
| 78346 | 371 |       new Server(forward_host, forward_port, ssh) {
 | 
| 76147 | 372 | override def toString: String = forward | 
| 76122 
b8f26c20d3b1
ssh client via regular OpenSSH tools, with authentic use of .ssh/config (notably proxy configuration);
 wenzelm parents: 
76119diff
changeset | 373 |         override def close(): Unit = {
 | 
| 76147 | 374 | cancel() | 
| 375 | Isabelle_System.remove_shutdown_hook(shutdown_hook) | |
| 76122 
b8f26c20d3b1
ssh client via regular OpenSSH tools, with authentic use of .ssh/config (notably proxy configuration);
 wenzelm parents: 
76119diff
changeset | 376 | if (ssh_close) ssh.close() | 
| 
b8f26c20d3b1
ssh client via regular OpenSSH tools, with authentic use of .ssh/config (notably proxy configuration);
 wenzelm parents: 
76119diff
changeset | 377 | } | 
| 
b8f26c20d3b1
ssh client via regular OpenSSH tools, with authentic use of .ssh/config (notably proxy configuration);
 wenzelm parents: 
76119diff
changeset | 378 | } | 
| 64137 | 379 | } | 
| 64123 | 380 | } | 
| 66570 | 381 | |
| 78346 | 382 | |
| 383 | /* server port forwarding */ | |
| 78340 
5790e48f7573
clarified signature: more uniform SSH.Port_Forwarding;
 wenzelm parents: 
78339diff
changeset | 384 | |
| 78346 | 385 | def open_server( | 
| 386 | options: Options, | |
| 387 | host: String, | |
| 388 | port: Int = 0, | |
| 389 | user: String = "", | |
| 390 | remote_port: Int = 0, | |
| 391 | remote_host: String = "localhost", | |
| 392 | local_port: Int = 0, | |
| 393 | local_host: String = "localhost" | |
| 394 |   ): Server = {
 | |
| 395 | val ssh = open_session(options, host, port = port, user = user) | |
| 396 |     try {
 | |
| 397 | ssh.open_server(remote_port = remote_port, remote_host = remote_host, | |
| 398 | local_port = local_port, local_host = local_host, ssh_close = true) | |
| 399 | } | |
| 400 |     catch { case exn: Throwable => ssh.close(); throw exn }
 | |
| 78341 | 401 | } | 
| 402 | ||
| 78345 | 403 | def local_server(port: Int = 0, host: String = "localhost"): Server = | 
| 404 | new Local_Server(host, port) | |
| 78341 | 405 | |
| 78345 | 406 | val no_server: Server = new No_Server | 
| 76116 
c4dc343fdbcb
clarified signature: avoid exposure of JSch types;
 wenzelm parents: 
76115diff
changeset | 407 | |
| 78346 | 408 | class Server private[SSH]( | 
| 409 | val host: String, | |
| 410 | val port: Int, | |
| 78347 | 411 | val ssh_system: System | 
| 78346 | 412 |   ) extends AutoCloseable {
 | 
| 413 | def defined: Boolean = host.nonEmpty && port > 0 | |
| 414 | override def close(): Unit = () | |
| 415 | } | |
| 416 | ||
| 417 | class Local_Server private[SSH](host: String, port: Int) | |
| 418 |   extends Server(host, port, Local) {
 | |
| 419 | override def toString: String = if_proper(host, host + ":") + port | |
| 420 | } | |
| 421 | ||
| 422 |   class No_Server extends Server("", 0, Local) {
 | |
| 423 | override def toString: String = "0" | |
| 424 | } | |
| 425 | ||
| 66570 | 426 | |
| 427 | /* system operations */ | |
| 428 | ||
| 77783 
fb61887c069a
clarified underlying SSH session of "isabelle hg_sync" and "isabelle sync";
 wenzelm parents: 
77782diff
changeset | 429 | def open_system( | 
| 
fb61887c069a
clarified underlying SSH session of "isabelle hg_sync" and "isabelle sync";
 wenzelm parents: 
77782diff
changeset | 430 | options: Options, | 
| 
fb61887c069a
clarified underlying SSH session of "isabelle hg_sync" and "isabelle sync";
 wenzelm parents: 
77782diff
changeset | 431 | host: String, | 
| 
fb61887c069a
clarified underlying SSH session of "isabelle hg_sync" and "isabelle sync";
 wenzelm parents: 
77782diff
changeset | 432 | port: Int = 0, | 
| 
fb61887c069a
clarified underlying SSH session of "isabelle hg_sync" and "isabelle sync";
 wenzelm parents: 
77782diff
changeset | 433 | user: String = "" | 
| 
fb61887c069a
clarified underlying SSH session of "isabelle hg_sync" and "isabelle sync";
 wenzelm parents: 
77782diff
changeset | 434 |   ): System = {
 | 
| 78924 | 435 | if (is_local(host)) Local | 
| 77783 
fb61887c069a
clarified underlying SSH session of "isabelle hg_sync" and "isabelle sync";
 wenzelm parents: 
77782diff
changeset | 436 | else open_session(options, host = host, port = port, user = user) | 
| 
fb61887c069a
clarified underlying SSH session of "isabelle hg_sync" and "isabelle sync";
 wenzelm parents: 
77782diff
changeset | 437 | } | 
| 
fb61887c069a
clarified underlying SSH session of "isabelle hg_sync" and "isabelle sync";
 wenzelm parents: 
77782diff
changeset | 438 | |
| 75393 | 439 |   trait System extends AutoCloseable {
 | 
| 78346 | 440 | def ssh_session: Option[Session] | 
| 441 | def is_local: Boolean = ssh_session.isEmpty | |
| 77782 
127d077cccfe
clarified signature: avoid object-oriented "dispatch";
 wenzelm parents: 
77761diff
changeset | 442 | |
| 73634 | 443 | def close(): Unit = () | 
| 444 | ||
| 78425 | 445 | override def toString: String = LOCAL | 
| 77761 | 446 | def print: String = "" | 
| 66570 | 447 | def hg_url: String = "" | 
| 77783 
fb61887c069a
clarified underlying SSH session of "isabelle hg_sync" and "isabelle sync";
 wenzelm parents: 
77782diff
changeset | 448 | def client_command: String = "" | 
| 66570 | 449 | |
| 75500 | 450 | def rsync_prefix: String = "" | 
| 75517 | 451 | def rsync_path(path: Path): String = rsync_prefix + expand_path(path).implode | 
| 75500 | 452 | |
| 66570 | 453 | def expand_path(path: Path): Path = path.expand | 
| 77076 | 454 | def absolute_path(path: Path): Path = path.absolute | 
| 67066 | 455 | def bash_path(path: Path): String = File.bash_path(path) | 
| 69300 
8b6ab9989bcd
is_file/is_dir/read_dir: more uniform treatment of errors and boundary cases, notably for symlinks in ssh;
 wenzelm parents: 
67771diff
changeset | 456 | def is_dir(path: Path): Boolean = path.is_dir | 
| 66570 | 457 | def is_file(path: Path): Boolean = path.is_file | 
| 77092 | 458 | def delete(path: Path): Unit = path.file.delete | 
| 78161 | 459 | def restrict(path: Path): Unit = File.restrict(path) | 
| 78298 
3b0f8f1010f2
clarified signature, with subtle change of semantics (amending 8b5a2e4b16d4);
 wenzelm parents: 
78161diff
changeset | 460 | def set_executable(path: Path, reset: Boolean = false): Unit = | 
| 
3b0f8f1010f2
clarified signature, with subtle change of semantics (amending 8b5a2e4b16d4);
 wenzelm parents: 
78161diff
changeset | 461 | File.set_executable(path, reset = reset) | 
| 72376 | 462 | def make_directory(path: Path): Path = Isabelle_System.make_directory(path) | 
| 77092 | 463 | def rm_tree(dir: Path): Unit = Isabelle_System.rm_tree(dir) | 
| 73634 | 464 |     def with_tmp_dir[A](body: Path => A): A = Isabelle_System.with_tmp_dir("tmp")(body)
 | 
| 77096 | 465 | def read_dir(path: Path): List[String] = File.read_dir(path) | 
| 77059 | 466 | def copy_file(path1: Path, path2: Path): Unit = Isabelle_System.copy_file(path1, path2) | 
| 73634 | 467 | def read_file(path1: Path, path2: Path): Unit = Isabelle_System.copy_file(path1, path2) | 
| 75513 | 468 | def read_bytes(path: Path): Bytes = Bytes.read(path) | 
| 469 | def read(path: Path): String = File.read(path) | |
| 77092 | 470 | def write_file(path1: Path, path2: Path): Unit = Isabelle_System.copy_file(path2, path1) | 
| 471 | def write_bytes(path: Path, bytes: Bytes): Unit = Bytes.write(path, bytes) | |
| 472 | def write(path: Path, text: String): Unit = File.write(path, text) | |
| 66570 | 473 | |
| 474 | def execute(command: String, | |
| 475 | progress_stdout: String => Unit = (_: String) => (), | |
| 476 | progress_stderr: String => Unit = (_: String) => (), | |
| 77092 | 477 | redirect: Boolean = false, | 
| 73634 | 478 | settings: Boolean = true, | 
| 66570 | 479 | strict: Boolean = true): Process_Result = | 
| 73634 | 480 | Isabelle_System.bash(command, | 
| 481 | progress_stdout = progress_stdout, | |
| 482 | progress_stderr = progress_stderr, | |
| 77092 | 483 | redirect = redirect, | 
| 73634 | 484 | env = if (settings) Isabelle_System.settings() else null, | 
| 485 | strict = strict) | |
| 72338 | 486 | |
| 77760 | 487 | def new_directory(path: Path): Path = | 
| 488 |       if (is_dir(path)) error("Directory already exists: " + absolute_path(path))
 | |
| 489 | else make_directory(path) | |
| 490 | ||
| 77054 | 491 | def download_file(url_name: String, file: Path, progress: Progress = new Progress): Unit = | 
| 492 | Isabelle_System.download_file(url_name, file, progress = progress) | |
| 493 | ||
| 72340 | 494 | def isabelle_platform: Isabelle_Platform = Isabelle_Platform() | 
| 77130 | 495 | |
| 78610 | 496 | def isabelle_platform_family: Platform.Family = | 
| 77130 | 497 | Platform.Family.parse(isabelle_platform.ISABELLE_PLATFORM_FAMILY) | 
| 66570 | 498 | } | 
| 499 | ||
| 77782 
127d077cccfe
clarified signature: avoid object-oriented "dispatch";
 wenzelm parents: 
77761diff
changeset | 500 |   object Local extends System {
 | 
| 78346 | 501 | override def ssh_session: Option[Session] = None | 
| 77782 
127d077cccfe
clarified signature: avoid object-oriented "dispatch";
 wenzelm parents: 
77761diff
changeset | 502 | } | 
| 64123 | 503 | } |