1 /* Common definitions for ttyname tests.
2 Copyright (C) 2017-2024 Free Software Foundation, Inc.
3 This file is part of the GNU C Library.
5 The GNU C Library is free software; you can redistribute it and/or
6 modify it under the terms of the GNU Lesser General Public License as
7 published by the Free Software Foundation; either version 2.1 of the
8 License, or (at your option) any later version.
10 The GNU C Library is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 Lesser General Public License for more details.
15 You should have received a copy of the GNU Lesser General Public
16 License along with the GNU C Library; see the file COPYING.LIB. If
17 not, see <https://www.gnu.org/licenses/>. */
27 #include <sys/mount.h>
28 #include <sys/resource.h>
32 #include <support/check.h>
33 #include <support/support.h>
34 #include <support/temp_file.h>
35 #include <support/test-driver.h>
36 #include <support/xunistd.h>
38 /* generic utilities */
40 #define VERIFY(expr) \
44 printf ("error: %s:%d: %s: %m\n", \
45 __FILE__, __LINE__, #expr); \
51 touch (const char *path
, mode_t mode
)
53 xclose (xopen (path
, O_WRONLY
|O_CREAT
|O_NOCTTY
, mode
));
57 trim_prefix (char *str
, size_t str_len
, const char *prefix
)
59 size_t prefix_len
= strlen (prefix
);
60 if (str_len
> prefix_len
&& memcmp (str
, prefix
, prefix_len
) == 0)
62 memmove (str
, str
+ prefix_len
, str_len
- prefix_len
);
63 return str_len
- prefix_len
;
68 /* returns a pointer to static storage */
70 proc_fd_readlink (const char *linkname
)
72 static char target
[PATH_MAX
+1];
73 ssize_t target_len
= readlink (linkname
, target
, PATH_MAX
);
74 VERIFY (target_len
> 0);
75 target_len
= trim_prefix (target
, target_len
, "(unreachable)");
76 target
[target_len
] = '\0';
80 /* plain ttyname runner */
88 /* strings in result structure are in static storage */
94 ret
.name
= ttyname (fd
);
100 eq_ttyname (struct result actual
, struct result expected
)
102 char *actual_name
, *expected_name
;
104 if ((actual
.err
== expected
.err
)
105 && (!actual
.name
== !expected
.name
)
106 && (actual
.name
? strcmp (actual
.name
, expected
.name
) == 0 : true))
109 expected_name
= xasprintf ("\"%s\"", expected
.name
);
111 expected_name
= xstrdup ("NULL");
113 printf ("info: ttyname: PASS {name=%s, errno=%d}\n",
114 expected_name
, expected
.err
);
116 free (expected_name
);
121 actual_name
= xasprintf ("\"%s\"", actual
.name
);
123 actual_name
= xstrdup ("NULL");
126 expected_name
= xasprintf ("\"%s\"", expected
.name
);
128 expected_name
= xstrdup ("NULL");
130 printf ("error: ttyname: actual {name=%s, errno=%d} != expected {name=%s, errno=%d}\n",
131 actual_name
, actual
.err
,
132 expected_name
, expected
.err
);
135 free (expected_name
);
139 /* ttyname_r runner */
148 /* strings in result structure are in static storage */
149 static struct result_r
150 run_ttyname_r (int fd
)
152 static char buf
[TTY_NAME_MAX
];
156 ret
.ret
= ttyname_r (fd
, buf
, TTY_NAME_MAX
);
166 eq_ttyname_r (struct result_r actual
, struct result_r expected
)
168 char *actual_name
, *expected_name
;
170 if ((actual
.err
== expected
.err
)
171 && (actual
.ret
== expected
.ret
)
172 && (!actual
.name
== !expected
.name
)
173 && (actual
.name
? strcmp (actual
.name
, expected
.name
) == 0 : true))
176 expected_name
= xasprintf ("\"%s\"", expected
.name
);
178 expected_name
= xstrdup ("NULL");
180 printf ("info: ttyname_r: PASS {name=%s, ret=%d, errno=%d}\n",
181 expected_name
, expected
.ret
, expected
.err
);
183 free (expected_name
);
188 actual_name
= xasprintf ("\"%s\"", actual
.name
);
190 actual_name
= xstrdup ("NULL");
193 expected_name
= xasprintf ("\"%s\"", expected
.name
);
195 expected_name
= xstrdup ("NULL");
197 printf ("error: ttyname_r: actual {name=%s, ret=%d, errno=%d} != expected {name=%s, ret=%d, errno=%d}\n",
198 actual_name
, actual
.ret
, actual
.err
,
199 expected_name
, expected
.ret
, expected
.err
);
202 free (expected_name
);
206 /* combined runner */
209 doit (int fd
, const char *testname
, struct result_r expected_r
)
211 struct result expected
= {.name
=expected_r
.name
, .err
=expected_r
.ret
};
214 printf ("info: testcase: %s\n", testname
);
216 if (!eq_ttyname (run_ttyname (fd
), expected
))
218 if (!eq_ttyname_r (run_ttyname_r (fd
), expected_r
))
222 support_record_failure ();
229 static char *chrootdir
;
232 prepare (int argc
, char **argv
)
234 chrootdir
= xasprintf ("%s/tst-ttyname-XXXXXX", test_dir
);
235 if (mkdtemp (chrootdir
) == NULL
)
236 FAIL_EXIT1 ("mkdtemp (\"%s\"): %m", chrootdir
);
237 add_temp_file (chrootdir
);
239 #define PREPARE prepare
241 /* Adjust the file limit so that we have a chance to open PTY. */
243 adjust_file_limit (const char *pty
)
246 if (sscanf (pty
, "/dev/pts/%d", &number
) != 1 || number
< 0)
247 FAIL_EXIT1 ("invalid PTY name: \"%s\"", pty
);
249 /* Add a few additional descriptors to cover standard I/O streams
251 rlim_t desired_limit
= number
+ 10;
254 if (getrlimit (RLIMIT_NOFILE
, &lim
) != 0)
255 FAIL_EXIT1 ("getrlimit (RLIMIT_NOFILE): %m");
256 if (lim
.rlim_cur
< desired_limit
)
258 printf ("info: adjusting RLIMIT_NOFILE from %llu to %llu\n",
259 (unsigned long long int) lim
.rlim_cur
,
260 (unsigned long long int) desired_limit
);
261 lim
.rlim_cur
= desired_limit
;
262 if (setrlimit (RLIMIT_NOFILE
, &lim
) != 0)
263 printf ("warning: setrlimit (RLIMIT_NOFILE) failed: %m\n");
270 run_chroot_tests (const char *slavename
, int slave
)
275 /* There are 3 groups of tests here. The first group fairly
276 generically does things known to mess up ttyname, and verifies
277 that ttyname copes correctly. The remaining groups are
278 increasingly convoluted, as we target specific parts of ttyname
279 to try to confuse. */
281 /* Basic tests that it doesn't get confused by multiple devpts
284 VERIFY (stat (slavename
, &st
) < 0); /* sanity check */
285 if (!doit (slave
, "no conflict, no match",
286 (struct result_r
){.name
=NULL
, .ret
=ENODEV
, .err
=ENODEV
}))
288 VERIFY (mount ("/console", "/dev/console", NULL
, MS_BIND
, NULL
) == 0);
289 if (!doit (slave
, "no conflict, console",
290 (struct result_r
){.name
="/dev/console", .ret
=0, .err
=0}))
292 VERIFY (umount ("/dev/console") == 0);
294 /* Keep creating PTYs until we we get a name collision. */
297 if (stat (slavename
, &st
) == 0)
299 if (posix_openpt (O_RDWR
|O_NOCTTY
|O_NONBLOCK
) < 0)
301 if (errno
== ENOSPC
|| errno
== EMFILE
|| errno
== ENFILE
)
302 FAIL_UNSUPPORTED ("cannot re-create PTY \"%s\" in chroot: %m"
303 " (consider increasing limits)", slavename
);
305 FAIL_EXIT1 ("cannot re-create PTY \"%s\" chroot: %m", slavename
);
309 if (!doit (slave
, "conflict, no match",
310 (struct result_r
){.name
=NULL
, .ret
=ENODEV
, .err
=ENODEV
}))
312 VERIFY (mount ("/console", "/dev/console", NULL
, MS_BIND
, NULL
) == 0);
313 if (!doit (slave
, "conflict, console",
314 (struct result_r
){.name
="/dev/console", .ret
=0, .err
=0}))
316 VERIFY (umount ("/dev/console") == 0);
319 /* The first tests kinda assumed that they hit certain code-paths
320 based on assuming that the readlink target is 'slavename', but
321 that's not quite always true. They're still a good preliminary
322 sanity check, so keep them, but let's add tests that make sure
323 that those code-paths are hit by doing a readlink ourself. */
325 char *linkname
= xasprintf ("/proc/self/fd/%d", slave
);
326 char *target
= proc_fd_readlink (linkname
);
328 /* Depending on how we set up the chroot, the kernel may or may not
329 trim the leading path to the target (it may give us "/6",
330 instead of "/dev/pts/6"). We test it both ways (do_in_chroot_1
331 and do_in_chroot_2). This test group relies on the target
332 existing, so guarantee that it does exist by creating it if
334 if (stat (target
, &st
) < 0)
336 VERIFY (errno
== ENOENT
);
340 VERIFY (mount ("/console", "/dev/console", NULL
, MS_BIND
, NULL
) == 0);
341 VERIFY (mount ("/console", target
, NULL
, MS_BIND
, NULL
) == 0);
342 if (!doit (slave
, "with readlink target",
343 (struct result_r
){.name
=target
, .ret
=0, .err
=0}))
345 VERIFY (umount (target
) == 0);
346 VERIFY (umount ("/dev/console") == 0);
348 VERIFY (mount ("/console", "/dev/console", NULL
, MS_BIND
, NULL
) == 0);
349 VERIFY (mount (slavename
, target
, NULL
, MS_BIND
, NULL
) == 0);
350 if (!doit (slave
, "with readlink trap; fallback",
351 (struct result_r
){.name
="/dev/console", .ret
=0, .err
=0}))
353 VERIFY (umount (target
) == 0);
354 VERIFY (umount ("/dev/console") == 0);
356 VERIFY (mount (slavename
, target
, NULL
, MS_BIND
, NULL
) == 0);
357 if (!doit (slave
, "with readlink trap; no fallback",
358 (struct result_r
){.name
=NULL
, .ret
=ENODEV
, .err
=ENODEV
}))
360 VERIFY (umount (target
) == 0);
363 /* This test makes sure that everything still works OK if readdir
364 finds a pseudo-match before and/or after the actual match. Now,
365 to do that, we need to control that readdir finds the
366 pseudo-matches before and after the actual match; and there's no
367 good way to control that order in absence of whitebox testing.
368 So, just create 3 files, then use opendir/readdir to see what
369 order they are in, and assign meaning based on that order, not by
370 name; assigning the first to be a pseudo-match, the second to be
371 the actual match, and the third to be a pseudo-match. This
372 assumes that (on tmpfs) ordering within the directory is stable
373 in the absence of modification, which seems reasonably safe. */
375 /* since we're testing the fallback search, disable the readlink
377 VERIFY (umount2 ("/proc", MNT_DETACH
) == 0);
379 touch ("/dev/console1", 0);
380 touch ("/dev/console2", 0);
381 touch ("/dev/console3", 0);
385 DIR *dirstream
= opendir ("/dev");
386 VERIFY (dirstream
!= NULL
);
388 while ((d
= readdir (dirstream
)) != NULL
&& ci
< 3)
390 if (strcmp (d
->d_name
, "console1")
391 && strcmp (d
->d_name
, "console2")
392 && strcmp (d
->d_name
, "console3") )
394 c
[ci
++] = xasprintf ("/dev/%s", d
->d_name
);
397 VERIFY (closedir (dirstream
) == 0);
399 VERIFY (mount (slavename
, c
[0], NULL
, MS_BIND
, NULL
) == 0);
400 VERIFY (mount ("/console", c
[1], NULL
, MS_BIND
, NULL
) == 0);
401 VERIFY (mount (slavename
, c
[2], NULL
, MS_BIND
, NULL
) == 0);
402 VERIFY (umount2 ("/dev/pts", MNT_DETACH
) == 0);
403 if (!doit (slave
, "with search-path trap",
404 (struct result_r
){.name
=c
[1], .ret
=0, .err
=0}))
406 for (int i
= 0; i
< 3; i
++)
408 VERIFY (umount (c
[i
]) == 0);
409 VERIFY (unlink (c
[i
]) == 0);