1
0
mirror of https://github.com/ARM-software/devlib.git synced 2025-01-31 02:00:45 +00:00

target: Robustify read_tree_values()

target.read_tree_values() has several weaknesses. It doesn't support
files with ':' in their name, and it fails when reading binary files.
In essence, these limitations are cause by its fragile implementation
based on grep in shutils.

In order to robustify read_tree_values(), use tar and base64 to send the
content of a tree to the host, which can then process it from there. In
the process, read_tree_values() gains two new arguments:
 - decode_unicode: must be set to work text/utf-8 content;
 - strip_null_chars: must be set to remove '\00' chars from text files.

Both are set to true by default to keep backward compatibility with the
existing code.

Suggested-by: Douglas Raillard <douglas.raillard@arm.com>
Signed-off-by: Quentin Perret <quentin.perret@arm.com>
This commit is contained in:
Quentin Perret 2019-01-25 13:25:05 +00:00 committed by Marc Bonnici
parent 911a9f2ef4
commit 7e79eeb9cb
2 changed files with 71 additions and 24 deletions

View File

@ -255,26 +255,32 @@ sched_get_kernel_attributes() {
# Misc # Misc
################################################################################ ################################################################################
read_tree_values() { read_tree_tgz_b64() {
BASEPATH=$1 BASEPATH=$1
MAXDEPTH=$2 MAXDEPTH=$2
TMPBASE=$3
if [ ! -e $BASEPATH ]; then if [ ! -e $BASEPATH ]; then
echo "ERROR: $BASEPATH does not exist" echo "ERROR: $BASEPATH does not exist"
exit 1 exit 1
fi fi
PATHS=$($BUSYBOX find $BASEPATH -follow -maxdepth $MAXDEPTH) cd $TMPBASE
i=0 TMP_FOLDER=$($BUSYBOX realpath $($BUSYBOX mktemp -d XXXXXX))
for path in $PATHS; do
i=$(expr $i + 1) # 'tar' doesn't work as expected on debugfs, so copy the tree first to
if [ $i -gt 1 ]; then # workaround the issue
break; cd $BASEPATH
fi for CUR_FILE in $($BUSYBOX find . -follow -type f -maxdepth $MAXDEPTH); do
$BUSYBOX cp --parents $CUR_FILE $TMP_FOLDER/ 2> /dev/null
done done
if [ $i -gt 1 ]; then
$BUSYBOX grep -s '' $PATHS cd $TMP_FOLDER
fi $BUSYBOX tar cz * | $BUSYBOX base64
# Clean-up the tmp folder since we won't need it any more
cd $TMPBASE
rm -rf $TMP_FOLDER
} }
get_linux_system_id() { get_linux_system_id() {
@ -347,8 +353,8 @@ ftrace_get_function_stats)
hotplug_online_all) hotplug_online_all)
hotplug_online_all hotplug_online_all
;; ;;
read_tree_values) read_tree_tgz_b64)
read_tree_values $* read_tree_tgz_b64 $*
;; ;;
get_linux_system_id) get_linux_system_id)
get_linux_system_id $* get_linux_system_id $*

View File

@ -13,6 +13,9 @@
# limitations under the License. # limitations under the License.
# #
import io
import base64
import gzip
import os import os
import re import re
import time import time
@ -684,23 +687,61 @@ class Target(object):
timeout = duration + 10 timeout = duration + 10
self.execute('sleep {}'.format(duration), timeout=timeout) self.execute('sleep {}'.format(duration), timeout=timeout)
def read_tree_values_flat(self, path, depth=1, check_exit_code=True): def read_tree_values_flat(self, path, depth=1, check_exit_code=True,
command = 'read_tree_values {} {}'.format(quote(path), depth) decode_unicode=True, strip_null_chars=True):
command = 'read_tree_tgz_b64 {} {} {}'.format(quote(path), depth,
quote(self.working_directory))
output = self._execute_util(command, as_root=self.is_rooted, output = self._execute_util(command, as_root=self.is_rooted,
check_exit_code=check_exit_code) check_exit_code=check_exit_code)
accumulator = defaultdict(list) result = {}
for entry in output.strip().split('\n'):
if ':' not in entry: # Unpack the archive in memory
continue tar_gz = base64.b64decode(output)
path, value = entry.strip().split(':', 1) tar_gz_bytes = io.BytesIO(tar_gz)
accumulator[path].append(value) tar_buf = gzip.GzipFile(fileobj=tar_gz_bytes).read()
tar_bytes = io.BytesIO(tar_buf)
with tarfile.open(fileobj=tar_bytes) as tar:
for member in tar.getmembers():
try:
content_f = tar.extractfile(member)
# ignore exotic members like sockets
except Exception:
continue
# if it is a file and not a folder
if content_f:
content = content_f.read()
if decode_unicode:
try:
content = content.decode('utf-8').strip()
if strip_null_chars:
content = content.replace('\x00', '').strip()
except UnicodeDecodeError:
content = ''
name = self.path.join(path, member.name)
result[name] = content
result = {k: '\n'.join(v).strip() for k, v in accumulator.items()}
return result return result
def read_tree_values(self, path, depth=1, dictcls=dict, check_exit_code=True): def read_tree_values(self, path, depth=1, dictcls=dict,
value_map = self.read_tree_values_flat(path, depth, check_exit_code) check_exit_code=True, decode_unicode=True,
strip_null_chars=True):
"""
Reads the content of all files under a given tree
:path: path to the tree
:depth: maximum tree depth to read
:dictcls: type of the dict used to store the results
:check_exit_code: raise an exception if the shutil command fails
:decode_unicode: decode the content of files as utf-8
:strip_null_chars: remove '\x00' chars from the content of utf-8
decoded files
:returns: a tree-like dict with the content of files as leafs
"""
value_map = self.read_tree_values_flat(path, depth, check_exit_code,
decode_unicode, strip_null_chars)
return _build_path_tree(value_map, path, self.path.sep, dictcls) return _build_path_tree(value_map, path, self.path.sep, dictcls)
# internal methods # internal methods