summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLudovic Pouzenc <lpouzenc@lud-GB1>2015-06-12 08:34:26 +0200
committerLudovic Pouzenc <lpouzenc@lud-GB1>2015-06-12 08:34:26 +0200
commit5943acb92ce0159e9f482748e4fa4aadddae6851 (patch)
treee4fc66dabda439f8a98535f10287018e871c2e41
parent49c830d2d70547c31cab1b1f0bc9f26d77d62a7e (diff)
downloadraidguessfs-5943acb92ce0159e9f482748e4fa4aadddae6851.tar.gz
raidguessfs-5943acb92ce0159e9f482748e4fa4aadddae6851.tar.bz2
raidguessfs-5943acb92ce0159e9f482748e4fa4aadddae6851.zip
Initial import.
RAID 5 xor parity checking is working, data reading on left-assymetric RAID 5 is working. Nothing done on other RAID types.
-rw-r--r--mybinview.py96
-rw-r--r--mydisks.py75
-rw-r--r--myraid.py154
-rw-r--r--mystat.py57
-rwxr-xr-xraidguessfs.py351
-rwxr-xr-xtools/pattern.py12
6 files changed, 745 insertions, 0 deletions
diff --git a/mybinview.py b/mybinview.py
new file mode 100644
index 0000000..5064013
--- /dev/null
+++ b/mybinview.py
@@ -0,0 +1,96 @@
+#!/usr/bin/env python
+
+# RaidGuessFS, a FUSE pseudo-filesystem to guess RAID parameters of a damaged device
+# Copyright (C) 2015 Ludovic Pouzenc <ludovic@pouzenc.fr>
+#
+# 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 <http://www.gnu.org/licenses/>
+
+import struct
+
+class MyBinView():
+ """"Auxiliary class, dumping binary data as image files"""
+
+ def __init__(self, *args, **kwargs):
+ self.bmp_start_offset=0
+ self.bmp_height=4096
+ self.bmp_width=1024
+
+ self.bmp_pixeldata_start=0
+ self.bmp_size=0
+ self.bmp_header=bytearray()
+
+ def get_bmp_start_offset(self):
+ return self.bmp_start_offset
+
+ def get_bmp_height(self):
+ return self.bmp_height
+
+ def get_bmp_width(self):
+ return self.bmp_width
+
+ def set_bmp_start_offset(self, new_bmp_start_offset):
+ """Offset to choose which fiel part you want to render as image"""
+ self.bmp_start_offset = new_bmp_start_offset
+
+ def set_bmp_width(self, new_bmp_width):
+ """Update the BMP (yes bitmap images) width in pixels"""
+ self.bmp_width = new_bmp_width / 4 * 4
+ self.refresh_bmp()
+ self.refresh_disks_dentries()
+
+ def set_bmp_height(self, new_bmp_height):
+ """Update the BMP (yes bitmap images) height in pixels"""
+ self.bmp_height = new_bmp_height
+ self.refresh_bmp()
+ self.refresh_disks_dentries()
+
+ def refresh_bmp(self):
+ """Update the BMP headers accroding to bitmap settings"""
+ bmp_header_fmt = struct.Struct('< 2s I x x x x I I i i H H I I i i I I')
+
+ bitmap_pixel_count = self.bmp_width * self.bmp_height
+ # From https://en.wikipedia.org/wiki/BMP_file_format
+ bitmap_magic='BM'
+ dib_size=40 # BITMAPINFOHEADER (Windows NT, 3.1x or later)
+ bitmap_bpp=8
+ bitmap_compress=0
+ imagehres=2835 # pixels per meter
+ imagevres=2835
+ palette_size=256
+ palette_start = bmp_header_fmt.size
+
+ self.bmp_pixeldata_start = palette_start + palette_size*4
+ self.bmp_size = self.bmp_pixeldata_start + bitmap_pixel_count
+ self.bmp_header = bytearray(self.bmp_pixeldata_start)
+ bmp_header_fmt.pack_into(self.bmp_header,0,
+ 'BM', self.bmp_size, self.bmp_pixeldata_start, dib_size,
+ self.bmp_width, self.bmp_height, 1, bitmap_bpp, bitmap_compress,
+ bitmap_pixel_count, imagehres, imagevres, palette_size, palette_size
+ )
+
+ for i in range(palette_size):
+ struct.pack_into('< B B B B', self.bmp_header, bmp_header_fmt.size + i*4, i,i,i,0) # All shades of gray in RGBA
+
+ def read(self, fd, offset, size):
+ if offset < self.bmp_pixeldata_start:
+ data = str(self.bmp_header[offset:offset+size-1])
+ if offset + size > self.bmp_pixeldata_start:
+ data += self.read(fd, self.bmp_pixeldata_start, size - self.bmp_pixeldata_start)
+ return data
+ else:
+ fd.seek(self.bmp_start_offset + offset - self.bmp_pixeldata_start)
+ return fd.read(size)
+
diff --git a/mydisks.py b/mydisks.py
new file mode 100644
index 0000000..c3b7716
--- /dev/null
+++ b/mydisks.py
@@ -0,0 +1,75 @@
+#!/usr/bin/env python
+
+# RaidGuessFS, a FUSE pseudo-filesystem to guess RAID parameters of a damaged device
+# Copyright (C) 2015 Ludovic Pouzenc <ludovic@pouzenc.fr>
+#
+# 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 <http://www.gnu.org/licenses/>
+
+import os, sys, logging
+
+class MyDisks():
+ """Auxiliary class, managing disk layer"""
+
+ def __init__(self, *args, **kwargs):
+ self.disks = []
+ self.disk_paths = []
+ self.disk_count = 0
+ self.disks_size = [0]
+ self.max_disks = 16
+
+ def get_disk_count(self):
+ return self.disk_count
+
+ def set_disks_path(self,disk_path_list):
+ """Set the list of real filepath to disks or images and (re)open them"""
+ self.disk_paths = disk_path_list
+
+ def set_disk_count(self,new_disk_count):
+ """Update the number of attached disks on the fly"""
+ self.disk_count = min(new_disk_count,self.max_disks)
+ logging.info("MyDisks.set_disk_count(%d) : setting disk_count to %d" % (new_disk_count, self.disk_count))
+
+ def open_disks(self):
+ """(re)open all disks"""
+ logging.debug("Enter open_disks()")
+ for fd in self.disks:
+ try:
+ fd.close()
+ except:
+ pass
+ self.disks = [ None for d in range(self.disk_count) ]
+ self.disks_size = [ 0 for d in range(self.disk_count) ]
+
+ for d in range(self.disk_count):
+ path = self.disk_paths[d]
+ logging.debug("Try to open disk #%2d"%d)
+ try:
+ self.disks[d] = open(path, "r")
+ self.disks_size[d] = os.lstat(path).st_size
+ logging.debug("Opened disk #%2d"%d)
+ except IOError as e:
+ logging.error("Can't open disk #%2d ('%s') : %s"%(d, path, e.strerror))
+ self.disks_size[d] = 0
+ except:
+ logging.error("Can't open disk #%2d ('%s') : unhandled exception"%(d, path))
+ self.disks_size[d] = 0
+ logging.debug("Exit. open_disks()")
+
+ def read(self,disk_no,offset,size):
+ self.disks[disk_no].seek(offset)
+ return self.disks[disk_no].read(size)
+
+
diff --git a/myraid.py b/myraid.py
new file mode 100644
index 0000000..60e8b28
--- /dev/null
+++ b/myraid.py
@@ -0,0 +1,154 @@
+#!/usr/bin/env python
+
+# RaidGuessFS, a FUSE pseudo-filesystem to guess RAID parameters of a damaged device
+# Copyright (C) 2015 Ludovic Pouzenc <ludovic@pouzenc.fr>
+#
+# 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 <http://www.gnu.org/licenses/>
+
+import logging, numpy
+
+class MyRaid():
+ """Auxiliary class, managing RAID layer"""
+
+ def __init__(self, *args, **kwargs):
+ self.raid_start = 0
+ self.raid_end = 0
+ self.raid_size = 0
+ self.raid_sector_size = 512
+ self.raid_chunk_size = 65536
+ self.raid_disk_order = range(15)
+ self.raid_types = [ '0', '1', '5', '5+0' ]
+
+ def _update_raid_size(self):
+ if self.raid_end > self.raid_start:
+ self.raid_size = self.raid_end - self.raid_start
+ else:
+ self.raid_size = 0
+
+ def get_raid_start(self):
+ return self.raid_start
+
+ def get_raid_chunk_size(self):
+ return self.raid_chunk_size
+
+ def get_raid_disk_order(self):
+ return self.raid_disk_order
+
+ def get_raid_disk_order_str(self):
+ return ' '.join(map(str,self.raid_disk_order))
+
+ def set_raid_start(self, new_raid_start):
+ """Update the start offset of raid data on underlying disks"""
+ self.raid_start = new_raid_start
+ self._update_raid_size()
+
+ def set_raid_end(self, new_raid_end):
+ """Update the end offset of raid data on underlying disks"""
+ self.raid_end = new_raid_end
+ self._update_raid_size()
+
+ def set_raid_chunk_size(self, new_raid_chunk_size):
+ """Update the size of chucks of data (or slice size)"""
+ self.raid_chunk_size = new_raid_chunk_size
+
+ def set_raid_disk_order(self, new_raid_disk_order):
+ """Update the raid logical disk order"""
+ self.raid_disk_order = new_raid_disk_order
+
+
+ def read_data(self,raid_type,disks,offset,size):
+ """TODO"""
+ disk_count = len(self.raid_disk_order)
+
+ # This code is RAID 5 only
+
+ slice_no = offset / self.raid_chunk_size
+ slice_off = offset % self.raid_chunk_size
+ segment=slice_no/(disk_count-1)
+ par_disk=(disk_count-1) - (segment % disk_count) # TODO : equivalent a : segment-1 % disk_count ?
+ data_disk=( par_disk + 1 + (slice_no % (disk_count-1)) ) % disk_count
+ off_disk = self.raid_start + segment * self.raid_chunk_size + slice_off
+
+ size2 = min(size, (slice_no+1) * self.raid_chunk_size - offset)
+
+ logging.info("raid.read_data(%s): offset=%d,slice_no=%d,slice_off=%d,segment=%d,par_disk=%d,data_disk=%d,off_disk=%d,size2=%d,slice_off+size2=%d"
+ % (raid_type,offset,slice_no,slice_off,segment,par_disk,data_disk,off_disk,size2,slice_off+size2) )
+
+ data_fd = disks[self.raid_disk_order[data_disk]]
+ data_fd.seek(off_disk)
+ data = data_fd.read(size2)
+
+ # This kills performance but don't make short reads before EOF
+ #if size2 < size:
+ # data += self.read_data(self,raid_type,disks,offset+size2,size-size2)
+
+ return data
+
+ def xor_blocks(self,fd_list, offset, size):
+ """TODO"""
+ logging.info("Enter xor_blocks(fd_list,%d,%d)"%(offset, size))
+
+ assert(size % 8 == 0), "Size must be multiple of 8"
+ dt = numpy.dtype('<Q8')
+
+ fd_list[0].seek(offset)
+ str_b1=fd_list[0].read(size)
+ numpy_b1 = numpy.fromstring(str_b1, dtype=dt)
+ all_zero = (numpy.count_nonzero(numpy_b1) == 0 )
+ any_zero = all_zero
+
+ for fd in fd_list[1:]:
+ fd.seek(offset)
+ str_b2=fd.read(size)
+ numpy_b2 = numpy.fromstring(str_b2, dtype=dt)
+ b2_zero = (numpy.count_nonzero(numpy_b2) == 0 )
+ if all_zero == True:
+ all_zero = b2_zero
+ if any_zero == False:
+ any_zero = b2_zero
+
+ numpy.bitwise_xor(numpy_b1,numpy_b2,numpy_b1)
+
+ if all_zero == True:
+ result = 'z'
+ elif numpy.count_nonzero(numpy_b1) == 0:
+ if any_zero:
+ result = 'g'
+ else:
+ result = 'G'
+ else:
+ result = 'b'
+
+ logging.info("Exit. xor_blocks(fd_list,%d,%d)"%(offset, size))
+ return result
+
+ def check_data(self,raid_type,disks,offset,size):
+ """TODO"""
+ logging.warn("Enter check_data(%s,disks,%d,%d)"%(raid_type,offset,size))
+ #import binascii
+ #logging.warn(binascii.hexlify(numpy_b1))
+
+ #result = ''.join([ self.xor_blocks(disks, (offset+i)*self.raid_sector_size, self.raid_sector_size) for i in range(size)])
+ result = ''.join([ '0x%011x %c\n'%( (offset/16+i)*self.raid_sector_size, self.xor_blocks(disks, (offset/16+i)*self.raid_sector_size, self.raid_sector_size)) for i in range(size/16) ])
+ # TODO donner des offests RAID et pas disques
+
+ logging.warn("Exit. check_data(%s,disks,%d,%d)"%(raid_type,offset,size))
+
+ return result
+
+
+
+
diff --git a/mystat.py b/mystat.py
new file mode 100644
index 0000000..719c7e3
--- /dev/null
+++ b/mystat.py
@@ -0,0 +1,57 @@
+#!/usr/bin/env python
+
+# RaidGuessFS, a FUSE pseudo-filesystem to guess RAID parameters of a damaged device
+# Copyright (C) 2015 Ludovic Pouzenc <ludovic@pouzenc.fr>
+#
+# 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 <http://www.gnu.org/licenses/>
+
+import stat, os, time, copy, fuse
+
+class MyStat(fuse.Stat):
+ """Handy class to produce fake file or dir attributes"""
+
+ def __init__(self):
+ self.st_mode = stat.S_IFDIR | 0555
+ self.st_ino = 0
+ self.st_dev = 0
+ self.st_nlink = 2
+ self.st_uid = os.getuid()
+ self.st_gid = os.getgid()
+ self.st_size = 0
+ self.st_atime = time.time()
+ self.st_mtime = self.st_atime
+ self.st_ctime = self.st_atime
+
+ def __str__(self):
+ return str(self.__dict__)
+
+ def make_fake_dir(self):
+ return copy.copy(self)
+
+ def make_fake_file(self,size,mode=0444):
+ st = copy.copy(self)
+ st.st_size = size
+ st.st_mode = stat.S_IFREG | mode
+ st.st_nlink = 1
+ return st
+
+#def main():
+# st = MyStat()
+# print st.make_fake_dir()
+# print st.make_fake_file(12)
+#
+#if __name__ == '__main__':
+# main()
diff --git a/raidguessfs.py b/raidguessfs.py
new file mode 100755
index 0000000..ffb0912
--- /dev/null
+++ b/raidguessfs.py
@@ -0,0 +1,351 @@
+#!/usr/bin/env python
+
+# RaidGuessFS, a FUSE pseudo-filesystem to guess RAID parameters of a damaged device
+# Copyright (C) 2015 Ludovic Pouzenc <ludovic@pouzenc.fr>
+#
+# Inspired by various python-fuse examples :
+# hello.py
+# Copyright (C) 2006 Andrew Straw <strawman@astraw.com>
+#
+# nullfs.py
+# Copyright (C) 2001 Jeff Epler <jepler@unpythonic.dhs.org>
+# Copyright (C) 2006 Csaba Henk <csaba.henk@creo.hu>
+#
+# templatefs.py
+# Copyright (c) 2009 Matt Giuca
+#
+# Since they use LGPL and BSD licence, I choose GPLv3 (seems okay)
+# 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 <http://www.gnu.org/licenses/>
+
+# Standard modules
+import sys, os, errno, logging, re, fuse
+# Custom modules
+import mystat, mydisks, mybinview, myraid
+
+if not hasattr(fuse, '__version__'):
+ raise RuntimeError, \
+ "your fuse-py doesn't know of fuse.__version__, probably it's too old."
+
+
+class RaidGuessFS(fuse.Fuse):
+ """Main class, implementing the actual pseudo-filesystem"""
+
+ def __init__(self, *args, **kwargs):
+ logging.info("Initializing filesystem")
+ logging.debug("Enter RaidGuessFS.__init__()")
+ super(RaidGuessFS,self).__init__(*args, **kwargs)
+
+ # Early initialisations (notably the static directory tree part)
+ self.d = mydisks.MyDisks()
+ self.raid = myraid.MyRaid()
+ self.bmp = mybinview.MyBinView()
+ self.st = mystat.MyStat()
+
+ self.bmp.refresh_bmp()
+
+ self.dattr = { '/': self.st.make_fake_dir() }
+ self.fattr = { '/fsinit_has_failed': self.st.make_fake_file(0) }
+
+ self.settings = [
+ 'disk_count',
+ 'raid_start', 'raid_chunk_size', 'raid_disk_order',
+ 'bmp_height', 'bmp_width', 'bmp_start_offset'
+ ]
+
+ self.settings_getters = [
+ self.d.get_disk_count,
+ self.raid.get_raid_start, self.raid.get_raid_chunk_size, self.raid.get_raid_disk_order_str,
+ self.bmp.get_bmp_height, self.bmp.get_bmp_width, self.bmp.get_bmp_start_offset
+ ]
+
+ self.settings_updaters = [
+ self.update_disk_count,
+ self.update_raid_start, self.update_raid_chunk_size, self.update_raid_disk_order,
+ self.update_bmp_height, self.update_bmp_width, self.update_bmp_start_offset
+ ]
+
+ self.dentries = {
+ '/' : [ fuse.Direntry(name) for name in ['config','disk','raid','visual'] ],
+ '/config': [ fuse.Direntry(name) for name in self.settings ],
+ '/raid' : [ fuse.Direntry(name) for name in self.raid.raid_types ],
+ '/disk' : [ ], # Filled in _refresh_disk_dentries()
+ '/visual': [ ], # Filled in _refresh_disk_dentries()
+ }
+
+ for raid_type in self.raid.raid_types:
+ self.dentries.update( {
+ # TODO : all type of raid don't need the same pseudo files
+ '/raid/%s'%raid_type: [ fuse.Direntry(name) for name in ['result','data_xor','parity'] ],
+ }
+ )
+ logging.debug("Exit. RaidGuessFS.__init__()")
+
+ def _refresh_disk_dentries(self):
+ """Internal function to update directory entries about all disks"""
+ logging.debug("Enter _refresh_disk_dentries()")
+ disk_dentries = []
+ visual_dentries = []
+ for d in range(self.d.disk_count):
+ st_img = self.st.make_fake_file(self.d.disks_size[d])
+ st_bmp = self.st.make_fake_file(self.bmp.bmp_size)
+ self.fattr.update( {
+ '/disk/disk%02d.img'%d: st_img,
+ '/visual/disk%02d.bmp'%d: st_bmp,
+ }
+ )
+ disk_dentries.append( fuse.Direntry('disk%02d.img'%d) )
+ visual_dentries.append( fuse.Direntry('disk%02d.bmp'%d) )
+
+ self.dentries.update( { '/disk': disk_dentries } )
+ self.dentries.update( { '/visual': visual_dentries } )
+ logging.debug("Exit. _refresh_disk_dentries()")
+
+ def _refresh_raid_fattr(self):
+ """Update the raid computed attributes after a config change"""
+ logging.debug("Enter _refresh_raid_fattr()")
+
+ for raid_type in self.raid.raid_types:
+ self.fattr['/raid/%s/data_xor'%raid_type].st_size = 0 # self.raid.raid_size
+ self.fattr['/raid/%s/parity'%raid_type].st_size = min(self.d.disks_size) / self.raid.raid_sector_size * 16
+ self.fattr['/raid/%s/result'%raid_type].st_size = self.raid.raid_size
+
+ logging.debug("Exit. _refresh_raid_fattr()")
+
+ def _split_path(self,path):
+ """Internal function to explode path for read() and write() calls"""
+ m = re.match('/([^/]+)/([^./0-9]*)([0-9+]*)(\.[^/]+)?(?:/([^/]+))?$', path)
+ if m:
+ return m.groups()
+ else:
+ return []
+
+ def update_disk_count(self,arg):
+ i = int(arg)
+ assert (i > 0), "Negative value make no sense here"
+ self.d.set_disk_count(i)
+ self.d.open_disks()
+ self._refresh_disk_dentries()
+ self.raid.set_raid_end(min(self.d.disks_size)-1)
+ self.update_raid_disk_order(range(i))
+ self._refresh_raid_fattr()
+
+ def update_bmp_start_offset(self, arg):
+ i = int(arg)
+ assert (i >= 0), "Negative value make no sense here"
+ self.bmp.set_bmp_start_offset(i)
+ self.bmp.refresh_bmp()
+ self._refresh_disk_dentries()
+
+ def update_bmp_width(self, arg):
+ i = int(arg)
+ assert (i > 0), "Non-positive value make no sense here"
+ self.bmp.set_bmp_width(i)
+ self.bmp.refresh_bmp()
+ self._refresh_disk_dentries()
+
+ def update_bmp_height(self, arg):
+ i = int(arg)
+ assert (i > 0), "Non-positive value make no sense here"
+ self.bmp.set_bmp_height(i)
+ self.bmp.refresh_bmp()
+ self._refresh_disk_dentries()
+
+ def update_raid_start(self, arg):
+ i = int(arg)
+ assert (i >= 0), "Negative value make no sense here"
+ self.raid.set_raid_start(i)
+ self._refresh_raid_fattr()
+
+ def update_raid_chunk_size(self, arg):
+ i = int(arg)
+ assert (i > 0), "Non-positive value make no sense here"
+ self.raid.set_raid_chunk_size(i)
+
+ def update_raid_disk_order(self, arg):
+ logging.debug("Enter update_raid_disk_order(%s)"%arg)
+ if type(arg) is str:
+ l = arg.split()
+ else:
+ l = arg
+ # TODO : sanity checks (every disk number below disk count, len(list) below disk count, no double...)
+ logging.debug("==> %s (%d/%d)"%(l,len(l),self.d.disk_count))
+ self.raid.set_raid_disk_order(l)
+ logging.debug("Exit. update_raid_disk_order(%s)"%arg)
+
+
+########################################################
+# Actual File System operations implementation follows #
+########################################################
+
+
+ def fsinit(self):
+ """Make some run-time initalisations after argument parsing"""
+ logging.info("Mounting filesystem...")
+ # WARNING : this method is called by FUSE in a context that don't show fatal exceptions,
+ # even with -d[ebug] flag set, so log all exceptions stupidly
+ try:
+ self.dattr = {
+ path: self.st.make_fake_dir() for path in self.dentries.keys()
+ }
+ self.fattr = {
+ '/config/%s'%s: self.st.make_fake_file(64,0666) for s in self.settings
+ }
+ for raid_type in self.raid.raid_types:
+ self.fattr.update( {
+ '/raid/%s/data_xor'%raid_type: self.st.make_fake_file(0),
+ '/raid/%s/parity'%raid_type: self.st.make_fake_file(0),
+ '/raid/%s/result'%raid_type: self.st.make_fake_file(0),
+ })
+
+ self.d.set_disks_path([getattr(self.parser.values,'disk%02d'%d) for d in range(self.d.max_disks)])
+ self.update_disk_count(len(self.d.disk_paths))
+
+ self._refresh_disk_dentries()
+ self._refresh_raid_fattr()
+
+ logging.info("Mounted.")
+ except Exception as e:
+ logging.error(e)
+
+ def getattr(self, path):
+ logging.info("getattr: %s" % path)
+ res = self.dattr.get(path) or self.fattr.get(path) or -errno.ENOENT
+ logging.debug("==> " + str(res))
+ return res
+
+ def fgetattr(self, path, fh=None):
+ #logging.debug("fgetattr: %s (fh %s)" % (path, fh))
+ return self.getattr(path)
+
+ def readdir(self, path, offset, dh=None):
+ logging.info("readdir: %s (offset %s, dh %s)" % (path, offset, dh))
+ return self.dentries[path]
+
+ def truncate(self, path, size):
+ logging.info("truncate: %s (size %s)" % (path, size))
+ if path.startswith('/config/'):
+ return # Ignore truncates on pseudo config files
+ else:
+ return -errno.EOPNOTSUPP
+
+ def ftruncate(self, path, size, fh=None):
+ logging.info("ftruncate: %s (size %s, fh %s)" % (path, size, fh))
+ return self.truncate(path, size)
+
+ def read(self, path, size, offset, fh=None):
+ logging.info("read: %s (size %s, offset %s, fh %s)" % (path, size, offset, fh))
+
+ path_chuncks = self._split_path(path)
+ if path_chuncks:
+ try:
+ if path_chuncks[0] == 'config':
+ # TODO take care here
+ idx = self.settings.index(path_chuncks[1])
+ return str(self.settings_getters[idx]()) + "\n"
+
+ if path_chuncks[0] == 'disk':
+ if path_chuncks[1] == 'disk':
+ i = int(path_chuncks[2])
+ if 0 <= i <= self.d.disk_count:
+ if path_chuncks[3] == '.img':
+ return self.d.read(i,offset,size)
+ if path_chuncks[3] == '.bmp':
+ return self.bmp.read(self.d.disks[i],offset,size)
+
+ if path_chuncks[0] == 'visual':
+ if path_chuncks[1] == 'disk':
+ i = int(path_chuncks[2])
+ if 0 <= i <= self.d.disk_count:
+ if path_chuncks[3] == '.bmp':
+ return self.bmp.read(self.d.disks[i],offset,size)
+
+ if path_chuncks[0] == 'raid':
+ raid_type=path_chuncks[2]
+ if raid_type in self.raid.raid_types:
+ if path_chuncks[4] == 'result':
+ return self.raid.read_data(raid_type,self.d.disks,offset,size)
+ if path_chuncks[4] == 'parity':
+ return self.raid.check_data(raid_type,self.d.disks,offset,size)
+
+ except Exception as e:
+ logging.error(e)
+ return -errno.ENOENT
+
+ logging.error("Unimplemented read of '%s' (%s)"%(path, str(path_chuncks)))
+ return -errno.ENOENT
+
+ def write(self, path, buf, offset, fh=None):
+ logging.info("write: %s (offset %s, fh %s)" % (path, offset, fh))
+
+ path_chuncks = self._split_path(path)
+ if path_chuncks:
+ try:
+ if path_chuncks[0] == 'config':
+ # TODO take care here
+ idx = self.settings.index(path_chuncks[1])
+ try:
+ self.settings_updaters[idx](buf)
+ return len(buf)
+ except Exception as e:
+ logging.error(e)
+ return -errno.EIO
+
+ except Exception as e:
+ logging.error(e)
+ return -errno.ENOENT
+
+ logging.error("Unimplemented write of '%s' (%s)"%(path, str(path_chuncks)))
+ return -errno.ENOENT
+
+
+def main():
+ usage = fuse.Fuse.fusage + """
+
+RaidGuessFS is a pseudo-filesystem that allows to guess parameters and disk order of a damaged RAID devices.
+ Takes disk image files as arguments (defaults to ./diskNN.img).
+ Could take advantage of ddrecue logs file (metadata about unreadable sectors)
+"""
+ fuse.fuse_python_api = (0, 2)
+
+ LOG_FILENAME = "raidguessfs.log"
+ logging.basicConfig(filename=LOG_FILENAME,level=logging.WARN,)
+ #logging.basicConfig(filename=LOG_FILENAME,level=logging.INFO,)
+ #logging.basicConfig(filename=LOG_FILENAME,level=logging.DEBUG,)
+
+ server = RaidGuessFS(version="%prog " + fuse.__version__,usage=usage,dash_s_do='setsingle')
+ server.multithreaded = False
+
+ cwd = os.getcwd()
+ for num in range(server.d.max_disks):
+ server.parser.add_option(
+ mountopt="disk%02d"%num,
+ metavar="ABS_PATH",
+ default="%s/disk%02d.img"%(cwd,num),
+ help="Disk #%d image file path [default: ./disk%02d.img]"%(num,num)
+ )
+ server.parser.add_option(
+ mountopt="logf%02d"%num,
+ metavar="ABS_PATH",
+ default="%s/disk%02d.log"%(cwd,num),
+ help="Disk #%d ddrescue log file [default: ./disk%02d.log]"%(num,num)
+ )
+
+ server.parse(errex=1)
+ server.main()
+
+if __name__ == '__main__':
+ main()
+
diff --git a/tools/pattern.py b/tools/pattern.py
new file mode 100755
index 0000000..0367698
--- /dev/null
+++ b/tools/pattern.py
@@ -0,0 +1,12 @@
+#!/usr/bin/env python
+
+def main():
+ i=0
+ max=pow(10,15)
+ while True:
+ print "%015d"%i,
+ i=(i+1)%max
+
+
+if __name__ == '__main__':
+ main()