| /** @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 <cassert> | |
| #include <cctype> | |
| #include <cinttypes> | |
| #include <cstdlib> | |
| #include <getopt.h> | |
| #include <string_view> | |
| namespace | |
| { | |
| constexpr std::string_view DefaultSliceSkipHeader = {"X-Slicer-Info"}; | |
| constexpr std::string_view DefaultCrrIdentHeader = {"X-Crr-Ident"}; | |
| constexpr std::string_view DefaultPurgeProbeHeader = {"X-Slice-Purge-Probe"}; | |
| } // namespace | |
| Config::~Config() | |
| { | |
| if (nullptr != m_regex) { | |
| delete m_regex; | |
| } | |
| prefetchCleanup(); | |
| } | |
| 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 *>("disable-errorlog"), no_argument, nullptr, 'd'}, | |
| {const_cast<char *>("exclude-regex"), required_argument, nullptr, 'e'}, | |
| {const_cast<char *>("crr-ident-header"), required_argument, nullptr, 'g'}, | |
| {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'}, | |
| {const_cast<char *>("minimum-size"), required_argument, nullptr, 'm'}, | |
| {const_cast<char *>("metadata-cache-size"), required_argument, nullptr, 'z'}, | |
| {const_cast<char *>("stats-prefix"), required_argument, nullptr, 'x'}, | |
| {const_cast<char *>("purge-probe-blocks"), required_argument, nullptr, 'q'}, | |
| {const_cast<char *>("purge-probe-header"), required_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:de:g:H:i:lm:p:q:r:s:t:x:z:", 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 'd': { | |
| m_paceerrsecs = -1; | |
| } break; | |
| case 'e': { | |
| if (None != m_regex_type) { | |
| ERROR_LOG("Regex already specified!"); | |
| break; | |
| } | |
| std::string err; | |
| int erroffset; | |
| m_regexstr = optarg; | |
| m_regex = new Regex(); | |
| if (m_regex->compile(m_regexstr, err, erroffset)) { | |
| m_regex_type = Exclude; | |
| DEBUG_LOG("Using regex for url exclude: '%s'", m_regexstr.c_str()); | |
| } else { | |
| ERROR_LOG("Invalid regex: '%s' - %s at column %d", m_regexstr.c_str(), err.c_str(), erroffset); | |
| } | |
| } break; | |
| case 'g': { | |
| m_crr_ident_header.assign(optarg); | |
| DEBUG_LOG("Using override crr ident header %s", optarg); | |
| } break; | |
| case 'i': { | |
| if (None != m_regex_type) { | |
| ERROR_LOG("Regex already specified!"); | |
| break; | |
| } | |
| std::string err; | |
| int erroffset; | |
| m_regexstr = optarg; | |
| m_regex = new Regex(); | |
| if (m_regex->compile(m_regexstr, err, erroffset)) { | |
| m_regex_type = Include; | |
| DEBUG_LOG("Using regex for url include: '%s'", m_regexstr.c_str()); | |
| } else { | |
| ERROR_LOG("Invalid regex: '%s' - %s at column %d", m_regexstr.c_str(), err.c_str(), erroffset); | |
| } | |
| } 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; | |
| case 'm': { | |
| int64_t const bytesread = bytesFrom(optarg); | |
| if (bytesread < 0) { | |
| DEBUG_LOG("Invalid minimum-size: %s", optarg); | |
| } | |
| m_min_size_to_slice = bytesread; | |
| DEBUG_LOG("Only slicing objects %" PRIu64 " bytes or larger", m_min_size_to_slice); | |
| } break; | |
| case 'z': { | |
| try { | |
| size_t size = std::stoul(optarg); | |
| setCacheSize(size); | |
| DEBUG_LOG("Metadata cache size: %zu entries", size); | |
| } catch (const std::invalid_argument &e) { | |
| ERROR_LOG("Invalid metadata cache size argument: %s", optarg); | |
| } catch (const std::out_of_range &e) { | |
| ERROR_LOG("Metadata cache size out of range: %s", optarg); | |
| } | |
| } break; | |
| case 'x': { | |
| stat_prefix = optarg; | |
| DEBUG_LOG("Stat prefix: %s", stat_prefix.c_str()); | |
| } break; | |
| case 'q': { | |
| int const blocksread = atoi(optarg); | |
| if (0 < blocksread) { | |
| m_purge_probe_blocks = blocksread; | |
| DEBUG_LOG("Using purge probe blocks %d", m_purge_probe_blocks); | |
| } else { | |
| ERROR_LOG("Invalid purge-probe-blocks: %s", optarg); | |
| } | |
| } break; | |
| case 'H': { | |
| m_purge_probe_header.assign(optarg); | |
| DEBUG_LOG("Using purge probe header %s", optarg); | |
| } 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_ident_header.empty()) { | |
| m_crr_ident_header = DefaultCrrIdentHeader; | |
| DEBUG_LOG("Using default crr ident header %s", m_crr_ident_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()); | |
| } | |
| if (m_purge_probe_header.empty()) { | |
| m_purge_probe_header = DefaultPurgeProbeHeader; | |
| DEBUG_LOG("Using default purge probe header %s", m_purge_probe_header.c_str()); | |
| } | |
| if (m_min_size_to_slice > 0) { | |
| if (m_oscache.has_value()) { | |
| DEBUG_LOG("Metadata cache size: %zu", m_oscache->cache_capacity()); | |
| } else { | |
| ERROR_LOG("--metadata-cache-size is required when --minimum-size is specified! Using a default size of 16384."); | |
| setCacheSize(16384); | |
| } | |
| } | |
| 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: { | |
| // Exclude means if the regex matches, it doesn't match | |
| if (m_regex->exec({url, static_cast<size_t>(urllen)})) { | |
| matches = false; | |
| } | |
| } break; | |
| case Include: { | |
| // Include means if the regex matches, it matches | |
| if (!m_regex->exec({url, static_cast<size_t>(urllen)})) { | |
| matches = false; | |
| } | |
| } break; | |
| default: | |
| break; | |
| } | |
| return matches; | |
| } | |
| void | |
| Config::setCacheSize(size_t entries) | |
| { | |
| if (entries == 0) { | |
| m_oscache.reset(); | |
| } else { | |
| m_oscache.emplace(entries); | |
| } | |
| } | |
| bool | |
| Config::isKnownLargeObj(std::string_view url) | |
| { | |
| if (m_min_size_to_slice <= 0) { | |
| // If conditional slicing is not set, all objects are large enough to slice | |
| return true; | |
| } | |
| assert(m_oscache.has_value()); // object size cache is always present when conditionally slicing | |
| std::optional<uint64_t> size = m_oscache->get(url); | |
| if (size.has_value()) { | |
| DEBUG_LOG("Found url in cache: %.*s -> %" PRIu64, static_cast<int>(url.size()), url.data(), size.value()); | |
| if (size.value() >= m_min_size_to_slice) { | |
| return true; | |
| } | |
| } | |
| return false; | |
| } | |
| void | |
| Config::sizeCacheAdd(std::string_view url, uint64_t size) | |
| { | |
| if (m_oscache) { | |
| DEBUG_LOG("Adding url to cache: %.*s -> %" PRIu64, static_cast<int>(url.size()), url.data(), size); | |
| m_oscache->set(url, size); | |
| } | |
| } | |
| void | |
| Config::sizeCacheRemove(std::string_view url) | |
| { | |
| if (m_oscache) { | |
| DEBUG_LOG("Removing url from cache: %.*s", static_cast<int>(url.size()), url.data()); | |
| m_oscache->remove(url); | |
| } | |
| } | |
| std::pair<bool, BgBlockFetch *> | |
| Config::prefetchAcquire(const std::string &key) | |
| { | |
| std::lock_guard<std::mutex> const guard(m_prefetch_mutex); | |
| auto [it, inserted] = m_prefetch_active.insert(key); | |
| if (!inserted) { | |
| return {false, nullptr}; | |
| } | |
| BgBlockFetch *bg = nullptr; | |
| if (!m_prefetch_freelist.empty()) { | |
| bg = m_prefetch_freelist.back(); | |
| m_prefetch_freelist.pop_back(); | |
| } | |
| return {true, bg}; | |
| } | |
| #if defined(UNITTEST) | |
| // Stubs for unit tests that don't link prefetch.cc | |
| void | |
| Config::prefetchRelease(BgBlockFetch *) | |
| { | |
| } | |
| void | |
| Config::prefetchCleanup() | |
| { | |
| } | |
| #endif |