CloverBootloader/CloverApp/CloverDaemonNew/CloverDaemonNew/main.swift

558 lines
18 KiB
Swift

//
// main.swift
// cloverDaemonSwift
//
// Created by vector sigma on 03/11/2019.
// Copyright © 2019 CloverHackyColor. All rights reserved.
//
import Foundation
let daemonVersion = "1.0.9"
let fm = FileManager.default
var mountRWCalled = fm.isWritableFile(atPath: "/")
func execAttr() -> [FileAttributeKey : Any] {
var attributes = [FileAttributeKey : Any]()
attributes[.posixPermissions] = NSNumber(value: 755) //NSNumber object. Use the int16Value method to retrieve the integer value for the permissions.
attributes[.groupOwnerAccountID] = NSNumber(value: 0) // NSNumber object containing an unsigned long.
attributes[.groupOwnerAccountName] = "wheel" as NSString
attributes[.ownerAccountID] = NSNumber(value: 0) //NSNumber object containing an unsigned long.
attributes[.ownerAccountName] = "root" as NSString
return attributes
}
func launchAttr() -> [FileAttributeKey : Any] {
var attributes = [FileAttributeKey : Any]()
attributes[.posixPermissions] = NSNumber(value: 420) //NSNumber object. Use the int16Value method to retrieve the integer value for the permissions.
attributes[.groupOwnerAccountID] = NSNumber(value: 0) // NSNumber object containing an unsigned long.
attributes[.groupOwnerAccountName] = "wheel" as NSString
attributes[.ownerAccountID] = NSNumber(value: 0) //NSNumber object containing an unsigned long.
attributes[.ownerAccountName] = "root" as NSString
return attributes
}
func run(cmd: String) {
let task = Process()
if #available(OSX 10.13, *) {
task.executableURL = URL(fileURLWithPath: "/bin/bash")
} else {
task.launchPath = "/bin/bash"
}
task.environment = ProcessInfo.init().environment
task.arguments = ["-c", cmd]
task.launch()
}
func checkSleepProxyClient(nvram: NSDictionary) {
let mDNSResponderPath = "/System/Library/LaunchDaemons/com.apple.mDNSResponder.plist"
let disableOption = "-DisableSleepProxyClient"
var value = "false"
if let nvdata = nvram.object(forKey: "Clover.DisableSleepProxyClient") as? Data {
value = String(decoding: nvdata, as: UTF8.self)
print("Clover.DisableSleepProxyClient=\(value).")
} else {
print("Clover.DisableSleepProxyClient is not set.")
}
if !fm.fileExists(atPath: mDNSResponderPath) {
print("Error: cannot found \(mDNSResponderPath).")
return
}
if !fm.isWritableFile(atPath: "/") {
print("Note: root filesystem is read-only.")
return
}
let toDisable : Bool = value == "true"
// check if enabled or disabled
if let mDNSResponder = NSDictionary(contentsOfFile: mDNSResponderPath) {
if let ProgramArguments = mDNSResponder.object(forKey: "ProgramArguments") as? NSArray {
var index : Int = -1
var serviceIsDisabled = false
for i in 0..<ProgramArguments.count {
if let arg = ProgramArguments.object(at: i) as? String {
if arg == disableOption {
index = i
serviceIsDisabled = true
break
}
}
}
// no need to do anything if already as user wants.
if toDisable && serviceIsDisabled {
print("Sleep Proxy Client is already disabled.")
return
} else if !toDisable && !serviceIsDisabled {
print("Sleep Proxy Client is already enabled as default.")
return
}
if toDisable {
print("Trying to disable Sleep Proxy Client service.")
let cmd = "/usr/libexec/PlistBuddy -c \"Add ProgramArguments: string \(disableOption)\" \(mDNSResponderPath)"
run(cmd: cmd)
} else {
print("Trying to enable Sleep Proxy Client service as default.")
if index >= 0 {
let cmd = "/usr/libexec/PlistBuddy -c \"delete :ProgramArguments:\(index)\" \(mDNSResponderPath)"
run(cmd: cmd)
}
}
}
}
}
func getLogOutHook() -> String? {
return NSDictionary(contentsOfFile: loginWindowPath)?.object(forKey: "LogoutHook") as? String
}
func removeCloverRCScripts() {
let oldFiles : [String] = ["/etc/rc.boot.d/10.save_and_rotate_boot_log.local",
"/etc/rc.boot.d/20.mount_ESP.local",
"/etc/rc.boot.d/70.disable_sleep_proxy_client.local.disabled",
"/etc/rc.boot.d/70.disable_sleep_proxy_client.local",
"/etc/rc.clover.lib",
"/etc/rc.shutdown.d/80.save_nvram_plist.local",
oldLaunchDaemonPath,
oldLaunchPlistPath,
oldLogoutHookPath]
if fm.fileExists(atPath: oldLaunchPlistPath) {
print("unloading old CloverDaemon..")
run(cmd: "launchctl unload \(oldLaunchPlistPath)")
}
for f in oldFiles {
if fm.fileExists(atPath: f) {
if !fm.isWritableFile(atPath: "/") {
run(cmd: "mount -uw /")
sleep(3)
}
print("Removing \(f).")
do {
try fm.removeItem(atPath: f)
} catch {
print(error)
}
}
}
if getLogOutHook() == oldLogoutHookPath {
print("Removing old logoutHook (\(oldLogoutHookPath))..")
run(cmd: "defaults delete com.apple.loginwindow LogoutHook")
}
}
func installHook() {
print("Installing the LogoutHook..")
// if a hook exist and is not our one, then add a wrapper
if let loh = getLogOutHook() {
if loh != cloverLogOut {
if fm.fileExists(atPath: loh) {
let wrapper =
"""
#!/bin/sh
'\(cloverLogOut)'
# This file is automatically generated by CloverDaemonNew because
# it has detected you added a logout script somewhere else.
# if you edit this file, be aware that if you start to boot in UEFI
# CloverDaemonNew will take care to restore your custom logout script,
# but your script must be the last line to be detected again.
# The script path must be escaped within '' if contains spaces in the middle.
# NOTE: if your will is to add multiple scripts, please add them to
# the following script (and so not in this script):
'\(loh)'
"""
print("Detected user logout hook at \(loh), merging it with CloverLogOut in CloverWrapper.sh.")
do {
try wrapper.write(toFile: wrapperPath, atomically: false, encoding: .utf8)
try fm.setAttributes(execAttr(), ofItemAtPath: wrapperPath)
run(cmd: "defaults write com.apple.loginwindow LogoutHook '\(wrapperPath)'")
} catch {
print(error)
}
} else {
print("Detected user logout hook at \(loh), but the executable did not exist, installing our.")
run(cmd: "defaults write com.apple.loginwindow LogoutHook '\(cloverLogOut)'")
}
} else {
print("LogoutHook is already set.")
}
} else {
print("LogoutHook is not set, writing it.")
run(cmd: "defaults write com.apple.loginwindow LogoutHook '\(cloverLogOut)'")
}
}
func unInstallHook() {
// what to do? remove the logout hook if it's our
var uninstall = false
if let loh = getLogOutHook() {
if (loh == cloverLogOut || loh == oldLogoutHookPath){
// it is CloverLogOut
print("Removing CloverLogOut hook as EmuVariableUefiPresent doesn't exixts.")
uninstall = true
} else if loh == wrapperPath {
print("Removing CloverWrapper.sh logout hook as EmuVariableUefiPresent doesn't exixts.")
// it is CloverWrapper.sh. We have to mantain user script!
// get the user script path:
var scriptPath : String? = nil
if let lines = try? String(contentsOf: URL(fileURLWithPath: wrapperPath),
encoding: .utf8).components(separatedBy: .newlines) {
if lines.count > 0 {
scriptPath = lines.last
}
}
if scriptPath != nil {
if fm.fileExists(atPath: scriptPath!) {
print("..but taking user logout hook script at \(scriptPath!).")
run(cmd: "defaults write com.apple.loginwindow LogoutHook \(scriptPath!)")
} else {
uninstall = true
}
} else {
uninstall = true
}
}
}
if uninstall {
run(cmd: "defaults delete com.apple.loginwindow LogoutHook")
}
}
func main() {
let df = DateFormatter()
df.locale = Locale(identifier: "en_US")
df.dateFormat = "yyyy-MM-dd hh:mm:ss"
var now = df.string(from: Date())
print("\n\n--------------------------------------------")
print("- CloverDaemonNew v\(daemonVersion)")
print("- macOS \(ProcessInfo().operatingSystemVersionString)")
print("- System start at \(now)")
print("------")
run(cmd: "kextunload /System/Library/Extensions/msdosfs.kext 2>/dev/null")
if let volname = getMediaName(from: "/") {
print("root mount point is '/Volumes/\(volname)'")
}
// check Clover revision
if let revision = findCloverRevision() {
print("Started with Clover r\(revision).")
} else {
print("Started with an unknown firmware.")
}
// check if old daemon exist
removeCloverRCScripts()
/*
Clean some lines from clover.daemon.log.
This is not going to go into the System log and is not
controllable by the nvram.
*/
let logLinesMax : Int = 500
let logDir = "/Library/Logs/CloverEFI"
let logPath = "/Library/Logs/CloverEFI/clover.daemon.log"
if !fm.fileExists(atPath: logDir) {
try? fm.createDirectory(atPath: logDir,
withIntermediateDirectories: false,
attributes: nil)
}
if fm.fileExists(atPath: logPath) {
if let log : String? = try? String(contentsOfFile: logPath) {
if let lines = log?.components(separatedBy: CharacterSet.newlines) {
if lines.count > logLinesMax {
// take only latests
run(cmd: "tail -n \(logLinesMax) \(logPath) 2>/dev/null > \(logPath).tmp")
run(cmd: "mv -f \(logPath).tmp \(logPath)")
}
}
}
}
// check options in nvram
let fv = getFirmawareVendor() ?? "unknown"
var needsLogoutHook = isLegacyFirmware()
print("Firmware vendor is '\(fv)'.")
if let nvram = getNVRAM() {
if let nvdata = nvram.object(forKey: "Clover.RootRW") as? Data {
let value = String(decoding: nvdata, as: UTF8.self)
if value == "true" {
if !mountRWCalled {
mountRWCalled = true
print("making '/' writable as Clover.RootRW=true.")
run(cmd: "mount -uw /")
}
}
}
if !needsLogoutHook {
if (nvram.object(forKey: "EmuVariableUefiPresent") != nil ||
nvram.object(forKey: "TestEmuVariableUefiPresent") != nil) {
print("Found EmuVariableUefiPresent in NVRAM.")
needsLogoutHook = true
} else {
print("EmuVariableUefiPresent doesn't exist.")
}
}
if let nvdata = nvram.object(forKey: "Clover.DisableSleepProxyClient") as? Data {
let value = String(decoding: nvdata, as: UTF8.self)
if value == "true" {
if !fm.isWritableFile(atPath: "/") {
if !mountRWCalled {
print("making '/' writable as Clover.DisableSleepProxyClient=true.")
}
run(cmd: "mount -uw /")
sleep(2)
}
}
}
checkSleepProxyClient(nvram: nvram)
/*
Clean old nvram.plist user may have in all volumes
Note: never delete in / as this will be done at shut down/restart
if the nvram is correctly saved somewhere else (e.g. in the ESP).
Also don't delete nvram.plist from external devices as this is not or
shouldn't be our business.
*/
for v in getVolumes() {
let nvramtPath = v.addPath("nvram.plist")
if v != "/" && isInternalDevice(diskOrMtp: v) {
if fm.fileExists(atPath: nvramtPath) {
if fm.isDeletableFile(atPath: nvramtPath) {
do {
try fm.removeItem(atPath: nvramtPath)
print("old '\(nvramtPath)' removed.")
} catch {
print("Error: can't remove '\(nvramtPath)'.")
}
} else {
print("Error: '\(nvramtPath)' is not deletable.")
}
}
}
}
/*
Mount ESP if required
Clover.MountEFI=Yes|diskX|GUID [default No]
*/
if let nvdata = nvram.object(forKey: "Clover.MountEFI") as? Data {
let value = String(decoding: nvdata, as: UTF8.self)
print("Clover.MountEFI=\(value)")
var disk : String? = nil
let allEsps = getAllESPs()
if value == "Yes" {
// mount the boot device
disk = findBootPartitionDevice()
} else if value.hasPrefix("disk") {
disk = value
} else if value.hasPrefix("/dev/") {
// mount desired ESP /dev/disk-rdisk
var tmpdisk = value
if tmpdisk.hasPrefix("/dev/disk") {
tmpdisk = value.replacingOccurrences(of: "/dev/", with: "")
} else if tmpdisk.hasPrefix("/dev/rdisk") {
tmpdisk = value.replacingOccurrences(of: "/dev/r", with: "")
}
if tmpdisk.hasPrefix("disk") {
disk = tmpdisk
}
} else if value.count == 36 && ((value .range(of: "-") != nil)) {
// mount desired ESP by Volume UUID
for d in allEsps {
if let uuid = getMediaUUID(from: d) {
if uuid == value {
disk = d
break
}
}
}
}
if (disk != nil) {
if (getBSDName(of: disk!) != nil) {
if allEsps.contains(disk!) {
print("try to mount \(disk!) as requested.")
mount(disk: disk!, at: nil)
} else {
print("\(disk!) is not an EFI System Partition.")
}
} else {
print("\(disk!) is not a valid disk object or is unavailable at the moment.")
}
} else {
print("Nothing to mount.")
}
}
} else {
print("Error: nvram not present in this System.")
}
if needsLogoutHook {
installHook()
} else {
unInstallHook()
}
print("Logout hook is: \(getLogOutHook() ?? "none")")
signal(SIGTERM, SIG_IGN) //preferred
let sigtermSource = DispatchSource.makeSignalSource(signal: SIGTERM)
sigtermSource.setEventHandler {
now = df.string(from: Date())
print("")
print("SIGTERM received at \(now)")
if let nvram = getNVRAM() {
checkSleepProxyClient(nvram: nvram)
} else {
print("nvram not present in this machine.")
}
exit(EXIT_SUCCESS)
}
sigtermSource.resume()
RunLoop.current.run()
//dispatchMain()
}
let myPath = CommandLine.arguments[0]
let myName = (myPath as NSString).lastPathComponent
if CommandLine.arguments.contains("--install") {
print("Installing daemon...")
// build the launch daemon
let launch = NSMutableDictionary()
launch.setValue(true, forKey: "KeepAlive")
launch.setValue(true, forKey: "RunAtLoad")
launch.setValue("com.slice.CloverDaemonNew", forKey: "Label")
launch.setValue("/Library/Logs/CloverEFI/clover.daemon.log", forKey: "StandardErrorPath")
launch.setValue("/Library/Logs/CloverEFI/clover.daemon.log", forKey: "StandardOutPath")
let ProgramArguments = NSArray(object: cloverDaemonNewPath)
launch.setValue(ProgramArguments, forKey: "ProgramArguments")
removeCloverRCScripts()
do {
if !fm.fileExists(atPath: "/Library/Application Support/Clover") {
try fm.createDirectory(atPath: "/Library/Application Support/Clover",
withIntermediateDirectories: false,
attributes: nil)
}
if fm.fileExists(atPath: launchPlistPath) {
try fm.removeItem(atPath: launchPlistPath)
}
if fm.fileExists(atPath: cloverDaemonNewPath) {
try fm.removeItem(atPath: cloverDaemonNewPath)
}
launch.write(toFile: launchPlistPath, atomically: true)
try fm.copyItem(atPath: myPath, toPath: cloverDaemonNewPath)
try fm.setAttributes(execAttr(),
ofItemAtPath: cloverDaemonNewPath)
let logouthookSrc = myPath.deletingLastPath.addPath(cloverLogOut.lastPath)
if fm.fileExists(atPath: cloverLogOut) {
try fm.removeItem(atPath: cloverLogOut)
}
if fm.fileExists(atPath: logouthookSrc) {
try fm.copyItem(atPath: logouthookSrc,
toPath: cloverLogOut)
try fm.setAttributes(execAttr(),
ofItemAtPath: cloverLogOut)
}
var needsLogoutHook = isLegacyFirmware()
if !needsLogoutHook {
if let nvram = getNVRAM() {
if (nvram.object(forKey: "EmuVariableUefiPresent") != nil ||
nvram.object(forKey: "TestEmuVariableUefiPresent") != nil) {
needsLogoutHook = true
}
}
}
if needsLogoutHook {
installHook()
} else {
unInstallHook()
}
try fm.setAttributes(launchAttr(), ofItemAtPath: launchPlistPath)
if fm.fileExists(atPath: launchPlistPath) {
run(cmd: "launchctl unload \(launchPlistPath)")
}
run(cmd: "launchctl load \(launchPlistPath)")
run(cmd: "launchctl start \(launchPlistPath)")
exit(EXIT_SUCCESS)
} catch {
print(error)
}
} else if CommandLine.arguments.contains("--uninstall") {
print("uninstalling daemon...")
do {
if fm.fileExists(atPath: launchPlistPath) {
run(cmd: "launchctl unload \(launchPlistPath)")
try fm.removeItem(atPath: launchPlistPath)
}
if fm.fileExists(atPath: cloverDaemonNewPath) {
try fm.removeItem(atPath: cloverDaemonNewPath)
}
if fm.fileExists(atPath: cloverLogOut) {
try fm.removeItem(atPath: cloverLogOut)
}
if fm.fileExists(atPath: wrapperPath) {
try fm.removeItem(atPath: wrapperPath)
}
if fm.fileExists(atPath: frameworksPath) {
try fm.removeItem(atPath: frameworksPath)
}
unInstallHook()
exit(EXIT_SUCCESS)
} catch {
print(error)
}
} else {
main()
}
exit(EXIT_FAILURE)