author | wenzelm |
Tue, 06 May 2025 16:52:39 +0200 | |
changeset 82610 | 3133f9748ea8 |
parent 82304 | 4fbdef3e2a55 |
permissions | -rw-r--r-- |
64123 | 1 |
/* Title: Pure/General/ssh.scala |
2 |
Author: Makarius |
|
3 |
||
80209 | 4 |
SSH client on top of OpenSSH command-line tools, preferably with connection |
76122
b8f26c20d3b1
ssh client via regular OpenSSH tools, with authentic use of .ssh/config (notably proxy configuration);
wenzelm
parents:
76119
diff
changeset
|
5 |
multiplexing, but this does not work on Windows. |
64123 | 6 |
*/ |
7 |
||
8 |
package isabelle |
|
9 |
||
10 |
||
80189 | 11 |
import scala.annotation.tailrec |
12 |
||
64123 | 13 |
|
75393 | 14 |
object SSH { |
76170 | 15 |
/* client command */ |
16 |
||
17 |
def client_command(port: Int = 0, control_path: String = ""): String = |
|
18 |
if (control_path.isEmpty || control_path == Bash.string(control_path)) { |
|
19 |
"ssh" + |
|
20 |
(if (port > 0) " -p " + port else "") + |
|
77369 | 21 |
if_proper(control_path, " -o ControlPath=" + control_path) |
76170 | 22 |
} |
23 |
else error ("Malformed SSH control socket: " + quote(control_path)) |
|
24 |
||
25 |
||
76122
b8f26c20d3b1
ssh client via regular OpenSSH tools, with authentic use of .ssh/config (notably proxy configuration);
wenzelm
parents:
76119
diff
changeset
|
26 |
/* OpenSSH configuration and command-line */ |
b8f26c20d3b1
ssh client via regular OpenSSH tools, with authentic use of .ssh/config (notably proxy configuration);
wenzelm
parents:
76119
diff
changeset
|
27 |
|
b8f26c20d3b1
ssh client via regular OpenSSH tools, with authentic use of .ssh/config (notably proxy configuration);
wenzelm
parents:
76119
diff
changeset
|
28 |
// 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:
76119
diff
changeset
|
29 |
object Config { |
b8f26c20d3b1
ssh client via regular OpenSSH tools, with authentic use of .ssh/config (notably proxy configuration);
wenzelm
parents:
76119
diff
changeset
|
30 |
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:
76119
diff
changeset
|
31 |
def entry(x: String, y: Int): String = entry(x, y.toString) |
76165 | 32 |
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:
76119
diff
changeset
|
33 |
def entry(x: String, y: Boolean): String = entry(x, if (y) "yes" else "no") |
64141 | 34 |
|
76122
b8f26c20d3b1
ssh client via regular OpenSSH tools, with authentic use of .ssh/config (notably proxy configuration);
wenzelm
parents:
76119
diff
changeset
|
35 |
def make(options: Options, |
76131
8b695e59db3f
clarified default: do not override port from ssh_config, which could be different from 22;
wenzelm
parents:
76130
diff
changeset
|
36 |
port: Int = 0, |
76122
b8f26c20d3b1
ssh client via regular OpenSSH tools, with authentic use of .ssh/config (notably proxy configuration);
wenzelm
parents:
76119
diff
changeset
|
37 |
user: String = "", |
b8f26c20d3b1
ssh client via regular OpenSSH tools, with authentic use of .ssh/config (notably proxy configuration);
wenzelm
parents:
76119
diff
changeset
|
38 |
control_master: Boolean = false, |
b8f26c20d3b1
ssh client via regular OpenSSH tools, with authentic use of .ssh/config (notably proxy configuration);
wenzelm
parents:
76119
diff
changeset
|
39 |
control_path: String = "" |
b8f26c20d3b1
ssh client via regular OpenSSH tools, with authentic use of .ssh/config (notably proxy configuration);
wenzelm
parents:
76119
diff
changeset
|
40 |
): List[String] = { |
76168 | 41 |
val ssh_batch_mode = options.bool("ssh_batch_mode") |
76167 | 42 |
val ssh_compression = options.bool("ssh_compression") |
43 |
val ssh_alive_interval = options.real("ssh_alive_interval").round |
|
44 |
val ssh_alive_count_max = options.int("ssh_alive_count_max") |
|
45 |
||
76168 | 46 |
List( |
47 |
entry("BatchMode", ssh_batch_mode), |
|
48 |
entry("Compression", ssh_compression)) ::: |
|
76167 | 49 |
(if (ssh_alive_interval >= 0) List(entry("ServerAliveInterval", ssh_alive_interval)) else Nil) ::: |
50 |
(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:
76130
diff
changeset
|
51 |
(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:
76119
diff
changeset
|
52 |
(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:
76119
diff
changeset
|
53 |
(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:
76119
diff
changeset
|
54 |
(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:
76119
diff
changeset
|
55 |
} |
64325 | 56 |
|
76147 | 57 |
def option(entry: String): String = "-o " + Bash.string(entry) |
58 |
def option(x: String, y: String): String = option(entry(x, y)) |
|
59 |
def option(x: String, y: Int): String = option(entry(x, y)) |
|
76165 | 60 |
def option(x: String, y: Long): String = option(entry(x, y)) |
76147 | 61 |
def option(x: String, y: Boolean): String = option(entry(x, y)) |
62 |
||
63 |
def command(command: String, config: List[String]): String = |
|
64 |
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:
76119
diff
changeset
|
65 |
} |
b8f26c20d3b1
ssh client via regular OpenSSH tools, with authentic use of .ssh/config (notably proxy configuration);
wenzelm
parents:
76119
diff
changeset
|
66 |
|
b8f26c20d3b1
ssh client via regular OpenSSH tools, with authentic use of .ssh/config (notably proxy configuration);
wenzelm
parents:
76119
diff
changeset
|
67 |
def sftp_string(str: String): String = { |
76150 | 68 |
val special = "[]?*\\{} \"'" |
69 |
if (str.isEmpty) "\"\"" |
|
70 |
else if (str.exists(special.contains)) { |
|
80441 | 71 |
Library.string_builder() { res => |
72 |
for (c <- str) { |
|
73 |
if (special.contains(c)) res += '\\' |
|
74 |
res += c |
|
75 |
} |
|
76122
b8f26c20d3b1
ssh client via regular OpenSSH tools, with authentic use of .ssh/config (notably proxy configuration);
wenzelm
parents:
76119
diff
changeset
|
76 |
} |
b8f26c20d3b1
ssh client via regular OpenSSH tools, with authentic use of .ssh/config (notably proxy configuration);
wenzelm
parents:
76119
diff
changeset
|
77 |
} |
b8f26c20d3b1
ssh client via regular OpenSSH tools, with authentic use of .ssh/config (notably proxy configuration);
wenzelm
parents:
76119
diff
changeset
|
78 |
else str |
b8f26c20d3b1
ssh client via regular OpenSSH tools, with authentic use of .ssh/config (notably proxy configuration);
wenzelm
parents:
76119
diff
changeset
|
79 |
} |
67273
c573cfb2c407
more robust connection: prefer ServerAliveCountMax=3 (ssh default) instead of 1 (jsch default);
wenzelm
parents:
67067
diff
changeset
|
80 |
|
64123 | 81 |
|
78425 | 82 |
/* local host (not "localhost") */ |
83 |
||
84 |
val LOCAL = "local" |
|
85 |
||
86 |
def is_local(host: String): Boolean = host.isEmpty || host == LOCAL |
|
87 |
||
88 |
def print_local(host: String): String = if (is_local(host)) LOCAL else host |
|
89 |
||
90 |
||
76115
f17393e21388
clarified signature: discontinue somewhat pointless SSH.Context;
wenzelm
parents:
76100
diff
changeset
|
91 |
/* open session */ |
f17393e21388
clarified signature: discontinue somewhat pointless SSH.Context;
wenzelm
parents:
76100
diff
changeset
|
92 |
|
f17393e21388
clarified signature: discontinue somewhat pointless SSH.Context;
wenzelm
parents:
76100
diff
changeset
|
93 |
def open_session( |
f17393e21388
clarified signature: discontinue somewhat pointless SSH.Context;
wenzelm
parents:
76100
diff
changeset
|
94 |
options: Options, |
f17393e21388
clarified signature: discontinue somewhat pointless SSH.Context;
wenzelm
parents:
76100
diff
changeset
|
95 |
host: String, |
76122
b8f26c20d3b1
ssh client via regular OpenSSH tools, with authentic use of .ssh/config (notably proxy configuration);
wenzelm
parents:
76119
diff
changeset
|
96 |
port: Int = 0, |
79633 | 97 |
user: String = "", |
98 |
user_home: String = "" |
|
76115
f17393e21388
clarified signature: discontinue somewhat pointless SSH.Context;
wenzelm
parents:
76100
diff
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:
76147
diff
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:
76119
diff
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:
76119
diff
changeset
|
105 |
else (false, "") |
79633 | 106 |
new Session(options, host, port, user, user_home, 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:
76119
diff
changeset
|
109 |
class Session private[SSH]( |
b8f26c20d3b1
ssh client via regular OpenSSH tools, with authentic use of .ssh/config (notably proxy configuration);
wenzelm
parents:
76119
diff
changeset
|
110 |
val options: Options, |
b8f26c20d3b1
ssh client via regular OpenSSH tools, with authentic use of .ssh/config (notably proxy configuration);
wenzelm
parents:
76119
diff
changeset
|
111 |
val host: String, |
b8f26c20d3b1
ssh client via regular OpenSSH tools, with authentic use of .ssh/config (notably proxy configuration);
wenzelm
parents:
76119
diff
changeset
|
112 |
val port: Int, |
b8f26c20d3b1
ssh client via regular OpenSSH tools, with authentic use of .ssh/config (notably proxy configuration);
wenzelm
parents:
76119
diff
changeset
|
113 |
val user: String, |
79633 | 114 |
user_home0: String, |
76122
b8f26c20d3b1
ssh client via regular OpenSSH tools, with authentic use of .ssh/config (notably proxy configuration);
wenzelm
parents:
76119
diff
changeset
|
115 |
control_master: Boolean, |
76136
1bb677cceea4
let rsync re-use ssh connection via control path;
wenzelm
parents:
76133
diff
changeset
|
116 |
val control_path: String |
76122
b8f26c20d3b1
ssh client via regular OpenSSH tools, with authentic use of .ssh/config (notably proxy configuration);
wenzelm
parents:
76119
diff
changeset
|
117 |
) extends System { |
b8f26c20d3b1
ssh client via regular OpenSSH tools, with authentic use of .ssh/config (notably proxy configuration);
wenzelm
parents:
76119
diff
changeset
|
118 |
ssh => |
64128 | 119 |
|
78346 | 120 |
override def ssh_session: Option[Session] = Some(ssh) |
77782
127d077cccfe
clarified signature: avoid object-oriented "dispatch";
wenzelm
parents:
77761
diff
changeset
|
121 |
|
76133 | 122 |
def port_suffix: String = if (port > 0) ":" + port else "" |
80210 | 123 |
def user_prefix: String = if_proper(user, user + "@") |
76133 | 124 |
|
125 |
override def toString: String = user_prefix + host + port_suffix |
|
77761 | 126 |
override def print: String = " (ssh " + toString + ")" |
76132 | 127 |
override def hg_url: String = "ssh://" + toString + "/" |
77783
fb61887c069a
clarified underlying SSH session of "isabelle hg_sync" and "isabelle sync";
wenzelm
parents:
77782
diff
changeset
|
128 |
override def client_command: String = |
fb61887c069a
clarified underlying SSH session of "isabelle hg_sync" and "isabelle sync";
wenzelm
parents:
77782
diff
changeset
|
129 |
SSH.client_command(port = port, control_path = control_path) |
76133 | 130 |
override def rsync_prefix: String = user_prefix + host + ":" |
64191 | 131 |
|
132 |
||
76147 | 133 |
/* local ssh commands */ |
64256
c3197aeae90b
simplified SSH.Session: sftp channel is always open and its operations provided by the main interface;
wenzelm
parents:
64254
diff
changeset
|
134 |
|
80214 | 135 |
def make_command( |
136 |
command: String = "ssh", |
|
76122
b8f26c20d3b1
ssh client via regular OpenSSH tools, with authentic use of .ssh/config (notably proxy configuration);
wenzelm
parents:
76119
diff
changeset
|
137 |
master: Boolean = false, |
b8f26c20d3b1
ssh client via regular OpenSSH tools, with authentic use of .ssh/config (notably proxy configuration);
wenzelm
parents:
76119
diff
changeset
|
138 |
opts: String = "", |
80214 | 139 |
args_host: Boolean = false, |
140 |
args: String = "" |
|
141 |
): String = { |
|
76122
b8f26c20d3b1
ssh client via regular OpenSSH tools, with authentic use of .ssh/config (notably proxy configuration);
wenzelm
parents:
76119
diff
changeset
|
142 |
val config = |
b8f26c20d3b1
ssh client via regular OpenSSH tools, with authentic use of .ssh/config (notably proxy configuration);
wenzelm
parents:
76119
diff
changeset
|
143 |
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:
76119
diff
changeset
|
144 |
control_master = master, control_path = control_path) |
80214 | 145 |
val args1 = if_proper(args_host, Bash.string(host) + if_proper(args, " ")) + args |
146 |
Config.command(command, config) + |
|
77369 | 147 |
if_proper(opts, " " + opts) + |
80214 | 148 |
if_proper(args1, " -- " + args1) |
76122
b8f26c20d3b1
ssh client via regular OpenSSH tools, with authentic use of .ssh/config (notably proxy configuration);
wenzelm
parents:
76119
diff
changeset
|
149 |
} |
64134
57581e4026fe
proper support for exec channel (see also bash.scala);
wenzelm
parents:
64133
diff
changeset
|
150 |
|
76164
5e8bc80df6b3
clarified run_sftp: avoid platform_path via careful use of tmp_dir, to support both Windows and Cygwin ssh;
wenzelm
parents:
76163
diff
changeset
|
151 |
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:
76163
diff
changeset
|
152 |
script: String, |
5e8bc80df6b3
clarified run_sftp: avoid platform_path via careful use of tmp_dir, to support both Windows and Cygwin ssh;
wenzelm
parents:
76163
diff
changeset
|
153 |
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:
76163
diff
changeset
|
154 |
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:
76163
diff
changeset
|
155 |
): Process_Result = { |
80212 | 156 |
Isabelle_System.with_tmp_dir("sftp") { dir => |
76164
5e8bc80df6b3
clarified run_sftp: avoid platform_path via careful use of tmp_dir, to support both Windows and Cygwin ssh;
wenzelm
parents:
76163
diff
changeset
|
157 |
init(dir) |
5e8bc80df6b3
clarified run_sftp: avoid platform_path via careful use of tmp_dir, to support both Windows and Cygwin ssh;
wenzelm
parents:
76163
diff
changeset
|
158 |
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:
76163
diff
changeset
|
159 |
val result = |
80214 | 160 |
Isabelle_System.bash( |
80224
db92e0b6a11a
clarified signature: prefer symbolic isabelle.Path over physical java.io.File;
wenzelm
parents:
80221
diff
changeset
|
161 |
make_command("sftp", opts = "-b script", args_host = true), cwd = dir).check |
76164
5e8bc80df6b3
clarified run_sftp: avoid platform_path via careful use of tmp_dir, to support both Windows and Cygwin ssh;
wenzelm
parents:
76163
diff
changeset
|
162 |
exit(dir) |
5e8bc80df6b3
clarified run_sftp: avoid platform_path via careful use of tmp_dir, to support both Windows and Cygwin ssh;
wenzelm
parents:
76163
diff
changeset
|
163 |
result |
76116
c4dc343fdbcb
clarified signature: avoid exposure of JSch types;
wenzelm
parents:
76115
diff
changeset
|
164 |
} |
c4dc343fdbcb
clarified signature: avoid exposure of JSch types;
wenzelm
parents:
76115
diff
changeset
|
165 |
} |
65009 | 166 |
|
80214 | 167 |
def run_ssh(master: Boolean = false, opts: String = "", args: String = ""): Process_Result = |
168 |
Isabelle_System.bash(make_command(master = master, opts = opts, args_host = true, args = args)) |
|
64256
c3197aeae90b
simplified SSH.Session: sftp channel is always open and its operations provided by the main interface;
wenzelm
parents:
64254
diff
changeset
|
169 |
|
76122
b8f26c20d3b1
ssh client via regular OpenSSH tools, with authentic use of .ssh/config (notably proxy configuration);
wenzelm
parents:
76119
diff
changeset
|
170 |
|
b8f26c20d3b1
ssh client via regular OpenSSH tools, with authentic use of .ssh/config (notably proxy configuration);
wenzelm
parents:
76119
diff
changeset
|
171 |
/* init and exit */ |
b8f26c20d3b1
ssh client via regular OpenSSH tools, with authentic use of .ssh/config (notably proxy configuration);
wenzelm
parents:
76119
diff
changeset
|
172 |
|
79633 | 173 |
override val home: String = { |
76154
dfddb80fc515
more robust: do not assume Bash syntax while testing for it;
wenzelm
parents:
76151
diff
changeset
|
174 |
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:
76151
diff
changeset
|
175 |
match { |
79633 | 176 |
case List(home, shell) => |
82304
4fbdef3e2a55
SSH connections allow zsh as well: this happens to work with the existing Bash.char / Bach.string operations;
wenzelm
parents:
82142
diff
changeset
|
177 |
if (shell.endsWith("/bash") || shell.endsWith("/zsh")) home |
76149
ccc748255342
more robust: Bash.string operations require remote bash;
wenzelm
parents:
76148
diff
changeset
|
178 |
else { |
82304
4fbdef3e2a55
SSH connections allow zsh as well: this happens to work with the existing Bash.char / Bach.string operations;
wenzelm
parents:
82142
diff
changeset
|
179 |
error("Bad SHELL for " + quote(toString) + " -- expected bash or zsh, but found " + shell) |
76149
ccc748255342
more robust: Bash.string operations require remote bash;
wenzelm
parents:
76148
diff
changeset
|
180 |
} |
ccc748255342
more robust: Bash.string operations require remote bash;
wenzelm
parents:
76148
diff
changeset
|
181 |
case _ => error("Malformed remote environment for " + quote(toString)) |
ccc748255342
more robust: Bash.string operations require remote bash;
wenzelm
parents:
76148
diff
changeset
|
182 |
} |
76151 | 183 |
} |
76122
b8f26c20d3b1
ssh client via regular OpenSSH tools, with authentic use of .ssh/config (notably proxy configuration);
wenzelm
parents:
76119
diff
changeset
|
184 |
|
79633 | 185 |
override val user_home: String = { |
186 |
val path1 = |
|
187 |
try { Path.explode(home).expand_env(Isabelle_System.No_Env) } |
|
188 |
catch { case ERROR(msg) => error(msg + " -- in SSH HOME") } |
|
189 |
val path2 = |
|
190 |
try { Path.explode(user_home0).expand_env(Isabelle_System.No_Env) } |
|
191 |
catch { case ERROR(msg) => error(msg + "-- in SSH USER_HOME") } |
|
192 |
(path1 + path2).implode |
|
193 |
} |
|
194 |
||
195 |
val settings: Isabelle_System.Settings = { |
|
196 |
case "HOME" => home |
|
197 |
case "USER_HOME" => user_home |
|
198 |
case _ => "" |
|
199 |
} |
|
64256
c3197aeae90b
simplified SSH.Session: sftp channel is always open and its operations provided by the main interface;
wenzelm
parents:
64254
diff
changeset
|
200 |
|
76122
b8f26c20d3b1
ssh client via regular OpenSSH tools, with authentic use of .ssh/config (notably proxy configuration);
wenzelm
parents:
76119
diff
changeset
|
201 |
override def close(): Unit = { |
b8f26c20d3b1
ssh client via regular OpenSSH tools, with authentic use of .ssh/config (notably proxy configuration);
wenzelm
parents:
76119
diff
changeset
|
202 |
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:
76119
diff
changeset
|
203 |
} |
b8f26c20d3b1
ssh client via regular OpenSSH tools, with authentic use of .ssh/config (notably proxy configuration);
wenzelm
parents:
76119
diff
changeset
|
204 |
|
b8f26c20d3b1
ssh client via regular OpenSSH tools, with authentic use of .ssh/config (notably proxy configuration);
wenzelm
parents:
76119
diff
changeset
|
205 |
|
b8f26c20d3b1
ssh client via regular OpenSSH tools, with authentic use of .ssh/config (notably proxy configuration);
wenzelm
parents:
76119
diff
changeset
|
206 |
/* remote commands */ |
64256
c3197aeae90b
simplified SSH.Session: sftp channel is always open and its operations provided by the main interface;
wenzelm
parents:
64254
diff
changeset
|
207 |
|
80218 | 208 |
override def kill_process(group_pid: String, signal: String): Boolean = { |
209 |
val script = |
|
210 |
make_command(args_host = true, |
|
211 |
args = "kill -" + Bash.string(signal) + " -" + Bash.string(group_pid)) |
|
212 |
Isabelle_System.bash(script).ok |
|
213 |
} |
|
214 |
||
80226
17a10bea79a1
more operations for SSH.System: bash_process and bash;
wenzelm
parents:
80225
diff
changeset
|
215 |
override def bash_process(remote_script: String, |
17a10bea79a1
more operations for SSH.System: bash_process and bash;
wenzelm
parents:
80225
diff
changeset
|
216 |
description: String = "", |
17a10bea79a1
more operations for SSH.System: bash_process and bash;
wenzelm
parents:
80225
diff
changeset
|
217 |
cwd: Path = Path.current, |
17a10bea79a1
more operations for SSH.System: bash_process and bash;
wenzelm
parents:
80225
diff
changeset
|
218 |
redirect: Boolean = false, |
80229 | 219 |
settings: Boolean = true, // ignored for remote ssh |
80226
17a10bea79a1
more operations for SSH.System: bash_process and bash;
wenzelm
parents:
80225
diff
changeset
|
220 |
cleanup: () => Unit = () => () |
17a10bea79a1
more operations for SSH.System: bash_process and bash;
wenzelm
parents:
80225
diff
changeset
|
221 |
): Bash.Process = { |
80227
af6b60c75d7d
clarified context for (remote) bash scripts: export variables are optional, support cwd;
wenzelm
parents:
80226
diff
changeset
|
222 |
Bash.process( |
af6b60c75d7d
clarified context for (remote) bash scripts: export variables are optional, support cwd;
wenzelm
parents:
80226
diff
changeset
|
223 |
Bash.context(remote_script, user_home = user_home), |
af6b60c75d7d
clarified context for (remote) bash scripts: export variables are optional, support cwd;
wenzelm
parents:
80226
diff
changeset
|
224 |
description = description, cwd = cwd, redirect = redirect, cleanup = cleanup, ssh = ssh) |
80226
17a10bea79a1
more operations for SSH.System: bash_process and bash;
wenzelm
parents:
80225
diff
changeset
|
225 |
} |
17a10bea79a1
more operations for SSH.System: bash_process and bash;
wenzelm
parents:
80225
diff
changeset
|
226 |
|
80214 | 227 |
override def execute(remote_script: String, |
76122
b8f26c20d3b1
ssh client via regular OpenSSH tools, with authentic use of .ssh/config (notably proxy configuration);
wenzelm
parents:
76119
diff
changeset
|
228 |
progress_stdout: String => Unit = (_: String) => (), |
b8f26c20d3b1
ssh client via regular OpenSSH tools, with authentic use of .ssh/config (notably proxy configuration);
wenzelm
parents:
76119
diff
changeset
|
229 |
progress_stderr: String => Unit = (_: String) => (), |
77092 | 230 |
redirect: Boolean = false, |
80229 | 231 |
settings: Boolean = true, // ignored for remote ssh |
76122
b8f26c20d3b1
ssh client via regular OpenSSH tools, with authentic use of .ssh/config (notably proxy configuration);
wenzelm
parents:
76119
diff
changeset
|
232 |
strict: Boolean = true |
b8f26c20d3b1
ssh client via regular OpenSSH tools, with authentic use of .ssh/config (notably proxy configuration);
wenzelm
parents:
76119
diff
changeset
|
233 |
): Process_Result = { |
80227
af6b60c75d7d
clarified context for (remote) bash scripts: export variables are optional, support cwd;
wenzelm
parents:
80226
diff
changeset
|
234 |
val script = |
af6b60c75d7d
clarified context for (remote) bash scripts: export variables are optional, support cwd;
wenzelm
parents:
80226
diff
changeset
|
235 |
make_command( |
af6b60c75d7d
clarified context for (remote) bash scripts: export variables are optional, support cwd;
wenzelm
parents:
80226
diff
changeset
|
236 |
args_host = true, |
af6b60c75d7d
clarified context for (remote) bash scripts: export variables are optional, support cwd;
wenzelm
parents:
80226
diff
changeset
|
237 |
args = Bash.string(Bash.context(remote_script, user_home = user_home))) |
af6b60c75d7d
clarified context for (remote) bash scripts: export variables are optional, support cwd;
wenzelm
parents:
80226
diff
changeset
|
238 |
Isabelle_System.bash(script, |
77077
c2e8ba15a10a
discontinued adhoc change of environment (from c62b99e3ec07), which has been mostly superseded by expand_path / remote_path (from ef6f7e8a018c);
wenzelm
parents:
77076
diff
changeset
|
239 |
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:
77076
diff
changeset
|
240 |
progress_stderr = progress_stderr, |
77092 | 241 |
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:
77076
diff
changeset
|
242 |
strict = strict) |
64256
c3197aeae90b
simplified SSH.Session: sftp channel is always open and its operations provided by the main interface;
wenzelm
parents:
64254
diff
changeset
|
243 |
} |
76122
b8f26c20d3b1
ssh client via regular OpenSSH tools, with authentic use of .ssh/config (notably proxy configuration);
wenzelm
parents:
76119
diff
changeset
|
244 |
|
77054 | 245 |
override def download_file( |
246 |
url_name: String, |
|
247 |
file: Path, |
|
248 |
progress: Progress = new Progress |
|
249 |
): Unit = { |
|
250 |
val cmd_line = |
|
251 |
File.read(Path.explode("~~/lib/scripts/download_file")) + "\n" + |
|
252 |
"download_file " + Bash.string(url_name) + " " + bash_path(file) |
|
253 |
execute(cmd_line, |
|
77509
3bc49507bae5
clarified treatment of "verbose" messages, e.g. Progress.theory();
wenzelm
parents:
77369
diff
changeset
|
254 |
progress_stdout = progress.echo(_), |
3bc49507bae5
clarified treatment of "verbose" messages, e.g. Progress.theory();
wenzelm
parents:
77369
diff
changeset
|
255 |
progress_stderr = progress.echo(_)).check |
77054 | 256 |
} |
257 |
||
82610 | 258 |
override lazy val isabelle_platform: Isabelle_Platform = |
259 |
Isabelle_Platform.remote(ssh) |
|
76122
b8f26c20d3b1
ssh client via regular OpenSSH tools, with authentic use of .ssh/config (notably proxy configuration);
wenzelm
parents:
76119
diff
changeset
|
260 |
|
b8f26c20d3b1
ssh client via regular OpenSSH tools, with authentic use of .ssh/config (notably proxy configuration);
wenzelm
parents:
76119
diff
changeset
|
261 |
|
b8f26c20d3b1
ssh client via regular OpenSSH tools, with authentic use of .ssh/config (notably proxy configuration);
wenzelm
parents:
76119
diff
changeset
|
262 |
/* remote file-system */ |
b8f26c20d3b1
ssh client via regular OpenSSH tools, with authentic use of .ssh/config (notably proxy configuration);
wenzelm
parents:
76119
diff
changeset
|
263 |
|
66570 | 264 |
override def expand_path(path: Path): Path = path.expand_env(settings) |
77076 | 265 |
override def absolute_path(path: Path): Path = { |
266 |
val path1 = expand_path(path) |
|
79633 | 267 |
if (path1.is_absolute) path1 else Path.explode(home) + path1 |
77076 | 268 |
} |
269 |
||
64256
c3197aeae90b
simplified SSH.Session: sftp channel is always open and its operations provided by the main interface;
wenzelm
parents:
64254
diff
changeset
|
270 |
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:
76119
diff
changeset
|
271 |
|
67066 | 272 |
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:
76119
diff
changeset
|
273 |
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:
64254
diff
changeset
|
274 |
|
76122
b8f26c20d3b1
ssh client via regular OpenSSH tools, with authentic use of .ssh/config (notably proxy configuration);
wenzelm
parents:
76119
diff
changeset
|
275 |
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:
76119
diff
changeset
|
276 |
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:
67771
diff
changeset
|
277 |
|
80187 | 278 |
override def eq_file(path1: Path, path2: Path): Boolean = |
279 |
path1 == path2 || execute("test " + bash_path(path1) + " -ef " + bash_path(path2)).ok |
|
280 |
||
80221
0d89f0a39854
minor performance tuning: save approx. 70ms per SSH command;
wenzelm
parents:
80220
diff
changeset
|
281 |
override def delete(paths: Path*): Unit = |
0d89f0a39854
minor performance tuning: save approx. 70ms per SSH command;
wenzelm
parents:
80220
diff
changeset
|
282 |
if (paths.nonEmpty) { |
0d89f0a39854
minor performance tuning: save approx. 70ms per SSH command;
wenzelm
parents:
80220
diff
changeset
|
283 |
val script = |
0d89f0a39854
minor performance tuning: save approx. 70ms per SSH command;
wenzelm
parents:
80220
diff
changeset
|
284 |
"set -e\n" + |
0d89f0a39854
minor performance tuning: save approx. 70ms per SSH command;
wenzelm
parents:
80220
diff
changeset
|
285 |
"for X in " + paths.iterator.map(bash_path).mkString(" ") + "\n" + |
0d89f0a39854
minor performance tuning: save approx. 70ms per SSH command;
wenzelm
parents:
80220
diff
changeset
|
286 |
"""do if test -d "$X"; then rmdir "$X"; else rm -f "$X"; fi; done""" |
0d89f0a39854
minor performance tuning: save approx. 70ms per SSH command;
wenzelm
parents:
80220
diff
changeset
|
287 |
execute(script).check |
0d89f0a39854
minor performance tuning: save approx. 70ms per SSH command;
wenzelm
parents:
80220
diff
changeset
|
288 |
} |
77092 | 289 |
|
78161 | 290 |
override def restrict(path: Path): Unit = |
291 |
if (!execute("chmod g-rwx,o-rwx " + bash_path(path)).ok) { |
|
292 |
error("Failed to change permissions of " + quote(remote_path(path))) |
|
293 |
} |
|
294 |
||
78298
3b0f8f1010f2
clarified signature, with subtle change of semantics (amending 8b5a2e4b16d4);
wenzelm
parents:
78161
diff
changeset
|
295 |
override def set_executable(path: Path, reset: Boolean = false): Unit = |
3b0f8f1010f2
clarified signature, with subtle change of semantics (amending 8b5a2e4b16d4);
wenzelm
parents:
78161
diff
changeset
|
296 |
if (!execute("chmod a" + (if (reset) "-" else "+") + "x " + bash_path(path)).ok) { |
77092 | 297 |
error("Failed to change executable status of " + quote(remote_path(path))) |
298 |
} |
|
299 |
||
75393 | 300 |
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:
76119
diff
changeset
|
301 |
if (!execute("mkdir -p " + bash_path(path)).ok) { |
76118 | 302 |
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:
64254
diff
changeset
|
303 |
} |
72376 | 304 |
path |
305 |
} |
|
64256
c3197aeae90b
simplified SSH.Session: sftp channel is always open and its operations provided by the main interface;
wenzelm
parents:
64254
diff
changeset
|
306 |
|
77059 | 307 |
override def copy_file(src: Path, dst: Path): Unit = { |
80187 | 308 |
val target = if (is_dir(dst)) dst + expand_path(src).base else dst |
309 |
if (!eq_file(src, target)) { |
|
310 |
if (!execute("cp -a " + bash_path(src) + " " + bash_path(target)).ok) { |
|
311 |
error("Failed to copy file " + |
|
312 |
absolute_path(src) + " to " + absolute_path(target) + " (ssh " + toString + ")") |
|
313 |
} |
|
77059 | 314 |
} |
315 |
} |
|
316 |
||
77096 | 317 |
override def read_dir(path: Path): List[String] = |
76241 | 318 |
run_sftp("@cd " + sftp_path(path) + "\n@ls -1 -a").out_lines.flatMap(s => |
319 |
if (s == "." || s == "..") None |
|
76122
b8f26c20d3b1
ssh client via regular OpenSSH tools, with authentic use of .ssh/config (notably proxy configuration);
wenzelm
parents:
76119
diff
changeset
|
320 |
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:
64254
diff
changeset
|
321 |
|
76164
5e8bc80df6b3
clarified run_sftp: avoid platform_path via careful use of tmp_dir, to support both Windows and Cygwin ssh;
wenzelm
parents:
76163
diff
changeset
|
322 |
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:
76163
diff
changeset
|
323 |
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:
76163
diff
changeset
|
324 |
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:
76163
diff
changeset
|
325 |
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:
76163
diff
changeset
|
326 |
result.get |
5e8bc80df6b3
clarified run_sftp: avoid platform_path via careful use of tmp_dir, to support both Windows and Cygwin ssh;
wenzelm
parents:
76163
diff
changeset
|
327 |
} |
5e8bc80df6b3
clarified run_sftp: avoid platform_path via careful use of tmp_dir, to support both Windows and Cygwin ssh;
wenzelm
parents:
76163
diff
changeset
|
328 |
|
5e8bc80df6b3
clarified run_sftp: avoid platform_path via careful use of tmp_dir, to support both Windows and Cygwin ssh;
wenzelm
parents:
76163
diff
changeset
|
329 |
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:
76163
diff
changeset
|
330 |
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:
76163
diff
changeset
|
331 |
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:
76163
diff
changeset
|
332 |
|
73634 | 333 |
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:
76163
diff
changeset
|
334 |
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:
76119
diff
changeset
|
335 |
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:
76163
diff
changeset
|
336 |
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:
76163
diff
changeset
|
337 |
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:
76163
diff
changeset
|
338 |
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:
64254
diff
changeset
|
339 |
|
73634 | 340 |
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:
76163
diff
changeset
|
341 |
put_file(path, Isabelle_System.copy_file(local_path, _)) |
77092 | 342 |
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:
76163
diff
changeset
|
343 |
put_file(path, Bytes.write(_, bytes)) |
77092 | 344 |
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:
76163
diff
changeset
|
345 |
put_file(path, File.write(_, text)) |
72338 | 346 |
|
64137 | 347 |
|
80217 | 348 |
/* tmp dirs and files */ |
64137 | 349 |
|
80215 | 350 |
override def rm_tree(dir: Path): Unit = |
351 |
execute("rm -r -f " + bash_path(dir)).check |
|
64306
7b6dc1b36f20
tuned signature, in accordance to Isabelle_System;
wenzelm
parents:
64304
diff
changeset
|
352 |
|
80215 | 353 |
override def tmp_dir(): Path = |
354 |
Path.explode(execute("mktemp -d /tmp/ssh-XXXXXXXXXXXX").check.out) |
|
64137 | 355 |
|
80217 | 356 |
override def tmp_file(name: String, ext: String = ""): Path = { |
357 |
val file_name = name + "-XXXXXXXXXXXX" + if_proper(ext, "." + ext) |
|
358 |
Path.explode(execute("mktemp /tmp/" + Bash.string(file_name)).check.out) |
|
359 |
} |
|
360 |
||
80220
928e02d0cab7
minor performance tuning: save approx. 70ms per SSH command;
wenzelm
parents:
80218
diff
changeset
|
361 |
override def tmp_files(names: List[String]): List[Path] = { |
928e02d0cab7
minor performance tuning: save approx. 70ms per SSH command;
wenzelm
parents:
80218
diff
changeset
|
362 |
val script = names.map(name => "mktemp /tmp/" + Bash.string(name) + "-XXXXXXXXXXXX") |
928e02d0cab7
minor performance tuning: save approx. 70ms per SSH command;
wenzelm
parents:
80218
diff
changeset
|
363 |
Library.trim_split_lines(execute(script.mkString(" && ")).check.out).map(Path.explode) |
928e02d0cab7
minor performance tuning: save approx. 70ms per SSH command;
wenzelm
parents:
80218
diff
changeset
|
364 |
} |
928e02d0cab7
minor performance tuning: save approx. 70ms per SSH command;
wenzelm
parents:
80218
diff
changeset
|
365 |
|
75393 | 366 |
override def with_tmp_dir[A](body: Path => A): A = { |
80216 | 367 |
val path = tmp_dir() |
368 |
try { body(path) } finally { rm_tree(path) } |
|
76122
b8f26c20d3b1
ssh client via regular OpenSSH tools, with authentic use of .ssh/config (notably proxy configuration);
wenzelm
parents:
76119
diff
changeset
|
369 |
} |
b8f26c20d3b1
ssh client via regular OpenSSH tools, with authentic use of .ssh/config (notably proxy configuration);
wenzelm
parents:
76119
diff
changeset
|
370 |
|
80217 | 371 |
override def with_tmp_file[A](name: String, ext: String = "")(body: Path => A): A = { |
372 |
val path = tmp_file(name, ext = ext) |
|
373 |
try { body(path) } finally { delete(path) } |
|
374 |
} |
|
375 |
||
76122
b8f26c20d3b1
ssh client via regular OpenSSH tools, with authentic use of .ssh/config (notably proxy configuration);
wenzelm
parents:
76119
diff
changeset
|
376 |
|
78345 | 377 |
/* open server on remote host */ |
76122
b8f26c20d3b1
ssh client via regular OpenSSH tools, with authentic use of .ssh/config (notably proxy configuration);
wenzelm
parents:
76119
diff
changeset
|
378 |
|
78345 | 379 |
def open_server( |
78339 | 380 |
remote_port: Int = 0, |
76122
b8f26c20d3b1
ssh client via regular OpenSSH tools, with authentic use of .ssh/config (notably proxy configuration);
wenzelm
parents:
76119
diff
changeset
|
381 |
remote_host: String = "localhost", |
b8f26c20d3b1
ssh client via regular OpenSSH tools, with authentic use of .ssh/config (notably proxy configuration);
wenzelm
parents:
76119
diff
changeset
|
382 |
local_port: Int = 0, |
b8f26c20d3b1
ssh client via regular OpenSSH tools, with authentic use of .ssh/config (notably proxy configuration);
wenzelm
parents:
76119
diff
changeset
|
383 |
local_host: String = "localhost", |
b8f26c20d3b1
ssh client via regular OpenSSH tools, with authentic use of .ssh/config (notably proxy configuration);
wenzelm
parents:
76119
diff
changeset
|
384 |
ssh_close: Boolean = false |
78345 | 385 |
): Server = { |
78340
5790e48f7573
clarified signature: more uniform SSH.Port_Forwarding;
wenzelm
parents:
78339
diff
changeset
|
386 |
val forward_host = local_host |
5790e48f7573
clarified signature: more uniform SSH.Port_Forwarding;
wenzelm
parents:
78339
diff
changeset
|
387 |
val forward_port = if (local_port > 0) local_port else Isabelle_System.local_port() |
5790e48f7573
clarified signature: more uniform SSH.Port_Forwarding;
wenzelm
parents:
78339
diff
changeset
|
388 |
val forward = List(forward_host, forward_port, remote_host, remote_port).mkString(":") |
76147 | 389 |
val forward_option = "-L " + Bash.string(forward) |
390 |
||
391 |
val cancel: () => Unit = |
|
392 |
if (control_path.nonEmpty) { |
|
393 |
run_ssh(opts = forward_option + " -O forward").check |
|
394 |
() => run_ssh(opts = forward_option + " -O cancel") // permissive |
|
395 |
} |
|
76148
769ebb139a32
support port forwarding without multiplexing (for the sake of Windows);
wenzelm
parents:
76147
diff
changeset
|
396 |
else { |
769ebb139a32
support port forwarding without multiplexing (for the sake of Windows);
wenzelm
parents:
76147
diff
changeset
|
397 |
val result = Synchronized[Exn.Result[Boolean]](Exn.Res(false)) |
78345 | 398 |
val thread = Isabelle_Thread.fork("ssh_server") { |
76148
769ebb139a32
support port forwarding without multiplexing (for the sake of Windows);
wenzelm
parents:
76147
diff
changeset
|
399 |
val opts = |
769ebb139a32
support port forwarding without multiplexing (for the sake of Windows);
wenzelm
parents:
76147
diff
changeset
|
400 |
forward_option + |
769ebb139a32
support port forwarding without multiplexing (for the sake of Windows);
wenzelm
parents:
76147
diff
changeset
|
401 |
" " + Config.option("SessionType", "none") + |
769ebb139a32
support port forwarding without multiplexing (for the sake of Windows);
wenzelm
parents:
76147
diff
changeset
|
402 |
" " + Config.option("PermitLocalCommand", true) + |
769ebb139a32
support port forwarding without multiplexing (for the sake of Windows);
wenzelm
parents:
76147
diff
changeset
|
403 |
" " + Config.option("LocalCommand", "pwd") |
769ebb139a32
support port forwarding without multiplexing (for the sake of Windows);
wenzelm
parents:
76147
diff
changeset
|
404 |
try { |
80214 | 405 |
Isabelle_System.bash(make_command(opts = opts, args_host = true), |
76148
769ebb139a32
support port forwarding without multiplexing (for the sake of Windows);
wenzelm
parents:
76147
diff
changeset
|
406 |
progress_stdout = _ => result.change(_ => Exn.Res(true))).check |
769ebb139a32
support port forwarding without multiplexing (for the sake of Windows);
wenzelm
parents:
76147
diff
changeset
|
407 |
} |
769ebb139a32
support port forwarding without multiplexing (for the sake of Windows);
wenzelm
parents:
76147
diff
changeset
|
408 |
catch { case exn: Throwable => result.change(_ => Exn.Exn(exn)) } |
769ebb139a32
support port forwarding without multiplexing (for the sake of Windows);
wenzelm
parents:
76147
diff
changeset
|
409 |
} |
769ebb139a32
support port forwarding without multiplexing (for the sake of Windows);
wenzelm
parents:
76147
diff
changeset
|
410 |
result.guarded_access { |
769ebb139a32
support port forwarding without multiplexing (for the sake of Windows);
wenzelm
parents:
76147
diff
changeset
|
411 |
case res@Exn.Res(ok) => if (ok) Some((), res) else None |
769ebb139a32
support port forwarding without multiplexing (for the sake of Windows);
wenzelm
parents:
76147
diff
changeset
|
412 |
case Exn.Exn(exn) => throw exn |
769ebb139a32
support port forwarding without multiplexing (for the sake of Windows);
wenzelm
parents:
76147
diff
changeset
|
413 |
} |
769ebb139a32
support port forwarding without multiplexing (for the sake of Windows);
wenzelm
parents:
76147
diff
changeset
|
414 |
() => thread.interrupt() |
769ebb139a32
support port forwarding without multiplexing (for the sake of Windows);
wenzelm
parents:
76147
diff
changeset
|
415 |
} |
76147 | 416 |
|
417 |
val shutdown_hook = |
|
418 |
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:
76119
diff
changeset
|
419 |
|
78346 | 420 |
new Server(forward_host, forward_port, ssh) { |
76147 | 421 |
override def toString: String = forward |
76122
b8f26c20d3b1
ssh client via regular OpenSSH tools, with authentic use of .ssh/config (notably proxy configuration);
wenzelm
parents:
76119
diff
changeset
|
422 |
override def close(): Unit = { |
76147 | 423 |
cancel() |
424 |
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:
76119
diff
changeset
|
425 |
if (ssh_close) ssh.close() |
b8f26c20d3b1
ssh client via regular OpenSSH tools, with authentic use of .ssh/config (notably proxy configuration);
wenzelm
parents:
76119
diff
changeset
|
426 |
} |
b8f26c20d3b1
ssh client via regular OpenSSH tools, with authentic use of .ssh/config (notably proxy configuration);
wenzelm
parents:
76119
diff
changeset
|
427 |
} |
64137 | 428 |
} |
64123 | 429 |
} |
66570 | 430 |
|
78346 | 431 |
|
432 |
/* server port forwarding */ |
|
78340
5790e48f7573
clarified signature: more uniform SSH.Port_Forwarding;
wenzelm
parents:
78339
diff
changeset
|
433 |
|
78346 | 434 |
def open_server( |
435 |
options: Options, |
|
436 |
host: String, |
|
437 |
port: Int = 0, |
|
438 |
user: String = "", |
|
79633 | 439 |
user_home: String = "", |
78346 | 440 |
remote_port: Int = 0, |
441 |
remote_host: String = "localhost", |
|
442 |
local_port: Int = 0, |
|
443 |
local_host: String = "localhost" |
|
444 |
): Server = { |
|
79633 | 445 |
val ssh = open_session(options, host, port = port, user = user, user_home = user_home) |
78346 | 446 |
try { |
447 |
ssh.open_server(remote_port = remote_port, remote_host = remote_host, |
|
448 |
local_port = local_port, local_host = local_host, ssh_close = true) |
|
449 |
} |
|
450 |
catch { case exn: Throwable => ssh.close(); throw exn } |
|
78341 | 451 |
} |
452 |
||
78345 | 453 |
def local_server(port: Int = 0, host: String = "localhost"): Server = |
454 |
new Local_Server(host, port) |
|
78341 | 455 |
|
78345 | 456 |
val no_server: Server = new No_Server |
76116
c4dc343fdbcb
clarified signature: avoid exposure of JSch types;
wenzelm
parents:
76115
diff
changeset
|
457 |
|
78346 | 458 |
class Server private[SSH]( |
459 |
val host: String, |
|
460 |
val port: Int, |
|
78347 | 461 |
val ssh_system: System |
78346 | 462 |
) extends AutoCloseable { |
463 |
def defined: Boolean = host.nonEmpty && port > 0 |
|
464 |
override def close(): Unit = () |
|
465 |
} |
|
466 |
||
467 |
class Local_Server private[SSH](host: String, port: Int) |
|
468 |
extends Server(host, port, Local) { |
|
469 |
override def toString: String = if_proper(host, host + ":") + port |
|
470 |
} |
|
471 |
||
472 |
class No_Server extends Server("", 0, Local) { |
|
473 |
override def toString: String = "0" |
|
474 |
} |
|
475 |
||
66570 | 476 |
|
477 |
/* system operations */ |
|
478 |
||
77783
fb61887c069a
clarified underlying SSH session of "isabelle hg_sync" and "isabelle sync";
wenzelm
parents:
77782
diff
changeset
|
479 |
def open_system( |
fb61887c069a
clarified underlying SSH session of "isabelle hg_sync" and "isabelle sync";
wenzelm
parents:
77782
diff
changeset
|
480 |
options: Options, |
fb61887c069a
clarified underlying SSH session of "isabelle hg_sync" and "isabelle sync";
wenzelm
parents:
77782
diff
changeset
|
481 |
host: String, |
fb61887c069a
clarified underlying SSH session of "isabelle hg_sync" and "isabelle sync";
wenzelm
parents:
77782
diff
changeset
|
482 |
port: Int = 0, |
79633 | 483 |
user: String = "", |
484 |
user_home: String = "" |
|
77783
fb61887c069a
clarified underlying SSH session of "isabelle hg_sync" and "isabelle sync";
wenzelm
parents:
77782
diff
changeset
|
485 |
): System = { |
79635 | 486 |
if (is_local(host)) { |
487 |
if (user_home.isEmpty) Local |
|
488 |
else error("Illegal user home for local host") |
|
489 |
} |
|
79633 | 490 |
else open_session(options, host = host, port = port, user = user, user_home = user_home) |
77783
fb61887c069a
clarified underlying SSH session of "isabelle hg_sync" and "isabelle sync";
wenzelm
parents:
77782
diff
changeset
|
491 |
} |
fb61887c069a
clarified underlying SSH session of "isabelle hg_sync" and "isabelle sync";
wenzelm
parents:
77782
diff
changeset
|
492 |
|
75393 | 493 |
trait System extends AutoCloseable { |
78346 | 494 |
def ssh_session: Option[Session] |
495 |
def is_local: Boolean = ssh_session.isEmpty |
|
77782
127d077cccfe
clarified signature: avoid object-oriented "dispatch";
wenzelm
parents:
77761
diff
changeset
|
496 |
|
79633 | 497 |
def home: String = "" |
498 |
def user_home: String = "" |
|
499 |
||
73634 | 500 |
def close(): Unit = () |
501 |
||
78425 | 502 |
override def toString: String = LOCAL |
77761 | 503 |
def print: String = "" |
66570 | 504 |
def hg_url: String = "" |
77783
fb61887c069a
clarified underlying SSH session of "isabelle hg_sync" and "isabelle sync";
wenzelm
parents:
77782
diff
changeset
|
505 |
def client_command: String = "" |
66570 | 506 |
|
75500 | 507 |
def rsync_prefix: String = "" |
75517 | 508 |
def rsync_path(path: Path): String = rsync_prefix + expand_path(path).implode |
75500 | 509 |
|
80189 | 510 |
def find_path[A](start: Path, detect: Path => Option[A]): Option[A] = { |
511 |
@tailrec def find(root: Path): Option[A] = |
|
512 |
detect(root) match { |
|
513 |
case None => if (root.is_root) None else find(root + Path.parent) |
|
514 |
case some => some |
|
515 |
} |
|
516 |
||
517 |
find(expand_path(start)) |
|
518 |
} |
|
519 |
||
66570 | 520 |
def expand_path(path: Path): Path = path.expand |
77076 | 521 |
def absolute_path(path: Path): Path = path.absolute |
67066 | 522 |
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:
67771
diff
changeset
|
523 |
def is_dir(path: Path): Boolean = path.is_dir |
66570 | 524 |
def is_file(path: Path): Boolean = path.is_file |
80187 | 525 |
def eq_file(path1: Path, path2: Path): Boolean = File.eq(path1, path2) |
80221
0d89f0a39854
minor performance tuning: save approx. 70ms per SSH command;
wenzelm
parents:
80220
diff
changeset
|
526 |
def delete(paths: Path*): Unit = paths.foreach(path => path.file.delete) |
78161 | 527 |
def restrict(path: Path): Unit = File.restrict(path) |
78298
3b0f8f1010f2
clarified signature, with subtle change of semantics (amending 8b5a2e4b16d4);
wenzelm
parents:
78161
diff
changeset
|
528 |
def set_executable(path: Path, reset: Boolean = false): Unit = |
3b0f8f1010f2
clarified signature, with subtle change of semantics (amending 8b5a2e4b16d4);
wenzelm
parents:
78161
diff
changeset
|
529 |
File.set_executable(path, reset = reset) |
72376 | 530 |
def make_directory(path: Path): Path = Isabelle_System.make_directory(path) |
77092 | 531 |
def rm_tree(dir: Path): Unit = Isabelle_System.rm_tree(dir) |
80215 | 532 |
def tmp_dir(): Path = File.path(Isabelle_System.tmp_dir("tmp")) |
73634 | 533 |
def with_tmp_dir[A](body: Path => A): A = Isabelle_System.with_tmp_dir("tmp")(body) |
80217 | 534 |
def tmp_file(name: String, ext: String = ""): Path = |
535 |
File.path(Isabelle_System.tmp_file(name, ext = ext)) |
|
536 |
def with_tmp_file[A](name: String, ext: String = "")(body: Path => A): A = |
|
537 |
Isabelle_System.with_tmp_file(name, ext = ext)(body) |
|
80220
928e02d0cab7
minor performance tuning: save approx. 70ms per SSH command;
wenzelm
parents:
80218
diff
changeset
|
538 |
def tmp_files(names: List[String]): List[Path] = names.map(tmp_file(_)) |
77096 | 539 |
def read_dir(path: Path): List[String] = File.read_dir(path) |
77059 | 540 |
def copy_file(path1: Path, path2: Path): Unit = Isabelle_System.copy_file(path1, path2) |
73634 | 541 |
def read_file(path1: Path, path2: Path): Unit = Isabelle_System.copy_file(path1, path2) |
75513 | 542 |
def read_bytes(path: Path): Bytes = Bytes.read(path) |
543 |
def read(path: Path): String = File.read(path) |
|
77092 | 544 |
def write_file(path1: Path, path2: Path): Unit = Isabelle_System.copy_file(path2, path1) |
545 |
def write_bytes(path: Path, bytes: Bytes): Unit = Bytes.write(path, bytes) |
|
546 |
def write(path: Path, text: String): Unit = File.write(path, text) |
|
66570 | 547 |
|
80218 | 548 |
def kill_process(group_pid: String, signal: String): Boolean = |
549 |
isabelle.setup.Environment.kill_process(Bash.string(group_pid), Bash.string(signal)) |
|
550 |
||
80226
17a10bea79a1
more operations for SSH.System: bash_process and bash;
wenzelm
parents:
80225
diff
changeset
|
551 |
def bash_process(script: String, |
17a10bea79a1
more operations for SSH.System: bash_process and bash;
wenzelm
parents:
80225
diff
changeset
|
552 |
description: String = "", |
17a10bea79a1
more operations for SSH.System: bash_process and bash;
wenzelm
parents:
80225
diff
changeset
|
553 |
cwd: Path = Path.current, |
17a10bea79a1
more operations for SSH.System: bash_process and bash;
wenzelm
parents:
80225
diff
changeset
|
554 |
redirect: Boolean = false, |
17a10bea79a1
more operations for SSH.System: bash_process and bash;
wenzelm
parents:
80225
diff
changeset
|
555 |
settings: Boolean = true, |
17a10bea79a1
more operations for SSH.System: bash_process and bash;
wenzelm
parents:
80225
diff
changeset
|
556 |
cleanup: () => Unit = () => () |
17a10bea79a1
more operations for SSH.System: bash_process and bash;
wenzelm
parents:
80225
diff
changeset
|
557 |
): Bash.Process = { |
17a10bea79a1
more operations for SSH.System: bash_process and bash;
wenzelm
parents:
80225
diff
changeset
|
558 |
Bash.process(script, description = description, cwd = cwd, redirect = redirect, |
17a10bea79a1
more operations for SSH.System: bash_process and bash;
wenzelm
parents:
80225
diff
changeset
|
559 |
env = if (settings) Isabelle_System.settings() else null, |
17a10bea79a1
more operations for SSH.System: bash_process and bash;
wenzelm
parents:
80225
diff
changeset
|
560 |
cleanup = cleanup) |
17a10bea79a1
more operations for SSH.System: bash_process and bash;
wenzelm
parents:
80225
diff
changeset
|
561 |
} |
17a10bea79a1
more operations for SSH.System: bash_process and bash;
wenzelm
parents:
80225
diff
changeset
|
562 |
|
17a10bea79a1
more operations for SSH.System: bash_process and bash;
wenzelm
parents:
80225
diff
changeset
|
563 |
def bash(script: String, |
17a10bea79a1
more operations for SSH.System: bash_process and bash;
wenzelm
parents:
80225
diff
changeset
|
564 |
description: String = "", |
17a10bea79a1
more operations for SSH.System: bash_process and bash;
wenzelm
parents:
80225
diff
changeset
|
565 |
cwd: Path = Path.current, |
17a10bea79a1
more operations for SSH.System: bash_process and bash;
wenzelm
parents:
80225
diff
changeset
|
566 |
input: String = "", |
17a10bea79a1
more operations for SSH.System: bash_process and bash;
wenzelm
parents:
80225
diff
changeset
|
567 |
progress_stdout: String => Unit = (_: String) => (), |
17a10bea79a1
more operations for SSH.System: bash_process and bash;
wenzelm
parents:
80225
diff
changeset
|
568 |
progress_stderr: String => Unit = (_: String) => (), |
80235 | 569 |
watchdog: Bash.Watchdog = Bash.Watchdog.none, |
80226
17a10bea79a1
more operations for SSH.System: bash_process and bash;
wenzelm
parents:
80225
diff
changeset
|
570 |
redirect: Boolean = false, |
17a10bea79a1
more operations for SSH.System: bash_process and bash;
wenzelm
parents:
80225
diff
changeset
|
571 |
settings: Boolean = true, |
17a10bea79a1
more operations for SSH.System: bash_process and bash;
wenzelm
parents:
80225
diff
changeset
|
572 |
strict: Boolean = true, |
17a10bea79a1
more operations for SSH.System: bash_process and bash;
wenzelm
parents:
80225
diff
changeset
|
573 |
cleanup: () => Unit = () => () |
17a10bea79a1
more operations for SSH.System: bash_process and bash;
wenzelm
parents:
80225
diff
changeset
|
574 |
): Process_Result = { |
17a10bea79a1
more operations for SSH.System: bash_process and bash;
wenzelm
parents:
80225
diff
changeset
|
575 |
bash_process(script, description = description, cwd = cwd, redirect = redirect, |
17a10bea79a1
more operations for SSH.System: bash_process and bash;
wenzelm
parents:
80225
diff
changeset
|
576 |
settings = settings, cleanup = cleanup). |
17a10bea79a1
more operations for SSH.System: bash_process and bash;
wenzelm
parents:
80225
diff
changeset
|
577 |
result(input = input, progress_stdout = progress_stdout, progress_stderr = progress_stderr, |
17a10bea79a1
more operations for SSH.System: bash_process and bash;
wenzelm
parents:
80225
diff
changeset
|
578 |
watchdog = watchdog, strict = strict) |
17a10bea79a1
more operations for SSH.System: bash_process and bash;
wenzelm
parents:
80225
diff
changeset
|
579 |
} |
17a10bea79a1
more operations for SSH.System: bash_process and bash;
wenzelm
parents:
80225
diff
changeset
|
580 |
|
17a10bea79a1
more operations for SSH.System: bash_process and bash;
wenzelm
parents:
80225
diff
changeset
|
581 |
def execute(script: String, |
66570 | 582 |
progress_stdout: String => Unit = (_: String) => (), |
583 |
progress_stderr: String => Unit = (_: String) => (), |
|
77092 | 584 |
redirect: Boolean = false, |
73634 | 585 |
settings: Boolean = true, |
80226
17a10bea79a1
more operations for SSH.System: bash_process and bash;
wenzelm
parents:
80225
diff
changeset
|
586 |
strict: Boolean = true |
17a10bea79a1
more operations for SSH.System: bash_process and bash;
wenzelm
parents:
80225
diff
changeset
|
587 |
): Process_Result = { |
17a10bea79a1
more operations for SSH.System: bash_process and bash;
wenzelm
parents:
80225
diff
changeset
|
588 |
bash(script, progress_stdout = progress_stdout, progress_stderr = progress_stderr, |
17a10bea79a1
more operations for SSH.System: bash_process and bash;
wenzelm
parents:
80225
diff
changeset
|
589 |
redirect = redirect, settings = settings, strict = strict) |
17a10bea79a1
more operations for SSH.System: bash_process and bash;
wenzelm
parents:
80225
diff
changeset
|
590 |
} |
72338 | 591 |
|
77760 | 592 |
def new_directory(path: Path): Path = |
593 |
if (is_dir(path)) error("Directory already exists: " + absolute_path(path)) |
|
594 |
else make_directory(path) |
|
595 |
||
77054 | 596 |
def download_file(url_name: String, file: Path, progress: Progress = new Progress): Unit = |
597 |
Isabelle_System.download_file(url_name, file, progress = progress) |
|
598 |
||
82610 | 599 |
def isabelle_platform: Isabelle_Platform = Isabelle_Platform.local |
77130 | 600 |
|
78610 | 601 |
def isabelle_platform_family: Platform.Family = |
77130 | 602 |
Platform.Family.parse(isabelle_platform.ISABELLE_PLATFORM_FAMILY) |
66570 | 603 |
} |
604 |
||
77782
127d077cccfe
clarified signature: avoid object-oriented "dispatch";
wenzelm
parents:
77761
diff
changeset
|
605 |
object Local extends System { |
78346 | 606 |
override def ssh_session: Option[Session] = None |
77782
127d077cccfe
clarified signature: avoid object-oriented "dispatch";
wenzelm
parents:
77761
diff
changeset
|
607 |
} |
64123 | 608 |
} |