thread.c 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770
  1. #include <shim_internal.h>
  2. #include <shim_table.h>
  3. #include <shim_thread.h>
  4. #include <shim_handle.h>
  5. #include <shim_fs.h>
  6. #include <shim_utils.h>
  7. #include <pal.h>
  8. #include <pal_error.h>
  9. #include <errno.h>
  10. #include <linux/stat.h>
  11. #include <linux/fcntl.h>
  12. #include <asm/fcntl.h>
  13. #include <asm/mman.h>
  14. #include <asm/unistd.h>
  15. #include <asm/prctl.h>
  16. static int parse_thread_name (const char * name, IDTYPE * pidptr,
  17. const char ** next, size_t * next_len,
  18. const char ** nextnext)
  19. {
  20. const char * p = name;
  21. IDTYPE pid = 0;
  22. if (*p == '/')
  23. p++;
  24. if (strpartcmp_static(p, "self")) {
  25. p += static_strlen("self");
  26. if (*p && *p != '/')
  27. return -ENOENT;
  28. pid = get_cur_tid();
  29. } else {
  30. for ( ; *p && *p != '/' ; p++) {
  31. if (*p < '0' || *p > '9')
  32. return -ENOENT;
  33. pid = pid * 10 + *p - '0';
  34. }
  35. }
  36. if (next) {
  37. if (*(p++) == '/' && *p) {
  38. *next = p;
  39. if (next_len || nextnext)
  40. for ( ; *p && *p != '/' ; p++);
  41. if (next_len)
  42. *next_len = p - *next;
  43. if (nextnext)
  44. *nextnext = (*(p++) == '/' && *p) ? p : NULL;
  45. } else {
  46. *next = NULL;
  47. }
  48. }
  49. if (pidptr) *pidptr = pid;
  50. return 0;
  51. }
  52. static int find_thread_link (const char * name, struct shim_qstr * link,
  53. struct shim_dentry ** dentptr,
  54. struct shim_thread ** threadptr)
  55. {
  56. const char * next, * nextnext;
  57. size_t next_len;
  58. IDTYPE pid;
  59. int ret = parse_thread_name(name, &pid, &next, &next_len, &nextnext);
  60. if (ret < 0)
  61. return ret;
  62. struct shim_thread * thread = lookup_thread(pid);
  63. struct shim_dentry * dent = NULL;
  64. if (!thread)
  65. return -ENOENT;
  66. if (!thread->in_vm) {
  67. ret = -ENOENT;
  68. goto out;
  69. }
  70. lock(&thread->lock);
  71. if (next_len == static_strlen("root") && !memcmp(next, "root", next_len)) {
  72. dent = thread->root;
  73. get_dentry(dent);
  74. }
  75. if (next_len == static_strlen("cwd") && !memcmp(next, "cwd", next_len)) {
  76. dent = thread->cwd;
  77. get_dentry(dent);
  78. }
  79. if (next_len == static_strlen("exe") && !memcmp(next, "exe", next_len)) {
  80. struct shim_handle * exec = thread->exec;
  81. if (!exec->dentry) {
  82. unlock(&thread->lock);
  83. ret = -EINVAL;
  84. goto out;
  85. }
  86. dent = exec->dentry;
  87. get_dentry(dent);
  88. }
  89. unlock(&thread->lock);
  90. if (nextnext) {
  91. struct shim_dentry * next_dent = NULL;
  92. ret = path_lookupat(dent, nextnext, 0, &next_dent, dent->fs);
  93. if (ret < 0)
  94. goto out;
  95. put_dentry(dent);
  96. dent = next_dent;
  97. }
  98. if (link) {
  99. size_t size;
  100. char * path = dentry_get_path(dent, true, &size);
  101. qstrsetstr(link, path, size);
  102. }
  103. if (dentptr) {
  104. get_dentry(dent);
  105. *dentptr = dent;
  106. }
  107. if (threadptr) {
  108. get_thread(thread);
  109. *threadptr = thread;
  110. }
  111. ret = 0;
  112. out:
  113. if (dent)
  114. put_dentry(dent);
  115. if (thread)
  116. put_thread(thread);
  117. return ret;
  118. }
  119. static int proc_thread_link_open (struct shim_handle * hdl,
  120. const char * name, int flags)
  121. {
  122. struct shim_dentry * dent;
  123. int ret = find_thread_link(name, NULL, &dent, NULL);
  124. if (ret < 0)
  125. return ret;
  126. if (!dent->fs || !dent->fs->d_ops || !dent->fs->d_ops->open) {
  127. ret = -EACCES;
  128. goto out;
  129. }
  130. ret = dent->fs->d_ops->open(hdl, dent, flags);
  131. out:
  132. put_dentry(dent);
  133. return 0;
  134. }
  135. static int proc_thread_link_mode (const char * name, mode_t * mode)
  136. {
  137. struct shim_dentry * dent;
  138. int ret = find_thread_link(name, NULL, &dent, NULL);
  139. if (ret < 0)
  140. return ret;
  141. if (!dent->fs || !dent->fs->d_ops || !dent->fs->d_ops->mode) {
  142. ret = -EACCES;
  143. goto out;
  144. }
  145. ret = dent->fs->d_ops->mode(dent, mode);
  146. out:
  147. put_dentry(dent);
  148. return ret;
  149. }
  150. static int proc_thread_link_stat (const char * name, struct stat * buf)
  151. {
  152. struct shim_dentry * dent;
  153. int ret = find_thread_link(name, NULL, &dent, NULL);
  154. if (ret < 0)
  155. return ret;
  156. if (!dent->fs || !dent->fs->d_ops || !dent->fs->d_ops->stat) {
  157. ret = -EACCES;
  158. goto out;
  159. }
  160. ret = dent->fs->d_ops->stat(dent, buf);
  161. out:
  162. put_dentry(dent);
  163. return ret;
  164. }
  165. static int proc_thread_link_follow_link (const char * name,
  166. struct shim_qstr * link)
  167. {
  168. return find_thread_link(name, link, NULL, NULL);
  169. }
  170. static const struct proc_fs_ops fs_thread_link = {
  171. .open = &proc_thread_link_open,
  172. .mode = &proc_thread_link_mode,
  173. .stat = &proc_thread_link_stat,
  174. .follow_link = &proc_thread_link_follow_link,
  175. };
  176. /* If *phdl is returned on success, the ref count is incremented */
  177. static int parse_thread_fd (const char * name, const char ** rest,
  178. struct shim_handle ** phdl)
  179. {
  180. const char * next, * nextnext;
  181. size_t next_len;
  182. IDTYPE pid;
  183. int ret = parse_thread_name(name, &pid, &next, &next_len, &nextnext);
  184. if (ret < 0)
  185. return ret;
  186. if (!next || !nextnext || memcmp(next, "fd", next_len))
  187. return -EINVAL;
  188. const char * p = nextnext;
  189. FDTYPE fd = 0;
  190. for ( ; *p && *p != '/' ; p++) {
  191. if (*p < '0' || *p > '9')
  192. return -ENOENT;
  193. fd = fd * 10 + *p - '0';
  194. if (fd >= __rlim[RLIMIT_NOFILE].rlim_cur)
  195. return -ENOENT;
  196. }
  197. struct shim_thread * thread = lookup_thread(pid);
  198. if (!thread)
  199. return -ENOENT;
  200. struct shim_handle_map * handle_map = get_cur_handle_map(thread);
  201. lock(&handle_map->lock);
  202. if (fd >= handle_map->fd_top ||
  203. handle_map->map[fd] == NULL ||
  204. handle_map->map[fd]->handle == NULL) {
  205. unlock(&handle_map->lock);
  206. return -ENOENT;
  207. }
  208. if (phdl) {
  209. *phdl = handle_map->map[fd]->handle;
  210. get_handle(*phdl);
  211. }
  212. unlock(&handle_map->lock);
  213. if (rest)
  214. *rest = *p ? p + 1 : NULL;
  215. return 0;
  216. }
  217. static int proc_match_thread_each_fd (const char * name)
  218. {
  219. return parse_thread_fd(name, NULL, NULL) == 0 ? 1 : 0;
  220. }
  221. static int proc_list_thread_each_fd (const char * name,
  222. struct shim_dirent ** buf, int count)
  223. {
  224. const char * next;
  225. size_t next_len;
  226. IDTYPE pid;
  227. int ret = parse_thread_name(name, &pid, &next, &next_len, NULL);
  228. if (ret < 0)
  229. return ret;
  230. if (!next || memcmp(next, "fd", next_len))
  231. return -EINVAL;
  232. struct shim_thread * thread = lookup_thread(pid);
  233. if (!thread)
  234. return -ENOENT;
  235. struct shim_handle_map * handle_map = get_cur_handle_map(thread);
  236. int err = 0, bytes = 0;
  237. struct shim_dirent * dirent = *buf, ** last = NULL;
  238. lock(&handle_map->lock);
  239. for (int i = 0 ; i < handle_map->fd_size ; i++)
  240. if (handle_map->map[i] &&
  241. handle_map->map[i]->handle) {
  242. int d = i, l = 0;
  243. for ( ; d ; d /= 10, l++);
  244. l = l ? : 1;
  245. bytes += sizeof(struct shim_dirent) + l + 1;
  246. if (bytes > count) {
  247. err = -ENOMEM;
  248. break;
  249. }
  250. dirent->next = (void *) (dirent + 1) + l + 1;
  251. dirent->ino = 1;
  252. dirent->type = LINUX_DT_LNK;
  253. dirent->name[0] = '0';
  254. dirent->name[l--] = 0;
  255. for (d = i ; d ; d /= 10)
  256. dirent->name[l--] = '0' + d % 10;
  257. last = &dirent->next;
  258. dirent = dirent->next;
  259. }
  260. unlock(&handle_map->lock);
  261. put_thread(thread);
  262. if (last)
  263. *last = NULL;
  264. *buf = dirent;
  265. return err;
  266. }
  267. static const struct proc_nm_ops nm_thread_each_fd = {
  268. .match_name = &proc_match_thread_each_fd,
  269. .list_name = &proc_list_thread_each_fd,
  270. };
  271. static int find_thread_each_fd (const char * name, struct shim_qstr * link,
  272. struct shim_dentry ** dentptr)
  273. {
  274. const char * rest;
  275. struct shim_handle * handle;
  276. struct shim_dentry * dent = NULL;
  277. int ret;
  278. if ((ret = parse_thread_fd(name, &rest, &handle)) < 0)
  279. return ret;
  280. lock(&handle->lock);
  281. if (handle->dentry) {
  282. dent = handle->dentry;
  283. get_dentry(dent);
  284. }
  285. unlock(&handle->lock);
  286. if (!dent) {
  287. ret = -ENOENT;
  288. goto out;
  289. }
  290. if (rest) {
  291. struct shim_dentry * next_dent = NULL;
  292. ret = path_lookupat(dent, rest, 0, &next_dent, dent->fs);
  293. if (ret < 0)
  294. goto out;
  295. put_dentry(dent);
  296. dent = next_dent;
  297. }
  298. if (link) {
  299. size_t size;
  300. char * path = dentry_get_path(dent, true, &size);
  301. qstrsetstr(link, path, size);
  302. }
  303. if (dentptr) {
  304. get_dentry(dent);
  305. *dentptr = dent;
  306. }
  307. out:
  308. if (dent)
  309. put_dentry(dent);
  310. put_handle(handle);
  311. return ret;
  312. }
  313. static int proc_thread_each_fd_open (struct shim_handle * hdl,
  314. const char * name, int flags)
  315. {
  316. struct shim_dentry * dent;
  317. int ret = find_thread_each_fd(name, NULL, &dent);
  318. if (ret < 0)
  319. return ret;
  320. if (!dent->fs || !dent->fs->d_ops || !dent->fs->d_ops->open) {
  321. ret = -EACCES;
  322. goto out;
  323. }
  324. ret = dent->fs->d_ops->open(hdl, dent, flags);
  325. out:
  326. put_dentry(dent);
  327. return 0;
  328. }
  329. static int proc_thread_each_fd_mode (const char * name, mode_t * mode)
  330. {
  331. struct shim_dentry * dent;
  332. int ret = find_thread_each_fd(name, NULL, &dent);
  333. if (ret < 0)
  334. return ret;
  335. if (!dent->fs || !dent->fs->d_ops || !dent->fs->d_ops->mode) {
  336. ret = -EACCES;
  337. goto out;
  338. }
  339. ret = dent->fs->d_ops->mode(dent, mode);
  340. out:
  341. put_dentry(dent);
  342. return 0;
  343. }
  344. static int proc_thread_each_fd_stat (const char * name, struct stat * buf)
  345. {
  346. struct shim_dentry * dent;
  347. int ret = find_thread_each_fd(name, NULL, &dent);
  348. if (ret < 0)
  349. return ret;
  350. if (!dent->fs || !dent->fs->d_ops || !dent->fs->d_ops->stat) {
  351. ret = -EACCES;
  352. goto out;
  353. }
  354. ret = dent->fs->d_ops->stat(dent, buf);
  355. out:
  356. put_dentry(dent);
  357. return 0;
  358. }
  359. static int proc_thread_each_fd_follow_link (const char * name,
  360. struct shim_qstr * link)
  361. {
  362. return find_thread_each_fd(name, link, NULL);
  363. }
  364. static const struct proc_fs_ops fs_thread_each_fd = {
  365. .open = &proc_thread_each_fd_open,
  366. .mode = &proc_thread_each_fd_mode,
  367. .stat = &proc_thread_each_fd_stat,
  368. .follow_link = &proc_thread_each_fd_follow_link,
  369. };
  370. static const struct proc_dir dir_fd = { .size = 1, .ent = { {
  371. .nm_ops = &nm_thread_each_fd, .fs_ops = &fs_thread_each_fd,
  372. }, }, };
  373. static int proc_thread_maps_open (struct shim_handle * hdl,
  374. const char * name, int flags)
  375. {
  376. if (flags & (O_WRONLY|O_RDWR))
  377. return -EACCES;
  378. const char * next;
  379. size_t next_len;
  380. IDTYPE pid;
  381. int ret = parse_thread_name(name, &pid, &next, &next_len, NULL);
  382. if (ret < 0)
  383. return ret;
  384. struct shim_thread * thread = lookup_thread(pid);
  385. if (!thread)
  386. return -ENOENT;
  387. size_t count = DEFAULT_VMA_COUNT;
  388. struct shim_vma_val * vmas = malloc(sizeof(struct shim_vma_val) * count);
  389. if (!vmas) {
  390. ret = -ENOMEM;
  391. goto out;
  392. }
  393. retry_dump_vmas:
  394. ret = dump_all_vmas(vmas, count);
  395. if (ret == -EOVERFLOW) {
  396. struct shim_vma_val * new_vmas
  397. = malloc(sizeof(struct shim_vma_val) * count * 2);
  398. if (!new_vmas) {
  399. ret = -ENOMEM;
  400. goto err;
  401. }
  402. free(vmas);
  403. vmas = new_vmas;
  404. count *= 2;
  405. goto retry_dump_vmas;
  406. }
  407. if (ret < 0)
  408. goto err;
  409. #define DEFAULT_VMA_BUFFER_SIZE 256
  410. count = ret;
  411. size_t buffer_size = DEFAULT_VMA_BUFFER_SIZE, offset = 0;
  412. char * buffer = malloc(buffer_size);
  413. if (!buffer) {
  414. ret = -ENOMEM;
  415. goto err;
  416. }
  417. for (struct shim_vma_val * vma = vmas ; vma < vmas + count ; vma++) {
  418. size_t old_offset = offset;
  419. uintptr_t start = (uintptr_t) vma->addr;
  420. uintptr_t end = (uintptr_t) vma->addr + vma->length;
  421. char pt[3] = {
  422. (vma->prot & PROT_READ) ? 'r' : '-',
  423. (vma->prot & PROT_WRITE) ? 'w' : '-',
  424. (vma->prot & PROT_EXEC) ? 'x' : '-',
  425. };
  426. char pr = (vma->flags & MAP_PRIVATE) ? 'p' : 's';
  427. #define ADDR_FMT(addr) ((addr) > 0xffffffff ? "%lx" : "%08lx")
  428. #define EMIT(fmt ...) \
  429. do { \
  430. offset += snprintf(buffer + offset, buffer_size - offset, \
  431. fmt); \
  432. } while (0)
  433. retry_emit_vma:
  434. if (vma->file) {
  435. int dev_major = 0, dev_minor = 0;
  436. unsigned long ino = vma->file->dentry ? vma->file->dentry->ino : 0;
  437. const char * name = "[unknown]";
  438. if (!qstrempty(&vma->file->path))
  439. name = qstrgetstr(&vma->file->path);
  440. EMIT(ADDR_FMT(start), start);
  441. EMIT("-");
  442. EMIT(ADDR_FMT(end), end);
  443. EMIT(" %c%c%c%c %08lx %02d:%02d %lu %s\n", pt[0], pt[1], pt[2], pr,
  444. vma->offset, dev_major, dev_minor, ino, name);
  445. } else {
  446. EMIT(ADDR_FMT(start), start);
  447. EMIT("-");
  448. EMIT(ADDR_FMT(end), end);
  449. if (vma->comment[0])
  450. EMIT(" %c%c%c%c 00000000 00:00 0 %s\n", pt[0], pt[1], pt[2], pr,
  451. vma->comment);
  452. else
  453. EMIT(" %c%c%c%c 00000000 00:00 0\n", pt[0], pt[1], pt[2], pr);
  454. }
  455. if (offset >= buffer_size) {
  456. char * new_buffer = malloc(buffer_size * 2);
  457. if (!new_buffer) {
  458. ret = -ENOMEM;
  459. goto err;
  460. }
  461. offset = old_offset;
  462. memcpy(new_buffer, buffer, old_offset);
  463. free(buffer);
  464. buffer = new_buffer;
  465. buffer_size *= 2;
  466. goto retry_emit_vma;
  467. }
  468. }
  469. struct shim_str_data * data = calloc(1, sizeof(struct shim_str_data));
  470. if (!data) {
  471. ret = -ENOMEM;
  472. goto err;
  473. }
  474. data->str = buffer;
  475. data->len = offset;
  476. hdl->type = TYPE_STR;
  477. hdl->flags = flags & ~O_RDONLY;
  478. hdl->acc_mode = MAY_READ;
  479. hdl->info.str.data = data;
  480. ret = 0;
  481. out:
  482. put_thread(thread);
  483. if (vmas)
  484. free_vma_val_array(vmas, count);
  485. return ret;
  486. err:
  487. if (buffer)
  488. free(buffer);
  489. goto out;
  490. }
  491. static int proc_thread_maps_mode (const char * name, mode_t * mode)
  492. {
  493. // Only used by one file
  494. __UNUSED(name);
  495. *mode = 0400;
  496. return 0;
  497. }
  498. static int proc_thread_maps_stat (const char * name, struct stat * buf)
  499. {
  500. // Only used by one file
  501. __UNUSED(name);
  502. memset(buf, 0, sizeof(struct stat));
  503. buf->st_dev = buf->st_ino = 1;
  504. buf->st_mode = 0400|S_IFREG;
  505. buf->st_uid = 0;
  506. buf->st_gid = 0;
  507. buf->st_size = 0;
  508. return 0;
  509. }
  510. static const struct proc_fs_ops fs_thread_maps = {
  511. .open = &proc_thread_maps_open,
  512. .mode = &proc_thread_maps_mode,
  513. .stat = &proc_thread_maps_stat,
  514. };
  515. static int proc_thread_dir_open (struct shim_handle * hdl,
  516. const char * name, int flags)
  517. {
  518. __UNUSED(hdl);
  519. __UNUSED(name);
  520. if (flags & (O_WRONLY|O_RDWR))
  521. return -EISDIR;
  522. // Don't really need to do any work here, but keeping as a placeholder,
  523. // just in case.
  524. return 0;
  525. }
  526. static int proc_thread_dir_mode (const char * name, mode_t * mode)
  527. {
  528. const char * next;
  529. size_t next_len;
  530. IDTYPE pid;
  531. int ret = parse_thread_name(name, &pid, &next, &next_len, NULL);
  532. if (ret < 0)
  533. return ret;
  534. *mode = 0500;
  535. return 0;
  536. }
  537. static int proc_thread_dir_stat (const char * name, struct stat * buf)
  538. {
  539. const char * next;
  540. size_t next_len;
  541. IDTYPE pid;
  542. int ret = parse_thread_name(name, &pid, &next, &next_len, NULL);
  543. if (ret < 0)
  544. return ret;
  545. struct shim_thread * thread = lookup_thread(pid);
  546. if (!thread)
  547. return -ENOENT;
  548. memset(buf, 0, sizeof(struct stat));
  549. buf->st_dev = buf->st_ino = 1;
  550. buf->st_mode = 0500|S_IFDIR;
  551. lock(&thread->lock);
  552. buf->st_uid = thread->uid;
  553. buf->st_gid = thread->gid;
  554. unlock(&thread->lock);
  555. buf->st_size = 4096;
  556. return 0;
  557. }
  558. static const struct proc_fs_ops fs_thread_fd = {
  559. .mode = &proc_thread_dir_mode,
  560. .stat = &proc_thread_dir_stat,
  561. };
  562. static int proc_match_thread (const char * name)
  563. {
  564. IDTYPE pid;
  565. if (parse_thread_name(name, &pid, NULL, NULL, NULL) < 0)
  566. return 0;
  567. struct shim_thread * thread = lookup_thread(pid);
  568. return thread ? 1 : 0;
  569. }
  570. struct walk_thread_arg {
  571. struct shim_dirent * buf, * buf_end;
  572. };
  573. static int walk_cb (struct shim_thread * thread, void * arg, bool * unlocked)
  574. {
  575. // unlocked needed for kill
  576. __UNUSED(unlocked);
  577. struct walk_thread_arg * args = (struct walk_thread_arg *) arg;
  578. IDTYPE pid = thread->tid;
  579. int p = pid, l = 0;
  580. for ( ; p ; p /= 10, l++);
  581. if ((void *) (args->buf + 1) + l + 1 > (void *) args->buf_end)
  582. return -ENOBUFS;
  583. struct shim_dirent * buf = args->buf;
  584. buf->next = (void *) (buf + 1) + l + 1;
  585. buf->ino = 1;
  586. buf->type = LINUX_DT_DIR;
  587. buf->name[l--] = 0;
  588. for (p = pid ; p ; p /= 10)
  589. buf->name[l--] = p % 10 + '0';
  590. args->buf = buf->next;
  591. return 1;
  592. }
  593. static int proc_list_thread (const char * name, struct shim_dirent ** buf,
  594. int len)
  595. {
  596. __UNUSED(name); // We know this is for "/proc/self"
  597. struct walk_thread_arg args =
  598. { .buf = *buf, .buf_end = (void *) *buf + len, };
  599. int ret = walk_thread_list(&walk_cb, &args);
  600. if (ret < 0)
  601. return ret;
  602. *buf = args.buf;
  603. return 0;
  604. }
  605. const struct proc_nm_ops nm_thread = {
  606. .match_name = &proc_match_thread,
  607. .list_name = &proc_list_thread,
  608. };
  609. const struct proc_fs_ops fs_thread = {
  610. .open = &proc_thread_dir_open,
  611. .mode = &proc_thread_dir_mode,
  612. .stat = &proc_thread_dir_stat,
  613. };
  614. const struct proc_dir dir_thread = { .size = 5, .ent = {
  615. { .name = "cwd", .fs_ops = &fs_thread_link, },
  616. { .name = "exe", .fs_ops = &fs_thread_link, },
  617. { .name = "root", .fs_ops = &fs_thread_link, },
  618. { .name = "fd", .dir = &dir_fd, .fs_ops = &fs_thread_fd, },
  619. { .name = "maps", .fs_ops = &fs_thread_maps, },
  620. }, };