tty-test.c 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206
  1. // This is an open source non-commercial project. Dear PVS-Studio, please check
  2. // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
  3. #include <stdbool.h>
  4. #include <stdio.h>
  5. #include <stdlib.h>
  6. #include <uv.h>
  7. #ifdef _WIN32
  8. # include <windows.h>
  9. #else
  10. # include <unistd.h>
  11. #endif
  12. // -V:STRUCT_CAST:641
  13. #define STRUCT_CAST(Type, obj) ((Type *)(obj))
  14. #define is_terminal(stream) (uv_guess_handle(fileno(stream)) == UV_TTY)
  15. #define BUF_SIZE 0xfff
  16. #define CTRL_C 0x03
  17. uv_tty_t tty;
  18. uv_tty_t tty_out;
  19. bool owns_tty(void); // silence -Wmissing-prototypes
  20. bool owns_tty(void)
  21. {
  22. #ifdef _WIN32
  23. // XXX: We need to make proper detect owns tty
  24. // HWND consoleWnd = GetConsoleWindow();
  25. // DWORD dwProcessId;
  26. // GetWindowThreadProcessId(consoleWnd, &dwProcessId);
  27. // return GetCurrentProcessId() == dwProcessId;
  28. return true;
  29. #else
  30. return getsid(0) == getpid();
  31. #endif
  32. }
  33. static void walk_cb(uv_handle_t *handle, void *arg)
  34. {
  35. if (!uv_is_closing(handle)) {
  36. #ifdef WIN32
  37. uv_tty_set_mode(&tty, UV_TTY_MODE_NORMAL);
  38. #endif
  39. uv_close(handle, NULL);
  40. }
  41. }
  42. #ifndef WIN32
  43. static void sig_handler(int signum)
  44. {
  45. switch (signum) {
  46. case SIGWINCH: {
  47. int width, height;
  48. uv_tty_get_winsize(&tty, &width, &height);
  49. fprintf(stderr, "rows: %d, cols: %d\n", height, width);
  50. return;
  51. }
  52. case SIGHUP:
  53. exit(42); // arbitrary exit code to test against
  54. return;
  55. default:
  56. return;
  57. }
  58. }
  59. #endif
  60. #ifdef WIN32
  61. static void sigwinch_cb(uv_signal_t *handle, int signum)
  62. {
  63. int width, height;
  64. uv_tty_get_winsize(&tty_out, &width, &height);
  65. fprintf(stderr, "rows: %d, cols: %d\n", height, width);
  66. }
  67. #endif
  68. static void alloc_cb(uv_handle_t *handle, size_t suggested, uv_buf_t *buf)
  69. {
  70. buf->len = BUF_SIZE;
  71. buf->base = malloc(BUF_SIZE);
  72. }
  73. static void read_cb(uv_stream_t *stream, ssize_t cnt, const uv_buf_t *buf)
  74. {
  75. if (cnt <= 0) {
  76. uv_read_stop(stream);
  77. return;
  78. }
  79. int *interrupted = stream->data;
  80. for (int i = 0; i < cnt; i++) {
  81. if (buf->base[i] == CTRL_C) {
  82. (*interrupted)++;
  83. }
  84. }
  85. uv_loop_t write_loop;
  86. uv_loop_init(&write_loop);
  87. uv_tty_t out;
  88. uv_tty_init(&write_loop, &out, fileno(stdout), 0);
  89. uv_write_t req;
  90. uv_buf_t b = {
  91. .base = buf->base,
  92. #ifdef WIN32
  93. .len = (ULONG)cnt
  94. #else
  95. .len = (size_t)cnt
  96. #endif
  97. };
  98. uv_write(&req, STRUCT_CAST(uv_stream_t, &out), &b, 1, NULL);
  99. uv_run(&write_loop, UV_RUN_DEFAULT);
  100. uv_close(STRUCT_CAST(uv_handle_t, &out), NULL);
  101. uv_run(&write_loop, UV_RUN_DEFAULT);
  102. if (uv_loop_close(&write_loop)) {
  103. abort();
  104. }
  105. free(buf->base);
  106. if (*interrupted >= 2) {
  107. uv_walk(uv_default_loop(), walk_cb, NULL);
  108. } else if (*interrupted == 1) {
  109. fprintf(stderr, "interrupt received, press again to exit\n");
  110. }
  111. }
  112. static void prepare_cb(uv_prepare_t *handle)
  113. {
  114. fprintf(stderr, "tty ready\n");
  115. uv_prepare_stop(handle);
  116. }
  117. int main(int argc, char **argv)
  118. {
  119. if (!owns_tty()) {
  120. fprintf(stderr, "process does not own the terminal\n");
  121. exit(2);
  122. }
  123. if (!is_terminal(stdin)) {
  124. fprintf(stderr, "stdin is not a terminal\n");
  125. exit(2);
  126. }
  127. if (!is_terminal(stdout)) {
  128. fprintf(stderr, "stdout is not a terminal\n");
  129. exit(2);
  130. }
  131. if (!is_terminal(stderr)) {
  132. fprintf(stderr, "stderr is not a terminal\n");
  133. exit(2);
  134. }
  135. if (argc > 1) {
  136. errno = 0;
  137. int count = (int)strtol(argv[1], NULL, 10);
  138. if (errno != 0) {
  139. abort();
  140. }
  141. count = (count < 0 || count > 99999) ? 0 : count;
  142. for (int i = 0; i < count; i++) {
  143. printf("line%d\n", i);
  144. }
  145. fflush(stdout);
  146. return 0;
  147. }
  148. int interrupted = 0;
  149. uv_prepare_t prepare;
  150. uv_prepare_init(uv_default_loop(), &prepare);
  151. uv_prepare_start(&prepare, prepare_cb);
  152. #ifndef WIN32
  153. uv_tty_init(uv_default_loop(), &tty, fileno(stderr), 1);
  154. #else
  155. uv_tty_init(uv_default_loop(), &tty, fileno(stdin), 1);
  156. uv_tty_init(uv_default_loop(), &tty_out, fileno(stdout), 0);
  157. int width, height;
  158. uv_tty_get_winsize(&tty_out, &width, &height);
  159. #endif
  160. uv_tty_set_mode(&tty, UV_TTY_MODE_RAW);
  161. tty.data = &interrupted;
  162. uv_read_start(STRUCT_CAST(uv_stream_t, &tty), alloc_cb, read_cb);
  163. #ifndef WIN32
  164. struct sigaction sa;
  165. sigemptyset(&sa.sa_mask);
  166. sa.sa_flags = 0;
  167. sa.sa_handler = sig_handler;
  168. sigaction(SIGHUP, &sa, NULL);
  169. sigaction(SIGWINCH, &sa, NULL);
  170. #else
  171. uv_signal_t sigwinch_watcher;
  172. uv_signal_init(uv_default_loop(), &sigwinch_watcher);
  173. uv_signal_start(&sigwinch_watcher, sigwinch_cb, SIGWINCH);
  174. #endif
  175. uv_run(uv_default_loop(), UV_RUN_DEFAULT);
  176. #ifndef WIN32
  177. // XXX: Without this the SIGHUP handler is skipped on some systems.
  178. sleep(100);
  179. #endif
  180. return 0;
  181. }