Skip to content

Commit c7c2e98

Browse files
committed
feat: Add function for sending fd
Signed-off-by: ComixHe <ComixHe1895@outlook.com>
1 parent 7146400 commit c7c2e98

5 files changed

Lines changed: 326 additions & 0 deletions

File tree

libs/common/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ pfl_add_library(
1414
src/linglong/common/dir.h
1515
src/linglong/common/display.cpp
1616
src/linglong/common/display.h
17+
src/linglong/common/socket.cpp
18+
src/linglong/common/socket.h
1719
src/linglong/common/strings.cpp
1820
src/linglong/common/strings.h
1921
src/linglong/common/xdg.cpp
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd.
2+
//
3+
// SPDX-License-Identifier: LGPL-3.0-or-later
4+
5+
#include "linglong/common/socket.h"
6+
7+
#include <cstring>
8+
9+
#include <sys/socket.h>
10+
#include <unistd.h>
11+
12+
namespace linglong::common::socket {
13+
14+
tl::expected<SocketData, std::string> recvFdWithPayload(int fd, std::size_t bufSize)
15+
{
16+
if (fd < 0) {
17+
return tl::make_unexpected("Invalid file descriptor");
18+
}
19+
20+
struct msghdr msg{};
21+
std::string payload_buffer(bufSize, '\0');
22+
23+
struct iovec iov{};
24+
iov.iov_base = payload_buffer.data();
25+
iov.iov_len = payload_buffer.size();
26+
msg.msg_iov = &iov;
27+
msg.msg_iovlen = 1;
28+
29+
std::array<std::byte, CMSG_SPACE(sizeof(int))> control_buf{};
30+
msg.msg_control = control_buf.data();
31+
msg.msg_controllen = control_buf.size();
32+
33+
auto n = [&]() -> ssize_t {
34+
while (true) {
35+
auto ret = recvmsg(fd, &msg, 0);
36+
if (ret < 0) {
37+
if (errno == EINTR) {
38+
continue;
39+
}
40+
41+
return -1;
42+
}
43+
44+
return ret;
45+
}
46+
}();
47+
48+
if (n < 0) {
49+
return tl::make_unexpected(std::string{ "Failed to receive message" } + ::strerror(errno));
50+
}
51+
52+
int received_fd{ -1 };
53+
struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg);
54+
55+
if (cmsg == nullptr) {
56+
return tl::make_unexpected("Failed to receive data");
57+
}
58+
59+
if (cmsg->cmsg_level != SOL_SOCKET || cmsg->cmsg_type != SCM_RIGHTS) {
60+
return tl::make_unexpected("Not ancillary data with file descriptor");
61+
}
62+
63+
std::memcpy(&received_fd, CMSG_DATA(cmsg), sizeof(int));
64+
65+
return SocketData{ received_fd, std::string(payload_buffer.data(), n) };
66+
}
67+
68+
tl::expected<void, std::string> sendFdWithPayload(int socketFd, int fd, const std::string &payload)
69+
{
70+
struct msghdr msg{};
71+
72+
struct iovec iov{};
73+
iov.iov_base = const_cast<char *>(payload.data());
74+
iov.iov_len = payload.size();
75+
msg.msg_iov = &iov;
76+
msg.msg_iovlen = 1;
77+
78+
alignas(struct cmsghdr) std::array<std::byte, CMSG_SPACE(sizeof(int))> control_buf{};
79+
80+
msg.msg_control = control_buf.data();
81+
msg.msg_controllen = control_buf.size();
82+
83+
struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg);
84+
cmsg->cmsg_level = SOL_SOCKET;
85+
cmsg->cmsg_type = SCM_RIGHTS;
86+
cmsg->cmsg_len = CMSG_LEN(sizeof(int));
87+
88+
std::memcpy(CMSG_DATA(cmsg), &fd, sizeof(int));
89+
90+
auto n = [&]() -> ssize_t {
91+
while (true) {
92+
auto ret = sendmsg(socketFd, &msg, 0);
93+
if (ret < 0) {
94+
if (errno == EINTR) {
95+
continue;
96+
}
97+
98+
return -1;
99+
}
100+
101+
return ret;
102+
}
103+
}();
104+
105+
if (n < 0) {
106+
return tl::make_unexpected("sendmsg failed: " + std::string(strerror(errno)));
107+
}
108+
109+
if (static_cast<size_t>(n) < payload.size()) {
110+
return tl::make_unexpected("Partial write: payload not fully sent");
111+
}
112+
113+
return {};
114+
}
115+
116+
} // namespace linglong::common::socket
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd.
2+
//
3+
// SPDX-License-Identifier: LGPL-3.0-or-later
4+
5+
#pragma once
6+
7+
#include <tl/expected.hpp>
8+
9+
#include <string>
10+
11+
namespace linglong::common::socket {
12+
13+
struct SocketData
14+
{
15+
int fd;
16+
std::string payload;
17+
};
18+
19+
tl::expected<SocketData, std::string> recvFdWithPayload(int fd, std::size_t bufSize = 4096);
20+
21+
tl::expected<void, std::string> sendFdWithPayload(int socketFd, int fd, const std::string &payload);
22+
23+
} // namespace linglong::common::socket

libs/linglong/tests/ll-tests/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ pfl_add_executable(
1717
src/linglong/builder/source_fetcher_test.cpp
1818
src/linglong/cli/cli_test.cpp
1919
src/linglong/common/strings_test.cpp
20+
src/linglong/common/socket_test.cpp
2021
src/linglong/common/xdg_test.cpp
2122
src/linglong/mocks/layer_packager_mock.h
2223
src/linglong/mocks/linglong_builder_mock.h
@@ -59,6 +60,7 @@ pfl_add_executable(
5960
src/linglong/utils/xdg/directory_test.cpp
6061
src/main.cpp
6162

63+
6264
# ll-driver-detect tests
6365
${PROJECT_SOURCE_DIR}/apps/ll-driver-detect/src/nvidia_driver_detector.cpp
6466
${PROJECT_SOURCE_DIR}/apps/ll-driver-detect/src/driver_detection_config.cpp
Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd.
2+
//
3+
// SPDX-License-Identifier: LGPL-3.0-or-later
4+
5+
#include <gtest/gtest.h>
6+
7+
#include "linglong/common/socket.h"
8+
9+
#include <sys/socket.h>
10+
11+
class SocketFdTest : public ::testing::Test
12+
{
13+
protected:
14+
std::array<int, 2> sv;
15+
16+
void SetUp() override { ASSERT_EQ(socketpair(AF_UNIX, SOCK_SEQPACKET, 0, sv.data()), 0); }
17+
18+
void TearDown() override
19+
{
20+
close(sv[0]);
21+
close(sv[1]);
22+
}
23+
};
24+
25+
using namespace linglong::common::socket;
26+
27+
TEST_F(SocketFdTest, NormalCrossProcessTransfer)
28+
{
29+
const std::string payload = R"({"action": "test-fd-transfer"})";
30+
const std::string secret = "linglong-security-content";
31+
32+
auto pid = fork();
33+
ASSERT_NE(pid, -1);
34+
35+
if (pid == 0) {
36+
close(sv[1]);
37+
std::array<int, 2> pipefds{};
38+
ASSERT_EQ(pipe(pipefds.data()), 0);
39+
40+
ASSERT_NE(write(pipefds[1], secret.data(), secret.size()), -1);
41+
auto res = sendFdWithPayload(sv[0], pipefds[0], payload);
42+
43+
close(pipefds[0]);
44+
close(pipefds[1]);
45+
close(sv[0]);
46+
_exit(res.has_value() ? EXIT_SUCCESS : EXIT_FAILURE);
47+
}
48+
49+
close(sv[0]);
50+
auto res = recvFdWithPayload(sv[1], payload.size());
51+
52+
int status{ 0 };
53+
waitpid(pid, &status, 0);
54+
EXPECT_EQ(WEXITSTATUS(status), 0);
55+
56+
ASSERT_TRUE(res.has_value()) << res.error();
57+
EXPECT_EQ(res->payload, payload);
58+
ASSERT_GT(res->fd, 0);
59+
60+
std::string buf(secret.size(), '\0');
61+
const ssize_t n = read(res->fd, buf.data(), buf.size());
62+
ASSERT_NE(n, -1);
63+
EXPECT_EQ(std::string(buf.data(), n), secret);
64+
65+
close(res->fd);
66+
}
67+
68+
TEST_F(SocketFdTest, InvalidFileDescriptor)
69+
{
70+
auto res = recvFdWithPayload(-1, 1024);
71+
EXPECT_FALSE(res.has_value());
72+
EXPECT_EQ(res.error(), "Invalid file descriptor");
73+
}
74+
75+
TEST_F(SocketFdTest, EmptyAncillaryData)
76+
{
77+
if (fork() == 0) {
78+
close(sv[1]);
79+
std::string data = "just-text-no-fd";
80+
ASSERT_NE(write(sv[0], data.data(), data.size()), -1);
81+
close(sv[0]);
82+
_exit(0);
83+
}
84+
85+
close(sv[0]);
86+
auto res = recvFdWithPayload(sv[1], 1024);
87+
EXPECT_FALSE(res.has_value());
88+
wait(nullptr);
89+
}
90+
91+
TEST_F(SocketFdTest, PayloadBufferTruncation)
92+
{
93+
const std::string long_payload(200, 'Z');
94+
if (fork() == 0) {
95+
close(sv[1]);
96+
auto ret = sendFdWithPayload(sv[0], STDOUT_FILENO, long_payload);
97+
EXPECT_TRUE(ret) << ret.error();
98+
close(sv[0]);
99+
_exit(0);
100+
}
101+
close(sv[0]);
102+
103+
auto res = recvFdWithPayload(sv[1], 50);
104+
ASSERT_TRUE(res.has_value());
105+
EXPECT_EQ(res->payload.size(), 50);
106+
EXPECT_EQ(res->payload, long_payload.substr(0, 50));
107+
108+
if (res->fd >= 0) {
109+
close(res->fd);
110+
}
111+
112+
wait(nullptr);
113+
}
114+
115+
TEST_F(SocketFdTest, SignalInterruptionRecovery)
116+
{
117+
auto handler = signal(SIGUSR1, [](int) { });
118+
ASSERT_NE(handler, SIG_ERR);
119+
120+
if (fork() == 0) {
121+
close(sv[1]);
122+
usleep(50000);
123+
kill(getppid(), SIGUSR1);
124+
usleep(50000);
125+
auto ret = sendFdWithPayload(sv[0], STDERR_FILENO, "recovered");
126+
EXPECT_TRUE(ret) << ret.error();
127+
close(sv[0]);
128+
_exit(0);
129+
}
130+
131+
close(sv[0]);
132+
auto res = recvFdWithPayload(sv[1], 1024);
133+
ASSERT_TRUE(res.has_value());
134+
EXPECT_EQ(res->payload, "recovered");
135+
136+
if (res->fd >= 0) {
137+
close(res->fd);
138+
}
139+
140+
wait(nullptr);
141+
142+
ASSERT_NE(signal(SIGUSR1, handler), SIG_ERR);
143+
}
144+
145+
TEST_F(SocketFdTest, PeerClosedBeforeSending)
146+
{
147+
if (fork() == 0) {
148+
close(sv[1]);
149+
close(sv[0]);
150+
_exit(0);
151+
}
152+
close(sv[0]);
153+
auto res = recvFdWithPayload(sv[1], 1024);
154+
EXPECT_FALSE(res.has_value());
155+
wait(nullptr);
156+
}
157+
158+
TEST_F(SocketFdTest, SendInvalidFdHandle)
159+
{
160+
auto res = sendFdWithPayload(sv[0], 9999, "bad-fd");
161+
EXPECT_FALSE(res.has_value());
162+
}
163+
164+
TEST_F(SocketFdTest, LargePayloadHandling)
165+
{
166+
const std::string large_payload(65536, 'B');
167+
if (fork() == 0) {
168+
close(sv[1]);
169+
auto ret = sendFdWithPayload(sv[0], STDIN_FILENO, large_payload);
170+
close(sv[0]);
171+
_exit(0);
172+
}
173+
close(sv[0]);
174+
auto res = recvFdWithPayload(sv[1], 70000);
175+
ASSERT_TRUE(res.has_value());
176+
EXPECT_EQ(res->payload, large_payload);
177+
178+
if (res->fd >= 0) {
179+
close(res->fd);
180+
}
181+
182+
wait(nullptr);
183+
}

0 commit comments

Comments
 (0)