1/* Test case for cache invalidation after concurrent write (bug 24882).
2 Copyright (C) 2019-2022 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. */
41static char *utmp_file;
42
43/* Used to synchronize the subprocesses. The barrier itself is
44 allocated in shared memory. */
45static pthread_barrier_t *barrier;
46
47/* setutxent with error checking. */
48static void
49xsetutxent (void)
50{
51 errno = 0;
52 setutxent ();
53 TEST_COMPARE (errno, 0);
54}
55
56/* getutxent with error checking. */
57static struct utmpx *
58xgetutxent (void)
59{
60 errno = 0;
61 struct utmpx *result = getutxent ();
62 if (result == NULL)
63 FAIL_EXIT1 ("getutxent: %m");
64 return result;
65}
66
67static void
68put_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
85static __attribute__ ((noreturn)) void
86process1 (void)
87{
88 TEST_COMPARE (utmpname (utmp_file), 0);
89
90 /* Create an entry. */
91 xsetutxent ();
92 put_entry (id: "1", pid: 101, user: "root", line: "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 (line: &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 (id: "1", pid: 103, user: "root", line: "process1");
118
119 _exit (0);
120}
121
122static void
123process2 (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 (id: "2", pid: 102, user: "user", line: "process2");
137
138 /* Signal the other process that the entry has been replaced. */
139 xpthread_barrier_wait (barrier);
140}
141
142static int
143do_test (void)
144{
145 xclose (create_temp_file (base: "tst-tumpx-cache-write-", filename: &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 (size: sizeof (*barrier));
151 xpthread_barrier_init (barrier, attr: &attr, count: 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 (callback: process2, NULL);
160 int status;
161 xwaitpid (pid1, status: &status, flags: 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 (ptr: utmp_file);
189
190 return 0;
191}
192
193#include <support/test-driver.c>
194

source code of glibc/login/tst-pututxline-cache.c