# encoding: utf-8 # # Copyright 2009-2017 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.'