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