3 /// FUSE filesystem for Blackberry databases, using Barry.
7 Copyright (C) 2008-2009, 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-2009, 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
== ServiceBook::GetDBName() ) {
185 new RecordParser
<ServiceBook
, Store
<ServiceBook
> > (
186 new Store
<ServiceBook
>(os
)));
189 else if( name
== Memo::GetDBName() ) {
191 new RecordParser
<Memo
, Store
<Memo
> > (
192 new Store
<Memo
>(os
)));
194 else if( name
== Task::GetDBName() ) {
196 new RecordParser
<Task
, Store
<Task
> > (
197 new Store
<Task
>(os
)));
199 else if( name
== PINMessage::GetDBName() ) {
201 new RecordParser
<PINMessage
, Store
<PINMessage
> > (
202 new Store
<PINMessage
>(os
)));
204 else if( name
== SavedMessage::GetDBName() ) {
206 new RecordParser
<SavedMessage
, Store
<SavedMessage
> > (
207 new Store
<SavedMessage
>(os
)));
209 else if( name
== Folder::GetDBName() ) {
211 new RecordParser
<Folder
, Store
<Folder
> > (
212 new Store
<Folder
>(os
)));
214 else if( name
== Timezone::GetDBName() ) {
216 new RecordParser
<Timezone
, Store
<Timezone
> > (
217 new Store
<Timezone
>(os
)));
220 // unknown database, use null parser
221 return ParserPtr( new DataDumpParser(os
) );
225 /////////////////////////////////////////////////////////////////////////////
230 std::string m_pin
, m_db
, m_record
, m_field
, m_remainder
;
232 int m_level
; // the number of slashes, minus the first
233 // i.e. first level is 0
237 explicit PathSplit(const char *path
)
242 return; // return in a failed state
244 if( *(path
+1) == 0 ) {
249 const char *s
= path
, *e
= path
;
251 while( *e
&& *e
!= '/' )
256 if( s
!= e
&& (s
+1) != e
) {
257 string
token(s
+1, e
);
261 case 0: // root level, should not have token here
263 return; // failed state
273 case 3: // have record
277 case 4: // have field
281 default: // too many, store remainder and done
282 m_remainder
= s
; // keeps slash
298 bool IsRoot() const { return m_is_root
; }
299 const std::string
& Pin() const { return m_pin
; }
300 const std::string
& DB() const { return m_db
; }
301 const std::string
& Record() const { return m_record
; }
302 const std::string
& Field() const { return m_field
; }
303 const std::string
& Remainder() const { return m_remainder
; }
304 int Level() const { return m_level
; }
308 /////////////////////////////////////////////////////////////////////////////
317 class Directory
: public Entry
320 virtual int ReadDir(void *buf
, fuse_fill_dir_t filler
) = 0;
321 virtual void FillDirStat(struct stat
*st
)
323 st
->st_mode
= S_IFDIR
| 0555;
328 class File
: public Entry
331 virtual void FillFileStat(const char *path
, struct stat
*st
) = 0;
332 virtual bool AccessOk(int flags
)
334 // default to readonly files
335 return (flags
& (O_RDONLY
| O_WRONLY
| O_RDWR
)) == O_RDONLY
;
337 virtual int ReadFile(const char *path
, char *buf
, size_t size
, off_t offset
) = 0;
340 typedef Directory
* DirectoryPtr
;
341 typedef File
* FilePtr
;
342 typedef std::string NameT
;
343 typedef std::map
<NameT
, DirectoryPtr
> DirMap
;
344 typedef std::map
<NameT
, FilePtr
> FileMap
;
346 static DirMap g_dirmap
;
347 static FileMap g_filemap
;
349 static Directory
* FindDir(const NameT
&name
)
351 DirMap::iterator di
= g_dirmap
.find(name
);
352 return di
== g_dirmap
.end() ? 0 : di
->second
;
355 static File
* FindFile(const NameT
&name
)
357 FileMap::iterator fi
= g_filemap
.find(name
);
358 return fi
== g_filemap
.end() ? 0 : fi
->second
;
361 /////////////////////////////////////////////////////////////////////////////
364 class Database
: public Directory
, public File
367 Barry::Mode::Desktop
&m_desk
;
369 const Barry::DatabaseItem
*m_pdb
;
372 Database(Barry::Mode::Desktop
&desktop
,
373 const std::string
&pin
, const Barry::DatabaseItem
*pdb
)
377 m_name
= string("/") + pin
+ "/" + m_pdb
->Name
;
379 // add to directory list
380 g_dirmap
[ m_name
] = this;
385 // remove any entries that point to us
386 FileMap::iterator b
= g_filemap
.begin(), e
= g_filemap
.end();
387 for( ; b
!= e
; ++b
) {
388 if( b
->second
== this ) {
393 // erase ourselves from the directory list
394 g_dirmap
.erase( m_name
);
397 void AddFile(const std::string
&recordId
)
399 // FIXME - this is a hack to redirect all record files
400 // to this database class... next step is to possibly
401 // split out records into field files if we have a
402 // parser, or just dump the hex if we don't
403 string name
= m_name
+ "/" + recordId
;
404 g_filemap
[ name
] = this;
407 virtual int ReadDir(void *buf
, fuse_fill_dir_t filler
)
409 filler(buf
, ".", NULL
, 0);
410 filler(buf
, "..", NULL
, 0);
412 // list all records in database, by recordId
413 Barry::RecordStateTable rst
;
414 m_desk
.GetRecordStateTable(m_pdb
->Number
, rst
);
416 Barry::RecordStateTable::StateMapType::iterator
417 b
= rst
.StateMap
.begin(),
418 e
= rst
.StateMap
.end();
419 for( ; b
!= e
; ++ b
) {
421 oss
<< hex
<< b
->second
.RecordId
;
422 filler(buf
, oss
.str().c_str(), NULL
, 0);
429 virtual void FillFileStat(const char *path
, struct stat
*st
)
431 // use the path to find the proper record
434 string constructed
= string("/") + ps
.Pin() + "/" + ps
.DB();
435 if( constructed
!= m_name
) {
436 // FIXME - this is shoddy error handling
437 throw std::logic_error("Constructed != name");
440 string data
= GetRecordData(ps
.Record());
442 st
->st_mode
= S_IFREG
| 0444;
444 st
->st_size
= data
.size();
447 virtual int ReadFile(const char *path
, char *buf
, size_t size
, off_t offset
)
449 // use the path to find the proper record
452 string constructed
= string("/") + ps
.Pin() + "/" + ps
.DB();
453 if( constructed
!= m_name
) {
454 // FIXME - this is shoddy error handling
455 throw std::logic_error("Constructed != name");
458 string data
= GetRecordData(ps
.Record());
460 size_t len
= data
.size();
462 if( (offset
+ size
) > len
)
464 memcpy(buf
, data
.data() + offset
, size
);
472 const std::string
& GetDBName() const { return m_pdb
->Name
; }
474 std::string
GetRecordData(const std::string
&recordId
)
478 Barry::RecordStateTable rst
;
479 m_desk
.GetRecordStateTable(m_pdb
->Number
, rst
);
481 uint32_t recid
= strtoul(recordId
.c_str(), NULL
, 16);
482 RecordStateTable::IndexType index
;
483 if( rst
.GetIndex(recid
, &index
) ) {
485 ParserPtr parser
= GetParser(m_pdb
->Name
, oss
, false);
486 m_desk
.GetRecord(m_pdb
->Number
, index
, *parser
);
494 class DesktopCon
: public Directory
497 typedef std::tr1::shared_ptr
<Database
> DatabasePtr
;
498 typedef std::list
<DatabasePtr
> DBList
;
500 Barry::Controller m_con
;
501 Barry::Mode::Desktop m_desk
;
505 DesktopCon(const Barry::ProbeResult
&result
, const std::string
&pin
)
510 // add to directory list
511 g_dirmap
[ string("/") + pin
] = this;
516 // remove from directory list
517 g_dirmap
.erase( string("/") + m_pin
);
520 virtual int ReadDir(void *buf
, fuse_fill_dir_t filler
)
522 filler(buf
, ".", NULL
, 0);
523 filler(buf
, "..", NULL
, 0);
525 // list all databases in list
526 DBList::const_iterator b
= m_dblist
.begin(), e
= m_dblist
.end();
527 for( ; b
!= e
; ++ b
) {
528 filler(buf
, (*b
)->GetDBName().c_str(), NULL
, 0);
533 void Open(const char *password
= 0)
536 m_desk
.Open(password
);
538 // add all databases as directories
539 DatabaseDatabase::DatabaseArrayType::const_iterator
540 dbi
= m_desk
.GetDBDB().Databases
.begin(),
541 dbe
= m_desk
.GetDBDB().Databases
.end();
542 for( ; dbi
!= dbe
; ++dbi
) {
543 DatabasePtr db
= DatabasePtr(
544 new Database(m_desk
, m_pin
, &(*dbi
)) );
545 m_dblist
.push_back(db
);
550 class Context
: public Directory
, public File
553 typedef std::auto_ptr
<Barry::Probe
> ProbePtr
;
554 typedef std::tr1::shared_ptr
<DesktopCon
> DesktopConPtr
;
555 typedef std::string PinT
;
556 typedef std::map
<PinT
, DesktopConPtr
> PinMap
;
563 string m_limit_pin
; // only mount device with this pin
564 string m_password
; // use this password when connecting
567 Context(const string
&limit_pin
= "", const string
&password
= "")
568 : m_limit_pin(limit_pin
)
569 , m_password(password
)
571 g_dirmap
["/"] = this;
572 g_filemap
[string("/") + error_log_filename
] = this;
574 m_error_log
= "Hello FUSE world. This is Barry. Pleased to meet you.\n";
580 g_filemap
.erase(string("/") + error_log_filename
);
583 virtual int ReadDir(void *buf
, fuse_fill_dir_t filler
)
585 filler(buf
, ".", NULL
, 0);
586 filler(buf
, "..", NULL
, 0);
587 filler(buf
, error_log_filename
, NULL
, 0);
589 // list all pins in map
590 PinMap::const_iterator b
= m_pinmap
.begin(), e
= m_pinmap
.end();
591 for( ; b
!= e
; ++ b
) {
592 filler(buf
, b
->first
.c_str(), NULL
, 0);
597 virtual void FillFileStat(const char *path
, struct stat
*st
)
599 st
->st_mode
= S_IFREG
| 0444;
601 st
->st_size
= m_error_log
.size();
604 virtual int ReadFile(const char *path
, char *buf
, size_t size
, off_t offset
)
606 size_t len
= m_error_log
.size();
608 if( (offset
+ size
) > len
)
610 memcpy(buf
, m_error_log
.data() + offset
, size
);
618 void Log(const std::string
&msg
)
624 const std::string
& GetLog() const { return m_error_log
; }
628 // probe the USB bus for Blackberry devices
629 m_probe
.reset( new Probe
);
631 // connect to all PINs found, and add them to our map
632 for( int i
= 0; i
< m_probe
->GetCount(); i
++ ) {
633 string curpin
= m_probe
->Get(i
).m_pin
.str();
635 // don't add a blank or pre-existing pin
636 if( !curpin
.size() || m_pinmap
.find(curpin
) != m_pinmap
.end() ) {
640 // don't add non-PIN device if pin specified
641 if( m_limit_pin
.size() && curpin
!= m_limit_pin
) {
645 DesktopConPtr dev
= DesktopConPtr (
646 new DesktopCon(m_probe
->Get(i
), curpin
) );
647 dev
->Open(m_password
.c_str());
648 m_pinmap
[ curpin
] = dev
;
652 DesktopCon
* FindPin(PinT pin
)
654 PinMap::iterator pi
= m_pinmap
.find(pin
);
655 return pi
== m_pinmap
.end() ? 0 : pi
->second
.get();
660 /////////////////////////////////////////////////////////////////////////////
663 static void* bfuse_init()
665 // Initialize the barry library. Must be called before
672 ctx
= new Context(cmdline_pin
, cmdline_password
);
675 catch( std::exception
&e
) {
684 static void bfuse_destroy(void *data
)
687 Context
*ctx
= (Context
*) data
;
692 static int bfuse_getattr(const char *path
, struct stat
*st
)
694 memset(st
, 0, sizeof(*st
));
696 if( Directory
*dir
= FindDir(path
) ) {
697 dir
->FillDirStat(st
);
700 else if( File
*file
= FindFile(path
) ) {
701 file
->FillFileStat(path
, st
);
708 static int bfuse_readdir(const char *path
, void *buf
, fuse_fill_dir_t filler
,
709 off_t
/*offset*/, struct fuse_file_info
* /*fi*/)
711 Directory
*dir
= FindDir(path
);
714 return dir
->ReadDir(buf
, filler
);
717 static int bfuse_open(const char *path
, struct fuse_file_info
*fi
)
719 File
*file
= FindFile(path
);
723 if( !file
->AccessOk(fi
->flags
) )
729 static int bfuse_read(const char *path
, char *buf
, size_t size
, off_t offset
,
730 struct fuse_file_info
*fi
)
732 File
*file
= FindFile(path
);
736 return file
->ReadFile(path
, buf
, size
, offset
);
739 // static struct here automatically zeros data
740 static struct fuse_operations bfuse_oper
;
743 /////////////////////////////////////////////////////////////////////////////
746 int main(int argc
, char *argv
[])
750 cout
.sync_with_stdio(true); // leave this on, since libusb uses
751 // stdio for debug messages
755 // initialize the operation hooks
756 bfuse_oper
.init
= bfuse_init
;
757 bfuse_oper
.destroy
= bfuse_destroy
;
758 bfuse_oper
.getattr
= bfuse_getattr
;
759 bfuse_oper
.readdir
= bfuse_readdir
;
760 bfuse_oper
.open
= bfuse_open
;
761 bfuse_oper
.read
= bfuse_read
;
763 // process command line options before FUSE does
764 // FUSE does its own command line processing, and
765 // doesn't seem to have a way to plug into it,
766 // so do our own first
768 char **fuse_argv
= new char*[argc
];
770 for( int i
= 0; i
< argc
; i
++ ) {
771 if( argv
[i
][0] == '-' ) {
775 // case 'd': // mount dbname
776 // dbNames.push_back(string(optarg));
779 // case 'n': // use null parser
780 // null_parser = true;
783 case 'p': // Blackberry PIN
785 cmdline_pin
= argv
[++i
];
789 case 'P': // Device password
791 cmdline_password
= argv
[++i
];
801 // if we get here, add this option to FUSE's
802 fuse_argv
[fuse_argc
] = argv
[i
];
806 int ret
= fuse_main(fuse_argc
, fuse_argv
, &bfuse_oper
);