This is an automated email from the ASF dual-hosted git repository.

bowenliang pushed a commit to branch branch-1.10
in repository https://gitbox.apache.org/repos/asf/kyuubi.git


The following commit(s) were added to refs/heads/branch-1.10 by this push:
     new fdabc36671 [KYUUBI #6772] Fix ProcessBuilder to properly handle Java 
opts as a list
fdabc36671 is described below

commit fdabc36671842708a39b546e7c0c379eab352eef
Author: senmiaoliu <[email protected]>
AuthorDate: Wed Oct 23 13:28:58 2024 +0800

    [KYUUBI #6772] Fix ProcessBuilder to properly handle Java opts as a list
    
    # :mag: Description
    ## Issue References ๐Ÿ”—
    
    ## Describe Your Solution ๐Ÿ”ง
    
    This PR addresses an issue in the ProcessBuilder class where Java options 
passed as a single string (e.g., "-Dxxx -Dxxx") do not take effect. The command 
list must separate these options into individual elements to ensure they are 
recognized correctly by the Java runtime.
    
    ## Types of changes :bookmark:
    
    - [ ] Bugfix (non-breaking change which fixes an issue)
    - [ ] New feature (non-breaking change which adds functionality)
    - [ ] Breaking change (fix or feature that would cause existing 
functionality to change)
    
    ## Test Plan ๐Ÿงช
    
    #### Behavior Without This Pull Request :coffin:
    
    #### Behavior With This Pull Request :tada:
    
    #### Related Unit Tests
    
    ---
    
    # Checklist ๐Ÿ“
    
    - [x] This patch was not authored or co-authored using [Generative 
Tooling](https://www.apache.org/legal/generative-tooling.html)
    
    **Be nice. Be informative.**
    
    Closes #6772 from lsm1/branch-fix-processBuilder.
    
    Closes #6772
    
    fb6d53234 [senmiaoliu] fix process builder java opts
    
    Authored-by: senmiaoliu <[email protected]>
    Signed-off-by: Bowen Liang <[email protected]>
    (cherry picked from commit f876600c4a95af988949734d566d34126843777a)
    Signed-off-by: Bowen Liang <[email protected]>
---
 .../kyuubi/engine/flink/FlinkProcessBuilder.scala  |  3 +-
 .../kyuubi/engine/hive/HiveProcessBuilder.scala    |  2 +-
 .../kyuubi/engine/jdbc/JdbcProcessBuilder.scala    |  5 +-
 .../kyuubi/engine/trino/TrinoProcessBuilder.scala  |  2 +-
 .../kyuubi/util/command/CommandLineUtils.scala     | 86 ++++++++++++++++++++++
 .../kyuubi/util/command/CommandUtilsSuite.scala    |  7 ++
 6 files changed, 99 insertions(+), 6 deletions(-)

diff --git 
a/kyuubi-server/src/main/scala/org/apache/kyuubi/engine/flink/FlinkProcessBuilder.scala
 
b/kyuubi-server/src/main/scala/org/apache/kyuubi/engine/flink/FlinkProcessBuilder.scala
index 9f6e7e68c1..e5ead4e53d 100644
--- 
a/kyuubi-server/src/main/scala/org/apache/kyuubi/engine/flink/FlinkProcessBuilder.scala
+++ 
b/kyuubi-server/src/main/scala/org/apache/kyuubi/engine/flink/FlinkProcessBuilder.scala
@@ -198,9 +198,8 @@ class FlinkProcessBuilder(
         buffer += s"-Xmx$memory"
         val javaOptions = 
conf.get(ENGINE_FLINK_JAVA_OPTIONS).filter(StringUtils.isNotBlank(_))
         if (javaOptions.isDefined) {
-          buffer += javaOptions.get
+          buffer ++= parseOptionString(javaOptions.get)
         }
-
         val classpathEntries = new mutable.LinkedHashSet[String]
         // flink engine runtime jar
         mainResource.foreach(classpathEntries.add)
diff --git 
a/kyuubi-server/src/main/scala/org/apache/kyuubi/engine/hive/HiveProcessBuilder.scala
 
b/kyuubi-server/src/main/scala/org/apache/kyuubi/engine/hive/HiveProcessBuilder.scala
index 8fc14d4ca5..bbdf316445 100644
--- 
a/kyuubi-server/src/main/scala/org/apache/kyuubi/engine/hive/HiveProcessBuilder.scala
+++ 
b/kyuubi-server/src/main/scala/org/apache/kyuubi/engine/hive/HiveProcessBuilder.scala
@@ -65,7 +65,7 @@ class HiveProcessBuilder(
     buffer += s"-Xmx$memory"
     val javaOptions = 
conf.get(ENGINE_HIVE_JAVA_OPTIONS).filter(StringUtils.isNotBlank(_))
     if (javaOptions.isDefined) {
-      buffer += javaOptions.get
+      buffer ++= parseOptionString(javaOptions.get)
     }
     // -Xmx5g
     // java options
diff --git 
a/kyuubi-server/src/main/scala/org/apache/kyuubi/engine/jdbc/JdbcProcessBuilder.scala
 
b/kyuubi-server/src/main/scala/org/apache/kyuubi/engine/jdbc/JdbcProcessBuilder.scala
index 6d7b66aac0..9e76afa190 100644
--- 
a/kyuubi-server/src/main/scala/org/apache/kyuubi/engine/jdbc/JdbcProcessBuilder.scala
+++ 
b/kyuubi-server/src/main/scala/org/apache/kyuubi/engine/jdbc/JdbcProcessBuilder.scala
@@ -73,8 +73,9 @@ class JdbcProcessBuilder(
     buffer += s"-Xmx$memory"
 
     val javaOptions = 
conf.get(ENGINE_JDBC_JAVA_OPTIONS).filter(StringUtils.isNotBlank(_))
-    javaOptions.foreach(buffer += _)
-
+    if (javaOptions.isDefined) {
+      buffer ++= parseOptionString(javaOptions.get)
+    }
     val classpathEntries = new mutable.LinkedHashSet[String]
     mainResource.foreach(classpathEntries.add)
     mainResource.foreach { path =>
diff --git 
a/kyuubi-server/src/main/scala/org/apache/kyuubi/engine/trino/TrinoProcessBuilder.scala
 
b/kyuubi-server/src/main/scala/org/apache/kyuubi/engine/trino/TrinoProcessBuilder.scala
index 6c21215b56..43c8dc7781 100644
--- 
a/kyuubi-server/src/main/scala/org/apache/kyuubi/engine/trino/TrinoProcessBuilder.scala
+++ 
b/kyuubi-server/src/main/scala/org/apache/kyuubi/engine/trino/TrinoProcessBuilder.scala
@@ -65,7 +65,7 @@ class TrinoProcessBuilder(
     buffer += s"-Xmx$memory"
     val javaOptions = 
conf.get(ENGINE_TRINO_JAVA_OPTIONS).filter(StringUtils.isNotBlank(_))
     if (javaOptions.isDefined) {
-      buffer += javaOptions.get
+      buffer ++= parseOptionString(javaOptions.get)
     }
 
     val classpathEntries = new mutable.LinkedHashSet[String]
diff --git 
a/kyuubi-util-scala/src/main/scala/org/apache/kyuubi/util/command/CommandLineUtils.scala
 
b/kyuubi-util-scala/src/main/scala/org/apache/kyuubi/util/command/CommandLineUtils.scala
index 91327223a6..9e805052be 100644
--- 
a/kyuubi-util-scala/src/main/scala/org/apache/kyuubi/util/command/CommandLineUtils.scala
+++ 
b/kyuubi-util-scala/src/main/scala/org/apache/kyuubi/util/command/CommandLineUtils.scala
@@ -19,6 +19,7 @@ package org.apache.kyuubi.util.command
 
 import java.io.File
 
+import scala.collection.mutable.ListBuffer
 import scala.util.matching.Regex
 
 object CommandLineUtils {
@@ -72,4 +73,89 @@ object CommandLineUtils {
         }
     }
   }
+
+  /**
+   * copy from org.apache.spark.launcher.CommandBuilderUtils#parseOptionString
+   * Parse a string as if it were a list of arguments, following bash 
semantics.
+   * For example:
+   *
+   * Input: "\"ab cd\" efgh 'i \" j'"
+   * Output: [ "ab cd", "efgh", "i \" j" ]
+   */
+  def parseOptionString(s: String): List[String] = {
+    val opts = ListBuffer[String]()
+    val opt = new StringBuilder()
+    var inOpt = false
+    var inSingleQuote = false
+    var inDoubleQuote = false
+    var escapeNext = false
+    var hasData = false
+
+    var i = 0
+    while (i < s.length) {
+      val c = s.codePointAt(i)
+      if (escapeNext) {
+        opt.appendAll(Character.toChars(c))
+        escapeNext = false
+      } else if (inOpt) {
+        c match {
+          case '\\' =>
+            if (inSingleQuote) {
+              opt.appendAll(Character.toChars(c))
+            } else {
+              escapeNext = true
+            }
+          case '\'' =>
+            if (inDoubleQuote) {
+              opt.appendAll(Character.toChars(c))
+            } else {
+              inSingleQuote = !inSingleQuote
+            }
+          case '"' =>
+            if (inSingleQuote) {
+              opt.appendAll(Character.toChars(c))
+            } else {
+              inDoubleQuote = !inDoubleQuote
+            }
+          case _ =>
+            if (!Character.isWhitespace(c) || inSingleQuote || inDoubleQuote) {
+              opt.appendAll(Character.toChars(c))
+            } else {
+              opts += opt.toString()
+              opt.setLength(0)
+              inOpt = false
+              hasData = false
+            }
+        }
+      } else {
+        c match {
+          case '\'' =>
+            inSingleQuote = true
+            inOpt = true
+            hasData = true
+          case '"' =>
+            inDoubleQuote = true
+            inOpt = true
+            hasData = true
+          case '\\' =>
+            escapeNext = true
+            inOpt = true
+            hasData = true
+          case _ =>
+            if (!Character.isWhitespace(c)) {
+              inOpt = true
+              hasData = true
+              opt.appendAll(Character.toChars(c))
+            }
+        }
+      }
+      i += Character.charCount(c)
+    }
+
+    require(!inSingleQuote && !inDoubleQuote && !escapeNext, s"Invalid option 
string: $s")
+    if (hasData) {
+      opts += opt.toString()
+    }
+    opts.toList
+  }
 }
diff --git 
a/kyuubi-util-scala/src/test/scala/org/apache/kyuubi/util/command/CommandUtilsSuite.scala
 
b/kyuubi-util-scala/src/test/scala/org/apache/kyuubi/util/command/CommandUtilsSuite.scala
index e000e7478b..104d02b07b 100644
--- 
a/kyuubi-util-scala/src/test/scala/org/apache/kyuubi/util/command/CommandUtilsSuite.scala
+++ 
b/kyuubi-util-scala/src/test/scala/org/apache/kyuubi/util/command/CommandUtilsSuite.scala
@@ -47,4 +47,11 @@ class CommandUtilsSuite extends AnyFunSuite {
     assertResult(Seq("-cp", "/path/a.jar:/path2/b*.jar"))(
       genClasspathOption(Seq("/path/a.jar", "/path2/b*.jar")))
   }
+
+  test("parseOptionString should parse a string as a list of arguments") {
+    val input = "\"ab cd\" efgh 'i \" j'"
+    val expectedOutput = List("ab cd", "efgh", "i \" j")
+    val actualOutput = CommandLineUtils.parseOptionString(input)
+    assert(actualOutput == expectedOutput)
+  }
 }

Reply via email to