thread.c 19 KB

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