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/qjsondocument.h>
6#include <QtCore/private/qohoslogger_p.h>
7#include <cerrno>
8#include <cstddef>
9#include <cstring>
10#include <chrono>
11#include <fcntl.h>
12#include <memory>
13#include <optional>
14#include <string>
15#include <sys/socket.h>
16#include <sys/stat.h>
17#include <sys/time.h>
18#include <sys/types.h>
19#include <sys/un.h>
20#include <thread>
21#include <unistd.h>
22
23using namespace std::string_literals;
24
25QT_BEGIN_NAMESPACE
26
27namespace QtOhos {
28
29namespace {
30
31// Note: we hard-code this, as the child process doesn't have access to app context
32const auto appTempDir = "/data/storage/el2/base/temp";
33
34constexpr unsigned qChildSetupDataSendTimeoutSecs = 5;
35
36constexpr auto qChildSetupDataSendRetryDelay = std::chrono::milliseconds(50);
37
38constexpr std::size_t qChildSetupMaxSetupDataSize = 64 * 1024;
39
40std::string getSetupDataExchangeDirPath()
41{
42 return appTempDir + "/qohos-child-setup-data"s;
43}
44
45std::string getChildSetupDataPath(int childPid)
46{
47 return getSetupDataExchangeDirPath() + "/"s + std::to_string(childPid);
48}
49
50bool tryMakeSetupDataExchangeDirIfNeeded()
51{
52 auto setupDataExchangeDirPath = getSetupDataExchangeDirPath();
53
54 if (::mkdir(setupDataExchangeDirPath.c_str(), 0700) != 0 && errno != EEXIST) {
55 qOhosPrintfError(
56 "%s: error creating 'child setup data' directory: %s",
57 Q_FUNC_INFO, std::strerror(errno));
58 return false;
59 }
60
61 struct ::stat statBuf;
62 if (::stat(setupDataExchangeDirPath.c_str(), &statBuf) != 0) {
63 qOhosPrintfError(
64 "%s: stat() failed for 'child setup data' directory: %s",
65 Q_FUNC_INFO, std::strerror(errno));
66 return false;
67 }
68
69 if (!S_ISDIR(statBuf.st_mode)) {
70 qOhosPrintfError(
71 "%s: non-directory found at 'child setup data' directory path", Q_FUNC_INFO);
72 return false;
73 }
74
75 return true;
76}
77
78bool tryWriteChildSetupDataFile(int childPid, const std::string &setupDataStr)
79{
80 auto childSetupDataPath = getChildSetupDataPath(childPid);
81 auto tmpChildSetupDataPath = childSetupDataPath + ".tmp"s;
82
83 if (!tryMakeSetupDataExchangeDirIfNeeded())
84 return false;
85
86 (void) ::unlink(childSetupDataPath.c_str());
87 (void) ::unlink(tmpChildSetupDataPath.c_str());
88
89 auto openRes = ::open(tmpChildSetupDataPath.c_str(), O_WRONLY | O_CREAT | O_EXCL, 0600);
90 if (openRes < 0) {
91 qOhosPrintfError(
92 "%s: error opening 'child setup data' file '%s' for write: %s",
93 Q_FUNC_INFO, tmpChildSetupDataPath.c_str(), std::strerror(errno));
94 return false;
95 }
96 int fileFd = openRes;
97
98 auto writeRes = ::write(fileFd, setupDataStr.data(), setupDataStr.size());
99 int writeErrno = errno;
100
101 (void) ::close(fileFd);
102
103 if (writeRes < 0) {
104 (void) ::unlink(tmpChildSetupDataPath.c_str());
105 qOhosPrintfError(
106 "%s: error writing to 'child setup data' file '%s': %s",
107 Q_FUNC_INFO, tmpChildSetupDataPath.c_str(), std::strerror(writeErrno));
108 return false;
109 }
110 auto writtenSize = static_cast<std::size_t>(writeRes);
111 if (writtenSize != setupDataStr.size()) {
112 (void) ::unlink(tmpChildSetupDataPath.c_str());
113 qOhosPrintfError(
114 "%s: incomplete write to 'child setup data' file: %zu/%zu",
115 Q_FUNC_INFO, writtenSize, setupDataStr.size());
116 return false;
117 }
118
119 if (::rename(tmpChildSetupDataPath.c_str(), childSetupDataPath.c_str()) != 0) {
120 qOhosPrintfError(
121 "%s: error renaming 'child setup data' file '%s': %s",
122 Q_FUNC_INFO, childSetupDataPath.c_str(), std::strerror(errno));
123 return false;
124 }
125
126 return true;
127}
128
129// Returns std::nullopt on error, an empty string when the file is not available (yet).
130std::optional<std::string> tryReadChildSetupDataFile(int childPid)
131{
132 auto childSetupDataPath = getChildSetupDataPath(childPid);
133
134 auto openRes = ::open(childSetupDataPath.c_str(), O_RDONLY);
135 if (openRes < 0 && errno == ENOENT)
136 return std::string();
137 if (openRes < 0) {
138 qOhosPrintfError(
139 "%s: error opening 'child setup data' file '%s' for read: %s",
140 Q_FUNC_INFO, childSetupDataPath.c_str(), std::strerror(errno));
141 return {};
142 }
143 int fileFd = openRes;
144
145 char readBuffer[qChildSetupMaxSetupDataSize + 1];
146 auto readRes = ::read(fileFd, readBuffer, sizeof(readBuffer));
147 int readErrno = errno;
148
149 (void) ::close(fileFd);
150
151 if (readRes < 0) {
152 qOhosPrintfError(
153 "%s: error reading 'child setup data' file '%s': %s",
154 Q_FUNC_INFO, childSetupDataPath.c_str(), std::strerror(readErrno));
155 return {};
156 }
157 auto readSize = static_cast<std::size_t>(readRes);
158 if (readSize > qChildSetupMaxSetupDataSize) {
159 qOhosPrintfError(
160 "%s: received 'child setup data' file '%s' is too big",
161 Q_FUNC_INFO, childSetupDataPath.c_str());
162 return {};
163 }
164 if (readSize == 0) {
165 qOhosPrintfError(
166 "%s: received 'child setup data' file '%s' is empty",
167 Q_FUNC_INFO, childSetupDataPath.c_str());
168 return {};
169 }
170
171 return std::string(readBuffer, readSize);
172}
173
174bool checkIfChildSetupDataFileExists(int childPid)
175{
176 return ::access(getChildSetupDataPath(childPid).c_str(), F_OK) == 0;
177}
178
179void removeChildSetupDataFileIfExists(int childPid)
180{
181 (void) ::unlink(getChildSetupDataPath(childPid).c_str());
182}
183
184// Returns the setup data, or std::nullopt if it couldn't be read (the reason is logged).
185std::optional<std::string> tryWaitForChildSetupData(int childPid)
186{
187 namespace ch = std::chrono;
188
189 auto timeoutEnd = ch::steady_clock::now() + ch::seconds(qChildSetupDataSendTimeoutSecs);
190 do {
191 auto optSetupDataStr = tryReadChildSetupDataFile(childPid);
192 if (!optSetupDataStr)
193 return {};
194 if (!optSetupDataStr->empty()) {
195 removeChildSetupDataFileIfExists(childPid);
196 return optSetupDataStr;
197 }
198 std::this_thread::sleep_for(qChildSetupDataSendRetryDelay);
199 } while (ch::steady_clock::now() < timeoutEnd);
200
201 qOhosPrintfError("%s: timeout waiting for 'child setup data' file", Q_FUNC_INFO);
202 return {};
203}
204
205}
206
208{
209 auto optSetupDataStr = tryWaitForChildSetupData(::getpid());
210 if (!optSetupDataStr)
211 return QNapi::makeObject(env);
212
213 QNapi::Object global = env.Global();
214 try {
215 return global.eval<QNapi::Object>("JSON.parse(*)", {optSetupDataStr.value()});
216 } catch (const Napi::Error &e) {
217 qOhosPrintfError("%s: subprocess setup data has illegal format: %s", Q_FUNC_INFO, e.what());
218 return QNapi::makeObject(env);
219 }
220}
221
222void sendChildProcessSetupData(int childPid, QJsonObject setupData)
223{
224 namespace ch = std::chrono;
225
226 auto senderThread = std::thread(
227 [childPid, setupData]() {
228 auto setupDataStr = QJsonDocument(setupData).toJson(QJsonDocument::Compact).toStdString();
229
230 if (!tryWriteChildSetupDataFile(childPid, setupDataStr))
231 return;
232
233 auto startTime = ch::steady_clock::now();
234 auto timeoutEnd = startTime + ch::seconds(qChildSetupDataSendTimeoutSecs);
235 do {
236 std::this_thread::sleep_for(qChildSetupDataSendRetryDelay);
237 } while (checkIfChildSetupDataFileExists(childPid) && ch::steady_clock::now() < timeoutEnd);
238
239 if (checkIfChildSetupDataFileExists(childPid)) {
240 removeChildSetupDataFileIfExists(childPid);
241 qOhosPrintfError("%s: failed to send setup data to child %d", Q_FUNC_INFO, childPid);
242 }
243 });
244 senderThread.detach();
245}
246
247}
248
249QT_END_NAMESPACE
QNapi::Object readChildProcessSetupData(Napi::Env env)
void sendChildProcessSetupData(int childPid, QJsonObject setupData)