spiffsgen.py
  1  #!/usr/bin/env python
  2  #
  3  # spiffsgen is a tool used to generate a spiffs image from a directory
  4  #
  5  # SPDX-FileCopyrightText: 2019-2022 Espressif Systems (Shanghai) CO LTD
  6  # SPDX-License-Identifier: Apache-2.0
  7  
  8  from __future__ import division, print_function
  9  
 10  import argparse
 11  import io
 12  import math
 13  import os
 14  import struct
 15  
 16  try:
 17      import typing
 18  
 19      TSP = typing.TypeVar('TSP', bound='SpiffsObjPageWithIdx')
 20      ObjIdsItem = typing.Tuple[int, typing.Type[TSP]]
 21  except ImportError:
 22      pass
 23  
 24  
 25  SPIFFS_PH_FLAG_USED_FINAL_INDEX = 0xF8
 26  SPIFFS_PH_FLAG_USED_FINAL = 0xFC
 27  
 28  SPIFFS_PH_FLAG_LEN = 1
 29  SPIFFS_PH_IX_SIZE_LEN = 4
 30  SPIFFS_PH_IX_OBJ_TYPE_LEN = 1
 31  SPIFFS_TYPE_FILE = 1
 32  
 33  # Based on typedefs under spiffs_config.h
 34  SPIFFS_OBJ_ID_LEN = 2  # spiffs_obj_id
 35  SPIFFS_SPAN_IX_LEN = 2  # spiffs_span_ix
 36  SPIFFS_PAGE_IX_LEN = 2  # spiffs_page_ix
 37  SPIFFS_BLOCK_IX_LEN = 2  # spiffs_block_ix
 38  
 39  
 40  class SpiffsBuildConfig(object):
 41      def __init__(self,
 42                   page_size,  # type: int
 43                   page_ix_len,  # type: int
 44                   block_size,  # type: int
 45                   block_ix_len,  # type: int
 46                   meta_len,  # type: int
 47                   obj_name_len,  # type: int
 48                   obj_id_len,  # type: int
 49                   span_ix_len,  # type: int
 50                   packed,  # type: bool
 51                   aligned,  # type: bool
 52                   endianness,  # type: str
 53                   use_magic,  # type: bool
 54                   use_magic_len,  # type: bool
 55                   aligned_obj_ix_tables  # type: bool
 56                   ):
 57          if block_size % page_size != 0:
 58              raise RuntimeError('block size should be a multiple of page size')
 59  
 60          self.page_size = page_size
 61          self.block_size = block_size
 62          self.obj_id_len = obj_id_len
 63          self.span_ix_len = span_ix_len
 64          self.packed = packed
 65          self.aligned = aligned
 66          self.obj_name_len = obj_name_len
 67          self.meta_len = meta_len
 68          self.page_ix_len = page_ix_len
 69          self.block_ix_len = block_ix_len
 70          self.endianness = endianness
 71          self.use_magic = use_magic
 72          self.use_magic_len = use_magic_len
 73          self.aligned_obj_ix_tables = aligned_obj_ix_tables
 74  
 75          self.PAGES_PER_BLOCK = self.block_size // self.page_size
 76          self.OBJ_LU_PAGES_PER_BLOCK = int(math.ceil(self.block_size / self.page_size * self.obj_id_len / self.page_size))
 77          self.OBJ_USABLE_PAGES_PER_BLOCK = self.PAGES_PER_BLOCK - self.OBJ_LU_PAGES_PER_BLOCK
 78  
 79          self.OBJ_LU_PAGES_OBJ_IDS_LIM = self.page_size // self.obj_id_len
 80  
 81          self.OBJ_DATA_PAGE_HEADER_LEN = self.obj_id_len + self.span_ix_len + SPIFFS_PH_FLAG_LEN
 82  
 83          pad = 4 - (4 if self.OBJ_DATA_PAGE_HEADER_LEN % 4 == 0 else self.OBJ_DATA_PAGE_HEADER_LEN % 4)
 84  
 85          self.OBJ_DATA_PAGE_HEADER_LEN_ALIGNED = self.OBJ_DATA_PAGE_HEADER_LEN + pad
 86          self.OBJ_DATA_PAGE_HEADER_LEN_ALIGNED_PAD = pad
 87          self.OBJ_DATA_PAGE_CONTENT_LEN = self.page_size - self.OBJ_DATA_PAGE_HEADER_LEN
 88  
 89          self.OBJ_INDEX_PAGES_HEADER_LEN = (self.OBJ_DATA_PAGE_HEADER_LEN_ALIGNED + SPIFFS_PH_IX_SIZE_LEN +
 90                                             SPIFFS_PH_IX_OBJ_TYPE_LEN + self.obj_name_len + self.meta_len)
 91          if aligned_obj_ix_tables:
 92              self.OBJ_INDEX_PAGES_HEADER_LEN_ALIGNED = (self.OBJ_INDEX_PAGES_HEADER_LEN + SPIFFS_PAGE_IX_LEN - 1) & ~(SPIFFS_PAGE_IX_LEN - 1)
 93              self.OBJ_INDEX_PAGES_HEADER_LEN_ALIGNED_PAD = self.OBJ_INDEX_PAGES_HEADER_LEN_ALIGNED - self.OBJ_INDEX_PAGES_HEADER_LEN
 94          else:
 95              self.OBJ_INDEX_PAGES_HEADER_LEN_ALIGNED = self.OBJ_INDEX_PAGES_HEADER_LEN
 96              self.OBJ_INDEX_PAGES_HEADER_LEN_ALIGNED_PAD = 0
 97  
 98          self.OBJ_INDEX_PAGES_OBJ_IDS_HEAD_LIM = (self.page_size - self.OBJ_INDEX_PAGES_HEADER_LEN_ALIGNED) // self.block_ix_len
 99          self.OBJ_INDEX_PAGES_OBJ_IDS_LIM = (self.page_size - self.OBJ_DATA_PAGE_HEADER_LEN_ALIGNED) // self.block_ix_len
100  
101  
102  class SpiffsFullError(RuntimeError):
103      pass
104  
105  
106  class SpiffsPage(object):
107      _endianness_dict = {
108          'little': '<',
109          'big': '>'
110      }
111  
112      _len_dict = {
113          1: 'B',
114          2: 'H',
115          4: 'I',
116          8: 'Q'
117      }
118  
119      def __init__(self, bix, build_config):  # type: (int, SpiffsBuildConfig) -> None
120          self.build_config = build_config
121          self.bix = bix
122  
123      def to_binary(self):  # type: () -> bytes
124          raise NotImplementedError()
125  
126  
127  class SpiffsObjPageWithIdx(SpiffsPage):
128      def __init__(self, obj_id, build_config):  # type: (int, SpiffsBuildConfig) -> None
129          super(SpiffsObjPageWithIdx, self).__init__(0, build_config)
130          self.obj_id = obj_id
131  
132      def to_binary(self):  # type: () -> bytes
133          raise NotImplementedError()
134  
135  
136  class SpiffsObjLuPage(SpiffsPage):
137      def __init__(self, bix, build_config):  # type: (int, SpiffsBuildConfig) -> None
138          SpiffsPage.__init__(self, bix, build_config)
139  
140          self.obj_ids_limit = self.build_config.OBJ_LU_PAGES_OBJ_IDS_LIM
141          self.obj_ids = list()  # type: typing.List[ObjIdsItem]
142  
143      def _calc_magic(self, blocks_lim):  # type: (int) -> int
144          # Calculate the magic value mirroring computation done by the macro SPIFFS_MAGIC defined in
145          # spiffs_nucleus.h
146          magic = 0x20140529 ^ self.build_config.page_size
147          if self.build_config.use_magic_len:
148              magic = magic ^ (blocks_lim - self.bix)
149          # narrow the result to build_config.obj_id_len bytes
150          mask = (2 << (8 * self.build_config.obj_id_len)) - 1
151          return magic & mask
152  
153      def register_page(self, page):  # type: (TSP) -> None
154          if not self.obj_ids_limit > 0:
155              raise SpiffsFullError()
156  
157          obj_id = (page.obj_id, page.__class__)
158          self.obj_ids.append(obj_id)
159          self.obj_ids_limit -= 1
160  
161      def to_binary(self):  # type: () -> bytes
162          img = b''
163  
164          for (obj_id, page_type) in self.obj_ids:
165              if page_type == SpiffsObjIndexPage:
166                  obj_id ^= (1 << ((self.build_config.obj_id_len * 8) - 1))
167              img += struct.pack(SpiffsPage._endianness_dict[self.build_config.endianness] +
168                                 SpiffsPage._len_dict[self.build_config.obj_id_len], obj_id)
169  
170          assert len(img) <= self.build_config.page_size
171  
172          img += b'\xFF' * (self.build_config.page_size - len(img))
173  
174          return img
175  
176      def magicfy(self, blocks_lim):  # type: (int) -> None
177          # Only use magic value if no valid obj id has been written to the spot, which is the
178          # spot taken up by the last obj id on last lookup page. The parent is responsible
179          # for determining which is the last lookup page and calling this function.
180          remaining = self.obj_ids_limit
181          empty_obj_id_dict = {
182              1: 0xFF,
183              2: 0xFFFF,
184              4: 0xFFFFFFFF,
185              8: 0xFFFFFFFFFFFFFFFF
186          }
187          if remaining >= 2:
188              for i in range(remaining):
189                  if i == remaining - 2:
190                      self.obj_ids.append((self._calc_magic(blocks_lim), SpiffsObjDataPage))
191                      break
192                  else:
193                      self.obj_ids.append((empty_obj_id_dict[self.build_config.obj_id_len], SpiffsObjDataPage))
194                  self.obj_ids_limit -= 1
195  
196  
197  class SpiffsObjIndexPage(SpiffsObjPageWithIdx):
198      def __init__(self, obj_id, span_ix, size, name, build_config
199                   ):  # type: (int, int, int, str, SpiffsBuildConfig) -> None
200          super(SpiffsObjIndexPage, self).__init__(obj_id, build_config)
201          self.span_ix = span_ix
202          self.name = name
203          self.size = size
204  
205          if self.span_ix == 0:
206              self.pages_lim = self.build_config.OBJ_INDEX_PAGES_OBJ_IDS_HEAD_LIM
207          else:
208              self.pages_lim = self.build_config.OBJ_INDEX_PAGES_OBJ_IDS_LIM
209  
210          self.pages = list()  # type: typing.List[int]
211  
212      def register_page(self, page):  # type: (SpiffsObjDataPage) -> None
213          if not self.pages_lim > 0:
214              raise SpiffsFullError
215  
216          self.pages.append(page.offset)
217          self.pages_lim -= 1
218  
219      def to_binary(self):  # type: () -> bytes
220          obj_id = self.obj_id ^ (1 << ((self.build_config.obj_id_len * 8) - 1))
221          img = struct.pack(SpiffsPage._endianness_dict[self.build_config.endianness] +
222                            SpiffsPage._len_dict[self.build_config.obj_id_len] +
223                            SpiffsPage._len_dict[self.build_config.span_ix_len] +
224                            SpiffsPage._len_dict[SPIFFS_PH_FLAG_LEN],
225                            obj_id,
226                            self.span_ix,
227                            SPIFFS_PH_FLAG_USED_FINAL_INDEX)
228  
229          # Add padding before the object index page specific information
230          img += b'\xFF' * self.build_config.OBJ_DATA_PAGE_HEADER_LEN_ALIGNED_PAD
231  
232          # If this is the first object index page for the object, add filname, type
233          # and size information
234          if self.span_ix == 0:
235              img += struct.pack(SpiffsPage._endianness_dict[self.build_config.endianness] +
236                                 SpiffsPage._len_dict[SPIFFS_PH_IX_SIZE_LEN]  +
237                                 SpiffsPage._len_dict[SPIFFS_PH_FLAG_LEN],
238                                 self.size,
239                                 SPIFFS_TYPE_FILE)
240  
241              img += self.name.encode() + (b'\x00' * (
242                  (self.build_config.obj_name_len - len(self.name))
243                  + self.build_config.meta_len
244                  + self.build_config.OBJ_INDEX_PAGES_HEADER_LEN_ALIGNED_PAD))
245  
246          # Finally, add the page index of daa pages
247          for page in self.pages:
248              page = page >> int(math.log(self.build_config.page_size, 2))
249              img += struct.pack(SpiffsPage._endianness_dict[self.build_config.endianness] +
250                                 SpiffsPage._len_dict[self.build_config.page_ix_len], page)
251  
252          assert len(img) <= self.build_config.page_size
253  
254          img += b'\xFF' * (self.build_config.page_size - len(img))
255  
256          return img
257  
258  
259  class SpiffsObjDataPage(SpiffsObjPageWithIdx):
260      def __init__(self, offset, obj_id, span_ix, contents, build_config
261                   ):  # type: (int, int, int, bytes, SpiffsBuildConfig) -> None
262          super(SpiffsObjDataPage, self).__init__(obj_id, build_config)
263          self.span_ix = span_ix
264          self.contents = contents
265          self.offset = offset
266  
267      def to_binary(self):  # type: () -> bytes
268          img = struct.pack(SpiffsPage._endianness_dict[self.build_config.endianness] +
269                            SpiffsPage._len_dict[self.build_config.obj_id_len] +
270                            SpiffsPage._len_dict[self.build_config.span_ix_len] +
271                            SpiffsPage._len_dict[SPIFFS_PH_FLAG_LEN],
272                            self.obj_id,
273                            self.span_ix,
274                            SPIFFS_PH_FLAG_USED_FINAL)
275  
276          img += self.contents
277  
278          assert len(img) <= self.build_config.page_size
279  
280          img += b'\xFF' * (self.build_config.page_size - len(img))
281  
282          return img
283  
284  
285  class SpiffsBlock(object):
286      def _reset(self):  # type: () -> None
287          self.cur_obj_index_span_ix = 0
288          self.cur_obj_data_span_ix = 0
289          self.cur_obj_id = 0
290          self.cur_obj_idx_page = None  # type: typing.Optional[SpiffsObjIndexPage]
291  
292      def __init__(self, bix, build_config):  # type: (int, SpiffsBuildConfig) -> None
293          self.build_config = build_config
294          self.offset = bix * self.build_config.block_size
295          self.remaining_pages = self.build_config.OBJ_USABLE_PAGES_PER_BLOCK
296          self.pages = list()  # type: typing.List[SpiffsPage]
297          self.bix = bix
298  
299          lu_pages = list()
300          for i in range(self.build_config.OBJ_LU_PAGES_PER_BLOCK):
301              page = SpiffsObjLuPage(self.bix, self.build_config)
302              lu_pages.append(page)
303  
304          self.pages.extend(lu_pages)
305  
306          self.lu_page_iter = iter(lu_pages)
307          self.lu_page = next(self.lu_page_iter)
308  
309          self._reset()
310  
311      def _register_page(self, page):  # type: (TSP) -> None
312          if isinstance(page, SpiffsObjDataPage):
313              assert self.cur_obj_idx_page is not None
314              self.cur_obj_idx_page.register_page(page)  # can raise SpiffsFullError
315  
316          try:
317              self.lu_page.register_page(page)
318          except SpiffsFullError:
319              self.lu_page = next(self.lu_page_iter)
320              try:
321                  self.lu_page.register_page(page)
322              except AttributeError:  # no next lookup page
323                  # Since the amount of lookup pages is pre-computed at every block instance,
324                  # this should never occur
325                  raise RuntimeError('invalid attempt to add page to a block when there is no more space in lookup')
326  
327          self.pages.append(page)
328  
329      def begin_obj(self, obj_id, size, name, obj_index_span_ix=0, obj_data_span_ix=0
330                    ):  # type: (int, int, str, int, int) -> None
331          if not self.remaining_pages > 0:
332              raise SpiffsFullError()
333          self._reset()
334  
335          self.cur_obj_id = obj_id
336          self.cur_obj_index_span_ix = obj_index_span_ix
337          self.cur_obj_data_span_ix = obj_data_span_ix
338  
339          page = SpiffsObjIndexPage(obj_id, self.cur_obj_index_span_ix, size, name, self.build_config)
340          self._register_page(page)
341  
342          self.cur_obj_idx_page = page
343  
344          self.remaining_pages -= 1
345          self.cur_obj_index_span_ix += 1
346  
347      def update_obj(self, contents):  # type: (bytes) -> None
348          if not self.remaining_pages > 0:
349              raise SpiffsFullError()
350          page = SpiffsObjDataPage(self.offset + (len(self.pages) * self.build_config.page_size),
351                                   self.cur_obj_id, self.cur_obj_data_span_ix, contents, self.build_config)
352  
353          self._register_page(page)
354  
355          self.cur_obj_data_span_ix += 1
356          self.remaining_pages -= 1
357  
358      def end_obj(self):  # type: () -> None
359          self._reset()
360  
361      def is_full(self):  # type: () -> bool
362          return self.remaining_pages <= 0
363  
364      def to_binary(self, blocks_lim):  # type: (int) -> bytes
365          img = b''
366  
367          if self.build_config.use_magic:
368              for (idx, page) in enumerate(self.pages):
369                  if idx == self.build_config.OBJ_LU_PAGES_PER_BLOCK - 1:
370                      assert isinstance(page, SpiffsObjLuPage)
371                      page.magicfy(blocks_lim)
372                  img += page.to_binary()
373          else:
374              for page in self.pages:
375                  img += page.to_binary()
376  
377          assert len(img) <= self.build_config.block_size
378  
379          img += b'\xFF' * (self.build_config.block_size - len(img))
380          return img
381  
382  
383  class SpiffsFS(object):
384      def __init__(self, img_size, build_config):  # type: (int, SpiffsBuildConfig) -> None
385          if img_size % build_config.block_size != 0:
386              raise RuntimeError('image size should be a multiple of block size')
387  
388          self.img_size = img_size
389          self.build_config = build_config
390  
391          self.blocks = list()  # type: typing.List[SpiffsBlock]
392          self.blocks_lim = self.img_size // self.build_config.block_size
393          self.remaining_blocks = self.blocks_lim
394          self.cur_obj_id = 1  # starting object id
395  
396      def _create_block(self):  # type: () -> SpiffsBlock
397          if self.is_full():
398              raise SpiffsFullError('the image size has been exceeded')
399  
400          block = SpiffsBlock(len(self.blocks), self.build_config)
401          self.blocks.append(block)
402          self.remaining_blocks -= 1
403          return block
404  
405      def is_full(self):  # type: () -> bool
406          return self.remaining_blocks <= 0
407  
408      def create_file(self, img_path, file_path):  # type: (str, str) -> None
409          if len(img_path) > self.build_config.obj_name_len:
410              raise RuntimeError("object name '%s' too long" % img_path)
411  
412          name = img_path
413  
414          with open(file_path, 'rb') as obj:
415              contents = obj.read()
416  
417          stream = io.BytesIO(contents)
418  
419          try:
420              block = self.blocks[-1]
421              block.begin_obj(self.cur_obj_id, len(contents), name)
422          except (IndexError, SpiffsFullError):
423              block = self._create_block()
424              block.begin_obj(self.cur_obj_id, len(contents), name)
425  
426          contents_chunk = stream.read(self.build_config.OBJ_DATA_PAGE_CONTENT_LEN)
427  
428          while contents_chunk:
429              try:
430                  block = self.blocks[-1]
431                  try:
432                      # This can fail because either (1) all the pages in block have been
433                      # used or (2) object index has been exhausted.
434                      block.update_obj(contents_chunk)
435                  except SpiffsFullError:
436                      # If its (1), use the outer exception handler
437                      if block.is_full():
438                          raise SpiffsFullError
439                      # If its (2), write another object index page
440                      block.begin_obj(self.cur_obj_id, len(contents), name,
441                                      obj_index_span_ix=block.cur_obj_index_span_ix,
442                                      obj_data_span_ix=block.cur_obj_data_span_ix)
443                      continue
444              except (IndexError, SpiffsFullError):
445                  # All pages in the block have been exhausted. Create a new block, copying
446                  # the previous state of the block to a new one for the continuation of the
447                  # current object
448                  prev_block = block
449                  block = self._create_block()
450                  block.cur_obj_id = prev_block.cur_obj_id
451                  block.cur_obj_idx_page = prev_block.cur_obj_idx_page
452                  block.cur_obj_data_span_ix = prev_block.cur_obj_data_span_ix
453                  block.cur_obj_index_span_ix = prev_block.cur_obj_index_span_ix
454                  continue
455  
456              contents_chunk = stream.read(self.build_config.OBJ_DATA_PAGE_CONTENT_LEN)
457  
458          block.end_obj()
459  
460          self.cur_obj_id += 1
461  
462      def to_binary(self):  # type: () -> bytes
463          img = b''
464          all_blocks = []
465          for block in self.blocks:
466              all_blocks.append(block.to_binary(self.blocks_lim))
467          bix = len(self.blocks)
468          if self.build_config.use_magic:
469              # Create empty blocks with magic numbers
470              while self.remaining_blocks > 0:
471                  block = SpiffsBlock(bix, self.build_config)
472                  all_blocks.append(block.to_binary(self.blocks_lim))
473                  self.remaining_blocks -= 1
474                  bix += 1
475          else:
476              # Just fill remaining spaces FF's
477              all_blocks.append(b'\xFF' * (self.img_size - len(all_blocks) * self.build_config.block_size))
478          img += b''.join([blk for blk in all_blocks])
479          return img
480  
481  
482  class CustomHelpFormatter(argparse.HelpFormatter):
483      """
484      Similar to argparse.ArgumentDefaultsHelpFormatter, except it
485      doesn't add the default value if "(default:" is already present.
486      This helps in the case of options with action="store_false", like
487      --no-magic or --no-magic-len.
488      """
489      def _get_help_string(self, action):  # type: (argparse.Action) -> str
490          if action.help is None:
491              return ''
492          if '%(default)' not in action.help and '(default:' not in action.help:
493              if action.default is not argparse.SUPPRESS:
494                  defaulting_nargs = [argparse.OPTIONAL, argparse.ZERO_OR_MORE]
495                  if action.option_strings or action.nargs in defaulting_nargs:
496                      return action.help + ' (default: %(default)s)'
497          return action.help
498  
499  
500  def main():  # type: () -> None
501      parser = argparse.ArgumentParser(description='SPIFFS Image Generator',
502                                       formatter_class=CustomHelpFormatter)
503  
504      parser.add_argument('image_size',
505                          help='Size of the created image')
506  
507      parser.add_argument('base_dir',
508                          help='Path to directory from which the image will be created')
509  
510      parser.add_argument('output_file',
511                          help='Created image output file path')
512  
513      parser.add_argument('--page-size',
514                          help='Logical page size. Set to value same as CONFIG_SPIFFS_PAGE_SIZE.',
515                          type=int,
516                          default=256)
517  
518      parser.add_argument('--block-size',
519                          help="Logical block size. Set to the same value as the flash chip's sector size (g_rom_flashchip.sector_size).",
520                          type=int,
521                          default=4096)
522  
523      parser.add_argument('--obj-name-len',
524                          help='File full path maximum length. Set to value same as CONFIG_SPIFFS_OBJ_NAME_LEN.',
525                          type=int,
526                          default=32)
527  
528      parser.add_argument('--meta-len',
529                          help='File metadata length. Set to value same as CONFIG_SPIFFS_META_LENGTH.',
530                          type=int,
531                          default=4)
532  
533      parser.add_argument('--use-magic',
534                          dest='use_magic',
535                          help='Use magic number to create an identifiable SPIFFS image. Specify if CONFIG_SPIFFS_USE_MAGIC.',
536                          action='store_true')
537  
538      parser.add_argument('--no-magic',
539                          dest='use_magic',
540                          help='Inverse of --use-magic (default: --use-magic is enabled)',
541                          action='store_false')
542  
543      parser.add_argument('--use-magic-len',
544                          dest='use_magic_len',
545                          help='Use position in memory to create different magic numbers for each block. Specify if CONFIG_SPIFFS_USE_MAGIC_LENGTH.',
546                          action='store_true')
547  
548      parser.add_argument('--no-magic-len',
549                          dest='use_magic_len',
550                          help='Inverse of --use-magic-len (default: --use-magic-len is enabled)',
551                          action='store_false')
552  
553      parser.add_argument('--follow-symlinks',
554                          help='Take into account symbolic links during partition image creation.',
555                          action='store_true')
556  
557      parser.add_argument('--big-endian',
558                          help='Specify if the target architecture is big-endian. If not specified, little-endian is assumed.',
559                          action='store_true')
560  
561      parser.add_argument('--aligned-obj-ix-tables',
562                          action='store_true',
563                          help='Use aligned object index tables. Specify if SPIFFS_ALIGNED_OBJECT_INDEX_TABLES is set.')
564  
565      parser.set_defaults(use_magic=True, use_magic_len=True)
566  
567      args = parser.parse_args()
568  
569      if not os.path.exists(args.base_dir):
570          raise RuntimeError('given base directory %s does not exist' % args.base_dir)
571  
572      with open(args.output_file, 'wb') as image_file:
573          image_size = int(args.image_size, 0)
574          spiffs_build_default = SpiffsBuildConfig(args.page_size, SPIFFS_PAGE_IX_LEN,
575                                                   args.block_size, SPIFFS_BLOCK_IX_LEN, args.meta_len,
576                                                   args.obj_name_len, SPIFFS_OBJ_ID_LEN, SPIFFS_SPAN_IX_LEN,
577                                                   True, True, 'big' if args.big_endian else 'little',
578                                                   args.use_magic, args.use_magic_len, args.aligned_obj_ix_tables)
579  
580          spiffs = SpiffsFS(image_size, spiffs_build_default)
581  
582          for root, dirs, files in os.walk(args.base_dir, followlinks=args.follow_symlinks):
583              for f in files:
584                  full_path = os.path.join(root, f)
585                  spiffs.create_file('/' + os.path.relpath(full_path, args.base_dir).replace('\\', '/'), full_path)
586  
587          image = spiffs.to_binary()
588  
589          image_file.write(image)
590  
591  
592  if __name__ == '__main__':
593      main()