linuxdeploy/src/core/elf.cpp
TheAssassin 030b21379b Move exception to header
Otherwise, it obviously can't be caught by users of the ElfFile class.
2018-06-04 14:08:24 +02:00

171 lines
6.3 KiB
C++

// system includes
#include <fstream>
// library includes
#include <boost/regex.hpp>
#include <subprocess.hpp>
// local headers
#include "linuxdeploy/core/elf.h"
#include "linuxdeploy/core/log.h"
#include "util.h"
using namespace linuxdeploy::core::log;
namespace bf = boost::filesystem;
namespace linuxdeploy {
namespace core {
namespace elf {
class ElfFile::PrivateData {
public:
const bf::path path;
public:
explicit PrivateData(const bf::path& path) : path(path) {}
};
ElfFile::ElfFile(const boost::filesystem::path& path) {
// check if file exists
if (!bf::exists(path))
throw ElfFileParseError("No such file or directory: " + path.string());
// check magic bytes
std::ifstream ifs(path.string());
if (!ifs)
throw ElfFileParseError("Could not open file: " + path.string());
std::vector<char> magicBytes(4);
ifs.read(magicBytes.data(), 4);
if (strncmp(magicBytes.data(), "\177ELF", 4) != 0)
throw ElfFileParseError("Invalid magic bytes in file header");
d = new PrivateData(path);
}
ElfFile::~ElfFile() {
delete d;
}
std::vector<bf::path> ElfFile::traceDynamicDependencies() {
// this method's purpose is to abstract this process
// the caller doesn't care _how_ it's done, after all
// for now, we use the same ldd based method linuxdeployqt uses
std::vector<bf::path> paths;
subprocess::Popen lddProc(
{"ldd", d->path.string().c_str()},
subprocess::output{subprocess::PIPE},
subprocess::error{subprocess::PIPE}
);
auto lddOutput = lddProc.communicate();
auto& lddStdout = lddOutput.first;
auto& lddStderr = lddOutput.second;
if (lddProc.retcode() != 0) {
ldLog() << LD_ERROR << "Call to ldd failed:" << std::endl << lddStderr.buf.data() << std::endl;
return {};
}
std::string lddStdoutContents(lddStdout.buf.data());
const boost::regex expr(R"(\s*(.+)\s+\=>\s+(.+)\s+\((.+)\)\s*)");
boost::smatch what;
for (const auto& line : util::splitLines(lddStdoutContents)) {
if (boost::regex_search(line, what, expr)) {
auto libraryPath = what[2].str();
util::trim(libraryPath);
paths.push_back(bf::absolute(libraryPath));
} else {
ldLog() << LD_DEBUG << "Invalid ldd output: " << line << std::endl;
}
}
return paths;
}
std::string getPatchelfPath() {
// by default, try to use a patchelf next to the linuxdeploy binary
// if that isn't available, fall back to searching for patchelf in the PATH
std::string patchelfPath = "patchelf";
// FIXME: reading /proc/self/exe line is Linux specific
std::vector<char> buf(PATH_MAX, '\0');
if (readlink("/proc/self/exe", buf.data(), buf.size()) != -1) {
auto binDirPath = bf::path(buf.data());
auto localPatchelfPath = binDirPath.parent_path() / "patchelf";
if (bf::exists(localPatchelfPath))
patchelfPath = localPatchelfPath.string();
}
ldLog() << LD_DEBUG << "Using patchelf:" << patchelfPath << std::endl;
return patchelfPath;
}
std::string ElfFile::getRPath() {
try {
subprocess::Popen patchelfProc(
{getPatchelfPath().c_str(), "--print-rpath", d->path.c_str()},
subprocess::output(subprocess::PIPE),
subprocess::error(subprocess::PIPE)
);
auto patchelfOutput = patchelfProc.communicate();
auto& patchelfStdout = patchelfOutput.first;
auto& patchelfStderr = patchelfOutput.second;
if (patchelfProc.retcode() != 0) {
std::string errStr(patchelfStderr.buf.data());
// if file is not an ELF executable, there is no need for a detailed error message
if (patchelfProc.retcode() == 1 && errStr.find("not an ELF executable")) {
return "";
} else {
ldLog() << LD_ERROR << "Call to patchelf failed:" << std::endl << errStr;
return "";
}
}
std::string retval = patchelfStdout.buf.data();
util::trim(retval, '\n');
util::trim(retval);
return retval;
} catch (const std::exception&) {
return "";
}
}
bool ElfFile::setRPath(const std::string& value) {
try {
subprocess::Popen patchelfProc(
{getPatchelfPath().c_str(), "--set-rpath", value.c_str(), d->path.c_str()},
subprocess::output(subprocess::PIPE),
subprocess::error(subprocess::PIPE)
);
auto patchelfOutput = patchelfProc.communicate();
auto& patchelfStdout = patchelfOutput.first;
auto& patchelfStderr = patchelfOutput.second;
if (patchelfProc.retcode() != 0) {
ldLog() << LD_ERROR << "Call to patchelf failed:" << std::endl << patchelfStderr.buf;
return false;
}
} catch (const std::exception&) {
return false;
}
return true;
}
}
}
}