logo

openpt

Fake a pseudo-terminal for testsuites

openpt.c (3932B)


  1. // SPDX-FileCopyrightText: 2017 Haelwenn (lanodan) Monnier <contact+utils-openpt@hacktivis.me>
  2. // SPDX-License-Identifier: MIT
  3. #define _POSIX_C_SOURCE 200809L
  4. #define _XOPEN_SOURCE 800
  5. #include <stdlib.h> // posix_openpt, ptsname
  6. #include <fcntl.h> // O_*
  7. #include <string.h> // strerror
  8. #include <stdio.h> // fprintf, perror
  9. #include <errno.h>
  10. #include <poll.h>
  11. #include <unistd.h> // execvp, dup2
  12. #include <sys/wait.h>
  13. #include <signal.h>
  14. static int child_status = 0;
  15. static pid_t pid = -1;
  16. static void
  17. handle_sigchld(int sig)
  18. {
  19. (void)sig;
  20. waitpid(pid, &child_status, WNOHANG);
  21. exit(WEXITSTATUS(child_status));
  22. }
  23. int
  24. main(int argc, char *argv[])
  25. {
  26. argc--;
  27. argv++;
  28. if(argc < 1)
  29. {
  30. fprintf(stderr, "openpt: Expected >= 1 arguments, got %d\n", argc);
  31. fprintf(stderr, "Usage: openpt command [argument...]\n");
  32. return 1;
  33. }
  34. int manager = posix_openpt(O_RDWR|O_NOCTTY);
  35. if(manager < 0)
  36. {
  37. fprintf(stderr, "openpt: Failed opening pseudo-terminal: %s\n", strerror(errno));
  38. return 1;
  39. }
  40. if(grantpt(manager) < 0)
  41. {
  42. perror("openpt: Error in grantpt(manager)");
  43. return 1;
  44. }
  45. if(unlockpt(manager) < 0)
  46. {
  47. perror("openpt: Error in unlockpt(manager)");
  48. return 1;
  49. }
  50. pid = fork();
  51. if(pid < 0)
  52. {
  53. fprintf(stderr, "openpt: Fork failed: %s\n", strerror(errno));
  54. return 1;
  55. }
  56. if(pid != 0)
  57. {
  58. nfds_t nfds = 2;
  59. struct pollfd pfds[2] = {
  60. {
  61. .fd = manager,
  62. .events = POLLIN
  63. },
  64. {
  65. .fd = STDIN_FILENO,
  66. .events = POLLIN
  67. }
  68. };
  69. if(signal(SIGCHLD, handle_sigchld) == SIG_ERR)
  70. {
  71. fprintf(stderr, "timeout: Failed registering handler for SIGCHLD: %s\n", strerror(errno));
  72. return 1;
  73. }
  74. // parent
  75. do {
  76. int ret = poll(pfds, nfds, -1);
  77. if(ret < 0)
  78. {
  79. fprintf(stderr, "openpt: Poll failed: %s\n", strerror(errno));
  80. return 1;
  81. }
  82. if(ret == 0) continue; // timeout
  83. if(pfds[0].revents & POLLIN)
  84. {
  85. char buf[BUFSIZ] = "";
  86. ssize_t nread = read(pfds[0].fd, buf, BUFSIZ);
  87. if(nread < 0)
  88. {
  89. fprintf(stderr, "openpt: Failed reading from pseudo-terminal: %s\n", strerror(errno));
  90. return 1;
  91. }
  92. if(nread == 0) break;
  93. if(write(STDOUT_FILENO, buf, nread) < 0)
  94. {
  95. fprintf(stderr, "openpt: Failed writing pseudo-terminal content to stdout: %s\n", strerror(errno));
  96. return 1;
  97. }
  98. }
  99. if(pfds[1].revents & POLLIN)
  100. {
  101. char buf[BUFSIZ] = "";
  102. ssize_t nread = read(pfds[1].fd, buf, BUFSIZ);
  103. if(nread < 0)
  104. {
  105. fprintf(stderr, "openpt: Failed reading from stdin: %s\n", strerror(errno));
  106. return 1;
  107. }
  108. if(nread == 0) break;
  109. if(write(manager, buf, nread) < 0)
  110. {
  111. fprintf(stderr, "openpt: Failed writing stdin content to pseudo-terminal: %s\n", strerror(errno));
  112. return 1;
  113. }
  114. }
  115. if(waitpid(pid, &child_status, WNOHANG) < 0)
  116. {
  117. fprintf(stderr, "openpt: Failed getting status for child process: %s\n", strerror(errno));
  118. return 1;
  119. }
  120. } while(child_status == 0 || !WIFEXITED(child_status));
  121. return WEXITSTATUS(child_status);
  122. }
  123. else
  124. {
  125. // child
  126. char *name = ptsname(manager);
  127. int sub = open(name, O_RDWR|O_NOCTTY);
  128. if(sub < 0)
  129. {
  130. fprintf(stderr, "openpt: Failed opening subsidiary pseudo-terminal at '%s': %s\n", name, strerror(errno));
  131. return 1;
  132. }
  133. if(dup2(sub, STDIN_FILENO) < 0)
  134. {
  135. fprintf(stderr, "openpt: Failed assigning subsidiary pseudo-terminal to stdin: %s\n", strerror(errno));
  136. return 1;
  137. }
  138. if(dup2(sub, STDOUT_FILENO) < 0)
  139. {
  140. fprintf(stderr, "openpt: Failed assigning subsidiary pseudo-terminal to stdout: %s\n", strerror(errno));
  141. return 1;
  142. }
  143. if(dup2(sub, STDERR_FILENO) < 0)
  144. {
  145. fprintf(stderr, "openpt: Failed assigning subsidiary pseudo-terminal to stderr: %s\n", strerror(errno));
  146. return 1;
  147. }
  148. if(execvp(argv[0], argv) < 0)
  149. {
  150. fprintf(stderr, "openpt: Failed to execute '%s': %s\n", argv[0], strerror(errno));
  151. return errno == ENOENT ? 127 : 126;
  152. }
  153. abort();
  154. }
  155. }