blob: 65ca648ba8f54e3acbc70c90dcb5f403e7e1bc8b [file]
/** @file
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 "Config.h"
#include <cctype>
#include <cinttypes>
#include <cstdlib>
#include <getopt.h>
#include <string_view>
#include "ts/experimental.h"
namespace
{
constexpr std::string_view DefaultSliceSkipHeader = {"X-Slicer-Info"};
constexpr std::string_view DefaultCrrImsHeader = {"X-Crr-Ims"};
} // namespace
Config::~Config()
{
if (nullptr != m_regex_extra) {
#ifndef PCRE_STUDY_JIT_COMPILE
pcre_free(m_regex_extra);
#else
pcre_free_study(m_regex_extra);
#endif
}
if (nullptr != m_regex) {
pcre_free(m_regex);
}
}
int64_t
Config::bytesFrom(char const *const valstr)
{
char *endptr = nullptr;
int64_t blockbytes = strtoll(valstr, &endptr, 10);
constexpr int64_t kib = 1024;
if (nullptr != endptr && valstr < endptr) {
size_t const dist = endptr - valstr;
if (dist < strlen(valstr) && 0 <= blockbytes) {
switch (tolower(*endptr)) {
case 'g':
blockbytes *= (kib * kib * kib);
break;
case 'm':
blockbytes *= (kib * kib);
break;
case 'k':
blockbytes *= kib;
break;
default:
break;
}
}
}
if (blockbytes < 0) {
blockbytes = 0;
}
return blockbytes;
}
bool
Config::fromArgs(int const argc, char const *const argv[])
{
DEBUG_LOG("Number of arguments: %d", argc);
for (int index = 0; index < argc; ++index) {
DEBUG_LOG("args[%d] = %s", index, argv[index]);
}
// look for lowest priority deprecated blockbytes
int64_t blockbytes = 0;
// backwards compat: look for blockbytes
for (int index = 0; index < argc; ++index) {
std::string_view const argstr = argv[index];
std::size_t const spos = argstr.find_first_of(':');
if (spos != std::string_view::npos) {
std::string_view const key = argstr.substr(0, spos);
std::string_view const val = argstr.substr(spos + 1);
if (!key.empty() && !val.empty()) {
char const *const valstr = val.data(); // inherits argv's null
int64_t const bytesread = bytesFrom(valstr);
if (blockbytesmin <= bytesread && bytesread <= blockbytesmax) {
DEBUG_LOG("Found deprecated blockbytes %" PRId64, bytesread);
blockbytes = bytesread;
}
}
}
}
// standard parsing
constexpr struct option longopts[] = {
{const_cast<char *>("blockbytes"), required_argument, nullptr, 'b'},
{const_cast<char *>("crr-ims-header"), required_argument, nullptr, 'c'},
{const_cast<char *>("disable-errorlog"), no_argument, nullptr, 'd'},
{const_cast<char *>("exclude-regex"), required_argument, nullptr, 'e'},
{const_cast<char *>("include-regex"), required_argument, nullptr, 'i'},
{const_cast<char *>("ref-relative"), no_argument, nullptr, 'l'},
{const_cast<char *>("pace-errorlog"), required_argument, nullptr, 'p'},
{const_cast<char *>("remap-host"), required_argument, nullptr, 'r'},
{const_cast<char *>("skip-header"), required_argument, nullptr, 's'},
{const_cast<char *>("blockbytes-test"), required_argument, nullptr, 't'},
{const_cast<char *>("prefetch-count"), required_argument, nullptr, 'f'},
{const_cast<char *>("strip-range-for-head"), no_argument, nullptr, 'h'},
{nullptr, 0, nullptr, 0},
};
// getopt assumes args start at '1' so this hack is needed
char *const *argvp = (const_cast<char *const *>(argv) - 1);
for (;;) {
int const opt = getopt_long(argc + 1, argvp, "b:dc:e:i:lp:r:s:t:", longopts, nullptr);
if (-1 == opt) {
break;
}
DEBUG_LOG("processing '%c' %s", (char)opt, argvp[optind - 1]);
switch (opt) {
case 'b': {
int64_t const bytesread = bytesFrom(optarg);
if (blockbytesmin <= bytesread && bytesread <= blockbytesmax) {
DEBUG_LOG("Using blockbytes %" PRId64, bytesread);
blockbytes = bytesread;
} else {
ERROR_LOG("Invalid blockbytes: %s", optarg);
}
} break;
case 'c': {
m_crr_ims_header.assign(optarg);
DEBUG_LOG("Using override crr ims header %s", optarg);
} break;
case 'd': {
m_paceerrsecs = -1;
} break;
case 'e': {
if (None != m_regex_type) {
ERROR_LOG("Regex already specified!");
break;
}
const char *errptr;
int erroffset;
m_regexstr = optarg;
m_regex = pcre_compile(m_regexstr.c_str(), 0, &errptr, &erroffset, nullptr);
if (nullptr == m_regex) {
ERROR_LOG("Invalid regex: '%s'", m_regexstr.c_str());
} else {
m_regex_type = Exclude;
m_regex_extra = pcre_study(m_regex, 0, &errptr);
DEBUG_LOG("Using regex for url exclude: '%s'", m_regexstr.c_str());
}
} break;
case 'i': {
if (None != m_regex_type) {
ERROR_LOG("Regex already specified!");
break;
}
const char *errptr;
int erroffset;
m_regexstr = optarg;
m_regex = pcre_compile(m_regexstr.c_str(), 0, &errptr, &erroffset, nullptr);
if (nullptr == m_regex) {
ERROR_LOG("Invalid regex: '%s'", m_regexstr.c_str());
} else {
m_regex_type = Include;
m_regex_extra = pcre_study(m_regex, 0, &errptr);
DEBUG_LOG("Using regex for url include: '%s'", m_regexstr.c_str());
}
} break;
case 'l': {
m_reftype = RefType::Relative;
DEBUG_LOG("Reference slice relative to request (not slice block 0)");
} break;
case 'p': {
int const secsread = atoi(optarg);
if (0 < secsread) {
m_paceerrsecs = std::min(secsread, 60);
} else {
ERROR_LOG("Ignoring pace-errlog argument");
}
} break;
case 'r': {
m_remaphost = optarg;
DEBUG_LOG("Using loopback remap host override: %s", m_remaphost.c_str());
} break;
case 's': {
m_skip_header.assign(optarg);
DEBUG_LOG("Using slice skip header %s", optarg);
} break;
case 't': {
if (0 == blockbytes) {
int64_t const bytesread = bytesFrom(optarg);
if (0 < bytesread) {
DEBUG_LOG("Using blockbytes-test %" PRId64, bytesread);
blockbytes = bytesread;
} else {
ERROR_LOG("Invalid blockbytes-test: %s", optarg);
}
} else {
DEBUG_LOG("Skipping blockbytes-test in favor of blockbytes");
}
} break;
case 'f': {
m_prefetchcount = atoi(optarg);
} break;
case 'h': {
m_head_strip_range = true;
} break;
default:
break;
}
}
if (0 < blockbytes) {
DEBUG_LOG("Using configured blockbytes %" PRId64, blockbytes);
m_blockbytes = blockbytes;
} else {
DEBUG_LOG("Using default blockbytes %" PRId64, m_blockbytes);
}
if (m_paceerrsecs < 0) {
DEBUG_LOG("Block stitching error logs disabled");
} else if (0 == m_paceerrsecs) {
DEBUG_LOG("Block stitching error logs enabled");
} else {
DEBUG_LOG("Block stitching error logs at most every %d sec(s)", m_paceerrsecs);
}
if (m_crr_ims_header.empty()) {
m_crr_ims_header = DefaultCrrImsHeader;
DEBUG_LOG("Using default crr ims header %s", m_crr_ims_header.c_str());
}
if (m_skip_header.empty()) {
m_skip_header = DefaultSliceSkipHeader;
DEBUG_LOG("Using default slice skip header %s", m_skip_header.c_str());
}
return true;
}
bool
Config::canLogError()
{
if (m_paceerrsecs < 0) {
return false;
} else if (0 == m_paceerrsecs) {
return true;
}
#if !defined(UNITTEST)
TSHRTime const timenow = TShrtime();
#endif
std::lock_guard<std::mutex> const guard(m_mutex);
#if !defined(UNITTEST)
if (timenow < m_nextlogtime) {
return false;
}
m_nextlogtime = timenow + TS_HRTIME_SECONDS(m_paceerrsecs);
#else
m_nextlogtime = 0; // needed by clang
#endif
return true;
}
bool
Config::matchesRegex(char const *const url, int const urllen) const
{
bool matches = true;
switch (m_regex_type) {
case Exclude: {
if (0 <= pcre_exec(m_regex, m_regex_extra, url, urllen, 0, 0, nullptr, 0)) {
matches = false;
}
} break;
case Include: {
if (pcre_exec(m_regex, m_regex_extra, url, urllen, 0, 0, nullptr, 0) < 0) {
matches = false;
}
} break;
default:
break;
}
return matches;
}