Skip to content

Commit 9f6136d

Browse files
author
Konstantin Kondrashov
committed
Merge branch 'feature/parttable_tool_use_only_ascii_for_names' into 'master'
fix(partition_table): Ignore UTF-8 BOM bytes in csv file See merge request espressif/esp-idf!38954
2 parents c707faa + 179eb5a commit 9f6136d

File tree

7 files changed

+301
-159
lines changed

7 files changed

+301
-159
lines changed

components/partition_table/gen_esp32part.py

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
# SPDX-License-Identifier: Apache-2.0
1212
import argparse
1313
import binascii
14+
import codecs
1415
import errno
1516
import hashlib
1617
import os
@@ -175,21 +176,36 @@ def critical(msg):
175176
sys.stderr.write('\n')
176177

177178

179+
def get_encoding(first_bytes):
180+
"""Detect the encoding by checking for BOM (Byte Order Mark)"""
181+
BOMS = {
182+
codecs.BOM_UTF8: 'utf-8-sig',
183+
codecs.BOM_UTF16_LE: 'utf-16',
184+
codecs.BOM_UTF16_BE: 'utf-16',
185+
codecs.BOM_UTF32_LE: 'utf-32',
186+
codecs.BOM_UTF32_BE: 'utf-32',
187+
}
188+
for bom, encoding in BOMS.items():
189+
if first_bytes.startswith(bom):
190+
return encoding
191+
return 'utf-8'
192+
193+
178194
class PartitionTable(list):
179195
def __init__(self):
180196
super(PartitionTable, self).__init__(self)
181197

182198
@classmethod
183199
def from_file(cls, f):
184-
data = f.read()
185-
data_is_binary = data[0:2] == PartitionDefinition.MAGIC_BYTES
200+
bin_data = f.read()
201+
data_is_binary = bin_data[0:2] == PartitionDefinition.MAGIC_BYTES
186202
if data_is_binary:
187203
status('Parsing binary partition input...')
188-
return cls.from_binary(data), True
204+
return cls.from_binary(bin_data), True
189205

190-
data = data.decode()
206+
str_data = bin_data.decode(get_encoding(bin_data))
191207
status('Parsing CSV input...')
192-
return cls.from_csv(data), False
208+
return cls.from_csv(str_data), False
193209

194210
@classmethod
195211
def from_csv(cls, csv_contents):

components/partition_table/parttool.py

Lines changed: 91 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
# parttool is used to perform partition level operations - reading,
44
# writing, erasing and getting info about the partition.
55
#
6-
# SPDX-FileCopyrightText: 2018-2024 Espressif Systems (Shanghai) CO LTD
6+
# SPDX-FileCopyrightText: 2018-2025 Espressif Systems (Shanghai) CO LTD
77
# SPDX-License-Identifier: Apache-2.0
88
import argparse
99
import os
@@ -30,8 +30,7 @@ def status(msg):
3030
print(msg)
3131

3232

33-
class _PartitionId():
34-
33+
class _PartitionId:
3534
def __init__(self, name=None, p_type=None, subtype=None, part_list=None):
3635
self.name = name
3736
self.type = p_type
@@ -40,30 +39,39 @@ def __init__(self, name=None, p_type=None, subtype=None, part_list=None):
4039

4140

4241
class PartitionName(_PartitionId):
43-
4442
def __init__(self, name):
4543
_PartitionId.__init__(self, name=name)
4644

4745

4846
class PartitionType(_PartitionId):
49-
5047
def __init__(self, p_type, subtype, part_list=None):
5148
_PartitionId.__init__(self, p_type=p_type, subtype=subtype, part_list=part_list)
5249

5350

5451
PARTITION_BOOT_DEFAULT = _PartitionId()
5552

5653

57-
class ParttoolTarget():
58-
59-
def __init__(self, port=None, baud=None, partition_table_offset=PARTITION_TABLE_OFFSET, primary_bootloader_offset=None, recovery_bootloader_offset=None,
60-
partition_table_file=None, esptool_args=[], esptool_write_args=[], esptool_read_args=[], esptool_erase_args=[]):
54+
class ParttoolTarget:
55+
def __init__(
56+
self,
57+
port=None,
58+
baud=None,
59+
partition_table_offset=PARTITION_TABLE_OFFSET,
60+
primary_bootloader_offset=None,
61+
recovery_bootloader_offset=None,
62+
partition_table_file=None,
63+
esptool_args=[],
64+
esptool_write_args=[],
65+
esptool_read_args=[],
66+
esptool_erase_args=[],
67+
):
6168
self.port = port
6269
self.baud = baud
6370

6471
gen.offset_part_table = partition_table_offset
6572
gen.primary_bootloader_offset = primary_bootloader_offset
6673
gen.recovery_bootloader_offset = recovery_bootloader_offset
74+
gen.quiet = True
6775

6876
def parse_esptool_args(esptool_args):
6977
results = list()
@@ -84,23 +92,16 @@ def parse_esptool_args(esptool_args):
8492
self.esptool_erase_args = parse_esptool_args(esptool_erase_args)
8593

8694
if partition_table_file:
87-
partition_table = None
8895
with open(partition_table_file, 'rb') as f:
89-
input_is_binary = (f.read(2) == gen.PartitionDefinition.MAGIC_BYTES)
90-
f.seek(0)
91-
if input_is_binary:
92-
partition_table = gen.PartitionTable.from_binary(f.read())
93-
94-
if partition_table is None:
95-
with open(partition_table_file, 'r', encoding='utf-8') as f:
96-
f.seek(0)
97-
partition_table = gen.PartitionTable.from_csv(f.read())
96+
partition_table, _ = gen.PartitionTable.from_file(f)
9897
else:
9998
temp_file = tempfile.NamedTemporaryFile(delete=False)
10099
temp_file.close()
101100

102101
try:
103-
self._call_esptool(['read_flash', str(partition_table_offset), str(gen.MAX_PARTITION_LENGTH), temp_file.name])
102+
self._call_esptool(
103+
['read_flash', str(partition_table_offset), str(gen.MAX_PARTITION_LENGTH), temp_file.name]
104+
)
104105
with open(temp_file.name, 'rb') as f:
105106
partition_table = gen.PartitionTable.from_binary(f.read())
106107
finally:
@@ -152,30 +153,30 @@ def get_partition_info(self, partition_id):
152153

153154
def erase_partition(self, partition_id):
154155
partition = self.get_partition_info(partition_id)
155-
self._call_esptool(['erase_region', str(partition.offset), str(partition.size)] + self.esptool_erase_args)
156+
self._call_esptool(['erase_region', str(partition.offset), str(partition.size)] + self.esptool_erase_args)
156157

157158
def read_partition(self, partition_id, output):
158159
partition = self.get_partition_info(partition_id)
159160
self._call_esptool(['read_flash', str(partition.offset), str(partition.size), output] + self.esptool_read_args)
160161

161-
def write_partition(self, partition_id, input, ignore_readonly=False):
162+
def write_partition(self, partition_id, input, ignore_readonly=False): # noqa: A002
162163
partition = self.get_partition_info(partition_id)
163164

164165
if partition.readonly and not ignore_readonly:
165166
raise SystemExit(f'"{partition.name}" partition is read-only, (use the --ignore-readonly flag to skip it)')
166167

167168
self.erase_partition(partition_id)
168169

169-
with open(input, 'rb') as input_file:
170-
content_len = len(input_file.read())
170+
with open(input, 'rb') as f:
171+
content_len = len(f.read())
171172

172173
if content_len > partition.size:
173174
raise Exception('Input file size exceeds partition size')
174175

175176
self._call_esptool(['write_flash', str(partition.offset), input] + self.esptool_write_args)
176177

177178

178-
def _write_partition(target, partition_id, input, ignore_readonly=False):
179+
def _write_partition(target, partition_id, input, ignore_readonly=False): # noqa: A002
179180
target.write_partition(partition_id, input, ignore_readonly)
180181
partition = target.get_partition_info(partition_id)
181182
status("Written contents of file '{}' at offset 0x{:x}".format(input, partition.offset))
@@ -184,8 +185,11 @@ def _write_partition(target, partition_id, input, ignore_readonly=False):
184185
def _read_partition(target, partition_id, output):
185186
target.read_partition(partition_id, output)
186187
partition = target.get_partition_info(partition_id)
187-
status("Read partition '{}' contents from device at offset 0x{:x} to file '{}'"
188-
.format(partition.name, partition.offset, output))
188+
status(
189+
"Read partition '{}' contents from device at offset 0x{:x} to file '{}'".format(
190+
partition.name, partition.offset, output
191+
)
192+
)
189193

190194

191195
def _erase_partition(target, partition_id):
@@ -213,7 +217,7 @@ def _get_partition_info(target, partition_id, info):
213217
'offset': '0x{:x}'.format(p.offset),
214218
'size': '0x{:x}'.format(p.size),
215219
'encrypted': '{}'.format(p.encrypted),
216-
'readonly': '{}'.format(p.readonly)
220+
'readonly': '{}'.format(p.readonly),
217221
}
218222
for i in info:
219223
infos += [info_dict[i]]
@@ -232,19 +236,29 @@ def main():
232236
parser.add_argument('--esptool-args', help='additional main arguments for esptool', nargs='+')
233237
parser.add_argument('--esptool-write-args', help='additional subcommand arguments when writing to flash', nargs='+')
234238
parser.add_argument('--esptool-read-args', help='additional subcommand arguments when reading flash', nargs='+')
235-
parser.add_argument('--esptool-erase-args', help='additional subcommand arguments when erasing regions of flash', nargs='+')
239+
parser.add_argument(
240+
'--esptool-erase-args', help='additional subcommand arguments when erasing regions of flash', nargs='+'
241+
)
236242

237243
# By default the device attached to the specified port is queried for the partition table. If a partition table file
238244
# is specified, that is used instead.
239-
parser.add_argument('--port', '-p', help='port where the target device of the command is connected to; the partition table is sourced from this device \
240-
when the partition table file is not defined')
245+
parser.add_argument(
246+
'--port',
247+
'-p',
248+
help='port where the target device of the command is connected to; the partition table is sourced from '
249+
'this device when the partition table file is not defined',
250+
)
241251
parser.add_argument('--baud', '-b', help='baudrate to use', type=int)
242252

243253
parser.add_argument('--partition-table-offset', '-o', help='offset to read the partition table from', type=str)
244254
parser.add_argument('--primary-bootloader-offset', help='offset for primary bootloader', type=str)
245255
parser.add_argument('--recovery-bootloader-offset', help='offset for recovery bootloader', type=str)
246-
parser.add_argument('--partition-table-file', '-f', help='file (CSV/binary) to read the partition table from; \
247-
overrides device attached to specified port as the partition table source when defined')
256+
parser.add_argument(
257+
'--partition-table-file',
258+
'-f',
259+
help='file (CSV/binary) to read the partition table from; '
260+
'overrides device attached to specified port as the partition table source when defined',
261+
)
248262

249263
partition_selection_parser = argparse.ArgumentParser(add_help=False)
250264

@@ -254,31 +268,54 @@ def main():
254268

255269
partition_selection_args.add_argument('--partition-name', '-n', help='name of the partition')
256270
partition_selection_args.add_argument('--partition-type', '-t', help='type of the partition')
257-
partition_selection_args.add_argument('--partition-boot-default', '-d', help='select the default boot partition \
258-
using the same fallback logic as the IDF bootloader', action='store_true')
271+
partition_selection_args.add_argument(
272+
'--partition-boot-default',
273+
'-d',
274+
help='select the default boot partition \
275+
using the same fallback logic as the IDF bootloader',
276+
action='store_true',
277+
)
259278

260279
partition_selection_parser.add_argument('--partition-subtype', '-s', help='subtype of the partition')
261-
partition_selection_parser.add_argument('--extra-partition-subtypes', help='Extra partition subtype entries', nargs='*')
280+
partition_selection_parser.add_argument(
281+
'--extra-partition-subtypes', help='Extra partition subtype entries', nargs='*'
282+
)
262283

263284
subparsers = parser.add_subparsers(dest='operation', help='run parttool -h for additional help')
264285

265286
# Specify the supported operations
266-
read_part_subparser = subparsers.add_parser('read_partition', help='read partition from device and dump contents into a file',
267-
parents=[partition_selection_parser])
287+
read_part_subparser = subparsers.add_parser(
288+
'read_partition',
289+
help='read partition from device and dump contents into a file',
290+
parents=[partition_selection_parser],
291+
)
268292
read_part_subparser.add_argument('--output', help='file to dump the read partition contents to')
269293

270-
write_part_subparser = subparsers.add_parser('write_partition', help='write contents of a binary file to partition on device',
271-
parents=[partition_selection_parser])
294+
write_part_subparser = subparsers.add_parser(
295+
'write_partition',
296+
help='write contents of a binary file to partition on device',
297+
parents=[partition_selection_parser],
298+
)
272299
write_part_subparser.add_argument('--input', help='file whose contents are to be written to the partition offset')
273300
write_part_subparser.add_argument('--ignore-readonly', help='Ignore read-only attribute', action='store_true')
274301

275-
subparsers.add_parser('erase_partition', help='erase the contents of a partition on the device', parents=[partition_selection_parser])
276-
277-
print_partition_info_subparser = subparsers.add_parser('get_partition_info', help='get partition information', parents=[partition_selection_parser])
278-
print_partition_info_subparser.add_argument('--info', help='type of partition information to get',
279-
choices=['name', 'type', 'subtype', 'offset', 'size', 'encrypted', 'readonly'],
280-
default=['offset', 'size'], nargs='+')
281-
print_partition_info_subparser.add_argument('--part_list', help='Get a list of partitions suitable for a given type', action='store_true')
302+
subparsers.add_parser(
303+
'erase_partition', help='erase the contents of a partition on the device', parents=[partition_selection_parser]
304+
)
305+
306+
print_partition_info_subparser = subparsers.add_parser(
307+
'get_partition_info', help='get partition information', parents=[partition_selection_parser]
308+
)
309+
print_partition_info_subparser.add_argument(
310+
'--info',
311+
help='type of partition information to get',
312+
choices=['name', 'type', 'subtype', 'offset', 'size', 'encrypted', 'readonly'],
313+
default=['offset', 'size'],
314+
nargs='+',
315+
)
316+
print_partition_info_subparser.add_argument(
317+
'--part_list', help='Get a list of partitions suitable for a given type', action='store_true'
318+
)
282319

283320
args = parser.parse_args()
284321
quiet = args.quiet
@@ -299,8 +336,10 @@ def main():
299336
elif args.partition_boot_default:
300337
partition_id = PARTITION_BOOT_DEFAULT
301338
else:
302-
raise RuntimeError('Partition to operate on should be defined using --partition-name OR \
303-
partition-type,--partition-subtype OR partition-boot-default')
339+
raise RuntimeError(
340+
'Partition to operate on should be defined using --partition-name OR \
341+
partition-type,--partition-subtype OR partition-boot-default'
342+
)
304343

305344
# Prepare the device to perform operation on
306345
target_args = {}
@@ -341,18 +380,18 @@ def main():
341380
target = ParttoolTarget(**target_args)
342381

343382
# Create the operation table and execute the operation
344-
common_args = {'target':target, 'partition_id':partition_id}
383+
common_args = {'target': target, 'partition_id': partition_id}
345384
parttool_ops = {
346385
'erase_partition': (_erase_partition, []),
347386
'read_partition': (_read_partition, ['output']),
348387
'write_partition': (_write_partition, ['input', 'ignore_readonly']),
349-
'get_partition_info': (_get_partition_info, ['info'])
388+
'get_partition_info': (_get_partition_info, ['info']),
350389
}
351390

352391
(op, op_args) = parttool_ops[args.operation]
353392

354393
for op_arg in op_args:
355-
common_args.update({op_arg:vars(args)[op_arg]})
394+
common_args.update({op_arg: vars(args)[op_arg]})
356395

357396
if quiet:
358397
# If exceptions occur, suppress and exit quietly

0 commit comments

Comments
 (0)