More efficient methods to get installed package information for processing removals. Speeds up generating the receipt database.

This commit is contained in:
Greg Neagle
2026-01-09 15:59:53 -08:00
parent 85c79b8ca0
commit cf708d87fd

View File

@@ -201,33 +201,24 @@ func insertFileInfoIntoPkgDB(
}
}
/// Gets info about pkg from pkgutil
func getPkgMetaData(_ pkg: String) async throws -> PkgData {
let result = await runCliAsync(
"/usr/sbin/pkgutil", arguments: ["--pkg-info-plist", pkg]
)
if result.exitcode != 0 {
throw MunkiError("Error calling pkgutil: \(result.error)")
}
let (pliststr, _) = parseFirstPlist(fromString: result.output)
guard let plist = try readPlist(fromString: pliststr) as? PlistDict else {
throw MunkiError("Could not parse expected data from pkgutil")
}
/// Given a plist generated by `pkgutil --pkg-info-plist`,
/// returns a PkgData structure
func getPkgMetaData(_ pkg: PlistDict) throws -> PkgData {
var timestamp = 0
var version = "0"
var ppath = ""
guard let pkgid = plist["pkgid"] as? String else {
guard let pkgid = pkg["pkgid"] as? String else {
// something terribly wrong
throw MunkiError("Could not parse expected data from pkgutil")
throw MunkiError("Could not parse expected info data from pkgutil")
}
if let pkgVersion = plist["pkg-version"] as? String {
if let pkgVersion = pkg["pkg-version"] as? String {
version = pkgVersion
}
if let installTime = plist["install-time"] as? Int {
if let installTime = pkg["install-time"] as? Int {
timestamp = installTime
}
if let installLocation = plist["install-location"] as? String {
if let installLocation = pkg["install-location"] as? String {
ppath = installLocation
if ppath.hasPrefix("./") {
ppath.removeFirst(2)
@@ -241,31 +232,82 @@ func getPkgMetaData(_ pkg: String) async throws -> PkgData {
)
}
/// Returns a list of files installed by pkg
func getFilesForPkg(_ pkg: String) async throws -> [String] {
let result = await runCliAsync("/usr/sbin/pkgutil", arguments: ["--files", pkg])
/// Uses lsbom to get a list of files for pkgid
func getBomFilesForPkg(_ pkgid: String) -> [String]? {
let bomPath = "/private/var/db/receipts/\(pkgid).bom"
if !pathExists(bomPath) {
return nil
}
let result = runCLI(
"/usr/bin/lsbom", arguments: ["-s", bomPath]
)
if result.exitcode != 0 {
throw MunkiError("Error calling pkgutil: \(result.error)")
return nil
}
var filelist = result.output.components(separatedBy: "\n").filter { !$0.isEmpty && $0 != "." }
for i in 0 ..< filelist.count {
if filelist[i].hasPrefix("./") {
filelist[i].removeFirst(2)
}
}
return filelist
}
/// Returns a list of files installed by pkg
func getFilesForPkg(_ pkgid: String) async throws -> [String] {
if !pkgid.hasPrefix("com.apple.") {
// try to use lsbom to get the list of files
// (this is faster than using pkgutil)
if let bomFileList = getBomFilesForPkg(pkgid) {
return bomFileList
}
}
// use pkgutil to get the list of files
let result = try await runCliAsync("/usr/sbin/pkgutil", arguments: ["--files", pkgid], timeout: 10)
if result.exitcode != 0 {
throw MunkiError(
"Error calling pkgutil to get list of files for \(pkgid): \(result.error)"
)
}
return result.output.components(separatedBy: "\n").filter { !$0.isEmpty }
}
/// Adds metadata for pkgid to our database
func getPkgDataAndAddtoDB(pkgid: String) async throws {
async let tempPkgdata = try getPkgMetaData(pkgid)
async let fileList = try getFilesForPkg(pkgid)
var pkgdata = try await tempPkgdata
pkgdata.files = try await fileList
func getPkgDataAndAddtoDB(pkg: PlistDict) async throws {
var pkgdata = try getPkgMetaData(pkg)
let fileList = try await getFilesForPkg(pkgdata.pkgid)
pkgdata.files = fileList
try insertPkgDataIntoPkgDB(pkgdata: pkgdata)
}
/// Imports package data from pkgutil into our internal package database.
func importFromPkgutil() async throws {
let result = await runCliAsync("/usr/sbin/pkgutil", arguments: ["--pkgs"])
if result.exitcode != 0 {
throw MunkiError("Error calling pkgutil: \(result.error)")
var pkglist = [PlistDict]()
// get info plists about all installed pkgs
let results = await runCliAsync(
"/usr/sbin/pkgutil", arguments: ["--regexp", "--pkg-info-plist", ".*"]
)
if results.exitcode != 0 {
throw MunkiError("Error calling pkgutil to get info plists for all packages: \(results.error)")
}
let pkglist = result.output.components(separatedBy: "\n").filter { !$0.isEmpty }
// output is multiple XML-formatted plists, concatenated
// iterate through the output, processing each plist
var out = results.output
while !out.isEmpty {
let (pliststr, tempOut) = parseFirstPlist(fromString: out)
out = tempOut
if pliststr.isEmpty {
break
}
if let plist = try? readPlist(fromString: pliststr) as? PlistDict {
if plist["pkgid"] as? String != nil,
plist["pkg-version"] as? String != nil
{
pkglist.append(plist)
}
}
}
// import each package into the receipt database
let pkgCount = pkglist.count
var current = 0
displayPercentDone(current: current, maximum: pkgCount)
@@ -274,8 +316,9 @@ func importFromPkgutil() async throws {
throw UserCancelled()
}
current += 1
display.detail("Importing \(pkg)...")
try await getPkgDataAndAddtoDB(pkgid: pkg)
let pkgid = pkg["pkgid"] as? String ?? "<unknown>"
display.detail("Importing \(pkgid)...")
try await getPkgDataAndAddtoDB(pkg: pkg)
displayPercentDone(current: current, maximum: pkgCount)
}
}
@@ -372,7 +415,7 @@ func forgetPkgFromAppleDB(_ pkgid: String) {
display.detail(result.output)
}
} else {
// maybe a warning?
display.warning("pkgutil --forget for \(pkgid) returned an error: \(result.error)")
}
}
@@ -490,7 +533,7 @@ func removeFilesystemItems(pathsToRemove: [String], forceDeleteBundles: Bool) {
itemIndex += 1
let pathToRemove = "/" + item
if pathIsRegularFile(pathToRemove) || pathIsSymlink(pathToRemove) {
display.detail("Removing : \(pathToRemove)")
display.detail("Removing: \(pathToRemove)")
removeItemOrRecordError(pathToRemove)
continue
}