3 /// FUSE filesystem for Blackberry databases, using Barry.
7 Copyright (C) 2008-2010, Net Direct Inc. (http://www.netdirect.ca/)
9 This program is free software; you can redistribute it and/or modify
10 it under the terms of the GNU General Public License as published by
11 the Free Software Foundation; either version 2 of the License, or
12 (at your option) any later version.
14 This program is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
18 See the GNU General Public License in the COPYING file at the
19 root directory of this project for more details.
22 #define FUSE_USE_VERSION 25
26 #include <barry/barry.h>
36 #include <sys/types.h>
42 using namespace std::tr1
;
43 using namespace Barry
;
46 const char *error_log_filename
= "error.log";
48 // Global command line args
50 string cmdline_password
;
53 // Data from the command line
56 /////////////////////////////////////////////////////////////////////////////
57 // Command line option handling, through fuse
66 const char *Version
= Barry::Version(major
, minor
);
69 << "bfuse - FUSE filesystem for Blackberry databases\n"
70 << " Copyright 2008-2010, Net Direct Inc. (http://www.netdirect.ca/)\n"
71 << " Using: " << Version
<< "\n"
79 << "Barry specific options:\n"
80 << " -p pin PIN of device to talk with\n"
81 << " If only one device is plugged in, this flag is optional\n"
82 << " -P pass Simplistic method to specify device password\n"
85 << " -d db Specify which database to mount. If no -d options exist\n"
86 << " then all databases will be mounted.\n"
87 << " Can be used multiple times to mount more than one DB\n"
89 << " -n Use null parser on all databases.\n"
93 /////////////////////////////////////////////////////////////////////////////
94 // FUSE specific exception
96 class fuse_error
: public std::runtime_error
100 fuse_error(int errno_
, const std::string
&msg
)
101 : std::runtime_error(msg
), m_errno(errno_
)
104 int get_errno() const { return m_errno
; }
108 /////////////////////////////////////////////////////////////////////////////
109 // Barry record parsers
111 class DataDumpParser
: public Barry::Parser
117 explicit DataDumpParser(std::ostream
&os
)
122 virtual void Clear() {}
124 virtual void SetIds(uint8_t RecType
, uint32_t UniqueId
)
129 virtual void ParseHeader(const Barry::Data
&, size_t &) {}
131 virtual void ParseFields(const Barry::Data
&data
, size_t &offset
,
132 const Barry::IConverter
*ic
)
134 m_os
<< "Raw record dump for record: "
135 << std::hex
<< m_id
<< std::endl
;
136 m_os
<< data
<< std::endl
;
139 virtual void Store() {}
142 template <class Record
>
147 explicit Store(std::ostream
&os
)
153 void operator()(const Record
&rec
)
159 typedef std::auto_ptr
<Barry::Parser
> ParserPtr
;
161 ParserPtr
GetParser(const string
&name
, std::ostream
&os
, bool null_parser
)
165 return ParserPtr( new DataDumpParser(os
) );
167 // check for recognized database names
168 else if( name
== Contact::GetDBName() ) {
170 new RecordParser
<Contact
, Store
<Contact
> > (
171 new Store
<Contact
>(os
)));
173 else if( name
== Message::GetDBName() ) {
175 new RecordParser
<Message
, Store
<Message
> > (
176 new Store
<Message
>(os
)));
178 else if( name
== Calendar::GetDBName() ) {
180 new RecordParser
<Calendar
, Store
<Calendar
> > (
181 new Store
<Calendar
>(os
)));
183 else if( name
== CalendarAll::GetDBName() ) {
185 new RecordParser
<CalendarAll
, Store
<CalendarAll
> > (
186 new Store
<CalendarAll
>(os
)));
188 else if( name
== ServiceBook::GetDBName() ) {
190 new RecordParser
<ServiceBook
, Store
<ServiceBook
> > (
191 new Store
<ServiceBook
>(os
)));
194 else if( name
== Memo::GetDBName() ) {
196 new RecordParser
<Memo
, Store
<Memo
> > (
197 new Store
<Memo
>(os
)));
199 else if( name
== Task::GetDBName() ) {
201 new RecordParser
<Task
, Store
<Task
> > (
202 new Store
<Task
>(os
)));
204 else if( name
== PINMessage::GetDBName() ) {
206 new RecordParser
<PINMessage
, Store
<PINMessage
> > (
207 new Store
<PINMessage
>(os
)));
209 else if( name
== SavedMessage::GetDBName() ) {
211 new RecordParser
<SavedMessage
, Store
<SavedMessage
> > (
212 new Store
<SavedMessage
>(os
)));
214 else if( name
== Folder::GetDBName() ) {
216 new RecordParser
<Folder
, Store
<Folder
> > (
217 new Store
<Folder
>(os
)));
219 else if( name
== Timezone::GetDBName() ) {
221 new RecordParser
<Timezone
, Store
<Timezone
> > (
222 new Store
<Timezone
>(os
)));
225 // unknown database, use null parser
226 return ParserPtr( new DataDumpParser(os
) );
230 /////////////////////////////////////////////////////////////////////////////
235 std::string m_pin
, m_db
, m_record
, m_field
, m_remainder
;
237 int m_level
; // the number of slashes, minus the first
238 // i.e. first level is 0
242 explicit PathSplit(const char *path
)
247 return; // return in a failed state
249 if( *(path
+1) == 0 ) {
254 const char *s
= path
, *e
= path
;
256 while( *e
&& *e
!= '/' )
261 if( s
!= e
&& (s
+1) != e
) {
262 string
token(s
+1, e
);
266 case 0: // root level, should not have token here
268 return; // failed state
278 case 3: // have record
282 case 4: // have field
286 default: // too many, store remainder and done
287 m_remainder
= s
; // keeps slash
303 bool IsRoot() const { return m_is_root
; }
304 const std::string
& Pin() const { return m_pin
; }
305 const std::string
& DB() const { return m_db
; }
306 const std::string
& Record() const { return m_record
; }
307 const std::string
& Field() const { return m_field
; }
308 const std::string
& Remainder() const { return m_remainder
; }
309 int Level() const { return m_level
; }
313 /////////////////////////////////////////////////////////////////////////////
322 class Directory
: public Entry
325 virtual int ReadDir(void *buf
, fuse_fill_dir_t filler
) = 0;
326 virtual void FillDirStat(struct stat
*st
)
328 st
->st_mode
= S_IFDIR
| 0555;
333 class File
: public Entry
336 virtual void FillFileStat(const char *path
, struct stat
*st
) = 0;
337 virtual bool AccessOk(int flags
)
339 // default to readonly files
340 return (flags
& (O_RDONLY
| O_WRONLY
| O_RDWR
)) == O_RDONLY
;
342 virtual int ReadFile(const char *path
, char *buf
, size_t size
, off_t offset
) = 0;
345 typedef Directory
* DirectoryPtr
;
346 typedef File
* FilePtr
;
347 typedef std::string NameT
;
348 typedef std::map
<NameT
, DirectoryPtr
> DirMap
;
349 typedef std::map
<NameT
, FilePtr
> FileMap
;
351 static DirMap g_dirmap
;
352 static FileMap g_filemap
;
354 static Directory
* FindDir(const NameT
&name
)
356 DirMap::iterator di
= g_dirmap
.find(name
);
357 return di
== g_dirmap
.end() ? 0 : di
->second
;
360 static File
* FindFile(const NameT
&name
)
362 FileMap::iterator fi
= g_filemap
.find(name
);
363 return fi
== g_filemap
.end() ? 0 : fi
->second
;
366 /////////////////////////////////////////////////////////////////////////////
369 class Database
: public Directory
, public File
372 Barry::Mode::Desktop
&m_desk
;
374 const Barry::DatabaseItem
*m_pdb
;
377 Database(Barry::Mode::Desktop
&desktop
,
378 const std::string
&pin
, const Barry::DatabaseItem
*pdb
)
382 m_name
= string("/") + pin
+ "/" + m_pdb
->Name
;
384 // add to directory list
385 g_dirmap
[ m_name
] = this;
390 // remove any entries that point to us
391 FileMap::iterator b
= g_filemap
.begin(), e
= g_filemap
.end();
392 for( ; b
!= e
; ++b
) {
393 if( b
->second
== this ) {
398 // erase ourselves from the directory list
399 g_dirmap
.erase( m_name
);
402 void AddFile(const std::string
&recordId
)
404 // FIXME - this is a hack to redirect all record files
405 // to this database class... next step is to possibly
406 // split out records into field files if we have a
407 // parser, or just dump the hex if we don't
408 string name
= m_name
+ "/" + recordId
;
409 g_filemap
[ name
] = this;
412 virtual int ReadDir(void *buf
, fuse_fill_dir_t filler
)
414 filler(buf
, ".", NULL
, 0);
415 filler(buf
, "..", NULL
, 0);
417 // list all records in database, by recordId
418 Barry::RecordStateTable rst
;
419 m_desk
.GetRecordStateTable(m_pdb
->Number
, rst
);
421 Barry::RecordStateTable::StateMapType::iterator
422 b
= rst
.StateMap
.begin(),
423 e
= rst
.StateMap
.end();
424 for( ; b
!= e
; ++ b
) {
426 oss
<< hex
<< b
->second
.RecordId
;
427 filler(buf
, oss
.str().c_str(), NULL
, 0);
434 virtual void FillFileStat(const char *path
, struct stat
*st
)
436 // use the path to find the proper record
439 string constructed
= string("/") + ps
.Pin() + "/" + ps
.DB();
440 if( constructed
!= m_name
) {
441 // FIXME - this is shoddy error handling
442 throw std::logic_error("Constructed != name");
445 string data
= GetRecordData(ps
.Record());
447 st
->st_mode
= S_IFREG
| 0444;
449 st
->st_size
= data
.size();
452 virtual int ReadFile(const char *path
, char *buf
, size_t size
, off_t offset
)
454 // use the path to find the proper record
457 string constructed
= string("/") + ps
.Pin() + "/" + ps
.DB();
458 if( constructed
!= m_name
) {
459 // FIXME - this is shoddy error handling
460 throw std::logic_error("Constructed != name");
463 string data
= GetRecordData(ps
.Record());
465 size_t len
= data
.size();
467 if( (offset
+ size
) > len
)
469 memcpy(buf
, data
.data() + offset
, size
);
477 const std::string
& GetDBName() const { return m_pdb
->Name
; }
479 std::string
GetRecordData(const std::string
&recordId
)
483 Barry::RecordStateTable rst
;
484 m_desk
.GetRecordStateTable(m_pdb
->Number
, rst
);
486 uint32_t recid
= strtoul(recordId
.c_str(), NULL
, 16);
487 RecordStateTable::IndexType index
;
488 if( rst
.GetIndex(recid
, &index
) ) {
490 ParserPtr parser
= GetParser(m_pdb
->Name
, oss
, false);
491 m_desk
.GetRecord(m_pdb
->Number
, index
, *parser
);
499 class DesktopCon
: public Directory
502 typedef std::tr1::shared_ptr
<Database
> DatabasePtr
;
503 typedef std::list
<DatabasePtr
> DBList
;
505 Barry::Controller m_con
;
506 Barry::Mode::Desktop m_desk
;
510 DesktopCon(const Barry::ProbeResult
&result
, const std::string
&pin
)
515 // add to directory list
516 g_dirmap
[ string("/") + pin
] = this;
521 // remove from directory list
522 g_dirmap
.erase( string("/") + m_pin
);
525 virtual int ReadDir(void *buf
, fuse_fill_dir_t filler
)
527 filler(buf
, ".", NULL
, 0);
528 filler(buf
, "..", NULL
, 0);
530 // list all databases in list
531 DBList::const_iterator b
= m_dblist
.begin(), e
= m_dblist
.end();
532 for( ; b
!= e
; ++ b
) {
533 filler(buf
, (*b
)->GetDBName().c_str(), NULL
, 0);
538 void Open(const char *password
= 0)
541 m_desk
.Open(password
);
543 // add all databases as directories
544 DatabaseDatabase::DatabaseArrayType::const_iterator
545 dbi
= m_desk
.GetDBDB().Databases
.begin(),
546 dbe
= m_desk
.GetDBDB().Databases
.end();
547 for( ; dbi
!= dbe
; ++dbi
) {
548 DatabasePtr db
= DatabasePtr(
549 new Database(m_desk
, m_pin
, &(*dbi
)) );
550 m_dblist
.push_back(db
);
555 class Context
: public Directory
, public File
558 typedef std::auto_ptr
<Barry::Probe
> ProbePtr
;
559 typedef std::tr1::shared_ptr
<DesktopCon
> DesktopConPtr
;
560 typedef std::string PinT
;
561 typedef std::map
<PinT
, DesktopConPtr
> PinMap
;
568 string m_limit_pin
; // only mount device with this pin
569 string m_password
; // use this password when connecting
572 Context(const string
&limit_pin
= "", const string
&password
= "")
573 : m_limit_pin(limit_pin
)
574 , m_password(password
)
576 g_dirmap
["/"] = this;
577 g_filemap
[string("/") + error_log_filename
] = this;
579 m_error_log
= "Hello FUSE world. This is Barry. Pleased to meet you.\n";
585 g_filemap
.erase(string("/") + error_log_filename
);
588 virtual int ReadDir(void *buf
, fuse_fill_dir_t filler
)
590 filler(buf
, ".", NULL
, 0);
591 filler(buf
, "..", NULL
, 0);
592 filler(buf
, error_log_filename
, NULL
, 0);
594 // list all pins in map
595 PinMap::const_iterator b
= m_pinmap
.begin(), e
= m_pinmap
.end();
596 for( ; b
!= e
; ++ b
) {
597 filler(buf
, b
->first
.c_str(), NULL
, 0);
602 virtual void FillFileStat(const char *path
, struct stat
*st
)
604 st
->st_mode
= S_IFREG
| 0444;
606 st
->st_size
= m_error_log
.size();
609 virtual int ReadFile(const char *path
, char *buf
, size_t size
, off_t offset
)
611 size_t len
= m_error_log
.size();
613 if( (offset
+ size
) > len
)
615 memcpy(buf
, m_error_log
.data() + offset
, size
);
623 void Log(const std::string
&msg
)
629 const std::string
& GetLog() const { return m_error_log
; }
633 // probe the USB bus for Blackberry devices
634 m_probe
.reset( new Probe
);
636 // connect to all PINs found, and add them to our map
637 for( int i
= 0; i
< m_probe
->GetCount(); i
++ ) {
638 string curpin
= m_probe
->Get(i
).m_pin
.str();
640 // don't add a blank or pre-existing pin
641 if( !curpin
.size() || m_pinmap
.find(curpin
) != m_pinmap
.end() ) {
645 // don't add non-PIN device if pin specified
646 if( m_limit_pin
.size() && curpin
!= m_limit_pin
) {
650 DesktopConPtr dev
= DesktopConPtr (
651 new DesktopCon(m_probe
->Get(i
), curpin
) );
652 dev
->Open(m_password
.c_str());
653 m_pinmap
[ curpin
] = dev
;
657 DesktopCon
* FindPin(PinT pin
)
659 PinMap::iterator pi
= m_pinmap
.find(pin
);
660 return pi
== m_pinmap
.end() ? 0 : pi
->second
.get();
665 /////////////////////////////////////////////////////////////////////////////
668 static void* bfuse_init()
670 // Initialize the barry library. Must be called before
677 ctx
= new Context(cmdline_pin
, cmdline_password
);
680 catch( std::exception
&e
) {
689 static void bfuse_destroy(void *data
)
692 Context
*ctx
= (Context
*) data
;
697 static int bfuse_getattr(const char *path
, struct stat
*st
)
699 memset(st
, 0, sizeof(*st
));
701 if( Directory
*dir
= FindDir(path
) ) {
702 dir
->FillDirStat(st
);
705 else if( File
*file
= FindFile(path
) ) {
706 file
->FillFileStat(path
, st
);
713 static int bfuse_readdir(const char *path
, void *buf
, fuse_fill_dir_t filler
,
714 off_t
/*offset*/, struct fuse_file_info
* /*fi*/)
716 Directory
*dir
= FindDir(path
);
719 return dir
->ReadDir(buf
, filler
);
722 static int bfuse_open(const char *path
, struct fuse_file_info
*fi
)
724 File
*file
= FindFile(path
);
728 if( !file
->AccessOk(fi
->flags
) )
734 static int bfuse_read(const char *path
, char *buf
, size_t size
, off_t offset
,
735 struct fuse_file_info
*fi
)
737 File
*file
= FindFile(path
);
741 return file
->ReadFile(path
, buf
, size
, offset
);
744 // static struct here automatically zeros data
745 static struct fuse_operations bfuse_oper
;
748 /////////////////////////////////////////////////////////////////////////////
751 int main(int argc
, char *argv
[])
755 cout
.sync_with_stdio(true); // leave this on, since libusb uses
756 // stdio for debug messages
760 // initialize the operation hooks
761 bfuse_oper
.init
= bfuse_init
;
762 bfuse_oper
.destroy
= bfuse_destroy
;
763 bfuse_oper
.getattr
= bfuse_getattr
;
764 bfuse_oper
.readdir
= bfuse_readdir
;
765 bfuse_oper
.open
= bfuse_open
;
766 bfuse_oper
.read
= bfuse_read
;
768 // process command line options before FUSE does
769 // FUSE does its own command line processing, and
770 // doesn't seem to have a way to plug into it,
771 // so do our own first
773 char **fuse_argv
= new char*[argc
];
775 for( int i
= 0; i
< argc
; i
++ ) {
776 if( argv
[i
][0] == '-' ) {
780 // case 'd': // mount dbname
781 // dbNames.push_back(string(optarg));
784 // case 'n': // use null parser
785 // null_parser = true;
788 case 'p': // Blackberry PIN
790 cmdline_pin
= argv
[++i
];
794 case 'P': // Device password
796 cmdline_password
= argv
[++i
];
806 // if we get here, add this option to FUSE's
807 fuse_argv
[fuse_argc
] = argv
[i
];
811 int ret
= fuse_main(fuse_argc
, fuse_argv
, &bfuse_oper
);