3 /// FUSE filesystem for Blackberry databases, using Barry.
7 Copyright (C) 2008, 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>
41 using namespace std::tr1
;
42 using namespace Barry
;
45 const char *error_log_filename
= "error.log";
48 // Data from the command line
51 /////////////////////////////////////////////////////////////////////////////
52 // Command line option handling, through fuse
61 const char *Version
= Barry::Version(major
, minor
);
64 << "bfuse - FUSE filesystem for Blackberry databases\n"
65 << " Copyright 2008, Net Direct Inc. (http://www.netdirect.ca/)\n"
66 << " Using: " << Version
<< "\n"
70 << " -d db Specify which database to mount. If no -d options exist\n"
71 << " then all databases will be mounted.\n"
72 << " Can be used multiple times to mount more than one DB\n"
74 << " -n Use null parser on all databases.\n"
75 << " -p pin PIN of device to talk with\n"
76 << " If only one device is plugged in, this flag is optional\n"
77 << " -P pass Simplistic method to specify device password\n"
81 /////////////////////////////////////////////////////////////////////////////
82 // FUSE specific exception
84 class fuse_error
: public std::runtime_error
88 fuse_error(int errno_
, const std::string
&msg
)
89 : std::runtime_error(msg
), m_errno(errno_
)
92 int get_errno() const { return m_errno
; }
96 /////////////////////////////////////////////////////////////////////////////
97 // Barry record parsers
99 class DataDumpParser
: public Barry::Parser
105 explicit DataDumpParser(std::ostream
&os
)
110 virtual void Clear() {}
112 virtual void SetIds(uint8_t RecType
, uint32_t UniqueId
)
117 virtual void ParseHeader(const Barry::Data
&, size_t &) {}
119 virtual void ParseFields(const Barry::Data
&data
, size_t &offset
,
120 const Barry::IConverter
*ic
)
122 m_os
<< "Raw record dump for record: "
123 << std::hex
<< m_id
<< std::endl
;
124 m_os
<< data
<< std::endl
;
127 virtual void Store() {}
130 template <class Record
>
135 explicit Store(std::ostream
&os
)
141 void operator()(const Record
&rec
)
147 typedef std::auto_ptr
<Barry::Parser
> ParserPtr
;
149 ParserPtr
GetParser(const string
&name
, std::ostream
&os
, bool null_parser
)
153 return ParserPtr( new DataDumpParser(os
) );
155 // check for recognized database names
156 else if( name
== Contact::GetDBName() ) {
158 new RecordParser
<Contact
, Store
<Contact
> > (
159 new Store
<Contact
>(os
)));
161 else if( name
== Message::GetDBName() ) {
163 new RecordParser
<Message
, Store
<Message
> > (
164 new Store
<Message
>(os
)));
166 else if( name
== Calendar::GetDBName() ) {
168 new RecordParser
<Calendar
, Store
<Calendar
> > (
169 new Store
<Calendar
>(os
)));
171 else if( name
== ServiceBook::GetDBName() ) {
173 new RecordParser
<ServiceBook
, Store
<ServiceBook
> > (
174 new Store
<ServiceBook
>(os
)));
177 else if( name
== Memo::GetDBName() ) {
179 new RecordParser
<Memo
, Store
<Memo
> > (
180 new Store
<Memo
>(os
)));
182 else if( name
== Task::GetDBName() ) {
184 new RecordParser
<Task
, Store
<Task
> > (
185 new Store
<Task
>(os
)));
187 else if( name
== PINMessage::GetDBName() ) {
189 new RecordParser
<PINMessage
, Store
<PINMessage
> > (
190 new Store
<PINMessage
>(os
)));
192 else if( name
== SavedMessage::GetDBName() ) {
194 new RecordParser
<SavedMessage
, Store
<SavedMessage
> > (
195 new Store
<SavedMessage
>(os
)));
197 else if( name
== Folder::GetDBName() ) {
199 new RecordParser
<Folder
, Store
<Folder
> > (
200 new Store
<Folder
>(os
)));
202 else if( name
== Timezone::GetDBName() ) {
204 new RecordParser
<Timezone
, Store
<Timezone
> > (
205 new Store
<Timezone
>(os
)));
208 // unknown database, use null parser
209 return ParserPtr( new DataDumpParser(os
) );
213 /////////////////////////////////////////////////////////////////////////////
218 std::string m_pin
, m_db
, m_record
, m_field
, m_remainder
;
220 int m_level
; // the number of slashes, minus the first
221 // i.e. first level is 0
225 explicit PathSplit(const char *path
)
230 return; // return in a failed state
232 if( *(path
+1) == 0 ) {
237 const char *s
= path
, *e
= path
;
239 while( *e
&& *e
!= '/' )
244 if( s
!= e
&& (s
+1) != e
) {
245 string
token(s
+1, e
);
249 case 0: // root level, should not have token here
251 return; // failed state
261 case 3: // have record
265 case 4: // have field
269 default: // too many, store remainder and done
270 m_remainder
= s
; // keeps slash
286 bool IsRoot() const { return m_is_root
; }
287 const std::string
& Pin() const { return m_pin
; }
288 const std::string
& DB() const { return m_db
; }
289 const std::string
& Record() const { return m_record
; }
290 const std::string
& Field() const { return m_field
; }
291 const std::string
& Remainder() const { return m_remainder
; }
292 int Level() const { return m_level
; }
296 /////////////////////////////////////////////////////////////////////////////
305 class Directory
: public Entry
308 virtual int ReadDir(void *buf
, fuse_fill_dir_t filler
) = 0;
309 virtual void FillDirStat(struct stat
*st
)
311 st
->st_mode
= S_IFDIR
| 0555;
316 class File
: public Entry
319 virtual void FillFileStat(const char *path
, struct stat
*st
) = 0;
320 virtual bool AccessOk(int flags
)
322 // default to readonly files
323 return (flags
& (O_RDONLY
| O_WRONLY
| O_RDWR
)) == O_RDONLY
;
325 virtual int ReadFile(const char *path
, char *buf
, size_t size
, off_t offset
) = 0;
328 typedef Directory
* DirectoryPtr
;
329 typedef File
* FilePtr
;
330 typedef std::string NameT
;
331 typedef std::map
<NameT
, DirectoryPtr
> DirMap
;
332 typedef std::map
<NameT
, FilePtr
> FileMap
;
334 static DirMap g_dirmap
;
335 static FileMap g_filemap
;
337 static Directory
* FindDir(const NameT
&name
)
339 DirMap::iterator di
= g_dirmap
.find(name
);
340 return di
== g_dirmap
.end() ? 0 : di
->second
;
343 static File
* FindFile(const NameT
&name
)
345 FileMap::iterator fi
= g_filemap
.find(name
);
346 return fi
== g_filemap
.end() ? 0 : fi
->second
;
349 /////////////////////////////////////////////////////////////////////////////
352 class Database
: public Directory
, public File
355 Barry::Mode::Desktop
&m_desk
;
357 const Barry::DatabaseItem
*m_pdb
;
360 Database(Barry::Mode::Desktop
&desktop
,
361 const std::string
&pin
, const Barry::DatabaseItem
*pdb
)
365 m_name
= string("/") + pin
+ "/" + m_pdb
->Name
;
367 // add to directory list
368 g_dirmap
[ m_name
] = this;
373 // remove any entries that point to us
374 FileMap::iterator b
= g_filemap
.begin(), e
= g_filemap
.end();
375 for( ; b
!= e
; ++b
) {
376 if( b
->second
== this ) {
381 // erase ourselves from the directory list
382 g_dirmap
.erase( m_name
);
385 void AddFile(const std::string
&recordId
)
387 // FIXME - this is a hack to redirect all record files
388 // to this database class... next step is to possibly
389 // split out records into field files if we have a
390 // parser, or just dump the hex if we don't
391 string name
= m_name
+ "/" + recordId
;
392 g_filemap
[ name
] = this;
395 virtual int ReadDir(void *buf
, fuse_fill_dir_t filler
)
397 filler(buf
, ".", NULL
, 0);
398 filler(buf
, "..", NULL
, 0);
400 // list all records in database, by recordId
401 Barry::RecordStateTable rst
;
402 m_desk
.GetRecordStateTable(m_pdb
->Number
, rst
);
404 Barry::RecordStateTable::StateMapType::iterator
405 b
= rst
.StateMap
.begin(),
406 e
= rst
.StateMap
.end();
407 for( ; b
!= e
; ++ b
) {
409 oss
<< hex
<< b
->second
.RecordId
;
410 filler(buf
, oss
.str().c_str(), NULL
, 0);
417 virtual void FillFileStat(const char *path
, struct stat
*st
)
419 // use the path to find the proper record
422 string constructed
= string("/") + ps
.Pin() + "/" + ps
.DB();
423 if( constructed
!= m_name
) {
424 // FIXME - this is shoddy error handling
425 throw std::logic_error("Constructed != name");
428 string data
= GetRecordData(ps
.Record());
430 st
->st_mode
= S_IFREG
| 0444;
432 st
->st_size
= data
.size();
435 virtual int ReadFile(const char *path
, char *buf
, size_t size
, off_t offset
)
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 size_t len
= data
.size();
450 if( (offset
+ size
) > len
)
452 memcpy(buf
, data
.data() + offset
, size
);
460 const std::string
& GetDBName() const { return m_pdb
->Name
; }
462 std::string
GetRecordData(const std::string
&recordId
)
466 Barry::RecordStateTable rst
;
467 m_desk
.GetRecordStateTable(m_pdb
->Number
, rst
);
469 uint32_t recid
= strtoul(recordId
.c_str(), NULL
, 16);
470 RecordStateTable::IndexType index
;
471 if( rst
.GetIndex(recid
, &index
) ) {
473 ParserPtr parser
= GetParser(m_pdb
->Name
, oss
, false);
474 m_desk
.GetRecord(m_pdb
->Number
, index
, *parser
);
482 class DesktopCon
: public Directory
485 typedef std::tr1::shared_ptr
<Database
> DatabasePtr
;
486 typedef std::list
<DatabasePtr
> DBList
;
488 Barry::Controller m_con
;
489 Barry::Mode::Desktop m_desk
;
493 DesktopCon(const Barry::ProbeResult
&result
, const std::string
&pin
)
498 // add to directory list
499 g_dirmap
[ string("/") + pin
] = this;
504 // remove from directory list
505 g_dirmap
.erase( string("/") + m_pin
);
508 virtual int ReadDir(void *buf
, fuse_fill_dir_t filler
)
510 filler(buf
, ".", NULL
, 0);
511 filler(buf
, "..", NULL
, 0);
513 // list all databases in list
514 DBList::const_iterator b
= m_dblist
.begin(), e
= m_dblist
.end();
515 for( ; b
!= e
; ++ b
) {
516 filler(buf
, (*b
)->GetDBName().c_str(), NULL
, 0);
521 void Open(const char *password
= 0)
524 m_desk
.Open(password
);
526 // add all databases as directories
527 DatabaseDatabase::DatabaseArrayType::const_iterator
528 dbi
= m_desk
.GetDBDB().Databases
.begin(),
529 dbe
= m_desk
.GetDBDB().Databases
.end();
530 for( ; dbi
!= dbe
; ++dbi
) {
531 DatabasePtr db
= DatabasePtr(
532 new Database(m_desk
, m_pin
, &(*dbi
)) );
533 m_dblist
.push_back(db
);
538 class Context
: public Directory
, public File
541 typedef std::auto_ptr
<Barry::Probe
> ProbePtr
;
542 typedef std::tr1::shared_ptr
<DesktopCon
> DesktopConPtr
;
543 typedef std::string PinT
;
544 typedef std::map
<PinT
, DesktopConPtr
> PinMap
;
554 g_dirmap
["/"] = this;
555 g_filemap
[string("/") + error_log_filename
] = this;
557 m_error_log
= "Hello FUSE world. This is Barry. Pleased to meet you.\n";
563 g_filemap
.erase(string("/") + error_log_filename
);
566 virtual int ReadDir(void *buf
, fuse_fill_dir_t filler
)
568 filler(buf
, ".", NULL
, 0);
569 filler(buf
, "..", NULL
, 0);
570 filler(buf
, error_log_filename
, NULL
, 0);
572 // list all pins in map
573 PinMap::const_iterator b
= m_pinmap
.begin(), e
= m_pinmap
.end();
574 for( ; b
!= e
; ++ b
) {
575 filler(buf
, b
->first
.c_str(), NULL
, 0);
580 virtual void FillFileStat(const char *path
, struct stat
*st
)
582 st
->st_mode
= S_IFREG
| 0444;
584 st
->st_size
= m_error_log
.size();
587 virtual int ReadFile(const char *path
, char *buf
, size_t size
, off_t offset
)
589 size_t len
= m_error_log
.size();
591 if( (offset
+ size
) > len
)
593 memcpy(buf
, m_error_log
.data() + offset
, size
);
601 void Log(const std::string
&msg
)
607 const std::string
& GetLog() const { return m_error_log
; }
611 // probe the USB bus for Blackberry devices
612 m_probe
.reset( new Probe
);
614 // connect to all PINs found, and add them to our map
615 for( int i
= 0; i
< m_probe
->GetCount(); i
++ ) {
617 oss
<< hex
<< m_probe
->Get(i
).m_pin
;
619 if( !oss
.str().size() || m_pinmap
.find(oss
.str()) != m_pinmap
.end() ) {
620 // don't add a blank or pre-existing pin
624 DesktopConPtr dev
= DesktopConPtr (
625 new DesktopCon(m_probe
->Get(i
), oss
.str()) );
627 m_pinmap
[ oss
.str() ] = dev
;
631 DesktopCon
* FindPin(PinT pin
)
633 PinMap::iterator pi
= m_pinmap
.find(pin
);
634 return pi
== m_pinmap
.end() ? 0 : pi
->second
.get();
639 /////////////////////////////////////////////////////////////////////////////
642 static void* bfuse_init()
644 // Initialize the barry library. Must be called before
654 catch( std::exception
&e
) {
663 static void bfuse_destroy(void *data
)
666 Context
*ctx
= (Context
*) data
;
671 static int bfuse_getattr(const char *path
, struct stat
*st
)
673 memset(st
, 0, sizeof(*st
));
675 if( Directory
*dir
= FindDir(path
) ) {
676 dir
->FillDirStat(st
);
679 else if( File
*file
= FindFile(path
) ) {
680 file
->FillFileStat(path
, st
);
687 static int bfuse_readdir(const char *path
, void *buf
, fuse_fill_dir_t filler
,
688 off_t
/*offset*/, struct fuse_file_info
* /*fi*/)
690 Directory
*dir
= FindDir(path
);
693 return dir
->ReadDir(buf
, filler
);
696 static int bfuse_open(const char *path
, struct fuse_file_info
*fi
)
698 File
*file
= FindFile(path
);
702 if( !file
->AccessOk(fi
->flags
) )
708 static int bfuse_read(const char *path
, char *buf
, size_t size
, off_t offset
,
709 struct fuse_file_info
*fi
)
711 File
*file
= FindFile(path
);
715 return file
->ReadFile(path
, buf
, size
, offset
);
718 // static struct here automatically zeros data
719 static struct fuse_operations bfuse_oper
;
722 /////////////////////////////////////////////////////////////////////////////
725 int main(int argc
, char *argv
[])
727 cout
.sync_with_stdio(true); // leave this on, since libusb uses
728 // stdio for debug messages
732 // initialize the operation hooks
733 bfuse_oper
.init
= bfuse_init
;
734 bfuse_oper
.destroy
= bfuse_destroy
;
735 bfuse_oper
.getattr
= bfuse_getattr
;
736 bfuse_oper
.readdir
= bfuse_readdir
;
737 bfuse_oper
.open
= bfuse_open
;
738 bfuse_oper
.read
= bfuse_read
;
741 // process command line options
743 int cmd = getopt(argc, argv, "d:hnp:P:");
749 case 'd': // mount dbname
750 dbNames.push_back(string(optarg));
753 case 'n': // use null parser
757 case 'p': // Blackberry PIN
758 pin = strtoul(optarg, NULL, 16);
761 case 'P': // Device password
773 return fuse_main(argc
, argv
, &bfuse_oper
);