1 /* Test case for cache invalidation after concurrent write (bug 24882).
2    Copyright (C) 2019-2021 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 <http://www.gnu.org/licenses/>.  */
18 
19 /* This test writes an entry to the utmpx file, reads it (so that it
20    is cached) in process1, and overwrites the same entry in process2
21    with something that does not match the search criteria.  At this
22    point, the cache of the first process is stale, and when process1
23    attempts to write a new record which would have gone to the same
24    place (as indicated by the cache), it needs to realize that it has
25    to pick a different slot because the old slot is now used for
26    something else.  */
27 
28 #include <errno.h>
29 #include <stdlib.h>
30 #include <string.h>
31 #include <support/check.h>
32 #include <support/namespace.h>
33 #include <support/support.h>
34 #include <support/temp_file.h>
35 #include <support/xthread.h>
36 #include <support/xunistd.h>
37 #include <utmp.h>
38 #include <utmpx.h>
39 
40 /* Set to the path of the utmp file.  */
41 static char *utmp_file;
42 
43 /* Used to synchronize the subprocesses.  The barrier itself is
44    allocated in shared memory.  */
45 static pthread_barrier_t *barrier;
46 
47 /* setutxent with error checking.  */
48 static void
xsetutxent(void)49 xsetutxent (void)
50 {
51   errno = 0;
52   setutxent ();
53   TEST_COMPARE (errno, 0);
54 }
55 
56 /* getutxent with error checking.  */
57 static struct utmpx *
xgetutxent(void)58 xgetutxent (void)
59 {
60   errno = 0;
61   struct utmpx *result = getutxent ();
62   if (result == NULL)
63     FAIL_EXIT1 ("getutxent: %m");
64   return result;
65 }
66 
67 static void
put_entry(const char * id,pid_t pid,const char * user,const char * line)68 put_entry (const char *id, pid_t pid, const char *user, const char *line)
69 {
70   struct utmpx ut =
71     {
72      .ut_type = LOGIN_PROCESS,
73      .ut_pid = pid,
74      .ut_host = "localhost",
75     };
76   strcpy (ut.ut_id, id);
77   strncpy (ut.ut_user, user, sizeof (ut.ut_user));
78   strncpy (ut.ut_line, line, sizeof (ut.ut_line));
79   TEST_VERIFY (pututxline (&ut) != NULL);
80 }
81 
82 /* Use two cooperating subprocesses to avoid issues related to
83    unlock-on-close semantics of POSIX advisory locks.  */
84 
85 static __attribute__ ((noreturn)) void
process1(void)86 process1 (void)
87 {
88   TEST_COMPARE (utmpname (utmp_file), 0);
89 
90   /* Create an entry.  */
91   xsetutxent ();
92   put_entry ("1", 101, "root", "process1");
93 
94   /* Retrieve the entry.  This will fill the internal cache.  */
95   {
96     errno = 0;
97     setutxent ();
98     TEST_COMPARE (errno, 0);
99     struct utmpx ut =
100       {
101        .ut_type = LOGIN_PROCESS,
102        .ut_line = "process1",
103       };
104     struct utmpx *result = getutxline (&ut);
105     if (result == NULL)
106       FAIL_EXIT1 ("getutxline (\"process1\"): %m");
107     TEST_COMPARE (result->ut_pid, 101);
108   }
109 
110   /* Signal the other process to overwrite the entry.  */
111   xpthread_barrier_wait (barrier);
112 
113   /* Wait for the other process to complete the write operation.  */
114   xpthread_barrier_wait (barrier);
115 
116   /* Add another entry.  Note: This time, there is no setutxent call.  */
117   put_entry ("1", 103, "root", "process1");
118 
119   _exit (0);
120 }
121 
122 static void
process2(void * closure)123 process2 (void *closure)
124 {
125   /* Wait for the first process to write its entry.  */
126   xpthread_barrier_wait (barrier);
127 
128   /* Truncate the file.  The glibc interface does not support
129      re-purposing records, but an external expiration mechanism may
130      trigger this.  */
131   TEST_COMPARE (truncate64 (utmp_file, 0), 0);
132 
133   /* Write the replacement entry.  */
134   TEST_COMPARE (utmpname (utmp_file), 0);
135   xsetutxent ();
136   put_entry ("2", 102, "user", "process2");
137 
138   /* Signal the other process that the entry has been replaced.  */
139   xpthread_barrier_wait (barrier);
140 }
141 
142 static int
do_test(void)143 do_test (void)
144 {
145   xclose (create_temp_file ("tst-tumpx-cache-write-", &utmp_file));
146   {
147     pthread_barrierattr_t attr;
148     xpthread_barrierattr_init (&attr);
149     xpthread_barrierattr_setpshared (&attr, PTHREAD_SCOPE_PROCESS);
150     barrier = support_shared_allocate (sizeof (*barrier));
151     xpthread_barrier_init (barrier, &attr, 2);
152   }
153 
154   /* Run both subprocesses in parallel.  */
155   {
156     pid_t pid1 = xfork ();
157     if (pid1 == 0)
158       process1 ();
159     support_isolate_in_subprocess (process2, NULL);
160     int status;
161     xwaitpid (pid1, &status, 0);
162     TEST_COMPARE (status, 0);
163   }
164 
165   /* Check that the utmpx database contains the expected records.  */
166   {
167     TEST_COMPARE (utmpname (utmp_file), 0);
168     xsetutxent ();
169 
170     struct utmpx *ut = xgetutxent ();
171     TEST_COMPARE_STRING (ut->ut_id, "2");
172     TEST_COMPARE (ut->ut_pid, 102);
173     TEST_COMPARE_STRING (ut->ut_user, "user");
174     TEST_COMPARE_STRING (ut->ut_line, "process2");
175 
176     ut = xgetutxent ();
177     TEST_COMPARE_STRING (ut->ut_id, "1");
178     TEST_COMPARE (ut->ut_pid, 103);
179     TEST_COMPARE_STRING (ut->ut_user, "root");
180     TEST_COMPARE_STRING (ut->ut_line, "process1");
181 
182     if (getutxent () != NULL)
183       FAIL_EXIT1 ("additional utmpx entry");
184   }
185 
186   xpthread_barrier_destroy (barrier);
187   support_shared_free (barrier);
188   free (utmp_file);
189 
190   return 0;
191 }
192 
193 #include <support/test-driver.c>
194