configfile.cc

Go to the documentation of this file.
00001 ///
00002 /// \file       configfile.cc
00003 ///             Barry configuraion class, for one device PIN
00004 ///
00005 
00006 /*
00007     Copyright (C) 2007-2012, Net Direct Inc. (http://www.netdirect.ca/)
00008 
00009     This program is free software; you can redistribute it and/or modify
00010     it under the terms of the GNU General Public License as published by
00011     the Free Software Foundation; either version 2 of the License, or
00012     (at your option) any later version.
00013 
00014     This program is distributed in the hope that it will be useful,
00015     but WITHOUT ANY WARRANTY; without even the implied warranty of
00016     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
00017 
00018     See the GNU General Public License in the COPYING file at the
00019     root directory of this project for more details.
00020 */
00021 
00022 #include "configfile.h"
00023 #include "error.h"
00024 #include "r_message.h"
00025 #include "getpwuid.h"
00026 #include <string.h>
00027 #include <errno.h>
00028 #include <unistd.h>
00029 #include <fstream>
00030 #include <sstream>
00031 #include <iostream>
00032 #include <iomanip>
00033 #include <sys/types.h>
00034 #include <sys/stat.h>
00035 
00036 namespace Barry {
00037 
00038 /// Creates a tar.gz filename using PIN + date + time + label.
00039 /// Does not include any path, just returns a new filename.
00040 std::string MakeBackupFilename(const Barry::Pin &pin,
00041                                 const std::string &label)
00042 {
00043         using namespace std;
00044 
00045         time_t t = time(NULL);
00046         struct tm *lt = localtime(&t);
00047 
00048         std::string fileLabel = label;
00049         if( fileLabel.size() ) {
00050                 // prepend a hyphen
00051                 fileLabel.insert(fileLabel.begin(), '-');
00052 
00053                 // translate all spaces and slashes
00054                 for( size_t i = 0; i < fileLabel.size(); i++ ) {
00055                         if( fileLabel[i] == ' ' )
00056                                 fileLabel[i] = '_';
00057                         else if( fileLabel[i] == '/' )
00058                                 fileLabel[i] = '-';
00059                         else if( fileLabel[i] == '\\' )
00060                                 fileLabel[i] = '-';
00061                 }
00062         }
00063 
00064         ostringstream tarfilename;
00065         tarfilename << pin.Str() << "-"
00066                 << setw(4) << setfill('0') << (lt->tm_year + 1900)
00067                 << setw(2) << setfill('0') << (lt->tm_mon + 1)
00068                 << setw(2) << setfill('0') << lt->tm_mday
00069                 << "-"
00070                 << setw(2) << setfill('0') << lt->tm_hour
00071                 << setw(2) << setfill('0') << lt->tm_min
00072                 << setw(2) << setfill('0') << lt->tm_sec
00073                 << fileLabel
00074                 << ".tar.gz";
00075         return tarfilename.str();
00076 }
00077 
00078 bool ConfigFile::DBListType::IsSelected(const std::string &dbname) const
00079 {
00080         const_iterator i = begin();
00081         for( ; i != end(); ++i ) {
00082                 if( *i == dbname ) {
00083                         return true;
00084                 }
00085         }
00086         return false;
00087 }
00088 
00089 std::ostream& operator<< (std::ostream &os, const ConfigFile::DBListType &list)
00090 {
00091         os << "DBListType dump:\n";
00092 
00093         for( ConfigFile::DBListType::const_iterator i = list.begin();
00094                 i != list.end();
00095                 ++i )
00096         {
00097                 os << "   " << *i << "\n";
00098         }
00099         return os;
00100 }
00101 
00102 
00103 //////////////////////////////////////////////////////////////////////////////
00104 // ConfigFile class members
00105 
00106 /// Loads config file for the given pin, and ends up in an
00107 /// unenlightened state.  Throws ConfigFileError on error,
00108 /// but it is not an error if the config does not exist.
00109 /// Never use this if you have a DatabaseDatabase object!
00110 /// This ctor is only for temporary loading of config data.
00111 ConfigFile::ConfigFile(Barry::Pin pin)
00112         : m_pin(pin)
00113         , m_loaded(false)
00114         , m_promptBackupLabel(false)
00115         , m_autoSelectAll(false)
00116 {
00117         if( m_pin == 0 )
00118                 throw ConfigFileError("Configfile: empty pin");
00119 
00120         BuildFilename();
00121         BuildDefaultPath(); // this handles the situation that path is not set
00122         Load();
00123 }
00124 
00125 /// Opens and loads config file for given pin, and calls Enlighten
00126 /// Throws ConfigFileError on error.  Should never fail unless
00127 /// passed a bad pin.
00128 ConfigFile::ConfigFile(Barry::Pin pin,
00129                        const Barry::DatabaseDatabase &db)
00130         : m_pin(pin)
00131         , m_loaded(false)
00132         , m_promptBackupLabel(false)
00133         , m_autoSelectAll(false)
00134 {
00135         if( m_pin == 0 )
00136                 throw ConfigFileError("Configfile: empty pin");
00137 
00138         BuildFilename();
00139         BuildDefaultPath();
00140         Load();
00141         Enlighten(db);
00142 }
00143 
00144 ConfigFile::~ConfigFile()
00145 {
00146 }
00147 
00148 void ConfigFile::BuildFilename()
00149 {
00150         size_t strsize = 255 * 5;
00151         char *strbuf = new char[strsize];
00152         struct passwd pwbuf;
00153         struct passwd *pw;
00154 
00155         getpwuid_r(getuid(), &pwbuf, strbuf, strsize, &pw);
00156         if( !pw ) {
00157                 delete [] strbuf;
00158                 throw ConfigFileError("BuildFilename: getpwuid failed", errno);
00159         }
00160 
00161         m_filename = pw->pw_dir;
00162         m_filename += "/.barry/backup/";
00163         m_filename += m_pin.Str();
00164         m_filename += "/config";
00165 
00166         delete [] strbuf;
00167 }
00168 
00169 void ConfigFile::BuildDefaultPath()
00170 {
00171         struct passwd *pw = getpwuid(getuid());
00172         m_path = pw->pw_dir;
00173         m_path += "/.barry/backup/";
00174         m_path += m_pin.Str();
00175 }
00176 
00177 void ConfigFile::Clear()
00178 {
00179         m_loaded = false;
00180         m_backupList.clear();
00181         m_restoreList.clear();
00182         m_deviceName.clear();
00183         m_promptBackupLabel = false;
00184         m_autoSelectAll = false;
00185 }
00186 
00187 /// Attempt to load the configuration file, but do not fail if not available
00188 void ConfigFile::Load()
00189 {
00190         // start fresh
00191         Clear();
00192 
00193         // open input file
00194         std::ifstream in(m_filename.c_str(), std::ios::in | std::ios::binary);
00195         if( !in )
00196                 return;
00197 
00198         std::string line;
00199         DBListType *pList = 0;
00200 
00201         while( std::getline(in, line) ) {
00202                 std::string keyword;
00203                 std::istringstream iss(line);
00204                 iss >> keyword;
00205 
00206                 if( keyword == "backup_list" ) {
00207                         pList = &m_backupList;
00208                 }
00209                 else if( keyword == "restore_list" ) {
00210                         pList = &m_restoreList;
00211                 }
00212                 else if( line[0] == ' ' && pList ) {
00213                         pList->push_back(line.c_str() + 1);
00214                 }
00215                 else {
00216                         pList = 0;
00217 
00218                         // add all remaining keyword checks here
00219                         if( keyword == "device_name" ) {
00220                                 iss >> std::ws;
00221                                 std::getline(iss, m_deviceName);
00222                                 if( m_deviceName.size() == 0 ) {
00223                                         // if there is a device_name setting,
00224                                         // then this value must hold something,
00225                                         // so that the user can ignore this
00226                                         // field, and not get pestered all
00227                                         // the time
00228                                         m_deviceName = " ";
00229                                 }
00230                         }
00231                         else if( keyword == "backup_path" ) {
00232                                 iss >> std::ws;
00233                                 std::getline(iss, m_path);
00234                                 if( (m_path.size() == 0) || !(CheckPath(m_path)))
00235                                         BuildDefaultPath();
00236                         }
00237                         else if( keyword == "prompt_backup_label" ) {
00238                                 int flag;
00239                                 iss >> flag;
00240                                 m_promptBackupLabel = flag;
00241                         }
00242                         else if( keyword == "auto_select_all" ) {
00243                                 int flag;
00244                                 iss >> flag;
00245                                 m_autoSelectAll = flag;
00246                         }
00247                 }
00248         }
00249 
00250         m_loaded = true;
00251 }
00252 
00253 /// Saves current device's config, overwriting or creating a config file
00254 bool ConfigFile::Save()
00255 {
00256         using namespace std;
00257 
00258         if( !CheckPath(m_path, &m_last_error) )
00259                 return false;
00260 
00261         ofstream out(m_filename.c_str(), std::ios::out | std::ios::binary);
00262         if( !out ) {
00263                 m_last_error = "Unable to open " + m_filename + " for writing.";
00264                 return false;
00265         }
00266 
00267         out << "backup_list" << endl;
00268         for( DBListType::iterator i = m_backupList.begin(); i != m_backupList.end(); ++i ) {
00269                 out << " " << *i << endl;
00270         }
00271 
00272         out << "restore_list" << endl;
00273         for( DBListType::iterator i = m_restoreList.begin(); i != m_restoreList.end(); ++i ) {
00274                 out << " " << *i << endl;
00275         }
00276 
00277         if( m_deviceName.size() ) {
00278                 out << "device_name " << m_deviceName << endl;
00279         }
00280 
00281         if( m_path.size() ) {
00282                 out << "backup_path " << m_path << endl;
00283         }
00284 
00285         out << "prompt_backup_label " << (m_promptBackupLabel ? 1 : 0) << endl;
00286         out << "auto_select_all " << (m_autoSelectAll ? 1 : 0) << endl;
00287 
00288         if( !out ) {
00289                 m_last_error = "Error during write.  Config may be incomplete.";
00290                 return false;
00291         }
00292         return true;
00293 }
00294 
00295 /// Compares a given databasedatabase from a real device with the
00296 /// current config.  If not yet configured, initialize with valid
00297 /// defaults.
00298 void ConfigFile::Enlighten(const Barry::DatabaseDatabase &db)
00299 {
00300         if( !m_loaded ) {
00301                 // if not fully loaded, we use db as our default list
00302                 // our defaults are: backup everything, restore everything
00303                 // except email
00304 
00305                 m_backupList.clear();
00306                 m_restoreList.clear();
00307 
00308                 Barry::DatabaseDatabase::DatabaseArrayType::const_iterator i =
00309                         db.Databases.begin();
00310                 for( ; i != db.Databases.end(); ++i ) {
00311                         // backup everything
00312                         m_backupList.push_back(i->Name);
00313 
00314                         // restore everything except email (which could take ages)
00315                         // and Handheld Agent (which seems write protected)
00316                         if( i->Name != Barry::Message::GetDBName() &&
00317                             i->Name != "Handheld Agent" )
00318                         {
00319                                 m_restoreList.push_back(i->Name);
00320                         }
00321                 }
00322         }
00323 }
00324 
00325 // fill list with all databases from dbdb
00326 ConfigFile:: DBListType& ConfigFile::DBListType::operator=(const DatabaseDatabase &dbdb)
00327 {
00328         // start empty
00329         clear();
00330 
00331         // copy over all DB names
00332         DatabaseDatabase::DatabaseArrayType::const_iterator
00333                 i = dbdb.Databases.begin(), e = dbdb.Databases.end();
00334         for( ; i != e; ++i ) {
00335                 push_back(i->Name);
00336         }
00337 
00338         return *this;
00339 }
00340 
00341 /// Sets list with new config
00342 void ConfigFile::SetBackupList(const DBListType &list)
00343 {
00344         m_backupList = list;
00345         m_loaded = true;
00346 }
00347 
00348 void ConfigFile::SetRestoreList(const DBListType &list)
00349 {
00350         m_restoreList = list;
00351         m_loaded = true;
00352 }
00353 
00354 void ConfigFile::SetDeviceName(const std::string &name)
00355 {
00356         if( name.size() )
00357                 m_deviceName = name;
00358         else
00359                 m_deviceName = " ";
00360 }
00361 
00362 void ConfigFile::SetBackupPath(const std::string &path)
00363 {
00364         if( path.size() && CheckPath(path) )
00365                 m_path = path;
00366         else
00367                 BuildDefaultPath();
00368 }
00369 
00370 void ConfigFile::SetPromptBackupLabel(bool prompt)
00371 {
00372         m_promptBackupLabel = prompt;
00373 }
00374 
00375 void ConfigFile::SetAutoSelectAll(bool asa)
00376 {
00377         m_autoSelectAll = asa;
00378 }
00379 
00380 /// Checks that the path in path exists, and if not, creates it.
00381 /// Returns false if unable to create path, true if ok.
00382 bool ConfigFile::CheckPath(const std::string &path, std::string *perr)
00383 {
00384         if( path.size() == 0 ) {
00385                 if( perr )
00386                         *perr = "path is empty!";
00387                 return false;
00388         }
00389 
00390         if( access(path.c_str(), F_OK) == 0 )
00391                 return true;
00392 
00393         std::string base;
00394         std::string::size_type slash = 0;
00395         while( (slash = path.find('/', slash + 1)) != std::string::npos ) {
00396                 base = path.substr(0, slash);
00397                 if( access(base.c_str(), F_OK) != 0 ) {
00398                         if( mkdir(base.c_str(), 0755) == -1 ) {
00399                                 if( perr ) {
00400                                         *perr = "mkdir(" + base + ") failed: ";
00401                                         *perr += strerror(errno);
00402                                 }
00403                                 return false;
00404                         }
00405                 }
00406         }
00407         if( mkdir(path.c_str(), 0755) == -1 ) {
00408                 if( perr ) {
00409                         *perr = "last mkdir(" + path + ") failed: ";
00410                         *perr += strerror(errno);
00411                 }
00412                 return false;
00413         }
00414         return true;
00415 }
00416 
00417 
00418 
00419 //////////////////////////////////////////////////////////////////////////////
00420 // GlobalConfigFile class members
00421 
00422 GlobalConfigFile::GlobalConfigFile()
00423         : m_loaded(false)
00424         , m_verboseLogging(false)
00425 {
00426         BuildFilename();
00427         Load();
00428 }
00429 
00430 GlobalConfigFile::GlobalConfigFile(const std::string &appname)
00431         : m_loaded(false)
00432         , m_appname(appname)
00433         , m_verboseLogging(false)
00434 {
00435         // there can be no spaces in the appname
00436         if( m_appname.find(' ') != std::string::npos )
00437                 throw std::logic_error("App name must have no spaces.");
00438 
00439         BuildFilename();
00440         Load();
00441 }
00442 
00443 GlobalConfigFile::~GlobalConfigFile()
00444 {
00445 }
00446 
00447 void GlobalConfigFile::BuildFilename()
00448 {
00449         struct passwd *pw = getpwuid(getuid());
00450         if( !pw )
00451                 throw ConfigFileError("BuildFilename: getpwuid failed", errno);
00452 
00453         m_filename = pw->pw_dir;
00454         m_filename += "/.barry/config";
00455 
00456         // build the global path too, since this never changes
00457         m_path = pw->pw_dir;
00458         m_path += "/.barry";
00459 }
00460 
00461 void GlobalConfigFile::Clear()
00462 {
00463         m_loaded = false;
00464         m_lastDevice = 0;
00465 }
00466 
00467 void GlobalConfigFile::Load()
00468 {
00469         // start fresh
00470         Clear();
00471 
00472         // open input file
00473         std::ifstream in(m_filename.c_str(), std::ios::in | std::ios::binary);
00474         if( !in )
00475                 return;
00476 
00477         std::string line;
00478 
00479         while( std::getline(in, line) ) {
00480                 std::string keyword;
00481                 std::istringstream iss(line);
00482                 iss >> keyword;
00483 
00484                 if( keyword == "last_device" ) {
00485                         iss >> std::ws;
00486                         m_lastDevice.Clear();
00487                         iss >> m_lastDevice;
00488                 }
00489                 else if( keyword == "verbose_logging" ) {
00490                         int flag = 0;
00491                         iss >> flag;
00492                         m_verboseLogging = flag;
00493                 }
00494                 else {
00495                         // store any other keys as app keys
00496                         if( keyword.substr(0, 2) == "X-" ) {
00497                                 iss >> std::ws;
00498                                 line.clear();
00499                                 std::getline(iss, line);
00500                                 m_keymap[keyword] = line;
00501                         }
00502                 }
00503         }
00504 
00505         m_loaded = true;
00506 }
00507 
00508 /// Save the current global config, overwriting or creating as needed
00509 bool GlobalConfigFile::Save()
00510 {
00511         if( !ConfigFile::CheckPath(m_path, &m_last_error) )
00512                 return false;
00513 
00514         std::ofstream out(m_filename.c_str(), std::ios::out | std::ios::binary);
00515         if( !out ) {
00516                 m_last_error = "Unable to open " + m_filename + " for writing.";
00517                 return false;
00518         }
00519 
00520         if( !(m_lastDevice == 0) ) {
00521                 out << "last_device " << m_lastDevice.Str() << std::endl;
00522         }
00523 
00524         out << "verbose_logging " << (m_verboseLogging ? 1 : 0) << std::endl;
00525 
00526         // store all app keys
00527         keymap_type::const_iterator ci = m_keymap.begin();
00528         for( ; ci != m_keymap.end(); ++ci ) {
00529                 out << ci->first << " " << ci->second << std::endl;
00530         }
00531 
00532         if( !out ) {
00533                 m_last_error = "Error during write.  Config may be incomplete.";
00534                 return false;
00535         }
00536         return true;
00537 }
00538 
00539 void GlobalConfigFile::SetKey(const std::string &key, const std::string &value)
00540 {
00541         if( !m_appname.size() )
00542                 throw std::logic_error("Cannot use SetKey() without specifying an appname in the constructor.");
00543 
00544         if( value.find_first_of("\n\r") != std::string::npos )
00545                 throw std::logic_error("SetKey values may not contain newline characters.");
00546 
00547         std::string fullkey = "X-" + m_appname + "-" + key;
00548         m_keymap[fullkey] = value;
00549 }
00550 
00551 std::string GlobalConfigFile::GetKey(const std::string &key,
00552                                      const std::string &default_value) const
00553 {
00554         if( !m_appname.size() )
00555                 throw std::logic_error("Cannot use SetKey() without specifying an appname in the constructor.");
00556 
00557         std::string fullkey = "X-" + m_appname + "-" + key;
00558         keymap_type::const_iterator ci = m_keymap.find(fullkey);
00559         if( ci == m_keymap.end() )
00560                 return default_value;
00561         return ci->second;
00562 }
00563 
00564 void GlobalConfigFile::SetLastDevice(const Barry::Pin &pin)
00565 {
00566         m_lastDevice = pin;
00567 }
00568 
00569 void GlobalConfigFile::SetVerboseLogging(bool verbose)
00570 {
00571         m_verboseLogging = verbose;
00572 }
00573 
00574 
00575 } // namespace Barry
00576