initial commit
Some checks failed
sgraves/cpp-build-system_mac/pipeline/head There was a failure building this commit
sgraves/cpp-build-system/pipeline/head There was a failure building this commit

This commit is contained in:
2025-10-17 07:44:16 -05:00
parent 933c973c79
commit 92e3e495ce
1099 changed files with 102722 additions and 0 deletions

View File

@@ -0,0 +1,107 @@
////////////////////////////////////////////////////////////
//
// This software is provided 'as-is', without any express or implied warranty.
// In no event will the authors be held liable for any damages arising from the
// use of this software.
//
// Permission is granted to anyone to use this software for any purpose,
// including commercial applications, and to alter it and redistribute it
// freely, subject to the following restrictions:
//
// 1. The origin of this software must not be misrepresented;
// you must not claim that you wrote the original software.
// If you use this software in a product, an acknowledgment
// in the product documentation would be appreciated but is not required.
//
// 2. Altered source versions must be plainly marked as such,
// and must not be misrepresented as being the original software.
//
// 3. This notice may not be removed or altered from any source distribution.
//
////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////
// Headers
////////////////////////////////////////////////////////////
#include "RoundedRectangleShape.hpp"
#if defined(PROJECT_ENABLE_SFML)
#include <cmath>
namespace sf {
////////////////////////////////////////////////////////////
RoundedRectangleShape::RoundedRectangleShape(const Vector2f &size, float radius,
unsigned int cornerPointCount) {
mySize = size;
myRadius = radius;
myCornerPointCount = cornerPointCount;
update();
}
////////////////////////////////////////////////////////////
void RoundedRectangleShape::setSize(const Vector2f &size) {
mySize = size;
update();
}
////////////////////////////////////////////////////////////
const Vector2f &RoundedRectangleShape::getSize() const { return mySize; }
////////////////////////////////////////////////////////////
void RoundedRectangleShape::setCornersRadius(float radius) {
myRadius = radius;
update();
}
////////////////////////////////////////////////////////////
float RoundedRectangleShape::getCornersRadius() const { return myRadius; }
////////////////////////////////////////////////////////////
void RoundedRectangleShape::setCornerPointCount(unsigned int count) {
myCornerPointCount = count;
update();
}
////////////////////////////////////////////////////////////
std::size_t RoundedRectangleShape::getPointCount() const {
return myCornerPointCount * 4;
}
////////////////////////////////////////////////////////////
sf::Vector2f RoundedRectangleShape::getPoint(std::size_t index) const {
if (index >= myCornerPointCount * 4)
return sf::Vector2f(0, 0);
float deltaAngle = 90.0f / (myCornerPointCount - 1);
sf::Vector2f center;
unsigned int centerIndex = index / myCornerPointCount;
static const float pi = 3.141592654f;
switch (centerIndex) {
case 0:
center.x = mySize.x - myRadius;
center.y = myRadius;
break;
case 1:
center.x = myRadius;
center.y = myRadius;
break;
case 2:
center.x = myRadius;
center.y = mySize.y - myRadius;
break;
case 3:
center.x = mySize.x - myRadius;
center.y = mySize.y - myRadius;
break;
}
return sf::Vector2f(
myRadius * cos(deltaAngle * (index - centerIndex) * pi / 180) + center.x,
-myRadius * sin(deltaAngle * (index - centerIndex) * pi / 180) +
center.y);
}
} // namespace sf
#endif // defined(PROJECT_ENABLE_SFML)

529
support/src/Text2.cpp Normal file
View File

@@ -0,0 +1,529 @@
////////////////////////////////////////////////////////////
//
// SFML - Simple and Fast Multimedia Library
// Copyright (C) 2007-2018 Laurent Gomila (laurent@sfml-dev.org)
//
// This software is provided 'as-is', without any express or implied warranty.
// In no event will the authors be held liable for any damages arising from the
// use of this software.
//
// Permission is granted to anyone to use this software for any purpose,
// including commercial applications, and to alter it and redistribute it
// freely, subject to the following restrictions:
//
// 1. The origin of this software must not be misrepresented;
// you must not claim that you wrote the original software.
// If you use this software in a product, an acknowledgment
// in the product documentation would be appreciated but is not required.
//
// 2. Altered source versions must be plainly marked as such,
// and must not be misrepresented as being the original software.
//
// 3. This notice may not be removed or altered from any source distribution.
//
////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////
// Headers
////////////////////////////////////////////////////////////
#include "Text2.hpp"
#if defined(PROJECT_ENABLE_SFML)
#include <cmath>
#include <SFML/Graphics/RenderTarget.hpp>
#include <SFML/Graphics/Texture.hpp>
namespace {
// Add an underline or strikethrough line to the vertex array
void addLine(sf::VertexArray &vertices, float lineLength, float lineTop,
const sf::Color &color, float offset, float thickness,
float outlineThickness = 0) {
float top = std::floor(lineTop + offset - (thickness / 2) + 0.5f);
float bottom = top + std::floor(thickness + 0.5f);
vertices.append(
sf::Vertex(sf::Vector2f(-outlineThickness, top - outlineThickness), color,
sf::Vector2f(1, 1)));
vertices.append(sf::Vertex(
sf::Vector2f(lineLength + outlineThickness, top - outlineThickness),
color, sf::Vector2f(1, 1)));
vertices.append(
sf::Vertex(sf::Vector2f(-outlineThickness, bottom + outlineThickness),
color, sf::Vector2f(1, 1)));
vertices.append(
sf::Vertex(sf::Vector2f(-outlineThickness, bottom + outlineThickness),
color, sf::Vector2f(1, 1)));
vertices.append(sf::Vertex(
sf::Vector2f(lineLength + outlineThickness, top - outlineThickness),
color, sf::Vector2f(1, 1)));
vertices.append(sf::Vertex(
sf::Vector2f(lineLength + outlineThickness, bottom + outlineThickness),
color, sf::Vector2f(1, 1)));
}
// Add a glyph quad to the vertex array
void addGlyphQuad(sf::VertexArray &vertices, sf::Vector2f position,
const sf::Color &color, const sf::Glyph &glyph,
float italicShear, float outlineThickness = 0) {
float padding = 1.0;
float left = glyph.bounds.left - padding;
float top = glyph.bounds.top - padding;
float right = glyph.bounds.left + glyph.bounds.width + padding;
float bottom = glyph.bounds.top + glyph.bounds.height + padding;
float u1 = static_cast<float>(glyph.textureRect.left) - padding;
float v1 = static_cast<float>(glyph.textureRect.top) - padding;
float u2 =
static_cast<float>(glyph.textureRect.left + glyph.textureRect.width) +
padding;
float v2 =
static_cast<float>(glyph.textureRect.top + glyph.textureRect.height) +
padding;
vertices.append(sf::Vertex(
sf::Vector2f(position.x + left - italicShear * top - outlineThickness,
position.y + top - outlineThickness),
color, sf::Vector2f(u1, v1)));
vertices.append(sf::Vertex(
sf::Vector2f(position.x + right - italicShear * top - outlineThickness,
position.y + top - outlineThickness),
color, sf::Vector2f(u2, v1)));
vertices.append(sf::Vertex(
sf::Vector2f(position.x + left - italicShear * bottom - outlineThickness,
position.y + bottom - outlineThickness),
color, sf::Vector2f(u1, v2)));
vertices.append(sf::Vertex(
sf::Vector2f(position.x + left - italicShear * bottom - outlineThickness,
position.y + bottom - outlineThickness),
color, sf::Vector2f(u1, v2)));
vertices.append(sf::Vertex(
sf::Vector2f(position.x + right - italicShear * top - outlineThickness,
position.y + top - outlineThickness),
color, sf::Vector2f(u2, v1)));
vertices.append(sf::Vertex(
sf::Vector2f(position.x + right - italicShear * bottom - outlineThickness,
position.y + bottom - outlineThickness),
color, sf::Vector2f(u2, v2)));
}
} // namespace
namespace sf {
////////////////////////////////////////////////////////////
Text2::Text2()
: m_string(), m_font(NULL), m_characterSize(30), m_letterSpacingFactor(1.f),
m_lineSpacingFactor(1.f), m_style(Regular), m_fillColor(255, 255, 255),
m_outlineColor(0, 0, 0), m_outlineThickness(0), m_vertices(Triangles),
m_outlineVertices(Triangles), m_bounds(), m_geometryNeedUpdate(false),
m_tabSpaceCount(2U) {}
////////////////////////////////////////////////////////////
Text2::Text2(const String &string, const Font &font, unsigned int characterSize)
: m_string(string), m_font(&font), m_characterSize(characterSize),
m_letterSpacingFactor(1.f), m_lineSpacingFactor(1.f), m_style(Regular),
m_fillColor(255, 255, 255), m_outlineColor(0, 0, 0),
m_outlineThickness(0), m_vertices(Triangles),
m_outlineVertices(Triangles), m_bounds(), m_geometryNeedUpdate(true),
m_tabSpaceCount(2U) {}
////////////////////////////////////////////////////////////
void Text2::setString(const String &string) {
if (m_string != string) {
m_string = string;
m_geometryNeedUpdate = true;
}
}
////////////////////////////////////////////////////////////
void Text2::setFont(const Font &font) {
if (m_font != &font) {
m_font = &font;
m_geometryNeedUpdate = true;
}
}
////////////////////////////////////////////////////////////
void Text2::setCharacterSize(unsigned int size) {
if (m_characterSize != size) {
m_characterSize = size;
m_geometryNeedUpdate = true;
}
}
////////////////////////////////////////////////////////////
void Text2::setLetterSpacing(float spacingFactor) {
if (m_letterSpacingFactor != spacingFactor) {
m_letterSpacingFactor = spacingFactor;
m_geometryNeedUpdate = true;
}
}
////////////////////////////////////////////////////////////
void Text2::setLineSpacing(float spacingFactor) {
if (m_lineSpacingFactor != spacingFactor) {
m_lineSpacingFactor = spacingFactor;
m_geometryNeedUpdate = true;
}
}
////////////////////////////////////////////////////////////
void Text2::setStyle(Uint32 style) {
if (m_style != style) {
m_style = style;
m_geometryNeedUpdate = true;
}
}
////////////////////////////////////////////////////////////
void Text2::setColor(const Color &color) { setFillColor(color); }
////////////////////////////////////////////////////////////
void Text2::setFillColor(const Color &color) {
if (color != m_fillColor) {
m_fillColor = color;
// Change vertex colors directly, no need to update whole geometry
// (if geometry is updated anyway, we can skip this step)
if (!m_geometryNeedUpdate) {
for (std::size_t i = 0; i < m_vertices.getVertexCount(); ++i)
m_vertices[i].color = m_fillColor;
}
}
}
////////////////////////////////////////////////////////////
void Text2::setOutlineColor(const Color &color) {
if (color != m_outlineColor) {
m_outlineColor = color;
// Change vertex colors directly, no need to update whole geometry
// (if geometry is updated anyway, we can skip this step)
if (!m_geometryNeedUpdate) {
for (std::size_t i = 0; i < m_outlineVertices.getVertexCount(); ++i)
m_outlineVertices[i].color = m_outlineColor;
}
}
}
////////////////////////////////////////////////////////////
void Text2::setOutlineThickness(float thickness) {
if (thickness != m_outlineThickness) {
m_outlineThickness = thickness;
m_geometryNeedUpdate = true;
}
}
////////////////////////////////////////////////////////////
void Text2::setTabSpaceCount(Uint8 spaces) {
spaces = std::max(Uint8(1U), spaces);
if (m_tabSpaceCount != spaces) {
m_tabSpaceCount = spaces;
m_geometryNeedUpdate = true;
}
}
////////////////////////////////////////////////////////////
const String &Text2::getString() const { return m_string; }
////////////////////////////////////////////////////////////
const Font *Text2::getFont() const { return m_font; }
////////////////////////////////////////////////////////////
unsigned int Text2::getCharacterSize() const { return m_characterSize; }
////////////////////////////////////////////////////////////
float Text2::getLetterSpacing() const { return m_letterSpacingFactor; }
////////////////////////////////////////////////////////////
float Text2::getLineSpacing() const { return m_lineSpacingFactor; }
////////////////////////////////////////////////////////////
Uint32 Text2::getStyle() const { return m_style; }
////////////////////////////////////////////////////////////
const Color &Text2::getColor() const { return getFillColor(); }
////////////////////////////////////////////////////////////
const Color &Text2::getFillColor() const { return m_fillColor; }
////////////////////////////////////////////////////////////
const Color &Text2::getOutlineColor() const { return m_outlineColor; }
////////////////////////////////////////////////////////////
float Text2::getOutlineThickness() const { return m_outlineThickness; }
////////////////////////////////////////////////////////////
Vector2f Text2::findCharacterPos(std::size_t index) const {
// Make sure that we have a valid font
if (!m_font)
return Vector2f();
// Adjust the index if it's out of range
if (index > m_string.getSize())
index = m_string.getSize();
// Precompute the variables needed by the algorithm
bool isBold = m_style & Bold;
float whitespaceWidth =
m_font->getGlyph(L' ', m_characterSize, isBold).advance;
float letterSpacing = (whitespaceWidth / 3.f) * (m_letterSpacingFactor - 1.f);
whitespaceWidth += letterSpacing;
float lineSpacing =
m_font->getLineSpacing(m_characterSize) * m_lineSpacingFactor;
// Compute the position
Vector2f position;
Uint32 prevChar = 0;
for (std::size_t i = 0; i < index; ++i) {
Uint32 curChar = m_string[i];
// Apply the kerning offset
position.x += m_font->getKerning(prevChar, curChar, m_characterSize);
prevChar = curChar;
// Handle special characters
switch (curChar) {
case ' ':
position.x += whitespaceWidth;
continue;
case '\t':
position.x += whitespaceWidth * static_cast<float>(m_tabSpaceCount);
continue;
case '\n':
position.y += lineSpacing;
position.x = 0;
continue;
}
// For regular characters, add the advance offset of the glyph
position.x += m_font->getGlyph(curChar, m_characterSize, isBold).advance +
letterSpacing;
}
// Transform the position to global coordinates
position = getTransform().transformPoint(position);
return position;
}
////////////////////////////////////////////////////////////
FloatRect Text2::getLocalBounds() const {
ensureGeometryUpdate();
return m_bounds;
}
////////////////////////////////////////////////////////////
FloatRect Text2::getGlobalBounds() const {
return getTransform().transformRect(getLocalBounds());
}
////////////////////////////////////////////////////////////
Uint8 Text2::getTabSpaceCount() const { return m_tabSpaceCount; }
////////////////////////////////////////////////////////////
void Text2::draw(RenderTarget &target, RenderStates states) const {
if (m_font) {
ensureGeometryUpdate();
states.transform *= getTransform();
states.texture = &m_font->getTexture(m_characterSize);
// Only draw the outline if there is something to draw
if (m_outlineThickness != 0)
target.draw(m_outlineVertices, states);
target.draw(m_vertices, states);
}
}
////////////////////////////////////////////////////////////
void Text2::ensureGeometryUpdate() const {
if (!m_font)
return;
// Do nothing, if geometry has not changed and the font texture has not
// changed
if (!m_geometryNeedUpdate)
return;
// Mark geometry as updated
m_geometryNeedUpdate = false;
// Clear the previous geometry
m_vertices.clear();
m_outlineVertices.clear();
m_bounds = FloatRect();
// No text: nothing to draw
if (m_string.isEmpty())
return;
// Compute values related to the text style
bool isBold = m_style & Bold;
bool isUnderlined = m_style & Underlined;
bool isStrikeThrough = m_style & StrikeThrough;
float italicShear =
(m_style & Italic) ? 0.209f : 0.f; // 12 degrees in radians
float underlineOffset = m_font->getUnderlinePosition(m_characterSize);
float underlineThickness = m_font->getUnderlineThickness(m_characterSize);
// Compute the location of the strike through dynamically
// We use the center point of the lowercase 'x' glyph as the reference
// We reuse the underline thickness as the thickness of the strike through as
// well
FloatRect xBounds = m_font->getGlyph(L'x', m_characterSize, isBold).bounds;
float strikeThroughOffset = xBounds.top + xBounds.height / 2.f;
// Precompute the variables needed by the algorithm
float whitespaceWidth =
m_font->getGlyph(L' ', m_characterSize, isBold).advance;
float letterSpacing = (whitespaceWidth / 3.f) * (m_letterSpacingFactor - 1.f);
whitespaceWidth += letterSpacing;
float lineSpacing =
m_font->getLineSpacing(m_characterSize) * m_lineSpacingFactor;
float x = 0.f;
float y = static_cast<float>(m_characterSize);
// Create one quad for each character
float minX = static_cast<float>(m_characterSize);
float minY = static_cast<float>(m_characterSize);
float maxX = 0.f;
float maxY = 0.f;
Uint32 prevChar = 0;
for (std::size_t i = 0; i < m_string.getSize(); ++i) {
Uint32 curChar = m_string[i];
// Skip the \r char to avoid weird graphical issues
if (curChar == '\r')
continue;
// Apply the kerning offset
x += m_font->getKerning(prevChar, curChar, m_characterSize);
// If we're using the underlined style and there's a new line, draw a line
if (isUnderlined && (curChar == L'\n' && prevChar != L'\n')) {
addLine(m_vertices, x, y, m_fillColor, underlineOffset,
underlineThickness);
if (m_outlineThickness != 0)
addLine(m_outlineVertices, x, y, m_outlineColor, underlineOffset,
underlineThickness, m_outlineThickness);
}
// If we're using the strike through style and there's a new line, draw a
// line across all characters
if (isStrikeThrough && (curChar == L'\n' && prevChar != L'\n')) {
addLine(m_vertices, x, y, m_fillColor, strikeThroughOffset,
underlineThickness);
if (m_outlineThickness != 0)
addLine(m_outlineVertices, x, y, m_outlineColor, strikeThroughOffset,
underlineThickness, m_outlineThickness);
}
prevChar = curChar;
// Handle special characters
if ((curChar == L' ') || (curChar == L'\n') || (curChar == L'\t')) {
// Update the current bounds (min coordinates)
minX = std::min(minX, x);
minY = std::min(minY, y);
switch (curChar) {
case L' ':
x += whitespaceWidth;
break;
case L'\t':
x += whitespaceWidth * static_cast<float>(m_tabSpaceCount);
break;
case L'\n':
y += lineSpacing;
x = 0;
break;
}
// Update the current bounds (max coordinates)
maxX = std::max(maxX, x);
maxY = std::max(maxY, y);
// Next glyph, no need to create a quad for whitespace
continue;
}
// Apply the outline
if (m_outlineThickness != 0) {
const Glyph &glyph = m_font->getGlyph(curChar, m_characterSize, isBold,
m_outlineThickness);
float left = glyph.bounds.left;
float top = glyph.bounds.top;
float right = glyph.bounds.left + glyph.bounds.width;
float bottom = glyph.bounds.top + glyph.bounds.height;
// Add the outline glyph to the vertices
addGlyphQuad(m_outlineVertices, Vector2f(x, y), m_outlineColor, glyph,
italicShear, m_outlineThickness);
// Update the current bounds with the outlined glyph bounds
minX =
std::min(minX, x + left - italicShear * bottom - m_outlineThickness);
maxX = std::max(maxX, x + right - italicShear * top - m_outlineThickness);
minY = std::min(minY, y + top - m_outlineThickness);
maxY = std::max(maxY, y + bottom - m_outlineThickness);
}
// Extract the current glyph's description
const Glyph &glyph = m_font->getGlyph(curChar, m_characterSize, isBold);
// Add the glyph to the vertices
addGlyphQuad(m_vertices, Vector2f(x, y), m_fillColor, glyph, italicShear);
// Update the current bounds with the non outlined glyph bounds
if (m_outlineThickness == 0) {
float left = glyph.bounds.left;
float top = glyph.bounds.top;
float right = glyph.bounds.left + glyph.bounds.width;
float bottom = glyph.bounds.top + glyph.bounds.height;
minX = std::min(minX, x + left - italicShear * bottom);
maxX = std::max(maxX, x + right - italicShear * top);
minY = std::min(minY, y + top);
maxY = std::max(maxY, y + bottom);
}
// Advance to the next character
x += glyph.advance + letterSpacing;
}
// If we're using the underlined style, add the last line
if (isUnderlined && (x > 0)) {
addLine(m_vertices, x, y, m_fillColor, underlineOffset, underlineThickness);
if (m_outlineThickness != 0)
addLine(m_outlineVertices, x, y, m_outlineColor, underlineOffset,
underlineThickness, m_outlineThickness);
}
// If we're using the strike through style, add the last line across all
// characters
if (isStrikeThrough && (x > 0)) {
addLine(m_vertices, x, y, m_fillColor, strikeThroughOffset,
underlineThickness);
if (m_outlineThickness != 0)
addLine(m_outlineVertices, x, y, m_outlineColor, strikeThroughOffset,
underlineThickness, m_outlineThickness);
}
// Update the bounding rectangle
m_bounds.left = minX;
m_bounds.top = minY;
m_bounds.width = maxX - minX;
m_bounds.height = maxY - minY;
}
} // namespace sf
#endif // defined(PROJECT_ENABLE_SFML)

45
support/src/backward.cpp Normal file
View File

@@ -0,0 +1,45 @@
// Pick your poison.
//
// On GNU/Linux, you have few choices to get the most out of your stack trace.
//
// By default you get:
// - object filename
// - function name
//
// In order to add:
// - source filename
// - line and column numbers
// - source code snippet (assuming the file is accessible)
// Install one of the following libraries then uncomment one of the macro (or
// better, add the detection of the lib and the macro definition in your build
// system)
// - apt-get install libdw-dev ...
// - g++/clang++ -ldw ...
// #define BACKWARD_HAS_DW 1
// - apt-get install binutils-dev ...
// - g++/clang++ -lbfd ...
// #define BACKWARD_HAS_BFD 1
// - apt-get install libdwarf-dev ...
// - g++/clang++ -ldwarf ...
// #define BACKWARD_HAS_DWARF 1
// Regardless of the library you choose to read the debug information,
// for potentially more detailed stack traces you can use libunwind
// - apt-get install libunwind-dev
// - g++/clang++ -lunwind
// #define BACKWARD_HAS_LIBUNWIND 1
#include "backward.hpp"
#if defined(PROJECT_ENABLE_BACKWARD_CPP)
namespace backward {
backward::SignalHandling sh;
} // namespace backward
#endif // defined(PROJECT_ENABLE_BACKWARD_CPP)

1272
support/src/fzf.c Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,498 @@
/*
Its is under the MIT license, to encourage reuse by cut-and-paste.
The original files are hosted here: https://github.com/sago007/PlatformFolders
Copyright (c) 2015-2016 Poul Sander
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation files
(the "Software"), to deal in the Software without restriction,
including without limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of the Software,
and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#include "platform_folders.hpp"
#if defined(PROJECT_ENABLE_SAGO_PLATFORM_FOLDERS)
#include <cstdio>
#include <cstdlib>
#include <iostream>
#include <stdexcept>
#ifndef _WIN32
#include <pwd.h>
#include <unistd.h>
namespace sago {
namespace internal {
/**
* Retrives the effective user's home dir.
* If the user is running as root we ignore the HOME environment. It works badly
* with sudo. Writing to $HOME as root implies security concerns that a
* multiplatform program cannot be assumed to handle.
* @return The home directory. HOME environment is respected for non-root users
* if it exists.
*/
std::string getHome() {
std::string res;
int uid = getuid();
const char *homeEnv = std::getenv("HOME");
if (uid != 0 && homeEnv) {
// We only acknowlegde HOME if not root.
res = homeEnv;
return res;
}
struct passwd *pw = nullptr;
struct passwd pwd;
long bufsize = sysconf(_SC_GETPW_R_SIZE_MAX);
if (bufsize < 1) {
bufsize = 16384;
}
std::vector<char> buffer;
buffer.resize(bufsize);
int error_code = getpwuid_r(uid, &pwd, buffer.data(), buffer.size(), &pw);
while (error_code == ERANGE) {
// The buffer was too small. Try again with a larger buffer.
bufsize *= 2;
buffer.resize(bufsize);
error_code = getpwuid_r(uid, &pwd, buffer.data(), buffer.size(), &pw);
}
if (error_code) {
throw std::runtime_error("Unable to get passwd struct.");
}
const char *tempRes = pw->pw_dir;
if (!tempRes) {
throw std::runtime_error("User has no home directory");
}
res = tempRes;
return res;
}
} // namespace internal
} // namespace sago
#endif
#ifdef _WIN32
// Make sure we don't bring in all the extra junk with windows.h
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
// stringapiset.h depends on this
#include <windows.h>
// For SUCCEEDED macro
#include <winerror.h>
// For WideCharToMultiByte
#include <stringapiset.h>
// For SHGetFolderPathW and various CSIDL "magic numbers"
#include <shlobj.h>
namespace sago {
namespace internal {
std::string win32_utf16_to_utf8(const wchar_t *wstr) {
std::string res;
// If the 6th parameter is 0 then WideCharToMultiByte returns the number of
// bytes needed to store the result.
int actualSize =
WideCharToMultiByte(CP_UTF8, 0, wstr, -1, nullptr, 0, nullptr, nullptr);
if (actualSize > 0) {
// If the converted UTF-8 string could not be in the initial buffer.
// Allocate one that can hold it.
std::vector<char> buffer(actualSize);
actualSize =
WideCharToMultiByte(CP_UTF8, 0, wstr, -1, &buffer[0],
static_cast<int>(buffer.size()), nullptr, nullptr);
res = buffer.data();
}
if (actualSize == 0) {
// WideCharToMultiByte return 0 for errors.
throw std::runtime_error("UTF16 to UTF8 failed with error code: " +
std::to_string(GetLastError()));
}
return res;
}
} // namespace internal
} // namespace sago
class FreeCoTaskMemory {
LPWSTR pointer = NULL;
public:
explicit FreeCoTaskMemory(LPWSTR pointer) : pointer(pointer) {};
~FreeCoTaskMemory() { CoTaskMemFree(pointer); }
};
static std::string GetKnownWindowsFolder(REFKNOWNFOLDERID folderId,
const char *errorMsg) {
LPWSTR wszPath = NULL;
HRESULT hr;
hr = SHGetKnownFolderPath(folderId, KF_FLAG_CREATE, NULL, &wszPath);
FreeCoTaskMemory scopeBoundMemory(wszPath);
if (!SUCCEEDED(hr)) {
throw std::runtime_error(errorMsg);
}
return sago::internal::win32_utf16_to_utf8(wszPath);
}
static std::string GetAppData() {
return GetKnownWindowsFolder(FOLDERID_RoamingAppData,
"RoamingAppData could not be found");
}
static std::string GetAppDataCommon() {
return GetKnownWindowsFolder(FOLDERID_ProgramData,
"ProgramData could not be found");
}
static std::string GetAppDataLocal() {
return GetKnownWindowsFolder(FOLDERID_LocalAppData,
"LocalAppData could not be found");
}
#elif defined(__APPLE__)
#else
#include <fstream>
#include <map>
#include <sys/types.h>
// For strlen and strtok
#include <cstring>
#include <sstream>
// Typically Linux. For easy reading the comments will just say Linux but should
// work with most *nixes
static void throwOnRelative(const char *envName, const char *envValue) {
if (envValue[0] != '/') {
char buffer[200];
std::snprintf(
buffer, sizeof(buffer),
"Environment \"%s\" does not start with an '/'. XDG specifies that the "
"value must be absolute. The current value is: \"%s\"",
envName, envValue);
throw std::runtime_error(buffer);
}
}
static std::string getLinuxFolderDefault(const char *envName,
const char *defaultRelativePath) {
std::string res;
const char *tempRes = std::getenv(envName);
if (tempRes) {
throwOnRelative(envName, tempRes);
res = tempRes;
return res;
}
res = sago::internal::getHome() + "/" + defaultRelativePath;
return res;
}
static void appendExtraFolders(const char *envName, const char *defaultValue,
std::vector<std::string> &folders) {
const char *envValue = std::getenv(envName);
if (!envValue) {
envValue = defaultValue;
}
sago::internal::appendExtraFoldersTokenizer(envName, envValue, folders);
}
#endif
namespace sago {
#if !defined(_WIN32) && !defined(__APPLE__)
namespace internal {
void appendExtraFoldersTokenizer(const char *envName, const char *envValue,
std::vector<std::string> &folders) {
std::stringstream ss(envValue);
std::string value;
while (std::getline(ss, value, ':')) {
if (value[0] == '/') {
folders.push_back(value);
} else {
// Unless the system is wrongly configured this should never happen... But
// of course some systems will be incorectly configured. The XDG
// documentation indicates that the folder should be ignored but that the
// program should continue.
std::cerr << "Skipping path \"" << value << "\" in \"" << envName
<< "\" because it does not start with a \"/\"\n";
}
}
}
} // namespace internal
#endif
std::string getDataHome() {
#ifdef _WIN32
return GetAppData();
#elif defined(__APPLE__)
return sago::internal::getHome() + "/Library/Application Support";
#else
return getLinuxFolderDefault("XDG_DATA_HOME", ".local/share");
#endif
}
std::string getConfigHome() {
#ifdef _WIN32
return GetAppData();
#elif defined(__APPLE__)
return sago::internal::getHome() + "/Library/Application Support";
#else
return getLinuxFolderDefault("XDG_CONFIG_HOME", ".config");
#endif
}
std::string getCacheDir() {
#ifdef _WIN32
return GetAppDataLocal();
#elif defined(__APPLE__)
return sago::internal::getHome() + "/Library/Caches";
#else
return getLinuxFolderDefault("XDG_CACHE_HOME", ".cache");
#endif
}
std::string getStateDir() {
#ifdef _WIN32
return GetAppDataLocal();
#elif defined(__APPLE__)
return sago::internal::getHome() + "/Library/Application Support";
#else
return getLinuxFolderDefault("XDG_STATE_HOME", ".local/state");
#endif
}
void appendAdditionalDataDirectories(std::vector<std::string> &homes) {
#ifdef _WIN32
homes.push_back(GetAppDataCommon());
#elif !defined(__APPLE__)
appendExtraFolders("XDG_DATA_DIRS", "/usr/local/share/:/usr/share/", homes);
#endif
}
void appendAdditionalConfigDirectories(std::vector<std::string> &homes) {
#ifdef _WIN32
homes.push_back(GetAppDataCommon());
#elif !defined(__APPLE__)
appendExtraFolders("XDG_CONFIG_DIRS", "/etc/xdg", homes);
#endif
}
#if !defined(_WIN32) && !defined(__APPLE__)
struct PlatformFolders::PlatformFoldersData {
std::map<std::string, std::string> folders;
};
static void
PlatformFoldersAddFromFile(const std::string &filename,
std::map<std::string, std::string> &folders) {
std::ifstream infile(filename.c_str());
std::string line;
while (std::getline(infile, line)) {
if (line.length() == 0 || line.at(0) == '#' ||
line.substr(0, 4) != "XDG_" || line.find("_DIR") == std::string::npos) {
continue;
}
try {
std::size_t splitPos = line.find('=');
std::string key = line.substr(0, splitPos);
std::size_t valueStart = line.find('"', splitPos);
std::size_t valueEnd = line.find('"', valueStart + 1);
std::string value =
line.substr(valueStart + 1, valueEnd - valueStart - 1);
folders[key] = value;
} catch (std::exception &e) {
std::cerr << "WARNING: Failed to process \"" << line << "\" from \""
<< filename << "\". Error: " << e.what() << "\n";
continue;
}
}
}
static void
PlatformFoldersFillData(std::map<std::string, std::string> &folders) {
folders["XDG_DOCUMENTS_DIR"] = "$HOME/Documents";
folders["XDG_DESKTOP_DIR"] = "$HOME/Desktop";
folders["XDG_DOWNLOAD_DIR"] = "$HOME/Downloads";
folders["XDG_MUSIC_DIR"] = "$HOME/Music";
folders["XDG_PICTURES_DIR"] = "$HOME/Pictures";
folders["XDG_PUBLICSHARE_DIR"] = "$HOME/Public";
folders["XDG_TEMPLATES_DIR"] = "$HOME/.Templates";
folders["XDG_VIDEOS_DIR"] = "$HOME/Videos";
PlatformFoldersAddFromFile(getConfigHome() + "/user-dirs.dirs", folders);
for (std::map<std::string, std::string>::iterator itr = folders.begin();
itr != folders.end(); ++itr) {
std::string &value = itr->second;
if (value.compare(0, 5, "$HOME") == 0) {
value = sago::internal::getHome() + value.substr(5, std::string::npos);
}
}
}
#endif
PlatformFolders::PlatformFolders() {
#if !defined(_WIN32) && !defined(__APPLE__)
this->data = new PlatformFolders::PlatformFoldersData();
try {
PlatformFoldersFillData(data->folders);
} catch (...) {
delete this->data;
throw;
}
#endif
}
PlatformFolders::~PlatformFolders() {
#if !defined(_WIN32) && !defined(__APPLE__)
delete this->data;
#endif
}
std::string PlatformFolders::getDocumentsFolder() const {
#ifdef _WIN32
return GetKnownWindowsFolder(FOLDERID_Documents,
"Failed to find My Documents folder");
#elif defined(__APPLE__)
return sago::internal::getHome() + "/Documents";
#else
return data->folders["XDG_DOCUMENTS_DIR"];
#endif
}
std::string PlatformFolders::getDesktopFolder() const {
#ifdef _WIN32
return GetKnownWindowsFolder(FOLDERID_Desktop,
"Failed to find Desktop folder");
#elif defined(__APPLE__)
return sago::internal::getHome() + "/Desktop";
#else
return data->folders["XDG_DESKTOP_DIR"];
#endif
}
std::string PlatformFolders::getPicturesFolder() const {
#ifdef _WIN32
return GetKnownWindowsFolder(FOLDERID_Pictures,
"Failed to find My Pictures folder");
#elif defined(__APPLE__)
return sago::internal::getHome() + "/Pictures";
#else
return data->folders["XDG_PICTURES_DIR"];
#endif
}
std::string PlatformFolders::getPublicFolder() const {
#ifdef _WIN32
return GetKnownWindowsFolder(FOLDERID_Public,
"Failed to find the Public folder");
#elif defined(__APPLE__)
return sago::internal::getHome() + "/Public";
#else
return data->folders["XDG_PUBLICSHARE_DIR"];
#endif
}
std::string PlatformFolders::getDownloadFolder1() const {
#ifdef _WIN32
return GetKnownWindowsFolder(FOLDERID_Downloads,
"Failed to find My Downloads folder");
#elif defined(__APPLE__)
return sago::internal::getHome() + "/Downloads";
#else
return data->folders["XDG_DOWNLOAD_DIR"];
#endif
}
std::string PlatformFolders::getMusicFolder() const {
#ifdef _WIN32
return GetKnownWindowsFolder(FOLDERID_Music,
"Failed to find My Music folder");
#elif defined(__APPLE__)
return sago::internal::getHome() + "/Music";
#else
return data->folders["XDG_MUSIC_DIR"];
#endif
}
std::string PlatformFolders::getVideoFolder() const {
#ifdef _WIN32
return GetKnownWindowsFolder(FOLDERID_Videos,
"Failed to find My Video folder");
#elif defined(__APPLE__)
return sago::internal::getHome() + "/Movies";
#else
return data->folders["XDG_VIDEOS_DIR"];
#endif
}
std::string PlatformFolders::getSaveGamesFolder1() const {
#ifdef _WIN32
// A dedicated Save Games folder was not introduced until Vista. For XP and
// older save games are most often saved in a normal folder named "My Games".
// Data that should not be user accessible should be placed under
// GetDataHome() instead
return GetKnownWindowsFolder(FOLDERID_Documents,
"Failed to find My Documents folder") +
"\\My Games";
#elif defined(__APPLE__)
return sago::internal::getHome() + "/Library/Application Support";
#else
return getDataHome();
#endif
}
std::string getDesktopFolder() { return PlatformFolders().getDesktopFolder(); }
std::string getDocumentsFolder() {
return PlatformFolders().getDocumentsFolder();
}
std::string getDownloadFolder() {
return PlatformFolders().getDownloadFolder1();
}
std::string getDownloadFolder1() { return getDownloadFolder(); }
std::string getPicturesFolder() {
return PlatformFolders().getPicturesFolder();
}
std::string getPublicFolder() { return PlatformFolders().getPublicFolder(); }
std::string getMusicFolder() { return PlatformFolders().getMusicFolder(); }
std::string getVideoFolder() { return PlatformFolders().getVideoFolder(); }
std::string getSaveGamesFolder1() {
return PlatformFolders().getSaveGamesFolder1();
}
std::string getSaveGamesFolder2() {
#ifdef _WIN32
return GetKnownWindowsFolder(FOLDERID_SavedGames,
"Failed to find Saved Games folder");
#else
return PlatformFolders().getSaveGamesFolder1();
#endif
}
} // namespace sago
#endif // defined(PROJECT_ENABLE_SAGO_PLATFORM_FOLDERS)

View File

@@ -0,0 +1,198 @@
/*
Copyright <2018-2025> <scott.e.graves@protonmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#include "utils/common.hpp"
#include "utils/error.hpp"
#include "utils/string.hpp"
namespace fifthgrid::utils {
auto compare_version_strings(std::string version1, std::string version2)
-> std::int32_t {
if (utils::string::contains(version1, "-")) {
version1 = utils::string::split(version1, '-', true)[0U];
}
if (utils::string::contains(version2, "-")) {
version2 = utils::string::split(version2, '-', true)[0U];
}
auto nums1 = utils::string::split(version1, '.', true);
auto nums2 = utils::string::split(version2, '.', true);
while (nums1.size() > nums2.size()) {
nums2.emplace_back("0");
}
while (nums2.size() > nums1.size()) {
nums1.emplace_back("0");
}
for (std::size_t idx = 0U; idx < nums1.size(); idx++) {
auto int1 = utils::string::to_uint32(nums1[idx]);
auto int2 = utils::string::to_uint32(nums2[idx]);
auto res = std::memcmp(&int1, &int2, sizeof(int1));
if (res != 0) {
return res;
}
}
return 0;
}
auto compare_version_strings(std::wstring_view version1,
std::wstring_view version2) -> std::int32_t {
return compare_version_strings(utils::string::to_utf8(version1),
utils::string::to_utf8(version2));
}
#if defined(PROJECT_ENABLE_STDUUID)
auto create_uuid_string() -> std::string {
std::random_device random_device{};
auto seed_data = std::array<int, std::mt19937::state_size>{};
std::generate(std::begin(seed_data), std::end(seed_data),
std::ref(random_device));
std::seed_seq seq(std::begin(seed_data), std::end(seed_data));
std::mt19937 generator(seq);
uuids::uuid_random_generator gen{generator};
return uuids::to_string(gen());
}
auto create_uuid_wstring() -> std::wstring {
return utils::string::from_utf8(create_uuid_string());
}
#endif // defined(PROJECT_ENABLE_STDUUID)
auto generate_random_string(std::size_t length) -> std::string {
std::string ret;
if (length == 0U) {
return ret;
}
ret.resize(length);
for (auto &ch : ret) {
std::array<unsigned int, 3U> random_list{
generate_random_between(0U, 57U),
generate_random_between(65U, 90U),
generate_random_between(97U, 255U),
};
ch = static_cast<char>(
random_list.at(generate_random_between(0U, 2U)) % 74U + 48U);
}
return ret;
}
auto generate_random_wstring(std::size_t length) -> std::wstring {
return utils::string::from_utf8(generate_random_string(length));
}
auto get_environment_variable(std::string_view variable) -> std::string {
static std::mutex mtx{};
mutex_lock lock{mtx};
const auto *val = std::getenv(std::string{variable}.c_str());
return std::string{val == nullptr ? "" : val};
}
auto get_environment_variable(std::wstring_view variable) -> std::wstring {
return utils::string::from_utf8(
get_environment_variable(utils::string::to_utf8(variable)));
}
#if defined(PROJECT_ENABLE_BOOST)
auto get_next_available_port(std::uint16_t first_port,
std::uint16_t &available_port) -> bool {
if (first_port == 0U) {
return false;
}
using namespace boost::asio;
using ip::tcp;
boost::system::error_code error_code{};
std::uint32_t check_port{first_port};
while (check_port <= 65535U) {
{
io_context ctx{};
tcp::socket socket(ctx);
socket.connect(
{
tcp::endpoint(ip::address_v4::loopback(),
static_cast<std::uint16_t>(check_port)),
},
error_code);
if (not error_code) {
++check_port;
continue;
}
}
{
io_context ctx{};
tcp::acceptor acceptor(ctx);
acceptor.open(tcp::v4(), error_code);
if (error_code) {
++check_port;
continue;
}
acceptor.set_option(boost::asio::ip::tcp::acceptor::linger(true, 0));
acceptor.bind({tcp::v4(), static_cast<std::uint16_t>(check_port)},
error_code);
if (error_code) {
++check_port;
continue;
}
}
available_port = static_cast<std::uint16_t>(check_port);
return true;
}
return false;
}
#endif // defined(PROJECT_ENABLE_BOOST)
auto retry_action(retryable_action_t action, std::size_t retry_count,
std::chrono::milliseconds retry_wait) -> bool {
FIFTHGRID_USES_FUNCTION_NAME();
try {
for (std::size_t idx = 0U; idx < retry_count; ++idx) {
if (action()) {
return true;
}
std::this_thread::sleep_for(retry_wait);
}
} catch (const std::exception &e) {
utils::error::handle_exception(function_name, e);
} catch (...) {
utils::error::handle_exception(function_name);
}
return false;
}
} // namespace fifthgrid::utils

View File

@@ -0,0 +1,250 @@
/*
Copyright <2018-2025> <scott.e.graves@protonmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#if defined(PROJECT_ENABLE_SQLITE)
#include "utils/db/sqlite/db_common.hpp"
#include "utils/common.hpp"
#include "utils/error.hpp"
namespace fifthgrid::utils::db::sqlite {
void sqlite3_deleter::operator()(sqlite3 *db3) const {
FIFTHGRID_USES_FUNCTION_NAME();
if (db3 == nullptr) {
return;
}
std::string err_msg;
if (not execute_sql(*db3, "VACUUM;", err_msg)) {
utils::error::handle_error(function_name,
utils::error::create_error_message({
"failed to vacuum database",
err_msg,
}));
}
if (not utils::retry_action([&db3]() -> bool {
auto res = sqlite3_close_v2(db3);
if (res == SQLITE_OK) {
return true;
}
auto &&err_str = sqlite3_errstr(res);
utils::error::handle_error(
function_name,
utils::error::create_error_message({
"failed to close database",
(err_str == nullptr ? std::to_string(res) : err_str),
}));
return false;
})) {
fifthgrid::utils::error::handle_error(function_name,
"failed to close database");
}
}
db_result::db_column::db_column(std::int32_t index, std::string name,
db_types_t value) noexcept
: index_(index), name_(std::move(name)), value_(std::move(value)) {}
#if defined(PROJECT_ENABLE_JSON)
auto db_result::db_column::get_value_as_json() const -> nlohmann::json {
return std::visit(
overloaded{
[this](std::int64_t value) -> auto {
return nlohmann::json({{name_, value}});
},
[](auto &&value) -> auto { return nlohmann::json::parse(value); },
},
value_);
}
#endif // defined(PROJECT_ENABLE_JSON)
db_result::db_row::db_row(std::shared_ptr<context> ctx) {
FIFTHGRID_USES_FUNCTION_NAME();
auto column_count = sqlite3_column_count(ctx->stmt.get());
for (std::int32_t col = 0; col < column_count; col++) {
std::string name{sqlite3_column_name(ctx->stmt.get(), col)};
auto column_type = sqlite3_column_type(ctx->stmt.get(), col);
db_types_t value;
switch (column_type) {
case SQLITE_INTEGER: {
value = sqlite3_column_int64(ctx->stmt.get(), col);
} break;
case SQLITE_TEXT: {
const auto *text = reinterpret_cast<const char *>(
sqlite3_column_text(ctx->stmt.get(), col));
value = std::string(text == nullptr ? "" : text);
} break;
default:
throw utils::error::create_exception(function_name,
{
"column type not implemented",
std::to_string(column_type),
});
}
columns_[name] = db_column{col, name, value};
}
}
auto db_result::db_row::get_columns() const -> std::vector<db_column> {
std::vector<db_column> ret;
for (const auto &item : columns_) {
ret.push_back(item.second);
}
return ret;
}
auto db_result::db_row::get_column(std::int32_t index) const -> db_column {
auto iter = std::find_if(
columns_.begin(), columns_.end(),
[&index](auto &&col) -> bool { return col.second.get_index() == index; });
if (iter == columns_.end()) {
throw std::out_of_range("");
}
return iter->second;
}
auto db_result::db_row::get_column(std::string name) const -> db_column {
return columns_.at(name);
}
db_result::db_result(db3_stmt_t stmt, std::int32_t res)
: ctx_(std::make_shared<context>()), res_(res) {
ctx_->stmt = std::move(stmt);
if (res == SQLITE_OK) {
set_res(sqlite3_step(ctx_->stmt.get()));
}
}
auto db_result::get_error_str() const -> std::string {
auto &&err_msg = sqlite3_errstr(res_);
return err_msg == nullptr ? std::to_string(res_) : err_msg;
}
auto db_result::get_row(std::optional<row> &opt_row) const -> bool {
opt_row.reset();
if (not has_row()) {
return false;
}
opt_row = db_row{ctx_};
set_res(sqlite3_step(ctx_->stmt.get()));
return true;
}
auto db_result::has_row() const -> bool { return res_ == SQLITE_ROW; }
void db_result::next_row() const {
if (not has_row()) {
return;
}
set_res(sqlite3_step(ctx_->stmt.get()));
}
auto db_result::ok() const -> bool {
return res_ == SQLITE_DONE || res_ == SQLITE_ROW;
}
auto create_db(std::string db_path,
const std::map<std::string, std::string> &sql_create_tables)
-> db3_t {
FIFTHGRID_USES_FUNCTION_NAME();
sqlite3 *db_ptr{nullptr};
auto db_res =
sqlite3_open_v2(db_path.c_str(), &db_ptr,
SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, nullptr);
if (db_res != SQLITE_OK) {
const auto *msg = sqlite3_errstr(db_res);
throw utils::error::create_exception(
function_name, {
"failed to open db",
db_path,
(msg == nullptr ? std::to_string(db_res) : msg),
});
}
auto db3 = db3_t{
db_ptr,
sqlite3_deleter(),
};
for (auto &&create_item : sql_create_tables) {
std::string err_msg;
if (not sqlite::execute_sql(*db3, create_item.second, err_msg)) {
db3.reset();
throw utils::error::create_exception(function_name, {
err_msg,
});
}
}
set_journal_mode(*db3);
return db3;
}
auto execute_sql(sqlite3 &db3, const std::string &sql, std::string &err)
-> bool {
FIFTHGRID_USES_FUNCTION_NAME();
char *err_msg{nullptr};
auto res = sqlite3_exec(&db3, sql.c_str(), nullptr, nullptr, &err_msg);
if (err_msg != nullptr) {
err = err_msg;
sqlite3_free(err_msg);
err_msg = nullptr;
}
if (res == SQLITE_OK) {
return true;
}
err = utils::error::create_error_message({
function_name,
"failed to execute sql",
err,
sql,
});
return false;
}
void set_journal_mode(sqlite3 &db3) {
sqlite3_exec(&db3,
"PRAGMA journal_mode = WAL;PRAGMA synchronous = NORMAL;PRAGMA "
"auto_vacuum = NONE;",
nullptr, nullptr, nullptr);
}
} // namespace fifthgrid::utils::db::sqlite
#endif // defined(PROJECT_ENABLE_SQLITE)

View File

@@ -0,0 +1,115 @@
/*
Copyright <2018-2025> <scott.e.graves@protonmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#if defined(PROJECT_ENABLE_SQLITE)
#include "utils/db/sqlite/db_delete.hpp"
namespace fifthgrid::utils::db::sqlite {
auto db_delete::context::db_delete_op_t::dump() const -> std::string {
return db_delete{ctx}.dump();
}
auto db_delete::context::db_delete_op_t::go() const -> db_result {
return db_delete{ctx}.go();
}
auto db_delete::dump() const -> std::string {
std::stringstream query;
query << "DELETE FROM \"" << ctx_->table_name << "\"";
if (ctx_->where_data) {
std::int32_t idx{};
query << " WHERE " << ctx_->where_data->base.dump(idx);
}
query << ';';
return query.str();
}
auto db_delete::go() const -> db_result {
sqlite3_stmt *stmt_ptr{nullptr};
auto query_str = dump();
auto res =
sqlite3_prepare_v2(ctx_->db3, query_str.c_str(), -1, &stmt_ptr, nullptr);
auto stmt = db3_stmt_t{
stmt_ptr,
sqlite3_statement_deleter(),
};
if (res != SQLITE_OK) {
return {std::move(stmt), res};
}
if (not ctx_->where_data) {
return {std::move(stmt), res};
}
for (std::int32_t idx = 0;
idx < static_cast<std::int32_t>(ctx_->where_data->values.size());
idx++) {
res =
std::visit(overloaded{
[&stmt, &idx](std::int64_t data) -> std::int32_t {
return sqlite3_bind_int64(stmt.get(), idx + 1, data);
},
[&stmt, &idx](const std::string &data) -> std::int32_t {
return sqlite3_bind_text(stmt.get(), idx + 1,
data.c_str(), -1, nullptr);
},
},
ctx_->where_data->values.at(static_cast<std::size_t>(idx)));
if (res != SQLITE_OK) {
return {std::move(stmt), res};
}
}
return {std::move(stmt), res};
}
auto db_delete::group(context::w_t::group_func_t func) -> context::w_t::wn_t {
if (not ctx_->where_data) {
ctx_->where_data = std::make_unique<context::wd_t>(context::wd_t{
context::w_t{0U, ctx_},
{},
{},
});
}
return ctx_->where_data->base.group(std::move(func));
}
auto db_delete::where(std::string column_name) const -> context::w_t::cn_t {
if (not ctx_->where_data) {
ctx_->where_data = std::make_unique<context::wd_t>(context::wd_t{
context::w_t{0U, ctx_},
{},
{},
});
}
return ctx_->where_data->base.where(column_name);
}
} // namespace fifthgrid::utils::db::sqlite
#endif // defined(PROJECT_ENABLE_SQLITE)

View File

@@ -0,0 +1,99 @@
/*
Copyright <2018-2025> <scott.e.graves@protonmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#if defined(PROJECT_ENABLE_SQLITE)
#include "utils/db/sqlite/db_insert.hpp"
namespace fifthgrid::utils::db::sqlite {
auto db_insert::column_value(std::string column_name, db_types_t value)
-> db_insert {
ctx_->values[column_name] = value;
return *this;
}
auto db_insert::dump() const -> std::string {
std::stringstream query;
query << "INSERT ";
if (ctx_->or_replace) {
query << "OR REPLACE ";
}
query << "INTO \"" << ctx_->table_name << "\" (";
for (std::int32_t idx = 0;
idx < static_cast<std::int32_t>(ctx_->values.size()); idx++) {
if (idx > 0) {
query << ", ";
}
query << '"' << std::next(ctx_->values.begin(), idx)->first << '"';
}
query << ") VALUES (";
for (std::int32_t idx = 0;
idx < static_cast<std::int32_t>(ctx_->values.size()); idx++) {
if (idx > 0) {
query << ", ";
}
query << "?" << (idx + 1);
}
query << ");";
return query.str();
}
auto db_insert::go() const -> db_result {
sqlite3_stmt *stmt_ptr{nullptr};
auto query_str = dump();
auto res =
sqlite3_prepare_v2(ctx_->db3, query_str.c_str(), -1, &stmt_ptr, nullptr);
auto stmt = db3_stmt_t{
stmt_ptr,
sqlite3_statement_deleter(),
};
if (res != SQLITE_OK) {
return {std::move(stmt), res};
}
for (std::int32_t idx = 0;
idx < static_cast<std::int32_t>(ctx_->values.size()); idx++) {
res =
std::visit(overloaded{
[&idx, &stmt](std::int64_t data) -> std::int32_t {
return sqlite3_bind_int64(stmt.get(), idx + 1, data);
},
[&idx, &stmt](const std::string &data) -> std::int32_t {
return sqlite3_bind_text(stmt.get(), idx + 1,
data.c_str(), -1, nullptr);
},
},
std::next(ctx_->values.begin(), idx)->second);
if (res != SQLITE_OK) {
return {std::move(stmt), res};
}
}
return {std::move(stmt), res};
}
} // namespace fifthgrid::utils::db::sqlite
#endif // defined(PROJECT_ENABLE_SQLITE)

View File

@@ -0,0 +1,221 @@
/*
Copyright <2018-2025> <scott.e.graves@protonmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#if defined(PROJECT_ENABLE_SQLITE)
#include "utils/db/sqlite/db_select.hpp"
namespace fifthgrid::utils::db::sqlite {
auto db_select::context::db_select_op_t::dump() const -> std::string {
return db_select{ctx}.dump();
}
auto db_select::context::db_select_op_t::go() const -> db_result {
return db_select{ctx}.go();
}
auto db_select::context::db_select_op_t::group_by(std::string column_name)
-> db_select::context::db_select_op_t {
db_select{ctx}.group_by(column_name);
return *this;
}
auto db_select::context::db_select_op_t::limit(std::int32_t value)
-> db_select::context::db_select_op_t {
db_select{ctx}.limit(value);
return *this;
}
auto db_select::context::db_select_op_t::offset(std::int32_t value)
-> db_select::context::db_select_op_t {
db_select{ctx}.offset(value);
return *this;
}
auto db_select::context::db_select_op_t::order_by(std::string column_name,
bool ascending)
-> db_select::context::db_select_op_t {
db_select{ctx}.order_by(column_name, ascending);
return *this;
}
auto db_select::column(std::string column_name) -> db_select {
ctx_->columns.push_back(column_name);
return *this;
}
auto db_select::count(std::string column_name, std::string as_column_name)
-> db_select {
ctx_->count_columns[column_name] = as_column_name;
return *this;
}
auto db_select::dump() const -> std::string {
std::stringstream query;
query << "SELECT ";
bool has_column{false};
if (ctx_->columns.empty()) {
if (ctx_->count_columns.empty()) {
query << "*";
has_column = true;
}
} else {
has_column = not ctx_->columns.empty();
for (std::size_t idx = 0U; idx < ctx_->columns.size(); idx++) {
if (idx > 0U) {
query << ", ";
}
query << ctx_->columns.at(idx);
}
}
for (std::int32_t idx = 0U;
idx < static_cast<std::int32_t>(ctx_->count_columns.size()); idx++) {
if (has_column || idx > 0) {
query << ", ";
}
query << "COUNT(\"";
auto &count_column = *std::next(ctx_->count_columns.begin(), idx);
query << count_column.first << "\") AS \"" << count_column.second << '"';
}
query << " FROM \"" << ctx_->table_name << "\"";
if (ctx_->where_data) {
std::int32_t idx{};
query << " WHERE " << ctx_->where_data->base.dump(idx);
}
if (not ctx_->group_by.empty()) {
query << " GROUP BY ";
for (std::size_t idx = 0U; idx < ctx_->group_by.size(); idx++) {
if (idx > 0U) {
query << ", ";
}
query << "\"" << ctx_->group_by.at(idx) << "\"";
}
}
if (ctx_->order_by.has_value()) {
query << " ORDER BY \"" << ctx_->order_by.value().first << "\" ";
query << (ctx_->order_by.value().second ? "ASC" : "DESC");
}
if (ctx_->limit.has_value()) {
query << " LIMIT " << ctx_->limit.value();
}
if (ctx_->offset.has_value()) {
query << " OFFSET " << ctx_->offset.value();
}
query << ';';
return query.str();
}
auto db_select::go() const -> db_result {
sqlite3_stmt *stmt_ptr{nullptr};
auto query_str = dump();
auto res =
sqlite3_prepare_v2(ctx_->db3, query_str.c_str(), -1, &stmt_ptr, nullptr);
auto stmt = db3_stmt_t{
stmt_ptr,
sqlite3_statement_deleter(),
};
if (res != SQLITE_OK) {
return {std::move(stmt), res};
}
if (not ctx_->where_data) {
return {std::move(stmt), res};
}
for (std::int32_t idx = 0;
idx < static_cast<std::int32_t>(ctx_->where_data->values.size());
idx++) {
res =
std::visit(overloaded{
[&idx, &stmt](std::int64_t data) -> std::int32_t {
return sqlite3_bind_int64(stmt.get(), idx + 1, data);
},
[&idx, &stmt](const std::string &data) -> std::int32_t {
return sqlite3_bind_text(stmt.get(), idx + 1,
data.c_str(), -1, nullptr);
},
},
ctx_->where_data->values.at(static_cast<std::size_t>(idx)));
if (res != SQLITE_OK) {
return {std::move(stmt), res};
}
}
return {std::move(stmt), res};
}
auto db_select::group(context::w_t::group_func_t func) -> context::w_t::wn_t {
if (not ctx_->where_data) {
ctx_->where_data = std::make_unique<context::wd_t>(context::wd_t{
context::w_t{0U, ctx_},
{},
{},
});
}
return ctx_->where_data->base.group(std::move(func));
}
auto db_select::group_by(std::string column_name) -> db_select {
ctx_->group_by.emplace_back(std::move(column_name));
return *this;
}
auto db_select::limit(std::int32_t value) -> db_select {
ctx_->limit = value;
return *this;
}
auto db_select::offset(std::int32_t value) -> db_select {
ctx_->offset = value;
return *this;
}
auto db_select::order_by(std::string column_name, bool ascending) -> db_select {
ctx_->order_by = {column_name, ascending};
return *this;
}
auto db_select::where(std::string column_name) const -> context::w_t::cn_t {
if (not ctx_->where_data) {
ctx_->where_data = std::make_unique<context::wd_t>(context::wd_t{
context::w_t{0U, ctx_},
{},
{},
});
}
return ctx_->where_data->base.where(column_name);
}
} // namespace fifthgrid::utils::db::sqlite
#endif // defined(PROJECT_ENABLE_SQLITE)

View File

@@ -0,0 +1,189 @@
/*
Copyright <2018-2025> <scott.e.graves@protonmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#if defined(PROJECT_ENABLE_SQLITE)
#include "utils/db/sqlite/db_update.hpp"
namespace fifthgrid::utils::db::sqlite {
auto db_update::context::db_update_op_t::dump() const -> std::string {
return db_update{ctx}.dump();
}
auto db_update::context::db_update_op_t::go() const -> db_result {
return db_update{ctx}.go();
}
auto db_update::context::db_update_op_t::limit(std::int32_t value)
-> db_update::context::db_update_op_t {
db_update{ctx}.limit(value);
return *this;
}
auto db_update::context::db_update_op_t::order_by(std::string column_name,
bool ascending)
-> db_update::context::db_update_op_t {
db_update{ctx}.order_by(column_name, ascending);
return *this;
}
auto db_update::column_value(std::string column_name, db_types_t value)
-> db_update {
ctx_->column_values[column_name] = value;
return *this;
}
auto db_update::dump() const -> std::string {
std::stringstream query;
query << "UPDATE \"" << ctx_->table_name << "\" SET ";
for (std::int32_t idx = 0;
idx < static_cast<std::int32_t>(ctx_->column_values.size()); idx++) {
if (idx > 0) {
query << ", ";
}
auto column = std::next(ctx_->column_values.begin(), idx);
query << '"' << column->first << "\"=?" + std::to_string(idx + 1);
}
if (ctx_->where_data) {
auto idx{static_cast<std::int32_t>(ctx_->column_values.size())};
query << " WHERE " << ctx_->where_data->base.dump(idx);
}
if (ctx_->order_by.has_value()) {
query << " ORDER BY \"" << ctx_->order_by.value().first << "\" ";
query << (ctx_->order_by.value().second ? "ASC" : "DESC");
}
if (ctx_->limit.has_value()) {
query << " LIMIT " << ctx_->limit.value();
}
query << ';';
return query.str();
}
auto db_update::go() const -> db_result {
sqlite3_stmt *stmt_ptr{nullptr};
auto query_str = dump();
auto res =
sqlite3_prepare_v2(ctx_->db3, query_str.c_str(), -1, &stmt_ptr, nullptr);
auto stmt = db3_stmt_t{
stmt_ptr,
sqlite3_statement_deleter(),
};
if (res != SQLITE_OK) {
return {std::move(stmt), res};
}
for (std::int32_t idx = 0;
idx < static_cast<std::int32_t>(ctx_->column_values.size()); idx++) {
res =
std::visit(overloaded{
[&idx, &stmt](std::int64_t data) -> std::int32_t {
return sqlite3_bind_int64(stmt.get(), idx + 1, data);
},
[&idx, &stmt](const std::string &data) -> std::int32_t {
return sqlite3_bind_text(stmt.get(), idx + 1,
data.c_str(), -1, nullptr);
},
},
std::next(ctx_->column_values.begin(), idx)->second);
if (res != SQLITE_OK) {
return {std::move(stmt), res};
}
}
if (not ctx_->where_data) {
return {std::move(stmt), res};
}
for (std::int32_t idx = 0;
idx < static_cast<std::int32_t>(ctx_->where_data->values.size());
idx++) {
res = std::visit(
overloaded{
[this, &idx, &stmt](std::int64_t data) -> std::int32_t {
return sqlite3_bind_int64(
stmt.get(),
idx + static_cast<std::int32_t>(ctx_->column_values.size()) +
1,
data);
},
[this, &idx, &stmt](const std::string &data) -> std::int32_t {
return sqlite3_bind_text(
stmt.get(),
idx + static_cast<std::int32_t>(ctx_->column_values.size()) +
1,
data.c_str(), -1, nullptr);
},
},
ctx_->where_data->values.at(static_cast<std::size_t>(idx)));
if (res != SQLITE_OK) {
return {std::move(stmt), res};
}
}
return {std::move(stmt), res};
}
auto db_update::group(context::w_t::group_func_t func) -> context::w_t::wn_t {
if (not ctx_->where_data) {
ctx_->where_data = std::make_unique<context::wd_t>(context::wd_t{
context::w_t{0U, ctx_},
{},
{},
});
}
return ctx_->where_data->base.group(std::move(func));
}
auto db_update::limit(std::int32_t value) -> db_update {
ctx_->limit = value;
return *this;
}
auto db_update::order_by(std::string column_name, bool ascending) -> db_update {
ctx_->order_by = {column_name, ascending};
return *this;
}
auto db_update::where(std::string column_name) const -> context::w_t::cn_t {
if (not ctx_->where_data) {
ctx_->where_data = std::make_unique<context::wd_t>(context::wd_t{
context::w_t{0U, ctx_},
{},
{},
});
}
return ctx_->where_data->base.where(column_name);
}
} // namespace fifthgrid::utils::db::sqlite
#endif // defined(PROJECT_ENABLE_SQLITE)

View File

@@ -0,0 +1,52 @@
/*
Copyright <2018-2025> <scott.e.graves@protonmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#include "utils/file.hpp"
#include "utils/common.hpp"
#include "utils/error.hpp"
#if defined(_WIN32)
#include "utils/path.hpp"
#endif // defined(_WIN32)
namespace fifthgrid::utils::directory {
auto temp() -> std::string {
FIFTHGRID_USES_FUNCTION_NAME();
#if defined(_WIN32)
auto ret{utils::get_environment_variable("TEMP")};
if (ret.empty()) {
ret = utils::path::combine(utils::get_environment_variable("LOCALAPPDATA"),
{"Temp"});
}
#else // !defined(_WIN32)
std::string ret{"/tmp"};
#endif // defined(_WIN32)
if (not utils::file::directory{ret}.create_directory()) {
utils::error::handle_error(function_name,
utils::error::create_error_message(
{"failed to create directory", ret}));
}
return ret;
}
} // namespace fifthgrid::utils::directory

View File

@@ -0,0 +1,701 @@
/*
Copyright <2018-2025> <scott.e.graves@protonmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#if defined(PROJECT_ENABLE_LIBSODIUM) && defined(PROJECT_ENABLE_BOOST)
#include "utils/encrypting_reader.hpp"
#include "utils/base64.hpp"
#include "utils/collection.hpp"
#include "utils/common.hpp"
#include "utils/config.hpp"
#include "utils/encryption.hpp"
#include "utils/error.hpp"
#include "utils/file.hpp"
#include "utils/unix.hpp"
#include "utils/windows.hpp"
#if !defined(CURL_READFUNC_ABORT)
#define CURL_READFUNC_ABORT (-1)
#endif // !defined(CURL_READFUNC_ABORT)
namespace fifthgrid::utils::encryption {
class encrypting_streambuf final : public encrypting_reader::streambuf {
public:
encrypting_streambuf(const encrypting_streambuf &) = default;
encrypting_streambuf(encrypting_streambuf &&) = delete;
auto operator=(const encrypting_streambuf &)
-> encrypting_streambuf & = delete;
auto operator=(encrypting_streambuf &&) -> encrypting_streambuf & = delete;
explicit encrypting_streambuf(const encrypting_reader &reader)
: reader_(reader) {
setg(reinterpret_cast<char *>(0), reinterpret_cast<char *>(0),
reinterpret_cast<char *>(reader_.get_total_size()));
}
~encrypting_streambuf() override = default;
private:
encrypting_reader reader_;
protected:
auto seekoff(off_type off, std::ios_base::seekdir dir,
std::ios_base::openmode which = std::ios_base::out |
std::ios_base::in)
-> pos_type override {
FIFTHGRID_USES_FUNCTION_NAME();
if ((which & std::ios_base::in) != std::ios_base::in) {
throw utils::error::create_exception(function_name,
{
"output is not supported",
});
}
const auto set_position = [this](char *next) -> pos_type {
if ((next <= egptr()) && (next >= eback())) {
setg(eback(), next, reinterpret_cast<char *>(reader_.get_total_size()));
return static_cast<std::streamoff>(
reinterpret_cast<std::uintptr_t>(gptr()));
}
return {traits_type::eof()};
};
switch (dir) {
case std::ios_base::beg:
return set_position(eback() + off);
case std::ios_base::cur:
return set_position(gptr() + off);
case std::ios_base::end:
return set_position(egptr() + off);
}
return encrypting_reader::streambuf::seekoff(off, dir, which);
}
auto seekpos(pos_type pos,
std::ios_base::openmode which = std::ios_base::out |
std::ios_base::in)
-> pos_type override {
return seekoff(pos, std::ios_base::beg, which);
}
auto uflow() -> int_type override {
auto ret = underflow();
if (ret == traits_type::eof()) {
return ret;
}
gbump(1);
return ret;
}
auto underflow() -> int_type override {
if (gptr() == egptr()) {
return traits_type::eof();
}
reader_.set_read_position(reinterpret_cast<std::uintptr_t>(gptr()));
char c{};
auto res = encrypting_reader::reader_function(&c, 1U, 1U, &reader_);
if (res != 1) {
return traits_type::eof();
}
return c;
}
auto xsgetn(char *ptr, std::streamsize count) -> std::streamsize override {
if (gptr() == egptr()) {
return traits_type::eof();
}
reader_.set_read_position(reinterpret_cast<std::uintptr_t>(gptr()));
auto res = encrypting_reader::reader_function(
ptr, 1U, static_cast<std::size_t>(count), &reader_);
if ((res == reader_.get_error_return()) ||
(reader_.get_stop_requested() &&
(res == static_cast<std::size_t>(CURL_READFUNC_ABORT)))) {
return traits_type::eof();
}
setg(eback(), gptr() + res,
reinterpret_cast<char *>(reader_.get_total_size()));
return static_cast<std::streamsize>(res);
}
};
class encrypting_reader_iostream final : public encrypting_reader::iostream {
public:
encrypting_reader_iostream(const encrypting_reader_iostream &) = delete;
encrypting_reader_iostream(encrypting_reader_iostream &&) = delete;
auto operator=(const encrypting_reader_iostream &)
-> encrypting_reader_iostream & = delete;
auto operator=(encrypting_reader_iostream &&)
-> encrypting_reader_iostream & = delete;
explicit encrypting_reader_iostream(
std::unique_ptr<encrypting_streambuf> buffer)
: encrypting_reader::iostream(buffer.get()), buffer_(std::move(buffer)) {}
~encrypting_reader_iostream() override = default;
private:
std::unique_ptr<encrypting_streambuf> buffer_;
};
const std::size_t encrypting_reader::header_size_ = ([]() {
return crypto_aead_xchacha20poly1305_IETF_NPUBBYTES +
crypto_aead_xchacha20poly1305_IETF_ABYTES;
})();
const std::size_t encrypting_reader::data_chunk_size_ = (8UL * 1024UL * 1024UL);
const std::size_t encrypting_reader::encrypted_chunk_size_ =
data_chunk_size_ + header_size_;
encrypting_reader::encrypting_reader(
std::string_view file_name, std::string_view source_path,
stop_type_callback stop_requested_cb, std::string_view token,
std::optional<std::string> relative_parent_path, std::size_t error_return)
: keys_(utils::encryption::generate_key<utils::hash::hash_256_t>(token),
utils::encryption::generate_key<utils::hash::hash_256_t>(token)),
stop_requested_cb_(std::move(stop_requested_cb)),
error_return_(error_return),
source_file_(utils::file::file::open_or_create_file(source_path, true)) {
common_initialize(true);
create_encrypted_paths(file_name, relative_parent_path);
}
encrypting_reader::encrypting_reader(stop_type_callback stop_requested_cb,
std::string_view encrypted_file_path,
std::string_view source_path,
std::string_view token,
std::size_t error_return)
: keys_(utils::encryption::generate_key<utils::hash::hash_256_t>(token),
utils::encryption::generate_key<utils::hash::hash_256_t>(token)),
stop_requested_cb_(std::move(stop_requested_cb)),
error_return_(error_return),
source_file_(utils::file::file::open_or_create_file(source_path, true)),
encrypted_file_name_(
utils::path::strip_to_file_name(std::string{encrypted_file_path})),
encrypted_file_path_(encrypted_file_path) {
common_initialize(true);
}
encrypting_reader::encrypting_reader(
stop_type_callback stop_requested_cb, std::string_view encrypted_file_path,
std::string_view source_path, std::string_view token,
std::vector<
std::array<unsigned char, crypto_aead_xchacha20poly1305_IETF_NPUBBYTES>>
iv_list,
std::size_t error_return)
: keys_(utils::encryption::generate_key<utils::hash::hash_256_t>(token),
utils::encryption::generate_key<utils::hash::hash_256_t>(token)),
stop_requested_cb_(std::move(stop_requested_cb)),
error_return_(error_return),
source_file_(utils::file::file::open_or_create_file(source_path, true)),
encrypted_file_name_(
utils::path::strip_to_file_name(std::string{encrypted_file_path})),
encrypted_file_path_(encrypted_file_path),
iv_list_(std::move(iv_list)) {
common_initialize(false);
}
encrypting_reader::encrypting_reader(
std::string_view file_name, std::string_view source_path,
stop_type_callback stop_requested_cb, std::string_view token,
kdf_config cfg, std::optional<std::string> relative_parent_path,
std::size_t error_return)
: stop_requested_cb_(std::move(stop_requested_cb)),
error_return_(error_return),
source_file_(utils::file::file::open_or_create_file(source_path, true)) {
common_initialize_kdf_keys(token, cfg);
common_initialize(true);
create_encrypted_paths(file_name, relative_parent_path);
}
encrypting_reader::encrypting_reader(stop_type_callback stop_requested_cb,
std::string_view encrypted_file_path,
std::string_view source_path,
std::string_view token, kdf_config cfg,
std::size_t error_return)
: stop_requested_cb_(std::move(stop_requested_cb)),
error_return_(error_return),
source_file_(utils::file::file::open_or_create_file(source_path, true)),
encrypted_file_name_(
utils::path::strip_to_file_name(std::string{encrypted_file_path})),
encrypted_file_path_(encrypted_file_path) {
common_initialize_kdf_keys(token, cfg);
common_initialize(true);
}
encrypting_reader::encrypting_reader(
stop_type_callback stop_requested_cb, std::string_view encrypted_file_path,
std::string_view source_path, std::string_view token, kdf_config cfg,
std::vector<
std::array<unsigned char, crypto_aead_xchacha20poly1305_IETF_NPUBBYTES>>
iv_list,
std::size_t error_return)
: stop_requested_cb_(std::move(stop_requested_cb)),
error_return_(error_return),
source_file_(utils::file::file::open_or_create_file(source_path, true)),
encrypted_file_name_(
utils::path::strip_to_file_name(std::string{encrypted_file_path})),
encrypted_file_path_(encrypted_file_path),
iv_list_(std::move(iv_list)) {
common_initialize_kdf_keys(token, cfg);
common_initialize(false);
}
encrypting_reader::encrypting_reader(
std::string_view file_name, std::string_view source_path,
stop_type_callback stop_requested_cb,
const utils::hash::hash_256_t &master_key, const kdf_config &cfg,
std::optional<std::string> relative_parent_path, std::size_t error_return)
: stop_requested_cb_(std::move(stop_requested_cb)),
error_return_(error_return),
source_file_(utils::file::file::open_or_create_file(source_path, true)) {
common_initialize_kdf_data(cfg, master_key);
auto [path_key, path_cfg] = cfg.create_subkey(
kdf_context::path, utils::generate_secure_random<std::uint64_t>(),
master_key);
keys_.second = std::move(path_key);
kdf_headers_->second = path_cfg.to_header();
common_initialize(true);
create_encrypted_paths(file_name, relative_parent_path);
}
encrypting_reader::encrypting_reader(
std::string_view file_name, std::string_view source_path,
stop_type_callback stop_requested_cb,
const utils::hash::hash_256_t &master_key,
const std::pair<kdf_config, kdf_config> &configs,
std::optional<std::string> relative_parent_path, std::size_t error_return)
: stop_requested_cb_(std::move(stop_requested_cb)),
error_return_(error_return),
source_file_(utils::file::file::open_or_create_file(source_path, true)) {
keys_ = {
configs.first.recreate_subkey(utils::encryption::kdf_context::data,
master_key),
configs.second.recreate_subkey(utils::encryption::kdf_context::path,
master_key),
};
kdf_headers_ = {
configs.first.to_header(),
configs.second.to_header(),
};
common_initialize(true);
create_encrypted_paths(file_name, relative_parent_path);
}
encrypting_reader::encrypting_reader(stop_type_callback stop_requested_cb,
std::string_view encrypted_file_path,
std::string_view source_path,
const utils::hash::hash_256_t &master_key,
const kdf_config &cfg,
std::size_t error_return)
: stop_requested_cb_(std::move(stop_requested_cb)),
error_return_(error_return),
source_file_(utils::file::file::open_or_create_file(source_path, true)),
encrypted_file_name_(
utils::path::strip_to_file_name(std::string{encrypted_file_path})),
encrypted_file_path_(encrypted_file_path) {
common_initialize_kdf_data(cfg, master_key);
common_initialize_kdf_path(master_key);
common_initialize(true);
}
encrypting_reader::encrypting_reader(
stop_type_callback stop_requested_cb, std::string_view encrypted_file_path,
std::string_view source_path, const utils::hash::hash_256_t &master_key,
const kdf_config &cfg,
std::vector<
std::array<unsigned char, crypto_aead_xchacha20poly1305_IETF_NPUBBYTES>>
iv_list,
std::size_t error_return)
: stop_requested_cb_(std::move(stop_requested_cb)),
error_return_(error_return),
source_file_(utils::file::file::open_or_create_file(source_path, true)),
encrypted_file_name_(
utils::path::strip_to_file_name(std::string{encrypted_file_path})),
encrypted_file_path_(encrypted_file_path),
iv_list_(std::move(iv_list)) {
common_initialize_kdf_data(cfg, master_key);
common_initialize_kdf_path(master_key);
common_initialize(false);
}
encrypting_reader::encrypting_reader(
stop_type_callback stop_requested_cb, std::string_view encrypted_file_path,
std::string_view source_path, const utils::hash::hash_256_t &master_key,
const std::pair<kdf_config, kdf_config> &configs,
std::vector<
std::array<unsigned char, crypto_aead_xchacha20poly1305_IETF_NPUBBYTES>>
iv_list,
std::size_t error_return)
: stop_requested_cb_(std::move(stop_requested_cb)),
error_return_(error_return),
source_file_(utils::file::file::open_or_create_file(source_path, true)),
encrypted_file_name_(
utils::path::strip_to_file_name(std::string{encrypted_file_path})),
encrypted_file_path_(encrypted_file_path),
iv_list_(std::move(iv_list)) {
keys_.first = configs.first.recreate_subkey(
utils::encryption::kdf_context::data, master_key);
keys_.second = configs.second.recreate_subkey(
utils::encryption::kdf_context::path, master_key);
kdf_headers_ = {
configs.first.to_header(),
configs.second.to_header(),
};
common_initialize(false);
}
encrypting_reader::encrypting_reader(const encrypting_reader &reader)
: keys_(reader.keys_),
stop_requested_cb_(reader.stop_requested_cb_),
error_return_(reader.error_return_),
source_file_(
utils::file::file::open_file(reader.source_file_->get_path(), true)),
encrypted_file_name_(reader.encrypted_file_name_),
encrypted_file_path_(reader.encrypted_file_path_),
iv_list_(reader.iv_list_),
chunk_buffers_(reader.chunk_buffers_),
kdf_headers_(reader.kdf_headers_),
last_data_chunk_(reader.last_data_chunk_),
last_data_chunk_size_(reader.last_data_chunk_size_),
read_offset_(reader.read_offset_),
total_size_(reader.total_size_) {
FIFTHGRID_USES_FUNCTION_NAME();
if (not *source_file_) {
throw utils::error::create_exception(
function_name, {
"file open failed",
std::to_string(utils::get_last_error_code()),
source_file_->get_path(),
});
}
}
auto encrypting_reader::calculate_decrypted_size(std::uint64_t total_size,
bool uses_kdf)
-> std::uint64_t {
if (uses_kdf) {
total_size -= kdf_config::size();
}
return total_size - (utils::divide_with_ceiling(
total_size, static_cast<std::uint64_t>(
get_encrypted_chunk_size())) *
encryption_header_size);
}
auto encrypting_reader::calculate_encrypted_size(std::string_view source_path,
bool uses_kdf)
-> std::uint64_t {
FIFTHGRID_USES_FUNCTION_NAME();
auto opt_size = utils::file::file{source_path}.size();
if (not opt_size.has_value()) {
throw utils::error::create_exception(
function_name, {
"get file size failed",
std::to_string(utils::get_last_error_code()),
source_path,
});
}
return calculate_encrypted_size(opt_size.value(), uses_kdf);
}
auto encrypting_reader::calculate_encrypted_size(std::uint64_t size,
bool uses_kdf)
-> std::uint64_t {
auto total_chunks = utils::divide_with_ceiling(
size, static_cast<std::uint64_t>(data_chunk_size_));
return size + (total_chunks * encryption_header_size) +
(uses_kdf ? kdf_config::size() : 0U);
}
void encrypting_reader::common_initialize(bool procces_iv_list) {
FIFTHGRID_USES_FUNCTION_NAME();
if (not *source_file_) {
throw utils::error::create_exception(function_name,
{
"file open failed",
source_file_->get_path(),
});
}
auto opt_size = source_file_->size();
if (not opt_size.has_value()) {
throw utils::error::create_exception(function_name,
{
"failed to get file size",
source_file_->get_path(),
});
}
auto file_size = opt_size.value();
auto total_chunks = utils::divide_with_ceiling(
file_size, static_cast<std::uint64_t>(data_chunk_size_));
total_size_ = file_size + (total_chunks * encryption_header_size) +
(kdf_headers_.has_value() ? kdf_headers_->first.size() : 0U);
last_data_chunk_ = total_chunks - 1U;
last_data_chunk_size_ = (file_size <= data_chunk_size_) ? file_size
: (file_size % data_chunk_size_) == 0U
? data_chunk_size_
: file_size % data_chunk_size_;
if (not procces_iv_list) {
return;
}
iv_list_.resize(total_chunks);
for (auto &data : iv_list_) {
randombytes_buf(data.data(), data.size());
}
}
void encrypting_reader::common_initialize_kdf_data(
const kdf_config &cfg, const utils::hash::hash_256_t &master_key) {
auto [data_key, data_cfg] = cfg.create_subkey(
kdf_context::data, utils::generate_secure_random<std::uint64_t>(),
master_key);
keys_.first = std::move(data_key);
kdf_headers_ = {data_cfg.to_header(), {}};
}
void encrypting_reader::common_initialize_kdf_keys(std::string_view token,
kdf_config &cfg) {
auto key =
utils::encryption::generate_key<utils::hash::hash_256_t>(token, cfg);
keys_ = {key, key};
kdf_headers_ = {cfg.to_header(), cfg.to_header()};
}
void encrypting_reader::common_initialize_kdf_path(
const utils::hash::hash_256_t &master_key) {
FIFTHGRID_USES_FUNCTION_NAME();
auto buffer = macaron::Base64::Decode(encrypted_file_path_);
kdf_config path_cfg;
if (not kdf_config::from_header(buffer, path_cfg)) {
throw utils::error::create_exception(
function_name, {"failed to create path kdf config from header"});
}
utils::hash::hash_256_t path_key;
std::tie(path_key, std::ignore) =
path_cfg.create_subkey(kdf_context::path, path_cfg.unique_id, master_key);
kdf_headers_->second = path_cfg.to_header();
}
void encrypting_reader::create_encrypted_paths(
std::string_view file_name,
std::optional<std::string> relative_parent_path) {
data_buffer result;
utils::encryption::encrypt_data(
keys_.second, reinterpret_cast<const unsigned char *>(file_name.data()),
file_name.size(), result);
if (kdf_headers_.has_value()) {
result.insert(result.begin(), kdf_headers_->second.begin(),
kdf_headers_->second.end());
}
encrypted_file_name_ =
kdf_headers_.has_value()
? macaron::Base64::EncodeUrlSafe(result.data(), result.size())
: utils::collection::to_hex_string(result);
if (not relative_parent_path.has_value()) {
return;
}
for (const auto &part :
utils::string::split(relative_parent_path.value(),
utils::path::directory_seperator, false)) {
utils::encryption::encrypt_data(
keys_.second, reinterpret_cast<const unsigned char *>(part.c_str()),
strnlen(part.c_str(), part.size()), result);
if (kdf_headers_.has_value()) {
result.insert(result.begin(), kdf_headers_->second.begin(),
kdf_headers_->second.end());
}
encrypted_file_path_ +=
'/' +
(kdf_headers_.has_value()
? macaron::Base64::EncodeUrlSafe(result.data(), result.size())
: utils::collection::to_hex_string(result));
}
encrypted_file_path_ += '/' + encrypted_file_name_;
}
auto encrypting_reader::create_iostream() const
-> std::shared_ptr<encrypting_reader::iostream> {
return std::make_shared<encrypting_reader_iostream>(
std::make_unique<encrypting_streambuf>(*this));
}
auto encrypting_reader::get_kdf_config_for_data() const
-> std::optional<kdf_config> {
FIFTHGRID_USES_FUNCTION_NAME();
if (not kdf_headers_.has_value()) {
return std::nullopt;
}
kdf_config cfg;
if (not kdf_config::from_header(kdf_headers_->first, cfg)) {
throw utils::error::create_exception(function_name,
{
"invalid kdf header",
});
}
return cfg;
}
auto encrypting_reader::get_kdf_config_for_path() const
-> std::optional<kdf_config> {
FIFTHGRID_USES_FUNCTION_NAME();
if (not kdf_headers_.has_value()) {
return std::nullopt;
}
kdf_config cfg;
if (not kdf_config::from_header(kdf_headers_->second, cfg)) {
throw utils::error::create_exception(function_name,
{
"invalid kdf header",
});
}
return cfg;
}
auto encrypting_reader::reader_function(char *buffer, size_t size,
size_t nitems) -> size_t {
FIFTHGRID_USES_FUNCTION_NAME();
auto read_size =
static_cast<std::uint64_t>(size) * static_cast<std::uint64_t>(nitems);
if (read_size == 0U) {
return 0U;
}
std::span<char> dest(buffer, read_size);
auto read_offset{read_offset_};
std::size_t total_read{};
auto total_size{total_size_};
if (kdf_headers_.has_value()) {
auto &hdr = kdf_headers_->first;
total_size -= hdr.size();
if (read_offset < hdr.size()) {
auto to_read{
utils::calculate_read_size(hdr.size(), read_size, read_offset),
};
read_offset_ += to_read;
std::memcpy(&dest[total_read], &hdr.at(read_offset), to_read);
if (read_size - to_read == 0) {
return to_read;
}
read_offset = 0U;
read_size -= to_read;
total_read += to_read;
} else {
read_offset -= hdr.size();
}
}
auto chunk = static_cast<std::size_t>(read_offset / encrypted_chunk_size_);
auto chunk_offset =
static_cast<std::size_t>(read_offset % encrypted_chunk_size_);
auto remain = utils::calculate_read_size(total_size, read_size, read_offset);
auto ret{false};
if (read_offset < total_size) {
try {
ret = true;
while (not get_stop_requested() && ret && (remain != 0U)) {
if (not chunk_buffers_.contains(chunk)) {
auto &chunk_buffer = chunk_buffers_[chunk];
data_buffer file_data(chunk == last_data_chunk_
? last_data_chunk_size_
: data_chunk_size_);
chunk_buffer.resize(file_data.size() + encryption_header_size);
std::size_t bytes_read{};
ret = source_file_->read(
file_data,
static_cast<std::uint64_t>(chunk) *
static_cast<std::uint64_t>(data_chunk_size_),
&bytes_read);
if (ret) {
utils::encryption::encrypt_data(iv_list_.at(chunk), keys_.first,
file_data, chunk_buffer);
}
} else if (chunk != 0U) {
chunk_buffers_.erase(chunk - 1U);
}
auto &chunk_buffer = chunk_buffers_[chunk];
auto to_read = std::min(chunk_buffer.size() - chunk_offset, remain);
std::memcpy(&dest[total_read], &chunk_buffer[chunk_offset], to_read);
total_read += to_read;
remain -= to_read;
chunk_offset = 0U;
++chunk;
read_offset_ += to_read;
}
} catch (const std::exception &e) {
utils::error::handle_exception(function_name, e);
ret = false;
} catch (...) {
utils::error::handle_exception(function_name);
ret = false;
}
}
return get_stop_requested() ? static_cast<std::size_t>(CURL_READFUNC_ABORT)
: ret ? total_read
: error_return_;
}
} // namespace fifthgrid::utils::encryption
#endif // defined(PROJECT_ENABLE_LIBSODIUM) && defined(PROJECT_ENABLE_BOOST)

View File

@@ -0,0 +1,313 @@
/*
Copyright <2018-2025> <scott.e.graves@protonmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#if defined(PROJECT_ENABLE_LIBSODIUM) && defined(PROJECT_ENABLE_BOOST)
#include "utils/encryption.hpp"
#include "utils/base64.hpp"
#include "utils/collection.hpp"
#include "utils/config.hpp"
#include "utils/encrypting_reader.hpp"
#include "utils/hash.hpp"
#include "utils/path.hpp"
namespace {
constexpr auto resize_by(fifthgrid::data_span &data, std::size_t /* size */)
-> fifthgrid::data_span & {
return data;
}
auto resize_by(fifthgrid::data_buffer &data, std::size_t size)
-> fifthgrid::data_buffer & {
data.resize(data.size() + size);
return data;
}
} // namespace
namespace fifthgrid::utils::encryption {
auto kdf_config::to_header() const -> data_buffer {
kdf_config tmp{*this};
tmp.checksum = boost::endian::native_to_big(tmp.checksum);
tmp.unique_id = boost::endian::native_to_big(tmp.unique_id);
data_buffer ret(size());
std::memcpy(ret.data(), &tmp, ret.size());
return ret;
}
auto kdf_config::generate_checksum() const -> std::uint64_t {
FIFTHGRID_USES_FUNCTION_NAME();
kdf_config tmp = *this;
tmp.checksum = 0;
auto hash = utils::hash::create_hash_blake2b_64(tmp.to_header());
std::uint64_t ret{};
std::memcpy(&ret, hash.data(), hash.size());
return ret;
}
auto kdf_config::from_header(data_cspan data, kdf_config &cfg,
bool ignore_checksum) -> bool {
if (data.size() < kdf_config::size()) {
return false;
}
std::memcpy(&cfg, data.data(), kdf_config::size());
cfg.checksum = boost::endian::big_to_native(cfg.checksum);
cfg.unique_id = boost::endian::big_to_native(cfg.unique_id);
return cfg.version == kdf_version::v1 && cfg.kdf == kdf_type::argon2id &&
cfg.memlimit >= memlimit_level::level1 &&
cfg.memlimit <= memlimit_level::level4 &&
cfg.opslimit >= opslimit_level::level1 &&
cfg.opslimit <= opslimit_level::level3 &&
(ignore_checksum || cfg.checksum == cfg.generate_checksum());
}
void kdf_config::seal() {
randombytes_buf(salt.data(), salt.size());
checksum = generate_checksum();
}
auto decrypt_file_name(std::string_view encryption_token,
std::string &file_name) -> bool {
data_buffer buffer;
if (not utils::collection::from_hex_string(file_name, buffer)) {
return false;
}
file_name.clear();
return utils::encryption::decrypt_data(encryption_token, buffer, file_name);
}
auto decrypt_file_name(std::string_view encryption_token, const kdf_config &cfg,
std::string &file_name) -> bool {
FIFTHGRID_USES_FUNCTION_NAME();
try {
auto buffer = macaron::Base64::Decode(file_name);
file_name.clear();
return utils::encryption::decrypt_data(encryption_token, cfg, buffer,
file_name);
} catch (const std::exception &e) {
utils::error::handle_exception(function_name, e);
} catch (...) {
utils::error::handle_exception(function_name);
}
return false;
}
auto decrypt_file_name(const utils::hash::hash_256_t &master_key,
std::string &file_name) -> bool {
FIFTHGRID_USES_FUNCTION_NAME();
try {
auto buffer = macaron::Base64::Decode(file_name);
utils::encryption::kdf_config path_cfg;
if (not utils::encryption::kdf_config::from_header(buffer, path_cfg)) {
return false;
}
auto path_key = path_cfg.recreate_subkey(
utils::encryption::kdf_context::path, master_key);
file_name.clear();
return utils::encryption::decrypt_data(
path_key, &buffer[utils::encryption::kdf_config::size()],
buffer.size() - utils::encryption::kdf_config::size(), file_name);
} catch (const std::exception &e) {
utils::error::handle_exception(function_name, e);
} catch (...) {
utils::error::handle_exception(function_name);
}
return false;
}
auto decrypt_file_path(std::string_view encryption_token,
std::string &file_path) -> bool {
std::vector<std::string> decrypted_parts;
for (const auto &part : std::filesystem::path(file_path)) {
auto file_name = part.string();
if (file_name == "/") {
continue;
}
if (not decrypt_file_name(encryption_token, file_name)) {
return false;
}
decrypted_parts.push_back(file_name);
}
file_path =
utils::path::create_api_path(utils::string::join(decrypted_parts, '/'));
return true;
}
auto decrypt_file_path(std::string_view encryption_token, const kdf_config &cfg,
std::string &file_path) -> bool {
std::vector<std::string> decrypted_parts;
for (const auto &part : std::filesystem::path(file_path)) {
auto file_name = part.string();
if (file_name == "/") {
continue;
}
if (not decrypt_file_name(encryption_token, cfg, file_name)) {
return false;
}
decrypted_parts.push_back(file_name);
}
file_path =
utils::path::create_api_path(utils::string::join(decrypted_parts, '/'));
return true;
}
auto decrypt_file_path(const utils::hash::hash_256_t &master_key,
std::string &file_path) -> bool {
std::vector<std::string> decrypted_parts;
for (const auto &part : std::filesystem::path(file_path)) {
auto file_name = part.string();
if (file_name == "/") {
continue;
}
if (not decrypt_file_name(master_key, file_name)) {
return false;
}
decrypted_parts.push_back(file_name);
}
file_path =
utils::path::create_api_path(utils::string::join(decrypted_parts, '/'));
return true;
}
template <typename data_t>
[[nodiscard]] auto
read_encrypted_range(http_range range, const utils::hash::hash_256_t &key,
reader_func_t reader_func, std::uint64_t total_size,
data_t &data, std::uint8_t file_header_size,
std::size_t &bytes_read) -> bool {
bytes_read = 0U;
{
if (total_size == 0U) {
return true;
}
std::uint64_t begin = range.begin;
std::uint64_t end = range.end;
if (begin >= total_size) {
return true;
}
std::uint64_t last = total_size - 1U;
if (end > last) {
end = last;
}
if (end < begin) {
return true;
}
range = http_range{
.begin = begin,
.end = end,
};
}
auto encrypted_chunk_size =
utils::encryption::encrypting_reader::get_encrypted_chunk_size();
auto data_chunk_size =
utils::encryption::encrypting_reader::get_data_chunk_size();
auto start_chunk = static_cast<std::size_t>(range.begin / data_chunk_size);
auto end_chunk = static_cast<std::size_t>(range.end / data_chunk_size);
auto remain = range.end - range.begin + 1U;
auto source_offset = static_cast<std::size_t>(range.begin % data_chunk_size);
for (std::size_t chunk = start_chunk; chunk <= end_chunk; chunk++) {
data_buffer cipher;
auto start_offset = (chunk * encrypted_chunk_size) + file_header_size;
auto end_offset = std::min(
start_offset + (total_size - (chunk * data_chunk_size)) +
encryption_header_size - 1U,
static_cast<std::uint64_t>(start_offset + encrypted_chunk_size - 1U));
if (not reader_func(cipher, start_offset, end_offset)) {
return false;
}
data_buffer source_buffer;
if (not utils::encryption::decrypt_data(key, cipher, source_buffer)) {
return false;
}
cipher.clear();
auto data_size = static_cast<std::size_t>(std::min(
remain, static_cast<std::uint64_t>(data_chunk_size - source_offset)));
std::copy(std::next(source_buffer.begin(),
static_cast<std::int64_t>(source_offset)),
std::next(source_buffer.begin(),
static_cast<std::int64_t>(source_offset + data_size)),
std::next(resize_by(data, data_size).begin(),
static_cast<std::int64_t>(bytes_read)));
remain -= data_size;
bytes_read += data_size;
source_offset = 0U;
}
return true;
}
auto read_encrypted_range(const http_range &range,
const utils::hash::hash_256_t &key, bool uses_kdf,
reader_func_t reader_func, std::uint64_t total_size,
data_buffer &data) -> bool {
std::size_t bytes_read{};
return read_encrypted_range<data_buffer>(
range, key, reader_func, total_size, data,
uses_kdf ? kdf_config::size() : 0U, bytes_read);
}
[[nodiscard]] auto read_encrypted_range(
const http_range &range, const utils::hash::hash_256_t &key, bool uses_kdf,
reader_func_t reader_func, std::uint64_t total_size, unsigned char *data,
std::size_t size, std::size_t &bytes_read) -> bool {
data_span dest_buffer(data, size);
return read_encrypted_range<data_span>(
range, key, reader_func, total_size, dest_buffer,
uses_kdf ? kdf_config::size() : 0U, bytes_read);
}
} // namespace fifthgrid::utils::encryption
#endif // defined(PROJECT_ENABLE_LIBSODIUM) && defined (PROJECT_ENABLE_BOOST)

View File

@@ -0,0 +1,50 @@
/*
Copyright <2018-2025> <scott.e.graves@protonmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#include "utils/error.hpp"
namespace fifthgrid::utils::error {
auto create_error_message(std::vector<std::string_view> items) -> std::string {
std::stringstream stream{};
for (std::size_t idx = 0U; idx < items.size(); ++idx) {
if (idx > 0) {
stream << '|';
}
stream << items.at(idx);
}
return stream.str();
}
auto create_error_message(std::string_view function_name,
std::vector<std::string_view> items) -> std::string {
items.insert(items.begin(), function_name);
return create_error_message(items);
}
auto create_exception(std::string_view function_name,
std::vector<std::string_view> items)
-> std::runtime_error {
return std::runtime_error(create_error_message(function_name, items));
}
} // namespace fifthgrid::utils::error

View File

@@ -0,0 +1,323 @@
/*
Copyright <2018-2025> <scott.e.graves@protonmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#include "utils/error.hpp"
#include "utils/config.hpp"
#include "utils/string.hpp"
namespace fifthgrid::utils::error {
std::atomic<const i_exception_handler *> exception_handler{
&default_exception_handler,
};
#if defined(PROJECT_ENABLE_V2_ERRORS)
void iostream_exception_handler::handle_debug(std::string_view function_name,
std::string_view msg) const {
std::cout << create_error_message({
"debug",
function_name,
msg,
})
<< std::endl;
}
#endif // defined(PROJECT_ENABLE_V2_ERRORS)
void iostream_exception_handler::handle_error(std::string_view function_name,
std::string_view msg) const {
std::cerr << create_error_message({
"error",
function_name,
msg,
})
<< std::endl;
}
void iostream_exception_handler::handle_exception(
std::string_view function_name) const {
std::cerr << create_error_message({
"error",
function_name,
"exception",
"unknown",
})
<< std::endl;
}
void iostream_exception_handler::handle_exception(
std::string_view function_name, const std::exception &ex) const {
std::cerr << create_error_message({
"error",
function_name,
"exception",
(ex.what() == nullptr ? "unknown" : ex.what()),
})
<< std::endl;
}
#if defined(PROJECT_ENABLE_V2_ERRORS)
void iostream_exception_handler::handle_info(std::string_view function_name,
std::string_view msg) const {
std::cout << create_error_message({
"info",
function_name,
msg,
})
<< std::endl;
}
void iostream_exception_handler::handle_trace(std::string_view function_name,
std::string_view msg) const {
std::cout << create_error_message({
"trace",
function_name,
msg,
})
<< std::endl;
}
void iostream_exception_handler::handle_warn(std::string_view function_name,
std::string_view msg) const {
std::cout << create_error_message({
"warn",
function_name,
msg,
})
<< std::endl;
}
#endif // defined(PROJECT_ENABLE_V2_ERRORS)
#if defined(PROJECT_ENABLE_SPDLOG) && defined(PROJECT_ENABLE_V2_ERRORS)
void spdlog_exception_handler::handle_debug(std::string_view function_name,
std::string_view msg) const {
auto console = spdlog::get("console");
if (console) {
console->debug(utils::error::create_error_message(function_name, {msg}));
} else {
fallback.handle_debug(function_name, msg);
}
auto file = spdlog::get("file");
if (not file) {
return;
}
file->debug(utils::error::create_error_message(function_name, {msg}));
}
void spdlog_exception_handler::handle_error(std::string_view function_name,
std::string_view msg) const {
auto console = spdlog::get("console");
if (console) {
console->error(utils::error::create_error_message(function_name, {msg}));
} else {
fallback.handle_error(function_name, msg);
}
auto file = spdlog::get("file");
if (not file) {
return;
}
file->error(utils::error::create_error_message(function_name, {msg}));
}
void spdlog_exception_handler::handle_exception(
std::string_view function_name) const {
auto console = spdlog::get("console");
if (console) {
console->error(utils::error::create_error_message(function_name,
{
"exception",
"unknown exception",
}));
} else {
fallback.handle_exception(function_name);
}
auto file = spdlog::get("file");
if (not file) {
return;
}
file->error(
utils::error::create_error_message(function_name, {
"exception",
"unknown exception",
}));
}
void spdlog_exception_handler::handle_exception(
std::string_view function_name, const std::exception &ex) const {
auto console = spdlog::get("console");
if (console) {
console->error(utils::error::create_error_message(
function_name, {
"exception",
(ex.what() == nullptr ? "unknown" : ex.what()),
}));
} else {
fallback.handle_exception(function_name, ex);
}
auto file = spdlog::get("file");
if (not file) {
return;
}
file->error(utils::error::create_error_message(
function_name, {
"exception",
(ex.what() == nullptr ? "unknown" : ex.what()),
}));
}
void spdlog_exception_handler::handle_info(std::string_view function_name,
std::string_view msg) const {
auto console = spdlog::get("console");
if (console) {
console->info(utils::error::create_error_message(function_name, {msg}));
} else {
fallback.handle_info(function_name, msg);
}
auto file = spdlog::get("file");
if (not file) {
return;
}
file->info(utils::error::create_error_message(function_name, {msg}));
}
void spdlog_exception_handler::handle_trace(std::string_view function_name,
std::string_view msg) const {
auto console = spdlog::get("console");
if (console) {
console->trace(utils::error::create_error_message(function_name, {msg}));
} else {
fallback.handle_trace(function_name, msg);
}
auto file = spdlog::get("file");
if (not file) {
return;
}
file->trace(utils::error::create_error_message(function_name, {msg}));
}
void spdlog_exception_handler::handle_warn(std::string_view function_name,
std::string_view msg) const {
auto console = spdlog::get("console");
if (console) {
console->warn(utils::error::create_error_message(function_name, {msg}));
} else {
fallback.handle_warn(function_name, msg);
}
auto file = spdlog::get("file");
if (not file) {
return;
}
file->warn(utils::error::create_error_message(function_name, {msg}));
}
#endif // defined(PROJECT_ENABLE_SPDLOG) && defined(PROJECT_ENABLE_V2_ERRORS)
#if defined(PROJECT_ENABLE_V2_ERRORS)
void handle_debug(std::string_view function_name, std::string_view msg) {
const i_exception_handler *handler{exception_handler};
if (handler != nullptr) {
handler->handle_debug(function_name, msg);
return;
}
default_exception_handler.handle_debug(function_name, msg);
}
#endif // defined(PROJECT_ENABLE_V2_ERRORS)
void handle_error(std::string_view function_name, std::string_view msg) {
const i_exception_handler *handler{exception_handler};
if (handler != nullptr) {
handler->handle_error(function_name, msg);
return;
}
default_exception_handler.handle_error(function_name, msg);
}
void handle_exception(std::string_view function_name) {
const i_exception_handler *handler{exception_handler};
if (handler != nullptr) {
handler->handle_exception(function_name);
return;
}
default_exception_handler.handle_exception(function_name);
}
void handle_exception(std::string_view function_name,
const std::exception &ex) {
const i_exception_handler *handler{exception_handler};
if (handler != nullptr) {
handler->handle_exception(function_name, ex);
return;
}
default_exception_handler.handle_exception(function_name, ex);
}
#if defined(PROJECT_ENABLE_V2_ERRORS)
void handle_info(std::string_view function_name, std::string_view msg) {
const i_exception_handler *handler{exception_handler};
if (handler != nullptr) {
handler->handle_info(function_name, msg);
return;
}
default_exception_handler.handle_info(function_name, msg);
}
void handle_trace(std::string_view function_name, std::string_view msg) {
const i_exception_handler *handler{exception_handler};
if (handler != nullptr) {
handler->handle_trace(function_name, msg);
return;
}
default_exception_handler.handle_trace(function_name, msg);
}
void handle_warn(std::string_view function_name, std::string_view msg) {
const i_exception_handler *handler{exception_handler};
if (handler != nullptr) {
handler->handle_warn(function_name, msg);
return;
}
default_exception_handler.handle_warn(function_name, msg);
}
#endif // defined(PROJECT_ENABLE_V2_ERRORS)
void set_exception_handler(const i_exception_handler *handler) {
exception_handler = handler;
}
} // namespace fifthgrid::utils::error

744
support/src/utils/file.cpp Normal file
View File

@@ -0,0 +1,744 @@
/*
Copyright <2018-2025> <scott.e.graves@protonmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#include "utils/file.hpp"
#include "utils/common.hpp"
#include "utils/encryption.hpp"
#include "utils/error.hpp"
#include "utils/path.hpp"
#include "utils/string.hpp"
#include "utils/time.hpp"
#include "utils/unix.hpp"
#include "utils/windows.hpp"
namespace fifthgrid::utils::file {
auto change_to_process_directory() -> bool {
FIFTHGRID_USES_FUNCTION_NAME();
try {
#if defined(_WIN32)
std::string file_name;
file_name.resize(fifthgrid::max_path_length + 1U);
::GetModuleFileNameA(nullptr, file_name.data(),
static_cast<DWORD>(file_name.size() - 1U));
auto path = utils::path::get_parent_path(file_name.c_str());
auto res = ::SetCurrentDirectoryA(path.c_str()) != 0;
if (not res) {
throw utils::error::create_exception(
function_name, {
"failed to set current directory",
std::to_string(utils::get_last_error_code()),
path,
});
}
#else // !defined(_WIN32)
std::string path;
path.resize(fifthgrid::max_path_length + 1);
#if defined(__APPLE__)
auto res = proc_pidpath(getpid(), reinterpret_cast<void *>(path.data()),
static_cast<uint32_t>(path.size()));
path = path.c_str();
#else // !defined(__APPLE__)
auto res = readlink("/proc/self/exe", path.data(), path.size());
if (res == -1) {
throw utils::error::create_exception(
function_name, {
"failed to readlink",
std::to_string(utils::get_last_error_code()),
path,
});
}
#endif // defined(__APPLE__)
path = utils::path::get_parent_path(path);
res = chdir(path.c_str());
if (res != 0) {
throw utils::error::create_exception(
function_name, {
"failed to chdir",
std::to_string(utils::get_last_error_code()),
path,
});
}
#endif // defined(_WIN32)
return true;
} catch (const std::exception &e) {
utils::error::handle_exception(function_name, e);
} catch (...) {
utils::error::handle_exception(function_name);
}
return false;
}
auto create_temp_name(std::string_view file_part) -> std::string {
std::array<std::uint8_t, 8U> data{
utils::generate_random_between<std::uint8_t>(0U, 9U),
utils::generate_random_between<std::uint8_t>(0U, 9U),
utils::generate_random_between<std::uint8_t>(0U, 9U),
utils::generate_random_between<std::uint8_t>(0U, 9U),
utils::generate_random_between<std::uint8_t>(0U, 9U),
utils::generate_random_between<std::uint8_t>(0U, 9U),
utils::generate_random_between<std::uint8_t>(0U, 9U),
utils::generate_random_between<std::uint8_t>(0U, 9U),
};
return std::accumulate(data.begin(), data.end(), std::string{file_part} + '_',
[](auto &&name, auto &&val) -> auto {
return name + std::to_string(val);
});
}
auto create_temp_name(std::wstring_view file_part) -> std::wstring {
return utils::string::from_utf8(
create_temp_name(utils::string::to_utf8(file_part)));
}
auto get_free_drive_space(std::string_view path)
-> std::optional<std::uint64_t> {
FIFTHGRID_USES_FUNCTION_NAME();
try {
#if defined(_WIN32)
ULARGE_INTEGER li{};
if (not ::GetDiskFreeSpaceEx(std::string{path}.c_str(), &li, nullptr,
nullptr)) {
throw utils::error::create_exception(
function_name, {
"failed to get free disk space",
std::to_string(utils::get_last_error_code()),
path,
});
}
return li.QuadPart;
#endif // defined(_WIN32)
#if defined(__linux__)
struct statfs64 u_stat{};
if (statfs64(std::string{path}.c_str(), &u_stat) != 0) {
throw utils::error::create_exception(
function_name, {
"failed to get free disk space",
std::to_string(utils::get_last_error_code()),
path,
});
}
return u_stat.f_bfree * static_cast<std::uint64_t>(u_stat.f_bsize);
#endif // defined(__linux__)
#if defined(__APPLE__)
struct statvfs u_stat{};
if (statvfs(std::string{path}.c_str(), &u_stat) != 0) {
throw utils::error::create_exception(
function_name, {
"failed to get free disk space",
std::to_string(utils::get_last_error_code()),
path,
});
}
return u_stat.f_bfree * static_cast<std::uint64_t>(u_stat.f_frsize);
#endif // defined(__APPLE__)
} catch (const std::exception &e) {
utils::error::handle_exception(function_name, e);
} catch (...) {
utils::error::handle_exception(function_name);
}
return std::nullopt;
}
auto get_free_drive_space(std::wstring_view path)
-> std::optional<std::uint64_t> {
return get_free_drive_space(utils::string::to_utf8(path));
}
auto get_time(std::string_view path, time_type type)
-> std::optional<std::uint64_t> {
auto times = get_times(path);
if (times.has_value()) {
return times->get(type);
}
return std::nullopt;
}
auto get_time(std::wstring_view path, time_type type)
-> std::optional<std::uint64_t> {
return get_time(utils::string::to_utf8(path), type);
}
auto get_times(std::string_view path) -> std::optional<file_times> {
FIFTHGRID_USES_FUNCTION_NAME();
try {
file_times ret{};
#if defined(_WIN32)
auto file_handle = ::CreateFileA(
std::string{path}.c_str(), GENERIC_READ,
FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr,
OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr);
if (file_handle != INVALID_HANDLE_VALUE) {
std::array<FILETIME, 3U> times{};
auto res = ::GetFileTime(file_handle, &times.at(0U), &times.at(1U),
&times.at(2U));
::CloseHandle(file_handle);
if (res) {
ret.accessed =
utils::time::windows_file_time_to_unix_time(times.at(1U));
ret.changed = utils::time::windows_file_time_to_unix_time(times.at(2U));
ret.created = utils::time::windows_file_time_to_unix_time(times.at(0U));
ret.modified =
utils::time::windows_file_time_to_unix_time(times.at(2U));
ret.written = utils::time::windows_file_time_to_unix_time(times.at(2U));
return ret;
}
}
struct _stat64 u_stat{};
if (_stat64(std::string{path}.c_str(), &u_stat) != 0) {
throw utils::error::create_exception(
function_name, {
"failed to get file times",
std::to_string(utils::get_last_error_code()),
path,
});
}
ret.accessed = utils::time::windows_time_t_to_unix_time(u_stat.st_atime);
ret.changed = utils::time::windows_time_t_to_unix_time(u_stat.st_ctime);
ret.created = utils::time::windows_time_t_to_unix_time(u_stat.st_ctime);
ret.modified = utils::time::windows_time_t_to_unix_time(u_stat.st_mtime);
ret.written = utils::time::windows_time_t_to_unix_time(u_stat.st_mtime);
#else // !defined(_WIN32)
struct stat64 u_stat{};
if (stat64(std::string{path}.c_str(), &u_stat) != 0) {
throw utils::error::create_exception(
function_name, {
"failed to get file times",
std::to_string(utils::get_last_error_code()),
path,
});
}
#if defined(__APPLE__)
ret.accessed = static_cast<std::uint64_t>(u_stat.st_atimespec.tv_nsec) +
static_cast<std::uint64_t>(u_stat.st_atimespec.tv_sec) *
utils::time::NANOS_PER_SECOND;
ret.created = static_cast<std::uint64_t>(u_stat.st_birthtimespec.tv_nsec) +
static_cast<std::uint64_t>(u_stat.st_birthtimespec.tv_sec) *
utils::time::NANOS_PER_SECOND;
ret.changed = static_cast<std::uint64_t>(u_stat.st_ctimespec.tv_nsec) +
static_cast<std::uint64_t>(u_stat.st_ctimespec.tv_sec) *
utils::time::NANOS_PER_SECOND;
ret.modified = static_cast<std::uint64_t>(u_stat.st_mtimespec.tv_nsec) +
static_cast<std::uint64_t>(u_stat.st_mtimespec.tv_sec) *
utils::time::NANOS_PER_SECOND;
ret.written = static_cast<std::uint64_t>(u_stat.st_mtimespec.tv_nsec) +
static_cast<std::uint64_t>(u_stat.st_mtimespec.tv_sec) *
utils::time::NANOS_PER_SECOND;
#else // !defined(__APPLE__)
ret.accessed = static_cast<std::uint64_t>(u_stat.st_atim.tv_nsec) +
static_cast<std::uint64_t>(u_stat.st_atim.tv_sec) *
utils::time::NANOS_PER_SECOND;
ret.changed = static_cast<std::uint64_t>(u_stat.st_ctim.tv_nsec) +
static_cast<std::uint64_t>(u_stat.st_ctim.tv_sec) *
utils::time::NANOS_PER_SECOND;
ret.created = static_cast<std::uint64_t>(u_stat.st_ctim.tv_nsec) +
static_cast<std::uint64_t>(u_stat.st_ctim.tv_sec) *
utils::time::NANOS_PER_SECOND;
ret.modified = static_cast<std::uint64_t>(u_stat.st_mtim.tv_nsec) +
static_cast<std::uint64_t>(u_stat.st_mtim.tv_sec) *
utils::time::NANOS_PER_SECOND;
ret.written = static_cast<std::uint64_t>(u_stat.st_mtim.tv_nsec) +
static_cast<std::uint64_t>(u_stat.st_mtim.tv_sec) *
utils::time::NANOS_PER_SECOND;
#endif // defined(__APPLE__)
#endif // defined(_WIN32)
return ret;
} catch (const std::exception &e) {
utils::error::handle_exception(function_name, e);
} catch (...) {
utils::error::handle_exception(function_name);
}
return std::nullopt;
}
auto get_times(std::wstring_view path) -> std::optional<file_times> {
return get_times(utils::string::to_utf8(path));
}
auto get_total_drive_space(std::string_view path)
-> std::optional<std::uint64_t> {
FIFTHGRID_USES_FUNCTION_NAME();
try {
#if defined(_WIN32)
ULARGE_INTEGER li{};
if (not ::GetDiskFreeSpaceEx(std::string{path}.c_str(), nullptr, &li,
nullptr)) {
throw utils::error::create_exception(
function_name, {
"failed to get total disk space",
std::to_string(utils::get_last_error_code()),
path,
});
}
return li.QuadPart;
#endif // defined(_WIN32)
#if defined(__linux__)
struct statfs64 u_stat{};
if (statfs64(std::string{path}.c_str(), &u_stat) != 0) {
throw utils::error::create_exception(
function_name, {
"failed to get total disk space",
std::to_string(utils::get_last_error_code()),
path,
});
}
return u_stat.f_blocks * static_cast<std::uint64_t>(u_stat.f_bsize);
#endif // defined(__linux__)
#if defined(__APPLE__)
struct statvfs u_stat{};
if (statvfs(std::string{path}.c_str(), &u_stat) != 0) {
throw utils::error::create_exception(
function_name, {
"failed to get total disk space",
std::to_string(utils::get_last_error_code()),
path,
});
}
return u_stat.f_blocks * static_cast<std::uint64_t>(u_stat.f_frsize);
#endif // defined(__APPLE__)
} catch (const std::exception &e) {
utils::error::handle_exception(function_name, e);
} catch (...) {
utils::error::handle_exception(function_name);
}
return std::nullopt;
}
auto get_total_drive_space(std::wstring_view path)
-> std::optional<std::uint64_t> {
return get_total_drive_space(utils::string::to_utf8(path));
}
auto i_fs_item::get_time(time_type type) const -> std::optional<std::uint64_t> {
return utils::file::get_time(get_path(), type);
}
auto i_file::read_all(data_buffer &data, std::uint64_t offset,
std::size_t *total_read) -> bool {
data_buffer buffer;
buffer.resize(get_read_buffer_size());
std::size_t current_read{};
while (read(reinterpret_cast<unsigned char *>(buffer.data()),
buffer.size() * sizeof(data_buffer::value_type), offset,
&current_read)) {
if (total_read != nullptr) {
*total_read += current_read;
}
if (current_read != 0U) {
offset += current_read;
data.insert(
data.end(), buffer.begin(),
std::next(buffer.begin(),
static_cast<std::int64_t>(
current_read / sizeof(data_buffer::value_type))));
continue;
}
return true;
}
return false;
}
#if defined(PROJECT_ENABLE_JSON)
#if defined(PROJECT_ENABLE_LIBSODIUM) && defined(PROJECT_ENABLE_BOOST)
auto read_json_file(std::string_view path, nlohmann::json &data,
std::optional<std::string_view> password) -> bool {
#else // !defined(PROJECT_ENABLE_LIBSODIUM) && defined(PROJECT_ENABLE_BOOST)
auto read_json_file(std::string_view path, nlohmann::json &data) -> bool {
#endif // defined(PROJECT_ENABLE_LIBSODIUM) && defined(PROJECT_ENABLE_BOOST)
FIFTHGRID_USES_FUNCTION_NAME();
try {
auto abs_path = utils::path::absolute(path);
auto file = file::open_file(abs_path);
if (not *file) {
return false;
}
try {
data_buffer buffer{};
if (not file->read_all(buffer, 0U)) {
return false;
}
#if defined(PROJECT_ENABLE_LIBSODIUM) && defined(PROJECT_ENABLE_BOOST)
if (password.has_value()) {
data_buffer decrypted_data{};
if (not utils::encryption::decrypt_data(*password, buffer,
decrypted_data)) {
return false;
}
buffer = decrypted_data;
}
#endif // defined(PROJECT_ENABLE_LIBSODIUM) && defined(PROJECT_ENABLE_BOOST)
std::string json_str(buffer.begin(), buffer.end());
if (not json_str.empty()) {
data = nlohmann::json::parse(json_str);
}
} catch (const std::exception &e) {
utils::error::handle_exception(function_name, e);
return false;
} catch (...) {
utils::error::handle_exception(function_name);
return false;
}
return true;
} catch (const std::exception &e) {
utils::error::handle_exception(function_name, e);
} catch (...) {
utils::error::handle_exception(function_name);
}
return false;
}
#if defined(PROJECT_ENABLE_LIBSODIUM) && defined(PROJECT_ENABLE_BOOST)
auto write_json_file(std::string_view path, const nlohmann::json &data,
std::optional<std::string_view> password) -> bool {
#else // !defined(PROJECT_ENABLE_LIBSODIUM) && defined(PROJECT_ENABLE_BOOST)
auto write_json_file(std::string_view path, const nlohmann::json &data)
-> bool {
#endif // defined(PROJECT_ENABLE_LIBSODIUM) && defined(PROJECT_ENABLE_BOOST)
FIFTHGRID_USES_FUNCTION_NAME();
try {
auto file = file::open_or_create_file(path);
if (not file->truncate()) {
throw utils::error::create_exception(
function_name, {
"failed to truncate file",
std::to_string(utils::get_last_error_code()),
path,
});
}
#if defined(PROJECT_ENABLE_LIBSODIUM) && defined(PROJECT_ENABLE_BOOST)
if (password.has_value()) {
const auto str_data = data.dump(2);
data_buffer encrypted_data{};
utils::encryption::encrypt_data(
*password, reinterpret_cast<const unsigned char *>(str_data.c_str()),
str_data.size(), encrypted_data);
return file->write(encrypted_data, 0U);
}
#endif // defined(PROJECT_ENABLE_LIBSODIUM) && defined(PROJECT_ENABLE_BOOST)
auto json_str = data.dump(2);
return file->write(
reinterpret_cast<const unsigned char *>(json_str.c_str()),
json_str.size(), 0U);
} catch (const std::exception &e) {
utils::error::handle_exception(function_name, e);
} catch (...) {
utils::error::handle_exception(function_name);
}
return false;
}
#if defined(PROJECT_ENABLE_LIBSODIUM) && defined(PROJECT_ENABLE_BOOST)
auto read_json_file(std::wstring_view path, nlohmann::json &data,
std::optional<std::wstring_view> password) -> bool {
if (password.has_value()) {
auto password_a = utils::string::to_utf8(*password);
return read_json_file(utils::string::to_utf8(path), data, password_a);
}
return read_json_file(utils::string::to_utf8(path), data, std::nullopt);
}
auto write_json_file(std::wstring_view path, const nlohmann::json &data,
std::optional<std::wstring_view> password) -> bool {
if (password.has_value()) {
auto password_a = utils::string::to_utf8(*password);
return write_json_file(utils::string::to_utf8(path), data, password_a);
}
return write_json_file(utils::string::to_utf8(path), data, std::nullopt);
}
#else // !defined(PROJECT_ENABLE_LIBSODIUM) && defined(PROJECT_ENABLE_BOOST)
auto read_json_file(std::wstring_view path, nlohmann::json &data) -> bool {
return read_json_file(utils::string::to_utf8(path), data);
}
auto write_json_file(std::wstring_view path, const nlohmann::json &data)
-> bool {
return write_json_file(utils::string::to_utf8(path), data);
}
#endif // defined(PROJECT_ENABLE_LIBSODIUM) && defined(PROJECT_ENABLE_BOOST)
#endif // defined(PROJECT_ENABLE_JSON)
#if defined(PROJECT_ENABLE_LIBDSM)
static constexpr auto validate_smb_path = [](std::string_view path) -> bool {
return (not utils::string::begins_with(path, "///") &&
utils::string::begins_with(path, "//") &&
// not utils::string::contains(path, " ") &&
std::count(path.begin(), path.end(), '/') >= 3U);
};
auto smb_create_smb_path(std::string_view smb_path, std::string_view rel_path)
-> std::string {
FIFTHGRID_USES_FUNCTION_NAME();
if (not validate_smb_path(smb_path)) {
throw utils::error::create_exception(function_name, {
"invalid smb path",
smb_path,
});
}
std::string path{rel_path};
utils::path::format_path(path, "/", "\\");
utils::string::left_trim(path, '/');
auto old_parts =
fifthgrid::utils::string::split(smb_path.substr(2U), '/', false);
auto new_parts = fifthgrid::utils::string::split(path, '/', false);
old_parts.insert(old_parts.end(), new_parts.begin(), new_parts.end());
path = utils::string::join(old_parts, '/');
path = "//" + utils::path::format_path(path, "/", "\\");
if (not validate_smb_path(path)) {
throw utils::error::create_exception(function_name, {
"invalid smb path",
path,
});
}
return path;
}
auto smb_create_and_validate_relative_path(std::string_view smb_path,
std::string_view path)
-> std::string {
FIFTHGRID_USES_FUNCTION_NAME();
if (not validate_smb_path(smb_path)) {
throw utils::error::create_exception(function_name, {
"invalid smb path",
smb_path,
});
}
std::string dir_path;
if (utils::string::begins_with(path, "//")) {
if (not utils::file::smb_parent_is_same(smb_path, path)) {
throw utils::error::create_exception(function_name,
{
"failed to validate path",
"parent paths are not the same",
smb_path,
path,
});
}
return utils::file::smb_create_relative_path(path);
}
return utils::file::smb_create_relative_path(std::string{smb_path} + '/' +
std::string{path});
}
auto smb_create_relative_path(std::string_view smb_path) -> std::string {
FIFTHGRID_USES_FUNCTION_NAME();
if (not validate_smb_path(smb_path)) {
throw utils::error::create_exception(function_name, {
"invalid smb path",
smb_path,
});
}
std::string path{smb_path};
utils::path::format_path(path, "\\", "/");
utils::string::left_trim(path, '\\');
auto parts = fifthgrid::utils::string::split(path, '\\', false);
parts.erase(parts.begin(), std::next(parts.begin(), 2U));
return "\\" + utils::string::join(parts, '\\');
}
auto smb_create_search_path(std::string_view smb_path) -> std::string {
FIFTHGRID_USES_FUNCTION_NAME();
if (not validate_smb_path(smb_path)) {
throw utils::error::create_exception(function_name, {
"invalid smb path",
smb_path,
});
}
std::string path{smb_path};
utils::string::left_trim(path, '/');
auto parts = fifthgrid::utils::string::split(path, '/', false);
parts.erase(parts.begin(), std::next(parts.begin(), 2U));
auto search_path = fifthgrid::utils::string::join(parts, '\\');
return search_path.empty() ? "\\*" : "\\" + search_path + "\\*";
}
auto smb_get_parent_path(std::string_view smb_path) -> std::string {
FIFTHGRID_USES_FUNCTION_NAME();
if (not validate_smb_path(smb_path)) {
throw utils::error::create_exception(function_name, {
"invalid smb path",
smb_path,
});
}
auto parts = fifthgrid::utils::string::split(smb_path.substr(2U), '/', false);
if (parts.size() > 2U) {
parts.erase(std::prev(parts.end()), parts.end());
}
auto parent_smb_path = "//" + utils::string::join(parts, '/');
if (not validate_smb_path(parent_smb_path)) {
throw utils::error::create_exception(function_name,
{
"invalid parent smb path",
parent_smb_path,
});
}
return parent_smb_path;
}
auto smb_get_root_path(std::string_view smb_path) -> std::string {
FIFTHGRID_USES_FUNCTION_NAME();
if (not validate_smb_path(smb_path)) {
throw utils::error::create_exception(function_name, {
"invalid smb path",
smb_path,
});
}
auto parts = fifthgrid::utils::string::split(smb_path.substr(2U), '/', false);
if (parts.size() > 2U) {
parts.erase(std::next(parts.begin(), 2U), parts.end());
}
return "//" + utils::string::join(parts, '/');
}
auto smb_get_unc_path(std::string_view smb_path) -> std::string {
FIFTHGRID_USES_FUNCTION_NAME();
if (not validate_smb_path(smb_path)) {
throw utils::error::create_exception(function_name, {
"invalid smb path",
smb_path,
});
}
std::string unc_path{smb_path};
utils::path::format_path(unc_path, "\\", "/");
return '\\' + unc_path;
}
auto smb_get_uri_path(std::string_view smb_path) -> std::string {
FIFTHGRID_USES_FUNCTION_NAME();
if (not validate_smb_path(smb_path)) {
throw utils::error::create_exception(function_name, {
"invalid smb path",
smb_path,
});
}
return "smb:" + std::string{smb_path};
}
auto smb_get_uri_path(std::string_view smb_path, std::string_view user,
std::string_view password) -> std::string {
FIFTHGRID_USES_FUNCTION_NAME();
if (not validate_smb_path(smb_path)) {
throw utils::error::create_exception(function_name, {
"invalid smb path",
smb_path,
});
}
return "smb://" + std::string{user} + ':' + std::string{password} + '@' +
std::string{smb_path.substr(2U)};
}
auto smb_parent_is_same(std::string_view smb_path1, std::string_view smb_path2)
-> bool {
if (not(validate_smb_path(smb_path1) && validate_smb_path(smb_path2))) {
return false;
}
auto parts1 = utils::string::split(smb_path1.substr(2U), "/", false);
auto parts2 = utils::string::split(smb_path2.substr(2U), "/", false);
if (parts1.size() < 2U || parts2.size() < 2U) {
return false;
}
if (parts2.at(1U).empty() || parts1.at(1U).empty()) {
return false;
}
return std::equal(parts1.begin(), std::next(parts1.begin(), 2U),
parts2.begin());
}
#endif // defined(PROJECT_ENABLE_LIBDSM)
} // namespace fifthgrid::utils::file

View File

@@ -0,0 +1,597 @@
/*
Copyright <2018-2025> <scott.e.graves@protonmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#include "utils/file_directory.hpp"
#include "utils/com_init_wrapper.hpp"
#include "utils/common.hpp"
#include "utils/error.hpp"
#include "utils/string.hpp"
#include "utils/unix.hpp"
#include "utils/windows.hpp"
namespace {
auto traverse_directory(
std::string_view path,
std::function<bool(fifthgrid::utils::file::directory)> directory_action,
std::function<bool(fifthgrid::utils::file::file)> file_action,
fifthgrid::stop_type *stop_requested) -> bool {
FIFTHGRID_USES_FUNCTION_NAME();
auto res{true};
const auto is_stop_requested = [&stop_requested]() -> bool {
return (stop_requested != nullptr && *stop_requested);
};
#if defined(_WIN32)
WIN32_FIND_DATAA fd{};
auto search = fifthgrid::utils::path::combine(path, {"*.*"});
auto find = ::FindFirstFileA(search.c_str(), &fd);
if (find == INVALID_HANDLE_VALUE) {
throw fifthgrid::utils::error::create_exception(
function_name,
{
"failed to open directory",
std::to_string(fifthgrid::utils::get_last_error_code()),
path,
});
}
do {
if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
if ((std::string_view(fd.cFileName) != ".") &&
(std::string_view(fd.cFileName) != "..")) {
res = directory_action(fifthgrid::utils::file::directory{
fifthgrid::utils::path::combine(path, {fd.cFileName}),
stop_requested,
});
}
} else {
res = file_action(fifthgrid::utils::file::file(
fifthgrid::utils::path::combine(path, {fd.cFileName})));
}
} while (res && (::FindNextFileA(find, &fd) != 0) && !is_stop_requested());
::FindClose(find);
#else // !defined(_WIN32)
auto *root = opendir(std::string{path}.c_str());
if (root == nullptr) {
throw fifthgrid::utils::error::create_exception(
function_name,
{
"failed to open directory",
std::to_string(fifthgrid::utils::get_last_error_code()),
path,
});
}
struct dirent *entry{nullptr};
while (res && (entry = ::readdir(root)) && !is_stop_requested()) {
if (entry->d_type == DT_DIR) {
if ((std::string_view(entry->d_name) == ".") ||
(std::string_view(entry->d_name) == "..")) {
continue;
}
res = directory_action(fifthgrid::utils::file::directory(
fifthgrid::utils::path::combine(path, {entry->d_name})));
} else {
res = file_action(fifthgrid::utils::file::file(
fifthgrid::utils::path::combine(path, {entry->d_name})));
}
}
closedir(root);
#endif // defined(_WIN32)
return res;
}
} // namespace
namespace fifthgrid::utils::file {
auto directory::copy_to(std::string_view new_path, bool overwrite) const
-> bool {
FIFTHGRID_USES_FUNCTION_NAME();
try {
if (not exists()) {
throw utils::error::create_exception(function_name,
{
"failed to copy directory",
"source does not exist",
path_,
std::string{new_path},
});
}
auto src_root = utils::path::finalize(path_);
auto dst_root = utils::path::finalize(new_path);
if (directory{dst_root}.exists()) {
auto src_base = utils::path::strip_to_file_name(src_root);
dst_root = utils::path::combine(dst_root, {src_base});
} else {
auto dst_parent = utils::path::get_parent_path(dst_root);
if (not dst_parent.empty() && not directory{dst_parent}.exists()) {
auto parent_parent = utils::path::get_parent_path(dst_parent);
auto last_piece = utils::path::strip_to_file_name(dst_parent);
[[maybe_unused]] auto sub_dir =
directory{parent_parent}.create_directory(last_piece);
}
if (not directory{dst_root}.exists()) {
auto root_parent = utils::path::get_parent_path(dst_root);
auto root_name = utils::path::strip_to_file_name(dst_root);
[[maybe_unused]] auto sub_dir =
directory{root_parent}.create_directory(root_name);
}
}
auto success = traverse_directory(
src_root,
[this, &dst_root, &src_root](auto &&dir_item) -> bool {
auto child_src = dir_item.get_path();
auto rel_path = utils::path::get_relative_path(child_src, src_root);
auto child_dst = utils::path::combine(dst_root, {rel_path});
auto child_parent = utils::path::get_parent_path(child_dst);
auto child_name = utils::path::strip_to_file_name(child_dst);
[[maybe_unused]] auto sub_dir =
directory{child_parent}.create_directory(child_name);
return not is_stop_requested();
},
[this, &dst_root, overwrite, &src_root](auto &&file_item) -> bool {
auto child_src = file_item.get_path();
auto rel_path = utils::path::get_relative_path(child_src, src_root);
auto child_dst = utils::path::combine(dst_root, {rel_path});
if (not file{child_src}.copy_to(child_dst, overwrite)) {
return false;
}
return not is_stop_requested();
},
stop_requested_);
return success && not is_stop_requested();
} catch (const std::exception &ex) {
utils::error::handle_exception(function_name, ex);
} catch (...) {
utils::error::handle_exception(function_name);
}
return false;
}
auto directory::count(bool recursive) const -> std::uint64_t {
FIFTHGRID_USES_FUNCTION_NAME();
try {
std::uint64_t ret{0U};
traverse_directory(
path_,
[&ret, &recursive](auto &&dir_item) -> bool {
if (recursive) {
ret += dir_item.count(true);
}
++ret;
return true;
},
[&ret](auto && /* file_item */) -> bool {
++ret;
return true;
},
stop_requested_);
return ret;
} catch (const std::exception &e) {
utils::error::handle_exception(function_name, e);
} catch (...) {
utils::error::handle_exception(function_name);
}
return 0U;
}
auto directory::create_directory(std::string_view path) const
-> fs_directory_t {
FIFTHGRID_USES_FUNCTION_NAME();
try {
auto abs_path = utils::path::combine(path_, {path});
if (directory{abs_path, stop_requested_}.exists()) {
return std::make_unique<directory>(abs_path);
}
#if defined(_WIN32)
[[maybe_unused]] thread_local const utils::com_init_wrapper wrapper;
auto res = ::SHCreateDirectory(nullptr,
utils::string::from_utf8(abs_path).c_str());
if (res != ERROR_SUCCESS) {
throw utils::error::create_exception(function_name,
{
"failed to create directory",
std::to_string(res),
abs_path,
});
}
#else // !defined(_WIN32)
auto ret{true};
auto paths =
utils::string::split(abs_path, utils::path::directory_seperator, false);
std::string current_path;
for (std::size_t idx = 0U;
!is_stop_requested() && ret && (idx < paths.size()); ++idx) {
if (paths.at(idx).empty()) {
current_path = utils::path::directory_seperator;
continue;
}
current_path = utils::path::combine(current_path, {paths.at(idx)});
auto status = mkdir(current_path.c_str(), S_IRWXU);
ret = ((status == 0) || (errno == EEXIST));
}
#endif // defined(_WIN32)
return std::make_unique<directory>(abs_path);
} catch (const std::exception &e) {
utils::error::handle_exception(function_name, e);
} catch (...) {
utils::error::handle_exception(function_name);
}
return nullptr;
}
auto directory::exists() const -> bool {
#if defined(_WIN32)
return ::PathIsDirectoryA(path_.c_str()) != 0;
#else // !defined(_WIN32)
struct stat64 u_stat{};
return (stat64(path_.c_str(), &u_stat) == 0 && S_ISDIR(u_stat.st_mode));
#endif // defined(_WIN32)
return false;
}
auto directory::get_directory(std::string_view path) const -> fs_directory_t {
FIFTHGRID_USES_FUNCTION_NAME();
try {
auto dir_path = utils::path::combine(path_, {path});
return fs_directory_t{
directory{dir_path, stop_requested_}.exists()
? new directory(dir_path, stop_requested_)
: nullptr,
};
} catch (const std::exception &e) {
utils::error::handle_exception(function_name, e);
} catch (...) {
utils::error::handle_exception(function_name);
}
return nullptr;
}
auto directory::get_directories() const -> std::vector<fs_directory_t> {
FIFTHGRID_USES_FUNCTION_NAME();
try {
std::vector<fs_directory_t> ret{};
traverse_directory(
path_,
[this, &ret](auto &&dir_item) -> bool {
ret.emplace_back(fs_directory_t{
new directory(dir_item.get_path(), stop_requested_),
});
return true;
},
[](auto && /* file_item */) -> bool { return true; }, stop_requested_);
return ret;
} catch (const std::exception &e) {
utils::error::handle_exception(function_name, e);
} catch (...) {
utils::error::handle_exception(function_name);
}
return {};
}
auto directory::create_file(std::string_view file_name, bool read_only) const
-> fs_file_t {
FIFTHGRID_USES_FUNCTION_NAME();
try {
auto file_path = utils::path::combine(path_, {file_name});
return file::open_or_create_file(file_path, read_only);
} catch (const std::exception &e) {
utils::error::handle_exception(function_name, e);
} catch (...) {
utils::error::handle_exception(function_name);
}
return nullptr;
}
auto directory::get_file(std::string_view path) const -> fs_file_t {
FIFTHGRID_USES_FUNCTION_NAME();
try {
auto file_path = utils::path::combine(path_, {path});
return fs_file_t{
file{file_path}.exists() ? new file(file_path) : nullptr,
};
} catch (const std::exception &e) {
utils::error::handle_exception(function_name, e);
} catch (...) {
utils::error::handle_exception(function_name);
}
return nullptr;
}
auto directory::get_files() const -> std::vector<fs_file_t> {
FIFTHGRID_USES_FUNCTION_NAME();
try {
std::vector<fs_file_t> ret{};
traverse_directory(
path_, [](auto && /* dir_item */) -> bool { return true; },
[&ret](auto &&file_item) -> bool {
ret.emplace_back(fs_file_t{
new file(file_item.get_path()),
});
return true;
},
stop_requested_);
return ret;
} catch (const std::exception &e) {
utils::error::handle_exception(function_name, e);
} catch (...) {
utils::error::handle_exception(function_name);
}
return {};
}
auto directory::get_items() const -> std::vector<fs_item_t> {
FIFTHGRID_USES_FUNCTION_NAME();
try {
std::vector<fs_item_t> ret{};
traverse_directory(
path_,
[this, &ret](auto &&dir_item) -> bool {
ret.emplace_back(fs_item_t{
new directory(dir_item.get_path(), stop_requested_),
});
return true;
},
[&ret](auto &&file_item) -> bool {
ret.emplace_back(fs_item_t{
new file(file_item.get_path()),
});
return true;
},
stop_requested_);
return ret;
} catch (const std::exception &e) {
utils::error::handle_exception(function_name, e);
} catch (...) {
utils::error::handle_exception(function_name);
}
return {};
}
auto directory::is_stop_requested() const -> bool {
return (stop_requested_ != nullptr) && *stop_requested_;
}
auto directory::is_symlink() const -> bool {
FIFTHGRID_USES_FUNCTION_NAME();
try {
return std::filesystem::is_symlink(path_);
} catch (const std::exception &e) {
utils::error::handle_exception(function_name, e);
} catch (...) {
utils::error::handle_exception(function_name);
}
return false;
}
auto directory::move_to(std::string_view new_path) -> bool {
FIFTHGRID_USES_FUNCTION_NAME();
try {
if (not exists()) {
return true;
}
auto src_root = utils::path::finalize(path_);
auto dst_root = utils::path::finalize(new_path);
if (src_root == dst_root) {
return true;
}
if (directory{dst_root}.exists()) {
throw utils::error::create_exception(function_name,
{
"failed to move directory",
"destination exists",
src_root,
dst_root,
});
}
auto dst_parent = utils::path::get_parent_path(dst_root);
if (not dst_parent.empty() && not directory{dst_parent}.exists()) {
auto parent_parent = utils::path::get_parent_path(dst_parent);
auto last_piece = utils::path::strip_to_file_name(dst_parent);
[[maybe_unused]] auto sub_dir =
directory{parent_parent}.create_directory(last_piece);
}
if (not directory{dst_root}.exists()) {
auto root_parent = utils::path::get_parent_path(dst_root);
auto root_name = utils::path::strip_to_file_name(dst_root);
[[maybe_unused]] auto sub_dir =
directory{root_parent}.create_directory(root_name);
}
if (not copy_to(dst_root, true)) {
throw utils::error::create_exception(function_name,
{
"failed to move directory",
"copy failed",
src_root,
dst_root,
});
}
if (is_stop_requested()) {
return false;
}
if (not remove_recursively()) {
throw utils::error::create_exception(function_name,
{
"failed to move directory",
"remove source failed",
src_root,
});
}
path_ = dst_root;
return true;
} catch (const std::exception &ex) {
utils::error::handle_exception(function_name, ex);
} catch (...) {
utils::error::handle_exception(function_name);
}
return false;
}
auto directory::remove() -> bool {
FIFTHGRID_USES_FUNCTION_NAME();
return utils::retry_action([this]() -> bool {
try {
#if defined(_WIN32)
auto ret = not exists() || (::RemoveDirectoryA(path_.c_str()) != 0);
#else // !defined(_WIN32)
auto ret = not exists() || (rmdir(path_.c_str()) == 0);
#endif // defined(_WIN32)
if (not ret) {
utils::error::handle_error(function_name,
utils::error::create_error_message({
"failed to remove directory",
path_,
}));
}
return ret;
} catch (const std::exception &e) {
utils::error::handle_exception(function_name, e);
} catch (...) {
utils::error::handle_exception(function_name);
}
return false;
});
}
auto directory::remove_recursively() -> bool {
FIFTHGRID_USES_FUNCTION_NAME();
try {
if (not exists()) {
return true;
}
if (not traverse_directory(
path_,
[](auto &&dir_item) -> bool {
return dir_item.remove_recursively();
},
[](auto &&file_item) -> bool { return file_item.remove(); },
stop_requested_)) {
return false;
}
return remove();
} catch (const std::exception &e) {
utils::error::handle_exception(function_name, e);
} catch (...) {
utils::error::handle_exception(function_name);
}
return false;
}
auto directory::size(bool recursive) const -> std::uint64_t {
FIFTHGRID_USES_FUNCTION_NAME();
try {
std::uint64_t ret{0U};
traverse_directory(
path_,
[&ret, &recursive](auto &&dir_item) -> bool {
if (recursive) {
ret += dir_item.size(true);
}
return true;
},
[&ret](auto &&file_item) -> bool {
auto cur_size = file_item.size();
if (cur_size.has_value()) {
ret += cur_size.value();
}
return true;
},
stop_requested_);
return ret;
} catch (const std::exception &e) {
utils::error::handle_exception(function_name, e);
} catch (...) {
utils::error::handle_exception(function_name);
}
return 0U;
}
} // namespace fifthgrid::utils::file

View File

@@ -0,0 +1,189 @@
/*
Copyright <2018-2025> <scott.e.graves@protonmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#if defined(PROJECT_ENABLE_LIBSODIUM) && defined(PROJECT_ENABLE_BOOST)
#include "utils/file_enc_file.hpp"
#include "utils/common.hpp"
#include "utils/encrypting_reader.hpp"
#include "utils/encryption.hpp"
namespace fifthgrid::utils::file {
auto enc_file::attach_file(fs_file_t file) -> fs_file_t {
return fs_file_t{
new enc_file(std::move(file)),
};
}
enc_file::enc_file(fs_file_t file) : file_(std::move(file)) {}
void enc_file::close() { file_->close(); }
auto enc_file::copy_to(std::string_view new_path, bool overwrite) const
-> bool {
return file_->copy_to(new_path, overwrite);
}
void enc_file::flush() const { file_->flush(); }
auto enc_file::move_to(std::string_view path) -> bool {
return file_->move_to(path);
}
auto enc_file::read(unsigned char *data, std::size_t to_read,
std::uint64_t offset, std::size_t *total_read) -> bool {
if (total_read != nullptr) {
*total_read = 0U;
}
auto file_size{size()};
if (not file_size.has_value()) {
return false;
}
to_read = utils::calculate_read_size(file_size.value(), to_read, offset);
if (to_read == 0U) {
return true;
}
std::size_t bytes_read{};
auto ret{
utils::encryption::read_encrypted_range(
{.begin = offset, .end = offset + to_read - 1U},
utils::encryption::generate_key<utils::hash::hash_256_t>(
encryption_token_),
[&](auto &&ct_buffer, auto &&start_offset,
auto &&end_offset) -> bool {
ct_buffer.resize(end_offset - start_offset + 1U);
return file_->read(ct_buffer, start_offset);
},
file_size.value(), data, to_read, bytes_read),
};
if (ret && total_read != nullptr) {
*total_read = bytes_read;
}
return ret;
}
auto enc_file::remove() -> bool { return file_->remove(); }
auto enc_file::truncate(std::size_t size) -> bool {
if (size == 0U) {
return file_->truncate(size);
}
auto file_size{this->size()};
if (not file_size.has_value()) {
return false;
}
if (size == file_size.value()) {
return true;
}
auto chunks{
size / utils::encryption::encrypting_reader::get_data_chunk_size(),
};
auto real_size{
(chunks * utils::encryption::encrypting_reader::get_data_chunk_size()) +
(chunks * utils::encryption::encrypting_reader::get_header_size()),
};
auto remain{
size % utils::encryption::encrypting_reader::get_data_chunk_size(),
};
if (remain > 0U) {
real_size +=
(remain + utils::encryption::encrypting_reader::get_header_size());
}
if (size < file_size.value()) {
if (remain == 0U) {
return file_->truncate(real_size);
}
auto begin_chunk{
size / utils::encryption::encrypting_reader::get_data_chunk_size(),
};
auto offset{
begin_chunk *
utils::encryption::encrypting_reader::get_data_chunk_size(),
};
std::size_t total_read{};
data_buffer data(
utils::encryption::encrypting_reader::get_data_chunk_size());
if (not i_file::read(data, offset, &total_read)) {
return false;
}
data.resize(remain);
if (not file_->truncate(real_size)) {
return false;
}
return i_file::write(data, offset);
}
/* auto begin_chunk{
file_size.value() /
utils::encryption::encrypting_reader::get_data_chunk_size(),
};
auto end_chunk{
utils::divide_with_ceiling(
file_size.value(),
utils::encryption::encrypting_reader::get_data_chunk_size()),
}; */
return false;
}
auto enc_file::write(const unsigned char * /* data */, std::size_t to_write,
std::size_t offset, std::size_t * /* total_written */)
-> bool {
auto file_size{size()};
if (not file_size.has_value()) {
return false;
}
if ((offset + to_write) > file_size.value()) {
if (not truncate((offset + to_write) - file_size.value())) {
return false;
}
}
return false;
}
auto enc_file::size() const -> std::optional<std::uint64_t> {
auto file_size = file_->size();
if (not file_size.has_value()) {
return std::nullopt;
}
return utils::encryption::encrypting_reader::calculate_decrypted_size(
file_size.value(), false);
}
} // namespace fifthgrid::utils::file
#endif // defined(PROJECT_ENABLE_LIBSODIUM) && defined(PROJECT_ENABLE_BOOST)

View File

@@ -0,0 +1,595 @@
/*
Copyright <2018-2025> <scott.e.graves@protonmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#include "utils/file_file.hpp"
#include "utils/collection.hpp"
#include "utils/common.hpp"
#include "utils/error.hpp"
#include "utils/path.hpp"
namespace {
[[nodiscard]] auto get_file_size(std::string_view path,
std::uint64_t &file_size) -> bool {
auto abs_path = fifthgrid::utils::path::absolute(path);
file_size = 0U;
#if defined(_WIN32)
struct _stat64 u_stat{};
auto res = _stat64(std::string{path}.c_str(), &u_stat);
if (res != 0) {
return false;
}
file_size = static_cast<std::uint64_t>(u_stat.st_size);
return true;
#else // !defined(_WIN32)
std::error_code ec{};
file_size = std::filesystem::file_size(abs_path, ec);
return (ec.value() == 0);
#endif // defined(_WIN32)
}
[[nodiscard]] auto is_file(std::string_view path) -> bool {
auto abs_path = fifthgrid::utils::path::absolute(path);
#if defined(_WIN32)
return ((::PathFileExistsA(abs_path.c_str()) != 0) &&
(::PathIsDirectoryA(abs_path.c_str()) == 0));
#else // !defined(_WIN32)
struct stat64 u_stat{};
return (stat64(abs_path.c_str(), &u_stat) == 0 &&
not S_ISDIR(u_stat.st_mode));
#endif // defined(_WIN32)
}
} // namespace
namespace fifthgrid::utils::file {
// auto file::attach_file(native_handle handle,
// bool read_only) -> fs_file_t {
// FIFTHGRID_USES_FUNCTION_NAME();
//
// try {
// std::string path;
//
// #if defined(_WIN32)
// path.resize(fifthgrid::max_path_length + 1U);
// ::GetFinalPathNameByHandleA(handle, path.data(),
// static_cast<DWORD>(path.size()),
// FILE_NAME_NORMALIZED | VOLUME_NAME_DOS);
// #else // !defined(_WIN32)
// path.resize(fifthgrid::max_path_length + 1U);
//
// #if defined(__APPLE__)
// fcntl(handle, F_GETPATH, source_path.data());
// #else // !defined(__APPLE__)
// readlink(("/proc/self/fd/" + std::to_string(handle)).c_str(),
// path.data(),
// path.size());
// #endif // defined(__APPLE__)
// #endif // defined(_WIN32)
//
// path = path.c_str();
//
// #if defined(_WIN32)
// auto *ptr = _fdopen(
// static_cast<int>(_open_osfhandle(reinterpret_cast<intptr_t>(handle),
// read_only ? _O_RDONLY : _O_RDWR)),
// read_only ? "rb" : "rb+");
// #else // !defined(_WIN32)
// auto *ptr = fdopen(handle, read_only ? "rb" : "rb+");
// #endif // defined(_WIN32)
//
// return fs_file_t(new file{
// file_t{ptr},
// utils::path::absolute(path),
// read_only,
// });
// } catch (const std::exception &e) {
// utils::error::handle_exception(function_name, e);
// } catch (...) {
// utils::error::handle_exception(function_name);
// }
//
// return nullptr;
// }
void file::open() {
FIFTHGRID_USES_FUNCTION_NAME();
if (not is_file(path_)) {
throw utils::error::create_exception(function_name, {
"file not found",
path_,
});
}
#if defined(_WIN32)
file_ = file_t{
_fsopen(path_.c_str(), read_only_ ? "rb" : "rb+", _SH_DENYNO),
file_deleter(),
};
#else // !defined(_WIN32)
file_ = file_t{
fopen(path_.c_str(), read_only_ ? "rb" : "rb+"),
file_deleter(),
};
#endif // defined(_WIN32)
}
auto file::open_file(std::string_view path, bool read_only) -> fs_file_t {
FIFTHGRID_USES_FUNCTION_NAME();
auto *ptr = new file{
nullptr,
utils::path::absolute(path),
read_only,
};
auto new_file = fs_file_t(ptr);
try {
ptr->open();
} catch (const std::exception &e) {
utils::error::handle_exception(function_name, e);
} catch (...) {
utils::error::handle_exception(function_name);
}
return new_file;
}
auto file::open_or_create_file(std::string_view path, bool read_only)
-> fs_file_t {
auto abs_path = utils::path::absolute(path);
if (not is_file(abs_path)) {
#if defined(_WIN32)
int old_mode{};
_umask_s(077, &old_mode);
#else // !defined(_WIN32)
auto old_mode = umask(077);
#endif // defined(_WIN32)
#if defined(_WIN32)
auto *ptr = _fsopen(abs_path.c_str(), "ab+", _SH_DENYNO);
#else // !defined(_WIN32)
auto *ptr = fopen(abs_path.c_str(), "ab+");
#endif // defined(_WIN32)
if (ptr != nullptr) {
fclose(ptr);
}
#if defined(_WIN32)
_umask_s(old_mode, nullptr);
#else // !defined(_WIN32)
umask(old_mode);
#endif // defined(_WIN32)
}
return open_file(abs_path, read_only);
}
void file::close() { file_.reset(); }
auto file::copy_to(std::string_view new_path, bool overwrite) const -> bool {
FIFTHGRID_USES_FUNCTION_NAME();
try {
auto to_path = utils::path::absolute(new_path);
if (directory(to_path).exists()) {
return false;
}
#if defined(_WIN32)
return ::CopyFileA(path_.c_str(), to_path.c_str(),
overwrite ? TRUE : FALSE) != 0;
#else // !defined(_WIN32)
return std::filesystem::copy_file(
path_, to_path,
overwrite ? std::filesystem::copy_options::overwrite_existing
: std::filesystem::copy_options::skip_existing);
#endif // defined(_WIN32)
} catch (const std::exception &e) {
utils::error::handle_exception(function_name, e);
} catch (...) {
utils::error::handle_exception(function_name);
}
return false;
}
auto file::exists() const -> bool { return is_file(path_); }
void file::flush() const {
if (file_) {
fflush(file_.get());
}
}
auto file::get_handle() const -> native_handle {
if (file_) {
#if defined(_WIN32)
return reinterpret_cast<native_handle>(
_get_osfhandle(_fileno(file_.get())));
#else // !defined(_WIN32)
return fileno(file_.get());
#endif // defined(_WIN32)
}
return INVALID_HANDLE_VALUE;
}
auto file::is_symlink() const -> bool {
FIFTHGRID_USES_FUNCTION_NAME();
try {
return std::filesystem::is_symlink(path_);
} catch (const std::exception &e) {
utils::error::handle_exception(function_name, e);
} catch (...) {
utils::error::handle_exception(function_name);
}
return false;
}
auto file::move_to(std::string_view path) -> bool {
FIFTHGRID_USES_FUNCTION_NAME();
auto abs_path = utils::path::absolute(path);
auto reopen{false};
if (file_) {
reopen = true;
close();
}
auto success{false};
#if defined(_WIN32)
success = ::MoveFileExA(path_.c_str(), abs_path.c_str(),
MOVEFILE_REPLACE_EXISTING) != 0;
#else // !defined(_WIN32)
std::error_code ec{};
std::filesystem::rename(path_, abs_path, ec);
success = ec.value() == 0;
#endif // defined(_WIN32)
if (success) {
path_ = abs_path;
}
if (reopen) {
try {
open();
return success;
} catch (const std::exception &e) {
utils::error::handle_exception(function_name, e);
} catch (...) {
utils::error::handle_exception(function_name);
}
}
return false;
}
auto file::read(unsigned char *data, std::size_t to_read, std::uint64_t offset,
std::size_t *total_read) -> bool {
FIFTHGRID_USES_FUNCTION_NAME();
if (total_read != nullptr) {
(*total_read) = 0U;
}
try {
if (not file_) {
throw utils::error::create_exception(function_name,
{
"file is not open for reading",
path_,
});
}
if (fseeko(file_.get(), static_cast<std::int64_t>(offset), SEEK_SET) ==
-1) {
throw utils::error::create_exception(function_name,
{
"failed to seek before read",
path_,
});
}
std::size_t bytes_read{0U};
while (bytes_read != to_read) {
auto res =
fread(&data[bytes_read], 1U, to_read - bytes_read, file_.get());
if (not feof(file_.get()) && ferror(file_.get())) {
throw utils::error::create_exception(function_name,
{
"failed to read file bytes",
path_,
});
}
if (res == 0) {
break;
}
bytes_read += static_cast<std::size_t>(res);
}
if (total_read != nullptr) {
(*total_read) = bytes_read;
}
return true;
} catch (const std::exception &e) {
utils::error::handle_exception(function_name, e);
} catch (...) {
utils::error::handle_exception(function_name);
}
return false;
}
#if defined(PROJECT_ENABLE_LIBSODIUM)
auto file::sha256() -> std::optional<std::string> {
FIFTHGRID_USES_FUNCTION_NAME();
auto should_close{false};
auto read_only{read_only_};
std::optional<std::string> ret;
try {
if (file_ == nullptr) {
should_close = true;
read_only_ = true;
this->open();
}
crypto_hash_sha256_state state{};
auto res = crypto_hash_sha256_init(&state);
if (res != 0) {
throw utils::error::create_exception(function_name,
{
"failed to initialize sha256",
std::to_string(res),
path_,
});
}
{
data_buffer buffer(get_read_buffer_size());
std::uint64_t read_offset{0U};
std::size_t bytes_read{0U};
while (i_file::read(buffer, read_offset, &bytes_read)) {
if (not bytes_read) {
break;
}
read_offset += bytes_read;
res = crypto_hash_sha256_update(
&state, reinterpret_cast<const unsigned char *>(buffer.data()),
bytes_read);
if (res != 0) {
throw utils::error::create_exception(function_name,
{
"failed to update sha256",
std::to_string(res),
path_,
});
}
}
}
std::array<unsigned char, crypto_hash_sha256_BYTES> out{};
res = crypto_hash_sha256_final(&state, out.data());
if (res != 0) {
throw utils::error::create_exception(function_name,
{
"failed to finalize sha256",
std::to_string(res),
path_,
});
}
ret = utils::collection::to_hex_string(out);
} catch (const std::exception &e) {
utils::error::handle_exception(function_name, e);
} catch (...) {
utils::error::handle_exception(function_name);
}
if (should_close) {
read_only_ = read_only;
close();
}
return ret;
}
#endif // defined(PROJECT_ENABLE_LIBSODIUM)
auto file::remove() -> bool {
FIFTHGRID_USES_FUNCTION_NAME();
close();
return utils::retry_action([this]() -> bool {
try {
#if defined(_WIN32)
auto ret = not exists() || (::DeleteFileA(path_.c_str()) != 0);
#else // !defined(_WIN32)
std::error_code ec{};
auto ret = not exists() || std::filesystem::remove(path_, ec);
#endif // defined(_WIN32)
if (not ret) {
utils::error::handle_error(function_name,
utils::error::create_error_message({
"failed to remove file",
path_,
}));
}
return ret;
} catch (const std::exception &e) {
utils::error::handle_exception(function_name, e);
} catch (...) {
utils::error::handle_exception(function_name);
}
return false;
});
}
auto file::truncate(std::size_t size) -> bool {
FIFTHGRID_USES_FUNCTION_NAME();
auto reopen{false};
if (file_) {
reopen = true;
close();
}
std::error_code ec{};
std::filesystem::resize_file(path_, size, ec);
auto success{ec.value() == 0};
if (reopen) {
try {
open();
} catch (const std::exception &e) {
utils::error::handle_exception(function_name, e);
success = false;
} catch (...) {
utils::error::handle_exception(function_name);
success = false;
}
}
return success;
}
auto file::write(const unsigned char *data, std::size_t to_write,
std::size_t offset, std::size_t *total_written) -> bool {
FIFTHGRID_USES_FUNCTION_NAME();
if (total_written != nullptr) {
(*total_written) = 0U;
}
try {
if (not file_) {
throw utils::error::create_exception(function_name,
{
"file is not open for writing",
path_,
});
}
auto res = fseeko(file_.get(), static_cast<std::int64_t>(offset), SEEK_SET);
if (res == -1) {
throw utils::error::create_exception(function_name,
{
"failed to seek before write",
path_,
});
}
std::size_t bytes_written{0U};
while (bytes_written != to_write) {
auto written =
fwrite(reinterpret_cast<const char *>(&data[bytes_written]), 1U,
to_write - bytes_written, file_.get());
if (not feof(file_.get()) && ferror(file_.get())) {
throw utils::error::create_exception(function_name,
{
"failed to write file bytes",
path_,
});
}
if (written == 0U) {
break;
}
bytes_written += written;
}
flush();
if (total_written != nullptr) {
(*total_written) = bytes_written;
}
return true;
} catch (const std::exception &e) {
utils::error::handle_exception(function_name, e);
} catch (...) {
utils::error::handle_exception(function_name);
}
return false;
}
auto file::size() const -> std::optional<std::uint64_t> {
FIFTHGRID_USES_FUNCTION_NAME();
try {
if (file_) {
if (fseeko(file_.get(), 0, SEEK_END) == -1) {
throw utils::error::create_exception(function_name,
{
"failed to seek",
path_,
});
}
auto size = ftello(file_.get());
if (size == -1) {
throw utils::error::create_exception(function_name,
{
"failed to get position",
path_,
});
}
return static_cast<std::uint64_t>(size);
}
std::uint64_t size{};
if (not get_file_size(path_, size)) {
throw utils::error::create_exception(function_name,
{
"failed to get file size",
path_,
});
}
return size;
} catch (const std::exception &e) {
utils::error::handle_exception(function_name, e);
} catch (...) {
utils::error::handle_exception(function_name);
}
return std::nullopt;
}
} // namespace fifthgrid::utils::file

View File

@@ -0,0 +1,789 @@
/*
Copyright <2018-2025> <scott.e.graves@protonmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#if defined(PROJECT_ENABLE_LIBDSM)
#include "utils/file_smb_directory.hpp"
#include "utils/common.hpp"
#include "utils/error.hpp"
#include "utils/unix.hpp"
#include "utils/windows.hpp"
namespace fifthgrid::utils::file {
auto smb_directory::open(std::string_view host, std::string_view user,
std::string_view password, std::string_view path,
stop_type *stop_requested) -> smb_directory_t {
FIFTHGRID_USES_FUNCTION_NAME();
try {
smb_session_t session{
smb_session_new(),
smb_session_deleter,
};
netbios_ns_t ns{
netbios_ns_new(),
netbios_ns_deleter(),
};
sockaddr_in addr{};
auto res = netbios_ns_resolve(
ns.get(), std::string{host}.c_str(), NETBIOS_FILESERVER,
reinterpret_cast<std::uint32_t *>(&addr.sin_addr.s_addr));
if (res != DSM_SUCCESS) {
res = inet_pton(AF_INET, std::string{host}.c_str(), &addr.sin_addr);
if (res != 1) {
throw utils::error::create_exception(
function_name, {
"failed to resolve host",
std::to_string(utils::get_last_error_code()),
host,
});
}
}
res = smb_session_connect(session.get(), std::string{host}.c_str(),
static_cast<std::uint32_t>(addr.sin_addr.s_addr),
SMB_TRANSPORT_TCP);
if (res != DSM_SUCCESS) {
throw utils::error::create_exception(function_name,
{
"failed to connect to host",
std::to_string(res),
host,
});
}
smb_session_set_creds(session.get(), std::string{host}.c_str(),
std::string{user}.c_str(),
std::string{password}.c_str());
res = smb_session_login(session.get());
if (res != DSM_SUCCESS) {
throw utils::error::create_exception(function_name,
{
"failed to logon to host",
std::to_string(res),
host,
user,
});
}
auto share_name = utils::string::split(path, '/', false).at(0U);
smb_tid tid{};
res = smb_tree_connect(session.get(), share_name.c_str(), &tid);
if (res != DSM_SUCCESS) {
throw utils::error::create_exception(function_name,
{
"failed to connect to share",
std::to_string(res),
share_name,
});
}
return smb_directory_t{
new smb_directory{
"//" + std::string{host} + "/" + std::string{path},
session,
share_name,
tid,
stop_requested,
},
};
} catch (const std::exception &e) {
utils::error::handle_exception(function_name, e);
} catch (...) {
utils::error::handle_exception(function_name);
}
return nullptr;
}
auto smb_directory::open(std::wstring_view host, std::wstring_view user,
std::wstring_view password, std::wstring_view path,
stop_type *stop_requested) -> smb_directory_t {
return open(utils::string::to_utf8(host), utils::string::to_utf8(user),
utils::string::to_utf8(password), utils::string::to_utf8(path),
stop_requested);
}
auto smb_directory::copy_to(std::string_view new_path,
bool /* overwrite */) const -> bool {
FIFTHGRID_USES_FUNCTION_NAME();
try {
if (not session_) {
throw utils::error::create_exception(function_name,
{
"session not found",
path_,
});
}
// auto to_path = utils::path::absolute(new_path);
throw utils::error::create_exception(function_name,
{
"failed to copy directory",
"not implemented",
new_path,
path_,
});
} catch (const std::exception &e) {
utils::error::handle_exception(function_name, e);
} catch (...) {
utils::error::handle_exception(function_name);
}
return false;
}
auto smb_directory::count(bool recursive) const -> std::uint64_t {
FIFTHGRID_USES_FUNCTION_NAME();
try {
if (not session_) {
throw utils::error::create_exception(function_name,
{
"session not found",
path_,
});
}
smb_stat_list_t list{
smb_find(session_.get(), tid_, smb_create_search_path(path_).c_str()),
smb_stat_list_deleter(),
};
auto count = smb_stat_list_count(list.get());
if (not recursive) {
return count;
}
throw utils::error::create_exception(
function_name, {
"failed to get directory count recursively",
"not implemented",
utils::string::from_bool(recursive),
path_,
});
} catch (const std::exception &e) {
utils::error::handle_exception(function_name, e);
} catch (...) {
utils::error::handle_exception(function_name);
}
return 0U;
}
auto smb_directory::create_directory(std::string_view path) const
-> fs_directory_t {
FIFTHGRID_USES_FUNCTION_NAME();
try {
if (not session_) {
throw utils::error::create_exception(function_name,
{
"session not found",
path_,
});
}
auto dir = get_directory(path);
if (dir) {
return dir;
}
auto res = smb_directory_create(
session_.get(), tid_,
smb_create_and_validate_relative_path(path_, path).c_str());
if (res != DSM_SUCCESS) {
throw utils::error::create_exception(function_name,
{
"failed to create directory",
std::to_string(res),
path_,
});
}
return get_directory(path);
} catch (const std::exception &e) {
utils::error::handle_exception(function_name, e);
} catch (...) {
utils::error::handle_exception(function_name);
}
return nullptr;
}
auto smb_directory::create_file(std::string_view file_name,
bool read_only) const -> fs_file_t {
FIFTHGRID_USES_FUNCTION_NAME();
try {
if (not session_) {
throw utils::error::create_exception(function_name,
{
"session not found",
path_,
});
}
auto fs_file = get_file(file_name);
if (fs_file) {
if (not dynamic_cast<smb_file *>(fs_file.get())->open(read_only)) {
throw utils::error::create_exception(function_name,
{
"failed to open existing file",
file_name,
});
}
return fs_file;
}
auto rel_path = smb_create_and_validate_relative_path(path_, file_name);
smb_fd fd{};
auto res =
smb_fopen(session_.get(), tid_, rel_path.c_str(), SMB_MOD_RW, &fd);
if (res != DSM_SUCCESS) {
return nullptr;
}
smb_fclose(session_.get(), fd);
res = smb_fopen(session_.get(), tid_, rel_path.c_str(),
read_only ? SMB_MOD_RO : SMB_MOD_RW2, &fd);
if (res != DSM_SUCCESS) {
return nullptr;
}
return std::make_unique<smb_file>(
fd, smb_create_smb_path(path_, std::string{rel_path}), session_,
share_name_, tid_);
} catch (const std::exception &e) {
utils::error::handle_exception(function_name, e);
} catch (...) {
utils::error::handle_exception(function_name);
}
return nullptr;
}
auto smb_directory::exists() const -> bool {
FIFTHGRID_USES_FUNCTION_NAME();
try {
if (not session_) {
throw utils::error::create_exception(function_name,
{
"session not found",
path_,
});
}
smb_stat_t st{
smb_fstat(session_.get(), tid_,
smb_create_relative_path(path_).c_str()),
smb_stat_deleter(),
};
if (not st) {
return false;
}
return smb_stat_get(st.get(), SMB_STAT_ISDIR) != 0U;
} catch (const std::exception &e) {
utils::error::handle_exception(function_name, e);
} catch (...) {
utils::error::handle_exception(function_name);
}
return false;
}
auto smb_directory::get_directory(std::string_view path) const
-> fs_directory_t {
FIFTHGRID_USES_FUNCTION_NAME();
try {
if (not session_) {
throw utils::error::create_exception(function_name,
{
"session not found",
path_,
});
}
auto rel_path = smb_create_and_validate_relative_path(path_, path);
smb_stat_t st{
smb_fstat(session_.get(), tid_, rel_path.c_str()),
smb_stat_deleter(),
};
if (not st) {
throw utils::error::create_exception(function_name,
{
"failed to stat directory",
rel_path,
path_,
});
}
bool is_dir{smb_stat_get(st.get(), SMB_STAT_ISDIR) != 0U};
if (not is_dir) {
throw utils::error::create_exception(function_name,
{
"path is not a directory",
rel_path,
path_,
});
}
return smb_directory_t{
new smb_directory{
smb_create_smb_path(path_, rel_path),
session_,
share_name_,
tid_,
stop_requested_,
},
};
} catch (const std::exception &e) {
utils::error::handle_exception(function_name, e);
} catch (...) {
utils::error::handle_exception(function_name);
}
return nullptr;
}
auto smb_directory::get_directories() const -> std::vector<fs_directory_t> {
FIFTHGRID_USES_FUNCTION_NAME();
try {
if (not session_) {
throw utils::error::create_exception(function_name,
{
"session not found",
path_,
});
}
smb_stat_list_t list{
smb_find(session_.get(), tid_, smb_create_search_path(path_).c_str()),
smb_stat_list_deleter(),
};
if (not list) {
throw utils::error::create_exception(function_name,
{
"failed to get directory list",
path_,
});
}
std::vector<fs_directory_t> ret{};
auto count = smb_stat_list_count(list.get());
for (std::size_t idx = 0U; !is_stop_requested() && idx < count; ++idx) {
auto *item_st = smb_stat_list_at(list.get(), idx);
bool is_dir{smb_stat_get(item_st, SMB_STAT_ISDIR) != 0U};
if (not is_dir) {
continue;
}
std::string name{smb_stat_name(item_st)};
if (name == "." || name == "..") {
continue;
}
try {
ret.emplace_back(smb_directory_t{
new smb_directory{
smb_create_smb_path(path_, name),
session_,
share_name_,
tid_,
stop_requested_,
},
});
} catch (const std::exception &e) {
utils::error::handle_exception(function_name, e);
} catch (...) {
utils::error::handle_exception(function_name);
}
}
return ret;
} catch (const std::exception &e) {
utils::error::handle_exception(function_name, e);
} catch (...) {
utils::error::handle_exception(function_name);
}
return {};
}
auto smb_directory::get_file(std::string_view path) const -> fs_file_t {
FIFTHGRID_USES_FUNCTION_NAME();
try {
if (not session_) {
throw utils::error::create_exception(function_name,
{
"session not found",
path_,
});
}
auto rel_path = smb_create_and_validate_relative_path(path_, path);
smb_stat_t st{
smb_fstat(session_.get(), tid_, rel_path.c_str()),
smb_stat_deleter(),
};
if (not st) {
throw utils::error::create_exception(function_name,
{
"failed to stat file",
rel_path,
path_,
});
}
bool is_dir{smb_stat_get(st.get(), SMB_STAT_ISDIR) != 0U};
if (is_dir) {
throw utils::error::create_exception(function_name,
{
"path is not a file",
rel_path,
path_,
});
}
return std::make_unique<smb_file>(
std::nullopt, smb_create_smb_path(path_, std::string{rel_path}),
session_, share_name_, tid_);
} catch (const std::exception &e) {
utils::error::handle_exception(function_name, e);
} catch (...) {
utils::error::handle_exception(function_name);
}
return nullptr;
}
auto smb_directory::get_files() const -> std::vector<fs_file_t> {
FIFTHGRID_USES_FUNCTION_NAME();
try {
if (not session_) {
throw utils::error::create_exception(function_name,
{
"session not found",
path_,
});
}
smb_stat_list_t list{
smb_find(session_.get(), tid_, smb_create_search_path(path_).c_str()),
smb_stat_list_deleter(),
};
if (not list) {
throw utils::error::create_exception(function_name,
{
"failed to get file list",
path_,
});
}
std::vector<fs_file_t> ret{};
auto count = smb_stat_list_count(list.get());
for (std::size_t idx = 0U; !is_stop_requested() && idx < count; ++idx) {
auto *item_st = smb_stat_list_at(list.get(), idx);
bool is_dir{smb_stat_get(item_st, SMB_STAT_ISDIR) != 0U};
if (is_dir) {
continue;
}
try {
std::string name{smb_stat_name(item_st)};
ret.emplace_back(std::make_unique<smb_file>(
std::nullopt, smb_create_smb_path(path_, name), session_,
share_name_, tid_));
} catch (const std::exception &e) {
utils::error::handle_exception(function_name, e);
} catch (...) {
utils::error::handle_exception(function_name);
}
}
return ret;
} catch (const std::exception &e) {
utils::error::handle_exception(function_name, e);
} catch (...) {
utils::error::handle_exception(function_name);
}
return {};
}
auto smb_directory::get_items() const -> std::vector<fs_item_t> {
FIFTHGRID_USES_FUNCTION_NAME();
try {
if (not session_) {
throw utils::error::create_exception(function_name,
{
"session not found",
path_,
});
}
smb_stat_list_t list{
smb_find(session_.get(), tid_, smb_create_search_path(path_).c_str()),
smb_stat_list_deleter(),
};
if (not list) {
throw utils::error::create_exception(function_name,
{
"failed to get item list",
path_,
});
}
std::vector<fs_item_t> ret{};
auto count = smb_stat_list_count(list.get());
for (std::size_t idx = 0U; !is_stop_requested() && idx < count; ++idx) {
auto *item_st = smb_stat_list_at(list.get(), idx);
bool is_dir{smb_stat_get(item_st, SMB_STAT_ISDIR) != 0U};
std::string name{smb_stat_name(item_st)};
if (is_dir) {
if (name == "." || name == "..") {
continue;
}
try {
ret.emplace_back(smb_directory_t{
new smb_directory{
path_ + '/' + name,
session_,
share_name_,
tid_,
stop_requested_,
},
});
} catch (const std::exception &e) {
utils::error::handle_exception(function_name, e);
} catch (...) {
utils::error::handle_exception(function_name);
}
continue;
}
try {
ret.emplace_back(std::make_unique<smb_file>(
std::nullopt, smb_create_smb_path(path_, name), session_,
share_name_, tid_));
} catch (const std::exception &e) {
utils::error::handle_exception(function_name, e);
} catch (...) {
utils::error::handle_exception(function_name);
}
}
return ret;
} catch (const std::exception &e) {
utils::error::handle_exception(function_name, e);
} catch (...) {
utils::error::handle_exception(function_name);
}
return {};
}
auto smb_directory::get_time(time_type type) const
-> std::optional<std::uint64_t> {
return smb_file::get_time(session_.get(), tid_, path_, type);
}
auto smb_directory::is_stop_requested() const -> bool {
return (stop_requested_ != nullptr) && *stop_requested_;
}
auto smb_directory::is_symlink() const -> bool {
FIFTHGRID_USES_FUNCTION_NAME();
try {
if (not session_) {
throw utils::error::create_exception(function_name,
{
"session not found",
path_,
});
}
} catch (const std::exception &e) {
utils::error::handle_exception(function_name, e);
} catch (...) {
utils::error::handle_exception(function_name);
}
return false;
}
auto smb_directory::move_to(std::string_view new_path) -> bool {
FIFTHGRID_USES_FUNCTION_NAME();
try {
if (not session_) {
throw utils::error::create_exception(function_name,
{
"session not found",
path_,
});
}
throw utils::error::create_exception(function_name,
{
"failed to move directory",
"not implemented",
new_path,
path_,
});
} catch (const std::exception &e) {
utils::error::handle_exception(function_name, e);
} catch (...) {
utils::error::handle_exception(function_name);
}
return false;
}
auto smb_directory::remove() -> bool {
FIFTHGRID_USES_FUNCTION_NAME();
try {
if (not session_) {
throw utils::error::create_exception(function_name,
{
"session not found",
path_,
});
}
return utils::retry_action([this]() -> bool {
if (not exists()) {
return true;
}
try {
auto res = smb_directory_rm(session_.get(), tid_,
smb_create_relative_path(path_).c_str());
if (res != DSM_SUCCESS) {
throw utils::error::create_exception(function_name,
{
"failed to remove directory",
std::to_string(res),
path_,
});
}
return true;
} catch (const std::exception &e) {
utils::error::handle_exception(function_name, e);
} catch (...) {
utils::error::handle_exception(function_name);
}
return false;
});
} catch (const std::exception &e) {
utils::error::handle_exception(function_name, e);
} catch (...) {
utils::error::handle_exception(function_name);
}
return false;
}
auto smb_directory::remove_recursively() -> bool {
FIFTHGRID_USES_FUNCTION_NAME();
try {
if (not session_) {
throw utils::error::create_exception(function_name,
{
"session not found",
path_,
});
}
if (not exists()) {
return true;
}
throw utils::error::create_exception(
function_name, {
"failed to remove directory recursively",
"not implemented",
path_,
});
} catch (const std::exception &e) {
utils::error::handle_exception(function_name, e);
} catch (...) {
utils::error::handle_exception(function_name);
}
return false;
}
auto smb_directory::size(bool /* recursive */) const -> std::uint64_t {
FIFTHGRID_USES_FUNCTION_NAME();
try {
if (not session_) {
throw utils::error::create_exception(function_name,
{
"session not found",
path_,
});
}
throw utils::error::create_exception(function_name,
{
"failed to get directory size",
"not implemented",
path_,
});
} catch (const std::exception &e) {
utils::error::handle_exception(function_name, e);
} catch (...) {
utils::error::handle_exception(function_name);
}
return false;
}
} // namespace fifthgrid::utils::file
#endif // defined(PROJECT_ENABLE_LIBDSM)

View File

@@ -0,0 +1,549 @@
/*
Copyright <2018-2025> <scott.e.graves@protonmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#if defined(PROJECT_ENABLE_LIBDSM)
#include "utils/file_smb_file.hpp"
#include "utils/common.hpp"
#include "utils/error.hpp"
#include "utils/string.hpp"
namespace fifthgrid::utils::file {
void smb_file::close() {
if (fd_.has_value()) {
smb_fclose(session_.get(), *fd_);
fd_.reset();
}
}
auto smb_file::copy_to(std::string_view new_path,
bool overwrite) const -> bool {
FIFTHGRID_USES_FUNCTION_NAME();
try {
if (not session_) {
throw utils::error::create_exception(function_name,
{
"session not found",
path_,
});
}
// auto to_path = utils::path::absolute(new_path);
throw utils::error::create_exception(function_name,
{
"failed to copy file",
"not implemented",
std::to_string(overwrite),
new_path,
path_,
});
} catch (const std::exception &e) {
utils::error::handle_exception(function_name, e);
} catch (...) {
utils::error::handle_exception(function_name);
}
return false;
}
auto smb_file::exists() const -> bool {
FIFTHGRID_USES_FUNCTION_NAME();
try {
if (not session_) {
throw utils::error::create_exception(function_name,
{
"session not found",
path_,
});
}
smb_stat_t st{
smb_fstat(session_.get(), tid_,
smb_create_relative_path(path_).c_str()),
smb_stat_deleter(),
};
if (not st) {
return false;
}
return smb_stat_get(st.get(), SMB_STAT_ISDIR) == 0U;
} catch (const std::exception &e) {
utils::error::handle_exception(function_name, e);
} catch (...) {
utils::error::handle_exception(function_name);
}
return false;
}
void smb_file::flush() const {
FIFTHGRID_USES_FUNCTION_NAME();
try {
throw utils::error::create_exception(function_name,
{
"failed to flush file",
"not implemented",
path_,
});
} catch (const std::exception &e) {
utils::error::handle_exception(function_name, e);
} catch (...) {
utils::error::handle_exception(function_name);
}
}
auto smb_file::get_time(smb_session *session, smb_tid tid, std::string path,
time_type type) -> std::optional<std::uint64_t> {
FIFTHGRID_USES_FUNCTION_NAME();
try {
if (session == nullptr) {
throw utils::error::create_exception(function_name,
{
"session not found",
path,
});
}
auto rel_path = smb_create_relative_path(path);
smb_stat_t st{
smb_fstat(session, tid, rel_path.c_str()),
smb_stat_deleter(),
};
if (not st) {
throw utils::error::create_exception(function_name,
{
"failed to stat file",
"not implemented",
rel_path,
path,
});
}
switch (type) {
case time_type::accessed:
return smb_stat_get(st.get(), SMB_STAT_ATIME);
case time_type::created:
return smb_stat_get(st.get(), SMB_STAT_CTIME);
case time_type::modified:
return smb_stat_get(st.get(), SMB_STAT_MTIME);
case time_type::written:
return smb_stat_get(st.get(), SMB_STAT_WTIME);
}
} catch (const std::exception &e) {
utils::error::handle_exception(function_name, e);
} catch (...) {
utils::error::handle_exception(function_name);
}
return std::nullopt;
}
auto smb_file::is_symlink() const -> bool {
FIFTHGRID_USES_FUNCTION_NAME();
try {
if (not session_) {
throw utils::error::create_exception(function_name,
{
"session not found",
path_,
});
}
} catch (const std::exception &e) {
utils::error::handle_exception(function_name, e);
} catch (...) {
utils::error::handle_exception(function_name);
}
return false;
}
auto smb_file::move_to(std::string_view new_path) -> bool {
FIFTHGRID_USES_FUNCTION_NAME();
try {
if (utils::string::begins_with(new_path, "//")) {
throw utils::error::create_exception(function_name,
{
"failed to move file",
"new path must be in same share",
new_path,
path_,
});
}
auto from_path = smb_create_relative_path(path_);
auto to_path = smb_create_and_validate_relative_path(
utils::string::begins_with(new_path, "/") ? smb_get_root_path(path_)
: smb_get_parent_path(path_),
new_path);
auto was_open{false};
if (fd_.has_value()) {
close();
was_open = true;
}
auto res = smb_tree_connect(session_.get(), share_name_.c_str(), &tid_);
if (res != DSM_SUCCESS) {
throw utils::error::create_exception(function_name,
{
"failed to connect to share",
std::to_string(res),
share_name_,
path_,
});
}
res = smb_file_mv(session_.get(), tid_, from_path.c_str(), to_path.c_str());
if (res != DSM_SUCCESS) {
throw utils::error::create_exception(function_name,
{
"failed to move file",
std::to_string(res),
from_path,
to_path,
});
}
path_ = smb_create_smb_path(path_, to_path);
if (was_open) {
return open(read_only_);
}
return true;
} catch (const std::exception &e) {
utils::error::handle_exception(function_name, e);
} catch (...) {
utils::error::handle_exception(function_name);
}
return false;
}
auto smb_file::open(bool read_only) -> bool {
FIFTHGRID_USES_FUNCTION_NAME();
try {
if (fd_.has_value()) {
if (read_only == read_only_) {
return true;
}
close();
}
auto rel_path = smb_create_relative_path(path_);
auto res = smb_tree_connect(session_.get(), share_name_.c_str(), &tid_);
if (res != DSM_SUCCESS) {
throw utils::error::create_exception(function_name,
{
"failed to connect to share",
std::to_string(res),
share_name_,
path_,
});
}
smb_fd fd{};
res = smb_fopen(session_.get(), tid_, rel_path.c_str(),
read_only ? SMB_MOD_RO : SMB_MOD_RW2, &fd);
if (res != DSM_SUCCESS) {
throw utils::error::create_exception(
function_name, {
"failed to open file",
std::to_string(res),
utils::string::from_bool(read_only),
rel_path,
path_,
});
}
fd_ = fd;
read_only_ = read_only;
return true;
} catch (const std::exception &e) {
utils::error::handle_exception(function_name, e);
} catch (...) {
utils::error::handle_exception(function_name);
}
return false;
}
auto smb_file::read(unsigned char *data, std::size_t to_read,
std::uint64_t offset, std::size_t *total_read) -> bool {
FIFTHGRID_USES_FUNCTION_NAME();
try {
if (total_read != nullptr) {
(*total_read) = 0U;
}
if (not fd_.has_value()) {
throw utils::error::create_exception(function_name,
{
"failed to read file",
"file not open",
path_,
});
}
auto res = smb_fseek(session_.get(), *fd_, static_cast<off_t>(offset),
SMB_SEEK_SET);
if (res == -1) {
throw utils::error::create_exception(function_name,
{
"failed to seek file",
std::to_string(res),
std::to_string(offset),
path_,
});
}
std::size_t bytes_read{0U};
while (bytes_read != to_read) {
res = smb_fread(session_.get(), *fd_, &data[bytes_read],
to_read - bytes_read);
if (res == -1) {
throw utils::error::create_exception(function_name,
{
"failed to read file",
std::to_string(res),
std::to_string(offset),
std::to_string(to_read),
path_,
});
}
if (res == 0) {
break;
}
bytes_read += static_cast<std::size_t>(res);
}
if (total_read != nullptr) {
(*total_read) = bytes_read;
}
return true;
} catch (const std::exception &e) {
utils::error::handle_exception(function_name, e);
} catch (...) {
utils::error::handle_exception(function_name);
}
return false;
}
auto smb_file::remove() -> bool {
FIFTHGRID_USES_FUNCTION_NAME();
try {
close();
return utils::retry_action([this]() -> bool {
if (not exists()) {
return true;
}
try {
auto res = smb_tree_connect(session_.get(), share_name_.c_str(), &tid_);
if (res != DSM_SUCCESS) {
throw utils::error::create_exception(function_name,
{
"failed to connect to share",
std::to_string(res),
share_name_,
path_,
});
}
auto rel_path = smb_create_relative_path(path_);
res = smb_file_rm(session_.get(), tid_, rel_path.c_str());
if (res != DSM_SUCCESS) {
throw utils::error::create_exception(
function_name,
{
"failed to remove file",
std::to_string(res),
std::to_string(smb_session_get_nt_status(session_.get())),
rel_path,
path_,
});
}
return true;
} catch (const std::exception &e) {
utils::error::handle_exception(function_name, e);
} catch (...) {
utils::error::handle_exception(function_name);
}
return false;
});
} catch (const std::exception &e) {
utils::error::handle_exception(function_name, e);
} catch (...) {
utils::error::handle_exception(function_name);
}
return false;
}
auto smb_file::size() const -> std::optional<std::uint64_t> {
FIFTHGRID_USES_FUNCTION_NAME();
try {
if (not session_) {
throw utils::error::create_exception(function_name,
{
"session not found",
path_,
});
}
auto rel_path = smb_create_relative_path(path_);
smb_stat_t st{
smb_fstat(session_.get(), tid_, rel_path.c_str()),
smb_stat_deleter(),
};
if (not st) {
throw utils::error::create_exception(function_name,
{
"failed to stat directory",
rel_path,
path_,
});
}
return smb_stat_get(st.get(), SMB_STAT_SIZE);
} catch (const std::exception &e) {
utils::error::handle_exception(function_name, e);
} catch (...) {
utils::error::handle_exception(function_name);
}
return std::nullopt;
}
auto smb_file::truncate(std::size_t size) -> bool {
FIFTHGRID_USES_FUNCTION_NAME();
try {
throw utils::error::create_exception(function_name,
{
"failed to truncate file",
"not implemented",
std::to_string(size),
path_,
});
} catch (const std::exception &e) {
utils::error::handle_exception(function_name, e);
} catch (...) {
utils::error::handle_exception(function_name);
}
return false;
}
auto smb_file::write(const unsigned char *data, std::size_t to_write,
std::size_t offset, std::size_t *total_written) -> bool {
FIFTHGRID_USES_FUNCTION_NAME();
try {
if (total_written != nullptr) {
(*total_written) = 0U;
}
if (not fd_.has_value()) {
throw utils::error::create_exception(function_name,
{
"failed to write file",
"file not open",
path_,
});
}
auto res = smb_fseek(session_.get(), *fd_, static_cast<off_t>(offset),
SMB_SEEK_SET);
if (res == -1) {
throw utils::error::create_exception(function_name,
{
"failed to seek file",
std::to_string(res),
std::to_string(offset),
path_,
});
}
std::size_t bytes_written{0U};
while (bytes_written != to_write) {
res = smb_fwrite(session_.get(), *fd_,
const_cast<unsigned char *>(&data[bytes_written]),
to_write - bytes_written);
if (res == -1) {
throw utils::error::create_exception(function_name,
{
"failed to write file",
std::to_string(res),
std::to_string(offset),
std::to_string(to_write),
path_,
});
}
if (res == 0) {
break;
}
bytes_written += static_cast<std::size_t>(res);
}
if (total_written != nullptr) {
(*total_written) = bytes_written;
}
return true;
} catch (const std::exception &e) {
utils::error::handle_exception(function_name, e);
} catch (...) {
utils::error::handle_exception(function_name);
}
return false;
}
} // namespace fifthgrid::utils::file
#endif // defined(PROJECT_ENABLE_LIBDSM)

View File

@@ -0,0 +1,198 @@
/*
Copyright <2018-2025> <scott.e.graves@protonmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#include "utils/file_thread_file.hpp"
namespace fifthgrid::utils::file {
// auto thread_file::attach_file(native_handle handle,
// bool read_only) -> fs_file_t {}
auto thread_file::attach_file(fs_file_t file) -> fs_file_t {
return fs_file_t{
new thread_file(std::move(file)),
};
}
auto thread_file::open_file(std::string_view path,
bool read_only) -> fs_file_t {
return fs_file_t{
new thread_file(file::open_file(path, read_only)),
};
}
auto thread_file::open_or_create_file(std::string_view path,
bool read_only) -> fs_file_t {
return fs_file_t{
new thread_file(file::open_or_create_file(path, read_only)),
};
}
void thread_file::io_item::done(bool result) {
unique_mutex_lock lock(*mtx);
complete = true;
success = result;
notify->notify_all();
}
void thread_file::io_item::wait() const {
if (complete) {
return;
}
unique_mutex_lock lock(*mtx);
while (not complete) {
notify->wait(lock);
}
notify->notify_all();
}
thread_file::thread_file(std::string_view path) : file_(new file(path)) {}
thread_file::thread_file(std::wstring_view path)
: file_(new file(utils::string::to_utf8(path))) {}
thread_file::thread_file(fs_file_t file) : file_(std::move(file)) {}
thread_file::~thread_file() {
close();
if (io_thread_) {
io_thread_->join();
}
}
void thread_file::close() {
do_io([this]() -> bool {
file_->close();
stop_requested_ = true;
return true;
});
}
auto thread_file::copy_to(std::string_view new_path,
bool overwrite) const -> bool {
return do_io([this, &new_path, &overwrite]() -> bool {
return file_->copy_to(new_path, overwrite);
});
}
auto thread_file::do_io(action_t action) const -> bool {
unique_mutex_lock lock(*mtx_);
if (stop_requested_) {
return false;
}
if (not io_thread_) {
io_thread_ = std::make_unique<std::thread>([this]() { thread_func(); });
}
auto item = std::make_shared<io_item>(action);
actions_.emplace_back(item);
notify_->notify_all();
lock.unlock();
item->wait();
return item->success;
}
void thread_file::flush() const {
do_io([this]() -> bool {
file_->flush();
return true;
});
}
auto thread_file::move_to(std::string_view path) -> bool {
return do_io([this, &path]() -> bool { return file_->move_to(path); });
}
auto thread_file::read(unsigned char *data, std::size_t to_read,
std::uint64_t offset, std::size_t *total_read) -> bool {
return do_io([this, &data, &to_read, &offset, &total_read]() -> bool {
return file_->read(data, to_read, offset, total_read);
});
}
auto thread_file::remove() -> bool {
return do_io([this]() -> bool { return file_->remove(); });
}
void thread_file::thread_func() const {
unique_mutex_lock lock(*mtx_);
notify_->notify_all();
lock.unlock();
const auto run_actions = [this, &lock]() {
auto actions = actions_;
actions_.clear();
notify_->notify_all();
lock.unlock();
for (auto &&action : actions) {
action->done(action->action());
}
};
while (not stop_requested_) {
lock.lock();
if (stop_requested_) {
lock.unlock();
break;
}
while (not stop_requested_ && actions_.empty()) {
notify_->wait(lock);
}
if (stop_requested_) {
lock.unlock();
break;
}
run_actions();
}
lock.lock();
run_actions();
}
auto thread_file::truncate(std::size_t size) -> bool {
return do_io([this, &size]() -> bool { return file_->truncate(size); });
}
auto thread_file::write(const unsigned char *data, std::size_t to_write,
std::size_t offset,
std::size_t *total_written) -> bool {
return do_io([this, &data, &to_write, &offset, &total_written]() -> bool {
return file_->write(data, to_write, offset, total_written);
});
}
auto thread_file::size() const -> std::optional<std::uint64_t> {
std::optional<std::uint64_t> size;
do_io([this, &size]() -> bool {
size = file_->size();
return size.has_value();
});
return size;
}
} // namespace fifthgrid::utils::file

240
support/src/utils/hash.cpp Normal file
View File

@@ -0,0 +1,240 @@
/*
Copyright <2018-2025> <scott.e.graves@protonmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#if defined(PROJECT_ENABLE_LIBSODIUM)
#include "utils/hash.hpp"
#include "utils/error.hpp"
namespace fifthgrid::utils::hash {
auto create_hash_blake2b_32(std::string_view data) -> hash_32_t {
return create_hash_blake2b_t<hash_32_t>(
reinterpret_cast<const unsigned char *>(data.data()), data.size());
}
auto create_hash_blake2b_32(std::wstring_view data) -> hash_32_t {
return create_hash_blake2b_t<hash_32_t>(
reinterpret_cast<const unsigned char *>(data.data()),
data.size() * sizeof(wchar_t));
}
auto create_hash_blake2b_32(const data_buffer &data) -> hash_32_t {
return create_hash_blake2b_t<hash_32_t>(
reinterpret_cast<const unsigned char *>(data.data()),
data.size() * sizeof(data_buffer::value_type));
}
auto create_hash_blake2b_64(std::string_view data) -> hash_64_t {
return create_hash_blake2b_t<hash_64_t>(
reinterpret_cast<const unsigned char *>(data.data()), data.size());
}
auto create_hash_blake2b_64(std::wstring_view data) -> hash_64_t {
return create_hash_blake2b_t<hash_64_t>(
reinterpret_cast<const unsigned char *>(data.data()),
data.size() * sizeof(wchar_t));
}
auto create_hash_blake2b_64(const data_buffer &data) -> hash_64_t {
return create_hash_blake2b_t<hash_64_t>(
reinterpret_cast<const unsigned char *>(data.data()),
data.size() * sizeof(data_buffer::value_type));
}
auto create_hash_blake2b_128(std::string_view data) -> hash_128_t {
return create_hash_blake2b_t<hash_128_t>(
reinterpret_cast<const unsigned char *>(data.data()), data.size());
}
auto create_hash_blake2b_128(std::wstring_view data) -> hash_128_t {
return create_hash_blake2b_t<hash_128_t>(
reinterpret_cast<const unsigned char *>(data.data()),
data.size() * sizeof(wchar_t));
}
auto create_hash_blake2b_128(const data_buffer &data) -> hash_128_t {
return create_hash_blake2b_t<hash_128_t>(
reinterpret_cast<const unsigned char *>(data.data()),
data.size() * sizeof(data_buffer::value_type));
}
auto create_hash_blake2b_256(std::string_view data) -> hash_256_t {
return create_hash_blake2b_t<hash_256_t>(
reinterpret_cast<const unsigned char *>(data.data()), data.size());
}
auto create_hash_blake2b_256(std::wstring_view data) -> hash_256_t {
return create_hash_blake2b_t<hash_256_t>(
reinterpret_cast<const unsigned char *>(data.data()),
data.size() * sizeof(wchar_t));
}
auto create_hash_blake2b_256(const data_buffer &data) -> hash_256_t {
return create_hash_blake2b_t<hash_256_t>(
reinterpret_cast<const unsigned char *>(data.data()),
data.size() * sizeof(data_buffer::value_type));
}
auto create_hash_blake2b_384(std::string_view data) -> hash_384_t {
return create_hash_blake2b_t<hash_384_t>(
reinterpret_cast<const unsigned char *>(data.data()), data.size());
}
auto create_hash_blake2b_384(std::wstring_view data) -> hash_384_t {
return create_hash_blake2b_t<hash_384_t>(
reinterpret_cast<const unsigned char *>(data.data()),
data.size() * sizeof(wchar_t));
}
auto create_hash_blake2b_384(const data_buffer &data) -> hash_384_t {
return create_hash_blake2b_t<hash_384_t>(
reinterpret_cast<const unsigned char *>(data.data()),
data.size() * sizeof(data_buffer::value_type));
}
auto create_hash_blake2b_512(std::string_view data) -> hash_512_t {
return create_hash_blake2b_t<hash_512_t>(
reinterpret_cast<const unsigned char *>(data.data()), data.size());
}
auto create_hash_blake2b_512(std::wstring_view data) -> hash_512_t {
return create_hash_blake2b_t<hash_512_t>(
reinterpret_cast<const unsigned char *>(data.data()),
data.size() * sizeof(wchar_t));
}
auto create_hash_blake2b_512(const data_buffer &data) -> hash_512_t {
return create_hash_blake2b_t<hash_512_t>(
reinterpret_cast<const unsigned char *>(data.data()),
data.size() * sizeof(data_buffer::value_type));
}
auto create_hash_sha256(std::string_view data) -> hash_256_t {
return create_hash_sha256(
reinterpret_cast<const unsigned char *>(data.data()), data.size());
}
auto create_hash_sha256(std::wstring_view data) -> hash_256_t {
return create_hash_sha256(
reinterpret_cast<const unsigned char *>(data.data()),
data.size() * sizeof(wchar_t));
}
auto create_hash_sha256(const data_buffer &data) -> hash_256_t {
return create_hash_sha256(
reinterpret_cast<const unsigned char *>(data.data()),
data.size() * sizeof(data_buffer::value_type));
}
auto create_hash_sha512(std::string_view data) -> hash_512_t {
return create_hash_sha512(
reinterpret_cast<const unsigned char *>(data.data()), data.size());
}
auto create_hash_sha512(std::wstring_view data) -> hash_512_t {
return create_hash_sha512(
reinterpret_cast<const unsigned char *>(data.data()),
data.size() * sizeof(wchar_t));
}
auto create_hash_sha512(const data_buffer &data) -> hash_512_t {
return create_hash_sha512(
reinterpret_cast<const unsigned char *>(data.data()),
data.size() * sizeof(data_buffer::value_type));
}
auto create_hash_sha512(const unsigned char *data, std::size_t data_size)
-> hash_512_t {
FIFTHGRID_USES_FUNCTION_NAME();
hash_512_t hash{};
crypto_hash_sha512_state state{};
auto res = crypto_hash_sha512_init(&state);
if (res != 0) {
throw utils::error::create_exception(function_name,
{
"failed to initialize sha-512",
std::to_string(res),
});
}
res = crypto_hash_sha512_update(&state, data, data_size);
if (res != 0) {
throw utils::error::create_exception(function_name,
{
"failed to update sha-512",
std::to_string(res),
});
}
res = crypto_hash_sha512_final(&state, hash.data());
if (res != 0) {
throw utils::error::create_exception(function_name,
{
"failed to finalize sha-512",
std::to_string(res),
});
}
return hash;
}
auto create_hash_sha256(const unsigned char *data, std::size_t data_size)
-> hash_256_t {
FIFTHGRID_USES_FUNCTION_NAME();
hash_256_t hash{};
crypto_hash_sha256_state state{};
auto res = crypto_hash_sha256_init(&state);
if (res != 0) {
throw utils::error::create_exception(function_name,
{
"failed to initialize sha-256",
std::to_string(res),
});
}
res = crypto_hash_sha256_update(&state, data, data_size);
if (res != 0) {
throw utils::error::create_exception(function_name,
{
"failed to update sha-256",
std::to_string(res),
});
}
res = crypto_hash_sha256_final(&state, hash.data());
if (res != 0) {
throw utils::error::create_exception(function_name,
{
"failed to finalize sha-256",
std::to_string(res),
});
}
return hash;
}
} // namespace fifthgrid::utils::hash
#endif // defined(PROJECT_ENABLE_LIBSODIUM)

315
support/src/utils/path.cpp Normal file
View File

@@ -0,0 +1,315 @@
/*
Copyright <2018-2025> <scott.e.graves@protonmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#include "utils/path.hpp"
#include "utils/common.hpp"
#include "utils/error.hpp"
#include "utils/file.hpp"
#include "utils/string.hpp"
#include "utils/unix.hpp"
namespace {
[[nodiscard]] auto resolve(std::string path) -> std::string {
FIFTHGRID_USES_FUNCTION_NAME();
#if defined(_WIN32)
if (fifthgrid::utils::string::contains(path, "~\\")) {
fifthgrid::utils::string::replace(path, "~\\", "%USERPROFILE%\\");
}
if (fifthgrid::utils::string::contains(path, "~/")) {
fifthgrid::utils::string::replace(path, "~/", "%USERPROFILE%\\");
}
if (fifthgrid::utils::string::contains(path, "%")) {
auto size = ::ExpandEnvironmentStringsA(path.c_str(), nullptr, 0);
std::string dest;
dest.resize(size);
::ExpandEnvironmentStringsA(path.c_str(), dest.data(),
static_cast<DWORD>(dest.size()));
path = dest.c_str();
}
#else // !defined (_WIN32)
if (fifthgrid::utils::string::contains(path, "~\\")) {
fifthgrid::utils::string::replace(path, "~\\", "~/");
}
if (fifthgrid::utils::string::contains(path, "~/")) {
std::string home{};
auto res =
fifthgrid::utils::use_getpwuid(getuid(), [&home](struct passwd *pw) {
home = (pw->pw_dir ? pw->pw_dir : "");
if (home.empty() ||
((home == fifthgrid::utils::path::slash) && (getuid() != 0))) {
home = fifthgrid::utils::path::combine("/home", {pw->pw_name});
}
});
if (not res) {
throw fifthgrid::utils::error::create_exception(function_name,
{
"failed to getpwuid",
res.reason,
});
}
path = fifthgrid::utils::string::replace(path, "~/", home + "/");
}
#endif // defined (_WIN32)
return fifthgrid::utils::path::finalize(path);
}
} // namespace
namespace fifthgrid::utils::path {
auto absolute(std::string_view path) -> std::string {
std::string abs_path{path};
if (abs_path.empty()) {
return abs_path;
}
abs_path = resolve(abs_path);
#if defined(_WIN32)
if (not utils::string::contains(abs_path, dot)) {
return abs_path;
}
std::string temp;
temp.resize(fifthgrid::max_path_length + 1U);
::GetFullPathNameA(abs_path.c_str(), static_cast<DWORD>(temp.size()),
temp.data(), nullptr);
#else // !defined(_WIN32)
if (not utils::string::contains(abs_path, dot) ||
utils::string::begins_with(abs_path, slash)) {
return abs_path;
}
auto found{false};
std::string tmp{abs_path};
do {
auto *res = realpath(tmp.c_str(), nullptr);
if (res != nullptr) {
abs_path =
res + std::string{directory_seperator} + abs_path.substr(tmp.size());
free(res);
found = true;
} else if (tmp == dot) {
found = true;
} else {
tmp = dirname(tmp.data());
}
} while (not found);
#endif // defined(_WIN32)
return finalize(abs_path);
}
auto absolute(std::wstring_view path) -> std::wstring {
return utils::string::from_utf8(absolute(utils::string::to_utf8(path)));
}
auto exists(std::string_view path) -> bool {
return utils::file::file{path}.exists() ||
utils::file::directory{path}.exists();
}
auto exists(std::wstring_view path) -> bool {
return exists(utils::string::to_utf8(path));
}
auto find_program_in_path(const std::string &name_without_extension)
-> std::string {
static std::mutex mtx{};
static std::unordered_map<std::string, std::string> found_items{};
mutex_lock lock(mtx);
if (found_items.contains(name_without_extension)) {
return found_items.at(name_without_extension);
}
auto path = utils::get_environment_variable("PATH");
if (path.empty()) {
return path;
}
#if defined(_WIN32)
static constexpr std::array<std::string_view, 4U> extension_list{
".bat",
".cmd",
".exe",
".ps1",
};
static constexpr auto split_char = ';';
#else // !defined(_WIN32)
static constexpr std::array<std::string_view, 2U> extension_list{
"",
".sh",
};
static constexpr auto split_char = ':';
#endif // defined(_WIN32)
auto search_path_list = utils::string::split(path, split_char, false);
for (auto &&search_path : search_path_list) {
for (auto &&extension : extension_list) {
auto exec_path = combine(
search_path, {name_without_extension + std::string{extension}});
if (utils::file::file(exec_path).exists()) {
found_items[name_without_extension] = exec_path;
return exec_path;
}
}
}
return "";
}
[[nodiscard]] auto
find_program_in_path(std::wstring_view name_without_extension) -> std::wstring {
return utils::string::from_utf8(
find_program_in_path(utils::string::to_utf8(name_without_extension)));
}
auto get_parent_path(std::string_view path) -> std::string {
auto abs_path = absolute(path);
#if defined(_WIN32)
::PathRemoveFileSpecA(abs_path.data());
abs_path = abs_path.c_str();
#else // !defined(_WIN32)
abs_path = std::filesystem::path{abs_path}.parent_path().string();
#endif // defined(_WIN32)
return finalize(abs_path);
}
auto get_parent_path(std::wstring_view path) -> std::wstring {
return utils::string::from_utf8(
get_parent_path(utils::string::to_utf8(path)));
}
auto get_relative_path(std::string_view path, std::string_view root_path)
-> std::string {
auto abs_path = absolute(path);
auto abs_root_path =
absolute(root_path) + std::string{get_directory_seperator<char>()};
#if defined(_WIN32)
if (utils::string::to_lower(abs_path).starts_with(
utils::string::to_lower(abs_root_path))) {
#else // !defined(_WIN32)
if (abs_path.starts_with(abs_root_path)) {
#endif // defined(_WIN32)
return abs_path.substr(abs_root_path.size());
}
return abs_path;
}
auto get_relative_path(std::wstring_view path, std::wstring_view root_path)
-> std::wstring {
return utils::string::from_utf8(get_relative_path(
utils::string::to_utf8(path), utils::string::to_utf8(root_path)));
}
auto contains_trash_directory(std::string_view path) -> bool {
auto parts = utils::string::split(utils::string::to_lower(absolute(path)),
get_directory_seperator<char>(), false);
return std::find_if(parts.begin(), parts.end(), [](auto &&part) -> bool {
return utils::string::begins_with(part, ".trash-") ||
part == ".trashes" || part == "$recycle.bin";
}) != parts.end();
}
auto contains_trash_directory(std::wstring_view path) -> bool {
return contains_trash_directory(utils::string::to_utf8(path));
}
auto make_file_uri(std::string_view path) -> std::string {
auto abs_path = absolute(path);
#if defined(_WIN32)
utils::string::replace(abs_path, backslash, slash);
abs_path = std::string{slash} + abs_path;
#endif // defined(_WIN32)
return "file://" + abs_path;
}
auto make_file_uri(std::wstring_view path) -> std::wstring {
return utils::string::from_utf8(make_file_uri(utils::string::to_utf8(path)));
}
auto strip_to_file_name(std::string path) -> std::string {
if (path == "." || path == "..") {
return path;
}
#if defined(_WIN32)
return ::PathFindFileNameA(path.c_str());
#else // !defined(_WIN32)
return utils::string::contains(path, slash) ? basename(path.data()) : path;
#endif // defined(_WIN32)
}
auto strip_to_file_name(std::wstring path) -> std::wstring {
return utils::string::from_utf8(
strip_to_file_name(utils::string::to_utf8(path)));
}
auto unmake_file_uri(std::string_view uri) -> std::string {
static constexpr std::array<std::string_view, 23U> escape_characters = {
{
" ", "<", ">", "#", "%", "+", "{", "}", "|", "\\", "^", "~",
"[", "]", "`", ";", "/", "?", ":", "@", "=", "&", "$",
}};
static constexpr std::array<std::string_view, 23U>
escape_sequences_lower = {{
"%20", "%3c", "%3e", "%23", "%25", "%2b", "%7b", "%7d",
"%7c", "%5c", "%5e", "%7e", "%5b", "%5d", "%60", "%3b",
"%2f", "%3f", "%3a", "%40", "%3d", "%26", "%24",
}};
static constexpr std::array<std::string_view, 23U>
escape_sequences_upper = {{
"%20", "%3C", "%3E", "%23", "%25", "%2B", "%7B", "%7D",
"%7C", "%5C", "%5E", "%7E", "%5B", "%5D", "%60", "%3B",
"%2F", "%3F", "%3A", "%40", "%3D", "%26", "%24",
}};
std::string ret_uri{uri};
#if defined(_WIN32)
ret_uri = ret_uri.substr(8U);
#else
ret_uri = ret_uri.substr(7U);
#endif
for (std::size_t idx = 0U; idx < escape_characters.size(); idx++) {
utils::string::replace(ret_uri, escape_sequences_lower.at(idx),
escape_characters.at(idx));
utils::string::replace(ret_uri, escape_sequences_upper.at(idx),
escape_characters.at(idx));
}
return absolute(ret_uri);
}
auto unmake_file_uri(std::wstring_view uri) -> std::wstring {
return utils::string::from_utf8(unmake_file_uri(utils::string::to_utf8(uri)));
}
} // namespace fifthgrid::utils::path

View File

@@ -0,0 +1,219 @@
/*
Copyright <2018-2025> <scott.e.graves@protonmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#include "utils/string.hpp"
namespace fifthgrid::utils::string {
auto from_bool(bool val) -> std::string {
return std::to_string(static_cast<int>(val));
}
#if defined(PROJECT_ENABLE_BOOST)
auto from_dynamic_bitset(const boost::dynamic_bitset<> &bitset) -> std::string {
std::stringstream stream;
boost::archive::text_oarchive archive(stream);
archive << bitset;
return stream.str();
}
#endif // defined(PROJECT_ENABLE_BOOST)
auto from_utf8(std::string_view str) -> std::wstring {
if (str.empty()) {
return L"";
}
std::wstring out;
const auto *str_ptr = reinterpret_cast<const std::uint8_t *>(str.data());
std::int32_t idx{};
auto len{static_cast<std::int32_t>(str.size())};
#if WCHAR_MAX <= 0xFFFF
out.reserve((str.size() + 1U) / 2U);
while (idx < len) {
UChar32 uni_ch{};
U8_NEXT(str_ptr, idx, len, uni_ch);
if (uni_ch < 0 || not U_IS_UNICODE_CHAR(uni_ch)) {
throw std::runtime_error("from_utf8: invalid UTF-8 sequence");
}
std::array<UChar, 2U> units{};
std::int32_t off{};
auto err{false};
U16_APPEND(units.data(), off, 2, uni_ch, err);
if (err || off <= 0) {
throw std::runtime_error("from_utf8: U16_APPEND failed");
}
out.push_back(static_cast<wchar_t>(units[0U]));
if (off == 2) {
out.push_back(static_cast<wchar_t>(units[1U]));
}
}
#else // WCHAR_MAX > 0xFFFF
out.reserve(str.size());
while (idx < len) {
UChar32 uni_ch{};
U8_NEXT(str_ptr, idx, len, uni_ch);
if (uni_ch < 0 || not U_IS_UNICODE_CHAR(uni_ch)) {
throw std::runtime_error("from_utf8: invalid UTF-8 sequence");
}
out.push_back(static_cast<wchar_t>(uni_ch));
}
#endif // WCHAR_MAX <= 0xFFFF
return out;
}
#if defined(PROJECT_ENABLE_SFML)
auto replace_sf(sf::String &src, const sf::String &find, const sf::String &with,
std::size_t start_pos) -> sf::String & {
if (not src.isEmpty() && (start_pos < src.getSize())) {
while ((start_pos = src.find(find, start_pos)) != std::string::npos) {
src.replace(start_pos, find.getSize(), with);
start_pos += with.getSize();
}
}
return src;
}
auto split_sf(sf::String str, wchar_t delim, bool should_trim)
-> std::vector<sf::String> {
auto result = std::views::split(str.toWideString(), delim);
std::vector<sf::String> ret{};
for (auto &&word : result) {
auto val = std::wstring{word.begin(), word.end()};
if (should_trim) {
trim(val);
}
ret.emplace_back(val);
}
return ret;
}
#endif // defined(PROJECT_ENABLE_SFML)
auto to_bool(std::string val) -> bool {
auto ret = false;
trim(val);
if (is_numeric(val)) {
if (contains(val, ".")) {
ret = (to_double(val) != 0.0);
} else {
std::istringstream(val) >> ret;
}
} else {
std::istringstream(to_lower(val)) >> std::boolalpha >> ret;
}
return ret;
}
auto to_double(const std::string &str) -> double { return std::stod(str); }
#if defined(PROJECT_ENABLE_BOOST)
auto to_dynamic_bitset(const std::string &val) -> boost::dynamic_bitset<> {
std::stringstream stream(val);
boost::dynamic_bitset<> bitset;
boost::archive::text_iarchive archive(stream);
archive >> bitset;
return bitset;
}
#endif // defined(PROJECT_ENABLE_BOOST)
auto to_int32(const std::string &val) -> std::int32_t { return std::stoi(val); }
auto to_int64(const std::string &val) -> std::int64_t {
return std::stoll(val);
}
auto to_size_t(const std::string &val) -> std::size_t {
return static_cast<std::size_t>(std::stoull(val));
}
auto to_uint8(const std::string &val) -> std::uint8_t {
return static_cast<std::uint8_t>(std::stoul(val));
}
auto to_uint16(const std::string &val) -> std::uint16_t {
return static_cast<std::uint16_t>(std::stoul(val));
}
auto to_uint32(const std::string &val) -> std::uint32_t {
return static_cast<std::uint32_t>(std::stoul(val));
}
auto to_uint64(const std::string &val) -> std::uint64_t {
return std::stoull(val);
}
auto to_utf8(std::string_view str) -> std::string { return std::string{str}; }
auto to_utf8(std::wstring_view str) -> std::string {
if (str.empty()) {
return "";
}
std::string out;
out.reserve(static_cast<size_t>(str.size()) * 4);
#if WCHAR_MAX <= 0xFFFF
const auto *u16 = reinterpret_cast<const UChar *>(str.data());
std::int32_t idx{};
auto len{static_cast<int32_t>(str.size())};
while (idx < len) {
UChar32 uni_ch{};
U16_NEXT(u16, idx, len, uni_ch);
if (uni_ch < 0 || not U_IS_UNICODE_CHAR(uni_ch)) {
throw std::runtime_error("to_utf8: invalid UTF-16 sequence");
}
std::array<std::uint8_t, U8_MAX_LENGTH> buf{};
std::int32_t off{0};
auto err{false};
U8_APPEND(buf, off, U8_MAX_LENGTH, uni_ch, err);
if (err || off <= 0) {
throw std::runtime_error("to_utf8: U8_APPEND failed");
}
out.append(reinterpret_cast<const char *>(buf.data()),
static_cast<std::size_t>(off));
}
#else // WCHAR_MAX > 0xFFFF
for (const auto &cur_ch : str) {
auto uni_char{static_cast<UChar32>(cur_ch)};
if (not U_IS_UNICODE_CHAR(uni_char)) {
throw std::runtime_error("to_utf8: invalid Unicode scalar value");
}
std::array<std::uint8_t, U8_MAX_LENGTH> buf{};
std::int32_t off{0};
auto err{false};
U8_APPEND(buf, off, U8_MAX_LENGTH, uni_char, err);
if (err || off <= 0) {
throw std::runtime_error("to_utf8: U8_APPEND failed");
}
out.append(reinterpret_cast<const char *>(buf.data()),
static_cast<std::size_t>(off));
}
#endif // WCHAR_MAX <= 0xFFFF
return out;
}
} // namespace fifthgrid::utils::string

View File

@@ -0,0 +1,88 @@
/*
Copyright <2018-2025> <scott.e.graves@protonmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#include "utils/time.hpp"
namespace fifthgrid::utils::time {
void get_local_time_now(struct tm &local_time) {
std::memset(&local_time, 0, sizeof(local_time));
const auto now =
std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
#if defined(_WIN32)
localtime_s(&local_time, &now);
#else // !defined(_WIN32)
localtime_r(&now, &local_time);
#endif // defined(_WIN32)
}
auto get_time_now() -> std::uint64_t {
return static_cast<std::uint64_t>(
std::chrono::duration_cast<std::chrono::nanoseconds>(
std::chrono::system_clock::now().time_since_epoch())
.count());
}
#if defined(_WIN32)
// https://stackoverflow.com/questions/321849/strptime-equivalent-on-windows
auto strptime(const char *s, const char *f, struct tm *tm) -> const char * {
std::istringstream input{s};
input.imbue(std::locale(setlocale(LC_ALL, nullptr)));
input >> std::get_time(tm, f);
if (input.fail()) {
return nullptr;
}
return reinterpret_cast<const char *>(s + input.tellg());
}
// https://www.frenk.com/2009/12/convert-filetime-to-unix-timestamp/
auto unix_time_to_filetime(std::uint64_t unix_time) -> FILETIME {
auto win_time = unix_time_to_windows_time(unix_time);
FILETIME file_time{};
file_time.dwHighDateTime = static_cast<DWORD>(win_time >> 32U);
file_time.dwLowDateTime = win_time & 0xFFFFFFFF;
return file_time;
}
auto windows_file_time_to_unix_time(FILETIME win_time) -> std::uint64_t {
return windows_time_to_unix_time(
(static_cast<std::uint64_t>(win_time.dwHighDateTime) << 32ULL) |
static_cast<std::uint64_t>(win_time.dwLowDateTime));
}
auto windows_time_t_to_unix_time(__time64_t win_time) -> std::uint64_t {
return static_cast<std::uint64_t>(
std::chrono::duration_cast<std::chrono::nanoseconds>(
std::chrono::system_clock::from_time_t(win_time).time_since_epoch())
.count());
}
#endif // defined(_WIN32)
auto unix_time_to_windows_time(std::uint64_t unix_time) -> std::uint64_t {
return (unix_time / WIN32_TIME_NANOS_PER_TICK) + WIN32_TIME_CONVERSION;
}
auto windows_time_to_unix_time(std::uint64_t win_time) -> std::uint64_t {
return (win_time - WIN32_TIME_CONVERSION) * WIN32_TIME_NANOS_PER_TICK;
}
} // namespace fifthgrid::utils::time

View File

@@ -0,0 +1,82 @@
/*
Copyright <2018-2025> <scott.e.graves@protonmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#include "utils/timeout.hpp"
namespace fifthgrid::utils {
timeout::timeout(callback_t timeout_callback,
std::chrono::system_clock::duration duration)
: duration_(duration),
timeout_callback_(std::move(timeout_callback)),
timeout_killed_(duration <= std::chrono::system_clock::duration::zero()) {
if (timeout_killed_) {
return;
}
timeout_thread_ = std::make_unique<std::thread>([this]() {
std::unique_lock<std::mutex> loc_lock(timeout_mutex_);
while (not timeout_killed_) {
auto res = timeout_notify_.wait_for(loc_lock, duration_);
if (res != std::cv_status::timeout) {
continue;
}
if (timeout_killed_) {
return;
}
timeout_killed_ = true;
loc_lock.unlock();
try {
timeout_callback_();
} catch (...) {
}
return;
}
});
}
timeout::~timeout() { disable(); }
void timeout::disable() {
unique_mutex_lock lock(timeout_mutex_);
std::unique_ptr<std::thread> timeout_thread{nullptr};
std::swap(timeout_thread, timeout_thread_);
if (not timeout_thread) {
timeout_notify_.notify_all();
return;
}
timeout_killed_ = true;
timeout_notify_.notify_all();
lock.unlock();
timeout_thread->join();
}
void timeout::reset() {
mutex_lock lock(timeout_mutex_);
timeout_notify_.notify_all();
}
} // namespace fifthgrid::utils

424
support/src/utils/unix.cpp Normal file
View File

@@ -0,0 +1,424 @@
/*
Copyright <2018-2025> <scott.e.graves@protonmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#if !defined(_WIN32)
#include "utils/unix.hpp"
#include "utils/collection.hpp"
#include "utils/config.hpp"
#include "utils/error.hpp"
#include "utils/file.hpp"
#include "utils/path.hpp"
namespace {
[[nodiscard]] auto get_group_list(auto *pass) -> std::vector<gid_t> {
FIFTHGRID_USES_FUNCTION_NAME();
std::vector<gid_t> groups{};
#if defined(__APPLE__)
constexpr int buffer_count{8};
constexpr int max_group_count{1024};
groups.resize(buffer_count);
std::size_t orig_count{0U};
while (true) {
auto group_count{static_cast<int>(groups.size())};
if (group_count > max_group_count) {
fifthgrid::utils::error::handle_error(function_name,
"group list has too many groups");
break;
}
auto res{
getgrouplist(pass->pw_name, static_cast<int>(pass->pw_gid),
reinterpret_cast<int *>(groups.data()), &group_count),
};
if (res < 0) {
if (orig_count == 0U) {
fifthgrid::utils::error::handle_error(
function_name, std::string{"failed to get group list|error|"} +
std::to_string(errno));
}
break;
}
groups.resize(static_cast<std::size_t>(group_count));
if (groups.size() == orig_count) {
break;
}
orig_count = groups.size();
}
#else // !defined(__APPLE__)
int group_count{};
auto res = getgrouplist(pass->pw_name, pass->pw_gid, nullptr, &group_count);
if (res >= 0) {
fifthgrid::utils::error::handle_error(
function_name, std::string{"failed to get group list count|error|"} +
std::to_string(errno));
}
groups.resize(static_cast<std::size_t>(group_count));
res = getgrouplist(pass->pw_name, pass->pw_gid, groups.data(), &group_count);
if (res >= 0) {
fifthgrid::utils::error::handle_error(
function_name,
std::string{"failed to get group list|error|"} + std::to_string(errno));
}
#endif // !defined(__APPLE__)
return groups;
}
#if defined(__linux__)
[[nodiscard]] auto sanitize_basename(std::string_view app_name) -> std::string {
std::string out;
out.reserve(app_name.size());
for (const auto &cur_ch : app_name) {
if ((cur_ch >= 'a' && cur_ch <= 'z') || (cur_ch >= '0' && cur_ch <= '9') ||
(cur_ch == '-' || cur_ch == '_')) {
out.push_back(cur_ch);
} else if (cur_ch >= 'A' && cur_ch <= 'Z') {
out.push_back(static_cast<char>(cur_ch - 'A' + 'a'));
} else {
out.push_back('-'); // replace spaces/symbols
}
}
std::string collapsed;
collapsed.reserve(out.size());
bool prev_dash = false;
for (const auto &cur_ch : out) {
if (cur_ch == '-') {
if (not prev_dash) {
collapsed.push_back(cur_ch);
}
prev_dash = true;
} else {
collapsed.push_back(cur_ch);
prev_dash = false;
}
}
if (collapsed.empty()) {
collapsed = "app";
}
return collapsed;
}
[[nodiscard]] auto get_autostart_dir() -> std::string {
auto config = fifthgrid::utils::get_environment_variable("XDG_CONFIG_HOME");
if (config.empty()) {
config = fifthgrid::utils::path::combine(
fifthgrid::utils::get_environment_variable("HOME"), {".config"});
}
return fifthgrid::utils::path::combine(config, {"autostart"});
}
[[nodiscard]] auto desktop_file_path_for(std::string_view app_name)
-> std::string {
return fifthgrid::utils::path::combine(
get_autostart_dir(), {
sanitize_basename(app_name) + ".desktop",
});
}
[[nodiscard]] auto join_args_for_exec(const std::vector<std::string> &args)
-> std::string {
std::string str;
for (const auto &arg : args) {
if (not str.empty()) {
str += ' ';
}
auto needs_quotes = arg.find_first_of(" \t\"'\\$`") != std::string::npos;
if (needs_quotes) {
str += '"';
for (const auto &cur_ch : arg) {
if (cur_ch == '"' || cur_ch == '\\') {
str += '\\';
}
str += cur_ch;
}
str += '"';
} else {
str += arg;
}
}
return str;
}
#endif // defined(__linux__)
} // namespace
namespace fifthgrid::utils {
#if !defined(__APPLE__)
auto convert_to_uint64(const pthread_t &thread) -> std::uint64_t {
return static_cast<std::uint64_t>(thread);
}
#endif // !defined(__APPLE__)
#if defined(__linux__)
auto create_autostart_entry(const autostart_cfg &cfg, bool overwrite_existing)
-> bool {
FIFTHGRID_USES_FUNCTION_NAME();
auto file = desktop_file_path_for(cfg.app_name);
if (utils::file::file{file}.exists() && not overwrite_existing) {
return true;
}
auto dir = get_autostart_dir();
if (dir.empty()) {
return false;
}
if (not utils::file::directory{dir}.create_directory()) {
return false;
}
auto exec_line = cfg.exec_path;
if (not cfg.exec_args.empty()) {
exec_line += ' ';
exec_line += join_args_for_exec(cfg.exec_args);
}
std::ofstream out(file, std::ios::binary | std::ios::trunc);
if (not out) {
return false;
}
out << "[Desktop Entry]\n";
out << "Type=Application\n";
out << "Version=1.0\n";
out << "Name=" << cfg.app_name << "\n";
out << "Exec=" << exec_line << "\n";
out << "Terminal=" << (cfg.terminal ? "true" : "false") << "\n";
if (cfg.comment && not cfg.comment->empty()) {
out << "Comment=" << *cfg.comment << "\n";
}
if (cfg.icon_path && not cfg.icon_path->empty()) {
out << "Icon=" << *cfg.icon_path << "\n";
}
if (not cfg.only_show_in.empty()) {
out << "OnlyShowIn=";
for (std::size_t idx = 0U; idx < cfg.only_show_in.size(); ++idx) {
if (idx != 0U) {
out << ';';
}
out << cfg.only_show_in[idx];
}
out << ";\n";
}
if (not cfg.enabled) {
out << "X-GNOME-Autostart-enabled=false\n";
out << "Hidden=true\n";
}
out.flush();
if (not out) {
return false;
}
#if defined(__linux__) || defined(__APPLE__)
chmod(file.c_str(), 0644);
#endif // defined(__linux__) || defined(__APPLE__)
return true;
}
#endif // defined(__linux__)
auto get_last_error_code() -> int { return errno; }
auto get_thread_id() -> std::uint64_t {
return convert_to_uint64(pthread_self());
}
auto is_uid_member_of_group(uid_t uid, gid_t gid) -> bool {
FIFTHGRID_USES_FUNCTION_NAME();
std::vector<gid_t> groups{};
auto res = use_getpwuid(
uid, [&groups](struct passwd *pass) { groups = get_group_list(pass); });
if (not res) {
throw utils::error::create_exception(res.function_name,
{"use_getpwuid failed", res.reason});
}
return collection::includes(groups, gid);
}
#if defined(__linux__)
auto remove_autostart_entry(std::string_view name) -> bool {
FIFTHGRID_USES_FUNCTION_NAME();
auto file = desktop_file_path_for(name);
if (not utils::file::file{file}.exists()) {
return true;
}
return utils::file::file{file}.remove();
}
#endif // defined(__linux__)
auto use_getpwuid(uid_t uid, passwd_callback_t callback) -> result {
FIFTHGRID_USES_FUNCTION_NAME();
static std::mutex mtx{};
mutex_lock lock{mtx};
auto *temp_pw = getpwuid(uid);
if (temp_pw == nullptr) {
return {
.function_name = std::string{function_name},
.ok = false,
.reason = "'getpwuid' returned nullptr",
};
}
callback(temp_pw);
return {
.function_name = std::string{function_name},
};
}
#if defined(__APPLE__)
#if defined(PROJECT_ENABLE_PUGIXML)
auto generate_launchd_plist(const plist_cfg &cfg, bool overwrite_existing)
-> bool {
auto file = utils::path::combine(cfg.plist_path, {cfg.label + ".plist"});
if (utils::file::file{file}.exists() && not overwrite_existing) {
return true;
}
pugi::xml_document doc;
auto decl = doc.append_child(pugi::node_declaration);
decl.append_attribute("version") = "1.0";
decl.append_attribute("encoding") = "UTF-8";
auto plist = doc.append_child("plist");
plist.append_attribute("version") = "1.0";
auto dict = plist.append_child("dict");
dict.append_child("key").text().set("Label");
dict.append_child("string").text().set(cfg.label.c_str());
dict.append_child("key").text().set("ProgramArguments");
auto array = dict.append_child("array");
for (const auto &arg : cfg.args) {
array.append_child("string").text().set(arg.c_str());
}
dict.append_child("key").text().set("EnvironmentVariables");
pugi::xml_node env_dict = dict.append_child("dict");
if (not utils::get_environment_variable("PROJECT_TEST_CONFIG_DIR").empty()) {
env_dict.append_child("key").text().set("PROJECT_TEST_CONFIG_DIR");
env_dict.append_child("string").text().set(
utils::get_environment_variable("PROJECT_TEST_CONFIG_DIR"));
}
if (not utils::get_environment_variable("PROJECT_TEST_INPUT_DIR").empty()) {
env_dict.append_child("key").text().set("PROJECT_TEST_INPUT_DIR");
env_dict.append_child("string").text().set(
utils::get_environment_variable("PROJECT_TEST_INPUT_DIR"));
}
dict.append_child("key").text().set("WorkingDirectory");
dict.append_child("string").text().set(cfg.working_dir.c_str());
dict.append_child("key").text().set("KeepAlive");
dict.append_child(cfg.keep_alive ? "true" : "false");
dict.append_child("key").text().set("RunAtLoad");
dict.append_child(cfg.run_at_load ? "true" : "false");
dict.append_child("key").text().set("StandardOutPath");
dict.append_child("string").text().set(cfg.stdout_log.c_str());
dict.append_child("key").text().set("StandardErrorPath");
dict.append_child("string").text().set(cfg.stderr_log.c_str());
return doc.save_file(file.c_str(), " ",
pugi::format_indent | pugi::format_write_bom);
}
#endif // defined(PROJECT_ENABLE_PUGIXML)
#if defined(PROJECT_ENABLE_SPDLOG) || defined(PROJECT_ENABLE_FMT)
auto launchctl_command(std::string_view label, launchctl_type type) -> int {
switch (type) {
case launchctl_type::bootout:
return system(
fmt::format("launchctl bootout gui/{} '{}' 1>/dev/null 2>&1", getuid(),
utils::path::combine("~",
{
"/Library/LaunchAgents",
fmt::format("{}.plist", label),
}))
.c_str());
case launchctl_type::bootstrap:
return system(
fmt::format("launchctl bootstrap gui/{} '{}' 1>/dev/null 2>&1",
getuid(),
utils::path::combine("~",
{
"/Library/LaunchAgents",
fmt::format("{}.plist", label),
}))
.c_str());
case launchctl_type::kickstart:
return system(
fmt::format("launchctl kickstart gui/{}/{}", getuid(), label).c_str());
}
return -1;
}
auto remove_launchd_plist(std::string_view plist_path, std::string_view label,
bool should_bootout) -> bool {
auto file = utils::file::file{
utils::path::combine(plist_path, {std::string{label} + ".plist"}),
};
if (not file.exists()) {
return true;
}
auto res =
should_bootout ? launchctl_command(label, launchctl_type::bootout) : 0;
if (not file.remove()) {
return false;
}
return res == 0;
}
#endif // defined(PROJECT_ENABLE_SPDLOG) || defined(PROJECT_ENABLE_FMT)
#endif // defined(__APPLE__)
} // namespace fifthgrid::utils
#endif // !defined(_WIN32)

View File

@@ -0,0 +1,361 @@
/*
Copyright <2018-2025> <scott.e.graves@protonmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#if defined(_WIN32)
#include "utils/windows.hpp"
#include "utils/com_init_wrapper.hpp"
#include "utils/error.hpp"
#include "utils/file.hpp"
#include "utils/path.hpp"
#include "utils/string.hpp"
namespace {
constexpr std::array<std::string_view, 26U> drive_letters{
"a:", "b:", "c:", "d:", "e:", "f:", "g:", "h:", "i:",
"j:", "k:", "l:", "m:", "n:", "o:", "p:", "q:", "r:",
"s:", "t:", "u:", "v:", "w:", "x:", "y:", "z:",
};
}
namespace fifthgrid::utils {
void create_console() {
if (::AllocConsole() == 0) {
return;
}
FILE *dummy{nullptr};
freopen_s(&dummy, "CONOUT$", "w", stdout);
freopen_s(&dummy, "CONOUT$", "w", stderr);
freopen_s(&dummy, "CONIN$", "r", stdin);
std::cout.clear();
std::clog.clear();
std::cerr.clear();
std::cin.clear();
auto *out_w = CreateFileW(L"CONOUT$", GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr,
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
auto *in_w = CreateFileW(L"CONIN$", GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr,
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
SetStdHandle(STD_OUTPUT_HANDLE, out_w);
SetStdHandle(STD_ERROR_HANDLE, out_w);
SetStdHandle(STD_INPUT_HANDLE, in_w);
std::wcout.clear();
std::wclog.clear();
std::wcerr.clear();
std::wcin.clear();
}
void free_console() { ::FreeConsole(); }
auto get_available_drive_letter(char first) -> std::optional<std::string_view> {
const auto *begin = std::ranges::find_if(
drive_letters, [first](auto &&val) { return val.at(0U) == first; });
if (begin == drive_letters.end()) {
begin = drive_letters.begin();
}
auto available =
std::ranges::find_if(begin, drive_letters.end(), [](auto &&val) -> bool {
return not utils::file::directory{utils::path::combine(val, {"\\"})}
.exists();
});
if (available == drive_letters.end()) {
return std::nullopt;
}
return *available;
}
auto get_available_drive_letters(char first) -> std::vector<std::string_view> {
const auto *begin =
std::ranges::find_if(drive_letters, [first](auto &&val) -> bool {
return val.at(0U) == first;
});
if (begin == drive_letters.end()) {
begin = drive_letters.begin();
}
return std::accumulate(
begin, drive_letters.end(), std::vector<std::string_view>(),
[](auto &&vec, auto &&letter) -> auto {
if (utils::file::directory{utils::path::combine(letter, {"\\"})}
.exists()) {
return vec;
}
vec.emplace_back(letter);
return vec;
});
}
auto get_last_error_code() -> DWORD { return ::GetLastError(); }
auto get_local_app_data_directory() -> const std::string & {
FIFTHGRID_USES_FUNCTION_NAME();
[[maybe_unused]] thread_local const utils::com_init_wrapper wrapper;
static std::string app_data = ([]() -> std::string {
PWSTR local_app_data{};
if (SUCCEEDED(::SHGetKnownFolderPath(FOLDERID_LocalAppData, 0, nullptr,
&local_app_data))) {
auto ret = utils::string::to_utf8(local_app_data);
::CoTaskMemFree(local_app_data);
return ret;
}
throw utils::error::create_exception(
function_name, {
"unable to detect local application data folder",
});
})();
return app_data;
}
auto get_thread_id() -> std::uint64_t {
return static_cast<std::uint64_t>(::GetCurrentThreadId());
}
auto is_process_elevated() -> bool {
auto ret{false};
HANDLE token{INVALID_HANDLE_VALUE};
if (::OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token)) {
TOKEN_ELEVATION token_elevation{};
DWORD size = sizeof(token_elevation);
if (::GetTokenInformation(token, TokenElevation, &token_elevation,
sizeof(token_elevation), &size)) {
ret = (token_elevation.TokenIsElevated != 0);
}
}
if (token != INVALID_HANDLE_VALUE) {
::CloseHandle(token);
}
return ret;
}
auto run_process_elevated(std::vector<const char *> args) -> int {
std::cout << "Elevating Process" << std::endl;
std::string parameters{"-hidden"};
for (std::size_t idx = 1U; idx < args.size(); idx++) {
parameters +=
(parameters.empty() ? args.at(idx) : " " + std::string(args.at(idx)));
}
std::string full_path;
full_path.resize(fifthgrid::max_path_length + 1);
if (::GetModuleFileNameA(nullptr, full_path.data(),
fifthgrid::max_path_length)) {
SHELLEXECUTEINFOA sei{};
sei.fMask = SEE_MASK_NOCLOSEPROCESS;
sei.cbSize = sizeof(sei);
sei.lpVerb = "runas";
sei.lpFile = full_path.c_str();
sei.lpParameters = parameters.c_str();
sei.hwnd = nullptr;
sei.nShow = SW_NORMAL;
if (::ShellExecuteExA(&sei)) {
::WaitForSingleObject(sei.hProcess, INFINITE);
DWORD exit_code{};
::GetExitCodeProcess(sei.hProcess, &exit_code);
::CloseHandle(sei.hProcess);
return static_cast<int>(exit_code);
}
}
return static_cast<int>(::GetLastError());
}
void set_last_error_code(DWORD error_code) { ::SetLastError(error_code); }
auto get_startup_folder() -> std::wstring {
[[maybe_unused]] thread_local const utils::com_init_wrapper wrapper;
PWSTR raw{nullptr};
auto result = ::SHGetKnownFolderPath(FOLDERID_Startup, 0, nullptr, &raw);
if (FAILED(result)) {
if (raw != nullptr) {
::CoTaskMemFree(raw);
}
return {};
}
std::wstring str{raw};
::CoTaskMemFree(raw);
return str;
}
auto create_shortcut(const shortcut_cfg &cfg, bool overwrite_existing) -> bool {
FIFTHGRID_USES_FUNCTION_NAME();
[[maybe_unused]] thread_local const utils::com_init_wrapper wrapper;
const auto hr_hex = [](HRESULT result) -> std::string {
std::ostringstream oss;
oss << "0x" << std::uppercase << std::hex << std::setw(8)
<< std::setfill('0') << static_cast<std::uint32_t>(result);
return oss.str();
};
if (cfg.location.empty()) {
utils::error::handle_error(function_name, "shortcut location was empty");
return false;
}
if (not utils::file::directory{cfg.location}.create_directory()) {
utils::error::handle_error(function_name,
"failed to create shortcut directory|path|" +
utils::string::to_utf8(cfg.location));
return false;
}
auto final_name = cfg.shortcut_name.empty()
? utils::path::strip_to_file_name(cfg.exe_path)
: cfg.shortcut_name;
if (not final_name.ends_with(L".lnk")) {
final_name += L".lnk";
}
auto lnk_path = utils::path::combine(cfg.location, {final_name});
if (utils::file::file{lnk_path}.exists() && not overwrite_existing) {
return true;
}
IShellLinkW *psl{nullptr};
auto result = ::CoCreateInstance(CLSID_ShellLink, nullptr,
CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&psl));
if (FAILED(result)) {
utils::error::handle_error(
function_name,
std::string("CoCreateInstance(CLSID_ShellLink) failed: ") +
hr_hex(result));
return false;
}
result = psl->SetPath(cfg.exe_path.c_str());
if (FAILED(result)) {
utils::error::handle_error(function_name,
std::string("IShellLink::SetPath failed: ") +
hr_hex(result));
psl->Release();
return false;
}
if (not cfg.arguments.empty()) {
result = psl->SetArguments(cfg.arguments.c_str());
if (FAILED(result)) {
utils::error::handle_error(
function_name,
std::string("IShellLink::SetArguments failed: ") + hr_hex(result));
psl->Release();
return false;
}
}
if (not cfg.working_directory.empty()) {
result = psl->SetWorkingDirectory(cfg.working_directory.c_str());
if (FAILED(result)) {
utils::error::handle_error(
function_name,
std::string("IShellLink::SetWorkingDirectory failed: ") +
hr_hex(result));
psl->Release();
return false;
}
}
result = psl->SetShowCmd(SW_SHOWNORMAL);
if (FAILED(result)) {
utils::error::handle_error(function_name,
std::string("IShellLink::SetShowCmd failed: ") +
hr_hex(result));
psl->Release();
return false;
}
if (not cfg.icon_path.empty()) {
result = psl->SetIconLocation(cfg.icon_path.c_str(), 0);
if (FAILED(result)) {
utils::error::handle_error(
function_name,
std::string("IShellLink::SetIconLocation failed: ") + hr_hex(result));
psl->Release();
return false;
}
}
if (not utils::file::file{lnk_path}.remove()) {
utils::error::handle_error(function_name,
"failed to remove existing shortcut|path|" +
utils::string::to_utf8(lnk_path));
return false;
}
IPersistFile *ppf{nullptr};
result = psl->QueryInterface(IID_PPV_ARGS(&ppf));
if (FAILED(result)) {
utils::error::handle_error(
function_name,
std::string("QueryInterface(IPersistFile) failed: ") + hr_hex(result));
psl->Release();
return false;
}
result = ppf->Save(lnk_path.c_str(), TRUE);
ppf->SaveCompleted(lnk_path.c_str());
ppf->Release();
psl->Release();
if (FAILED(result)) {
utils::error::handle_error(function_name,
std::string("IPersistFile::Save failed: ") +
hr_hex(result));
return false;
}
return true;
}
auto remove_shortcut(std::wstring shortcut_name, const std::wstring &location)
-> bool {
if (not shortcut_name.ends_with(L".lnk")) {
shortcut_name += L".lnk";
}
auto file = utils::path::combine(location, {shortcut_name});
if (not utils::file::file{file}.exists()) {
return true;
}
return utils::file::file{file}.remove();
}
} // namespace fifthgrid::utils
#endif // defined(_WIN32)