mirror of
https://github.com/munki/munki.git
synced 2026-05-04 03:20:19 -05:00
Attempt to address #1264
This commit is contained in:
@@ -50,6 +50,7 @@
|
||||
C0C5DF6C20F5C27700CA0687 /* MSCStatusController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0C5DF6B20F5C27700CA0687 /* MSCStatusController.swift */; };
|
||||
C0D30BEB20CA445A005E876E /* MunkiItems.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0D30BEA20CA445A005E876E /* MunkiItems.swift */; };
|
||||
C0E2599E210AD8CE00C3A3D9 /* Socket.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0E2599D210AD8CD00C3A3D9 /* Socket.swift */; };
|
||||
C0E8F96B2E6A3FB8003A298C /* UNIXProcessInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0E8F96A2E6A3FB8003A298C /* UNIXProcessInfo.swift */; };
|
||||
C0FBF1BD20D4945B00FAD1BE /* mschtml.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0FBF1BC20D4945B00FAD1BE /* mschtml.swift */; };
|
||||
C0FBF1BF20D4A0BB00FAD1BE /* Template.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0FBF1BE20D4A0BB00FAD1BE /* Template.swift */; };
|
||||
C0FD710A20D485A80018A002 /* HtmlFilter.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0FD710920D485A80018A002 /* HtmlFilter.swift */; };
|
||||
@@ -140,6 +141,7 @@
|
||||
C0C5DF6B20F5C27700CA0687 /* MSCStatusController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MSCStatusController.swift; sourceTree = "<group>"; };
|
||||
C0D30BEA20CA445A005E876E /* MunkiItems.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MunkiItems.swift; sourceTree = "<group>"; };
|
||||
C0E2599D210AD8CD00C3A3D9 /* Socket.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Socket.swift; sourceTree = "<group>"; };
|
||||
C0E8F96A2E6A3FB8003A298C /* UNIXProcessInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UNIXProcessInfo.swift; sourceTree = "<group>"; };
|
||||
C0F8E3DF2105622F00718259 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
C0F8E3E221057A1500718259 /* da */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = da; path = da.lproj/MainMenu.strings; sourceTree = "<group>"; };
|
||||
C0F8E3E421057A2D00718259 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/MainMenu.strings; sourceTree = "<group>"; };
|
||||
@@ -237,6 +239,7 @@
|
||||
C04F827D20BB319B00F9C57D /* Managed Software Center */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
C0E8F96A2E6A3FB8003A298C /* UNIXProcessInfo.swift */,
|
||||
8428C7F12E0DB23200D83D41 /* XIBs */,
|
||||
C0E2599D210AD8CD00C3A3D9 /* Socket.swift */,
|
||||
C0F8E3DF2105622F00718259 /* Info.plist */,
|
||||
@@ -484,6 +487,7 @@
|
||||
C0BCAD7D2442A0B3001D2FDD /* appleupdates.swift in Sources */,
|
||||
C04CA61E20E6ADA100711461 /* MainWindowController.swift in Sources */,
|
||||
8428C7E82E0CB55000D83D41 /* MainSplitViewController.swift in Sources */,
|
||||
C0E8F96B2E6A3FB8003A298C /* UNIXProcessInfo.swift in Sources */,
|
||||
C01603CF20CF8B6100DEF9E4 /* iconutils.swift in Sources */,
|
||||
56C33E812174D7A200D727DC /* MSCTableCellView.swift in Sources */,
|
||||
8428C7F02E0DABC700D83D41 /* LogWindowController.swift in Sources */,
|
||||
|
||||
+1
-2
@@ -69,7 +69,6 @@ class MSCStatusController: NSObject {
|
||||
// Monitors managedsoftwareupdate process for failure to start
|
||||
// or unexpected exit, so we're not waiting around forever if
|
||||
// managedsoftwareupdate isn't running.
|
||||
let PYTHON_SCRIPT_NAME = "managedsoftwareupdate"
|
||||
let NEVER_STARTED = -2
|
||||
let UNEXPECTEDLY_QUIT = -1
|
||||
if !session_started {
|
||||
@@ -82,7 +81,7 @@ class MSCStatusController: NSObject {
|
||||
saw_process = true
|
||||
// clear the flag so we have to get another status update
|
||||
got_status_update = false
|
||||
} else if pythonScriptRunning(PYTHON_SCRIPT_NAME) {
|
||||
} else if managedsoftwareupdateInstanceRunning() != nil {
|
||||
timeout_counter = 6
|
||||
saw_process = true
|
||||
} else {
|
||||
|
||||
@@ -0,0 +1,198 @@
|
||||
//
|
||||
// UNIXProcessInfo.swift
|
||||
// munki
|
||||
//
|
||||
// Created by Greg Neagle on 9/4/24.
|
||||
//
|
||||
// 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 Darwin
|
||||
import Foundation
|
||||
|
||||
struct UNIXProcessInfo {
|
||||
let pid: Int32
|
||||
let ppid: Int32
|
||||
let uid: UInt32
|
||||
let command: String
|
||||
}
|
||||
|
||||
/// Returns a list of running processes
|
||||
func UNIXProcessList() -> [UNIXProcessInfo] {
|
||||
var list = [UNIXProcessInfo]()
|
||||
var mib: [Int32] = [CTL_KERN, KERN_PROC, KERN_PROC_ALL, 0]
|
||||
var size = 0
|
||||
|
||||
// First need to need size of process list array
|
||||
var result = sysctl(&mib, u_int(mib.count), nil, &size, nil, 0)
|
||||
assert(result == KERN_SUCCESS)
|
||||
|
||||
// Get process list
|
||||
let procCount = size / MemoryLayout<kinfo_proc>.stride
|
||||
var kinfoList = [kinfo_proc](repeating: kinfo_proc(), count: procCount)
|
||||
result = sysctl(&mib, u_int(mib.count), &kinfoList, &size, nil, 0)
|
||||
assert(result == KERN_SUCCESS)
|
||||
|
||||
for task in kinfoList {
|
||||
var kinfo = task
|
||||
let command = withUnsafePointer(to: &kinfo.kp_proc.p_comm) {
|
||||
String(cString: UnsafeRawPointer($0).assumingMemoryBound(to: CChar.self))
|
||||
}
|
||||
list.append(
|
||||
UNIXProcessInfo(
|
||||
pid: kinfo.kp_proc.p_pid,
|
||||
ppid: kinfo.kp_eproc.e_ppid,
|
||||
uid: kinfo.kp_eproc.e_ucred.cr_uid,
|
||||
command: command
|
||||
)
|
||||
)
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
/// Returns a list of running processes with the given parent pid
|
||||
func processesWithPPID(_ ppid: Int32) -> [UNIXProcessInfo] {
|
||||
let list = UNIXProcessList()
|
||||
return list.filter { $0.ppid == ppid }
|
||||
}
|
||||
|
||||
/// Gets the (raw) process argument data
|
||||
func argumentData(for pid: pid_t) -> Data? {
|
||||
// Lifted from Quinn's work here: https://developer.apple.com/forums/thread/681817
|
||||
|
||||
// There should be a better way to get a process’s arguments
|
||||
// (FB9149624) but right now you have to use `KERN_PROCARGS2`
|
||||
// and then parse the results.
|
||||
|
||||
var argMax: CInt = 0
|
||||
var argMaxSize = size_t(MemoryLayout.size(ofValue: argMax))
|
||||
let err = sysctlbyname("kern.argmax", &argMax, &argMaxSize, nil, 0)
|
||||
guard err >= 0 else {
|
||||
return nil
|
||||
}
|
||||
// precondition(argMaxSize != 0)
|
||||
var result = Data(count: Int(argMax))
|
||||
let resultSize = result.withUnsafeMutableBytes { buf -> Int in
|
||||
var mib: [CInt] = [
|
||||
CTL_KERN,
|
||||
KERN_PROCARGS2,
|
||||
pid,
|
||||
]
|
||||
var bufSize = buf.count
|
||||
let err = sysctl(&mib, CUnsignedInt(mib.count), buf.baseAddress!, &bufSize, nil, 0)
|
||||
guard err >= 0 else {
|
||||
return -1
|
||||
}
|
||||
return bufSize
|
||||
}
|
||||
if resultSize < 0 {
|
||||
return nil
|
||||
}
|
||||
result = result.prefix(resultSize)
|
||||
return result
|
||||
}
|
||||
|
||||
enum ParseError: Error {
|
||||
case unexpectedEnd
|
||||
case argumentIsNotUTF8
|
||||
}
|
||||
|
||||
/// Parses the argument data into a list of strings
|
||||
func parseArgumentData(_ data: Data) throws -> [String] {
|
||||
// Lifted from Quinn's work here: https://developer.apple.com/forums/thread/681817
|
||||
|
||||
// The algorithm here was was ‘stolen’ from the Darwin source for `ps`.
|
||||
//
|
||||
// <https://opensource.apple.com/source/adv_cmds/adv_cmds-176/ps/print.c.auto.html>
|
||||
|
||||
// returns a list of strings: [0] is the executable path,
|
||||
// the rest is `argv[0]` through `argv[argc - 1]
|
||||
|
||||
// Parse `argc`. We’re assuming the value is little endian here, which is
|
||||
// currently accurate but it could be a problem if we’ve “gone back to
|
||||
// metric”.
|
||||
var remaining = data[...]
|
||||
guard remaining.count >= 6 else {
|
||||
throw ParseError.unexpectedEnd
|
||||
}
|
||||
let count32 = remaining.prefix(4).reversed().reduce(0) { $0 << 8 | UInt32($1) }
|
||||
remaining = remaining.dropFirst(4)
|
||||
|
||||
// Get the executable path
|
||||
let exeBytes = remaining.prefix(while: { $0 != 0 })
|
||||
guard let executable = String(bytes: exeBytes, encoding: .utf8) else {
|
||||
throw ParseError.argumentIsNotUTF8
|
||||
}
|
||||
remaining = remaining.dropFirst(exeBytes.count)
|
||||
guard remaining.count != 0 else {
|
||||
throw ParseError.unexpectedEnd
|
||||
}
|
||||
// Skip any zeros until the next non-zero
|
||||
remaining = remaining.drop(while: { $0 == 0 })
|
||||
|
||||
// Now parse `argv[0]` through `argv[argc - 1]`.
|
||||
var result: [String] = [executable]
|
||||
for _ in 0 ..< count32 {
|
||||
let argBytes = remaining.prefix(while: { $0 != 0 })
|
||||
guard let arg = String(bytes: argBytes, encoding: .utf8) else {
|
||||
throw ParseError.argumentIsNotUTF8
|
||||
}
|
||||
result.append(arg)
|
||||
remaining = remaining.dropFirst(argBytes.count)
|
||||
guard remaining.count != 0 else {
|
||||
throw ParseError.unexpectedEnd
|
||||
}
|
||||
remaining = remaining.dropFirst()
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
/// Returns the executable path and all arguments as a list of strings
|
||||
func executableAndArgsForPid(_ pid: Int32) -> [String]? {
|
||||
if let data = argumentData(for: pid) {
|
||||
return try? parseArgumentData(data)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
struct UNIXProcessInfoWithPath {
|
||||
let pid: Int32
|
||||
let ppid: Int32
|
||||
let uid: UInt32
|
||||
let command: String
|
||||
let path: String
|
||||
}
|
||||
|
||||
/// Returns a list of running processes with pid, ppid, uid, command, and path
|
||||
func UNIXProcessListWithPaths() -> [UNIXProcessInfoWithPath] {
|
||||
let procList = UNIXProcessList()
|
||||
var processes = [UNIXProcessInfoWithPath]()
|
||||
for proc in procList {
|
||||
if proc.pid != 0,
|
||||
let data = argumentData(for: proc.pid)
|
||||
{
|
||||
let args = (try? parseArgumentData(data)) ?? []
|
||||
if !args.isEmpty {
|
||||
processes.append(
|
||||
UNIXProcessInfoWithPath(
|
||||
pid: proc.pid,
|
||||
ppid: proc.ppid,
|
||||
uid: proc.uid,
|
||||
command: proc.command,
|
||||
path: args[0]
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
return processes
|
||||
}
|
||||
@@ -484,20 +484,70 @@ func justUpdate() throws {
|
||||
}
|
||||
}
|
||||
|
||||
func pythonScriptRunning(_ scriptName: String) -> Bool {
|
||||
let output = exec("/bin/ps", args: ["-eo", "command="])
|
||||
let lines = output.components(separatedBy: "\n")
|
||||
for line in lines {
|
||||
let part = line.components(separatedBy: " ")
|
||||
if (part[0].contains("/MacOS/Python") || part[0].contains("python")) {
|
||||
if part.count > 1 {
|
||||
if part[1].contains(scriptName) {
|
||||
return true
|
||||
/// Returns ProcessID for a running python script matching the scriptName
|
||||
/// as long as the pid is not the same as ours
|
||||
/// this is used to see if the managedsoftwareupdate script is already running
|
||||
func pythonScriptRunning(_ scriptName: String) -> Int32? {
|
||||
let ourPid = ProcessInfo().processIdentifier
|
||||
let processes = UNIXProcessListWithPaths()
|
||||
for item in processes {
|
||||
if item.pid == ourPid {
|
||||
continue
|
||||
}
|
||||
let executable = (item.path as NSString).lastPathComponent
|
||||
if executable.contains("python") || executable.contains("Python") {
|
||||
// get all the args for this pid
|
||||
if var args = executableAndArgsForPid(item.pid), args.count > 2 {
|
||||
// first value is executable path, drop it
|
||||
// next value is command, drop it
|
||||
args = Array(args.dropFirst(2))
|
||||
// drop leading args that start with a hyphen
|
||||
args = Array(args.drop(while: { $0.hasPrefix("-") }))
|
||||
if args.count > 0, args[0].hasSuffix(scriptName) {
|
||||
return item.pid
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
return nil
|
||||
}
|
||||
|
||||
/// Returns Process ID for a running executable matching the name
|
||||
func executableRunning(_ name: String) -> Int32? {
|
||||
let processes = UNIXProcessListWithPaths()
|
||||
for item in processes {
|
||||
if name.hasPrefix("/") {
|
||||
// full path, so exact comparison
|
||||
if item.path == name {
|
||||
return item.pid
|
||||
}
|
||||
} else {
|
||||
// does executable path end with the name?
|
||||
if item.path.hasSuffix(name) {
|
||||
return item.pid
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
/// Returns the pid of managedsoftwareupdate process, if found
|
||||
func managedsoftwareupdateInstanceRunning() -> Int32? {
|
||||
// A Python version of managedsoftwareupdate might be running,
|
||||
// or a compiled version
|
||||
if let pid = executableRunning("managedsoftwareupdate") {
|
||||
return pid
|
||||
}
|
||||
if let pid = pythonScriptRunning(".managedsoftwareupdate.py") {
|
||||
return pid
|
||||
}
|
||||
if let pid = pythonScriptRunning("managedsoftwareupdate.py") {
|
||||
return pid
|
||||
}
|
||||
if let pid = pythonScriptRunning("managedsoftwareupdate") {
|
||||
return pid
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getRunningProcessesWithUsers() -> [[String:String]] {
|
||||
|
||||
Reference in New Issue
Block a user