#!/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.15 (08 Dec 2010)
# Ramfs Class and Afiolzofs Class
# 0.0.14 (08 Dec 2010)
# start to separate Temp Ramfs Block and AFIO access Block
# 0.0.13 (07 Dec 2010)
# _get_inode
# read_afio_symlink		no read symlinkpath at first
# read_afio_item_offsetsize	no read all and store for non compressed data at only read.
# 0.0.12 (06 Dec 2010)
#  fix some error
# 0.0.11 (05 Dec 2010)
#  x permission
# 0.0.10 (04 Dec 2010)
#  r,w permission
# 0.0.9 (03 Dec 2010)
#  test implementation for permission to dirread
# 0.0.8 (02 Dec 2010)
#  fix some error
# 0.0.7 (01 Dec 2010)
#  fix some of the atime,mtime,ctime
#  fix some error code
# 0.0.6 (30 Nov 2010)
#  fix some of the atime,mtime,ctime
#  use get_fuse_context
# 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 XXX<space>XXX.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 <verigak@gmail.com> 
#  
# 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 
"""
S_IFMT 		0170000 	bitmask for the file type bitfields
S_IFSOCK 	0140000 	socket
S_IFLNK 	0120000 	symbolic link
S_IFREG 	0100000 	regular file
S_IFBLK 	0060000 	block device
S_IFDIR 	0040000 	directory
S_IFCHR 	0020000 	character device
S_IFIFO 	0010000 	FIFO
S_ISUID 	0004000 	set UID bit
S_ISGID 	0002000 	set-group-ID bit (see below)
S_ISVTX 	0001000 	sticky bit (see below)
S_IRWXU 	00700 	mask for file owner permissions
S_IRUSR 	00400 	owner has read permission
S_IWUSR 	00200 	owner has write permission
S_IXUSR 	00100 	owner has execute permission
S_IRWXG 	00070 	mask for group permissions
S_IRGRP 	00040 	group has read permission
S_IWGRP 	00020 	group has write permission
S_IXGRP 	00010 	group has execute permission
S_IRWXO 	00007 	mask for permissions for others (not in group)
S_IROTH 	00004 	others have read permission
S_IWOTH 	00002 	others have write permission
S_IXOTH 	00001 	others have execute permission
"""


from stat import S_ISDIR, S_ISLNK, S_ISREG, S_ISCHR, S_ISBLK # function
from stat import S_IMODE	# or 0007777	os.chmod() で設定することのできる一部のファイルモード
from stat import S_IFMT		# or 0170000	ファイル種別を示すビット領域を表すビットマスク
#
from stat import S_IFSOCK	# 0140000	ソケットファイル
from stat import S_IFLNK	# 0120000	シンボリックリンク
from stat import S_IFREG	# 0100000	通常ファイル
from stat import S_IFBLK	# 0060000	ブロック型デバイスファイル
from stat import S_IFDIR	# 0040000	ディレクトリ
from stat import S_IFCHR	# 0020000	キャラクタ型デバイスファイル
from stat import S_IFIFO	# 0010000	パイプ
from stat import S_ISUID	# 0004000	セットUID
from stat import S_ISGID	# 0002000	セットGID
from stat import S_ISVTX	# 0001000	ステッキービット。ファイルの名前を変更したり削除したりできるのはそのファイルの所有者、ディレクトリの所有者、特権プロセスだけとなる
from stat import S_IRWXU	#   00700	ファイル所有者のアクセス許可用のビットマスク
from stat import S_IRUSR	#   00400	所有者の読み込み許可
from stat import S_IWUSR	#   00200	所有者の書き込み許可
from stat import S_IXUSR	#   00100	所有者の実行許可
from stat import S_IRWXG	#   00070	グループのアクセス許可用のビットマスク
from stat import S_IRGRP	#   00040	グループの読み込み許可
from stat import S_IWGRP	#   00020	グループの書き込み許可
from stat import S_IXGRP	#   00010	グループの実行許可
from stat import S_IRWXO	#   00007	他人 (others) のアクセス許可用のビットマスク
from stat import S_IROTH	#   00004	他人の読み込み許可
from stat import S_IWOTH	#   00002	他人の書き込み許可
from stat import S_IXOTH	#   00001	他人の実行許可

from sys import argv, exit 
from time import time,mktime ,strptime
 
from fuse import FUSE, FuseOSError, Operations, LoggingMixIn ,fuse_get_context
import subprocess
import pwd
import grp
import os

from errno import * 
"""
ENOSYS		(Function not implemented) 
EPERM		(Operation not permitted) 
ENOENT		(No such file or directory) 
EIO		(I/O error) 
ENXIO		(No such device or address) 
EBADF		(Bad file number) 
ENOMEM		(Out of memory) 
EACCES		(Permission denied) 
EFAULT		(Bad address) 
ENOTBLK		(Block device required) 
EBUSY		(Device or resource busy) 
EEXIST		(File exists) 
EXDEV		(Cross-device link) 
ENODEV		(No such device) 
ENOTDIR		(Not a directory) 
EISDIR		(Is a directory) 
ENOSPC		(No space left on device) 
ESPIPE		(Illegal seek) 
EROFS		(Read-only file system) 
EPIPE		(Broken pipe) 
ENAMETOOLONG	(File name too long) 
ENOTEMPTY	(Directory not empty) 
"""

###### RamDisk Function ######
###### Inode Class ###### 
class Inode():
	def __getitem__(self,name):
	  x=getattr(self,name,None)
	  if x != None: return x
	def __setitem__(self,name,value):
	  if name in self.keys():setattr(self,name,value)
	def keys(self):
	  return ['st_dev','__pad1','__st_ino','st_mode','st_nlink',
		'st_uid','st_gid','st_rdev','__pad2','st_size','st_blksize',
		'st_blocks','st_atime','st_mtime','st_ctime','st_ino',
		'i_data','xattr']
	def get(self,name,default=None):
	  return getattr(self,name,default)
	def get_inode(self):						# return default inode		
	  return dict(st_uid=0)
	def items(self):
	  x = [
		('st_ino',id(self)),					# fix me ! ls- li return st_ino, but ls -i cannot return it.
		]
	  inode=None
	  for s in ['st_uid','st_gid','st_rdev','st_atime','st_ctime','st_mtime','st_size','st_nlink','st_mode']:
	    if hasattr(self,s):
	      x.append((s,getattr(self,s)))
	    else:
	      if inode == None:inode=self.get_inode()
	      x.append((s,inode[s]))
	  return x
	def read_data(self):return ''						# call from read symlink,write,truncate
	def read_data_offsetsize(self,offset,size):return ''			# call from read
#### FUSE Operation class ####
class Ramfs(LoggingMixIn, Operations):		 # for debug
#class Ramfs(Operations):			# for normal use
    """Memory filesystem. Supports temporaly read write, no save files. """ 
    def __init__(self,mountpoint): 
        self.mountpoint=mountpoint
        self.ino = {} # for read afio file
        self.fd = 0 
        now = time() 
        inode = dict(		
			st_mode=(S_IFDIR | 0755), 
			st_atime=now,	# access time shuld be changed with read (not be changed with write, open or read without permision)
			st_mtime=now, 	# modify time shuld be changed with write or truncate
			st_ctime=now,	# change time shuld be changed with write, rename, truncate, chmod, link or chown
			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
    ######## Temporaly Randisk function (Permission check) ########
    def _is_inode_R_OK(self,inode,context,uname):
	mode=inode['st_mode']
	if not (mode & S_IROTH) and not((mode & S_IRUSR) and (inode['st_uid'] == context[0])):
	  if (mode & S_IRGRP):
	    if not(uname in grp.getgrgid(inode['st_gid']).gr_mem):
	      if context[1] != inode['st_gid']:	raise FuseOSError(EACCES)	# (Permission denied)
	  else:					raise FuseOSError(EACCES)	# (Permission denied)
    def _is_inode_W_OK(self,inode,context,uname):
	mode=inode['st_mode']
	if not (mode & S_IWOTH) and not((mode & S_IWUSR) and (inode['st_uid'] == context[0])):
	  if (mode & S_IWGRP):
	    if not(uname in grp.getgrgid(inode['st_gid']).gr_mem):
	      if context[1] != inode['st_gid']:	raise FuseOSError(EACCES)	# (Permission denied)
	  else:					raise FuseOSError(EACCES)	# (Permission denied)
    def _is_inode_X_OK(self,inode,context,uname):
	mode=inode['st_mode']
	if not (mode & S_IXOTH) and not((mode & S_IXUSR) and (inode['st_uid'] == context[0])):
	  if (mode & S_IXGRP):
	    if not(uname in grp.getgrgid(inode['st_gid']).gr_mem):
	      if context[1] != inode['st_gid']:	raise FuseOSError(EACCES)	# (Permission denied)
	  else:					raise FuseOSError(EACCES)	# (Permission denied)
    ######## Temporaly Ramdisk function (Get inode with Permission check) ########
    def _get_inode_nopermisson(self,path):				# get inode without any permission checking
	  if path=="/":
	    dentry=["/","."]
	    return self.inode
	  else:
	    dentry=os.path.split(path)
	  try:
	    inode_dir=self._get_inode_nopermisson(dentry[0])
	    inode=inode_dir['i_data'][dentry[1]]
	    return inode
	  except:
	    raise FuseOSError(ENOENT)							# No such file or directory
    def _get_inode(self,path,context,uname):				###### Get inode from path,context,uname #######
	if path=="/":
	  dentry=["/","."]
	  inode=self.inode
	else:
	  dentry=os.path.split(path)
	  inode_dir=self._get_inode(dentry[0],context,uname)		
	  self._is_inode_X_OK(inode_dir,context,uname)			# dir access permission check
	  try:inode=inode_dir['i_data'][dentry[1]]
	  except:raise FuseOSError(ENOENT)				# (No such file or directory)
	return inode
    def _get_inode_for_access(self,path):				###### Get inode for Access ######
	context=fuse_get_context()					# get system call uid and gid
	uname=pwd.getpwuid(context[0]).pw_name				# get system call uname
	return self._get_inode(path,context,uname),context,uname
    def _get_inode_for_read(self,path):				###### Get inode for Read ######
	inode,context,uname=self._get_inode_for_access(path)		# access permission check
	self._is_inode_R_OK(inode,context,uname)				# read permission check
	return inode
    def _get_inode_for_write(self,path):				#
	inode,context,uname=self._get_inode_for_access(path)		# access permission check
	self._is_inode_W_OK(inode,context,uname)				# write permission check
	return inode
    def _get_dir_inode_for_create(self,path):				#
	dentry=os.path.split(path)					# split to dirpath and filename 
	inode,context,uname=self._get_inode_for_access(dentry[0])	# dir access permission check
	self._is_inode_W_OK(inode,context,uname)				# dir write permission check
	return inode,dentry[1]						# return inode_dir and filename
    def _get_inode_for_delete(self,path):				#
	dentry=os.path.split(path)					# split to dirpath and filename 
	inode,context,uname=self._get_inode_for_access(dentry[0])	# dir access permission check
	self._is_inode_W_OK(inode,context,uname)				# dir write permission check
	now=time()
	inode_file=inode['i_data'][dentry[1]]
	return inode_file,inode,dentry[1],now				# return inode_file, inode_dir, filename and now
    def _get_inode_for_create(self,path):				#
	dentry=os.path.split(path)					# split to dirpath and filename 
	inode,context,uname=self._get_inode_for_access(dentry[0])	# dir access permission check
	self._is_inode_W_OK(inode,context,uname)				# dir write permission check
	now=time()
        inode_file = dict(						# set default common parameters
		st_nlink=0,st_size=0, 					# nlink and size
		st_ctime=now,st_mtime=now,st_atime=now,			# ctime,mtime,atime
		st_uid=context[0],st_gid=context[1],			# uid,gid
		) 
        inode_file['st_ino']=id(inode)					# inode number <-- address of inode_file
	return inode_file,inode,dentry[1],now				# return inode_file, inode_dir, filename and now
    ######## Temporaly Ramdisk function (inode Operation) ########
    def _update_atime(self,inode):					# read,readdir,readlink -> update atime 
	now = time() 
	inode['st_atime']=now						# read dir list -> change atime
    def _write_filedata(self,inode,filedata):				#
	now=time()
        inode['i_data'] = filedata
        inode['st_mtime'] = now						# write file data -> change mtime
        inode['st_size'] = len(filedata) 
        inode['st_ctime'] = now 					# change size -> change ctime
    def _add_inode_nopermisson(self,path,inode,dirinode=False):	# add inode without any permission checking
	  dentry=os.path.split(path)
	  inode_dir=self._get_inode_nopermisson(dentry[0])
	  inode_dir['i_data'][dentry[1]] = inode
	  if dirinode:inode_dir['st_nlink'] += 1
    def _add_inode_as_dir(self,inode_dir,inode,filename,now):		#
	inode_dir['i_data'][filename] = inode
	inode_dir['st_mtime'] = now					# write to dir list	-> change mtime
        inode_dir['st_nlink'] += 1
	inode_dir['st_ctime'] = now					# change nlink 		-> change ctime
        inode['st_nlink'] += 1
	inode['st_ctime']=now						# change nlink 		-> change ctime
    def _add_inode_as_file(self,inode_dir,inode,filename,now):		#
	inode_dir['i_data'][filename] = inode
	inode_dir['st_mtime'] = now					# write to dir list	-> change mtime
	inode_dir['st_ctime']=now 					# ???
        inode['st_nlink'] += 1
	inode['st_ctime']=now						# change nlink 		-> change ctime
    def _removefile_inode(self,inode,inode_dir,filename,now):		#
	inode_dir['i_data'].pop(filename)
	inode_dir['st_mtime']=now					# change dir list -> change mtime
        inode_dir['st_nlink'] -= 1
	inode_dir['st_ctime']=now 					# ???
        inode['st_nlink'] -= 1
	inode['st_ctime']=now 						# change nlink -> change ctime
	inode['st_mtime']=now						# ???
    def _removedir_inode(self,inode,inode_dir,filename,now):		#
	if name == '/':						# Shuld be check root? ??
	  raise FuseOSError(EPERM)					# Operation not permitted
	elif len(inode['i_data'])>=2:					# Attention!!!  empty means only '.' in the Dir list
	  raise FuseOSError(ENOTEMPTY)					# Directory not empty 
	else:
	  inode_dir['i_data'].pop(filename)
	  inode_dir['st_mtime']=now					# change dir list -> change mtime
          inode_dir['st_nlink'] -= 1
	  inode_dir['st_ctime']=now 					# change nlink -> change ctime
	  # not for rmdir, but for rename. 
          inode['st_nlink'] -= 1
	  inode['st_ctime']=now 					# change nlink -> change ctime
	  inode['st_mtime']=now 					# change dir list '..' -> change mtime
    ######## Root Symlink Check ########
    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,inode_dir,filename,now=self._get_inode_for_create(targetdir)	# write permission check
            inode['st_mode']= S_IMODE(0755) | S_IFDIR 			# set mode (dir)
            inode['i_data']={'.':inode}					# add '.' link to self
            inode['st_nlink'] +=1					# add nlink 1
	    inode['st_ctime']=st.st_ctime
	    inode['st_mtime']=st.st_mtime
	    inode['st_atime']=st.st_atime
	    inode['st_uid']=st.st_uid
	    inode['st_gid']=st.st_gid
	    inode['st_blksize']=st.st_blksize	# fix me !
	    inode['st_blocks']=st.st_blocks	# fix me !
	    inode['st_dev']=st.st_dev		# fix me !
	    inode['st_ino']=st.st_ino		# fix me !
	    inode['st_rdev']=st.st_rdev		# fix me !
	    inode['st_size']=st.st_size		# fix me !
	    self._add_inode_as_dir(inode_dir,inode,filename,st.st_ctime)# add inode as dir
            Afio_load(targetdir,sourceabspath,self)
            return targetname						# afio.lzo file
          else:
            return source						# normal symlink
	return source
    ######## FUSE Operating function (Start File System) ########
    def init(self, path):
        """Called on filesystem initialization. Path is always /
           Use it instead of __init__ if you start threads on initialization."""
        pass
    def statfs(self, path): 
        return dict(f_bsize=512, f_blocks=4096, f_bavail=2048) 
	#   long    f_type;     /* type of filesystem (see below) */
	#   long    f_bsize;    /* optimal transfer block size */
	#   long    f_blocks;   /* total data blocks in file system */
	#   long    f_bfree;    /* free blocks in fs */
	#   long    f_bavail;   /* free blocks avail to non-superuser */
	#   long    f_files;    /* total file nodes in file system */
	#   long    f_ffree;    /* free file nodes in fs */
	#   fsid_t  f_fsid;     /* file system id */
	#   long    f_namelen;  /* maximum length of filenames */
    def destroy(self, path):
        """Called on filesystem destruction. Path is always /"""
        pass
    ######## FUSE Operating function (Attr) ########
    def access(self, path, amode):
	inode,context,uname=self._get_inode_for_access(path)		# access permission check
        return 0							# ????
    def getattr(self, path, fh=None): 					###### getattr ######
	inode,context,uname=self._get_inode_for_access(path)		# access permission check
        return inode							# return inode				# fix me ! inode contain many other data. use xattr!
	#    st_dev;     /* ID of device containing file */
	#    st_ino;     /* inode number */
	#    st_mode;    /* protection */
	#    st_nlink;   /* number of hard links */
	#    st_uid;     /* user ID of owner */
	#    st_gid;     /* group ID of owner */
	#    st_rdev;    /* device ID (if special file) */
	#    st_size;    /* total size, in bytes */
	#    st_blksize; /* blocksize for filesystem I/O */
	#    st_blocks;  /* number of blocks allocated */
	#    st_atime;   /* time of last access */
	#    st_mtime;   /* time of last modification */
	#    st_ctime;   /* time of last status change */
    def chmod(self, path, mode):					###### chmod ######
	inode,context,uname=self._get_inode_for_access(path)		# access permission check
	inode['st_mode'] = S_IFMT(inode['st_mode']) | S_IMODE(mode)	# see S_IFMT,S_IMODE
	now=time()
        inode['st_ctime'] = now 					# change inode data -> change ctime
        return 0 							# return 0 				# ???
    def chown(self, path, uid, gid):					###### chown ######
	inode,context,uname=self._get_inode_for_access(path)		# access permission check
        if uid != -1:inode['st_uid'] = uid 				# set uid with check -1 
        if gid != -1:inode['st_gid'] = gid 				# set gid with check -1 
	now=time()
        inode['st_ctime'] = now 					# change inode data -> change ctime
    def utimens(self, path, times=None):				###### utime ######
	# atime mtime can be changed by touch (utimes),but not ctime
	# In BSD symlink's atime,mtime,ctime is can changed with lutimes, but not in Linux because no lutimes.
	inode,context,uname=self._get_inode_for_access(path)		# access permission check
        now = time() 
        atime, mtime = times if times else (now, now) 
	inode['st_atime'] = atime 
	inode['st_mtime'] = mtime
	# ctime <-- now ?
    ######## FUSE Operating function (XAttr) ########
    def listxattr(self, path): 
	inode,context,uname=self._get_inode_for_access(path)		# access permission check
        attrs = inode.get('attrs', {}) 
        return attrs.keys() 
    def getxattr(self, path, name, position=0): 
	inode,context,uname=self._get_inode_for_access(path)		# access permission check
        attrs = inode.get('attrs', {}) 
        try: 
            return attrs[name] 
        except KeyError: 
	  raise FuseOSError(ENOSYS)					# Function not implemented
    def setxattr(self, path, name, value, options, position=0):	# Ignore options 
	inode,context,uname=self._get_inode_for_access(path)		# access permission check
        attrs = inode.setdefault('attrs', {}) 
        attrs[name] = value 
    def removexattr(self, path, name): 
	inode,context,uname=self._get_inode_for_access(path)		# access permission check
        attrs = inode.get('attrs', {}) 
        try: 
            del attrs[name] 
        except KeyError: 
	  raise FuseOSError(ENOSYS)					# Function not implemented
    ######## FUSE Operating function (Read Dir) ########
    def opendir(self, path):
        """Returns a numerical file handle."""
        return 0
    def readdir(self, path, fh):					###### readdir ######
        inode=self._get_inode_for_read(path)				# read permission check
        if not S_ISDIR(inode['st_mode']):raise FuseOSError(ENOSYS)	# Function not implemented
        dirnames=inode['i_data'].keys()					# read dir list
	dirnames.append('..')						# append parent dir '..'
	self._update_atime(inode)					# read dir list -> update atime
	return dirnames						# return dir list
    def releasedir(self, path, fh):
        return 0
    def fsyncdir(self, path, datasync, fh):
        return 0
    ######## FUSE Operating function (Create or Remove Dir) ########
    def mkdir(self, path, mode): 					###### mkdir ######
	inode,inode_dir,filename,now=self._get_inode_for_create(path)	# write permission check
        inode['st_mode']= S_IMODE(mode) | S_IFDIR 			# set mode (dir)
        inode['i_data']={'.':inode}					# add '.' link to self
        inode['st_nlink'] +=1						# add nlink 1
	self._add_inode_as_dir(inode_dir,inode,filename,now)		# add inode as dir
    def rmdir(self, path): 						###### rmdir ######
        inode,inode_dir,filename,now=self._get_inode_for_delete(path)	# write permission check
        if not S_ISDIR(inode['st_mode']):raise FuseOSError(ENOSYS)	# Function not implemented (not Dir shuld be delete unlink)
	self._removedir_inode(inode,inode_dir,name,now)			# remove dir
    ######## FUSE Operating function (Create or Remove File) ########
    def mknod(self, path, mode, dev):					###### mknod ######
	mask=S_IFSOCK | S_IFBLK | S_IFCHR | S_IFIFO			# ??? S_IFREG ?			# fix me!
	if (mode & mask) == 0 :raise FuseOSError(EPERM)		# (Operation not permitted) 					# 
	inode,inode_dir,filename,now=self._get_inode_for_create(path)	# write permission check
	inode['st_mode']= S_IMODE(mode) | (mode & mask)			# set node mode 		# fix me!
	inode['st_dev']=dev						# set dev			# fix me!
	self._add_inode_as_file(inode_dir,inode,filename,now)		# add inode as file
    def create(self, path, mode): 					###### create ######
	inode,inode_dir,filename,now=self._get_inode_for_create(path)	# write permission check
        inode['st_mode']= S_IFREG | S_IMODE(mode)			# set reguler file mode
        inode['i_data']=''						# set no data
	self._add_inode_as_file(inode_dir,inode,filename,now)		# add inode as file
	self.fd += 1 
        return self.fd 						# return fd
    def unlink(self, path): 						###### unlink ######
        inode,inode_dir,filename,now=self._get_inode_for_delete(path)	# write permission check
        if S_ISDIR(inode['st_mode']):raise FuseOSError(ENOSYS)		# Function not implemented (Dir shuld be delete rmdir)
	self._removefile_inode(inode,inode_dir,filename,now)		# remove file
    def rename(self, old, new): 
	# change time shuld be changed with write, rename, truncate, chmod, link or chown
        inode,old_dir,old_name,now=self._get_inode_for_delete(old)	# write permission check
	inode_dir,filename=self._get_dir_inode_for_create(new)		# write permission check
	# If  oldpath  and  newpath are existing hard links referring to the same			# fix me! 
	# 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 directory. */
	# 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(inode['st_mode']):
	  self._add_inode_as_dir(inode_dir,inode,filename,now)		# add dir link
	  self._removedir_inode(inode,old_dir,old_name,now)		# remove dir (Should be first ?)
	else:
	  self._add_inode_as_file(inode_dir,inode,filename,now)		# add file link
	  self._removefile_inode(inode,old_dir,old_name,now)		# remove file
    def link(self, target, source):					###### link ######
	inode_dir,filename=self._get_dir_inode_for_create(target)	# write permission check
        inode=self._get_inode_nopermisson(source)							# fix me ! what permission should be use?
	now=time()							# get time now
        if S_ISDIR(inode['st_mode']):raise FuseOSError(ENOSYS)		# Function not implemented
	self._add_inode_as_file(inode_dir,inode,filename,now)		# add inode as file
    ######## FUSE Operating function (Sym Link) ########
    def readlink(self, path):						###### readlink ###### 
        inode=self._get_inode_for_read(path)				# read permission check
        if not S_ISLNK(inode['st_mode']):raise FuseOSError(EPERM)	#(Operation not permitted)
	self._update_atime(inode)					# read link path -> update atime
        data=inode.get('i_data',None)
	if data==None:
	  return inode.read_data()
	else:
	  return data	 						# return source path
    def symlink(self, target, source): 				###### symlink ######
	inode,inode_dir,filename,now=self._get_inode_for_create(target)	# write permission check
        inode['st_mode']=(S_IFLNK | S_IRWXU | S_IRWXG | S_IRWXO )	# set symlink mode
        inode['st_size']=len(source)					# set size
        inode['i_data']=source						# set source path as data
	self._add_inode_as_file(inode_dir,inode,filename,now)		# add inode as file
    ######## FUSE Operating function (Read or Write File) ########
    def open(self, path, flags): 
        self.fd += 1 							# any time shuld not be changed with only open
        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):  				###### read ######
        inode=self._get_inode_for_read(path)				# read permission check
        if not S_ISREG(inode['st_mode']):raise FuseOSError(EPERM)	#(Operation not permitted)
	self._update_atime(inode)					# read filedata -> update atime
        filedata=inode.get('i_data',None)  
        if filedata==None:
	  return inode.read_data_offsetsize(offset,size)		# return read data
	else:
          return filedata[offset:(offset + size)] 			# return read data
    def write(self, path, data, offset, fh): 				###### write ######
        inode=self._get_inode_for_write(path)				# write permission check
        if not S_ISREG(inode['st_mode']):raise FuseOSError(EPERM)	#(Operation not permitted)
        filedata=inode.get('i_data',None)  
        if filedata==None:
          filedata=inode.read_data()
	filedata=filedata[:offset] + data
        self._write_filedata(inode,filedata)				# wite filedata
	return len(data)						# return write length
    def truncate(self, path, length, fh=None): 			###### truncate ######
        inode=self._get_inode_for_write(path)				# write permission check
        if not S_ISREG(inode['st_mode']):raise FuseOSError(EPERM)	#(Operation not permitted)
        filedata=inode.get('i_data',None)  
        if filedata==None:
          filedata=inode.read_data()
	filedata=filedata[:length]
        self._write_filedata(inode,filedata)				# wite filedata
    def release(self, path, fh):
        return 0
    def flush(self, path, fh):
        return 0
    def fsync(self, path, datasync, fh):
        return 0


###### AfioLxoFs Function ######
###### Get Information Function ###### 
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])
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 _lzop_size(path,seek,size):
	if size>50:size=50							# read first 50 bytes
	f=open(path)
	f.seek(seek)
	top50=f.read(size)
	size=int("%02x%02x%02x%02x"%(ord(top50[38]),ord(top50[39]),ord(top50[40]),ord(top50[41])),16)
	f.close()
	return size
###### Inode Class ###### 
class Inode_Afio_Base(Inode):
	def __init__(self,seek=None,loader=None):
	  self.loader=loader
	  self.header_address=seek
	  st_path,st_rdev=self.get_inode(init=True)
	  self.set_inode(st_path=st_path,st_rdev=st_rdev)
	def get_inode(self,init=False):					# call from getatter,__init__
	  inode,leng,s1,s2=self.get_inode_dict()
	  if init:
	    self.st_mode=	inode['st_mode']
	    self.st_nlink=	inode['st_nlink']
	    self.data_size=	inode['st_size']
	    hdr2=self.loader.read_data(self.header_address,leng)
	    st_path=		self.loader.read_data(self.header_address+leng,inode['st_nlen']-1)
            self.data_addr=	self.header_address+leng+inode['st_nlen']
	    print s1
	    print s2
            print "%s%s" % (hdr2,st_path)
	    return st_path,inode['st_rdev']
	  else:
	    inode['st_atime']=inode['st_mtime']
	    inode['st_ctime']=inode['st_mtime']
	    size=self.get('st_size')
	    if self.get('compress'):inode['st_size']=self.compress[1](self.loader.abspath,self.data_addr,self.data_size)
	    return inode
	def set_inode(self,st_path,st_rdev):					# call from __init__
          if S_ISDIR(self.st_mode):						# DIR
            self.i_data={'.':self}						# empty dir {'.'}
	    self.st_nlink=2							# nlink = 2
	    path='%s/%s' % (self.loader.target,st_path)
            self.loader.fs._add_inode_nopermisson(path,self,dirinode=True)	# count up parent dir st_nlink
          elif S_ISLNK(self.st_mode):						# SLINK
	    if self.st_nlink>1:inode=self.loader._setnlink(self)		# count up st_nlink, if inode is exist
	    path='%s/%s' % (self.loader.target,st_path)
	    self.loader.fs._add_inode_nopermisson(path,self)
          elif S_ISREG(self.st_mode):						# REG FILE
            if ((st_rdev & 1)== 0) and (st_path[-2:] =='.z'):			# '.z' and 'rdev & 1==0' mean compressed file in afio header
              st_path=st_path[:-2]						# remove ".z"
	      self.compress=self.loader.read_compress_format(self.data_addr,self.data_size) # if not lzop?
	    path='%s/%s' % (self.loader.target,st_path)
	    if self.st_nlink>1:inode=self.loader.fs._setnlink(self)
	    self.loader.fs._add_inode_nopermisson(path,self)
	  else:								# Special File
	    self.loader.fs._add_inode_nopermisson(path,self)
	def read_data(self):							# call from read symlink,write,truncate
	    return self.loader.read_data(self.data_addr,self.data_size)
	def read_data_offsetsize(self,offset,size):				# call from read
            compress=self.get('compress')
            if compress!=None:
	      data=self.loader.read_data(self.data_addr,self.data_size)
	      data=compress[0](data)       
	      self.i_data=data
	      return data[offset:(offset + size)]
	    else:
	      return self.loader.read_data(self.data_addr+offset,min(self.data_size-offset,size))
class Inode_Afio_070707(Inode_Afio_Base):					# old ASCII magic number
	def get_inode_dict(self):
	  ID=6;LEN=70
	  hdr2=self.loader.read_data(self.header_address+ID,LEN)
	  inode=dict(
	    st_dev=	int(hdr2[0:6],8),
	    st_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),
	    st_rdev=	int(hdr2[36:42],8),
	    st_mtime=	int(hdr2[42:53],8),
	    st_nlen=	int(hdr2[53:59],8),
	    st_size=	int(hdr2[59:70],8),
	  )
	  s1= "|23456|23456|23456|23456|23456|23456|23456|23456|23456789ab|23456|23456789ab|..."
	  s2= "|  hdr|  dev|  ino| mode|  uid|  gid|nlink| rdev|     mtime|nmlen|      size|pathname"
	  return inode,ID+LEN,s1,s2
class Inode_Afio_070717(Inode_Afio_Base):					# extended ASCII magic number
	def get_inode_dict(self):
	  ID=6;LEN=75
	  hdr2=self.loader.read_data(self.header_address+ID,LEN)
	  inode=dict(
	    st_dev=	int(hdr2[0:6],8),
	    st_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),
	    st_rdev=	int(hdr2[41:47],8),
	    st_mtime=	int(hdr2[47:58],8),
	    st_nlen=	int(hdr2[58:64],8),
	    st_size=	int(hdr2[64:75],8),
	  )
	  s1= "|23456|23456|23456789ab|23456|23456|23456|23456|23456|23456789ab|23456|23456789ab|..."
	  s2= "|  hdr|  dev|       ino| mode|  uid|  gid|nlink| rdev|     mtime|nmlen|      size|pathname"
	  return inode,ID+LEN,s1,s2
class Inode_Afio_070727(Inode_Afio_Base):					# large ASCII magic number	
	def get_inode_dict(self):
	  ID=6;LEN=110
	  hdr2=self.loader.read_data(self.header_address+ID,LEN)
	  inode=dict(
	    st_dev=	int(hdr2[0:6],8),
	    st_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),
	    st_rdev=	int(hdr2[41:47],8),
	    st_mtime=	int(hdr2[47:58],8),
	    st_nlen=	int(hdr2[58:64],8),
	    st_size=	int(hdr2[64:75],8),
	  )
	  s1= "|23456|2345678|234567890123456m|23456|2345678|2345678|2345678|2345678|234567890123456n|234|234|234s|234567890123456:|..."
	  s2= "|  hdr|    dev|            inoM|  mod|    uid|    gid|  nlink|   rdev|          mtimeN|nml|flg|xszS|           size:|pathname"
	  return inode,ID+LEN,s1,s2
###### Loading Class ###### 
class Afio_load():
	def __init__(self,target,abspath,fs):
	  self.target=target						# path to mount afio file in the file system
	  self.abspath=abspath						# abspath of the afio file
	  self.fs=fs							# file system
	  self.compress=[None,None]
	  self.load()
	def load(self):						# load each inode in the afio file
	  self.ino={}							# init st_ino(in afio file) Dict
	  pipe=subprocess.Popen('afio -tvBZ "%s"' % self.abspath, shell=True,stdout=subprocess.PIPE).stdout
	  f=open(self.abspath,"r")
          for line in pipe:
            i=int(line.split(None,2)[0])
	    f.seek(i)
	    hdr=f.read(6)
            self.load_header(i,hdr)
          f.close()  
	  pipe.close()
	  self.ino=None			# fix me ! del ino
	def load_header(self,seek=None,hdr=''):
	  if	hdr=='070707':Inode_Afio_070707(seek=seek,loader=self);return	# old ASCII magic number
	  elif	hdr=='070717':Inode_Afio_070717(seek=seek,loader=self);return	# extended ASCII magic number
	  elif	hdr=='070727':Inode_Afio_070727(seek=seek,loader=self);return	# large ASCII magic number		not implemented yet
	  elif hdr=='070701':return 						# cpio new ASCII magic number	length: 110	 not implemented yet
	  elif hdr=='070702':return 						# cpio new ASCII magic number with CRC	 not implemented yet
	  elif hdr=='070703':return 						# Tcpio magic number of TI/E		 not implemented yet
          else:return
	def _setnlink(self,inode):
	  ino=inode['i_ino']
	  inode2=self.ino.get(ino,None)
	  if inode2==None:
	    self.ino[ino]=inode
	    inode['st_nlink']=1
	    return inode
	  else:
	    inode2['st_nlink']+=1
	    return inode2
	def read_data(self,addr,size):
	  f=open(self.abspath,"r")
	  f.seek(addr)
	  data=f.read(size)
	  f.close()
	  return data
	def read_compress_format(self,addr,size):
	  if self.compress[0] == None:
	    f=open(self.abspath,"r")
	    f.seek(addr)
            if size>50:size=50							# read first 50 bytes
            top50=self.read_data(addr,size)
	    filetype=_filetype(top50)
	    if filetype=='lzop compressed data':
	      self.compress=[_lzop,_lzop_size]
	  return self.compress
#### FUSE Operation class ####
class Afiolzofs(Ramfs):
    """Afio Lzo filesystem. """ 
    ######## Root Symlink Check ########
    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,inode_dir,filename,now=self._get_inode_for_create(targetdir)	# write permission check
            inode['st_mode']= S_IMODE(0755) | S_IFDIR 				# set mode (dir)
            inode['i_data']={'.':inode}						# add '.' link to self
            inode['st_nlink'] +=1						# add nlink 1
	    inode['st_ctime']=st.st_ctime
	    inode['st_mtime']=st.st_mtime
	    inode['st_atime']=st.st_atime
	    inode['st_uid']=st.st_uid
	    inode['st_gid']=st.st_gid
	    inode['st_blksize']=st.st_blksize	# fix me !
	    inode['st_blocks']=st.st_blocks	# fix me !
	    inode['st_dev']=st.st_dev		# fix me !
	    inode['st_ino']=st.st_ino		# fix me !
	    inode['st_rdev']=st.st_rdev		# fix me !
	    inode['st_size']=st.st_size		# fix me !
	    self._add_inode_as_dir(inode_dir,inode,filename,st.st_ctime)	# add inode as dir
            Afio_load(targetdir,sourceabspath,self)
            return targetname							# afio.lzo file
          else:
            return source							# normal symlink
	return source
    def symlink(self, target, source): 					###### symlink ######
        source=self._check_symlink(target,source)				# check mount root -> mount afio.lzo
	# make normal symlink
	Ramfs.symlink(self,target, source)
 
if __name__ == "__main__": 
    if len(argv) != 2: 
        print 'usage: %s <mountpoint>' % 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) 

