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
Line 
1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3
4# $URL: baculafs/trunk/baculafs.py $
5# $Id: baculafs.py 783 2009-08-25 15:23:49Z joergs $
6
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
20BACULA_FS_VERSION = "$Rev: 783 $"
21
22LOG_FILENAME = '/tmp/baculafs.log'
23#LOG_BCONSOLE = '/tmp/bconsole.log'
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):
41 #logging.debug('BC init')
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 )
51 #logging.debug( "BC alive: " + str(self.bconsole.isalive()) )
52 #logging.debug('BC init done')
53
54 def cd(self, path):
55 path = path + "/"
56 logging.debug( "(" + path + ")" )
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 ] )
63 logging.debug( "cd result: " + str(index) )
64
65 if index == 0:
66 # path ok, now wait for prompt
67 self.bconsole.expect( BCONSOLE_RESTORE_PROMPT )
68 return True
69 elif index == 1:
70 #print "wrong path"
71 return False
72 elif index == 2:
73 logging.error( "EOF bconsole" )
74 #raise?
75 return False
76 elif index == 3:
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:
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
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
127 def _getdir(self, path):
128
129 # TODO: may cause problems with filenames that ends with "/"
130 path = path.rstrip( '/' )
131 logging.debug( '"' + path + '"' )
132
133 if (path in self.files):
134 #logging.debug( "(" + path + ")=" + str(self.files[path]) )
135 if self.files[path]['type'] == self.TYPE_NONE:
136 logging.info( '"' + path + '" does not exist (cached)' )
137 return self.files[path]
138 elif self.files[path]['type'] == self.TYPE_FILE:
139 logging.info( '"' + path + '"=file (cached)' )
140 return self.files[path]
141 elif ((self.files[path]['type'] == self.TYPE_DIR) and ('files' in self.files[path])):
142 logging.info( '"' + path + '"=dir (cached)' )
143 return self.files[path]
144
145 try:
146 files = Bconsole().ls(path)
147 logging.debug( " files: " + str( files ) )
148
149 # setting initial empty directory. Add entires later in this function
150 self.files[path] = { 'type': self.TYPE_DIR, 'dirs': [ ".", ".." ], 'files': [] }
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
163 except Exception as e:
164 logging.exception(e)
165 logging.error( "no access to path " + path )
166 self.files[path] = { 'type': TYPE_NONE }
167 logging.debug( '"' + path + '"=' + str( self.files[path] ) )
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
175 # TODO: may cause problems with filenames that ends with "/"
176 path = path.rstrip( '/' )
177 logging.debug( '"' + path + '"' )
178
179 st = fuse.Stat()
180
181 if not (path in self.files):
182 self._getattr(path)
183
184 if not (path in self.files):
185 return -errno.ENOENT
186
187 file = self.files[path]
188
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
193 return st
194 elif file['type'] == self.TYPE_DIR:
195 st.st_mode = stat.S_IFDIR | 0755
196 if 'dirs' in file:
197 st.st_nlink = len(file['dirs'])
198 else:
199 st.st_nlink = 2
200 return st
201
202 # TODO: check for existens
203 return -errno.ENOENT
204
205
206
207
208 def readdir(self, path, offset):
209 logging.debug( '"' + path + '", offset=' + str(offset) + ')' )
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__":
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
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)
251 fs.parse()
252
253 fs.main()
Note: See TracBrowser for help on using the repository browser.