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.
This commit is contained in:
Ethan Atkins 2019-11-18 13:04:38 -08:00
parent f0d1e075db
commit 283d486796
4 changed files with 47 additions and 14 deletions

View File

@ -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)

View File

@ -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

View File

@ -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
)

View File

@ -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
}
}