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 ParseRecord(const Barry::DBData
&data
,
123 const Barry::IConverter
*ic
)
125 m_id
= data
.GetUniqueId();
126 m_os
<< "Raw record dump for record: "
127 << std::hex
<< m_id
<< std::endl
;
128 m_os
<< data
.GetData() << std::endl
;
132 template <class Record
>
137 explicit Store(std::ostream
&os
)
143 void operator()(const Record
&rec
)
149 typedef std::auto_ptr
<Barry::Parser
> ParserPtr
;
151 ParserPtr
GetParser(const string
&name
, std::ostream
&os
, bool null_parser
)
155 return ParserPtr( new DataDumpParser(os
) );
157 // check for recognized database names
158 else if( name
== Contact::GetDBName() ) {
160 new RecordParser
<Contact
, Store
<Contact
> > (
161 new Store
<Contact
>(os
)));
163 else if( name
== Message::GetDBName() ) {
165 new RecordParser
<Message
, Store
<Message
> > (
166 new Store
<Message
>(os
)));
168 else if( name
== Calendar::GetDBName() ) {
170 new RecordParser
<Calendar
, Store
<Calendar
> > (
171 new Store
<Calendar
>(os
)));
173 else if( name
== CalendarAll::GetDBName() ) {
175 new RecordParser
<CalendarAll
, Store
<CalendarAll
> > (
176 new Store
<CalendarAll
>(os
)));
178 else if( name
== ServiceBook::GetDBName() ) {
180 new RecordParser
<ServiceBook
, Store
<ServiceBook
> > (
181 new Store
<ServiceBook
>(os
)));
184 else if( name
== Memo::GetDBName() ) {
186 new RecordParser
<Memo
, Store
<Memo
> > (
187 new Store
<Memo
>(os
)));
189 else if( name
== Task::GetDBName() ) {
191 new RecordParser
<Task
, Store
<Task
> > (
192 new Store
<Task
>(os
)));
194 else if( name
== PINMessage::GetDBName() ) {
196 new RecordParser
<PINMessage
, Store
<PINMessage
> > (
197 new Store
<PINMessage
>(os
)));
199 else if( name
== SavedMessage::GetDBName() ) {
201 new RecordParser
<SavedMessage
, Store
<SavedMessage
> > (
202 new Store
<SavedMessage
>(os
)));
204 else if( name
== Folder::GetDBName() ) {
206 new RecordParser
<Folder
, Store
<Folder
> > (
207 new Store
<Folder
>(os
)));
209 else if( name
== Timezone::GetDBName() ) {
211 new RecordParser
<Timezone
, Store
<Timezone
> > (
212 new Store
<Timezone
>(os
)));
215 // unknown database, use null parser
216 return ParserPtr( new DataDumpParser(os
) );
220 /////////////////////////////////////////////////////////////////////////////
225 std::string m_pin
, m_db
, m_record
, m_field
, m_remainder
;
227 int m_level
; // the number of slashes, minus the first
228 // i.e. first level is 0
232 explicit PathSplit(const char *path
)
237 return; // return in a failed state
239 if( *(path
+1) == 0 ) {
244 const char *s
= path
, *e
= path
;
246 while( *e
&& *e
!= '/' )
251 if( s
!= e
&& (s
+1) != e
) {
252 string
token(s
+1, e
);
256 case 0: // root level, should not have token here
258 return; // failed state
268 case 3: // have record
272 case 4: // have field
276 default: // too many, store remainder and done
277 m_remainder
= s
; // keeps slash
293 bool IsRoot() const { return m_is_root
; }
294 const std::string
& Pin() const { return m_pin
; }
295 const std::string
& DB() const { return m_db
; }
296 const std::string
& Record() const { return m_record
; }
297 const std::string
& Field() const { return m_field
; }
298 const std::string
& Remainder() const { return m_remainder
; }
299 int Level() const { return m_level
; }
303 /////////////////////////////////////////////////////////////////////////////
312 class Directory
: public Entry
315 virtual int ReadDir(void *buf
, fuse_fill_dir_t filler
) = 0;
316 virtual void FillDirStat(struct stat
*st
)
318 st
->st_mode
= S_IFDIR
| 0555;
323 class File
: public Entry
326 virtual void FillFileStat(const char *path
, struct stat
*st
) = 0;
327 virtual bool AccessOk(int flags
)
329 // default to readonly files
330 return (flags
& (O_RDONLY
| O_WRONLY
| O_RDWR
)) == O_RDONLY
;
332 virtual int ReadFile(const char *path
, char *buf
, size_t size
, off_t offset
) = 0;
335 typedef Directory
* DirectoryPtr
;
336 typedef File
* FilePtr
;
337 typedef std::string NameT
;
338 typedef std::map
<NameT
, DirectoryPtr
> DirMap
;
339 typedef std::map
<NameT
, FilePtr
> FileMap
;
341 static DirMap g_dirmap
;
342 static FileMap g_filemap
;
344 static Directory
* FindDir(const NameT
&name
)
346 DirMap::iterator di
= g_dirmap
.find(name
);
347 return di
== g_dirmap
.end() ? 0 : di
->second
;
350 static File
* FindFile(const NameT
&name
)
352 FileMap::iterator fi
= g_filemap
.find(name
);
353 return fi
== g_filemap
.end() ? 0 : fi
->second
;
356 /////////////////////////////////////////////////////////////////////////////
359 class Database
: public Directory
, public File
362 Barry::Mode::Desktop
&m_desk
;
364 const Barry::DatabaseItem
*m_pdb
;
367 Database(Barry::Mode::Desktop
&desktop
,
368 const std::string
&pin
, const Barry::DatabaseItem
*pdb
)
372 m_name
= string("/") + pin
+ "/" + m_pdb
->Name
;
374 // add to directory list
375 g_dirmap
[ m_name
] = this;
380 // remove any entries that point to us
381 FileMap::iterator b
= g_filemap
.begin(), e
= g_filemap
.end();
382 for( ; b
!= e
; ++b
) {
383 if( b
->second
== this ) {
388 // erase ourselves from the directory list
389 g_dirmap
.erase( m_name
);
392 void AddFile(const std::string
&recordId
)
394 // FIXME - this is a hack to redirect all record files
395 // to this database class... next step is to possibly
396 // split out records into field files if we have a
397 // parser, or just dump the hex if we don't
398 string name
= m_name
+ "/" + recordId
;
399 g_filemap
[ name
] = this;
402 virtual int ReadDir(void *buf
, fuse_fill_dir_t filler
)
404 filler(buf
, ".", NULL
, 0);
405 filler(buf
, "..", NULL
, 0);
407 // list all records in database, by recordId
408 Barry::RecordStateTable rst
;
409 m_desk
.GetRecordStateTable(m_pdb
->Number
, rst
);
411 Barry::RecordStateTable::StateMapType::iterator
412 b
= rst
.StateMap
.begin(),
413 e
= rst
.StateMap
.end();
414 for( ; b
!= e
; ++ b
) {
416 oss
<< hex
<< b
->second
.RecordId
;
417 filler(buf
, oss
.str().c_str(), NULL
, 0);
424 virtual void FillFileStat(const char *path
, struct stat
*st
)
426 // use the path to find the proper record
429 string constructed
= string("/") + ps
.Pin() + "/" + ps
.DB();
430 if( constructed
!= m_name
) {
431 // FIXME - this is shoddy error handling
432 throw std::logic_error("Constructed != name");
435 string data
= GetRecordData(ps
.Record());
437 st
->st_mode
= S_IFREG
| 0444;
439 st
->st_size
= data
.size();
442 virtual int ReadFile(const char *path
, char *buf
, size_t size
, off_t offset
)
444 // use the path to find the proper record
447 string constructed
= string("/") + ps
.Pin() + "/" + ps
.DB();
448 if( constructed
!= m_name
) {
449 // FIXME - this is shoddy error handling
450 throw std::logic_error("Constructed != name");
453 string data
= GetRecordData(ps
.Record());
455 size_t len
= data
.size();
457 if( (offset
+ size
) > len
)
459 memcpy(buf
, data
.data() + offset
, size
);
467 const std::string
& GetDBName() const { return m_pdb
->Name
; }
469 std::string
GetRecordData(const std::string
&recordId
)
473 Barry::RecordStateTable rst
;
474 m_desk
.GetRecordStateTable(m_pdb
->Number
, rst
);
476 uint32_t recid
= strtoul(recordId
.c_str(), NULL
, 16);
477 RecordStateTable::IndexType index
;
478 if( rst
.GetIndex(recid
, &index
) ) {
480 ParserPtr parser
= GetParser(m_pdb
->Name
, oss
, false);
481 m_desk
.GetRecord(m_pdb
->Number
, index
, *parser
);
489 class DesktopCon
: public Directory
492 typedef std::tr1::shared_ptr
<Database
> DatabasePtr
;
493 typedef std::list
<DatabasePtr
> DBList
;
495 Barry::Controller m_con
;
496 Barry::Mode::Desktop m_desk
;
500 DesktopCon(const Barry::ProbeResult
&result
, const std::string
&pin
)
505 // add to directory list
506 g_dirmap
[ string("/") + pin
] = this;
511 // remove from directory list
512 g_dirmap
.erase( string("/") + m_pin
);
515 virtual int ReadDir(void *buf
, fuse_fill_dir_t filler
)
517 filler(buf
, ".", NULL
, 0);
518 filler(buf
, "..", NULL
, 0);
520 // list all databases in list
521 DBList::const_iterator b
= m_dblist
.begin(), e
= m_dblist
.end();
522 for( ; b
!= e
; ++ b
) {
523 filler(buf
, (*b
)->GetDBName().c_str(), NULL
, 0);
528 void Open(const char *password
= 0)
531 m_desk
.Open(password
);
533 // add all databases as directories
534 DatabaseDatabase::DatabaseArrayType::const_iterator
535 dbi
= m_desk
.GetDBDB().Databases
.begin(),
536 dbe
= m_desk
.GetDBDB().Databases
.end();
537 for( ; dbi
!= dbe
; ++dbi
) {
538 DatabasePtr db
= DatabasePtr(
539 new Database(m_desk
, m_pin
, &(*dbi
)) );
540 m_dblist
.push_back(db
);
545 class Context
: public Directory
, public File
548 typedef std::auto_ptr
<Barry::Probe
> ProbePtr
;
549 typedef std::tr1::shared_ptr
<DesktopCon
> DesktopConPtr
;
550 typedef std::string PinT
;
551 typedef std::map
<PinT
, DesktopConPtr
> PinMap
;
558 string m_limit_pin
; // only mount device with this pin
559 string m_password
; // use this password when connecting
562 Context(const string
&limit_pin
= "", const string
&password
= "")
563 : m_limit_pin(limit_pin
)
564 , m_password(password
)
566 g_dirmap
["/"] = this;
567 g_filemap
[string("/") + error_log_filename
] = this;
569 m_error_log
= "Hello FUSE world. This is Barry. Pleased to meet you.\n";
575 g_filemap
.erase(string("/") + error_log_filename
);
578 virtual int ReadDir(void *buf
, fuse_fill_dir_t filler
)
580 filler(buf
, ".", NULL
, 0);
581 filler(buf
, "..", NULL
, 0);
582 filler(buf
, error_log_filename
, NULL
, 0);
584 // list all pins in map
585 PinMap::const_iterator b
= m_pinmap
.begin(), e
= m_pinmap
.end();
586 for( ; b
!= e
; ++ b
) {
587 filler(buf
, b
->first
.c_str(), NULL
, 0);
592 virtual void FillFileStat(const char *path
, struct stat
*st
)
594 st
->st_mode
= S_IFREG
| 0444;
596 st
->st_size
= m_error_log
.size();
599 virtual int ReadFile(const char *path
, char *buf
, size_t size
, off_t offset
)
601 size_t len
= m_error_log
.size();
603 if( (offset
+ size
) > len
)
605 memcpy(buf
, m_error_log
.data() + offset
, size
);
613 void Log(const std::string
&msg
)
619 const std::string
& GetLog() const { return m_error_log
; }
623 // probe the USB bus for Blackberry devices
624 m_probe
.reset( new Probe
);
626 // connect to all PINs found, and add them to our map
627 for( int i
= 0; i
< m_probe
->GetCount(); i
++ ) {
628 string curpin
= m_probe
->Get(i
).m_pin
.str();
630 // don't add a blank or pre-existing pin
631 if( !curpin
.size() || m_pinmap
.find(curpin
) != m_pinmap
.end() ) {
635 // don't add non-PIN device if pin specified
636 if( m_limit_pin
.size() && curpin
!= m_limit_pin
) {
640 DesktopConPtr dev
= DesktopConPtr (
641 new DesktopCon(m_probe
->Get(i
), curpin
) );
642 dev
->Open(m_password
.c_str());
643 m_pinmap
[ curpin
] = dev
;
647 DesktopCon
* FindPin(PinT pin
)
649 PinMap::iterator pi
= m_pinmap
.find(pin
);
650 return pi
== m_pinmap
.end() ? 0 : pi
->second
.get();
655 /////////////////////////////////////////////////////////////////////////////
658 static void* bfuse_init()
660 // Initialize the barry library. Must be called before
667 ctx
= new Context(cmdline_pin
, cmdline_password
);
670 catch( std::exception
&e
) {
679 static void bfuse_destroy(void *data
)
682 Context
*ctx
= (Context
*) data
;
687 static int bfuse_getattr(const char *path
, struct stat
*st
)
689 memset(st
, 0, sizeof(*st
));
691 if( Directory
*dir
= FindDir(path
) ) {
692 dir
->FillDirStat(st
);
695 else if( File
*file
= FindFile(path
) ) {
696 file
->FillFileStat(path
, st
);
703 static int bfuse_readdir(const char *path
, void *buf
, fuse_fill_dir_t filler
,
704 off_t
/*offset*/, struct fuse_file_info
* /*fi*/)
706 Directory
*dir
= FindDir(path
);
709 return dir
->ReadDir(buf
, filler
);
712 static int bfuse_open(const char *path
, struct fuse_file_info
*fi
)
714 File
*file
= FindFile(path
);
718 if( !file
->AccessOk(fi
->flags
) )
724 static int bfuse_read(const char *path
, char *buf
, size_t size
, off_t offset
,
725 struct fuse_file_info
*fi
)
727 File
*file
= FindFile(path
);
731 return file
->ReadFile(path
, buf
, size
, offset
);
734 // static struct here automatically zeros data
735 static struct fuse_operations bfuse_oper
;
738 /////////////////////////////////////////////////////////////////////////////
741 int main(int argc
, char *argv
[])
745 cout
.sync_with_stdio(true); // leave this on, since libusb uses
746 // stdio for debug messages
750 // initialize the operation hooks
751 bfuse_oper
.init
= bfuse_init
;
752 bfuse_oper
.destroy
= bfuse_destroy
;
753 bfuse_oper
.getattr
= bfuse_getattr
;
754 bfuse_oper
.readdir
= bfuse_readdir
;
755 bfuse_oper
.open
= bfuse_open
;
756 bfuse_oper
.read
= bfuse_read
;
758 // process command line options before FUSE does
759 // FUSE does its own command line processing, and
760 // doesn't seem to have a way to plug into it,
761 // so do our own first
763 char **fuse_argv
= new char*[argc
];
765 for( int i
= 0; i
< argc
; i
++ ) {
766 if( argv
[i
][0] == '-' ) {
770 // case 'd': // mount dbname
771 // dbNames.push_back(string(optarg));
774 // case 'n': // use null parser
775 // null_parser = true;
778 case 'p': // Blackberry PIN
780 cmdline_pin
= argv
[++i
];
784 case 'P': // Device password
786 cmdline_password
= argv
[++i
];
796 // if we get here, add this option to FUSE's
797 fuse_argv
[fuse_argc
] = argv
[i
];
801 int ret
= fuse_main(fuse_argc
, fuse_argv
, &bfuse_oper
);