source: baculafs/trunk/baculafs.py@ 782

Last change on this file since 782 was 782, checked in by joergs, on Aug 25, 2009 at 5:20:48 PM

added option parsing, debug, singleThreaded and version

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