blob: 9df4ea0b37c4ebf493c0b88c24ced52c3ce368da [file] [log] [blame]
/** @file
A brief file description
@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 "ink_unused.h" /* MAGIC_EDITING_TAG */
#include "ink_platform.h"
#include "Compatability.h"
#include "ink_resource.h"
#include "ink_error.h"
#include "FileManager.h"
#include "Main.h"
#include "Rollback.h"
#include "WebMgmtUtils.h"
#include "MgmtUtils.h"
#include "WebGlobals.h"
#include "ExpandingArray.h"
#include "MgmtSocket.h"
/****************************************************************************
*
* FileManager.cc - Code for class to manage configuration updates
*
*
****************************************************************************/
static const char snapDir[] = "snapshots";
#ifndef _WIN32
#define DIR_MODE S_IRWXU
#define FILE_MODE S_IRWXU
#else
#define FILE_MODE S_IWRITE
#endif
typedef fileEntry snapshot;
char *SnapshotStrings[] = { "Request Successful\n",
"No Snapshot Directory",
"Snapshot was not found\n",
"Creation of snapshot directory failed\n",
"Creation of snapshot file failed\n",
"Access to snapshot file Failed\n",
"Unable to write to snapshot file\n",
"Remove of Snapshot failed\n",
"Internal Error: Form Submission was invalid\n",
"No Snapshot Name Was Given\n",
"Invalid Snapshot name\n"
};
FileManager::FileManager()
{
char configTmp[PATH_NAME_MAX];
int pathLen;
struct stat statBuf;
int err;
bindings = ink_hash_table_create(InkHashTableKeyType_String);
ink_assert(bindings != NULL);
ink_mutex_init(&accessLock, "File Manager Mutex");
ink_mutex_init(&cbListLock, "File Changed Callback Mutex");
if (varStrFromName("proxy.config.config_dir", configTmp, sizeof(configTmp)) == false) {
mgmt_fatal(stderr,
"[FileManager::FileManager] Unable to find configuration directory from proxy.config.config_dir\n");
}
if ((err = stat(configTmp, &statBuf)) < 0) {
ink_strncpy(configTmp, system_config_directory,sizeof(configTmp));
if ((err = stat(configTmp, &statBuf)) < 0) {
mgmt_elog("[FileManager::FileManager] unable to stat() directory '%s': %d %d, %s\n",
mgmt_path, err, errno, strerror(errno));
mgmt_elog("[FileManager::FileManager] please set config path via command line '-path <path>' or 'proxy.config.config_dir' \n");
_exit(1);
}
}
// Set up the path to the snap shot dir
pathLen = strlen(configTmp) + strlen(snapDir) + 3;
const size_t snapshotDir_size = pathLen + 1;
snapshotDir = new char[snapshotDir_size];
snprintf(snapshotDir, snapshotDir_size, "%s%s%s", configTmp, DIR_SEP, snapDir);
// Set up info for MultiFile
managedDir = snapshotDir;
dirDescript = "snapshot";
// Check to see if the directory already exists, if not create
// it
if (stat(snapshotDir, &statBuf) < 0) {
#ifndef _WIN32
if (mkdir(snapshotDir, DIR_MODE) < 0) {
#else
if (mkdir(snapshotDir) < 0) {
#endif
// Failed to create the snapshot directory
mgmt_fatal(stderr, "[FileManager::FileManager] Failed to create the snapshot directory %s: %s\n", snapshotDir, strerror(errno));
}
}
}
// FileManager::~FileManager
//
// There is only FileManager object in the process and it
// should never need to be destructed except at
// program exit
//
FileManager::~FileManager()
{
callbackListable *cb;
fileBinding *bind;
InkHashTableEntry *entry;
InkHashTableIteratorState iterator_state;
// Let other operations finish and do not start any
// new ones
ink_mutex_acquire(&accessLock);
delete[]snapshotDir;
for (cb = cblist.pop(); cb != NULL; cb = cblist.pop()) {
delete cb;
}
for (entry = ink_hash_table_iterator_first(bindings, &iterator_state);
entry != NULL; entry = ink_hash_table_iterator_next(bindings, &iterator_state)) {
bind = (fileBinding *) ink_hash_table_entry_value(bindings, entry);
delete bind->rb;
delete bind;
}
ink_hash_table_destroy(bindings);
ink_mutex_destroy(&accessLock);
ink_mutex_destroy(&cbListLock);
}
// void FileManager::registerCallback(FileCallbackFunc func)
//
// Adds a new callback function
// callbacks are made whenever a configuration file has
// changed
//
// The callback function is responsible for free'ing
// the string the string it is passed
//
void
FileManager::registerCallback(FileCallbackFunc func)
{
callbackListable *newcb = new callbackListable();
ink_assert(newcb != NULL);
newcb->func = func;
ink_mutex_acquire(&cbListLock);
cblist.push(newcb);
ink_mutex_release(&cbListLock);
}
// void FileManager::addFile(char* baseFileName, const configFileInfo* file_info)
//
// for the baseFile, creates a Rollback object for it
//
// if file_info is not null, a WebFileEdit object is also created for
// the file
//
// Pointers to the new objects are stored in the bindings hashtable
//
void
FileManager::addFile(char *baseFileName, bool root_access_needed)
{
ink_assert(baseFileName != NULL);
fileBinding *newBind = new fileBinding;
newBind->rb = new Rollback(baseFileName, root_access_needed);
ink_mutex_acquire(&accessLock);
ink_hash_table_insert(bindings, baseFileName, newBind);
ink_mutex_release(&accessLock);
}
// bool FileManager::getRollbackObj(char* baseFileName, Rollback** rbPtr)
//
// Sets rbPtr to the rollback object associated
// with the passed in baseFileName.
//
// If there is no binding, falseis returned
//
bool
FileManager::getRollbackObj(char *baseFileName, Rollback ** rbPtr)
{
InkHashTableValue lookup;
fileBinding *bind;
int found;
ink_mutex_acquire(&accessLock);
found = ink_hash_table_lookup(bindings, baseFileName, &lookup);
ink_mutex_release(&accessLock);
bind = (fileBinding *) lookup;
if (found == 0) {
return false;
} else {
*rbPtr = bind->rb;
return true;
}
}
// bool FileManager::fileChanged(const char* baseFileName)
//
// Called by the Rollback class whenever a a config has changed
// Initiates callbacks
//
//
void
FileManager::fileChanged(const char *baseFileName)
{
callbackListable *cb;
char *filenameCopy;
ink_mutex_acquire(&cbListLock);
for (cb = cblist.head; cb != NULL; cb = cb->link.next) {
// Dup the string for each callback to be
// defensive incase it modified when it is not supposed to be
filenameCopy = xstrdup(baseFileName);
(*cb->func) (filenameCopy);
xfree(filenameCopy);
}
ink_mutex_release(&cbListLock);
}
// textBuffer* FileManager::filesManaged()
//
// Returns a comma separated list of all files currently being
// managed by this object
//
// CALLEE DELETES the returned space
textBuffer *
FileManager::filesManaged()
{
textBuffer *result = new textBuffer(1024);
const char *currentName;
const char separator[] = "\n";
fileBinding *bind;
InkHashTableEntry *entry;
InkHashTableIteratorState iterator_state;
ink_mutex_acquire(&accessLock);
// To get a stable snap shot, we need to get the rollback
// locks on all configuration files so the files
// do not change from under us
for (entry = ink_hash_table_iterator_first(bindings, &iterator_state);
entry != NULL; entry = ink_hash_table_iterator_next(bindings, &iterator_state)) {
bind = (fileBinding *) ink_hash_table_entry_value(bindings, entry);
currentName = bind->rb->getBaseName();
ink_assert(currentName);
result->copyFrom(currentName, strlen(currentName));
result->copyFrom(separator, 1);
}
ink_mutex_release(&accessLock);
return result;
}
// void FileManager::doRollbackLocks(lockAction_t action)
//
// Iterates through the Rollback objects we are managing
// and performs the parameter specified action on each lock
//
// CALLLEE must hold this->accessLock
//
void
FileManager::doRollbackLocks(lockAction_t action)
{
fileBinding *bind;
InkHashTableEntry *entry;
InkHashTableIteratorState iterator_state;
for (entry = ink_hash_table_iterator_first(bindings, &iterator_state);
entry != NULL; entry = ink_hash_table_iterator_next(bindings, &iterator_state)) {
bind = (fileBinding *) ink_hash_table_entry_value(bindings, entry);
switch (action) {
case ACQUIRE_LOCK:
bind->rb->acquireLock();
break;
case RELEASE_LOCK:
bind->rb->releaseLock();
break;
default:
ink_assert(0);
break;
}
}
}
// void FileManager::abortRestore(const char* abortTo)
//
// Iterates through the hash table of managed files
// and Rollsback one version until we get to the file
// named abortTo
//
// It is a fatal error for any of the Rollbacks to
// fail since that leaves the configuration in an
// indeterminate state
//
// CALLEE should be holding the locks on all the Rollback
// objects
//
void
FileManager::abortRestore(const char *abortTo)
{
fileBinding *bind;
InkHashTableEntry *entry;
InkHashTableIteratorState iterator_state;
version_t currentVersion;
ink_assert(abortTo != NULL);
for (entry = ink_hash_table_iterator_first(bindings, &iterator_state);
entry != NULL; entry = ink_hash_table_iterator_next(bindings, &iterator_state)) {
bind = (fileBinding *) ink_hash_table_entry_value(bindings, entry);
// We are done
if (strcmp(abortTo, bind->rb->getBaseName()) == 0) {
return;
}
currentVersion = bind->rb->getCurrentVersion();
if (bind->rb->revertToVersion_ml(currentVersion - 1) != OK_ROLLBACK) {
mgmt_fatal(stderr,
"[FileManager::abortRestore] Unable to abort a failed snapshot restore. Configuration files have been left in a inconsistent state\n");
}
}
}
// SnapResult FileManager::restoresSnap(const char* snapName)
//
// Restores the snapshot with snapName.
//
// If restoration fails, calls this->abortRestore
// to reset the configuraton state
//
SnapResult
FileManager::restoreSnap(const char *snapName, const char *snapDir)
{
fileBinding *bind;
InkHashTableEntry *entry;
InkHashTableIteratorState iterator_state;
SnapResult result = SNAP_OK;
char *snapPath;
char *filePath = NULL;
struct stat fileInfo;
textBuffer storage(2048);
snapPath = newPathString(snapDir, snapName);
ink_mutex_acquire(&accessLock);
if (stat(snapPath, &fileInfo) < 0) {
delete[]snapPath;
ink_mutex_release(&accessLock);
return SNAP_NOT_FOUND;
}
// To get a stable restore, we need to get the rollback
// locks on all configuration files so that the active files
// do not change from under us
doRollbackLocks(ACQUIRE_LOCK);
// For each file, load the snap shot file and Roll a new version
// of the active file
//
for (entry = ink_hash_table_iterator_first(bindings, &iterator_state);
entry != NULL; entry = ink_hash_table_iterator_next(bindings, &iterator_state)) {
bind = (fileBinding *) ink_hash_table_entry_value(bindings, entry);
filePath = newPathString(snapPath, bind->rb->getBaseName());
if (readFile(filePath, &storage) != SNAP_OK) {
abortRestore(bind->rb->getBaseName());
result = SNAP_FILE_ACCESS_FAILED;
break;
}
if (bind->rb->forceUpdate_ml(&storage) != OK_ROLLBACK) {
abortRestore(bind->rb->getBaseName());
result = SNAP_FILE_ACCESS_FAILED;
break;
}
delete[]filePath;
filePath = NULL;
storage.reUse();
}
doRollbackLocks(RELEASE_LOCK);
ink_mutex_release(&accessLock);
if (filePath != NULL) {
delete[]filePath;
}
delete[]snapPath;
return result;
}
// SnapResult FileManager::removeSnap(const char* snapName)
//
//
SnapResult
FileManager::removeSnap(const char *snapName, const char *snapDir)
{
#ifndef _WIN32
struct dirent *dirEntrySpace;
struct dirent *entryPtr;
DIR *dir;
#else
char *searchPattern;
WIN32_FIND_DATA W32FD;
#endif
char *snapPath;
char *snapFilePath;
bool unlinkFailed = false;
SnapResult result = SNAP_OK;
snapPath = newPathString(snapDir, snapName);
#ifndef _WIN32
dir = opendir(snapPath);
if (dir == NULL) {
mgmt_log(stderr, "[FileManager::removeSnap] Unable to open snapshot %s: %s\n", snapName, strerror(errno));
delete[]snapPath;
return SNAP_NOT_FOUND;
}
dirEntrySpace = (struct dirent *) xmalloc(sizeof(struct dirent) + pathconf(".", _PC_NAME_MAX) + 1);
while (ink_readdir_r(dir, dirEntrySpace, &entryPtr) == 0) {
if (!entryPtr)
break;
if (strcmp(".", entryPtr->d_name) == 0 || strcmp("..", entryPtr->d_name) == 0) {
continue;
}
snapFilePath = newPathString(snapPath, entryPtr->d_name);
if (unlink(snapFilePath) < 0) {
mgmt_log(stderr, "[FileManager::removeSnap] Unlink failed for %s: %s\n", snapFilePath, strerror(errno));
unlinkFailed = true;
result = SNAP_REMOVE_FAILED;
}
delete[]snapFilePath;
}
xfree(dirEntrySpace);
closedir(dir);
#else
// Append '\*' as a wildcard for FindFirstFile()
searchPattern = newPathString(snapPath, "*");
HANDLE hDInfo = FindFirstFile(searchPattern, &W32FD);
if (INVALID_HANDLE_VALUE == hDInfo) {
mgmt_log(stderr, "[FileManager::removeSnap] FindFirstFile failed for %s: %s\n", searchPattern, ink_last_err());
delete[]searchPattern;
return SNAP_NOT_FOUND;
}
delete[]searchPattern;
while (FindNextFile(hDInfo, &W32FD)) {
if (W32FD.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
continue; // ignore directories
}
snapFilePath = newPathString(snapPath, W32FD.cFileName);
if (unlink(snapFilePath) < 0) {
mgmt_log(stderr, "[FileManager::removeSnap] Unlink failed for %s: %s\n", snapFilePath, strerror(errno));
unlinkFailed = true;
result = SNAP_REMOVE_FAILED;
}
delete[]snapFilePath;
}
FindClose(hDInfo);
#endif // !_WIN32
// If we managed to get everything, remove the directory
//
if (unlinkFailed == false) {
if (rmdir(snapPath) < 0) {
// strerror() isn't reentrant/thread-safe ... Problem? /leif
mgmt_log(stderr,
"[FileManager::removeSnap] Unable to remove snapshot directory %s: %s\n", snapPath, strerror(errno));
result = SNAP_REMOVE_FAILED;
} else {
result = SNAP_OK;
}
}
delete[]snapPath;
return result;
}
//
// Creates a new snapshot with snapName
// Creates a directory named snapName in the snapshot directory
// Places a copy of every config file into the new directory
//
SnapResult
FileManager::takeSnap(const char *snapName, const char *snapDir)
{
fileBinding *bind;
InkHashTableEntry *entry;
InkHashTableIteratorState iterator_state;
char *snapPath;
SnapResult callResult = SNAP_OK;
int ret_val = 0;
struct stat snapDirStat;
// Make sure the user sent us a name
if (snapName == NULL || *snapName == '\0') {
return SNAP_NO_NAME_GIVEN;
}
if (strchr(snapName, '/') != NULL) {
return SNAP_ILLEGAL_NAME;
}
// make sure the name is legal and cleaned up
if (!checkValidName(snapName)) {
return SNAP_ILLEGAL_NAME;
}
snapPath = newPathString(snapDir, snapName);
if (!stat(snapPath, &snapDirStat)) {
if (!S_ISDIR(snapDirStat.st_mode)) {
delete[]snapPath;
return SNAP_DIR_CREATE_FAILED;
}
}
#ifndef _WIN32
if ((ret_val = mkdir(snapPath, DIR_MODE)) < 0) {
#else
if (mkdir(snapPath) < 0) {
#endif
mgmt_log(stderr, "[FileManager::takeSnap] Failed to create directory for snapshot %s: %s\n",
snapName, strerror(errno));
delete[]snapPath;
return SNAP_DIR_CREATE_FAILED;
}
ink_mutex_acquire(&accessLock);
// To get a stable snap shot, we need to get the rollback
// locks on all configuration files so the files
// do not change from under us
doRollbackLocks(ACQUIRE_LOCK);
// For each file, make a copy in the snap shot directory
for (entry = ink_hash_table_iterator_first(bindings, &iterator_state);
entry != NULL; entry = ink_hash_table_iterator_next(bindings, &iterator_state)) {
bind = (fileBinding *) ink_hash_table_entry_value(bindings, entry);
callResult = this->copyFile(bind->rb, snapPath);
if (callResult != SNAP_OK) {
// Remove the failed napshot so that we do not have a partial
// one hanging around
if (removeSnap(snapName, snapDir) != SNAP_OK) {
mgmt_log(stderr,
"[FileManager::takeSnap] Unable to remove failed snapshot %s. This snapshot should be removed by hand\n",
snapName);
}
break;
}
}
// Free all the locks since we are done
//
doRollbackLocks(RELEASE_LOCK);
ink_mutex_release(&accessLock);
delete[]snapPath;
return callResult;
}
//
// SnapResult FileManager::readFile(const char* filePath, textBuffer* contents)
//
// Reads the specified file into the textBuffer. Returns SNAP_OK if
// the file was successfully read and an error code otherwise
//
SnapResult
FileManager::readFile(const char *filePath, textBuffer * contents)
{
int diskFD;
int readResult;
ink_assert(contents != NULL);
diskFD = mgmt_open(filePath, O_RDONLY);
if (diskFD < 0) {
mgmt_log(stderr, "[FileManager::readFile] Open of snapshot file failed %s: %s\n", filePath, strerror(errno));
return SNAP_FILE_ACCESS_FAILED;
}
#ifndef _WIN32 // no need to set close-on-exec on NT
fcntl(diskFD, F_SETFD, 1);
#endif
while ((readResult = contents->readFromFD(diskFD)) > 0);
close(diskFD);
if (readResult < 0) {
mgmt_log(stderr, "[FileManager::readFile] Read of snapshot file failed %s: %s\n", filePath, strerror(errno));
return SNAP_FILE_ACCESS_FAILED;
}
return SNAP_OK;
}
// SnapResult FileManager::copyFile(Rollback* rb, const char* snapPath)
//
// Copies a file (represented by Rollback* rb) into a snapshot
// directory (snapPath)
//
SnapResult
FileManager::copyFile(Rollback * rb, const char *snapPath)
{
const char *fileName;
char *filePath;
int diskFD;
textBuffer *copyBuf;
SnapResult result;
fileName = rb->getBaseName();
// Load the current config into memory
//
// The Rollback lock is held by CALLEE
if (rb->getVersion_ml(rb->getCurrentVersion(), &copyBuf) != OK_ROLLBACK) {
mgmt_log(stderr, "[FileManager::copyFile] Unable to retreive current version of %s\n", fileName);
return SNAP_FILE_ACCESS_FAILED;
}
// Create the new file
filePath = newPathString(snapPath, fileName);
diskFD = mgmt_open_mode(filePath, O_RDWR | O_CREAT, FILE_MODE);
if (diskFD < 0) {
mgmt_log(stderr, "[FileManager::copyFile] Unable to create snapshot file %s: %s\n", fileName, strerror(errno));
delete[]filePath;
delete copyBuf;
return SNAP_FILE_CREATE_FAILED;
}
#ifndef _WIN32 // no need to set close-on-exec on NT
fcntl(diskFD, F_SETFD, 1);
#endif
// Write the file contents to the copy
if (write(diskFD, copyBuf->bufPtr(), copyBuf->spaceUsed()) < 0) {
mgmt_log(stderr, "[FileManager::copyFile] Unable to write snapshot file %s: %s\n", fileName, strerror(errno));
result = SNAP_WRITE_FAILED;
} else {
result = SNAP_OK;
}
delete[]filePath;
delete copyBuf;
close(diskFD);
return result;
}
// SnapResult FileManager::WalkSnaps(ExpandingArray* snapList)
//
// Iterates through the snapshot directory and adds every snapshot
// into the parameter snapList
//
// CALLEE should be holding this->accessLock
//
SnapResult
FileManager::WalkSnaps(ExpandingArray * snapList)
{
MFresult r;
// These aren't used.
//char altSnapshotDir[256];
//InkHashTableValue snapDirLookup;
//fileBinding* snapBind;
//InkHashTableEntry *entry;
//InkHashTableIteratorState iterator_state;
//char *filePath;
bool found;
char config_dir[256];
char *snapshot_dir;
ink_mutex_acquire(&accessLock);
snapshot_dir = (char *) REC_readString("proxy.config.snapshot_dir", &found);
ink_assert(found);
//config_dir = lmgmt->record_data->readString("proxy.config.config_dir", &found);
//ink_assert(found);
if (varStrFromName("proxy.config.config_dir", config_dir, 256) == false) {
mgmt_fatal(stderr,
"[FileManager::FileManager] Unable to find configuration directory from proxy.config.config_dir\n");
}
if (snapshot_dir == NULL) {
const size_t snapshot_dir_size = strlen("snapshots");
snapshot_dir = new char[snapshot_dir_size];
ink_assert(snapshot_dir);
snprintf(snapshot_dir, snapshot_dir_size, "%s", "snapshots");
REC_setString("proxy.config.snapshot_dir", snapshot_dir);
}
//if(strncmp(snapshot_dir, config_dir, strlen(config_dir)))
if (snapshot_dir[0] != '/')
managedDir = newPathString(config_dir, snapshot_dir);
else
managedDir = snapshot_dir;
//else
//managedDir = snapshot_dir;
r = WalkFiles(snapList);
//lmgmt->record_data ->setString("proxy.config.snapshot_dir", managedDir);
ink_mutex_release(&accessLock);
return (SnapResult) r;
}
// void FileManger::rereadConfig()
//
// Interates through the list of managed files and
// calls Rollback::checkForUserUpdate on them
//
// although it is tempting, DO NOT CALL FROM SIGNAL HANDLERS
// This function is not Async-Signal Safe. It
// is thread safe
void
FileManager::rereadConfig()
{
fileBinding *bind;
InkHashTableEntry *entry;
InkHashTableIteratorState iterator_state;
ink_mutex_acquire(&accessLock);
for (entry = ink_hash_table_iterator_first(bindings, &iterator_state);
entry != NULL; entry = ink_hash_table_iterator_next(bindings, &iterator_state)) {
bind = (fileBinding *) ink_hash_table_entry_value(bindings, entry);
bind->rb->checkForUserUpdate();
}
ink_mutex_release(&accessLock);
// INKqa11910
// need to first check that enable_customizations is enabled
bool found;
int enabled = (int) REC_readInteger("proxy.config.body_factory.enable_customizations",
&found);
if (found && enabled) {
fileChanged("proxy.config.body_factory.template_sets_dir");
}
}
// void FileManager::displaySnapPage(textBuffer* output, httpResponse& answerHdr)
//
// Generates an HTML page with the add form and the list
// of current snapshots
//
void
FileManager::displaySnapOption(textBuffer * output)
{
ExpandingArray snap_list(25, true);
SnapResult snap_result;
int num_snaps;
snap_result = WalkSnaps(&snap_list);
if (snap_result == SNAP_OK) {
num_snaps = snap_list.getNumEntries();
if (num_snaps > 0) {
addSelectOptions(output, &snap_list);
}
}
}
// void FileManger::createSelect(char* formVar, textBuffer* output, ExpandingArray*)
//
// Creats a form with a select list. The select options come
// from the expanding array. Action is the value for the hidden input
// tag with name action
//
void
FileManager::createSelect(char *action, textBuffer * output, ExpandingArray * options)
{
const char formOpen[] = "<form method=POST action=\"/configure/snap_action.html\">\n<select name=snap>\n";
const char formEnd[] = "</form>";
const char submitButton[] = "<input type=submit value=\"";
const char hiddenInput[] = "<input type=hidden name=action value=";
int numOptions;
numOptions = options->getNumEntries();
if (numOptions > 0) {
output->copyFrom(formOpen, strlen(formOpen));
addSelectOptions(output, options);
output->copyFrom(hiddenInput, strlen(hiddenInput));
output->copyFrom(action, strlen(action));
output->copyFrom(">\n", 2);
output->copyFrom(submitButton, strlen(submitButton));
output->copyFrom(action, strlen(action));
output->copyFrom("\">\n", 3);
output->copyFrom(formEnd, strlen(formEnd));
}
}
// bool checkValidName(const char* name)
//
// if the string is invalid, ie. all white spaces or contains "irregular" chars,
// returns 0 ; returns 1 if valid string
//
bool
FileManager::checkValidName(const char *name)
{
int length = strlen(name);
for (int i = 0; i < length; i++) {
if (!isprint(name[i]))
return false; // invalid - unprintable char
if (!isspace(name[i]))
return true; // has non-white space that is printable
}
return false; // all white spaces
}
// int snapEntryCmpFunc(void* e1, void* e2)
//
// a cmp function for snapshot structs that can
// used with qsort
//
// compares c_time
//
int
snapEntryCmpFunc(const void *e1, const void *e2)
{
snapshot *entry1 = (snapshot *) * (void **) e1;
snapshot *entry2 = (snapshot *) * (void **) e2;
if (entry1->c_time > entry2->c_time) {
return 1;
} else if (entry1->c_time < entry2->c_time) {
return -1;
} else {
return 0;
}
}