blob: 11321dff6680e5166e5d492c6936e0d721a9c38d [file] [log] [blame]
/** @file
This file contains code for class to allow rollback of configuration files
@section license License
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#include "tscore/ink_platform.h"
#include "tscore/ink_string.h"
#include "tscore/ink_memory.h"
#include "tscore/ink_time.h"
#include "tscore/ink_file.h"
#include "LocalManager.h"
#include "Rollback.h"
#include "WebMgmtUtils.h"
#include "MgmtUtils.h"
#include "ExpandingArray.h"
#include "MgmtSocket.h"
#include "tscore/ink_cap.h"
#include "tscore/I_Layout.h"
#include "FileManager.h"
#include "ProxyConfig.h"
#define MAX_VERSION_DIGITS 11
#define DEFAULT_BACKUPS 2
// Error Strings
const char *RollbackStrings[] = {"Rollback Ok", "File was not found", "Version was out of date", "System Call Error",
"Invalid Version - Version Numbers Must Increase"};
Rollback::Rollback(const char *fileName_, bool root_access_needed_, Rollback *parentRollback_, unsigned flags)
: configFiles(nullptr),
root_access_needed(root_access_needed_),
parentRollback(parentRollback_),
currentVersion(0),
fileLastModified(0),
numVersions(0),
numberBackups(0)
{
version_t highestSeen; // the highest backup version
ExpandingArray existVer(25, true); // Exsisting versions
struct stat fileInfo;
MgmtInt numBak;
char *alarmMsg;
// To Test, Read/Write access to the file
int testFD; // For open test
int testErrno; // For open test
// In case the file is missing
char *highestSeenStr;
char *activeVerStr;
bool needZeroLength;
ink_assert(fileName_ != nullptr);
// parent must not also have a parent
if (parentRollback) {
ink_assert(parentRollback->parentRollback == nullptr);
}
// Copy the file name.
fileNameLen = strlen(fileName_);
fileName = ats_strdup(fileName_);
// Extract the file base name.
fileBaseName = strrchr(fileName, '/');
if (fileBaseName) {
fileBaseName++;
} else {
fileBaseName = fileName;
}
ink_mutex_init(&fileAccessLock);
if (varIntFromName("proxy.config.admin.number_config_bak", &numBak) == true) {
if (numBak > 1) {
numberBackups = (int)numBak;
} else {
numberBackups = 1;
}
} else {
numberBackups = DEFAULT_BACKUPS;
}
// If we are not doing backups, bail early.
if ((numberBackups <= 0) || (flags & CONFIG_FLAG_UNVERSIONED)) {
currentVersion = 0;
setLastModifiedTime();
numberBackups = 0;
return;
}
currentVersion = 0; // Prevent UMR with stat file
highestSeen = findVersions_ml(versionQ);
// Check to make sure that our configuratio file exists
//
// If we can't find our file, do our best to rollback
// or create an empty one. If that fails, just
// give up
//
if (statFile(ACTIVE_VERSION, &fileInfo) < 0) {
// If we can't find an active version because there is not
// one, attempt to rollback to a previous verision if one exists
//
// If it does not, create a zero length file to prevent total havoc
//
if (errno == ENOENT) {
mgmt_log("[RollBack::Rollback] Missing Configuration File: %s\n", fileName);
if (highestSeen > 0) {
highestSeenStr = createPathStr(highestSeen);
activeVerStr = createPathStr(ACTIVE_VERSION);
if (rename(highestSeenStr, activeVerStr) < 0) {
mgmt_log("[RollBack::Rollback] Automatic Rollback to prior version failed for %s : %s\n", fileName, strerror(errno));
needZeroLength = true;
} else {
mgmt_log("[RollBack::Rollback] Automatic Rollback to version succeded for %s\n", fileName, strerror(errno));
needZeroLength = false;
highestSeen--;
// Since we've made the highestVersion active
// remove it from the backup verision q
versionQ.remove(versionQ.tail);
}
ats_free(highestSeenStr);
ats_free(activeVerStr);
} else {
needZeroLength = true;
}
if (needZeroLength == true) {
int fd = openFile(ACTIVE_VERSION, O_RDWR | O_CREAT);
if (fd >= 0) {
alarmMsg = (char *)ats_malloc(2048);
snprintf(alarmMsg, 2048, "Created zero length place holder for config file %s", fileName);
mgmt_log("[RollBack::Rollback] %s\n", alarmMsg);
lmgmt->alarm_keeper->signalAlarm(MGMT_ALARM_CONFIG_UPDATE_FAILED, alarmMsg);
ats_free(alarmMsg);
closeFile(fd, true);
} else {
mgmt_fatal(0, "[RollBack::Rollback] Unable to find configuration file %s.\n\tCreation of a placeholder failed : %s\n",
fileName, strerror(errno));
}
}
currentVersion = highestSeen + 1;
setLastModifiedTime();
} else {
// If is there but we can not stat it, it is unusable to manager
// probably due to permissions problems. Bail!
mgmt_fatal(0, "[RollBack::Rollback] Unable to find configuration file %s.\n\tStat failed : %s\n", fileName, strerror(errno));
}
} else {
fileLastModified = TS_ARCHIVE_STAT_MTIME(fileInfo);
currentVersion = highestSeen + 1;
// Make sure that we have a backup of the file
if (highestSeen == 0) {
TextBuffer *version0 = nullptr;
char failStr[] = "[Rollback::Rollback] Automatic Roll of Version 1 failed: %s";
if (getVersion_ml(ACTIVE_VERSION, &version0) != OK_ROLLBACK) {
mgmt_log(failStr, fileName);
} else {
if (forceUpdate_ml(version0) != OK_ROLLBACK) {
mgmt_log(failStr, fileName);
}
}
if (version0 != nullptr) {
delete version0;
}
}
Debug("rollback", "[Rollback::Rollback] Current Version of %s is %d", fileName, currentVersion);
}
// Now that we'll got every thing set up, try opening
// the file to make sure that we will actually be able to
// read and write it
testFD = openFile(ACTIVE_VERSION, O_RDWR, &testErrno);
if (testFD < 0) {
// We failed to open read-write
alarmMsg = (char *)ats_malloc(2048);
testFD = openFile(ACTIVE_VERSION, O_RDONLY, &testErrno);
if (testFD < 0) {
// We are unable to either read or write the file
snprintf(alarmMsg, 2048, "Unable to read or write config file");
mgmt_log("[Rollback::Rollback] %s %s: %s\n", alarmMsg, fileName, strerror(errno));
lmgmt->alarm_keeper->signalAlarm(MGMT_ALARM_CONFIG_UPDATE_FAILED, alarmMsg);
} else {
// Read is OK and write fails
snprintf(alarmMsg, 2048, "Config file is read-only");
mgmt_log("[Rollback::Rollback] %s : %s\n", alarmMsg, fileName);
lmgmt->alarm_keeper->signalAlarm(MGMT_ALARM_CONFIG_UPDATE_FAILED, alarmMsg);
closeFile(testFD, false);
}
ats_free(alarmMsg);
} else {
closeFile(testFD, true);
}
}
Rollback::~Rollback()
{
ats_free(fileName);
}
// Rollback::createPathStr(version_t version)
//
// CALLEE DELETES STORAGE
//
char *
Rollback::createPathStr(version_t version)
{
int bufSize = 0;
char *buffer = nullptr;
std::string sysconfdir(RecConfigReadConfigDir());
bufSize = sysconfdir.size() + fileNameLen + MAX_VERSION_DIGITS + 1;
buffer = (char *)ats_malloc(bufSize);
Layout::get()->relative_to(buffer, bufSize, sysconfdir, fileName);
if (version != ACTIVE_VERSION) {
size_t pos = strlen(buffer);
snprintf(buffer + pos, bufSize - pos, "_%d", version);
}
return buffer;
}
//
//
// int Rollback::statFile(version_t)
//
// A wrapper for stat()
//
int
Rollback::statFile(version_t version, struct stat *buf)
{
int statResult;
if (version == this->currentVersion) {
version = ACTIVE_VERSION;
}
ats_scoped_str filePath(createPathStr(version));
statResult = root_access_needed ? elevating_stat(filePath, buf) : stat(filePath, buf);
return statResult;
}
//
// int Rollback::openFile(version_t)
//
// a wrapper for open()
//
int
Rollback::openFile(version_t version, int oflags, int *errnoPtr)
{
int fd;
ats_scoped_str filePath(createPathStr(version));
// TODO: Use the original permissions
// Anyhow the _1 files should not be created inside Syconfdir.
//
fd = mgmt_open_mode_elevate(filePath, oflags, 0644, root_access_needed);
if (fd < 0) {
if (errnoPtr != nullptr) {
*errnoPtr = errno;
}
mgmt_log("[Rollback::openFile] Open of %s failed: %s\n", fileName, strerror(errno));
} else {
fcntl(fd, F_SETFD, FD_CLOEXEC);
}
return fd;
}
int
Rollback::closeFile(int fd, bool callSync)
{
int result = 0;
if (callSync && fsync(fd) < 0) {
result = -1;
mgmt_log("[Rollback::closeFile] fsync failed for file '%s' (%d: %s)\n", fileName, errno, strerror(errno));
}
if (result == 0) {
result = close(fd);
} else {
close(fd);
}
return result;
}
RollBackCodes
Rollback::updateVersion(TextBuffer *buf, version_t basedOn, version_t newVersion, bool notifyChange, bool incVersion)
{
RollBackCodes returnCode;
this->acquireLock();
returnCode = this->updateVersion_ml(buf, basedOn, newVersion, notifyChange, incVersion);
this->releaseLock();
return returnCode;
}
RollBackCodes
Rollback::updateVersion_ml(TextBuffer *buf, version_t basedOn, version_t newVersion, bool notifyChange, bool incVersion)
{
RollBackCodes returnCode;
if (basedOn != currentVersion) {
returnCode = VERSION_NOT_CURRENT_ROLLBACK;
} else {
returnCode = internalUpdate(buf, newVersion, notifyChange, incVersion);
}
return returnCode;
}
RollBackCodes
Rollback::forceUpdate(TextBuffer *buf, version_t newVersion)
{
RollBackCodes r;
ink_mutex_acquire(&fileAccessLock);
r = this->forceUpdate_ml(buf, newVersion);
ink_mutex_release(&fileAccessLock);
return r;
}
RollBackCodes
Rollback::forceUpdate_ml(TextBuffer *buf, version_t newVersion)
{
return this->internalUpdate(buf, newVersion);
}
// Rollback::internalUpdate()
//
// Creates a version from buf. Callee must be holding the lock
//
RollBackCodes
Rollback::internalUpdate(TextBuffer *buf, version_t newVersion, bool notifyChange, bool incVersion)
{
RollBackCodes returnCode;
char *activeVersion;
char *currentVersion_local;
char *nextVersion;
ssize_t writeBytes;
int diskFD;
int ret;
versionInfo *toRemove;
versionInfo *newBak;
bool failedLink = false;
char *alarmMsg = nullptr;
// Check to see if the callee has specified a newVersion number
// If the newVersion argument is less than zero, the callee
// is telling us to use the next version in squence
if (newVersion < 0) {
newVersion = this->currentVersion + 1;
if (incVersion) {
incVersion = false; // because the version already increment
}
} else {
// We need to make sure that the specified version is valid
// We can NOT go back in time to a smaller version number
// than the one we have now
if (newVersion <= this->currentVersion) {
return INVALID_VERSION_ROLLBACK;
}
}
Debug("rollback", "[Rollback::internalUpdate] Moving %s from version %d to version %d", this->fileName, this->currentVersion,
newVersion);
currentVersion_local = createPathStr(this->currentVersion);
activeVersion = createPathStr(ACTIVE_VERSION);
nextVersion = createPathStr(newVersion);
// Create the new configuration file
// TODO: Make sure they are not created in Sysconfigdir!
diskFD = openFile(newVersion, O_WRONLY | O_CREAT | O_TRUNC);
if (diskFD < 0) {
// Could not create the new file. The operation is aborted
mgmt_log("[Rollback::internalUpdate] Unable to create new version of %s : %s\n", fileName, strerror(errno));
returnCode = SYS_CALL_ERROR_ROLLBACK;
goto UPDATE_CLEANUP;
}
// Write the buffer into the new configuration file
writeBytes = write(diskFD, buf->bufPtr(), buf->spaceUsed());
ret = closeFile(diskFD, true);
if ((ret < 0) || ((size_t)writeBytes != buf->spaceUsed())) {
mgmt_log("[Rollback::internalUpdate] Unable to write new version of %s : %s\n", fileName, strerror(errno));
returnCode = SYS_CALL_ERROR_ROLLBACK;
goto UPDATE_CLEANUP;
}
// Now that we got a the new version on the disk lets do some renaming
if (link(activeVersion, currentVersion_local) < 0) {
mgmt_log("[Rollback::internalUpdate] Link failed : %s\n", strerror(errno));
// If the file was lost, it is lost and log the error and
// install a new file so that we do not go around in
// an endless loop
if (errno == ENOENT) {
mgmt_log("[Rollback::internalUpdate] The active version of %s was lost.\n\tThe updated copy was installed.\n", fileName);
failedLink = true;
} else {
returnCode = SYS_CALL_ERROR_ROLLBACK;
goto UPDATE_CLEANUP;
}
}
if (rename(nextVersion, activeVersion) < 0) {
mgmt_log("[Rollback::internalUpdate] Rename failed : %s\n", strerror(errno));
mgmt_log("[Rollback::internalUpdate] Unable to create new version of %s. Using prior version\n", fileName);
returnCode = SYS_CALL_ERROR_ROLLBACK;
goto UPDATE_CLEANUP;
}
setLastModifiedTime();
// Check to see if we need to delete an excess backup versions
//
// We subtract one from numVersions to exclude the active
// copy we just created. If we subtracted two, but left
// the toRemove calculation the same, version one would
// never get deleted
//
if (numVersions >= this->numberBackups && failedLink == false) {
toRemove = versionQ.head;
ink_release_assert(toRemove != nullptr);
ink_assert((toRemove->version) < this->currentVersion);
removeVersion_ml(toRemove->version);
}
// If we created a backup version add it to the
// List of backup Versions
if (failedLink == false) {
newBak = new versionInfo;
newBak->version = this->currentVersion;
newBak->modTime = 0;
versionQ.enqueue(newBak);
}
// Update instance varibles
this->numVersions++;
this->currentVersion = newVersion;
returnCode = OK_ROLLBACK;
// Post the change to the config file manager
if (notifyChange && configFiles) {
configFiles->fileChanged(fileName, incVersion);
}
UPDATE_CLEANUP:
// Signal an alarm if we failed since if we are unable
// to manipulate the disk, the error might not get
// written to disk
if (returnCode != OK_ROLLBACK) {
alarmMsg = (char *)ats_malloc(1024);
snprintf(alarmMsg, 1024, "[TrafficManager] Configuration File Update Failed: %s", strerror(errno));
lmgmt->alarm_keeper->signalAlarm(MGMT_ALARM_CONFIG_UPDATE_FAILED, alarmMsg);
ats_free(alarmMsg);
// Remove both the link from currentVersion_local
// to the active version and the new version
// that they will not screw up our version id on restart
unlink(currentVersion_local);
unlink(nextVersion);
}
ats_free(currentVersion_local);
ats_free(activeVersion);
ats_free(nextVersion);
return returnCode;
}
RollBackCodes
Rollback::getVersion(version_t version, TextBuffer **buffer)
{
RollBackCodes r;
this->acquireLock();
r = getVersion_ml(version, buffer);
this->releaseLock();
return r;
}
// CALLEE DELETES STORAGE
RollBackCodes
Rollback::getVersion_ml(version_t version, TextBuffer **buffer)
{
int diskFD; // file descriptor for version of the file we are fetching
RollBackCodes returnCode; // our eventual return value
struct stat fileInfo; // Info from fstat
int readResult; // return val of (indirect) read calls
TextBuffer *newBuffer; // return buffer
*buffer = nullptr;
if (version == currentVersion) {
version = ACTIVE_VERSION;
}
diskFD = openFile(version, O_RDONLY);
if (diskFD < 0) {
returnCode = FILE_NOT_FOUND_ROLLBACK;
goto GET_CLEANUP;
}
// fstat the file so that we know what size is supposed to be
if (fstat(diskFD, &fileInfo) < 0) {
mgmt_log("[Rollback::getVersion] fstat on %s version %d failed: %s\n", fileName, version, strerror(errno));
returnCode = FILE_NOT_FOUND_ROLLBACK;
goto GET_CLEANUP;
}
// Create a textbuffer of the
newBuffer = new TextBuffer(fileInfo.st_size + 1);
do {
readResult = newBuffer->readFromFD(diskFD);
if (readResult < 0) {
mgmt_log("[Rollback::getVersion] read failed on %s version %d: %s\n", fileName, version, strerror(errno));
returnCode = SYS_CALL_ERROR_ROLLBACK;
delete newBuffer;
goto GET_CLEANUP;
}
} while (readResult > 0);
if ((off_t)newBuffer->spaceUsed() != fileInfo.st_size) {
mgmt_log("[Rollback::getVersion] Incorrect amount of data retrieved from %s version %d. Expected: %d Got: %d\n", fileName,
version, fileInfo.st_size, newBuffer->spaceUsed());
returnCode = SYS_CALL_ERROR_ROLLBACK;
delete newBuffer;
goto GET_CLEANUP;
}
*buffer = newBuffer;
returnCode = OK_ROLLBACK;
GET_CLEANUP:
if (diskFD >= 0) {
closeFile(diskFD, false);
}
return returnCode;
}
RollBackCodes
Rollback::revertToVersion(version_t version)
{
RollBackCodes r;
ink_mutex_acquire(&fileAccessLock);
r = this->revertToVersion_ml(version);
ink_mutex_release(&fileAccessLock);
return r;
}
// Rollback::revertToVersion_ml(version_t version)
//
// assumes callee is holding this->fileAccessLock
//
// moves the current version to fileName_currentVersion
// copies fileName_revertToVersion fileName
// increases this->currentVersion, this->numVersion
//
RollBackCodes
Rollback::revertToVersion_ml(version_t version)
{
RollBackCodes returnCode;
TextBuffer *revertTo;
returnCode = this->getVersion_ml(version, &revertTo);
if (returnCode != OK_ROLLBACK) {
mgmt_log("[Rollback::revertToVersion] Unable to open version %d of %s\n", version, fileName);
return returnCode;
}
returnCode = forceUpdate_ml(revertTo);
if (returnCode != OK_ROLLBACK) {
mgmt_log("[Rollback::revertToVersion] Unable to revert to version %d of %s\n", version, fileName);
}
delete revertTo;
return OK_ROLLBACK;
}
version_t
Rollback::findVersions(ExpandingArray *listNames)
{
version_t result;
ink_mutex_acquire(&fileAccessLock);
result = this->findVersions_ml(listNames);
ink_mutex_release(&fileAccessLock);
return result;
}
// Rollback::findVersions_ml()
//
// scans the configuration directory and returns the high
// version number encountered. If no versions of the
// file were found, zero is returned
//
version_t
Rollback::findVersions_ml(ExpandingArray *listNames)
{
int count = 0;
version_t highestSeen = 0, version = 0;
ats_scoped_str sysconfdir(RecConfigReadConfigDir());
DIR *dir;
struct dirent *entryPtr;
dir = opendir(sysconfdir);
if (dir == nullptr) {
mgmt_log("[Rollback::findVersions] Unable to open configuration directory: %s: %s\n", (const char *)sysconfdir,
strerror(errno));
return INVALID_VERSION;
}
while ((entryPtr = readdir(dir))) {
if ((version = extractVersionInfo(listNames, entryPtr->d_name)) != INVALID_VERSION) {
count++;
if (version > highestSeen) {
highestSeen = version;
}
}
}
closedir(dir);
numVersions = count;
return highestSeen;
}
// version_t Rollback::extractVersionInfo(ExpandingArray* listNames,
// const char* testFileName)
//
// Extracts the version number out of testFileName if it matches
// with the fileName_version format; adds the fileInfo to
// listNames if there is a match; returns INVALID_VERSION
// if there is no match.
//
version_t
Rollback::extractVersionInfo(ExpandingArray *listNames, const char *testFileName)
{
const char *currentVersionStr, *str;
version_t version = INVALID_VERSION;
// Check to see if the current entry is a rollback file
// fileFormat: fileName_version
//
// Check to see if the prefix of the current entry
// is the same as our fileName
if (strlen(testFileName) > fileNameLen) {
if (strncmp(testFileName, fileName, fileNameLen) == 0) {
// Check for the underscore
if (*(testFileName + fileNameLen) == '_') {
// Check for the integer version number
currentVersionStr = str = testFileName + fileNameLen + 1;
for (; isdigit(*str) && *str != '\0'; str++) {
;
}
// Do not tolerate anything but numbers on the end
// of the file
if (*str == '\0') {
version = atoi(currentVersionStr);
// Add info about version number and modTime
if (listNames != nullptr) {
struct stat fileInfo;
versionInfo *verInfo;
if (statFile(version, &fileInfo) >= 0) {
verInfo = (versionInfo *)ats_malloc(sizeof(versionInfo));
verInfo->version = version;
verInfo->modTime = fileInfo.st_mtime;
listNames->addEntry((void *)verInfo);
}
}
}
}
}
}
return version;
}
// version_t Rollback::findVersions_ml(Queue<versionInfo>& q)
//
// Add wrapper to
// version_t Rollback::findVersions_ml(ExpandingArray* listNames)
//
// Puts the data in a queue rather than an ExpaningArray
//
version_t
Rollback::findVersions_ml(Queue<versionInfo> &q)
{
ExpandingArray versions(25, true);
int num;
versionInfo *foundVer;
versionInfo *addInfo;
version_t highest;
// Get the version info and sort it
highest = this->findVersions_ml(&versions);
num = versions.getNumEntries();
versions.sortWithFunction(versionCmp);
// Add the entries on to our passed in q
for (int i = 0; i < num; i++) {
foundVer = (versionInfo *)versions[i];
// We need to create our own copy so that
// constructor gets run
addInfo = new versionInfo;
addInfo->version = foundVer->version;
addInfo->modTime = foundVer->modTime;
q.enqueue(addInfo);
}
return highest;
}
RollBackCodes
Rollback::removeVersion(version_t version)
{
RollBackCodes r;
ink_mutex_acquire(&fileAccessLock);
r = this->removeVersion_ml(version);
ink_mutex_release(&fileAccessLock);
return r;
}
RollBackCodes
Rollback::removeVersion_ml(version_t version)
{
struct stat statInfo;
char *versionPath;
versionInfo *removeInfo = nullptr;
bool infoFound = false;
if (this->statFile(version, &statInfo) < 0) {
mgmt_log("[Rollback::removeVersion] Stat failed on %s version %d\n", fileName, version);
return FILE_NOT_FOUND_ROLLBACK;
}
versionPath = createPathStr(version);
if (unlink(versionPath) < 0) {
ats_free(versionPath);
mgmt_log("[Rollback::removeVersion] Unlink failed on %s version %d: %s\n", fileName, version, strerror(errno));
return SYS_CALL_ERROR_ROLLBACK;
}
// Take the version we just removed off of the backup queue
// We are doing a linear search but since we almost always
// are deleting the oldest version, the head of the queue
// should be what we are looking for
for (removeInfo = versionQ.head; removeInfo != nullptr; removeInfo = removeInfo->link.next) {
if (removeInfo->version == version) {
infoFound = true;
break;
}
}
if (infoFound == true) {
versionQ.remove(removeInfo);
delete removeInfo;
} else {
mgmt_log("[Rollback::removeVersion] Unable to find info about %s version %d\n", fileName, version);
}
numVersions--;
ats_free(versionPath);
return OK_ROLLBACK;
}
time_t
Rollback::versionTimeStamp(version_t version)
{
time_t t;
ink_mutex_acquire(&fileAccessLock);
t = versionTimeStamp_ml(version);
ink_mutex_release(&fileAccessLock);
return t;
}
time_t
Rollback::versionTimeStamp_ml(version_t version)
{
struct stat buf;
if (this->statFile(version, &buf) < 0) {
return -1;
} else {
return buf.st_mtime;
}
}
int
Rollback::statVersion(version_t version, struct stat *buf)
{
int r;
ink_mutex_acquire(&fileAccessLock);
r = this->statFile(version, buf);
ink_mutex_release(&fileAccessLock);
return r;
}
bool
Rollback::setLastModifiedTime()
{
struct stat fileInfo;
// Now we need to get the modification time off of the new active file
if (statFile(ACTIVE_VERSION, &fileInfo) >= 0) {
fileLastModified = TS_ARCHIVE_STAT_MTIME(fileInfo);
return true;
} else {
// We really shoudn't fail to stat the file since we just
// created it. If we do, just punt and just use the current
// time.
fileLastModified = (time(nullptr) - ink_timezone()) * 1000000000;
return false;
}
}
// bool Rollback::checkForUserUpdate()
//
// Called to check if the file has been changed
// by the user. Timestamps are compared to see if a
// change occured
//
// If the file has been changed, a new version is rolled.
// The new current version and its predicessor will
// be the same in this case. While this is pointless,
// for Rolling backward, we need to the version number
// to up'ed so that WebFileEdit knows that the file has
// changed. Rolling a new verion also has the effect
// of creating a new timestamp
//
bool
Rollback::checkForUserUpdate(RollBackCheckType how)
{
struct stat fileInfo;
bool result;
// Variables to roll the current version
version_t currentVersion_local;
TextBuffer *buf;
RollBackCodes r;
ink_mutex_acquire(&fileAccessLock);
if (this->statFile(ACTIVE_VERSION, &fileInfo) < 0) {
ink_mutex_release(&fileAccessLock);
return false;
}
if (fileLastModified < TS_ARCHIVE_STAT_MTIME(fileInfo)) {
if (how == ROLLBACK_CHECK_AND_UPDATE) {
// We've been modified, Roll a new version
currentVersion_local = this->getCurrentVersion();
r = this->getVersion_ml(currentVersion_local, &buf);
if (r == OK_ROLLBACK) {
r = this->updateVersion_ml(buf, currentVersion_local);
delete buf;
}
if (r != OK_ROLLBACK) {
mgmt_log("[Rollback::checkForUserUpdate] Failed to roll changed user file %s: %s", fileName, RollbackStrings[r]);
}
mgmt_log("User has changed config file %s\n", fileName);
}
result = true;
} else {
result = false;
}
ink_mutex_release(&fileAccessLock);
return result;
}
// int versionCmp(const void* i1, const void* i2) {
// A function that can be passed to qsort to sort arrays
// of versionInfo ptrs
//
int
versionCmp(const void *i1, const void *i2)
{
versionInfo *v1 = (versionInfo *)*(void **)i1;
versionInfo *v2 = (versionInfo *)*(void **)i2;
if ((v1->version) < v2->version) {
return -1;
} else if (v1->version == v2->version) {
return 0;
} else {
return 1;
}
}