#!/usr/bin/env python # -*- coding: UTF-8 -*- # afiolzofs : Support to mount afio archives with lzop compression. # Copyright (c) 2010, Yoshiteru Ishimaru # All rights reserved. # # Redistribution and use in source and binary forms, with or without modification, # are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the Yoshiteru Ishimaru nor the names of its contributors # may be used to endorse or promote products derived from this software # without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, # THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; # OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, # WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF # ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # 0.0.3 (26 Nov 2010) # fix "cannot mount XXXXXX.afio.lzo" # 0.0.2 (26 Nov 2010) # BUG fix "REG FILE" # 0.0.1 (21 Nov 2010) # New release # This program is based on fusepy. And it is started by coping the 'fuse.py' and 'memory.py' # # Copyright of the fuse.py and memory.py # # Copyright (c) 2008 Giorgos Verigakis # # Permission to use, copy, modify, and distribute this software for any # purpose with or without fee is hereby granted, provided that the above # copyright notice and this permission notice appear in all copies. # # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. from collections import defaultdict from errno import ENOENT from stat import S_IFDIR, S_IFLNK, S_IFREG from stat import S_IFMT ,S_ISDIR, S_ISLNK, S_ISREG from sys import argv, exit from time import time,mktime ,strptime from fuse import FUSE, FuseOSError, Operations, LoggingMixIn import subprocess import pwd import grp import os #### Get information #### def _lzop(data,compress=False): if compress: args=['lzop','-c9'] else: args=['lzop','-dc'] proc = subprocess.Popen(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, close_fds=True, ) pid = os.fork() if pid==0: # child proc.stdout.close() proc.stdin.write(data) proc.stdin.flush() proc.stdin.close() os._exit(os.EX_OSERR) else: # parent proc.stdin.close() data=proc.stdout.read() proc.stdout.close() return data def _filetype(data): pipe=subprocess.Popen("file -" ,shell=True,stdin=subprocess.PIPE,stdout=subprocess.PIPE) pipe.stdin.write(data); pipe.stdin.close() s=pipe.stdout.read().split(); pipe.stdout.close() return " ".join(s[1:4]) #### FUSE Operation class #### class Afiolzofs(LoggingMixIn, Operations): # for debug #class afiolzofs(Operations): # for normal use """Example memory filesystem. Supports only one level of files.""" def __init__(self,mountpoint): self.mountpoint=mountpoint self.inode = defaultdict(dict) self.ino = {} self.fd = 0 now = time() inode = dict( st_mode=(S_IFDIR | 0755), st_ctime=now, st_mtime=now, st_atime=now, st_uid=os.getuid(), st_gid=os.getgid(), st_nlink=2, ) inode['st_ino']=id(inode) self.inode['/']['.']=inode #### local function #### def _adddir(self,path,inode): inode['st_ino']=id(inode) dentry=os.path.split(path) self.inode[dentry[0]][dentry[1]] = inode self.inode[dentry[0]]['.']['st_nlink'] += 1 self.inode[path] = {'.':inode} def _addfile(self,path,inode): inode['st_ino']=id(inode) dentry=os.path.split(path) self.inode[dentry[0]][dentry[1]] = inode def _getinode(self,path): if path=="/": dentry=["/","."] else: dentry=os.path.split(path) try: return self.inode[dentry[0]][dentry[1]] except: raise FuseOSError(ENOENT) def _getinodedir(self,path): if path=="/": dentry=["/","."] else: dentry=os.path.split(path) try: inode_dir=self.inode[dentry[0]] inode=inode_parent[dentry[1]] return inode,inode_dir,dentry[1] except: raise FuseOSError(ENOENT) def _setnlink(self,inode): ino=inode['i_ino'] inode2=self.ino.get(ino,None) if inode2==None: self.ino[ino]=inode return inode,True else: return inode2,False def read_afio_item(self,inode): f=open(inode['i_abspath'],"r") f.seek(inode['i_startaddr']) data=f.read(inode['i_readsize']) f.close() compfunc=inode['i_compfunc'] if compfunc != None:data=compfunc(data) return data def load_afio_file(self,f=None,seek=None,target="",abspath=""): f.seek(seek) hdr=f.read(6) if hdr=='070701': # cpio magic number ? not implement yet hdr2=f.read(110) # length: 110 inode={} pass elif hdr=='070707': # old ASCII magic number hdr2=f.read(70) # length: 70 inode=dict( i_dev= int(hdr2[0:6],8), i_ino= int(hdr2[6:12],8), st_mode= int(hdr2[12:18],8), st_uid= int(hdr2[18:24],8), st_gid= int(hdr2[24:30],8), st_nlink= int(hdr2[30:36],8), rdev= int(hdr2[36:42],8), st_mtime= int(hdr2[42:53],8), i_namelength= int(hdr2[53:59],8), st_size= int(hdr2[59:70],8), ) inode['st_ctime']=inode['st_mtime'] inode['st_atime']=inode['st_mtime'] inode['i_readsize']=inode['st_size'] inode['i_filepath']=f.read(inode['i_namelength'])[:-1] # next field is path name inode['i_startaddr']=seek+6+70+inode['i_namelength'] # calc data address #print id(inode) print "|23456|23456|23456|23456|23456|23456|23456|23456|23456789ab|23456|23456789ab|..." print "| hdr| dev| ino| mode| uid| gid|nlink| rdev| mtime|nmlen| size|pathname" print "%s%s%s" % (hdr,hdr2,inode['i_filepath']) elif hdr=='070717': # extended ASCII magic number not implement yet hdr2=f.read(75) # length 75 pass inode={} elif hdr=='070727': # large ASCII magic number not implement yet hdr2=f.read(110) # length 110 print "|23456|2345678|234567890123456m|23456|2345678|2345678|2345678|2345678|234567890123456n|234|234|234s|234567890123456:|..." print "| hdr| dev| inoM| mod| uid| gid| nlink| rdev| mtimeN|nml|flg|xszS| size:|pathname" print "%s%s" % (hdr,hdr2) inode={} else: return inode['i_readfunc']=self.read_afio_item inode['i_compfunc']=None inode['i_abspath']=abspath # abspath of the afio.lzo file mode=inode['st_mode'] if S_ISDIR(mode): # DIR if inode['st_nlink']>2:inode,new=self._setnlink(inode) path='%s/%s' % (target,inode['i_filepath']) self._adddir(path,inode) elif S_ISLNK(mode): # SLINK if inode['st_nlink']>1:inode,new=self._setnlink(inode) else: new=True if new:inode['i_data'] = f.read(inode['i_readsize']) path='%s/%s' % (target,inode['i_filepath']) self._addfile(path,inode) elif S_ISREG(mode): # REG FILE # '.z' and 'rdev & 1==0' mean compressed file in afio header if ((inode['rdev'] & 1)== 0) and (inode['i_filepath'][-2:] =='.z'): # Chake Compressed or Not. inode['i_filepath']=inode['i_filepath'][:-2] # remove ".z" size=inode['i_readsize'] if size>50:size=50 # read first 50 bytes top50=f.read(size) filetype=_filetype(top50) if filetype=='lzop compressed data': inode['i_compfunc']=_lzop # read size from lzop header inode['st_size']=int("%02x%02x%02x%02x"%(ord(top50[38]),ord(top50[39]),ord(top50[40]),ord(top50[41])),16) self._addfile(path,inode) path='%s/%s' % (target,inode['i_filepath']) if inode['st_nlink']>1:inode,new=self._setnlink(inode) self._addfile(path,inode) print "============================== normal file ",path else: path='%s/%s' % (target,inode['i_filepath']) print "============ not impliment ===================",path def load_afio_files(self,targetdir,sourceabspath): pipe=subprocess.Popen("afio -tvBZ \"%s\"" % sourceabspath, shell=True,stdout=subprocess.PIPE).stdout self.ino = {} # clear ino dict f=open(sourceabspath,"r") for line in pipe: i=int(line.split(None,2)[0]) self.load_afio_file(f,i,targetdir,sourceabspath) f.close() def _check_symlink(self,target,source): targetpath=os.path.split(target) if targetpath[0]=="/": # if target is root of the mountpoint targetname=".%s" % targetpath[1] targetdir=os.path.join(targetpath[0],targetname) sourceabspath=os.path.normpath(os.path.join(self.mountpoint,source)) f=open(sourceabspath) top50=f.read(50);f.close() filetype=_filetype(top50) if "ASCII cpio archive": st=os.stat(sourceabspath) inode=dict( st_mode=(S_IFDIR | 0755), st_ctime=st.st_ctime, st_mtime=st.st_mtime, st_atime=st.st_atime, st_uid=st.st_uid, st_gid=st.st_gid, st_nlink=2, st_blksize=st.st_blksize, st_blocks=st.st_blocks, st_dev=st.st_dev, st_ino=st.st_ino, st_rdev=st.st_rdev, st_size=st.st_size, ) self._adddir(targetdir,inode) self.load_afio_files(targetdir,sourceabspath) return targetname else: return source return source ######## FUSE Operations ######## #### Start File System #### def statfs(self, path): return dict(f_bsize=512, f_blocks=4096, f_bavail=2048) #### Attr #### def getattr(self, path, fh=None): #if path not in self.files: # raise FuseOSError(ENOENT) inode=self._getinode(path) return inode def chmod(self, path, mode): inode=self._getinode(path) inode['st_mode'] &= 0770000 inode['st_mode'] |= mode return 0 def chown(self, path, uid, gid): inode=self._getinode(path) inode['st_uid'] = uid inode['st_gid'] = gid def utimens(self, path, times=None): now = time() atime, mtime = times if times else (now, now) inode=self._getinode(path) inode['st_atime'] = atime inode['st_mtime'] = mtime #### XAttr #### def listxattr(self, path): inode=self._getinode(path) attrs = inode.get('attrs', {}) return attrs.keys() def getxattr(self, path, name, position=0): inode=self._getinode(path) attrs = inode.get('attrs', {}) try: return attrs[name] except KeyError: return '' # Should return ENOATTR ? def setxattr(self, path, name, value, options, position=0): # Ignore options inode=self._getinode(path) attrs = inode.setdefault('attrs', {}) attrs[name] = value def removexattr(self, path, name): inode=self._getinode(path) attrs = inode.get('attrs', {}) try: del attrs[name] except KeyError: pass # Should return ENOATTR ? #### Create or Remove Dir #### def readdir(self, path, fh): dentries=self.inode[path] dirnames=[name for name in dentries] dirnames.append('..') return dirnames def mkdir(self, path, mode): inode = dict( st_mode=(S_IFDIR | mode), st_nlink=2, st_size=0, st_ctime=time(), st_mtime=time(), st_atime=time(), st_uid=os.getuid(), st_gid=os.getgid(), ) self._adddir(path,inode) def rmdir(self, path): dentry=os.path.split(path) inode,inode_dir,filename=self._getinodedir(path) inode_dir.pop(filename) inode_dir['st_nlink'] -= 1 #### Create or Remove File #### def create(self, path, mode): inode=dict( st_mode=(S_IFREG | mode), st_nlink=1, st_size=0, st_ctime=time(), st_mtime=time(), st_atime=time(), st_uid=os.getuid(), st_gid=os.getgid(), i_data='' ) self._addfile(path,inode) self.fd += 1 return self.fd def unlink(self, path): inode,inode_dir,filename=self._getinodedir(path) inode['st_nlink'] -= 1 inode_dir.pop(filename) def rename(self, old, new): dentry_new=os.path.split(new) dentry_old=os.path.split(old) self.inode[dentry_new[0]][dentry_new[1]] = self.inode[dentry_old[0]].pop(dentry_old[1]) def link(self, target, source): inode_terget=self._getinode(path) inode_source=self._getinode(source) raise FuseOSError(EROFS) #### Sym Link #### def readlink(self, path): inode=self._getinode(path) return inode['i_data'] def symlink(self, target, source): # print target, source source=self._check_symlink(target,source) # make normal symlink inode = dict( st_mode=(S_IFLNK | 0777), st_nlink=1, st_size=len(source), st_uid=os.getuid(), st_gid=os.getgid(), i_data= source, ) self._addfile(target,inode) #### Read or Write File #### def open(self, path, flags): self.fd += 1 return self.fd def read(self, path, size, offset, fh): print fh inode=self._getinode(path) data=inode.get('i_data',None) if data==None: data=inode['i_readfunc'](inode) inode['i_data']=data return data[offset:(offset + size)] def write(self, path, data, offset, fh): inode=self._getinode(path) data_old=inode.get('i_data',None) if data_old==None: data_old=inode['i_readfunc'](inode) inode['i_data']=data_old data_new=data_old[:offset] + data ##### fix me ! inode['i_data'] = data_new inode['st_size'] = len(data_new) return len(data) def truncate(self, path, length, fh=None): inode=self._getinode(path) inode['i_data'] = inode['i_data'][:length] inode['st_size'] = length #### Release Flush Fsync #### def release(self, path, fh): print fh return 0 def flush(self, path, fh): print fh return 0 def fsync(self, path, datasync, fh): print fh return 0 def fsyncdir(self, path, datasync, fh): print fh return 0 if __name__ == "__main__": if len(argv) != 2: print 'usage: %s ' % argv[0] print ' make symlink "old ASCII cpio" or "afio" file in the mountpoint root to mount the archive' exit(1) mountpoint=os.path.abspath(argv[1]) afiolzofs= Afiolzofs(mountpoint) fuse = FUSE(afiolzofs, mountpoint,foreground=True)