shim_ipc.c 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600
  1. /* Copyright (C) 2014 Stony Brook University
  2. This file is part of Graphene Library OS.
  3. Graphene Library OS is free software: you can redistribute it and/or
  4. modify it under the terms of the GNU Lesser General Public License
  5. as published by the Free Software Foundation, either version 3 of the
  6. License, or (at your option) any later version.
  7. Graphene Library OS is distributed in the hope that it will be useful,
  8. but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  10. GNU Lesser General Public License for more details.
  11. You should have received a copy of the GNU Lesser General Public License
  12. along with this program. If not, see <http://www.gnu.org/licenses/>. */
  13. /*
  14. * shim_ipc.c
  15. *
  16. * This file contains code to maintain generic bookkeeping of IPC: operations
  17. * on shim_ipc_msg (one-way IPC messages), shim_ipc_msg_duplex (IPC messages
  18. * with acknowledgement), shim_ipc_info (IPC ports of process), shim_process.
  19. */
  20. #include <list.h>
  21. #include <pal.h>
  22. #include <pal_error.h>
  23. #include <shim_checkpoint.h>
  24. #include <shim_handle.h>
  25. #include <shim_internal.h>
  26. #include <shim_ipc.h>
  27. #include <shim_profile.h>
  28. #include <shim_thread.h>
  29. #include <shim_unistd.h>
  30. #include <shim_utils.h>
  31. #define IPC_INFO_MGR_ALLOC 32
  32. #define OBJ_TYPE struct shim_ipc_info
  33. #include "memmgr.h"
  34. static MEM_MGR ipc_info_mgr;
  35. struct shim_lock ipc_info_lock;
  36. struct shim_process cur_process;
  37. #define CLIENT_HASH_BITLEN 6
  38. #define CLIENT_HASH_NUM (1 << CLIENT_HASH_BITLEN)
  39. #define CLIENT_HASH_MASK (CLIENT_HASH_NUM - 1)
  40. #define CLIENT_HASH(vmid) ((vmid)&CLIENT_HASH_MASK)
  41. DEFINE_LISTP(shim_ipc_info);
  42. static LISTP_TYPE(shim_ipc_info) info_hlist[CLIENT_HASH_NUM];
  43. DEFINE_PROFILE_CATEGORY(ipc, );
  44. DEFINE_PROFILE_OCCURENCE(syscall_use_ipc, ipc);
  45. int init_ipc_ports(void);
  46. int init_ns_pid(void);
  47. int init_ns_sysv(void);
  48. int init_ipc(void) {
  49. int ret = 0;
  50. create_lock(&ipc_info_lock);
  51. if (!(ipc_info_mgr = create_mem_mgr(init_align_up(IPC_INFO_MGR_ALLOC))))
  52. return -ENOMEM;
  53. if ((ret = init_ipc_ports()) < 0)
  54. return ret;
  55. if ((ret = init_ns_pid()) < 0)
  56. return ret;
  57. if ((ret = init_ns_sysv()) < 0)
  58. return ret;
  59. return 0;
  60. }
  61. int prepare_ns_leaders(void) {
  62. int ret = 0;
  63. if ((ret = prepare_pid_leader()) < 0)
  64. return ret;
  65. if ((ret = prepare_sysv_leader()) < 0)
  66. return ret;
  67. return 0;
  68. }
  69. static struct shim_ipc_info* __create_ipc_info(IDTYPE vmid, const char* uri, size_t len) {
  70. struct shim_ipc_info* info =
  71. get_mem_obj_from_mgr_enlarge(ipc_info_mgr, size_align_up(IPC_INFO_MGR_ALLOC));
  72. if (!info)
  73. return NULL;
  74. memset(info, 0, sizeof(struct shim_ipc_info));
  75. info->vmid = vmid;
  76. if (uri)
  77. qstrsetstr(&info->uri, uri, len);
  78. REF_SET(info->ref_count, 1);
  79. INIT_LIST_HEAD(info, hlist);
  80. return info;
  81. }
  82. static void __free_ipc_info(struct shim_ipc_info* info) {
  83. if (info->pal_handle) {
  84. DkObjectClose(info->pal_handle);
  85. info->pal_handle = NULL;
  86. }
  87. if (info->port)
  88. put_ipc_port(info->port);
  89. qstrfree(&info->uri);
  90. free_mem_obj_to_mgr(ipc_info_mgr, info);
  91. }
  92. static void __get_ipc_info(struct shim_ipc_info* info) {
  93. REF_INC(info->ref_count);
  94. }
  95. static void __put_ipc_info(struct shim_ipc_info* info) {
  96. int ref_count = REF_DEC(info->ref_count);
  97. if (!ref_count)
  98. __free_ipc_info(info);
  99. }
  100. void get_ipc_info(struct shim_ipc_info* info) {
  101. /* no need to grab ipc_info_lock because __get_ipc_info() is atomic */
  102. __get_ipc_info(info);
  103. }
  104. void put_ipc_info(struct shim_ipc_info* info) {
  105. /* this is atomic so we don't grab lock in common case of ref_count > 0 */
  106. int ref_count = REF_DEC(info->ref_count);
  107. if (!ref_count) {
  108. lock(&ipc_info_lock);
  109. __free_ipc_info(info);
  110. unlock(&ipc_info_lock);
  111. }
  112. }
  113. struct shim_ipc_info* create_ipc_info(IDTYPE vmid, const char* uri, size_t len) {
  114. lock(&ipc_info_lock);
  115. struct shim_ipc_info* info = __create_ipc_info(vmid, uri, len);
  116. unlock(&ipc_info_lock);
  117. return info;
  118. }
  119. struct shim_ipc_info* create_ipc_info_in_list(IDTYPE vmid, const char* uri, size_t len) {
  120. assert(vmid);
  121. struct shim_ipc_info* info;
  122. lock(&ipc_info_lock);
  123. /* check if info with this vmid & uri already exists and return it */
  124. LISTP_TYPE(shim_ipc_info)* info_bucket = &info_hlist[CLIENT_HASH(vmid)];
  125. LISTP_FOR_EACH_ENTRY(info, info_bucket, hlist) {
  126. if (info->vmid == vmid && !qstrcmpstr(&info->uri, uri, len)) {
  127. get_ipc_info(info);
  128. unlock(&ipc_info_lock);
  129. return info;
  130. }
  131. }
  132. /* otherwise create new info and return it */
  133. info = __create_ipc_info(vmid, uri, len);
  134. if (info) {
  135. LISTP_ADD(info, info_bucket, hlist);
  136. get_ipc_info(info);
  137. }
  138. unlock(&ipc_info_lock);
  139. return info;
  140. }
  141. void put_ipc_info_in_list(struct shim_ipc_info* info) {
  142. LISTP_TYPE(shim_ipc_info)* info_bucket = &info_hlist[CLIENT_HASH(info->vmid)];
  143. lock(&ipc_info_lock);
  144. __put_ipc_info(info);
  145. if (REF_GET(info->ref_count) == 1) {
  146. LISTP_DEL_INIT(info, info_bucket, hlist);
  147. __put_ipc_info(info);
  148. }
  149. unlock(&ipc_info_lock);
  150. }
  151. struct shim_ipc_info* lookup_ipc_info(IDTYPE vmid) {
  152. assert(vmid);
  153. lock(&ipc_info_lock);
  154. struct shim_ipc_info* info;
  155. LISTP_TYPE(shim_ipc_info)* info_bucket = &info_hlist[CLIENT_HASH(vmid)];
  156. LISTP_FOR_EACH_ENTRY(info, info_bucket, hlist) {
  157. if (info->vmid == vmid && !qstrempty(&info->uri)) {
  158. __get_ipc_info(info);
  159. unlock(&ipc_info_lock);
  160. return info;
  161. }
  162. }
  163. unlock(&ipc_info_lock);
  164. return NULL;
  165. }
  166. struct shim_process* create_process(bool dup_cur_process) {
  167. struct shim_process* new_process = calloc(1, sizeof(struct shim_process));
  168. if (!new_process)
  169. return NULL;
  170. lock(&cur_process.lock);
  171. /* current process must have been initialized with info on its own IPC info */
  172. assert(cur_process.self);
  173. assert(cur_process.self->pal_handle && !qstrempty(&cur_process.self->uri));
  174. if (dup_cur_process) {
  175. /* execve case, new process assumes identity of current process and thus has
  176. * - same vmid as current process
  177. * - same self IPC info as current process
  178. * - same parent IPC info as current process
  179. */
  180. new_process->vmid = cur_process.vmid;
  181. new_process->self = create_ipc_info(
  182. cur_process.self->vmid, qstrgetstr(&cur_process.self->uri), cur_process.self->uri.len);
  183. new_process->self->pal_handle = cur_process.self->pal_handle;
  184. if (!new_process->self) {
  185. unlock(&cur_process.lock);
  186. return NULL;
  187. }
  188. /* there is a corner case of execve in very first process; such process does
  189. * not have parent process, so cannot copy parent IPC info */
  190. if (cur_process.parent) {
  191. new_process->parent =
  192. create_ipc_info(cur_process.parent->vmid, qstrgetstr(&cur_process.parent->uri),
  193. cur_process.parent->uri.len);
  194. new_process->parent->pal_handle = cur_process.parent->pal_handle;
  195. }
  196. } else {
  197. /* fork/clone case, new process has new identity but inherits parent */
  198. new_process->vmid = 0;
  199. new_process->self = NULL;
  200. new_process->parent = create_ipc_info(
  201. cur_process.self->vmid, qstrgetstr(&cur_process.self->uri), cur_process.self->uri.len);
  202. }
  203. if (cur_process.parent && !new_process->parent) {
  204. if (new_process->self)
  205. put_ipc_info(new_process->self);
  206. unlock(&cur_process.lock);
  207. return NULL;
  208. }
  209. /* new process inherits the same namespace leaders */
  210. for (int i = 0; i < TOTAL_NS; i++) {
  211. if (cur_process.ns[i]) {
  212. new_process->ns[i] =
  213. create_ipc_info(cur_process.ns[i]->vmid, qstrgetstr(&cur_process.ns[i]->uri),
  214. cur_process.ns[i]->uri.len);
  215. if (!new_process->ns[i]) {
  216. if (new_process->self)
  217. put_ipc_info(new_process->self);
  218. if (new_process->parent)
  219. put_ipc_info(new_process->parent);
  220. for (int j = 0; j < i; j++) {
  221. put_ipc_info(new_process->ns[j]);
  222. }
  223. unlock(&cur_process.lock);
  224. return NULL;
  225. }
  226. }
  227. }
  228. unlock(&cur_process.lock);
  229. return new_process;
  230. }
  231. void free_process(struct shim_process* process) {
  232. if (process->self)
  233. put_ipc_info(process->self);
  234. if (process->parent)
  235. put_ipc_info(process->parent);
  236. for (int i = 0; i < TOTAL_NS; i++)
  237. if (process->ns[i])
  238. put_ipc_info(process->ns[i]);
  239. free(process);
  240. }
  241. void init_ipc_msg(struct shim_ipc_msg* msg, int code, size_t size, IDTYPE dest) {
  242. msg->code = code;
  243. msg->size = get_ipc_msg_size(size);
  244. msg->src = cur_process.vmid;
  245. msg->dst = dest;
  246. msg->seq = 0;
  247. }
  248. void init_ipc_msg_duplex(struct shim_ipc_msg_duplex* msg, int code, size_t size, IDTYPE dest) {
  249. init_ipc_msg(&msg->msg, code, size, dest);
  250. msg->thread = NULL;
  251. INIT_LIST_HEAD(msg, list);
  252. msg->retval = 0;
  253. msg->private = NULL;
  254. }
  255. int send_ipc_message(struct shim_ipc_msg* msg, struct shim_ipc_port* port) {
  256. assert(msg->size >= IPC_MSG_MINIMAL_SIZE);
  257. msg->src = cur_process.vmid;
  258. debug("Sending ipc message to port %p (handle %p)\n", port, port->pal_handle);
  259. size_t total_bytes = msg->size;
  260. size_t bytes = 0;
  261. do {
  262. size_t ret =
  263. DkStreamWrite(port->pal_handle, 0, total_bytes - bytes, (void*)msg + bytes, NULL);
  264. if (!ret) {
  265. if (PAL_ERRNO == EINTR || PAL_ERRNO == EAGAIN || PAL_ERRNO == EWOULDBLOCK)
  266. continue;
  267. debug("Port %p (handle %p) was removed during sending\n", port, port->pal_handle);
  268. del_ipc_port_fini(port, -ECHILD);
  269. return -PAL_ERRNO;
  270. }
  271. bytes += ret;
  272. } while (bytes < total_bytes);
  273. return 0;
  274. }
  275. struct shim_ipc_msg_duplex* pop_ipc_msg_duplex(struct shim_ipc_port* port, unsigned long seq) {
  276. struct shim_ipc_msg_duplex* found = NULL;
  277. lock(&port->msgs_lock);
  278. struct shim_ipc_msg_duplex* tmp;
  279. LISTP_FOR_EACH_ENTRY(tmp, &port->msgs, list) {
  280. if (tmp->msg.seq == seq) {
  281. found = tmp;
  282. LISTP_DEL_INIT(tmp, &port->msgs, list);
  283. break;
  284. }
  285. }
  286. unlock(&port->msgs_lock);
  287. return found;
  288. }
  289. int send_ipc_message_duplex(struct shim_ipc_msg_duplex* msg, struct shim_ipc_port* port,
  290. unsigned long* seq, void* private_data) {
  291. int ret = 0;
  292. struct shim_thread* thread = get_cur_thread();
  293. assert(thread);
  294. /* prepare thread which will send the message for waiting for response
  295. * (this also acquires reference to the thread) */
  296. if (!msg->thread)
  297. thread_setwait(&msg->thread, thread);
  298. static struct atomic_int ipc_seq_counter;
  299. msg->msg.seq = atomic_inc_return(&ipc_seq_counter);
  300. /* save the message to list of port msgs together with its private data */
  301. lock(&port->msgs_lock);
  302. msg->private = private_data;
  303. LISTP_ADD_TAIL(msg, &port->msgs, list);
  304. unlock(&port->msgs_lock);
  305. ret = send_ipc_message(&msg->msg, port);
  306. if (ret < 0)
  307. goto out;
  308. if (seq)
  309. *seq = msg->msg.seq;
  310. debug("Waiting for response (seq = %lu)\n", msg->msg.seq);
  311. /* force thread which will send the message to wait for response;
  312. * ignore unrelated interrupts but fail on actual errors */
  313. do {
  314. ret = thread_sleep(NO_TIMEOUT);
  315. if (ret < 0 && ret != -EINTR && ret != -EAGAIN)
  316. goto out;
  317. } while (ret != 0);
  318. debug("Finished waiting for response (seq = %lu, ret = %d)\n", msg->msg.seq, msg->retval);
  319. ret = msg->retval;
  320. out:
  321. lock(&port->msgs_lock);
  322. if (!LIST_EMPTY(msg, list))
  323. LISTP_DEL_INIT(msg, &port->msgs, list);
  324. unlock(&port->msgs_lock);
  325. if (msg->thread) {
  326. /* put reference to the thread acquired earlier */
  327. put_thread(msg->thread);
  328. msg->thread = NULL;
  329. }
  330. return ret;
  331. }
  332. /* must be called with cur_process.lock taken */
  333. struct shim_ipc_info* create_ipc_info_cur_process(bool is_self_ipc_info) {
  334. struct shim_ipc_info* info = create_ipc_info(cur_process.vmid, NULL, 0);
  335. if (!info)
  336. return NULL;
  337. /* pipe for cur_process.self is of format "pipe:<cur_process.vmid>", others with random name */
  338. char uri[PIPE_URI_SIZE];
  339. if (create_pipe(NULL, uri, PIPE_URI_SIZE, &info->pal_handle, &info->uri, is_self_ipc_info) <
  340. 0) {
  341. put_ipc_info(info);
  342. return NULL;
  343. }
  344. add_ipc_port_by_id(cur_process.vmid, info->pal_handle, IPC_PORT_SERVER, NULL, &info->port);
  345. return info;
  346. }
  347. int get_ipc_info_cur_process(struct shim_ipc_info** info) {
  348. lock(&cur_process.lock);
  349. if (!cur_process.self) {
  350. cur_process.self = create_ipc_info_cur_process(true);
  351. if (!cur_process.self) {
  352. unlock(&cur_process.lock);
  353. return -EACCES;
  354. }
  355. }
  356. get_ipc_info(cur_process.self);
  357. *info = cur_process.self;
  358. unlock(&cur_process.lock);
  359. return 0;
  360. }
  361. DEFINE_PROFILE_INTERVAL(ipc_checkpoint_send, ipc);
  362. DEFINE_PROFILE_INTERVAL(ipc_checkpoint_callback, ipc);
  363. /* Graphene's checkpoint() syscall broadcasts a msg to all processes
  364. * asking to checkpoint their state and save in process-unique file in
  365. * directory cpdir under session cpsession. */
  366. int ipc_checkpoint_send(const char* cpdir, IDTYPE cpsession) {
  367. BEGIN_PROFILE_INTERVAL();
  368. int ret;
  369. size_t len = strlen(cpdir);
  370. size_t total_msg_size = get_ipc_msg_size(sizeof(struct shim_ipc_checkpoint) + len);
  371. struct shim_ipc_msg* msg = __alloca(total_msg_size);
  372. init_ipc_msg(msg, IPC_CHECKPOINT, total_msg_size, 0);
  373. struct shim_ipc_checkpoint* msgin = (struct shim_ipc_checkpoint*)&msg->msg;
  374. msgin->cpsession = cpsession;
  375. memcpy(&msgin->cpdir, cpdir, len + 1);
  376. debug("IPC broadcast to all: IPC_CHECKPOINT(%u, %s)\n", cpsession, cpdir);
  377. /* broadcast to all including myself (so I can also checkpoint) */
  378. ret = broadcast_ipc(msg, IPC_PORT_DIRCLD | IPC_PORT_DIRPRT,
  379. /*exclude_port=*/NULL);
  380. SAVE_PROFILE_INTERVAL(ipc_checkpoint_send);
  381. return ret;
  382. }
  383. /* This process is asked to create a checkpoint, so it:
  384. * - sends a Graphene-specific SIGCP signal to all its threads (for
  385. * all to stop and join the checkpoint for consistent state),
  386. * - broadcasts checkpoint msg further to other processes. */
  387. int ipc_checkpoint_callback(struct shim_ipc_msg* msg, struct shim_ipc_port* port) {
  388. BEGIN_PROFILE_INTERVAL();
  389. int ret = 0;
  390. struct shim_ipc_checkpoint* msgin = (struct shim_ipc_checkpoint*)msg->msg;
  391. debug("IPC callback from %u: IPC_CHECKPOINT(%u, %s)\n", msg->src, msgin->cpsession,
  392. msgin->cpdir);
  393. ret = create_checkpoint(msgin->cpdir, &msgin->cpsession);
  394. if (ret < 0)
  395. goto out;
  396. kill_all_threads(NULL, msgin->cpsession, SIGCP);
  397. broadcast_ipc(msg, IPC_PORT_DIRCLD | IPC_PORT_DIRPRT, port);
  398. out:
  399. SAVE_PROFILE_INTERVAL(ipc_checkpoint_callback);
  400. return ret;
  401. }
  402. BEGIN_CP_FUNC(ipc_info) {
  403. __UNUSED(size);
  404. assert(size == sizeof(struct shim_ipc_info));
  405. struct shim_ipc_info* info = (struct shim_ipc_info*)obj;
  406. struct shim_ipc_info* new_info = NULL;
  407. ptr_t off = GET_FROM_CP_MAP(obj);
  408. if (!off) {
  409. off = ADD_CP_OFFSET(sizeof(struct shim_ipc_info));
  410. ADD_TO_CP_MAP(obj, off);
  411. new_info = (struct shim_ipc_info*)(base + off);
  412. memcpy(new_info, info, sizeof(struct shim_ipc_info));
  413. REF_SET(new_info->ref_count, 0);
  414. /* call qstr-specific checkpointing function for new_info->uri */
  415. DO_CP_IN_MEMBER(qstr, new_info, uri);
  416. if (info->pal_handle) {
  417. struct shim_palhdl_entry* entry;
  418. /* call palhdl-specific checkpointing function to checkpoint
  419. * info->pal_handle and return created object in entry */
  420. DO_CP(palhdl, info->pal_handle, &entry);
  421. /* info's PAL handle will be re-opened with new URI during
  422. * palhdl restore (see checkpoint.c) */
  423. entry->uri = &new_info->uri;
  424. entry->phandle = &new_info->pal_handle;
  425. }
  426. } else {
  427. /* already checkpointed */
  428. new_info = (struct shim_ipc_info*)(base + off);
  429. }
  430. if (new_info && objp)
  431. *objp = (void*)new_info;
  432. }
  433. END_CP_FUNC_NO_RS(ipc_info)
  434. BEGIN_CP_FUNC(process) {
  435. __UNUSED(size);
  436. assert(size == sizeof(struct shim_process));
  437. struct shim_process* process = (struct shim_process*)obj;
  438. struct shim_process* new_process = NULL;
  439. ptr_t off = GET_FROM_CP_MAP(obj);
  440. if (!off) {
  441. off = ADD_CP_OFFSET(sizeof(struct shim_process));
  442. ADD_TO_CP_MAP(obj, off);
  443. new_process = (struct shim_process*)(base + off);
  444. memcpy(new_process, process, sizeof(struct shim_process));
  445. /* call ipc_info-specific checkpointing functions
  446. * for new_process's self, parent, and ns infos */
  447. if (process->self)
  448. DO_CP_MEMBER(ipc_info, process, new_process, self);
  449. if (process->parent)
  450. DO_CP_MEMBER(ipc_info, process, new_process, parent);
  451. for (int i = 0; i < TOTAL_NS; i++)
  452. if (process->ns[i])
  453. DO_CP_MEMBER(ipc_info, process, new_process, ns[i]);
  454. ADD_CP_FUNC_ENTRY(off);
  455. } else {
  456. /* already checkpointed */
  457. new_process = (struct shim_process*)(base + off);
  458. }
  459. if (objp)
  460. *objp = (void*)new_process;
  461. }
  462. END_CP_FUNC(process)
  463. BEGIN_RS_FUNC(process) {
  464. __UNUSED(offset);
  465. struct shim_process* process = (void*)(base + GET_CP_FUNC_ENTRY());
  466. /* process vmid = 0: fork/clone case, forces to pick up new host-OS vmid
  467. * process vmid != 0: execve case, forces to re-use vmid of parent */
  468. if (!process->vmid)
  469. process->vmid = cur_process.vmid;
  470. CP_REBASE(process->self);
  471. CP_REBASE(process->parent);
  472. CP_REBASE(process->ns);
  473. if (process->self) {
  474. process->self->vmid = process->vmid;
  475. get_ipc_info(process->self);
  476. }
  477. if (process->parent)
  478. get_ipc_info(process->parent);
  479. for (int i = 0; i < TOTAL_NS; i++)
  480. if (process->ns[i])
  481. get_ipc_info(process->ns[i]);
  482. memcpy(&cur_process, process, sizeof(struct shim_process));
  483. create_lock(&cur_process.lock);
  484. DEBUG_RS("vmid=%u,uri=%s,parent=%u(%s)", process->vmid,
  485. process->self ? qstrgetstr(&process->self->uri) : "",
  486. process->parent ? process->parent->vmid : 0,
  487. process->parent ? qstrgetstr(&process->parent->uri) : "");
  488. }
  489. END_RS_FUNC(process)