#!/usr/bin/env python # RaidGuessFS, a FUSE pseudo-filesystem to guess RAID parameters of a damaged device # Copyright (C) 2015 Ludovic Pouzenc # # This file is part of RaidGuessFS. # # RaidGuessFS is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # RaidGuessFS is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with RaidGuessFS. If not, see import logging, numpy import mydisks class MyRaid(): """Auxiliary class, managing RAID layer""" RAID_TYPES = [ '0', '1', '5', '5+0' ] RAID5_LAYOUTS = [ 'la', 'ra', 'ls', 'rs' ] @staticmethod def xor_blocks(fd_list, offset, size): """Compute bitwise XOR against a bunch of disks slice""" logging.info("Enter xor_blocks(fd_list(%i),0x%011x,%d)"%(len(fd_list), offset, size)) if size % 8 != 0: raise ValueError('xor_blocks : size must be multiple of 8') dt = numpy.dtype('= 3 else 0, '5+0': size * (self.raid_disk_count - 2) if self.raid_disk_count >= 6 and self.raid_disk_count % 2 == 0 else 0, }[raid_type] def sizeof_disk_xor(self, raid_type): return max(0, self.raid_end - self.raid_start) def sizeof_disk_parity(self, raid_type): size = max(0, self.raid_end - self.raid_start) / self.raid_sector_size * 16 return { '0' : 64, '1' : size if self.raid_disk_count == 2 else 64, '5' : size if self.raid_disk_count >= 3 else 64, '5+0': size if self.raid_disk_count >= 6 and self.raid_disk_count % 2 == 0 else 64, }[raid_type] def read_disk_xor(self,raid_type,offset,size): """Returns raw bitwise XOR against a bunch of disks slice""" return MyRaid.xor_blocks(self.raid_disks,offset,size)[1].tostring() def read_disk_parity(self,raid_type,offset,size): """Returns textual information about parity status of each sector""" logging.warn("Enter read_disk_parity(%s,%d,%d)"%(raid_type,offset,size)) msg = { '0' : 'There no notion of parity in RAID 0 mode\n', '1' : None if self.raid_disk_count == 2 else 'Wrong disk count (should be 2)\n', '5' : None if self.raid_disk_count >= 3 else 'Wrong disk count (should be >=3)\n', '5+0': None if self.raid_disk_count >= 6 and self.raid_disk_count % 2 == 0 else 'Wrong disk count (should be >=6 and even)\n', }[raid_type] if msg: return msg[offset:offset+size] start = self.raid_start + offset * self.raid_sector_size / 16 end = start + size * self.raid_sector_size / 16 #TODO : improove for nested levels if raid_type in ['1','5', '5+0']: result = ''.join( [ '0x%011x %c\n'%( addr, MyRaid.xor_blocks(self.raid_disks, addr, self.raid_sector_size)[0]) for addr in range(start, end, self.raid_sector_size) ]) else: result = None logging.warn("Exit. read_disk_parity(%s,%d,%d)"%(raid_type,offset,size)) return result def read_raid_result(self,raid_type,offset,size): """Returns actual RAID data""" if raid_type == '0': segment_no = offset / self.raid_chunk_size segment_off = offset % self.raid_chunk_size stripe_no = segment_no / self.raid_disk_count par_disk = -1 data_disk = segment_no % self.raid_disk_count off_disk = self.raid_start + stripe_no * self.raid_chunk_size + segment_off size2 = min(size, (segment_no+1) * self.raid_chunk_size - offset) elif raid_type == '1': segment_no = -1 segment_off = -1 stripe_no = -1 par_disk = 1 data_disk = 0 off_disk = self.raid_start + offset size2 = size elif raid_type == '5': segment_no = offset / self.raid_chunk_size segment_off = offset % self.raid_chunk_size stripe_no = segment_no / (self.raid_disk_count-1) if self.raid_layout in ['ls','la']: par_disk = (self.raid_disk_count-1) - (stripe_no % self.raid_disk_count) else: # self.raid_layout in ['rs','ra']: par_disk = stripe_no % self.raid_disk_count if self.raid_layout in ['ls','rs']: data_disk = (par_disk+1 + (segment_no % (self.raid_disk_count-1)) ) % self.raid_disk_count else: # self.raid_layout in ['la','ra']: data_disk = segment_no % (self.raid_disk_count-1) if data_disk >= par_disk: data_disk = data_disk + 1 off_disk = self.raid_start + stripe_no * self.raid_chunk_size + segment_off # Note : self make shorter read than asked but convince the reader to be chunck aligned, which is great size2 = min(size, (segment_no+1) * self.raid_chunk_size - offset) else: raise Exception('Unimplemented read_raid_result() for raid_type == %s', raid_type) logging.debug("raid.read_result(%s): offset=%d,segment_no=%d,segment_off=%d,stripe_no=%d,par_disk=%d,data_disk=%d,off_disk=%d,size2=%d,segment_off+size2=%d" % (raid_type,offset,segment_no,segment_off,stripe_no,par_disk,data_disk,off_disk,size2,segment_off+size2) ) data_fd = self.raid_disks[data_disk] if self.d.is_readable(self.raid_disk_order[data_disk],off_disk,size2): # No damaged sectors until the end of the chunck, so just read the data disk data_fd.seek(off_disk) data = data_fd.read(size2) else: logging.warn('Try to recovering damaged chunck (raid_offset: 0x%011x, data_disk: %i, disk_offset: 0x%011x' % (offset, self.raid_disk_order[data_disk], off_disk) ) # Damaged sectors, check / recover every sector other_disks = list(self.raid_disk_order) other_disks.remove(self.raid_disk_order[data_disk]) other_fds = list(self.raid_disks) other_fds.remove(data_fd) data_arr = [] for s in range(off_disk, off_disk+size2, self.raid_sector_size): if self.d.is_readable(self.raid_disk_order[data_disk],s,self.raid_sector_size): # Current sector is readable from data disk, read it logging.debug('-> 0x%011x : readable'%s) data_fd.seek(off_disk) data_arr.append(data_fd.read(self.raid_sector_size)) else: # Current sector is dead on data disk, recover it if possible recoverable = reduce(lambda a,b: a and b, [ self.d.is_readable(other_disk,off_disk,self.raid_sector_size) for other_disk in other_disks ]) if recoverable: logging.info('-> 0x%011x : recoverable'%s) data_arr.append( MyRaid.xor_blocks(other_fds, s,self.raid_sector_size)[1].tostring() ) else: logging.warn('-> 0x%011x : unrecoverable'%s) data_arr.append( '\0' * self.raid_sector_size) data = ''.join(data_arr) return data