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(const std::string
&DbName
,
125 uint8_t RecType
, uint32_t UniqueId
)
130 virtual void ParseHeader(const Barry::Data
&, size_t &) {}
132 virtual void ParseFields(const Barry::Data
&data
, size_t &offset
,
133 const Barry::IConverter
*ic
)
135 m_os
<< "Raw record dump for record: "
136 << std::hex
<< m_id
<< std::endl
;
137 m_os
<< data
<< std::endl
;
140 virtual void Store() {}
143 template <class Record
>
148 explicit Store(std::ostream
&os
)
154 void operator()(const Record
&rec
)
160 typedef std::auto_ptr
<Barry::Parser
> ParserPtr
;
162 ParserPtr
GetParser(const string
&name
, std::ostream
&os
, bool null_parser
)
166 return ParserPtr( new DataDumpParser(os
) );
168 // check for recognized database names
169 else if( name
== Contact::GetDBName() ) {
171 new RecordParser
<Contact
, Store
<Contact
> > (
172 new Store
<Contact
>(os
)));
174 else if( name
== Message::GetDBName() ) {
176 new RecordParser
<Message
, Store
<Message
> > (
177 new Store
<Message
>(os
)));
179 else if( name
== Calendar::GetDBName() ) {
181 new RecordParser
<Calendar
, Store
<Calendar
> > (
182 new Store
<Calendar
>(os
)));
184 else if( name
== CalendarAll::GetDBName() ) {
186 new RecordParser
<CalendarAll
, Store
<CalendarAll
> > (
187 new Store
<CalendarAll
>(os
)));
189 else if( name
== ServiceBook::GetDBName() ) {
191 new RecordParser
<ServiceBook
, Store
<ServiceBook
> > (
192 new Store
<ServiceBook
>(os
)));
195 else if( name
== Memo::GetDBName() ) {
197 new RecordParser
<Memo
, Store
<Memo
> > (
198 new Store
<Memo
>(os
)));
200 else if( name
== Task::GetDBName() ) {
202 new RecordParser
<Task
, Store
<Task
> > (
203 new Store
<Task
>(os
)));
205 else if( name
== PINMessage::GetDBName() ) {
207 new RecordParser
<PINMessage
, Store
<PINMessage
> > (
208 new Store
<PINMessage
>(os
)));
210 else if( name
== SavedMessage::GetDBName() ) {
212 new RecordParser
<SavedMessage
, Store
<SavedMessage
> > (
213 new Store
<SavedMessage
>(os
)));
215 else if( name
== Folder::GetDBName() ) {
217 new RecordParser
<Folder
, Store
<Folder
> > (
218 new Store
<Folder
>(os
)));
220 else if( name
== Timezone::GetDBName() ) {
222 new RecordParser
<Timezone
, Store
<Timezone
> > (
223 new Store
<Timezone
>(os
)));
226 // unknown database, use null parser
227 return ParserPtr( new DataDumpParser(os
) );
231 /////////////////////////////////////////////////////////////////////////////
236 std::string m_pin
, m_db
, m_record
, m_field
, m_remainder
;
238 int m_level
; // the number of slashes, minus the first
239 // i.e. first level is 0
243 explicit PathSplit(const char *path
)
248 return; // return in a failed state
250 if( *(path
+1) == 0 ) {
255 const char *s
= path
, *e
= path
;
257 while( *e
&& *e
!= '/' )
262 if( s
!= e
&& (s
+1) != e
) {
263 string
token(s
+1, e
);
267 case 0: // root level, should not have token here
269 return; // failed state
279 case 3: // have record
283 case 4: // have field
287 default: // too many, store remainder and done
288 m_remainder
= s
; // keeps slash
304 bool IsRoot() const { return m_is_root
; }
305 const std::string
& Pin() const { return m_pin
; }
306 const std::string
& DB() const { return m_db
; }
307 const std::string
& Record() const { return m_record
; }
308 const std::string
& Field() const { return m_field
; }
309 const std::string
& Remainder() const { return m_remainder
; }
310 int Level() const { return m_level
; }
314 /////////////////////////////////////////////////////////////////////////////
323 class Directory
: public Entry
326 virtual int ReadDir(void *buf
, fuse_fill_dir_t filler
) = 0;
327 virtual void FillDirStat(struct stat
*st
)
329 st
->st_mode
= S_IFDIR
| 0555;
334 class File
: public Entry
337 virtual void FillFileStat(const char *path
, struct stat
*st
) = 0;
338 virtual bool AccessOk(int flags
)
340 // default to readonly files
341 return (flags
& (O_RDONLY
| O_WRONLY
| O_RDWR
)) == O_RDONLY
;
343 virtual int ReadFile(const char *path
, char *buf
, size_t size
, off_t offset
) = 0;
346 typedef Directory
* DirectoryPtr
;
347 typedef File
* FilePtr
;
348 typedef std::string NameT
;
349 typedef std::map
<NameT
, DirectoryPtr
> DirMap
;
350 typedef std::map
<NameT
, FilePtr
> FileMap
;
352 static DirMap g_dirmap
;
353 static FileMap g_filemap
;
355 static Directory
* FindDir(const NameT
&name
)
357 DirMap::iterator di
= g_dirmap
.find(name
);
358 return di
== g_dirmap
.end() ? 0 : di
->second
;
361 static File
* FindFile(const NameT
&name
)
363 FileMap::iterator fi
= g_filemap
.find(name
);
364 return fi
== g_filemap
.end() ? 0 : fi
->second
;
367 /////////////////////////////////////////////////////////////////////////////
370 class Database
: public Directory
, public File
373 Barry::Mode::Desktop
&m_desk
;
375 const Barry::DatabaseItem
*m_pdb
;
378 Database(Barry::Mode::Desktop
&desktop
,
379 const std::string
&pin
, const Barry::DatabaseItem
*pdb
)
383 m_name
= string("/") + pin
+ "/" + m_pdb
->Name
;
385 // add to directory list
386 g_dirmap
[ m_name
] = this;
391 // remove any entries that point to us
392 FileMap::iterator b
= g_filemap
.begin(), e
= g_filemap
.end();
393 for( ; b
!= e
; ++b
) {
394 if( b
->second
== this ) {
399 // erase ourselves from the directory list
400 g_dirmap
.erase( m_name
);
403 void AddFile(const std::string
&recordId
)
405 // FIXME - this is a hack to redirect all record files
406 // to this database class... next step is to possibly
407 // split out records into field files if we have a
408 // parser, or just dump the hex if we don't
409 string name
= m_name
+ "/" + recordId
;
410 g_filemap
[ name
] = this;
413 virtual int ReadDir(void *buf
, fuse_fill_dir_t filler
)
415 filler(buf
, ".", NULL
, 0);
416 filler(buf
, "..", NULL
, 0);
418 // list all records in database, by recordId
419 Barry::RecordStateTable rst
;
420 m_desk
.GetRecordStateTable(m_pdb
->Number
, rst
);
422 Barry::RecordStateTable::StateMapType::iterator
423 b
= rst
.StateMap
.begin(),
424 e
= rst
.StateMap
.end();
425 for( ; b
!= e
; ++ b
) {
427 oss
<< hex
<< b
->second
.RecordId
;
428 filler(buf
, oss
.str().c_str(), NULL
, 0);
435 virtual void FillFileStat(const char *path
, struct stat
*st
)
437 // use the path to find the proper record
440 string constructed
= string("/") + ps
.Pin() + "/" + ps
.DB();
441 if( constructed
!= m_name
) {
442 // FIXME - this is shoddy error handling
443 throw std::logic_error("Constructed != name");
446 string data
= GetRecordData(ps
.Record());
448 st
->st_mode
= S_IFREG
| 0444;
450 st
->st_size
= data
.size();
453 virtual int ReadFile(const char *path
, char *buf
, size_t size
, off_t offset
)
455 // use the path to find the proper record
458 string constructed
= string("/") + ps
.Pin() + "/" + ps
.DB();
459 if( constructed
!= m_name
) {
460 // FIXME - this is shoddy error handling
461 throw std::logic_error("Constructed != name");
464 string data
= GetRecordData(ps
.Record());
466 size_t len
= data
.size();
468 if( (offset
+ size
) > len
)
470 memcpy(buf
, data
.data() + offset
, size
);
478 const std::string
& GetDBName() const { return m_pdb
->Name
; }
480 std::string
GetRecordData(const std::string
&recordId
)
484 Barry::RecordStateTable rst
;
485 m_desk
.GetRecordStateTable(m_pdb
->Number
, rst
);
487 uint32_t recid
= strtoul(recordId
.c_str(), NULL
, 16);
488 RecordStateTable::IndexType index
;
489 if( rst
.GetIndex(recid
, &index
) ) {
491 ParserPtr parser
= GetParser(m_pdb
->Name
, oss
, false);
492 m_desk
.GetRecord(m_pdb
->Number
, index
, *parser
);
500 class DesktopCon
: public Directory
503 typedef std::tr1::shared_ptr
<Database
> DatabasePtr
;
504 typedef std::list
<DatabasePtr
> DBList
;
506 Barry::Controller m_con
;
507 Barry::Mode::Desktop m_desk
;
511 DesktopCon(const Barry::ProbeResult
&result
, const std::string
&pin
)
516 // add to directory list
517 g_dirmap
[ string("/") + pin
] = this;
522 // remove from directory list
523 g_dirmap
.erase( string("/") + m_pin
);
526 virtual int ReadDir(void *buf
, fuse_fill_dir_t filler
)
528 filler(buf
, ".", NULL
, 0);
529 filler(buf
, "..", NULL
, 0);
531 // list all databases in list
532 DBList::const_iterator b
= m_dblist
.begin(), e
= m_dblist
.end();
533 for( ; b
!= e
; ++ b
) {
534 filler(buf
, (*b
)->GetDBName().c_str(), NULL
, 0);
539 void Open(const char *password
= 0)
542 m_desk
.Open(password
);
544 // add all databases as directories
545 DatabaseDatabase::DatabaseArrayType::const_iterator
546 dbi
= m_desk
.GetDBDB().Databases
.begin(),
547 dbe
= m_desk
.GetDBDB().Databases
.end();
548 for( ; dbi
!= dbe
; ++dbi
) {
549 DatabasePtr db
= DatabasePtr(
550 new Database(m_desk
, m_pin
, &(*dbi
)) );
551 m_dblist
.push_back(db
);
556 class Context
: public Directory
, public File
559 typedef std::auto_ptr
<Barry::Probe
> ProbePtr
;
560 typedef std::tr1::shared_ptr
<DesktopCon
> DesktopConPtr
;
561 typedef std::string PinT
;
562 typedef std::map
<PinT
, DesktopConPtr
> PinMap
;
569 string m_limit_pin
; // only mount device with this pin
570 string m_password
; // use this password when connecting
573 Context(const string
&limit_pin
= "", const string
&password
= "")
574 : m_limit_pin(limit_pin
)
575 , m_password(password
)
577 g_dirmap
["/"] = this;
578 g_filemap
[string("/") + error_log_filename
] = this;
580 m_error_log
= "Hello FUSE world. This is Barry. Pleased to meet you.\n";
586 g_filemap
.erase(string("/") + error_log_filename
);
589 virtual int ReadDir(void *buf
, fuse_fill_dir_t filler
)
591 filler(buf
, ".", NULL
, 0);
592 filler(buf
, "..", NULL
, 0);
593 filler(buf
, error_log_filename
, NULL
, 0);
595 // list all pins in map
596 PinMap::const_iterator b
= m_pinmap
.begin(), e
= m_pinmap
.end();
597 for( ; b
!= e
; ++ b
) {
598 filler(buf
, b
->first
.c_str(), NULL
, 0);
603 virtual void FillFileStat(const char *path
, struct stat
*st
)
605 st
->st_mode
= S_IFREG
| 0444;
607 st
->st_size
= m_error_log
.size();
610 virtual int ReadFile(const char *path
, char *buf
, size_t size
, off_t offset
)
612 size_t len
= m_error_log
.size();
614 if( (offset
+ size
) > len
)
616 memcpy(buf
, m_error_log
.data() + offset
, size
);
624 void Log(const std::string
&msg
)
630 const std::string
& GetLog() const { return m_error_log
; }
634 // probe the USB bus for Blackberry devices
635 m_probe
.reset( new Probe
);
637 // connect to all PINs found, and add them to our map
638 for( int i
= 0; i
< m_probe
->GetCount(); i
++ ) {
639 string curpin
= m_probe
->Get(i
).m_pin
.str();
641 // don't add a blank or pre-existing pin
642 if( !curpin
.size() || m_pinmap
.find(curpin
) != m_pinmap
.end() ) {
646 // don't add non-PIN device if pin specified
647 if( m_limit_pin
.size() && curpin
!= m_limit_pin
) {
651 DesktopConPtr dev
= DesktopConPtr (
652 new DesktopCon(m_probe
->Get(i
), curpin
) );
653 dev
->Open(m_password
.c_str());
654 m_pinmap
[ curpin
] = dev
;
658 DesktopCon
* FindPin(PinT pin
)
660 PinMap::iterator pi
= m_pinmap
.find(pin
);
661 return pi
== m_pinmap
.end() ? 0 : pi
->second
.get();
666 /////////////////////////////////////////////////////////////////////////////
669 static void* bfuse_init()
671 // Initialize the barry library. Must be called before
678 ctx
= new Context(cmdline_pin
, cmdline_password
);
681 catch( std::exception
&e
) {
690 static void bfuse_destroy(void *data
)
693 Context
*ctx
= (Context
*) data
;
698 static int bfuse_getattr(const char *path
, struct stat
*st
)
700 memset(st
, 0, sizeof(*st
));
702 if( Directory
*dir
= FindDir(path
) ) {
703 dir
->FillDirStat(st
);
706 else if( File
*file
= FindFile(path
) ) {
707 file
->FillFileStat(path
, st
);
714 static int bfuse_readdir(const char *path
, void *buf
, fuse_fill_dir_t filler
,
715 off_t
/*offset*/, struct fuse_file_info
* /*fi*/)
717 Directory
*dir
= FindDir(path
);
720 return dir
->ReadDir(buf
, filler
);
723 static int bfuse_open(const char *path
, struct fuse_file_info
*fi
)
725 File
*file
= FindFile(path
);
729 if( !file
->AccessOk(fi
->flags
) )
735 static int bfuse_read(const char *path
, char *buf
, size_t size
, off_t offset
,
736 struct fuse_file_info
*fi
)
738 File
*file
= FindFile(path
);
742 return file
->ReadFile(path
, buf
, size
, offset
);
745 // static struct here automatically zeros data
746 static struct fuse_operations bfuse_oper
;
749 /////////////////////////////////////////////////////////////////////////////
752 int main(int argc
, char *argv
[])
756 cout
.sync_with_stdio(true); // leave this on, since libusb uses
757 // stdio for debug messages
761 // initialize the operation hooks
762 bfuse_oper
.init
= bfuse_init
;
763 bfuse_oper
.destroy
= bfuse_destroy
;
764 bfuse_oper
.getattr
= bfuse_getattr
;
765 bfuse_oper
.readdir
= bfuse_readdir
;
766 bfuse_oper
.open
= bfuse_open
;
767 bfuse_oper
.read
= bfuse_read
;
769 // process command line options before FUSE does
770 // FUSE does its own command line processing, and
771 // doesn't seem to have a way to plug into it,
772 // so do our own first
774 char **fuse_argv
= new char*[argc
];
776 for( int i
= 0; i
< argc
; i
++ ) {
777 if( argv
[i
][0] == '-' ) {
781 // case 'd': // mount dbname
782 // dbNames.push_back(string(optarg));
785 // case 'n': // use null parser
786 // null_parser = true;
789 case 'p': // Blackberry PIN
791 cmdline_pin
= argv
[++i
];
795 case 'P': // Device password
797 cmdline_password
= argv
[++i
];
807 // if we get here, add this option to FUSE's
808 fuse_argv
[fuse_argc
] = argv
[i
];
812 int ret
= fuse_main(fuse_argc
, fuse_argv
, &bfuse_oper
);