3 /// FUSE filesystem for Blackberry databases, using Barry.
7 Copyright (C) 2008-2013, 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>
35 #include <sys/types.h>
41 #include "barrygetopt.h"
44 using namespace std::tr1
;
45 using namespace Barry
;
48 const char *error_log_filename
= "error.log";
50 // Global command line args
52 string cmdline_password
;
55 // Data from the command line
58 /////////////////////////////////////////////////////////////////////////////
59 // Command line option handling, through fuse
67 int logical
, major
, minor
;
68 const char *Version
= Barry::Version(logical
, major
, minor
);
70 cerr
<< string_vprintf(
71 _("bfuse - FUSE filesystem for Blackberry databases\n"
72 " Copyright 2008-2013, Net Direct Inc. (http://www.netdirect.ca/)\n"
73 " Using: %s\n"), Version
)
81 "Barry specific options:\n"
82 " -p pin PIN of device to talk with\n"
83 " If only one device is plugged in, this flag is optional\n"
84 " -P pass Simplistic method to specify device password\n")
87 << " -d db Specify which database to mount. If no -d options exist\n"
88 << " then all databases will be mounted.\n"
89 << " Can be used multiple times to mount more than one DB\n"
91 << " -n Use null parser on all databases.\n"
95 /////////////////////////////////////////////////////////////////////////////
96 // FUSE specific exception
98 class fuse_error
: public std::runtime_error
102 fuse_error(int errno_
, const std::string
&msg
)
103 : std::runtime_error(msg
), m_errno(errno_
)
106 int get_errno() const { return m_errno
; }
110 /////////////////////////////////////////////////////////////////////////////
111 // Barry record parsers
113 class DataDumpParser
: public Barry::Parser
119 explicit DataDumpParser(std::ostream
&os
)
124 virtual void ParseRecord(const Barry::DBData
&data
,
125 const Barry::IConverter
*ic
)
127 m_id
= data
.GetUniqueId();
128 m_os
<< _("Raw record dump for record: ")
129 << std::hex
<< m_id
<< std::endl
;
130 m_os
<< data
.GetData() << std::endl
;
134 template <class Record
>
139 explicit Store(std::ostream
&os
)
145 void operator()(const Record
&rec
)
151 typedef std::auto_ptr
<Barry::Parser
> ParserPtr
;
153 ParserPtr
GetParser(const string
&name
, std::ostream
&os
, bool null_parser
)
157 return ParserPtr( new DataDumpParser(os
) );
159 // check for recognized database names
160 else if( name
== Contact::GetDBName() ) {
162 new RecordParser
<Contact
, Store
<Contact
> > (
163 new Store
<Contact
>(os
)));
165 else if( name
== Message::GetDBName() ) {
167 new RecordParser
<Message
, Store
<Message
> > (
168 new Store
<Message
>(os
)));
170 else if( name
== Calendar::GetDBName() ) {
172 new RecordParser
<Calendar
, Store
<Calendar
> > (
173 new Store
<Calendar
>(os
)));
175 else if( name
== CalendarAll::GetDBName() ) {
177 new RecordParser
<CalendarAll
, Store
<CalendarAll
> > (
178 new Store
<CalendarAll
>(os
)));
180 else if( name
== ServiceBook::GetDBName() ) {
182 new RecordParser
<ServiceBook
, Store
<ServiceBook
> > (
183 new Store
<ServiceBook
>(os
)));
186 else if( name
== Memo::GetDBName() ) {
188 new RecordParser
<Memo
, Store
<Memo
> > (
189 new Store
<Memo
>(os
)));
191 else if( name
== Task::GetDBName() ) {
193 new RecordParser
<Task
, Store
<Task
> > (
194 new Store
<Task
>(os
)));
196 else if( name
== PINMessage::GetDBName() ) {
198 new RecordParser
<PINMessage
, Store
<PINMessage
> > (
199 new Store
<PINMessage
>(os
)));
201 else if( name
== SavedMessage::GetDBName() ) {
203 new RecordParser
<SavedMessage
, Store
<SavedMessage
> > (
204 new Store
<SavedMessage
>(os
)));
206 else if( name
== Folder::GetDBName() ) {
208 new RecordParser
<Folder
, Store
<Folder
> > (
209 new Store
<Folder
>(os
)));
211 else if( name
== TimeZone::GetDBName() ) {
213 new RecordParser
<TimeZone
, Store
<TimeZone
> > (
214 new Store
<TimeZone
>(os
)));
217 // unknown database, use null parser
218 return ParserPtr( new DataDumpParser(os
) );
222 /////////////////////////////////////////////////////////////////////////////
227 std::string m_pin
, m_db
, m_record
, m_field
, m_remainder
;
229 int m_level
; // the number of slashes, minus the first
230 // i.e. first level is 0
234 explicit PathSplit(const char *path
)
239 return; // return in a failed state
241 if( *(path
+1) == 0 ) {
246 const char *s
= path
, *e
= path
;
248 while( *e
&& *e
!= '/' )
253 if( s
!= e
&& (s
+1) != e
) {
254 string
token(s
+1, e
);
258 case 0: // root level, should not have token here
260 return; // failed state
270 case 3: // have record
274 case 4: // have field
278 default: // too many, store remainder and done
279 m_remainder
= s
; // keeps slash
295 bool IsRoot() const { return m_is_root
; }
296 const std::string
& Pin() const { return m_pin
; }
297 const std::string
& DB() const { return m_db
; }
298 const std::string
& Record() const { return m_record
; }
299 const std::string
& Field() const { return m_field
; }
300 const std::string
& Remainder() const { return m_remainder
; }
301 int Level() const { return m_level
; }
305 /////////////////////////////////////////////////////////////////////////////
314 class Directory
: public Entry
317 virtual int ReadDir(void *buf
, fuse_fill_dir_t filler
) = 0;
318 virtual void FillDirStat(struct stat
*st
)
320 st
->st_mode
= S_IFDIR
| 0555;
325 class File
: public Entry
328 virtual void FillFileStat(const char *path
, struct stat
*st
) = 0;
329 virtual bool AccessOk(int flags
)
331 // default to readonly files
332 return (flags
& (O_RDONLY
| O_WRONLY
| O_RDWR
)) == O_RDONLY
;
334 virtual int ReadFile(const char *path
, char *buf
, size_t size
, off_t offset
) = 0;
337 typedef Directory
* DirectoryPtr
;
338 typedef File
* FilePtr
;
339 typedef std::string NameT
;
340 typedef std::map
<NameT
, DirectoryPtr
> DirMap
;
341 typedef std::map
<NameT
, FilePtr
> FileMap
;
343 static DirMap g_dirmap
;
344 static FileMap g_filemap
;
346 static Directory
* FindDir(const NameT
&name
)
348 DirMap::iterator di
= g_dirmap
.find(name
);
349 return di
== g_dirmap
.end() ? 0 : di
->second
;
352 static File
* FindFile(const NameT
&name
)
354 FileMap::iterator fi
= g_filemap
.find(name
);
355 return fi
== g_filemap
.end() ? 0 : fi
->second
;
358 /////////////////////////////////////////////////////////////////////////////
361 class Database
: public Directory
, public File
364 Barry::Mode::Desktop
&m_desk
;
366 const Barry::DatabaseItem
*m_pdb
;
369 Database(Barry::Mode::Desktop
&desktop
,
370 const std::string
&pin
, const Barry::DatabaseItem
*pdb
)
374 m_name
= string("/") + pin
+ "/" + m_pdb
->Name
;
376 // add to directory list
377 g_dirmap
[ m_name
] = this;
382 // remove any entries that point to us
383 FileMap::iterator b
= g_filemap
.begin(), e
= g_filemap
.end();
385 // we advance the iterator inside the loop
386 // so that we can take advantage of this
387 // nice safe construct: .erase(b++);
388 // otherwise, b will not be safe to use after
389 // the erase(), but using it together with the
390 // erase function call, it keeps a copy of the
391 // old b to erase, then advances the iterator,
392 // then calls erase()
393 if( b
->second
== this ) {
394 g_filemap
.erase(b
++);
401 // erase ourselves from the directory list
402 g_dirmap
.erase( m_name
);
405 void AddFile(const std::string
&recordId
)
407 // FIXME - this is a hack to redirect all record files
408 // to this database class... next step is to possibly
409 // split out records into field files if we have a
410 // parser, or just dump the hex if we don't
411 string name
= m_name
+ "/" + recordId
;
412 g_filemap
[ name
] = this;
415 virtual int ReadDir(void *buf
, fuse_fill_dir_t filler
)
417 filler(buf
, ".", NULL
, 0);
418 filler(buf
, "..", NULL
, 0);
420 // list all records in database, by recordId
421 Barry::RecordStateTable rst
;
422 m_desk
.GetRecordStateTable(m_pdb
->Number
, rst
);
424 Barry::RecordStateTable::StateMapType::iterator
425 b
= rst
.StateMap
.begin(),
426 e
= rst
.StateMap
.end();
427 for( ; b
!= e
; ++ b
) {
429 oss
<< hex
<< b
->second
.RecordId
;
430 filler(buf
, oss
.str().c_str(), NULL
, 0);
437 virtual void FillFileStat(const char *path
, struct stat
*st
)
439 // use the path to find the proper record
442 string constructed
= string("/") + ps
.Pin() + "/" + ps
.DB();
443 if( constructed
!= m_name
) {
444 // FIXME - this is shoddy error handling
445 throw std::logic_error(_("Constructed != name"));
448 string data
= GetRecordData(ps
.Record());
450 st
->st_mode
= S_IFREG
| 0444;
452 st
->st_size
= data
.size();
455 virtual int ReadFile(const char *path
, char *buf
, size_t size
, off_t offset
)
457 // use the path to find the proper record
460 string constructed
= string("/") + ps
.Pin() + "/" + ps
.DB();
461 if( constructed
!= m_name
) {
462 // FIXME - this is shoddy error handling
463 throw std::logic_error(_("Constructed != name"));
466 string data
= GetRecordData(ps
.Record());
468 size_t len
= data
.size();
469 if( offset
>= 0 && offset
< (off_t
)len
) {
470 if( (offset
+ size
) > len
)
472 memcpy(buf
, data
.data() + offset
, size
);
480 const std::string
& GetDBName() const { return m_pdb
->Name
; }
482 std::string
GetRecordData(const std::string
&recordId
)
486 Barry::RecordStateTable rst
;
487 m_desk
.GetRecordStateTable(m_pdb
->Number
, rst
);
489 uint32_t recid
= strtoul(recordId
.c_str(), NULL
, 16);
490 RecordStateTable::IndexType index
;
491 if( rst
.GetIndex(recid
, &index
) ) {
493 ParserPtr parser
= GetParser(m_pdb
->Name
, oss
, false);
494 m_desk
.GetRecord(m_pdb
->Number
, index
, *parser
);
502 class DesktopCon
: public Directory
505 typedef std::tr1::shared_ptr
<Database
> DatabasePtr
;
506 typedef std::list
<DatabasePtr
> DBList
;
508 Barry::Controller m_con
;
509 Barry::Mode::Desktop m_desk
;
513 DesktopCon(const Barry::ProbeResult
&result
, const std::string
&pin
)
518 // add to directory list
519 g_dirmap
[ string("/") + pin
] = this;
524 // remove from directory list
525 g_dirmap
.erase( string("/") + m_pin
);
528 virtual int ReadDir(void *buf
, fuse_fill_dir_t filler
)
530 filler(buf
, ".", NULL
, 0);
531 filler(buf
, "..", NULL
, 0);
533 // list all databases in list
534 DBList::const_iterator b
= m_dblist
.begin(), e
= m_dblist
.end();
535 for( ; b
!= e
; ++ b
) {
536 filler(buf
, (*b
)->GetDBName().c_str(), NULL
, 0);
541 void Open(const char *password
= 0)
544 m_desk
.Open(password
);
546 // add all databases as directories
547 DatabaseDatabase::DatabaseArrayType::const_iterator
548 dbi
= m_desk
.GetDBDB().Databases
.begin(),
549 dbe
= m_desk
.GetDBDB().Databases
.end();
550 for( ; dbi
!= dbe
; ++dbi
) {
551 DatabasePtr db
= DatabasePtr(
552 new Database(m_desk
, m_pin
, &(*dbi
)) );
553 m_dblist
.push_back(db
);
558 class Context
: public Directory
, public File
561 typedef std::auto_ptr
<Barry::Probe
> ProbePtr
;
562 typedef std::tr1::shared_ptr
<DesktopCon
> DesktopConPtr
;
563 typedef std::string PinT
;
564 typedef std::map
<PinT
, DesktopConPtr
> PinMap
;
571 string m_limit_pin
; // only mount device with this pin
572 string m_password
; // use this password when connecting
575 Context(const string
&limit_pin
= "", const string
&password
= "")
576 : m_limit_pin(limit_pin
)
577 , m_password(password
)
579 g_dirmap
["/"] = this;
580 g_filemap
[string("/") + error_log_filename
] = this;
582 m_error_log
= _("Hello FUSE world. This is Barry. Pleased to meet you.\n");
588 g_filemap
.erase(string("/") + error_log_filename
);
591 virtual int ReadDir(void *buf
, fuse_fill_dir_t filler
)
593 filler(buf
, ".", NULL
, 0);
594 filler(buf
, "..", NULL
, 0);
595 filler(buf
, error_log_filename
, NULL
, 0);
597 // list all pins in map
598 PinMap::const_iterator b
= m_pinmap
.begin(), e
= m_pinmap
.end();
599 for( ; b
!= e
; ++ b
) {
600 filler(buf
, b
->first
.c_str(), NULL
, 0);
605 virtual void FillFileStat(const char *path
, struct stat
*st
)
607 st
->st_mode
= S_IFREG
| 0444;
609 st
->st_size
= m_error_log
.size();
612 virtual int ReadFile(const char *path
, char *buf
, size_t size
, off_t offset
)
614 size_t len
= m_error_log
.size();
615 if( offset
>= 0 && offset
< (off_t
)len
) {
616 if( (offset
+ size
) > len
)
618 memcpy(buf
, m_error_log
.data() + offset
, size
);
626 void Log(const std::string
&msg
)
632 const std::string
& GetLog() const { return m_error_log
; }
636 // probe the USB bus for Blackberry devices
637 m_probe
.reset( new Probe
);
639 // connect to all PINs found, and add them to our map
640 for( int i
= 0; i
< m_probe
->GetCount(); i
++ ) {
641 string curpin
= m_probe
->Get(i
).m_pin
.Str();
643 // don't add a blank or pre-existing pin
644 if( !curpin
.size() || m_pinmap
.find(curpin
) != m_pinmap
.end() ) {
648 // don't add non-PIN device if pin specified
649 if( m_limit_pin
.size() && curpin
!= m_limit_pin
) {
653 DesktopConPtr dev
= DesktopConPtr (
654 new DesktopCon(m_probe
->Get(i
), curpin
) );
655 dev
->Open(m_password
.c_str());
656 m_pinmap
[ curpin
] = dev
;
660 DesktopCon
* FindPin(PinT pin
)
662 PinMap::iterator pi
= m_pinmap
.find(pin
);
663 return pi
== m_pinmap
.end() ? 0 : pi
->second
.get();
668 /////////////////////////////////////////////////////////////////////////////
671 static void* bfuse_init()
673 // Initialize the barry library. Must be called before
680 ctx
= new Context(cmdline_pin
, cmdline_password
);
683 catch( std::exception
&e
) {
692 static void bfuse_destroy(void *data
)
695 Context
*ctx
= (Context
*) data
;
700 static int bfuse_getattr(const char *path
, struct stat
*st
)
702 memset(st
, 0, sizeof(*st
));
704 if( Directory
*dir
= FindDir(path
) ) {
705 dir
->FillDirStat(st
);
708 else if( File
*file
= FindFile(path
) ) {
709 file
->FillFileStat(path
, st
);
716 static int bfuse_readdir(const char *path
, void *buf
, fuse_fill_dir_t filler
,
717 off_t
/*offset*/, struct fuse_file_info
* /*fi*/)
719 Directory
*dir
= FindDir(path
);
722 return dir
->ReadDir(buf
, filler
);
725 static int bfuse_open(const char *path
, struct fuse_file_info
*fi
)
727 File
*file
= FindFile(path
);
731 if( !file
->AccessOk(fi
->flags
) )
737 static int bfuse_read(const char *path
, char *buf
, size_t size
, off_t offset
,
738 struct fuse_file_info
*fi
)
740 File
*file
= FindFile(path
);
744 return file
->ReadFile(path
, buf
, size
, offset
);
747 // static struct here automatically zeros data
748 static struct fuse_operations bfuse_oper
;
751 /////////////////////////////////////////////////////////////////////////////
754 int main(int argc
, char *argv
[])
758 cout
.sync_with_stdio(true); // leave this on, since libusb uses
759 // stdio for debug messages
763 // initialize the operation hooks
764 bfuse_oper
.init
= bfuse_init
;
765 bfuse_oper
.destroy
= bfuse_destroy
;
766 bfuse_oper
.getattr
= bfuse_getattr
;
767 bfuse_oper
.readdir
= bfuse_readdir
;
768 bfuse_oper
.open
= bfuse_open
;
769 bfuse_oper
.read
= bfuse_read
;
771 // process command line options before FUSE does
772 // FUSE does its own command line processing, and
773 // doesn't seem to have a way to plug into it,
774 // so do our own first
776 char **fuse_argv
= new char*[argc
];
778 for( int i
= 0; i
< argc
; i
++ ) {
779 if( argv
[i
][0] == '-' ) {
783 // case 'd': // mount dbname
784 // dbNames.push_back(string(optarg));
787 // case 'n': // use null parser
788 // null_parser = true;
791 case 'p': // Blackberry PIN
793 cmdline_pin
= argv
[++i
];
797 case 'P': // Device password
799 cmdline_password
= argv
[++i
];
809 // if we get here, add this option to FUSE's
810 fuse_argv
[fuse_argc
] = argv
[i
];
814 int ret
= fuse_main(fuse_argc
, fuse_argv
, &bfuse_oper
);