File indexing completed on 2024-05-12 05:45:31

0001 /*
0002     Copyright Hannah von Reth <vonreth@kde.org>
0003 
0004     Redistribution and use in source and binary forms, with or without
0005     modification, are permitted provided that the following conditions
0006     are met:
0007     1. Redistributions of source code must retain the above copyright
0008        notice, this list of conditions and the following disclaimer.
0009     2. Redistributions in binary form must reproduce the above copyright
0010        notice, this list of conditions and the following disclaimer in the
0011        documentation and/or other materials provided with the distribution.
0012 
0013     THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
0014     ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
0015     IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
0016     ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
0017     FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
0018     DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
0019     OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
0020     HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
0021     LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
0022     OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
0023     SUCH DAMAGE.
0024 */
0025 #include "kshimgen.h"
0026 #include "kshimdata.h"
0027 #include "kshimgen_p.h"
0028 
0029 #include <algorithm>
0030 #include <cstring>
0031 
0032 #ifndef _WIN32
0033 #include <sys/stat.h>
0034 #endif
0035 
0036 #include <cmrc/cmrc.hpp>
0037 CMRC_DECLARE(KShimEmbeddeResource);
0038 
0039 namespace {
0040 
0041 std::filesystem::path normaliseApplicationName(const std::filesystem::path &app)
0042 {
0043 #ifdef _WIN32
0044     std::filesystem::path out = app;
0045     out.replace_extension(KShimLib::exeSuffixW);
0046     return out;
0047 #else
0048     return app;
0049 #endif
0050 }
0051 
0052 std::vector<char> readBinary(bool createGuiApplication)
0053 {
0054     std::string name = "bin/kshim"s + std::string(KShimLib::exeSuffix);
0055 #ifdef _WIN32
0056     if (createGuiApplication) {
0057         name = "bin/kshimgui"s + std::string(KShimLib::exeSuffix);
0058     }
0059 #else
0060     (void)createGuiApplication;
0061 #endif
0062     const auto filesystem = cmrc::KShimEmbeddeResource::get_filesystem();
0063     const auto binary = filesystem.open(name);
0064     return std::vector<char>(binary.begin(), binary.end());
0065 }
0066 
0067 bool writeBinary(const std::filesystem::path &name, const KShimData &shimData,
0068                  const std::vector<char> &binary)
0069 {
0070     std::vector<char> dataOut = binary;
0071 
0072     const auto &_name = name;
0073     {
0074         std::ofstream out(_name, std::ios::out | std::ios::binary);
0075         if (!out.is_open()) {
0076             kLog2(KLog::Type::Error) << "Failed to open out: " << name;
0077             return false;
0078         }
0079         out.write(dataOut.data(), static_cast<std::streamsize>(binary.size()));
0080 
0081         kLog << "Wrote: " << name << " " << out.tellp() << " bytes";
0082         out.close();
0083     }
0084 
0085     KShimGenPrivate::setPayload(name, shimData.toJson());
0086 
0087 #ifndef _WIN32
0088     chmod(name.string().data(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
0089 #ifdef __APPLE__
0090     const auto codesign = KShimLib::findInPath(std::filesystem::path(KShimLib::string("codesign")));
0091     const int result = KShimLib::run(KShimData(codesign), { "-s", "-", name.string() });
0092     if (result != 0) {
0093         kLog << "Faied to sign" << name.string() << "exit code:" << result;
0094         return false;
0095     }
0096 #endif
0097 #else
0098     // we can't use shimData.appAbs() as it depends on the location of the current binary, which
0099     // atm is kshimgen
0100     auto src = shimData.app();
0101     if (!src.is_absolute()) {
0102         src = name.parent_path() / shimData.app();
0103     }
0104     if (!KShimLib::exists(src)) {
0105         src = KShimLib::findInPath(shimData.app());
0106     }
0107     KShimGenPrivate::updateIcon(src, name);
0108 #endif
0109     return true;
0110 }
0111 }
0112 
0113 bool KShimGen::createShim(const KShimLib::string_view &appName, const std::filesystem::path &target,
0114                           const std::vector<KShimLib::string_view> &args,
0115                           const std::vector<KShimLib::string_view> &_env, bool createGuiApplication,
0116                           bool enableEnvOverride, bool keepArg0)
0117 {
0118     std::vector<std::pair<KShimLib::string_view, KShimLib::string_view>> env;
0119     env.reserve(_env.size());
0120     for (const auto &e : _env) {
0121         const auto pos = e.find('=');
0122         env.push_back({ e.substr(0, pos), e.substr(pos + 1) });
0123     }
0124     const auto outApp = normaliseApplicationName(appName);
0125     KShimData shimData;
0126     shimData.setApp(target);
0127     shimData.setArgs(args);
0128     shimData.setEnv(env);
0129     shimData.setEnvOverrideEnabled(enableEnvOverride);
0130     shimData.setKeepArgv0Enabled(keepArg0);
0131     const std::vector<char> binary = readBinary(createGuiApplication);
0132     if (!binary.empty()) {
0133         return writeBinary(outApp, shimData, binary);
0134     }
0135     return false;
0136 }
0137 
0138 int KShimGen::main(const std::vector<KShimLib::string_view> &args)
0139 {
0140     KShimLib::string_view target;
0141     KShimLib::string_view app;
0142     std::vector<KShimLib::string_view> arguments;
0143     std::vector<KShimLib::string_view> env;
0144     bool gui = false;
0145     bool enableEnvOverride = false;
0146     bool keepArgv0 = false;
0147 
0148     auto help = [](const KShimLib::string_view &msg) {
0149         kLog2(KLog::Type::Error)
0150                 << msg << "\n"
0151                 << "--create shim target\t\t\tCreate a shim\n"
0152                 << "--env key=val\t\t\t\tadditional environment varriables for the shim\n"
0153                 << "--enable-env-override\t\t\twhether to allow overriding the target with "
0154                    "the env var KSHIM_shim\n"
0155                 << "--keep-argv0\t\t\t\twhether to keep the original arg0 (emulates symlinks)\n"
0156 #ifdef _WIN32
0157                 << "--gui\t\t\t\t\tcreate a gui application (only supported on Windows)\n"
0158 #endif
0159                 << "-- arg1 arg2 arg3...\t\t\targuments that get passed to the target";
0160     };
0161     auto nextArg = [&](std::vector<KShimLib::string_view>::const_iterator &it,
0162                        const KShimLib::string_view &helpText) -> KShimLib::string_view {
0163         if (it != args.cend()) {
0164             return *it++;
0165         } else {
0166             help(helpText);
0167             exit(1);
0168         }
0169     };
0170 
0171     auto it = args.cbegin() + 1;
0172     while (it != args.cend()) {
0173         const auto arg = nextArg(it, KSTRING(""));
0174         if (arg == KSTRING("--create")) {
0175             const auto msg = KSTRING("--create shim target");
0176             app = nextArg(it, msg);
0177             target = nextArg(it, msg);
0178         } else if (arg == KSTRING("--env")) {
0179             env.push_back(nextArg(it, KSTRING("--env key=val")));
0180         } else if (arg == KSTRING("--enable-env-override")) {
0181             enableEnvOverride = true;
0182         } else if (arg == KSTRING("--keep-argv0")) {
0183             keepArgv0 = true;
0184         } else if (arg == KSTRING("--")) {
0185             while (it != args.cend()) {
0186                 arguments.push_back(nextArg(it, KSTRING("")));
0187             }
0188             break;
0189 #ifdef _WIN32
0190         } else if (arg == KSTRING("--gui")) {
0191             gui = true;
0192 #endif
0193         } else if (arg == KSTRING("-h")) {
0194             help(KSTRING(""));
0195             return 0;
0196         } else {
0197             KShimLib::stringstream str;
0198             str << "Unknwon arg " << arg;
0199             help(str.str());
0200         }
0201     }
0202     if (enableEnvOverride) {
0203         if (!env.empty()) {
0204             help(KSTRING("--enable-env-override and --env are mutual exclusive"s));
0205         }
0206         if (!arguments.empty()) {
0207             help(KSTRING(
0208                     "When using --enable-env-override it is not supported to provide extra args after --"s));
0209         }
0210     }
0211     if (!target.empty()) {
0212         return KShimGen::createShim(app, target, arguments, env, gui, enableEnvOverride, keepArgv0)
0213                 ? 0
0214                 : -1;
0215     }
0216     help(KSTRING(""));
0217     return -1;
0218 }