00001 #include <stdlib.h>
00002 #include <stdio.h>
00003 #include <sys/types.h>
00004 #include <sys/stat.h>
00005 #include <unistd.h>
00006
00007 #include <qdir.h>
00008 #include <qfile.h>
00009 #include <qregexp.h>
00010 #include <qdatetime.h>
00011
00012 #include "dbutil.h"
00013 #include "mythcontext.h"
00014 #include "mythdbcon.h"
00015 #include "storagegroup.h"
00016 #include "util.h"
00017
00018 #define LOC QString("DBUtil: ")
00019 #define LOC_ERR QString("DBUtil Error: ")
00020
00021 const int DBUtil::kUnknownVersionNumber = INT_MIN;
00022
00026 DBUtil::DBUtil(void)
00027 : m_versionString(QString::null), m_versionMajor(-1), m_versionMinor(-1),
00028 m_versionPoint(-1)
00029 {
00030 }
00031
00036 QString DBUtil::GetDBMSVersion(void)
00037 {
00038 if (m_versionString.isEmpty())
00039 QueryDBMSVersion();
00040 return m_versionString;
00041 }
00042
00055 int DBUtil::CompareDBMSVersion(int major, int minor, int point)
00056 {
00057 if (m_versionMajor < 0)
00058 if (!ParseDBMSVersion())
00059 return kUnknownVersionNumber;
00060
00061 int result = 0;
00062 int version[3] = {m_versionMajor, m_versionMinor, m_versionPoint};
00063 int compareto[3] = {major, minor, point};
00064 for (int i = 0; i < 3 && !result; i++)
00065 {
00066 if ((version[i] > -1) || (compareto[i] != 0))
00067 result = version[i] - compareto[i];
00068 }
00069
00070 return result;
00071 }
00072
00077 bool DBUtil::IsBackupInProgress(void)
00078 {
00079 QString backupStartTimeStr = gContext->GetSetting("BackupDBLastRunStart");
00080 QString backupEndTimeStr = gContext->GetSetting("BackupDBLastRunEnd");
00081
00082 if (backupStartTimeStr.isEmpty())
00083 {
00084 VERBOSE(VB_DATABASE, "DBUtil::BackupInProgress(): No start time found, "
00085 "database backup is not in progress.");
00086 return false;
00087 }
00088
00089 backupStartTimeStr.replace(" ", "T");
00090
00091 QDateTime backupStartTime =
00092 QDateTime::fromString(backupStartTimeStr, Qt::ISODate);
00093
00094
00095 if (backupEndTimeStr.isEmpty())
00096 {
00097
00098 if (backupStartTime.secsTo(QDateTime::currentDateTime()) < 600)
00099 {
00100 VERBOSE(VB_DATABASE, QString("DBUtil::BackupInProgress(): Found "
00101 "database backup start time of %1 which was %2 seconds "
00102 "ago, therefore it appears the backup is still running.")
00103 .arg(backupStartTimeStr)
00104 .arg(backupStartTime.secsTo(QDateTime::currentDateTime())));
00105 return true;
00106 }
00107 else
00108 {
00109 VERBOSE(VB_DATABASE, QString("DBUtil::BackupInProgress(): "
00110 "Database backup started at %1, but no end time was found. "
00111 "The backup started %2 seconds ago and should have "
00112 "finished by now therefore it appears it is not running .")
00113 .arg(backupStartTimeStr)
00114 .arg(backupStartTime.secsTo(QDateTime::currentDateTime())));
00115 return false;
00116 }
00117 }
00118 else
00119 {
00120 backupEndTimeStr.replace(" ", "T");
00121
00122 QDateTime backupEndTime =
00123 QDateTime::fromString(backupEndTimeStr, Qt::ISODate);
00124
00125 if (backupEndTime >= backupStartTime)
00126 {
00127 VERBOSE(VB_DATABASE, QString("DBUtil::BackupInProgress(): Found "
00128 "database backup end time of %1 later than start time "
00129 "of %2, therefore backup is not running.")
00130 .arg(backupEndTimeStr).arg(backupStartTimeStr));
00131 return false;
00132 }
00133 else if (backupStartTime.secsTo(QDateTime::currentDateTime()) > 600)
00134 {
00135 VERBOSE(VB_DATABASE, QString("DBUtil::BackupInProgress(): "
00136 "Database backup started at %1, but has not ended yet. "
00137 "The backup started %2 seconds ago and should have "
00138 "finished by now therefore it appears it is not running")
00139 .arg(backupStartTimeStr)
00140 .arg(backupStartTime.secsTo(QDateTime::currentDateTime())));
00141 return false;
00142 }
00143 else
00144 {
00145
00146 VERBOSE(VB_DATABASE, QString("DBUtil::BackupInProgress(): "
00147 "Database backup started at %1, and is still running.")
00148 .arg(backupStartTimeStr));
00149 return true;
00150 }
00151 }
00152
00153
00154 return false;
00155 }
00156
00163 bool DBUtil::BackupDB(QString &filename)
00164 {
00165 bool result = false;
00166 MSqlQuery query(MSqlQuery::InitCon());
00167
00168 gContext->SaveSettingOnHost("BackupDBLastRunStart",
00169 QDateTime::currentDateTime()
00170 .toString("yyyy-MM-dd hh:mm:ss"), NULL);
00171
00172 result = DoBackup(filename);
00173
00174 gContext->SaveSettingOnHost("BackupDBLastRunEnd",
00175 QDateTime::currentDateTime()
00176 .toString("yyyy-MM-dd hh:mm:ss"), NULL);
00177
00178 if (query.isConnected())
00179 {
00180 QString dbTag("BackupDB");
00181 query.prepare("DELETE FROM housekeeping WHERE tag = :TAG ;");
00182 query.bindValue(":TAG", dbTag);
00183 query.exec();
00184
00185 query.prepare("INSERT INTO housekeeping(tag,lastrun) "
00186 "values(:TAG ,now()) ;");
00187 query.bindValue(":TAG", dbTag);
00188 query.exec();
00189 }
00190
00191 return result;
00192 }
00193
00203 QStringList DBUtil::GetTables(void)
00204 {
00205 QStringList result;
00206 MSqlQuery query(MSqlQuery::InitCon());
00207 if (query.isConnected())
00208 {
00209 QString sql;
00210
00211
00212 bool supportsTableType = (CompareDBMSVersion(5, 0, 2) >= 0);
00213 if (supportsTableType)
00214 sql = "SHOW FULL TABLES;";
00215 else
00216 sql = "SHOW TABLES;";
00217
00218 query.prepare(sql);
00219
00220 if (query.exec() && query.size() > 0)
00221 {
00222 while(query.next())
00223 {
00224 if (supportsTableType)
00225 if (query.value(1).toString() == "VIEW")
00226 continue;
00227 result.append(query.value(0).toString());
00228 }
00229 }
00230 else
00231 MythContext::DBError("DBUtil Finding Tables", query);
00232 }
00233 return result;
00234 }
00235
00248 QString DBUtil::CreateBackupFilename(QString prefix, QString extension)
00249 {
00250 QDateTime now = QDateTime::currentDateTime();
00251 QString time = now.toString("yyyyMMddhhmmss");
00252 return QString("%1-%2%3").arg(prefix).arg(time).arg(extension);
00253 }
00254
00264 QString DBUtil::GetBackupDirectory()
00265 {
00266 QString directory;
00267 StorageGroup sgroup("DB Backups", gContext->GetHostName());
00268 QStringList dirList = sgroup.GetDirList();
00269 if (dirList.size())
00270 {
00271 directory = sgroup.FindNextDirMostFree();
00272
00273 if (!QDir(directory).exists())
00274 {
00275 VERBOSE(VB_FILE, "GetBackupDirectory() - ignoring "
00276 + directory + ", using /tmp");
00277 directory = QString::null;
00278 }
00279 }
00280
00281 if (!directory)
00282
00283
00284
00285
00286 directory = "/tmp";
00287
00288 return directory;
00289 }
00290
00294 bool DBUtil::DoBackup(QString &filename)
00295 {
00296 DatabaseParams dbParams = gContext->GetDatabaseParams();
00297 QString dbSchemaVer = gContext->GetSetting("DBSchemaVer");
00298 QString backupDirectory = GetBackupDirectory();
00299
00300
00301
00302
00303
00304
00305 QString tempExtraConfFile = QDeepCopy<QString>(
00306 createTempFile("/tmp/mythtv_db_backup_conf_XXXXXX"));
00307 FILE *fp;
00308 if (!(fp = fopen(tempExtraConfFile.ascii(), "w")))
00309 {
00310 VERBOSE(VB_IMPORTANT, LOC_ERR + QString("Unable to create temporary "
00311 "configuration file for creating DB backup: %1")
00312 .arg(tempExtraConfFile.ascii()));
00313 VERBOSE(VB_IMPORTANT, LOC_ERR + QString("Attempting backup, anyway. "
00314 "If the backup fails, please add the %1 user's database "
00315 "password to your MySQL option file.")
00316 .arg(dbParams.dbUserName));
00317 }
00318 else
00319 {
00320 chmod(tempExtraConfFile.ascii(), S_IRUSR);
00321 fprintf(fp, QString("[client]\npassword=%1\n"
00322 "[mysqldump]\npassword=%2\n")
00323 .arg(dbParams.dbPassword).arg(dbParams.dbPassword));
00324 if (fclose(fp))
00325 VERBOSE(VB_IMPORTANT, LOC_ERR + QString("Error closing %1: %2")
00326 .arg(tempExtraConfFile.ascii()).arg(strerror(errno)));
00327 }
00328
00329 QString command;
00330 QString compressCommand("");
00331 QString extension = ".sql";
00332 if (QFile::exists("/bin/gzip"))
00333 compressCommand = "/bin/gzip";
00334 else if (QFile::exists("/usr/bin/gzip"))
00335 compressCommand = "/usr/bin/gzip";
00336 else
00337 VERBOSE(VB_IMPORTANT, "Neither /bin/gzip nor /usr/bin/gzip exist. "
00338 "The database backup will be uncompressed.");
00339
00340 QString backupPathname = backupDirectory + "/" +
00341 CreateBackupFilename(dbParams.dbName + "-" +
00342 dbSchemaVer, extension);
00343 command = QString("mysqldump --defaults-extra-file='%1' --host='%2'"
00344 " --user='%3' --add-drop-table --add-locks"
00345 " --allow-keywords --complete-insert"
00346 " --extended-insert --lock-tables --no-create-db --quick"
00347 " '%4' > '%5' 2>/dev/null")
00348 .arg(tempExtraConfFile).arg(dbParams.dbHostName)
00349 .arg(dbParams.dbUserName).arg(dbParams.dbName)
00350 .arg(backupPathname);
00351 VERBOSE(VB_FILE, QString("Backing up database with command: %1")
00352 .arg(command.ascii()));
00353 VERBOSE(VB_IMPORTANT, QString("Backing up database to file: %1")
00354 .arg(backupPathname.ascii()));
00355 uint status = system(command.ascii());
00356
00357 unlink(tempExtraConfFile.ascii());
00358
00359 if (status)
00360 {
00361 VERBOSE(VB_IMPORTANT, LOC_ERR +
00362 QString("Error backing up database: %1 (%2)")
00363 .arg(command.ascii()).arg(status));
00364 filename = "__FAILED__";
00365 return false;
00366 }
00367
00368 if (compressCommand != "")
00369 {
00370 VERBOSE(VB_IMPORTANT, "Compressing database backup file.");
00371 compressCommand += " " + backupPathname;
00372 status = system(compressCommand.ascii());
00373
00374 if (status)
00375 {
00376 VERBOSE(VB_IMPORTANT,
00377 "Compression failed, backup file will remain uncompressed.");
00378 }
00379 else
00380 {
00381 backupPathname += ".gz";
00382
00383 VERBOSE(VB_IMPORTANT, QString("Database Backup filename: %1")
00384 .arg(backupPathname.ascii()));
00385 }
00386 }
00387
00388 VERBOSE(VB_IMPORTANT, "Database Backup complete.");
00389
00390 filename = backupPathname;
00391 return true;
00392 }
00393
00398 bool DBUtil::QueryDBMSVersion(void)
00399 {
00400
00401
00402
00403 QString dbmsVersion = gContext->GetSetting("DBMSVersionOverride");
00404
00405 if (dbmsVersion.isEmpty())
00406 {
00407 MSqlQuery query(MSqlQuery::InitCon());
00408 query.prepare("SELECT VERSION();");
00409 if (!query.exec() || !query.next())
00410 {
00411 VERBOSE(VB_IMPORTANT, LOC_ERR + "Unable to determine MySQL "
00412 "version.");
00413 MythContext::DBError("DBUtil Querying DBMS version", query);
00414 dbmsVersion = QString::null;
00415 }
00416 else
00417 dbmsVersion = QString::fromUtf8(query.value(0).toString());
00418 }
00419 m_versionString = dbmsVersion;
00420
00421 return !m_versionString.isEmpty();
00422 }
00423
00427 bool DBUtil::ParseDBMSVersion()
00428 {
00429 if (m_versionString.isEmpty())
00430 if (!QueryDBMSVersion())
00431 return false;
00432
00433 bool ok;
00434 QString section;
00435 int pos = 0, i = 0;
00436 int version[3] = {-1, -1, -1};
00437 QRegExp digits("(\\d+)");
00438
00439 while ((i < 3) && ((pos = digits.search(m_versionString, pos)) > -1))
00440 {
00441 section = digits.cap(1);
00442 pos += digits.matchedLength();
00443 version[i] = section.toInt(&ok, 10);
00444 if (!ok)
00445 version[i] = -1;
00446 i++;
00447 }
00448
00449 m_versionMajor = version[0];
00450 m_versionMinor = version[1];
00451 m_versionPoint = version[2];
00452
00453 return m_versionMajor > -1;
00454 }
00455
00456