Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion apps/ll-cli/src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,13 @@
#include "linglong/cli/cli.h"
#include "linglong/cli/cli_printer.h"
#include "linglong/cli/dbus_notifier.h"
#include "linglong/cli/dummy_notifier.h"

Check warning on line 11 in apps/ll-cli/src/main.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: "linglong/cli/dummy_notifier.h" not found.
#include "linglong/cli/json_printer.h"

Check warning on line 12 in apps/ll-cli/src/main.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: "linglong/cli/json_printer.h" not found.
#include "linglong/cli/terminal_notifier.h"

Check warning on line 13 in apps/ll-cli/src/main.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: "linglong/cli/terminal_notifier.h" not found.
#include "linglong/common/error.h"

Check warning on line 14 in apps/ll-cli/src/main.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: "linglong/repo/config.h" not found.
#include "linglong/repo/config.h"

Check warning on line 15 in apps/ll-cli/src/main.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: "linglong/repo/ostree_repo.h" not found.
#include "linglong/repo/ostree_repo.h"

Check warning on line 16 in apps/ll-cli/src/main.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: "linglong/runtime/container_builder.h" not found.
#include "linglong/runtime/container_builder.h"

Check warning on line 17 in apps/ll-cli/src/main.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: "linglong/utils/finally/finally.h" not found.
#include "linglong/utils/finally/finally.h"
#include "linglong/utils/gettext.h"
#include "linglong/utils/global/initialize.h"
Expand Down Expand Up @@ -112,7 +113,7 @@
return 0;
}

qCritical() << "failed to open lock" << lock << errorString(errno);
LogE("failed to open lock {}: {}", lock, linglong::common::error::errorString(errno));
return -1;
}

Expand Down
4 changes: 4 additions & 0 deletions libs/common/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ pfl_add_library(
src/linglong/common/dir.h
src/linglong/common/display.cpp
src/linglong/common/display.h
src/linglong/common/error.h
src/linglong/common/error.cpp
src/linglong/common/socket.cpp
src/linglong/common/socket.h
src/linglong/common/strings.cpp
src/linglong/common/strings.h
src/linglong/common/xdg.cpp
Expand Down
16 changes: 16 additions & 0 deletions libs/common/src/linglong/common/error.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd.
//
// SPDX-License-Identifier: LGPL-3.0-or-later

#include "linglong/common/error.h"

#include <system_error>

namespace linglong::common::error {

std::string errorString(int err)
{
return std::system_category().message(err);
}

} // namespace linglong::common::error
13 changes: 13 additions & 0 deletions libs/common/src/linglong/common/error.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd.
//
// SPDX-License-Identifier: LGPL-3.0-or-later

#pragma once

#include <string>

namespace linglong::common::error {

std::string errorString(int err);

} // namespace linglong::common::error
142 changes: 142 additions & 0 deletions libs/common/src/linglong/common/socket.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd.
//
// SPDX-License-Identifier: LGPL-3.0-or-later

#include "linglong/common/socket.h"

#include "linglong/common/error.h"

#include <sys/ioctl.h>

#include <cstring>

#include <sys/socket.h>
#include <unistd.h>

namespace linglong::common::socket {

tl::expected<SocketData, std::string> recvFdWithPayload(int socketFd, std::size_t bufSize)
{
if (socketFd < 0) {
return tl::make_unexpected("Invalid file descriptor");
}

std::string buffer(bufSize, '\0');
struct iovec iov{};
iov.iov_base = buffer.data();
iov.iov_len = buffer.size();

alignas(struct cmsghdr) std::array<char, CMSG_SPACE(sizeof(int))> control_buf{};

struct msghdr msg{};
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
msg.msg_control = control_buf.data();
msg.msg_controllen = sizeof(control_buf);

ssize_t n;
while (true) {
n = recvmsg(socketFd, &msg, 0);
if (n < 0 && errno == EINTR) {
continue;
}

break;
}

if (n < 0) {
return tl::make_unexpected("recvmsg failed: " + error::errorString(errno));
}

if (n == 0) {
return tl::make_unexpected("Connection closed");
}

int received_fd{ -1 };
struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg);

Copilot AI Jan 20, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If cmsg is nullptr, the function returns an error but the received_fd is not closed. However, in this specific case, if there's no control message header, no file descriptor was received, so received_fd remains -1 and doesn't need to be closed. This is correct but could benefit from a code comment explaining this for maintainability.

Suggested change
// If there is no control message header, no file descriptor was received.
// In this case received_fd remains -1 and does not need to be closed.

Copilot uses AI. Check for mistakes.
if (cmsg != nullptr && cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_RIGHTS) {
std::memcpy(&received_fd, CMSG_DATA(cmsg), sizeof(int));
}

if ((static_cast<std::size_t>(msg.msg_flags) & MSG_CTRUNC) != 0) {
if (received_fd != -1) {
::close(received_fd);
}

return tl::make_unexpected("Control data truncated");
}

if (received_fd == -1) {
return tl::make_unexpected("No file descriptor received");
}

bool isTruncated{ false };
if ((static_cast<std::size_t>(msg.msg_flags) & MSG_TRUNC) != 0) {
isTruncated = true;
} else if (static_cast<std::size_t>(n) == bufSize) {
int bytes_available{ 0 };
if (ioctl(socketFd, FIONREAD, &bytes_available) < 0) {
return tl::make_unexpected("FIONREAD socket failed: " + error::errorString(errno));
}

if (bytes_available > 0) {
isTruncated = true;
}
}

buffer.resize(n);
return SocketData{ isTruncated, received_fd, std::move(buffer) };
}

tl::expected<void, std::string> sendFdWithPayload(int socketFd, int fd, const std::string &payload)
{

Copilot AI Jan 20, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The function sendFdWithPayload doesn't validate that socketFd is a valid file descriptor before attempting to use it. Consider adding a check similar to recvFdWithPayload (socketFd < 0) to provide better error messages and fail fast.

Suggested change
{
{
if (socketFd < 0) {
return tl::make_unexpected("Invalid socket file descriptor");
}

Copilot uses AI. Check for mistakes.
if (socketFd < 0) {
return tl::make_unexpected("Invalid socket file descriptor");
}

std::size_t totalSent{ 0 };
bool fdSent{ false };

while (totalSent < payload.size() || !fdSent) {
struct msghdr msg{};
struct iovec iov{};

iov.iov_base = const_cast<char *>(payload.data() + totalSent);
iov.iov_len = payload.size() - totalSent;
msg.msg_iov = &iov;
msg.msg_iovlen = 1;

alignas(struct cmsghdr) std::array<std::byte, CMSG_SPACE(sizeof(int))> control_buf{};
if (!fdSent) {
msg.msg_control = control_buf.data();
msg.msg_controllen = control_buf.size();

struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg);
cmsg->cmsg_level = SOL_SOCKET;
cmsg->cmsg_type = SCM_RIGHTS;
cmsg->cmsg_len = CMSG_LEN(sizeof(int));
std::memcpy(CMSG_DATA(cmsg), &fd, sizeof(int));
}

const auto n = sendmsg(socketFd, &msg, 0);
if (n < 0) {
if (errno == EINTR) {
continue;
}

return tl::make_unexpected("sendmsg failed: " + error::errorString(errno));
}

fdSent = true;
totalSent += n;

if (payload.empty()) {
break;
}
}

return {};
}

} // namespace linglong::common::socket
24 changes: 24 additions & 0 deletions libs/common/src/linglong/common/socket.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd.
//
// SPDX-License-Identifier: LGPL-3.0-or-later

#pragma once

#include <tl/expected.hpp>

#include <string>

namespace linglong::common::socket {

struct SocketData
{
bool more_data{ false };
int fd;
std::string payload;
};

tl::expected<SocketData, std::string> recvFdWithPayload(int socketFd, std::size_t bufSize = 4096);

Comment on lines +19 to +21

Copilot AI Jan 20, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The public API functions recvFdWithPayload and sendFdWithPayload lack documentation. Add docstrings explaining their purpose, parameters, return values, and error conditions. This is especially important for API functions that will be used by other components.

Suggested change
tl::expected<SocketData, std::string> recvFdWithPayload(int fd, std::size_t bufSize = 4096);
/**
* @brief Receives a file descriptor and an optional payload from a socket.
*
* This function expects the peer to send one file descriptor along with an
* associated payload (typically a small control message) over a socket
* that supports passing file descriptors (e.g. a UNIX domain socket).
*
* @param fd
* A valid, connected socket file descriptor to receive from.
* @param bufSize
* Maximum number of bytes to read for the payload. If the incoming
* payload is larger than this value, the behavior is implementation
* defined (e.g. it may be truncated or treated as an error). The
* default buffer size is 4096 bytes.
*
* @return tl::expected<SocketData, std::string>
* - On success, contains a SocketData instance with the received file
* descriptor and payload string. The caller is responsible for
* managing (and eventually closing) the received file descriptor.
* - On failure, contains a human-readable error message describing the
* problem (for example, an invalid socket, protocol error, or system
* call failure).
*/
tl::expected<SocketData, std::string> recvFdWithPayload(int fd, std::size_t bufSize = 4096);
/**
* @brief Sends a file descriptor and an associated payload over a socket.
*
* This function sends the given file descriptor together with the provided
* payload (typically a small control message) over a socket that supports
* passing file descriptors (e.g. a UNIX domain socket).
*
* @param socketFd
* A valid, connected socket file descriptor to send on.
* @param fd
* The file descriptor to send to the peer. Ownership of this file
* descriptor is not transferred by this function; the caller remains
* responsible for closing it unless otherwise specified by the
* surrounding protocol.
* @param payload
* A string payload to send alongside the file descriptor. Very large
* payloads may fail depending on the underlying implementation or OS
* limits.
*
* @return tl::expected<void, std::string>
* - On success, contains no value (i.e. an engaged expected<void>).
* - On failure, contains a human-readable error message describing the
* problem (for example, an invalid argument, protocol error, or
* system call failure).
*/

Copilot uses AI. Check for mistakes.
tl::expected<void, std::string> sendFdWithPayload(int socketFd, int fd, const std::string &payload);

} // namespace linglong::common::socket
16 changes: 8 additions & 8 deletions libs/linglong/src/linglong/cli/cli.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,10 @@
#include "linglong/api/types/v1/PackageManager1SearchResult.hpp"
#include "linglong/api/types/v1/PackageManager1UninstallParameters.hpp"
#include "linglong/api/types/v1/State.hpp"
#include "linglong/api/types/v1/UpgradeListResult.hpp"

Check warning on line 25 in libs/linglong/src/linglong/cli/cli.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: "linglong/api/types/v1/UpgradeListResult.hpp" not found.
#include "linglong/cli/printer.h"

Check warning on line 26 in libs/linglong/src/linglong/cli/cli.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: "linglong/cli/printer.h" not found.
#include "linglong/common/dir.h"

Check warning on line 27 in libs/linglong/src/linglong/cli/cli.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: "linglong/common/dir.h" not found.
#include "linglong/common/error.h"
#include "linglong/common/strings.h"
#include "linglong/oci-cfg-generators/container_cfg_builder.h"
#include "linglong/package/layer_file.h"
Expand Down Expand Up @@ -53,7 +54,6 @@

#include <algorithm>
#include <cassert>
#include <charconv>
#include <cstdint>
#include <cstdlib>
#include <filesystem>
Expand Down Expand Up @@ -483,7 +483,7 @@
// placeholder file
auto fd = ::open(pidFile.c_str(), O_WRONLY | O_CREAT | O_EXCL, mode);
if (fd == -1) {
LogE("create file {} error: {}", pidFile, errorString(errno));
LogE("create file {} error: {}", pidFile, common::error::errorString(errno));
QCoreApplication::exit(-1);
return -1;
}
Expand Down Expand Up @@ -1711,7 +1711,7 @@
if (real != nullptr) {
target = real;
} else {
LogE("resolve symlink {} error: {}", file, errorString(errno));
LogE("resolve symlink {} error: {}", file, common::error::errorString(errno));
}
}

Expand Down Expand Up @@ -1991,7 +1991,7 @@

auto fd = ::shm_open(info.id.c_str(), O_RDWR | O_CREAT, 0600);
if (fd < 0) {
return LINGLONG_ERR("shm_open error:" + errorString(errno));
return LINGLONG_ERR("shm_open error:" + common::error::errorString(errno));
}
auto closeFd = utils::finally::finally([fd] {
::close(fd);
Expand All @@ -2016,7 +2016,7 @@
continue;
}

return LINGLONG_ERR("fcntl lock error: " + errorString(errno));
return LINGLONG_ERR("fcntl lock error: " + common::error::errorString(errno));
}

if (!anotherRunning) {
Expand All @@ -2026,7 +2026,7 @@
lock.l_type = F_UNLCK;
ret = ::fcntl(fd, F_SETLK, &lock);
if (ret == -1) {
return LINGLONG_ERR("fcntl unlock error: " + errorString(errno));
return LINGLONG_ERR("fcntl unlock error: " + common::error::errorString(errno));
}

return LINGLONG_OK;
Expand All @@ -2036,11 +2036,11 @@
auto releaseResource = utils::finally::finally([&info, &lock, fd] {
lock.l_type = F_UNLCK;
if (::fcntl(fd, F_SETLK, &lock) == -1) {
LogD("failed to unlock mem file: {}", errorString(errno));
LogD("failed to unlock mem file: {}", common::error::errorString(errno));
}

if (::shm_unlink(info.id.c_str()) == -1) {
LogD("shm_unlink error: {}", errorString(errno));
LogD("shm_unlink error: {}", common::error::errorString(errno));
}
});

Expand Down
8 changes: 5 additions & 3 deletions libs/linglong/src/linglong/package/layer_file.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,11 @@

#include "linglong/package/layer_file.h"

#include "linglong/api/types/v1/Generators.hpp"
#include "linglong/api/types/v1/Generators.hpp" // IWYU pragma: keep
#include "linglong/api/types/v1/LayerInfo.hpp"
#include "linglong/common/error.h"
#include "linglong/utils/error/error.h"
#include "linglong/utils/log/formatter.h"
#include "linglong/utils/log/formatter.h" // IWYU pragma: keep
#include "linglong/utils/serialize/json.h"

#include <QDataStream>
Expand All @@ -33,7 +34,8 @@ utils::error::Result<QSharedPointer<LayerFile>> LayerFile::New(const QString &pa
LINGLONG_TRACE("install layer file from path")
auto fd = ::open(path.toLocal8Bit(), O_RDONLY);
if (fd < 0) {
return LINGLONG_ERR(fmt::format("failed to open {}: {}", path, ::errorString(errno)));
return LINGLONG_ERR(
fmt::format("failed to open {}: {}", path, common::error::errorString(errno)));
}

return New(fd);
Expand Down
13 changes: 8 additions & 5 deletions libs/linglong/src/linglong/package/uab_file.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include "linglong/package/uab_file.h"

#include "linglong/api/types/v1/Generators.hpp"
#include "linglong/common/error.h"
#include "linglong/utils/cmd.h"
#include "linglong/utils/error/error.h"
#include "linglong/utils/finally/finally.h"
Expand Down Expand Up @@ -342,7 +343,7 @@ utils::error::Result<std::filesystem::path> UABFile::extractSignData() noexcept
continue;
}
return LINGLONG_ERR(
fmt::format("read from sign section error: {}", ::errorString(errno)));
fmt::format("read from sign section error: {}", common::error::errorString(errno)));
}

while (true) {
Expand All @@ -353,7 +354,7 @@ utils::error::Result<std::filesystem::path> UABFile::extractSignData() noexcept
continue;
}
return LINGLONG_ERR(
fmt::format("write to sign.tar error: {}", ::errorString(errno)));
fmt::format("write to sign.tar error: {}", common::error::errorString(errno)));
}

if (writeBytes != readBytes) {
Expand All @@ -366,12 +367,14 @@ utils::error::Result<std::filesystem::path> UABFile::extractSignData() noexcept
}

if (::fsync(tarFd) == -1) {
return LINGLONG_ERR(fmt::format("fsync sign.tar error: {}", ::errorString(errno)));
return LINGLONG_ERR(
fmt::format("fsync sign.tar error: {}", common::error::errorString(errno)));
}

if (::close(tarFd) == -1) {
tarFd = -1; // no need to try twice
return LINGLONG_ERR(fmt::format("failed to close tar: {}", ::errorString(errno)));
return LINGLONG_ERR(
fmt::format("failed to close tar: {}", common::error::errorString(errno)));
}
tarFd = -1;

Expand Down Expand Up @@ -415,7 +418,7 @@ utils::error::Result<void> UABFile::saveErofsToFile(const std::string &path)
auto bytesRead = ::read(handle(), buf.data(), readBytes);
if (bytesRead == -1) {
return LINGLONG_ERR(
fmt::format("read from bundle section error: {}", ::errorString(errno)));
fmt::format("read from bundle section error: {}", common::error::errorString(errno)));
}
ofs.write(buf.data(), bytesRead);
if (ofs.fail()) {
Expand Down
Loading