Move the tar functionality to tar_common
This commit is contained in:
		
							parent
							
								
									698e92fca8
								
							
						
					
					
						commit
						6ac61139ae
					
				@ -43,6 +43,7 @@ add_library(lubuntuci_lib SHARED
 | 
			
		||||
    utilities.cpp
 | 
			
		||||
    db_common.cpp
 | 
			
		||||
    git_common.cpp
 | 
			
		||||
    tar_common.cpp
 | 
			
		||||
    sources_parser.cpp
 | 
			
		||||
    ci_logic.cpp
 | 
			
		||||
    ci_database_objs.cpp
 | 
			
		||||
 | 
			
		||||
@ -13,11 +13,12 @@
 | 
			
		||||
// You should have received a copy of the GNU General Public License
 | 
			
		||||
// along with this program.  If not, see <https://www.gnu.org/licenses/>.
 | 
			
		||||
 | 
			
		||||
#include "task_queue.h"
 | 
			
		||||
#include "ci_logic.h"
 | 
			
		||||
#include "utilities.h"
 | 
			
		||||
#include "db_common.h"
 | 
			
		||||
#include "git_common.h"
 | 
			
		||||
#include "tar_common.h"
 | 
			
		||||
#include "task_queue.h"
 | 
			
		||||
#include "utilities.h"
 | 
			
		||||
 | 
			
		||||
#include <yaml-cpp/yaml.h>
 | 
			
		||||
#include <filesystem>
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										214
									
								
								cpp/tar_common.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										214
									
								
								cpp/tar_common.cpp
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,214 @@
 | 
			
		||||
// Copyright (C) 2025 Simon Quigley <tsimonq2@ubuntu.com>
 | 
			
		||||
//
 | 
			
		||||
// This program is free software: you can redistribute it and/or modify
 | 
			
		||||
// it under the terms of the GNU General Public License as published by
 | 
			
		||||
// the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
// (at your option) any later version.
 | 
			
		||||
//
 | 
			
		||||
// This program is distributed in the hope that it will be useful,
 | 
			
		||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
// GNU General Public License for more details.
 | 
			
		||||
//
 | 
			
		||||
// You should have received a copy of the GNU General Public License
 | 
			
		||||
// along with this program.  If not, see <https://www.gnu.org/licenses/>.
 | 
			
		||||
 | 
			
		||||
#include <pwd.h>
 | 
			
		||||
#include <grp.h>
 | 
			
		||||
#include <unistd.h>
 | 
			
		||||
#include <cstring>
 | 
			
		||||
#include <cstdlib>
 | 
			
		||||
#include <fstream>
 | 
			
		||||
#include <iostream>
 | 
			
		||||
#include <filesystem>
 | 
			
		||||
#include <format>
 | 
			
		||||
#include <sstream>
 | 
			
		||||
#include <ranges>
 | 
			
		||||
#include <sys/stat.h>
 | 
			
		||||
#include <unordered_set>
 | 
			
		||||
 | 
			
		||||
#include "/usr/include/archive.h"
 | 
			
		||||
#include <archive_entry.h>
 | 
			
		||||
#include "tar_common.h"
 | 
			
		||||
 | 
			
		||||
namespace fs = std::filesystem;
 | 
			
		||||
 | 
			
		||||
static const std::string clean_utf8(const char* name) {
 | 
			
		||||
    if (!name) return "unknown";
 | 
			
		||||
    try {
 | 
			
		||||
        std::wstring_convert<std::codecvt_utf8<char32_t>, char32_t> converter;
 | 
			
		||||
        converter.from_bytes(name);
 | 
			
		||||
        return std::string(name);
 | 
			
		||||
    } catch (const std::range_error&) { return "unknown"; }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void create_tarball(const std::string &tarball_path,
 | 
			
		||||
                    const std::string &directory,
 | 
			
		||||
                    const std::vector<std::string> &exclusions,
 | 
			
		||||
                    std::shared_ptr<Log> log) {
 | 
			
		||||
    if (log) log->append("Creating tarball: " + tarball_path);
 | 
			
		||||
 | 
			
		||||
    try {
 | 
			
		||||
        if (!fs::exists(directory) || !fs::is_directory(directory)) throw std::runtime_error("Source directory does not exist or is not a directory: " + directory);
 | 
			
		||||
 | 
			
		||||
        struct ArchiveDeleter {
 | 
			
		||||
            void operator()(struct archive *a) const {
 | 
			
		||||
                if (a) archive_write_free(a);
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
        std::unique_ptr<struct archive, ArchiveDeleter> a(archive_write_new());
 | 
			
		||||
        if (!a) throw std::runtime_error("Failed to create a new archive writer.");
 | 
			
		||||
 | 
			
		||||
        if (archive_write_add_filter_gzip(a.get()) != ARCHIVE_OK) {
 | 
			
		||||
            std::string err = "Failed to add gzip filter: ";
 | 
			
		||||
            err += archive_error_string(a.get());
 | 
			
		||||
            throw std::runtime_error(err);
 | 
			
		||||
        }
 | 
			
		||||
        if (archive_write_set_format_pax_restricted(a.get()) != ARCHIVE_OK) {
 | 
			
		||||
            std::string err = "Failed to set archive format: ";
 | 
			
		||||
            err += archive_error_string(a.get());
 | 
			
		||||
            throw std::runtime_error(err);
 | 
			
		||||
        }
 | 
			
		||||
        if (archive_write_open_filename(a.get(), tarball_path.c_str()) != ARCHIVE_OK) {
 | 
			
		||||
            std::string err = "Could not open tarball for writing: ";
 | 
			
		||||
            err += archive_error_string(a.get());
 | 
			
		||||
            throw std::runtime_error(err);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Get the name of the directory we want to include as the top-level folder.
 | 
			
		||||
        fs::path base_dir = fs::path(directory).filename();
 | 
			
		||||
        std::string base_dir_str = base_dir.string();
 | 
			
		||||
 | 
			
		||||
        // First add an entry for the top-level directory (with a trailing slash)
 | 
			
		||||
        {
 | 
			
		||||
            struct archive_entry *entry = archive_entry_new();
 | 
			
		||||
            if (!entry) throw std::runtime_error("Failed to create archive entry for top-level directory.");
 | 
			
		||||
            std::string top_dir = base_dir_str + "/";
 | 
			
		||||
            struct stat file_stat;
 | 
			
		||||
            if (stat(top_dir.c_str(), &file_stat) == 0) {
 | 
			
		||||
                std::string uname = clean_utf8(getpwuid(file_stat.st_uid) ? getpwuid(file_stat.st_uid)->pw_name : "lugito");
 | 
			
		||||
                std::string gname = clean_utf8(getgrgid(file_stat.st_gid) ? getgrgid(file_stat.st_gid)->gr_name : "lugito");
 | 
			
		||||
                archive_entry_set_uname(entry, uname.c_str());
 | 
			
		||||
                archive_entry_set_gname(entry, gname.c_str());
 | 
			
		||||
                archive_entry_set_uid(entry, file_stat.st_uid);
 | 
			
		||||
                archive_entry_set_gid(entry, file_stat.st_gid);
 | 
			
		||||
                archive_entry_set_perm(entry, file_stat.st_mode);
 | 
			
		||||
            } else {
 | 
			
		||||
                if (log) log->append("Failed to stat: " + top_dir);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            archive_entry_set_pathname(entry, top_dir.c_str());
 | 
			
		||||
            archive_entry_set_size(entry, 0);
 | 
			
		||||
            archive_entry_set_filetype(entry, AE_IFDIR);
 | 
			
		||||
            std::time_t now_time = std::time(nullptr);
 | 
			
		||||
            archive_entry_set_mtime(entry, now_time, 0);
 | 
			
		||||
            if (archive_write_header(a.get(), entry) != ARCHIVE_OK) {
 | 
			
		||||
                std::string err = "Failed to write header for top-level directory: ";
 | 
			
		||||
                err += archive_error_string(a.get());
 | 
			
		||||
                archive_entry_free(entry);
 | 
			
		||||
                throw std::runtime_error(err);
 | 
			
		||||
            }
 | 
			
		||||
            archive_write_finish_entry(a.get());
 | 
			
		||||
            archive_entry_free(entry);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Use a set to keep track of directories already added.
 | 
			
		||||
        std::unordered_set<std::string> added_directories;
 | 
			
		||||
 | 
			
		||||
        // Now iterate recursively through the source directory.
 | 
			
		||||
        for (auto it = fs::recursive_directory_iterator(directory,
 | 
			
		||||
                     fs::directory_options::skip_permission_denied | fs::directory_options::follow_directory_symlink);
 | 
			
		||||
             it != fs::recursive_directory_iterator(); ++it) {
 | 
			
		||||
 | 
			
		||||
            const auto& path = it->path();
 | 
			
		||||
 | 
			
		||||
            // Skip excluded paths early
 | 
			
		||||
            if (std::any_of(exclusions.begin(), exclusions.end(),
 | 
			
		||||
                [&path](const std::string& excl) { return path.string().find(excl) != std::string::npos; })) {
 | 
			
		||||
                it.disable_recursion_pending();  // Skip further traversal into excluded directories
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            std::error_code ec;
 | 
			
		||||
            const fs::file_status fstatus = it->status(ec);
 | 
			
		||||
            if (ec) {
 | 
			
		||||
                if (log) log->append("Skipping path due to error: " + path.string() + " (" + ec.message() + ")");
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Ensure we skip any duplicate by checking conflicts between files and directories
 | 
			
		||||
            if ((fs::is_directory(fstatus) && fs::exists(path)) || 
 | 
			
		||||
                (fs::is_regular_file(fstatus) && fs::is_directory(path))) {
 | 
			
		||||
                continue;  // Conflict detected, skip it
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Generate archive entry
 | 
			
		||||
            struct archive_entry* entry = archive_entry_new();
 | 
			
		||||
            if (!entry) {
 | 
			
		||||
                if (log) log->append("Failed to create archive entry for: " + path.string());
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Set path for the tarball entry (prepend base directory)
 | 
			
		||||
            const std::string archive_path = fs::relative(path, directory).string();
 | 
			
		||||
            archive_entry_set_pathname(entry, archive_path.c_str());
 | 
			
		||||
 | 
			
		||||
            // Handle directory/file-specific logic
 | 
			
		||||
            if (fs::is_directory(fstatus)) {
 | 
			
		||||
                archive_entry_set_filetype(entry, AE_IFDIR);
 | 
			
		||||
                archive_entry_set_size(entry, 0);
 | 
			
		||||
            } else if (fs::is_regular_file(fstatus)) {
 | 
			
		||||
                archive_entry_set_filetype(entry, AE_IFREG);
 | 
			
		||||
                archive_entry_set_size(entry, fs::file_size(path, ec));
 | 
			
		||||
            } else if (fs::is_symlink(fstatus)) {
 | 
			
		||||
                const auto target = fs::read_symlink(path, ec);
 | 
			
		||||
                if (!ec) archive_entry_set_symlink(entry, target.c_str());
 | 
			
		||||
                archive_entry_set_filetype(entry, AE_IFLNK);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Set permissions and ownership
 | 
			
		||||
            struct stat file_stat;
 | 
			
		||||
            if (stat(path.c_str(), &file_stat) == 0) {
 | 
			
		||||
                archive_entry_set_perm(entry, file_stat.st_mode);
 | 
			
		||||
                archive_entry_set_uid(entry, file_stat.st_uid);
 | 
			
		||||
                archive_entry_set_gid(entry, file_stat.st_gid);
 | 
			
		||||
                archive_entry_set_uname(entry, clean_utf8(getpwuid(file_stat.st_uid) ? getpwuid(file_stat.st_uid)->pw_name : "unknown").c_str());
 | 
			
		||||
                archive_entry_set_gname(entry, clean_utf8(getgrgid(file_stat.st_gid) ? getgrgid(file_stat.st_gid)->gr_name : "unknown").c_str());
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Write the entry to the archive
 | 
			
		||||
            if (archive_write_header(a.get(), entry) != ARCHIVE_OK) {
 | 
			
		||||
                if (log) log->append("Failed to write header for: " + path.string() + " - " + archive_error_string(a.get()));
 | 
			
		||||
                archive_entry_free(entry);
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Handle file content streaming
 | 
			
		||||
            if (fs::is_regular_file(fstatus)) {
 | 
			
		||||
                std::ifstream in_file(path, std::ios::binary);
 | 
			
		||||
                if (in_file) {
 | 
			
		||||
                    char buffer[8192];
 | 
			
		||||
                    while (in_file.read(buffer, sizeof(buffer)) || in_file.gcount() > 0) {
 | 
			
		||||
                        if (archive_write_data(a.get(), buffer, in_file.gcount()) < 0) {
 | 
			
		||||
                            if (log) log->append("Failed to write file data: " + path.string());
 | 
			
		||||
                            break;
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            archive_write_finish_entry(a.get());
 | 
			
		||||
            archive_entry_free(entry);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (archive_write_close(a.get()) != ARCHIVE_OK) {
 | 
			
		||||
            std::string err = "Failed to close archive: ";
 | 
			
		||||
            err += archive_error_string(a.get());
 | 
			
		||||
            throw std::runtime_error(err);
 | 
			
		||||
        }
 | 
			
		||||
    } catch (const std::exception &e) {
 | 
			
		||||
        if (log) log->append("Failed to create tarball: " + tarball_path + "\n" + e.what());
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
    if (log) log->append("Tarball created and compressed: " + tarball_path);
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										25
									
								
								cpp/tar_common.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								cpp/tar_common.h
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,25 @@
 | 
			
		||||
// Copyright (C) 2024-2025 Simon Quigley <tsimonq2@ubuntu.com>
 | 
			
		||||
//
 | 
			
		||||
// This program is free software: you can redistribute it and/or modify
 | 
			
		||||
// it under the terms of the GNU General Public License as published by
 | 
			
		||||
// the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
// (at your option) any later version.
 | 
			
		||||
//
 | 
			
		||||
// This program is distributed in the hope that it will be useful,
 | 
			
		||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
// GNU General Public License for more details.
 | 
			
		||||
//
 | 
			
		||||
// You should have received a copy of the GNU General Public License
 | 
			
		||||
// along with this program.  If not, see <https://www.gnu.org/licenses/>.
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <string>
 | 
			
		||||
#include <vector>
 | 
			
		||||
#include "utilities.h"
 | 
			
		||||
 | 
			
		||||
void create_tarball(const std::string& tarball_path,
 | 
			
		||||
                    const std::string& directory,
 | 
			
		||||
                    const std::vector<std::string>& exclusions,
 | 
			
		||||
                    std::shared_ptr<Log> log = nullptr);
 | 
			
		||||
@ -13,8 +13,6 @@
 | 
			
		||||
// You should have received a copy of the GNU General Public License
 | 
			
		||||
// along with this program.  If not, see <https://www.gnu.org/licenses/>.
 | 
			
		||||
 | 
			
		||||
#include <pwd.h>
 | 
			
		||||
#include <grp.h>
 | 
			
		||||
#include <unistd.h>
 | 
			
		||||
#include <cstring>
 | 
			
		||||
#include <cstdlib>
 | 
			
		||||
@ -31,8 +29,6 @@
 | 
			
		||||
 | 
			
		||||
#include "utilities.h"
 | 
			
		||||
 | 
			
		||||
#include "/usr/include/archive.h"
 | 
			
		||||
#include <archive_entry.h>
 | 
			
		||||
#include <zlib.h>
 | 
			
		||||
#include <curl/curl.h>
 | 
			
		||||
 | 
			
		||||
@ -403,183 +399,3 @@ std::vector<std::string> extract_files_excluded(const std::string& filepath) {
 | 
			
		||||
 | 
			
		||||
    return files_excluded;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static const std::string clean_utf8(const char* name) {
 | 
			
		||||
    if (!name) return "unknown";
 | 
			
		||||
    try {
 | 
			
		||||
        std::wstring_convert<std::codecvt_utf8<char32_t>, char32_t> converter;
 | 
			
		||||
        converter.from_bytes(name);
 | 
			
		||||
        return std::string(name);
 | 
			
		||||
    } catch (const std::range_error&) { return "unknown"; }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void create_tarball(const std::string &tarball_path,
 | 
			
		||||
                    const std::string &directory,
 | 
			
		||||
                    const std::vector<std::string> &exclusions,
 | 
			
		||||
                    std::shared_ptr<Log> log) {
 | 
			
		||||
    if (log) log->append("Creating tarball: " + tarball_path);
 | 
			
		||||
 | 
			
		||||
    try {
 | 
			
		||||
        if (!fs::exists(directory) || !fs::is_directory(directory)) throw std::runtime_error("Source directory does not exist or is not a directory: " + directory);
 | 
			
		||||
 | 
			
		||||
        struct ArchiveDeleter {
 | 
			
		||||
            void operator()(struct archive *a) const {
 | 
			
		||||
                if (a) archive_write_free(a);
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
        std::unique_ptr<struct archive, ArchiveDeleter> a(archive_write_new());
 | 
			
		||||
        if (!a) throw std::runtime_error("Failed to create a new archive writer.");
 | 
			
		||||
 | 
			
		||||
        if (archive_write_add_filter_gzip(a.get()) != ARCHIVE_OK) {
 | 
			
		||||
            std::string err = "Failed to add gzip filter: ";
 | 
			
		||||
            err += archive_error_string(a.get());
 | 
			
		||||
            throw std::runtime_error(err);
 | 
			
		||||
        }
 | 
			
		||||
        if (archive_write_set_format_pax_restricted(a.get()) != ARCHIVE_OK) {
 | 
			
		||||
            std::string err = "Failed to set archive format: ";
 | 
			
		||||
            err += archive_error_string(a.get());
 | 
			
		||||
            throw std::runtime_error(err);
 | 
			
		||||
        }
 | 
			
		||||
        if (archive_write_open_filename(a.get(), tarball_path.c_str()) != ARCHIVE_OK) {
 | 
			
		||||
            std::string err = "Could not open tarball for writing: ";
 | 
			
		||||
            err += archive_error_string(a.get());
 | 
			
		||||
            throw std::runtime_error(err);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Get the name of the directory we want to include as the top-level folder.
 | 
			
		||||
        fs::path base_dir = fs::path(directory).filename();
 | 
			
		||||
        std::string base_dir_str = base_dir.string();
 | 
			
		||||
 | 
			
		||||
        // First add an entry for the top-level directory (with a trailing slash)
 | 
			
		||||
        {
 | 
			
		||||
            struct archive_entry *entry = archive_entry_new();
 | 
			
		||||
            if (!entry) throw std::runtime_error("Failed to create archive entry for top-level directory.");
 | 
			
		||||
            std::string top_dir = base_dir_str + "/";
 | 
			
		||||
            struct stat file_stat;
 | 
			
		||||
            if (stat(top_dir.c_str(), &file_stat) == 0) {
 | 
			
		||||
                std::string uname = clean_utf8(getpwuid(file_stat.st_uid) ? getpwuid(file_stat.st_uid)->pw_name : "lugito");
 | 
			
		||||
                std::string gname = clean_utf8(getgrgid(file_stat.st_gid) ? getgrgid(file_stat.st_gid)->gr_name : "lugito");
 | 
			
		||||
                archive_entry_set_uname(entry, uname.c_str());
 | 
			
		||||
                archive_entry_set_gname(entry, gname.c_str());
 | 
			
		||||
                archive_entry_set_uid(entry, file_stat.st_uid);
 | 
			
		||||
                archive_entry_set_gid(entry, file_stat.st_gid);
 | 
			
		||||
                archive_entry_set_perm(entry, file_stat.st_mode);
 | 
			
		||||
            } else {
 | 
			
		||||
                if (log) log->append("Failed to stat: " + top_dir);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            archive_entry_set_pathname(entry, top_dir.c_str());
 | 
			
		||||
            archive_entry_set_size(entry, 0);
 | 
			
		||||
            archive_entry_set_filetype(entry, AE_IFDIR);
 | 
			
		||||
            std::time_t now_time = std::time(nullptr);
 | 
			
		||||
            archive_entry_set_mtime(entry, now_time, 0);
 | 
			
		||||
            if (archive_write_header(a.get(), entry) != ARCHIVE_OK) {
 | 
			
		||||
                std::string err = "Failed to write header for top-level directory: ";
 | 
			
		||||
                err += archive_error_string(a.get());
 | 
			
		||||
                archive_entry_free(entry);
 | 
			
		||||
                throw std::runtime_error(err);
 | 
			
		||||
            }
 | 
			
		||||
            archive_write_finish_entry(a.get());
 | 
			
		||||
            archive_entry_free(entry);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Use a set to keep track of directories already added.
 | 
			
		||||
        std::unordered_set<std::string> added_directories;
 | 
			
		||||
 | 
			
		||||
        // Now iterate recursively through the source directory.
 | 
			
		||||
        for (auto it = fs::recursive_directory_iterator(directory,
 | 
			
		||||
                     fs::directory_options::skip_permission_denied | fs::directory_options::follow_directory_symlink);
 | 
			
		||||
             it != fs::recursive_directory_iterator(); ++it) {
 | 
			
		||||
 | 
			
		||||
            const auto& path = it->path();
 | 
			
		||||
 | 
			
		||||
            // Skip excluded paths early
 | 
			
		||||
            if (std::any_of(exclusions.begin(), exclusions.end(),
 | 
			
		||||
                [&path](const std::string& excl) { return path.string().find(excl) != std::string::npos; })) {
 | 
			
		||||
                it.disable_recursion_pending();  // Skip further traversal into excluded directories
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            std::error_code ec;
 | 
			
		||||
            const fs::file_status fstatus = it->status(ec);
 | 
			
		||||
            if (ec) {
 | 
			
		||||
                if (log) log->append("Skipping path due to error: " + path.string() + " (" + ec.message() + ")");
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Ensure we skip any duplicate by checking conflicts between files and directories
 | 
			
		||||
            if ((fs::is_directory(fstatus) && fs::exists(path)) || 
 | 
			
		||||
                (fs::is_regular_file(fstatus) && fs::is_directory(path))) {
 | 
			
		||||
                continue;  // Conflict detected, skip it
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Generate archive entry
 | 
			
		||||
            struct archive_entry* entry = archive_entry_new();
 | 
			
		||||
            if (!entry) {
 | 
			
		||||
                if (log) log->append("Failed to create archive entry for: " + path.string());
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Set path for the tarball entry (prepend base directory)
 | 
			
		||||
            const std::string archive_path = fs::relative(path, directory).string();
 | 
			
		||||
            archive_entry_set_pathname(entry, archive_path.c_str());
 | 
			
		||||
 | 
			
		||||
            // Handle directory/file-specific logic
 | 
			
		||||
            if (fs::is_directory(fstatus)) {
 | 
			
		||||
                archive_entry_set_filetype(entry, AE_IFDIR);
 | 
			
		||||
                archive_entry_set_size(entry, 0);
 | 
			
		||||
            } else if (fs::is_regular_file(fstatus)) {
 | 
			
		||||
                archive_entry_set_filetype(entry, AE_IFREG);
 | 
			
		||||
                archive_entry_set_size(entry, fs::file_size(path, ec));
 | 
			
		||||
            } else if (fs::is_symlink(fstatus)) {
 | 
			
		||||
                const auto target = fs::read_symlink(path, ec);
 | 
			
		||||
                if (!ec) archive_entry_set_symlink(entry, target.c_str());
 | 
			
		||||
                archive_entry_set_filetype(entry, AE_IFLNK);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Set permissions and ownership
 | 
			
		||||
            struct stat file_stat;
 | 
			
		||||
            if (stat(path.c_str(), &file_stat) == 0) {
 | 
			
		||||
                archive_entry_set_perm(entry, file_stat.st_mode);
 | 
			
		||||
                archive_entry_set_uid(entry, file_stat.st_uid);
 | 
			
		||||
                archive_entry_set_gid(entry, file_stat.st_gid);
 | 
			
		||||
                archive_entry_set_uname(entry, clean_utf8(getpwuid(file_stat.st_uid) ? getpwuid(file_stat.st_uid)->pw_name : "unknown").c_str());
 | 
			
		||||
                archive_entry_set_gname(entry, clean_utf8(getgrgid(file_stat.st_gid) ? getgrgid(file_stat.st_gid)->gr_name : "unknown").c_str());
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Write the entry to the archive
 | 
			
		||||
            if (archive_write_header(a.get(), entry) != ARCHIVE_OK) {
 | 
			
		||||
                if (log) log->append("Failed to write header for: " + path.string() + " - " + archive_error_string(a.get()));
 | 
			
		||||
                archive_entry_free(entry);
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Handle file content streaming
 | 
			
		||||
            if (fs::is_regular_file(fstatus)) {
 | 
			
		||||
                std::ifstream in_file(path, std::ios::binary);
 | 
			
		||||
                if (in_file) {
 | 
			
		||||
                    char buffer[8192];
 | 
			
		||||
                    while (in_file.read(buffer, sizeof(buffer)) || in_file.gcount() > 0) {
 | 
			
		||||
                        if (archive_write_data(a.get(), buffer, in_file.gcount()) < 0) {
 | 
			
		||||
                            if (log) log->append("Failed to write file data: " + path.string());
 | 
			
		||||
                            break;
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            archive_write_finish_entry(a.get());
 | 
			
		||||
            archive_entry_free(entry);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (archive_write_close(a.get()) != ARCHIVE_OK) {
 | 
			
		||||
            std::string err = "Failed to close archive: ";
 | 
			
		||||
            err += archive_error_string(a.get());
 | 
			
		||||
            throw std::runtime_error(err);
 | 
			
		||||
        }
 | 
			
		||||
    } catch (const std::exception &e) {
 | 
			
		||||
        if (log) log->append("Failed to create tarball: " + tarball_path + "\n" + e.what());
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
    if (log) log->append("Tarball created and compressed: " + tarball_path);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -118,9 +118,3 @@ bool run_command(const std::vector<std::string> &cmd,
 | 
			
		||||
 | 
			
		||||
// Function to extract excluded files from a copyright file
 | 
			
		||||
std::vector<std::string> extract_files_excluded(const std::string& filepath);
 | 
			
		||||
 | 
			
		||||
// Function to create a tarball
 | 
			
		||||
void create_tarball(const std::string& tarballPath,
 | 
			
		||||
                    const std::string& directory,
 | 
			
		||||
                    const std::vector<std::string>& exclusions,
 | 
			
		||||
                    std::shared_ptr<Log> log = nullptr);
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user