stdlib: Optimization qsort{_r} swap implementation
[glibc.git] / login / utmp_file.c
blob7055041d5f0739f82ed6850cb9e18274086db830
1 /* Copyright (C) 1996-2023 Free Software Foundation, Inc.
2 This file is part of the GNU C Library.
4 The GNU C Library is free software; you can redistribute it and/or
5 modify it under the terms of the GNU Lesser General Public
6 License as published by the Free Software Foundation; either
7 version 2.1 of the License, or (at your option) any later version.
9 The GNU C Library is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 Lesser General Public License for more details.
14 You should have received a copy of the GNU Lesser General Public
15 License along with the GNU C Library; if not, see
16 <https://www.gnu.org/licenses/>. */
18 #include <assert.h>
19 #include <errno.h>
20 #include <fcntl.h>
21 #include <signal.h>
22 #include <stdbool.h>
23 #include <stdio.h>
24 #include <string.h>
25 #include <unistd.h>
26 #include <utmp.h>
27 #include <not-cancel.h>
28 #include <kernel-features.h>
29 #include <sigsetops.h>
30 #include <not-cancel.h>
32 #include "utmp-private.h"
33 #include "utmp-equal.h"
36 /* Descriptor for the file and position. */
37 static int file_fd = -1;
38 static bool file_writable;
39 static off64_t file_offset;
41 /* Cache for the last read entry. */
42 static struct utmp last_entry;
44 /* Returns true if *ENTRY matches last_entry, based on
45 data->ut_type. */
46 static bool
47 matches_last_entry (const struct utmp *data)
49 if (file_offset <= 0)
50 /* Nothing has been read. last_entry is stale and cannot match. */
51 return false;
53 if (data->ut_type == RUN_LVL
54 || data->ut_type == BOOT_TIME
55 || data->ut_type == OLD_TIME
56 || data->ut_type == NEW_TIME)
57 /* For some entry types, only a type match is required. */
58 return data->ut_type == last_entry.ut_type;
59 else
60 /* For the process-related entries, a full match is needed. */
61 return __utmp_equal (&last_entry, data);
64 /* Locking timeout. */
65 #ifndef TIMEOUT
66 # define TIMEOUT 10
67 #endif
69 /* Do-nothing handler for locking timeout. */
70 static void timeout_handler (int signum) {};
73 /* try_file_lock (LOCKING, FD, TYPE) returns true if the locking
74 operation failed and recovery needs to be performed.
76 file_unlock (FD) removes the lock (which must have been
77 successfully acquired). */
79 static bool
80 try_file_lock (int fd, int type)
82 /* Cancel any existing alarm. */
83 int old_timeout = alarm (0);
85 /* Establish signal handler. */
86 struct sigaction old_action;
87 struct sigaction action;
88 action.sa_handler = timeout_handler;
89 __sigemptyset (&action.sa_mask);
90 action.sa_flags = 0;
91 __sigaction (SIGALRM, &action, &old_action);
93 alarm (TIMEOUT);
95 /* Try to get the lock. */
96 struct flock64 fl =
98 .l_type = type,
99 .l_whence = SEEK_SET,
102 bool status = __fcntl64_nocancel (fd, F_SETLKW, &fl) < 0;
103 int saved_errno = errno;
105 /* Reset the signal handler and alarm. We must reset the alarm
106 before resetting the handler so our alarm does not generate a
107 spurious SIGALRM seen by the user. However, we cannot just set
108 the user's old alarm before restoring the handler, because then
109 it's possible our handler could catch the user alarm's SIGARLM and
110 then the user would never see the signal he expected. */
111 alarm (0);
112 __sigaction (SIGALRM, &old_action, NULL);
113 if (old_timeout != 0)
114 alarm (old_timeout);
116 __set_errno (saved_errno);
117 return status;
120 static void
121 file_unlock (int fd)
123 struct flock64 fl =
125 .l_type = F_UNLCK,
127 __fcntl64_nocancel (fd, F_SETLKW, &fl);
130 #ifndef TRANSFORM_UTMP_FILE_NAME
131 # define TRANSFORM_UTMP_FILE_NAME(file_name) (file_name)
132 #endif
135 __libc_setutent (void)
137 if (file_fd < 0)
139 const char *file_name;
141 file_name = TRANSFORM_UTMP_FILE_NAME (__libc_utmp_file_name);
143 file_writable = false;
144 file_fd = __open_nocancel
145 (file_name, O_RDONLY | O_LARGEFILE | O_CLOEXEC);
146 if (file_fd == -1)
147 return 0;
150 __lseek64 (file_fd, 0, SEEK_SET);
151 file_offset = 0;
153 return 1;
156 /* Perform initialization if necessary. */
157 static bool
158 maybe_setutent (void)
160 return file_fd >= 0 || __libc_setutent ();
163 /* Reads the entry at file_offset, storing it in last_entry and
164 updating file_offset on success. Returns -1 for a read error, 0
165 for EOF, and 1 for a successful read. last_entry and file_offset
166 are only updated on a successful and complete read. */
167 static ssize_t
168 read_last_entry (void)
170 struct utmp buffer;
171 ssize_t nbytes = __pread64_nocancel (file_fd, &buffer, sizeof (buffer),
172 file_offset);
173 if (nbytes < 0)
174 return -1;
175 else if (nbytes != sizeof (buffer))
176 /* Assume EOF. */
177 return 0;
178 else
180 last_entry = buffer;
181 file_offset += sizeof (buffer);
182 return 1;
187 __libc_getutent_r (struct utmp *buffer, struct utmp **result)
189 int saved_errno = errno;
191 if (!maybe_setutent ())
193 /* Not available. */
194 *result = NULL;
195 return -1;
198 if (try_file_lock (file_fd, F_RDLCK))
199 return -1;
201 ssize_t nbytes = read_last_entry ();
202 file_unlock (file_fd);
204 if (nbytes <= 0) /* Read error or EOF. */
206 if (nbytes == 0)
207 /* errno should be unchanged to indicate success. A premature
208 EOF is treated like an EOF (missing complete record at the
209 end). */
210 __set_errno (saved_errno);
211 *result = NULL;
212 return -1;
215 memcpy (buffer, &last_entry, sizeof (struct utmp));
216 *result = buffer;
218 return 0;
222 /* Search for *ID, updating last_entry and file_offset. Return 0 on
223 success and -1 on failure. Does not perform locking; for that see
224 internal_getut_r below. */
225 static int
226 internal_getut_nolock (const struct utmp *id)
228 while (1)
230 ssize_t nbytes = read_last_entry ();
231 if (nbytes < 0)
232 return -1;
233 if (nbytes == 0)
235 /* End of file reached. */
236 __set_errno (ESRCH);
237 return -1;
240 if (matches_last_entry (id))
241 break;
244 return 0;
247 /* Search for *ID, updating last_entry and file_offset. Return 0 on
248 success and -1 on failure. If the locking operation failed, write
249 true to *LOCK_FAILED. */
250 static int
251 internal_getut_r (const struct utmp *id, bool *lock_failed)
253 if (try_file_lock (file_fd, F_RDLCK))
255 *lock_failed = true;
256 return -1;
259 int result = internal_getut_nolock (id);
260 file_unlock (file_fd);
261 return result;
264 /* For implementing this function we don't use the getutent_r function
265 because we can avoid the reposition on every new entry this way. */
267 __libc_getutid_r (const struct utmp *id, struct utmp *buffer,
268 struct utmp **result)
270 if (!maybe_setutent ())
272 *result = NULL;
273 return -1;
276 /* We don't have to distinguish whether we can lock the file or
277 whether there is no entry. */
278 bool lock_failed = false;
279 if (internal_getut_r (id, &lock_failed) < 0)
281 *result = NULL;
282 return -1;
285 memcpy (buffer, &last_entry, sizeof (struct utmp));
286 *result = buffer;
288 return 0;
291 /* For implementing this function we don't use the getutent_r function
292 because we can avoid the reposition on every new entry this way. */
294 __libc_getutline_r (const struct utmp *line, struct utmp *buffer,
295 struct utmp **result)
297 if (!maybe_setutent ())
299 *result = NULL;
300 return -1;
303 if (try_file_lock (file_fd, F_RDLCK))
305 *result = NULL;
306 return -1;
309 while (1)
311 ssize_t nbytes = read_last_entry ();
312 if (nbytes < 0)
314 file_unlock (file_fd);
315 *result = NULL;
316 return -1;
318 if (nbytes == 0)
320 /* End of file reached. */
321 file_unlock (file_fd);
322 __set_errno (ESRCH);
323 *result = NULL;
324 return -1;
327 /* Stop if we found a user or login entry. */
328 if ((last_entry.ut_type == USER_PROCESS
329 || last_entry.ut_type == LOGIN_PROCESS)
330 && (strncmp (line->ut_line, last_entry.ut_line, sizeof line->ut_line)
331 == 0))
332 break;
335 file_unlock (file_fd);
336 memcpy (buffer, &last_entry, sizeof (struct utmp));
337 *result = buffer;
339 return 0;
343 struct utmp *
344 __libc_pututline (const struct utmp *data)
346 if (!maybe_setutent ())
347 return NULL;
349 struct utmp *pbuf;
351 if (! file_writable)
353 /* We must make the file descriptor writable before going on. */
354 const char *file_name = TRANSFORM_UTMP_FILE_NAME (__libc_utmp_file_name);
356 int new_fd = __open_nocancel
357 (file_name, O_RDWR | O_LARGEFILE | O_CLOEXEC);
358 if (new_fd == -1)
359 return NULL;
361 if (__dup2 (new_fd, file_fd) < 0)
363 __close_nocancel_nostatus (new_fd);
364 return NULL;
366 __close_nocancel_nostatus (new_fd);
367 file_writable = true;
370 /* Exclude other writers before validating the cache. */
371 if (try_file_lock (file_fd, F_WRLCK))
372 return NULL;
374 /* Find the correct place to insert the data. */
375 bool found = false;
376 if (matches_last_entry (data))
378 /* Read back the entry under the write lock. */
379 file_offset -= sizeof (last_entry);
380 ssize_t nbytes = read_last_entry ();
381 if (nbytes < 0)
383 file_unlock (file_fd);
384 return NULL;
387 if (nbytes == 0)
388 /* End of file reached. */
389 found = false;
390 else
391 found = matches_last_entry (data);
394 if (!found)
395 /* Search forward for the entry. */
396 found = internal_getut_nolock (data) >= 0;
398 off64_t write_offset;
399 if (!found)
401 /* We append the next entry. */
402 write_offset = __lseek64 (file_fd, 0, SEEK_END);
404 /* Round down to the next multiple of the entry size. This
405 ensures any partially-written record is overwritten by the
406 new record. */
407 write_offset = (write_offset / sizeof (struct utmp)
408 * sizeof (struct utmp));
410 else
411 /* Overwrite last_entry. */
412 write_offset = file_offset - sizeof (struct utmp);
414 /* Write the new data. */
415 ssize_t nbytes;
416 if (__lseek64 (file_fd, write_offset, SEEK_SET) < 0
417 || (nbytes = __write_nocancel (file_fd, data, sizeof (struct utmp))) < 0)
419 /* There is no need to recover the file position because all
420 reads use pread64, and any future write is preceded by
421 another seek. */
422 file_unlock (file_fd);
423 return NULL;
426 if (nbytes != sizeof (struct utmp))
428 /* If we appended a new record this is only partially written.
429 Remove it. */
430 if (!found)
431 (void) __ftruncate64 (file_fd, write_offset);
432 file_unlock (file_fd);
433 /* Assume that the write failure was due to missing disk
434 space. */
435 __set_errno (ENOSPC);
436 return NULL;
439 file_unlock (file_fd);
440 file_offset = write_offset + sizeof (struct utmp);
441 pbuf = (struct utmp *) data;
443 return pbuf;
447 void
448 __libc_endutent (void)
450 if (file_fd >= 0)
452 __close_nocancel_nostatus (file_fd);
453 file_fd = -1;
459 __libc_updwtmp (const char *file, const struct utmp *utmp)
461 int result = -1;
462 off64_t offset;
463 int fd;
465 /* Open WTMP file. */
466 fd = __open_nocancel (file, O_WRONLY | O_LARGEFILE | O_CLOEXEC);
467 if (fd < 0)
468 return -1;
470 if (try_file_lock (fd, F_WRLCK))
472 __close_nocancel_nostatus (fd);
473 return -1;
476 /* Remember original size of log file. */
477 offset = __lseek64 (fd, 0, SEEK_END);
478 if (offset % sizeof (struct utmp) != 0)
480 offset -= offset % sizeof (struct utmp);
481 __ftruncate64 (fd, offset);
483 if (__lseek64 (fd, 0, SEEK_END) < 0)
484 goto unlock_return;
487 /* Write the entry. If we can't write all the bytes, reset the file
488 size back to the original size. That way, no partial entries
489 will remain. */
490 if (__write_nocancel (fd, utmp, sizeof (struct utmp))
491 != sizeof (struct utmp))
493 __ftruncate64 (fd, offset);
494 goto unlock_return;
497 result = 0;
499 unlock_return:
500 file_unlock (fd);
502 /* Close WTMP file. */
503 __close_nocancel_nostatus (fd);
505 return result;