ServiceBook: added IConverter support, and general cleanup
[barry.git] / tools / bfuse.cc
blob7aa89fa7990c00d10931cf83ff8fcd3b74fb22d3
1 ///
2 /// \file bfuse.cc
3 /// FUSE filesystem for Blackberry databases, using Barry.
4 ///
6 /*
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
23 #include <fuse.h>
24 #include <fuse_opt.h>
26 #include <barry/barry.h>
27 #include <sstream>
28 #include <getopt.h>
29 #include <vector>
30 #include <list>
31 #include <string>
32 #include <stdexcept>
33 #include <memory>
34 #include <tr1/memory>
35 #include <errno.h>
36 #include <sys/types.h>
37 #include <fcntl.h>
38 #include <string.h>
40 using namespace std;
41 using namespace std::tr1;
42 using namespace Barry;
44 // Global filenames
45 const char *error_log_filename = "error.log";
48 // Data from the command line
51 /////////////////////////////////////////////////////////////////////////////
52 // Command line option handling, through fuse
54 //struct opt {
55 // char
56 //};
58 void Blurb()
60 int major, minor;
61 const char *Version = Barry::Version(major, minor);
63 cerr
64 << "bfuse - FUSE filesystem for Blackberry databases\n"
65 << " Copyright 2008, Net Direct Inc. (http://www.netdirect.ca/)\n"
66 << " Using: " << Version << "\n"
67 << endl;
69 << "\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"
73 << " -h This help\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
86 int m_errno;
87 public:
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
101 uint32_t m_id;
102 std::ostream &m_os;
104 public:
105 explicit DataDumpParser(std::ostream &os)
106 : m_os(os)
110 virtual void Clear() {}
112 virtual void SetIds(uint8_t RecType, uint32_t UniqueId)
114 m_id = 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>
131 struct Store
133 std::ostream &m_os;
135 explicit Store(std::ostream &os)
136 : m_os(os)
140 // storage operator
141 void operator()(const Record &rec)
143 m_os << rec;
147 typedef std::auto_ptr<Barry::Parser> ParserPtr;
149 ParserPtr GetParser(const string &name, std::ostream &os, bool null_parser)
151 if( null_parser ) {
152 // use null parser
153 return ParserPtr( new DataDumpParser(os) );
155 // check for recognized database names
156 else if( name == Contact::GetDBName() ) {
157 return ParserPtr(
158 new RecordParser<Contact, Store<Contact> > (
159 new Store<Contact>(os)));
161 else if( name == Message::GetDBName() ) {
162 return ParserPtr(
163 new RecordParser<Message, Store<Message> > (
164 new Store<Message>(os)));
166 else if( name == Calendar::GetDBName() ) {
167 return ParserPtr(
168 new RecordParser<Calendar, Store<Calendar> > (
169 new Store<Calendar>(os)));
171 else if( name == ServiceBook::GetDBName() ) {
172 return ParserPtr(
173 new RecordParser<ServiceBook, Store<ServiceBook> > (
174 new Store<ServiceBook>(os)));
177 else if( name == Memo::GetDBName() ) {
178 return ParserPtr(
179 new RecordParser<Memo, Store<Memo> > (
180 new Store<Memo>(os)));
182 else if( name == Task::GetDBName() ) {
183 return ParserPtr(
184 new RecordParser<Task, Store<Task> > (
185 new Store<Task>(os)));
187 else if( name == PINMessage::GetDBName() ) {
188 return ParserPtr(
189 new RecordParser<PINMessage, Store<PINMessage> > (
190 new Store<PINMessage>(os)));
192 else if( name == SavedMessage::GetDBName() ) {
193 return ParserPtr(
194 new RecordParser<SavedMessage, Store<SavedMessage> > (
195 new Store<SavedMessage>(os)));
197 else if( name == Folder::GetDBName() ) {
198 return ParserPtr(
199 new RecordParser<Folder, Store<Folder> > (
200 new Store<Folder>(os)));
202 else if( name == Timezone::GetDBName() ) {
203 return ParserPtr(
204 new RecordParser<Timezone, Store<Timezone> > (
205 new Store<Timezone>(os)));
207 else {
208 // unknown database, use null parser
209 return ParserPtr( new DataDumpParser(os) );
213 /////////////////////////////////////////////////////////////////////////////
214 // PathSplit class
216 class PathSplit
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
222 bool m_is_root;
224 public:
225 explicit PathSplit(const char *path)
226 : m_level(-1)
227 , m_is_root(false)
229 if( *path != '/' )
230 return; // return in a failed state
232 if( *(path+1) == 0 ) {
233 m_is_root = true;
234 return;
237 const char *s = path, *e = path;
238 while( *e ) {
239 while( *e && *e != '/' )
240 e++;
242 m_level++;
244 if( s != e && (s+1) != e ) {
245 string token(s+1, e);
247 switch( m_level )
249 case 0: // root level, should not have token here
250 m_level = -1;
251 return; // failed state
253 case 1: // have pin
254 m_pin = token;
255 break;
257 case 2: // have db
258 m_db = token;
259 break;
261 case 3: // have record
262 m_record = token;
263 break;
265 case 4: // have field
266 m_field = token;
267 break;
269 default: // too many, store remainder and done
270 m_remainder = s; // keeps slash
271 return;
274 // next
275 s = e;
276 if( *e )
277 e++;
279 else if( *e ) {
280 // next
281 e++;
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 /////////////////////////////////////////////////////////////////////////////
297 // API classes
299 class Entry
301 public:
302 virtual ~Entry() {}
305 class Directory : public Entry
307 public:
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;
312 st->st_nlink = 2;
316 class File : public Entry
318 public:
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 /////////////////////////////////////////////////////////////////////////////
350 // Context classes
352 class Database : public Directory, public File
354 public:
355 Barry::Mode::Desktop &m_desk;
356 std::string m_name;
357 const Barry::DatabaseItem *m_pdb;
359 public:
360 Database(Barry::Mode::Desktop &desktop,
361 const std::string &pin, const Barry::DatabaseItem *pdb)
362 : m_desk(desktop)
363 , m_pdb(pdb)
365 m_name = string("/") + pin + "/" + m_pdb->Name;
367 // add to directory list
368 g_dirmap[ m_name ] = this;
371 ~Database()
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 ) {
377 g_filemap.erase(b);
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 ) {
408 ostringstream oss;
409 oss << hex << b->second.RecordId;
410 filler(buf, oss.str().c_str(), NULL, 0);
412 AddFile(oss.str());
414 return 0;
417 virtual void FillFileStat(const char *path, struct stat *st)
419 // use the path to find the proper record
420 PathSplit ps(path);
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;
431 st->st_nlink = 1;
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
438 PathSplit ps(path);
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();
449 if( offset < len ) {
450 if( (offset + size) > len )
451 size = len - offset;
452 memcpy(buf, data.data() + offset, size);
454 else {
455 size = 0;
457 return size;
460 const std::string& GetDBName() const { return m_pdb->Name; }
462 std::string GetRecordData(const std::string &recordId)
464 string data;
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) ) {
472 ostringstream oss;
473 ParserPtr parser = GetParser(m_pdb->Name, oss, false);
474 m_desk.GetRecord(m_pdb->Number, index, *parser);
475 data = oss.str();
478 return data;
482 class DesktopCon : public Directory
484 public:
485 typedef std::tr1::shared_ptr<Database> DatabasePtr;
486 typedef std::list<DatabasePtr> DBList;
487 public:
488 Barry::Controller m_con;
489 Barry::Mode::Desktop m_desk;
490 std::string m_pin;
491 DBList m_dblist;
493 DesktopCon(const Barry::ProbeResult &result, const std::string &pin)
494 : m_con(result)
495 , m_desk(m_con)
496 , m_pin(pin)
498 // add to directory list
499 g_dirmap[ string("/") + pin ] = this;
502 ~DesktopCon()
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);
518 return 0;
521 void Open(const char *password = 0)
523 // open our device
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
540 public:
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;
546 ProbePtr m_probe;
547 PinMap m_pinmap;
549 string m_error_log;
551 public:
552 Context()
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";
560 ~Context()
562 g_dirmap.erase("/");
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);
577 return 0;
580 virtual void FillFileStat(const char *path, struct stat *st)
582 st->st_mode = S_IFREG | 0444;
583 st->st_nlink = 1;
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();
590 if( offset < len ) {
591 if( (offset + size) > len )
592 size = len - offset;
593 memcpy(buf, m_error_log.data() + offset, size);
595 else {
596 size = 0;
598 return size;
601 void Log(const std::string &msg)
603 m_error_log += msg;
604 m_error_log += "\n";
607 const std::string& GetLog() const { return m_error_log; }
609 void ProbeAll()
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++ ) {
616 ostringstream oss;
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
621 continue;
624 DesktopConPtr dev = DesktopConPtr (
625 new DesktopCon(m_probe->Get(i), oss.str()) );
626 dev->Open();
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 /////////////////////////////////////////////////////////////////////////////
640 // FUSE API hooks
642 static void* bfuse_init()
644 // Initialize the barry library. Must be called before
645 // anything else.
646 Barry::Init(false);
648 Context *ctx = 0;
650 try {
651 ctx = new Context;
652 ctx->ProbeAll();
654 catch( std::exception &e ) {
655 if( ctx ) {
656 ctx->Log(e.what());
660 return ctx;
663 static void bfuse_destroy(void *data)
665 if( data ) {
666 Context *ctx = (Context*) data;
667 delete ctx;
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);
677 return 0;
679 else if( File *file = FindFile(path) ) {
680 file->FillFileStat(path, st);
681 return 0;
683 else
684 return -ENOENT;
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);
691 if( !dir )
692 return -ENOENT;
693 return dir->ReadDir(buf, filler);
696 static int bfuse_open(const char *path, struct fuse_file_info *fi)
698 File *file = FindFile(path);
699 if( !file )
700 return -ENOENT;
702 if( !file->AccessOk(fi->flags) )
703 return -EACCES;
705 return 0;
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);
712 if( !file )
713 return -ENOENT;
715 return file->ReadFile(path, buf, size, offset);
718 // static struct here automatically zeros data
719 static struct fuse_operations bfuse_oper;
722 /////////////////////////////////////////////////////////////////////////////
723 // main
725 int main(int argc, char *argv[])
727 cout.sync_with_stdio(true); // leave this on, since libusb uses
728 // stdio for debug messages
730 Blurb();
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
742 for(;;) {
743 int cmd = getopt(argc, argv, "d:hnp:P:");
744 if( cmd == -1 )
745 break;
747 switch( cmd )
749 case 'd': // mount dbname
750 dbNames.push_back(string(optarg));
751 break;
753 case 'n': // use null parser
754 null_parser = true;
755 break;
757 case 'p': // Blackberry PIN
758 pin = strtoul(optarg, NULL, 16);
759 break;
761 case 'P': // Device password
762 password = optarg;
763 break;
765 case 'h': // help
766 default:
767 Usage();
768 return 0;
773 return fuse_main(argc, argv, &bfuse_oper);