Bash.process always uses a closed script instead of an open argument list, for extra robustness on Windows, where quoting is not well-defined;
authorwenzelm
Mon, 07 Mar 2016 18:20:22 +0100
changeset 62545 8ebffdaf2ce2
parent 62544 efa178abe023
child 62546 973b12bccbc5
Bash.process always uses a closed script instead of an open argument list, for extra robustness on Windows, where quoting is not well-defined; more robust File.bash_escape; more robust treatment of ML_OPTIONS; clarified prover args (again);
src/Pure/Concurrent/bash.scala
src/Pure/General/file.scala
src/Pure/PIDE/batch_session.scala
src/Pure/PIDE/resources.scala
src/Pure/PIDE/session.scala
src/Pure/System/isabelle_process.scala
src/Pure/System/isabelle_system.scala
src/Pure/System/ml_process.scala
src/Pure/Tools/check_sources.scala
src/Tools/jEdit/src/isabelle_logic.scala
--- a/src/Pure/Concurrent/bash.scala	Mon Mar 07 15:21:50 2016 +0100
+++ b/src/Pure/Concurrent/bash.scala	Mon Mar 07 18:20:22 2016 +0100
@@ -26,20 +26,21 @@
     }
   }
 
-  def process(cwd: JFile, env: Map[String, String], redirect: Boolean, args: String*): Process =
-    new Process(cwd, env, redirect, args:_*)
+  def process(script: String,
+      cwd: JFile = null, env: Map[String, String] = null, redirect: Boolean = false): Process =
+    new Process(script, cwd, env, redirect)
 
   class Process private [Bash](
-      cwd: JFile, env: Map[String, String], redirect: Boolean, args: String*)
+      script: String, cwd: JFile, env: Map[String, String], redirect: Boolean)
     extends Prover.System_Process
   {
+    val script_file = Isabelle_System.tmp_file("bash_script")
+    File.write(script_file, script)
+
     private val proc =
-    {
-      val params =
-        List(File.platform_path(Path.variable("ISABELLE_BASH_PROCESS")), "-", "bash")
-      Isabelle_System.process(
-        cwd, Isabelle_System.settings(env), redirect, (params ::: args.toList):_*)
-    }
+      Isabelle_System.process(cwd, Isabelle_System.settings(env), redirect,
+        File.platform_path(Path.variable("ISABELLE_BASH_PROCESS")), "-",
+          "bash", File.standard_path(script_file))
 
 
     // channels
@@ -90,14 +91,20 @@
     try { Runtime.getRuntime.addShutdownHook(shutdown_hook) }
     catch { case _: IllegalStateException => }
 
-    private def cleanup() =
+
+    /* join */
+
+    def join: Int =
+    {
+      val rc = proc.waitFor
+
       try { Runtime.getRuntime.removeShutdownHook(shutdown_hook) }
       catch { case _: IllegalStateException => }
 
+      script_file.delete
 
-    /* join */
-
-    def join: Int = { val rc = proc.waitFor; cleanup(); rc }
+      rc
+    }
 
 
     /* result */
--- a/src/Pure/General/file.scala	Mon Mar 07 15:21:50 2016 +0100
+++ b/src/Pure/General/file.scala	Mon Mar 07 18:20:22 2016 +0100
@@ -101,11 +101,34 @@
   }
 
 
-  /* shell path (bash) */
+  /* bash path */
 
-  def shell_quote(s: String): String = "'" + s + "'"
-  def shell_path(path: Path): String = shell_quote(standard_path(path))
-  def shell_path(file: JFile): String = shell_quote(standard_path(file))
+  private def bash_escape(c: Byte): String =
+  {
+    val ch = c.toChar
+    ch match {
+      case '\t' => "$'\\t'"
+      case '\n' => "$'\\n'"
+      case '\f' => "$'\\f'"
+      case '\r' => "$'\\r'"
+      case _ =>
+        if (Symbol.is_ascii_letter(ch) || Symbol.is_ascii_digit(ch) || "-./:_".contains(ch))
+          Symbol.ascii(ch)
+        else if (c < 0) "$'\\x" + Integer.toHexString(256 + c) + "'"
+        else if (c < 16) "$'\\x0" + Integer.toHexString(c) + "'"
+        else if (c < 32) "$'\\x" + Integer.toHexString(c) + "'"
+        else  "\\" + ch
+    }
+  }
+
+  def bash_escape(s: String): String =
+    UTF8.bytes(s).iterator.map(bash_escape(_)).mkString
+
+  def bash_escape(args: List[String]): String =
+    args.iterator.map(bash_escape(_)).mkString(" ")
+
+  def bash_path(path: Path): String = bash_escape(standard_path(path))
+  def bash_path(file: JFile): String = bash_escape(standard_path(file))
 
 
   /* directory entries */
--- a/src/Pure/PIDE/batch_session.scala	Mon Mar 07 15:21:50 2016 +0100
+++ b/src/Pure/PIDE/batch_session.scala	Mon Mar 07 18:20:22 2016 +0100
@@ -58,7 +58,7 @@
         case _ =>
       }
 
-    prover_session.start("Isabelle", "-q " + quote(parent_session))
+    prover_session.start("Isabelle", List("-q", parent_session))
 
     batch_session
   }
--- a/src/Pure/PIDE/resources.scala	Mon Mar 07 15:21:50 2016 +0100
+++ b/src/Pure/PIDE/resources.scala	Mon Mar 07 18:20:22 2016 +0100
@@ -136,7 +136,7 @@
 
   /* prover process */
 
-  def start_prover(receiver: Prover.Message => Unit, name: String, args: String): Prover =
+  def start_prover(receiver: Prover.Message => Unit, name: String, args: List[String]): Prover =
     Isabelle_Process(receiver, args)
 }
 
--- a/src/Pure/PIDE/session.scala	Mon Mar 07 15:21:50 2016 +0100
+++ b/src/Pure/PIDE/session.scala	Mon Mar 07 18:20:22 2016 +0100
@@ -212,7 +212,7 @@
 
   /* internal messages */
 
-  private case class Start(name: String, args: String)
+  private case class Start(name: String, args: List[String])
   private case object Stop
   private case class Cancel_Exec(exec_id: Document_ID.Exec)
   private case class Protocol_Command(name: String, args: List[String])
@@ -601,7 +601,7 @@
       pending_edits: List[Text.Edit] = Nil): Document.Snapshot =
     global_state.value.snapshot(name, pending_edits)
 
-  def start(name: String, args: String)
+  def start(name: String, args: List[String])
   { manager.send(Start(name, args)) }
 
   def stop()
--- a/src/Pure/System/isabelle_process.scala	Mon Mar 07 15:21:50 2016 +0100
+++ b/src/Pure/System/isabelle_process.scala	Mon Mar 07 18:20:22 2016 +0100
@@ -11,16 +11,14 @@
 {
   def apply(
     receiver: Prover.Message => Unit = Console.println(_),
-    prover_args: String = ""): Isabelle_Process =
+    prover_args: List[String] = Nil): Isabelle_Process =
   {
     val system_channel = System_Channel()
     val system_process =
       try {
-        val script =
-          File.shell_quote(Isabelle_System.getenv_strict("ISABELLE_PROCESS")) +
-            " -P " + system_channel.server_name +
-            (if (prover_args == "") "" else " " + prover_args)
-        val process = Bash.process(null, null, false, "-c", script)
+        val process =
+          Bash.process("\"$ISABELLE_PROCESS\" -P " + File.bash_escape(system_channel.server_name) +
+            " " + File.bash_escape(prover_args))
         process.stdin.close
         process
       }
--- a/src/Pure/System/isabelle_system.scala	Mon Mar 07 15:21:50 2016 +0100
+++ b/src/Pure/System/isabelle_system.scala	Mon Mar 07 18:20:22 2016 +0100
@@ -167,7 +167,7 @@
 
   def mkdirs(path: Path): Unit =
     if (!path.is_dir) {
-      bash("perl -e \"use File::Path make_path; make_path(" + File.shell_path(path) + ");\"")
+      bash("perl -e \"use File::Path make_path; make_path('" + File.standard_path(path) + "');\"")
       if (!path.is_dir) error("Failed to create directory: " + quote(File.platform_path(path)))
     }
 
@@ -308,11 +308,7 @@
     progress_limit: Option[Long] = None,
     strict: Boolean = true): Process_Result =
   {
-    with_tmp_file("isabelle_script") { script_file =>
-      File.write(script_file, script)
-      Bash.process(cwd, env, false, File.standard_path(script_file)).
-        result(progress_stdout, progress_stderr, progress_limit, strict)
-    }
+    Bash.process(script, cwd, env).result(progress_stdout, progress_stderr, progress_limit, strict)
   }
 
 
@@ -342,7 +338,7 @@
     bash("exec \"$PDF_VIEWER\" '" + File.standard_path(arg) + "' >/dev/null 2>/dev/null &")
 
   def hg(cmd_line: String, cwd: Path = Path.current): Process_Result =
-    bash("cd " + File.shell_path(cwd) + " && \"${HG:-hg}\" " + cmd_line)
+    bash("cd " + File.bash_path(cwd) + " && \"${HG:-hg}\" " + cmd_line)
 
 
 
--- a/src/Pure/System/ml_process.scala	Mon Mar 07 15:21:50 2016 +0100
+++ b/src/Pure/System/ml_process.scala	Mon Mar 07 18:20:22 2016 +0100
@@ -66,13 +66,18 @@
     // options
     val isabelle_process_options = Isabelle_System.tmp_file("options")
     File.write(isabelle_process_options, YXML.string_of_body(options.encode))
-    val bash_env = Map("ISABELLE_PROCESS_OPTIONS" -> File.standard_path(isabelle_process_options))
+    val env = Map("ISABELLE_PROCESS_OPTIONS" -> File.standard_path(isabelle_process_options))
     val eval_options = List("Options.load_default ()")
 
     val eval_process =
       if (process_socket == "") Nil
       else List("Isabelle_Process.init " + ML_Syntax.print_string_raw(process_socket))
 
+    // bash
+    val bash_args =
+      Word.explode(Isabelle_System.getenv("ML_OPTIONS")) :::
+      (eval_heaps ::: eval_exit ::: eval_secure ::: eval_modes ::: eval_options ::: eval_process).
+        map(eval => List("--eval", eval)).flatten ::: args
     val bash_script =
       """
         [ -z "$ISABELLE_TMP_PREFIX" ] && ISABELLE_TMP_PREFIX=/tmp/isabelle
@@ -83,7 +88,7 @@
         chmod $(umask -S) "$ISABELLE_TMP"
 
         librarypath "$ML_HOME"
-        "$ML_HOME/poly" -q -i $ML_OPTIONS "$@"
+        "$ML_HOME/poly" -q -i """ + File.bash_escape(bash_args) + """
         RC="$?"
 
         rm -f "$ISABELLE_PROCESS_OPTIONS"
@@ -92,11 +97,6 @@
         exit "$RC"
       """
 
-    val bash_args =
-      List("-c", bash_script, "ml_process") :::
-      (eval_heaps ::: eval_exit ::: eval_secure ::: eval_modes ::: eval_options ::: eval_process).
-        map(eval => List("--eval", eval)).flatten ::: args
-
-    Bash.process(null, bash_env, false, bash_args:_*)
+    Bash.process(bash_script, env = env)
   }
 }
--- a/src/Pure/Tools/check_sources.scala	Mon Mar 07 15:21:50 2016 +0100
+++ b/src/Pure/Tools/check_sources.scala	Mon Mar 07 18:20:22 2016 +0100
@@ -41,7 +41,7 @@
   def check_hg(root: Path)
   {
     Output.writeln("Checking " + root + " ...")
-    Isabelle_System.hg("--repository " + File.shell_path(root) + " root").check
+    Isabelle_System.hg("--repository " + File.bash_path(root) + " root").check
     for {
       file <- Isabelle_System.hg("manifest", root).check.out_lines
       if file.endsWith(".thy") || file.endsWith(".ML") || file.endsWith("/ROOT")
--- a/src/Tools/jEdit/src/isabelle_logic.scala	Mon Mar 07 15:21:50 2016 +0100
+++ b/src/Tools/jEdit/src/isabelle_logic.scala	Mon Mar 07 18:20:22 2016 +0100
@@ -69,12 +69,11 @@
       dirs = session_dirs(), sessions = List(session_name()))
   }
 
-  def session_args(): String =
+  def session_args(): List[String] =
   {
-    val print_modes =
-      (space_explode(',', PIDE.options.string("jedit_print_mode")) :::
-       space_explode(',', Isabelle_System.getenv("JEDIT_PRINT_MODE"))).map("-m " + _)
-    (print_modes ::: List("-q", File.shell_quote(session_name()))).mkString(" ")
+    (space_explode(',', PIDE.options.string("jedit_print_mode")) :::
+     space_explode(',', Isabelle_System.getenv("JEDIT_PRINT_MODE"))).
+      map(m => List("-m", m)).flatten ::: List("-q", session_name())
   }
 
   def session_start(): Unit = PIDE.session.start("Isabelle", session_args())