Blame login/tst-pututxline-cache.c

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