def update_ucode_fit_entry (ifwi_bin, ucode_path): ifwi = IFWI_PARSER.parse_ifwi_binary (ifwi_bin) if not ifwi: print ("Not a valid ifwi image!") return -2 # Get microcode ucode_comps = IFWI_PARSER.locate_components (ifwi, ucode_path) if len(ucode_comps) == 0: print ("Cannot find microcode component in ifwi image!" % path) return -3 # Get partition from path bp = IFWI_PARSER.get_boot_partition_from_path (ucode_path) # Get fit entry path = 'IFWI/BIOS/TS%d/SG1A' % bp ifwi_comps = IFWI_PARSER.locate_components (ifwi, path) if len(ifwi_comps) == 0: path = 'IFWI/BIOS/SG1A' % bp ifwi_comps = IFWI_PARSER.locate_components (ifwi, path) if len(ifwi_comps) == 0: print ("Cannot find 'SG1A' in ifwi image!" % path) return -4 img_base = 0x100000000 - len(ifwi_bin) fit_addr = c_uint32.from_buffer(ifwi_bin, ifwi_comps[0].offset + ifwi_comps[0].length + FIT_ENTRY.FIT_OFFSET) fit_offset = fit_addr.value - img_base fit_header = FIT_ENTRY.from_buffer(ifwi_bin, fit_offset) if fit_header.address != bytes_to_value (bytearray(FIT_ENTRY.FIT_SIGNATURE)): print ("Cannot find FIT table !" % path) return -4 # Update Ucode entry address ucode_idx = 0 ucode_off = ucode_comps[0].offset ucode_list = UCODE_PARSER.parse (ifwi_bin[ucode_off:]) for fit_type in [0x01, 0x7f]: for idx in range(fit_header.size): fit_entry = FIT_ENTRY.from_buffer(ifwi_bin, fit_offset + (idx + 1) * 16) if fit_entry.type == fit_type: if ucode_idx < len(ucode_list): fit_entry.set_values(img_base + ucode_off, 0, 0x100, 0x1, 0) ucode_off += len(ucode_list[ucode_idx]) ucode_idx += 1 else: # more fit entry is available, clear this entry fit_entry.type = 0x7f if ucode_idx != len(ucode_list): print ("Not all microcode can be listed in FIT table due to limited FIT entry number !") return -5 # Update FIT checksum fit_header.checksum = 0 fit_sum = sum(ifwi_bin[fit_offset:fit_offset+fit_header.size*16]) fit_header.checksum = (0 - fit_sum) & 0xff return 0
def find_ifwi_region (spi_descriptor, rgn_name): frba = ((spi_descriptor.fl_map0 >> 16) & 0xFF) << 4 fl_reg = spi_descriptor.FLASH_REGIONS[rgn_name] + frba rgn_off = c_uint32.from_buffer(spi_descriptor, fl_reg) rgn_base = (rgn_off.value & 0x7FFF) << 12 rgn_limit = ((rgn_off.value & 0x7FFF0000) >> 4) | 0xFFF if rgn_limit <= rgn_base: return None, None else: return (rgn_base, rgn_limit)
def FindIfwiRegion (self, SpiDescriptor, RgnName): Frba = ((SpiDescriptor.FlMap0 >> 16) & 0xFF) << 4 FlReg = SpiDescriptor.FLASH_REGIONS[RgnName] + Frba RgnOff = c_uint32.from_buffer(SpiDescriptor, FlReg) RgnBase = (RgnOff.value & 0x7FFF) << 12 RgnLimit = ((RgnOff.value & 0x7FFF0000) >> 4) | 0xFFF if RgnLimit <= RgnBase: return None else: return (RgnBase, RgnLimit)
def _check_is_macho_header(self, offset: StaticFilePointer) -> bool: """Check if the data located at a file offset represents a valid Mach-O header, based on the magic Args: offset: File offset to read magic from Returns: True if the byte content of the file at 'offset' contain the magic number for a Mach-O slice, False if the magic is anything else """ magic = c_uint32.from_buffer(bytearray(self.get_bytes(offset, sizeof(c_uint32)))).value return magic in MachoParser._MACHO_MAGIC
def FindComponent (self, BiosBins, Name): # Find CFGDATA in SlimBoot image BiosSize = len(BiosBins) BiosBase = 0x100000000 - BiosSize FlashMapAddr = c_uint32.from_buffer(BiosBins, BiosSize - 8) FlashMapOff = FlashMapAddr.value - BiosBase FlashMapStr = FlashMap.from_buffer(BiosBins, FlashMapOff) CompList = [] if FlashMapStr.sig == FlashMap.FLASH_MAP_SIGNATURE: Offset = BiosSize EntryNum = ( FlashMapStr.length - sizeof(FlashMap)) // sizeof(FlashMapDesc) for idx in xrange(EntryNum): Desc = FlashMapDesc.from_buffer( BiosBins, FlashMapOff + sizeof(FlashMap) + idx * sizeof(FlashMapDesc)) Offset -= Desc.size # print '%s @ offset 0x%06X size 0x%x' % (Desc.sig, offset, Desc.size) if Name == Desc.sig: CompList.append((Offset, Desc.size)) return CompList
def handle_ts(bios_image, set_ts_val=0): # Since QEMU does not support flash top swap, this script will help # reassemble the image according to the top swap request inf = open(bios_image, "rb") bios_bins = bytearray(inf.read()) inf.close() # Get flash map bios_size = len(bios_bins) bios_base = 0x100000000 - bios_size flash_map_addr = c_uint32.from_buffer(bios_bins, bios_size - 8) flash_map_off = flash_map_addr.value - bios_base flash_map = FlashMap.from_buffer(bios_bins, flash_map_off) is_backup = 1 if flash_map.attributes & flash_map.FLASH_MAP_ATTRIBUTES[ 'BACKUP_REGION'] else 0 top_swap_size = 0 redundant_size = 0 entry_num = (flash_map.length - sizeof(FlashMap)) // sizeof(FlashMapDesc) for idx in range(entry_num): desc = FlashMapDesc.from_buffer( bios_bins, flash_map_off + sizeof(FlashMap) + idx * sizeof(FlashMapDesc)) if (desc.flags & FlashMap.FLASH_MAP_DESC_FLAGS['TOP_SWAP'] ) and not (desc.flags & FlashMap.FLASH_MAP_DESC_FLAGS['BACKUP']): top_swap_size += desc.size if (desc.flags & FlashMap.FLASH_MAP_DESC_FLAGS['REDUNDANT'] ) and not (desc.flags & FlashMap.FLASH_MAP_DESC_FLAGS['BACKUP']): redundant_size += desc.size top_a = None top_b = None curr = top_swap_size tmp = bios_bins[-curr:] # Get the FWU flag fwu_flg = tmp[-10] if fwu_flg != 0x90: tmp[-10] = 0x90 # Get the top swap request from image ts_req = tmp[-11] if ts_req != 0x90: tmp[-11] = 0x90 print("---------------------------------------------") print("State: %02X %02X" % (ts_req, fwu_flg)) if ts_req != 0x90: print("Automatically change TS !") else: print("Manually change TS !") inp = set_ts_val if inp == 0: ts_req = 0x00 elif inp == 1: ts_req = 0x80 else: ts_req = 0x90 print("Booting from BP%d" % is_backup) if is_backup: top_b = tmp else: top_a = tmp curr += top_swap_size tmp = bios_bins[-curr:-curr + top_swap_size] if is_backup: top_a = tmp else: top_b = tmp top_n = '\xff' * top_swap_size redundant_n = '\xff' * redundant_size curr += redundant_size redundant_a = bios_bins[-curr:-curr + redundant_size] curr += redundant_size redundant_b = bios_bins[-curr:-curr + redundant_size] non_redundant = bios_bins[:bios_size - curr] if ts_req == 0x00: ts = 0 tv = '0' elif ts_req == 0x80: ts = 1 tv = '1' else: # do not change anything ts = 0x80 tv = 'N' print("TS Current: (%d)" % (is_backup)) print("TS Reqest: %02X (%s)" % (ts_req, tv)) print("FWU Flags: %02X (%d)" % (fwu_flg, 0 if fwu_flg == 0x90 else 1)) if ts == 0x80: bios_dat = bios_bins print("No change") else: if ts & 1 == 0: # Boot from partition 0 bios_dat = non_redundant + redundant_b + redundant_a + top_b + top_a print("Activate Partition A") else: # Boot from partition 1 bios_dat = non_redundant + redundant_b + redundant_a + top_a + top_b print("Activate Partition B") inf = open(bios_image, "wb") inf.write(bios_dat) inf.close() return fwu_flg
def int_from_buffer(mmap_area, pos): return c_uint32.from_buffer(mmap_area, pos) #@UndefinedVariable
def file_magic(self) -> int: """Read file magic """ return c_uint32.from_buffer(bytearray(self.get_bytes(StaticFilePointer(0), sizeof(c_uint32)))).value
def cmd_create(args): """Create an ias-image""" print('Creating ias-image with %d files' % len(args.file)) # dictionary with image types names image_types_names = {IasHeader.TYPE_UNKNOWN: 'Unspecified', \ IasHeader.TYPE_KERNEL_CMDLINE: 'Linux command line', \ IasHeader.TYPE_KERNEL_BZIMAGE: 'Linux kernel (bzImage)', \ IasHeader.TYPE_MULTIFILE_BOOT: 'Multi-file boot image', \ IasHeader.TYPE_ELF_MULTI_BOOT: 'Stand-alone ELF multi-boot', \ IasHeader.TYPE_UPDATE_PACKAGE: 'Update Package Image with extra header', \ IasHeader.TYPE_ABL_CONFIG: 'ABL Configuration', \ IasHeader.TYPE_MRC_TRAINING_PARAM: 'MRC training parameter set', \ IasHeader.TYPE_IFWI_UPDATE_PACKAGE: 'IFWI update package', \ IasHeader.TYPE_PDR_UPDATE_PACKAGE: 'PDR update package', \ IasHeader.TYPE_FW_PACKAGE: 'Firmware package', \ IasHeader.TYPE_PREOS_CHECKER: 'Pre-OS checker'} # list of all multiple files image types multi_files_image_type = [IasHeader.TYPE_UNKNOWN, \ IasHeader.TYPE_MULTIFILE_BOOT, \ IasHeader.TYPE_ELF_MULTI_BOOT, \ IasHeader.TYPE_FW_PACKAGE] # process command line parameters verbose = args.verbose if args.imagetype != None: try: image_type = int(args.imagetype, 0) & 0xF0000 except ValueError: print("Error: No digits were found") return 1 if image_type not in image_types_names: print("Error: Not supported type image") return 1 public_key_present = int(args.imagetype, 0) & IasHeader.PUBKEY_PRESENT signature_present = int(args.imagetype, 0) & IasHeader.SIGNATURE_PRESENT else: image_type = int(IasHeader.TYPE_UNKNOWN) public_key_present = 0 signature_present = 0 page_aligned_num = 0 print('Detected image type is (0x%x) - %s' % (image_type, image_types_names[image_type])) # set values of alignment if args.page_aligned is None: if verbose > 0: print("Files in image will not be page aligned") elif int(args.page_aligned) >= 0: page_aligned_num = int(args.page_aligned) # correct to default value if necessary if (page_aligned_num == 0) and (image_type == IasHeader.TYPE_MULTIFILE_BOOT): page_aligned_num = 5 if verbose > 0: print('Creation of Multi-file boot image, default alignment from %d file' % page_aligned_num) elif (page_aligned_num == 0) and (image_type == IasHeader.TYPE_ELF_MULTI_BOOT): page_aligned_num = 4 if verbose > 0: print('Creation of Stand-alone ELF multi-boot image, default alignment from %d file' % page_aligned_num) elif (page_aligned_num == 0) and (image_type == IasHeader.TYPE_FW_PACKAGE): page_aligned_num = 2 if verbose > 0: print('Creation of Firmware Package Image, default alignment from %d file' % page_aligned_num) elif (page_aligned_num >= 0) and (multi_files_image_type.count(image_type) == 0): sys.stderr.write( 'Page alignment not supported for image type 0x%x (%s)\n' % (image_type, image_types_names[image_type])) return 1 elif (page_aligned_num == 0) and (image_type == IasHeader.TYPE_UNKNOWN): page_aligned_num = 2 # default value for Unknown image if (verbose > 0) and (args.page_aligned != None): print('Page alignment for image from file %d detected' % page_aligned_num) # Read file data files = [] for fpath in args.file: try: with open(fpath, 'rb') as input_file: sys.stdout.write(' %s ' % fpath) # file path # files.append(bytearray(input_file.read())) except IOError: print('Error: No such file or directory: %s' % fpath) return 1 print("(%s)" % human_size(len(files[-1]))) # file size # # check if there is enough files for page alignment, otherwise error if page_aligned_num > len(files): sys.stderr.write('Error. Page alignment number %d is higher than a number of input files %d\n' % ( page_aligned_num, len(files))) return 1 # Creating of Multi-file Boot image # Dummy files will be added if page alignment to 4KiB required if (image_type == IasHeader.TYPE_MULTIFILE_BOOT) or ((image_type == IasHeader.TYPE_UNKNOWN) and (len(files) > 1)): if len(files) < 1: print('Error: Please supply at least one input files') return 1 # find number of required dummy files if args.page_aligned is None: dummy_files = 0 else: dummy_files = len(files) - page_aligned_num + 1 if verbose > 2: print('%d dummy files will be added to page align the image' % dummy_files) # set initial file offset (payload data offset in image) # Type specific header length = 4 bytes (c_uint32) * number of files (input files + dummy files) file_offset = sizeof(IasHeader) + sizeof(c_uint32) * len(files) + sizeof(c_uint32) * dummy_files temp_len = 0 while temp_len < len(files): if ((temp_len + 1) >= page_aligned_num) and (page_aligned_num != 0): # calculate size of dummy file dummy_size = align_up(file_offset, 4 * KB) - file_offset # if dummy_size >= 0: if verbose > 1: print('Adding 0x%x (%d) bytes size dummy file' % (dummy_size, dummy_size)) files.insert(temp_len, bytearray(dummy_size)) # create array with dummy data temp_len += 1 # set iteration to skip this file file_offset += dummy_size # align up to 4B file_offset += align_up(len(files[temp_len]), 4) if verbose > 1: print('Adding file of size: %d (0x%x)' % (len(files[temp_len]), len(files[temp_len]))) else: # align up to 4B only padding = align_up(len(files[temp_len]), 4) - len(files[temp_len]) if verbose > 2: print(' Adding 0x%x (%d) pad bytes to align file to 0x4' % (padding, padding)) file_offset += padding file_offset += len(files[temp_len]) temp_len += 1 # set to next file if verbose > 2: print('File offset is %d (0x%x)' % (file_offset, file_offset)) # Creating of: # - Firmware-package image # - Elf-multiboot image # Command line files will be extended with 0x0 at the end if page alignment to 4KiB required elif (image_type == IasHeader.TYPE_ELF_MULTI_BOOT) or (image_type == IasHeader.TYPE_FW_PACKAGE): # initialize file offset (payload data start) in image file_offset = sizeof(IasHeader) + sizeof(c_uint32) * len(files) for temp_len in range(len(files)): # check if processing cmdline file if ((temp_len + 1) >= (page_aligned_num - 1)) and (((temp_len + 1) % 2) == 1) and (page_aligned_num != 0): # calculate padding padding = align_up(file_offset + len(files[temp_len]), 4 * KB) - (file_offset + len(files[temp_len])) if padding > 0: # align up to 4KiB if verbose > 1: print('Adding 0x%x (%d) pad bytes to align cmdline' % (padding, padding)) files[temp_len] += bytearray(padding) # add padding 0x0 bytes to file (cmdLine) file_offset += len(files[temp_len]) else: # align up to 4B only padding = align_up(len(files[temp_len]), 4) - len(files[temp_len]) if verbose > 2: print('Adding 0x%x (%d) pad bytes to align binary to 0x4' % (padding, padding)) file_offset += padding file_offset += len(files[temp_len]) if verbose > 2: print('File offset is %d (0x%x)' % (file_offset, file_offset)) # single-file images # Only alignment to 4 bytes for file will be added else: if len(files) > 1: sys.stderr.write('Error: Please supply only one input file\n') return 1 file_offset = sizeof(IasHeader) padding = align_up(len(files[0]), 4) - len(files[0]) if verbose > 2: print('Note: Adding 0x%x (%d) pad bytes to align file to 0x4' % (padding, padding)) file_offset += padding file_offset += len(files[0]) # set image size image_size = file_offset image_size += sizeof(c_uint32) # Checksum at the end of image payload # declare array for all files data = bytearray(image_size) ptr = 0 # Create header hdr = IasHeader.from_buffer(data, ptr) hdr.magic_pattern = struct.unpack(">I", IasHeader.MAGIC)[0] # ">I" big endian 4 bytes (integer) if args.imagetype is None: hdr.image_type = image_type else: hdr.image_type = int(args.imagetype, 0) if args.devkey: if (not public_key_present) | (not signature_present): print('WARNING: No public key or signature flag in image type, adding both') hdr.image_type |= IasHeader.SIGNATURE_PRESENT hdr.image_type |= IasHeader.PUBKEY_PRESENT hdr.version = 0 if len(files) >= 1: hdr.data_length = file_offset - sizeof(IasHeader) - len(files) * sizeof(c_uint32) else: hdr.data_length = file_offset - sizeof(IasHeader) hdr.data_offset = 0 hdr.uncompressed_len = hdr.data_length hdr.header_crc = 0 ptr += sizeof(IasHeader) # Create extended header (for multiple files images, types 3,4,10) # in Type Specific Header field if len(files) >= 1: ehdr_start = ptr ehdr_limit = ehdr_start + sizeof(c_uint32) * len(files) ehdr = (c_uint32 * len(files)).from_buffer(data, ehdr_start) for i in range(len(files)): ehdr[i] = len(files[i]) print('File %d size %d bytes' % (i + 1, ehdr[i])) ptr = ehdr_limit hdr.data_offset = ptr hdr.header_crc = crc32c_buf(data[0:24]) # Add file data for item in range(len(files)): f_start = ptr f_limit = f_start + len(files[item]) if verbose > 1: print('Adding file %d @ [0x%08x-0x%08x]' % (item + 1, f_start, f_limit)) data[f_start:f_limit] = files[item] ptr = align_up(f_limit, 4) # Add payload checksum crc_start = ptr crc_limit = crc_start + sizeof(c_uint32) crc = c_uint32.from_buffer(data, crc_start) sys.stdout.write('Calculating Checksum... ') crc.value = crc32c_buf(data[sizeof(hdr):hdr.data_offset + hdr.data_length]) print('Ok') ptr = crc_limit # delete all the views that will prevent resizing the data buffer when # signing del hdr del ehdr del crc if args.devkey: sys.stdout.write('Signing... ') try: with open(args.devkey, 'rb') as rsa_key_file: key = serialization.load_pem_private_key( rsa_key_file.read(), password=None, backend=default_backend() ) except IOError: print('Error: No such file or directory: %s' % args.devkey) return 1 # Calculate a PKCS#1 v1.5 signature signature = key.sign(bytes(data), crypto_padding.PKCS1v15(), hashes.SHA256()) # Extract public key from loaded key puk = key.public_key() puk_num = puk.public_numbers() mod_buf = pack_num(puk_num.n, RSA_KEYMOD_SIZE) exp_buf = pack_num(puk_num.e, RSA_KEYEXP_SIZE) data += bytearray([0xff] * (align_up(ptr, 256) - ptr)) data += signature data += reverse_bytearray(mod_buf) + exp_buf print('Ok') # Write file out sys.stdout.write('Writing... ') with open(args.output, 'wb') as output_file: output_file.write(data) print('Ok') return 0
class Inotify: """Base class for `TreeWatcher`. Interfaces with inotify(7). While `TreeWatcher` provides functionality for watching directories recursively, this is suitable for watching a file (or files). Attributes: exclusive_handlers (dict): maps `INFlags` to sets of `Inotify.EventHandler`s. Handler is executed iff event.mask == handler mask. inclusive_handlers (dict): maps `INFlags` to sets of `Inotify.EventHandler`s. Handler will be executed if a bitwise AND of the event.mask with the handler mask is non-zero. inotify_fd (int): file descriptor returned by call to `inotify_init1`. watch_flags (INFlags): flags to be passed to `inotify_add_watch`. watch_fds (dict): a dict mapping watch descriptors to their associated filenames. files (set): a set of filenames currently being watched. n_buffers (int): number of per instance read buffers. read_buffers (list): per instance read buffers. max_read (int): maximum bytes we can read. buf_size (int): in bytes. LEN_OFFSET (int): we need to read the length of the name before unpacking the bytes to the struct format. See the underlying struct inotify_event. """ LEN_OFFSET = sizeof(c_int) + sizeof(c_uint32) * 2 EventHandler = Callable[["Inotify", "InotifyEvent"], None] def __init__( self, *files: str, blocking: bool = True, watch_flags: INFlags = INFlags.NO_FLAGS, n_buffers: int = 1, buf_size: int = 1024, ): init_flags = INFlags.NO_FLAGS if blocking else INFlags.NONBLOCK self.inotify_fd = inotify_init1(init_flags) if self.inotify_fd < 0: raise OSError(os.strerror(get_errno())) self.n_buffers = n_buffers self.read_buffers = [bytearray(buf_size) for _ in range(n_buffers)] self.max_read = n_buffers * buf_size self.buf_size = buf_size self.exclusive_handlers: Dict[INFlags, Set[Inotify.EventHandler]] = {} self.inclusive_handlers: Dict[INFlags, Set[Inotify.EventHandler]] = {} self.watch_flags = watch_flags self.watch_fds: Dict[int, str] = {} self.files: Set[str] = set() for fname in files: self._add_watch(os.path.abspath(fname)) def teardown(self) -> None: for fd in self.watch_fds: self._rm_watch(fd) os.close(self.inotify_fd) def register_handler(self, event_mask: INFlags, handler: Inotify.EventHandler, exclusive=True): """Register a handler for matching events. Args: event_mask (INFlags): event mask to match. handler (Inotify.EventHandler): handler to call on matching event. exclusive (bool): whether to register it as an exclusive handler (otherwise, it will be inclusive). """ container = self.exclusive_handlers if exclusive else self.inclusive_handlers if event_mask in container: container[event_mask].add(handler) else: container[event_mask] = {handler} def _add_watch(self, fname: str, add_flags: INFlags = INFlags.MASK_CREATE) -> None: """Add an inotify watch for given file and update instance helper fields. Args: fname (string): file to watch. add_flags (INFlags): flags to pass to `inotify_add_watch`. Raises: OSError: `inotify_add_watch` returned -1 and set errno. """ if not os.path.exists(fname): raise FileNotFoundError(fname) watch_fd = inotify_add_watch( c_int(self.inotify_fd), c_char_p(fname.encode("utf-8")), c_uint32(self.watch_flags | add_flags), ) if watch_fd < 0: err = os.strerror(get_errno()) raise OSError(err) self.files.add(fname) self.watch_fds[watch_fd] = fname def _rm_watch(self, fd: int) -> None: if inotify_rm_watch(c_int(self.inotify_fd), c_int(fd)) < 0: print( f"Inotify: got error removing watch fd {fd} ({self.watch_fds[fd]}):", file=sys.stderr, ) print(os.strerror(get_errno()), file=sys.stderr) def get_event_abs_path(self, event: InotifyEvent) -> str: return f"{self.watch_fds[event.wd]}/{event.name}" def _handle_event(self, event: InotifyEvent) -> None: """Called for every event read from the inotify fd. To match events to exclusive handlers: - lookup event.mask in self.exclusive_handlers; and - execute every handler in the associated set. To match events to inclusive handlers: - iterate over the keys of self.inclusive_handlers; - bitwise AND each key with the event.handler; and - execute every handler in the associated set, iff the result of the AND is non-zero. Args: event (InotifyEvent): event read from the inotify fd. """ for handler in self.exclusive_handlers.get(INFlags(event.mask), []): handler(self, event) if self.inclusive_handlers: for _, handlers in filter(lambda x: x[0] & event.mask, self.inclusive_handlers.items()): for handler in handlers: handler(self, event) @staticmethod def get_event_struct_format(name_len: int) -> str: """Given an event with name of length (name_len), return the correct format string to pass to struct.unpack. Args: name_len (int): length of event name, taken from struct inotify_event.len. Returns: the struct format string. """ return f"iIII{name_len}s" def read(self) -> int: """Read from the inotify fd into buffers, unpack bytes to InotifyEvent instances, and call the event handler. Returns: number of bytes read. Raises: BufferError: bytes_read was equal to the total combined buffer size. """ if (bytes_read := os.readv(self.inotify_fd, self.read_buffers)) == self.max_read: raise BufferError("Inotify.read exceeded allocated buffers") if bytes_read < self.buf_size: buf = self.read_buffers[0] else: buf = reduce(add, self.read_buffers[:ceil(bytes_read / self.buf_size)]) offset = 0 while offset < bytes_read: name_len = c_uint32.from_buffer(buf, offset + self.LEN_OFFSET) fmt = self.get_event_struct_format(name_len.value) obj_size = calcsize(fmt) self._handle_event( InotifyEvent.from_struct( unpack(fmt, buf[offset:offset + obj_size]))) offset += obj_size return bytes_read