1  /* Common definitions for ttyname tests.
       2     Copyright (C) 2017-2023 Free Software Foundation, Inc.
       3     This file is part of the GNU C Library.
       4  
       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.
       9  
      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.
      14  
      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/>.  */
      18  
      19  #include <dirent.h>
      20  #include <errno.h>
      21  #include <fcntl.h>
      22  #include <limits.h>
      23  #include <stdbool.h>
      24  #include <stdio.h>
      25  #include <stdlib.h>
      26  #include <string.h>
      27  #include <sys/mount.h>
      28  #include <sys/resource.h>
      29  #include <sys/stat.h>
      30  #include <unistd.h>
      31  
      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>
      37  
      38  /* generic utilities */
      39  
      40  #define VERIFY(expr)                                                    \
      41    do {                                                                  \
      42      if (!(expr))                                                        \
      43        {                                                                 \
      44          printf ("error: %s:%d: %s: %m\n",                               \
      45                  __FILE__, __LINE__, #expr);                             \
      46          exit (1);                                                       \
      47        }                                                                 \
      48    } while (0)
      49  
      50  static void
      51  touch (const char *path, mode_t mode)
      52  {
      53    xclose (xopen (path, O_WRONLY|O_CREAT|O_NOCTTY, mode));
      54  }
      55  
      56  static size_t
      57  trim_prefix (char *str, size_t str_len, const char *prefix)
      58  {
      59    size_t prefix_len = strlen (prefix);
      60    if (str_len > prefix_len && memcmp (str, prefix, prefix_len) == 0)
      61      {
      62        memmove (str, str + prefix_len, str_len - prefix_len);
      63        return str_len - prefix_len;
      64      }
      65    return str_len;
      66  }
      67  
      68  /* returns a pointer to static storage */
      69  static char *
      70  proc_fd_readlink (const char *linkname)
      71  {
      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';
      77    return target;
      78  }
      79  
      80  /* plain ttyname runner */
      81  
      82  struct result
      83  {
      84    const char *name;
      85    int err;
      86  };
      87  
      88  /* strings in result structure are in static storage */
      89  static struct result
      90  run_ttyname (int fd)
      91  {
      92    struct result ret;
      93    errno = 0;
      94    ret.name = ttyname (fd);
      95    ret.err = errno;
      96    return ret;
      97  }
      98  
      99  static bool
     100  eq_ttyname (struct result actual, struct result expected)
     101  {
     102    char *actual_name, *expected_name;
     103  
     104    if ((actual.err == expected.err)
     105        && (!actual.name == !expected.name)
     106        && (actual.name ? strcmp (actual.name, expected.name) == 0 : true))
     107      {
     108        if (expected.name)
     109          expected_name = xasprintf ("\"%s\"", expected.name);
     110        else
     111  	expected_name = xstrdup ("NULL");
     112  
     113        printf ("info:      ttyname: PASS {name=%s, errno=%d}\n",
     114  	      expected_name, expected.err);
     115  
     116        free (expected_name);
     117        return true;
     118      }
     119  
     120    if (actual.name)
     121      actual_name = xasprintf ("\"%s\"", actual.name);
     122    else
     123      actual_name = xstrdup ("NULL");
     124  
     125    if (expected.name)
     126      expected_name = xasprintf ("\"%s\"", expected.name);
     127    else
     128      expected_name = xstrdup ("NULL");
     129  
     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);
     133  
     134    free (actual_name);
     135    free (expected_name);
     136    return false;
     137  }
     138  
     139  /* ttyname_r runner */
     140  
     141  struct result_r
     142  {
     143    const char *name;
     144    int ret;
     145    int err;
     146  };
     147  
     148  /* strings in result structure are in static storage */
     149  static struct result_r
     150  run_ttyname_r (int fd)
     151  {
     152    static char buf[TTY_NAME_MAX];
     153  
     154    struct result_r ret;
     155    errno = 0;
     156    ret.ret = ttyname_r (fd, buf, TTY_NAME_MAX);
     157    ret.err = errno;
     158    if (ret.ret == 0)
     159      ret.name = buf;
     160    else
     161      ret.name = NULL;
     162    return ret;
     163  }
     164  
     165  static bool
     166  eq_ttyname_r (struct result_r actual, struct result_r expected)
     167  {
     168    char *actual_name, *expected_name;
     169  
     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))
     174      {
     175        if (expected.name)
     176          expected_name = xasprintf ("\"%s\"", expected.name);
     177        else
     178          expected_name = xstrdup ("NULL");
     179  
     180        printf ("info:      ttyname_r: PASS {name=%s, ret=%d, errno=%d}\n",
     181                expected_name, expected.ret, expected.err);
     182  
     183        free (expected_name);
     184        return true;
     185      }
     186  
     187    if (actual.name)
     188      actual_name = xasprintf ("\"%s\"", actual.name);
     189    else
     190      actual_name = xstrdup ("NULL");
     191  
     192    if (expected.name)
     193      expected_name = xasprintf ("\"%s\"", expected.name);
     194    else
     195      expected_name = xstrdup ("NULL");
     196  
     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);
     200  
     201    free (actual_name);
     202    free (expected_name);
     203    return false;
     204  }
     205  
     206  /* combined runner */
     207  
     208  static bool
     209  doit (int fd, const char *testname, struct result_r expected_r)
     210  {
     211    struct result expected = {.name=expected_r.name, .err=expected_r.ret};
     212    bool ret = true;
     213  
     214    printf ("info:    testcase: %s\n", testname);
     215  
     216    if (!eq_ttyname (run_ttyname (fd), expected))
     217      ret = false;
     218    if (!eq_ttyname_r (run_ttyname_r (fd), expected_r))
     219      ret = false;
     220  
     221    if (!ret)
     222      support_record_failure ();
     223  
     224    return ret;
     225  }
     226  
     227  /* chroot setup */
     228  
     229  static char *chrootdir;
     230  
     231  static void
     232  prepare (int argc, char **argv)
     233  {
     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);
     238  }
     239  #define PREPARE prepare
     240  
     241  /* Adjust the file limit so that we have a chance to open PTY.  */
     242  static void
     243  adjust_file_limit (const char *pty)
     244  {
     245    int number = -1;
     246    if (sscanf (pty, "/dev/pts/%d", &number) != 1 || number < 0)
     247      FAIL_EXIT1 ("invalid PTY name: \"%s\"", pty);
     248  
     249    /* Add a few additional descriptors to cover standard I/O streams
     250       etc.  */
     251    rlim_t desired_limit = number + 10;
     252  
     253    struct rlimit lim;
     254    if (getrlimit (RLIMIT_NOFILE, &lim) != 0)
     255      FAIL_EXIT1 ("getrlimit (RLIMIT_NOFILE): %m");
     256    if (lim.rlim_cur < desired_limit)
     257      {
     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");
     264      }
     265  }
     266  
     267  /* main test */
     268  
     269  static int
     270  run_chroot_tests (const char *slavename, int slave)
     271  {
     272    struct stat st;
     273    bool ok = true;
     274  
     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.  */
     280  
     281    /* Basic tests that it doesn't get confused by multiple devpts
     282       instances.  */
     283    {
     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}))
     287        ok = false;
     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}))
     291        ok = false;
     292      VERIFY (umount ("/dev/console") == 0);
     293  
     294      /* Keep creating PTYs until we we get a name collision.  */
     295      while (true)
     296        {
     297  	if (stat (slavename, &st) == 0)
     298  	  break;
     299  	if (posix_openpt (O_RDWR|O_NOCTTY|O_NONBLOCK) < 0)
     300  	  {
     301  	    if (errno == ENOSPC || errno == EMFILE || errno == ENFILE)
     302  	      FAIL_UNSUPPORTED ("cannot re-create PTY \"%s\" in chroot: %m"
     303  				" (consider increasing limits)", slavename);
     304  	    else
     305  	      FAIL_EXIT1 ("cannot re-create PTY \"%s\" chroot: %m", slavename);
     306  	  }
     307        }
     308  
     309      if (!doit (slave, "conflict, no match",
     310                 (struct result_r){.name=NULL, .ret=ENODEV, .err=ENODEV}))
     311        ok = false;
     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}))
     315        ok = false;
     316      VERIFY (umount ("/dev/console") == 0);
     317    }
     318  
     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.  */
     324    {
     325      char *linkname = xasprintf ("/proc/self/fd/%d", slave);
     326      char *target = proc_fd_readlink (linkname);
     327      free (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
     333         necessary.  */
     334      if (stat (target, &st) < 0)
     335        {
     336          VERIFY (errno == ENOENT);
     337          touch (target, 0);
     338        }
     339  
     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}))
     344        ok = false;
     345      VERIFY (umount (target) == 0);
     346      VERIFY (umount ("/dev/console") == 0);
     347  
     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}))
     352        ok = false;
     353      VERIFY (umount (target) == 0);
     354      VERIFY (umount ("/dev/console") == 0);
     355  
     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}))
     359        ok = false;
     360      VERIFY (umount (target) == 0);
     361    }
     362  
     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.  */
     374    {
     375      /* since we're testing the fallback search, disable the readlink
     376         happy-path */
     377      VERIFY (umount2 ("/proc", MNT_DETACH) == 0);
     378  
     379      touch ("/dev/console1", 0);
     380      touch ("/dev/console2", 0);
     381      touch ("/dev/console3", 0);
     382  
     383      char *c[3];
     384      int ci = 0;
     385      DIR *dirstream = opendir ("/dev");
     386      VERIFY (dirstream != NULL);
     387      struct dirent *d;
     388      while ((d = readdir (dirstream)) != NULL && ci < 3)
     389        {
     390          if (strcmp (d->d_name, "console1")
     391              && strcmp (d->d_name, "console2")
     392              && strcmp (d->d_name, "console3") )
     393            continue;
     394          c[ci++] = xasprintf ("/dev/%s", d->d_name);
     395        }
     396      VERIFY (ci == 3);
     397      VERIFY (closedir (dirstream) == 0);
     398  
     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}))
     405        ok = false;
     406      for (int i = 0; i < 3; i++)
     407        {
     408          VERIFY (umount (c[i]) == 0);
     409          VERIFY (unlink (c[i]) == 0);
     410          free (c[i]);
     411        }
     412    }
     413  
     414    return ok ? 0 : 1;
     415  }