across-fork.c 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285
  1. #include "../config-host.h"
  2. /* SPDX-License-Identifier: MIT */
  3. /*
  4. * Description: test sharing a ring across a fork
  5. */
  6. #include <fcntl.h>
  7. #include <pthread.h>
  8. #include <signal.h>
  9. #include <stdio.h>
  10. #include <stdlib.h>
  11. #include <string.h>
  12. #include <sys/mman.h>
  13. #include <sys/stat.h>
  14. #include <sys/types.h>
  15. #include <sys/wait.h>
  16. #include <unistd.h>
  17. #include "liburing.h"
  18. #include "helpers.h"
  19. struct forktestmem
  20. {
  21. struct io_uring ring;
  22. pthread_barrier_t barrier;
  23. pthread_barrierattr_t barrierattr;
  24. };
  25. static int open_tempfile(const char *dir, const char *fname)
  26. {
  27. int fd;
  28. char buf[32];
  29. snprintf(buf, sizeof(buf), "%s/%s",
  30. dir, fname);
  31. fd = open(buf, O_RDWR | O_CREAT | O_APPEND, S_IRUSR | S_IWUSR);
  32. if (fd < 0) {
  33. perror("open");
  34. exit(1);
  35. }
  36. return fd;
  37. }
  38. static int submit_write(struct io_uring *ring, int fd, const char *str,
  39. int wait)
  40. {
  41. struct io_uring_sqe *sqe;
  42. struct iovec iovec;
  43. int ret;
  44. sqe = io_uring_get_sqe(ring);
  45. if (!sqe) {
  46. fprintf(stderr, "could not get sqe\n");
  47. return 1;
  48. }
  49. iovec.iov_base = (char *) str;
  50. iovec.iov_len = strlen(str);
  51. io_uring_prep_writev(sqe, fd, &iovec, 1, 0);
  52. ret = io_uring_submit_and_wait(ring, wait);
  53. if (ret < 0) {
  54. fprintf(stderr, "submit failed: %s\n", strerror(-ret));
  55. return 1;
  56. }
  57. return 0;
  58. }
  59. static int wait_cqe(struct io_uring *ring, const char *stage)
  60. {
  61. struct io_uring_cqe *cqe;
  62. int ret;
  63. ret = io_uring_wait_cqe(ring, &cqe);
  64. if (ret) {
  65. fprintf(stderr, "%s wait_cqe failed %d\n", stage, ret);
  66. return 1;
  67. }
  68. if (cqe->res < 0) {
  69. fprintf(stderr, "%s cqe failed %d\n", stage, cqe->res);
  70. return 1;
  71. }
  72. io_uring_cqe_seen(ring, cqe);
  73. return 0;
  74. }
  75. static int verify_file(const char *tmpdir, const char *fname, const char* expect)
  76. {
  77. int fd;
  78. char buf[512];
  79. int err = 0;
  80. memset(buf, 0, sizeof(buf));
  81. fd = open_tempfile(tmpdir, fname);
  82. if (fd < 0)
  83. return 1;
  84. if (read(fd, buf, sizeof(buf) - 1) < 0)
  85. return 1;
  86. if (strcmp(buf, expect) != 0) {
  87. fprintf(stderr, "content mismatch for %s\n"
  88. "got:\n%s\n"
  89. "expected:\n%s\n",
  90. fname, buf, expect);
  91. err = 1;
  92. }
  93. close(fd);
  94. return err;
  95. }
  96. static void cleanup(const char *tmpdir)
  97. {
  98. char buf[32];
  99. /* don't check errors, called during partial runs */
  100. snprintf(buf, sizeof(buf), "%s/%s", tmpdir, "shared");
  101. unlink(buf);
  102. snprintf(buf, sizeof(buf), "%s/%s", tmpdir, "parent1");
  103. unlink(buf);
  104. snprintf(buf, sizeof(buf), "%s/%s", tmpdir, "parent2");
  105. unlink(buf);
  106. snprintf(buf, sizeof(buf), "%s/%s", tmpdir, "child");
  107. unlink(buf);
  108. rmdir(tmpdir);
  109. }
  110. int main(int argc, char *argv[])
  111. {
  112. struct forktestmem *shmem;
  113. char tmpdir[] = "forktmpXXXXXX";
  114. int shared_fd;
  115. int ret;
  116. pid_t p;
  117. if (argc > 1)
  118. return T_EXIT_SKIP;
  119. shmem = mmap(0, sizeof(struct forktestmem), PROT_READ|PROT_WRITE,
  120. MAP_SHARED | MAP_ANONYMOUS, 0, 0);
  121. if (!shmem) {
  122. fprintf(stderr, "mmap failed\n");
  123. exit(T_EXIT_FAIL);
  124. }
  125. pthread_barrierattr_init(&shmem->barrierattr);
  126. pthread_barrierattr_setpshared(&shmem->barrierattr, 1);
  127. pthread_barrier_init(&shmem->barrier, &shmem->barrierattr, 2);
  128. ret = io_uring_queue_init(10, &shmem->ring, 0);
  129. if (ret < 0) {
  130. fprintf(stderr, "queue init failed\n");
  131. exit(T_EXIT_FAIL);
  132. }
  133. if (mkdtemp(tmpdir) == NULL) {
  134. fprintf(stderr, "temp directory creation failed\n");
  135. exit(T_EXIT_FAIL);
  136. }
  137. shared_fd = open_tempfile(tmpdir, "shared");
  138. /*
  139. * First do a write before the fork, to test whether child can
  140. * reap that
  141. */
  142. if (submit_write(&shmem->ring, shared_fd, "before fork: write shared fd\n", 0))
  143. goto errcleanup;
  144. p = fork();
  145. switch (p) {
  146. case -1:
  147. fprintf(stderr, "fork failed\n");
  148. goto errcleanup;
  149. default: {
  150. /* parent */
  151. int parent_fd1;
  152. int parent_fd2;
  153. int wstatus;
  154. /* wait till fork is started up */
  155. pthread_barrier_wait(&shmem->barrier);
  156. parent_fd1 = open_tempfile(tmpdir, "parent1");
  157. parent_fd2 = open_tempfile(tmpdir, "parent2");
  158. /* do a parent write to the shared fd */
  159. if (submit_write(&shmem->ring, shared_fd, "parent: write shared fd\n", 0))
  160. goto errcleanup;
  161. /* do a parent write to an fd where same numbered fd exists in child */
  162. if (submit_write(&shmem->ring, parent_fd1, "parent: write parent fd 1\n", 0))
  163. goto errcleanup;
  164. /* do a parent write to an fd where no same numbered fd exists in child */
  165. if (submit_write(&shmem->ring, parent_fd2, "parent: write parent fd 2\n", 0))
  166. goto errcleanup;
  167. /* wait to switch read/writ roles with child */
  168. pthread_barrier_wait(&shmem->barrier);
  169. /* now wait for child to exit, to ensure we still can read completion */
  170. waitpid(p, &wstatus, 0);
  171. if (WEXITSTATUS(wstatus) != 0) {
  172. fprintf(stderr, "child failed\n");
  173. goto errcleanup;
  174. }
  175. if (wait_cqe(&shmem->ring, "p cqe 1"))
  176. goto errcleanup;
  177. if (wait_cqe(&shmem->ring, "p cqe 2"))
  178. goto errcleanup;
  179. /* check that IO can still be submitted after child exited */
  180. if (submit_write(&shmem->ring, shared_fd, "parent: write shared fd after child exit\n", 0))
  181. goto errcleanup;
  182. if (wait_cqe(&shmem->ring, "p cqe 3"))
  183. goto errcleanup;
  184. break;
  185. }
  186. case 0: {
  187. /* child */
  188. int child_fd;
  189. /* wait till fork is started up */
  190. pthread_barrier_wait(&shmem->barrier);
  191. child_fd = open_tempfile(tmpdir, "child");
  192. if (wait_cqe(&shmem->ring, "c cqe shared"))
  193. exit(1);
  194. if (wait_cqe(&shmem->ring, "c cqe parent 1"))
  195. exit(1);
  196. if (wait_cqe(&shmem->ring, "c cqe parent 2"))
  197. exit(1);
  198. if (wait_cqe(&shmem->ring, "c cqe parent 3"))
  199. exit(1);
  200. /* wait to switch read/writ roles with parent */
  201. pthread_barrier_wait(&shmem->barrier);
  202. if (submit_write(&shmem->ring, child_fd, "child: write child fd\n", 0))
  203. exit(1);
  204. /* ensure both writes have finished before child exits */
  205. if (submit_write(&shmem->ring, shared_fd, "child: write shared fd\n", 2))
  206. exit(1);
  207. exit(0);
  208. }
  209. }
  210. if (verify_file(tmpdir, "shared",
  211. "before fork: write shared fd\n"
  212. "parent: write shared fd\n"
  213. "child: write shared fd\n"
  214. "parent: write shared fd after child exit\n") ||
  215. verify_file(tmpdir, "parent1", "parent: write parent fd 1\n") ||
  216. verify_file(tmpdir, "parent2", "parent: write parent fd 2\n") ||
  217. verify_file(tmpdir, "child", "child: write child fd\n"))
  218. goto errcleanup;
  219. cleanup(tmpdir);
  220. exit(T_EXIT_PASS);
  221. errcleanup:
  222. cleanup(tmpdir);
  223. exit(T_EXIT_FAIL);
  224. }