Add back the clarified logging from commit c7685942140b123bf11
[barry.git] / tools / bfuse.cc
blob25714614831627cc31572d90d9905eb7917968e3
1 ///
2 /// \file bfuse.cc
3 /// FUSE filesystem for Blackberry databases, using Barry.
4 ///
6 /*
7 Copyright (C) 2008-2010, 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>
39 #include "i18n.h"
41 using namespace std;
42 using namespace std::tr1;
43 using namespace Barry;
45 // Global filenames
46 const char *error_log_filename = "error.log";
48 // Global command line args
49 string cmdline_pin;
50 string cmdline_password;
53 // Data from the command line
56 /////////////////////////////////////////////////////////////////////////////
57 // Command line option handling, through fuse
59 //struct opt {
60 // char
61 //};
63 void Blurb()
65 int major, minor;
66 const char *Version = Barry::Version(major, minor);
68 cerr
69 << "bfuse - FUSE filesystem for Blackberry databases\n"
70 << " Copyright 2008-2010, Net Direct Inc. (http://www.netdirect.ca/)\n"
71 << " Using: " << Version << "\n"
72 << endl;
75 void Usage()
77 cerr
78 << "\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"
83 << endl;
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"
88 << " -h This help\n"
89 << " -n Use null parser on all databases.\n"
93 /////////////////////////////////////////////////////////////////////////////
94 // FUSE specific exception
96 class fuse_error : public std::runtime_error
98 int m_errno;
99 public:
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
113 uint32_t m_id;
114 std::ostream &m_os;
116 public:
117 explicit DataDumpParser(std::ostream &os)
118 : m_os(os)
122 virtual void Clear() {}
124 virtual void SetIds(const std::string &DbName,
125 uint8_t RecType, uint32_t UniqueId)
127 m_id = UniqueId;
130 virtual void ParseHeader(const Barry::Data &, size_t &) {}
132 virtual void ParseFields(const Barry::Data &data, size_t &offset,
133 const Barry::IConverter *ic)
135 m_os << "Raw record dump for record: "
136 << std::hex << m_id << std::endl;
137 m_os << data << std::endl;
140 virtual void Store() {}
143 template <class Record>
144 struct Store
146 std::ostream &m_os;
148 explicit Store(std::ostream &os)
149 : m_os(os)
153 // storage operator
154 void operator()(const Record &rec)
156 m_os << rec;
160 typedef std::auto_ptr<Barry::Parser> ParserPtr;
162 ParserPtr GetParser(const string &name, std::ostream &os, bool null_parser)
164 if( null_parser ) {
165 // use null parser
166 return ParserPtr( new DataDumpParser(os) );
168 // check for recognized database names
169 else if( name == Contact::GetDBName() ) {
170 return ParserPtr(
171 new RecordParser<Contact, Store<Contact> > (
172 new Store<Contact>(os)));
174 else if( name == Message::GetDBName() ) {
175 return ParserPtr(
176 new RecordParser<Message, Store<Message> > (
177 new Store<Message>(os)));
179 else if( name == Calendar::GetDBName() ) {
180 return ParserPtr(
181 new RecordParser<Calendar, Store<Calendar> > (
182 new Store<Calendar>(os)));
184 else if( name == CalendarAll::GetDBName() ) {
185 return ParserPtr(
186 new RecordParser<CalendarAll, Store<CalendarAll> > (
187 new Store<CalendarAll>(os)));
189 else if( name == ServiceBook::GetDBName() ) {
190 return ParserPtr(
191 new RecordParser<ServiceBook, Store<ServiceBook> > (
192 new Store<ServiceBook>(os)));
195 else if( name == Memo::GetDBName() ) {
196 return ParserPtr(
197 new RecordParser<Memo, Store<Memo> > (
198 new Store<Memo>(os)));
200 else if( name == Task::GetDBName() ) {
201 return ParserPtr(
202 new RecordParser<Task, Store<Task> > (
203 new Store<Task>(os)));
205 else if( name == PINMessage::GetDBName() ) {
206 return ParserPtr(
207 new RecordParser<PINMessage, Store<PINMessage> > (
208 new Store<PINMessage>(os)));
210 else if( name == SavedMessage::GetDBName() ) {
211 return ParserPtr(
212 new RecordParser<SavedMessage, Store<SavedMessage> > (
213 new Store<SavedMessage>(os)));
215 else if( name == Folder::GetDBName() ) {
216 return ParserPtr(
217 new RecordParser<Folder, Store<Folder> > (
218 new Store<Folder>(os)));
220 else if( name == Timezone::GetDBName() ) {
221 return ParserPtr(
222 new RecordParser<Timezone, Store<Timezone> > (
223 new Store<Timezone>(os)));
225 else {
226 // unknown database, use null parser
227 return ParserPtr( new DataDumpParser(os) );
231 /////////////////////////////////////////////////////////////////////////////
232 // PathSplit class
234 class PathSplit
236 std::string m_pin, m_db, m_record, m_field, m_remainder;
238 int m_level; // the number of slashes, minus the first
239 // i.e. first level is 0
240 bool m_is_root;
242 public:
243 explicit PathSplit(const char *path)
244 : m_level(-1)
245 , m_is_root(false)
247 if( *path != '/' )
248 return; // return in a failed state
250 if( *(path+1) == 0 ) {
251 m_is_root = true;
252 return;
255 const char *s = path, *e = path;
256 while( *e ) {
257 while( *e && *e != '/' )
258 e++;
260 m_level++;
262 if( s != e && (s+1) != e ) {
263 string token(s+1, e);
265 switch( m_level )
267 case 0: // root level, should not have token here
268 m_level = -1;
269 return; // failed state
271 case 1: // have pin
272 m_pin = token;
273 break;
275 case 2: // have db
276 m_db = token;
277 break;
279 case 3: // have record
280 m_record = token;
281 break;
283 case 4: // have field
284 m_field = token;
285 break;
287 default: // too many, store remainder and done
288 m_remainder = s; // keeps slash
289 return;
292 // next
293 s = e;
294 if( *e )
295 e++;
297 else if( *e ) {
298 // next
299 e++;
304 bool IsRoot() const { return m_is_root; }
305 const std::string& Pin() const { return m_pin; }
306 const std::string& DB() const { return m_db; }
307 const std::string& Record() const { return m_record; }
308 const std::string& Field() const { return m_field; }
309 const std::string& Remainder() const { return m_remainder; }
310 int Level() const { return m_level; }
314 /////////////////////////////////////////////////////////////////////////////
315 // API classes
317 class Entry
319 public:
320 virtual ~Entry() {}
323 class Directory : public Entry
325 public:
326 virtual int ReadDir(void *buf, fuse_fill_dir_t filler) = 0;
327 virtual void FillDirStat(struct stat *st)
329 st->st_mode = S_IFDIR | 0555;
330 st->st_nlink = 2;
334 class File : public Entry
336 public:
337 virtual void FillFileStat(const char *path, struct stat *st) = 0;
338 virtual bool AccessOk(int flags)
340 // default to readonly files
341 return (flags & (O_RDONLY | O_WRONLY | O_RDWR)) == O_RDONLY;
343 virtual int ReadFile(const char *path, char *buf, size_t size, off_t offset) = 0;
346 typedef Directory* DirectoryPtr;
347 typedef File* FilePtr;
348 typedef std::string NameT;
349 typedef std::map<NameT, DirectoryPtr> DirMap;
350 typedef std::map<NameT, FilePtr> FileMap;
352 static DirMap g_dirmap;
353 static FileMap g_filemap;
355 static Directory* FindDir(const NameT &name)
357 DirMap::iterator di = g_dirmap.find(name);
358 return di == g_dirmap.end() ? 0 : di->second;
361 static File* FindFile(const NameT &name)
363 FileMap::iterator fi = g_filemap.find(name);
364 return fi == g_filemap.end() ? 0 : fi->second;
367 /////////////////////////////////////////////////////////////////////////////
368 // Context classes
370 class Database : public Directory, public File
372 public:
373 Barry::Mode::Desktop &m_desk;
374 std::string m_name;
375 const Barry::DatabaseItem *m_pdb;
377 public:
378 Database(Barry::Mode::Desktop &desktop,
379 const std::string &pin, const Barry::DatabaseItem *pdb)
380 : m_desk(desktop)
381 , m_pdb(pdb)
383 m_name = string("/") + pin + "/" + m_pdb->Name;
385 // add to directory list
386 g_dirmap[ m_name ] = this;
389 ~Database()
391 // remove any entries that point to us
392 FileMap::iterator b = g_filemap.begin(), e = g_filemap.end();
393 for( ; b != e; ++b ) {
394 if( b->second == this ) {
395 g_filemap.erase(b);
399 // erase ourselves from the directory list
400 g_dirmap.erase( m_name );
403 void AddFile(const std::string &recordId)
405 // FIXME - this is a hack to redirect all record files
406 // to this database class... next step is to possibly
407 // split out records into field files if we have a
408 // parser, or just dump the hex if we don't
409 string name = m_name + "/" + recordId;
410 g_filemap[ name ] = this;
413 virtual int ReadDir(void *buf, fuse_fill_dir_t filler)
415 filler(buf, ".", NULL, 0);
416 filler(buf, "..", NULL, 0);
418 // list all records in database, by recordId
419 Barry::RecordStateTable rst;
420 m_desk.GetRecordStateTable(m_pdb->Number, rst);
422 Barry::RecordStateTable::StateMapType::iterator
423 b = rst.StateMap.begin(),
424 e = rst.StateMap.end();
425 for( ; b != e; ++ b ) {
426 ostringstream oss;
427 oss << hex << b->second.RecordId;
428 filler(buf, oss.str().c_str(), NULL, 0);
430 AddFile(oss.str());
432 return 0;
435 virtual void FillFileStat(const char *path, struct stat *st)
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 st->st_mode = S_IFREG | 0444;
449 st->st_nlink = 1;
450 st->st_size = data.size();
453 virtual int ReadFile(const char *path, char *buf, size_t size, off_t offset)
455 // use the path to find the proper record
456 PathSplit ps(path);
458 string constructed = string("/") + ps.Pin() + "/" + ps.DB();
459 if( constructed != m_name ) {
460 // FIXME - this is shoddy error handling
461 throw std::logic_error("Constructed != name");
464 string data = GetRecordData(ps.Record());
466 size_t len = data.size();
467 if( offset < len ) {
468 if( (offset + size) > len )
469 size = len - offset;
470 memcpy(buf, data.data() + offset, size);
472 else {
473 size = 0;
475 return size;
478 const std::string& GetDBName() const { return m_pdb->Name; }
480 std::string GetRecordData(const std::string &recordId)
482 string data;
484 Barry::RecordStateTable rst;
485 m_desk.GetRecordStateTable(m_pdb->Number, rst);
487 uint32_t recid = strtoul(recordId.c_str(), NULL, 16);
488 RecordStateTable::IndexType index;
489 if( rst.GetIndex(recid, &index) ) {
490 ostringstream oss;
491 ParserPtr parser = GetParser(m_pdb->Name, oss, false);
492 m_desk.GetRecord(m_pdb->Number, index, *parser);
493 data = oss.str();
496 return data;
500 class DesktopCon : public Directory
502 public:
503 typedef std::tr1::shared_ptr<Database> DatabasePtr;
504 typedef std::list<DatabasePtr> DBList;
505 public:
506 Barry::Controller m_con;
507 Barry::Mode::Desktop m_desk;
508 std::string m_pin;
509 DBList m_dblist;
511 DesktopCon(const Barry::ProbeResult &result, const std::string &pin)
512 : m_con(result)
513 , m_desk(m_con)
514 , m_pin(pin)
516 // add to directory list
517 g_dirmap[ string("/") + pin ] = this;
520 ~DesktopCon()
522 // remove from directory list
523 g_dirmap.erase( string("/") + m_pin );
526 virtual int ReadDir(void *buf, fuse_fill_dir_t filler)
528 filler(buf, ".", NULL, 0);
529 filler(buf, "..", NULL, 0);
531 // list all databases in list
532 DBList::const_iterator b = m_dblist.begin(), e = m_dblist.end();
533 for( ; b != e; ++ b ) {
534 filler(buf, (*b)->GetDBName().c_str(), NULL, 0);
536 return 0;
539 void Open(const char *password = 0)
541 // open our device
542 m_desk.Open(password);
544 // add all databases as directories
545 DatabaseDatabase::DatabaseArrayType::const_iterator
546 dbi = m_desk.GetDBDB().Databases.begin(),
547 dbe = m_desk.GetDBDB().Databases.end();
548 for( ; dbi != dbe; ++dbi ) {
549 DatabasePtr db = DatabasePtr(
550 new Database(m_desk, m_pin, &(*dbi)) );
551 m_dblist.push_back(db);
556 class Context : public Directory, public File
558 public:
559 typedef std::auto_ptr<Barry::Probe> ProbePtr;
560 typedef std::tr1::shared_ptr<DesktopCon> DesktopConPtr;
561 typedef std::string PinT;
562 typedef std::map<PinT, DesktopConPtr> PinMap;
564 ProbePtr m_probe;
565 PinMap m_pinmap;
567 string m_error_log;
569 string m_limit_pin; // only mount device with this pin
570 string m_password; // use this password when connecting
572 public:
573 Context(const string &limit_pin = "", const string &password = "")
574 : m_limit_pin(limit_pin)
575 , m_password(password)
577 g_dirmap["/"] = this;
578 g_filemap[string("/") + error_log_filename] = this;
580 m_error_log = "Hello FUSE world. This is Barry. Pleased to meet you.\n";
583 ~Context()
585 g_dirmap.erase("/");
586 g_filemap.erase(string("/") + error_log_filename);
589 virtual int ReadDir(void *buf, fuse_fill_dir_t filler)
591 filler(buf, ".", NULL, 0);
592 filler(buf, "..", NULL, 0);
593 filler(buf, error_log_filename, NULL, 0);
595 // list all pins in map
596 PinMap::const_iterator b = m_pinmap.begin(), e = m_pinmap.end();
597 for( ; b != e; ++ b ) {
598 filler(buf, b->first.c_str(), NULL, 0);
600 return 0;
603 virtual void FillFileStat(const char *path, struct stat *st)
605 st->st_mode = S_IFREG | 0444;
606 st->st_nlink = 1;
607 st->st_size = m_error_log.size();
610 virtual int ReadFile(const char *path, char *buf, size_t size, off_t offset)
612 size_t len = m_error_log.size();
613 if( offset < len ) {
614 if( (offset + size) > len )
615 size = len - offset;
616 memcpy(buf, m_error_log.data() + offset, size);
618 else {
619 size = 0;
621 return size;
624 void Log(const std::string &msg)
626 m_error_log += msg;
627 m_error_log += "\n";
630 const std::string& GetLog() const { return m_error_log; }
632 void ProbeAll()
634 // probe the USB bus for Blackberry devices
635 m_probe.reset( new Probe );
637 // connect to all PINs found, and add them to our map
638 for( int i = 0; i < m_probe->GetCount(); i++ ) {
639 string curpin = m_probe->Get(i).m_pin.str();
641 // don't add a blank or pre-existing pin
642 if( !curpin.size() || m_pinmap.find(curpin) != m_pinmap.end() ) {
643 continue;
646 // don't add non-PIN device if pin specified
647 if( m_limit_pin.size() && curpin != m_limit_pin ) {
648 continue;
651 DesktopConPtr dev = DesktopConPtr (
652 new DesktopCon(m_probe->Get(i), curpin) );
653 dev->Open(m_password.c_str());
654 m_pinmap[ curpin ] = dev;
658 DesktopCon* FindPin(PinT pin)
660 PinMap::iterator pi = m_pinmap.find(pin);
661 return pi == m_pinmap.end() ? 0 : pi->second.get();
666 /////////////////////////////////////////////////////////////////////////////
667 // FUSE API hooks
669 static void* bfuse_init()
671 // Initialize the barry library. Must be called before
672 // anything else.
673 Barry::Init(false);
675 Context *ctx = 0;
677 try {
678 ctx = new Context(cmdline_pin, cmdline_password);
679 ctx->ProbeAll();
681 catch( std::exception &e ) {
682 if( ctx ) {
683 ctx->Log(e.what());
687 return ctx;
690 static void bfuse_destroy(void *data)
692 if( data ) {
693 Context *ctx = (Context*) data;
694 delete ctx;
698 static int bfuse_getattr(const char *path, struct stat *st)
700 memset(st, 0, sizeof(*st));
702 if( Directory *dir = FindDir(path) ) {
703 dir->FillDirStat(st);
704 return 0;
706 else if( File *file = FindFile(path) ) {
707 file->FillFileStat(path, st);
708 return 0;
710 else
711 return -ENOENT;
714 static int bfuse_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
715 off_t /*offset*/, struct fuse_file_info * /*fi*/)
717 Directory *dir = FindDir(path);
718 if( !dir )
719 return -ENOENT;
720 return dir->ReadDir(buf, filler);
723 static int bfuse_open(const char *path, struct fuse_file_info *fi)
725 File *file = FindFile(path);
726 if( !file )
727 return -ENOENT;
729 if( !file->AccessOk(fi->flags) )
730 return -EACCES;
732 return 0;
735 static int bfuse_read(const char *path, char *buf, size_t size, off_t offset,
736 struct fuse_file_info *fi)
738 File *file = FindFile(path);
739 if( !file )
740 return -ENOENT;
742 return file->ReadFile(path, buf, size, offset);
745 // static struct here automatically zeros data
746 static struct fuse_operations bfuse_oper;
749 /////////////////////////////////////////////////////////////////////////////
750 // main
752 int main(int argc, char *argv[])
754 INIT_I18N(PACKAGE);
756 cout.sync_with_stdio(true); // leave this on, since libusb uses
757 // stdio for debug messages
759 Blurb();
761 // initialize the operation hooks
762 bfuse_oper.init = bfuse_init;
763 bfuse_oper.destroy = bfuse_destroy;
764 bfuse_oper.getattr = bfuse_getattr;
765 bfuse_oper.readdir = bfuse_readdir;
766 bfuse_oper.open = bfuse_open;
767 bfuse_oper.read = bfuse_read;
769 // process command line options before FUSE does
770 // FUSE does its own command line processing, and
771 // doesn't seem to have a way to plug into it,
772 // so do our own first
773 int fuse_argc = 0;
774 char **fuse_argv = new char*[argc];
776 for( int i = 0; i < argc; i++ ) {
777 if( argv[i][0] == '-' ) {
779 switch( argv[i][1] )
781 // case 'd': // mount dbname
782 // dbNames.push_back(string(optarg));
783 // break;
785 // case 'n': // use null parser
786 // null_parser = true;
787 // break;
789 case 'p': // Blackberry PIN
790 if( i+1 < argc ) {
791 cmdline_pin = argv[++i];
793 continue;
795 case 'P': // Device password
796 if( i+1 < argc ) {
797 cmdline_password = argv[++i];
799 continue;
801 case 'h': // help
802 Usage();
803 break;
807 // if we get here, add this option to FUSE's
808 fuse_argv[fuse_argc] = argv[i];
809 fuse_argc++;
812 int ret = fuse_main(fuse_argc, fuse_argv, &bfuse_oper);
813 delete [] fuse_argv;
814 return ret;