Diagnostic/mdsd/mdsdutil/Utility.cc (898 lines of code) (raw):
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
#include "Utility.hh"
#include "MdsTime.hh"
#include <string>
#include <sstream>
#include <iomanip>
#include <ctime>
#include <cstring>
#include <boost/tokenizer.hpp>
#include <boost/regex.hpp>
#include <algorithm>
#include <cctype>
#include <vector>
extern "C" {
#include <sys/uio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/file.h>
#include <unistd.h>
#include <fcntl.h>
#include <dirent.h>
#include <signal.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <sys/socket.h>
#include <sys/un.h>
}
//////// Begin MdsdUtil namespace
namespace MdsdUtil {
void
ReplaceSubstring(std::string& str, const std::string& from, const std::string& to)
{
size_t pos = 0;
while ((pos = str.find(from, pos)) != std::string::npos) {
str.replace(pos, from.length(), to);
pos += to.length();
}
}
// Replace escaped characters with the actual character. Because ampersand is an escapable character,
// it's possible than an escape sequence was, itself, escaped; this function iterates until there are
// no remaining escape sequences.
std::string
UnquoteXmlAttribute(std::string target)
{
static std::vector<std::pair<std::string, std::string>>
conversions { { "<", "<" }, { ">", ">" }, { "'", "'" }, { "'", "'" }, { """, "\"" } };
bool more_work;
if (target.length() >= 4) { // Shortest escape sequence is 4 characters
do {
size_t before = target.length();
// If I replace an & escape, I may have created a new legit escape sequence
ReplaceSubstring(target, "&", "&");
ReplaceSubstring(target, "&", "&");
more_work = (target.length() < before);
} while (more_work);
for (const auto & repl : conversions) {
ReplaceSubstring(target, repl.first, repl.second);
}
}
return target;
}
bool
NotValidName(const std::string& str)
{
if (str.length() == 0 || str.find(" ") != std::string::npos)
return true;
return false;
}
std::string
Join(const std::vector<std::string>& vec, const std::string& sep)
{
std::string result { "" };
for (auto it = vec.begin(); it != vec.end(); /*deliberately empty*/ ) {
result.append(*it);
++it;
if (it != vec.end()) {
result.append(sep);
}
}
return result;
}
unsigned long long
EasyHash(const std::string& str)
{
unsigned long long strhash = 0;
const unsigned long long c_Multiplier = 37;
for(size_t i = 0; i < str.size(); i++) {
strhash = c_Multiplier * strhash + (unsigned int)str[i];
}
return strhash;
}
std::string
ZeroFill(unsigned long long num, size_t len)
{
std::stringstream s1;
s1 << std::setfill('0') << std::setw(len) << num;
return s1.str();
}
// Convert seconds+microseconds to an RFC3339 string with 100ns resolution
std::string
Rfc3339(const time_t sec, const suseconds_t usec)
{
// Special case: 0.0 is the beginning of the Windows DateTime Epoch
if (sec == 0 && usec == 0) {
return std::string("1601-01-01T00:00:00.0000001Z");
}
struct tm zulu;
char timebuf[100];
(void) gmtime_r(&sec, &zulu);
size_t n = strftime(timebuf, sizeof(timebuf), "%Y-%m-%dT%H:%M:%S", &zulu);
// Note that usec is microseconds, but the Windows DateTime is precise to 100ns. We hard-code
// an extra 0 to match the required precision.
std::ostringstream result;
result << std::string { timebuf, n };
result << "." << std::setw(6) << std::setfill('0') << static_cast<unsigned long>(usec) << "0Z";
return result.str();
}
// Get the RFC3339 form of the current date/time
std::string
Rfc3339()
{
struct timeval tv;
(void)gettimeofday(&tv, 0);
return MdsdUtil::Rfc3339(tv.tv_sec, tv.tv_usec);
}
// Code below is based on and slightly modified from Glib's g_time_Val_from_iso8601(),
// which is not really fully ISO8601-compliant.
//
// Valid examples for this code:
// "2015-12-17T08:53:45.123456Z" (Z: UTC)
// "20151217T085345.123456Z"
// "2015-12-17T08:53:45.123456" (Local timezone)
// "2015-12-17T08:53:45" (Fractional second is optional)
// "2015-12-17T08:53:45,123-08:00" (UTC-08:00, ',' can be used for fractional second)
// "2015-12-17" (Time portition is optional--treated as 00:00:00)
// "2015-12-17-08:00" (TZD can be still given with date only)
//
// Non-delimited date/time with reduced size (e.g., YYYY instead of YYYYMMDD) will
// result in incorrect result, and it's not supported by this code.
// (e.g., "20151217T0853" will be treated as "2015-12-17T00:08:53", not "2015-12-17T08:53:00.000"
// as stated in ISO8601)
bool
TimeValFromIso8601Restricted(const char* datetime, long& secondsOut, long& uSecondsOut)
{
if (datetime == nullptr)
{
return false;
}
while (isspace(*datetime))
{
++datetime;
}
if (*datetime == '\0' || !isdigit(*datetime))
{
return false;
}
struct tm _tm;
memset(&_tm, 0, sizeof(_tm));
// Date
long parsedVal = strtoul(datetime, (char**)&datetime, 10);
bool isDateDelimited = false;
if (*datetime != '-')
{
// YYYYMMDD
_tm.tm_year = parsedVal / 10000 - 1900;
_tm.tm_mon = (parsedVal % 10000) / 100 - 1; // January is 0
_tm.tm_mday = parsedVal % 100;
}
else
{
// YYYY-MM-DD
isDateDelimited = true;
_tm.tm_year = parsedVal - 1900;
++datetime;
_tm.tm_mon = strtoul(datetime, (char**)&datetime, 10) - 1;
if (*datetime != '-')
{
return false;
}
++datetime;
_tm.tm_mday = strtoul(datetime, (char**)&datetime, 10);
}
if (*datetime == 'T')
{
// Time
++datetime;
if (!isdigit(*datetime))
{
return false;
}
parsedVal = strtoul(datetime, (char**)&datetime, 10);
const bool isTimeDelimited = *datetime == ':';
if (isTimeDelimited != isDateDelimited)
{ // Time must be delimited if and only if date is delimited.
return false;
}
if (!isTimeDelimited)
{
// hhmmss
_tm.tm_hour = parsedVal / 10000;
_tm.tm_min = (parsedVal % 10000) / 100;
_tm.tm_sec = parsedVal % 100;
}
else
{
// hh:mm:ss
_tm.tm_hour = parsedVal;
++datetime;
_tm.tm_min = strtoul(datetime, (char**)&datetime, 10);
if (*datetime != ':')
{
return false;
}
++datetime;
_tm.tm_sec = strtoul(datetime, (char**)&datetime, 10);
}
// Fractional seconds
uSecondsOut = 0;
if (*datetime == '.' || *datetime == ',')
{
++datetime;
long multiplier = 100000;
parsedVal = 0;
while (isdigit(*datetime))
{
parsedVal += multiplier * (*datetime - '0');
multiplier /= 10;
++datetime;
}
uSecondsOut = parsedVal;
}
}
// Timezone
long offsetSec = 0;
auto makeTimeFunc = timegm; // To switch between UTC or local time. UTC by default.
if (*datetime == '+' || *datetime == '-')
{
int sign = *datetime == '+' ? -1 : 1; // Note the sign inversion
++datetime;
parsedVal = strtoul(datetime, (char**)&datetime, 10);
const bool isTimezoneDelimited = *datetime == ':';
if (isTimezoneDelimited != isDateDelimited)
{ // Timezone must be delimited if and only if date is delimited.
return false;
}
if (!isTimezoneDelimited)
{
// hhmm
offsetSec = 3600 * (parsedVal / 100) + 60 * (parsedVal % 100);
}
else
{
// hh:mm
offsetSec = 3600 * parsedVal;
++datetime;
offsetSec += strtoul(datetime, (char**)&datetime, 10);
}
offsetSec *= sign;
}
else if (*datetime != 'Z') // No UTC, no offset, so local time
{
_tm.tm_isdst = -1;
makeTimeFunc = mktime;
}
else // *datetime == 'Z', nothing else to do except skipping the char
{
++datetime;
}
// Finally make time (sec since Epoch)
secondsOut = makeTimeFunc(&_tm) + offsetSec;
// Make sure no other non-whitespace char follows
while (isspace(*datetime))
{
++datetime;
}
return *datetime == '\0';
}
time_t
IntervalStart(const time_t sec, const int interval)
{
if (interval == 0) {
return sec;
}
return sec - (sec % interval);
}
void
ParseQueryString(const std::string& qry, std::map<std::string, std::string> & elements)
{
typedef boost::tokenizer<boost::char_separator<char> > tokenizer;
boost::char_separator<char> ampsep("&");
tokenizer tokens(qry, ampsep);
for (const std::string& tok : tokens) {
size_t pos = tok.find("=");
if (pos != std::string::npos && pos > 0) {
elements[tok.substr(0, pos)] = tok.substr(pos+1);
}
}
}
bool
IsEmptyOrWhiteSpace(const std::string& str)
{
return std::all_of(str.cbegin(), str.cend(), isspace);
}
std::string
UriDecode(const std::string &src)
{
std::string result;
for (size_t n = 0; n < src.length(); n++) {
if (src[n] == '%') {
int decoded = 0;
std::istringstream str(src.substr(n+1, 2));
str >> std::hex >> decoded;
result += static_cast<char>(decoded);
n += 2;
} else {
result += src[n];
}
}
return result;
}
bool
to_bool(const std::string & val)
{
if (0 == strcasecmp(val.c_str(), "true") || val == "1") {
return true;
}
return false;
}
std::string
to_lower(const std::string & input)
{
std::string results;
results.reserve(input.length());
std::transform(input.begin(), input.end(), std::insert_iterator<std::string>(results, results.begin()), ::tolower);
return results;
}
unsigned long long
MurmurHash64(const std::string &input, unsigned long seed = 0)
{
const unsigned long C1 = 0x239b961b;
const unsigned long C2 = 0xab0e9789;
const unsigned long C3 = 0x561ccd1b;
const unsigned long C4 = 0x0bcaa747;
const unsigned long C5 = 0x85ebca6b;
const unsigned long C6 = 0xc2b2ae35;
auto length = input.size();
const char *data = input.c_str();
unsigned long h1 = seed;
unsigned long h2 = seed;
size_t index = 0;
while (index + 7 < length)
{
unsigned long k1 = (unsigned long)(data[index + 0] | data[index + 1] << 8 | data[index + 2] << 16 | data[index + 3] << 24);
unsigned long k2 = (unsigned long)(data[index + 4] | data[index + 5] << 8 | data[index + 6] << 16 | data[index + 7] << 24);
k1 *= C1;
k1 = RotateLeft(k1, 15);
k1 *= C2;
h1 ^= k1;
h1 = RotateLeft(h1, 19);
h1 += h2;
h1 = (h1 * 5) + C3;
k2 *= C2;
k2 = RotateLeft(k2, 17);
k2 *= C1;
h2 ^= k2;
h2 = RotateLeft(h2, 13);
h2 += h1;
h2 = (h2 * 5) + C4;
index += 8;
}
int tail = length - index;
if (tail > 0)
{
unsigned long k1 =
(tail >= 4) ? (unsigned long)(data[index + 0] | data[index + 1] << 8 | data[index + 2] << 16 | data[index + 3] << 24) :
(tail == 3) ? (unsigned long)(data[index + 0] | data[index + 1] << 8 | data[index + 2] << 16) :
(tail == 2) ? (unsigned long)(data[index + 0] | data[index + 1] << 8) :
(unsigned long)data[index + 0];
k1 *= C1;
k1 = RotateLeft(k1, 15);
k1 *= C2;
h1 ^= k1;
if (tail > 4)
{
unsigned long k2 =
(tail == 7) ? (unsigned long)(data[index + 4] | data[index + 5] << 8 | data[index + 6] << 16) :
(tail == 6) ? (unsigned long)(data[index + 4] | data[index + 5] << 8) :
(unsigned long)data[index + 4];
k2 *= C2;
k2 = RotateLeft(k2, 17);
k2 *= C1;
h2 ^= k2;
}
}
h1 ^= (unsigned long)length;
h2 ^= (unsigned long)length;
h1 += h2;
h2 += h1;
h1 ^= h1 >> 16;
h1 *= C5;
h1 ^= h1 >> 13;
h1 *= C6;
h1 ^= h1 >> 16;
h2 ^= h2 >> 16;
h2 *= C5;
h2 ^= h2 >> 13;
h2 *= C6;
h2 ^= h2 >> 16;
h1 += h2;
h2 += h1;
return ((unsigned long long)h2 << 32) | (unsigned long long)h1;
}
std::string
GetErrnoStr(int errnum)
{
char errorstr[256];
char* errRC = strerror_r(errnum, errorstr, sizeof(errorstr));
return std::string(errRC);
}
// Write the buffer, followed by a newline, to the fd. This appears to be a lot of code,
// but it does the job in a single syscall without any string copies or construction, and
// throw a std::runtime_error in the unlikely event of an error or short write.
// Throw a unique exception for EWOULDBLOCK so it's easier to handle.
void
WriteBufferAndNewline(int fd, const char * buf, size_t len)
{
if (buf == nullptr) {
throw std::invalid_argument("Invalid argument; cannot be nullptr");
}
struct iovec iov[2];
ssize_t total;
char newline = '\n';
// Deliberately cast the const away. The C++ standard permits this as long as the
// caller doesn't actually try to change write to the const object. The POSIX
// standard defines iovec::iov_base as a void* so the struct definition can be
// shared with readv() and writev().
iov[0].iov_base = static_cast<void*>(const_cast<char*>(buf));
iov[0].iov_len = len;
total = len;
iov[1].iov_base = static_cast<void*>(&newline);
iov[1].iov_len = 1;
total += 1;
ssize_t result = writev(fd, iov, sizeof(iov)/sizeof(struct iovec));
if (result == -1) {
auto saved_errno = errno;
if (EWOULDBLOCK == errno) {
throw would_block();
} else {
throw std::system_error(saved_errno, std::system_category(), "writev() failed.");
}
} else if (result != total) {
std::ostringstream msg;
msg << "Writev() short write: requested " << total << " but wrote " << result;
throw std::runtime_error(msg.str());
}
}
void
WriteBufferAndNewline(int fd, const char * buf)
{
if (buf == nullptr) {
throw std::invalid_argument("Invalid argument; cannot be nullptr");
}
MdsdUtil::WriteBufferAndNewline(fd, buf, strlen(buf));
}
void
WriteBufferAndNewline(int fd, const std::string& msg)
{
MdsdUtil::WriteBufferAndNewline(fd, msg.c_str(), msg.length());
}
// Convert a multi-byte string (UTF-8) to a wide-char string. On Linux, a wstring is
// a sequence of 32-bit wchar_t characters. The natural encoding would be UTF-32
// (as provided by mbrtowc), but other encodings are possible. In particular, the
// Windows platform expects wstring to be a sequence of 16-bit wchar_t encoded in
// UTF-16. This function converts UTF-8 strings to the wide string expected by Windows
// using the cpprest utf8_to_utf16 function (which returns a std::u16string) and
// copying characters from that into an std::wstring.
std::wstring
to_utf16(const std::string& input)
{
auto utf16_result = utility::conversions::utf8_to_utf16(input);
std::wstring result;
result.reserve(utf16_result.length());
for (const auto & c : utf16_result) {
result.push_back((const wchar_t)c);
}
return result;
}
bool
CreateDirIfNotExists(const std::string& filepath, mode_t mode)
{
if (filepath.empty()) {
throw std::invalid_argument("Invalid, empty file path is given.");
}
struct stat sb;
if (stat(filepath.c_str(), &sb)) {
auto errnoCopy = errno;
if (ENOENT != errnoCopy) {
throw std::system_error(errnoCopy, std::system_category(),
"stat() failed on file path '" + filepath + "'");
}
else {
if (mkdir(filepath.c_str(), mode)) {
throw std::system_error(errno, std::system_category(),
"Failed to mkdir for file path '" + filepath + "'");
}
return true;
}
}
if (!S_ISDIR(sb.st_mode)) {
throw std::runtime_error("File path '" + filepath + "' already exists and is not a directory.");
}
return false;
}
std::string
GetStorageAccountNameFromEndpointURL(const std::string& url)
{
boost::regex re { R"(^\s*(https?://)?([^.]+)\..*$)" }; // Boost regex requires full string match, so ".*$" at the end is needed.
// Submatch 0 is the whole string
// Submatch 2 is the account name
boost::smatch matches;
if (!boost::regex_match(url, matches, re))
{
throw std::runtime_error("Storage account name not found from storage endpoint URL: " + url);
}
return std::string(matches[2].first, matches[2].second);
}
std::string
GetEnvironmentVariable(const std::string & VariableName)
{
char * envariable = getenv(VariableName.c_str());
if (!envariable) {
throw std::runtime_error("Variable '" + VariableName + "' not found in environment");
}
return std::string(envariable);
}
std::string
GetEnvironmentVariableOrEmpty(const std::string & VariableName)
{
char * envariable = getenv(VariableName.c_str());
if (!envariable) {
return std::string();
}
return std::string(envariable);
}
std::string
GetHostname()
{
char hostnameBuffer[HOST_NAME_MAX];
(void) gethostname(hostnameBuffer, sizeof(hostnameBuffer));
return std::string(hostnameBuffer);
}
std::string
GetTenDaySuffix()
{
std::ostringstream strm;
MdsTime::Now().RoundTenDay().GetYMD(strm);
return strm.str();
}
bool
IsRegFileExists(
const std::string & filepath
)
{
if (filepath.empty()) {
throw std::invalid_argument("IsRegFileExists(): invalid, empty file path is given.");
}
struct stat sb;
auto rtn = stat(filepath.c_str(), &sb);
mode_t mode = sb.st_mode;
return (0 == rtn && S_ISREG(mode));
}
bool
IsDirExists(
const std::string & filepath
)
{
if (filepath.empty()) {
throw std::invalid_argument("IsDirExists(): invalid, empty file path is given.");
}
struct stat sb;
auto rtn = stat(filepath.c_str(), &sb);
mode_t mode = sb.st_mode;
return (0 == rtn && S_ISDIR(mode));
}
void
ValidateDirRWXByUser(
const std::string & filepath
)
{
const std::string funcname(__func__);
if (filepath.empty()) {
throw std::invalid_argument(funcname + ": invalid, empty file path is given.");
}
struct stat sb;
if (0 != stat(filepath.c_str(), &sb)) {
throw std::system_error(errno, std::system_category(),
funcname + ": failed to stat() path: " + filepath);
}
auto mode = sb.st_mode;
if (!S_ISDIR(mode)) {
throw std::runtime_error(funcname + ": invalid directory: " + filepath);
}
if (0 != access(filepath.c_str(), R_OK | W_OK | X_OK)) {
throw std::system_error(errno, std::system_category(),
funcname + ": failed to access() path: " + filepath);
}
}
bool
RemoveFileIfExists(
const std::string & filepath
)
{
if (!IsRegFileExists(filepath)) {
return false;
}
if (unlink(filepath.c_str())) {
std::string errmsg = MdsdUtil::GetErrnoStr(errno);
throw std::runtime_error("RemoveFileIfExists(): failed to remove file: '" +
filepath + "'. Reason: " + errmsg);
}
return true;
}
bool
RenameFileIfExists(
const std::string & oldpath,
const std::string & newpath
)
{
if (!IsRegFileExists(oldpath)) {
return false;
}
if (rename(oldpath.c_str(), newpath.c_str())) {
std::string errmsg = MdsdUtil::GetErrnoStr(errno);
throw std::runtime_error("RenameFileIfExists(): failed to rename from '" +
oldpath + "' to '" + newpath + "'. Reason: " + errmsg);
}
return true;
}
void
CopyFile(
const std::string & frompath,
const std::string & topath
)
{
if (frompath.empty()) {
throw std::invalid_argument("CopyFile(): invalid, empty frompath is given.");
}
if (topath.empty()) {
throw std::invalid_argument("CopyFile(): invalid, empty topath is given.");
}
struct TmpDeleter {
std::string m_filename;
bool m_Delete = true;
TmpDeleter(const std::string & filename) : m_filename(filename) {}
~TmpDeleter() {
if (m_Delete) {
RemoveFileIfExists(m_filename);
}
}
};
int fromfd = open(frompath.c_str(), O_RDONLY);
if (-1 == fromfd) {
auto errmsg = GetErrnoStr(errno);
throw std::runtime_error("CopyFile(): failed to open fromfile '" + frompath + "'. Reason: " + errmsg);
}
FdCloser fromFdCloser(fromfd);
int tofd = open(topath.c_str(), O_CREAT | O_WRONLY, 0644);
if (-1 == tofd) {
auto errmsg = GetErrnoStr(errno);
throw std::runtime_error("CopyFile(): failed to open tofile '" + topath + "'. Reason: " + errmsg);
}
FdCloser toFdCloser(tofd);
TmpDeleter tmpDeleter(topath);
ssize_t bytesRead = 0;
char buf[4096];
while((bytesRead = read(fromfd, buf, sizeof(buf))) > 0) {
if (write(tofd, buf, bytesRead) == -1) {
auto errmsg = GetErrnoStr(errno);
throw std::runtime_error("CopyFile(): failed to write to file '" + topath + "'. Reason: " + errmsg);
}
}
if (-1 == bytesRead) {
auto errmsg = GetErrnoStr(errno);
throw std::runtime_error("CopyFile(): failed to read from file '" + frompath + "'. Reason: " + errmsg);
}
tmpDeleter.m_Delete = false;
}
time_t
GetLastModificationTime(
const std::string & filename
)
{
struct stat sb;
auto rtn = stat(filename.c_str(), &sb);
if (rtn) {
throw std::system_error(errno, std::system_category(), "stat() failed on file '" + filename + "'.");
}
return sb.st_mtime;
}
std::string
GetMostRecentlyModifiedFile(
const std::vector<std::string> & filelist
)
{
if (filelist.empty()) {
throw std::invalid_argument("filelist cannot be empty.");
}
if (1 == filelist.size()) {
return filelist[0];
}
uint64_t max_mtime = 0;
std::string resultFilePath;
const uint64_t s2ns = 1000*1000*1000;
for (const auto f : filelist) {
struct stat sb;
auto rtn = stat(f.c_str(), &sb);
if (rtn) {
throw std::system_error(errno, std::system_category(), "stat() failed on file '" + f + "'.");
}
uint64_t mtime = sb.st_mtim.tv_nsec + ((uint64_t)sb.st_mtime)*s2ns;
if (max_mtime < mtime) {
max_mtime = mtime;
resultFilePath = f;
}
}
return resultFilePath;
}
void
MaskSignal(bool isBlock, int signum)
{
sigset_t ss;
if (sigemptyset(&ss)) {
throw std::system_error(errno, std::system_category(), "sigemptyset() failed");
}
if (sigaddset(&ss, signum)) {
throw std::system_error(errno, std::system_category(), "sigaddset() failed");
}
int how = isBlock? SIG_BLOCK : SIG_UNBLOCK;
if (sigprocmask(how, &ss, NULL)) {
throw std::system_error(errno, std::system_category(), "sigprocmask() failed");
}
}
void
TouchFileUs(const std::string & filename)
{
if (utimes(filename.c_str(), NULL)) {
throw std::system_error(errno, std::system_category(), "utimes(" + filename + ") failed");
}
}
std::string
GetFileBasename(
const std::string & filepath
)
{
auto p = filepath.find_last_of('/');
if (p == std::string::npos) {
return filepath;
}
return filepath.substr(p+1);
}
LockedFile::LockedFile(const std::string& filepath) : m_fd(-1)
{
if (!filepath.empty()) { // Make LockedFile("") the same as LockedFile()
Open(filepath);
}
}
void
LockedFile::Open(const std::string& filepath)
{
if (IsOpen()) {
if (filepath == m_filepath) { // same file is being opened/locked again, so just noop
return;
}
throw std::logic_error("LockedFile::Open(): The object is already holding a lock on a different file '" + m_filepath + "'");
}
m_fd = open(filepath.c_str(), O_WRONLY | O_CREAT, 0644);
if (-1 == m_fd) {
auto errmsg = GetErrnoStr(errno);
throw std::runtime_error("LockedFile::Open(): failed to open file '" + filepath + "'. Reason: " + errmsg);
}
if (flock(m_fd, LOCK_EX | LOCK_NB)) {
if (errno == EWOULDBLOCK) {
throw AlreadyLocked("LockedFile::Open() : File '" + filepath + "' is already locked");
}
auto errmsg = GetErrnoStr(errno);
throw std::runtime_error("LockedFile::Open(): failed to lock file '" + filepath + "'. Reason: " + errmsg);
}
m_filepath = filepath;
}
LockedFile::~LockedFile()
{
Remove();
}
void
LockedFile::WriteLine(const std::string& line) const
{
if (!IsOpen()) {
throw std::runtime_error("LockedFile::WriteLine(): No file has been opened");
}
WriteBufferAndNewline(m_fd, line);
}
void
LockedFile::Remove()
{
if (IsOpen()) {
// Truncate first
try {
RemoveFileIfExists(m_filepath);
} catch(std::runtime_error& ex) {
// Remove failed, try to truncate instead.
if (-1 == ftruncate(m_fd, 0)) {
auto errmsg = GetErrnoStr(errno);
throw std::runtime_error("LockedFile::Remove(): Remove failed and failed to truncate file '" + m_filepath + "'. Reason: " + errmsg);
}
}
close(m_fd);
m_filepath.clear();
m_fd = -1;
}
}
void
LockedFile::TruncateAndClose()
{
if (IsOpen()) {
if (-1 == ftruncate(m_fd, 0)) {
auto errmsg = GetErrnoStr(errno);
throw std::runtime_error("LockedFile::TruncateAndClose(): failed to truncate file '" + m_filepath + "'. Reason: " + errmsg);
}
close(m_fd);
m_filepath.clear();
m_fd = -1;
}
}
std::string
StringNCopy(const char* src, size_t maxbytes)
{
if (!src || maxbytes == 0) {
return std::string();
}
auto len = std::min(maxbytes, strlen(src));
std::vector<char> dest(len+1);
memcpy(dest.data(), src, len);
dest[len] = '\0';
return std::string(dest.data());
}
std::string GetTid()
{
pthread_t tid = pthread_self();
return "Tid-" + std::to_string(tid);
}
FdCloser::~FdCloser()
{
if (m_fd > -1) {
close(m_fd);
}
}
void FdCloser::Release()
{
m_fd = -1;
}
int32_t
GetNumFileResourceSoftLimit()
{
struct rlimit rlim;
if (getrlimit(RLIMIT_NOFILE, &rlim) < 0) {
throw std::system_error(errno, std::system_category(), "getrlimit() failed");
}
if(RLIM_INFINITY == rlim.rlim_cur) {
return 0;
}
return static_cast<int32_t> (rlim.rlim_cur);
}
static std::vector<const char*>&
GetSyslogSeverityStringVector()
{
static auto v = new std::vector<const char*> (
{
"\"Emergency\"", "\"Alert\"", "\"Critical\"", "\"Error\"",
"\"Warning\"", "\"Notice\"", "\"Informational\"", "\"Debug\""
});
return *v;
}
const char*
GetSyslogSeverityStringFromValue(int severity)
{
auto v = GetSyslogSeverityStringVector();
if (severity < 0 || severity >= (int) v.size()) {
return "\"Other\"";
}
return v[(size_t) severity];
}
int
CreateAndBindUnixSocket(
const std::string & sockFilePath
)
{
if (sockFilePath.empty()) {
throw std::invalid_argument("CreateAndBindUnixSocket: socket filepath cannot be empty.");
}
struct sockaddr_un addr;
// maxLength: maximum permitted length of a path of a Unix domain socket
constexpr auto maxLength = sizeof(addr.sun_path);
if (sockFilePath.size() > maxLength) {
throw std::invalid_argument("UnixSockAddr: socketfile '" + sockFilePath +
"' exceeds max allowed length " + std::to_string(maxLength));
}
int fd = socket(AF_UNIX, SOCK_STREAM, 0);
if (-1 == fd)
{
throw std::system_error(errno, std::system_category(), "socket(AF_UNIX, SOCK_STREAM)");
}
memset(&addr, 0, sizeof(struct sockaddr_un));
addr.sun_family = AF_UNIX;
sockFilePath.copy(addr.sun_path, sizeof(addr.sun_path));
unlink(sockFilePath.c_str());
if (bind(fd, (struct sockaddr *)&addr, sizeof(addr)))
{
close(fd);
throw std::system_error(errno, std::system_category(), "bind(AF_UNIX, " + sockFilePath + ")");
}
return fd;
}
// Return env var value if set, or default value if not.
// Throw a runtime_error is the specified dir doesn't exist.
std::string
GetEnvDirVar(const std::string& name, const std::string& default_value) {
char* envConfigDir = std::getenv(name.c_str());
if (envConfigDir != nullptr) {
if (!IsDirExists(envConfigDir)) {
throw std::runtime_error("The directory specified in the environment variable " + name + " does not exist: " + envConfigDir);
} else {
return envConfigDir;
}
}
return default_value;
}
void
ParseHttpsOrHttpUrl(const std::string & absUrl, std::string& baseUrl, std::string& params)
{
std::vector<std::string> supportedPrefixList = { "https://", "http://" };
for (const auto & prefix : supportedPrefixList) {
if (absUrl.size() > prefix.size()) {
if (0 == absUrl.compare(0, prefix.size(), prefix)) {
auto sepPos = absUrl.find_first_of('/', prefix.size());
if (sepPos != std::string::npos) {
baseUrl = absUrl.substr(0, sepPos);
params = absUrl.substr(sepPos);
}
else {
baseUrl = absUrl;
params = "";
}
return;
}
}
}
throw std::invalid_argument("ParseHttpsOrHttpUrl(): Invalid absURL: " + absUrl);
}
};
//////////// MdsdUtil namespace ends
// vim: se sw=8 :