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();
384 for( ; b
!= e
; ++b
) {
385 if( b
->second
== this ) {
390 // erase ourselves from the directory list
391 g_dirmap
.erase( m_name
);
394 void AddFile(const std::string
&recordId
)
396 // FIXME - this is a hack to redirect all record files
397 // to this database class... next step is to possibly
398 // split out records into field files if we have a
399 // parser, or just dump the hex if we don't
400 string name
= m_name
+ "/" + recordId
;
401 g_filemap
[ name
] = this;
404 virtual int ReadDir(void *buf
, fuse_fill_dir_t filler
)
406 filler(buf
, ".", NULL
, 0);
407 filler(buf
, "..", NULL
, 0);
409 // list all records in database, by recordId
410 Barry::RecordStateTable rst
;
411 m_desk
.GetRecordStateTable(m_pdb
->Number
, rst
);
413 Barry::RecordStateTable::StateMapType::iterator
414 b
= rst
.StateMap
.begin(),
415 e
= rst
.StateMap
.end();
416 for( ; b
!= e
; ++ b
) {
418 oss
<< hex
<< b
->second
.RecordId
;
419 filler(buf
, oss
.str().c_str(), NULL
, 0);
426 virtual void FillFileStat(const char *path
, struct stat
*st
)
428 // use the path to find the proper record
431 string constructed
= string("/") + ps
.Pin() + "/" + ps
.DB();
432 if( constructed
!= m_name
) {
433 // FIXME - this is shoddy error handling
434 throw std::logic_error(_("Constructed != name"));
437 string data
= GetRecordData(ps
.Record());
439 st
->st_mode
= S_IFREG
| 0444;
441 st
->st_size
= data
.size();
444 virtual int ReadFile(const char *path
, char *buf
, size_t size
, off_t offset
)
446 // use the path to find the proper record
449 string constructed
= string("/") + ps
.Pin() + "/" + ps
.DB();
450 if( constructed
!= m_name
) {
451 // FIXME - this is shoddy error handling
452 throw std::logic_error(_("Constructed != name"));
455 string data
= GetRecordData(ps
.Record());
457 size_t len
= data
.size();
458 if( offset
>= 0 && offset
< (off_t
)len
) {
459 if( (offset
+ size
) > len
)
461 memcpy(buf
, data
.data() + offset
, size
);
469 const std::string
& GetDBName() const { return m_pdb
->Name
; }
471 std::string
GetRecordData(const std::string
&recordId
)
475 Barry::RecordStateTable rst
;
476 m_desk
.GetRecordStateTable(m_pdb
->Number
, rst
);
478 uint32_t recid
= strtoul(recordId
.c_str(), NULL
, 16);
479 RecordStateTable::IndexType index
;
480 if( rst
.GetIndex(recid
, &index
) ) {
482 ParserPtr parser
= GetParser(m_pdb
->Name
, oss
, false);
483 m_desk
.GetRecord(m_pdb
->Number
, index
, *parser
);
491 class DesktopCon
: public Directory
494 typedef std::tr1::shared_ptr
<Database
> DatabasePtr
;
495 typedef std::list
<DatabasePtr
> DBList
;
497 Barry::Controller m_con
;
498 Barry::Mode::Desktop m_desk
;
502 DesktopCon(const Barry::ProbeResult
&result
, const std::string
&pin
)
507 // add to directory list
508 g_dirmap
[ string("/") + pin
] = this;
513 // remove from directory list
514 g_dirmap
.erase( string("/") + m_pin
);
517 virtual int ReadDir(void *buf
, fuse_fill_dir_t filler
)
519 filler(buf
, ".", NULL
, 0);
520 filler(buf
, "..", NULL
, 0);
522 // list all databases in list
523 DBList::const_iterator b
= m_dblist
.begin(), e
= m_dblist
.end();
524 for( ; b
!= e
; ++ b
) {
525 filler(buf
, (*b
)->GetDBName().c_str(), NULL
, 0);
530 void Open(const char *password
= 0)
533 m_desk
.Open(password
);
535 // add all databases as directories
536 DatabaseDatabase::DatabaseArrayType::const_iterator
537 dbi
= m_desk
.GetDBDB().Databases
.begin(),
538 dbe
= m_desk
.GetDBDB().Databases
.end();
539 for( ; dbi
!= dbe
; ++dbi
) {
540 DatabasePtr db
= DatabasePtr(
541 new Database(m_desk
, m_pin
, &(*dbi
)) );
542 m_dblist
.push_back(db
);
547 class Context
: public Directory
, public File
550 typedef std::auto_ptr
<Barry::Probe
> ProbePtr
;
551 typedef std::tr1::shared_ptr
<DesktopCon
> DesktopConPtr
;
552 typedef std::string PinT
;
553 typedef std::map
<PinT
, DesktopConPtr
> PinMap
;
560 string m_limit_pin
; // only mount device with this pin
561 string m_password
; // use this password when connecting
564 Context(const string
&limit_pin
= "", const string
&password
= "")
565 : m_limit_pin(limit_pin
)
566 , m_password(password
)
568 g_dirmap
["/"] = this;
569 g_filemap
[string("/") + error_log_filename
] = this;
571 m_error_log
= _("Hello FUSE world. This is Barry. Pleased to meet you.\n");
577 g_filemap
.erase(string("/") + error_log_filename
);
580 virtual int ReadDir(void *buf
, fuse_fill_dir_t filler
)
582 filler(buf
, ".", NULL
, 0);
583 filler(buf
, "..", NULL
, 0);
584 filler(buf
, error_log_filename
, NULL
, 0);
586 // list all pins in map
587 PinMap::const_iterator b
= m_pinmap
.begin(), e
= m_pinmap
.end();
588 for( ; b
!= e
; ++ b
) {
589 filler(buf
, b
->first
.c_str(), NULL
, 0);
594 virtual void FillFileStat(const char *path
, struct stat
*st
)
596 st
->st_mode
= S_IFREG
| 0444;
598 st
->st_size
= m_error_log
.size();
601 virtual int ReadFile(const char *path
, char *buf
, size_t size
, off_t offset
)
603 size_t len
= m_error_log
.size();
604 if( offset
>= 0 && offset
< (off_t
)len
) {
605 if( (offset
+ size
) > len
)
607 memcpy(buf
, m_error_log
.data() + offset
, size
);
615 void Log(const std::string
&msg
)
621 const std::string
& GetLog() const { return m_error_log
; }
625 // probe the USB bus for Blackberry devices
626 m_probe
.reset( new Probe
);
628 // connect to all PINs found, and add them to our map
629 for( int i
= 0; i
< m_probe
->GetCount(); i
++ ) {
630 string curpin
= m_probe
->Get(i
).m_pin
.Str();
632 // don't add a blank or pre-existing pin
633 if( !curpin
.size() || m_pinmap
.find(curpin
) != m_pinmap
.end() ) {
637 // don't add non-PIN device if pin specified
638 if( m_limit_pin
.size() && curpin
!= m_limit_pin
) {
642 DesktopConPtr dev
= DesktopConPtr (
643 new DesktopCon(m_probe
->Get(i
), curpin
) );
644 dev
->Open(m_password
.c_str());
645 m_pinmap
[ curpin
] = dev
;
649 DesktopCon
* FindPin(PinT pin
)
651 PinMap::iterator pi
= m_pinmap
.find(pin
);
652 return pi
== m_pinmap
.end() ? 0 : pi
->second
.get();
657 /////////////////////////////////////////////////////////////////////////////
660 static void* bfuse_init()
662 // Initialize the barry library. Must be called before
669 ctx
= new Context(cmdline_pin
, cmdline_password
);
672 catch( std::exception
&e
) {
681 static void bfuse_destroy(void *data
)
684 Context
*ctx
= (Context
*) data
;
689 static int bfuse_getattr(const char *path
, struct stat
*st
)
691 memset(st
, 0, sizeof(*st
));
693 if( Directory
*dir
= FindDir(path
) ) {
694 dir
->FillDirStat(st
);
697 else if( File
*file
= FindFile(path
) ) {
698 file
->FillFileStat(path
, st
);
705 static int bfuse_readdir(const char *path
, void *buf
, fuse_fill_dir_t filler
,
706 off_t
/*offset*/, struct fuse_file_info
* /*fi*/)
708 Directory
*dir
= FindDir(path
);
711 return dir
->ReadDir(buf
, filler
);
714 static int bfuse_open(const char *path
, struct fuse_file_info
*fi
)
716 File
*file
= FindFile(path
);
720 if( !file
->AccessOk(fi
->flags
) )
726 static int bfuse_read(const char *path
, char *buf
, size_t size
, off_t offset
,
727 struct fuse_file_info
*fi
)
729 File
*file
= FindFile(path
);
733 return file
->ReadFile(path
, buf
, size
, offset
);
736 // static struct here automatically zeros data
737 static struct fuse_operations bfuse_oper
;
740 /////////////////////////////////////////////////////////////////////////////
743 int main(int argc
, char *argv
[])
747 cout
.sync_with_stdio(true); // leave this on, since libusb uses
748 // stdio for debug messages
752 // initialize the operation hooks
753 bfuse_oper
.init
= bfuse_init
;
754 bfuse_oper
.destroy
= bfuse_destroy
;
755 bfuse_oper
.getattr
= bfuse_getattr
;
756 bfuse_oper
.readdir
= bfuse_readdir
;
757 bfuse_oper
.open
= bfuse_open
;
758 bfuse_oper
.read
= bfuse_read
;
760 // process command line options before FUSE does
761 // FUSE does its own command line processing, and
762 // doesn't seem to have a way to plug into it,
763 // so do our own first
765 char **fuse_argv
= new char*[argc
];
767 for( int i
= 0; i
< argc
; i
++ ) {
768 if( argv
[i
][0] == '-' ) {
772 // case 'd': // mount dbname
773 // dbNames.push_back(string(optarg));
776 // case 'n': // use null parser
777 // null_parser = true;
780 case 'p': // Blackberry PIN
782 cmdline_pin
= argv
[++i
];
786 case 'P': // Device password
788 cmdline_password
= argv
[++i
];
798 // if we get here, add this option to FUSE's
799 fuse_argv
[fuse_argc
] = argv
[i
];
803 int ret
= fuse_main(fuse_argc
, fuse_argv
, &bfuse_oper
);