3 /// FUSE filesystem for Blackberry databases, using Barry.
7 Copyright (C) 2008-2011, 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>
43 using namespace std::tr1
;
44 using namespace Barry
;
47 const char *error_log_filename
= "error.log";
49 // Global command line args
51 string cmdline_password
;
54 // Data from the command line
57 /////////////////////////////////////////////////////////////////////////////
58 // Command line option handling, through fuse
66 int logical
, major
, minor
;
67 const char *Version
= Barry::Version(logical
, major
, minor
);
70 << "bfuse - FUSE filesystem for Blackberry databases\n"
71 << " Copyright 2008-2011, Net Direct Inc. (http://www.netdirect.ca/)\n"
72 << " Using: " << Version
<< "\n"
80 << "Barry specific options:\n"
81 << " -p pin PIN of device to talk with\n"
82 << " If only one device is plugged in, this flag is optional\n"
83 << " -P pass Simplistic method to specify device password\n"
86 << " -d db Specify which database to mount. If no -d options exist\n"
87 << " then all databases will be mounted.\n"
88 << " Can be used multiple times to mount more than one DB\n"
90 << " -n Use null parser on all databases.\n"
94 /////////////////////////////////////////////////////////////////////////////
95 // FUSE specific exception
97 class fuse_error
: public std::runtime_error
101 fuse_error(int errno_
, const std::string
&msg
)
102 : std::runtime_error(msg
), m_errno(errno_
)
105 int get_errno() const { return m_errno
; }
109 /////////////////////////////////////////////////////////////////////////////
110 // Barry record parsers
112 class DataDumpParser
: public Barry::Parser
118 explicit DataDumpParser(std::ostream
&os
)
123 virtual void ParseRecord(const Barry::DBData
&data
,
124 const Barry::IConverter
*ic
)
126 m_id
= data
.GetUniqueId();
127 m_os
<< "Raw record dump for record: "
128 << std::hex
<< m_id
<< std::endl
;
129 m_os
<< data
.GetData() << std::endl
;
133 template <class Record
>
138 explicit Store(std::ostream
&os
)
144 void operator()(const Record
&rec
)
150 typedef std::auto_ptr
<Barry::Parser
> ParserPtr
;
152 ParserPtr
GetParser(const string
&name
, std::ostream
&os
, bool null_parser
)
156 return ParserPtr( new DataDumpParser(os
) );
158 // check for recognized database names
159 else if( name
== Contact::GetDBName() ) {
161 new RecordParser
<Contact
, Store
<Contact
> > (
162 new Store
<Contact
>(os
)));
164 else if( name
== Message::GetDBName() ) {
166 new RecordParser
<Message
, Store
<Message
> > (
167 new Store
<Message
>(os
)));
169 else if( name
== Calendar::GetDBName() ) {
171 new RecordParser
<Calendar
, Store
<Calendar
> > (
172 new Store
<Calendar
>(os
)));
174 else if( name
== CalendarAll::GetDBName() ) {
176 new RecordParser
<CalendarAll
, Store
<CalendarAll
> > (
177 new Store
<CalendarAll
>(os
)));
179 else if( name
== ServiceBook::GetDBName() ) {
181 new RecordParser
<ServiceBook
, Store
<ServiceBook
> > (
182 new Store
<ServiceBook
>(os
)));
185 else if( name
== Memo::GetDBName() ) {
187 new RecordParser
<Memo
, Store
<Memo
> > (
188 new Store
<Memo
>(os
)));
190 else if( name
== Task::GetDBName() ) {
192 new RecordParser
<Task
, Store
<Task
> > (
193 new Store
<Task
>(os
)));
195 else if( name
== PINMessage::GetDBName() ) {
197 new RecordParser
<PINMessage
, Store
<PINMessage
> > (
198 new Store
<PINMessage
>(os
)));
200 else if( name
== SavedMessage::GetDBName() ) {
202 new RecordParser
<SavedMessage
, Store
<SavedMessage
> > (
203 new Store
<SavedMessage
>(os
)));
205 else if( name
== Folder::GetDBName() ) {
207 new RecordParser
<Folder
, Store
<Folder
> > (
208 new Store
<Folder
>(os
)));
210 else if( name
== Timezone::GetDBName() ) {
212 new RecordParser
<Timezone
, Store
<Timezone
> > (
213 new Store
<Timezone
>(os
)));
216 // unknown database, use null parser
217 return ParserPtr( new DataDumpParser(os
) );
221 /////////////////////////////////////////////////////////////////////////////
226 std::string m_pin
, m_db
, m_record
, m_field
, m_remainder
;
228 int m_level
; // the number of slashes, minus the first
229 // i.e. first level is 0
233 explicit PathSplit(const char *path
)
238 return; // return in a failed state
240 if( *(path
+1) == 0 ) {
245 const char *s
= path
, *e
= path
;
247 while( *e
&& *e
!= '/' )
252 if( s
!= e
&& (s
+1) != e
) {
253 string
token(s
+1, e
);
257 case 0: // root level, should not have token here
259 return; // failed state
269 case 3: // have record
273 case 4: // have field
277 default: // too many, store remainder and done
278 m_remainder
= s
; // keeps slash
294 bool IsRoot() const { return m_is_root
; }
295 const std::string
& Pin() const { return m_pin
; }
296 const std::string
& DB() const { return m_db
; }
297 const std::string
& Record() const { return m_record
; }
298 const std::string
& Field() const { return m_field
; }
299 const std::string
& Remainder() const { return m_remainder
; }
300 int Level() const { return m_level
; }
304 /////////////////////////////////////////////////////////////////////////////
313 class Directory
: public Entry
316 virtual int ReadDir(void *buf
, fuse_fill_dir_t filler
) = 0;
317 virtual void FillDirStat(struct stat
*st
)
319 st
->st_mode
= S_IFDIR
| 0555;
324 class File
: public Entry
327 virtual void FillFileStat(const char *path
, struct stat
*st
) = 0;
328 virtual bool AccessOk(int flags
)
330 // default to readonly files
331 return (flags
& (O_RDONLY
| O_WRONLY
| O_RDWR
)) == O_RDONLY
;
333 virtual int ReadFile(const char *path
, char *buf
, size_t size
, off_t offset
) = 0;
336 typedef Directory
* DirectoryPtr
;
337 typedef File
* FilePtr
;
338 typedef std::string NameT
;
339 typedef std::map
<NameT
, DirectoryPtr
> DirMap
;
340 typedef std::map
<NameT
, FilePtr
> FileMap
;
342 static DirMap g_dirmap
;
343 static FileMap g_filemap
;
345 static Directory
* FindDir(const NameT
&name
)
347 DirMap::iterator di
= g_dirmap
.find(name
);
348 return di
== g_dirmap
.end() ? 0 : di
->second
;
351 static File
* FindFile(const NameT
&name
)
353 FileMap::iterator fi
= g_filemap
.find(name
);
354 return fi
== g_filemap
.end() ? 0 : fi
->second
;
357 /////////////////////////////////////////////////////////////////////////////
360 class Database
: public Directory
, public File
363 Barry::Mode::Desktop
&m_desk
;
365 const Barry::DatabaseItem
*m_pdb
;
368 Database(Barry::Mode::Desktop
&desktop
,
369 const std::string
&pin
, const Barry::DatabaseItem
*pdb
)
373 m_name
= string("/") + pin
+ "/" + m_pdb
->Name
;
375 // add to directory list
376 g_dirmap
[ m_name
] = this;
381 // remove any entries that point to us
382 FileMap::iterator b
= g_filemap
.begin(), e
= g_filemap
.end();
383 for( ; b
!= e
; ++b
) {
384 if( b
->second
== this ) {
389 // erase ourselves from the directory list
390 g_dirmap
.erase( m_name
);
393 void AddFile(const std::string
&recordId
)
395 // FIXME - this is a hack to redirect all record files
396 // to this database class... next step is to possibly
397 // split out records into field files if we have a
398 // parser, or just dump the hex if we don't
399 string name
= m_name
+ "/" + recordId
;
400 g_filemap
[ name
] = this;
403 virtual int ReadDir(void *buf
, fuse_fill_dir_t filler
)
405 filler(buf
, ".", NULL
, 0);
406 filler(buf
, "..", NULL
, 0);
408 // list all records in database, by recordId
409 Barry::RecordStateTable rst
;
410 m_desk
.GetRecordStateTable(m_pdb
->Number
, rst
);
412 Barry::RecordStateTable::StateMapType::iterator
413 b
= rst
.StateMap
.begin(),
414 e
= rst
.StateMap
.end();
415 for( ; b
!= e
; ++ b
) {
417 oss
<< hex
<< b
->second
.RecordId
;
418 filler(buf
, oss
.str().c_str(), NULL
, 0);
425 virtual void FillFileStat(const char *path
, struct stat
*st
)
427 // use the path to find the proper record
430 string constructed
= string("/") + ps
.Pin() + "/" + ps
.DB();
431 if( constructed
!= m_name
) {
432 // FIXME - this is shoddy error handling
433 throw std::logic_error("Constructed != name");
436 string data
= GetRecordData(ps
.Record());
438 st
->st_mode
= S_IFREG
| 0444;
440 st
->st_size
= data
.size();
443 virtual int ReadFile(const char *path
, char *buf
, size_t size
, off_t offset
)
445 // use the path to find the proper record
448 string constructed
= string("/") + ps
.Pin() + "/" + ps
.DB();
449 if( constructed
!= m_name
) {
450 // FIXME - this is shoddy error handling
451 throw std::logic_error("Constructed != name");
454 string data
= GetRecordData(ps
.Record());
456 size_t len
= data
.size();
457 if( offset
>= 0 && offset
< (off_t
)len
) {
458 if( (offset
+ size
) > len
)
460 memcpy(buf
, data
.data() + offset
, size
);
468 const std::string
& GetDBName() const { return m_pdb
->Name
; }
470 std::string
GetRecordData(const std::string
&recordId
)
474 Barry::RecordStateTable rst
;
475 m_desk
.GetRecordStateTable(m_pdb
->Number
, rst
);
477 uint32_t recid
= strtoul(recordId
.c_str(), NULL
, 16);
478 RecordStateTable::IndexType index
;
479 if( rst
.GetIndex(recid
, &index
) ) {
481 ParserPtr parser
= GetParser(m_pdb
->Name
, oss
, false);
482 m_desk
.GetRecord(m_pdb
->Number
, index
, *parser
);
490 class DesktopCon
: public Directory
493 typedef std::tr1::shared_ptr
<Database
> DatabasePtr
;
494 typedef std::list
<DatabasePtr
> DBList
;
496 Barry::Controller m_con
;
497 Barry::Mode::Desktop m_desk
;
501 DesktopCon(const Barry::ProbeResult
&result
, const std::string
&pin
)
506 // add to directory list
507 g_dirmap
[ string("/") + pin
] = this;
512 // remove from directory list
513 g_dirmap
.erase( string("/") + m_pin
);
516 virtual int ReadDir(void *buf
, fuse_fill_dir_t filler
)
518 filler(buf
, ".", NULL
, 0);
519 filler(buf
, "..", NULL
, 0);
521 // list all databases in list
522 DBList::const_iterator b
= m_dblist
.begin(), e
= m_dblist
.end();
523 for( ; b
!= e
; ++ b
) {
524 filler(buf
, (*b
)->GetDBName().c_str(), NULL
, 0);
529 void Open(const char *password
= 0)
532 m_desk
.Open(password
);
534 // add all databases as directories
535 DatabaseDatabase::DatabaseArrayType::const_iterator
536 dbi
= m_desk
.GetDBDB().Databases
.begin(),
537 dbe
= m_desk
.GetDBDB().Databases
.end();
538 for( ; dbi
!= dbe
; ++dbi
) {
539 DatabasePtr db
= DatabasePtr(
540 new Database(m_desk
, m_pin
, &(*dbi
)) );
541 m_dblist
.push_back(db
);
546 class Context
: public Directory
, public File
549 typedef std::auto_ptr
<Barry::Probe
> ProbePtr
;
550 typedef std::tr1::shared_ptr
<DesktopCon
> DesktopConPtr
;
551 typedef std::string PinT
;
552 typedef std::map
<PinT
, DesktopConPtr
> PinMap
;
559 string m_limit_pin
; // only mount device with this pin
560 string m_password
; // use this password when connecting
563 Context(const string
&limit_pin
= "", const string
&password
= "")
564 : m_limit_pin(limit_pin
)
565 , m_password(password
)
567 g_dirmap
["/"] = this;
568 g_filemap
[string("/") + error_log_filename
] = this;
570 m_error_log
= "Hello FUSE world. This is Barry. Pleased to meet you.\n";
576 g_filemap
.erase(string("/") + error_log_filename
);
579 virtual int ReadDir(void *buf
, fuse_fill_dir_t filler
)
581 filler(buf
, ".", NULL
, 0);
582 filler(buf
, "..", NULL
, 0);
583 filler(buf
, error_log_filename
, NULL
, 0);
585 // list all pins in map
586 PinMap::const_iterator b
= m_pinmap
.begin(), e
= m_pinmap
.end();
587 for( ; b
!= e
; ++ b
) {
588 filler(buf
, b
->first
.c_str(), NULL
, 0);
593 virtual void FillFileStat(const char *path
, struct stat
*st
)
595 st
->st_mode
= S_IFREG
| 0444;
597 st
->st_size
= m_error_log
.size();
600 virtual int ReadFile(const char *path
, char *buf
, size_t size
, off_t offset
)
602 size_t len
= m_error_log
.size();
603 if( offset
>= 0 && offset
< (off_t
)len
) {
604 if( (offset
+ size
) > len
)
606 memcpy(buf
, m_error_log
.data() + offset
, size
);
614 void Log(const std::string
&msg
)
620 const std::string
& GetLog() const { return m_error_log
; }
624 // probe the USB bus for Blackberry devices
625 m_probe
.reset( new Probe
);
627 // connect to all PINs found, and add them to our map
628 for( int i
= 0; i
< m_probe
->GetCount(); i
++ ) {
629 string curpin
= m_probe
->Get(i
).m_pin
.Str();
631 // don't add a blank or pre-existing pin
632 if( !curpin
.size() || m_pinmap
.find(curpin
) != m_pinmap
.end() ) {
636 // don't add non-PIN device if pin specified
637 if( m_limit_pin
.size() && curpin
!= m_limit_pin
) {
641 DesktopConPtr dev
= DesktopConPtr (
642 new DesktopCon(m_probe
->Get(i
), curpin
) );
643 dev
->Open(m_password
.c_str());
644 m_pinmap
[ curpin
] = dev
;
648 DesktopCon
* FindPin(PinT pin
)
650 PinMap::iterator pi
= m_pinmap
.find(pin
);
651 return pi
== m_pinmap
.end() ? 0 : pi
->second
.get();
656 /////////////////////////////////////////////////////////////////////////////
659 static void* bfuse_init()
661 // Initialize the barry library. Must be called before
668 ctx
= new Context(cmdline_pin
, cmdline_password
);
671 catch( std::exception
&e
) {
680 static void bfuse_destroy(void *data
)
683 Context
*ctx
= (Context
*) data
;
688 static int bfuse_getattr(const char *path
, struct stat
*st
)
690 memset(st
, 0, sizeof(*st
));
692 if( Directory
*dir
= FindDir(path
) ) {
693 dir
->FillDirStat(st
);
696 else if( File
*file
= FindFile(path
) ) {
697 file
->FillFileStat(path
, st
);
704 static int bfuse_readdir(const char *path
, void *buf
, fuse_fill_dir_t filler
,
705 off_t
/*offset*/, struct fuse_file_info
* /*fi*/)
707 Directory
*dir
= FindDir(path
);
710 return dir
->ReadDir(buf
, filler
);
713 static int bfuse_open(const char *path
, struct fuse_file_info
*fi
)
715 File
*file
= FindFile(path
);
719 if( !file
->AccessOk(fi
->flags
) )
725 static int bfuse_read(const char *path
, char *buf
, size_t size
, off_t offset
,
726 struct fuse_file_info
*fi
)
728 File
*file
= FindFile(path
);
732 return file
->ReadFile(path
, buf
, size
, offset
);
735 // static struct here automatically zeros data
736 static struct fuse_operations bfuse_oper
;
739 /////////////////////////////////////////////////////////////////////////////
742 int main(int argc
, char *argv
[])
746 cout
.sync_with_stdio(true); // leave this on, since libusb uses
747 // stdio for debug messages
751 // initialize the operation hooks
752 bfuse_oper
.init
= bfuse_init
;
753 bfuse_oper
.destroy
= bfuse_destroy
;
754 bfuse_oper
.getattr
= bfuse_getattr
;
755 bfuse_oper
.readdir
= bfuse_readdir
;
756 bfuse_oper
.open
= bfuse_open
;
757 bfuse_oper
.read
= bfuse_read
;
759 // process command line options before FUSE does
760 // FUSE does its own command line processing, and
761 // doesn't seem to have a way to plug into it,
762 // so do our own first
764 char **fuse_argv
= new char*[argc
];
766 for( int i
= 0; i
< argc
; i
++ ) {
767 if( argv
[i
][0] == '-' ) {
771 // case 'd': // mount dbname
772 // dbNames.push_back(string(optarg));
775 // case 'n': // use null parser
776 // null_parser = true;
779 case 'p': // Blackberry PIN
781 cmdline_pin
= argv
[++i
];
785 case 'P': // Device password
787 cmdline_password
= argv
[++i
];
797 // if we get here, add this option to FUSE's
798 fuse_argv
[fuse_argc
] = argv
[i
];
802 int ret
= fuse_main(fuse_argc
, fuse_argv
, &bfuse_oper
);