Skip to content

Commit

Permalink
feat(ll-cli): add subcommand 'inspect'
Browse files Browse the repository at this point in the history
Signed-off-by: ComixHe <[email protected]>
  • Loading branch information
ComixHe committed Jan 20, 2025
1 parent ed7dd78 commit bef4a49
Show file tree
Hide file tree
Showing 13 changed files with 249 additions and 2 deletions.
13 changes: 13 additions & 0 deletions api/schema/v1.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,16 @@
"$schema": "http://json-schema.org/draft-04/schema#",
"description": "Types used as v1 API of linglong D-Bus service, configuration files and CLI output. The top level type is a place holder to make quicktype work.",
"$defs": {
"InspectResult": {
"description": "the result of inspecting a container",
"type": "object",
"properties": {
"appID": {
"type": "string",
"description": "appID of container"
}
}
},
"ApplicationConfiguration": {
"title": "ApplicationConfiguration",
"description": "application configuration",
Expand Down Expand Up @@ -1147,6 +1157,9 @@
},
"type": "object",
"properties": {
"InspectResult": {
"$ref": "#/$defs/InspectResult"
},
"ApplicationConfiguration": {
"$ref": "#/$defs/ApplicationConfiguration"
},
Expand Down
7 changes: 7 additions & 0 deletions api/schema/v1.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,13 @@ description: Types used as v1 API of linglong
D-Bus service, configuration files and CLI output.
The top level type is a place holder to make quicktype work.
$defs:
InspectResult:
description: the result of inspecting a container
type: object
properties:
appID:
type: string
description: appID of container
ApplicationConfiguration:
title: ApplicationConfiguration
description: application configuration
Expand Down
28 changes: 27 additions & 1 deletion apps/ll-cli/src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -500,6 +500,31 @@ ll-cli list --upgradable
->group(CliAppManagingGroup)
->usage(_("Usage: ll-cli prune [OPTIONS]"));

// add sub command inspect
auto *cliInspect =
commandParser
.add_subcommand("inspect", _("Display the information of installed application"))
->group(CliHiddenGroup)
->usage(_("Usage: ll-cli inspect [OPTIONS]"));
cliInspect->footer("This subcommand is for internal use only currently");
cliInspect->add_option("-p,--pid", options.pid, _("Specify the process id"))
->check([](const std::string &input) -> std::string {
if (input.empty()) {
return _("Input parameter is empty, please input valid parameter instead");
}

try {
auto pid = std::stoull(input);
if (pid <= 0) {
return _("Invalid process id");
}
} catch (std::exception &e) {
return _("Invalid pid format");
}

return {};
});

auto res = transformOldExec(argc, argv);
std::reverse(res.begin(), res.end());
CLI11_PARSE(commandParser, res);
Expand Down Expand Up @@ -679,7 +704,8 @@ ll-cli list --upgradable
{ "list", &Cli::list },
{ "info", &Cli::info },
{ "content", &Cli::content },
{ "prune", &Cli::prune }
{ "prune", &Cli::prune },
{ "inspect", &Cli::inspect }
};

if (QObject::connect(QCoreApplication::instance(),
Expand Down
19 changes: 19 additions & 0 deletions libs/api/src/linglong/api/types/v1/Generators.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
#include "linglong/api/types/v1/InteractionRequest.hpp"
#include "linglong/api/types/v1/InteractionReply.hpp"
#include "linglong/api/types/v1/InteractionMessageType.hpp"
#include "linglong/api/types/v1/InspectResult.hpp"
#include "linglong/api/types/v1/DialogMessage.hpp"
#include "linglong/api/types/v1/DialogHandShakePayload.hpp"
#include "linglong/api/types/v1/ContainerProcessStateInfo.hpp"
Expand Down Expand Up @@ -122,6 +123,9 @@ void to_json(json & j, const DialogHandShakePayload & x);
void from_json(const json & j, DialogMessage & x);
void to_json(json & j, const DialogMessage & x);

void from_json(const json & j, InspectResult & x);
void to_json(json & j, const InspectResult & x);

void from_json(const json & j, InteractionReply & x);
void to_json(json & j, const InteractionReply & x);

Expand Down Expand Up @@ -503,6 +507,17 @@ j["payload"] = x.payload;
j["type"] = x.type;
}

inline void from_json(const json & j, InspectResult& x) {
x.appID = get_stack_optional<std::string>(j, "appID");
}

inline void to_json(json & j, const InspectResult & x) {
j = json::object();
if (x.appID) {
j["appID"] = x.appID;
}
}

inline void from_json(const json & j, InteractionReply& x) {
x.action = get_stack_optional<std::string>(j, "action");
}
Expand Down Expand Up @@ -970,6 +985,7 @@ x.commonResult = get_stack_optional<CommonResult>(j, "CommonResult");
x.containerProcessStateInfo = get_stack_optional<ContainerProcessStateInfo>(j, "ContainerProcessStateInfo");
x.dialogHandShakePayload = get_stack_optional<DialogHandShakePayload>(j, "DialogHandShakePayload");
x.dialogMessage = get_stack_optional<DialogMessage>(j, "DialogMessage");
x.inspectResult = get_stack_optional<InspectResult>(j, "InspectResult");
x.interactionMessageType = get_stack_optional<InteractionMessageType>(j, "InteractionMessageType");
x.interactionReply = get_stack_optional<InteractionReply>(j, "InteractionReply");
x.interactionRequest = get_stack_optional<InteractionRequest>(j, "InteractionRequest");
Expand Down Expand Up @@ -1034,6 +1050,9 @@ j["DialogHandShakePayload"] = x.dialogHandShakePayload;
if (x.dialogMessage) {
j["DialogMessage"] = x.dialogMessage;
}
if (x.inspectResult) {
j["InspectResult"] = x.inspectResult;
}
if (x.interactionMessageType) {
j["InteractionMessageType"] = x.interactionMessageType;
}
Expand Down
44 changes: 44 additions & 0 deletions libs/api/src/linglong/api/types/v1/InspectResult.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// This file is generated by tools/codegen.sh
// DO NOT EDIT IT.

// clang-format off

// To parse this JSON data, first install
//
// json.hpp https://github.com/nlohmann/json
//
// Then include this file, and then do
//
// InspectResult.hpp data = nlohmann::json::parse(jsonString);

#pragma once

#include <optional>
#include <nlohmann/json.hpp>
#include "linglong/api/types/v1/helper.hpp"

namespace linglong {
namespace api {
namespace types {
namespace v1 {
/**
* the result of inspecting a container
*/

using nlohmann::json;

/**
* the result of inspecting a container
*/
struct InspectResult {
/**
* appID of container
*/
std::optional<std::string> appID;
};
}
}
}
}

// clang-format on
2 changes: 2 additions & 0 deletions libs/api/src/linglong/api/types/v1/LinglongAPIV1.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
#include "linglong/api/types/v1/ContainerProcessStateInfo.hpp"
#include "linglong/api/types/v1/DialogHandShakePayload.hpp"
#include "linglong/api/types/v1/DialogMessage.hpp"
#include "linglong/api/types/v1/InspectResult.hpp"
#include "linglong/api/types/v1/InteractionReply.hpp"
#include "linglong/api/types/v1/InteractionRequest.hpp"
#include "linglong/api/types/v1/LayerInfo.hpp"
Expand Down Expand Up @@ -90,6 +91,7 @@ std::optional<CommonResult> commonResult;
std::optional<ContainerProcessStateInfo> containerProcessStateInfo;
std::optional<DialogHandShakePayload> dialogHandShakePayload;
std::optional<DialogMessage> dialogMessage;
std::optional<InspectResult> inspectResult;
std::optional<InteractionMessageType> interactionMessageType;
std::optional<InteractionReply> interactionReply;
std::optional<InteractionRequest> interactionRequest;
Expand Down
118 changes: 118 additions & 0 deletions libs/linglong/src/linglong/cli/cli.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
#include <QEventLoop>
#include <QFileInfo>

#include <charconv>
#include <cstdlib>
#include <filesystem>
#include <iostream>
Expand All @@ -56,6 +57,91 @@ static const std::string permissionNotifyMsg =

namespace {

linglong::utils::error::Result<bool> isChildProcess(pid_t parent, pid_t pid) noexcept
{
LINGLONG_TRACE(QString{ "check if %1 is child of %2" }.arg(pid).arg(parent));

auto getppid = [](pid_t pid) -> Result<pid_t> {
LINGLONG_TRACE(QString{ "get ppid of %1" }.arg(pid));
std::error_code ec;
auto stat = std::filesystem::path("/proc/" + std::to_string(pid) + "/stat");
auto fd = ::open(stat.c_str(), O_RDONLY);
if (fd == -1) {
return LINGLONG_ERR(
QString{ "failed to open %1: %2" }.arg(stat.c_str(), ::strerror(errno)));
}
auto closeFd = linglong::utils::finally::finally([fd] {
::close(fd);
});

// FIXME: Parsing /proc/pid/stat isn't an good idea, the consistency of file contents
// which in /proc is not guaranteed and the format may change. so we read all the content at
// first and then parse it.
// use read instead of std::ifstream to get more detailed error information the size
// of /proc/pid/stat is zero and we can't get the content size by stat,
// so we use 1024 as the buffer size.
std::array<char, 1024> buf{};
std::string content;
while (true) {
auto readBytes = ::read(fd, buf.data(), buf.size());
if (readBytes == -1) {
return LINGLONG_ERR(
QString{ "failed to read from %1: %2" }.arg(stat.c_str(), ::strerror(errno)));
}

if (readBytes == 0) {
break;
}

content.append(buf.data(), readBytes);
}

auto ppidOffset = 3;
auto left = 0;
auto right = 0;
for (int i = 0; i < content.size(); i++) {
if (ppidOffset == 0) {
left = i;
right = i;

while (content[right] != ' ') {
right += 1;
}

break;
}

if (content[i] == ' ') {
ppidOffset -= 1;
}
}

pid_t ppid{ -1 };
auto [_, err] = std::from_chars(content.c_str() + left, content.c_str() + right, ppid);
if (err != std::errc()) {
return LINGLONG_ERR(QString{ "failed to parse %1: %2" }.arg(
std::string_view(content.c_str() + left, right - left).data(),
::strerror(static_cast<int>(err))));
}

return ppid;
};

while (pid != parent) {
auto ppid = getppid(pid);
if (!ppid) {
return LINGLONG_ERR(ppid.error());
}

pid = *ppid;
if (pid < parent) {
return false;
}
}

return true;
}

linglong::utils::error::Result<void> ensureDirectory(const std::filesystem::path &dir)
{
LINGLONG_TRACE("ensure runtime directory");
Expand Down Expand Up @@ -2454,4 +2540,36 @@ void Cli::updateAM() noexcept
}
}
}

int Cli::inspect()
{
auto myContainersRet = getCurrentContainers();
if (!myContainersRet) {
this->printer.printErr(myContainersRet.error());
return -1;
}
const auto &myContainers = *myContainersRet;

api::types::v1::InspectResult result;

if (this->options.pid) {
qDebug() << "inspect by pid:" << this->options.pid.value();
for (const auto &container : myContainers) {
auto ret = isChildProcess(container.pid, this->options.pid.value());
if (!ret) {
this->printer.printErr(ret.error());
return -1;
}

if (*ret) {
result.appID = container.package;
break;
}
}
}

this->printer.printInspect(result);
return 0;
}

} // namespace linglong::cli
3 changes: 3 additions & 0 deletions libs/linglong/src/linglong/cli/cli.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ namespace linglong::cli {

class Printer;

// TODO: split this into multiple options
struct CliOptions
{
std::vector<std::string> filePaths;
Expand All @@ -36,6 +37,7 @@ struct CliOptions
bool showUpgradeList;
bool forceOpt;
bool confirmOpt;
std::optional<pid_t> pid;
};

class Cli : public QObject
Expand Down Expand Up @@ -69,6 +71,7 @@ class Cli : public QObject
int info();
int content();
int prune();
int inspect();

void cancelCurrentTask();

Expand Down
6 changes: 6 additions & 0 deletions libs/linglong/src/linglong/cli/cli_printer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -199,4 +199,10 @@ void CLIPrinter::printUpgradeList(std::vector<api::types::v1::UpgradeListResult>
}
}

void CLIPrinter::printInspect(const api::types::v1::InspectResult &result)
{
std::cout << "appID:\t" << (result.appID.has_value() ? result.appID.value() : "none")
<< std::endl;
}

} // namespace linglong::cli
3 changes: 2 additions & 1 deletion libs/linglong/src/linglong/cli/cli_printer.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ class CLIPrinter : public Printer
CLIPrinter(CLIPrinter &&) = delete;
CLIPrinter &operator=(const CLIPrinter &) = delete;
CLIPrinter &operator=(CLIPrinter &&) = delete;
~CLIPrinter() = default;
~CLIPrinter() override = default;

void printErr(const utils::error::Error &) override;
void printPackage(const api::types::v1::PackageInfoV2 &) override;
Expand All @@ -45,6 +45,7 @@ class CLIPrinter : public Printer
api::types::v1::SubState subState) override;
void printContent(const QStringList &filePaths) override;
void printUpgradeList(std::vector<api::types::v1::UpgradeListResult> &) override;
void printInspect(const api::types::v1::InspectResult &result) override;
};

} // namespace linglong::cli
5 changes: 5 additions & 0 deletions libs/linglong/src/linglong/cli/json_printer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -83,4 +83,9 @@ void JSONPrinter::printUpgradeList(std::vector<api::types::v1::UpgradeListResult
std::cout << nlohmann::json(list).dump(4) << std::endl;
}

void JSONPrinter::printInspect(const api::types::v1::InspectResult &result)
{
std::cout << nlohmann::json(result).dump(4) << std::endl;
}

} // namespace linglong::cli
Loading

0 comments on commit bef4a49

Please sign in to comment.