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. */ |
38 | static char *path; |
39 | |
40 | /* Used to synchronize the subprocesses. The barrier itself is |
41 | allocated in shared memory. */ |
42 | static pthread_barrier_t *barrier; |
43 | |
44 | /* Use pututxline to write an entry for PID. */ |
45 | static struct utmpx * |
46 | write_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. */ |
62 | static void |
63 | subprocess_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 |
71 | subprocess_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 | |
104 | static int |
105 | do_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 | |