From 283d486796f6bd3ebd7a54aebb365705d3f0aebf Mon Sep 17 00:00:00 2001 From: Ethan Atkins Date: Mon, 18 Nov 2019 13:04:38 -0800 Subject: [PATCH] Do not use temporary directories in java.io.tmpdir sbt should not by default create files in the location specified by java.io.tmpdir (which is the default behavior of apis like IO.createTemporaryDirectory or Files.createTempFile) because they have a tendency to leak and it also isn't even guaranteed that the user has write permissions there (though this is unlikely). Doing so creates the possibility for leaks I git grepped for `createTemp` and found these apis. After this change, the files created by sbt should largely be localized to the project and sbt global base directories. --- main/src/main/scala/sbt/BuildPaths.scala | 6 ++++ main/src/main/scala/sbt/Defaults.scala | 16 +++++++++-- main/src/main/scala/sbt/Main.scala | 11 ++++++-- .../sbt/scriptedtest/ScriptedTests.scala | 28 +++++++++++++------ 4 files changed, 47 insertions(+), 14 deletions(-) diff --git a/main/src/main/scala/sbt/BuildPaths.scala b/main/src/main/scala/sbt/BuildPaths.scala index b66d0e851..ba4219615 100644 --- a/main/src/main/scala/sbt/BuildPaths.scala +++ b/main/src/main/scala/sbt/BuildPaths.scala @@ -121,6 +121,10 @@ object BuildPaths { def outputDirectory(base: File) = base / DefaultTargetName def projectStandard(base: File) = base / "project" + def globalLoggingStandard(base: File): File = + base.getCanonicalFile / DefaultTargetName / GlobalLogging + def globalTaskDirectoryStandard(base: File): File = + base.getCanonicalFile / DefaultTargetName / TaskTempDirectory final val PluginsDirectoryName = "plugins" final val DefaultTargetName = "target" @@ -131,6 +135,8 @@ object BuildPaths { final val GlobalSettingsProperty = "sbt.global.settings" final val DependencyBaseProperty = "sbt.dependency.base" final val GlobalZincProperty = "sbt.global.zinc" + final val GlobalLogging = "global-logging" + final val TaskTempDirectory = "task-temp-directory" def crossPath(base: File, instance: xsbti.compile.ScalaInstance): File = base / ("scala_" + instance.version) diff --git a/main/src/main/scala/sbt/Defaults.scala b/main/src/main/scala/sbt/Defaults.scala index 9701871c4..42e9eeecf 100755 --- a/main/src/main/scala/sbt/Defaults.scala +++ b/main/src/main/scala/sbt/Defaults.scala @@ -291,7 +291,12 @@ object Defaults extends BuildCommon { }, watchSources :== Nil, // Although this is deprecated, it can't be removed or it breaks += for legacy builds. skip :== false, - taskTemporaryDirectory := { val dir = IO.createTemporaryDirectory; dir.deleteOnExit(); dir }, + taskTemporaryDirectory := { + val base = BuildPaths.globalTaskDirectoryStandard(appConfiguration.value.baseDirectory) + val dir = IO.createUniqueDirectory(base) + ShutdownHooks.add(() => IO.delete(dir)) + dir + }, onComplete := { val tempDirectory = taskTemporaryDirectory.value () => Clean.deleteContents(tempDirectory, _ => false) @@ -697,7 +702,7 @@ object Defaults extends BuildCommon { lazy val projectTasks: Seq[Setting[_]] = Seq( cleanFiles := cleanFilesTask.value, cleanKeepFiles := Vector.empty, - cleanKeepGlobs := historyPath.value.map(_.toGlob).toSeq, + cleanKeepGlobs ++= historyPath.value.map(_.toGlob).toVector, clean := Def.taskDyn(Clean.task(resolvedScoped.value.scope, full = true)).value, consoleProject := consoleProjectTask.value, transitiveDynamicInputs := SettingsGraph.task.value, @@ -2190,7 +2195,12 @@ object Classpaths { sourceArtifactTypes :== Artifact.DefaultSourceTypes.toVector, docArtifactTypes :== Artifact.DefaultDocTypes.toVector, cleanKeepFiles :== Nil, - cleanKeepGlobs :== Nil, + cleanKeepGlobs := { + val base = appConfiguration.value.baseDirectory.getCanonicalFile + val dirs = BuildPaths + .globalLoggingStandard(base) :: BuildPaths.globalTaskDirectoryStandard(base) :: Nil + dirs.flatMap(d => Glob(d) :: Glob(d, RecursiveGlob) :: Nil) + }, fileOutputs :== Nil, sbtDependency := { val app = appConfiguration.value diff --git a/main/src/main/scala/sbt/Main.scala b/main/src/main/scala/sbt/Main.scala index 4da7133ff..d570e6bba 100644 --- a/main/src/main/scala/sbt/Main.scala +++ b/main/src/main/scala/sbt/Main.scala @@ -141,12 +141,17 @@ object StandardMain { val console: ConsoleOut = ConsoleOut.systemOutOverwrite(ConsoleOut.overwriteContaining("Resolving ")) - def initialGlobalLogging: GlobalLogging = + private[this] def initialGlobalLogging(file: Option[File]): GlobalLogging = { + file.foreach(f => if (!f.exists()) IO.createDirectory(f)) GlobalLogging.initial( MainAppender.globalDefault(console), - File.createTempFile("sbt", ".log"), + File.createTempFile("sbt-global-log", ".log", file.orNull), console ) + } + def initialGlobalLogging(file: File): GlobalLogging = initialGlobalLogging(Option(file)) + @deprecated("use version that takes file argument", "1.4.0") + def initialGlobalLogging: GlobalLogging = initialGlobalLogging(None) def initialState( configuration: xsbti.AppConfiguration, @@ -171,7 +176,7 @@ object StandardMain { commands, State.newHistory, initAttrs, - initialGlobalLogging, + initialGlobalLogging(BuildPaths.globalLoggingStandard(configuration.baseDirectory)), None, State.Continue ) diff --git a/scripted-sbt-redux/src/main/scala/sbt/scriptedtest/ScriptedTests.scala b/scripted-sbt-redux/src/main/scala/sbt/scriptedtest/ScriptedTests.scala index db1041c44..e2d27bc1f 100644 --- a/scripted-sbt-redux/src/main/scala/sbt/scriptedtest/ScriptedTests.scala +++ b/scripted-sbt-redux/src/main/scala/sbt/scriptedtest/ScriptedTests.scala @@ -8,24 +8,26 @@ package sbt package scriptedtest -import java.io.{ File, FileNotFoundException } +import java.io.{ File, FileNotFoundException, IOException } import java.net.SocketException +import java.nio.file.Files import java.util.Properties import java.util.concurrent.ForkJoinPool -import scala.collection.GenSeq -import scala.collection.mutable -import scala.collection.parallel.ForkJoinTaskSupport -import scala.util.control.NonFatal -import sbt.internal.scripted._ import sbt.internal.io.Resources +import sbt.internal.scripted._ import sbt.internal.util.{ BufferedLogger, ConsoleOut, FullLogger, Util } +import sbt.io.FileFilter._ import sbt.io.syntax._ import sbt.io.{ DirectoryFilter, HiddenFileFilter, IO } -import sbt.io.FileFilter._ +import sbt.nio.file._ +import sbt.nio.file.syntax._ import sbt.util.{ AbstractLogger, Level, Logger } +import scala.collection.{ GenSeq, mutable } +import scala.collection.parallel.ForkJoinTaskSupport import scala.util.Try +import scala.util.control.NonFatal final class ScriptedTests( resourceBaseDirectory: File, @@ -359,7 +361,17 @@ final class ScriptedTests( // Run the test and delete files (except global that holds local scala jars) val result = runOrHandleDisabled(label, tempTestDir, runTest, buffer) - IO.delete(tempTestDir.*("*" -- "global").get) + val view = FileTreeView.default + val base = tempTestDir.getCanonicalFile.toGlob + val global = base / "global" + val globalLogging = base / ** / "global-logging" + def recursiveFilter(glob: Glob): PathFilter = (glob: PathFilter) || glob / ** + val keep: PathFilter = recursiveFilter(global) || recursiveFilter(globalLogging) + val toDelete = view.list(base / **, !keep).map(_._1).sorted.reverse + toDelete.foreach { p => + try Files.deleteIfExists(p) + catch { case _: IOException => } + } result } }