2016-12-10 2 views
0

Для иллюстрации этот сценарий создает файл mapfile, содержащий содержимое файлов, заданное в качестве аргументов, добавленное двоичным заголовком с контрольной суммой sha1, что позволяет обнаруживать дублирование при последующих прогонах.Python: ctypes hashable c_char замена массива без отключения над ' 0' байтами

Здесь нужна замена хэширования ctypes.c_char, которая может содержать контрольные суммы sha1 с минимальным пухом, но не задыхаться от байтов '\ 0'.

# -*- coding: utf8 -* 

import io 
import mmap 
import ctypes 
import hashlib 
import logging 

from collections import OrderedDict 

log = logging.getLogger(__file__) 

def align(size, alignment): 
    """return size aligned to alignment""" 
    excess = size % alignment 
    if excess: 
     size = size - excess + alignment 
    return size 


class Header(ctypes.Structure): 
    Identifier = b'HEAD' 
    _fields_ = [ 
     ('id', ctypes.c_char * 4), 
     ('hlen', ctypes.c_uint16), 
     ('plen', ctypes.c_uint32), 
     ('name', ctypes.c_char * 128), 
     ('sha1', ctypes.c_char * 20), 
    ] 
HeaderSize = ctypes.sizeof(Header) 

class CtsMap: 
    def __init__(self, ctcls, mm, offset = 0): 
     self.ctcls = ctcls 
     self.mm = mm 
     self.offset = offset 

    def __enter__(self): 
     mm = self.mm 
     offset = self.offset 
     ctsize = ctypes.sizeof(self.ctcls) 
     if offset + ctsize > mm.size(): 
      newsize = align(offset + ctsize, mmap.PAGESIZE) 
      mm.resize(newsize) 
     self.ctinst = self.ctcls.from_buffer(mm, offset) 
     return self.ctinst 

    def __exit__(self, exc_type, exc_value, exc_traceback): 
     del self.ctinst 
     self.ctinst = None 

class MapFile: 
    def __init__(self, filename): 
     try: 
      # try to create initial file 
      mapsize = mmap.PAGESIZE 
      self._fd = open(filename, 'x+b') 
      self._fd.write(b'\0' * mapsize) 
     except FileExistsError: 
      # file exists and is writable 
      self._fd = open(filename, 'r+b') 
      self._fd.seek(0, io.SEEK_END) 
      mapsize = self._fd.tell() 
     # mmap this file completely 
     self._fd.seek(0) 
     self._mm = mmap.mmap(self._fd.fileno(), mapsize) 
     self._offset = 0 
     self._toc = OrderedDict() 
     self.gen_toc() 

    def gen_toc(self): 
     while self._offset < self._mm.size(): 
      with CtsMap(Header, self._mm, self._offset) as hd: 
       if hd.id == Header.Identifier and hd.hlen == HeaderSize: 
        self._toc[hd.sha1] = self._offset 
        log.debug('toc: [%s]%s: %s', len(hd.sha1), hd.sha1, self._offset) 
        self._offset += HeaderSize + hd.plen 
       else: 
        break 
      del hd 

    def add_data(self, datafile, data): 
     datasize = len(data) 
     sha1 = hashlib.sha1() 
     sha1.update(data) 
     digest = sha1.digest() 

     if digest in self._toc: 
      log.debug('add_data: %s added already', digest) 
      return None 

     log.debug('add_data: %s, %s bytes, %s', datafile, datasize, digest) 
     with CtsMap(Header, self._mm, self._offset) as hd: 
      hd.id = Header.Identifier 
      hd.hlen = HeaderSize 
      hd.plen = datasize 
      hd.name = datafile 
      hd.sha1 = digest 
     del hd 
     self._offset += HeaderSize 

     log.debug('add_data: %s', datasize) 
     blktype = ctypes.c_char * datasize 
     with CtsMap(blktype, self._mm, self._offset) as blk: 
      blk.raw = data 
     del blk 
     self._offset += datasize 
     return HeaderSize + datasize 

    def close(self): 
     self._mm.close() 
     self._fd.close() 


if __name__ == '__main__': 
    import os 
    import sys 

    logconfig = dict(
     level = logging.DEBUG, 
     format = '%(levelname)5s: %(message)s', 
    ) 
    logging.basicConfig(**logconfig) 

    mf = MapFile('mapfile') 
    for datafile in sys.argv[1:]: 
     if os.path.isfile(datafile): 
      try: 
       data = open(datafile, 'rb').read() 
      except OSError: 
       continue 
      else: 
       mf.add_data(datafile.encode('utf-8'), data) 
    mf.close() 

Пробег: python3 hashable_ctypes_bytes.py somefiles*

Призывая это во второй раз, он читает файл для сбора всех элементов в упорядоченном Словаре, используя sha1 переваривать в качестве ключа. К сожалению, семантика массива c_char немного связана с проводкой, так как она также ведет себя как строка с '\ 0' с завершением c, что приводит к усеченным контрольным суммам здесь.

См линия 3 и 4:

DEBUG: toc: [20]b'\xcd0\xd7\xd3\xbf\x9f\xe1\xfe\xffr\xa6g#\xee\xf8\x84\xb5S,u': 0 
DEBUG: toc: [20]b'\xe9\xfe\x1a;i\xcdG0\x84\x1b\r\x7f\xf9\x14\x868\xbdVl\x8d': 1273 
DEBUG: toc: [19]b'\xa2\xdb\xff$&\xfe\x0f\xb4\xcaB<F\x92\xc0\xf1`(\x96N': 3642 
DEBUG: toc: [15]b'O\x1b~c\x82\xeb)\x8f\xb5\x9c\x15\xd5e:\xa9': 4650 
DEBUG: toc: [20]b'\x80\xe9\xbcF\x97\xdc\x93DG\x90\x19\x8c\xca\xfep\x05\xbdM\xfby': 13841 
DEBUG: add_data: b'\xcd0\xd7\xd3\xbf\x9f\xe1\xfe\xffr\xa6g#\xee\xf8\x84\xb5S,u' added already 
DEBUG: add_data: b'\xe9\xfe\x1a;i\xcdG0\x84\x1b\r\x7f\xf9\x14\x868\xbdVl\x8d' added already 
DEBUG: add_data: b'../python/tmp/colorselect.py', 848 bytes, b'\xa2\xdb\xff$&\xfe\x0f\xb4\xcaB<F\x92\xc0\xf1`(\x96N\x00' 
DEBUG: add_data: 848 
DEBUG: add_data: b'../python/tmp/DemoCode.py', 9031 bytes, b'O\x1b~c\x82\xeb)\x8f\xb5\x9c\x15\xd5e:\xa9\x00p\x0f\xc04' 
DEBUG: add_data: 9031 
DEBUG: add_data: b'\x80\xe9\xbcF\x97\xdc\x93DG\x90\x19\x8c\xca\xfep\x05\xbdM\xfby' added already 

Обычное предложение заменить c_char * 20 с c_byte * 20, и потерями прозрачных байт обработки на этом пути. Помимо проблем, связанных с преобразованием данных, массивы c_byte не являются хешируемыми из-за того, что они являются bytearrays. Я не нашел практического решения, не переживая тяжелые проблемы с конверсией взад и вперед или прибегая к шестигранникам, что удваивает требования к размеру таблицы sha1.

Я думаю, что решение дизайна c_char смешать его с семантикой терминации нуля нуля было ошибкой в ​​первую очередь. Чтобы справиться с этим, я мог бы представить, чтобы добавить тип c_char_nz в ctypes, что устраняет эту проблему.

Для тех из вас, кто внимательно прочитал код, вы можете задаться вопросом о действиях del в структурах ctypes. Обсуждение этого вопроса можно найти here:.

ответ

0

Хотя приведенный ниже код делает обратное преобразование, о котором вы упомянули, он действительно скрывает проблему. Я нашел хэш, содержащий нуль, и поле теперь можно использовать в качестве словарного ключа. Надеюсь, поможет.

from ctypes import * 
import hashlib 

class Test(Structure): 
    _fields_ = [('_sha1',c_ubyte * 20)] 

    @property 
    def sha1(self): 
     return bytes(self._sha1) 

    @sha1.setter 
    def sha1(self, value): 
     self._sha1 = (c_ubyte * 20)(*value) 

test = Test() 
test.sha1 = hashlib.sha1(b'aaaaaaaaaaa').digest() 
D = {test.sha1:0} 
print(D) 

Выход:

{b'u\\\x00\x1fJ\xe3\xc8\x84>ZP\xddj\xa2\xfa#\x89=\xd3\xad': 0} 
+0

Ааа, спасибо, что это именно то, что я после. Приведение в @ sha1.setter - хороший трюк, которого я никогда не видел в python, соответственно. ctypes. – user3780002

+0

@ user3780002 Похоже на C cast, но '(c_ubyte * 20)' является типом (фактически 'c_ubyte_Array_20'). Создание экземпляра этого типа '(c_ubyte * 20) (...)' принимает до 20 параметров, '* value' расширяет длину байтовой строки длиной 20 байтов в отдельные параметры байта. –

Смежные вопросы