# 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