1695b41eeSopenharmony_ci// Copyright 2012 Google Inc. All Rights Reserved. 2695b41eeSopenharmony_ci// 3695b41eeSopenharmony_ci// Licensed under the Apache License, Version 2.0 (the "License"); 4695b41eeSopenharmony_ci// you may not use this file except in compliance with the License. 5695b41eeSopenharmony_ci// You may obtain a copy of the License at 6695b41eeSopenharmony_ci// 7695b41eeSopenharmony_ci// http://www.apache.org/licenses/LICENSE-2.0 8695b41eeSopenharmony_ci// 9695b41eeSopenharmony_ci// Unless required by applicable law or agreed to in writing, software 10695b41eeSopenharmony_ci// distributed under the License is distributed on an "AS IS" BASIS, 11695b41eeSopenharmony_ci// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12695b41eeSopenharmony_ci// See the License for the specific language governing permissions and 13695b41eeSopenharmony_ci// limitations under the License. 14695b41eeSopenharmony_ci 15695b41eeSopenharmony_ci#include "subprocess.h" 16695b41eeSopenharmony_ci 17695b41eeSopenharmony_ci#include <sys/select.h> 18695b41eeSopenharmony_ci#include <assert.h> 19695b41eeSopenharmony_ci#include <errno.h> 20695b41eeSopenharmony_ci#include <fcntl.h> 21695b41eeSopenharmony_ci#include <unistd.h> 22695b41eeSopenharmony_ci#include <stdio.h> 23695b41eeSopenharmony_ci#include <string.h> 24695b41eeSopenharmony_ci#include <sys/wait.h> 25695b41eeSopenharmony_ci#include <spawn.h> 26695b41eeSopenharmony_ci 27695b41eeSopenharmony_ci#if defined(USE_PPOLL) 28695b41eeSopenharmony_ci#include <poll.h> 29695b41eeSopenharmony_ci#else 30695b41eeSopenharmony_ci#include <sys/select.h> 31695b41eeSopenharmony_ci#endif 32695b41eeSopenharmony_ci 33695b41eeSopenharmony_ciextern char** environ; 34695b41eeSopenharmony_ci 35695b41eeSopenharmony_ci#include "util.h" 36695b41eeSopenharmony_ci 37695b41eeSopenharmony_ciusing namespace std; 38695b41eeSopenharmony_ci 39695b41eeSopenharmony_ciSubprocess::Subprocess(bool use_console) : fd_(-1), pid_(-1), 40695b41eeSopenharmony_ci use_console_(use_console) { 41695b41eeSopenharmony_ci} 42695b41eeSopenharmony_ci 43695b41eeSopenharmony_ciSubprocess::~Subprocess() { 44695b41eeSopenharmony_ci if (fd_ >= 0) 45695b41eeSopenharmony_ci close(fd_); 46695b41eeSopenharmony_ci // Reap child if forgotten. 47695b41eeSopenharmony_ci if (pid_ != -1) 48695b41eeSopenharmony_ci Finish(); 49695b41eeSopenharmony_ci} 50695b41eeSopenharmony_ci 51695b41eeSopenharmony_cibool Subprocess::Start(SubprocessSet* set, const string& command) { 52695b41eeSopenharmony_ci int output_pipe[2]; 53695b41eeSopenharmony_ci if (pipe(output_pipe) < 0) 54695b41eeSopenharmony_ci Fatal("pipe: %s", strerror(errno)); 55695b41eeSopenharmony_ci fd_ = output_pipe[0]; 56695b41eeSopenharmony_ci#if !defined(USE_PPOLL) 57695b41eeSopenharmony_ci // If available, we use ppoll in DoWork(); otherwise we use pselect 58695b41eeSopenharmony_ci // and so must avoid overly-large FDs. 59695b41eeSopenharmony_ci if (fd_ >= static_cast<int>(FD_SETSIZE)) 60695b41eeSopenharmony_ci Fatal("pipe: %s", strerror(EMFILE)); 61695b41eeSopenharmony_ci#endif // !USE_PPOLL 62695b41eeSopenharmony_ci SetCloseOnExec(fd_); 63695b41eeSopenharmony_ci 64695b41eeSopenharmony_ci posix_spawn_file_actions_t action; 65695b41eeSopenharmony_ci int err = posix_spawn_file_actions_init(&action); 66695b41eeSopenharmony_ci if (err != 0) 67695b41eeSopenharmony_ci Fatal("posix_spawn_file_actions_init: %s", strerror(err)); 68695b41eeSopenharmony_ci 69695b41eeSopenharmony_ci err = posix_spawn_file_actions_addclose(&action, output_pipe[0]); 70695b41eeSopenharmony_ci if (err != 0) 71695b41eeSopenharmony_ci Fatal("posix_spawn_file_actions_addclose: %s", strerror(err)); 72695b41eeSopenharmony_ci 73695b41eeSopenharmony_ci posix_spawnattr_t attr; 74695b41eeSopenharmony_ci err = posix_spawnattr_init(&attr); 75695b41eeSopenharmony_ci if (err != 0) 76695b41eeSopenharmony_ci Fatal("posix_spawnattr_init: %s", strerror(err)); 77695b41eeSopenharmony_ci 78695b41eeSopenharmony_ci short flags = 0; 79695b41eeSopenharmony_ci 80695b41eeSopenharmony_ci flags |= POSIX_SPAWN_SETSIGMASK; 81695b41eeSopenharmony_ci err = posix_spawnattr_setsigmask(&attr, &set->old_mask_); 82695b41eeSopenharmony_ci if (err != 0) 83695b41eeSopenharmony_ci Fatal("posix_spawnattr_setsigmask: %s", strerror(err)); 84695b41eeSopenharmony_ci // Signals which are set to be caught in the calling process image are set to 85695b41eeSopenharmony_ci // default action in the new process image, so no explicit 86695b41eeSopenharmony_ci // POSIX_SPAWN_SETSIGDEF parameter is needed. 87695b41eeSopenharmony_ci 88695b41eeSopenharmony_ci if (!use_console_) { 89695b41eeSopenharmony_ci // Put the child in its own process group, so ctrl-c won't reach it. 90695b41eeSopenharmony_ci flags |= POSIX_SPAWN_SETPGROUP; 91695b41eeSopenharmony_ci // No need to posix_spawnattr_setpgroup(&attr, 0), it's the default. 92695b41eeSopenharmony_ci 93695b41eeSopenharmony_ci // Open /dev/null over stdin. 94695b41eeSopenharmony_ci err = posix_spawn_file_actions_addopen(&action, 0, "/dev/null", O_RDONLY, 95695b41eeSopenharmony_ci 0); 96695b41eeSopenharmony_ci if (err != 0) { 97695b41eeSopenharmony_ci Fatal("posix_spawn_file_actions_addopen: %s", strerror(err)); 98695b41eeSopenharmony_ci } 99695b41eeSopenharmony_ci 100695b41eeSopenharmony_ci err = posix_spawn_file_actions_adddup2(&action, output_pipe[1], 1); 101695b41eeSopenharmony_ci if (err != 0) 102695b41eeSopenharmony_ci Fatal("posix_spawn_file_actions_adddup2: %s", strerror(err)); 103695b41eeSopenharmony_ci err = posix_spawn_file_actions_adddup2(&action, output_pipe[1], 2); 104695b41eeSopenharmony_ci if (err != 0) 105695b41eeSopenharmony_ci Fatal("posix_spawn_file_actions_adddup2: %s", strerror(err)); 106695b41eeSopenharmony_ci err = posix_spawn_file_actions_addclose(&action, output_pipe[1]); 107695b41eeSopenharmony_ci if (err != 0) 108695b41eeSopenharmony_ci Fatal("posix_spawn_file_actions_addclose: %s", strerror(err)); 109695b41eeSopenharmony_ci // In the console case, output_pipe is still inherited by the child and 110695b41eeSopenharmony_ci // closed when the subprocess finishes, which then notifies ninja. 111695b41eeSopenharmony_ci } 112695b41eeSopenharmony_ci#ifdef POSIX_SPAWN_USEVFORK 113695b41eeSopenharmony_ci flags |= POSIX_SPAWN_USEVFORK; 114695b41eeSopenharmony_ci#endif 115695b41eeSopenharmony_ci 116695b41eeSopenharmony_ci err = posix_spawnattr_setflags(&attr, flags); 117695b41eeSopenharmony_ci if (err != 0) 118695b41eeSopenharmony_ci Fatal("posix_spawnattr_setflags: %s", strerror(err)); 119695b41eeSopenharmony_ci 120695b41eeSopenharmony_ci const char* spawned_args[] = { "/bin/sh", "-c", command.c_str(), NULL }; 121695b41eeSopenharmony_ci err = posix_spawn(&pid_, "/bin/sh", &action, &attr, 122695b41eeSopenharmony_ci const_cast<char**>(spawned_args), environ); 123695b41eeSopenharmony_ci if (err != 0) 124695b41eeSopenharmony_ci Fatal("posix_spawn: %s", strerror(err)); 125695b41eeSopenharmony_ci 126695b41eeSopenharmony_ci err = posix_spawnattr_destroy(&attr); 127695b41eeSopenharmony_ci if (err != 0) 128695b41eeSopenharmony_ci Fatal("posix_spawnattr_destroy: %s", strerror(err)); 129695b41eeSopenharmony_ci err = posix_spawn_file_actions_destroy(&action); 130695b41eeSopenharmony_ci if (err != 0) 131695b41eeSopenharmony_ci Fatal("posix_spawn_file_actions_destroy: %s", strerror(err)); 132695b41eeSopenharmony_ci 133695b41eeSopenharmony_ci close(output_pipe[1]); 134695b41eeSopenharmony_ci return true; 135695b41eeSopenharmony_ci} 136695b41eeSopenharmony_ci 137695b41eeSopenharmony_civoid Subprocess::OnPipeReady() { 138695b41eeSopenharmony_ci char buf[4 << 10]; 139695b41eeSopenharmony_ci ssize_t len = read(fd_, buf, sizeof(buf)); 140695b41eeSopenharmony_ci if (len > 0) { 141695b41eeSopenharmony_ci buf_.append(buf, len); 142695b41eeSopenharmony_ci } else { 143695b41eeSopenharmony_ci if (len < 0) 144695b41eeSopenharmony_ci Fatal("read: %s", strerror(errno)); 145695b41eeSopenharmony_ci close(fd_); 146695b41eeSopenharmony_ci fd_ = -1; 147695b41eeSopenharmony_ci } 148695b41eeSopenharmony_ci} 149695b41eeSopenharmony_ci 150695b41eeSopenharmony_ciExitStatus Subprocess::Finish() { 151695b41eeSopenharmony_ci assert(pid_ != -1); 152695b41eeSopenharmony_ci int status; 153695b41eeSopenharmony_ci if (waitpid(pid_, &status, 0) < 0) 154695b41eeSopenharmony_ci Fatal("waitpid(%d): %s", pid_, strerror(errno)); 155695b41eeSopenharmony_ci pid_ = -1; 156695b41eeSopenharmony_ci 157695b41eeSopenharmony_ci#ifdef _AIX 158695b41eeSopenharmony_ci if (WIFEXITED(status) && WEXITSTATUS(status) & 0x80) { 159695b41eeSopenharmony_ci // Map the shell's exit code used for signal failure (128 + signal) to the 160695b41eeSopenharmony_ci // status code expected by AIX WIFSIGNALED and WTERMSIG macros which, unlike 161695b41eeSopenharmony_ci // other systems, uses a different bit layout. 162695b41eeSopenharmony_ci int signal = WEXITSTATUS(status) & 0x7f; 163695b41eeSopenharmony_ci status = (signal << 16) | signal; 164695b41eeSopenharmony_ci } 165695b41eeSopenharmony_ci#endif 166695b41eeSopenharmony_ci 167695b41eeSopenharmony_ci if (WIFEXITED(status)) { 168695b41eeSopenharmony_ci int exit = WEXITSTATUS(status); 169695b41eeSopenharmony_ci if (exit == 0) 170695b41eeSopenharmony_ci return ExitSuccess; 171695b41eeSopenharmony_ci } else if (WIFSIGNALED(status)) { 172695b41eeSopenharmony_ci if (WTERMSIG(status) == SIGINT || WTERMSIG(status) == SIGTERM 173695b41eeSopenharmony_ci || WTERMSIG(status) == SIGHUP) 174695b41eeSopenharmony_ci return ExitInterrupted; 175695b41eeSopenharmony_ci } 176695b41eeSopenharmony_ci return ExitFailure; 177695b41eeSopenharmony_ci} 178695b41eeSopenharmony_ci 179695b41eeSopenharmony_cibool Subprocess::Done() const { 180695b41eeSopenharmony_ci return fd_ == -1; 181695b41eeSopenharmony_ci} 182695b41eeSopenharmony_ci 183695b41eeSopenharmony_ciconst string& Subprocess::GetOutput() const { 184695b41eeSopenharmony_ci return buf_; 185695b41eeSopenharmony_ci} 186695b41eeSopenharmony_ci 187695b41eeSopenharmony_ciint SubprocessSet::interrupted_; 188695b41eeSopenharmony_ci 189695b41eeSopenharmony_civoid SubprocessSet::SetInterruptedFlag(int signum) { 190695b41eeSopenharmony_ci interrupted_ = signum; 191695b41eeSopenharmony_ci} 192695b41eeSopenharmony_ci 193695b41eeSopenharmony_civoid SubprocessSet::HandlePendingInterruption() { 194695b41eeSopenharmony_ci sigset_t pending; 195695b41eeSopenharmony_ci sigemptyset(&pending); 196695b41eeSopenharmony_ci if (sigpending(&pending) == -1) { 197695b41eeSopenharmony_ci perror("ninja: sigpending"); 198695b41eeSopenharmony_ci return; 199695b41eeSopenharmony_ci } 200695b41eeSopenharmony_ci if (sigismember(&pending, SIGINT)) 201695b41eeSopenharmony_ci interrupted_ = SIGINT; 202695b41eeSopenharmony_ci else if (sigismember(&pending, SIGTERM)) 203695b41eeSopenharmony_ci interrupted_ = SIGTERM; 204695b41eeSopenharmony_ci else if (sigismember(&pending, SIGHUP)) 205695b41eeSopenharmony_ci interrupted_ = SIGHUP; 206695b41eeSopenharmony_ci} 207695b41eeSopenharmony_ci 208695b41eeSopenharmony_ciSubprocessSet::SubprocessSet() { 209695b41eeSopenharmony_ci sigset_t set; 210695b41eeSopenharmony_ci sigemptyset(&set); 211695b41eeSopenharmony_ci sigaddset(&set, SIGINT); 212695b41eeSopenharmony_ci sigaddset(&set, SIGTERM); 213695b41eeSopenharmony_ci sigaddset(&set, SIGHUP); 214695b41eeSopenharmony_ci if (sigprocmask(SIG_BLOCK, &set, &old_mask_) < 0) 215695b41eeSopenharmony_ci Fatal("sigprocmask: %s", strerror(errno)); 216695b41eeSopenharmony_ci 217695b41eeSopenharmony_ci struct sigaction act; 218695b41eeSopenharmony_ci memset(&act, 0, sizeof(act)); 219695b41eeSopenharmony_ci act.sa_handler = SetInterruptedFlag; 220695b41eeSopenharmony_ci if (sigaction(SIGINT, &act, &old_int_act_) < 0) 221695b41eeSopenharmony_ci Fatal("sigaction: %s", strerror(errno)); 222695b41eeSopenharmony_ci if (sigaction(SIGTERM, &act, &old_term_act_) < 0) 223695b41eeSopenharmony_ci Fatal("sigaction: %s", strerror(errno)); 224695b41eeSopenharmony_ci if (sigaction(SIGHUP, &act, &old_hup_act_) < 0) 225695b41eeSopenharmony_ci Fatal("sigaction: %s", strerror(errno)); 226695b41eeSopenharmony_ci} 227695b41eeSopenharmony_ci 228695b41eeSopenharmony_ciSubprocessSet::~SubprocessSet() { 229695b41eeSopenharmony_ci Clear(); 230695b41eeSopenharmony_ci 231695b41eeSopenharmony_ci if (sigaction(SIGINT, &old_int_act_, 0) < 0) 232695b41eeSopenharmony_ci Fatal("sigaction: %s", strerror(errno)); 233695b41eeSopenharmony_ci if (sigaction(SIGTERM, &old_term_act_, 0) < 0) 234695b41eeSopenharmony_ci Fatal("sigaction: %s", strerror(errno)); 235695b41eeSopenharmony_ci if (sigaction(SIGHUP, &old_hup_act_, 0) < 0) 236695b41eeSopenharmony_ci Fatal("sigaction: %s", strerror(errno)); 237695b41eeSopenharmony_ci if (sigprocmask(SIG_SETMASK, &old_mask_, 0) < 0) 238695b41eeSopenharmony_ci Fatal("sigprocmask: %s", strerror(errno)); 239695b41eeSopenharmony_ci} 240695b41eeSopenharmony_ci 241695b41eeSopenharmony_ciSubprocess *SubprocessSet::Add(const string& command, bool use_console) { 242695b41eeSopenharmony_ci Subprocess *subprocess = new Subprocess(use_console); 243695b41eeSopenharmony_ci if (!subprocess->Start(this, command)) { 244695b41eeSopenharmony_ci delete subprocess; 245695b41eeSopenharmony_ci return 0; 246695b41eeSopenharmony_ci } 247695b41eeSopenharmony_ci running_.push_back(subprocess); 248695b41eeSopenharmony_ci return subprocess; 249695b41eeSopenharmony_ci} 250695b41eeSopenharmony_ci 251695b41eeSopenharmony_ci#ifdef USE_PPOLL 252695b41eeSopenharmony_cibool SubprocessSet::DoWork() { 253695b41eeSopenharmony_ci vector<pollfd> fds; 254695b41eeSopenharmony_ci nfds_t nfds = 0; 255695b41eeSopenharmony_ci 256695b41eeSopenharmony_ci for (vector<Subprocess*>::iterator i = running_.begin(); 257695b41eeSopenharmony_ci i != running_.end(); ++i) { 258695b41eeSopenharmony_ci int fd = (*i)->fd_; 259695b41eeSopenharmony_ci if (fd < 0) 260695b41eeSopenharmony_ci continue; 261695b41eeSopenharmony_ci pollfd pfd = { fd, POLLIN | POLLPRI, 0 }; 262695b41eeSopenharmony_ci fds.push_back(pfd); 263695b41eeSopenharmony_ci ++nfds; 264695b41eeSopenharmony_ci } 265695b41eeSopenharmony_ci 266695b41eeSopenharmony_ci interrupted_ = 0; 267695b41eeSopenharmony_ci int ret = ppoll(&fds.front(), nfds, NULL, &old_mask_); 268695b41eeSopenharmony_ci if (ret == -1) { 269695b41eeSopenharmony_ci if (errno != EINTR) { 270695b41eeSopenharmony_ci perror("ninja: ppoll"); 271695b41eeSopenharmony_ci return false; 272695b41eeSopenharmony_ci } 273695b41eeSopenharmony_ci return IsInterrupted(); 274695b41eeSopenharmony_ci } 275695b41eeSopenharmony_ci 276695b41eeSopenharmony_ci HandlePendingInterruption(); 277695b41eeSopenharmony_ci if (IsInterrupted()) 278695b41eeSopenharmony_ci return true; 279695b41eeSopenharmony_ci 280695b41eeSopenharmony_ci nfds_t cur_nfd = 0; 281695b41eeSopenharmony_ci for (vector<Subprocess*>::iterator i = running_.begin(); 282695b41eeSopenharmony_ci i != running_.end(); ) { 283695b41eeSopenharmony_ci int fd = (*i)->fd_; 284695b41eeSopenharmony_ci if (fd < 0) 285695b41eeSopenharmony_ci continue; 286695b41eeSopenharmony_ci assert(fd == fds[cur_nfd].fd); 287695b41eeSopenharmony_ci if (fds[cur_nfd++].revents) { 288695b41eeSopenharmony_ci (*i)->OnPipeReady(); 289695b41eeSopenharmony_ci if ((*i)->Done()) { 290695b41eeSopenharmony_ci finished_.push(*i); 291695b41eeSopenharmony_ci i = running_.erase(i); 292695b41eeSopenharmony_ci continue; 293695b41eeSopenharmony_ci } 294695b41eeSopenharmony_ci } 295695b41eeSopenharmony_ci ++i; 296695b41eeSopenharmony_ci } 297695b41eeSopenharmony_ci 298695b41eeSopenharmony_ci return IsInterrupted(); 299695b41eeSopenharmony_ci} 300695b41eeSopenharmony_ci 301695b41eeSopenharmony_ci#else // !defined(USE_PPOLL) 302695b41eeSopenharmony_cibool SubprocessSet::DoWork() { 303695b41eeSopenharmony_ci fd_set set; 304695b41eeSopenharmony_ci int nfds = 0; 305695b41eeSopenharmony_ci FD_ZERO(&set); 306695b41eeSopenharmony_ci 307695b41eeSopenharmony_ci for (vector<Subprocess*>::iterator i = running_.begin(); 308695b41eeSopenharmony_ci i != running_.end(); ++i) { 309695b41eeSopenharmony_ci int fd = (*i)->fd_; 310695b41eeSopenharmony_ci if (fd >= 0) { 311695b41eeSopenharmony_ci FD_SET(fd, &set); 312695b41eeSopenharmony_ci if (nfds < fd+1) 313695b41eeSopenharmony_ci nfds = fd+1; 314695b41eeSopenharmony_ci } 315695b41eeSopenharmony_ci } 316695b41eeSopenharmony_ci 317695b41eeSopenharmony_ci interrupted_ = 0; 318695b41eeSopenharmony_ci int ret = pselect(nfds, &set, 0, 0, 0, &old_mask_); 319695b41eeSopenharmony_ci if (ret == -1) { 320695b41eeSopenharmony_ci if (errno != EINTR) { 321695b41eeSopenharmony_ci perror("ninja: pselect"); 322695b41eeSopenharmony_ci return false; 323695b41eeSopenharmony_ci } 324695b41eeSopenharmony_ci return IsInterrupted(); 325695b41eeSopenharmony_ci } 326695b41eeSopenharmony_ci 327695b41eeSopenharmony_ci HandlePendingInterruption(); 328695b41eeSopenharmony_ci if (IsInterrupted()) 329695b41eeSopenharmony_ci return true; 330695b41eeSopenharmony_ci 331695b41eeSopenharmony_ci for (vector<Subprocess*>::iterator i = running_.begin(); 332695b41eeSopenharmony_ci i != running_.end(); ) { 333695b41eeSopenharmony_ci int fd = (*i)->fd_; 334695b41eeSopenharmony_ci if (fd >= 0 && FD_ISSET(fd, &set)) { 335695b41eeSopenharmony_ci (*i)->OnPipeReady(); 336695b41eeSopenharmony_ci if ((*i)->Done()) { 337695b41eeSopenharmony_ci finished_.push(*i); 338695b41eeSopenharmony_ci i = running_.erase(i); 339695b41eeSopenharmony_ci continue; 340695b41eeSopenharmony_ci } 341695b41eeSopenharmony_ci } 342695b41eeSopenharmony_ci ++i; 343695b41eeSopenharmony_ci } 344695b41eeSopenharmony_ci 345695b41eeSopenharmony_ci return IsInterrupted(); 346695b41eeSopenharmony_ci} 347695b41eeSopenharmony_ci#endif // !defined(USE_PPOLL) 348695b41eeSopenharmony_ci 349695b41eeSopenharmony_ciSubprocess* SubprocessSet::NextFinished() { 350695b41eeSopenharmony_ci if (finished_.empty()) 351695b41eeSopenharmony_ci return NULL; 352695b41eeSopenharmony_ci Subprocess* subproc = finished_.front(); 353695b41eeSopenharmony_ci finished_.pop(); 354695b41eeSopenharmony_ci return subproc; 355695b41eeSopenharmony_ci} 356695b41eeSopenharmony_ci 357695b41eeSopenharmony_civoid SubprocessSet::Clear() { 358695b41eeSopenharmony_ci for (vector<Subprocess*>::iterator i = running_.begin(); 359695b41eeSopenharmony_ci i != running_.end(); ++i) 360695b41eeSopenharmony_ci // Since the foreground process is in our process group, it will receive 361695b41eeSopenharmony_ci // the interruption signal (i.e. SIGINT or SIGTERM) at the same time as us. 362695b41eeSopenharmony_ci if (!(*i)->use_console_) 363695b41eeSopenharmony_ci kill(-(*i)->pid_, interrupted_); 364695b41eeSopenharmony_ci for (vector<Subprocess*>::iterator i = running_.begin(); 365695b41eeSopenharmony_ci i != running_.end(); ++i) 366695b41eeSopenharmony_ci delete *i; 367695b41eeSopenharmony_ci running_.clear(); 368695b41eeSopenharmony_ci} 369