]>
git.ipfire.org Git - thirdparty/glibc.git/blob - sysdeps/unix/sysv/linux/tst-ttyname.c
c19bae70e896aa49f1a7c6f37e9340e06da8bf08
1 /* Copyright (C) 2017-2020 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 <https://www.gnu.org/licenses/>. */
26 #include <sys/mount.h>
27 #include <sys/prctl.h>
30 #include <sys/resource.h>
33 #include <support/check.h>
34 #include <support/namespace.h>
35 #include <support/support.h>
36 #include <support/temp_file.h>
37 #include <support/test-driver.h>
38 #include <support/xunistd.h>
40 /* generic utilities */
42 #define VERIFY(expr) \
46 printf ("error: %s:%d: %s: %m\n", \
47 __FILE__, __LINE__, #expr); \
53 touch (const char *path
, mode_t mode
)
55 xclose (xopen (path
, O_WRONLY
|O_CREAT
|O_NOCTTY
, mode
));
59 trim_prefix (char *str
, size_t str_len
, const char *prefix
)
61 size_t prefix_len
= strlen (prefix
);
62 if (str_len
> prefix_len
&& memcmp (str
, prefix
, prefix_len
) == 0)
64 memmove (str
, str
+ prefix_len
, str_len
- prefix_len
);
65 return str_len
- prefix_len
;
70 /* returns a pointer to static storage */
72 proc_fd_readlink (const char *linkname
)
74 static char target
[PATH_MAX
+1];
75 ssize_t target_len
= readlink (linkname
, target
, PATH_MAX
);
76 VERIFY (target_len
> 0);
77 target_len
= trim_prefix (target
, target_len
, "(unreachable)");
78 target
[target_len
] = '\0';
82 /* plain ttyname runner */
90 /* strings in result structure are in static storage */
96 ret
.name
= ttyname (fd
);
102 eq_ttyname (struct result actual
, struct result expected
)
104 char *actual_name
, *expected_name
;
106 if ((actual
.err
== expected
.err
)
107 && (!actual
.name
== !expected
.name
)
108 && (actual
.name
? strcmp (actual
.name
, expected
.name
) == 0 : true))
111 expected_name
= xasprintf ("\"%s\"", expected
.name
);
113 expected_name
= xstrdup ("NULL");
115 printf ("info: ttyname: PASS {name=%s, errno=%d}\n",
116 expected_name
, expected
.err
);
118 free (expected_name
);
123 actual_name
= xasprintf ("\"%s\"", actual
.name
);
125 actual_name
= xstrdup ("NULL");
128 expected_name
= xasprintf ("\"%s\"", expected
.name
);
130 expected_name
= xstrdup ("NULL");
132 printf ("error: ttyname: actual {name=%s, errno=%d} != expected {name=%s, errno=%d}\n",
133 actual_name
, actual
.err
,
134 expected_name
, expected
.err
);
137 free (expected_name
);
141 /* ttyname_r runner */
150 /* strings in result structure are in static storage */
151 static struct result_r
152 run_ttyname_r (int fd
)
154 static char buf
[TTY_NAME_MAX
];
158 ret
.ret
= ttyname_r (fd
, buf
, TTY_NAME_MAX
);
168 eq_ttyname_r (struct result_r actual
, struct result_r expected
)
170 char *actual_name
, *expected_name
;
172 if ((actual
.err
== expected
.err
)
173 && (actual
.ret
== expected
.ret
)
174 && (!actual
.name
== !expected
.name
)
175 && (actual
.name
? strcmp (actual
.name
, expected
.name
) == 0 : true))
178 expected_name
= xasprintf ("\"%s\"", expected
.name
);
180 expected_name
= xstrdup ("NULL");
182 printf ("info: ttyname_r: PASS {name=%s, ret=%d, errno=%d}\n",
183 expected_name
, expected
.ret
, expected
.err
);
185 free (expected_name
);
190 actual_name
= xasprintf ("\"%s\"", actual
.name
);
192 actual_name
= xstrdup ("NULL");
195 expected_name
= xasprintf ("\"%s\"", expected
.name
);
197 expected_name
= xstrdup ("NULL");
199 printf ("error: ttyname_r: actual {name=%s, ret=%d, errno=%d} != expected {name=%s, ret=%d, errno=%d}\n",
200 actual_name
, actual
.ret
, actual
.err
,
201 expected_name
, expected
.ret
, expected
.err
);
204 free (expected_name
);
208 /* combined runner */
211 doit (int fd
, const char *testname
, struct result_r expected_r
)
213 struct result expected
= {.name
=expected_r
.name
, .err
=expected_r
.ret
};
216 printf ("info: testcase: %s\n", testname
);
218 if (!eq_ttyname (run_ttyname (fd
), expected
))
220 if (!eq_ttyname_r (run_ttyname_r (fd
), expected_r
))
224 support_record_failure ();
231 static char *chrootdir
;
234 prepare (int argc
, char **argv
)
236 chrootdir
= xasprintf ("%s/tst-ttyname-XXXXXX", test_dir
);
237 if (mkdtemp (chrootdir
) == NULL
)
238 FAIL_EXIT1 ("mkdtemp (\"%s\"): %m", chrootdir
);
239 add_temp_file (chrootdir
);
241 #define PREPARE prepare
243 /* Adjust the file limit so that we have a chance to open PTY. */
245 adjust_file_limit (const char *pty
)
248 if (sscanf (pty
, "/dev/pts/%d", &number
) != 1 || number
< 0)
249 FAIL_EXIT1 ("invalid PTY name: \"%s\"", pty
);
251 /* Add a few additional descriptors to cover standard I/O streams
253 rlim_t desired_limit
= number
+ 10;
256 if (getrlimit (RLIMIT_NOFILE
, &lim
) != 0)
257 FAIL_EXIT1 ("getrlimit (RLIMIT_NOFILE): %m");
258 if (lim
.rlim_cur
< desired_limit
)
260 printf ("info: adjusting RLIMIT_NOFILE from %llu to %llu\n",
261 (unsigned long long int) lim
.rlim_cur
,
262 (unsigned long long int) desired_limit
);
263 lim
.rlim_cur
= desired_limit
;
264 if (setrlimit (RLIMIT_NOFILE
, &lim
) != 0)
265 printf ("warning: setrlimit (RLIMIT_NOFILE) failed: %m\n");
269 /* These chroot setup functions put the TTY at at "/console" (where it
270 won't be found by ttyname), and create "/dev/console" as an
271 ordinary file. This way, it's easier to write test-cases that
272 expect ttyname to fail; test-cases that expect it to succeed need
273 to explicitly remount it at "/dev/console". */
276 do_in_chroot_1 (int (*cb
)(const char *, int))
278 printf ("info: entering chroot 1\n");
280 /* Open the PTS that we'll be testing on. */
283 master
= posix_openpt (O_RDWR
|O_NOCTTY
|O_NONBLOCK
);
287 FAIL_UNSUPPORTED ("posix_openpt: %m");
289 FAIL_EXIT1 ("posix_openpt: %m");
291 VERIFY ((slavename
= ptsname (master
)));
292 VERIFY (unlockpt (master
) == 0);
293 if (strncmp (slavename
, "/dev/pts/", 9) != 0)
294 FAIL_UNSUPPORTED ("slave pseudo-terminal is not under /dev/pts/: %s",
296 adjust_file_limit (slavename
);
297 int slave
= xopen (slavename
, O_RDWR
, 0);
298 if (!doit (slave
, "basic smoketest",
299 (struct result_r
){.name
=slavename
, .ret
=0, .err
=0}))
302 pid_t pid
= xfork ();
307 if (!support_enter_mount_namespace ())
308 FAIL_UNSUPPORTED ("could not enter new mount namespace");
310 VERIFY (mount ("tmpfs", chrootdir
, "tmpfs", 0, "mode=755") == 0);
311 VERIFY (chdir (chrootdir
) == 0);
313 xmkdir ("proc", 0755);
314 xmkdir ("dev", 0755);
315 xmkdir ("dev/pts", 0755);
317 VERIFY (mount ("/proc", "proc", NULL
, MS_BIND
|MS_REC
, NULL
) == 0);
318 VERIFY (mount ("devpts", "dev/pts", "devpts",
320 "newinstance,ptmxmode=0666,mode=620") == 0);
321 VERIFY (symlink ("pts/ptmx", "dev/ptmx") == 0);
323 touch ("console", 0);
324 touch ("dev/console", 0);
325 VERIFY (mount (slavename
, "console", NULL
, MS_BIND
, NULL
) == 0);
329 char *linkname
= xasprintf ("/proc/self/fd/%d", slave
);
330 char *target
= proc_fd_readlink (linkname
);
331 VERIFY (strcmp (target
, slavename
) == 0);
334 _exit (cb (slavename
, slave
));
337 xwaitpid (pid
, &status
, 0);
338 VERIFY (WIFEXITED (status
));
341 return WEXITSTATUS (status
);
345 do_in_chroot_2 (int (*cb
)(const char *, int))
347 printf ("info: entering chroot 2\n");
354 /* Open the PTS that we'll be testing on. */
357 VERIFY ((master
= posix_openpt (O_RDWR
|O_NOCTTY
|O_NONBLOCK
)) >= 0);
358 VERIFY ((slavename
= ptsname (master
)));
359 VERIFY (unlockpt (master
) == 0);
360 if (strncmp (slavename
, "/dev/pts/", 9) != 0)
361 FAIL_UNSUPPORTED ("slave pseudo-terminal is not under /dev/pts/: %s",
363 adjust_file_limit (slavename
);
364 /* wait until in a new mount ns to open the slave */
366 /* enable `wait`ing on grandchildren */
367 VERIFY (prctl (PR_SET_CHILD_SUBREAPER
, 1) == 0);
369 pid_t pid
= xfork (); /* outer child */
373 xclose (pid_pipe
[0]);
374 xclose (exit_pipe
[1]);
376 if (!support_enter_mount_namespace ())
377 FAIL_UNSUPPORTED ("could not enter new mount namespace");
379 int slave
= xopen (slavename
, O_RDWR
, 0);
380 if (!doit (slave
, "basic smoketest",
381 (struct result_r
){.name
=slavename
, .ret
=0, .err
=0}))
384 VERIFY (mount ("tmpfs", chrootdir
, "tmpfs", 0, "mode=755") == 0);
385 VERIFY (chdir (chrootdir
) == 0);
387 xmkdir ("proc", 0755);
388 xmkdir ("dev", 0755);
389 xmkdir ("dev/pts", 0755);
391 VERIFY (mount ("devpts", "dev/pts", "devpts",
393 "newinstance,ptmxmode=0666,mode=620") == 0);
394 VERIFY (symlink ("pts/ptmx", "dev/ptmx") == 0);
396 touch ("console", 0);
397 touch ("dev/console", 0);
398 VERIFY (mount (slavename
, "console", NULL
, MS_BIND
, NULL
) == 0);
402 if (unshare (CLONE_NEWNS
| CLONE_NEWPID
) < 0)
403 FAIL_UNSUPPORTED ("could not enter new PID namespace");
404 pid
= xfork (); /* inner child */
407 xclose (pid_pipe
[1]);
409 /* wait until the outer child has exited */
411 VERIFY (read (exit_pipe
[0], &c
, 1) == 0);
412 xclose (exit_pipe
[0]);
414 VERIFY (mount ("proc", "/proc", "proc",
415 MS_NOSUID
|MS_NOEXEC
|MS_NODEV
, NULL
) == 0);
417 char *linkname
= xasprintf ("/proc/self/fd/%d", slave
);
418 char *target
= proc_fd_readlink (linkname
);
419 VERIFY (strcmp (target
, strrchr (slavename
, '/')) == 0);
422 _exit (cb (slavename
, slave
));
424 xwrite (pid_pipe
[1], &pid
, sizeof pid
);
427 xclose (pid_pipe
[1]);
428 xclose (exit_pipe
[0]);
429 xclose (exit_pipe
[1]);
431 /* wait for the outer child */
433 xwaitpid (pid
, &status
, 0);
434 VERIFY (WIFEXITED (status
));
435 int ret
= WEXITSTATUS (status
);
439 /* set 'pid' to the inner child */
440 VERIFY (read (pid_pipe
[0], &pid
, sizeof pid
) == sizeof pid
);
441 xclose (pid_pipe
[0]);
443 /* wait for the inner child */
444 xwaitpid (pid
, &status
, 0);
445 VERIFY (WIFEXITED (status
));
447 return WEXITSTATUS (status
);
453 run_chroot_tests (const char *slavename
, int slave
)
458 /* There are 3 groups of tests here. The first group fairly
459 generically does things known to mess up ttyname, and verifies
460 that ttyname copes correctly. The remaining groups are
461 increasingly convoluted, as we target specific parts of ttyname
462 to try to confuse. */
464 /* Basic tests that it doesn't get confused by multiple devpts
467 VERIFY (stat (slavename
, &st
) < 0); /* sanity check */
468 if (!doit (slave
, "no conflict, no match",
469 (struct result_r
){.name
=NULL
, .ret
=ENODEV
, .err
=ENODEV
}))
471 VERIFY (mount ("/console", "/dev/console", NULL
, MS_BIND
, NULL
) == 0);
472 if (!doit (slave
, "no conflict, console",
473 (struct result_r
){.name
="/dev/console", .ret
=0, .err
=0}))
475 VERIFY (umount ("/dev/console") == 0);
477 /* Keep creating PTYs until we we get a name collision. */
480 if (stat (slavename
, &st
) == 0)
482 if (posix_openpt (O_RDWR
|O_NOCTTY
|O_NONBLOCK
) < 0)
484 if (errno
== ENOSPC
|| errno
== EMFILE
|| errno
== ENFILE
)
485 FAIL_UNSUPPORTED ("cannot re-create PTY \"%s\" in chroot: %m"
486 " (consider increasing limits)", slavename
);
488 FAIL_EXIT1 ("cannot re-create PTY \"%s\" chroot: %m", slavename
);
492 if (!doit (slave
, "conflict, no match",
493 (struct result_r
){.name
=NULL
, .ret
=ENODEV
, .err
=ENODEV
}))
495 VERIFY (mount ("/console", "/dev/console", NULL
, MS_BIND
, NULL
) == 0);
496 if (!doit (slave
, "conflict, console",
497 (struct result_r
){.name
="/dev/console", .ret
=0, .err
=0}))
499 VERIFY (umount ("/dev/console") == 0);
502 /* The first tests kinda assumed that they hit certain code-paths
503 based on assuming that the readlink target is 'slavename', but
504 that's not quite always true. They're still a good preliminary
505 sanity check, so keep them, but let's add tests that make sure
506 that those code-paths are hit by doing a readlink ourself. */
508 char *linkname
= xasprintf ("/proc/self/fd/%d", slave
);
509 char *target
= proc_fd_readlink (linkname
);
511 /* Depeding on how we set up the chroot, the kernel may or may not
512 trim the leading path to the target (it may give us "/6",
513 instead of "/dev/pts/6"). We test it both ways (do_in_chroot_1
514 and do_in_chroot_2). This test group relies on the target
515 existing, so guarantee that it does exist by creating it if
517 if (stat (target
, &st
) < 0)
519 VERIFY (errno
== ENOENT
);
523 VERIFY (mount ("/console", "/dev/console", NULL
, MS_BIND
, NULL
) == 0);
524 VERIFY (mount ("/console", target
, NULL
, MS_BIND
, NULL
) == 0);
525 if (!doit (slave
, "with readlink target",
526 (struct result_r
){.name
=target
, .ret
=0, .err
=0}))
528 VERIFY (umount (target
) == 0);
529 VERIFY (umount ("/dev/console") == 0);
531 VERIFY (mount ("/console", "/dev/console", NULL
, MS_BIND
, NULL
) == 0);
532 VERIFY (mount (slavename
, target
, NULL
, MS_BIND
, NULL
) == 0);
533 if (!doit (slave
, "with readlink trap; fallback",
534 (struct result_r
){.name
="/dev/console", .ret
=0, .err
=0}))
536 VERIFY (umount (target
) == 0);
537 VERIFY (umount ("/dev/console") == 0);
539 VERIFY (mount (slavename
, target
, NULL
, MS_BIND
, NULL
) == 0);
540 if (!doit (slave
, "with readlink trap; no fallback",
541 (struct result_r
){.name
=NULL
, .ret
=ENODEV
, .err
=ENODEV
}))
543 VERIFY (umount (target
) == 0);
546 /* This test makes sure that everything still works OK if readdir
547 finds a pseudo-match before and/or after the actual match. Now,
548 to do that, we need to control that readdir finds the
549 pseudo-matches before and after the actual match; and there's no
550 good way to control that order in absence of whitebox testing.
551 So, just create 3 files, then use opendir/readdir to see what
552 order they are in, and assign meaning based on that order, not by
553 name; assigning the first to be a pseudo-match, the second to be
554 the actual match, and the third to be a pseudo-match. This
555 assumes that (on tmpfs) ordering within the directory is stable
556 in the absence of modification, which seems reasonably safe. */
558 /* since we're testing the fallback search, disable the readlink
560 VERIFY (umount2 ("/proc", MNT_DETACH
) == 0);
562 touch ("/dev/console1", 0);
563 touch ("/dev/console2", 0);
564 touch ("/dev/console3", 0);
568 DIR *dirstream
= opendir ("/dev");
569 VERIFY (dirstream
!= NULL
);
571 while ((d
= readdir (dirstream
)) != NULL
&& ci
< 3)
573 if (strcmp (d
->d_name
, "console1")
574 && strcmp (d
->d_name
, "console2")
575 && strcmp (d
->d_name
, "console3") )
577 c
[ci
++] = xasprintf ("/dev/%s", d
->d_name
);
580 VERIFY (closedir (dirstream
) == 0);
582 VERIFY (mount (slavename
, c
[0], NULL
, MS_BIND
, NULL
) == 0);
583 VERIFY (mount ("/console", c
[1], NULL
, MS_BIND
, NULL
) == 0);
584 VERIFY (mount (slavename
, c
[2], NULL
, MS_BIND
, NULL
) == 0);
585 VERIFY (umount2 ("/dev/pts", MNT_DETACH
) == 0);
586 if (!doit (slave
, "with search-path trap",
587 (struct result_r
){.name
=c
[1], .ret
=0, .err
=0}))
589 for (int i
= 0; i
< 3; i
++)
591 VERIFY (umount (c
[i
]) == 0);
592 VERIFY (unlink (c
[i
]) == 0);
603 support_become_root ();
605 int ret1
= do_in_chroot_1 (run_chroot_tests
);
606 if (ret1
== EXIT_UNSUPPORTED
)
609 int ret2
= do_in_chroot_2 (run_chroot_tests
);
610 if (ret2
== EXIT_UNSUPPORTED
)
616 #include <support/test-driver.c>