blob: 470cd95b1b675cf6a23d86c49c62b1208a30adb9 [file] [log] [blame]
/** @file
@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 <ts/ts.h>
#include <stdlib.h>
#include <stdio.h>
#include <time.h>
#include <string.h>
#include <stdbool.h>
#include <getopt.h>
#include <limits.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#ifdef HAVE_PCRE_PCRE_H
#include <pcre/pcre.h>
#else
#include <pcre.h>
#endif
#define CONFIG_TMOUT 60000
#define FREE_TMOUT 300000
#define OVECTOR_SIZE 30
#define LOG_ROLL_INTERVAL 86400
#define LOG_ROLL_OFFSET 0
static const char *const PLUGIN_NAME = "regex_revalidate";
static const char *const DEFAULT_DIR = "var/trafficserver"; /* Not perfect, but no better API) */
static char const *const RESULT_MISS = "MISS";
static char const *const RESULT_STALE = "STALE";
static char const *const RESULT_UNKNOWN = "UNKNOWN";
static int stat_id_stale = TS_ERROR;
static char const *const stat_name_stale = "plugin.regex_revalidate.stale";
static int stat_id_miss = TS_ERROR;
static char const *const stat_name_miss = "plugin.regex_revalidate.miss";
static DbgCtl dbg_ctl{PLUGIN_NAME};
static void
create_stats()
{
if (TS_ERROR == stat_id_stale && TS_ERROR == TSStatFindName(stat_name_stale, &stat_id_stale)) {
stat_id_stale = TSStatCreate(stat_name_stale, TS_RECORDDATATYPE_INT, TS_STAT_NON_PERSISTENT, TS_STAT_SYNC_COUNT);
if (TS_ERROR != stat_id_stale) {
Dbg(dbg_ctl, "Created stat '%s'", stat_name_stale);
}
}
if (TS_ERROR == stat_id_miss && TS_ERROR == TSStatFindName(stat_name_miss, &stat_id_miss)) {
stat_id_miss = TSStatCreate(stat_name_miss, TS_RECORDDATATYPE_INT, TS_STAT_NON_PERSISTENT, TS_STAT_SYNC_COUNT);
if (TS_ERROR != stat_id_miss) {
Dbg(dbg_ctl, "Created stat '%s'", stat_name_miss);
}
}
}
static void
increment_stat(TSCacheLookupResult const result)
{
switch (result) {
case TS_CACHE_LOOKUP_MISS:
if (TS_ERROR != stat_id_miss) {
TSStatIntIncrement(stat_id_miss, 1);
Dbg(dbg_ctl, "Incrementing stat '%s'", stat_name_miss);
}
break;
case TS_CACHE_LOOKUP_HIT_STALE:
if (TS_ERROR != stat_id_stale) {
TSStatIntIncrement(stat_id_stale, 1);
Dbg(dbg_ctl, "Incrementing stat '%s'", stat_name_stale);
}
break;
default:
break;
}
}
static const char *
strForResult(TSCacheLookupResult const result)
{
switch (result) {
case TS_CACHE_LOOKUP_MISS:
return RESULT_MISS;
break;
case TS_CACHE_LOOKUP_HIT_STALE:
return RESULT_STALE;
break;
default:
return RESULT_UNKNOWN;
break;
}
}
typedef struct invalidate_t {
const char *regex_text;
pcre *regex;
pcre_extra *regex_extra;
time_t epoch;
time_t expiry;
TSCacheLookupResult new_result;
struct invalidate_t *next;
} invalidate_t;
typedef struct {
invalidate_t *invalidate_list;
char *config_path;
char *match_header;
time_t last_load;
TSTextLogObject log;
char *state_path;
} plugin_state_t;
static invalidate_t *
init_invalidate_t(invalidate_t *i)
{
i->regex_text = nullptr;
i->regex = nullptr;
i->regex_extra = nullptr;
i->epoch = 0;
i->expiry = 0;
i->new_result = TS_CACHE_LOOKUP_HIT_STALE;
i->next = nullptr;
return i;
}
static void
free_invalidate_t(invalidate_t *i)
{
if (i->regex_extra) {
#ifndef PCRE_STUDY_JIT_COMPILE
pcre_free(i->regex_extra);
#else
pcre_free_study(i->regex_extra);
#endif
}
if (i->regex) {
pcre_free(i->regex);
}
if (i->regex_text) {
pcre_free_substring(i->regex_text);
}
TSfree(i);
}
static void
free_invalidate_t_list(invalidate_t *iptr)
{
while (nullptr != iptr) {
invalidate_t *const next = iptr->next;
free_invalidate_t(iptr);
iptr = next;
}
}
static plugin_state_t *
init_plugin_state_t(plugin_state_t *pstate)
{
pstate->invalidate_list = nullptr;
pstate->config_path = nullptr;
pstate->match_header = nullptr;
pstate->last_load = 0;
pstate->log = nullptr;
pstate->state_path = nullptr;
return pstate;
}
static void
free_plugin_state_t(plugin_state_t *pstate)
{
if (pstate->invalidate_list) {
free_invalidate_t_list(pstate->invalidate_list);
}
if (pstate->config_path) {
TSfree(pstate->config_path);
}
if (pstate->match_header) {
TSfree(pstate->match_header);
}
if (pstate->log) {
TSTextLogObjectDestroy(pstate->log);
}
if (pstate->state_path) {
TSfree(pstate->state_path);
}
TSfree(pstate);
}
static invalidate_t *
copy_invalidate_t(invalidate_t *i)
{
invalidate_t *iptr;
const char *errptr;
int erroffset;
iptr = (invalidate_t *)TSmalloc(sizeof(invalidate_t));
iptr->regex_text = TSstrdup(i->regex_text);
iptr->regex = pcre_compile(iptr->regex_text, 0, &errptr, &erroffset, nullptr); // There is no pcre_copy :-(
iptr->regex_extra = pcre_study(iptr->regex, 0, &errptr); // Assuming no errors since this worked before :-/
iptr->epoch = i->epoch;
iptr->expiry = i->expiry;
iptr->new_result = i->new_result;
iptr->next = nullptr;
return iptr;
}
static invalidate_t *
copy_config(invalidate_t *old_list)
{
invalidate_t *new_list = nullptr;
invalidate_t *iptr_old, *iptr_new;
if (old_list) {
new_list = copy_invalidate_t(old_list);
iptr_old = old_list->next;
iptr_new = new_list;
while (iptr_old) {
iptr_new->next = copy_invalidate_t(iptr_old);
iptr_new = iptr_new->next;
iptr_old = iptr_old->next;
}
}
return new_list;
}
static bool
prune_config(invalidate_t **i)
{
invalidate_t *iptr, *ilast;
time_t now;
bool pruned = false;
now = time(nullptr);
if (*i) {
iptr = *i;
ilast = nullptr;
while (iptr) {
if (iptr->expiry <= now) {
Dbg(dbg_ctl, "Removing %s expiry: %jd type: %s now: %jd", iptr->regex_text, (intmax_t)iptr->expiry,
strForResult(iptr->new_result), (intmax_t)now);
if (ilast) {
ilast->next = iptr->next;
free_invalidate_t(iptr);
iptr = ilast->next;
} else {
*i = iptr->next;
free_invalidate_t(iptr);
iptr = *i;
}
pruned = true;
} else {
ilast = iptr;
iptr = iptr->next;
}
}
}
return pruned;
}
static bool
load_state(plugin_state_t *pstate, invalidate_t **ilist)
{
if (NULL == *ilist) {
return true;
}
FILE *const fs = fopen(pstate->state_path, "r");
if (NULL == fs) {
Dbg(dbg_ctl, "Could not open state %s for reading", pstate->state_path);
return false;
}
time_t const now = time(nullptr);
const char *errptr;
int erroffset;
int ovector[OVECTOR_SIZE];
pcre *const config_re = pcre_compile("^([^#].+?)\\s+(\\d+)\\s+(\\d+)\\s+(\\w+)\\s*$", 0, &errptr, &erroffset, nullptr);
TSReleaseAssert(nullptr != config_re);
char line[LINE_MAX];
int ln = 0;
while (fgets(line, LINE_MAX, fs) != nullptr) {
Dbg(dbg_ctl, "state: processing: %d %s", ln, line);
++ln;
int const rc = pcre_exec(config_re, nullptr, line, strlen(line), 0, 0, ovector, OVECTOR_SIZE);
if (5 == rc) {
invalidate_t *const inv = (invalidate_t *)TSmalloc(sizeof(invalidate_t));
init_invalidate_t(inv);
pcre_get_substring(line, ovector, rc, 1, &(inv->regex_text));
inv->epoch = atoi(line + ovector[4]);
inv->expiry = atoi(line + ovector[6]);
if (inv->expiry < now) {
Dbg(dbg_ctl, "state: skipping expired : '%s'", inv->regex_text);
free_invalidate_t(inv);
continue;
}
int const len = ovector[9] - ovector[8];
char const *const type = line + ovector[8];
if (0 == strncasecmp(type, RESULT_STALE, len)) {
Dbg(dbg_ctl, "state: regex line set to result type %s: '%s'", RESULT_STALE, inv->regex_text);
} else if (0 == strncasecmp(type, RESULT_MISS, len)) {
Dbg(dbg_ctl, "state: regex line set to result type %s: '%s'", RESULT_MISS, inv->regex_text);
inv->new_result = TS_CACHE_LOOKUP_MISS;
} else {
Dbg(dbg_ctl, "state: unknown regex line result type '%.*s', skipping '%s'", len, type, inv->regex_text);
}
// iterate through the loaded config and try to merge
invalidate_t *iptr = *ilist;
do {
if (0 == strcmp(inv->regex_text, iptr->regex_text)) {
if (iptr->expiry == inv->expiry && iptr->new_result == inv->new_result) {
Dbg(dbg_ctl, "state: restoring epoch for %s", iptr->regex_text);
iptr->epoch = inv->epoch;
}
break;
}
if (nullptr == iptr->next) {
break;
}
iptr = iptr->next;
} while (nullptr != iptr);
free_invalidate_t(inv);
} else {
Dbg(dbg_ctl, "state: invalid line '%s'", line);
}
}
pcre_free(config_re);
fclose(fs);
return true;
}
static bool
load_config(plugin_state_t *pstate, invalidate_t **ilist)
{
size_t path_len;
char *path;
if (pstate->config_path[0] != '/') {
path_len = strlen(TSConfigDirGet()) + strlen(pstate->config_path) + 2;
path = static_cast<char *>(alloca(path_len));
snprintf(path, path_len, "%s/%s", TSConfigDirGet(), pstate->config_path);
} else {
path = pstate->config_path;
}
int const fd = open(path, O_RDONLY);
if (fd < 0) {
Dbg(dbg_ctl, "Could not open %s for reading", path);
return false;
}
struct stat s;
if (fstat(fd, &s) < 0) {
Dbg(dbg_ctl, "Could not stat %s", path);
close(fd);
return false;
}
// Don't load if mod time is older than our copy
if (pstate->last_load < s.st_mtime) {
time_t const now = time(nullptr);
FILE *const fs = fdopen(fd, "r");
if (NULL == fs) {
Dbg(dbg_ctl, "Could not open %s for reading", path);
close(fd);
return false;
}
Dbg(dbg_ctl, "Attempting to load rules from: '%s'", path);
const char *errptr;
int erroffset;
int ovector[OVECTOR_SIZE];
pcre *const config_re = pcre_compile("^([^#].+?)\\s+(\\d+)(\\s+(\\w+))?\\s*$", 0, &errptr, &erroffset, nullptr);
TSReleaseAssert(nullptr != config_re);
char line[LINE_MAX];
int ln = 0;
invalidate_t *iptr, *i;
while (fgets(line, LINE_MAX, fs) != nullptr) {
Dbg(dbg_ctl, "Processing: %d %s", ln, line);
++ln;
int const rc = pcre_exec(config_re, nullptr, line, strlen(line), 0, 0, ovector, OVECTOR_SIZE);
if (3 <= rc) {
i = (invalidate_t *)TSmalloc(sizeof(invalidate_t));
init_invalidate_t(i);
pcre_get_substring(line, ovector, rc, 1, &i->regex_text);
i->regex = pcre_compile(i->regex_text, 0, &errptr, &erroffset, nullptr);
i->epoch = now;
i->expiry = atoi(line + ovector[4]);
if (5 == rc) {
int const len = ovector[9] - ovector[8];
char const *const type = line + ovector[8];
if (0 == strncasecmp(type, RESULT_MISS, len)) {
Dbg(dbg_ctl, "Regex line set to result type %s: '%s'", RESULT_MISS, i->regex_text);
i->new_result = TS_CACHE_LOOKUP_MISS;
} else if (0 != strncasecmp(type, RESULT_STALE, len)) {
Dbg(dbg_ctl, "Unknown regex line result type '%s', using default '%s' '%s'", type, RESULT_STALE, i->regex_text);
}
}
if (i->expiry <= i->epoch) {
Dbg(dbg_ctl, "Rule is already expired!");
free_invalidate_t(i);
i = nullptr;
} else if (i->regex == nullptr) {
Dbg(dbg_ctl, "%s did not compile", i->regex_text);
free_invalidate_t(i);
i = nullptr;
} else {
i->regex_extra = pcre_study(i->regex, 0, &errptr);
if (!*ilist) {
*ilist = i;
Dbg(dbg_ctl, "Created new list and Loaded %s %jd %jd %s", i->regex_text, (intmax_t)i->epoch, (intmax_t)i->expiry,
strForResult(i->new_result));
} else {
iptr = *ilist;
while (1) {
if (strcmp(i->regex_text, iptr->regex_text) == 0) {
if (iptr->expiry != i->expiry) {
Dbg(dbg_ctl, "Updating duplicate %s", i->regex_text);
iptr->epoch = i->epoch;
iptr->expiry = i->expiry;
}
if (iptr->new_result != i->new_result) {
Dbg(dbg_ctl, "Resetting duplicate due to type change %s", i->regex_text);
iptr->new_result = i->new_result;
iptr->epoch = now;
}
free_invalidate_t(i);
i = nullptr;
break;
} else if (!iptr->next) {
break;
} else {
iptr = iptr->next;
}
}
if (i) {
iptr->next = i;
Dbg(dbg_ctl, "Loaded %s %jd %jd %s", i->regex_text, (intmax_t)i->epoch, (intmax_t)i->expiry,
strForResult(i->new_result));
}
}
}
} else {
Dbg(dbg_ctl, "Skipping line %d, too few fields", ln);
}
}
pcre_free(config_re);
fclose(fs);
pstate->last_load = s.st_mtime;
return true;
} else {
Dbg(dbg_ctl, "File mod time is not newer: ftime: %jd <= last_load: %jd", (intmax_t)s.st_mtime, (intmax_t)pstate->last_load);
}
close(fd);
return false;
}
static void
list_config(plugin_state_t *pstate, invalidate_t *i)
{
invalidate_t *iptr;
Dbg(dbg_ctl, "Current config:");
if (pstate->log) {
TSTextLogObjectWrite(pstate->log, "Current config:");
}
FILE *state_file = nullptr;
if (pstate->state_path) {
state_file = fopen(pstate->state_path, "w");
if (nullptr == state_file) {
Dbg(dbg_ctl, "Unable to open state file %s\n", pstate->state_path);
}
}
if (i) {
iptr = i;
while (iptr) {
char const *const typestr = strForResult(iptr->new_result);
Dbg(dbg_ctl, "%s epoch: %jd expiry: %jd result: %s", iptr->regex_text, (intmax_t)iptr->epoch, (intmax_t)iptr->expiry,
typestr);
if (pstate->log) {
TSTextLogObjectWrite(pstate->log, "%s epoch: %jd expiry: %jd result: %s", iptr->regex_text, (intmax_t)iptr->epoch,
(intmax_t)iptr->expiry, typestr);
}
if (state_file) {
fprintf(state_file, "%s %jd %jd %s\n", iptr->regex_text, (intmax_t)iptr->epoch, (intmax_t)iptr->expiry, typestr);
}
iptr = iptr->next;
}
} else {
Dbg(dbg_ctl, "EMPTY");
if (pstate->log) {
TSTextLogObjectWrite(pstate->log, "EMPTY");
}
}
if (nullptr != state_file) {
fclose(state_file);
}
}
static int
free_handler(TSCont cont, TSEvent /* event ATS_UNUSED */, void * /* edata ATS_UNUSED */)
{
invalidate_t *iptr;
Dbg(dbg_ctl, "Freeing old config");
iptr = (invalidate_t *)TSContDataGet(cont);
free_invalidate_t_list(iptr);
TSContDestroy(cont);
return 0;
}
static int
config_handler(TSCont cont, TSEvent event, void * /* edata ATS_UNUSED */)
{
plugin_state_t *pstate;
invalidate_t *i, *iptr;
TSCont free_cont;
bool updated;
TSMutex mutex;
Dbg(dbg_ctl, "In config_handler");
mutex = TSContMutexGet(cont);
TSMutexLock(mutex);
pstate = (plugin_state_t *)TSContDataGet(cont);
i = copy_config(pstate->invalidate_list);
updated = prune_config(&i);
updated = load_config(pstate, &i) || updated;
if (updated) {
list_config(pstate, i);
iptr = __sync_val_compare_and_swap(&(pstate->invalidate_list), pstate->invalidate_list, i);
if (iptr) {
free_cont = TSContCreate(free_handler, TSMutexCreate());
TSContDataSet(free_cont, (void *)iptr);
TSContScheduleOnPool(free_cont, FREE_TMOUT, TS_THREAD_POOL_TASK);
}
} else {
Dbg(dbg_ctl, "No Changes");
if (i) {
free_invalidate_t_list(i);
}
}
TSMutexUnlock(mutex);
// Don't reschedule for TS_EVENT_MGMT_UPDATE
if (event == TS_EVENT_TIMEOUT) {
TSContScheduleOnPool(cont, CONFIG_TMOUT, TS_THREAD_POOL_TASK);
}
return 0;
}
static time_t
get_date_from_cached_hdr(TSHttpTxn txn)
{
TSMBuffer buf;
TSMLoc hdr_loc, date_loc;
time_t date = 0;
if (TSHttpTxnCachedRespGet(txn, &buf, &hdr_loc) == TS_SUCCESS) {
date_loc = TSMimeHdrFieldFind(buf, hdr_loc, TS_MIME_FIELD_DATE, TS_MIME_LEN_DATE);
if (date_loc != TS_NULL_MLOC) {
date = TSMimeHdrFieldValueDateGet(buf, hdr_loc, date_loc);
TSHandleMLocRelease(buf, hdr_loc, date_loc);
}
TSHandleMLocRelease(buf, TS_NULL_MLOC, hdr_loc);
}
return date;
}
static void
add_header(TSHttpTxn txn, const char *const header, invalidate_t *const rule)
{
TSMBuffer bufp = NULL;
TSMLoc lochdr = TS_NULL_MLOC;
TSMLoc locfield = TS_NULL_MLOC;
char rulestr[LINE_MAX];
int rulelen = 0;
char encstr[LINE_MAX];
size_t enclen = 0;
TSReleaseAssert(header && rule);
if (TS_SUCCESS != TSHttpTxnClientReqGet(txn, &bufp, &lochdr)) {
Dbg(dbg_ctl, "Unable to get client request from transaction");
}
rulelen =
snprintf(rulestr, sizeof(rulestr), "%s %jd %s", rule->regex_text, (intmax_t)rule->expiry, strForResult(rule->new_result));
if (TS_SUCCESS != TSStringPercentEncode(rulestr, rulelen, encstr, sizeof(encstr), &enclen, NULL)) {
Dbg(dbg_ctl, "Unable to get encode matching rule '%s'", rulestr);
return;
}
locfield = TSMimeHdrFieldFind(bufp, lochdr, header, strlen(header));
if (TS_NULL_MLOC == locfield) { // create header
if (TS_SUCCESS == TSMimeHdrFieldCreateNamed(bufp, lochdr, header, strlen(header), &locfield)) {
if (TS_SUCCESS == TSMimeHdrFieldValueStringSet(bufp, lochdr, locfield, -1, encstr, enclen)) {
TSMimeHdrFieldAppend(bufp, lochdr, locfield);
Dbg(dbg_ctl, "Added header %s: '%.*s'", header, (int)enclen, encstr);
}
}
TSHandleMLocRelease(bufp, lochdr, locfield);
} else { // replace header
bool first = true;
while (locfield) {
const TSMLoc tmp = TSMimeHdrFieldNextDup(bufp, lochdr, locfield);
if (first) {
first = false;
TSMimeHdrFieldValueStringSet(bufp, lochdr, locfield, -1, encstr, enclen);
Dbg(dbg_ctl, "Added header '%s': '%.*s'", header, (int)enclen, encstr);
} else {
TSMimeHdrFieldDestroy(bufp, lochdr, locfield);
}
TSHandleMLocRelease(bufp, lochdr, locfield);
locfield = tmp;
}
}
}
static int
main_handler(TSCont cont, TSEvent event, void *edata)
{
TSHttpTxn txn = (TSHttpTxn)edata;
int status;
invalidate_t *iptr = NULL;
plugin_state_t *pstate = NULL;
time_t date = 0, now = 0;
char *url = nullptr;
int url_len = 0;
switch (event) {
case TS_EVENT_HTTP_CACHE_LOOKUP_COMPLETE:
if (TSHttpTxnCacheLookupStatusGet(txn, &status) == TS_SUCCESS) {
if (status == TS_CACHE_LOOKUP_HIT_FRESH) {
pstate = (plugin_state_t *)TSContDataGet(cont);
iptr = pstate->invalidate_list;
while (iptr) {
if (!date) {
date = get_date_from_cached_hdr(txn);
Dbg(dbg_ctl, "Cached Date header is: %jd", intmax_t(date));
now = time(nullptr);
}
if (date <= iptr->epoch && now < iptr->expiry) {
if (!url) {
url = TSHttpTxnEffectiveUrlStringGet(txn, &url_len);
Dbg(dbg_ctl, "Effective url is is '%.*s'", url_len, url);
}
if (pcre_exec(iptr->regex, iptr->regex_extra, url, url_len, 0, 0, nullptr, 0) >= 0) {
Dbg(dbg_ctl, "Forced revalidate, Match with rule regex: '%s' epoch: %jd, expiry: %jd, result: '%s'", iptr->regex_text,
intmax_t(iptr->epoch), intmax_t(iptr->expiry), strForResult(iptr->new_result));
TSHttpTxnCacheLookupStatusSet(txn, iptr->new_result);
increment_stat(iptr->new_result);
if (pstate->match_header) {
add_header(txn, pstate->match_header, iptr);
}
iptr = nullptr;
}
}
if (iptr) {
iptr = iptr->next;
}
}
if (url) {
TSfree(url);
}
}
}
break;
default:
break;
}
TSHttpTxnReenable(txn, TS_EVENT_HTTP_CONTINUE);
return 0;
}
static char *
make_state_path(const char *filename)
{
if ('/' == *filename) {
return TSstrdup(filename);
} else {
char buf[8192];
const char *dir = TSInstallDirGet();
snprintf(buf, sizeof(buf), "%s/%s/%s", dir, DEFAULT_DIR, filename);
return TSstrdup(buf);
}
return nullptr;
}
void
TSPluginInit(int argc, const char *argv[])
{
TSPluginRegistrationInfo info;
TSCont main_cont, config_cont;
plugin_state_t *pstate;
invalidate_t *iptr = nullptr;
bool disable_timed_reload = false;
Dbg(dbg_ctl, "Starting plugin init");
pstate = (plugin_state_t *)TSmalloc(sizeof(plugin_state_t));
init_plugin_state_t(pstate);
int c;
static const struct option longopts[] = {
{"config", required_argument, nullptr, 'c'},
{"log", required_argument, nullptr, 'l'},
{"disable-timed-reload", no_argument, nullptr, 'd'},
{"state-file", required_argument, nullptr, 'f'},
{"match-header", required_argument, nullptr, 'm'},
{nullptr, 0, nullptr, 0 }
};
while ((c = getopt_long(argc, (char *const *)argv, "c:l:f:m:", longopts, nullptr)) != -1) {
switch (c) {
case 'c':
pstate->config_path = TSstrdup(optarg);
break;
case 'l':
if (TS_SUCCESS == TSTextLogObjectCreate(optarg, TS_LOG_MODE_ADD_TIMESTAMP, &pstate->log)) {
TSTextLogObjectRollingIntervalSecSet(pstate->log, LOG_ROLL_INTERVAL);
TSTextLogObjectRollingOffsetHrSet(pstate->log, LOG_ROLL_OFFSET);
}
break;
case 'd':
disable_timed_reload = true;
break;
case 'f':
pstate->state_path = make_state_path(optarg);
break;
case 'm':
pstate->match_header = TSstrdup(optarg);
break;
default:
break;
}
}
if (nullptr == pstate->config_path) {
TSError("[regex_revalidate] Plugin requires a --config option along with a config file name");
free_plugin_state_t(pstate);
return;
}
if (!load_config(pstate, &iptr)) {
Dbg(dbg_ctl, "Problem loading config from file %s", pstate->config_path);
} else {
pstate->invalidate_list = iptr;
/* Load and merge previous state if provided */
if (nullptr != pstate->state_path) {
if (!load_state(pstate, &iptr)) {
Dbg(dbg_ctl, "Problem loading state from file %s", pstate->state_path);
} else {
Dbg(dbg_ctl, "Loaded state from file %s", pstate->state_path);
}
}
list_config(pstate, iptr);
}
info.plugin_name = PLUGIN_NAME;
info.vendor_name = "Apache Software Foundation";
info.support_email = "dev@trafficserver.apache.org";
if (TSPluginRegister(&info) != TS_SUCCESS) {
TSError("[regex_revalidate] Plugin registration failed");
free_plugin_state_t(pstate);
return;
} else {
Dbg(dbg_ctl, "Plugin registration succeeded");
}
create_stats();
main_cont = TSContCreate(main_handler, nullptr);
TSContDataSet(main_cont, (void *)pstate);
TSHttpHookAdd(TS_HTTP_CACHE_LOOKUP_COMPLETE_HOOK, main_cont);
config_cont = TSContCreate(config_handler, TSMutexCreate());
TSContDataSet(config_cont, (void *)pstate);
TSMgmtUpdateRegister(config_cont, PLUGIN_NAME);
if (!disable_timed_reload) {
TSContScheduleOnPool(config_cont, CONFIG_TMOUT, TS_THREAD_POOL_TASK);
}
Dbg(dbg_ctl, "Plugin Init Complete");
}