1/* Test the lock upgrade path in tst-pututxline.
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 <https://www.gnu.org/licenses/>. */
18
19/* pututxline upgrades the read lock on the file to a write lock.
20 This test verifies that if the lock upgrade fails, the utmp
21 subsystem remains in a consistent state, so that pututxline can be
22 called again. */
23
24#include <errno.h>
25#include <fcntl.h>
26#include <stdlib.h>
27#include <support/check.h>
28#include <support/namespace.h>
29#include <support/support.h>
30#include <support/temp_file.h>
31#include <support/xthread.h>
32#include <support/xunistd.h>
33#include <unistd.h>
34#include <utmp.h>
35#include <utmpx.h>
36
37/* Path to the temporary utmp file. */
38static char *path;
39
40/* Used to synchronize the subprocesses. The barrier itself is
41 allocated in shared memory. */
42static pthread_barrier_t *barrier;
43
44/* Use pututxline to write an entry for PID. */
45static struct utmpx *
46write_entry (pid_t pid)
47{
48 struct utmpx ut =
49 {
50 .ut_type = LOGIN_PROCESS,
51 .ut_id = "1",
52 .ut_user = "root",
53 .ut_pid = pid,
54 .ut_line = "entry",
55 .ut_host = "localhost",
56 };
57 return pututxline (utmpx: &ut);
58}
59
60/* Create the initial entry in a subprocess, so that the utmp
61 subsystem in the original process is not disturbed. */
62static void
63subprocess_create_entry (void *closure)
64{
65 TEST_COMPARE (utmpname (path), 0);
66 TEST_VERIFY (write_entry (101) != NULL);
67}
68
69/* Acquire an advisory read lock on PATH. */
70__attribute__ ((noreturn)) static void
71subprocess_lock_file (void)
72{
73 int fd = xopen (path, O_RDONLY, 0);
74
75 struct flock64 fl =
76 {
77 .l_type = F_RDLCK,
78 fl.l_whence = SEEK_SET,
79 };
80 TEST_COMPARE (fcntl64 (fd, F_SETLKW, &fl), 0);
81
82 /* Signal to the main process that the lock has been acquired. */
83 xpthread_barrier_wait (barrier);
84
85 /* Wait for the unlock request from the main process. */
86 xpthread_barrier_wait (barrier);
87
88 /* Implicitly unlock the file. */
89 xclose (fd);
90
91 /* Overwrite the existing entry. */
92 TEST_COMPARE (utmpname (path), 0);
93 errno = 0;
94 setutxent ();
95 TEST_COMPARE (errno, 0);
96 TEST_VERIFY (write_entry (102) != NULL);
97 errno = 0;
98 endutxent ();
99 TEST_COMPARE (errno, 0);
100
101 _exit (0);
102}
103
104static int
105do_test (void)
106{
107 xclose (create_temp_file (base: "tst-pututxline-lockfail-", filename: &path));
108
109 {
110 pthread_barrierattr_t attr;
111 xpthread_barrierattr_init (&attr);
112 xpthread_barrierattr_setpshared (&attr, PTHREAD_SCOPE_PROCESS);
113 barrier = support_shared_allocate (size: sizeof (*barrier));
114 xpthread_barrier_init (barrier, attr: &attr, count: 2);
115 xpthread_barrierattr_destroy (&attr);
116 }
117
118 /* Write the initial entry. */
119 support_isolate_in_subprocess (callback: subprocess_create_entry, NULL);
120
121 pid_t locker_pid = xfork ();
122 if (locker_pid == 0)
123 subprocess_lock_file ();
124
125 /* Wait for the file locking to complete. */
126 xpthread_barrier_wait (barrier);
127
128 /* Try to add another entry. This attempt will fail, with EINTR or
129 EAGAIN. */
130 TEST_COMPARE (utmpname (path), 0);
131 TEST_VERIFY (write_entry (102) == NULL);
132 if (errno != EINTR)
133 TEST_COMPARE (errno, EAGAIN);
134
135 /* Signal the subprocess to overwrite the entry. */
136 xpthread_barrier_wait (barrier);
137
138 /* Wait for write and unlock to complete. */
139 {
140 int status;
141 xwaitpid (locker_pid, status: &status, flags: 0);
142 TEST_COMPARE (status, 0);
143 }
144
145 /* The file is no longer locked, so this operation will succeed. */
146 TEST_VERIFY (write_entry (103) != NULL);
147 errno = 0;
148 endutxent ();
149 TEST_COMPARE (errno, 0);
150
151 /* Check that there is just one entry with the expected contents.
152 If pututxline becomes desynchronized internally, the entry is not
153 overwritten (bug 24902). */
154 errno = 0;
155 setutxent ();
156 TEST_COMPARE (errno, 0);
157 struct utmpx *ut = getutxent ();
158 TEST_VERIFY_EXIT (ut != NULL);
159 TEST_COMPARE (ut->ut_type, LOGIN_PROCESS);
160 TEST_COMPARE_STRING (ut->ut_id, "1");
161 TEST_COMPARE_STRING (ut->ut_user, "root");
162 TEST_COMPARE (ut->ut_pid, 103);
163 TEST_COMPARE_STRING (ut->ut_line, "entry");
164 TEST_COMPARE_STRING (ut->ut_host, "localhost");
165 TEST_VERIFY (getutxent () == NULL);
166 errno = 0;
167 endutxent ();
168 TEST_COMPARE (errno, 0);
169
170 xpthread_barrier_destroy (barrier);
171 support_shared_free (barrier);
172 free (ptr: path);
173 return 0;
174}
175
176#include <support/test-driver.c>
177

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