FilePathTemp.swift 2.8 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697
  1. /*
  2. This source file is part of the Swift System open source project
  3. Copyright (c) 2024 Apple Inc. and the Swift System project authors
  4. Licensed under Apache License v2.0 with Runtime Library Exception
  5. See https://swift.org/LICENSE.txt for license information
  6. */
  7. // MARK: - API
  8. /// Create a temporary path for the duration of the closure.
  9. ///
  10. /// - Parameters:
  11. /// - basename: The base name for the temporary path.
  12. /// - body: The closure to execute.
  13. ///
  14. /// Creates a temporary directory with a name based on the given `basename`,
  15. /// executes `body`, passing in the path of the created directory, then
  16. /// deletes the directory and all of its contents before returning.
  17. internal func withTemporaryFilePath<R>(
  18. basename: FilePath.Component,
  19. _ body: (FilePath) throws -> R
  20. ) throws -> R {
  21. let temporaryDir = try createUniqueTemporaryDirectory(basename: basename)
  22. defer {
  23. try? _recursiveRemove(at: temporaryDir)
  24. }
  25. return try body(temporaryDir)
  26. }
  27. // MARK: - Internals
  28. fileprivate let base64 = Array<UInt8>(
  29. "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_".utf8
  30. )
  31. /// Create a directory that is only accessible to the current user.
  32. ///
  33. /// - Parameters:
  34. /// - path: The path of the directory to create.
  35. /// - Returns: `true` if a new directory was created.
  36. ///
  37. /// This function will throw if there is an error, except if the error
  38. /// is that the directory exists, in which case it returns `false`.
  39. fileprivate func makeLockedDownDirectory(at path: FilePath) throws -> Bool {
  40. return try path.withPlatformString {
  41. if system_mkdir($0, 0o700) == 0 {
  42. return true
  43. }
  44. let err = system_errno
  45. if err == Errno.fileExists.rawValue {
  46. return false
  47. } else {
  48. throw Errno(rawValue: err)
  49. }
  50. }
  51. }
  52. /// Generate a random string of base64 filename safe characters.
  53. ///
  54. /// - Parameters:
  55. /// - length: The number of characters in the returned string.
  56. /// - Returns: A random string of length `length`.
  57. fileprivate func createRandomString(length: Int) -> String {
  58. return String(
  59. decoding: (0..<length).map{
  60. _ in base64[Int.random(in: 0..<64)]
  61. },
  62. as: UTF8.self
  63. )
  64. }
  65. /// Given a base name, create a uniquely named temporary directory.
  66. ///
  67. /// - Parameters:
  68. /// - basename: The base name for the new directory.
  69. /// - Returns: The path to the new directory.
  70. ///
  71. /// Creates a directory in the system temporary directory whose name
  72. /// starts with `basename`, followed by a `.` and then a random
  73. /// string of characters.
  74. fileprivate func createUniqueTemporaryDirectory(
  75. basename: FilePath.Component
  76. ) throws -> FilePath {
  77. var tempDir = try _getTemporaryDirectory()
  78. tempDir.append(basename)
  79. while true {
  80. tempDir.extension = createRandomString(length: 16)
  81. if try makeLockedDownDirectory(at: tempDir) {
  82. return tempDir
  83. }
  84. }
  85. }