#!/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.5 (29 Nov 2010) # list many item shuld be fix. # fix some of them. # 0.0.4 (27 Nov 2010) # support extended ASCII format # fix "unlink error" # fix "other magic number" # not to use defaultdict # fix "rename error" # change inode structure # 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 = {} # for read afio file 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, i_data={}, ) inode['st_ino']=id(inode) inode['i_data']['.']=inode self.inode = inode #### local function #### # 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_dir[dentry[1]] # return inode,inode_dir,dentry[1] # except: # raise FuseOSError(ENOENT) def _getinode(self,path): if path=="/": dentry=["/","."] return self.inode else: dentry=os.path.split(path) try: inode_dir=self._getinode(dentry[0]) inode=inode_dir['i_data'][dentry[1]] return inode except: raise FuseOSError(ENOENT) def _getinodedir(self,path): if path=="/": dentry=["/","."] # what should be return here? else: dentry=os.path.split(path) try: inode_dir=self._getinode(dentry[0]) inode=inode_dir['i_data'][dentry[1]] return inode,inode_dir,dentry[1] except: raise FuseOSError(ENOENT) def _addfile(self,path,inode): dentry=os.path.split(path) inode_dir=self._getinode(dentry[0]) inode_dir['i_data'][dentry[1]] = inode now = time() inode_dir['st_ctime']=now # Update parent dir. ctime,mtime OK? inode_dir['st_mtime']=now def _adddir(self,path,inode): dentry=os.path.split(path) inode_dir=self._getinode(dentry[0]) inode_dir['i_data'][dentry[1]] = inode inode_dir['st_nlink'] += 1 now = time() inode_dir['st_ctime']=now # Update parent dir. ctime,mtime OK? inode_dir['st_mtime']=now 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=='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 hdr2=f.read(75) # length 75 inode=dict( i_dev= int(hdr2[0:6],8), i_ino= int(hdr2[6:17],8), st_mode= int(hdr2[17:23],8), st_uid= int(hdr2[23:29],8), st_gid= int(hdr2[29:35],8), st_nlink= int(hdr2[35:41],8), rdev= int(hdr2[41:47],8), st_mtime= int(hdr2[47:58],8), i_namelength= int(hdr2[58:64],8), st_size= int(hdr2[64:75],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+75+inode['i_namelength'] # calc data address print "|23456|23456|23456789ab|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=='070727': # large ASCII magic number not implemented 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={} return elif hdr=='070701': # cpio new ASCII magic number not implemented yet hdr2=f.read(110) # length: 110 inode={} return elif hdr=='070702': # cpio new ASCII magic number with CRC not implemented yet #hdr2=f.read(110) # length: ?? inode={} return elif hdr=='070703': # Tcpio magic number of TI/E not implemented yet #hdr2=f.read(110) # length: ?? return else: return inode['st_ino']=id(inode) 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']) inode['i_data']={} # for DIR ({}: real empty dir entry) 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']) # for Symlink ("xxx": linked source name) 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) inode['i_data']=None # for REG FILE (None: not read data yet) 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 implimented ===================",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, i_data={} ) inode['st_ino']=id(inode) inode['i_data']['.']=inode 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 #### # access # destroy # at unmount the file system # fgetattr # init # mknod def getattr(self, path, fh=None): inode=self._getinode(path) return inode def chmod(self, path, mode): inode=self._getinode(path) inode['st_mode'] &= 0770000 inode['st_mode'] |= mode # mask = S_IRWXU | S_IRWXG | S_IRWXO | S_ISUID | S_ISGID | S_ISVTX; # Shuld I check these mask ? # inode->i_mode = (inode->i_mode & ~mask) | (mode & mask); # inode->i_ctime = tm; # Shuld I update ctime ? return 0 def chown(self, path, uid, gid): inode=self._getinode(path) if uid != -1:inode['st_uid'] = uid # Check -1 if gid != -1: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): inode_dir=self._getinode(path) dirnames=[name for name in inode_dir['i_data']] dirnames.append('..') return dirnames def mkdir(self, path, mode): now=time() inode = dict( st_mode=(S_IFDIR | mode), st_nlink=2, st_size=0, st_ctime=now, st_mtime=now, st_atime=now, st_uid=os.getuid(), st_gid=os.getgid(), i_data={} # {}: real empty dir entries ) inode['st_ino']=id(inode) inode['i_data']['.']=inode self._adddir(path,inode) # parent_inode.i_ctime = inode.i_mtime = tm; # shuld I update parent dir? def rmdir(self, path): dentry=os.path.split(path) inode_dir=self._getinode(dentry[0]) inode_dir['i_data'].pop(dentry[1]) inode_dir['st_nlink'] -= 1 # # shuld i check "empty" "dir" "root_dir" ? # parent_inode.i_ctime = inode.i_mtime = tm; # shuld I update parent dir? #### Create or Remove File #### def create(self, path, mode): now=time() inode=dict( st_mode=(S_IFREG | mode), st_nlink=1, st_size=0, st_ctime=now, st_mtime=now, st_atime=now, st_uid=os.getuid(), st_gid=os.getgid(), i_data='' ) inode['st_ino']=id(inode) self._addfile(path,inode) self.fd += 1 # parent_inode.i_ctime = inode.i_mtime = tm; return self.fd def unlink(self, path): inode,inode_dir,filename=self._getinodedir(path) inode['st_nlink'] -= 1 inode_dir['i_data'].pop(filename) # Shuld I check REG file(not DIR) # parent_inode.i_ctime = inode.i_mtime = tm; def rename(self, old, new): inode,old_inode_dir,old_filename=self._getinodedir(old) dentry_new=os.path.split(new) new_inode_dir=self._getinode(dentry_new[0]) new_filename=dentry_new[1] mode=inode['st_mode'] # /* If oldpath and newpath are existing hard links referring to the same # file, then rename() does nothing, and returns a success status. */ # /* EINVAL The new pathname contained a path prefix of the old: # this should be checked by fuse */ # /* EISDIR newpath is an existing directory, but oldpath is not a direcā€ # tory. */ # /* ENOTEMPTY newpath is a non-empty directory */ # rt = do_check_empty_dir(e2fs, dest_ino); # /* ENOTDIR: oldpath is a directory, and newpath exists but is not a # directory */ # Shuld I remove existing "new" ? # d_dest_inode.i_mtime = d_dest_inode.i_ctime = src_inode->i_ctime if S_ISDIR(mode): new_inode_dir['i_data'][new_filename]=inode new_inode_dir['st_nlink'] += 1 old_inode_dir['i_data'].pop(old_filename) # fix me !! if new==old , new is include old path old_inode_dir['st_nlink'] -= 1 else: new_inode_dir['i_data'][new_filename]=inode old_inode_dir['i_data'].pop(old_filename) # fix me !! if new==old def link(self, target, source): inode,old_inode_dir,old_filename=self._getinodedir(source) dentry_new=os.path.split(target) new_inode_dir=self._getinode(dentry_new[0]) new_filename=dentry_new[1] if S_ISDIR(mode): # fix me !!! no link if DIR new_inode_dir['i_data'][new_filename]=inode new_inode_dir['st_nlink'] += 1 inode['st_nlink'] += 1 else: new_inode_dir['i_data'][new_filename]=inode inode['st_nlink'] += 1 # d_inode.i_mtime = d_inode.i_ctime = inode->i_ctime = e2fs->now ? e2fs->now : time(NULL); #### Sym Link #### def readlink(self, path): # if (!LINUX_S_ISLNK(inode.i_mode)) { # debugf("%s is not a link", path); shuld I check? inode=self._getinode(path) return inode['i_data'] def symlink(self, target, source): source=self._check_symlink(target,source) # if mount root then mount afio.lzo file # 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, ) inode['st_ino']=id(inode) self._addfile(target,inode) #### Read or Write File #### def open(self, path, flags): self.fd += 1 return self.fd # shuld return file handle pointer. Can I use inode instead? Then Where shuld i save flags ? 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)