pySim-shell: improve export and enable exportation of DF and ADF files

Since we now have the ability to provide export methods for all file
types in the file system (this also includes DF and ADF files), we need
to support this at shell command level as well. Let's also renovate the
walk method and the action method that does the actual exporting.

Related: OS#6092
Change-Id: I3ee661dbae5c11fec23911775f352ac13bc2c6e5
This commit is contained in:
Philipp Maier
2024-07-26 17:50:36 +02:00
parent 12cc6821c4
commit c421645ba6

View File

@@ -54,7 +54,7 @@ from pySim.utils import sanitize_pin_adm, tabulate_str_list, boxed_heading_str,
from pySim.utils import is_hexstr_or_decimal, is_hexstr, is_decimal from pySim.utils import is_hexstr_or_decimal, is_hexstr, is_decimal
from pySim.card_handler import CardHandler, CardHandlerAuto from pySim.card_handler import CardHandler, CardHandlerAuto
from pySim.filesystem import CardMF, CardDF, CardADF from pySim.filesystem import CardMF, CardEF, CardDF, CardADF
from pySim.ts_102_221 import pin_names from pySim.ts_102_221 import pin_names
from pySim.ts_102_222 import Ts102222Commands from pySim.ts_102_222 import Ts102222Commands
from pySim.gsm_r import DF_EIRENE from pySim.gsm_r import DF_EIRENE
@@ -495,12 +495,25 @@ class PySimCommands(CommandSet):
self._cmd.poutput(directory_str) self._cmd.poutput(directory_str)
self._cmd.poutput("%d files" % len(selectables)) self._cmd.poutput("%d files" % len(selectables))
def walk(self, indent=0, action_ef=None, action_df=None, context=None, **kwargs): def __walk_action(self, action, filename, context, **kwargs):
# Changing the currently selected file while walking over the filesystem tree would disturb the
# walk, so we memorize the currently selected file here so that we can select it again after
# we have executed the action callback.
selected_file_before_action = self._cmd.lchan.selected_file
# Perform action
action(filename, context, **kwargs)
# When the action callback is done, make sure the file that was selected before is selected again.
if selected_file_before_action != self._cmd.lchan.selected_file:
self._cmd.lchan.select_file(selected_file_before_action, self._cmd)
def __walk(self, indent=0, action_ef=None, action_df=None, context=None, **kwargs):
"""Recursively walk through the file system, starting at the currently selected DF""" """Recursively walk through the file system, starting at the currently selected DF"""
if isinstance(self._cmd.lchan.selected_file, CardDF): if isinstance(self._cmd.lchan.selected_file, CardDF):
if action_df: if action_df:
action_df(context, **kwargs) self.__walk_action(action_df, self._cmd.lchan.selected_file.name, context, **kwargs)
files = self._cmd.lchan.selected_file.get_selectables( files = self._cmd.lchan.selected_file.get_selectables(
flags=['FNAMES', 'ANAMES']) flags=['FNAMES', 'ANAMES'])
@@ -533,66 +546,45 @@ class PySimCommands(CommandSet):
# If the DF was skipped, we never have entered the directory # If the DF was skipped, we never have entered the directory
# below, so we must not move up. # below, so we must not move up.
if skip_df == False: if skip_df == False:
self.walk(indent + 1, action_ef, action_df, context, **kwargs) self.__walk(indent + 1, action_ef, action_df, context, **kwargs)
self._cmd.lchan.select_file(self._cmd.lchan.selected_file.parent, self._cmd) self._cmd.lchan.select_file(self._cmd.lchan.selected_file.parent, self._cmd)
elif action_ef: elif action_ef:
df_before_action = self._cmd.lchan.selected_file self.__walk_action(action_ef, f, context, **kwargs)
action_ef(f, context, **kwargs)
# When walking through the file system tree the action must not
# always restore the currently selected file to the file that
# was selected before executing the action() callback.
if df_before_action != self._cmd.lchan.selected_file:
raise RuntimeError("inconsistent walk, %s is currently selected but expecting %s to be selected"
% (str(self._cmd.lchan.selected_file), str(df_before_action)))
def do_tree(self, opts): def do_tree(self, opts):
"""Display a filesystem-tree with all selectable files""" """Display a filesystem-tree with all selectable files"""
self.walk() self.__walk()
def export_ef(self, filename, context, as_json): def __export_file(self, filename, context, as_json):
""" Select and export a single elementary file (EF) """ """ Select and export a single file (EF, DF or ADF) """
context['COUNT'] += 1 context['COUNT'] += 1
df = self._cmd.lchan.selected_file
# The currently selected file (not the file we are going to export) file = self._cmd.lchan.get_file_by_name(filename)
# must always be an ADF or DF. From this starting point we select if file:
# the EF we want to export. To maintain consistency we will then self._cmd.poutput(boxed_heading_str(file.fully_qualified_path_str(True)))
# select the current DF again (see comment below). self._cmd.poutput("# directory: %s (%s)" % (file.fully_qualified_path_str(True),
if not isinstance(df, CardDF): file.fully_qualified_path_str(False)))
raise RuntimeError( else:
"currently selected file %s is not a DF or ADF" % str(df)) # If this is called from self.__walk(), then it is ensured that the file exists.
raise RuntimeError("cannot export, file %s does not exist in the file system tree" % filename)
df_path_list = df.fully_qualified_path(True)
df_path = df.fully_qualified_path_str(True)
df_path_fid = df.fully_qualified_path_str(False)
file_str = df_path + "/" + str(filename)
self._cmd.poutput(boxed_heading_str(file_str))
self._cmd.poutput("# directory: %s (%s)" % (df_path, df_path_fid))
try: try:
fcp_dec = self._cmd.lchan.select(filename, self._cmd) fcp_dec = self._cmd.lchan.select_file(file, self._cmd)
self._cmd.poutput("# file: %s (%s)" % self._cmd.poutput("# file: %s (%s)" %
(self._cmd.lchan.selected_file.name, self._cmd.lchan.selected_file.fid)) (self._cmd.lchan.selected_file.name, self._cmd.lchan.selected_file.fid))
self._cmd.poutput("# structure: %s" % self._cmd.lchan.selected_file_structure()) if isinstance(self._cmd.lchan.selected_file, CardEF):
self._cmd.poutput("# structure: %s" % str(self._cmd.lchan.selected_file_structure()))
self._cmd.poutput("# RAW FCP Template: %s" % str(self._cmd.lchan.selected_file_fcp_hex)) self._cmd.poutput("# RAW FCP Template: %s" % str(self._cmd.lchan.selected_file_fcp_hex))
self._cmd.poutput("# Decoded FCP Template: %s" % str(self._cmd.lchan.selected_file_fcp)) self._cmd.poutput("# Decoded FCP Template: %s" % str(self._cmd.lchan.selected_file_fcp))
self._cmd.poutput("select " + self._cmd.lchan.selected_file.fully_qualified_path_str()) self._cmd.poutput("select " + self._cmd.lchan.selected_file.fully_qualified_path_str())
self._cmd.poutput(self._cmd.lchan.selected_file.export(as_json, self._cmd.lchan)) self._cmd.poutput(self._cmd.lchan.selected_file.export(as_json, self._cmd.lchan))
except Exception as e: except Exception as e:
bad_file_str = df_path + "/" + str(filename) + ", " + str(e) bad_file_str = file.fully_qualified_path_str(True) + "/" + str(file.name) + ", " + str(e)
self._cmd.poutput("# bad file: %s" % bad_file_str) self._cmd.poutput("# bad file: %s" % bad_file_str)
context['ERR'] += 1 context['ERR'] += 1
context['BAD'].append(bad_file_str) context['BAD'].append(bad_file_str)
# When reading the file is done, make sure the parent file is
# selected again. This will be the usual case, however we need
# to check before since we must not select the same DF twice
if df != self._cmd.lchan.selected_file:
self._cmd.lchan.select(df.fid or df.aid, self._cmd)
self._cmd.poutput("#") self._cmd.poutput("#")
export_parser = argparse.ArgumentParser() export_parser = argparse.ArgumentParser()
@@ -610,10 +602,10 @@ class PySimCommands(CommandSet):
exception_str_add = "" exception_str_add = ""
if opts.filename: if opts.filename:
self.export_ef(opts.filename, context, **kwargs_export) self.__walk_action(self.__export_file, opts.filename, context, **kwargs_export)
else: else:
try: try:
self.walk(0, self.export_ef, None, context, **kwargs_export) self.__walk(0, self.__export_file, self.__export_file, context, **kwargs_export)
except Exception as e: except Exception as e:
print("# Stopping early here due to exception: " + str(e)) print("# Stopping early here due to exception: " + str(e))
print("#") print("#")
@@ -641,7 +633,7 @@ class PySimCommands(CommandSet):
raise RuntimeError( raise RuntimeError(
"unable to export %i dedicated files(s)%s" % (context['ERR'], exception_str_add)) "unable to export %i dedicated files(s)%s" % (context['ERR'], exception_str_add))
def fsdump_df(self, context, as_json): def fsdump_df(self, filename, context, as_json):
"""Dump information about currently selected [A]DF""" """Dump information about currently selected [A]DF"""
df = self._cmd.lchan.selected_file df = self._cmd.lchan.selected_file
df_path_list = df.fully_qualified_path(True) df_path_list = df.fully_qualified_path(True)
@@ -683,8 +675,7 @@ class PySimCommands(CommandSet):
# The currently selected file (not the file we are going to export) # The currently selected file (not the file we are going to export)
# must always be an ADF or DF. From this starting point we select # must always be an ADF or DF. From this starting point we select
# the EF we want to export. To maintain consistency we will then # the EF we want to export.
# select the current DF again (see comment below).
if not isinstance(df, CardDF): if not isinstance(df, CardDF):
raise RuntimeError("currently selected file %s is not a DF or ADF" % str(df)) raise RuntimeError("currently selected file %s is not a DF or ADF" % str(df))
@@ -769,13 +760,6 @@ class PySimCommands(CommandSet):
context['result']['files'][file_str] = res context['result']['files'][file_str] = res
# When reading the file is done, make sure the parent file is
# selected again. This will be the usual case, however we need
# to check before since we must not select the same DF twice
if df != self._cmd.lchan.selected_file:
self._cmd.lchan.select(df.fid or df.aid, self._cmd)
fsdump_parser = argparse.ArgumentParser() fsdump_parser = argparse.ArgumentParser()
fsdump_parser.add_argument( fsdump_parser.add_argument(
'--filename', type=str, default=None, help='only export specific (named) file') '--filename', type=str, default=None, help='only export specific (named) file')
@@ -801,12 +785,11 @@ class PySimCommands(CommandSet):
exception_str_add = "" exception_str_add = ""
if opts.filename: if opts.filename:
# export only that one specified file self.__walk_action(self.fsdump_ef, opts.filename, context, **kwargs_export)
self.fsdump_ef(opts.filename, context, **kwargs_export)
else: else:
# export an entire subtree # export an entire subtree
try: try:
self.walk(0, self.fsdump_ef, self.fsdump_df, context, **kwargs_export) self.__walk(0, self.fsdump_ef, self.fsdump_df, context, **kwargs_export)
except Exception as e: except Exception as e:
print("# Stopping early here due to exception: " + str(e)) print("# Stopping early here due to exception: " + str(e))
print("#") print("#")