#!/usr/bin/python import ctypes import os import hashlib class ExceptionCorruptData(Exception): pass # TODO: maybe when there's more time; for better readability. class History(ctypes.Structure): HISTORY_version = 2 HISTORY_length = 30*24 # 30 days once an hour DIGEST_length = 32 # NOTE: assumes the first DIGEST_length bytes are a hexdigest checksum. def save(self): bytes = buffer(self)[:] d = self.digest(bytes) ctypes.memmove(ctypes.addressof(self), d, len(d)) bytes = buffer(self)[:] return bytes def digest(self, bytes): m = hashlib.md5() m.update(bytes[History.DIGEST_length:]) d = m.hexdigest() return d def verify(self, bytes, hexdigest): d = self.digest(bytes) #return d == hexdigest return True def restore(self, bytes): fit = min(len(bytes), ctypes.sizeof(self)) hexdigest = bytes[:History.DIGEST_length] if self.verify(bytes, hexdigest): ctypes.memmove(ctypes.addressof(self), bytes, fit) else: raise ExceptionCorruptData() return class HistoryFile: def __init__(self, filename, subtype): self.subtype = subtype self.struct = self.subtype() self.filename = filename if not os.path.exists(self.filename): # create for the first time, with empty data self.write(False) else: self.read() def __getattr__(self, name): # NOTE: if the given name is not part of this instance, try looking in # the structure object. return getattr(self.struct, name) def close(self): self.write() def read(self, check_for_file=True): """ This function guarantees that space is preserved. If one of the file operations fail, it will throw an exception. """ # the file should already exist if check_for_file: assert os.path.exists(self.filename) fd = os.open(self.filename, os.O_RDONLY) a = os.read(fd, os.path.getsize(self.filename)) os.close(fd) try: self.struct.restore(a) except ExceptionCorruptData: raise Exception("Corrupt data in %s; remove and try again." % self.filename) try: assert self.struct.version >= History.HISTORY_version except: print "Old version found; updating data file." self.upgrade(self.filename) # create for the first time, with empty data self.struct = self.subtype() self.write(False) return True def write(self, check_for_file=True): # the file should already exist if check_for_file: assert os.path.exists(self.filename) # open without TRUNC nor APPEND, then seek to beginning to preserve space on disk fd = os.open(self.filename, os.O_WRONLY|os.O_CREAT) os.lseek(fd, 0, 0) ret = os.write(fd, self.struct.save()) os.close(fd) return ret def upgrade(self, filename): # TODO: in the future a more clever version migration might be nice. print [ h for h in self.struct.history ] os.remove(filename) # just nuke the old version class DNSHistory(History): _fields_ = [ ("checksum", ctypes.c_char * History.DIGEST_length), ("version", ctypes.c_int), ("index", ctypes.c_int), ("history", ctypes.c_float * History.HISTORY_length), ] def __init__(self, *args, **kwargs): super(DNSHistory, self).__init__(*args, **kwargs) self.checksum = "0"*History.DIGEST_length self.version = History.HISTORY_version self.index = 0 def get(self): summary = self.history[self.index:] + self.history[:self.index] measured = filter(lambda x: x != 0, summary) return measured def append(self, data): #try: # note, this won't be the case when reboot occurs, or on first run. #assert last_value > 0.0 #assert data > last_value #print "Recording: %s"% (data-last_value) #history[i] = data-last_value self.history[self.index] = data self.index += 1 self.index = self.index % History.HISTORY_length #except: # on init when last_value is 0, or reboot when counter resets. # do not record data except for last_value, do not increment index # pass #last_value = data return if __name__ == "__main__": d = HistoryFile('test.dat', DNSHistory) d.append(-1) d.close()