bfuse.cc

Go to the documentation of this file.
00001 ///
00002 /// \file       bfuse.cc
00003 ///             FUSE filesystem for Blackberry databases, using Barry.
00004 ///
00005 
00006 /*
00007     Copyright (C) 2008-2009, Net Direct Inc. (http://www.netdirect.ca/)
00008 
00009     This program is free software; you can redistribute it and/or modify
00010     it under the terms of the GNU General Public License as published by
00011     the Free Software Foundation; either version 2 of the License, or
00012     (at your option) any later version.
00013 
00014     This program is distributed in the hope that it will be useful,
00015     but WITHOUT ANY WARRANTY; without even the implied warranty of
00016     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
00017 
00018     See the GNU General Public License in the COPYING file at the
00019     root directory of this project for more details.
00020 */
00021 
00022 #define FUSE_USE_VERSION 25
00023 #include <fuse.h>
00024 #include <fuse_opt.h>
00025 
00026 #include <barry/barry.h>
00027 #include <sstream>
00028 #include <getopt.h>
00029 #include <vector>
00030 #include <list>
00031 #include <string>
00032 #include <stdexcept>
00033 #include <memory>
00034 #include <tr1/memory>
00035 #include <errno.h>
00036 #include <sys/types.h>
00037 #include <fcntl.h>
00038 #include <string.h>
00039 
00040 using namespace std;
00041 using namespace std::tr1;
00042 using namespace Barry;
00043 
00044 // Global filenames
00045 const char *error_log_filename = "error.log";
00046 
00047 //
00048 // Data from the command line
00049 //
00050 
00051 /////////////////////////////////////////////////////////////////////////////
00052 // Command line option handling, through fuse
00053 
00054 //struct opt {
00055 //      char
00056 //};
00057 
00058 void Blurb()
00059 {
00060    int major, minor;
00061    const char *Version = Barry::Version(major, minor);
00062 
00063    cerr
00064    << "bfuse - FUSE filesystem for Blackberry databases\n"
00065    << "        Copyright 2008-2009, Net Direct Inc. (http://www.netdirect.ca/)\n"
00066    << "        Using: " << Version << "\n"
00067    << endl;
00068 /*
00069    << "\n"
00070    << "   -d db     Specify which database to mount.  If no -d options exist\n"
00071    << "             then all databases will be mounted.\n"
00072    << "             Can be used multiple times to mount more than one DB\n"
00073    << "   -h        This help\n"
00074    << "   -n        Use null parser on all databases.\n"
00075    << "   -p pin    PIN of device to talk with\n"
00076    << "             If only one device is plugged in, this flag is optional\n"
00077    << "   -P pass   Simplistic method to specify device password\n"
00078 */
00079 }
00080 
00081 /////////////////////////////////////////////////////////////////////////////
00082 // FUSE specific exception
00083 
00084 class fuse_error : public std::runtime_error
00085 {
00086         int m_errno;
00087 public:
00088         fuse_error(int errno_, const std::string &msg)
00089                 : std::runtime_error(msg), m_errno(errno_)
00090         {}
00091 
00092         int get_errno() const { return m_errno; }
00093 };
00094 
00095 
00096 /////////////////////////////////////////////////////////////////////////////
00097 // Barry record parsers
00098 
00099 class DataDumpParser : public Barry::Parser
00100 {
00101         uint32_t m_id;
00102         std::ostream &m_os;
00103 
00104 public:
00105         explicit DataDumpParser(std::ostream &os)
00106                 : m_os(os)
00107         {
00108         }
00109 
00110         virtual void Clear() {}
00111 
00112         virtual void SetIds(uint8_t RecType, uint32_t UniqueId)
00113         {
00114                 m_id = UniqueId;
00115         }
00116 
00117         virtual void ParseHeader(const Barry::Data &, size_t &) {}
00118 
00119         virtual void ParseFields(const Barry::Data &data, size_t &offset,
00120                                 const Barry::IConverter *ic)
00121         {
00122                 m_os << "Raw record dump for record: "
00123                         << std::hex << m_id << std::endl;
00124                 m_os << data << std::endl;
00125         }
00126 
00127         virtual void Store() {}
00128 };
00129 
00130 template <class Record>
00131 struct Store
00132 {
00133         std::ostream &m_os;
00134 
00135         explicit Store(std::ostream &os)
00136                 : m_os(os)
00137         {
00138         }
00139 
00140         // storage operator
00141         void operator()(const Record &rec)
00142         {
00143                 m_os << rec;
00144         }
00145 };
00146 
00147 typedef std::auto_ptr<Barry::Parser>            ParserPtr;
00148 
00149 ParserPtr GetParser(const string &name, std::ostream &os, bool null_parser)
00150 {
00151         if( null_parser ) {
00152                 // use null parser
00153                 return ParserPtr( new DataDumpParser(os) );
00154         }
00155         // check for recognized database names
00156         else if( name == Contact::GetDBName() ) {
00157                 return ParserPtr(
00158                         new RecordParser<Contact, Store<Contact> > (
00159                                 new Store<Contact>(os)));
00160         }
00161         else if( name == Message::GetDBName() ) {
00162                 return ParserPtr(
00163                         new RecordParser<Message, Store<Message> > (
00164                                 new Store<Message>(os)));
00165         }
00166         else if( name == Calendar::GetDBName() ) {
00167                 return ParserPtr(
00168                         new RecordParser<Calendar, Store<Calendar> > (
00169                                 new Store<Calendar>(os)));
00170         }
00171         else if( name == ServiceBook::GetDBName() ) {
00172                 return ParserPtr(
00173                         new RecordParser<ServiceBook, Store<ServiceBook> > (
00174                                 new Store<ServiceBook>(os)));
00175         }
00176 
00177         else if( name == Memo::GetDBName() ) {
00178                 return ParserPtr(
00179                         new RecordParser<Memo, Store<Memo> > (
00180                                 new Store<Memo>(os)));
00181         }
00182         else if( name == Task::GetDBName() ) {
00183                 return ParserPtr(
00184                         new RecordParser<Task, Store<Task> > (
00185                                 new Store<Task>(os)));
00186         }
00187         else if( name == PINMessage::GetDBName() ) {
00188                 return ParserPtr(
00189                         new RecordParser<PINMessage, Store<PINMessage> > (
00190                                 new Store<PINMessage>(os)));
00191         }
00192         else if( name == SavedMessage::GetDBName() ) {
00193                 return ParserPtr(
00194                         new RecordParser<SavedMessage, Store<SavedMessage> > (
00195                                 new Store<SavedMessage>(os)));
00196         }
00197         else if( name == Folder::GetDBName() ) {
00198                 return ParserPtr(
00199                         new RecordParser<Folder, Store<Folder> > (
00200                                 new Store<Folder>(os)));
00201         }
00202         else if( name == Timezone::GetDBName() ) {
00203                 return ParserPtr(
00204                         new RecordParser<Timezone, Store<Timezone> > (
00205                                 new Store<Timezone>(os)));
00206         }
00207         else {
00208                 // unknown database, use null parser
00209                 return ParserPtr( new DataDumpParser(os) );
00210         }
00211 }
00212 
00213 /////////////////////////////////////////////////////////////////////////////
00214 // PathSplit class
00215 
00216 class PathSplit
00217 {
00218         std::string m_pin, m_db, m_record, m_field, m_remainder;
00219 
00220         int m_level;            // the number of slashes, minus the first
00221                                 // i.e. first level is 0
00222         bool m_is_root;
00223 
00224 public:
00225         explicit PathSplit(const char *path)
00226                 : m_level(-1)
00227                 , m_is_root(false)
00228         {
00229                 if( *path != '/' )
00230                         return;         // return in a failed state
00231 
00232                 if( *(path+1) == 0 ) {
00233                         m_is_root = true;
00234                         return;
00235                 }
00236 
00237                 const char *s = path, *e = path;
00238                 while( *e ) {
00239                         while( *e && *e != '/' )
00240                                 e++;
00241 
00242                         m_level++;
00243 
00244                         if( s != e && (s+1) != e ) {
00245                                 string token(s+1, e);
00246 
00247                                 switch( m_level )
00248                                 {
00249                                 case 0: // root level, should not have token here
00250                                         m_level = -1;
00251                                         return; // failed state
00252 
00253                                 case 1: // have pin
00254                                         m_pin = token;
00255                                         break;
00256 
00257                                 case 2: // have db
00258                                         m_db = token;
00259                                         break;
00260 
00261                                 case 3: // have record
00262                                         m_record = token;
00263                                         break;
00264 
00265                                 case 4: // have field
00266                                         m_field = token;
00267                                         break;
00268 
00269                                 default:        // too many, store remainder and done
00270                                         m_remainder = s;        // keeps slash
00271                                         return;
00272                                 }
00273 
00274                                 // next
00275                                 s = e;
00276                                 if( *e )
00277                                         e++;
00278                         }
00279                         else if( *e ) {
00280                                 // next
00281                                 e++;
00282                         }
00283                 }
00284         }
00285 
00286         bool IsRoot() const { return m_is_root; }
00287         const std::string& Pin() const { return m_pin; }
00288         const std::string& DB() const { return m_db; }
00289         const std::string& Record() const { return m_record; }
00290         const std::string& Field() const { return m_field; }
00291         const std::string& Remainder() const { return m_remainder; }
00292         int Level() const { return m_level; }
00293 };
00294 
00295 
00296 /////////////////////////////////////////////////////////////////////////////
00297 // API classes
00298 
00299 class Entry
00300 {
00301 public:
00302         virtual ~Entry() {}
00303 };
00304 
00305 class Directory : public Entry
00306 {
00307 public:
00308         virtual int ReadDir(void *buf, fuse_fill_dir_t filler) = 0;
00309         virtual void FillDirStat(struct stat *st)
00310         {
00311                 st->st_mode = S_IFDIR | 0555;
00312                 st->st_nlink = 2;
00313         }
00314 };
00315 
00316 class File : public Entry
00317 {
00318 public:
00319         virtual void FillFileStat(const char *path, struct stat *st) = 0;
00320         virtual bool AccessOk(int flags)
00321         {
00322                 // default to readonly files
00323                 return (flags & (O_RDONLY | O_WRONLY | O_RDWR)) == O_RDONLY;
00324         }
00325         virtual int ReadFile(const char *path, char *buf, size_t size, off_t offset) = 0;
00326 };
00327 
00328 typedef Directory*                              DirectoryPtr;
00329 typedef File*                                   FilePtr;
00330 typedef std::string                             NameT;
00331 typedef std::map<NameT, DirectoryPtr>           DirMap;
00332 typedef std::map<NameT, FilePtr>                FileMap;
00333 
00334 static DirMap g_dirmap;
00335 static FileMap g_filemap;
00336 
00337 static Directory* FindDir(const NameT &name)
00338 {
00339         DirMap::iterator di = g_dirmap.find(name);
00340         return di == g_dirmap.end() ? 0 : di->second;
00341 }
00342 
00343 static File* FindFile(const NameT &name)
00344 {
00345         FileMap::iterator fi = g_filemap.find(name);
00346         return fi == g_filemap.end() ? 0 : fi->second;
00347 }
00348 
00349 /////////////////////////////////////////////////////////////////////////////
00350 // Context classes
00351 
00352 class Database : public Directory, public File
00353 {
00354 public:
00355         Barry::Mode::Desktop &m_desk;
00356         std::string m_name;
00357         const Barry::DatabaseItem *m_pdb;
00358 
00359 public:
00360         Database(Barry::Mode::Desktop &desktop,
00361                 const std::string &pin, const Barry::DatabaseItem *pdb)
00362                 : m_desk(desktop)
00363                 , m_pdb(pdb)
00364         {
00365                 m_name = string("/") + pin + "/" + m_pdb->Name;
00366 
00367                 // add to directory list
00368                 g_dirmap[ m_name ] = this;
00369         }
00370 
00371         ~Database()
00372         {
00373                 // remove any entries that point to us
00374                 FileMap::iterator b = g_filemap.begin(), e = g_filemap.end();
00375                 for( ; b != e; ++b ) {
00376                         if( b->second == this ) {
00377                                 g_filemap.erase(b);
00378                         }
00379                 }
00380 
00381                 // erase ourselves from the directory list
00382                 g_dirmap.erase( m_name );
00383         }
00384 
00385         void AddFile(const std::string &recordId)
00386         {
00387                 // FIXME - this is a hack to redirect all record files
00388                 // to this database class... next step is to possibly
00389                 // split out records into field files if we have a
00390                 // parser, or just dump the hex if we don't
00391                 string name = m_name + "/" + recordId;
00392                 g_filemap[ name ] = this;
00393         }
00394 
00395         virtual int ReadDir(void *buf, fuse_fill_dir_t filler)
00396         {
00397                 filler(buf, ".", NULL, 0);
00398                 filler(buf, "..", NULL, 0);
00399 
00400                 // list all records in database, by recordId
00401                 Barry::RecordStateTable rst;
00402                 m_desk.GetRecordStateTable(m_pdb->Number, rst);
00403 
00404                 Barry::RecordStateTable::StateMapType::iterator
00405                         b = rst.StateMap.begin(),
00406                         e = rst.StateMap.end();
00407                 for( ; b != e; ++ b ) {
00408                         ostringstream oss;
00409                         oss << hex << b->second.RecordId;
00410                         filler(buf, oss.str().c_str(), NULL, 0);
00411 
00412                         AddFile(oss.str());
00413                 }
00414                 return 0;
00415         }
00416 
00417         virtual void FillFileStat(const char *path, struct stat *st)
00418         {
00419                 // use the path to find the proper record
00420                 PathSplit ps(path);
00421 
00422                 string constructed = string("/") + ps.Pin() + "/" + ps.DB();
00423                 if( constructed != m_name ) {
00424                         // FIXME - this is shoddy error handling
00425                         throw std::logic_error("Constructed != name");
00426                 }
00427 
00428                 string data = GetRecordData(ps.Record());
00429 
00430                 st->st_mode = S_IFREG | 0444;
00431                 st->st_nlink = 1;
00432                 st->st_size = data.size();
00433         }
00434 
00435         virtual int ReadFile(const char *path, char *buf, size_t size, off_t offset)
00436         {
00437                 // use the path to find the proper record
00438                 PathSplit ps(path);
00439 
00440                 string constructed = string("/") + ps.Pin() + "/" + ps.DB();
00441                 if( constructed != m_name ) {
00442                         // FIXME - this is shoddy error handling
00443                         throw std::logic_error("Constructed != name");
00444                 }
00445 
00446                 string data = GetRecordData(ps.Record());
00447 
00448                 size_t len = data.size();
00449                 if( offset < len ) {
00450                         if( (offset + size) > len )
00451                                 size = len - offset;
00452                         memcpy(buf, data.data() + offset, size);
00453                 }
00454                 else {
00455                         size = 0;
00456                 }
00457                 return size;
00458         }
00459 
00460         const std::string& GetDBName() const { return m_pdb->Name; }
00461 
00462         std::string GetRecordData(const std::string &recordId)
00463         {
00464                 string data;
00465 
00466                 Barry::RecordStateTable rst;
00467                 m_desk.GetRecordStateTable(m_pdb->Number, rst);
00468 
00469                 uint32_t recid = strtoul(recordId.c_str(), NULL, 16);
00470                 RecordStateTable::IndexType index;
00471                 if( rst.GetIndex(recid, &index) ) {
00472                         ostringstream oss;
00473                         ParserPtr parser = GetParser(m_pdb->Name, oss, false);
00474                         m_desk.GetRecord(m_pdb->Number, index, *parser);
00475                         data = oss.str();
00476                 }
00477 
00478                 return data;
00479         }
00480 };
00481 
00482 class DesktopCon : public Directory
00483 {
00484 public:
00485         typedef std::tr1::shared_ptr<Database>                  DatabasePtr;
00486         typedef std::list<DatabasePtr>                          DBList;
00487 public:
00488         Barry::Controller m_con;
00489         Barry::Mode::Desktop m_desk;
00490         std::string m_pin;
00491         DBList m_dblist;
00492 
00493         DesktopCon(const Barry::ProbeResult &result, const std::string &pin)
00494                 : m_con(result)
00495                 , m_desk(m_con)
00496                 , m_pin(pin)
00497         {
00498                 // add to directory list
00499                 g_dirmap[ string("/") + pin ] = this;
00500         }
00501 
00502         ~DesktopCon()
00503         {
00504                 // remove from directory list
00505                 g_dirmap.erase( string("/") + m_pin );
00506         }
00507 
00508         virtual int ReadDir(void *buf, fuse_fill_dir_t filler)
00509         {
00510                 filler(buf, ".", NULL, 0);
00511                 filler(buf, "..", NULL, 0);
00512 
00513                 // list all databases in list
00514                 DBList::const_iterator b = m_dblist.begin(), e = m_dblist.end();
00515                 for( ; b != e; ++ b ) {
00516                         filler(buf, (*b)->GetDBName().c_str(), NULL, 0);
00517                 }
00518                 return 0;
00519         }
00520 
00521         void Open(const char *password = 0)
00522         {
00523                 // open our device
00524                 m_desk.Open(password);
00525 
00526                 // add all databases as directories
00527                 DatabaseDatabase::DatabaseArrayType::const_iterator
00528                         dbi = m_desk.GetDBDB().Databases.begin(),
00529                         dbe = m_desk.GetDBDB().Databases.end();
00530                 for( ; dbi != dbe; ++dbi ) {
00531                         DatabasePtr db = DatabasePtr(
00532                                 new Database(m_desk, m_pin, &(*dbi)) );
00533                         m_dblist.push_back(db);
00534                 }
00535         }
00536 };
00537 
00538 class Context : public Directory, public File
00539 {
00540 public:
00541         typedef std::auto_ptr<Barry::Probe>                     ProbePtr;
00542         typedef std::tr1::shared_ptr<DesktopCon>                DesktopConPtr;
00543         typedef std::string                                     PinT;
00544         typedef std::map<PinT, DesktopConPtr>                   PinMap;
00545 
00546         ProbePtr m_probe;
00547         PinMap m_pinmap;
00548 
00549         string m_error_log;
00550 
00551 public:
00552         Context()
00553         {
00554                 g_dirmap["/"] = this;
00555                 g_filemap[string("/") + error_log_filename] = this;
00556 
00557                 m_error_log = "Hello FUSE world.  This is Barry.  Pleased to meet you.\n";
00558         }
00559 
00560         ~Context()
00561         {
00562                 g_dirmap.erase("/");
00563                 g_filemap.erase(string("/") + error_log_filename);
00564         }
00565 
00566         virtual int ReadDir(void *buf, fuse_fill_dir_t filler)
00567         {
00568                 filler(buf, ".", NULL, 0);
00569                 filler(buf, "..", NULL, 0);
00570                 filler(buf, error_log_filename, NULL, 0);
00571 
00572                 // list all pins in map
00573                 PinMap::const_iterator b = m_pinmap.begin(), e = m_pinmap.end();
00574                 for( ; b != e; ++ b ) {
00575                         filler(buf, b->first.c_str(), NULL, 0);
00576                 }
00577                 return 0;
00578         }
00579 
00580         virtual void FillFileStat(const char *path, struct stat *st)
00581         {
00582                 st->st_mode = S_IFREG | 0444;
00583                 st->st_nlink = 1;
00584                 st->st_size = m_error_log.size();
00585         }
00586 
00587         virtual int ReadFile(const char *path, char *buf, size_t size, off_t offset)
00588         {
00589                 size_t len = m_error_log.size();
00590                 if( offset < len ) {
00591                         if( (offset + size) > len )
00592                                 size = len - offset;
00593                         memcpy(buf, m_error_log.data() + offset, size);
00594                 }
00595                 else {
00596                         size = 0;
00597                 }
00598                 return size;
00599         }
00600 
00601         void Log(const std::string &msg)
00602         {
00603                 m_error_log += msg;
00604                 m_error_log += "\n";
00605         }
00606 
00607         const std::string& GetLog() const { return m_error_log; }
00608 
00609         void ProbeAll()
00610         {
00611                 // probe the USB bus for Blackberry devices
00612                 m_probe.reset( new Probe );
00613 
00614                 // connect to all PINs found, and add them to our map
00615                 for( int i = 0; i < m_probe->GetCount(); i++ ) {
00616                         ostringstream oss;
00617                         oss << hex << m_probe->Get(i).m_pin;
00618 
00619                         if( !oss.str().size() || m_pinmap.find(oss.str()) != m_pinmap.end() ) {
00620                                 // don't add a blank or pre-existing pin
00621                                 continue;
00622                         }
00623 
00624                         DesktopConPtr dev = DesktopConPtr (
00625                                 new DesktopCon(m_probe->Get(i), oss.str()) );
00626                         dev->Open();
00627                         m_pinmap[ oss.str() ] = dev;
00628                 }
00629         }
00630 
00631         DesktopCon* FindPin(PinT pin)
00632         {
00633                 PinMap::iterator pi = m_pinmap.find(pin);
00634                 return pi == m_pinmap.end() ? 0 : pi->second.get();
00635         }
00636 };
00637 
00638 
00639 /////////////////////////////////////////////////////////////////////////////
00640 // FUSE API hooks
00641 
00642 static void* bfuse_init()
00643 {
00644         // Initialize the barry library.  Must be called before
00645         // anything else.
00646         Barry::Init(false);
00647 
00648         Context *ctx = 0;
00649 
00650         try {
00651                 ctx = new Context;
00652                 ctx->ProbeAll();
00653         }
00654         catch( std::exception &e ) {
00655                 if( ctx ) {
00656                         ctx->Log(e.what());
00657                 }
00658         }
00659 
00660         return ctx;
00661 }
00662 
00663 static void bfuse_destroy(void *data)
00664 {
00665         if( data ) {
00666                 Context *ctx = (Context*) data;
00667                 delete ctx;
00668         }
00669 }
00670 
00671 static int bfuse_getattr(const char *path, struct stat *st)
00672 {
00673         memset(st, 0, sizeof(*st));
00674 
00675         if( Directory *dir = FindDir(path) ) {
00676                 dir->FillDirStat(st);
00677                 return 0;
00678         }
00679         else if( File *file = FindFile(path) ) {
00680                 file->FillFileStat(path, st);
00681                 return 0;
00682         }
00683         else
00684                 return -ENOENT;
00685 }
00686 
00687 static int bfuse_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
00688                          off_t /*offset*/, struct fuse_file_info * /*fi*/)
00689 {
00690         Directory *dir = FindDir(path);
00691         if( !dir )
00692                 return -ENOENT;
00693         return dir->ReadDir(buf, filler);
00694 }
00695 
00696 static int bfuse_open(const char *path, struct fuse_file_info *fi)
00697 {
00698         File *file = FindFile(path);
00699         if( !file )
00700                 return -ENOENT;
00701 
00702         if( !file->AccessOk(fi->flags) )
00703                 return -EACCES;
00704 
00705         return 0;
00706 }
00707 
00708 static int bfuse_read(const char *path, char *buf, size_t size, off_t offset,
00709                       struct fuse_file_info *fi)
00710 {
00711         File *file = FindFile(path);
00712         if( !file )
00713                 return -ENOENT;
00714 
00715         return file->ReadFile(path, buf, size, offset);
00716 }
00717 
00718 // static struct here automatically zeros data
00719 static struct fuse_operations bfuse_oper;
00720 
00721 
00722 /////////////////////////////////////////////////////////////////////////////
00723 // main
00724 
00725 int main(int argc, char *argv[])
00726 {
00727         cout.sync_with_stdio(true);     // leave this on, since libusb uses
00728                                         // stdio for debug messages
00729 
00730         Blurb();
00731 
00732         // initialize the operation hooks
00733         bfuse_oper.init         = bfuse_init;
00734         bfuse_oper.destroy      = bfuse_destroy;
00735         bfuse_oper.getattr      = bfuse_getattr;
00736         bfuse_oper.readdir      = bfuse_readdir;
00737         bfuse_oper.open         = bfuse_open;
00738         bfuse_oper.read         = bfuse_read;
00739 
00740 /*
00741         // process command line options
00742         for(;;) {
00743                 int cmd = getopt(argc, argv, "d:hnp:P:");
00744                 if( cmd == -1 )
00745                         break;
00746 
00747                 switch( cmd )
00748                 {
00749                 case 'd':       // mount dbname
00750                         dbNames.push_back(string(optarg));
00751                         break;
00752 
00753                 case 'n':       // use null parser
00754                         null_parser = true;
00755                         break;
00756 
00757                 case 'p':       // Blackberry PIN
00758                         pin = strtoul(optarg, NULL, 16);
00759                         break;
00760 
00761                 case 'P':       // Device password
00762                         password = optarg;
00763                         break;
00764 
00765                 case 'h':       // help
00766                 default:
00767                         Usage();
00768                         return 0;
00769                 }
00770         }
00771 */
00772 
00773         return fuse_main(argc, argv, &bfuse_oper);
00774 }
00775 

Generated on Tue Jun 30 16:08:13 2009 for Barry by  doxygen 1.5.8