Skip to content

Commit

Permalink
More changes to support softwareupdate in Big Sur
Browse files Browse the repository at this point in the history
  • Loading branch information
gregneagle committed Sep 4, 2020
1 parent a840bed commit 696e40c
Show file tree
Hide file tree
Showing 2 changed files with 123 additions and 26 deletions.
47 changes: 42 additions & 5 deletions code/client/munkilib/appleupdates/au.py
Original file line number Diff line number Diff line change
Expand Up @@ -195,13 +195,21 @@ def get_filtered_recommendedupdates(self):
return []
su_results = su_tool.run(['-l', '--no-scan'])
filtered_updates = []
# add update to filtered_updates only if it is also listed
# in `softwareupdate -l` output
for item in su_results.get('updates', []):
for update in recommended_updates:
if (item.get('identifier') == update.get('Identifier') and
item.get('version') == update.get('Display Version')):
# add update to filtered_updates only if it is also listed
# in `softwareupdate -l` output
filtered_updates.append(update)
item.update(update)
filtered_updates.append(item)
elif item.get('Title'):
# new-style info first seen in Catalina
if (item.get('Title') == update.get('Display Name') and
item.get('Version') == update.get('Display Version')
):
item.update(update)
filtered_updates.append(item)
return filtered_updates

def available_update_product_ids(self):
Expand Down Expand Up @@ -379,11 +387,16 @@ def get_apple_updates(self):
product_keys = []
english_su_info = {}
apple_updates = []
updates_dict = {}

# first, try to get the list from com.apple.SoftwareUpdate preferences
# and softwareupdate -l
recommended_updates = self.get_filtered_recommendedupdates()
for item in recommended_updates:
try:
updates_dict[item['Product Key']] = item
except (TypeError, AttributeError, KeyError):
pass
try:
update_display_names[item['Product Key']] = (
item['Display Name'])
Expand All @@ -401,7 +414,31 @@ def get_apple_updates(self):
pass

for product_key in product_keys:
if not self.update_downloaded(product_key):
if updates_dict.get(product_key, {}).get('MobileSoftwareUpdate'):
# New in Big Sur. These updates are not downloaded to
# /Library/Updates and we don't (yet) have access to any
# additional metadata
su_info = {}
su_info['productKey'] = product_key
su_info['name'] = updates_dict[
product_key].get('Display Name', '')
su_info['apple_product_name'] = su_info['name']
su_info['display_name'] = su_info['name']
su_info['version_to_install'] = updates_dict[
product_key].get('Display Version', '')
su_info['description'] = ''
try:
size = int(updates_dict[
product_key].get('Size', '0K')[:-1])
su_info['installer_item_size'] = size
su_info['installed_size'] = size
except (ValueError, TypeError, IndexError):
su_info['installed_size'] = 0
if updates_dict[product_key].get('Action') == 'restart':
su_info['RestartAction'] = 'RequireRestart'
apple_updates.append(su_info)
continue
elif not self.update_downloaded(product_key):
display.display_warning(
'Product %s does not appear to be downloaded',
product_key)
Expand Down Expand Up @@ -502,7 +539,7 @@ def display_apple_update_info(self):
for item in apple_updates:
display.display_info(
' + %s-%s' % (
item.get('display_name', ''),
item.get('display_name', item.get('name', '')),
item.get('version_to_install', '')))
if item.get('RestartAction') in self.RESTART_ACTIONS:
display.display_info(' *Restart required')
Expand Down
102 changes: 81 additions & 21 deletions code/client/munkilib/appleupdates/su_tool.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,78 @@ def find_ptty_tool():
return cmd


def parse_su_update_line_new_style(line):
'''Parses a new-style software update line'''
info = {}
line = line.strip().rstrip(',')
for subitem in line.split(', '):
key, _, value = subitem.partition(": ")
if key:
info[key] = value
return info


def parse_su_update_line_old_style(line):
'''Parses an old-style (pre-10.15) softwareupdate -l output line
into a dict'''
info = {}
# strip leading and trailing whitespace
line = line.strip()
title, seperator, line = line.partition("(")
if not seperator == "(":
# no idea of the format, just return an empty dict
return {}
info['Title'] = title.rstrip()
version, seperator, line = line.partition(")")
if not seperator == ")":
# no idea of the format, just return an empty dict
return {}
info['Version'] = version
line = line.lstrip(', ')
size, seperator, line = line.partition('K')
if seperator == 'K':
info['Size'] = '%sK' % size
# now start from the end
if line.endswith(" [restart]"):
line = line[0:-len(" [restart]")]
info['Action'] = 'restart'
if line.endswith(" [recommended]"):
line = line[0:-len(" [recommended]")]
info['Recommended'] = 'YES'
else:
info['Recommended'] = 'NO'
return info


def parse_su_identifier(line):
'''parses first line of softwareupdate -l item output'''
if line.startswith(' * '):
update_entry = line[5:]
elif line.startswith('* Label: '):
update_entry = line[9:]
else:
return {}
update_parts = update_entry.split('-')
# version is the bit after the last hyphen
# (let's hope there are no hyphens in the versions!)
vers = update_parts[-1]
# identifier is everything before the last hyphen
identifier = '-'.join(update_parts[0:-1])
return {'full_identifier': update_entry,
'identifier': identifier,
'version': vers}


def parse_su_update_lines(line1, line2):
'''Parses two lines from softwareupdate -l output and returns a dict'''
info = parse_su_identifier(line1)
if line1.startswith(' * '):
info.update(parse_su_update_line_old_style(line2))
elif line1.startswith('* Label: '):
info.update(parse_su_update_line_new_style(line2))
return info


def run(options_list, catalog_url=None, stop_allowed=False):
"""Runs /usr/sbin/softwareupdate with options.
Expand Down Expand Up @@ -168,28 +240,16 @@ def run(options_list, catalog_url=None, stop_allowed=False):

# --list-specific output
if mode == 'list':
if output.strip().startswith('*'):
if output.startswith((' * ', '* Label: ')):
# collect list of items available for install
if output.startswith(' * '):
update_entry = output[5:]
elif output.startswith('* Label: '):
# seen in 10.15 betas
update_entry = output[9:]
else:
# unknown format
continue
update_parts = update_entry.split('-')
# version is the bit after the last hyphen
# (let's hope there are no hyphens in the versions!)
vers = update_parts[-1]
# identifier is everything before the last hyphen
identifier = '-'.join(update_parts[0:-1])
results['updates'].append(
{'identifier': identifier, 'version': vers})
continue
else:
# we don't want any output from calling `softwareupdate -l`
continue
first_line = output
second_line = job.stdout.readline()
if second_line:
second_line = second_line.decode('UTF-8').rstrip('\n\r')
item = parse_su_update_lines(first_line, second_line)
results['updates'].append(item)
# we don't want any output from calling `softwareupdate -l`
continue

output = output.strip()
# --download-specific output
Expand Down

0 comments on commit 696e40c

Please sign in to comment.