Blame login/tst-pututxline-cache.c

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