Files
munki/code/client/managedsoftwareupdate

839 lines
33 KiB
Python
Executable File

#!/usr/bin/python
# encoding: utf-8
#
# Copyright 2009-2011 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
#
# http://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.
"""
managedsoftwareupdate
"""
#import grp
import optparse
import os
import re
#import stat
import subprocess
import sys
import time
import traceback
# Do not place any imports with ObjC bindings above this!
try:
from Foundation import NSDate
from Foundation import NSDistributedNotificationCenter
from Foundation import NSNotificationDeliverImmediately
from Foundation import NSNotificationPostToAllSessions
except:
# Python is missing ObjC bindings. Run external report script.
from munkilib import utils
print >> sys.stderr, 'Python is missing ObjC bindings.'
scriptdir = os.path.realpath(os.path.dirname(sys.argv[0]))
script = os.path.join(scriptdir, 'report_broken_client')
try:
result, stdout, stderr = utils.runExternalScript(script)
print >> sys.stderr, result, stdout, stderr
except utils.ScriptNotFoundError:
pass # script is not required, so pass
except utils.RunExternalScriptError, e:
print >> sys.stderr, str(e)
sys.exit(1)
from munkilib import munkicommon
from munkilib import updatecheck
from munkilib import installer
from munkilib import munkistatus
from munkilib import appleupdates
from munkilib import FoundationPlist
from munkilib import utils
def getIdleSeconds():
"""Returns the number of seconds since the last mouse
or keyboard event."""
cmd = ['/usr/sbin/ioreg', '-c', 'IOHIDSystem']
proc = subprocess.Popen(cmd, shell=False, stdin=subprocess.PIPE,
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
(output, unused_err) = proc.communicate()
ioreglines = str(output).splitlines()
idle_time = 0
regex = re.compile('"?HIDIdleTime"?\s+=\s+(\d+)')
for line in ioreglines:
idle_re = regex.search(line)
if idle_re:
idle_time = idle_re.group(1)
break
return int(int(idle_time)/1000000000)
def networkUp():
"""Determine if the network is up by looking for any non-loopback
internet network interfaces.
Returns:
Boolean. True if loopback is found (network is up), False otherwise.
"""
cmd = ['/sbin/ifconfig', '-a', 'inet']
proc = subprocess.Popen(cmd, shell=False, stdin=subprocess.PIPE,
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
(output, unused_err) = proc.communicate()
lines = str(output).splitlines()
for line in lines:
if 'inet' in line:
parts = line.split()
addr = parts[1]
if not addr in ['127.0.0.1', '0.0.0.0']:
return True
return False
def clearLastNotifiedDate():
"""Clear the last date the user was notified of updates."""
munkicommon.set_pref('LastNotifiedDate', None)
def createDirsIfNeeded(dirlist):
"""Create any missing directories needed by the munki tools.
Args:
dirlist: a sequence of directories.
Returns:
Boolean. True if all directories existed or were created,
False otherwise.
"""
for directory in dirlist:
if not os.path.exists(directory):
try:
os.mkdir(directory)
except (OSError, IOError):
print >> sys.stderr, 'ERROR: Could not create %s' % directory
return False
return True
def initMunkiDirs():
"""Figure out where data directories should be and create them if needed.
Returns:
Boolean. True if all data dirs existed or were created, False otherwise.
"""
ManagedInstallDir = munkicommon.pref('ManagedInstallDir')
manifestsdir = os.path.join(ManagedInstallDir, 'manifests')
catalogsdir = os.path.join(ManagedInstallDir, 'catalogs')
cachedir = os.path.join(ManagedInstallDir, 'Cache')
logdir = os.path.join(ManagedInstallDir, 'Logs')
if not createDirsIfNeeded([ManagedInstallDir, manifestsdir, catalogsdir,
cachedir, logdir]):
munkicommon.display_error('Could not create needed directories '
'in %s' % ManagedInstallDir)
return False
else:
return True
def runScript(script, display_name, runtype):
"""Run an external script. Do not run if the permissions on the external
script file are weaker than the current executable."""
result = 0
if os.path.exists(script):
munkicommon.display_status_minor(
'Performing %s tasks...' % display_name)
else:
return result
try:
utils.verifyFileOnlyWritableByMunkiAndRoot(script)
except utils.VerifyFilePermissionsError, e:
# preflight/postflight is insecure, but if the currently executing
# file is insecure too we are no worse off.
try:
utils.verifyFileOnlyWritableByMunkiAndRoot(__file__)
except utils.VerifyFilePermissionsError, e:
# OK, managedsoftwareupdate is insecure anyway - warn & execute.
munkicommon.display_warning('Multiple munki executable scripts '
'have insecure file permissions. Executing '
'%s anyway. Error: %s' % (display_name, e))
else:
# Just the preflight/postflight is insecure. Do not execute.
munkicommon.display_warning('Skipping execution of %s due to '
'insecure file permissions. Error: %s' % (display_name, e))
return result
try:
result, stdout, stderr = utils.runExternalScript(
script, allow_insecure=True, script_args=[runtype])
if result:
munkicommon.display_info('%s return code: %d'
% (display_name, result))
if stdout:
munkicommon.display_info('%s stdout: %s' % (display_name, stdout))
if stderr:
munkicommon.display_info('%s stderr: %s' % (display_name, stderr))
except utils.ScriptNotFoundError:
pass # script is not required, so pass
except utils.RunExternalScriptError, e:
munkicommon.display_warning(str(e))
return result
def doInstallTasks(only_unattended=False):
"""Perform our installation/removal tasks.
Args:
only_unattended: Boolean. If True, only do unattended_(un)install items.
Returns:
Boolean. True if a restart is required, False otherwise.
"""
if not only_unattended:
# first, clear the last notified date
# so we can get notified of new changes after this round
# of installs
clearLastNotifiedDate()
need_to_restart = False
# munki updates take priority over Apple Updates, because
# a munki install or (especially) removal could make a
# pending Apple update no longer necessary or even complicate
# or prevent the removal of another item.
# Therefore we only install Apple updates if there are no
# pending munki updates.
if munkiUpdatesAvailable():
# install munki updates
try:
need_to_restart = installer.run(only_unattended=only_unattended)
except:
munkicommon.display_error(
'Unexpected error in munkilib.installer:')
munkicommon.display_error(traceback.format_exc())
munkicommon.savereport()
exit(-1)
# clear any Apple update info since it may no longer
# be relevant
if not only_unattended:
appleupdates.clearAppleUpdateInfo()
elif ((munkicommon.pref('InstallAppleSoftwareUpdates') or
munkicommon.pref('AppleSoftwareUpdatesOnly'))
and not only_unattended):
# are we supposed to handle Apple Software Updates?
try:
need_to_restart = appleupdates.installAppleUpdates()
except:
munkicommon.display_error(
'Unexpected error in appleupdates.installAppleUpdates:')
munkicommon.display_error(traceback.format_exc())
munkicommon.savereport()
exit(-1)
munkicommon.savereport()
return need_to_restart
def startLogoutHelper():
"""Handle the need for a forced logout. Start our logouthelper"""
cmd = ['/bin/launchctl', 'start', 'com.googlecode.munki.logouthelper']
result = subprocess.call(cmd)
if result:
# some problem with the launchd job
munkicommon.display_error(
'Could not start com.googlecode.munki.logouthelper')
def doRestart():
"""Handle the need for a restart."""
restartMessage = 'Software installed or removed requires a restart.'
munkicommon.log(restartMessage)
if munkicommon.munkistatusoutput:
munkistatus.hideStopButton()
munkistatus.message(restartMessage)
munkistatus.detail('')
munkistatus.percent(-1)
else:
munkicommon.display_info(restartMessage)
# TODO: temporary fix for forced logout problem where we've killed
# loginwindow sessions, but munkicommon.currentGUIusers() still returns
# users. Need to find a better solution, though.
#if not munkicommon.currentGUIusers():
# # no-one is logged in and we're at the loginwindow
consoleuser = munkicommon.getconsoleuser()
if not consoleuser or consoleuser == u'loginwindow':
# no-one is logged in or we're at the loginwindow
time.sleep(5)
unused_retcode = subprocess.call(['/sbin/shutdown', '-r', 'now'])
else:
if munkicommon.munkistatusoutput:
# someone is logged in and we're using munkistatus
munkicommon.display_info(
'Notifying currently logged-in user to restart.')
munkistatus.activate()
munkistatus.restartAlert()
munkicommon.osascript(
'tell application "System Events" to restart')
else:
print 'Please restart immediately.'
def munkiUpdatesAvailable():
"""Return True if there are available updates, False otherwise."""
updatesavailable = False
installinfo = os.path.join(munkicommon.pref('ManagedInstallDir'),
'InstallInfo.plist')
if os.path.exists(installinfo):
try:
plist = FoundationPlist.readPlist(installinfo)
updatesavailable = len(plist.get('removals', [])) or \
len(plist.get('managed_installs', []))
except (AttributeError,
FoundationPlist.NSPropertyListSerializationException):
munkicommon.display_error('Install info at %s is invalid.' %
installinfo)
return updatesavailable
def recordUpdateCheckResult(result):
"""Record last check date and result"""
now = NSDate.new()
munkicommon.set_pref('LastCheckDate', now)
munkicommon.set_pref('LastCheckResult', result)
def sendUpdateNotification():
'''Sends an update notification via NSDistributedNotificationCenter
MSU.app registers to receive these events.'''
dnc = NSDistributedNotificationCenter.defaultCenter()
dnc.postNotificationName_object_userInfo_options_(
'com.googlecode.munki.ManagedSoftwareUpdate.update',
None, None,
NSNotificationDeliverImmediately + NSNotificationPostToAllSessions)
def notifyUserOfUpdates(force=False):
"""Notify the logged-in user of available updates.
Args:
force: bool, default False, forcefully notify user regardless
of LastNotifiedDate.
Returns:
Boolean. True if the user was notified, False otherwise.
"""
# called when options.auto == True
# someone is logged in, and we have updates.
# if we haven't notified in a while, notify:
user_was_notified = False
lastNotifiedString = munkicommon.pref('LastNotifiedDate')
try:
daysBetweenNotifications = int(
munkicommon.pref('DaysBetweenNotifications'))
except ValueError:
munkicommon.display_warning(
'DaysBetweenNotifications is not an integer: %s'
% munkicommon.pref('DaysBetweenNotifications'))
# continue with the default DaysBetweenNotifications
daysBetweenNotifications = 1
now = NSDate.new()
nextNotifyDate = now
if lastNotifiedString:
lastNotifiedDate = NSDate.dateWithString_(lastNotifiedString)
interval = daysBetweenNotifications * (24 * 60 * 60)
if daysBetweenNotifications > 0:
# we make this adjustment so a 'daily' notification
# doesn't require 24 hours to elapse
# subtract 6 hours
interval = interval - (6 * 60 * 60)
nextNotifyDate = lastNotifiedDate.dateByAddingTimeInterval_(interval)
if force or now.timeIntervalSinceDate_(nextNotifyDate) >= 0:
# record current notification date
munkicommon.set_pref('LastNotifiedDate', now)
munkicommon.log('Notifying user of available updates.')
munkicommon.log('LastNotifiedDate was %s' % lastNotifiedString)
# notify user of available updates using LaunchAgent to start
# Managed Software Update.app in the user context.
launchfile = '/var/run/com.googlecode.munki.ManagedSoftwareUpdate'
f = open(launchfile, 'w')
f.close()
time.sleep(0.5)
if os.path.exists(launchfile):
os.unlink(launchfile)
user_was_notified = True
return user_was_notified
def main():
"""Main"""
# check to see if we're root
if os.geteuid() != 0:
print >> sys.stderr, 'You must run this as root!'
exit(-1)
# save this for later
scriptdir = os.path.realpath(os.path.dirname(sys.argv[0]))
p = optparse.OptionParser()
p.set_usage("""Usage: %prog [options]""")
p.add_option('--auto', '-a', action='store_true',
help="""Used by launchd LaunchAgent for scheduled runs.
No user feedback or intervention. All other options
ignored.""")
p.add_option('--logoutinstall', '-l', action='store_true',
help="""Used by launchd LaunchAgent when running at the
loginwindow.""")
p.add_option('--installwithnologout', action='store_true',
help="""Used by Managed Software Update.app when user
triggers an install without logging out.""")
p.add_option('--manualcheck', action='store_true',
help="""Used by launchd LaunchAgent when checking
manually.""")
p.add_option('--munkistatusoutput', '-m', action='store_true',
help="""Uses MunkiStatus.app for progress feedback when
installing.""")
p.add_option('--id', default='',
help='Alternate identifier for catalog retreival')
p.add_option('--quiet', '-q', action='store_true',
help="""Quiet mode. Logs messages, but nothing to stdout.
--verbose is ignored if --quiet is used.""")
p.add_option('--verbose', '-v', action='count', default=1,
help="""More verbose output. May be specified multiple
times.""")
p.add_option('--checkonly', action='store_true',
help="""Check for updates, but don't install them.
This is the default behavior.""")
p.add_option('--installonly', action='store_true',
help='Skip checking and install any pending updates.')
p.add_option('--applesuspkgsonly', action='store_true',
help=('Only check/install Apple SUS packages, '
'skip Munki packages.'))
p.add_option('--munkipkgsonly', action='store_true',
help=('Only check/install Munki packages, '
'skip Apple SUS.'))
p.add_option('--version', '-V', action='store_true',
help='Print the version of the munki tools and exit.')
options, arguments = p.parse_args()
runtype = 'custom'
checkandinstallatstartupflag = \
'/Users/Shared/.com.googlecode.munki.checkandinstallatstartup'
installatstartupflag = \
'/Users/Shared/.com.googlecode.munki.installatstartup'
installatlogoutflag = '/private/tmp/com.googlecode.munki.installatlogout'
if options.version:
print munkicommon.get_version()
exit(0)
if options.auto:
# typically invoked by a launch daemon periodically.
# munkistatusoutput is false for checking, but true for installing
runtype = 'auto'
options.munkistatusoutput = False
options.quiet = True
options.checkonly = False
options.installonly = False
if options.logoutinstall:
# typically invoked by launchd agent
# running in the LoginWindow context
runtype = 'logoutinstall'
options.munkistatusoutput = True
options.quiet = True
options.checkonly = False
options.installonly = True
# if we're running at the loginwindow,
# let's make sure the user triggered
# the update before logging out, or we triggered it before restarting.
user_triggered = False
flagfiles = [checkandinstallatstartupflag,
installatstartupflag,
installatlogoutflag]
for filename in flagfiles:
if os.path.exists(filename):
user_triggered = True
if filename == checkandinstallatstartupflag:
runtype = 'checkandinstallatstartup'
options.installonly = False
options.auto = True
# HACK: sometimes this runs before the network is up.
# we'll attempt to wait up to 10 seconds for the
# network interfaces to come up
# before continuing
munkicommon.display_status_minor('Waiting for network...')
for i in range(5):
if networkUp():
break
time.sleep(2)
else:
# delete triggerfile if _not_ checkandinstallatstartup
os.unlink(filename)
if not user_triggered:
# no trigger file was found -- how'd we get launched?
munkicommon.cleanUpTmpDir()
exit(0)
if options.installwithnologout:
# typically invoked by Managed Software Update.app
# by user who decides not to logout
launchdtriggerfile = \
'/private/tmp/.com.googlecode.munki.managedinstall.launchd'
if os.path.exists(launchdtriggerfile):
# remove it so we aren't automatically relaunched
os.unlink(launchdtriggerfile)
runtype = 'installwithnologout'
options.munkistatusoutput = True
options.quiet = True
options.checkonly = False
options.installonly = True
if options.manualcheck:
# triggered by Managed Software Update.app
launchdtriggerfile = \
'/private/tmp/.com.googlecode.munki.updatecheck.launchd'
if os.path.exists(launchdtriggerfile):
# remove it so we aren't automatically relaunched
os.unlink(launchdtriggerfile)
runtype = 'manualcheck'
options.munkistatusoutput = True
options.quiet = True
options.checkonly = True
options.installonly = False
if options.quiet:
options.verbose = 0
if options.checkonly and options.installonly:
print >> sys.stderr, \
'--checkonly and --installonly options are mutually exclusive!'
exit(-1)
# set munkicommon globals
munkicommon.munkistatusoutput = options.munkistatusoutput
munkicommon.verbose = options.verbose
if options.installonly:
# we're only installing, not checking, so we should copy
# some report values from the prior run
munkicommon.readreport()
# start a new report
munkicommon.report['StartTime'] = munkicommon.format_time()
munkicommon.report['RunType'] = runtype
# Clearing arrays must be run before any call to display_warning/error.
munkicommon.report['Errors'] = []
munkicommon.report['Warnings'] = []
munkicommon.log("### Starting managedsoftwareupdate run: %s ###" % runtype)
if options.verbose:
print 'Managed Software Update Tool'
print 'Copyright 2010-2012 The Munki Project'
print 'http://code.google.com/p/munki\n'
munkicommon.display_status_major('Starting...')
# run the preflight script if it exists
preflightscript = os.path.join(scriptdir, 'preflight')
result = runScript(preflightscript, 'preflight', runtype)
if result:
# non-zero return code means don't run
munkicommon.display_info(
'managedsoftwareupdate run aborted by preflight script: %s'
% result)
# record the check result for use by Managed Software Update.app
# right now, we'll return the same code as if the munki server
# was unavailable. We need to revisit this and define additional
# update check results.
recordUpdateCheckResult(-2)
if options.munkistatusoutput:
# connect to socket and quit
munkistatus.activate()
munkistatus.quit()
munkicommon.cleanUpTmpDir()
exit(-1)
# Force a prefs refresh, in case preflight modified the prefs file.
munkicommon.reload_prefs()
# create needed directories if necessary
if not initMunkiDirs():
exit(-1)
# check to see if another instance of this script is running
myname = os.path.basename(sys.argv[0])
if munkicommon.pythonScriptRunning(myname):
# another instance of this script is running, so we should quit
if options.manualcheck:
# a manual update check was triggered
# (probably by Managed Software Update), but managedsoftwareupdate
# is already running. We should provide user feedback
munkistatus.activate()
munkistatus.message('Checking for available updates...')
while True:
# loop til the other instance exits
if not munkicommon.pythonScriptRunning(myname):
break
# or user clicks Stop
if munkicommon.stopRequested():
break
time.sleep(0.5)
munkistatus.quit()
else:
msg = 'Another instance of %s is running. Exiting.' % myname
munkicommon.log(msg)
print >> sys.stderr, msg
munkicommon.cleanUpTmpDir()
exit(0)
applesoftwareupdatesonly = (munkicommon.pref('AppleSoftwareUpdatesOnly')
or options.applesuspkgsonly)
if not options.installonly and not applesoftwareupdatesonly:
# check to see if we can talk to the manifest server
server = munkicommon.pref('ManifestURL') or \
munkicommon.pref('SoftwareRepoURL')
result = updatecheck.checkServer(server)
if result != (0, 'OK'):
munkicommon.display_error(
'managedsoftwareupdate: server check for %s failed: %s'
% (server, str(result)))
if options.manualcheck:
# record our result
recordUpdateCheckResult(-1)
# connect to socket and quit
munkistatus.activate()
munkistatus.quit()
munkicommon.cleanUpTmpDir()
exit(-1)
# reset our errors and warnings files, rotate main log if needed
munkicommon.reset_errors()
munkicommon.reset_warnings()
munkicommon.rotate_main_log()
# archive the previous session's report
munkicommon.archive_report()
if applesoftwareupdatesonly and options.verbose:
print ('NOTE: managedsoftwareupdate is configured to process Apple '
'Software Updates only.')
updatecheckresult = None
if not options.installonly and not applesoftwareupdatesonly:
try:
updatecheckresult = updatecheck.check(client_id=options.id)
except:
munkicommon.display_error('Unexpected error in updatecheck:')
munkicommon.display_error(traceback.format_exc())
munkicommon.savereport()
exit(-1)
if updatecheckresult is not None:
recordUpdateCheckResult(updatecheckresult)
updatesavailable = munkiUpdatesAvailable()
appleupdatesavailable = False
if (not updatesavailable and not options.installonly and
not munkicommon.stopRequested()):
# if there are no munki updates,
# are we supposed to check for and install Apple Software Updates?
if ((munkicommon.pref('InstallAppleSoftwareUpdates') or
applesoftwareupdatesonly) and not options.munkipkgsonly):
try:
appleupdatesavailable = \
appleupdates.appleSoftwareUpdatesAvailable(
forcecheck=(options.manualcheck or
runtype == 'checkandinstallatstartup' or
(runtype == 'custom' and
applesoftwareupdatesonly)))
except:
munkicommon.display_error('Unexpected error in appleupdates:')
munkicommon.display_error(traceback.format_exc())
munkicommon.savereport()
exit(-1)
if (not updatesavailable and options.installonly and
not options.munkipkgsonly and
(munkicommon.pref('InstallAppleSoftwareUpdates') or
applesoftwareupdatesonly)):
# just look and see if there are already downloaded Apple updates
# to install; don't run softwareupdate to check with Apple
try:
appleupdatesavailable = \
appleupdates.appleSoftwareUpdatesAvailable(suppresscheck=True)
except:
munkicommon.display_error('Unexpected error in appleupdates:')
munkicommon.display_error(traceback.format_exc())
munkicommon.savereport()
exit(-1)
# send a notification event so MSU can update its display
# if needed
sendUpdateNotification()
mustrestart = False
mustlogout = False
notify_user = False
force_action = None
if updatesavailable or appleupdatesavailable:
if options.installonly or options.logoutinstall:
# just install
mustrestart = doInstallTasks()
elif options.auto:
if not munkicommon.currentGUIusers(): # no GUI users
if getIdleSeconds() > 10:
if not munkicommon.pref('SuppressAutoInstall'):
# no GUI users, system is idle, so install
# enable status output over login window
munkicommon.munkistatusoutput = True
mustrestart = doInstallTasks()
else:
munkicommon.log('Skipping auto install because '
'SuppressAutoInstall is true.')
else:
munkicommon.log('Skipping auto install because system is '
'not idle (keyboard or mouse activity).')
else: # there are GUI users
unused_force_action = updatecheck.checkForceInstallPackages()
if not munkicommon.pref('SuppressAutoInstall'):
doInstallTasks(only_unattended=True)
else:
munkicommon.log('Skipping unattended installs because '
'SuppressAutoInstall is true.')
# send a notification event so MSU can update its display
# if needed
sendUpdateNotification()
force_action = updatecheck.checkForceInstallPackages()
# if any installs are still requiring force actions, just
# initiate a logout to get started. blocking apps might
# have stopped even non-logout/reboot installs from
# occuring.
if force_action in ['now', 'logout', 'restart']:
mustlogout = True
# it's possible that we no longer have any available updates
# so we need to check InstallInfo.plist again
# however Apple Updates have not been affected by the
# unattended install tasks (so that check is still valid).
if appleupdatesavailable or munkiUpdatesAvailable():
# set a flag to notify the user of available updates
# after we conclude this run.
notify_user = True
elif not options.quiet:
print ('\nRun %s --installonly to install the downloaded '
'updates.' % myname)
else:
# no updates available
if options.installonly and not options.quiet:
print 'Nothing to install or remove.'
if runtype == 'checkandinstallatstartup':
# we have nothing to do, so remove the
# checkandinstallatstartupflag file
# so we'll stop running at startup/logout
if os.path.exists(checkandinstallatstartupflag):
os.unlink(checkandinstallatstartupflag)
# finish our report
munkicommon.report['EndTime'] = munkicommon.format_time()
munkicommon.report['ManagedInstallVersion'] = munkicommon.get_version()
munkicommon.report['AvailableDiskSpace'] = \
munkicommon.getAvailableDiskSpace()
munkicommon.report['ConsoleUser'] = munkicommon.getconsoleuser() or \
'<None>'
munkicommon.savereport()
munkicommon.display_status_major('Finishing...')
# save application inventory data
munkicommon.saveappdata()
# run the postflight script if it exists
postflightscript = os.path.join(scriptdir, 'postflight')
result = runScript(postflightscript, 'postflight', runtype)
# we ignore the result of the postflight
munkicommon.log("### Ending managedsoftwareupdate run ###")
if options.verbose:
print 'Done.'
if options.manualcheck:
# just quit munkistatus; Managed Software Update will notify
munkistatus.quit()
elif notify_user:
# it may have been more than a minute since we ran our
# original updatecheck so tickle the updatecheck time
# so MSU.app knows to display results immediately
recordUpdateCheckResult(1)
consoleuser = munkicommon.getconsoleuser()
if consoleuser == u'loginwindow':
# someone is logged in, but we're sitting at
# the loginwindow due to fast user switching
# so do nothing
pass
elif force_action:
notifyUserOfUpdates(force=True)
time.sleep(2)
startLogoutHelper()
elif not munkicommon.pref('SuppressUserNotification'):
notifyUserOfUpdates()
else:
munkicommon.log('Skipping user notification because '
'SuppressUserNotification is true.')
munkicommon.cleanUpTmpDir()
if mustrestart:
doRestart()
elif munkicommon.munkistatusoutput:
munkistatus.quit()
if runtype == 'checkandinstallatstartup' and not mustrestart:
if os.path.exists(checkandinstallatstartupflag):
# we installed things but did not need to restart; we need to run
# again to check for more updates.
if not munkicommon.currentGUIusers():
# no-one is logged in
idleseconds = getIdleSeconds()
if not idleseconds > 10:
# system is not idle, but check again in case someone has
# simply briefly touched the mouse to see progress.
time.sleep(15)
idleseconds = getIdleSeconds()
if idleseconds > 10:
# no-one is logged in and the machine has been idle
# for a few seconds; kill the loginwindow
# (which will cause us to run again)
#munkicommon.log(
# 'Killing loginwindow so we will run again...')
#cmd = ['/usr/bin/killall', 'loginwindow']
#unused_retcode = subprocess.call(cmd)
# with the new LaunchAgent, we don't have to kill
# the loginwindow
pass
else:
# if the trigger file is present when we exit, we'll
# be relaunched by launchd, so we need to remove it
# to prevent automatic relaunch.
munkicommon.log(
'System not idle -- '
'removing trigger file to prevent relaunch')
try:
os.unlink(checkandinstallatstartupflag)
except OSError:
pass
if __name__ == '__main__':
main()