mirror of
https://github.com/munki/munki.git
synced 2026-01-07 06:59:57 -06:00
223 lines
7.4 KiB
Python
223 lines
7.4 KiB
Python
# encoding: utf-8
|
|
#
|
|
# Copyright 2009-2018 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.
|
|
"""
|
|
processes.py
|
|
|
|
Created by Greg Neagle on 2016-12-14.
|
|
|
|
|
|
Functions for finding, listing, etc processes
|
|
"""
|
|
|
|
import os
|
|
import signal
|
|
import subprocess
|
|
|
|
from .constants import LOGINWINDOW
|
|
from . import display
|
|
|
|
|
|
def get_running_processes():
|
|
"""Returns a list of paths of running processes"""
|
|
proc = subprocess.Popen(['/bin/ps', '-axo' 'comm='],
|
|
shell=False, stdin=subprocess.PIPE,
|
|
stdout=subprocess.PIPE,
|
|
stderr=subprocess.PIPE)
|
|
(output, dummy_err) = proc.communicate()
|
|
if proc.returncode == 0:
|
|
proc_list = [item for item in output.splitlines()
|
|
if item.startswith('/')]
|
|
launchcfmapp = ('/System/Library/Frameworks/Carbon.framework'
|
|
'/Versions/A/Support/LaunchCFMApp')
|
|
if launchcfmapp in proc_list:
|
|
# we have a really old Carbon app
|
|
proc = subprocess.Popen(['/bin/ps', '-axwwwo' 'args='],
|
|
shell=False, stdin=subprocess.PIPE,
|
|
stdout=subprocess.PIPE,
|
|
stderr=subprocess.PIPE)
|
|
(output, dummy_err) = proc.communicate()
|
|
if proc.returncode == 0:
|
|
carbon_apps = [item[len(launchcfmapp)+1:]
|
|
for item in output.splitlines()
|
|
if item.startswith(launchcfmapp)]
|
|
if carbon_apps:
|
|
proc_list.extend(carbon_apps)
|
|
return proc_list
|
|
else:
|
|
return []
|
|
|
|
|
|
def is_app_running(appname):
|
|
"""Tries to determine if the application in appname is currently
|
|
running"""
|
|
display.display_detail('Checking if %s is running...' % appname)
|
|
proc_list = get_running_processes()
|
|
matching_items = []
|
|
if appname.startswith('/'):
|
|
# search by exact path
|
|
matching_items = [item for item in proc_list
|
|
if item == appname]
|
|
elif appname.endswith('.app'):
|
|
# search by filename
|
|
matching_items = [item for item in proc_list
|
|
if '/'+ appname + '/Contents/MacOS/' in item]
|
|
else:
|
|
# check executable name
|
|
matching_items = [item for item in proc_list
|
|
if item.endswith('/' + appname)]
|
|
if not matching_items:
|
|
# try adding '.app' to the name and check again
|
|
matching_items = [item for item in proc_list
|
|
if '/'+ appname + '.app/Contents/MacOS/' in item]
|
|
|
|
if matching_items:
|
|
# it's running!
|
|
display.display_debug1('Matching process list: %s' % matching_items)
|
|
display.display_detail('%s is running!' % appname)
|
|
return True
|
|
|
|
# if we get here, we have no evidence that appname is running
|
|
return False
|
|
|
|
|
|
def blocking_applications_running(pkginfoitem):
|
|
"""Returns true if any application in the blocking_applications list
|
|
is running or, if there is no blocking_applications list, if any
|
|
application in the installs list is running."""
|
|
|
|
if 'blocking_applications' in pkginfoitem:
|
|
appnames = pkginfoitem['blocking_applications']
|
|
else:
|
|
# if no blocking_applications specified, get appnames
|
|
# from 'installs' list if it exists
|
|
appnames = [os.path.basename(item.get('path'))
|
|
for item in pkginfoitem.get('installs', [])
|
|
if item['type'] == 'application']
|
|
|
|
display.display_debug1("Checking for %s" % appnames)
|
|
running_apps = [appname for appname in appnames
|
|
if is_app_running(appname)]
|
|
if running_apps:
|
|
display.display_detail(
|
|
"Blocking apps for %s are running:" % pkginfoitem['name'])
|
|
display.display_detail(" %s" % running_apps)
|
|
return True
|
|
return False
|
|
|
|
|
|
def find_processes(user=None, exe=None):
|
|
"""Find processes in process list.
|
|
|
|
Args:
|
|
user: str, optional, username owning process
|
|
exe: str, optional, executable name of process
|
|
Returns:
|
|
dictionary of pids = {
|
|
pid: {
|
|
'user': str, username owning process,
|
|
'exe': str, string executable of process,
|
|
}
|
|
}
|
|
|
|
list of pids, or {} if none
|
|
"""
|
|
argv = ['/bin/ps', '-x', '-w', '-w', '-a', '-o', 'pid=,user=,comm=']
|
|
ps_proc = subprocess.Popen(
|
|
argv, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
|
(stdout, dummy_stderr) = ps_proc.communicate()
|
|
|
|
pids = {}
|
|
|
|
if not stdout or ps_proc.returncode != 0:
|
|
return pids
|
|
|
|
try:
|
|
lines = stdout.splitlines()
|
|
for proc in lines:
|
|
(p_pid, p_user, p_comm) = proc.split(None, 2)
|
|
|
|
if exe is not None:
|
|
if not p_comm.startswith(exe):
|
|
continue
|
|
if user is not None:
|
|
if p_user != user:
|
|
continue
|
|
pids[int(p_pid)] = {
|
|
'user': p_user,
|
|
'exe': p_comm,
|
|
}
|
|
|
|
except (ValueError, TypeError, IndexError):
|
|
return pids
|
|
|
|
return pids
|
|
|
|
|
|
def force_logout_now():
|
|
"""Force the logout of interactive GUI users and spawn MSU."""
|
|
try:
|
|
procs = find_processes(exe=LOGINWINDOW)
|
|
users = {}
|
|
for pid in procs:
|
|
users[procs[pid]['user']] = pid
|
|
|
|
if 'root' in users:
|
|
del users['root']
|
|
|
|
# force MSU GUI to raise
|
|
fileref = open('/private/tmp/com.googlecode.munki.installatlogout', 'w')
|
|
fileref.close()
|
|
|
|
# kill loginwindows to cause logout of current users, whether
|
|
# active or switched away via fast user switching.
|
|
for user in users:
|
|
try:
|
|
os.kill(users[user], signal.SIGKILL)
|
|
except OSError:
|
|
pass
|
|
|
|
except BaseException, err:
|
|
display.display_error('Exception in force_logout_now(): %s' % str(err))
|
|
|
|
|
|
# this function is maybe an odd fit for this module, but it's a way for the
|
|
# Managed Software Center.app and MunkiStatus.app processes to tell the
|
|
# managedsoftwareupdate process to stop/cancel, so here it is!
|
|
_STOP_REQUESTED = False
|
|
def stop_requested():
|
|
"""Allows user to cancel operations when GUI status is being used"""
|
|
global _STOP_REQUESTED
|
|
if _STOP_REQUESTED:
|
|
return True
|
|
stop_request_flag = (
|
|
'/private/tmp/'
|
|
'com.googlecode.munki.managedsoftwareupdate.stop_requested')
|
|
if os.path.exists(stop_request_flag):
|
|
# store this so it's persistent until this session is over
|
|
_STOP_REQUESTED = True
|
|
display.display_info('### User stopped session ###')
|
|
try:
|
|
os.unlink(stop_request_flag)
|
|
except OSError, err:
|
|
display.display_error(
|
|
'Could not remove %s: %s', stop_request_flag, err)
|
|
return True
|
|
return False
|
|
|
|
|
|
if __name__ == '__main__':
|
|
print 'This is a library of support tools for the Munki Suite.'
|