logo

pts

Fake a pseudo-terminal for testsuites

pts.c (3821B)


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