source: baculafs/trunk/baculafs.py@ 783

Last change on this file since 783 was 783, checked in by joergs, on Aug 25, 2009 at 5:23:49 PM

changed version and keywords

  • Property svn:executable set to *
  • Property svn:keywords set to
    Id
    Rev
    URL
File size: 7.6 KB
RevLine 
[778]1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3
[783]4# $URL: baculafs/trunk/baculafs.py $
5# $Id: baculafs.py 783 2009-08-25 15:23:49Z joergs $
6
[778]7import logging
8import fuse
9import stat
10import errno
11import os
12import pexpect
13import sys
14
15fuse.fuse_python_api = (0, 2)
16
17###### bconsole
18################
19
[783]20BACULA_FS_VERSION = "$Rev: 783 $"
[782]21
[778]22LOG_FILENAME = '/tmp/baculafs.log'
[780]23#LOG_BCONSOLE = '/tmp/bconsole.log'
[778]24LOG_BCONSOLE_DUMP = '/tmp/bconsole.out'
25
26
27#BACULA_CMD = 'strace -f -o /tmp/bconsole.strace /usr/sbin/bconsole -n'
28BACULA_CMD = '/usr/sbin/bconsole -n'
29# .clients
30# 5: ting-fd
31BACULA_CLIENT='5'
32
33BCONSOLE_CMD_PROMPT='\*'
34BCONSOLE_SELECT_PROMPT='Select item:.*'
35BCONSOLE_RESTORE_PROMPT='\$ '
36
37
38class Bconsole:
39
40 def __init__(self, client=BACULA_CLIENT):
[780]41 #logging.debug('BC init')
[778]42 self.bconsole = pexpect.spawn( BACULA_CMD, logfile=file(LOG_BCONSOLE_DUMP, 'w'), timeout=10 )
43 self.bconsole.setecho( False )
44 self.bconsole.expect( BCONSOLE_CMD_PROMPT )
45 self.bconsole.sendline( 'restore' )
46 #self.bconsole.expect( BCONSOLE_SELECT_PROMPT )
47 self.bconsole.sendline( "5" )
48 #self.bconsole.expect( BCONSOLE_SELECT_PROMPT )
49 self.bconsole.sendline( BACULA_CLIENT )
50 self.bconsole.expect( BCONSOLE_RESTORE_PROMPT )
[780]51 #logging.debug( "BC alive: " + str(self.bconsole.isalive()) )
52 #logging.debug('BC init done')
[778]53
[781]54 def cd(self, path):
[778]55 path = path + "/"
[780]56 logging.debug( "(" + path + ")" )
[778]57
58 self.bconsole.sendline( 'cd ' + path )
59 #self.bconsole.expect( BCONSOLE_RESTORE_PROMPT, timeout=10 )
60 #self.bconsole.sendline( 'pwd' )
61
62 index = self.bconsole.expect( ["cwd is: " + path + "[/]?", BCONSOLE_RESTORE_PROMPT, pexpect.EOF, pexpect.TIMEOUT ] )
[780]63 logging.debug( "cd result: " + str(index) )
[778]64
65 if index == 0:
66 # path ok, now wait for prompt
67 self.bconsole.expect( BCONSOLE_RESTORE_PROMPT )
[781]68 return True
[778]69 elif index == 1:
70 #print "wrong path"
[781]71 return False
[778]72 elif index == 2:
[781]73 logging.error( "EOF bconsole" )
74 #raise?
75 return False
[778]76 elif index == 3:
[781]77 logging.error( "TIMEOUT bconsole" )
78 return False
79
80 def ls(self, path):
81 logging.debug( "(" + path + ")" )
82
83 if self.cd( path ):
84 self.bconsole.sendline( 'ls' )
85 self.bconsole.expect( BCONSOLE_RESTORE_PROMPT )
86 lines = self.bconsole.before.splitlines()
87 #logging.debug( str(lines) )
88 return lines
89 else:
[778]90 return
91
92###############
93
94class BaculaFS(fuse.Fuse):
95
96 TYPE_NONE = 0
97 TYPE_FILE = 1
98 TYPE_DIR = 2
99
100 files = { '': {'type': TYPE_DIR} }
101
102 def __init__(self, *args, **kw):
103 logging.debug('init')
104 #self.console = Bconsole()
105 fuse.Fuse.__init__(self, *args, **kw)
106 #logging.debug('init finished')
107
[781]108
109 def _getattr(self,path):
110 # TODO: may cause problems with filenames that ends with "/"
111 path = path.rstrip( '/' )
112 logging.debug( '"' + path + '"' )
113
114 if (path in self.files):
115 #logging.debug( "(" + path + ")=" + str(self.files[path]) )
116 return self.files[path]
117
118 if Bconsole().cd(path):
119 # don't define files, because these have not been checked
120 self.files[path] = { 'type': self.TYPE_DIR, 'dirs': [ ".", ".." ] }
121
122 return self.files[path]
123
124
125
126
[778]127 def _getdir(self, path):
128
129 # TODO: may cause problems with filenames that ends with "/"
130 path = path.rstrip( '/' )
[780]131 logging.debug( '"' + path + '"' )
[778]132
133 if (path in self.files):
[780]134 #logging.debug( "(" + path + ")=" + str(self.files[path]) )
[781]135 if self.files[path]['type'] == self.TYPE_NONE:
136 logging.info( '"' + path + '" does not exist (cached)' )
[778]137 return self.files[path]
[781]138 elif self.files[path]['type'] == self.TYPE_FILE:
139 logging.info( '"' + path + '"=file (cached)' )
140 return self.files[path]
[778]141 elif ((self.files[path]['type'] == self.TYPE_DIR) and ('files' in self.files[path])):
[781]142 logging.info( '"' + path + '"=dir (cached)' )
[778]143 return self.files[path]
144
145 try:
146 files = Bconsole().ls(path)
[781]147 logging.debug( " files: " + str( files ) )
[780]148
149 # setting initial empty directory. Add entires later in this function
150 self.files[path] = { 'type': self.TYPE_DIR, 'dirs': [ ".", ".." ], 'files': [] }
[778]151 for i in files:
152 if i.endswith('/'):
153 # we expect a directory
154 # TODO: error with filesnames, that ends with '/'
155 i = i.rstrip( '/' )
156 self.files[path]['dirs'].append(i)
157 if not (i in self.files):
158 self.files[path + "/" + i] = { 'type': self.TYPE_DIR }
159 else:
160 self.files[path]['files'].append(i)
161 self.files[path + "/" + i] = { 'type': self.TYPE_FILE }
162
[780]163 except Exception as e:
164 logging.exception(e)
[778]165 logging.error( "no access to path " + path )
166 self.files[path] = { 'type': TYPE_NONE }
[780]167 logging.debug( '"' + path + '"=' + str( self.files[path] ) )
[778]168 return self.files[path]
169
170
171
172 # TODO: only works after readdir for the directory (eg. ls)
173 def getattr(self, path):
174
[780]175 # TODO: may cause problems with filenames that ends with "/"
176 path = path.rstrip( '/' )
177 logging.debug( '"' + path + '"' )
178
[778]179 st = fuse.Stat()
180
[781]181 if not (path in self.files):
182 self._getattr(path)
[778]183
[781]184 if not (path in self.files):
185 return -errno.ENOENT
186
187 file = self.files[path]
188
[778]189 if file['type'] == self.TYPE_FILE:
190 st.st_mode = stat.S_IFREG | 0444
191 st.st_nlink = 1
192 st.st_size = 0
[781]193 return st
[778]194 elif file['type'] == self.TYPE_DIR:
195 st.st_mode = stat.S_IFDIR | 0755
[779]196 if 'dirs' in file:
197 st.st_nlink = len(file['dirs'])
198 else:
199 st.st_nlink = 2
[781]200 return st
[778]201
[781]202 # TODO: check for existens
203 return -errno.ENOENT
204
[778]205
206
[781]207
[778]208 def readdir(self, path, offset):
[780]209 logging.debug( '"' + path + '", offset=' + str(offset) + ')' )
[778]210
211 dir = self._getdir( path )
212
213 #logging.debug( " readdir: type: " + str( dir['type'] ) )
214 #logging.debug( " readdir: dirs: " + str( dir['dirs'] ) )
215 #logging.debug( " readdir: file: " + str( dir['files'] ) )
216
217 if dir['type'] != self.TYPE_DIR:
218 return -errno.ENOENT
219 else:
220 return [fuse.Direntry(f) for f in dir['files'] + dir['dirs']]
221
222 #def open( self, path, flags ):
223 #logging.debug( "open " + path )
224 #return -errno.ENOENT
225
226 #def read( self, path, length, offset):
227 #logging.debug( "read " + path )
228 #return -errno.ENOENT
229
230if __name__ == "__main__":
[780]231 # initialize logging
232 logging.basicConfig(filename=LOG_FILENAME,level=logging.DEBUG,format="%(asctime)s %(process)5d(%(threadName)s) %(levelname)-7s %(funcName)s( %(message)s )")
233
[782]234
235 usage = """
236 Bacula filesystem: displays files from Bacula backups as a (userspace) filesystem.
237 Internaly, it uses Baculas bconsole.
238
239 """ + fuse.Fuse.fusage
240
241
242 fs = BaculaFS(
243 version="%prog: " + BACULA_FS_VERSION,
244 usage=usage,
245 # required to let "-s" set single-threaded execution
246 dash_s_do='setsingle'
247 )
248
249 #server.parser.add_option(mountopt="root", metavar="PATH", default='/',help="mirror filesystem from under PATH [default: %default]")
250 #server.parse(values=server, errex=1)
[778]251 fs.parse()
[782]252
[778]253 fs.main()
Note: See TracBrowser for help on using the repository browser.