Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qohosnouichildprocess.cpp
Go to the documentation of this file.
1// Copyright (C) 2025 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
5#include <QtCore/qbytearray.h>
6#include <QtCore/qjsondocument.h>
7#include <QtCore/private/qohoslogger_p.h>
8#include <cerrno>
9#include <cstddef>
10#include <cstring>
11#include <chrono>
12#include <fcntl.h>
13#include <memory>
14#include <stdexcept>
15#include <string>
16#include <sys/socket.h>
17#include <sys/stat.h>
18#include <sys/time.h>
19#include <sys/types.h>
20#include <sys/un.h>
21#include <system_error>
22#include <thread>
23#include <unistd.h>
24
25using namespace std::string_literals;
26
27QT_BEGIN_NAMESPACE
28
29namespace QtOhos {
30
31namespace {
32
33// Note: we hard-code this, as the child process doesn't have access to app context
34const auto appTempDir = "/data/storage/el2/base/temp";
35
36constexpr unsigned qChildSetupDataSendTimeoutSecs = 5;
37
38constexpr auto qChildSetupDataSendRetryDelay = std::chrono::milliseconds(50);
39
40constexpr std::size_t qChildSetupMaxSetupDataSize = 64 * 1024;
41
42std::system_error makeSystemErrorFromErrno(const std::string &message, int errnoValue)
43{
44 return std::system_error(errnoValue, std::generic_category(), message);
45}
46
47std::string getSetupDataExchangeDirPath()
48{
49 return appTempDir + "/qohos-child-setup-data"s;
50}
51
52std::string getChildSetupDataPath(int childPid)
53{
54 return getSetupDataExchangeDirPath() + "/"s + std::to_string(childPid);
55}
56
57void makeSetupDataExchangeDirIfNeeded()
58{
59 auto setupDataExchangeDirPath = getSetupDataExchangeDirPath();
60
61 if (::mkdir(setupDataExchangeDirPath.c_str(), 0700) != 0 && errno != EEXIST) {
62 throw makeSystemErrorFromErrno(
63 "error creating 'child setup data' directory", errno);
64 }
65
66 struct ::stat statBuf;
67 if (::stat(setupDataExchangeDirPath.c_str(), &statBuf) != 0) {
68 throw makeSystemErrorFromErrno(
69 "stat() failed for 'child setup data' directory", errno);
70 }
71
72 if (!S_ISDIR(statBuf.st_mode))
73 throw std::runtime_error("non-directory found at 'child setup data' directory path");
74}
75
76void writeChildSetupDataFile(int childPid, const std::string &setupDataStr)
77{
78 auto childSetupDataPath = getChildSetupDataPath(childPid);
79 auto tmpChildSetupDataPath = childSetupDataPath + ".tmp"s;
80
81 makeSetupDataExchangeDirIfNeeded();
82
83 (void) ::unlink(childSetupDataPath.c_str());
84 (void) ::unlink(tmpChildSetupDataPath.c_str());
85
86 auto openRes = ::open(tmpChildSetupDataPath.c_str(), O_WRONLY | O_CREAT | O_EXCL, 0600);
87 if (openRes < 0) {
88 throw makeSystemErrorFromErrno(
89 "error opening 'child setup data' file '"s + tmpChildSetupDataPath + "' for write"s, errno);
90 }
91 int fileFd = openRes;
92
93 auto writeRes = ::write(fileFd, setupDataStr.data(), setupDataStr.size());
94 int writeErrno = errno;
95
96 (void) ::close(fileFd);
97
98 if (writeRes < 0) {
99 (void) ::unlink(tmpChildSetupDataPath.c_str());
100 throw makeSystemErrorFromErrno(
101 "error writing to 'child setup data' file '"s + tmpChildSetupDataPath + "'"s, writeErrno);
102 }
103 auto writtenSize = static_cast<std::size_t>(writeRes);
104 if (writtenSize != setupDataStr.size()) {
105 (void) ::unlink(tmpChildSetupDataPath.c_str());
106 throw std::runtime_error(
107 "incomplete write to 'child setup data' file: "s
108 + std::to_string(writtenSize) + "/"s + std::to_string(setupDataStr.size()));
109 }
110
111 if (::rename(tmpChildSetupDataPath.c_str(), childSetupDataPath.c_str()) != 0) {
112 throw makeSystemErrorFromErrno(
113 "error renaming 'child setup data' file '"s + childSetupDataPath + "'"s, errno);
114 }
115}
116
117std::string tryReadChildSetupDataFile(int childPid)
118{
119 auto childSetupDataPath = getChildSetupDataPath(childPid);
120
121 auto openRes = ::open(childSetupDataPath.c_str(), O_RDONLY);
122 if (openRes < 0 && errno == ENOENT)
123 return {};
124 if (openRes < 0) {
125 throw makeSystemErrorFromErrno(
126 "error opening 'child setup data' file '"s + childSetupDataPath + "' for read"s, errno);
127 }
128 int fileFd = openRes;
129
130 char readBuffer[qChildSetupMaxSetupDataSize + 1];
131 auto readRes = ::read(fileFd, readBuffer, sizeof(readBuffer));
132 int readErrno = errno;
133
134 (void) ::close(fileFd);
135
136 if (readRes < 0) {
137 throw makeSystemErrorFromErrno(
138 "error reading 'child setup data' file '"s + childSetupDataPath + "'"s, readErrno);
139 }
140 auto readSize = static_cast<std::size_t>(readRes);
141 if (readSize > qChildSetupMaxSetupDataSize) {
142 throw std::runtime_error(
143 "received 'child setup data' file '"s + childSetupDataPath + "' is too big"s);
144 } else if (readSize == 0) {
145 throw std::runtime_error(
146 "received 'child setup data' file '"s + childSetupDataPath + "' is empty"s);
147 }
148
149 return std::string(readBuffer, readSize);
150}
151
152bool checkIfChildSetupDataFileExists(int childPid)
153{
154 return ::access(getChildSetupDataPath(childPid).c_str(), F_OK) == 0;
155}
156
157void removeChildSetupDataFileIfExists(int childPid)
158{
159 (void) ::unlink(getChildSetupDataPath(childPid).c_str());
160}
161
162}
163
165{
166 namespace ch = std::chrono;
167
168 auto childPid = ::getpid();
169
170 std::string setupDataStr;
171 try {
172 auto startTime = ch::steady_clock::now();
173 auto timeoutEnd = startTime + ch::seconds(qChildSetupDataSendTimeoutSecs);
174 do {
175 setupDataStr = tryReadChildSetupDataFile(childPid);
176 if (!setupDataStr.empty())
177 break;
178 std::this_thread::sleep_for(qChildSetupDataSendRetryDelay);
179 } while (ch::steady_clock::now() < timeoutEnd);
180
181 if (!setupDataStr.empty())
182 removeChildSetupDataFileIfExists(childPid);
183 else
184 throw std::runtime_error("timeout waiting for 'child setup data' file");
185 } catch (const std::exception &e) {
186 qOhosPrintfError("%s: error reading subprocess setup data: %s", Q_FUNC_INFO, e.what());
187 }
188
189 auto setupDataDoc = QJsonDocument::fromJson(QByteArray::fromStdString(setupDataStr));
190 if (!setupDataDoc.isObject())
191 qOhosPrintfError("%s: subprocess setup data has illegal format", Q_FUNC_INFO);
192
193 return setupDataDoc.object();
194}
195
196void sendChildProcessSetupData(int childPid, QJsonObject setupData)
197{
198 namespace ch = std::chrono;
199
200 auto senderThread = std::thread(
201 [childPid, setupData]() {
202 auto setupDataStr = QJsonDocument(setupData).toJson(QJsonDocument::Compact).toStdString();
203
204 try {
205 writeChildSetupDataFile(childPid, setupDataStr);
206 } catch (const std::exception &e) {
207 qOhosPrintfError("%s: error writing subprocess setup data: %s", Q_FUNC_INFO, e.what());
208 return;
209 }
210
211 auto startTime = ch::steady_clock::now();
212 auto timeoutEnd = startTime + ch::seconds(qChildSetupDataSendTimeoutSecs);
213 do {
214 std::this_thread::sleep_for(qChildSetupDataSendRetryDelay);
215 } while (checkIfChildSetupDataFileExists(childPid) && ch::steady_clock::now() < timeoutEnd);
216
217 if (checkIfChildSetupDataFileExists(childPid)) {
218 removeChildSetupDataFileIfExists(childPid);
219 qOhosPrintfError("%s: failed to send setup data to child %d", Q_FUNC_INFO, childPid);
220 }
221 });
222 senderThread.detach();
223}
224
225}
226
227QT_END_NAMESPACE
QJsonObject readChildProcessSetupData()
void sendChildProcessSetupData(int childPid, QJsonObject setupData)