// Copyright © 2011, 2012, 2014, 2015 Richard Kettlewell. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see . #include #include "rsbackup.h" #include "Conf.h" #include "Store.h" #include "Errors.h" #include "IO.h" #include "Command.h" #include "Utils.h" #include "Database.h" #include "Prune.h" #include #include #include #include #include #include #include /** @brief Context for configuration file parsing */ struct ConfContext { /** @brief Constructor * * @param conf_ Root configuration node */ ConfContext(Conf *conf_): conf(conf_), context(conf_), host(NULL), volume(NULL) {} /** @brief Root of configuration */ Conf *conf; /** @brief Current configuration node * * Could be a @ref Conf, @ref Host or @ref Volume. */ ConfBase *context; /** @brief Current host or null */ Host *host; /** @brief Current volume or null */ Volume *volume; /** @brief Parsed directive */ std::vector bits; }; struct Directive; /** @brief Type of name-to-directive map */ typedef std::map directives_type; /** @brief Map names to directives */ static directives_type *directives; /** @brief Base class for configuration file directives */ struct Directive { /** @brief Constructor * * @param name_ Name of directive * @param min_ Minimum number of arguments * @param max_ Maximum number of arguments * * Directives are automatically registered in this constructor. */ Directive(const char *name_, int min_=0, int max_=INT_MAX): name(name_), min(min_), max(max_) { if(!directives) directives = new directives_type(); assert((*directives).find(name) == (*directives).end()); (*directives)[name] = this; } /** @brief Name of directive */ const std::string name; /** @brief Minimum number of arguments */ int min; /** @brief Maximum number of arguments */ int max; /** @brief Check directive syntax * @param cc Context containing directive * * The base class implementation just checks the minimum and maximum number * of arguments. */ virtual void check(const ConfContext &cc) const { int args = cc.bits.size() - 1; if(args < min) throw SyntaxError("too few arguments to '" + name + "'"); if(args > max) throw SyntaxError("too many arguments to '" + name + "'"); } /** @brief Act on a directive * @param cc Context containing directive */ virtual void set(ConfContext &cc) const = 0; }; /** @brief Base class for directives that can only appear in a host context */ struct HostOnlyDirective: public Directive { /** @brief Constructor * * @param name_ Name of directive * @param min_ Minimum number of arguments * @param max_ Maximum number of arguments */ HostOnlyDirective(const char *name_, int min_=0, int max_=INT_MAX): Directive(name_, min_, max_) {} virtual void check(const ConfContext &cc) const { if(cc.host == NULL) throw SyntaxError("'" + name + "' command without 'host'"); Directive::check(cc); } }; /** @brief Base class for directives that can only appear in a volume context */ struct VolumeOnlyDirective: public Directive { /** @brief Constructor * * @param name_ Name of directive * @param min_ Minimum number of arguments * @param max_ Maximum number of arguments */ VolumeOnlyDirective(const char *name_, int min_=0, int max_=INT_MAX): Directive(name_, min_, max_) {} virtual void check(const ConfContext &cc) const { if(cc.volume == NULL) throw SyntaxError("'" + name + "' command without 'volume'"); Directive::check(cc); } }; // Global directives ---------------------------------------------------------- /** @brief The @c store directive */ static const struct StoreDirective: public Directive { StoreDirective(): Directive("store", 1, 1) {} void set(ConfContext &cc) const { cc.conf->stores[cc.bits[1]] = new Store(cc.bits[1]); } } store_directive; /** @brief The @c store-pattern directive */ static const struct StorePatternDirective: public Directive { StorePatternDirective(): Directive("store-pattern", 1, 1) {} void set(ConfContext &cc) const { std::vector files; globFiles(files, cc.bits[1], GLOB_NOCHECK); for(size_t n = 0; n < files.size(); ++n) cc.conf->stores[files[n]] = new Store(files[n]); } } store_pattern_directive; /** @brief The @c stylesheet directive */ static const struct StyleSheetDirective: public Directive { StyleSheetDirective(): Directive("stylesheet", 1, 1) {} void set(ConfContext &cc) const { cc.conf->stylesheet = cc.bits[1]; } } stylesheet_directive; /** @brief The @c colors directive */ static const struct ColorsDirective: public Directive { ColorsDirective(): Directive("colors", 2, 2) {} void set(ConfContext &cc) const { cc.conf->colorGood = parseInteger(cc.bits[1], 0, 0xFFFFFF, 0); cc.conf->colorBad = parseInteger(cc.bits[2], 0, 0xFFFFFF, 0); } } colors_directive; /** @brief The @c device directive */ static const struct DeviceDirective: public Directive { DeviceDirective(): Directive("device", 1, 1) {} void set(ConfContext &cc) const { cc.conf->devices[cc.bits[1]] = new Device(cc.bits[1]); } } device_directive; /** @brief The @c max-usage directive */ static const struct MaxUsageDirective: public Directive { MaxUsageDirective(): Directive("max-usage", 1, 1) {} void set(ConfContext &cc) const { cc.conf->maxUsage = parseInteger(cc.bits[1], 0, 100); } } max_usage_directive; /** @brief The @c max-file-usage directive */ static const struct MaxFileUsageDirective: public Directive { MaxFileUsageDirective(): Directive("max-file-usage", 1, 1) {} void set(ConfContext &cc) const { cc.conf->maxFileUsage = parseInteger(cc.bits[1], 0, 100); } } max_file_usage_directive; /** @brief The @c public directive */ static const struct PublicDirective: public Directive { PublicDirective(): Directive("public", 0, 0) {} void set(ConfContext &cc) const { cc.conf->publicStores = true; } } public_directive; /** @brief The @c logs directive */ static const struct LogsDirective: public Directive { LogsDirective(): Directive("logs", 1, 1) {} void set(ConfContext &cc) const { cc.conf->logs = cc.bits[1]; } } logs_directive; /** @brief The @c lock directive */ static const struct LockDirective: public Directive { LockDirective(): Directive("lock", 1, 1) {} void set(ConfContext &cc) const { cc.conf->lock = cc.bits[1]; } } lock_directive; /** @brief The @c sendmail directive */ static const struct SendmailDirective: public Directive { SendmailDirective(): Directive("sendmail", 1, 1) {} void set(ConfContext &cc) const { cc.conf->sendmail = cc.bits[1]; } } sendmail_directive; /** @brief The @c pre-access-hook directive */ static const struct PreAccessHookDirective: public Directive { PreAccessHookDirective(): Directive("pre-access-hook", 1, INT_MAX) {} void set(ConfContext &cc) const { cc.conf->preAccess.assign(cc.bits.begin() + 1, cc.bits.end()); } } pre_access_hook_directive; /** @brief The @c post-access-hook directive */ static const struct PostAccessHookDirective: public Directive { PostAccessHookDirective(): Directive("post-access-hook", 1, INT_MAX) {} void set(ConfContext &cc) const { cc.conf->postAccess.assign(cc.bits.begin() + 1, cc.bits.end()); } } post_access_hook_directive; /** @brief The @c keep-prune-logs directive */ static const struct KeepPruneLogsDirective: public Directive { KeepPruneLogsDirective(): Directive("keep-prune-logs", 1, 1) {} void set(ConfContext &cc) const { cc.conf->keepPruneLogs = parseInteger(cc.bits[1], 1); } } keep_prune_logs_directive; /** @brief The @c report-prune-logs directive */ static const struct ReportPruneLogsDirective: public Directive { ReportPruneLogsDirective(): Directive("report-prune-logs", 1, 1) {} void set(ConfContext &cc) const { cc.conf->reportPruneLogs = parseInteger(cc.bits[1], 1); } } report_prune_logs_directive; /** @brief The @c include directive */ static const struct IncludeDirective: public Directive { IncludeDirective(): Directive("include", 1, 1) {} void set(ConfContext &cc) const { cc.conf->includeFile(cc.bits[1]); } } include_directive; // Inheritable directives ----------------------------------------------------- /** @brief The @c max-age directive */ static const struct MaxAgeDirective: public Directive { MaxAgeDirective(): Directive("max-age", 1, 1) {} void set(ConfContext &cc) const { cc.context->maxAge = parseInteger(cc.bits[1], 1); } } max_age_directive; /** @brief The @c min-backups directive */ static const struct MinBackupsDirective: public Directive { MinBackupsDirective(): Directive("min-backups", 1, 1) {} void set(ConfContext &cc) const { warning("the 'min-backups' directive is deprecated, use 'prune-parameter min-backups' instead"); parseInteger(cc.bits[1], 1); cc.context->pruneParameters["min-backups"] = cc.bits[1]; } } min_backups_directive; /** @brief The @c prune-age directive */ static const struct PruneAgeDirective: public Directive { PruneAgeDirective(): Directive("prune-age", 1, 1) {} void set(ConfContext &cc) const { warning("the 'prune-age' directive is deprecated, use 'prune-parameter prune-age' instead"); parseInteger(cc.bits[1], 1); cc.context->pruneParameters["prune-age"] = cc.bits[1]; } } prune_age_directive; /** @brief The @c prune-policy directive */ static const struct PrunePolicyDirective: public Directive { PrunePolicyDirective(): Directive("prune-policy", 1, 1) {} void set(ConfContext &cc) const { if(cc.bits[1].size() > 0 && cc.bits[1].at(0) == '/') { cc.context->prunePolicy = "exec"; cc.context->pruneParameters["path"] = cc.bits[1]; } else cc.context->prunePolicy = cc.bits[1]; } } prune_policy_directive; /** @brief The @c prune-parameter directive */ static const struct PruneParameterDirective: public Directive { PruneParameterDirective(): Directive("prune-parameter", 2, 2) {} virtual void check(const ConfContext &cc) const { Directive::check(cc); const std::string &name = (cc.bits[1] != "--remove" ? cc.bits[1] : cc.bits[2]); if(!valid(name)) throw SyntaxError("invalid prune-parameter name"); } void set(ConfContext &cc) const { if(cc.bits[1] != "--remove") cc.context->pruneParameters[cc.bits[1]] = cc.bits[2]; else cc.context->pruneParameters.erase(cc.bits[2]); } static bool valid(const std::string &name) { return name.size() > 0 && name.at(0) != '-' && name.find_first_not_of(PRUNE_PARAMETER_VALID) == std::string::npos; } } prune_parameter_directive; /** @brief The @c pre-backup-hook directive */ static const struct PreBackupHookDirective: public Directive { PreBackupHookDirective(): Directive("pre-backup-hook", 1, INT_MAX) {} void set(ConfContext &cc) const { cc.context->preBackup.assign(cc.bits.begin() + 1, cc.bits.end()); } } pre_backup_hook_directive; /** @brief The @c post-backup-hook directive */ static const struct PostBackupHookDirective: public Directive { PostBackupHookDirective(): Directive("post-backup-hook", 1, INT_MAX) {} void set(ConfContext &cc) const { cc.context->postBackup.assign(cc.bits.begin() + 1, cc.bits.end()); } } post_backup_hook_directive; /** @brief The @c rsync-timeout directive */ static const struct RsyncTimeoutDirective: public Directive { RsyncTimeoutDirective(): Directive("rsync-timeout", 1, 1) {} void set(ConfContext &cc) const { cc.context->rsyncTimeout = parseInteger(cc.bits[1], 1); } } rsync_timeout_directive; /** @brief The @c hook-timeout directive */ static const struct HookTimeoutDirective: public Directive { HookTimeoutDirective(): Directive("hook-timeout", 1, 1) {} void set(ConfContext &cc) const { cc.context->hookTimeout = parseInteger(cc.bits[1], 1); } } hook_timeout_directive; /** @brief The @c ssh-timeout directive */ static const struct SshTimeoutDirective: public Directive { SshTimeoutDirective(): Directive("ssh-timeout", 1, 1) {} void set(ConfContext &cc) const { cc.context->sshTimeout = parseInteger(cc.bits[1], 1); } } ssh_timeout_directive; // Host directives ------------------------------------------------------------ /** @brief The @c host directive */ static const struct HostDirective: public Directive { HostDirective(): Directive("host", 1, 1) {} void set(ConfContext &cc) const { if(!Host::valid(cc.bits[1])) throw SyntaxError("invalid host name"); if(cc.conf->hosts.find(cc.bits[1]) != cc.conf->hosts.end()) throw SyntaxError("duplicate host"); cc.context = cc.host = new Host(cc.conf, cc.bits[1]); cc.volume = NULL; cc.host->hostname = cc.bits[1]; } } host_directive; /** @brief The @c hostname directive */ static const struct HostnameDirective: public HostOnlyDirective { HostnameDirective(): HostOnlyDirective("hostname", 1, 1) {} void set(ConfContext &cc) const { cc.host->hostname = cc.bits[1]; } } hostname_directive; /** @brief The @c always-up directive */ static const struct AlwaysUpDirective: public HostOnlyDirective { AlwaysUpDirective(): HostOnlyDirective("always-up", 0, 0) {} void set(ConfContext &cc) const { cc.host->alwaysUp = true; } } always_up_directive; /** @brief The @c priority directive */ static const struct PriorityDirective: public HostOnlyDirective { PriorityDirective(): HostOnlyDirective("priority", 1, 1) {} void set(ConfContext &cc) const { cc.host->priority = parseInteger(cc.bits[1]); } } priority_directive; /** @brief The @c user directive */ static const struct UserDirective: public HostOnlyDirective { UserDirective(): HostOnlyDirective("user", 1, 1) {} void set(ConfContext &cc) const { cc.host->user = cc.bits[1]; } } user_directive; // Volume directives ---------------------------------------------------------- /** @brief The @c volume directive */ static const struct VolumeDirective: public HostOnlyDirective { VolumeDirective(): HostOnlyDirective("volume", 2, 2) {} void set(ConfContext &cc) const { if(!Volume::valid(cc.bits[1])) throw SyntaxError("invalid volume name"); if(cc.host->volumes.find(cc.bits[1]) != cc.host->volumes.end()) throw SyntaxError("duplicate volume"); cc.context = cc.volume = new Volume(cc.host, cc.bits[1], cc.bits[2]); } } volume_directive; /** @brief The @c exclude directive */ static const struct ExcludeDirective: public VolumeOnlyDirective { ExcludeDirective(): VolumeOnlyDirective("exclude", 1, 1) {} void set(ConfContext &cc) const { cc.volume->exclude.push_back(cc.bits[1]); } } exclude_directive; /** @brief The @c traverse directive */ static const struct TraverseDirective: public VolumeOnlyDirective { TraverseDirective(): VolumeOnlyDirective("traverse", 0, 0) {} void set(ConfContext &cc) const { cc.volume->traverse = true; } } traverse_directive; /** @brief The @c devices directive */ static const struct DevicesDirective: public VolumeOnlyDirective { DevicesDirective(): VolumeOnlyDirective("devices", 1, 1) {} void set(ConfContext &cc) const { cc.volume->devicePattern = cc.bits[1]; } } devices_directive; /** @brief The @c check-file directive */ static const struct CheckFileDirective: public VolumeOnlyDirective { CheckFileDirective(): VolumeOnlyDirective("check-file", 1, 1) {} void set(ConfContext &cc) const { cc.volume->checkFile = cc.bits[1]; } } check_file_directive; /** @brief The @c check-mounted directive */ static const struct CheckMountedDirective: public VolumeOnlyDirective { CheckMountedDirective(): VolumeOnlyDirective("check-mounted", 0, 0) {} void set(ConfContext &cc) const { cc.volume->checkMounted = true; } } check_mounted_directive; void Conf::write(std::ostream &os, int step) const { ConfBase::write(os, step); if(publicStores) os << indent(step) << "public" << '\n'; os << indent(step) << "logs " << quote(logs) << '\n'; if(lock.size()) os << indent(step) << "lock " << quote(lock) << '\n'; os << indent(step) << "sendmail " << quote(sendmail) << '\n'; if(preAccess.size()) os << indent(step) << "pre-access-hook " << quote(preAccess) << '\n'; if(postAccess.size()) os << indent(step) << "post-access-hook " << quote(postAccess) << '\n'; if(stylesheet.size()) os << indent(step) << "stylesheet " << quote(stylesheet) << '\n'; if(colorGood != COLOR_GOOD || colorBad != COLOR_BAD) os << indent(step) << "colors " << std::hex << "0x" << std::setw(6) << std::setfill('0') << colorGood << ' ' << "0x" << std::setw(6) << std::setfill('0') << colorBad << '\n' << std::dec; for(auto it = devices.begin(); it != devices.end(); ++it) os << "device " << quote(it->first) << '\n'; for(auto it = hosts.begin(); it != hosts.end(); ++it) { os << '\n'; static_cast(it->second)->write(os, step); } } // Read the master configuration file plus anything it includes. void Conf::read() { readOneFile(command.configPath); } // Read one configuration file. Throws IOError if some file cannot be // read or ConfigError if the contents are bad. void Conf::readOneFile(const std::string &path) { ConfContext cc(this); IO input; D("Conf::readOneFile %s", path.c_str()); input.open(path, "r"); std::string line; int lineno = 0; while(input.readline(line)) { ++lineno; // keep track of where we are try { split(cc.bits, line); if(!cc.bits.size()) // skip blank lines continue; // Consider all the possible commands auto it = (*directives).find(cc.bits[0]); if(it != (*directives).end()) { const Directive *d = it->second; d->check(cc); d->set(cc); } else { throw SyntaxError("unknown command '" + cc.bits[0] + "'"); } } catch(SyntaxError &e) { // Wrap up in a ConfigError, which carries the path/line information. std::stringstream s; s << path << ":" << lineno << ": " << e.what(); throw ConfigError(s.str()); } } } // Implementation of the 'include' command. If PATH is a directory then // includes all the regular files it contains (excluding dotfiles and backups // but including symbolic links to regular files of any name), otherwise just // tries to read it. void Conf::includeFile(const std::string &path) { struct stat sb; D("Conf::includeFile %s", path.c_str()); if(stat(path.c_str(), &sb) >= 0 && S_ISDIR(sb.st_mode)) { std::vector files; Directory::getFiles(path, files); for(size_t n = 0; n < files.size(); ++n) { const std::string name = files.at(n); if(!name.size() || name.at(0) == '.' || name.at(0) == '#' || name.find('~') != std::string::npos) continue; std::string fullname = path + PATH_SEP + name; if(stat(fullname.c_str(), &sb) >= 0 && S_ISREG(sb.st_mode)) readOneFile(fullname); } } else readOneFile(path); } void Conf::validate() const { for(auto hosts_iterator = hosts.begin(); hosts_iterator != hosts.end(); ++hosts_iterator) { Host *host = hosts_iterator->second; for(auto volumes_iterator = host->volumes.begin(); volumes_iterator != host->volumes.end(); ++volumes_iterator) { validatePrunePolicy(volumes_iterator->second); } } } // (De-)select all hosts void Conf::selectAll(bool sense) { for(auto it = hosts.begin(); it != hosts.end(); ++it) it->second->select(sense); } // (De-)select one host (or all if hostName="*") void Conf::selectHost(const std::string &hostName, bool sense) { if(hostName == "*") { selectAll(sense); } else { auto hosts_iterator = hosts.find(hostName); if(hosts_iterator == hosts.end()) throw CommandError("no such host as '" + hostName + "'"); hosts_iterator->second->select(sense); } } // (De-)select one volume (or all if volumeName="*") void Conf::selectVolume(const std::string &hostName, const std::string &volumeName, bool sense) { if(volumeName == "*") { selectHost(hostName, sense); } else { auto hosts_iterator = hosts.find(hostName); if(hosts_iterator == hosts.end()) throw CommandError("no such host as '" + hostName + "'"); Host *host = hosts_iterator->second; auto volumes_iterator = host->volumes.find(volumeName); if(volumes_iterator == host->volumes.end()) throw CommandError("no such volume as '" + hostName + ":" + volumeName + "'"); volumes_iterator->second->select(sense); } } void Conf::addHost(Host *h) { hosts[h->name] = h; } // Find a host by name Host *Conf::findHost(const std::string &hostName) const { auto it = hosts.find(hostName); return it != hosts.end() ? it->second : NULL; } // Find a volume by name Volume *Conf::findVolume(const std::string &hostName, const std::string &volumeName) const { Host *host = findHost(hostName); return host ? host->findVolume(volumeName) : NULL; } // Find a device by name Device *Conf::findDevice(const std::string &deviceName) const { auto it = devices.find(deviceName); return it != devices.end() ? it->second : NULL; } // Read in logfiles void Conf::readState() { if(logsRead) return; std::string hostName, volumeName; std::vector files; const bool progress = command.verbose && isatty(2); std::vector upgraded; std::string log; // Read database contents // Better would be to read only the rows required, on demand. { Database::Statement stmt(getdb(), "SELECT host,volume,device,id,time,pruned,rc,status,log" " FROM backup", SQL_END); while(stmt.next()) { Backup backup; hostName = stmt.get_string(0); volumeName = stmt.get_string(1); backup.deviceName = stmt.get_string(2); backup.id = stmt.get_string(3); backup.time = stmt.get_int64(4); backup.date = Date(backup.time); backup.pruned = stmt.get_int64(5); backup.rc = stmt.get_int(6); backup.setStatus(stmt.get_int(7)); backup.contents = stmt.get_blob(8); addBackup(backup, hostName, volumeName); } } // Upgrade old-format logfiles Directory::getFiles(logs, files); for(size_t n = 0; n < files.size(); ++n) { if(progress) progressBar(IO::err, "Upgrading old logs", n, files.size()); // Parse the filename if(!logfileRegexp.matches(files[n])) continue; Backup backup; backup.date = logfileRegexp.sub(1); backup.id = logfileRegexp.sub(1); backup.time = backup.date.toTime(); backup.deviceName = logfileRegexp.sub(2); hostName = logfileRegexp.sub(3); volumeName = logfileRegexp.sub(4); // Read the log IO input; std::vector contents; input.open(logs + "/" + files[n], "r"); input.readlines(contents); // Skip empty files if(contents.size() == 0) { if(progress) progressBar(IO::err, NULL, 0, 0); warning("empty file: %s", files[n].c_str()); continue; } // Find the status code const std::string &last = contents[contents.size() - 1]; backup.rc = -1; if(last.compare(0, 3, "OK:") == 0) backup.rc = 0; else { std::string::size_type pos = last.rfind("error="); if(pos < std::string::npos) sscanf(last.c_str() + pos + 6, "%i", &backup.rc); } if(last.rfind("pruning") != std::string::npos) backup.setStatus(PRUNING); else if(backup.rc == 0) backup.setStatus(COMPLETE); else backup.setStatus(FAILED); for(auto it = contents.begin(); it != contents.end(); ++it) { backup.contents += *it; backup.contents += "\n"; } addBackup(backup, hostName, volumeName, true); if(command.act) { // addBackup might fail to set volume if(backup.volume != NULL) { if(upgraded.size() == 0) getdb()->begin(); try { backup.insert(getdb()); } catch(DatabaseError &e) { if(e.status != SQLITE_CONSTRAINT) throw; } upgraded.push_back(files[n]); } else { if(progress) progressBar(IO::err, NULL, 0, 0); warning("cannot upgrade %s", files[n].c_str()); } } } logsRead = true; if(command.act && upgraded.size()) { getdb()->commit(); bool upgradeFailure = false; for(auto it = upgraded.begin(); it != upgraded.end(); ++it) { const std::string path = logs + "/" + *it; if(unlink(path.c_str())) { error("removing %s: %s", path.c_str(), strerror(errno)); upgradeFailure = true; } } if(upgradeFailure) throw SystemError("could not remove old logfiles"); } if(progress) progressBar(IO::err, NULL, 0, 0); } void Conf::addBackup(Backup &backup, const std::string &hostName, const std::string &volumeName, bool forceWarn) { const bool progress = command.verbose && isatty(2); /* Don't keep pruned backups around */ if(backup.getStatus() == PRUNED) return; if(devices.find(backup.deviceName) == devices.end()) { if(unknownDevices.find(backup.deviceName) == unknownDevices.end()) { if(command.warnUnknown || forceWarn) { if(progress) progressBar(IO::err, NULL, 0, 0); warning("unknown device %s", backup.deviceName.c_str()); } unknownDevices.insert(backup.deviceName); ++config.unknownObjects; } return; } // Find the volume for this status record. If it cannot be found, we warn // about it once. Host *host = findHost(hostName); if(!host) { if(unknownHosts.find(hostName) == unknownHosts.end()) { if(command.warnUnknown || forceWarn) { if(progress) progressBar(IO::err, NULL, 0, 0); warning("unknown host %s", hostName.c_str()); } unknownHosts.insert(hostName); ++config.unknownObjects; } return; } Volume *volume = host->findVolume(volumeName); if(!volume) { if(host->unknownVolumes.find(volumeName) == host->unknownVolumes.end()) { if(command.warnUnknown || forceWarn) { if(progress) progressBar(IO::err, NULL, 0, 0); warning("unknown volume %s:%s", hostName.c_str(), volumeName.c_str()); } host->unknownVolumes.insert(volumeName); ++config.unknownObjects; } return; } backup.volume = volume; // Attach the status record to the volume volume->addBackup(new Backup(backup)); } // Create the mapping between stores and devices. void Conf::identifyDevices(int states) { if((devicesIdentified & states) == states) return; int found = 0; std::vector storeExceptions; for(auto storesIterator = stores.begin(); storesIterator != stores.end(); ++storesIterator) { Store *store = storesIterator->second; if(!(store->state & states)) continue; try { store->identify(); ++found; } catch(UnavailableStore &unavailableStoreException) { if(command.warnStore) warning("%s", unavailableStoreException.what()); storeExceptions.push_back(unavailableStoreException); } catch(FatalStoreError &fatalStoreException) { if(states == Store::Enabled) throw; else if(command.warnStore) warning("%s", fatalStoreException.what()); } catch(BadStore &badStoreException) { if(states == Store::Enabled) error("%s", badStoreException.what()); } } if(!found && states == Store::Enabled) { error("no backup devices found"); if(!command.warnStore) for(size_t n = 0; n < storeExceptions.size(); ++n) IO::err.writef(" %s\n", storeExceptions[n].what()); } devicesIdentified |= states; } Database *Conf::getdb() { if(!db) { if(command.database.size() == 0) command.database = logs + "/backups.db"; if(command.act) { db = new Database(command.database); if(!db->hasTable("backup")) createTables(); } else { try { db = new Database(command.database, false); } catch(DatabaseError &) { db = new Database(":memory:"); createTables(); } } } return db; } void Conf::createTables() { db->begin(); db->execute("CREATE TABLE backup (\n" " host TEXT,\n" " volume TEXT,\n" " device TEXT,\n" " id TEXT,\n" " time INTEGER,\n" " pruned INTEGER,\n" " rc INTEGER,\n" " status INTEGER,\n" " log BLOB,\n" " PRIMARY KEY (host,volume,device,id)\n" ")"); db->commit(); } ConfBase *Conf::getParent() const { return NULL; } // Regexp for parsing log filenames // Format is YYYY-MM-DD-DEVICE-HOST-VOLUME.log // Captures are: 1 date // 2 device // 3 host // 4 volume Regexp Conf::logfileRegexp("^([0-9]+-[0-9]+-[0-9]+)-([^-]+)-([^-]+)-([^-]+)\\.log$"); Conf config;