aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndrew Or <andrew@databricks.com>2015-06-03 12:10:12 -0700
committerAndrew Or <andrew@databricks.com>2015-06-03 14:47:09 -0700
commitc6a6dd0d0736d548ff9f255e5ed5df45b29c46c1 (patch)
tree2fef9bc877e6314b198fc892bb5b24e22806c1f8
parent20a26b595c74ac41cf7c19e6091d7e675e503321 (diff)
downloadspark-c6a6dd0d0736d548ff9f255e5ed5df45b29c46c1.tar.gz
spark-c6a6dd0d0736d548ff9f255e5ed5df45b29c46c1.tar.bz2
spark-c6a6dd0d0736d548ff9f255e5ed5df45b29c46c1.zip
[MINOR] [UI] Improve confusing message on log page
It's good practice to check if the input path is in the directory we expect to avoid potentially confusing error messages.
-rw-r--r--core/src/main/scala/org/apache/spark/deploy/worker/ui/LogPage.scala9
-rw-r--r--core/src/main/scala/org/apache/spark/util/Utils.scala16
-rw-r--r--core/src/test/scala/org/apache/spark/deploy/worker/ui/LogPageSuite.scala36
-rw-r--r--core/src/test/scala/org/apache/spark/util/UtilsSuite.scala65
4 files changed, 115 insertions, 11 deletions
diff --git a/core/src/main/scala/org/apache/spark/deploy/worker/ui/LogPage.scala b/core/src/main/scala/org/apache/spark/deploy/worker/ui/LogPage.scala
index dc2bee6f2b..53f8f9a46c 100644
--- a/core/src/main/scala/org/apache/spark/deploy/worker/ui/LogPage.scala
+++ b/core/src/main/scala/org/apache/spark/deploy/worker/ui/LogPage.scala
@@ -17,6 +17,8 @@
package org.apache.spark.deploy.worker.ui
+import java.io.File
+import java.net.URI
import javax.servlet.http.HttpServletRequest
import scala.xml.Node
@@ -135,6 +137,13 @@ private[ui] class LogPage(parent: WorkerWebUI) extends WebUIPage("logPage") with
return ("Error: Log type must be one of " + supportedLogTypes.mkString(", "), 0, 0, 0)
}
+ // Verify that the normalized path of the log directory is in the working directory
+ val normalizedUri = new URI(logDirectory).normalize()
+ val normalizedLogDir = new File(normalizedUri.getPath)
+ if (!Utils.isInDirectory(workDir, normalizedLogDir)) {
+ return ("Error: invalid log directory " + logDirectory, 0, 0, 0)
+ }
+
try {
val files = RollingFileAppender.getSortedRolledOverFiles(logDirectory, logType)
logDebug(s"Sorted log files of type $logType in $logDirectory:\n${files.mkString("\n")}")
diff --git a/core/src/main/scala/org/apache/spark/util/Utils.scala b/core/src/main/scala/org/apache/spark/util/Utils.scala
index 693e1a0a3d..5f13241054 100644
--- a/core/src/main/scala/org/apache/spark/util/Utils.scala
+++ b/core/src/main/scala/org/apache/spark/util/Utils.scala
@@ -2227,6 +2227,22 @@ private[spark] object Utils extends Logging {
}
}
+ /**
+ * Return whether the specified file is a parent directory of the child file.
+ */
+ def isInDirectory(parent: File, child: File): Boolean = {
+ if (child == null || parent == null) {
+ return false
+ }
+ if (!child.exists() || !parent.exists() || !parent.isDirectory()) {
+ return false
+ }
+ if (parent.equals(child)) {
+ return true
+ }
+ isInDirectory(parent, child.getParentFile)
+ }
+
}
private [util] class SparkShutdownHookManager {
diff --git a/core/src/test/scala/org/apache/spark/deploy/worker/ui/LogPageSuite.scala b/core/src/test/scala/org/apache/spark/deploy/worker/ui/LogPageSuite.scala
index 572360ddb9..72eaffb416 100644
--- a/core/src/test/scala/org/apache/spark/deploy/worker/ui/LogPageSuite.scala
+++ b/core/src/test/scala/org/apache/spark/deploy/worker/ui/LogPageSuite.scala
@@ -19,7 +19,7 @@ package org.apache.spark.deploy.worker.ui
import java.io.{File, FileWriter}
-import org.mockito.Mockito.mock
+import org.mockito.Mockito.{mock, when}
import org.scalatest.PrivateMethodTester
import org.apache.spark.SparkFunSuite
@@ -28,33 +28,47 @@ class LogPageSuite extends SparkFunSuite with PrivateMethodTester {
test("get logs simple") {
val webui = mock(classOf[WorkerWebUI])
+ val tmpDir = new File(sys.props("java.io.tmpdir"))
+ val workDir = new File(tmpDir, "work-dir")
+ workDir.mkdir()
+ when(webui.workDir).thenReturn(workDir)
val logPage = new LogPage(webui)
// Prepare some fake log files to read later
val out = "some stdout here"
val err = "some stderr here"
- val tmpDir = new File(sys.props("java.io.tmpdir"))
- val tmpOut = new File(tmpDir, "stdout")
- val tmpErr = new File(tmpDir, "stderr")
- val tmpRand = new File(tmpDir, "random")
+ val tmpOut = new File(workDir, "stdout")
+ val tmpErr = new File(workDir, "stderr")
+ val tmpErrBad = new File(tmpDir, "stderr") // outside the working directory
+ val tmpOutBad = new File(tmpDir, "stdout")
+ val tmpRand = new File(workDir, "random")
write(tmpOut, out)
write(tmpErr, err)
+ write(tmpOutBad, out)
+ write(tmpErrBad, err)
write(tmpRand, "1 6 4 5 2 7 8")
// Get the logs. All log types other than "stderr" or "stdout" will be rejected
val getLog = PrivateMethod[(String, Long, Long, Long)]('getLog)
val (stdout, _, _, _) =
- logPage invokePrivate getLog(tmpDir.getAbsolutePath, "stdout", None, 100)
+ logPage invokePrivate getLog(workDir.getAbsolutePath, "stdout", None, 100)
val (stderr, _, _, _) =
- logPage invokePrivate getLog(tmpDir.getAbsolutePath, "stderr", None, 100)
+ logPage invokePrivate getLog(workDir.getAbsolutePath, "stderr", None, 100)
val (error1, _, _, _) =
- logPage invokePrivate getLog(tmpDir.getAbsolutePath, "random", None, 100)
+ logPage invokePrivate getLog(workDir.getAbsolutePath, "random", None, 100)
val (error2, _, _, _) =
- logPage invokePrivate getLog(tmpDir.getAbsolutePath, "does-not-exist.txt", None, 100)
+ logPage invokePrivate getLog(workDir.getAbsolutePath, "does-not-exist.txt", None, 100)
+ // These files exist, but live outside the working directory
+ val (error3, _, _, _) =
+ logPage invokePrivate getLog(tmpDir.getAbsolutePath, "stderr", None, 100)
+ val (error4, _, _, _) =
+ logPage invokePrivate getLog(tmpDir.getAbsolutePath, "stdout", None, 100)
assert(stdout === out)
assert(stderr === err)
- assert(error1.startsWith("Error"))
- assert(error2.startsWith("Error"))
+ assert(error1.startsWith("Error: Log type must be one of "))
+ assert(error2.startsWith("Error: Log type must be one of "))
+ assert(error3.startsWith("Error: invalid log directory"))
+ assert(error4.startsWith("Error: invalid log directory"))
}
/** Write the specified string to the file. */
diff --git a/core/src/test/scala/org/apache/spark/util/UtilsSuite.scala b/core/src/test/scala/org/apache/spark/util/UtilsSuite.scala
index a867cf83dc..a61ea3918f 100644
--- a/core/src/test/scala/org/apache/spark/util/UtilsSuite.scala
+++ b/core/src/test/scala/org/apache/spark/util/UtilsSuite.scala
@@ -608,4 +608,69 @@ class UtilsSuite extends SparkFunSuite with ResetSystemProperties with Logging {
manager.runAll()
assert(output.toList === List(4, 3, 2))
}
+
+ test("isInDirectory") {
+ val tmpDir = new File(sys.props("java.io.tmpdir"))
+ val parentDir = new File(tmpDir, "parent-dir")
+ val childDir1 = new File(parentDir, "child-dir-1")
+ val childDir1b = new File(parentDir, "child-dir-1b")
+ val childFile1 = new File(parentDir, "child-file-1.txt")
+ val childDir2 = new File(childDir1, "child-dir-2")
+ val childDir2b = new File(childDir1, "child-dir-2b")
+ val childFile2 = new File(childDir1, "child-file-2.txt")
+ val childFile3 = new File(childDir2, "child-file-3.txt")
+ val nullFile: File = null
+ parentDir.mkdir()
+ childDir1.mkdir()
+ childDir1b.mkdir()
+ childDir2.mkdir()
+ childDir2b.mkdir()
+ childFile1.createNewFile()
+ childFile2.createNewFile()
+ childFile3.createNewFile()
+
+ // Identity
+ assert(Utils.isInDirectory(parentDir, parentDir))
+ assert(Utils.isInDirectory(childDir1, childDir1))
+ assert(Utils.isInDirectory(childDir2, childDir2))
+
+ // Valid ancestor-descendant pairs
+ assert(Utils.isInDirectory(parentDir, childDir1))
+ assert(Utils.isInDirectory(parentDir, childFile1))
+ assert(Utils.isInDirectory(parentDir, childDir2))
+ assert(Utils.isInDirectory(parentDir, childFile2))
+ assert(Utils.isInDirectory(parentDir, childFile3))
+ assert(Utils.isInDirectory(childDir1, childDir2))
+ assert(Utils.isInDirectory(childDir1, childFile2))
+ assert(Utils.isInDirectory(childDir1, childFile3))
+ assert(Utils.isInDirectory(childDir2, childFile3))
+
+ // Inverted ancestor-descendant pairs should fail
+ assert(!Utils.isInDirectory(childDir1, parentDir))
+ assert(!Utils.isInDirectory(childDir2, parentDir))
+ assert(!Utils.isInDirectory(childDir2, childDir1))
+ assert(!Utils.isInDirectory(childFile1, parentDir))
+ assert(!Utils.isInDirectory(childFile2, parentDir))
+ assert(!Utils.isInDirectory(childFile3, parentDir))
+ assert(!Utils.isInDirectory(childFile2, childDir1))
+ assert(!Utils.isInDirectory(childFile3, childDir1))
+ assert(!Utils.isInDirectory(childFile3, childDir2))
+
+ // Non-existent files or directories should fail
+ assert(!Utils.isInDirectory(parentDir, new File(parentDir, "one.txt")))
+ assert(!Utils.isInDirectory(parentDir, new File(parentDir, "one/two.txt")))
+ assert(!Utils.isInDirectory(parentDir, new File(parentDir, "one/two/three.txt")))
+
+ // Siblings should fail
+ assert(!Utils.isInDirectory(childDir1, childDir1b))
+ assert(!Utils.isInDirectory(childDir1, childFile1))
+ assert(!Utils.isInDirectory(childDir2, childDir2b))
+ assert(!Utils.isInDirectory(childDir2, childFile2))
+
+ // Null files should fail without throwing NPE
+ assert(!Utils.isInDirectory(parentDir, nullFile))
+ assert(!Utils.isInDirectory(childFile3, nullFile))
+ assert(!Utils.isInDirectory(nullFile, parentDir))
+ assert(!Utils.isInDirectory(nullFile, childFile3))
+ }
}