1/* Copyright (C) 1996-2022 Free Software Foundation, Inc.
2 This file is part of the GNU C Library.
3
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.
8
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.
13
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/>. */
17
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>
31
32#include "utmp-private.h"
33#include "utmp-equal.h"
34
35
36/* Descriptor for the file and position. */
37static int file_fd = -1;
38static bool file_writable;
39static off64_t file_offset;
40
41/* Cache for the last read entry. */
42static struct utmp last_entry;
43
44/* Returns true if *ENTRY matches last_entry, based on
45 data->ut_type. */
46static bool
47matches_last_entry (const struct utmp *data)
48{
49 if (file_offset <= 0)
50 /* Nothing has been read. last_entry is stale and cannot match. */
51 return false;
52
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 (entry: &last_entry, match: data);
62}
63
64/* Locking timeout. */
65#ifndef TIMEOUT
66# define TIMEOUT 10
67#endif
68
69/* Do-nothing handler for locking timeout. */
70static void timeout_handler (int signum) {};
71
72
73/* try_file_lock (LOCKING, FD, TYPE) returns true if the locking
74 operation failed and recovery needs to be performed.
75
76 file_unlock (FD) removes the lock (which must have been
77 successfully acquired). */
78
79static bool
80try_file_lock (int fd, int type)
81{
82 /* Cancel any existing alarm. */
83 int old_timeout = alarm (0);
84
85 /* Establish signal handler. */
86 struct sigaction old_action;
87 struct sigaction action;
88 action.sa_handler = timeout_handler;
89 __sigemptyset (set: &action.sa_mask);
90 action.sa_flags = 0;
91 __sigaction (SIGALRM, &action, &old_action);
92
93 alarm (TIMEOUT);
94
95 /* Try to get the lock. */
96 struct flock64 fl =
97 {
98 .l_type = type,
99 .l_whence = SEEK_SET,
100 };
101
102 bool status = __fcntl64_nocancel (fd, F_SETLKW, &fl) < 0;
103 int saved_errno = errno;
104
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);
115
116 __set_errno (saved_errno);
117 return status;
118}
119
120static void
121file_unlock (int fd)
122{
123 struct flock64 fl =
124 {
125 .l_type = F_UNLCK,
126 };
127 __fcntl64_nocancel (fd, F_SETLKW, &fl);
128}
129
130#ifndef TRANSFORM_UTMP_FILE_NAME
131# define TRANSFORM_UTMP_FILE_NAME(file_name) (file_name)
132#endif
133
134int
135__libc_setutent (void)
136{
137 if (file_fd < 0)
138 {
139 const char *file_name;
140
141 file_name = TRANSFORM_UTMP_FILE_NAME (__libc_utmp_file_name);
142
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;
148 }
149
150 __lseek64 (fd: file_fd, offset: 0, SEEK_SET);
151 file_offset = 0;
152
153 return 1;
154}
155
156/* Preform initialization if necessary. */
157static bool
158maybe_setutent (void)
159{
160 return file_fd >= 0 || __libc_setutent ();
161}
162
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. */
167static ssize_t
168read_last_entry (void)
169{
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
179 {
180 last_entry = buffer;
181 file_offset += sizeof (buffer);
182 return 1;
183 }
184}
185
186int
187__libc_getutent_r (struct utmp *buffer, struct utmp **result)
188{
189 int saved_errno = errno;
190
191 if (!maybe_setutent ())
192 {
193 /* Not available. */
194 *result = NULL;
195 return -1;
196 }
197
198 if (try_file_lock (fd: file_fd, F_RDLCK))
199 return -1;
200
201 ssize_t nbytes = read_last_entry ();
202 file_unlock (fd: file_fd);
203
204 if (nbytes <= 0) /* Read error or EOF. */
205 {
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;
213 }
214
215 memcpy (buffer, &last_entry, sizeof (struct utmp));
216 *result = buffer;
217
218 return 0;
219}
220
221
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. */
225static int
226internal_getut_nolock (const struct utmp *id)
227{
228 while (1)
229 {
230 ssize_t nbytes = read_last_entry ();
231 if (nbytes < 0)
232 return -1;
233 if (nbytes == 0)
234 {
235 /* End of file reached. */
236 __set_errno (ESRCH);
237 return -1;
238 }
239
240 if (matches_last_entry (data: id))
241 break;
242 }
243
244 return 0;
245}
246
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. */
250static int
251internal_getut_r (const struct utmp *id, bool *lock_failed)
252{
253 if (try_file_lock (fd: file_fd, F_RDLCK))
254 {
255 *lock_failed = true;
256 return -1;
257 }
258
259 int result = internal_getut_nolock (id);
260 file_unlock (fd: file_fd);
261 return result;
262}
263
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. */
266int
267__libc_getutid_r (const struct utmp *id, struct utmp *buffer,
268 struct utmp **result)
269{
270 if (!maybe_setutent ())
271 {
272 *result = NULL;
273 return -1;
274 }
275
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: &lock_failed) < 0)
280 {
281 *result = NULL;
282 return -1;
283 }
284
285 memcpy (buffer, &last_entry, sizeof (struct utmp));
286 *result = buffer;
287
288 return 0;
289}
290
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. */
293int
294__libc_getutline_r (const struct utmp *line, struct utmp *buffer,
295 struct utmp **result)
296{
297 if (!maybe_setutent ())
298 {
299 *result = NULL;
300 return -1;
301 }
302
303 if (try_file_lock (fd: file_fd, F_RDLCK))
304 {
305 *result = NULL;
306 return -1;
307 }
308
309 while (1)
310 {
311 ssize_t nbytes = read_last_entry ();
312 if (nbytes < 0)
313 {
314 file_unlock (fd: file_fd);
315 *result = NULL;
316 return -1;
317 }
318 if (nbytes == 0)
319 {
320 /* End of file reached. */
321 file_unlock (fd: file_fd);
322 __set_errno (ESRCH);
323 *result = NULL;
324 return -1;
325 }
326
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;
333 }
334
335 file_unlock (fd: file_fd);
336 memcpy (buffer, &last_entry, sizeof (struct utmp));
337 *result = buffer;
338
339 return 0;
340}
341
342
343struct utmp *
344__libc_pututline (const struct utmp *data)
345{
346 if (!maybe_setutent ())
347 return NULL;
348
349 struct utmp *pbuf;
350
351 if (! file_writable)
352 {
353 /* We must make the file descriptor writable before going on. */
354 const char *file_name = TRANSFORM_UTMP_FILE_NAME (__libc_utmp_file_name);
355
356 int new_fd = __open_nocancel
357 (file_name, O_RDWR | O_LARGEFILE | O_CLOEXEC);
358 if (new_fd == -1)
359 return NULL;
360
361 if (__dup2 (new_fd, file_fd) < 0)
362 {
363 __close_nocancel_nostatus (fd: new_fd);
364 return NULL;
365 }
366 __close_nocancel_nostatus (fd: new_fd);
367 file_writable = true;
368 }
369
370 /* Exclude other writers before validating the cache. */
371 if (try_file_lock (fd: file_fd, F_WRLCK))
372 return NULL;
373
374 /* Find the correct place to insert the data. */
375 bool found = false;
376 if (matches_last_entry (data))
377 {
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)
382 {
383 file_unlock (fd: file_fd);
384 return NULL;
385 }
386
387 if (nbytes == 0)
388 /* End of file reached. */
389 found = false;
390 else
391 found = matches_last_entry (data);
392 }
393
394 if (!found)
395 /* Search forward for the entry. */
396 found = internal_getut_nolock (id: data) >= 0;
397
398 off64_t write_offset;
399 if (!found)
400 {
401 /* We append the next entry. */
402 write_offset = __lseek64 (fd: file_fd, offset: 0, SEEK_END);
403
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));
409 }
410 else
411 /* Overwrite last_entry. */
412 write_offset = file_offset - sizeof (struct utmp);
413
414 /* Write the new data. */
415 ssize_t nbytes;
416 if (__lseek64 (fd: file_fd, offset: write_offset, SEEK_SET) < 0
417 || (nbytes = __write_nocancel (file_fd, data, sizeof (struct utmp))) < 0)
418 {
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 (fd: file_fd);
423 return NULL;
424 }
425
426 if (nbytes != sizeof (struct utmp))
427 {
428 /* If we appended a new record this is only partially written.
429 Remove it. */
430 if (!found)
431 (void) __ftruncate64 (fd: file_fd, length: write_offset);
432 file_unlock (fd: file_fd);
433 /* Assume that the write failure was due to missing disk
434 space. */
435 __set_errno (ENOSPC);
436 return NULL;
437 }
438
439 file_unlock (fd: file_fd);
440 file_offset = write_offset + sizeof (struct utmp);
441 pbuf = (struct utmp *) data;
442
443 return pbuf;
444}
445
446
447void
448__libc_endutent (void)
449{
450 if (file_fd >= 0)
451 {
452 __close_nocancel_nostatus (fd: file_fd);
453 file_fd = -1;
454 }
455}
456
457
458int
459__libc_updwtmp (const char *file, const struct utmp *utmp)
460{
461 int result = -1;
462 off64_t offset;
463 int fd;
464
465 /* Open WTMP file. */
466 fd = __open_nocancel (file, O_WRONLY | O_LARGEFILE);
467 if (fd < 0)
468 return -1;
469
470 if (try_file_lock (fd, F_WRLCK))
471 {
472 __close_nocancel_nostatus (fd);
473 return -1;
474 }
475
476 /* Remember original size of log file. */
477 offset = __lseek64 (fd: fd, offset: 0, SEEK_END);
478 if (offset % sizeof (struct utmp) != 0)
479 {
480 offset -= offset % sizeof (struct utmp);
481 __ftruncate64 (fd: fd, length: offset);
482
483 if (__lseek64 (fd: fd, offset: 0, SEEK_END) < 0)
484 goto unlock_return;
485 }
486
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))
492 {
493 __ftruncate64 (fd: fd, length: offset);
494 goto unlock_return;
495 }
496
497 result = 0;
498
499unlock_return:
500 file_unlock (fd);
501
502 /* Close WTMP file. */
503 __close_nocancel_nostatus (fd);
504
505 return result;
506}
507

source code of glibc/login/utmp_file.c