diff --git a/apps/ll-box/src/util/platform.cpp b/apps/ll-box/src/util/platform.cpp index 82b5e3f9f..7ba5a2996 100644 --- a/apps/ll-box/src/util/platform.cpp +++ b/apps/ll-box/src/util/platform.cpp @@ -90,7 +90,7 @@ static bool parse_wstatus(const int &wstatus, std::string &info) static int DoWait(const int pid, int target = 0) { logDbg() << util::format("DoWait called with pid=%d, target=%d", pid, target); - int wstatus; + int wstatus{ -1 }; while (int child = waitpid(pid, &wstatus, 0)) { if (child > 0) { std::string info; @@ -110,15 +110,16 @@ static int DoWait(const int pid, int target = 0) if (errno == ECHILD) { logDbg() << format("no child to wait"); return -1; - } else { - auto string = errnoString(); - logErr() << format("waitpid failed, %s", string.c_str()); - return -1; } + + auto string = errnoString(); + logErr() << format("waitpid failed, %s", string.c_str()); + return -1; } } // when we pass options=0 to waitpid, it will never return 0 logWan() << "waitpid return 0, this should not happen normally"; + return -1; } // wait all child diff --git a/apps/uab/header/CMakeLists.txt b/apps/uab/header/CMakeLists.txt index 6bb1d1c52..0c9d92be6 100644 --- a/apps/uab/header/CMakeLists.txt +++ b/apps/uab/header/CMakeLists.txt @@ -35,6 +35,7 @@ pfl_add_executable( LINK_LIBRARIES PRIVATE ${ABS_FILE} + linglong::api PkgConfig::elf_static PkgConfig::lz4 PkgConfig::lzma diff --git a/apps/uab/header/src/main.cpp b/apps/uab/header/src/main.cpp index 879a41bbc..0d2f7af30 100644 --- a/apps/uab/header/src/main.cpp +++ b/apps/uab/header/src/main.cpp @@ -2,6 +2,9 @@ // // SPDX-License-Identifier: LGPL-3.0-or-later +#include "linglong/api/types/v1/Generators.hpp" +#include "linglong/api/types/v1/UabMetaInfo.hpp" + #include #include #include @@ -42,19 +45,31 @@ uabBundle [uabOptions...] [-- loaderOptions...] --help print usage of uab [exclusive] )"; -std::string resolveRealPath(const std::string &source) +template +struct defer +{ + explicit defer(Func newF) + : f(std::move(newF)) + { + } + + ~defer() { f(); } + +private: + Func f; +}; + +std::string resolveRealPath(std::string_view source) noexcept { - std::string ret; std::array resolvedPath{}; - resolvedPath.fill(0); - if (::realpath(source.c_str(), resolvedPath._M_elems) == nullptr) { - std::cerr << "failed to resolve path:" << strerror(errno) << std::endl; - return ret; + auto *ptr = ::realpath(source.data(), resolvedPath.data()); + if (ptr == nullptr) { + std::cerr << "failed to resolve path:" << ::strerror(errno) << std::endl; + return {}; } - std::copy_n(resolvedPath.cbegin(), ::strlen(resolvedPath._M_elems), std::back_inserter(ret)); - return ret; + return { ptr }; } std::string detectLinglong() noexcept @@ -65,7 +80,10 @@ std::string detectLinglong() noexcept return {}; } - struct stat sb; + struct stat sb + { + }; + std::string path{ pathPtr }; std::string cliPath; size_t startPos = 0; @@ -73,7 +91,7 @@ std::string detectLinglong() noexcept while ((endPos = path.find(':', startPos)) != std::string::npos) { std::string binPath = path.substr(startPos, endPos - startPos) + "/ll-cli"; - if ((::stat(binPath.c_str(), &sb) == 0) && (sb.st_mode & S_IXOTH)) { + if ((::stat(binPath.c_str(), &sb) == 0) && ((sb.st_mode & S_IXOTH) != 0U)) { cliPath = binPath; break; } @@ -84,37 +102,117 @@ std::string detectLinglong() noexcept return cliPath; } -int importSelf(const std::string &cliBin, const char *uab) noexcept +int importSelf(std::string_view cliBin, std::string_view appRef, std::string_view uab) noexcept { + std::array out{}; + if (pipe(out.data()) == -1) { + std::cerr << "pipe() failed:" << ::strerror(errno) << std::endl; + return -1; + } + auto pid = fork(); if (pid < 0) { - std::cerr << "fork() failed:" << strerror(errno) << std::endl; + std::cerr << "fork() failed:" << ::strerror(errno) << std::endl; return -1; } if (pid == 0) { - return ::execl(cliBin.c_str(), cliBin.c_str(), "install", uab, nullptr); + ::close(out[0]); + if (::dup2(out[1], STDOUT_FILENO) == -1) { + std::cerr << "dup2() failed in sub-process:" << ::strerror(errno) << std::endl; + return -1; + } + + return ::execl(cliBin.data(), cliBin.data(), "--json", "list", nullptr); + } + + ::close(out[1]); + auto closeReadPipe = defer([fd = out[0]] { + ::close(fd); + }); + + std::array buf{}; + std::string content; + auto bytesRead{ -1 }; + while ((bytesRead = ::read(out[0], buf.data(), buf.size())) != 0) { + if (bytesRead == -1) { + if (errno == EINTR) { + continue; + } + + std::cerr << "read failed:" << ::strerror(errno) << std::endl; + return -1; + } + + content.append(buf.data(), bytesRead); } int status{ 0 }; auto ret = ::waitpid(pid, &status, 0); if (ret == -1) { - std::cerr << "wait failed:" << strerror(errno) << std::endl; + std::cerr << "waitpid() failed:" << ::strerror(errno) << std::endl; + return -1; + } + + if (auto result = WEXITSTATUS(status); result != 0) { + std::cerr << "ll-cli --json list failed, return code:" << result << std::endl; + return -1; + } + + std::vector packages; + try { + auto packagesJson = nlohmann::json::parse(content); + packages = packagesJson.get(); + } catch (nlohmann::detail::parse_error &e) { + std::cerr << "parse content from ll-cli list output error:" << e.what() << std::endl; + return -1; + } catch (std::exception &e) { + std::cerr << "catching an exception when parsing output of ll-cli list:" << e.what() + << std::endl; + return -1; + } catch (...) { + std::cerr << "catching unknown value" << std::endl; + return -1; + } + + for (const auto &package : packages) { + auto curRef = + package.channel + ":" + package.id + "/" + package.version + "/" + package.arch[0]; + if (curRef == appRef) { + return 0; // already exist + } + } + + // install a new application + pid = fork(); + if (pid < 0) { + std::cerr << "fork() failed:" << ::strerror(errno) << std::endl; + return -1; + } + + if (pid == 0) { + return ::execl(cliBin.data(), cliBin.data(), "install", uab, nullptr); + } + + status = -1; + ret = ::waitpid(pid, &status, 0); + if (ret == -1) { + std::cerr << "waitpid() failed:" << ::strerror(errno) << std::endl; return -1; } if (auto result = WEXITSTATUS(status); result != 0) { std::cerr << "ll-cli install failed, return code:" << result << std::endl; - ::exit(-1); + return -1; } return 0; } -std::optional getSectionHeader(int elfFd, const char *sectionName) noexcept +std::optional getSectionHeader(int elfFd, std::string_view sectionName) noexcept { std::error_code ec; - std::optional secHdr{ std::nullopt }; + std::optional secHdr; auto elfPath = std::filesystem::read_symlink(std::filesystem::path{ "/proc/self/fd" } / std::to_string(elfFd), @@ -130,99 +228,92 @@ std::optional getSectionHeader(int elfFd, const char *sectionName) no return std::nullopt; } - { - size_t shdrstrndx{ 0 }; - if (elf_getshdrstrndx(elf, &shdrstrndx) == -1) { - std::cerr << "failed to get section header index of bundle " << elfPath << ":" + auto closeElf = defer([elf] { + elf_end(elf); + }); + + size_t shdrstrndx{ 0 }; + if (elf_getshdrstrndx(elf, &shdrstrndx) == -1) { + std::cerr << "failed to get section header index of bundle " << elfPath << ":" + << elf_errmsg(errno) << std::endl; + return std::nullopt; + } + + Elf_Scn *scn = nullptr; + while ((scn = elf_nextscn(elf, scn)) != nullptr) { + GElf_Shdr shdr; + if (gelf_getshdr(scn, &shdr) == nullptr) { + std::cerr << "failed to get section header of bundle " << elfPath << ":" << elf_errmsg(errno) << std::endl; - goto ret; + break; } - Elf_Scn *scn = nullptr; - std::optional metaSh; - while ((scn = elf_nextscn(elf, scn)) != nullptr) { - GElf_Shdr shdr; - if (gelf_getshdr(scn, &shdr) == nullptr) { - std::cerr << "failed to get section header of bundle " << elfPath << ":" - << elf_errmsg(errno) << std::endl; - break; - } - - auto *sname = elf_strptr(elf, shdrstrndx, shdr.sh_name); - if (::strcmp(sname, sectionName) == 0) { - secHdr = shdr; - break; - } + std::string_view sname = elf_strptr(elf, shdrstrndx, shdr.sh_name); + if (sname == sectionName) { + secHdr = shdr; + break; } } -ret: - elf_end(elf); return secHdr; } -bool digestCheck(int fd, - std::size_t bundleOffset, - std::size_t bundleLength, - const std::string &expectedDigest) noexcept +std::string calculateDigest(int fd, std::size_t bundleOffset, std::size_t bundleLength) noexcept { auto file = ::dup(fd); if (file == -1) { - std::cerr << "dup() error:" << strerror(errno) << std::endl; - return false; + std::cerr << "dup() error:" << ::strerror(errno) << std::endl; + return {}; } - if (::lseek(file, bundleOffset, SEEK_SET) == -1) { - std::cerr << "lseek() error:" << strerror(errno) << std::endl; + auto closeFile = defer([file] { ::close(file); - return false; + }); + + if (::lseek(file, bundleOffset, SEEK_SET) == -1) { + std::cerr << "lseek() error:" << ::strerror(errno) << std::endl; + return {}; } - auto *ctx = EVP_MD_CTX_new(); - if (EVP_DigestInit_ex2(ctx, EVP_sha256(), nullptr) == 0) { + auto ctxDeleter = [](EVP_MD_CTX *self) { + EVP_MD_CTX_free(self); + }; + auto ctx = + std::unique_ptr(EVP_MD_CTX_new(), std::move(ctxDeleter)); + if (EVP_DigestInit_ex2(ctx.get(), EVP_sha256(), nullptr) == 0) { std::cerr << "init digest context error" << std::endl; - ::close(file); - EVP_MD_CTX_free(ctx); - return false; + return {}; } std::array buf{}; std::array md_value{}; - md_value.fill(0); - auto expectedRead = buf.size(); int readLength{ 0 }; unsigned int digestLength{ 0 }; + while ((readLength = ::read(file, buf.data(), expectedRead)) != 0) { if (readLength == -1) { - if (errno == EAGAIN) { + if (errno == EINTR) { continue; } - std::cerr << "read bundle error:" << strerror(errno) << std::endl; - ::close(file); - EVP_MD_CTX_free(ctx); - return false; + std::cerr << "read bundle error:" << ::strerror(errno) << std::endl; + return {}; } - if (EVP_DigestUpdate(ctx, buf.data(), readLength) == 0) { + if (EVP_DigestUpdate(ctx.get(), buf.data(), readLength) == 0) { std::cerr << "update digest error" << std::endl; - ::close(file); - EVP_MD_CTX_free(ctx); - return false; + return {}; } bundleLength -= readLength; - - if (bundleLength <= 0) { - if (EVP_DigestFinal(ctx, md_value._M_elems, &digestLength) == 1) { + if (bundleLength == 0) { + if (EVP_DigestFinal(ctx.get(), md_value.data(), &digestLength) == 1) { break; } - ::close(file); - EVP_MD_CTX_free(ctx); std::cerr << "get digest error" << std::endl; - return false; + return {}; } expectedRead = bundleLength > buf.size() ? buf.size() : bundleLength; @@ -232,61 +323,48 @@ bool digestCheck(int fd, stream << std::setfill('0') << std::hex; for (auto i = 0U; i < digestLength; i++) { - stream << std::setw(2) << static_cast(md_value[i]); - } - - auto digest = stream.str(); - bool same = (digest == expectedDigest); - if (!same) { - std::cerr << "sha256 mismatch, expected: " << expectedDigest << " calculated: " << digest - << std::endl; + stream << std::setw(2) << static_cast(md_value.at(i)); } - ::close(file); - EVP_MD_CTX_free(ctx); - return same; + return stream.str(); } -int mountSelfBundle(const char *selfBin, const nlohmann::json &meta) noexcept +int mountSelfBundle(std::string_view selfBin, + const linglong::api::types::v1::UabMetaInfo &meta) noexcept { - - if (!meta.contains("sections") || !meta["sections"].contains("bundle") - || !meta["sections"]["bundle"].is_string()) { - std::cerr << "couldn't get the name of bundle section" << std::endl; - return -1; - } - - if (!meta.contains("digest") || !meta["digest"].is_string()) { - std::cerr << "couldn't get the digest of bundle section" << std::endl; - return -1; - } - - auto selfBinFd = ::open(selfBin, O_RDONLY | O_CLOEXEC); + auto selfBinFd = ::open(selfBin.data(), O_RDONLY | O_CLOEXEC); if (selfBinFd == -1) { std::cerr << "failed to open bundle " << selfBin << std::endl; return {}; } - const std::string §ionName = meta["sections"]["bundle"]; - auto bundleSh = getSectionHeader(selfBinFd, sectionName.c_str()); - if (!bundleSh) { + auto closeSelfBin = defer([selfBinFd] { ::close(selfBinFd); + }); + + auto bundleSh = getSectionHeader(selfBinFd, meta.sections.bundle); + if (!bundleSh) { + std::cerr << "couldn't get bundle section '" << meta.sections.bundle << "'" << std::endl; return -1; } auto bundleOffset = bundleSh->sh_offset; - if (!digestCheck(selfBinFd, bundleOffset, bundleSh->sh_size, meta["digest"])) { - ::close(selfBinFd); + if (auto digest = calculateDigest(selfBinFd, bundleOffset, bundleSh->sh_size); + digest != meta.digest) { + std::cerr << "sha256 mismatched, expected: " << meta.digest << " calculated: " << digest + << std::endl; return -1; } auto offsetStr = "--offset=" + std::to_string(bundleOffset); - const char *erofs_argv[] = { "erofsfuse", offsetStr.c_str(), selfBin, mountPoint.c_str() }; + std::array erofs_argv = { "erofsfuse", + offsetStr.c_str(), + selfBin.data(), + mountPoint.c_str() }; auto fusePid = fork(); if (fusePid < 0) { - std::cerr << "fork() error:" << strerror(errno) << std::endl; - ::close(selfBinFd); + std::cerr << "fork() error:" << ::strerror(errno) << std::endl; return -1; } @@ -300,14 +378,13 @@ int mountSelfBundle(const char *selfBin, const nlohmann::json &meta) noexcept } } - return erofsfuse_main(4, (char **)erofs_argv); + return erofsfuse_main(4, const_cast(erofs_argv.data())); } int status{ 0 }; auto ret = ::waitpid(fusePid, &status, 0); if (ret == -1) { - std::cerr << "wait failed:" << strerror(errno) << std::endl; - ::close(selfBinFd); + std::cerr << "waitpid() failed:" << ::strerror(errno) << std::endl; return -1; } @@ -317,7 +394,6 @@ int mountSelfBundle(const char *selfBin, const nlohmann::json &meta) noexcept ret = -1; } - ::close(selfBinFd); return ret; } @@ -400,7 +476,7 @@ void handleSig() noexcept } } -int createMountPoint(const std::string &uuid) noexcept +int createMountPoint(std::string_view uuid) noexcept { const char *runtimeDirPtr{ nullptr }; runtimeDirPtr = ::getenv("XDG_RUNTIME_DIR"); @@ -415,7 +491,8 @@ int createMountPoint(const std::string &uuid) noexcept std::error_code ec; if (!std::filesystem::create_directories(mountPointPath, ec)) { if (ec.value() != EEXIST) { - std::cerr << "create mount point " << mountPoint << ": " << ec.message() << std::endl; + std::cerr << "couldn't create mount point " << mountPoint << ": " << ec.message() + << std::endl; return ec.value(); } } @@ -430,42 +507,36 @@ int createMountPoint(const std::string &uuid) noexcept return 0; } -nlohmann::json getMetaInfo(const char *uab) noexcept +std::optional getMetaInfo(std::string_view uab) noexcept { - auto selfBinFd = ::open(uab, O_RDONLY); + auto selfBinFd = ::open(uab.data(), O_RDONLY); if (selfBinFd == -1) { std::cerr << "failed to open bundle " << uab << std::endl; - return {}; + return std::nullopt; } + auto closeUAB = defer([selfBinFd] { + ::close(selfBinFd); + }); + auto metaSh = getSectionHeader(selfBinFd, "linglong.meta"); if (!metaSh) { std::cerr << "couldn't find meta section" << std::endl; - ::close(selfBinFd); - return {}; + return std::nullopt; } if (::lseek(selfBinFd, metaSh->sh_offset, SEEK_SET) == -1) { - std::cerr << "lseek failed:" << strerror(errno) << std::endl; - ::close(selfBinFd); - return {}; - } - - auto *buf = (char *)::malloc(sizeof(char) * metaSh->sh_size); - if (buf == nullptr) { - std::cerr << "malloc failed:" << strerror(errno) << std::endl; - ::close(selfBinFd); - return {}; + std::cerr << "lseek failed:" << ::strerror(errno) << std::endl; + return std::nullopt; } - if (::read(selfBinFd, buf, metaSh->sh_size) == -1) { - std::cerr << "read failed:" << strerror(errno) << std::endl; - ::close(selfBinFd); - ::free(buf); + std::string content; + content.resize(metaSh->sh_size, 0); + if (::read(selfBinFd, content.data(), content.size()) == -1) { + std::cerr << "read failed:" << ::strerror(errno) << std::endl; return {}; } - std::string content(reinterpret_cast(buf)); nlohmann::json meta; try { meta = nlohmann::json::parse(content); @@ -473,12 +544,10 @@ nlohmann::json getMetaInfo(const char *uab) noexcept std::cerr << "parse error: " << ex.what() << std::endl; } - ::close(selfBinFd); - ::free(buf); return meta; } -int extractBundle(const std::string &destination) noexcept +int extractBundle(std::string_view destination) noexcept { std::error_code ec; auto path = std::filesystem::path(destination); @@ -528,36 +597,48 @@ int extractBundle(const std::string &destination) noexcept return 0; } -void runAppLoader(const nlohmann::json &info, const std::vector &loaderArgs) noexcept +[[noreturn]] void runAppLoader(const linglong::api::types::v1::UabMetaInfo &meta, + const std::vector &loaderArgs) noexcept { auto loader = std::filesystem::path{ mountPoint } / "loader"; auto loaderStr = loader.string(); auto argc = loaderArgs.size() + 2; - auto *argv = (const char **)malloc(sizeof(char *) * argc); + auto *argv = new (std::nothrow) const char *[argc](); + if (argv == nullptr) { + std::cerr << "out of memory, exit." << std::endl; + cleanAndExit(ENOMEM); + } + + auto deleter = defer([argv] { + delete[] argv; + }); + argv[0] = loaderStr.c_str(); argv[argc - 1] = nullptr; for (std::size_t i = 0; i < loaderArgs.size(); ++i) { - argv[i + 1] = loaderArgs[i].c_str(); + argv[i + 1] = loaderArgs[i].data(); } std::string baseID; std::string runtimeID; std::string appID; - for (const auto &layer : info["layers"]) { - const auto &kind = layer["info"]["kind"].get(); + for (const auto &layer : meta.layers) { + const auto &kind = layer.info.kind; if (kind == "app") { - appID = layer["info"]["id"]; + appID = layer.info.id; - auto baseStr = layer["info"]["base"].get(); + const auto &baseStr = layer.info.base; auto splitSlash = std::find(baseStr.cbegin(), baseStr.cend(), '/'); auto splitColon = std::find(baseStr.cbegin(), baseStr.cend(), ':'); - baseID = std::string(splitColon + 1, splitSlash); + baseID = baseStr.substr(std::distance(baseStr.cbegin(), splitColon) + 1, + splitSlash - splitColon - 1); - if (layer["info"].contains("runtime")) { - auto runtimeStr = layer["info"]["runtime"].get(); + if (layer.info.runtime) { + const auto &runtimeStr = layer.info.runtime.value(); auto splitSlash = std::find(runtimeStr.cbegin(), runtimeStr.cend(), '/'); auto splitColon = std::find(runtimeStr.cbegin(), runtimeStr.cend(), ':'); - runtimeID = std::string(splitColon + 1, splitSlash); + runtimeID = runtimeStr.substr(std::distance(baseStr.cbegin(), splitColon) + 1, + splitSlash - splitColon - 1); } break; @@ -572,67 +653,64 @@ void runAppLoader(const nlohmann::json &info, const std::vector &lo auto loaderPid = fork(); if (loaderPid < 0) { - std::cerr << "fork() error" << ": " << strerror(errno) << std::endl; + std::cerr << "fork() error" << ": " << ::strerror(errno) << std::endl; cleanAndExit(errno); } if (loaderPid == 0) { - if (::setenv("UAB_BASE_ID", baseID.c_str(), 1) == -1) { - std::cerr << "setenv error:" << strerror(errno) << std::endl; - ::exit(errno); + if (::setenv("UAB_BASE_ID", baseID.data(), 1) == -1) { + std::cerr << "setenv() error:" << ::strerror(errno) << std::endl; + cleanAndExit(errno); } - if (!runtimeID.empty() && ::setenv("UAB_RUNTIME_ID", runtimeID.c_str(), 1) == -1) { - std::cerr << "setenv error:" << strerror(errno) << std::endl; - ::exit(errno); + if (!runtimeID.empty() && ::setenv("UAB_RUNTIME_ID", runtimeID.data(), 1) == -1) { + std::cerr << "setenv() error:" << ::strerror(errno) << std::endl; + cleanAndExit(errno); } - if (::setenv("UAB_APP_ID", appID.c_str(), 1) == -1) { - std::cerr << "setenv error:" << strerror(errno) << std::endl; - ::exit(errno); + if (::setenv("UAB_APP_ID", appID.data(), 1) == -1) { + std::cerr << "setenv() error:" << ::strerror(errno) << std::endl; + cleanAndExit(errno); } std::error_code ec; std::filesystem::current_path(mountPoint, ec); if (ec) { - std::cerr << "change working directory failed: " << ec.message() << std::endl; - ::exit(errno); + std::cerr << "changing working directory failed: " << ec.message() << std::endl; + cleanAndExit(errno); } - if (::execv(loaderStr.c_str(), (char *const *)(argv)) == -1) { - std::cerr << "execv(" << loaderStr << ") error: " << strerror(errno) << std::endl; - ::exit(errno); + if (::execv(loaderStr.c_str(), reinterpret_cast(const_cast(argv))) + == -1) { + std::cerr << "execv(" << loaderStr << ") error: " << ::strerror(errno) << std::endl; + cleanAndExit(errno); } } int status{ 0 }; auto ret = ::waitpid(loaderPid, &status, 0); if (ret == -1) { - std::cerr << "wait failed:" << strerror(errno) << std::endl; + std::cerr << "waitpid failed:" << ::strerror(errno) << std::endl; cleanAndExit(errno); } cleanAndExit(WEXITSTATUS(status)); } -void runAppLinglong(const std::string &cliBin, - const nlohmann::json &info, - [[maybe_unused]] const std::vector &loaderArgs) noexcept +[[noreturn]] void +runAppLinglong(std::string_view cliBin, + const linglong::api::types::v1::UabLayer &layer, + [[maybe_unused]] const std::vector &loaderArgs) noexcept { - if (!info.contains("id") || !info["id"].is_string()) { - std::cerr << "couldn't get appId, stop to delegate runnning operation to linglong" - << std::endl; - cleanAndExit(-1); - } - - const auto &appId = info["id"].get(); + const auto &appId = layer.info.id; std::array argv{}; - argv[0] = cliBin.c_str(); + argv[0] = cliBin.data(); argv[1] = "run"; argv[2] = appId.c_str(); argv[3] = nullptr; - cleanAndExit(::execv(cliBin.c_str(), (char *const *)argv.data())); + cleanAndExit( + ::execv(cliBin.data(), reinterpret_cast(const_cast(argv.data())))); } enum uabOption { @@ -646,40 +724,42 @@ struct argOption bool help{ false }; bool printMeta{ false }; std::string extractPath; - std::vector loaderArgs; + std::vector loaderArgs; }; -argOption parseArgs(int argc, char **argv) +argOption parseArgs(const std::vector &args) { argOption opts; - int splitIndex{ -1 }; - for (std::size_t i = 0; i < argc; ++i) { - if (::strcmp(argv[i], "--") == 0) { - splitIndex = i; - break; - } - } + auto splitter = std::find_if(args.cbegin(), args.cend(), [](std::string_view arg) { + return arg == "--"; + }); - if (splitIndex != -1) { - auto *loaderArgv = argv + splitIndex + 1; - auto loaderArgc = argc - splitIndex - 1; - std::vector loaderArgs{ static_cast(loaderArgc) }; - for (std::size_t i = 0; i < loaderArgc; ++i) { - loaderArgs.emplace_back(loaderArgv[i]); - } - opts.loaderArgs = std::move(loaderArgs); + auto uabArgc = args.size(); + if (splitter != args.cend()) { + uabArgc = std::distance(args.cbegin(), splitter); + opts.loaderArgs.assign(splitter + 1, args.cend()); } - struct option long_options[] = { { "print-meta", no_argument, nullptr, uabOption::Meta }, - { "extract", required_argument, nullptr, uabOption::Extract }, - { "help", no_argument, nullptr, uabOption::Help }, - { nullptr, 0, nullptr, 0 } }; - int ch; - int counter{ 0 }; - auto *uabArgv = argv; - auto uabArgc = splitIndex == -1 ? argc : splitIndex; + std::array long_options{ + { { "print-meta", no_argument, nullptr, uabOption::Meta }, + { "extract", required_argument, nullptr, uabOption::Extract }, + { "help", no_argument, nullptr, uabOption::Help }, + { nullptr, 0, nullptr, 0 } } + }; - while ((ch = getopt_long(uabArgc, uabArgv, "", long_options, nullptr)) != -1) { + std::vector rawArgs; + std::for_each(args.cbegin(), args.cend(), [&rawArgs](std::string_view arg) { + rawArgs.emplace_back(arg.data()); + }); + + int ch{ -1 }; + int counter{ 0 }; + while ((ch = ::getopt_long(uabArgc, + const_cast(rawArgs.data()), + "", + long_options.data(), + nullptr)) + != -1) { switch (ch) { case uabOption::Meta: { opts.printMeta = true; @@ -706,14 +786,10 @@ argOption parseArgs(int argc, char **argv) return opts; } -int mountSelf(const char *selfBin, const nlohmann::json &metaInfo) noexcept +int mountSelf(std::string_view selfBin, + const linglong::api::types::v1::UabMetaInfo &metaInfo) noexcept { - if (!metaInfo.contains("uuid") || !metaInfo["uuid"].is_string()) { - std::cerr << "couldn't get UUID from metaInfo" << std::endl; - return -1; - } - - const auto &uuid = metaInfo["uuid"]; + const auto &uuid = metaInfo.uuid; if (auto ret = createMountPoint(uuid); ret != 0) { return ret; } @@ -736,21 +812,29 @@ int main(int argc, char **argv) elf_version(EV_CURRENT); handleSig(); - auto *selfBin = argv[0]; - auto opts = parseArgs(argc, argv); + std::vector args; + args.reserve(argc); + for (auto i = 0; i < argc; ++i) { + args.emplace_back(argv[i]); + } + + auto selfBin = args.front(); + auto opts = parseArgs(args); if (opts.help) { std::cout << usage << std::endl; return 0; } - auto metaInfo = getMetaInfo(selfBin); - if (metaInfo.empty()) { + auto metaInfoRet = getMetaInfo(selfBin); + if (!metaInfoRet) { + std::cerr << "couldn't get metaInfo of this uab file" << std::endl; return -1; } + const auto &metaInfo = *metaInfoRet; if (opts.printMeta) { - std::cout << metaInfo.dump(4) << std::endl; + std::cout << nlohmann::json(metaInfo).dump(4) << std::endl; return 0; } @@ -768,38 +852,32 @@ int main(int argc, char **argv) cleanAndExit(extractBundle(opts.extractPath)); } - auto cliPath = detectLinglong(); - if (!cliPath.empty() && importSelf(cliPath, selfBin) == 0) { - std::cout << "import uab to linglong successfully, delegate running operation to linglong" - << std::endl; + const auto &layersRef = metaInfo.layers; + const auto &appLayer = std::find_if(layersRef.cbegin(), + layersRef.cend(), + [](const linglong::api::types::v1::UabLayer &layer) { + return layer.info.kind == "app"; + }); - if (!metaInfo.contains("layers") || !metaInfo["layers"].is_array()) { - std::cerr << "couldn't get layers info from metaInfo" << std::endl; - return -1; - } - - const auto &layersRef = metaInfo["layers"].get>(); - const auto &appLayer = - std::find_if(layersRef.cbegin(), layersRef.cend(), [](const nlohmann::json &layer) { - if (!layer.is_object() || !layer.contains("info") || !layer["info"].is_object()) { - return false; - } - - const auto &infoRef = layer["info"]; - if (!infoRef.contains("kind")) { - return false; - } - - return infoRef["kind"] == "app"; - }); + if (appLayer == layersRef.cend()) { + std::cerr << "couldn't find application layer" << std::endl; + return -1; + } + const auto &appInfo = appLayer->info; - if (appLayer == layersRef.cend()) { - std::cerr << "couldn't find application layer" << std::endl; + auto cliPath = detectLinglong(); + auto appRef = + appInfo.channel + ":" + appInfo.id + "/" + appInfo.version + "/" + appInfo.arch[0]; + if (!cliPath.empty()) { + if (importSelf(cliPath, appRef, selfBin) != 0) { + std::cerr << "failed to import uab by ll-cli" << std::endl; return -1; } - auto info = (*appLayer)["info"]; - runAppLinglong(cliPath, info, opts.loaderArgs); + std::cout << "import uab to linglong successfully, delegate running operation to linglong." + << std::endl; + + runAppLinglong(cliPath, *appLayer, opts.loaderArgs); } if (mountSelf(selfBin, metaInfo) != 0) { diff --git a/apps/uab/loader/src/main.cpp b/apps/uab/loader/src/main.cpp index 7e32d47fc..9c398e682 100644 --- a/apps/uab/loader/src/main.cpp +++ b/apps/uab/loader/src/main.cpp @@ -118,49 +118,80 @@ void applyExecutablePatch(ocppi::runtime::config::types::Config &cfg, std::array outPipe{ -1, -1 }; std::array errPipe{ -1, -1 }; if (::pipe(outPipe.data()) == -1) { - std::cerr << "pipe error:" << strerror(errno) << std::endl; + std::cerr << "pipe error:" << ::strerror(errno) << std::endl; return; } + auto clearPipe = [](int &fd) { + ::close(fd); + fd = -1; + }; + + auto closeInPipe = defer([&pipes = outPipe] { + for (auto &pipe : pipes) { + if (pipe != -1) { + ::close(pipe); + } + } + }); + if (::pipe(errPipe.data()) == -1) { - std::cerr << "pipe error:" << strerror(errno) << std::endl; + std::cerr << "pipe error:" << ::strerror(errno) << std::endl; return; } + auto closeErrPipe = defer([&pipes = errPipe] { + for (auto &pipe : pipes) { + if (pipe != -1) { + ::close(pipe); + } + } + }); + if (::pipe(inPipe.data()) == -1) { - std::cerr << "pipe error:" << strerror(errno) << std::endl; + std::cerr << "pipe error:" << ::strerror(errno) << std::endl; return; } + auto closeOPipe = defer([&pipes = inPipe] { + for (auto &pipe : pipes) { + if (pipe != -1) { + ::close(pipe); + } + } + }); + auto pid = fork(); + if (pid < 0) { + std::cerr << "fork error:" << ::strerror(errno) << std::endl; + return; + } + if (pid == 0) { ::dup2(inPipe[0], STDIN_FILENO); ::dup2(outPipe[1], STDOUT_FILENO); ::dup2(errPipe[1], STDERR_FILENO); - ::close(inPipe[1]); - ::close(errPipe[0]); - ::close(outPipe[0]); + clearPipe(inPipe[1]); + clearPipe(errPipe[0]); + clearPipe(outPipe[0]); if (::execl(info.c_str(), info.c_str(), nullptr) == -1) { - std::cerr << "execl " << info << " error:" << strerror(errno) << std::endl; + std::cerr << "execl " << info << " error:" << ::strerror(errno) << std::endl; return; } - } else if (pid < 0) { - std::cerr << "fork error:" << strerror(errno) << std::endl; - return; } - ::close(inPipe[0]); - ::close(errPipe[1]); - ::close(outPipe[1]); + clearPipe(inPipe[0]); + clearPipe(errPipe[1]); + clearPipe(outPipe[1]); auto input = nlohmann::json(cfg).dump(); auto bytesWrite = input.size(); while (true) { auto writeBytes = ::write(inPipe[1], input.c_str(), bytesWrite); if (writeBytes == -1) { - if (errno == EAGAIN) { + if (errno == EINTR) { continue; } @@ -172,24 +203,24 @@ void applyExecutablePatch(ocppi::runtime::config::types::Config &cfg, break; } } - ::close(inPipe[1]); + clearPipe(inPipe[1]); using namespace std::chrono_literals; - auto timeout = 200; + auto timeout = 400; int wstatus{ -1 }; std::string err; std::string out; while (true) { - std::this_thread::sleep_for(50ms); + std::this_thread::sleep_for(100ms); int child{ -1 }; if (child = ::waitpid(pid, &wstatus, WNOHANG); child == -1) { - std::cerr << "wait for process error:" << strerror(errno) << std::endl; + std::cerr << "wait for process error:" << ::strerror(errno) << std::endl; return; } if (child == 0) { - timeout -= 50; + timeout -= 100; if (timeout == 0) { std::cerr << "generator " << info << " timeout" << std::endl; return; @@ -201,7 +232,7 @@ void applyExecutablePatch(ocppi::runtime::config::types::Config &cfg, int reads{ -1 }; while ((reads = ::read(errPipe[0], buf.data(), buf.size())) != 0) { if (reads < 0) { - if (errno == EAGAIN) { + if (errno == EINTR) { continue; } std::cerr << "read error:" << strerror(errno) << std::endl; @@ -214,10 +245,10 @@ void applyExecutablePatch(ocppi::runtime::config::types::Config &cfg, reads = -1; while ((reads = ::read(outPipe[0], buf.data(), buf.size())) != 0) { if (reads < 0) { - if (errno == EAGAIN) { + if (errno == EINTR) { continue; } - std::cerr << "read error:" << strerror(errno) << std::endl; + std::cerr << "read error:" << ::strerror(errno) << std::endl; return; } @@ -250,8 +281,8 @@ void applyExecutablePatch(ocppi::runtime::config::types::Config &cfg, return; } - ::close(errPipe[0]); - ::close(outPipe[0]); + clearPipe(errPipe[0]); + clearPipe(outPipe[0]); cfg = std::move(modified); } @@ -259,6 +290,12 @@ void applyExecutablePatch(ocppi::runtime::config::types::Config &cfg, void applyPatches(ocppi::runtime::config::types::Config &cfg, const std::vector &patches) noexcept { + auto testExec = [](std::filesystem::perms perm) { + using p = std::filesystem::perms; + return (perm & p::owner_exec) != p::none || (perm & p::group_exec) != p::none + || (perm & p::others_exec) != p::none; + }; + std::error_code ec; for (const auto &info : patches) { if (!std::filesystem::is_regular_file(info, ec)) { @@ -272,17 +309,13 @@ void applyPatches(ocppi::runtime::config::types::Config &cfg, continue; } - struct stat fileStat - { - }; - - if (stat(info.c_str(), &fileStat) == -1) { - std::cerr << "stat file " << info << " err:" << strerror(errno) << std::endl; + auto status = std::filesystem::status(info, ec); + if (ec) { + std::cerr << "couldn't get status of " << info << std::endl; continue; } - auto mode = fileStat.st_mode; - if ((mode & S_IXUSR) || (mode & S_IXGRP) || (mode & S_IXOTH)) { + if (testExec(status.permissions())) { applyExecutablePatch(cfg, info); continue; } @@ -291,7 +324,7 @@ void applyPatches(ocppi::runtime::config::types::Config &cfg, } } -int main(int argc, char **argv) +int main([[maybe_unused]] int argc, [[maybe_unused]] char **argv) { auto *runtimeID = ::getenv("UAB_RUNTIME_ID"); @@ -368,7 +401,7 @@ int main(int argc, char **argv) return -1; } - auto compatibleFilePath = [](const std::string &layerID) -> std::string { + auto compatibleFilePath = [](std::string_view layerID) -> std::string { std::error_code ec; auto layerDir = std::filesystem::current_path() / "layers" / layerID; @@ -392,7 +425,7 @@ int main(int argc, char **argv) auto layerFilesDir = compatibleFilePath(baseID); if (layerFilesDir.empty()) { - std::cerr << "couldn't get compatiblePath" << std::endl; + std::cerr << "couldn't get compatiblePath of base" << std::endl; return -1; } @@ -412,7 +445,7 @@ int main(int argc, char **argv) if (runtimeID != nullptr) { layerFilesDir = compatibleFilePath(runtimeID); if (layerFilesDir.empty()) { - std::cerr << "couldn't get compatiblePath" << std::endl; + std::cerr << "couldn't get compatiblePath of runtime" << std::endl; return -1; } @@ -422,7 +455,7 @@ int main(int argc, char **argv) layerFilesDir = compatibleFilePath(appID); if (layerFilesDir.empty()) { - std::cerr << "couldn't get compatiblePath" << std::endl; + std::cerr << "couldn't get compatiblePath of application" << std::endl; return -1; } @@ -538,7 +571,7 @@ int main(int argc, char **argv) auto pid = fork(); if (pid < 0) { - std::cerr << "fork err: " << strerror(errno) << std::endl; + std::cerr << "fork() err: " << ::strerror(errno) << std::endl; return -1; } @@ -555,7 +588,7 @@ int main(int argc, char **argv) int wstatus{ -1 }; if (auto ret = ::waitpid(pid, &wstatus, 0); ret == -1) { - std::cerr << "waitpid err:" << strerror(errno) << std::endl; + std::cerr << "waitpid() err:" << ::strerror(errno) << std::endl; return -1; } diff --git a/docs/pages/en/guide/ll-builder/demo.md b/docs/pages/en/guide/ll-builder/demo.md index 44b087a49..01e8722c2 100644 --- a/docs/pages/en/guide/ll-builder/demo.md +++ b/docs/pages/en/guide/ll-builder/demo.md @@ -23,6 +23,7 @@ package: description: | calculator for deepin os. ``` + ### Fill in the runtime info ```text @@ -30,6 +31,7 @@ runtime: id: org.deepin.Runtime version: 23.0.0 ``` + ### Fill in the source code info Use git source code @@ -40,6 +42,7 @@ source: url: "https://github.com/linuxdeepin/deepin-calculator.git" commit: 7b5fdf8d133c356317636bb4b4a76fc73ef288c6 ``` + ### Fill in the dependencies ```text @@ -54,6 +57,7 @@ depends: - id: xcb-util type: runtime ``` + ### Choose build template The source code is a cmake project, and choose the build type as cmake (see cmake.yaml for the template content). @@ -77,6 +81,7 @@ variables: ``` Override the build command “build”: + ```text build: kind: cmake @@ -85,6 +90,7 @@ build: cd ${build_dir} && make -j8 ``` + ### Complete linglong.yaml ```text @@ -119,6 +125,7 @@ source: build: kind: cmake ``` + ## Start building Execute the build subcommand in the root directory of Linglong projects: @@ -126,15 +133,17 @@ Execute the build subcommand in the root directory of Linglong projects: ```text ll-builder build ``` + ## Export build content Execute the export subcommand in the root directory of Linglong projects to check out the build content and generate the bundle package. ```text -ll-builder export +ll-builder export --layer ``` + ## Push to repositories ```text ll-builder push org.deepin.calculator_5.7.21_x86_64.uab -``` \ No newline at end of file +``` diff --git a/docs/pages/en/guide/ll-builder/export.md b/docs/pages/en/guide/ll-builder/export.md index fa5041c25..a28832e47 100644 --- a/docs/pages/en/guide/ll-builder/export.md +++ b/docs/pages/en/guide/ll-builder/export.md @@ -20,9 +20,12 @@ Here is the output: Usage: ll-builder [options] Options: - -v, --verbose show detail log (deprecated, use QT_LOGGING_RULES) - -h, --help Displays help on commandline options. - --help-all Displays help including Qt specific options. + -v, --verbose show detail log (deprecated, use QT_LOGGING_RULES) + -h, --help Displays help on commandline options. + --help-all Displays help including Qt specific options. + -f, --file file path of the linglong.yaml (default is ./linglong.yaml) + -i, --icon uab icon (optional) + -l, --layer export layer file ``` The `ll-builder export` command creates a directory named `appid` in the project root directory, then checks out the local build cache to this directory, and generate layer file to the build result. @@ -30,9 +33,11 @@ The `ll-builder export` command creates a directory named `appid` in the project An example of the `ll-builder export` command is as follows: ```bash -ll-builder export +ll-builder export --layer ``` +`Tips: When the Linglong version is greater than 1.5.6, export defaults to exporting the uab file. If you want to export a layer file, you need to add the --layer parameter` + The directory structure after checkout is as follows: ```text diff --git a/docs/pages/en/guide/start/install.md b/docs/pages/en/guide/start/install.md index 9e046f20f..3e0efbe37 100644 --- a/docs/pages/en/guide/start/install.md +++ b/docs/pages/en/guide/start/install.md @@ -15,7 +15,7 @@ Linglong is composed of three parts. ## deepin v23 ```bash -sudo apt install linglong-builder +sudo apt install linglong-builder linglong-box linglong-bin ``` ## UOS 1070 @@ -28,7 +28,20 @@ echo "deb [trusted=yes] https://ci.deepin.com/repo/deepin/deepin-community/lingl ```bash sudo apt update -sudo apt install linglong-builder +sudo apt install linglong-builder linglong-box linglong-bin +``` + +## OpenEuler + +add Linglong repository source + +```bash +sudo curl -o /etc/yum.repos.d/linglong.repo -L https://eur.openeuler.openatom.cn/coprs/kamiyadm/linglong/repo/openeuler-24.03_LTS/kamiyadm-linglong-openeuler-24.03_LTS.repo +``` + +``` +sudo dnf update +sudo dnf install linglong-builder linglong-box linglong-bin ``` # Install the Pica tool diff --git a/docs/pages/guide/ll-builder/demo.md b/docs/pages/guide/ll-builder/demo.md index f3a5f22e6..0060b808b 100644 --- a/docs/pages/guide/ll-builder/demo.md +++ b/docs/pages/guide/ll-builder/demo.md @@ -99,7 +99,7 @@ ll-builder run 在玲珑工程根目录下执行export子命令,检出构建内容。 ```bash -ll-builder export +ll-builder export --layer ``` 目录结构如下: diff --git a/docs/pages/guide/ll-builder/export.md b/docs/pages/guide/ll-builder/export.md index 2659d0636..6b5e86133 100644 --- a/docs/pages/guide/ll-builder/export.md +++ b/docs/pages/guide/ll-builder/export.md @@ -20,9 +20,12 @@ ll-builder export --help Usage: ll-builder [options] Options: - -v, --verbose show detail log (deprecated, use QT_LOGGING_RULES) - -h, --help Displays help on commandline options. - --help-all Displays help including Qt specific options. + -v, --verbose show detail log (deprecated, use QT_LOGGING_RULES) + -h, --help Displays help on commandline options. + --help-all Displays help including Qt specific options. + -f, --file file path of the linglong.yaml (default is ./linglong.yaml) + -i, --icon uab icon (optional) + -l, --layer export layer file ``` `ll-builder export`命令在工程根目录下创建以 `appid`为名称的目录,并将本地构建缓存检出到该目录。同时根据该构建结果生成 layer 文件。 @@ -30,9 +33,11 @@ Options: `ll-builder export`命令使用示例如下: ```bash -ll-builder export +ll-builder export --layer ``` +`Tips: 在玲珑版本大于1.5.6时,export 默认导出 uab 包,如果要导出 layer 文件,需要加上 --layer 参数` + 检出后的目录结构如下: ```text diff --git a/docs/pages/guide/start/install.md b/docs/pages/guide/start/install.md index 4ecca996d..c4287e65b 100644 --- a/docs/pages/guide/start/install.md +++ b/docs/pages/guide/start/install.md @@ -31,6 +31,19 @@ sudo apt update sudo apt install linglong-builder linglong-box linglong-bin ``` +## OpenEuler + +添加玲珑仓库源 + +```bash +sudo curl -o /etc/yum.repos.d/linglong.repo -L https://eur.openeuler.openatom.cn/coprs/kamiyadm/linglong/repo/openeuler-24.03_LTS/kamiyadm-linglong-openeuler-24.03_LTS.repo +``` + +```bash +sudo dnf update +sudo dnf install linglong-builder linglong-box linglong-bin +``` + # 安装 pica 本工具目前提供 deb 包转换为玲珑包的能力,生成构建玲珑应用需要的 linglong.yaml 文件,并依赖 ll-builder 来实现应用构建和导出。