vformat.c: fixed incomplete base64_init()
[barry.git] / tools / bfuse.cc
blobb8723f0cb72014f4f956d78af1bbb7130497e9ba
1 ///
2 /// \file bfuse.cc
3 /// FUSE filesystem for Blackberry databases, using Barry.
4 ///
6 /*
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
23 #include <fuse.h>
24 #include <fuse_opt.h>
26 #include <barry/barry.h>
27 #include <sstream>
28 #include <vector>
29 #include <list>
30 #include <string>
31 #include <stdexcept>
32 #include <memory>
33 #include <tr1/memory>
34 #include <errno.h>
35 #include <sys/types.h>
36 #include <fcntl.h>
37 #include <string.h>
38 #include <stdlib.h>
39 #include "i18n.h"
41 #include "barrygetopt.h"
43 using namespace std;
44 using namespace std::tr1;
45 using namespace Barry;
47 // Global filenames
48 const char *error_log_filename = "error.log";
50 // Global command line args
51 string cmdline_pin;
52 string cmdline_password;
55 // Data from the command line
58 /////////////////////////////////////////////////////////////////////////////
59 // Command line option handling, through fuse
61 //struct opt {
62 // char
63 //};
65 void Blurb()
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)
74 << endl;
77 void Usage()
79 cerr <<
80 _("\n"
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")
85 << endl;
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"
90 << " -h This help\n"
91 << " -n Use null parser on all databases.\n"
95 /////////////////////////////////////////////////////////////////////////////
96 // FUSE specific exception
98 class fuse_error : public std::runtime_error
100 int m_errno;
101 public:
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
115 uint32_t m_id;
116 std::ostream &m_os;
118 public:
119 explicit DataDumpParser(std::ostream &os)
120 : m_os(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>
135 struct Store
137 std::ostream &m_os;
139 explicit Store(std::ostream &os)
140 : m_os(os)
144 // storage operator
145 void operator()(const Record &rec)
147 m_os << rec;
151 typedef std::auto_ptr<Barry::Parser> ParserPtr;
153 ParserPtr GetParser(const string &name, std::ostream &os, bool null_parser)
155 if( null_parser ) {
156 // use null parser
157 return ParserPtr( new DataDumpParser(os) );
159 // check for recognized database names
160 else if( name == Contact::GetDBName() ) {
161 return ParserPtr(
162 new RecordParser<Contact, Store<Contact> > (
163 new Store<Contact>(os)));
165 else if( name == Message::GetDBName() ) {
166 return ParserPtr(
167 new RecordParser<Message, Store<Message> > (
168 new Store<Message>(os)));
170 else if( name == Calendar::GetDBName() ) {
171 return ParserPtr(
172 new RecordParser<Calendar, Store<Calendar> > (
173 new Store<Calendar>(os)));
175 else if( name == CalendarAll::GetDBName() ) {
176 return ParserPtr(
177 new RecordParser<CalendarAll, Store<CalendarAll> > (
178 new Store<CalendarAll>(os)));
180 else if( name == ServiceBook::GetDBName() ) {
181 return ParserPtr(
182 new RecordParser<ServiceBook, Store<ServiceBook> > (
183 new Store<ServiceBook>(os)));
186 else if( name == Memo::GetDBName() ) {
187 return ParserPtr(
188 new RecordParser<Memo, Store<Memo> > (
189 new Store<Memo>(os)));
191 else if( name == Task::GetDBName() ) {
192 return ParserPtr(
193 new RecordParser<Task, Store<Task> > (
194 new Store<Task>(os)));
196 else if( name == PINMessage::GetDBName() ) {
197 return ParserPtr(
198 new RecordParser<PINMessage, Store<PINMessage> > (
199 new Store<PINMessage>(os)));
201 else if( name == SavedMessage::GetDBName() ) {
202 return ParserPtr(
203 new RecordParser<SavedMessage, Store<SavedMessage> > (
204 new Store<SavedMessage>(os)));
206 else if( name == Folder::GetDBName() ) {
207 return ParserPtr(
208 new RecordParser<Folder, Store<Folder> > (
209 new Store<Folder>(os)));
211 else if( name == TimeZone::GetDBName() ) {
212 return ParserPtr(
213 new RecordParser<TimeZone, Store<TimeZone> > (
214 new Store<TimeZone>(os)));
216 else {
217 // unknown database, use null parser
218 return ParserPtr( new DataDumpParser(os) );
222 /////////////////////////////////////////////////////////////////////////////
223 // PathSplit class
225 class PathSplit
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
231 bool m_is_root;
233 public:
234 explicit PathSplit(const char *path)
235 : m_level(-1)
236 , m_is_root(false)
238 if( *path != '/' )
239 return; // return in a failed state
241 if( *(path+1) == 0 ) {
242 m_is_root = true;
243 return;
246 const char *s = path, *e = path;
247 while( *e ) {
248 while( *e && *e != '/' )
249 e++;
251 m_level++;
253 if( s != e && (s+1) != e ) {
254 string token(s+1, e);
256 switch( m_level )
258 case 0: // root level, should not have token here
259 m_level = -1;
260 return; // failed state
262 case 1: // have pin
263 m_pin = token;
264 break;
266 case 2: // have db
267 m_db = token;
268 break;
270 case 3: // have record
271 m_record = token;
272 break;
274 case 4: // have field
275 m_field = token;
276 break;
278 default: // too many, store remainder and done
279 m_remainder = s; // keeps slash
280 return;
283 // next
284 s = e;
285 if( *e )
286 e++;
288 else if( *e ) {
289 // next
290 e++;
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 /////////////////////////////////////////////////////////////////////////////
306 // API classes
308 class Entry
310 public:
311 virtual ~Entry() {}
314 class Directory : public Entry
316 public:
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;
321 st->st_nlink = 2;
325 class File : public Entry
327 public:
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 /////////////////////////////////////////////////////////////////////////////
359 // Context classes
361 class Database : public Directory, public File
363 public:
364 Barry::Mode::Desktop &m_desk;
365 std::string m_name;
366 const Barry::DatabaseItem *m_pdb;
368 public:
369 Database(Barry::Mode::Desktop &desktop,
370 const std::string &pin, const Barry::DatabaseItem *pdb)
371 : m_desk(desktop)
372 , m_pdb(pdb)
374 m_name = string("/") + pin + "/" + m_pdb->Name;
376 // add to directory list
377 g_dirmap[ m_name ] = this;
380 ~Database()
382 // remove any entries that point to us
383 FileMap::iterator b = g_filemap.begin(), e = g_filemap.end();
384 for( ; b != e; ) {
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++);
396 else {
397 ++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 ) {
428 ostringstream oss;
429 oss << hex << b->second.RecordId;
430 filler(buf, oss.str().c_str(), NULL, 0);
432 AddFile(oss.str());
434 return 0;
437 virtual void FillFileStat(const char *path, struct stat *st)
439 // use the path to find the proper record
440 PathSplit ps(path);
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;
451 st->st_nlink = 1;
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
458 PathSplit ps(path);
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 )
471 size = len - offset;
472 memcpy(buf, data.data() + offset, size);
474 else {
475 size = 0;
477 return size;
480 const std::string& GetDBName() const { return m_pdb->Name; }
482 std::string GetRecordData(const std::string &recordId)
484 string data;
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) ) {
492 ostringstream oss;
493 ParserPtr parser = GetParser(m_pdb->Name, oss, false);
494 m_desk.GetRecord(m_pdb->Number, index, *parser);
495 data = oss.str();
498 return data;
502 class DesktopCon : public Directory
504 public:
505 typedef std::tr1::shared_ptr<Database> DatabasePtr;
506 typedef std::list<DatabasePtr> DBList;
507 public:
508 Barry::Controller m_con;
509 Barry::Mode::Desktop m_desk;
510 std::string m_pin;
511 DBList m_dblist;
513 DesktopCon(const Barry::ProbeResult &result, const std::string &pin)
514 : m_con(result)
515 , m_desk(m_con)
516 , m_pin(pin)
518 // add to directory list
519 g_dirmap[ string("/") + pin ] = this;
522 ~DesktopCon()
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);
538 return 0;
541 void Open(const char *password = 0)
543 // open our device
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
560 public:
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;
566 ProbePtr m_probe;
567 PinMap m_pinmap;
569 string m_error_log;
571 string m_limit_pin; // only mount device with this pin
572 string m_password; // use this password when connecting
574 public:
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");
585 ~Context()
587 g_dirmap.erase("/");
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);
602 return 0;
605 virtual void FillFileStat(const char *path, struct stat *st)
607 st->st_mode = S_IFREG | 0444;
608 st->st_nlink = 1;
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 )
617 size = len - offset;
618 memcpy(buf, m_error_log.data() + offset, size);
620 else {
621 size = 0;
623 return size;
626 void Log(const std::string &msg)
628 m_error_log += msg;
629 m_error_log += "\n";
632 const std::string& GetLog() const { return m_error_log; }
634 void ProbeAll()
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() ) {
645 continue;
648 // don't add non-PIN device if pin specified
649 if( m_limit_pin.size() && curpin != m_limit_pin ) {
650 continue;
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 /////////////////////////////////////////////////////////////////////////////
669 // FUSE API hooks
671 static void* bfuse_init()
673 // Initialize the barry library. Must be called before
674 // anything else.
675 Barry::Init(false);
677 Context *ctx = 0;
679 try {
680 ctx = new Context(cmdline_pin, cmdline_password);
681 ctx->ProbeAll();
683 catch( std::exception &e ) {
684 if( ctx ) {
685 ctx->Log(e.what());
689 return ctx;
692 static void bfuse_destroy(void *data)
694 if( data ) {
695 Context *ctx = (Context*) data;
696 delete ctx;
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);
706 return 0;
708 else if( File *file = FindFile(path) ) {
709 file->FillFileStat(path, st);
710 return 0;
712 else
713 return -ENOENT;
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);
720 if( !dir )
721 return -ENOENT;
722 return dir->ReadDir(buf, filler);
725 static int bfuse_open(const char *path, struct fuse_file_info *fi)
727 File *file = FindFile(path);
728 if( !file )
729 return -ENOENT;
731 if( !file->AccessOk(fi->flags) )
732 return -EACCES;
734 return 0;
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);
741 if( !file )
742 return -ENOENT;
744 return file->ReadFile(path, buf, size, offset);
747 // static struct here automatically zeros data
748 static struct fuse_operations bfuse_oper;
751 /////////////////////////////////////////////////////////////////////////////
752 // main
754 int main(int argc, char *argv[])
756 INIT_I18N(PACKAGE);
758 cout.sync_with_stdio(true); // leave this on, since libusb uses
759 // stdio for debug messages
761 Blurb();
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
775 int fuse_argc = 0;
776 char **fuse_argv = new char*[argc];
778 for( int i = 0; i < argc; i++ ) {
779 if( argv[i][0] == '-' ) {
781 switch( argv[i][1] )
783 // case 'd': // mount dbname
784 // dbNames.push_back(string(optarg));
785 // break;
787 // case 'n': // use null parser
788 // null_parser = true;
789 // break;
791 case 'p': // Blackberry PIN
792 if( i+1 < argc ) {
793 cmdline_pin = argv[++i];
795 continue;
797 case 'P': // Device password
798 if( i+1 < argc ) {
799 cmdline_password = argv[++i];
801 continue;
803 case 'h': // help
804 Usage();
805 break;
809 // if we get here, add this option to FUSE's
810 fuse_argv[fuse_argc] = argv[i];
811 fuse_argc++;
814 int ret = fuse_main(fuse_argc, fuse_argv, &bfuse_oper);
815 delete [] fuse_argv;
816 return ret;