Blame sysdeps/unix/sysv/linux/tst-ttyname.c

Packit 6c4009
/* Copyright (C) 2017-2018 Free Software Foundation, Inc.
Packit 6c4009
   This file is part of the GNU C Library.
Packit 6c4009
Packit 6c4009
   The GNU C Library is free software; you can redistribute it and/or
Packit 6c4009
   modify it under the terms of the GNU Lesser General Public License as
Packit 6c4009
   published by the Free Software Foundation; either version 2.1 of the
Packit 6c4009
   License, or (at your option) any later version.
Packit 6c4009
Packit 6c4009
   The GNU C Library is distributed in the hope that it will be useful,
Packit 6c4009
   but WITHOUT ANY WARRANTY; without even the implied warranty of
Packit 6c4009
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Packit 6c4009
   Lesser General Public License for more details.
Packit 6c4009
Packit 6c4009
   You should have received a copy of the GNU Lesser General Public
Packit 6c4009
   License along with the GNU C Library; see the file COPYING.LIB.  If
Packit 6c4009
   not, see <http://www.gnu.org/licenses/>.  */
Packit 6c4009
Packit 6c4009
#include <dirent.h>
Packit 6c4009
#include <errno.h>
Packit 6c4009
#include <fcntl.h>
Packit 6c4009
#include <limits.h>
Packit 6c4009
#include <sched.h>
Packit 6c4009
#include <stdio.h>
Packit 6c4009
#include <stdlib.h>
Packit 6c4009
#include <string.h>
Packit 6c4009
#include <sys/mount.h>
Packit 6c4009
#include <sys/prctl.h>
Packit 6c4009
#include <sys/stat.h>
Packit 6c4009
#include <sys/wait.h>
Packit 6c4009
#include <unistd.h>
Packit 6c4009
Packit 6c4009
#include <support/check.h>
Packit 6c4009
#include <support/namespace.h>
Packit 6c4009
#include <support/support.h>
Packit 6c4009
#include <support/temp_file.h>
Packit 6c4009
#include <support/test-driver.h>
Packit 6c4009
#include <support/xunistd.h>
Packit 6c4009
Packit 6c4009
/* generic utilities */
Packit 6c4009
Packit 6c4009
#define VERIFY(expr)                                                    \
Packit 6c4009
  do {                                                                  \
Packit 6c4009
    if (!(expr))                                                        \
Packit 6c4009
      {                                                                 \
Packit 6c4009
        printf ("error: %s:%d: %s: %m\n",                               \
Packit 6c4009
                __FILE__, __LINE__, #expr);                             \
Packit 6c4009
        exit (1);                                                       \
Packit 6c4009
      }                                                                 \
Packit 6c4009
  } while (0)
Packit 6c4009
Packit 6c4009
static void
Packit 6c4009
touch (const char *path, mode_t mode)
Packit 6c4009
{
Packit 6c4009
  xclose (xopen (path, O_WRONLY|O_CREAT|O_NOCTTY, mode));
Packit 6c4009
}
Packit 6c4009
Packit 6c4009
static size_t
Packit 6c4009
trim_prefix (char *str, size_t str_len, const char *prefix)
Packit 6c4009
{
Packit 6c4009
  size_t prefix_len = strlen (prefix);
Packit 6c4009
  if (str_len > prefix_len && memcmp (str, prefix, prefix_len) == 0)
Packit 6c4009
    {
Packit 6c4009
      memmove (str, str + prefix_len, str_len - prefix_len);
Packit 6c4009
      return str_len - prefix_len;
Packit 6c4009
    }
Packit 6c4009
  return str_len;
Packit 6c4009
}
Packit 6c4009
Packit 6c4009
/* returns a pointer to static storage */
Packit 6c4009
static char *
Packit 6c4009
proc_fd_readlink (const char *linkname)
Packit 6c4009
{
Packit 6c4009
  static char target[PATH_MAX+1];
Packit 6c4009
  ssize_t target_len = readlink (linkname, target, PATH_MAX);
Packit 6c4009
  VERIFY (target_len > 0);
Packit 6c4009
  target_len = trim_prefix (target, target_len, "(unreachable)");
Packit 6c4009
  target[target_len] = '\0';
Packit 6c4009
  return target;
Packit 6c4009
}
Packit 6c4009
Packit 6c4009
/* plain ttyname runner */
Packit 6c4009
Packit 6c4009
struct result
Packit 6c4009
{
Packit 6c4009
  const char *name;
Packit 6c4009
  int err;
Packit 6c4009
};
Packit 6c4009
Packit 6c4009
/* strings in result structure are in static storage */
Packit 6c4009
static struct result
Packit 6c4009
run_ttyname (int fd)
Packit 6c4009
{
Packit 6c4009
  struct result ret;
Packit 6c4009
  errno = 0;
Packit 6c4009
  ret.name = ttyname (fd);
Packit 6c4009
  ret.err = errno;
Packit 6c4009
  return ret;
Packit 6c4009
}
Packit 6c4009
Packit 6c4009
static bool
Packit 6c4009
eq_ttyname (struct result actual, struct result expected)
Packit 6c4009
{
Packit 6c4009
  char *actual_name, *expected_name;
Packit 6c4009
Packit 6c4009
  if ((actual.err == expected.err) &&
Packit 6c4009
      (!actual.name == !expected.name) &&
Packit 6c4009
      (actual.name ? strcmp (actual.name, expected.name) == 0 : true))
Packit 6c4009
    {
Packit 6c4009
      if (expected.name)
Packit 6c4009
        expected_name = xasprintf ("\"%s\"", expected.name);
Packit 6c4009
      else
Packit 6c4009
	expected_name = xstrdup ("NULL");
Packit 6c4009
Packit 6c4009
      printf ("info:      ttyname: PASS {name=%s, errno=%d}\n",
Packit 6c4009
	      expected_name, expected.err);
Packit 6c4009
Packit 6c4009
      free (expected_name);
Packit 6c4009
      return true;
Packit 6c4009
    }
Packit 6c4009
Packit 6c4009
  if (actual.name)
Packit 6c4009
    actual_name = xasprintf ("\"%s\"", actual.name);
Packit 6c4009
  else
Packit 6c4009
    actual_name = xstrdup ("NULL");
Packit 6c4009
Packit 6c4009
  if (expected.name)
Packit 6c4009
    expected_name = xasprintf ("\"%s\"", expected.name);
Packit 6c4009
  else
Packit 6c4009
    expected_name = xstrdup ("NULL");
Packit 6c4009
Packit 6c4009
  printf ("error:     ttyname: actual {name=%s, errno=%d} != expected {name=%s, errno=%d}\n",
Packit 6c4009
	  actual_name, actual.err,
Packit 6c4009
	  expected_name, expected.err);
Packit 6c4009
Packit 6c4009
  free (actual_name);
Packit 6c4009
  free (expected_name);
Packit 6c4009
  return false;
Packit 6c4009
}
Packit 6c4009
Packit 6c4009
/* ttyname_r runner */
Packit 6c4009
Packit 6c4009
struct result_r
Packit 6c4009
{
Packit 6c4009
  const char *name;
Packit 6c4009
  int ret;
Packit 6c4009
  int err;
Packit 6c4009
};
Packit 6c4009
Packit 6c4009
/* strings in result structure are in static storage */
Packit 6c4009
static struct result_r
Packit 6c4009
run_ttyname_r (int fd)
Packit 6c4009
{
Packit 6c4009
  static char buf[TTY_NAME_MAX];
Packit 6c4009
Packit 6c4009
  struct result_r ret;
Packit 6c4009
  errno = 0;
Packit 6c4009
  ret.ret = ttyname_r (fd, buf, TTY_NAME_MAX);
Packit 6c4009
  ret.err = errno;
Packit 6c4009
  if (ret.ret == 0)
Packit 6c4009
    ret.name = buf;
Packit 6c4009
  else
Packit 6c4009
    ret.name = NULL;
Packit 6c4009
  return ret;
Packit 6c4009
}
Packit 6c4009
Packit 6c4009
static bool
Packit 6c4009
eq_ttyname_r (struct result_r actual, struct result_r expected)
Packit 6c4009
{
Packit 6c4009
  char *actual_name, *expected_name;
Packit 6c4009
Packit 6c4009
  if ((actual.err == expected.err) &&
Packit 6c4009
      (actual.ret == expected.ret) &&
Packit 6c4009
      (!actual.name == !expected.name) &&
Packit 6c4009
      (actual.name ? strcmp (actual.name, expected.name) == 0 : true))
Packit 6c4009
    {
Packit 6c4009
      if (expected.name)
Packit 6c4009
        expected_name = xasprintf ("\"%s\"", expected.name);
Packit 6c4009
      else
Packit 6c4009
        expected_name = xstrdup ("NULL");
Packit 6c4009
Packit 6c4009
      printf ("info:      ttyname_r: PASS {name=%s, ret=%d, errno=%d}\n",
Packit 6c4009
              expected_name, expected.ret, expected.err);
Packit 6c4009
Packit 6c4009
      free (expected_name);
Packit 6c4009
      return true;
Packit 6c4009
    }
Packit 6c4009
Packit 6c4009
  if (actual.name)
Packit 6c4009
    actual_name = xasprintf ("\"%s\"", actual.name);
Packit 6c4009
  else
Packit 6c4009
    actual_name = xstrdup ("NULL");
Packit 6c4009
Packit 6c4009
  if (expected.name)
Packit 6c4009
    expected_name = xasprintf ("\"%s\"", expected.name);
Packit 6c4009
  else
Packit 6c4009
    expected_name = xstrdup ("NULL");
Packit 6c4009
Packit 6c4009
  printf ("error:     ttyname_r: actual {name=%s, ret=%d, errno=%d} != expected {name=%s, ret=%d, errno=%d}\n",
Packit 6c4009
	  actual_name, actual.ret, actual.err,
Packit 6c4009
	  expected_name, expected.ret, expected.err);
Packit 6c4009
Packit 6c4009
  free (actual_name);
Packit 6c4009
  free (expected_name);
Packit 6c4009
  return false;
Packit 6c4009
}
Packit 6c4009
Packit 6c4009
/* combined runner */
Packit 6c4009
Packit 6c4009
static bool
Packit 6c4009
doit (int fd, const char *testname, struct result_r expected_r)
Packit 6c4009
{
Packit 6c4009
  struct result expected = {.name=expected_r.name, .err=expected_r.ret};
Packit 6c4009
  bool ret = true;
Packit 6c4009
Packit 6c4009
  printf ("info:    testcase: %s\n", testname);
Packit 6c4009
Packit 6c4009
  if (!eq_ttyname (run_ttyname (fd), expected))
Packit 6c4009
    ret = false;
Packit 6c4009
  if (!eq_ttyname_r (run_ttyname_r (fd), expected_r))
Packit 6c4009
    ret = false;
Packit 6c4009
Packit 6c4009
  if (!ret)
Packit 6c4009
    support_record_failure ();
Packit 6c4009
Packit 6c4009
  return ret;
Packit 6c4009
}
Packit 6c4009
Packit 6c4009
/* chroot setup */
Packit 6c4009
Packit 6c4009
static char *chrootdir;
Packit 6c4009
Packit 6c4009
static void
Packit 6c4009
prepare (int argc, char **argv)
Packit 6c4009
{
Packit 6c4009
  chrootdir = xasprintf ("%s/tst-ttyname-XXXXXX", test_dir);
Packit 6c4009
  if (mkdtemp (chrootdir) == NULL)
Packit 6c4009
    FAIL_EXIT1 ("mkdtemp (\"%s\"): %m", chrootdir);
Packit 6c4009
  add_temp_file (chrootdir);
Packit 6c4009
}
Packit 6c4009
#define PREPARE prepare
Packit 6c4009
Packit 6c4009
/* These chroot setup functions put the TTY at at "/console" (where it
Packit 6c4009
   won't be found by ttyname), and create "/dev/console" as an
Packit 6c4009
   ordinary file.  This way, it's easier to write test-cases that
Packit 6c4009
   expect ttyname to fail; test-cases that expect it to succeed need
Packit 6c4009
   to explicitly remount it at "/dev/console".  */
Packit 6c4009
Packit 6c4009
static int
Packit 6c4009
do_in_chroot_1 (int (*cb)(const char *, int))
Packit 6c4009
{
Packit 6c4009
  printf ("info:  entering chroot 1\n");
Packit 6c4009
Packit 6c4009
  /* Open the PTS that we'll be testing on.  */
Packit 6c4009
  int master;
Packit 6c4009
  char *slavename;
Packit 6c4009
  master = posix_openpt (O_RDWR|O_NOCTTY|O_NONBLOCK);
Packit 6c4009
  if (master < 0)
Packit 6c4009
    {
Packit 6c4009
      if (errno == ENOENT)
Packit 6c4009
	FAIL_UNSUPPORTED ("posix_openpt: %m");
Packit 6c4009
      else
Packit 6c4009
	FAIL_EXIT1 ("posix_openpt: %m");
Packit 6c4009
    }
Packit 6c4009
  VERIFY ((slavename = ptsname (master)));
Packit 6c4009
  VERIFY (unlockpt (master) == 0);
Packit 6c4009
  if (strncmp (slavename, "/dev/pts/", 9) != 0)
Packit 6c4009
    FAIL_UNSUPPORTED ("slave pseudo-terminal is not under /dev/pts/: %s",
Packit 6c4009
                      slavename);
Packit 6c4009
  int slave = xopen (slavename, O_RDWR, 0);
Packit 6c4009
  if (!doit (slave, "basic smoketest",
Packit 6c4009
             (struct result_r){.name=slavename, .ret=0, .err=0}))
Packit 6c4009
    return 1;
Packit 6c4009
Packit 6c4009
  pid_t pid = xfork ();
Packit 6c4009
  if (pid == 0)
Packit 6c4009
    {
Packit 6c4009
      xclose (master);
Packit 6c4009
Packit 6c4009
      if (!support_enter_mount_namespace ())
Packit 6c4009
	FAIL_UNSUPPORTED ("could not enter new mount namespace");
Packit 6c4009
Packit 6c4009
      VERIFY (mount ("tmpfs", chrootdir, "tmpfs", 0, "mode=755") == 0);
Packit 6c4009
      VERIFY (chdir (chrootdir) == 0);
Packit 6c4009
Packit 6c4009
      xmkdir ("proc", 0755);
Packit 6c4009
      xmkdir ("dev", 0755);
Packit 6c4009
      xmkdir ("dev/pts", 0755);
Packit 6c4009
Packit 6c4009
      VERIFY (mount ("/proc", "proc", NULL, MS_BIND|MS_REC, NULL) == 0);
Packit 6c4009
      VERIFY (mount ("devpts", "dev/pts", "devpts",
Packit 6c4009
                     MS_NOSUID|MS_NOEXEC,
Packit 6c4009
                     "newinstance,ptmxmode=0666,mode=620") == 0);
Packit 6c4009
      VERIFY (symlink ("pts/ptmx", "dev/ptmx") == 0);
Packit 6c4009
Packit 6c4009
      touch ("console", 0);
Packit 6c4009
      touch ("dev/console", 0);
Packit 6c4009
      VERIFY (mount (slavename, "console", NULL, MS_BIND, NULL) == 0);
Packit 6c4009
Packit 6c4009
      xchroot (".");
Packit 6c4009
Packit 6c4009
      char *linkname = xasprintf ("/proc/self/fd/%d", slave);
Packit 6c4009
      char *target = proc_fd_readlink (linkname);
Packit 6c4009
      VERIFY (strcmp (target, slavename) == 0);
Packit 6c4009
      free (linkname);
Packit 6c4009
Packit 6c4009
      _exit (cb (slavename, slave));
Packit 6c4009
    }
Packit 6c4009
  int status;
Packit 6c4009
  xwaitpid (pid, &status, 0);
Packit 6c4009
  VERIFY (WIFEXITED (status));
Packit 6c4009
  xclose (master);
Packit 6c4009
  xclose (slave);
Packit 6c4009
  return WEXITSTATUS (status);
Packit 6c4009
}
Packit 6c4009
Packit 6c4009
static int
Packit 6c4009
do_in_chroot_2 (int (*cb)(const char *, int))
Packit 6c4009
{
Packit 6c4009
  printf ("info:  entering chroot 2\n");
Packit 6c4009
Packit 6c4009
  int pid_pipe[2];
Packit 6c4009
  xpipe (pid_pipe);
Packit 6c4009
  int exit_pipe[2];
Packit 6c4009
  xpipe (exit_pipe);
Packit 6c4009
Packit 6c4009
  /* Open the PTS that we'll be testing on.  */
Packit 6c4009
  int master;
Packit 6c4009
  char *slavename;
Packit 6c4009
  VERIFY ((master = posix_openpt (O_RDWR|O_NOCTTY|O_NONBLOCK)) >= 0);
Packit 6c4009
  VERIFY ((slavename = ptsname (master)));
Packit 6c4009
  VERIFY (unlockpt (master) == 0);
Packit 6c4009
  if (strncmp (slavename, "/dev/pts/", 9) != 0)
Packit 6c4009
    FAIL_UNSUPPORTED ("slave pseudo-terminal is not under /dev/pts/: %s",
Packit 6c4009
                      slavename);
Packit 6c4009
  /* wait until in a new mount ns to open the slave */
Packit 6c4009
Packit 6c4009
  /* enable `wait`ing on grandchildren */
Packit 6c4009
  VERIFY (prctl (PR_SET_CHILD_SUBREAPER, 1) == 0);
Packit 6c4009
Packit 6c4009
  pid_t pid = xfork (); /* outer child */
Packit 6c4009
  if (pid == 0)
Packit 6c4009
    {
Packit 6c4009
      xclose (master);
Packit 6c4009
      xclose (pid_pipe[0]);
Packit 6c4009
      xclose (exit_pipe[1]);
Packit 6c4009
Packit 6c4009
      if (!support_enter_mount_namespace ())
Packit 6c4009
	FAIL_UNSUPPORTED ("could not enter new mount namespace");
Packit 6c4009
Packit 6c4009
      int slave = xopen (slavename, O_RDWR, 0);
Packit 6c4009
      if (!doit (slave, "basic smoketest",
Packit 6c4009
                 (struct result_r){.name=slavename, .ret=0, .err=0}))
Packit 6c4009
        _exit (1);
Packit 6c4009
Packit 6c4009
      VERIFY (mount ("tmpfs", chrootdir, "tmpfs", 0, "mode=755") == 0);
Packit 6c4009
      VERIFY (chdir (chrootdir) == 0);
Packit 6c4009
Packit 6c4009
      xmkdir ("proc", 0755);
Packit 6c4009
      xmkdir ("dev", 0755);
Packit 6c4009
      xmkdir ("dev/pts", 0755);
Packit 6c4009
Packit 6c4009
      VERIFY (mount ("devpts", "dev/pts", "devpts",
Packit 6c4009
                     MS_NOSUID|MS_NOEXEC,
Packit 6c4009
                     "newinstance,ptmxmode=0666,mode=620") == 0);
Packit 6c4009
      VERIFY (symlink ("pts/ptmx", "dev/ptmx") == 0);
Packit 6c4009
Packit 6c4009
      touch ("console", 0);
Packit 6c4009
      touch ("dev/console", 0);
Packit 6c4009
      VERIFY (mount (slavename, "console", NULL, MS_BIND, NULL) == 0);
Packit 6c4009
Packit 6c4009
      xchroot (".");
Packit 6c4009
Packit 6c4009
      if (unshare (CLONE_NEWNS | CLONE_NEWPID) < 0)
Packit 6c4009
        FAIL_UNSUPPORTED ("could not enter new PID namespace");
Packit 6c4009
      pid = xfork (); /* inner child */
Packit 6c4009
      if (pid == 0)
Packit 6c4009
        {
Packit 6c4009
          xclose (pid_pipe[1]);
Packit 6c4009
Packit 6c4009
          /* wait until the outer child has exited */
Packit 6c4009
          char c;
Packit 6c4009
          VERIFY (read (exit_pipe[0], &c, 1) == 0);
Packit 6c4009
          xclose (exit_pipe[0]);
Packit 6c4009
Packit 6c4009
          VERIFY (mount ("proc", "/proc", "proc",
Packit 6c4009
                         MS_NOSUID|MS_NOEXEC|MS_NODEV, NULL) == 0);
Packit 6c4009
Packit 6c4009
          char *linkname = xasprintf ("/proc/self/fd/%d", slave);
Packit 6c4009
          char *target = proc_fd_readlink (linkname);
Packit 6c4009
          VERIFY (strcmp (target, strrchr (slavename, '/')) == 0);
Packit 6c4009
          free (linkname);
Packit 6c4009
Packit 6c4009
          _exit (cb (slavename, slave));
Packit 6c4009
        }
Packit 6c4009
      xwrite (pid_pipe[1], &pid, sizeof pid);
Packit 6c4009
      _exit (0);
Packit 6c4009
    }
Packit 6c4009
  xclose (pid_pipe[1]);
Packit 6c4009
  xclose (exit_pipe[0]);
Packit 6c4009
  xclose (exit_pipe[1]);
Packit 6c4009
Packit 6c4009
  /* wait for the outer child */
Packit 6c4009
  int status;
Packit 6c4009
  xwaitpid (pid, &status, 0);
Packit 6c4009
  VERIFY (WIFEXITED (status));
Packit 6c4009
  int ret = WEXITSTATUS (status);
Packit 6c4009
  if (ret != 0)
Packit 6c4009
    return ret;
Packit 6c4009
Packit 6c4009
  /* set 'pid' to the inner child */
Packit 6c4009
  VERIFY (read (pid_pipe[0], &pid, sizeof pid) == sizeof pid);
Packit 6c4009
  xclose (pid_pipe[0]);
Packit 6c4009
Packit 6c4009
  /* wait for the inner child */
Packit 6c4009
  xwaitpid (pid, &status, 0);
Packit 6c4009
  VERIFY (WIFEXITED (status));
Packit 6c4009
  xclose (master);
Packit 6c4009
  return WEXITSTATUS (status);
Packit 6c4009
}
Packit 6c4009
Packit 6c4009
/* main test */
Packit 6c4009
Packit 6c4009
static int
Packit 6c4009
run_chroot_tests (const char *slavename, int slave)
Packit 6c4009
{
Packit 6c4009
  struct stat st;
Packit 6c4009
  bool ok = true;
Packit 6c4009
Packit 6c4009
  /* There are 3 groups of tests here.  The first group fairly
Packit 6c4009
     generically does things known to mess up ttyname, and verifies
Packit 6c4009
     that ttyname copes correctly.  The remaining groups are
Packit 6c4009
     increasingly convoluted, as we target specific parts of ttyname
Packit 6c4009
     to try to confuse.  */
Packit 6c4009
Packit 6c4009
  /* Basic tests that it doesn't get confused by multiple devpts
Packit 6c4009
     instances.  */
Packit 6c4009
  {
Packit 6c4009
    VERIFY (stat (slavename, &st) < 0); /* sanity check */
Packit 6c4009
    if (!doit (slave, "no conflict, no match",
Packit 6c4009
               (struct result_r){.name=NULL, .ret=ENODEV, .err=ENODEV}))
Packit 6c4009
      ok = false;
Packit 6c4009
    VERIFY (mount ("/console", "/dev/console", NULL, MS_BIND, NULL) == 0);
Packit 6c4009
    if (!doit (slave, "no conflict, console",
Packit 6c4009
               (struct result_r){.name="/dev/console", .ret=0, .err=0}))
Packit 6c4009
      ok = false;
Packit 6c4009
    VERIFY (umount ("/dev/console") == 0);
Packit 6c4009
Packit 6c4009
    /* keep creating PTYs until we we get a name collision */
Packit 6c4009
    while (stat (slavename, &st) < 0)
Packit 6c4009
      posix_openpt (O_RDWR|O_NOCTTY|O_NONBLOCK);
Packit 6c4009
    VERIFY (stat (slavename, &st) == 0);
Packit 6c4009
Packit 6c4009
    if (!doit (slave, "conflict, no match",
Packit 6c4009
               (struct result_r){.name=NULL, .ret=ENODEV, .err=ENODEV}))
Packit 6c4009
      ok = false;
Packit 6c4009
    VERIFY (mount ("/console", "/dev/console", NULL, MS_BIND, NULL) == 0);
Packit 6c4009
    if (!doit (slave, "conflict, console",
Packit 6c4009
               (struct result_r){.name="/dev/console", .ret=0, .err=0}))
Packit 6c4009
      ok = false;
Packit 6c4009
    VERIFY (umount ("/dev/console") == 0);
Packit 6c4009
  }
Packit 6c4009
Packit 6c4009
  /* The first tests kinda assumed that they hit certain code-paths
Packit 6c4009
     based on assuming that the readlink target is 'slavename', but
Packit 6c4009
     that's not quite always true.  They're still a good preliminary
Packit 6c4009
     sanity check, so keep them, but let's add tests that make sure
Packit 6c4009
     that those code-paths are hit by doing a readlink ourself.  */
Packit 6c4009
  {
Packit 6c4009
    char *linkname = xasprintf ("/proc/self/fd/%d", slave);
Packit 6c4009
    char *target = proc_fd_readlink (linkname);
Packit 6c4009
    free (linkname);
Packit 6c4009
    /* Depeding on how we set up the chroot, the kernel may or may not
Packit 6c4009
       trim the leading path to the target (it may give us "/6",
Packit 6c4009
       instead of "/dev/pts/6").  We test it both ways (do_in_chroot_1
Packit 6c4009
       and do_in_chroot_2).  This test group relies on the target
Packit 6c4009
       existing, so guarantee that it does exist by creating it if
Packit 6c4009
       necessary.  */
Packit 6c4009
    if (stat (target, &st) < 0)
Packit 6c4009
      {
Packit 6c4009
        VERIFY (errno == ENOENT);
Packit 6c4009
        touch (target, 0);
Packit 6c4009
      }
Packit 6c4009
Packit 6c4009
    VERIFY (mount ("/console", "/dev/console", NULL, MS_BIND, NULL) == 0);
Packit 6c4009
    VERIFY (mount ("/console", target, NULL, MS_BIND, NULL) == 0);
Packit 6c4009
    if (!doit (slave, "with readlink target",
Packit 6c4009
               (struct result_r){.name=target, .ret=0, .err=0}))
Packit 6c4009
      ok = false;
Packit 6c4009
    VERIFY (umount (target) == 0);
Packit 6c4009
    VERIFY (umount ("/dev/console") == 0);
Packit 6c4009
Packit 6c4009
    VERIFY (mount ("/console", "/dev/console", NULL, MS_BIND, NULL) == 0);
Packit 6c4009
    VERIFY (mount (slavename, target, NULL, MS_BIND, NULL) == 0);
Packit 6c4009
    if (!doit (slave, "with readlink trap; fallback",
Packit 6c4009
               (struct result_r){.name="/dev/console", .ret=0, .err=0}))
Packit 6c4009
      ok = false;
Packit 6c4009
    VERIFY (umount (target) == 0);
Packit 6c4009
    VERIFY (umount ("/dev/console") == 0);
Packit 6c4009
Packit 6c4009
    VERIFY (mount (slavename, target, NULL, MS_BIND, NULL) == 0);
Packit 6c4009
    if (!doit (slave, "with readlink trap; no fallback",
Packit 6c4009
               (struct result_r){.name=NULL, .ret=ENODEV, .err=ENODEV}))
Packit 6c4009
      ok = false;
Packit 6c4009
    VERIFY (umount (target) == 0);
Packit 6c4009
  }
Packit 6c4009
Packit 6c4009
  /* This test makes sure that everything still works OK if readdir
Packit 6c4009
     finds a pseudo-match before and/or after the actual match.  Now,
Packit 6c4009
     to do that, we need to control that readdir finds the
Packit 6c4009
     pseudo-matches before and after the actual match; and there's no
Packit 6c4009
     good way to control that order in absence of whitebox testing.
Packit 6c4009
     So, just create 3 files, then use opendir/readdir to see what
Packit 6c4009
     order they are in, and assign meaning based on that order, not by
Packit 6c4009
     name; assigning the first to be a pseudo-match, the second to be
Packit 6c4009
     the actual match, and the third to be a pseudo-match.  This
Packit 6c4009
     assumes that (on tmpfs) ordering within the directory is stable
Packit 6c4009
     in the absence of modification, which seems reasonably safe.  */
Packit 6c4009
  {
Packit 6c4009
    /* since we're testing the fallback search, disable the readlink
Packit 6c4009
       happy-path */
Packit 6c4009
    VERIFY (umount2 ("/proc", MNT_DETACH) == 0);
Packit 6c4009
Packit 6c4009
    touch ("/dev/console1", 0);
Packit 6c4009
    touch ("/dev/console2", 0);
Packit 6c4009
    touch ("/dev/console3", 0);
Packit 6c4009
Packit 6c4009
    char *c[3];
Packit 6c4009
    int ci = 0;
Packit 6c4009
    DIR *dirstream = opendir ("/dev");
Packit 6c4009
    VERIFY (dirstream != NULL);
Packit 6c4009
    struct dirent *d;
Packit 6c4009
    while ((d = readdir (dirstream)) != NULL && ci < 3)
Packit 6c4009
      {
Packit 6c4009
        if (strcmp (d->d_name, "console1") &&
Packit 6c4009
            strcmp (d->d_name, "console2") &&
Packit 6c4009
            strcmp (d->d_name, "console3") )
Packit 6c4009
          continue;
Packit 6c4009
        c[ci++] = xasprintf ("/dev/%s", d->d_name);
Packit 6c4009
      }
Packit 6c4009
    VERIFY (ci == 3);
Packit 6c4009
    VERIFY (closedir (dirstream) == 0);
Packit 6c4009
Packit 6c4009
    VERIFY (mount (slavename, c[0], NULL, MS_BIND, NULL) == 0);
Packit 6c4009
    VERIFY (mount ("/console", c[1], NULL, MS_BIND, NULL) == 0);
Packit 6c4009
    VERIFY (mount (slavename, c[2], NULL, MS_BIND, NULL) == 0);
Packit 6c4009
    VERIFY (umount2 ("/dev/pts", MNT_DETACH) == 0);
Packit 6c4009
    if (!doit (slave, "with search-path trap",
Packit 6c4009
               (struct result_r){.name=c[1], .ret=0, .err=0}))
Packit 6c4009
      ok = false;
Packit 6c4009
    for (int i = 0; i < 3; i++)
Packit 6c4009
      {
Packit 6c4009
        VERIFY (umount (c[i]) == 0);
Packit 6c4009
        VERIFY (unlink (c[i]) == 0);
Packit 6c4009
        free (c[i]);
Packit 6c4009
      }
Packit 6c4009
  }
Packit 6c4009
Packit 6c4009
  return ok ? 0 : 1;
Packit 6c4009
}
Packit 6c4009
Packit 6c4009
static int
Packit 6c4009
do_test (void)
Packit 6c4009
{
Packit 6c4009
  support_become_root ();
Packit 6c4009
Packit 6c4009
  int ret1 = do_in_chroot_1 (run_chroot_tests);
Packit 6c4009
  if (ret1 == EXIT_UNSUPPORTED)
Packit 6c4009
    return ret1;
Packit 6c4009
Packit 6c4009
  int ret2 = do_in_chroot_2 (run_chroot_tests);
Packit 6c4009
  if (ret2 == EXIT_UNSUPPORTED)
Packit 6c4009
    return ret2;
Packit 6c4009
Packit 6c4009
  return  ret1 | ret2;
Packit 6c4009
}
Packit 6c4009
Packit 6c4009
#include <support/test-driver.c>