]> git.ipfire.org Git - thirdparty/glibc.git/blame - sysdeps/unix/sysv/linux/tst-ttyname.c
linux ttyname{_r}: Add tests
[thirdparty/glibc.git] / sysdeps / unix / sysv / linux / tst-ttyname.c
CommitLineData
d9611e30
LS
1/* Copyright (C) 2017 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
51static void
52touch (const char *path, mode_t mode)
53{
54 xclose (xopen (path, O_WRONLY|O_CREAT|O_NOCTTY, mode));
55}
56
57static size_t
58trim_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 */
70static char *
71proc_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
81static void
82become_root_in_mount_ns (void)
83{
84 uid_t orig_uid = getuid ();
85 gid_t orig_gid = getgid ();
86
87 support_become_root ();
88
89 if (unshare (CLONE_NEWNS) < 0)
90 FAIL_UNSUPPORTED ("could not enter new mount namespace");
91
92 /* support_become_root might have put us in a new user namespace;
93 most filesystems (including tmpfs) don't allow file or directory
94 creation from a user namespace unless uid and gid maps are set,
95 even if we have root privileges in the namespace (failing with
96 EOVERFLOW, since the uid overflows the empty (0-length) uid map).
97
98 Also, stat always reports that uid and gid maps are empty, so we
99 have to try actually reading from them to check if they are
100 empty. */
101 int fd;
102
103 if ((fd = open ("/proc/self/uid_map", O_RDWR, 0)) >= 0)
104 {
105 char buf;
106 if (read (fd, &buf, 1) == 0)
107 {
108 char *str = xasprintf ("0 %ld 1\n", (long)orig_uid);
109 if (write (fd, str, strlen (str)) < 0)
110 FAIL_EXIT1 ("write (uid_map, \"%s\"): %m", str);
111 free (str);
112 }
113 xclose (fd);
114 }
115
116 /* Setting the gid map has the additional complexity that we have to
117 first turn off setgroups. */
118 if ((fd = open ("/proc/self/setgroups", O_WRONLY, 0)) >= 0)
119 {
120 const char *str = "deny";
121 if (write (fd, str, strlen (str)) < 0)
122 FAIL_EXIT1 ("write (setroups, \"%s\"): %m", str);
123 xclose (fd);
124 }
125
126 if ((fd = open ("/proc/self/gid_map", O_RDWR, 0)) >= 0)
127 {
128 char buf;
129 if (read (fd, &buf, 1) == 0)
130 {
131 char *str = xasprintf ("0 %ld 1\n", (long)orig_gid);
132 if (write (fd, str, strlen (str)) < 0)
133 FAIL_EXIT1 ("write (gid_map, \"%s\"): %m", str);
134 free (str);
135 }
136 xclose (fd);
137 }
138}
139
140/* plain ttyname runner */
141
142struct result
143{
144 const char *name;
145 int err;
146};
147
148/* strings in result structure are in static storage */
149static struct result
150run_ttyname (int fd)
151{
152 struct result ret;
153 errno = 0;
154 ret.name = ttyname (fd);
155 ret.err = errno;
156 return ret;
157}
158
159static bool
160eq_ttyname (struct result actual, struct result expected)
161{
162 char *actual_name, *expected_name;
163
164 if ((actual.err == expected.err) &&
165 (!actual.name == !expected.name) &&
166 (actual.name ? strcmp (actual.name, expected.name) == 0 : true))
167 {
168 if (expected.name)
169 expected_name = xasprintf ("\"%s\"", expected.name);
170 else
171 expected_name = xstrdup ("NULL");
172
173 printf ("info: ttyname: PASS {name=%s, errno=%d}\n",
174 expected_name, expected.err);
175
176 free (expected_name);
177 return true;
178 }
179
180 if (actual.name)
181 actual_name = xasprintf ("\"%s\"", actual.name);
182 else
183 actual_name = xstrdup ("NULL");
184
185 if (expected.name)
186 expected_name = xasprintf ("\"%s\"", expected.name);
187 else
188 expected_name = xstrdup ("NULL");
189
190 printf ("error: ttyname: actual {name=%s, errno=%d} != expected {name=%s, errno=%d}\n",
191 actual_name, actual.err,
192 expected_name, expected.err);
193
194 free (actual_name);
195 free (expected_name);
196 return false;
197}
198
199/* ttyname_r runner */
200
201struct result_r
202{
203 const char *name;
204 int ret;
205 int err;
206};
207
208/* strings in result structure are in static storage */
209static struct result_r
210run_ttyname_r (int fd)
211{
212 static char buf[TTY_NAME_MAX];
213
214 struct result_r ret;
215 errno = 0;
216 ret.ret = ttyname_r (fd, buf, TTY_NAME_MAX);
217 ret.err = errno;
218 if (ret.ret == 0)
219 ret.name = buf;
220 else
221 ret.name = NULL;
222 return ret;
223}
224
225static bool
226eq_ttyname_r (struct result_r actual, struct result_r expected)
227{
228 char *actual_name, *expected_name;
229
230 if ((actual.err == expected.err) &&
231 (actual.ret == expected.ret) &&
232 (!actual.name == !expected.name) &&
233 (actual.name ? strcmp (actual.name, expected.name) == 0 : true))
234 {
235 if (expected.name)
236 expected_name = xasprintf ("\"%s\"", expected.name);
237 else
238 expected_name = xstrdup ("NULL");
239
240 printf ("info: ttyname_r: PASS {name=%s, ret=%d, errno=%d}\n",
241 expected_name, expected.ret, expected.err);
242
243 free (expected_name);
244 return true;
245 }
246
247 if (actual.name)
248 actual_name = xasprintf ("\"%s\"", actual.name);
249 else
250 actual_name = xstrdup ("NULL");
251
252 if (expected.name)
253 expected_name = xasprintf ("\"%s\"", expected.name);
254 else
255 expected_name = xstrdup ("NULL");
256
257 printf ("error: ttyname_r: actual {name=%s, ret=%d, errno=%d} != expected {name=%s, ret=%d, errno=%d}\n",
258 actual_name, actual.ret, actual.err,
259 expected_name, expected.ret, expected.err);
260
261 free (actual_name);
262 free (expected_name);
263 return false;
264}
265
266/* combined runner */
267
268static bool
269doit (int fd, const char *testname, struct result_r expected_r)
270{
271 struct result expected = {.name=expected_r.name, .err=expected_r.ret};
272 bool ret = true;
273
274 printf ("info: testcase: %s\n", testname);
275
276 if (!eq_ttyname (run_ttyname (fd), expected))
277 ret = false;
278 if (!eq_ttyname_r (run_ttyname_r (fd), expected_r))
279 ret = false;
280
281 if (!ret)
282 support_record_failure ();
283
284 return ret;
285}
286
287/* chroot setup */
288
289static char *chrootdir;
290
291static void
292prepare (int argc, char **argv)
293{
294 chrootdir = xasprintf ("%s/tst-ttyname-XXXXXX", test_dir);
295 if (mkdtemp (chrootdir) == NULL)
296 FAIL_EXIT1 ("mkdtemp (\"%s\"): %m", chrootdir);
297 add_temp_file (chrootdir);
298}
299#define PREPARE prepare
300
301/* These chroot setup functions put the TTY at at "/console" (where it
302 won't be found by ttyname), and create "/dev/console" as an
303 ordinary file. This way, it's easier to write test-cases that
304 expect ttyname to fail; test-cases that expect it to succeed need
305 to explicitly remount it at "/dev/console". */
306
307static int
308do_in_chroot_1 (int (*cb)(const char *, int))
309{
310 printf ("info: entering chroot 1\n");
311
312 /* Open the PTS that we'll be testing on. */
313 int master;
314 char *slavename;
315 VERIFY ((master = posix_openpt (O_RDWR|O_NOCTTY|O_NONBLOCK)) >= 0);
316 VERIFY ((slavename = ptsname (master)));
317 VERIFY (unlockpt (master) == 0);
318 if (strncmp (slavename, "/dev/pts/", 9) != 0)
319 FAIL_UNSUPPORTED ("slave pseudo-terminal is not under /dev/pts/: %s",
320 slavename);
321 int slave = xopen (slavename, O_RDWR, 0);
322 if (!doit (slave, "basic smoketest",
323 (struct result_r){.name=slavename, .ret=0, .err=0}))
324 return 1;
325
326 pid_t pid = xfork ();
327 if (pid == 0)
328 {
329 xclose (master);
330
331 become_root_in_mount_ns ();
332
333 VERIFY (mount ("tmpfs", chrootdir, "tmpfs", 0, "mode=755") == 0);
334 VERIFY (chdir (chrootdir) == 0);
335
336 xmkdir ("proc", 0755);
337 xmkdir ("dev", 0755);
338 xmkdir ("dev/pts", 0755);
339
340 VERIFY (mount ("/proc", "proc", NULL, MS_BIND|MS_REC, NULL) == 0);
341 VERIFY (mount ("devpts", "dev/pts", "devpts",
342 MS_NOSUID|MS_NOEXEC,
343 "newinstance,ptmxmode=0666,mode=620") == 0);
344 VERIFY (symlink ("pts/ptmx", "dev/ptmx") == 0);
345
346 touch ("console", 0);
347 touch ("dev/console", 0);
348 VERIFY (mount (slavename, "console", NULL, MS_BIND, NULL) == 0);
349
350 xchroot (".");
351
352 char *linkname = xasprintf ("/proc/self/fd/%d", slave);
353 char *target = proc_fd_readlink (linkname);
354 VERIFY (strcmp (target, slavename) == 0);
355 free (linkname);
356
357 _exit (cb (slavename, slave));
358 }
359 int status;
360 xwaitpid (pid, &status, 0);
361 VERIFY (WIFEXITED (status));
362 xclose (master);
363 xclose (slave);
364 return WEXITSTATUS (status);
365}
366
367static int
368do_in_chroot_2 (int (*cb)(const char *, int))
369{
370 printf ("info: entering chroot 2\n");
371
372 int pid_pipe[2];
373 xpipe (pid_pipe);
374 int exit_pipe[2];
375 xpipe (exit_pipe);
376
377 /* Open the PTS that we'll be testing on. */
378 int master;
379 char *slavename;
380 VERIFY ((master = posix_openpt (O_RDWR|O_NOCTTY|O_NONBLOCK)) >= 0);
381 VERIFY ((slavename = ptsname (master)));
382 VERIFY (unlockpt (master) == 0);
383 if (strncmp (slavename, "/dev/pts/", 9) != 0)
384 FAIL_UNSUPPORTED ("slave pseudo-terminal is not under /dev/pts/: %s",
385 slavename);
386 /* wait until in a new mount ns to open the slave */
387
388 /* enable `wait`ing on grandchildren */
389 VERIFY (prctl (PR_SET_CHILD_SUBREAPER, 1) == 0);
390
391 pid_t pid = xfork (); /* outer child */
392 if (pid == 0)
393 {
394 xclose (master);
395 xclose (pid_pipe[0]);
396 xclose (exit_pipe[1]);
397
398 become_root_in_mount_ns ();
399
400 int slave = xopen (slavename, O_RDWR, 0);
401 if (!doit (slave, "basic smoketest",
402 (struct result_r){.name=slavename, .ret=0, .err=0}))
403 _exit (1);
404
405 VERIFY (mount ("tmpfs", chrootdir, "tmpfs", 0, "mode=755") == 0);
406 VERIFY (chdir (chrootdir) == 0);
407
408 xmkdir ("proc", 0755);
409 xmkdir ("dev", 0755);
410 xmkdir ("dev/pts", 0755);
411
412 VERIFY (mount ("devpts", "dev/pts", "devpts",
413 MS_NOSUID|MS_NOEXEC,
414 "newinstance,ptmxmode=0666,mode=620") == 0);
415 VERIFY (symlink ("pts/ptmx", "dev/ptmx") == 0);
416
417 touch ("console", 0);
418 touch ("dev/console", 0);
419 VERIFY (mount (slavename, "console", NULL, MS_BIND, NULL) == 0);
420
421 xchroot (".");
422
423 if (unshare (CLONE_NEWNS | CLONE_NEWPID) < 0)
424 FAIL_UNSUPPORTED ("could not enter new PID namespace");
425 pid = xfork (); /* inner child */
426 if (pid == 0)
427 {
428 xclose (pid_pipe[1]);
429
430 /* wait until the outer child has exited */
431 char c;
432 VERIFY (read (exit_pipe[0], &c, 1) == 0);
433 xclose (exit_pipe[0]);
434
435 VERIFY (mount ("proc", "/proc", "proc",
436 MS_NOSUID|MS_NOEXEC|MS_NODEV, NULL) == 0);
437
438 char *linkname = xasprintf ("/proc/self/fd/%d", slave);
439 char *target = proc_fd_readlink (linkname);
440 VERIFY (strcmp (target, strrchr (slavename, '/')) == 0);
441 free (linkname);
442
443 _exit (cb (slavename, slave));
444 }
445 xwrite (pid_pipe[1], &pid, sizeof pid);
446 _exit (0);
447 }
448 xclose (pid_pipe[1]);
449 xclose (exit_pipe[0]);
450 xclose (exit_pipe[1]);
451
452 /* wait for the outer child */
453 int status;
454 xwaitpid (pid, &status, 0);
455 VERIFY (WIFEXITED (status));
456 int ret = WEXITSTATUS (status);
457 if (ret != 0)
458 return ret;
459
460 /* set 'pid' to the inner child */
461 VERIFY (read (pid_pipe[0], &pid, sizeof pid) == sizeof pid);
462 xclose (pid_pipe[0]);
463
464 /* wait for the inner child */
465 xwaitpid (pid, &status, 0);
466 VERIFY (WIFEXITED (status));
467 xclose (master);
468 return WEXITSTATUS (status);
469}
470
471/* main test */
472
473static int
474run_chroot_tests (const char *slavename, int slave)
475{
476 struct stat st;
477 bool ok = true;
478
479 /* There are 3 groups of tests here. The first group fairly
480 generically does things known to mess up ttyname, and verifies
481 that ttyname copes correctly. The remaining groups are
482 increasingly convoluted, as we target specific parts of ttyname
483 to try to confuse. */
484
485 /* Basic tests that it doesn't get confused by multiple devpts
486 instances. */
487 {
488 VERIFY (stat (slavename, &st) < 0); /* sanity check */
489 if (!doit (slave, "no conflict, no match",
490 (struct result_r){.name=NULL, .ret=ENODEV, .err=ENODEV}))
491 ok = false;
492 VERIFY (mount ("/console", "/dev/console", NULL, MS_BIND, NULL) == 0);
493 if (!doit (slave, "no conflict, console",
494 (struct result_r){.name="/dev/console", .ret=0, .err=0}))
495 ok = false;
496 VERIFY (umount ("/dev/console") == 0);
497
498 /* keep creating PTYs until we we get a name collision */
499 while (stat (slavename, &st) < 0)
500 posix_openpt (O_RDWR|O_NOCTTY|O_NONBLOCK);
501 VERIFY (stat (slavename, &st) == 0);
502
503 if (!doit (slave, "conflict, no match",
504 (struct result_r){.name=NULL, .ret=ENODEV, .err=ENODEV}))
505 ok = false;
506 VERIFY (mount ("/console", "/dev/console", NULL, MS_BIND, NULL) == 0);
507 if (!doit (slave, "conflict, console",
508 (struct result_r){.name="/dev/console", .ret=0, .err=0}))
509 ok = false;
510 VERIFY (umount ("/dev/console") == 0);
511 }
512
513 /* The first tests kinda assumed that they hit certain code-paths
514 based on assuming that the readlink target is 'slavename', but
515 that's not quite always true. They're still a good preliminary
516 sanity check, so keep them, but let's add tests that make sure
517 that those code-paths are hit by doing a readlink ourself. */
518 {
519 char *linkname = xasprintf ("/proc/self/fd/%d", slave);
520 char *target = proc_fd_readlink (linkname);
521 free (linkname);
522 /* Depeding on how we set up the chroot, the kernel may or may not
523 trim the leading path to the target (it may give us "/6",
524 instead of "/dev/pts/6"). We test it both ways (do_in_chroot_1
525 and do_in_chroot_2). This test group relies on the target
526 existing, so guarantee that it does exist by creating it if
527 necessary. */
528 if (stat (target, &st) < 0)
529 {
530 VERIFY (errno == ENOENT);
531 touch (target, 0);
532 }
533
534 VERIFY (mount ("/console", "/dev/console", NULL, MS_BIND, NULL) == 0);
535 VERIFY (mount ("/console", target, NULL, MS_BIND, NULL) == 0);
536 if (!doit (slave, "with readlink target",
537 (struct result_r){.name=target, .ret=0, .err=0}))
538 ok = false;
539 VERIFY (umount (target) == 0);
540 VERIFY (umount ("/dev/console") == 0);
541
542 VERIFY (mount ("/console", "/dev/console", NULL, MS_BIND, NULL) == 0);
543 VERIFY (mount (slavename, target, NULL, MS_BIND, NULL) == 0);
544 if (!doit (slave, "with readlink trap; fallback",
545 (struct result_r){.name="/dev/console", .ret=0, .err=0}))
546 ok = false;
547 VERIFY (umount (target) == 0);
548 VERIFY (umount ("/dev/console") == 0);
549
550 VERIFY (mount (slavename, target, NULL, MS_BIND, NULL) == 0);
551 if (!doit (slave, "with readlink trap; no fallback",
552 (struct result_r){.name=NULL, .ret=ENODEV, .err=ENODEV}))
553 ok = false;
554 VERIFY (umount (target) == 0);
555 }
556
557 /* This test makes sure that everything still works OK if readdir
558 finds a pseudo-match before and/or after the actual match. Now,
559 to do that, we need to control that readdir finds the
560 pseudo-matches before and after the actual match; and there's no
561 good way to control that order in absence of whitebox testing.
562 So, just create 3 files, then use opendir/readdir to see what
563 order they are in, and assign meaning based on that order, not by
564 name; assigning the first to be a pseudo-match, the second to be
565 the actual match, and the third to be a pseudo-match. This
566 assumes that (on tmpfs) ordering within the directory is stable
567 in the absence of modification, which seems reasonably safe. */
568 {
569 /* since we're testing the fallback search, disable the readlink
570 happy-path */
571 VERIFY (umount2 ("/proc", MNT_DETACH) == 0);
572
573 touch ("/dev/console1", 0);
574 touch ("/dev/console2", 0);
575 touch ("/dev/console3", 0);
576
577 char *c[3];
578 int ci = 0;
579 DIR *dirstream = opendir ("/dev");
580 VERIFY (dirstream != NULL);
581 struct dirent *d;
582 while ((d = readdir (dirstream)) != NULL && ci < 3)
583 {
584 if (strcmp (d->d_name, "console1") &&
585 strcmp (d->d_name, "console2") &&
586 strcmp (d->d_name, "console3") )
587 continue;
588 c[ci++] = xasprintf ("/dev/%s", d->d_name);
589 }
590 VERIFY (ci == 3);
591 VERIFY (closedir (dirstream) == 0);
592
593 VERIFY (mount (slavename, c[0], NULL, MS_BIND, NULL) == 0);
594 VERIFY (mount ("/console", c[1], NULL, MS_BIND, NULL) == 0);
595 VERIFY (mount (slavename, c[2], NULL, MS_BIND, NULL) == 0);
596 VERIFY (umount2 ("/dev/pts", MNT_DETACH) == 0);
597 if (!doit (slave, "with search-path trap",
598 (struct result_r){.name=c[1], .ret=0, .err=0}))
599 ok = false;
600 for (int i = 0; i < 3; i++)
601 {
602 VERIFY (umount (c[i]) == 0);
603 VERIFY (unlink (c[i]) == 0);
604 free (c[i]);
605 }
606 }
607
608 return ok ? 0 : 1;
609}
610
611static int
612do_test (void)
613{
614 int ret1 = do_in_chroot_1 (run_chroot_tests);
615 if (ret1 == EXIT_UNSUPPORTED)
616 return ret1;
617
618 int ret2 = do_in_chroot_2 (run_chroot_tests);
619 if (ret2 == EXIT_UNSUPPORTED)
620 return ret2;
621
622 return ret1 | ret2;
623}
624
625#include <support/test-driver.c>