maint: change script shebang to use bash explicitly and avoid the dash menace
[barry/progweb.git] / tools / bfuse.cc
blobb010f57935cab80e9d9327a5e56e2d9d1b668f02
1 ///
2 /// \file bfuse.cc
3 /// FUSE filesystem for Blackberry databases, using Barry.
4 ///
6 /*
7 Copyright (C) 2008-2011, 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 <stdlib.h>
40 #include "i18n.h"
42 using namespace std;
43 using namespace std::tr1;
44 using namespace Barry;
46 // Global filenames
47 const char *error_log_filename = "error.log";
49 // Global command line args
50 string cmdline_pin;
51 string cmdline_password;
54 // Data from the command line
57 /////////////////////////////////////////////////////////////////////////////
58 // Command line option handling, through fuse
60 //struct opt {
61 // char
62 //};
64 void Blurb()
66 int logical, major, minor;
67 const char *Version = Barry::Version(logical, major, minor);
69 cerr
70 << "bfuse - FUSE filesystem for Blackberry databases\n"
71 << " Copyright 2008-2011, Net Direct Inc. (http://www.netdirect.ca/)\n"
72 << " Using: " << Version << "\n"
73 << endl;
76 void Usage()
78 cerr
79 << "\n"
80 << "Barry specific options:\n"
81 << " -p pin PIN of device to talk with\n"
82 << " If only one device is plugged in, this flag is optional\n"
83 << " -P pass Simplistic method to specify device password\n"
84 << endl;
86 << " -d db Specify which database to mount. If no -d options exist\n"
87 << " then all databases will be mounted.\n"
88 << " Can be used multiple times to mount more than one DB\n"
89 << " -h This help\n"
90 << " -n Use null parser on all databases.\n"
94 /////////////////////////////////////////////////////////////////////////////
95 // FUSE specific exception
97 class fuse_error : public std::runtime_error
99 int m_errno;
100 public:
101 fuse_error(int errno_, const std::string &msg)
102 : std::runtime_error(msg), m_errno(errno_)
105 int get_errno() const { return m_errno; }
109 /////////////////////////////////////////////////////////////////////////////
110 // Barry record parsers
112 class DataDumpParser : public Barry::Parser
114 uint32_t m_id;
115 std::ostream &m_os;
117 public:
118 explicit DataDumpParser(std::ostream &os)
119 : m_os(os)
123 virtual void ParseRecord(const Barry::DBData &data,
124 const Barry::IConverter *ic)
126 m_id = data.GetUniqueId();
127 m_os << "Raw record dump for record: "
128 << std::hex << m_id << std::endl;
129 m_os << data.GetData() << std::endl;
133 template <class Record>
134 struct Store
136 std::ostream &m_os;
138 explicit Store(std::ostream &os)
139 : m_os(os)
143 // storage operator
144 void operator()(const Record &rec)
146 m_os << rec;
150 typedef std::auto_ptr<Barry::Parser> ParserPtr;
152 ParserPtr GetParser(const string &name, std::ostream &os, bool null_parser)
154 if( null_parser ) {
155 // use null parser
156 return ParserPtr( new DataDumpParser(os) );
158 // check for recognized database names
159 else if( name == Contact::GetDBName() ) {
160 return ParserPtr(
161 new RecordParser<Contact, Store<Contact> > (
162 new Store<Contact>(os)));
164 else if( name == Message::GetDBName() ) {
165 return ParserPtr(
166 new RecordParser<Message, Store<Message> > (
167 new Store<Message>(os)));
169 else if( name == Calendar::GetDBName() ) {
170 return ParserPtr(
171 new RecordParser<Calendar, Store<Calendar> > (
172 new Store<Calendar>(os)));
174 else if( name == CalendarAll::GetDBName() ) {
175 return ParserPtr(
176 new RecordParser<CalendarAll, Store<CalendarAll> > (
177 new Store<CalendarAll>(os)));
179 else if( name == ServiceBook::GetDBName() ) {
180 return ParserPtr(
181 new RecordParser<ServiceBook, Store<ServiceBook> > (
182 new Store<ServiceBook>(os)));
185 else if( name == Memo::GetDBName() ) {
186 return ParserPtr(
187 new RecordParser<Memo, Store<Memo> > (
188 new Store<Memo>(os)));
190 else if( name == Task::GetDBName() ) {
191 return ParserPtr(
192 new RecordParser<Task, Store<Task> > (
193 new Store<Task>(os)));
195 else if( name == PINMessage::GetDBName() ) {
196 return ParserPtr(
197 new RecordParser<PINMessage, Store<PINMessage> > (
198 new Store<PINMessage>(os)));
200 else if( name == SavedMessage::GetDBName() ) {
201 return ParserPtr(
202 new RecordParser<SavedMessage, Store<SavedMessage> > (
203 new Store<SavedMessage>(os)));
205 else if( name == Folder::GetDBName() ) {
206 return ParserPtr(
207 new RecordParser<Folder, Store<Folder> > (
208 new Store<Folder>(os)));
210 else if( name == Timezone::GetDBName() ) {
211 return ParserPtr(
212 new RecordParser<Timezone, Store<Timezone> > (
213 new Store<Timezone>(os)));
215 else {
216 // unknown database, use null parser
217 return ParserPtr( new DataDumpParser(os) );
221 /////////////////////////////////////////////////////////////////////////////
222 // PathSplit class
224 class PathSplit
226 std::string m_pin, m_db, m_record, m_field, m_remainder;
228 int m_level; // the number of slashes, minus the first
229 // i.e. first level is 0
230 bool m_is_root;
232 public:
233 explicit PathSplit(const char *path)
234 : m_level(-1)
235 , m_is_root(false)
237 if( *path != '/' )
238 return; // return in a failed state
240 if( *(path+1) == 0 ) {
241 m_is_root = true;
242 return;
245 const char *s = path, *e = path;
246 while( *e ) {
247 while( *e && *e != '/' )
248 e++;
250 m_level++;
252 if( s != e && (s+1) != e ) {
253 string token(s+1, e);
255 switch( m_level )
257 case 0: // root level, should not have token here
258 m_level = -1;
259 return; // failed state
261 case 1: // have pin
262 m_pin = token;
263 break;
265 case 2: // have db
266 m_db = token;
267 break;
269 case 3: // have record
270 m_record = token;
271 break;
273 case 4: // have field
274 m_field = token;
275 break;
277 default: // too many, store remainder and done
278 m_remainder = s; // keeps slash
279 return;
282 // next
283 s = e;
284 if( *e )
285 e++;
287 else if( *e ) {
288 // next
289 e++;
294 bool IsRoot() const { return m_is_root; }
295 const std::string& Pin() const { return m_pin; }
296 const std::string& DB() const { return m_db; }
297 const std::string& Record() const { return m_record; }
298 const std::string& Field() const { return m_field; }
299 const std::string& Remainder() const { return m_remainder; }
300 int Level() const { return m_level; }
304 /////////////////////////////////////////////////////////////////////////////
305 // API classes
307 class Entry
309 public:
310 virtual ~Entry() {}
313 class Directory : public Entry
315 public:
316 virtual int ReadDir(void *buf, fuse_fill_dir_t filler) = 0;
317 virtual void FillDirStat(struct stat *st)
319 st->st_mode = S_IFDIR | 0555;
320 st->st_nlink = 2;
324 class File : public Entry
326 public:
327 virtual void FillFileStat(const char *path, struct stat *st) = 0;
328 virtual bool AccessOk(int flags)
330 // default to readonly files
331 return (flags & (O_RDONLY | O_WRONLY | O_RDWR)) == O_RDONLY;
333 virtual int ReadFile(const char *path, char *buf, size_t size, off_t offset) = 0;
336 typedef Directory* DirectoryPtr;
337 typedef File* FilePtr;
338 typedef std::string NameT;
339 typedef std::map<NameT, DirectoryPtr> DirMap;
340 typedef std::map<NameT, FilePtr> FileMap;
342 static DirMap g_dirmap;
343 static FileMap g_filemap;
345 static Directory* FindDir(const NameT &name)
347 DirMap::iterator di = g_dirmap.find(name);
348 return di == g_dirmap.end() ? 0 : di->second;
351 static File* FindFile(const NameT &name)
353 FileMap::iterator fi = g_filemap.find(name);
354 return fi == g_filemap.end() ? 0 : fi->second;
357 /////////////////////////////////////////////////////////////////////////////
358 // Context classes
360 class Database : public Directory, public File
362 public:
363 Barry::Mode::Desktop &m_desk;
364 std::string m_name;
365 const Barry::DatabaseItem *m_pdb;
367 public:
368 Database(Barry::Mode::Desktop &desktop,
369 const std::string &pin, const Barry::DatabaseItem *pdb)
370 : m_desk(desktop)
371 , m_pdb(pdb)
373 m_name = string("/") + pin + "/" + m_pdb->Name;
375 // add to directory list
376 g_dirmap[ m_name ] = this;
379 ~Database()
381 // remove any entries that point to us
382 FileMap::iterator b = g_filemap.begin(), e = g_filemap.end();
383 for( ; b != e; ++b ) {
384 if( b->second == this ) {
385 g_filemap.erase(b);
389 // erase ourselves from the directory list
390 g_dirmap.erase( m_name );
393 void AddFile(const std::string &recordId)
395 // FIXME - this is a hack to redirect all record files
396 // to this database class... next step is to possibly
397 // split out records into field files if we have a
398 // parser, or just dump the hex if we don't
399 string name = m_name + "/" + recordId;
400 g_filemap[ name ] = this;
403 virtual int ReadDir(void *buf, fuse_fill_dir_t filler)
405 filler(buf, ".", NULL, 0);
406 filler(buf, "..", NULL, 0);
408 // list all records in database, by recordId
409 Barry::RecordStateTable rst;
410 m_desk.GetRecordStateTable(m_pdb->Number, rst);
412 Barry::RecordStateTable::StateMapType::iterator
413 b = rst.StateMap.begin(),
414 e = rst.StateMap.end();
415 for( ; b != e; ++ b ) {
416 ostringstream oss;
417 oss << hex << b->second.RecordId;
418 filler(buf, oss.str().c_str(), NULL, 0);
420 AddFile(oss.str());
422 return 0;
425 virtual void FillFileStat(const char *path, struct stat *st)
427 // use the path to find the proper record
428 PathSplit ps(path);
430 string constructed = string("/") + ps.Pin() + "/" + ps.DB();
431 if( constructed != m_name ) {
432 // FIXME - this is shoddy error handling
433 throw std::logic_error("Constructed != name");
436 string data = GetRecordData(ps.Record());
438 st->st_mode = S_IFREG | 0444;
439 st->st_nlink = 1;
440 st->st_size = data.size();
443 virtual int ReadFile(const char *path, char *buf, size_t size, off_t offset)
445 // use the path to find the proper record
446 PathSplit ps(path);
448 string constructed = string("/") + ps.Pin() + "/" + ps.DB();
449 if( constructed != m_name ) {
450 // FIXME - this is shoddy error handling
451 throw std::logic_error("Constructed != name");
454 string data = GetRecordData(ps.Record());
456 size_t len = data.size();
457 if( offset >= 0 && offset < (off_t)len ) {
458 if( (offset + size) > len )
459 size = len - offset;
460 memcpy(buf, data.data() + offset, size);
462 else {
463 size = 0;
465 return size;
468 const std::string& GetDBName() const { return m_pdb->Name; }
470 std::string GetRecordData(const std::string &recordId)
472 string data;
474 Barry::RecordStateTable rst;
475 m_desk.GetRecordStateTable(m_pdb->Number, rst);
477 uint32_t recid = strtoul(recordId.c_str(), NULL, 16);
478 RecordStateTable::IndexType index;
479 if( rst.GetIndex(recid, &index) ) {
480 ostringstream oss;
481 ParserPtr parser = GetParser(m_pdb->Name, oss, false);
482 m_desk.GetRecord(m_pdb->Number, index, *parser);
483 data = oss.str();
486 return data;
490 class DesktopCon : public Directory
492 public:
493 typedef std::tr1::shared_ptr<Database> DatabasePtr;
494 typedef std::list<DatabasePtr> DBList;
495 public:
496 Barry::Controller m_con;
497 Barry::Mode::Desktop m_desk;
498 std::string m_pin;
499 DBList m_dblist;
501 DesktopCon(const Barry::ProbeResult &result, const std::string &pin)
502 : m_con(result)
503 , m_desk(m_con)
504 , m_pin(pin)
506 // add to directory list
507 g_dirmap[ string("/") + pin ] = this;
510 ~DesktopCon()
512 // remove from directory list
513 g_dirmap.erase( string("/") + m_pin );
516 virtual int ReadDir(void *buf, fuse_fill_dir_t filler)
518 filler(buf, ".", NULL, 0);
519 filler(buf, "..", NULL, 0);
521 // list all databases in list
522 DBList::const_iterator b = m_dblist.begin(), e = m_dblist.end();
523 for( ; b != e; ++ b ) {
524 filler(buf, (*b)->GetDBName().c_str(), NULL, 0);
526 return 0;
529 void Open(const char *password = 0)
531 // open our device
532 m_desk.Open(password);
534 // add all databases as directories
535 DatabaseDatabase::DatabaseArrayType::const_iterator
536 dbi = m_desk.GetDBDB().Databases.begin(),
537 dbe = m_desk.GetDBDB().Databases.end();
538 for( ; dbi != dbe; ++dbi ) {
539 DatabasePtr db = DatabasePtr(
540 new Database(m_desk, m_pin, &(*dbi)) );
541 m_dblist.push_back(db);
546 class Context : public Directory, public File
548 public:
549 typedef std::auto_ptr<Barry::Probe> ProbePtr;
550 typedef std::tr1::shared_ptr<DesktopCon> DesktopConPtr;
551 typedef std::string PinT;
552 typedef std::map<PinT, DesktopConPtr> PinMap;
554 ProbePtr m_probe;
555 PinMap m_pinmap;
557 string m_error_log;
559 string m_limit_pin; // only mount device with this pin
560 string m_password; // use this password when connecting
562 public:
563 Context(const string &limit_pin = "", const string &password = "")
564 : m_limit_pin(limit_pin)
565 , m_password(password)
567 g_dirmap["/"] = this;
568 g_filemap[string("/") + error_log_filename] = this;
570 m_error_log = "Hello FUSE world. This is Barry. Pleased to meet you.\n";
573 ~Context()
575 g_dirmap.erase("/");
576 g_filemap.erase(string("/") + error_log_filename);
579 virtual int ReadDir(void *buf, fuse_fill_dir_t filler)
581 filler(buf, ".", NULL, 0);
582 filler(buf, "..", NULL, 0);
583 filler(buf, error_log_filename, NULL, 0);
585 // list all pins in map
586 PinMap::const_iterator b = m_pinmap.begin(), e = m_pinmap.end();
587 for( ; b != e; ++ b ) {
588 filler(buf, b->first.c_str(), NULL, 0);
590 return 0;
593 virtual void FillFileStat(const char *path, struct stat *st)
595 st->st_mode = S_IFREG | 0444;
596 st->st_nlink = 1;
597 st->st_size = m_error_log.size();
600 virtual int ReadFile(const char *path, char *buf, size_t size, off_t offset)
602 size_t len = m_error_log.size();
603 if( offset >= 0 && offset < (off_t)len ) {
604 if( (offset + size) > len )
605 size = len - offset;
606 memcpy(buf, m_error_log.data() + offset, size);
608 else {
609 size = 0;
611 return size;
614 void Log(const std::string &msg)
616 m_error_log += msg;
617 m_error_log += "\n";
620 const std::string& GetLog() const { return m_error_log; }
622 void ProbeAll()
624 // probe the USB bus for Blackberry devices
625 m_probe.reset( new Probe );
627 // connect to all PINs found, and add them to our map
628 for( int i = 0; i < m_probe->GetCount(); i++ ) {
629 string curpin = m_probe->Get(i).m_pin.Str();
631 // don't add a blank or pre-existing pin
632 if( !curpin.size() || m_pinmap.find(curpin) != m_pinmap.end() ) {
633 continue;
636 // don't add non-PIN device if pin specified
637 if( m_limit_pin.size() && curpin != m_limit_pin ) {
638 continue;
641 DesktopConPtr dev = DesktopConPtr (
642 new DesktopCon(m_probe->Get(i), curpin) );
643 dev->Open(m_password.c_str());
644 m_pinmap[ curpin ] = dev;
648 DesktopCon* FindPin(PinT pin)
650 PinMap::iterator pi = m_pinmap.find(pin);
651 return pi == m_pinmap.end() ? 0 : pi->second.get();
656 /////////////////////////////////////////////////////////////////////////////
657 // FUSE API hooks
659 static void* bfuse_init()
661 // Initialize the barry library. Must be called before
662 // anything else.
663 Barry::Init(false);
665 Context *ctx = 0;
667 try {
668 ctx = new Context(cmdline_pin, cmdline_password);
669 ctx->ProbeAll();
671 catch( std::exception &e ) {
672 if( ctx ) {
673 ctx->Log(e.what());
677 return ctx;
680 static void bfuse_destroy(void *data)
682 if( data ) {
683 Context *ctx = (Context*) data;
684 delete ctx;
688 static int bfuse_getattr(const char *path, struct stat *st)
690 memset(st, 0, sizeof(*st));
692 if( Directory *dir = FindDir(path) ) {
693 dir->FillDirStat(st);
694 return 0;
696 else if( File *file = FindFile(path) ) {
697 file->FillFileStat(path, st);
698 return 0;
700 else
701 return -ENOENT;
704 static int bfuse_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
705 off_t /*offset*/, struct fuse_file_info * /*fi*/)
707 Directory *dir = FindDir(path);
708 if( !dir )
709 return -ENOENT;
710 return dir->ReadDir(buf, filler);
713 static int bfuse_open(const char *path, struct fuse_file_info *fi)
715 File *file = FindFile(path);
716 if( !file )
717 return -ENOENT;
719 if( !file->AccessOk(fi->flags) )
720 return -EACCES;
722 return 0;
725 static int bfuse_read(const char *path, char *buf, size_t size, off_t offset,
726 struct fuse_file_info *fi)
728 File *file = FindFile(path);
729 if( !file )
730 return -ENOENT;
732 return file->ReadFile(path, buf, size, offset);
735 // static struct here automatically zeros data
736 static struct fuse_operations bfuse_oper;
739 /////////////////////////////////////////////////////////////////////////////
740 // main
742 int main(int argc, char *argv[])
744 INIT_I18N(PACKAGE);
746 cout.sync_with_stdio(true); // leave this on, since libusb uses
747 // stdio for debug messages
749 Blurb();
751 // initialize the operation hooks
752 bfuse_oper.init = bfuse_init;
753 bfuse_oper.destroy = bfuse_destroy;
754 bfuse_oper.getattr = bfuse_getattr;
755 bfuse_oper.readdir = bfuse_readdir;
756 bfuse_oper.open = bfuse_open;
757 bfuse_oper.read = bfuse_read;
759 // process command line options before FUSE does
760 // FUSE does its own command line processing, and
761 // doesn't seem to have a way to plug into it,
762 // so do our own first
763 int fuse_argc = 0;
764 char **fuse_argv = new char*[argc];
766 for( int i = 0; i < argc; i++ ) {
767 if( argv[i][0] == '-' ) {
769 switch( argv[i][1] )
771 // case 'd': // mount dbname
772 // dbNames.push_back(string(optarg));
773 // break;
775 // case 'n': // use null parser
776 // null_parser = true;
777 // break;
779 case 'p': // Blackberry PIN
780 if( i+1 < argc ) {
781 cmdline_pin = argv[++i];
783 continue;
785 case 'P': // Device password
786 if( i+1 < argc ) {
787 cmdline_password = argv[++i];
789 continue;
791 case 'h': // help
792 Usage();
793 break;
797 // if we get here, add this option to FUSE's
798 fuse_argv[fuse_argc] = argv[i];
799 fuse_argc++;
802 int ret = fuse_main(fuse_argc, fuse_argv, &bfuse_oper);
803 delete [] fuse_argv;
804 return ret;