Ticket #3639: mc-terminal-resize-test.c

File mc-terminal-resize-test.c, 5.4 KB (added by zaytsev, 3 years ago)
Line 
1// Verify that mc's subshell sees the correct terminal size
2// after it's changed.  See https://bugs.debian.org/825974#.
3
4/*
5This is free and unencumbered software released into the public domain.
6
7Anyone is free to copy, modify, publish, use, compile, sell, or
8distribute this software, either in source code form or as a compiled
9binary, for any purpose, commercial or non-commercial, and by any
10means.
11
12In jurisdictions that recognize copyright laws, the author or authors
13of this software dedicate any and all copyright interest in the
14software to the public domain. We make this dedication for the benefit
15of the public at large and to the detriment of our heirs and
16successors. We intend this dedication to be an overt act of
17relinquishment in perpetuity of all present and future rights to this
18software under copyright law.
19
20THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
22MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
23IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
24OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
25ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
26OTHER DEALINGS IN THE SOFTWARE.
27
28For more information, please refer to <https://unlicense.org/>
29*/
30
31#define _POSIX_C_SOURCE 200809L
32#define _XOPEN_SOURCE 700
33#include <errno.h>
34#include <fcntl.h>
35#include <pthread.h>
36#include <stdint.h>
37#include <stdio.h>
38#include <stdlib.h>
39#include <string.h>
40#include <sys/ioctl.h>
41#include <sys/wait.h>
42#include <unistd.h>
43
44static void
45die_perror(const char *str)
46{
47        perror(str);
48        exit(1);
49}
50
51static void
52die_usage(const char *name)
53{
54        fprintf(stderr, "Usage: %s\n", name);
55        exit(2);
56}
57
58static void*
59drainer(void *arg)
60{
61        char buf[1024];
62        int *p_fd = arg;
63        ssize_t rv;
64
65        do {
66                rv = read(*p_fd, &buf[0], sizeof buf);
67        } while (rv > 0);
68
69        return (rv == 0) ? NULL : (void*)(intptr_t)errno;
70}
71
72static void
73close_fd(int fd)
74{
75        do {} while (close(fd) < 0 && errno == EINTR);
76}
77
78static void
79write_str_or_die(int fd, const char *str)
80{
81        size_t offset = 0, len = strlen(str);
82        while (offset < len) {
83                ssize_t rv = write(fd, &str[offset], len);
84                if (rv < 0) {
85                        if (errno == EINTR) continue;
86                        die_perror("write");
87                }
88                offset += (size_t)rv;
89        }
90}
91
92static size_t
93read_to_eof_or_die(int fd, char *buf, size_t bufsz)
94{
95        size_t offset = 0;
96        while (offset < bufsz) {
97                ssize_t rv = read(fd, &buf[offset], bufsz-offset);
98                if (rv < 0) {
99                        if (errno == EINTR) continue;
100                        die_perror("read");
101                } else if (rv == 0) {
102                        break;
103                }
104                offset += (size_t)rv;
105        }
106        return offset;
107}
108
109int
110main(int argc, char **argv)
111{
112        int opt;
113        int pty_master, pty_slave;
114        int pipefd[2];
115        int child_pid;
116        const char *slave_path;
117        char buf[256];
118        pthread_t drainer_tid;
119        struct winsize winsz = { .ws_row = 25, .ws_col = 55 };
120
121        while ((opt = getopt(argc, argv, "")) != -1) {
122                switch (opt) {
123                default:
124                case '?':
125                        die_usage(argv[0]);
126                }
127        }
128        if (optind > argc) {
129                die_usage(argv[0]);
130        }
131
132        // Set up a pseudoterminal
133        pty_master = posix_openpt(O_RDWR | O_NOCTTY | O_CLOEXEC);
134        if (pty_master < 0) {
135                die_perror("posix_openpt");
136        }
137        if (grantpt(pty_master) < 0) {
138                die_perror("grantpt");
139        }
140        if (unlockpt(pty_master) < 0) {
141                die_perror("unlockpt");
142        }
143        slave_path = ptsname(pty_master);
144        if (!slave_path) {
145                die_perror("ptsname");
146        }
147
148        // Set the initial size
149        if (ioctl(pty_master, TIOCSWINSZ, &winsz) < 0) {
150                die_perror("ioctl(TIOCSWINSZ)");
151        }
152
153        pty_slave = open(slave_path, O_RDWR | O_NOCTTY);
154        if (pty_slave < 0) {
155                die_perror("open(slave)");
156        }
157
158        // Create a pipe to communicate with mc
159        if (pipe(&pipefd[0]) < 0) {
160                die_perror("pipe");
161        }
162
163        // Fork a process to run mc
164        child_pid = fork();
165        if (child_pid < 0) {
166                die_perror("fork");
167        } else if (!child_pid) {
168                char *exec_args[] = {"mc", (char*)NULL};
169
170                if (setsid() < 0) {
171                        die_perror("setsid");
172                }
173                if (ioctl(pty_slave, TIOCSCTTY, NULL) < 0) {
174                        die_perror("ioctl(TIOCSCTTY)");
175                }
176                if (dup2(pty_slave, 0) < 0
177                                || dup2(pty_slave, 1) < 0
178                                || dup2(pty_slave, 2) < 0
179                                || dup2(pipefd[1], 3) < 0) {
180                        die_perror("dup2");
181                }
182                if (pty_slave > 3) {
183                        close_fd(pty_slave);
184                }
185
186                (void)execvp(exec_args[0], exec_args);
187                die_perror("execvp");
188        }
189
190        close_fd(pty_slave);
191        pty_slave = -1;
192        close_fd(pipefd[1]);
193        pipefd[1] = -1;
194
195        // Have a thread read from the pty so mc won't block
196        errno = pthread_create(&drainer_tid, NULL, drainer, &pty_master);
197        if (errno) {
198                die_perror("pthread_create");
199        }
200
201        // Send some keystrokes and wait until they've been processed
202        write_str_or_die(pty_master, "\xf" /*CTRL+O*/);
203        write_str_or_die(pty_master, "echo '' >&3\n");
204        if (read(pipefd[0], &buf[0], 1) < 0) {
205                die_perror("read(pipe)");
206        }
207
208        // Change the terminal width
209        winsz.ws_col += 11;
210        if (ioctl(pty_master, TIOCSWINSZ, &winsz) < 0) {
211                die_perror("ioctl(TIOCSWINSZ)");
212        }
213
214        // Have mc write the detected terminal size to our pipe, then exit
215        write_str_or_die(pty_master, "stty size >&3; exit\n");
216
217        // Verify that we got the new width
218        {
219                ssize_t bytes;
220                int row, col;
221
222                bytes = read_to_eof_or_die(pipefd[0], &buf[0], sizeof buf - 1);
223
224                errno = 0;
225                buf[bytes] = '\0';
226                if (sscanf(&buf[0], "%d %d\n", &row, &col) != 2) {
227                        die_perror("sscanf");
228                }
229
230                printf("got size %dx%d, wanted %dx%d\n",
231                                col, row, winsz.ws_col, winsz.ws_row);
232                if (col == winsz.ws_col) {
233                        printf("PASS\n");
234                        return 0;
235                } else {
236                        printf("FAIL\n");
237                        return 1;
238                }
239        }
240}