]>
git.ipfire.org Git - thirdparty/glibc.git/blob - 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.
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.
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.
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/>. */
26 #include <sys/mount.h>
27 #include <sys/prctl.h>
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>
39 /* generic utilities */
41 #define VERIFY(expr) \
45 printf ("error: %s:%d: %s: %m\n", \
46 __FILE__, __LINE__, #expr); \
52 touch (const char *path
, mode_t mode
)
54 xclose (xopen (path
, O_WRONLY
|O_CREAT
|O_NOCTTY
, mode
));
58 trim_prefix (char *str
, size_t str_len
, const char *prefix
)
60 size_t prefix_len
= strlen (prefix
);
61 if (str_len
> prefix_len
&& memcmp (str
, prefix
, prefix_len
) == 0)
63 memmove (str
, str
+ prefix_len
, str_len
- prefix_len
);
64 return str_len
- prefix_len
;
69 /* returns a pointer to static storage */
71 proc_fd_readlink (const char *linkname
)
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';
81 /* plain ttyname runner */
89 /* strings in result structure are in static storage */
95 ret
.name
= ttyname (fd
);
101 eq_ttyname (struct result actual
, struct result expected
)
103 char *actual_name
, *expected_name
;
105 if ((actual
.err
== expected
.err
) &&
106 (!actual
.name
== !expected
.name
) &&
107 (actual
.name
? strcmp (actual
.name
, expected
.name
) == 0 : true))
110 expected_name
= xasprintf ("\"%s\"", expected
.name
);
112 expected_name
= xstrdup ("NULL");
114 printf ("info: ttyname: PASS {name=%s, errno=%d}\n",
115 expected_name
, expected
.err
);
117 free (expected_name
);
122 actual_name
= xasprintf ("\"%s\"", actual
.name
);
124 actual_name
= xstrdup ("NULL");
127 expected_name
= xasprintf ("\"%s\"", expected
.name
);
129 expected_name
= xstrdup ("NULL");
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
);
136 free (expected_name
);
140 /* ttyname_r runner */
149 /* strings in result structure are in static storage */
150 static struct result_r
151 run_ttyname_r (int fd
)
153 static char buf
[TTY_NAME_MAX
];
157 ret
.ret
= ttyname_r (fd
, buf
, TTY_NAME_MAX
);
167 eq_ttyname_r (struct result_r actual
, struct result_r expected
)
169 char *actual_name
, *expected_name
;
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))
177 expected_name
= xasprintf ("\"%s\"", expected
.name
);
179 expected_name
= xstrdup ("NULL");
181 printf ("info: ttyname_r: PASS {name=%s, ret=%d, errno=%d}\n",
182 expected_name
, expected
.ret
, expected
.err
);
184 free (expected_name
);
189 actual_name
= xasprintf ("\"%s\"", actual
.name
);
191 actual_name
= xstrdup ("NULL");
194 expected_name
= xasprintf ("\"%s\"", expected
.name
);
196 expected_name
= xstrdup ("NULL");
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
);
203 free (expected_name
);
207 /* combined runner */
210 doit (int fd
, const char *testname
, struct result_r expected_r
)
212 struct result expected
= {.name
=expected_r
.name
, .err
=expected_r
.ret
};
215 printf ("info: testcase: %s\n", testname
);
217 if (!eq_ttyname (run_ttyname (fd
), expected
))
219 if (!eq_ttyname_r (run_ttyname_r (fd
), expected_r
))
223 support_record_failure ();
230 static char *chrootdir
;
233 prepare (int argc
, char **argv
)
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
);
240 #define PREPARE prepare
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". */
249 do_in_chroot_1 (int (*cb
)(const char *, int))
251 printf ("info: entering chroot 1\n");
253 /* Open the PTS that we'll be testing on. */
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",
262 int slave
= xopen (slavename
, O_RDWR
, 0);
263 if (!doit (slave
, "basic smoketest",
264 (struct result_r
){.name
=slavename
, .ret
=0, .err
=0}))
267 pid_t pid
= xfork ();
272 if (!support_enter_mount_namespace ())
273 FAIL_UNSUPPORTED ("could not enter new mount namespace");
275 VERIFY (mount ("tmpfs", chrootdir
, "tmpfs", 0, "mode=755") == 0);
276 VERIFY (chdir (chrootdir
) == 0);
278 xmkdir ("proc", 0755);
279 xmkdir ("dev", 0755);
280 xmkdir ("dev/pts", 0755);
282 VERIFY (mount ("/proc", "proc", NULL
, MS_BIND
|MS_REC
, NULL
) == 0);
283 VERIFY (mount ("devpts", "dev/pts", "devpts",
285 "newinstance,ptmxmode=0666,mode=620") == 0);
286 VERIFY (symlink ("pts/ptmx", "dev/ptmx") == 0);
288 touch ("console", 0);
289 touch ("dev/console", 0);
290 VERIFY (mount (slavename
, "console", NULL
, MS_BIND
, NULL
) == 0);
294 char *linkname
= xasprintf ("/proc/self/fd/%d", slave
);
295 char *target
= proc_fd_readlink (linkname
);
296 VERIFY (strcmp (target
, slavename
) == 0);
299 _exit (cb (slavename
, slave
));
302 xwaitpid (pid
, &status
, 0);
303 VERIFY (WIFEXITED (status
));
306 return WEXITSTATUS (status
);
310 do_in_chroot_2 (int (*cb
)(const char *, int))
312 printf ("info: entering chroot 2\n");
319 /* Open the PTS that we'll be testing on. */
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",
328 /* wait until in a new mount ns to open the slave */
330 /* enable `wait`ing on grandchildren */
331 VERIFY (prctl (PR_SET_CHILD_SUBREAPER
, 1) == 0);
333 pid_t pid
= xfork (); /* outer child */
337 xclose (pid_pipe
[0]);
338 xclose (exit_pipe
[1]);
340 if (!support_enter_mount_namespace ())
341 FAIL_UNSUPPORTED ("could not enter new mount namespace");
343 int slave
= xopen (slavename
, O_RDWR
, 0);
344 if (!doit (slave
, "basic smoketest",
345 (struct result_r
){.name
=slavename
, .ret
=0, .err
=0}))
348 VERIFY (mount ("tmpfs", chrootdir
, "tmpfs", 0, "mode=755") == 0);
349 VERIFY (chdir (chrootdir
) == 0);
351 xmkdir ("proc", 0755);
352 xmkdir ("dev", 0755);
353 xmkdir ("dev/pts", 0755);
355 VERIFY (mount ("devpts", "dev/pts", "devpts",
357 "newinstance,ptmxmode=0666,mode=620") == 0);
358 VERIFY (symlink ("pts/ptmx", "dev/ptmx") == 0);
360 touch ("console", 0);
361 touch ("dev/console", 0);
362 VERIFY (mount (slavename
, "console", NULL
, MS_BIND
, NULL
) == 0);
366 if (unshare (CLONE_NEWNS
| CLONE_NEWPID
) < 0)
367 FAIL_UNSUPPORTED ("could not enter new PID namespace");
368 pid
= xfork (); /* inner child */
371 xclose (pid_pipe
[1]);
373 /* wait until the outer child has exited */
375 VERIFY (read (exit_pipe
[0], &c
, 1) == 0);
376 xclose (exit_pipe
[0]);
378 VERIFY (mount ("proc", "/proc", "proc",
379 MS_NOSUID
|MS_NOEXEC
|MS_NODEV
, NULL
) == 0);
381 char *linkname
= xasprintf ("/proc/self/fd/%d", slave
);
382 char *target
= proc_fd_readlink (linkname
);
383 VERIFY (strcmp (target
, strrchr (slavename
, '/')) == 0);
386 _exit (cb (slavename
, slave
));
388 xwrite (pid_pipe
[1], &pid
, sizeof pid
);
391 xclose (pid_pipe
[1]);
392 xclose (exit_pipe
[0]);
393 xclose (exit_pipe
[1]);
395 /* wait for the outer child */
397 xwaitpid (pid
, &status
, 0);
398 VERIFY (WIFEXITED (status
));
399 int ret
= WEXITSTATUS (status
);
403 /* set 'pid' to the inner child */
404 VERIFY (read (pid_pipe
[0], &pid
, sizeof pid
) == sizeof pid
);
405 xclose (pid_pipe
[0]);
407 /* wait for the inner child */
408 xwaitpid (pid
, &status
, 0);
409 VERIFY (WIFEXITED (status
));
411 return WEXITSTATUS (status
);
417 run_chroot_tests (const char *slavename
, int slave
)
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. */
428 /* Basic tests that it doesn't get confused by multiple devpts
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
}))
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}))
439 VERIFY (umount ("/dev/console") == 0);
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);
446 if (!doit (slave
, "conflict, no match",
447 (struct result_r
){.name
=NULL
, .ret
=ENODEV
, .err
=ENODEV
}))
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}))
453 VERIFY (umount ("/dev/console") == 0);
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. */
462 char *linkname
= xasprintf ("/proc/self/fd/%d", slave
);
463 char *target
= proc_fd_readlink (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
471 if (stat (target
, &st
) < 0)
473 VERIFY (errno
== ENOENT
);
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}))
482 VERIFY (umount (target
) == 0);
483 VERIFY (umount ("/dev/console") == 0);
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}))
490 VERIFY (umount (target
) == 0);
491 VERIFY (umount ("/dev/console") == 0);
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
}))
497 VERIFY (umount (target
) == 0);
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. */
512 /* since we're testing the fallback search, disable the readlink
514 VERIFY (umount2 ("/proc", MNT_DETACH
) == 0);
516 touch ("/dev/console1", 0);
517 touch ("/dev/console2", 0);
518 touch ("/dev/console3", 0);
522 DIR *dirstream
= opendir ("/dev");
523 VERIFY (dirstream
!= NULL
);
525 while ((d
= readdir (dirstream
)) != NULL
&& ci
< 3)
527 if (strcmp (d
->d_name
, "console1") &&
528 strcmp (d
->d_name
, "console2") &&
529 strcmp (d
->d_name
, "console3") )
531 c
[ci
++] = xasprintf ("/dev/%s", d
->d_name
);
534 VERIFY (closedir (dirstream
) == 0);
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}))
543 for (int i
= 0; i
< 3; i
++)
545 VERIFY (umount (c
[i
]) == 0);
546 VERIFY (unlink (c
[i
]) == 0);
557 support_become_root ();
559 int ret1
= do_in_chroot_1 (run_chroot_tests
);
560 if (ret1
== EXIT_UNSUPPORTED
)
563 int ret2
= do_in_chroot_2 (run_chroot_tests
);
564 if (ret2
== EXIT_UNSUPPORTED
)
570 #include <support/test-driver.c>