]> git.ipfire.org Git - thirdparty/glibc.git/blob - sysdeps/unix/sysv/linux/tst-ttyname.c
Update copyright dates with scripts/update-copyrights.
[thirdparty/glibc.git] / sysdeps / unix / sysv / linux / tst-ttyname.c
1 /* Copyright (C) 2017-2018 Free Software Foundation, Inc.
2 This file is part of the GNU C Library.
3
4 The GNU C Library is free software; you can redistribute it and/or
5 modify it under the terms of the GNU Lesser General Public License as
6 published by the Free Software Foundation; either version 2.1 of the
7 License, or (at your option) any later version.
8
9 The GNU C Library is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 Lesser General Public License for more details.
13
14 You should have received a copy of the GNU Lesser General Public
15 License along with the GNU C Library; see the file COPYING.LIB. If
16 not, see <http://www.gnu.org/licenses/>. */
17
18 #include <dirent.h>
19 #include <errno.h>
20 #include <fcntl.h>
21 #include <limits.h>
22 #include <sched.h>
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <string.h>
26 #include <sys/mount.h>
27 #include <sys/prctl.h>
28 #include <sys/stat.h>
29 #include <sys/wait.h>
30 #include <unistd.h>
31
32 #include <support/check.h>
33 #include <support/namespace.h>
34 #include <support/support.h>
35 #include <support/temp_file.h>
36 #include <support/test-driver.h>
37 #include <support/xunistd.h>
38
39 /* generic utilities */
40
41 #define VERIFY(expr) \
42 do { \
43 if (!(expr)) \
44 { \
45 printf ("error: %s:%d: %s: %m\n", \
46 __FILE__, __LINE__, #expr); \
47 exit (1); \
48 } \
49 } while (0)
50
51 static void
52 touch (const char *path, mode_t mode)
53 {
54 xclose (xopen (path, O_WRONLY|O_CREAT|O_NOCTTY, mode));
55 }
56
57 static size_t
58 trim_prefix (char *str, size_t str_len, const char *prefix)
59 {
60 size_t prefix_len = strlen (prefix);
61 if (str_len > prefix_len && memcmp (str, prefix, prefix_len) == 0)
62 {
63 memmove (str, str + prefix_len, str_len - prefix_len);
64 return str_len - prefix_len;
65 }
66 return str_len;
67 }
68
69 /* returns a pointer to static storage */
70 static char *
71 proc_fd_readlink (const char *linkname)
72 {
73 static char target[PATH_MAX+1];
74 ssize_t target_len = readlink (linkname, target, PATH_MAX);
75 VERIFY (target_len > 0);
76 target_len = trim_prefix (target, target_len, "(unreachable)");
77 target[target_len] = '\0';
78 return target;
79 }
80
81 /* plain ttyname runner */
82
83 struct result
84 {
85 const char *name;
86 int err;
87 };
88
89 /* strings in result structure are in static storage */
90 static struct result
91 run_ttyname (int fd)
92 {
93 struct result ret;
94 errno = 0;
95 ret.name = ttyname (fd);
96 ret.err = errno;
97 return ret;
98 }
99
100 static bool
101 eq_ttyname (struct result actual, struct result expected)
102 {
103 char *actual_name, *expected_name;
104
105 if ((actual.err == expected.err) &&
106 (!actual.name == !expected.name) &&
107 (actual.name ? strcmp (actual.name, expected.name) == 0 : true))
108 {
109 if (expected.name)
110 expected_name = xasprintf ("\"%s\"", expected.name);
111 else
112 expected_name = xstrdup ("NULL");
113
114 printf ("info: ttyname: PASS {name=%s, errno=%d}\n",
115 expected_name, expected.err);
116
117 free (expected_name);
118 return true;
119 }
120
121 if (actual.name)
122 actual_name = xasprintf ("\"%s\"", actual.name);
123 else
124 actual_name = xstrdup ("NULL");
125
126 if (expected.name)
127 expected_name = xasprintf ("\"%s\"", expected.name);
128 else
129 expected_name = xstrdup ("NULL");
130
131 printf ("error: ttyname: actual {name=%s, errno=%d} != expected {name=%s, errno=%d}\n",
132 actual_name, actual.err,
133 expected_name, expected.err);
134
135 free (actual_name);
136 free (expected_name);
137 return false;
138 }
139
140 /* ttyname_r runner */
141
142 struct result_r
143 {
144 const char *name;
145 int ret;
146 int err;
147 };
148
149 /* strings in result structure are in static storage */
150 static struct result_r
151 run_ttyname_r (int fd)
152 {
153 static char buf[TTY_NAME_MAX];
154
155 struct result_r ret;
156 errno = 0;
157 ret.ret = ttyname_r (fd, buf, TTY_NAME_MAX);
158 ret.err = errno;
159 if (ret.ret == 0)
160 ret.name = buf;
161 else
162 ret.name = NULL;
163 return ret;
164 }
165
166 static bool
167 eq_ttyname_r (struct result_r actual, struct result_r expected)
168 {
169 char *actual_name, *expected_name;
170
171 if ((actual.err == expected.err) &&
172 (actual.ret == expected.ret) &&
173 (!actual.name == !expected.name) &&
174 (actual.name ? strcmp (actual.name, expected.name) == 0 : true))
175 {
176 if (expected.name)
177 expected_name = xasprintf ("\"%s\"", expected.name);
178 else
179 expected_name = xstrdup ("NULL");
180
181 printf ("info: ttyname_r: PASS {name=%s, ret=%d, errno=%d}\n",
182 expected_name, expected.ret, expected.err);
183
184 free (expected_name);
185 return true;
186 }
187
188 if (actual.name)
189 actual_name = xasprintf ("\"%s\"", actual.name);
190 else
191 actual_name = xstrdup ("NULL");
192
193 if (expected.name)
194 expected_name = xasprintf ("\"%s\"", expected.name);
195 else
196 expected_name = xstrdup ("NULL");
197
198 printf ("error: ttyname_r: actual {name=%s, ret=%d, errno=%d} != expected {name=%s, ret=%d, errno=%d}\n",
199 actual_name, actual.ret, actual.err,
200 expected_name, expected.ret, expected.err);
201
202 free (actual_name);
203 free (expected_name);
204 return false;
205 }
206
207 /* combined runner */
208
209 static bool
210 doit (int fd, const char *testname, struct result_r expected_r)
211 {
212 struct result expected = {.name=expected_r.name, .err=expected_r.ret};
213 bool ret = true;
214
215 printf ("info: testcase: %s\n", testname);
216
217 if (!eq_ttyname (run_ttyname (fd), expected))
218 ret = false;
219 if (!eq_ttyname_r (run_ttyname_r (fd), expected_r))
220 ret = false;
221
222 if (!ret)
223 support_record_failure ();
224
225 return ret;
226 }
227
228 /* chroot setup */
229
230 static char *chrootdir;
231
232 static void
233 prepare (int argc, char **argv)
234 {
235 chrootdir = xasprintf ("%s/tst-ttyname-XXXXXX", test_dir);
236 if (mkdtemp (chrootdir) == NULL)
237 FAIL_EXIT1 ("mkdtemp (\"%s\"): %m", chrootdir);
238 add_temp_file (chrootdir);
239 }
240 #define PREPARE prepare
241
242 /* These chroot setup functions put the TTY at at "/console" (where it
243 won't be found by ttyname), and create "/dev/console" as an
244 ordinary file. This way, it's easier to write test-cases that
245 expect ttyname to fail; test-cases that expect it to succeed need
246 to explicitly remount it at "/dev/console". */
247
248 static int
249 do_in_chroot_1 (int (*cb)(const char *, int))
250 {
251 printf ("info: entering chroot 1\n");
252
253 /* Open the PTS that we'll be testing on. */
254 int master;
255 char *slavename;
256 VERIFY ((master = posix_openpt (O_RDWR|O_NOCTTY|O_NONBLOCK)) >= 0);
257 VERIFY ((slavename = ptsname (master)));
258 VERIFY (unlockpt (master) == 0);
259 if (strncmp (slavename, "/dev/pts/", 9) != 0)
260 FAIL_UNSUPPORTED ("slave pseudo-terminal is not under /dev/pts/: %s",
261 slavename);
262 int slave = xopen (slavename, O_RDWR, 0);
263 if (!doit (slave, "basic smoketest",
264 (struct result_r){.name=slavename, .ret=0, .err=0}))
265 return 1;
266
267 pid_t pid = xfork ();
268 if (pid == 0)
269 {
270 xclose (master);
271
272 if (!support_enter_mount_namespace ())
273 FAIL_UNSUPPORTED ("could not enter new mount namespace");
274
275 VERIFY (mount ("tmpfs", chrootdir, "tmpfs", 0, "mode=755") == 0);
276 VERIFY (chdir (chrootdir) == 0);
277
278 xmkdir ("proc", 0755);
279 xmkdir ("dev", 0755);
280 xmkdir ("dev/pts", 0755);
281
282 VERIFY (mount ("/proc", "proc", NULL, MS_BIND|MS_REC, NULL) == 0);
283 VERIFY (mount ("devpts", "dev/pts", "devpts",
284 MS_NOSUID|MS_NOEXEC,
285 "newinstance,ptmxmode=0666,mode=620") == 0);
286 VERIFY (symlink ("pts/ptmx", "dev/ptmx") == 0);
287
288 touch ("console", 0);
289 touch ("dev/console", 0);
290 VERIFY (mount (slavename, "console", NULL, MS_BIND, NULL) == 0);
291
292 xchroot (".");
293
294 char *linkname = xasprintf ("/proc/self/fd/%d", slave);
295 char *target = proc_fd_readlink (linkname);
296 VERIFY (strcmp (target, slavename) == 0);
297 free (linkname);
298
299 _exit (cb (slavename, slave));
300 }
301 int status;
302 xwaitpid (pid, &status, 0);
303 VERIFY (WIFEXITED (status));
304 xclose (master);
305 xclose (slave);
306 return WEXITSTATUS (status);
307 }
308
309 static int
310 do_in_chroot_2 (int (*cb)(const char *, int))
311 {
312 printf ("info: entering chroot 2\n");
313
314 int pid_pipe[2];
315 xpipe (pid_pipe);
316 int exit_pipe[2];
317 xpipe (exit_pipe);
318
319 /* Open the PTS that we'll be testing on. */
320 int master;
321 char *slavename;
322 VERIFY ((master = posix_openpt (O_RDWR|O_NOCTTY|O_NONBLOCK)) >= 0);
323 VERIFY ((slavename = ptsname (master)));
324 VERIFY (unlockpt (master) == 0);
325 if (strncmp (slavename, "/dev/pts/", 9) != 0)
326 FAIL_UNSUPPORTED ("slave pseudo-terminal is not under /dev/pts/: %s",
327 slavename);
328 /* wait until in a new mount ns to open the slave */
329
330 /* enable `wait`ing on grandchildren */
331 VERIFY (prctl (PR_SET_CHILD_SUBREAPER, 1) == 0);
332
333 pid_t pid = xfork (); /* outer child */
334 if (pid == 0)
335 {
336 xclose (master);
337 xclose (pid_pipe[0]);
338 xclose (exit_pipe[1]);
339
340 if (!support_enter_mount_namespace ())
341 FAIL_UNSUPPORTED ("could not enter new mount namespace");
342
343 int slave = xopen (slavename, O_RDWR, 0);
344 if (!doit (slave, "basic smoketest",
345 (struct result_r){.name=slavename, .ret=0, .err=0}))
346 _exit (1);
347
348 VERIFY (mount ("tmpfs", chrootdir, "tmpfs", 0, "mode=755") == 0);
349 VERIFY (chdir (chrootdir) == 0);
350
351 xmkdir ("proc", 0755);
352 xmkdir ("dev", 0755);
353 xmkdir ("dev/pts", 0755);
354
355 VERIFY (mount ("devpts", "dev/pts", "devpts",
356 MS_NOSUID|MS_NOEXEC,
357 "newinstance,ptmxmode=0666,mode=620") == 0);
358 VERIFY (symlink ("pts/ptmx", "dev/ptmx") == 0);
359
360 touch ("console", 0);
361 touch ("dev/console", 0);
362 VERIFY (mount (slavename, "console", NULL, MS_BIND, NULL) == 0);
363
364 xchroot (".");
365
366 if (unshare (CLONE_NEWNS | CLONE_NEWPID) < 0)
367 FAIL_UNSUPPORTED ("could not enter new PID namespace");
368 pid = xfork (); /* inner child */
369 if (pid == 0)
370 {
371 xclose (pid_pipe[1]);
372
373 /* wait until the outer child has exited */
374 char c;
375 VERIFY (read (exit_pipe[0], &c, 1) == 0);
376 xclose (exit_pipe[0]);
377
378 VERIFY (mount ("proc", "/proc", "proc",
379 MS_NOSUID|MS_NOEXEC|MS_NODEV, NULL) == 0);
380
381 char *linkname = xasprintf ("/proc/self/fd/%d", slave);
382 char *target = proc_fd_readlink (linkname);
383 VERIFY (strcmp (target, strrchr (slavename, '/')) == 0);
384 free (linkname);
385
386 _exit (cb (slavename, slave));
387 }
388 xwrite (pid_pipe[1], &pid, sizeof pid);
389 _exit (0);
390 }
391 xclose (pid_pipe[1]);
392 xclose (exit_pipe[0]);
393 xclose (exit_pipe[1]);
394
395 /* wait for the outer child */
396 int status;
397 xwaitpid (pid, &status, 0);
398 VERIFY (WIFEXITED (status));
399 int ret = WEXITSTATUS (status);
400 if (ret != 0)
401 return ret;
402
403 /* set 'pid' to the inner child */
404 VERIFY (read (pid_pipe[0], &pid, sizeof pid) == sizeof pid);
405 xclose (pid_pipe[0]);
406
407 /* wait for the inner child */
408 xwaitpid (pid, &status, 0);
409 VERIFY (WIFEXITED (status));
410 xclose (master);
411 return WEXITSTATUS (status);
412 }
413
414 /* main test */
415
416 static int
417 run_chroot_tests (const char *slavename, int slave)
418 {
419 struct stat st;
420 bool ok = true;
421
422 /* There are 3 groups of tests here. The first group fairly
423 generically does things known to mess up ttyname, and verifies
424 that ttyname copes correctly. The remaining groups are
425 increasingly convoluted, as we target specific parts of ttyname
426 to try to confuse. */
427
428 /* Basic tests that it doesn't get confused by multiple devpts
429 instances. */
430 {
431 VERIFY (stat (slavename, &st) < 0); /* sanity check */
432 if (!doit (slave, "no conflict, no match",
433 (struct result_r){.name=NULL, .ret=ENODEV, .err=ENODEV}))
434 ok = false;
435 VERIFY (mount ("/console", "/dev/console", NULL, MS_BIND, NULL) == 0);
436 if (!doit (slave, "no conflict, console",
437 (struct result_r){.name="/dev/console", .ret=0, .err=0}))
438 ok = false;
439 VERIFY (umount ("/dev/console") == 0);
440
441 /* keep creating PTYs until we we get a name collision */
442 while (stat (slavename, &st) < 0)
443 posix_openpt (O_RDWR|O_NOCTTY|O_NONBLOCK);
444 VERIFY (stat (slavename, &st) == 0);
445
446 if (!doit (slave, "conflict, no match",
447 (struct result_r){.name=NULL, .ret=ENODEV, .err=ENODEV}))
448 ok = false;
449 VERIFY (mount ("/console", "/dev/console", NULL, MS_BIND, NULL) == 0);
450 if (!doit (slave, "conflict, console",
451 (struct result_r){.name="/dev/console", .ret=0, .err=0}))
452 ok = false;
453 VERIFY (umount ("/dev/console") == 0);
454 }
455
456 /* The first tests kinda assumed that they hit certain code-paths
457 based on assuming that the readlink target is 'slavename', but
458 that's not quite always true. They're still a good preliminary
459 sanity check, so keep them, but let's add tests that make sure
460 that those code-paths are hit by doing a readlink ourself. */
461 {
462 char *linkname = xasprintf ("/proc/self/fd/%d", slave);
463 char *target = proc_fd_readlink (linkname);
464 free (linkname);
465 /* Depeding on how we set up the chroot, the kernel may or may not
466 trim the leading path to the target (it may give us "/6",
467 instead of "/dev/pts/6"). We test it both ways (do_in_chroot_1
468 and do_in_chroot_2). This test group relies on the target
469 existing, so guarantee that it does exist by creating it if
470 necessary. */
471 if (stat (target, &st) < 0)
472 {
473 VERIFY (errno == ENOENT);
474 touch (target, 0);
475 }
476
477 VERIFY (mount ("/console", "/dev/console", NULL, MS_BIND, NULL) == 0);
478 VERIFY (mount ("/console", target, NULL, MS_BIND, NULL) == 0);
479 if (!doit (slave, "with readlink target",
480 (struct result_r){.name=target, .ret=0, .err=0}))
481 ok = false;
482 VERIFY (umount (target) == 0);
483 VERIFY (umount ("/dev/console") == 0);
484
485 VERIFY (mount ("/console", "/dev/console", NULL, MS_BIND, NULL) == 0);
486 VERIFY (mount (slavename, target, NULL, MS_BIND, NULL) == 0);
487 if (!doit (slave, "with readlink trap; fallback",
488 (struct result_r){.name="/dev/console", .ret=0, .err=0}))
489 ok = false;
490 VERIFY (umount (target) == 0);
491 VERIFY (umount ("/dev/console") == 0);
492
493 VERIFY (mount (slavename, target, NULL, MS_BIND, NULL) == 0);
494 if (!doit (slave, "with readlink trap; no fallback",
495 (struct result_r){.name=NULL, .ret=ENODEV, .err=ENODEV}))
496 ok = false;
497 VERIFY (umount (target) == 0);
498 }
499
500 /* This test makes sure that everything still works OK if readdir
501 finds a pseudo-match before and/or after the actual match. Now,
502 to do that, we need to control that readdir finds the
503 pseudo-matches before and after the actual match; and there's no
504 good way to control that order in absence of whitebox testing.
505 So, just create 3 files, then use opendir/readdir to see what
506 order they are in, and assign meaning based on that order, not by
507 name; assigning the first to be a pseudo-match, the second to be
508 the actual match, and the third to be a pseudo-match. This
509 assumes that (on tmpfs) ordering within the directory is stable
510 in the absence of modification, which seems reasonably safe. */
511 {
512 /* since we're testing the fallback search, disable the readlink
513 happy-path */
514 VERIFY (umount2 ("/proc", MNT_DETACH) == 0);
515
516 touch ("/dev/console1", 0);
517 touch ("/dev/console2", 0);
518 touch ("/dev/console3", 0);
519
520 char *c[3];
521 int ci = 0;
522 DIR *dirstream = opendir ("/dev");
523 VERIFY (dirstream != NULL);
524 struct dirent *d;
525 while ((d = readdir (dirstream)) != NULL && ci < 3)
526 {
527 if (strcmp (d->d_name, "console1") &&
528 strcmp (d->d_name, "console2") &&
529 strcmp (d->d_name, "console3") )
530 continue;
531 c[ci++] = xasprintf ("/dev/%s", d->d_name);
532 }
533 VERIFY (ci == 3);
534 VERIFY (closedir (dirstream) == 0);
535
536 VERIFY (mount (slavename, c[0], NULL, MS_BIND, NULL) == 0);
537 VERIFY (mount ("/console", c[1], NULL, MS_BIND, NULL) == 0);
538 VERIFY (mount (slavename, c[2], NULL, MS_BIND, NULL) == 0);
539 VERIFY (umount2 ("/dev/pts", MNT_DETACH) == 0);
540 if (!doit (slave, "with search-path trap",
541 (struct result_r){.name=c[1], .ret=0, .err=0}))
542 ok = false;
543 for (int i = 0; i < 3; i++)
544 {
545 VERIFY (umount (c[i]) == 0);
546 VERIFY (unlink (c[i]) == 0);
547 free (c[i]);
548 }
549 }
550
551 return ok ? 0 : 1;
552 }
553
554 static int
555 do_test (void)
556 {
557 support_become_root ();
558
559 int ret1 = do_in_chroot_1 (run_chroot_tests);
560 if (ret1 == EXIT_UNSUPPORTED)
561 return ret1;
562
563 int ret2 = do_in_chroot_2 (run_chroot_tests);
564 if (ret2 == EXIT_UNSUPPORTED)
565 return ret2;
566
567 return ret1 | ret2;
568 }
569
570 #include <support/test-driver.c>