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-2011, 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 <pwd.h>
00026 #include <string.h>
00027 #include <errno.h>
00028 #include <unistd.h>
00029 #include <fstream>
00030 #include <sstream>
00031 #include <sys/types.h>
00032 #include <sys/stat.h>
00033 
00034 namespace Barry {
00035 
00036 bool ConfigFile::DBListType::IsSelected(const std::string &dbname) const
00037 {
00038         const_iterator i = begin();
00039         for( ; i != end(); ++i ) {
00040                 if( *i == dbname ) {
00041                         return true;
00042                 }
00043         }
00044         return false;
00045 }
00046 
00047 
00048 //////////////////////////////////////////////////////////////////////////////
00049 // ConfigFile class members
00050 
00051 /// Loads config file for the given pin, and ends up in an
00052 /// unenlightened state.  Throws ConfigFileError on error,
00053 /// but it is not an error if the config does not exist.
00054 /// Never use this if you have a DatabaseDatabase object!
00055 /// This ctor is only for temporary loading of config data.
00056 ConfigFile::ConfigFile(Barry::Pin pin)
00057         : m_pin(pin)
00058         , m_loaded(false)
00059         , m_promptBackupLabel(false)
00060 {
00061         if( m_pin == 0 )
00062                 throw ConfigFileError("Configfile: empty pin");
00063 
00064         BuildFilename();
00065         BuildDefaultPath(); // this handles the situation that path is not set
00066         Load();
00067 }
00068 
00069 /// Opens and loads config file for given pin, and calls Enlighten
00070 /// Throws ConfigFileError on error.  Should never fail unless
00071 /// passed a bad pin.
00072 ConfigFile::ConfigFile(Barry::Pin pin,
00073                        const Barry::DatabaseDatabase &db)
00074         : m_pin(pin)
00075         , m_loaded(false)
00076         , m_promptBackupLabel(false)
00077 {
00078         if( m_pin == 0 )
00079                 throw ConfigFileError("Configfile: empty pin");
00080 
00081         BuildFilename();
00082         BuildDefaultPath();
00083         Load();
00084         Enlighten(db);
00085 }
00086 
00087 ConfigFile::~ConfigFile()
00088 {
00089 }
00090 
00091 void ConfigFile::BuildFilename()
00092 {
00093         size_t strsize = 255 * 5;
00094         char *strbuf = new char[strsize];
00095         struct passwd pwbuf;
00096         struct passwd *pw;
00097 
00098         getpwuid_r(getuid(), &pwbuf, strbuf, strsize, &pw);
00099         if( !pw ) {
00100                 delete [] strbuf;
00101                 throw ConfigFileError("BuildFilename: getpwuid failed", errno);
00102         }
00103 
00104         m_filename = pw->pw_dir;
00105         m_filename += "/.barry/backup/";
00106         m_filename += m_pin.Str();
00107         m_filename += "/config";
00108 
00109         delete [] strbuf;
00110 }
00111 
00112 void ConfigFile::BuildDefaultPath()
00113 {
00114         struct passwd *pw = getpwuid(getuid());
00115         m_path = pw->pw_dir;
00116         m_path += "/.barry/backup/";
00117         m_path += m_pin.Str();
00118 }
00119 
00120 void ConfigFile::Clear()
00121 {
00122         m_loaded = false;
00123         m_backupList.clear();
00124         m_restoreList.clear();
00125         m_deviceName.clear();
00126         m_promptBackupLabel = false;
00127 }
00128 
00129 /// Attempt to load the configuration file, but do not fail if not available
00130 void ConfigFile::Load()
00131 {
00132         // start fresh
00133         Clear();
00134 
00135         // open input file
00136         std::ifstream in(m_filename.c_str(), std::ios::in | std::ios::binary);
00137         if( !in )
00138                 return;
00139 
00140         std::string line;
00141         DBListType *pList = 0;
00142 
00143         while( std::getline(in, line) ) {
00144                 std::string keyword;
00145                 std::istringstream iss(line);
00146                 iss >> keyword;
00147 
00148                 if( keyword == "backup_list" ) {
00149                         pList = &m_backupList;
00150                 }
00151                 else if( keyword == "restore_list" ) {
00152                         pList = &m_restoreList;
00153                 }
00154                 else if( line[0] == ' ' && pList ) {
00155                         pList->push_back(line.c_str() + 1);
00156                 }
00157                 else {
00158                         pList = 0;
00159 
00160                         // add all remaining keyword checks here
00161                         if( keyword == "device_name" ) {
00162                                 iss >> std::ws;
00163                                 std::getline(iss, m_deviceName);
00164                                 if( m_deviceName.size() == 0 ) {
00165                                         // if there is a device_name setting,
00166                                         // then this value must hold something,
00167                                         // so that the user can ignore this
00168                                         // field, and not get pestered all
00169                                         // the time
00170                                         m_deviceName = " ";
00171                                 }
00172                         }
00173                         else if( keyword == "backup_path" ) {
00174                                 iss >> std::ws;
00175                                 std::getline(iss, m_path);
00176                                 if( (m_path.size() == 0) || !(CheckPath(m_path)))
00177                                         BuildDefaultPath();
00178                         }
00179                         else if( keyword == "prompt_backup_label" ) {
00180                                 int flag;
00181                                 iss >> flag;
00182                                 m_promptBackupLabel = flag;
00183                         }
00184                 }
00185         }
00186 
00187         m_loaded = true;
00188 }
00189 
00190 /// Saves current device's config, overwriting or creating a config file
00191 bool ConfigFile::Save()
00192 {
00193         if( !CheckPath(m_path, &m_last_error) )
00194                 return false;
00195 
00196         std::ofstream out(m_filename.c_str(), std::ios::out | std::ios::binary);
00197         if( !out ) {
00198                 m_last_error = "Unable to open " + m_filename + " for writing.";
00199                 return false;
00200         }
00201 
00202         out << "backup_list" << std::endl;
00203         for( DBListType::iterator i = m_backupList.begin(); i != m_backupList.end(); ++i ) {
00204                 out << " " << *i << std::endl;
00205         }
00206 
00207         out << "restore_list" << std::endl;
00208         for( DBListType::iterator i = m_restoreList.begin(); i != m_restoreList.end(); ++i ) {
00209                 out << " " << *i << std::endl;
00210         }
00211 
00212         if( m_deviceName.size() ) {
00213                 out << "device_name " << m_deviceName << std::endl;
00214         }
00215 
00216         if( m_path.size() ) {
00217                 out << "backup_path " << m_path << std::endl;
00218         }
00219 
00220         out << "prompt_backup_label " << (m_promptBackupLabel ? 1 : 0) << std::endl;
00221 
00222         if( !out ) {
00223                 m_last_error = "Error during write.  Config may be incomplete.";
00224                 return false;
00225         }
00226         return true;
00227 }
00228 
00229 /// Compares a given databasedatabase from a real device with the
00230 /// current config.  If not yet configured, initialize with valid
00231 /// defaults.
00232 void ConfigFile::Enlighten(const Barry::DatabaseDatabase &db)
00233 {
00234         if( !m_loaded ) {
00235                 // if not fully loaded, we use db as our default list
00236                 // our defaults are: backup everything, restore everything
00237                 // except email
00238 
00239                 m_backupList.clear();
00240                 m_restoreList.clear();
00241 
00242                 Barry::DatabaseDatabase::DatabaseArrayType::const_iterator i =
00243                         db.Databases.begin();
00244                 for( ; i != db.Databases.end(); ++i ) {
00245                         // backup everything
00246                         m_backupList.push_back(i->Name);
00247 
00248                         // restore everything except email (which could take ages)
00249                         // and Handheld Agent (which seems write protected)
00250                         if( i->Name != Barry::Message::GetDBName() &&
00251                             i->Name != "Handheld Agent" )
00252                         {
00253                                 m_restoreList.push_back(i->Name);
00254                         }
00255                 }
00256         }
00257 }
00258 
00259 /// Sets list with new config
00260 void ConfigFile::SetBackupList(const DBListType &list)
00261 {
00262         m_backupList = list;
00263         m_loaded = true;
00264 }
00265 
00266 void ConfigFile::SetRestoreList(const DBListType &list)
00267 {
00268         m_restoreList = list;
00269         m_loaded = true;
00270 }
00271 
00272 void ConfigFile::SetDeviceName(const std::string &name)
00273 {
00274         if( name.size() )
00275                 m_deviceName = name;
00276         else
00277                 m_deviceName = " ";
00278 }
00279 
00280 void ConfigFile::SetBackupPath(const std::string &path)
00281 {
00282         if( path.size() && CheckPath(path) )
00283                 m_path = path;
00284         else
00285                 BuildDefaultPath();
00286 }
00287 
00288 void ConfigFile::SetPromptBackupLabel(bool prompt)
00289 {
00290         m_promptBackupLabel = prompt;
00291 }
00292 
00293 /// Checks that the path in path exists, and if not, creates it.
00294 /// Returns false if unable to create path, true if ok.
00295 bool ConfigFile::CheckPath(const std::string &path, std::string *perr)
00296 {
00297         if( path.size() == 0 ) {
00298                 if( perr )
00299                         *perr = "path is empty!";
00300                 return false;
00301         }
00302 
00303         if( access(path.c_str(), F_OK) == 0 )
00304                 return true;
00305 
00306         std::string base;
00307         std::string::size_type slash = 0;
00308         while( (slash = path.find('/', slash + 1)) != std::string::npos ) {
00309                 base = path.substr(0, slash);
00310                 if( access(base.c_str(), F_OK) != 0 ) {
00311                         if( mkdir(base.c_str(), 0755) == -1 ) {
00312                                 if( perr ) {
00313                                         *perr = "mkdir(" + base + ") failed: ";
00314                                         *perr += strerror(errno);
00315                                 }
00316                                 return false;
00317                         }
00318                 }
00319         }
00320         if( mkdir(path.c_str(), 0755) == -1 ) {
00321                 if( perr ) {
00322                         *perr = "last mkdir(" + path + ") failed: ";
00323                         *perr += strerror(errno);
00324                 }
00325                 return false;
00326         }
00327         return true;
00328 }
00329 
00330 
00331 
00332 //////////////////////////////////////////////////////////////////////////////
00333 // GlobalConfigFile class members
00334 
00335 GlobalConfigFile::GlobalConfigFile()
00336         : m_loaded(false)
00337         , m_verboseLogging(false)
00338 {
00339         BuildFilename();
00340         Load();
00341 }
00342 
00343 GlobalConfigFile::GlobalConfigFile(const std::string &appname)
00344         : m_loaded(false)
00345         , m_appname(appname)
00346         , m_verboseLogging(false)
00347 {
00348         // there can be no spaces in the appname
00349         if( m_appname.find(' ') != std::string::npos )
00350                 throw std::logic_error("App name must have no spaces.");
00351 
00352         BuildFilename();
00353         Load();
00354 }
00355 
00356 GlobalConfigFile::~GlobalConfigFile()
00357 {
00358 }
00359 
00360 void GlobalConfigFile::BuildFilename()
00361 {
00362         struct passwd *pw = getpwuid(getuid());
00363         if( !pw )
00364                 throw ConfigFileError("BuildFilename: getpwuid failed", errno);
00365 
00366         m_filename = pw->pw_dir;
00367         m_filename += "/.barry/config";
00368 
00369         // build the global path too, since this never changes
00370         m_path = pw->pw_dir;
00371         m_path += "/.barry";
00372 }
00373 
00374 void GlobalConfigFile::Clear()
00375 {
00376         m_loaded = false;
00377         m_lastDevice = 0;
00378 }
00379 
00380 void GlobalConfigFile::Load()
00381 {
00382         // start fresh
00383         Clear();
00384 
00385         // open input file
00386         std::ifstream in(m_filename.c_str(), std::ios::in | std::ios::binary);
00387         if( !in )
00388                 return;
00389 
00390         std::string line;
00391 
00392         while( std::getline(in, line) ) {
00393                 std::string keyword;
00394                 std::istringstream iss(line);
00395                 iss >> keyword;
00396 
00397                 if( keyword == "last_device" ) {
00398                         iss >> std::ws;
00399                         m_lastDevice.Clear();
00400                         iss >> m_lastDevice;
00401                 }
00402                 else if( keyword == "verbose_logging" ) {
00403                         int flag = 0;
00404                         iss >> flag;
00405                         m_verboseLogging = flag;
00406                 }
00407                 else {
00408                         // store any other keys as app keys
00409                         if( keyword.substr(0, 2) == "X-" ) {
00410                                 iss >> std::ws;
00411                                 line.clear();
00412                                 std::getline(iss, line);
00413                                 m_keymap[keyword] = line;
00414                         }
00415                 }
00416         }
00417 
00418         m_loaded = true;
00419 }
00420 
00421 /// Save the current global config, overwriting or creating as needed
00422 bool GlobalConfigFile::Save()
00423 {
00424         if( !ConfigFile::CheckPath(m_path, &m_last_error) )
00425                 return false;
00426 
00427         std::ofstream out(m_filename.c_str(), std::ios::out | std::ios::binary);
00428         if( !out ) {
00429                 m_last_error = "Unable to open " + m_filename + " for writing.";
00430                 return false;
00431         }
00432 
00433         if( !(m_lastDevice == 0) ) {
00434                 out << "last_device " << m_lastDevice.Str() << std::endl;
00435         }
00436 
00437         out << "verbose_logging " << (m_verboseLogging ? 1 : 0) << std::endl;
00438 
00439         // store all app keys
00440         keymap_type::const_iterator ci = m_keymap.begin();
00441         for( ; ci != m_keymap.end(); ++ci ) {
00442                 out << ci->first << " " << ci->second << std::endl;
00443         }
00444 
00445         if( !out ) {
00446                 m_last_error = "Error during write.  Config may be incomplete.";
00447                 return false;
00448         }
00449         return true;
00450 }
00451 
00452 void GlobalConfigFile::SetKey(const std::string &key, const std::string &value)
00453 {
00454         if( !m_appname.size() )
00455                 throw std::logic_error("Cannot use SetKey() without specifying an appname in the constructor.");
00456 
00457         if( value.find_first_of("\n\r") != std::string::npos )
00458                 throw std::logic_error("SetKey values may not contain newline characters.");
00459 
00460         std::string fullkey = "X-" + m_appname + "-" + key;
00461         m_keymap[fullkey] = value;
00462 }
00463 
00464 std::string GlobalConfigFile::GetKey(const std::string &key) const
00465 {
00466         if( !m_appname.size() )
00467                 throw std::logic_error("Cannot use SetKey() without specifying an appname in the constructor.");
00468 
00469         std::string fullkey = "X-" + m_appname + "-" + key;
00470         keymap_type::const_iterator ci = m_keymap.find(fullkey);
00471         if( ci == m_keymap.end() )
00472                 return "";
00473         return ci->second;
00474 }
00475 
00476 void GlobalConfigFile::SetLastDevice(const Barry::Pin &pin)
00477 {
00478         m_lastDevice = pin;
00479 }
00480 
00481 void GlobalConfigFile::SetVerboseLogging(bool verbose)
00482 {
00483         m_verboseLogging = verbose;
00484 }
00485 
00486 
00487 } // namespace Barry
00488