Blame login/tst-pututxline-cache.c

Packit Service f9b05c
/* Test case for cache invalidation after concurrent write (bug 24882).
Packit Service f9b05c
   Copyright (C) 2019 Free Software Foundation, Inc.
Packit Service f9b05c
   This file is part of the GNU C Library.
Packit Service f9b05c
Packit Service f9b05c
   The GNU C Library is free software; you can redistribute it and/or
Packit Service f9b05c
   modify it under the terms of the GNU Lesser General Public License as
Packit Service f9b05c
   published by the Free Software Foundation; either version 2.1 of the
Packit Service f9b05c
   License, or (at your option) any later version.
Packit Service f9b05c
Packit Service f9b05c
   The GNU C Library is distributed in the hope that it will be useful,
Packit Service f9b05c
   but WITHOUT ANY WARRANTY; without even the implied warranty of
Packit Service f9b05c
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Packit Service f9b05c
   Lesser General Public License for more details.
Packit Service f9b05c
Packit Service f9b05c
   You should have received a copy of the GNU Lesser General Public
Packit Service f9b05c
   License along with the GNU C Library; see the file COPYING.LIB.  If
Packit Service f9b05c
   not, see <http://www.gnu.org/licenses/>.  */
Packit Service f9b05c
Packit Service f9b05c
/* This test writes an entry to the utmpx file, reads it (so that it
Packit Service f9b05c
   is cached) in process1, and overwrites the same entry in process2
Packit Service f9b05c
   with something that does not match the search criteria.  At this
Packit Service f9b05c
   point, the cache of the first process is stale, and when process1
Packit Service f9b05c
   attempts to write a new record which would have gone to the same
Packit Service f9b05c
   place (as indicated by the cache), it needs to realize that it has
Packit Service f9b05c
   to pick a different slot because the old slot is now used for
Packit Service f9b05c
   something else.  */
Packit Service f9b05c
Packit Service f9b05c
#include <errno.h>
Packit Service f9b05c
#include <stdlib.h>
Packit Service f9b05c
#include <string.h>
Packit Service f9b05c
#include <support/check.h>
Packit Service f9b05c
#include <support/namespace.h>
Packit Service f9b05c
#include <support/support.h>
Packit Service f9b05c
#include <support/temp_file.h>
Packit Service f9b05c
#include <support/xthread.h>
Packit Service f9b05c
#include <support/xunistd.h>
Packit Service f9b05c
#include <utmp.h>
Packit Service f9b05c
#include <utmpx.h>
Packit Service f9b05c
Packit Service f9b05c
/* Set to the path of the utmp file.  */
Packit Service f9b05c
static char *utmp_file;
Packit Service f9b05c
Packit Service f9b05c
/* Used to synchronize the subprocesses.  The barrier itself is
Packit Service f9b05c
   allocated in shared memory.  */
Packit Service f9b05c
static pthread_barrier_t *barrier;
Packit Service f9b05c
Packit Service f9b05c
/* setutxent with error checking.  */
Packit Service f9b05c
static void
Packit Service f9b05c
xsetutxent (void)
Packit Service f9b05c
{
Packit Service f9b05c
  errno = 0;
Packit Service f9b05c
  setutxent ();
Packit Service f9b05c
  TEST_COMPARE (errno, 0);
Packit Service f9b05c
}
Packit Service f9b05c
Packit Service f9b05c
/* getutxent with error checking.  */
Packit Service f9b05c
static struct utmpx *
Packit Service f9b05c
xgetutxent (void)
Packit Service f9b05c
{
Packit Service f9b05c
  errno = 0;
Packit Service f9b05c
  struct utmpx *result = getutxent ();
Packit Service f9b05c
  if (result == NULL)
Packit Service f9b05c
    FAIL_EXIT1 ("getutxent: %m");
Packit Service f9b05c
  return result;
Packit Service f9b05c
}
Packit Service f9b05c
Packit Service f9b05c
static void
Packit Service f9b05c
put_entry (const char *id, pid_t pid, const char *user, const char *line)
Packit Service f9b05c
{
Packit Service f9b05c
  struct utmpx ut =
Packit Service f9b05c
    {
Packit Service f9b05c
     .ut_type = LOGIN_PROCESS,
Packit Service f9b05c
     .ut_pid = pid,
Packit Service f9b05c
     .ut_host = "localhost",
Packit Service f9b05c
    };
Packit Service f9b05c
  strcpy (ut.ut_id, id);
Packit Service f9b05c
  strncpy (ut.ut_user, user, sizeof (ut.ut_user));
Packit Service f9b05c
  strncpy (ut.ut_line, line, sizeof (ut.ut_line));
Packit Service f9b05c
  TEST_VERIFY (pututxline (&ut) != NULL);
Packit Service f9b05c
}
Packit Service f9b05c
Packit Service f9b05c
/* Use two cooperating subprocesses to avoid issues related to
Packit Service f9b05c
   unlock-on-close semantics of POSIX advisory locks.  */
Packit Service f9b05c
Packit Service f9b05c
static __attribute__ ((noreturn)) void
Packit Service f9b05c
process1 (void)
Packit Service f9b05c
{
Packit Service f9b05c
  TEST_COMPARE (utmpname (utmp_file), 0);
Packit Service f9b05c
Packit Service f9b05c
  /* Create an entry.  */
Packit Service f9b05c
  xsetutxent ();
Packit Service f9b05c
  put_entry ("1", 101, "root", "process1");
Packit Service f9b05c
Packit Service f9b05c
  /* Retrieve the entry.  This will fill the internal cache.  */
Packit Service f9b05c
  {
Packit Service f9b05c
    errno = 0;
Packit Service f9b05c
    setutxent ();
Packit Service f9b05c
    TEST_COMPARE (errno, 0);
Packit Service f9b05c
    struct utmpx ut =
Packit Service f9b05c
      {
Packit Service f9b05c
       .ut_type = LOGIN_PROCESS,
Packit Service f9b05c
       .ut_line = "process1",
Packit Service f9b05c
      };
Packit Service f9b05c
    struct utmpx *result = getutxline (&ut);
Packit Service f9b05c
    if (result == NULL)
Packit Service f9b05c
      FAIL_EXIT1 ("getutxline (\"process1\"): %m");
Packit Service f9b05c
    TEST_COMPARE (result->ut_pid, 101);
Packit Service f9b05c
  }
Packit Service f9b05c
Packit Service f9b05c
  /* Signal the other process to overwrite the entry.  */
Packit Service f9b05c
  xpthread_barrier_wait (barrier);
Packit Service f9b05c
Packit Service f9b05c
  /* Wait for the other process to complete the write operation.  */
Packit Service f9b05c
  xpthread_barrier_wait (barrier);
Packit Service f9b05c
Packit Service f9b05c
  /* Add another entry.  Note: This time, there is no setutxent call.  */
Packit Service f9b05c
  put_entry ("1", 103, "root", "process1");
Packit Service f9b05c
Packit Service f9b05c
  _exit (0);
Packit Service f9b05c
}
Packit Service f9b05c
Packit Service f9b05c
static void
Packit Service f9b05c
process2 (void *closure)
Packit Service f9b05c
{
Packit Service f9b05c
  /* Wait for the first process to write its entry.  */
Packit Service f9b05c
  xpthread_barrier_wait (barrier);
Packit Service f9b05c
Packit Service f9b05c
  /* Truncate the file.  The glibc interface does not support
Packit Service f9b05c
     re-purposing records, but an external expiration mechanism may
Packit Service f9b05c
     trigger this.  */
Packit Service f9b05c
  TEST_COMPARE (truncate64 (utmp_file, 0), 0);
Packit Service f9b05c
Packit Service f9b05c
  /* Write the replacement entry.  */
Packit Service f9b05c
  TEST_COMPARE (utmpname (utmp_file), 0);
Packit Service f9b05c
  xsetutxent ();
Packit Service f9b05c
  put_entry ("2", 102, "user", "process2");
Packit Service f9b05c
Packit Service f9b05c
  /* Signal the other process that the entry has been replaced.  */
Packit Service f9b05c
  xpthread_barrier_wait (barrier);
Packit Service f9b05c
}
Packit Service f9b05c
Packit Service f9b05c
static int
Packit Service f9b05c
do_test (void)
Packit Service f9b05c
{
Packit Service f9b05c
  xclose (create_temp_file ("tst-tumpx-cache-write-", &utmp_file));
Packit Service f9b05c
  {
Packit Service f9b05c
    pthread_barrierattr_t attr;
Packit Service f9b05c
    xpthread_barrierattr_init (&attr);
Packit Service f9b05c
    xpthread_barrierattr_setpshared (&attr, PTHREAD_SCOPE_PROCESS);
Packit Service f9b05c
    barrier = support_shared_allocate (sizeof (*barrier));
Packit Service f9b05c
    xpthread_barrier_init (barrier, &attr, 2);
Packit Service f9b05c
  }
Packit Service f9b05c
Packit Service f9b05c
  /* Run both subprocesses in parallel.  */
Packit Service f9b05c
  {
Packit Service f9b05c
    pid_t pid1 = xfork ();
Packit Service f9b05c
    if (pid1 == 0)
Packit Service f9b05c
      process1 ();
Packit Service f9b05c
    support_isolate_in_subprocess (process2, NULL);
Packit Service f9b05c
    int status;
Packit Service f9b05c
    xwaitpid (pid1, &status, 0);
Packit Service f9b05c
    TEST_COMPARE (status, 0);
Packit Service f9b05c
  }
Packit Service f9b05c
Packit Service f9b05c
  /* Check that the utmpx database contains the expected records.  */
Packit Service f9b05c
  {
Packit Service f9b05c
    TEST_COMPARE (utmpname (utmp_file), 0);
Packit Service f9b05c
    xsetutxent ();
Packit Service f9b05c
Packit Service f9b05c
    struct utmpx *ut = xgetutxent ();
Packit Service f9b05c
    TEST_COMPARE_STRING (ut->ut_id, "2");
Packit Service f9b05c
    TEST_COMPARE (ut->ut_pid, 102);
Packit Service f9b05c
    TEST_COMPARE_STRING (ut->ut_user, "user");
Packit Service f9b05c
    TEST_COMPARE_STRING (ut->ut_line, "process2");
Packit Service f9b05c
Packit Service f9b05c
    ut = xgetutxent ();
Packit Service f9b05c
    TEST_COMPARE_STRING (ut->ut_id, "1");
Packit Service f9b05c
    TEST_COMPARE (ut->ut_pid, 103);
Packit Service f9b05c
    TEST_COMPARE_STRING (ut->ut_user, "root");
Packit Service f9b05c
    TEST_COMPARE_STRING (ut->ut_line, "process1");
Packit Service f9b05c
Packit Service f9b05c
    if (getutxent () != NULL)
Packit Service f9b05c
      FAIL_EXIT1 ("additional utmpx entry");
Packit Service f9b05c
  }
Packit Service f9b05c
Packit Service f9b05c
  xpthread_barrier_destroy (barrier);
Packit Service f9b05c
  support_shared_free (barrier);
Packit Service f9b05c
  free (utmp_file);
Packit Service f9b05c
Packit Service f9b05c
  return 0;
Packit Service f9b05c
}
Packit Service f9b05c
Packit Service f9b05c
#include <support/test-driver.c>