| /** @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; |
| } |
| } |