Implementation of app_usage_monitor

This commit is contained in:
Greg Neagle
2024-08-01 19:00:32 -07:00
parent c3d58a6a1e
commit 8fa1f97536
4 changed files with 544 additions and 35 deletions
+202
View File
@@ -0,0 +1,202 @@
//
// main.swift
// app_usage_monitor
//
// Created by Greg Neagle on 8/1/24.
//
// Copyright 2024 Greg Neagle.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import AppKit
import Foundation
enum AppUsageClientError: Error {
case socketError(code: UNIXDomainSocketClientErrorCode, description: String)
case taskError(description: String)
}
class AppUsageClient {
// handles communication with appusaged daemon
let APPUSAGED_SOCKET = "/var/run/appusaged"
let socket = UNIXDomainSocketClient()
func connect() throws {
// Connect to appusaged
socket.connect(to: APPUSAGED_SOCKET)
if socket.errCode != .noError {
throw AppUsageClientError.socketError(
code: socket.errCode,
description: "Failed to connect to \(APPUSAGED_SOCKET)"
)
}
}
func sendRequest(_ request: PlistDict) throws -> String {
// Send a request to appusaged
let requestStr = try plistToString(request)
socket.write(requestStr)
if socket.errCode != .noError {
throw AppUsageClientError.socketError(
code: socket.errCode,
description: "Failed to write to \(APPUSAGED_SOCKET)"
)
}
let reply = socket.read(timeout: 1)
if reply.isEmpty {
return "ERROR:No reply"
}
return reply.trimmingCharacters(in: .whitespacesAndNewlines)
}
func disconnect() {
// Disconnect from appusaged
socket.close()
}
func process(_ request: PlistDict) throws -> String {
// Send a request and return the result
try connect()
let result = try sendRequest(request)
disconnect()
return result
}
}
class NotificationHandler: NSObject {
// A subclass of NSObject to handle workspace notifications
let usage = AppUsageClient()
let wsNotificationCenter = NSWorkspace.shared.notificationCenter
let distributedNotificationCenter = DistributedNotificationCenter.default()
override init() {
super.init()
wsNotificationCenter.addObserver(
self,
selector: #selector(didLaunchApplicationNotification(_:)),
name: NSWorkspace.didLaunchApplicationNotification,
object: nil
)
wsNotificationCenter.addObserver(
self,
selector: #selector(didActivateApplicationNotification(_:)),
name: NSWorkspace.didActivateApplicationNotification,
object: nil
)
wsNotificationCenter.addObserver(
self,
selector: #selector(didTerminateApplicationNotification(_:)),
name: NSWorkspace.didTerminateApplicationNotification,
object: nil
)
wsNotificationCenter.addObserver(
self,
selector: #selector(requestedItemForInstall(_:)),
name: NSNotification.Name("com.googlecode.munki.managedsoftwareupdate.installrequest"),
object: nil
)
}
deinit {
// Unregister for all the notifications we registered for
wsNotificationCenter.removeObserver(self)
distributedNotificationCenter.removeObserver(self)
}
func getInfoDictForApp(_ appObject: NSRunningApplication) -> [String: String] {
// Returns a dict with info about an application.
// Args:
// appObject: NSRunningApplication object
// Returns:
// appDict: ["bundle_id": str,
// "path": str,
// "version": str]
var bundleID = ""
var appPath = ""
var appVersion = "0"
if let url = appObject.bundleURL {
appPath = url.path()
}
if let _bundleID = appObject.bundleIdentifier {
bundleID = _bundleID
} else if !appPath.isEmpty {
// use the base filename
bundleID = (appPath as NSString).lastPathComponent
}
if !appPath.isEmpty {
// try to get the version from the bundle's plist
if let appInfoPlist = NSDictionary(
contentsOfFile: (appPath as NSString).appendingPathComponent("Contents/Info.plist")) as? PlistDict
{
appVersion = appInfoPlist["CFBundleShortVersionString"] as? String ?? appInfoPlist["CFBundleVersion"] as? String ?? "0"
}
}
return ["bundle_id": bundleID,
"path": appPath,
"version": appVersion]
}
func process(event: String, notification: NSNotification) {
if let appObject = notification.userInfo?["NSWorkspaceApplicationKey"] as? NSRunningApplication {
let appDict = getInfoDictForApp(appObject)
let _ = try? usage.process(
["event": event,
"app_dict": appDict]
)
}
}
@objc func didLaunchApplicationNotification(_ notification: NSNotification) {
// Handle NSWorkspaceDidLaunchApplicationNotification
process(event: "launch", notification: notification)
}
@objc func didActivateApplicationNotification(_ notification: NSNotification) {
// Handle NSWorkspaceDidActivateApplicationNotification
process(event: "activate", notification: notification)
}
@objc func didTerminateApplicationNotification(_ notification: NSNotification) {
// Handle NSWorkspaceDidTerminateApplicationNotification
process(event: "quit", notification: notification)
}
@objc func requestedItemForInstall(_ notification: NSNotification) {
// Handle com.googlecode.munki.managedsoftwareupdate.installrequest
if let installInfo = notification.userInfo as? [String: String] {
let _ = try? usage.process(
["event": installInfo["event"] ?? "unknown",
"name": installInfo["name"] ?? "unknown",
"version": installInfo["version"] ?? "unknown"]
)
}
}
}
func main() {
// Initialize our handler object and let NSWorkspace's notification center
// know we are interested in notifications
let notificationHandler = NotificationHandler()
while true {
// listen for notifications forever
// give time to the runloop so we can actually get notifications
RunLoop.current.run(until: Date(timeIntervalSinceNow: 0.1))
}
}
// run it!
main()
+134 -33
View File
@@ -18,7 +18,6 @@
C013644B2C2F8EFE008DB215 /* GitFileRepo.swift in Sources */ = {isa = PBXBuildFile; fileRef = C01364482C2F8EFE008DB215 /* GitFileRepo.swift */; };
C013644E2C30F5D8008DB215 /* RepoFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = C013644C2C30F5D8008DB215 /* RepoFactory.swift */; };
C013644F2C30F5D8008DB215 /* RepoFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = C013644C2C30F5D8008DB215 /* RepoFactory.swift */; };
C01364522C311DFA008DB215 /* ArgumentParser in Frameworks */ = {isa = PBXBuildFile; productRef = C01364512C311DFA008DB215 /* ArgumentParser */; };
C01364542C321FE7008DB215 /* dmgutils.swift in Sources */ = {isa = PBXBuildFile; fileRef = C01364532C321FE7008DB215 /* dmgutils.swift */; };
C01364552C321FE7008DB215 /* dmgutils.swift in Sources */ = {isa = PBXBuildFile; fileRef = C01364532C321FE7008DB215 /* dmgutils.swift */; };
C01364582C3265D6008DB215 /* display.swift in Sources */ = {isa = PBXBuildFile; fileRef = C01364572C3265D6008DB215 /* display.swift */; };
@@ -26,7 +25,6 @@
C030A98F2C39C135007F0B34 /* munkihash.swift in Sources */ = {isa = PBXBuildFile; fileRef = C030A98E2C39C135007F0B34 /* munkihash.swift */; };
C030A9902C39C135007F0B34 /* munkihash.swift in Sources */ = {isa = PBXBuildFile; fileRef = C030A98E2C39C135007F0B34 /* munkihash.swift */; };
C030A9982C3A2E18007F0B34 /* makepkginfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = C030A9972C3A2E18007F0B34 /* makepkginfo.swift */; };
C030A99D2C3B12A6007F0B34 /* ArgumentParser in Frameworks */ = {isa = PBXBuildFile; productRef = C030A99C2C3B12A6007F0B34 /* ArgumentParser */; };
C030A9A12C3B4F5B007F0B34 /* pkginfoOptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C030A99E2C3B4F04007F0B34 /* pkginfoOptions.swift */; };
C030A9A22C3B4F63007F0B34 /* pkginfolib.swift in Sources */ = {isa = PBXBuildFile; fileRef = C07074E12C3489FA00B86310 /* pkginfolib.swift */; };
C030A9A32C3B4FBC007F0B34 /* utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = C07A6FD12C2B654300090743 /* utils.swift */; };
@@ -53,13 +51,11 @@
C030A9C12C419565007F0B34 /* osinstaller.swift in Sources */ = {isa = PBXBuildFile; fileRef = C030A9B12C3DB88E007F0B34 /* osinstaller.swift */; };
C030A9C22C41B556007F0B34 /* pkginfolib.swift in Sources */ = {isa = PBXBuildFile; fileRef = C07074E12C3489FA00B86310 /* pkginfolib.swift */; };
C030A9C32C41B581007F0B34 /* pkginfoOptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C030A99E2C3B4F04007F0B34 /* pkginfoOptions.swift */; };
C030A9C52C41B59F007F0B34 /* ArgumentParser in Frameworks */ = {isa = PBXBuildFile; productRef = C030A9C42C41B59F007F0B34 /* ArgumentParser */; };
C030A9C82C41F32E007F0B34 /* munkiimportOptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C030A9C62C41F32E007F0B34 /* munkiimportOptions.swift */; };
C030A9D02C41F798007F0B34 /* munkiimport.swift in Sources */ = {isa = PBXBuildFile; fileRef = C030A9CF2C41F798007F0B34 /* munkiimport.swift */; };
C030A9D42C41F849007F0B34 /* munkiimportOptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C030A9C62C41F32E007F0B34 /* munkiimportOptions.swift */; };
C030A9D52C41F897007F0B34 /* pkginfoOptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C030A99E2C3B4F04007F0B34 /* pkginfoOptions.swift */; };
C030A9D62C41F8CB007F0B34 /* utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = C07A6FD12C2B654300090743 /* utils.swift */; };
C030A9D82C41F8FC007F0B34 /* ArgumentParser in Frameworks */ = {isa = PBXBuildFile; productRef = C030A9D72C41F8FC007F0B34 /* ArgumentParser */; };
C030A9D92C424057007F0B34 /* prefs.swift in Sources */ = {isa = PBXBuildFile; fileRef = C07A6FAF2C2B22A400090743 /* prefs.swift */; };
C030A9DA2C42405D007F0B34 /* constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = C07A6FB12C2B22D300090743 /* constants.swift */; };
C030A9DB2C424065007F0B34 /* cliutils.swift in Sources */ = {isa = PBXBuildFile; fileRef = C07A6FD92C2CF19600090743 /* cliutils.swift */; };
@@ -107,7 +103,6 @@
C07A6FC72C2B5C0700090743 /* makecatalogs.swift in Sources */ = {isa = PBXBuildFile; fileRef = C07A6FC62C2B5C0700090743 /* makecatalogs.swift */; };
C07A6FCB2C2B5C3A00090743 /* prefs.swift in Sources */ = {isa = PBXBuildFile; fileRef = C07A6FAF2C2B22A400090743 /* prefs.swift */; };
C07A6FCC2C2B5C3D00090743 /* constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = C07A6FB12C2B22D300090743 /* constants.swift */; };
C07A6FCF2C2B62A600090743 /* ArgumentParser in Frameworks */ = {isa = PBXBuildFile; productRef = C07A6FCE2C2B62A600090743 /* ArgumentParser */; };
C07A6FD22C2B654300090743 /* utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = C07A6FD12C2B654300090743 /* utils.swift */; };
C07A6FD32C2B659300090743 /* utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = C07A6FD12C2B654300090743 /* utils.swift */; };
C07A6FD42C2B659400090743 /* utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = C07A6FD12C2B654300090743 /* utils.swift */; };
@@ -136,12 +131,17 @@
C0D00FB82C45BCB90021DA9C /* errors.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0D00FB52C45BCB90021DA9C /* errors.swift */; };
C0D00FB92C45BCB90021DA9C /* errors.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0D00FB52C45BCB90021DA9C /* errors.swift */; };
C0D00FBA2C45BCB90021DA9C /* errors.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0D00FB52C45BCB90021DA9C /* errors.swift */; };
C0D9C2462C5C5B370019A067 /* socketclient.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0D9C2452C5C5B370019A067 /* socketclient.swift */; };
C0D9C24E2C5C5C920019A067 /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0D9C24D2C5C5C920019A067 /* main.swift */; };
C0D9C2522C5C5D1F0019A067 /* errors.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0D00FB52C45BCB90021DA9C /* errors.swift */; };
C0D9C2532C5C5D4F0019A067 /* socketclient.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0D9C2452C5C5B370019A067 /* socketclient.swift */; };
C0D9C2542C5C5EBC0019A067 /* constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = C07A6FB12C2B22D300090743 /* constants.swift */; };
C0D9C2552C5C5EC00019A067 /* plistutils.swift in Sources */ = {isa = PBXBuildFile; fileRef = C01364422C2DD1BA008DB215 /* plistutils.swift */; };
C0E993F82C5BEDAB006FDF44 /* removepackages.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0E993F72C5BEDAB006FDF44 /* removepackages.swift */; };
C0E993FC2C5BEDE7006FDF44 /* rmpkgs.swift in Sources */ = {isa = PBXBuildFile; fileRef = C043ED222C483EEE0047C025 /* rmpkgs.swift */; };
C0E993FD2C5BEDFA006FDF44 /* display.swift in Sources */ = {isa = PBXBuildFile; fileRef = C01364572C3265D6008DB215 /* display.swift */; };
C0E993FE2C5BEE0D006FDF44 /* cliutils.swift in Sources */ = {isa = PBXBuildFile; fileRef = C07A6FD92C2CF19600090743 /* cliutils.swift */; };
C0E993FF2C5BEE1B006FDF44 /* fileutils.swift in Sources */ = {isa = PBXBuildFile; fileRef = C030A9B52C3DF6D0007F0B34 /* fileutils.swift */; };
C0E994012C5BEE35006FDF44 /* ArgumentParser in Frameworks */ = {isa = PBXBuildFile; productRef = C0E994002C5BEE35006FDF44 /* ArgumentParser */; };
C0E994022C5BF4BD006FDF44 /* sqlite3.swift in Sources */ = {isa = PBXBuildFile; fileRef = C043ED1E2C4822C70047C025 /* sqlite3.swift */; };
C0E994032C5BF4CF006FDF44 /* munkilog.swift in Sources */ = {isa = PBXBuildFile; fileRef = C07074DB2C33AE5F00B86310 /* munkilog.swift */; };
C0E994042C5BF4DC006FDF44 /* prefs.swift in Sources */ = {isa = PBXBuildFile; fileRef = C07A6FAF2C2B22A400090743 /* prefs.swift */; };
@@ -151,6 +151,12 @@
C0E994082C5BF53D006FDF44 /* errors.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0D00FB52C45BCB90021DA9C /* errors.swift */; };
C0E994092C5BF54E006FDF44 /* utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = C07A6FD12C2B654300090743 /* utils.swift */; };
C0E9940A2C5BF56C006FDF44 /* version.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0D00FAF2C458EAA0021DA9C /* version.swift */; };
C0E9941A2C5C10E1006FDF44 /* ArgumentParser in Frameworks */ = {isa = PBXBuildFile; productRef = C0E994192C5C10E1006FDF44 /* ArgumentParser */; };
C0E9941C2C5C10F8006FDF44 /* ArgumentParser in Frameworks */ = {isa = PBXBuildFile; productRef = C0E9941B2C5C10F8006FDF44 /* ArgumentParser */; };
C0E9941E2C5C10FE006FDF44 /* ArgumentParser in Frameworks */ = {isa = PBXBuildFile; productRef = C0E9941D2C5C10FE006FDF44 /* ArgumentParser */; };
C0E994202C5C1103006FDF44 /* ArgumentParser in Frameworks */ = {isa = PBXBuildFile; productRef = C0E9941F2C5C1103006FDF44 /* ArgumentParser */; };
C0E994222C5C1109006FDF44 /* ArgumentParser in Frameworks */ = {isa = PBXBuildFile; productRef = C0E994212C5C1109006FDF44 /* ArgumentParser */; };
C0E994242C5C110E006FDF44 /* ArgumentParser in Frameworks */ = {isa = PBXBuildFile; productRef = C0E994232C5C110E006FDF44 /* ArgumentParser */; };
/* End PBXBuildFile section */
/* Begin PBXCopyFilesBuildPhase section */
@@ -199,6 +205,15 @@
);
runOnlyForDeploymentPostprocessing = 1;
};
C0D9C2492C5C5C920019A067 /* CopyFiles */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = /usr/share/man/man1/;
dstSubfolderSpec = 0;
files = (
);
runOnlyForDeploymentPostprocessing = 1;
};
C0E993F32C5BEDAB006FDF44 /* CopyFiles */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
@@ -255,6 +270,9 @@
C0D00FA72C45814F0021DA9C /* repoutils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = repoutils.swift; sourceTree = "<group>"; };
C0D00FAF2C458EAA0021DA9C /* version.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = version.swift; sourceTree = "<group>"; };
C0D00FB52C45BCB90021DA9C /* errors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = errors.swift; sourceTree = "<group>"; };
C0D9C2452C5C5B370019A067 /* socketclient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = socketclient.swift; sourceTree = "<group>"; };
C0D9C24B2C5C5C920019A067 /* app_usage_monitor */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = app_usage_monitor; sourceTree = BUILT_PRODUCTS_DIR; };
C0D9C24D2C5C5C920019A067 /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = "<group>"; };
C0E993F52C5BEDAB006FDF44 /* removepackages */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = removepackages; sourceTree = BUILT_PRODUCTS_DIR; };
C0E993F72C5BEDAB006FDF44 /* removepackages.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = removepackages.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */
@@ -264,7 +282,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
C030A99D2C3B12A6007F0B34 /* ArgumentParser in Frameworks */,
C0E994202C5C1103006FDF44 /* ArgumentParser in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -272,8 +290,8 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
C0E994222C5C1109006FDF44 /* ArgumentParser in Frameworks */,
C030A9F42C435113007F0B34 /* libedit.tbd in Frameworks */,
C030A9D82C41F8FC007F0B34 /* ArgumentParser in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -281,7 +299,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
C01364522C311DFA008DB215 /* ArgumentParser in Frameworks */,
C0E9941C2C5C10F8006FDF44 /* ArgumentParser in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -289,7 +307,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
C030A9C52C41B59F007F0B34 /* ArgumentParser in Frameworks */,
C0E9941A2C5C10E1006FDF44 /* ArgumentParser in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -297,7 +315,14 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
C07A6FCF2C2B62A600090743 /* ArgumentParser in Frameworks */,
C0E9941E2C5C10FE006FDF44 /* ArgumentParser in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
C0D9C2482C5C5C920019A067 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -305,7 +330,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
C0E994012C5BEE35006FDF44 /* ArgumentParser in Frameworks */,
C0E994242C5C110E006FDF44 /* ArgumentParser in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -379,6 +404,7 @@
C030A9962C3A2E18007F0B34 /* makepkginfo */,
C030A9CE2C41F798007F0B34 /* munkiimport */,
C0E993F62C5BEDAB006FDF44 /* removepackages */,
C0D9C24C2C5C5C920019A067 /* app_usage_monitor */,
C07A6FA62C2A82B400090743 /* Products */,
C01364502C311DFA008DB215 /* Frameworks */,
);
@@ -393,6 +419,7 @@
C030A9952C3A2E18007F0B34 /* makepkginfo */,
C030A9CD2C41F798007F0B34 /* munkiimport */,
C0E993F52C5BEDAB006FDF44 /* removepackages */,
C0D9C24B2C5C5C920019A067 /* app_usage_monitor */,
);
name = Products;
sourceTree = "<group>";
@@ -448,10 +475,19 @@
C0D00FAF2C458EAA0021DA9C /* version.swift */,
C0D00FB52C45BCB90021DA9C /* errors.swift */,
C043ED1E2C4822C70047C025 /* sqlite3.swift */,
C0D9C2452C5C5B370019A067 /* socketclient.swift */,
);
path = shared;
sourceTree = "<group>";
};
C0D9C24C2C5C5C920019A067 /* app_usage_monitor */ = {
isa = PBXGroup;
children = (
C0D9C24D2C5C5C920019A067 /* main.swift */,
);
path = app_usage_monitor;
sourceTree = "<group>";
};
C0E993F62C5BEDAB006FDF44 /* removepackages */ = {
isa = PBXGroup;
children = (
@@ -477,7 +513,7 @@
);
name = makepkginfo;
packageProductDependencies = (
C030A99C2C3B12A6007F0B34 /* ArgumentParser */,
C0E9941F2C5C1103006FDF44 /* ArgumentParser */,
);
productName = makepkginfo;
productReference = C030A9952C3A2E18007F0B34 /* makepkginfo */;
@@ -497,7 +533,7 @@
);
name = munkiimport;
packageProductDependencies = (
C030A9D72C41F8FC007F0B34 /* ArgumentParser */,
C0E994212C5C1109006FDF44 /* ArgumentParser */,
);
productName = munkiimport;
productReference = C030A9CD2C41F798007F0B34 /* munkiimport */;
@@ -517,7 +553,7 @@
);
name = managedsoftwareupdate;
packageProductDependencies = (
C01364512C311DFA008DB215 /* ArgumentParser */,
C0E9941B2C5C10F8006FDF44 /* ArgumentParser */,
);
productName = munki;
productReference = C07A6FA52C2A82B400090743 /* managedsoftwareupdate */;
@@ -537,7 +573,7 @@
);
name = munkitester;
packageProductDependencies = (
C030A9C42C41B59F007F0B34 /* ArgumentParser */,
C0E994192C5C10E1006FDF44 /* ArgumentParser */,
);
productName = munkitester;
productReference = C07A6FB72C2B5ADE00090743 /* munkitester */;
@@ -557,12 +593,29 @@
);
name = makecatalogs;
packageProductDependencies = (
C07A6FCE2C2B62A600090743 /* ArgumentParser */,
C0E9941D2C5C10FE006FDF44 /* ArgumentParser */,
);
productName = makecatalogs;
productReference = C07A6FC42C2B5C0700090743 /* makecatalogs */;
productType = "com.apple.product-type.tool";
};
C0D9C24A2C5C5C920019A067 /* app_usage_monitor */ = {
isa = PBXNativeTarget;
buildConfigurationList = C0D9C24F2C5C5C920019A067 /* Build configuration list for PBXNativeTarget "app_usage_monitor" */;
buildPhases = (
C0D9C2472C5C5C920019A067 /* Sources */,
C0D9C2482C5C5C920019A067 /* Frameworks */,
C0D9C2492C5C5C920019A067 /* CopyFiles */,
);
buildRules = (
);
dependencies = (
);
name = app_usage_monitor;
productName = app_usage_monitor;
productReference = C0D9C24B2C5C5C920019A067 /* app_usage_monitor */;
productType = "com.apple.product-type.tool";
};
C0E993F42C5BEDAB006FDF44 /* removepackages */ = {
isa = PBXNativeTarget;
buildConfigurationList = C0E993FB2C5BEDAB006FDF44 /* Build configuration list for PBXNativeTarget "removepackages" */;
@@ -577,7 +630,7 @@
);
name = removepackages;
packageProductDependencies = (
C0E994002C5BEE35006FDF44 /* ArgumentParser */,
C0E994232C5C110E006FDF44 /* ArgumentParser */,
);
productName = removepackages;
productReference = C0E993F52C5BEDAB006FDF44 /* removepackages */;
@@ -610,6 +663,9 @@
C07A6FC32C2B5C0700090743 = {
CreatedOnToolsVersion = 15.4;
};
C0D9C24A2C5C5C920019A067 = {
CreatedOnToolsVersion = 15.4;
};
C0E993F42C5BEDAB006FDF44 = {
CreatedOnToolsVersion = 15.4;
};
@@ -625,7 +681,7 @@
);
mainGroup = C07A6F9C2C2A82B400090743;
packageReferences = (
C07A6FCD2C2B62A600090743 /* XCRemoteSwiftPackageReference "swift-argument-parser" */,
C0E994182C5C10E1006FDF44 /* XCRemoteSwiftPackageReference "swift-argument-parser" */,
);
productRefGroup = C07A6FA62C2A82B400090743 /* Products */;
projectDirPath = "";
@@ -637,6 +693,7 @@
C030A9942C3A2E18007F0B34 /* makepkginfo */,
C030A9CC2C41F798007F0B34 /* munkiimport */,
C0E993F42C5BEDAB006FDF44 /* removepackages */,
C0D9C24A2C5C5C920019A067 /* app_usage_monitor */,
);
};
/* End PBXProject section */
@@ -744,6 +801,7 @@
C0D00FB72C45BCB90021DA9C /* errors.swift in Sources */,
C030A9C82C41F32E007F0B34 /* munkiimportOptions.swift in Sources */,
C030A9C02C409738007F0B34 /* iconutils.swift in Sources */,
C0D9C2462C5C5B370019A067 /* socketclient.swift in Sources */,
C030A9F62C435183007F0B34 /* readline.swift in Sources */,
C0D00FB12C458EAA0021DA9C /* version.swift in Sources */,
C030A9C32C41B581007F0B34 /* pkginfoOptions.swift in Sources */,
@@ -800,6 +858,18 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
C0D9C2472C5C5C920019A067 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
C0D9C2522C5C5D1F0019A067 /* errors.swift in Sources */,
C0D9C24E2C5C5C920019A067 /* main.swift in Sources */,
C0D9C2532C5C5D4F0019A067 /* socketclient.swift in Sources */,
C0D9C2542C5C5EBC0019A067 /* constants.swift in Sources */,
C0D9C2552C5C5EC00019A067 /* plistutils.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
C0E993F12C5BEDAB006FDF44 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
@@ -1086,6 +1156,28 @@
};
name = Release;
};
C0D9C2502C5C5C920019A067 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = 52ZFZKM6BK;
ENABLE_HARDENED_RUNTIME = YES;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
};
name = Debug;
};
C0D9C2512C5C5C920019A067 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = 52ZFZKM6BK;
ENABLE_HARDENED_RUNTIME = YES;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
};
name = Release;
};
C0E993F92C5BEDAB006FDF44 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
@@ -1165,6 +1257,15 @@
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
C0D9C24F2C5C5C920019A067 /* Build configuration list for PBXNativeTarget "app_usage_monitor" */ = {
isa = XCConfigurationList;
buildConfigurations = (
C0D9C2502C5C5C920019A067 /* Debug */,
C0D9C2512C5C5C920019A067 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
C0E993FB2C5BEDAB006FDF44 /* Build configuration list for PBXNativeTarget "removepackages" */ = {
isa = XCConfigurationList;
buildConfigurations = (
@@ -1177,45 +1278,45 @@
/* End XCConfigurationList section */
/* Begin XCRemoteSwiftPackageReference section */
C07A6FCD2C2B62A600090743 /* XCRemoteSwiftPackageReference "swift-argument-parser" */ = {
C0E994182C5C10E1006FDF44 /* XCRemoteSwiftPackageReference "swift-argument-parser" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/apple/swift-argument-parser.git";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 1.4.0;
minimumVersion = 1.5.0;
};
};
/* End XCRemoteSwiftPackageReference section */
/* Begin XCSwiftPackageProductDependency section */
C01364512C311DFA008DB215 /* ArgumentParser */ = {
C0E994192C5C10E1006FDF44 /* ArgumentParser */ = {
isa = XCSwiftPackageProductDependency;
package = C07A6FCD2C2B62A600090743 /* XCRemoteSwiftPackageReference "swift-argument-parser" */;
package = C0E994182C5C10E1006FDF44 /* XCRemoteSwiftPackageReference "swift-argument-parser" */;
productName = ArgumentParser;
};
C030A99C2C3B12A6007F0B34 /* ArgumentParser */ = {
C0E9941B2C5C10F8006FDF44 /* ArgumentParser */ = {
isa = XCSwiftPackageProductDependency;
package = C07A6FCD2C2B62A600090743 /* XCRemoteSwiftPackageReference "swift-argument-parser" */;
package = C0E994182C5C10E1006FDF44 /* XCRemoteSwiftPackageReference "swift-argument-parser" */;
productName = ArgumentParser;
};
C030A9C42C41B59F007F0B34 /* ArgumentParser */ = {
C0E9941D2C5C10FE006FDF44 /* ArgumentParser */ = {
isa = XCSwiftPackageProductDependency;
package = C07A6FCD2C2B62A600090743 /* XCRemoteSwiftPackageReference "swift-argument-parser" */;
package = C0E994182C5C10E1006FDF44 /* XCRemoteSwiftPackageReference "swift-argument-parser" */;
productName = ArgumentParser;
};
C030A9D72C41F8FC007F0B34 /* ArgumentParser */ = {
C0E9941F2C5C1103006FDF44 /* ArgumentParser */ = {
isa = XCSwiftPackageProductDependency;
package = C07A6FCD2C2B62A600090743 /* XCRemoteSwiftPackageReference "swift-argument-parser" */;
package = C0E994182C5C10E1006FDF44 /* XCRemoteSwiftPackageReference "swift-argument-parser" */;
productName = ArgumentParser;
};
C07A6FCE2C2B62A600090743 /* ArgumentParser */ = {
C0E994212C5C1109006FDF44 /* ArgumentParser */ = {
isa = XCSwiftPackageProductDependency;
package = C07A6FCD2C2B62A600090743 /* XCRemoteSwiftPackageReference "swift-argument-parser" */;
package = C0E994182C5C10E1006FDF44 /* XCRemoteSwiftPackageReference "swift-argument-parser" */;
productName = ArgumentParser;
};
C0E994002C5BEE35006FDF44 /* ArgumentParser */ = {
C0E994232C5C110E006FDF44 /* ArgumentParser */ = {
isa = XCSwiftPackageProductDependency;
package = C07A6FCD2C2B62A600090743 /* XCRemoteSwiftPackageReference "swift-argument-parser" */;
package = C0E994182C5C10E1006FDF44 /* XCRemoteSwiftPackageReference "swift-argument-parser" */;
productName = ArgumentParser;
};
/* End XCSwiftPackageProductDependency section */
@@ -6,8 +6,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-argument-parser.git",
"state" : {
"revision" : "0fbc8848e389af3bb55c182bc19ca9d5dc2f255b",
"version" : "1.4.0"
"revision" : "41982a3656a71c768319979febd796c6fd111d5c",
"version" : "1.5.0"
}
}
],
+206
View File
@@ -0,0 +1,206 @@
//
// socketclient.swift
//
// Created by Greg Neagle on 7/23/18.
// Copyright © 2018-2024 The Munki Project. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import CoreFoundation
import Darwin
import Foundation
enum UNIXDomainSocketClientErrorCode: Int {
case noError = 0, addressError, createError, socketError, connectError, readError, writeError, timeoutError
}
class UNIXDomainSocketClient {
// A basic implementation of Unix domain sockets for MSC
// We use CFSocket calls when we can, and fallback to Darwin (BSD/C) API
// when we must.
var socketRef: CFSocket?
var errCode: UNIXDomainSocketClientErrorCode = .noError
func close() {
// close the socket if it exists
if let socket = socketRef {
CFSocketInvalidate(socket)
socketRef = nil
}
}
private func addrRefCreate(_ path: String) -> CFData? {
// make a sockaddr struct (this is ugly), wrap it in a CFData obj
var socketAdr = sockaddr_un()
socketAdr.sun_family = sa_family_t(AF_UNIX)
socketAdr.sun_len = __uint8_t(MemoryLayout<sockaddr_un>.size)
if var cstring_path = path.cString(using: .utf8) {
if cstring_path.count > MemoryLayout.size(
ofValue: socketAdr.sun_path)
{
// path is too long for this 1970s era struct
return nil
}
memcpy(&socketAdr.sun_path, &cstring_path, cstring_path.count)
} else {
return nil
}
return NSData(bytes: &socketAdr,
length: MemoryLayout.size(ofValue: socketAdr)) as CFData
}
func connect(to path: String) {
// Create a UNIX domain socket object and connect
//
// get a CFData reference to our socket path
guard let adrDataRef = addrRefCreate(path) else {
errCode = .addressError
return
}
// create a CFSocket object
guard let socket = CFSocketCreate(kCFAllocatorDefault,
PF_UNIX,
SOCK_STREAM,
0,
0,
nil,
nil)
else {
errCode = .createError
return
}
// connect
guard CFSocketConnectToAddress(socket,
adrDataRef,
1) == .success
else {
errCode = .connectError
return
}
// save the socket obj for later use
socketRef = socket
}
func write(_ text: String) {
// send text data to our socket
//
// ensure we have a non-nil socketRef
guard let socket = socketRef else {
errCode = .socketError
return
}
if text.count == 0 || errCode != .noError {
return
}
// make a CFData reference from our text data
guard let data = text.data(using: .utf8) as CFData? else {
errCode = .writeError
return
}
errCode = .noError
guard CFSocketSendData(socket, nil, data, 30) == .success else {
errCode = .writeError
return
}
}
private func fdSet(_ fd: Int32, set: inout fd_set) {
// Replacement for FD_SET macro
let intOffset = Int(fd / 32)
let bitOffset = fd % 32
let mask = Int32(1 << bitOffset)
switch intOffset {
case 0: set.fds_bits.0 = set.fds_bits.0 | mask
case 1: set.fds_bits.1 = set.fds_bits.1 | mask
case 2: set.fds_bits.2 = set.fds_bits.2 | mask
case 3: set.fds_bits.3 = set.fds_bits.3 | mask
case 4: set.fds_bits.4 = set.fds_bits.4 | mask
case 5: set.fds_bits.5 = set.fds_bits.5 | mask
case 6: set.fds_bits.6 = set.fds_bits.6 | mask
case 7: set.fds_bits.7 = set.fds_bits.7 | mask
case 8: set.fds_bits.8 = set.fds_bits.8 | mask
case 9: set.fds_bits.9 = set.fds_bits.9 | mask
case 10: set.fds_bits.10 = set.fds_bits.10 | mask
case 11: set.fds_bits.11 = set.fds_bits.11 | mask
case 12: set.fds_bits.12 = set.fds_bits.12 | mask
case 13: set.fds_bits.13 = set.fds_bits.13 | mask
case 14: set.fds_bits.14 = set.fds_bits.14 | mask
case 15: set.fds_bits.15 = set.fds_bits.15 | mask
case 16: set.fds_bits.16 = set.fds_bits.16 | mask
case 17: set.fds_bits.17 = set.fds_bits.17 | mask
case 18: set.fds_bits.18 = set.fds_bits.18 | mask
case 19: set.fds_bits.19 = set.fds_bits.19 | mask
case 20: set.fds_bits.20 = set.fds_bits.20 | mask
case 21: set.fds_bits.21 = set.fds_bits.21 | mask
case 22: set.fds_bits.22 = set.fds_bits.22 | mask
case 23: set.fds_bits.23 = set.fds_bits.23 | mask
case 24: set.fds_bits.24 = set.fds_bits.24 | mask
case 25: set.fds_bits.25 = set.fds_bits.25 | mask
case 26: set.fds_bits.26 = set.fds_bits.26 | mask
case 27: set.fds_bits.27 = set.fds_bits.27 | mask
case 28: set.fds_bits.28 = set.fds_bits.28 | mask
case 29: set.fds_bits.29 = set.fds_bits.29 | mask
case 30: set.fds_bits.30 = set.fds_bits.30 | mask
case 31: set.fds_bits.31 = set.fds_bits.31 | mask
default: break
}
}
private func dataAvailable(timeout: Int = 10) -> Bool {
// uses POSIX select() to wait for data to be available on the socket
//
// ensure we have a non-nil socketRef
guard let socket = socketRef else {
errCode = .socketError
return false
}
var timer = timeval()
timer.tv_sec = timeout
timer.tv_usec = 0
let socket_fd = CFSocketGetNative(socket)
var readfds = fd_set(fds_bits: (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0))
fdSet(socket_fd, set: &readfds)
let result = select(socket_fd + 1, &readfds, nil, nil, &timer)
return result > 0
}
func read(maxsize: Int = 1024, timeout: Int = 10) -> String {
// read a message from our socket
// there's no CFSocketRead method, use BSD socket recv method instead
//
// ensure we have a non-nil socketRef
guard let socket = socketRef else {
errCode = .socketError
return ""
}
// wait up until timeout seconds for data to become available
if !dataAvailable(timeout: timeout) {
errCode = .timeoutError
return ""
}
// allocate some space for the return message.
let buffer = UnsafeMutablePointer<UInt8>.allocate(capacity: maxsize)
defer { buffer.deallocate() }
// read from socket
let msg_len = recv(CFSocketGetNative(socket), buffer, maxsize, 0)
if let msg = NSString(bytes: buffer,
length: msg_len,
encoding: String.Encoding.utf8.rawValue) as String?
{
return msg
}
errCode = .readError
return ""
}
}