Improve cache directory error messaging

This commit is contained in:
Nindi Gill 2022-12-08 20:33:37 +11:00
parent 4ecef6dd13
commit c586788a5b
10 changed files with 139 additions and 6 deletions

View file

@ -94,6 +94,7 @@
398734D028603D9E00B4C357 /* UInt8+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 398734CF28603D9E00B4C357 /* UInt8+Extension.swift */; };
398734D228603DE700B4C357 /* Array+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 398734D128603DE700B4C357 /* Array+Extension.swift */; };
398734D4286046B000B4C357 /* UInt32+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 398734D3286046B000B4C357 /* UInt32+Extension.swift */; };
39CA25E32941D8BB0030711E /* FileAttributesUpdater.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39CA25E22941D8BB0030711E /* FileAttributesUpdater.swift */; };
39CB5E3D293F5C2E00CFDBB8 /* Catalog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39CB5E3C293F5C2E00CFDBB8 /* Catalog.swift */; };
39CB5E3F2941486D00CFDBB8 /* CatalogSeedType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39CB5E3E2941486D00CFDBB8 /* CatalogSeedType.swift */; };
39CB5E5429418A2900CFDBB8 /* MistTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39CB5E5329418A2900CFDBB8 /* MistTests.swift */; };
@ -239,6 +240,7 @@
398734CF28603D9E00B4C357 /* UInt8+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UInt8+Extension.swift"; sourceTree = "<group>"; };
398734D128603DE700B4C357 /* Array+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Array+Extension.swift"; sourceTree = "<group>"; };
398734D3286046B000B4C357 /* UInt32+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UInt32+Extension.swift"; sourceTree = "<group>"; };
39CA25E22941D8BB0030711E /* FileAttributesUpdater.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileAttributesUpdater.swift; sourceTree = "<group>"; };
39CB5E3C293F5C2E00CFDBB8 /* Catalog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Catalog.swift; sourceTree = "<group>"; };
39CB5E3E2941486D00CFDBB8 /* CatalogSeedType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CatalogSeedType.swift; sourceTree = "<group>"; };
39CB5E5129418A2900CFDBB8 /* MistTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MistTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
@ -373,6 +375,7 @@
39CF56202861C992006FB5D2 /* DiskImageMounter.swift */,
39CF56232861CA85006FB5D2 /* DiskImageUnmounter.swift */,
3935F47D2864813B00760AB0 /* DownloadManager.swift */,
39CA25E22941D8BB0030711E /* FileAttributesUpdater.swift */,
39CF56382862D75D006FB5D2 /* FileCreator.swift */,
39CF56342862D4BF006FB5D2 /* FileCompressor.swift */,
39CF56162861BE66006FB5D2 /* FileCopier.swift */,
@ -803,6 +806,7 @@
390451D02856F63700E0B563 /* Installer.swift in Sources */,
3935F47628643AF000760AB0 /* UNNotificationAction+Extension.swift in Sources */,
39252AB3285C5D7700956C74 /* SettingsGeneralUpdatesView.swift in Sources */,
39CA25E32941D8BB0030711E /* FileAttributesUpdater.swift in Sources */,
3935F4AB286B04BC00760AB0 /* HelperToolInfoPropertyList.swift in Sources */,
393F35BC28641181005B7165 /* RefreshState.swift in Sources */,
390451CA2856F1D300E0B563 /* ScaledImage.swift in Sources */,

View file

@ -0,0 +1,36 @@
//
// FileAttributesUpdater.swift
// Mist
//
// Created by Nindi Gill on 8/12/2022.
//
import Foundation
import SecureXPC
/// Helper struct to update file / directory attributes
struct FileAttributesUpdater {
/// Update file / directory attributes at the provided URL.
///
/// - Parameters:
/// - url: The URL of the file / directory to update.
/// - ownerAccountName: The username of the user that will be used to set the file / directory ownership.
///
/// - Throws: An `Error` if the command failed to execute.
static func update(url: URL, ownerAccountName: String) async throws {
guard FileManager.default.fileExists(atPath: url.path) else {
return
}
let arguments: [String] = [url.path, ownerAccountName]
let client: XPCClient = XPCClient.forMachService(named: .helperIdentifier)
let request: HelperToolCommandRequest = HelperToolCommandRequest(type: .fileAttributes, arguments: arguments, environment: [:])
let response: HelperToolCommandResponse = try await client.sendMessage(request, to: XPCRoute.commandRoute)
guard response.terminationStatus == 0 else {
throw MistError.invalidTerminationStatus(status: response.terminationStatus, string: response.standardError)
}
}
}

View file

@ -6,6 +6,7 @@
//
import Foundation
import System
// swiftlint:disable file_length
// swiftlint:disable:next type_body_length
@ -231,6 +232,24 @@ class TaskManager: ObservableObject {
try await DirectoryCreator.create(cacheDirectoryURL, withIntermediateDirectories: true)
}
]
} else {
let attributes: [FileAttributeKey: Any] = try FileManager.default.attributesOfItem(atPath: cacheDirectoryURL.path)
guard let posixPermissions: NSNumber = attributes[.posixPermissions] as? NSNumber,
let ownerAccountName: String = attributes[.ownerAccountName] as? String,
let groupOwnerAccountName: String = attributes[.groupOwnerAccountName] as? String else {
throw MistError.missingFileAttributes
}
let filePermissions: FilePermissions = FilePermissions(rawValue: CModeT(posixPermissions.int16Value))
if filePermissions != [.ownerReadWriteExecute, .groupReadExecute, .otherReadExecute] || ownerAccountName != NSUserName() || groupOwnerAccountName != "staff" {
tasks += [
MistTask(type: .configure, description: "cache directory") {
try await FileAttributesUpdater.update(url: cacheDirectoryURL, ownerAccountName: ownerAccountName)
}
]
}
}
for package in installer.allDownloads {

View file

@ -10,4 +10,5 @@ import Foundation
enum DownloadAlertType: String {
case compatibility = "Compatiblity"
case helperTool = "Helper Tool"
case cacheDirectory = "Cache Directory"
}

View file

@ -19,6 +19,7 @@ enum MistError: Error, Equatable {
case invalidTerminationStatus(status: Int32, string: String?)
case invalidURL(_ url: String)
case maximumRetriesReached
case missingFileAttributes
case outputStreamBufferError
case outputStreamWriteError
case userCancelled
@ -51,6 +52,8 @@ enum MistError: Error, Equatable {
return "Invalid URL: '\(url)'"
case .maximumRetriesReached:
return "Maximum number of retries reached"
case .missingFileAttributes:
return "Missing file attributes"
case .outputStreamBufferError:
return "Output Stream Buffer Error"
case .outputStreamWriteError:

View file

@ -7,6 +7,7 @@
import Blessed
import SwiftUI
import System
struct ListRow: View {
var type: DownloadType
@ -21,6 +22,8 @@ struct ListRow: View {
@ObservedObject var taskManager: TaskManager
@State private var alertType: DownloadAlertType = .compatibility
@State private var showAlert: Bool = false
@AppStorage("cacheDownloads") private var cacheDownloads: Bool = false
@AppStorage("cacheDirectory") private var cacheDirectory: String = .cacheDirectory
private let length: CGFloat = 48
private let spacing: CGFloat = 5
private var compatibilityTitle: String {
@ -43,6 +46,12 @@ struct ListRow: View {
private var privilegedHelperToolMessage: String {
"The Mist Privileged Helper Tool is required to perform Administrator tasks when \(type == .firmware ? "downloading macOS Firmwares" : "creating macOS Installers")."
}
private var cacheDirectoryTitle: String {
"Cache directory settings incorrect!"
}
private var cacheDirectoryMessage: String {
"The cache directory has incorrect ownership and/or permissions, which will cause issues caching macOS Installers.\n\nRepair the cache directory ownership and/or permissions and try again."
}
var body: some View {
HStack {
@ -83,7 +92,14 @@ struct ListRow: View {
return Alert(
title: Text(privilegedHelperToolTitle),
message: Text(privilegedHelperToolMessage),
primaryButton: .default(Text("Install...")) { install() },
primaryButton: .default(Text("Install...")) { installPrivilegedHelperTool() },
secondaryButton: .default(Text("Cancel"))
)
case .cacheDirectory:
return Alert(
title: Text(cacheDirectoryTitle),
message: Text(cacheDirectoryMessage),
primaryButton: .default(Text("Repair...")) { Task { try await repairCacheDirectoryOwnershipAndPermissions() } },
secondaryButton: .default(Text("Cancel"))
)
}
@ -103,12 +119,47 @@ struct ListRow: View {
return
}
if cacheDownloads {
do {
let attributes: [FileAttributeKey: Any] = try FileManager.default.attributesOfItem(atPath: cacheDirectory)
guard let posixPermissions: NSNumber = attributes[.posixPermissions] as? NSNumber else {
alertType = .cacheDirectory
showAlert = true
return
}
let filePermissions: FilePermissions = FilePermissions(rawValue: CModeT(posixPermissions.int16Value))
guard filePermissions == [.ownerReadWriteExecute, .groupReadExecute, .otherReadExecute],
let ownerAccountName: String = attributes[.ownerAccountName] as? String,
ownerAccountName == NSUserName(),
let groupOwnerAccountName: String = attributes[.groupOwnerAccountName] as? String,
groupOwnerAccountName == "staff" else {
alertType = .cacheDirectory
showAlert = true
return
}
} catch {
alertType = .cacheDirectory
showAlert = true
return
}
}
showPanel = true
}
private func install() {
private func installPrivilegedHelperTool() {
try? PrivilegedHelperManager.shared.authorizeAndBless()
}
private func repairCacheDirectoryOwnershipAndPermissions() async throws {
let url: URL = URL(fileURLWithPath: cacheDirectory)
let ownerAccountName: String = NSUserName()
try await FileAttributesUpdater.update(url: url, ownerAccountName: ownerAccountName)
}
}
struct ListRow_Previews: PreviewProvider {

View file

@ -3,7 +3,7 @@
<plist version="1.0">
<dict>
<key>BuildHash</key>
<string>631381272a839c41efa70afa56e8d6e05a0bdbc167d07ecae792ed338ac0a353</string>
<string>977665398f7c4fc4f84fda51c877a98d25ab4a8ed94c7c532dc7070c8e1c845d</string>
<key>CFBundleIdentifier</key>
<string>com.ninxsoft.mist.helper</string>
<key>CFBundleInfoDictionaryVersion</key>

View file

@ -25,7 +25,7 @@ struct HelperToolCommandRunner {
case .remove:
guard let path: String = request.arguments.first else {
return HelperToolCommandResponse(terminationStatus: 1, standardOutput: nil, standardError: "Invalid URL")
return HelperToolCommandResponse(terminationStatus: 1, standardOutput: nil, standardError: "Invalid URL: \(request.arguments)")
}
guard FileManager.default.fileExists(atPath: path) else {
@ -38,6 +38,25 @@ struct HelperToolCommandRunner {
} catch {
return HelperToolCommandResponse(terminationStatus: 1, standardOutput: nil, standardError: error.localizedDescription)
}
case .fileAttributes:
guard let path: String = request.arguments.first,
let ownerAccountName: String = request.arguments.last else {
return HelperToolCommandResponse(terminationStatus: 1, standardOutput: nil, standardError: "Invalid attributes: \(request.arguments)")
}
let attributes: [FileAttributeKey: Any] = [
.posixPermissions: 0o755,
.ownerAccountName: ownerAccountName,
.groupOwnerAccountName: "staff"
]
do {
try FileManager.default.setAttributes(attributes, ofItemAtPath: path)
return HelperToolCommandResponse(terminationStatus: 0, standardOutput: nil, standardError: nil)
} catch {
return HelperToolCommandResponse(terminationStatus: 1, standardOutput: nil, standardError: error.localizedDescription)
}
case .kill:
ShellExecutor.shared.terminate()
return HelperToolCommandResponse(terminationStatus: 0, standardOutput: nil, standardError: nil)

View file

@ -11,6 +11,8 @@ enum HelperToolCommandType: String, Codable {
// swiftlint:disable:next redundant_string_enum_value
case remove = "remove"
// swiftlint:disable:next redundant_string_enum_value
case fileAttributes = "fileAttributes"
// swiftlint:disable:next redundant_string_enum_value
case installer = "installer"
// swiftlint:disable:next redundant_string_enum_value
case createinstallmedia = "createinstallmedia"

View file

@ -59,8 +59,6 @@ class ShellExecutor: NSObject {
return (terminationStatus: terminationStatus, standardOutput: standardOutput, standardError: (standardError ?? "").isEmpty ? nil : standardError)
}
// swiftlint:enable large_tuple
func terminate() {
guard process.isRunning else {