Files
linuxdeploy/src/core/appdir.cpp
T

958 lines
43 KiB
C++
Raw Normal View History

// system headers
#include <set>
#include <map>
#include <string>
#include <vector>
2018-05-30 19:21:08 +02:00
// library headers
#include <boost/filesystem.hpp>
2018-06-25 17:37:21 +02:00
#include <CImg.h>
2018-05-30 19:21:08 +02:00
#include <fnmatch.h>
2018-05-30 19:21:08 +02:00
// local headers
#include "linuxdeploy/core/appdir.h"
2021-05-28 23:14:51 +02:00
#include "linuxdeploy/core/elf_file.h"
2018-05-30 19:21:08 +02:00
#include "linuxdeploy/core/log.h"
2018-12-22 23:09:04 +01:00
#include "linuxdeploy/desktopfile/desktopfileentry.h"
2018-08-03 00:55:48 +02:00
#include "linuxdeploy/util/util.h"
#include "linuxdeploy/subprocess/subprocess.h"
2018-08-30 22:13:01 +02:00
#include "copyright.h"
// auto-generated headers
2018-05-30 19:21:08 +02:00
#include "excludelist.h"
#include "appdir_root_setup.h"
2018-05-30 19:21:08 +02:00
using namespace linuxdeploy::core;
2018-12-22 23:09:04 +01:00
using namespace linuxdeploy::desktopfile;
2018-05-30 19:21:08 +02:00
using namespace linuxdeploy::core::log;
2018-06-25 17:37:21 +02:00
using namespace cimg_library;
2018-05-30 19:21:08 +02:00
namespace bf = boost::filesystem;
2021-10-16 03:22:02 +02:00
namespace {
// equivalent to 0644
constexpr bf::perms DEFAULT_PERMS = bf::owner_write | bf::owner_read | bf::group_read | bf::others_read;
// equivalent to 0755
constexpr bf::perms EXECUTABLE_PERMS = DEFAULT_PERMS | bf::owner_exe | bf::group_exe | bf::others_exe;
class CopyOperation {
public:
bf::path fromPath;
bf::path toPath;
bf::perms addedPermissions;
};
typedef std::map<bf::path, CopyOperation> CopyOperationsMap;
/**
* Stores copy operations.
* This way, the storage logic does not have to be known to the using class.
*/
class CopyOperationsStorage {
private:
// using a map to make sure every target path is there only once
CopyOperationsMap _storedOperations;
public:
CopyOperationsStorage() = default;
/**
* Add copy operation.
* @param fromPath path to copy from
* @param toPath path to copy to
* @param addedPermissions permissions to add to the file's permissions
*/
void addOperation(const bf::path& fromPath, const bf::path& toPath, const bf::perms addedPermissions) {
CopyOperation operation{fromPath, toPath, addedPermissions};
_storedOperations[fromPath] = operation;
}
/**
* Export operations.
* @return vector containing all operations (random order).
*/
std::vector<CopyOperation> getOperations() {
std::vector<CopyOperation> operations;
operations.reserve(_storedOperations.size());
for (const auto& operationsPair : _storedOperations) {
operations.emplace_back(operationsPair.second);
}
return operations;
}
/**
* Clear internal storage.
*/
void clear() {
_storedOperations.clear();
}
};
}
2018-05-30 19:21:08 +02:00
namespace linuxdeploy {
namespace core {
namespace appdir {
class AppDir::PrivateData {
public:
bf::path appDirPath;
// store deferred operations
// these can be executed by calling excuteDeferredOperations
2021-10-16 03:22:02 +02:00
CopyOperationsStorage copyOperationsStorage;
std::set<bf::path> stripOperations;
2018-06-01 15:02:05 +02:00
std::map<bf::path, std::string> setElfRPathOperations;
2018-05-30 19:21:08 +02:00
2018-06-01 15:27:08 +02:00
// stores all files that have been visited by the deploy functions, e.g., when they're blacklisted,
// have been added to the deferred operations already, etc.
// lookups in a single container are a lot faster than having to look up in several ones, therefore
// the little amount of additional memory is worth it, considering the improved performance
std::set<bf::path> visitedFiles;
// used to automatically rename resources to improve the UX, e.g. icons
std::string appName;
// platform dependent implementation of copyright files deployment
2018-08-30 22:13:01 +02:00
std::shared_ptr<copyright::ICopyrightFilesManager> copyrightFilesManager;
// decides whether copyright files deployment is performed
bool disableCopyrightFilesDeployment = false;
2018-05-30 19:21:08 +02:00
public:
2021-10-16 03:22:02 +02:00
PrivateData() : copyOperationsStorage(), stripOperations(), setElfRPathOperations(), visitedFiles(), appDirPath() {
2018-08-30 22:13:01 +02:00
copyrightFilesManager = copyright::ICopyrightFilesManager::getInstance();
};
2018-05-30 19:21:08 +02:00
public:
2018-09-03 23:30:53 +02:00
// calculate library directory name for given ELF file, taking system architecture into account
static std::string getLibraryDirName(const bf::path& path) {
2021-05-28 23:14:51 +02:00
const auto systemElfClass = elf_file::ElfFile::getSystemElfClass();
const auto elfClass = elf_file::ElfFile(path).getElfClass();
2018-09-03 23:30:53 +02:00
std::string libDirName = "lib";
if (systemElfClass != elfClass) {
if (elfClass == ELFCLASS32)
libDirName += "32";
else
libDirName += "64";
}
return libDirName;
}
2018-05-30 19:21:08 +02:00
// actually copy file
// mimics cp command behavior
2021-10-16 03:22:02 +02:00
// also adds minimum file permissions (by default adds 0644 to existing permissions)
static bool copyFile(const bf::path& from, bf::path to, bf::perms addedPerms, bool overwrite = false) {
2018-05-30 19:21:08 +02:00
ldLog() << "Copying file" << from << "to" << to << std::endl;
try {
if (!to.parent_path().empty() && !bf::is_directory(to.parent_path()) && !bf::create_directories(to.parent_path())) {
ldLog() << LD_ERROR << "Failed to create parent directory" << to.parent_path() << "for path" << to << std::endl;
return false;
}
if (*(to.string().end() - 1) == '/' || bf::is_directory(to))
to /= from.filename();
if (!overwrite && bf::exists(to)) {
ldLog() << LD_DEBUG << "File exists, skipping:" << to << std::endl;
2018-06-11 23:43:21 +02:00
return true;
}
2018-06-11 23:43:21 +02:00
bf::copy_file(from, to, bf::copy_option::overwrite_if_exists);
2021-10-16 03:22:02 +02:00
bf::permissions(to, addedPerms | bf::add_perms);
2018-05-30 19:21:08 +02:00
} catch (const bf::filesystem_error& e) {
2018-06-11 23:52:29 +02:00
ldLog() << LD_ERROR << "Failed to copy file" << from << "to" << to << LD_NO_SPACE << ":" << e.what() << std::endl;
2018-05-30 19:21:08 +02:00
return false;
}
return true;
}
// create symlink
static bool symlinkFile(const bf::path& target, bf::path symlink, const bool useRelativePath = true) {
2018-05-30 19:21:08 +02:00
ldLog() << "Creating symlink for file" << target << "in/as" << symlink << std::endl;
if (!useRelativePath) {
ldLog() << LD_ERROR << "Not implemented" << std::endl;
return false;
}
bf::path relativeTargetPath;
// cannot use ln's --relative option any more since we want to support old distros as well
// (looking at you, CentOS 6!)
{
auto symlinkBase = symlink;
if (!bf::is_directory(symlinkBase))
symlinkBase = symlinkBase.parent_path();
relativeTargetPath = bf::relative(target, symlinkBase);
}
// if a directory is passed as path to create the symlink as/in, we need to complete it with
// the filename of the source file to mimic ln's behavior
if (bf::is_directory(symlink))
symlink /= target.filename();
2018-05-30 19:21:08 +02:00
// override existing target (similar to ln's -f flag)
if (bf::exists(symlink))
bf::remove(symlink);
2018-05-30 19:21:08 +02:00
// actually perform symlink creation
try {
bf::create_symlink(relativeTargetPath, symlink);
} catch (const bf::filesystem_error& e) {
ldLog() << LD_ERROR << "symlink creation failed:" << e.what() << std::endl;
2018-05-30 19:21:08 +02:00
return false;
}
return true;
}
2018-06-01 15:27:08 +02:00
bool hasBeenVisitedAlready(const bf::path& path) {
return visitedFiles.find(path) != visitedFiles.end();
2018-05-30 19:21:08 +02:00
}
// execute deferred copy operations registered with the deploy* functions
bool executeDeferredOperations() {
bool success = true;
2021-10-16 03:22:02 +02:00
const auto copyOperations = copyOperationsStorage.getOperations();
std::for_each(copyOperations.begin(), copyOperations.end(), [&success](const CopyOperation& operation) {
if (!copyFile(operation.fromPath, operation.toPath, operation.addedPermissions)) {
2018-05-30 19:21:08 +02:00
success = false;
2021-10-16 03:22:02 +02:00
}
});
copyOperationsStorage.clear();
2018-05-30 19:21:08 +02:00
if (!success)
return false;
2018-05-30 19:21:08 +02:00
2018-07-12 21:08:50 +02:00
if (getenv("NO_STRIP") != nullptr) {
ldLog() << LD_WARNING << "$NO_STRIP environment variable detected, not stripping binaries" << std::endl;
stripOperations.clear();
} else {
2018-08-21 01:18:00 +02:00
const auto stripPath = getStripPath();
2018-07-12 21:08:50 +02:00
while (!stripOperations.empty()) {
const auto& filePath = *(stripOperations.begin());
2018-05-30 19:21:08 +02:00
2021-05-28 23:14:51 +02:00
if (util::stringStartsWith(elf_file::ElfFile(filePath).getRPath(), "$")) {
2018-07-12 21:08:50 +02:00
ldLog() << LD_WARNING << "Not calling strip on binary" << filePath << LD_NO_SPACE
<< ": rpath starts with $" << std::endl;
} else {
ldLog() << "Calling strip on library" << filePath << std::endl;
subprocess::subprocess_env_map_t env;
2018-07-12 21:08:50 +02:00
env.insert(std::make_pair(std::string("LC_ALL"), std::string("C")));
subprocess::subprocess proc({stripPath, filePath.string()}, env);
const auto result = proc.run();
const auto& err = result.stderr_string();
if (result.exit_code() != 0 &&
2018-07-12 21:08:50 +02:00
!util::stringContains(err, "Not enough room for program headers")) {
ldLog() << LD_ERROR << "Strip call failed:" << err << std::endl;
success = false;
}
2018-06-28 14:56:54 +02:00
}
2018-07-12 21:08:50 +02:00
stripOperations.erase(stripOperations.begin());
}
2018-05-30 19:21:08 +02:00
}
if (!success)
return false;
while (!setElfRPathOperations.empty()) {
const auto& currentEntry = *(setElfRPathOperations.begin());
const auto& filePath = currentEntry.first;
const auto& rpath = currentEntry.second;
elf_file::ElfFile elfFile(filePath);
2019-01-29 01:00:36 +01:00
// no need to set rpath in debug symbols files
// also, patchelf crashes on such symbols
if (isInDebugSymbolsLocation(filePath) || elfFile.isDebugSymbolsFile()) {
ldLog() << LD_WARNING << "Not setting rpath in debug symbols file:" << filePath
<< std::endl;
} else if (!elfFile.isDynamicallyLinked()) {
ldLog() << LD_WARNING << "Not setting rpath in statically-linked file: " << filePath
<< std::endl;
2019-01-29 01:00:36 +01:00
} else {
ldLog() << "Setting rpath in ELF file" << filePath << "to" << rpath << std::endl;
2021-05-28 23:14:51 +02:00
if (!elf_file::ElfFile(filePath).setRPath(rpath)) {
2019-01-29 01:00:36 +01:00
ldLog() << LD_ERROR << "Failed to set rpath in ELF file:" << filePath << std::endl;
success = false;
}
}
setElfRPathOperations.erase(setElfRPathOperations.begin());
}
return true;
2018-05-30 19:21:08 +02:00
}
2018-06-26 03:15:01 +02:00
// search for copyright file for file and deploy it to AppDir
bool deployCopyrightFiles(const bf::path& from) {
if (disableCopyrightFilesDeployment)
return true;
2018-08-30 22:13:01 +02:00
if (copyrightFilesManager == nullptr)
return false;
auto copyrightFiles = copyrightFilesManager->getCopyrightFilesForPath(from);
2018-06-26 03:15:01 +02:00
if (copyrightFiles.empty())
return false;
ldLog() << "Deploying copyright files for file" << from << std::endl;
2019-01-29 01:00:36 +01:00
2018-06-26 03:15:01 +02:00
for (const auto& file : copyrightFiles) {
2018-06-26 04:13:07 +02:00
std::string targetDir = file.string();
targetDir.erase(0, 1);
2021-10-16 03:22:02 +02:00
deployFile(file, appDirPath / targetDir, DEFAULT_PERMS);
2018-06-26 03:15:01 +02:00
}
return true;
}
2018-05-30 19:21:08 +02:00
// register copy operation that will be executed later
// by compiling a list of files to copy instead of just copying everything, one can ensure that
// the files are touched once only
2019-02-06 23:23:48 +01:00
// returns the full path of the deployment destination (useful if to is a directory
2021-10-16 03:22:02 +02:00
bf::path deployFile(const bf::path& from, bf::path to, bf::perms addedPerms, bool verbose = false) {
2018-05-30 19:21:08 +02:00
// not sure whether this is 100% bullet proof, but it simulates the cp command behavior
if (to.string().back() == '/' || bf::is_directory(to)) {
to /= from.filename();
}
2019-02-06 23:23:48 +01:00
if (verbose)
ldLog() << "Deploying file" << from << "to" << to << std::endl;
2021-10-16 03:22:02 +02:00
copyOperationsStorage.addOperation(from, to, addedPerms);
2018-06-01 15:27:08 +02:00
// mark file as visited
visitedFiles.insert(from);
2019-02-06 23:23:48 +01:00
return to;
2018-05-30 19:21:08 +02:00
}
bool deployElfDependencies(const bf::path& path) {
ldLog() << "Deploying dependencies for ELF file" << path << std::endl;
2018-08-11 15:30:06 +02:00
try {
2021-05-28 23:14:51 +02:00
for (const auto &dependencyPath : elf_file::ElfFile(path).traceDynamicDependencies())
if (!deployLibrary(dependencyPath, false, false))
2018-08-11 15:30:06 +02:00
return false;
2021-05-28 23:14:51 +02:00
} catch (const elf_file::DependencyNotFoundError& e) {
2018-08-11 15:30:06 +02:00
ldLog() << LD_ERROR << e.what() << std::endl;
return false;
2018-05-30 19:21:08 +02:00
}
2018-08-11 15:30:06 +02:00
2018-05-30 19:21:08 +02:00
return true;
}
2018-06-28 02:36:46 +02:00
static std::string getStripPath() {
2018-08-21 01:18:00 +02:00
// by default, try to use a strip next to the linuxdeploy binary
// if that isn't available, fall back to searching for strip in the PATH
std::string stripPath = "strip";
2018-06-28 02:36:46 +02:00
2018-08-21 01:18:00 +02:00
auto binDirPath = bf::path(util::getOwnExecutablePath()).parent_path();
auto localStripPath = binDirPath / "strip";
2018-06-28 02:36:46 +02:00
if (bf::exists(localStripPath))
2018-08-21 01:18:00 +02:00
stripPath = localStripPath.string();
2018-06-28 02:36:46 +02:00
2018-08-21 01:18:00 +02:00
ldLog() << LD_DEBUG << "Using strip:" << stripPath << std::endl;
2018-06-28 02:36:46 +02:00
2018-08-21 01:18:00 +02:00
return stripPath;
2018-06-28 02:36:46 +02:00
}
static std::string calculateRelativeRPath(const bf::path& originDir, const bf::path& dependencyLibrariesDir) {
auto relPath = bf::relative(bf::absolute(dependencyLibrariesDir), bf::absolute(originDir));
std::string rpath = "$ORIGIN/" + relPath.string() + ":$ORIGIN";
return rpath;
}
bool deployLibrary(const bf::path& path, bool forceDeploy = false, bool deployDependencies = true, const bf::path& destination = bf::path()) {
2018-07-01 01:06:31 +02:00
if (!forceDeploy && hasBeenVisitedAlready(path)) {
ldLog() << LD_DEBUG << "File has been visited already:" << path << std::endl;
2018-05-30 19:21:08 +02:00
return true;
}
2019-02-23 18:01:47 +01:00
if (!bf::exists(path)) {
ldLog() << LD_ERROR << "Cannot deploy non-existing library file:" << path << std::endl;
2019-02-23 18:01:47 +01:00
return false;
}
static auto isInExcludelist = [](const bf::path& fileName) {
2018-05-30 19:21:08 +02:00
for (const auto& excludePattern : generatedExcludelist) {
// simple string match is faster than using fnmatch
if (excludePattern == fileName)
return true;
auto fnmatchResult = fnmatch(excludePattern.c_str(), fileName.string().c_str(), FNM_PATHNAME);
switch (fnmatchResult) {
case 0:
return true;
case FNM_NOMATCH:
break;
default:
ldLog() << LD_ERROR << "fnmatch() reported error:" << fnmatchResult << std::endl;
2018-05-30 19:21:08 +02:00
return false;
}
}
return false;
};
2018-07-01 02:28:44 +02:00
if (!forceDeploy && isInExcludelist(path.filename())) {
ldLog() << "Skipping deployment of blacklisted library" << path << std::endl;
2018-06-01 15:27:08 +02:00
// mark file as visited
visitedFiles.insert(path);
2018-05-30 19:21:08 +02:00
return true;
}
// note for self: make sure to have a trailing slash in libraryDir, otherwise copyFile won't
// create a directory
2018-09-04 14:33:25 +02:00
bf::path libraryDir = appDirPath / "usr" / (getLibraryDirName(path) + "/");
ldLog() << "Deploying shared library" << path;
2018-06-08 14:20:59 +02:00
if (!destination.empty())
ldLog() << " (destination:" << destination << LD_NO_SPACE << ")";
ldLog() << std::endl;
2019-02-06 23:23:48 +01:00
auto actualDestination = destination.empty() ? libraryDir : destination;
2018-05-30 19:21:08 +02:00
// not sure whether this is 100% bullet proof, but it simulates the cp command behavior
2019-02-06 23:23:48 +01:00
if (actualDestination.string().back() == '/' || bf::is_directory(actualDestination)) {
actualDestination /= path.filename();
}
2019-02-06 23:23:48 +01:00
// in case destinationPath is a directory, deployFile will give us the deployed file's path
2021-10-16 03:22:02 +02:00
actualDestination = deployFile(path, actualDestination, DEFAULT_PERMS);
deployCopyrightFiles(path);
std::string rpath = "$ORIGIN";
if (!destination.empty()) {
// destination is the place where to deploy this file
// therefore, rpathDestination means
std::string rpathOriginDir = destination.string();
if (destination.string().back() == '/') {
rpathOriginDir = destination.string();
while (rpathOriginDir.back() == '/')
rpathOriginDir.erase(rpathOriginDir.end() - 1, rpathOriginDir.end());
} else {
rpathOriginDir = destination.parent_path().string();
}
2018-06-08 11:36:18 +02:00
rpath = calculateRelativeRPath(rpathOriginDir, libraryDir);
}
2019-01-29 01:00:36 +01:00
// no need to set rpath in debug symbols files
// also, patchelf crashes on such symbols
2019-02-06 23:23:48 +01:00
if (!isInDebugSymbolsLocation(actualDestination)) {
setElfRPathOperations[actualDestination] = rpath;
2019-01-29 01:00:36 +01:00
}
2019-02-06 23:23:48 +01:00
stripOperations.insert(actualDestination);
2018-05-30 19:21:08 +02:00
if (!deployDependencies)
return true;
return deployElfDependencies(path);
2018-05-30 19:21:08 +02:00
}
bool deployExecutable(const bf::path& path, const boost::filesystem::path& destination) {
2018-06-01 15:27:08 +02:00
if (hasBeenVisitedAlready(path)) {
ldLog() << LD_DEBUG << "File has been visited already:" << path << std::endl;
2018-05-30 19:21:08 +02:00
return true;
}
ldLog() << "Deploying executable" << path << std::endl;
// FIXME: make executables executable
auto destinationPath = destination.empty() ? appDirPath / "usr/bin/" : destination;
2018-05-30 19:21:08 +02:00
2021-10-16 03:22:02 +02:00
deployFile(path, destinationPath, EXECUTABLE_PERMS);
2018-06-26 03:15:01 +02:00
deployCopyrightFiles(path);
2018-09-03 23:30:53 +02:00
std::string rpath = "$ORIGIN/../" + getLibraryDirName(path);
if (!destination.empty()) {
std::string rpathDestination = destination.string();
if (destination.string().back() == '/') {
rpathDestination = destination.string();
while (rpathDestination.back() == '/')
rpathDestination.erase(rpathDestination.end() - 1, rpathDestination.end());
} else {
rpathDestination = destination.parent_path().string();
}
2018-09-03 23:33:26 +02:00
auto relPath = bf::relative(bf::absolute(appDirPath) / "usr" / getLibraryDirName(path), bf::absolute(rpathDestination));
rpath = "$ORIGIN/" + relPath.string();
}
2018-06-14 21:21:32 +02:00
setElfRPathOperations[destinationPath / path.filename()] = rpath;
stripOperations.insert(destinationPath / path.filename());
2018-05-30 19:21:08 +02:00
2018-06-15 15:02:24 +02:00
if (!deployElfDependencies(path))
2018-05-30 19:21:08 +02:00
return false;
return true;
}
2018-11-15 20:53:16 +01:00
bool deployDesktopFile(const DesktopFile& desktopFile) {
2018-06-01 15:27:08 +02:00
if (hasBeenVisitedAlready(desktopFile.path())) {
ldLog() << LD_DEBUG << "File has been visited already:" << desktopFile.path() << std::endl;
2018-05-30 19:21:08 +02:00
return true;
}
2018-06-18 03:12:54 +02:00
if (!desktopFile.validate()) {
2018-06-18 03:13:09 +02:00
ldLog() << LD_ERROR << "Failed to validate desktop file:" << desktopFile.path() << std::endl;
2018-05-30 19:21:08 +02:00
}
ldLog() << "Deploying desktop file" << desktopFile.path() << std::endl;
2021-10-16 03:22:02 +02:00
deployFile(desktopFile.path(), appDirPath / "usr/share/applications/", DEFAULT_PERMS);
2018-05-30 19:21:08 +02:00
return true;
}
bool deployIcon(const bf::path& path, const std::string targetFilename = "") {
2018-06-01 15:27:08 +02:00
if (hasBeenVisitedAlready(path)) {
ldLog() << LD_DEBUG << "File has been visited already:" << path << std::endl;
2018-05-30 19:21:08 +02:00
return true;
}
ldLog() << "Deploying icon" << path << std::endl;
2018-06-16 03:33:55 +02:00
std::string resolution;
2018-05-30 19:21:08 +02:00
// if file is a vector image, use "scalable" directory
2018-06-16 03:50:43 +02:00
if (util::strLower(path.filename().extension().string()) == ".svg") {
2018-05-30 19:21:08 +02:00
resolution = "scalable";
} else {
2018-06-16 03:33:55 +02:00
try {
2018-06-25 17:37:21 +02:00
CImg<unsigned char> image(path.c_str());
2018-06-16 03:33:55 +02:00
2018-06-25 17:37:21 +02:00
auto xRes = image.width();
auto yRes = image.height();
2018-06-16 03:33:55 +02:00
2018-06-25 17:37:21 +02:00
if (xRes != yRes) {
ldLog() << LD_WARNING << "x and y resolution of icon are not equal:" << path;
}
2018-06-16 03:33:55 +02:00
2018-06-25 17:37:21 +02:00
resolution = std::to_string(xRes) + "x" + std::to_string(yRes);
2018-06-16 03:33:55 +02:00
2018-06-25 17:37:21 +02:00
// otherwise, test resolution against "known good" values, and reject invalid ones
2019-05-24 16:19:13 +02:00
const auto knownResolutions = {8, 16, 20, 22, 24, 28, 32, 36, 42, 48, 64, 72, 96, 128, 160, 192, 256, 384, 480, 512};
2018-05-30 19:21:08 +02:00
2018-06-25 17:37:21 +02:00
// assume invalid
bool invalidXRes = true, invalidYRes = true;
2018-05-30 19:21:08 +02:00
2018-06-25 17:37:21 +02:00
for (const auto res : knownResolutions) {
if (xRes == res)
invalidXRes = false;
if (yRes == res)
invalidYRes = false;
}
2018-05-30 19:21:08 +02:00
2019-05-24 23:25:03 +02:00
auto printIconHint = [&knownResolutions]() {
std::stringstream ss;
for (const auto res : knownResolutions) {
ss << res << "x" << res;
if (res != *(knownResolutions.end() - 1))
ss << ", ";
}
ldLog() << LD_ERROR << "Valid resolutions for icons are:" << ss.str() << std::endl;
};
2018-06-25 17:37:21 +02:00
if (invalidXRes) {
2019-05-24 22:39:18 +02:00
ldLog() << LD_ERROR << "Icon" << path << "has invalid x resolution:" << xRes << std::endl;
2019-05-24 23:25:03 +02:00
printIconHint();
2018-06-25 17:37:21 +02:00
return false;
}
2018-05-30 19:21:08 +02:00
2018-06-25 17:37:21 +02:00
if (invalidYRes) {
2019-05-24 22:39:18 +02:00
ldLog() << LD_ERROR << "Icon" << path << "has invalid y resolution:" << yRes << std::endl;
2019-05-24 23:25:03 +02:00
printIconHint();
2018-06-25 17:37:21 +02:00
return false;
}
} catch (const CImgException& e) {
ldLog() << LD_ERROR << "CImg error: " << e.what() << std::endl;
2018-05-30 19:21:08 +02:00
return false;
}
}
auto filename = path.filename().string();
// if the user wants us to automatically rename icon files, we can do so
// this is useful when passing multiple icons via -i in different resolutions
if (!targetFilename.empty()) {
auto newFilename = targetFilename + path.extension().string();
if (newFilename != filename) {
ldLog() << LD_WARNING << "Changing name of icon" << path << "to target filename" << newFilename << std::endl;
filename = newFilename;
}
}
2021-10-16 03:22:02 +02:00
deployFile(path, appDirPath / "usr/share/icons/hicolor" / resolution / "apps" / filename, DEFAULT_PERMS);
2018-06-26 03:15:01 +02:00
deployCopyrightFiles(path);
2018-05-30 19:21:08 +02:00
return true;
}
2019-01-29 01:00:36 +01:00
2019-07-25 17:43:45 +02:00
static bool isInDebugSymbolsLocation(const bf::path& path) {
2019-01-29 01:00:36 +01:00
// TODO: check if there's more potential locations for debug symbol files
for (const std::string& dbgSymbolsPrefix : {".debug/"}) {
if (path.string().substr(0, dbgSymbolsPrefix.size()) == dbgSymbolsPrefix)
return true;
}
return false;
}
2018-05-30 19:21:08 +02:00
};
AppDir::AppDir(const bf::path& path) {
2019-01-29 00:02:49 +01:00
d = std::make_shared<PrivateData>();
2018-05-30 19:21:08 +02:00
d->appDirPath = path;
}
AppDir::AppDir(const std::string& path) : AppDir(bf::path(path)) {}
2019-07-27 16:37:16 +02:00
bool AppDir::createBasicStructure() const {
2018-05-30 19:21:08 +02:00
std::vector<std::string> dirPaths = {
"usr/bin/",
"usr/lib/",
"usr/share/applications/",
"usr/share/icons/hicolor/",
};
for (const std::string& resolution : {"16x16", "32x32", "64x64", "128x128", "256x256", "scalable"}) {
auto iconPath = "usr/share/icons/hicolor/" + resolution + "/apps/";
dirPaths.push_back(iconPath);
}
for (const auto& dirPath : dirPaths) {
auto fullDirPath = d->appDirPath / dirPath;
ldLog() << "Creating directory" << fullDirPath << std::endl;
// skip directory if it exists
if (bf::is_directory(fullDirPath))
continue;
try {
bf::create_directories(fullDirPath);
} catch (const bf::filesystem_error&) {
ldLog() << LD_ERROR << "Failed to create directory" << fullDirPath;
return false;
}
}
return true;
}
bool AppDir::deployLibrary(const bf::path& path, const bf::path& destination) {
return d->deployLibrary(path, false, true, destination);
2018-06-30 23:27:02 +02:00
}
bool AppDir::forceDeployLibrary(const bf::path& path, const bf::path& destination) {
return d->deployLibrary(path, true, true, destination);
2018-05-30 19:21:08 +02:00
}
bool AppDir::deployExecutable(const bf::path& path, const boost::filesystem::path& destination) {
return d->deployExecutable(path, destination);
2018-05-30 19:21:08 +02:00
}
2018-11-15 20:53:16 +01:00
bool AppDir::deployDesktopFile(const DesktopFile& desktopFile) {
2018-05-30 19:21:08 +02:00
return d->deployDesktopFile(desktopFile);
}
2018-05-30 19:21:08 +02:00
bool AppDir::deployIcon(const bf::path& path) {
return d->deployIcon(path);
}
bool AppDir::deployIcon(const bf::path& path, const std::string& targetFilename) {
return d->deployIcon(path, targetFilename);
}
2018-05-30 19:21:08 +02:00
bool AppDir::executeDeferredOperations() {
return d->executeDeferredOperations();
}
2019-07-27 16:37:16 +02:00
boost::filesystem::path AppDir::path() const {
2018-05-30 19:21:08 +02:00
return d->appDirPath;
}
static std::vector<bf::path> listFilesInDirectory(const bf::path& path, const bool recursive = true) {
std::vector<bf::path> foundPaths;
2018-06-16 00:49:18 +02:00
// directory_iterators throw exceptions if the directory doesn't exist
2018-06-23 23:35:33 +02:00
if (!bf::is_directory(path)) {
ldLog() << LD_DEBUG << "No such directory:" << path << std::endl;
2018-06-16 00:49:18 +02:00
return {};
2018-06-23 23:35:33 +02:00
}
2018-06-16 00:49:18 +02:00
2018-05-30 19:21:08 +02:00
if (recursive) {
for (bf::recursive_directory_iterator i(path); i != bf::recursive_directory_iterator(); ++i) {
if (bf::is_regular_file(*i)) {
foundPaths.push_back((*i).path());
2018-05-30 19:21:08 +02:00
}
}
} else {
for (bf::directory_iterator i(path); i != bf::directory_iterator(); ++i) {
if (bf::is_regular_file(*i)) {
foundPaths.push_back((*i).path());
2018-05-30 19:21:08 +02:00
}
}
}
return foundPaths;
}
2019-07-27 16:37:16 +02:00
std::vector<bf::path> AppDir::deployedIconPaths() const {
auto icons = listFilesInDirectory(path() / "usr/share/icons/");
auto pixmaps = listFilesInDirectory(path() / "usr/share/pixmaps/", false);
icons.reserve(pixmaps.size());
std::copy(pixmaps.begin(), pixmaps.end(), std::back_inserter(icons));
return icons;
2018-05-30 19:21:08 +02:00
}
2019-07-27 16:37:16 +02:00
std::vector<bf::path> AppDir::deployedExecutablePaths() const {
2018-06-16 02:56:46 +02:00
return listFilesInDirectory(path() / "usr/bin/", false);
2018-05-30 19:21:08 +02:00
}
2019-07-27 16:37:16 +02:00
std::vector<DesktopFile> AppDir::deployedDesktopFiles() const {
2018-11-15 20:53:16 +01:00
std::vector<DesktopFile> desktopFiles;
2018-05-30 19:21:08 +02:00
auto paths = listFilesInDirectory(path() / "usr/share/applications/", false);
paths.erase(std::remove_if(paths.begin(), paths.end(), [](const bf::path& path) {
return path.extension() != ".desktop";
}), paths.end());
for (const auto& path : paths) {
2018-12-22 23:09:04 +01:00
desktopFiles.emplace_back(path.string());
2018-05-30 19:21:08 +02:00
}
return desktopFiles;
}
bool AppDir::setUpAppDirRoot(const DesktopFile& desktopFile, boost::filesystem::path customAppRunPath) {
AppDirRootSetup setup(*this);
2019-08-15 00:55:31 +02:00
return setup.run(desktopFile, customAppRunPath);
2018-05-30 19:21:08 +02:00
}
2019-02-06 23:23:48 +01:00
bf::path AppDir::deployFile(const boost::filesystem::path& from, const boost::filesystem::path& to) {
2021-10-16 03:22:02 +02:00
return d->deployFile(from, to, DEFAULT_PERMS, true);
2018-06-11 23:04:37 +02:00
}
bool AppDir::copyFile(const bf::path& from, const bf::path& to, bool overwrite) const {
2021-10-16 03:22:02 +02:00
return d->copyFile(from, to, DEFAULT_PERMS, overwrite);
}
2019-07-27 16:37:16 +02:00
bool AppDir::createRelativeSymlink(const bf::path& target, const bf::path& symlink) const {
2018-11-19 21:50:29 +01:00
return d->symlinkFile(target, symlink, true);
}
2019-07-27 16:37:16 +02:00
std::vector<bf::path> AppDir::listExecutables() const {
std::vector<bf::path> executables;
for (const auto& file : listFilesInDirectory(path() / "usr" / "bin", false)) {
2018-06-20 21:34:26 +02:00
// make sure it's an ELF file
try {
2021-05-28 23:14:51 +02:00
elf_file::ElfFile elfFile(file);
} catch (const elf_file::ElfFileParseError&) {
2018-06-20 21:34:26 +02:00
// FIXME: remove this workaround once the MIME check below works as intended
continue;
}
2018-08-20 23:54:35 +02:00
executables.push_back(file);
}
return executables;
}
2019-07-27 16:37:16 +02:00
std::vector<bf::path> AppDir::listSharedLibraries() const {
std::vector<bf::path> sharedLibraries;
for (const auto& file : listFilesInDirectory(path() / "usr" / "lib", true)) {
2019-01-29 01:00:36 +01:00
// exclude debug symbols
if (d->isInDebugSymbolsLocation(file))
continue;
2018-06-20 21:34:26 +02:00
// make sure it's an ELF file
try {
2021-05-28 23:14:51 +02:00
elf_file::ElfFile elfFile(file);
} catch (const elf_file::ElfFileParseError&) {
2018-06-20 21:34:26 +02:00
// FIXME: remove this workaround once the MIME check below works as intended
continue;
}
2018-08-20 23:54:35 +02:00
sharedLibraries.push_back(file);
}
return sharedLibraries;
}
2019-07-27 16:37:16 +02:00
bool AppDir::deployDependenciesForExistingFiles() const {
for (const auto& executable : listExecutables()) {
if (bf::is_symlink(executable))
continue;
if (!d->deployElfDependencies(executable))
return false;
2018-09-03 23:30:53 +02:00
std::string rpath = "$ORIGIN/../" + PrivateData::getLibraryDirName(executable);
2018-09-03 21:07:43 +02:00
d->setElfRPathOperations[executable] = rpath;
}
for (const auto& sharedLibrary : listSharedLibraries()) {
if (bf::is_symlink(sharedLibrary))
continue;
if (!d->deployElfDependencies(sharedLibrary))
return false;
d->setElfRPathOperations[sharedLibrary] = "$ORIGIN";
}
// used to bundle dependencies of executables or libraries in the AppDir without moving them
// useful e.g., for plugin systems, etc.
{
constexpr auto VAR_NAME = "ADDITIONAL_BIN_DIRS";
const auto additionalBinDirs = getenv(VAR_NAME);
if (additionalBinDirs != nullptr) {
ldLog() << LD_DEBUG << "Read value of" << VAR_NAME << LD_NO_SPACE << ":" << additionalBinDirs << std::endl;
auto additionalBinaryDirs = util::split(getenv(VAR_NAME));
for (const auto& additionalBinaryDir : additionalBinaryDirs) {
ldLog() << "Deploying additional executables in directory:" << additionalBinaryDir << std::endl;
if (!bf::is_directory(additionalBinaryDir)) {
ldLog() << LD_ERROR << "Could not find additional binary dir, skipping:" << additionalBinaryDir;
}
for (bf::directory_iterator it(additionalBinaryDir); it != bf::directory_iterator(); ++it) {
const auto entry = *it;
const auto& path = entry.path();
// can't bundle directories
if (!bf::is_regular_file(entry)) {
ldLog() << LD_DEBUG << "Skipping non-file directory entry:" << entry.path() << std::endl;
continue;
}
// make sure we have an ELF file
try {
2021-05-28 23:14:51 +02:00
elf_file::ElfFile(entry.path().string());
} catch (const elf_file::ElfFileParseError& e) {
ldLog() << LD_DEBUG << "Skipping non-ELF directory entry:" << entry.path() << std::endl;
}
ldLog() << "Deploying additional executable:" << entry.path().string() << std::endl;
// bundle dependencies
if (!d->deployElfDependencies(path))
return false;
// set rpath correctly
const auto rpathDestination = this->path() / "usr/lib";
const auto rpath = PrivateData::calculateRelativeRPath(additionalBinaryDir, rpathDestination);
ldLog() << LD_DEBUG << "Calculated rpath:" << rpath << std::endl;
d->setElfRPathOperations[path] = rpath;
}
}
}
}
return true;
}
2020-07-15 03:17:37 +02:00
// TODO: quite similar to deployDependenciesForExistingFiles... maybe they should be merged or use each other
bool AppDir::deployDependenciesOnlyForElfFile(const boost::filesystem::path& elfFilePath, bool failSilentForNonElfFile) {
// preconditions: file must be an ELF one, and file must be contained in the AppDir
2021-05-25 23:17:41 +02:00
const auto canonicalElfFilePath = bf::canonical(elfFilePath);
2020-07-15 03:17:37 +02:00
// can't bundle directories
2021-05-25 23:17:41 +02:00
if (!bf::is_regular_file(canonicalElfFilePath)) {
ldLog() << LD_DEBUG << "Skipping non-file directory entry:" << canonicalElfFilePath << std::endl;
2020-07-15 03:17:37 +02:00
return false;
}
2021-05-25 23:17:41 +02:00
// to do a proper prefix check, we need a proper absolute canonical path for the AppDir
const auto canonicalAppDirPath = bf::canonical(this->path());
ldLog() << LD_DEBUG << "absolute canonical AppDir path:" << canonicalAppDirPath << std::endl;
2020-07-15 03:17:37 +02:00
// a fancy way to check STL strings for prefixes is to "ab"use rfind
2021-05-25 23:17:41 +02:00
if (canonicalElfFilePath.string().rfind(canonicalAppDirPath.string()) != 0) {
ldLog() << LD_ERROR << "File" << canonicalElfFilePath << "is not contained in AppDir, its dependencies cannot be deployed into the AppDir" << std::endl;
2020-07-15 03:17:37 +02:00
return false;
}
// make sure we have an ELF file
try {
2021-05-28 23:14:51 +02:00
elf_file::ElfFile(canonicalElfFilePath.string());
} catch (const elf_file::ElfFileParseError& e) {
2020-07-15 03:17:37 +02:00
auto level = LD_ERROR;
if (failSilentForNonElfFile) {
level = LD_WARNING;
}
2021-05-25 23:17:41 +02:00
ldLog() << level << "Not an ELF file:" << canonicalElfFilePath << std::endl;
2020-07-15 03:17:37 +02:00
return failSilentForNonElfFile;
}
// relative path makes for a nicer and more consistent log
ldLog() << "Deploying dependencies for ELF file in AppDir:" << elfFilePath << std::endl;
// bundle dependencies
2021-05-25 23:17:41 +02:00
if (!d->deployElfDependencies(canonicalElfFilePath))
2020-07-15 03:17:37 +02:00
return false;
// set rpath correctly
const auto rpathDestination = this->path() / "usr/lib";
ldLog() << LD_DEBUG << "rpath destination:" << rpathDestination << std::endl;
const auto rpath = PrivateData::calculateRelativeRPath(elfFilePath.parent_path(), rpathDestination);
ldLog() << LD_DEBUG << "Calculated rpath:" << rpath << std::endl;
2021-05-25 23:17:41 +02:00
d->setElfRPathOperations[canonicalElfFilePath] = rpath;
2020-07-15 03:17:37 +02:00
return true;
}
2018-11-19 21:49:56 +01:00
void AppDir::setDisableCopyrightFilesDeployment(bool disable) {
d->disableCopyrightFilesDeployment = disable;
}
2018-05-30 19:21:08 +02:00
}
}
}