1/* Copyright (C) 1996-2020 Free Software Foundation, Inc.
2 This file is part of the GNU C Library.
3 Contributed by Ulrich Drepper <drepper@cygnus.com>
4 and Paul Janzen <pcj@primenet.com>, 1996.
5
6 The GNU C Library is free software; you can redistribute it and/or
7 modify it under the terms of the GNU Lesser General Public
8 License as published by the Free Software Foundation; either
9 version 2.1 of the License, or (at your option) any later version.
10
11 The GNU C Library is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 Lesser General Public License for more details.
15
16 You should have received a copy of the GNU Lesser General Public
17 License along with the GNU C Library; if not, see
18 <https://www.gnu.org/licenses/>. */
19
20#include <assert.h>
21#include <errno.h>
22#include <fcntl.h>
23#include <signal.h>
24#include <stdbool.h>
25#include <stdio.h>
26#include <string.h>
27#include <unistd.h>
28#include <utmp.h>
29#include <not-cancel.h>
30#include <kernel-features.h>
31#include <sigsetops.h>
32#include <not-cancel.h>
33
34#include "utmp-private.h"
35#include "utmp-equal.h"
36
37
38/* Descriptor for the file and position. */
39static int file_fd = -1;
40static bool file_writable;
41static off64_t file_offset;
42
43/* Cache for the last read entry. */
44static struct utmp last_entry;
45
46/* Returns true if *ENTRY matches last_entry, based on
47 data->ut_type. */
48static bool
49matches_last_entry (const struct utmp *data)
50{
51 if (file_offset <= 0)
52 /* Nothing has been read. last_entry is stale and cannot match. */
53 return false;
54
55 if (data->ut_type == RUN_LVL
56 || data->ut_type == BOOT_TIME
57 || data->ut_type == OLD_TIME
58 || data->ut_type == NEW_TIME)
59 /* For some entry types, only a type match is required. */
60 return data->ut_type == last_entry.ut_type;
61 else
62 /* For the process-related entries, a full match is needed. */
63 return __utmp_equal (&last_entry, data);
64}
65
66/* Locking timeout. */
67#ifndef TIMEOUT
68# define TIMEOUT 10
69#endif
70
71/* Do-nothing handler for locking timeout. */
72static void timeout_handler (int signum) {};
73
74
75/* try_file_lock (LOCKING, FD, TYPE) returns true if the locking
76 operation failed and recovery needs to be performed.
77
78 file_unlock (FD) removes the lock (which must have been
79 successfully acquired). */
80
81static bool
82try_file_lock (int fd, int type)
83{
84 /* Cancel any existing alarm. */
85 int old_timeout = alarm (0);
86
87 /* Establish signal handler. */
88 struct sigaction old_action;
89 struct sigaction action;
90 action.sa_handler = timeout_handler;
91 __sigemptyset (&action.sa_mask);
92 action.sa_flags = 0;
93 __sigaction (SIGALRM, &action, &old_action);
94
95 alarm (TIMEOUT);
96
97 /* Try to get the lock. */
98 struct flock64 fl =
99 {
100 .l_type = type,
101 .l_whence = SEEK_SET,
102 };
103
104 bool status = __fcntl64_nocancel (fd, F_SETLKW, &fl) < 0;
105 int saved_errno = errno;
106
107 /* Reset the signal handler and alarm. We must reset the alarm
108 before resetting the handler so our alarm does not generate a
109 spurious SIGALRM seen by the user. However, we cannot just set
110 the user's old alarm before restoring the handler, because then
111 it's possible our handler could catch the user alarm's SIGARLM and
112 then the user would never see the signal he expected. */
113 alarm (0);
114 __sigaction (SIGALRM, &old_action, NULL);
115 if (old_timeout != 0)
116 alarm (old_timeout);
117
118 __set_errno (saved_errno);
119 return status;
120}
121
122static void
123file_unlock (int fd)
124{
125 struct flock64 fl =
126 {
127 .l_type = F_UNLCK,
128 };
129 __fcntl64_nocancel (fd, F_SETLKW, &fl);
130}
131
132#ifndef TRANSFORM_UTMP_FILE_NAME
133# define TRANSFORM_UTMP_FILE_NAME(file_name) (file_name)
134#endif
135
136int
137__libc_setutent (void)
138{
139 if (file_fd < 0)
140 {
141 const char *file_name;
142
143 file_name = TRANSFORM_UTMP_FILE_NAME (__libc_utmp_file_name);
144
145 file_writable = false;
146 file_fd = __open_nocancel
147 (file_name, O_RDONLY | O_LARGEFILE | O_CLOEXEC);
148 if (file_fd == -1)
149 return 0;
150 }
151
152 __lseek64 (file_fd, 0, SEEK_SET);
153 file_offset = 0;
154
155 return 1;
156}
157
158/* Preform initialization if necessary. */
159static bool
160maybe_setutent (void)
161{
162 return file_fd >= 0 || __libc_setutent ();
163}
164
165/* Reads the entry at file_offset, storing it in last_entry and
166 updating file_offset on success. Returns -1 for a read error, 0
167 for EOF, and 1 for a successful read. last_entry and file_offset
168 are only updated on a successful and complete read. */
169static ssize_t
170read_last_entry (void)
171{
172 struct utmp buffer;
173 ssize_t nbytes = __pread64_nocancel (file_fd, &buffer, sizeof (buffer),
174 file_offset);
175 if (nbytes < 0)
176 return -1;
177 else if (nbytes != sizeof (buffer))
178 /* Assume EOF. */
179 return 0;
180 else
181 {
182 last_entry = buffer;
183 file_offset += sizeof (buffer);
184 return 1;
185 }
186}
187
188int
189__libc_getutent_r (struct utmp *buffer, struct utmp **result)
190{
191 int saved_errno = errno;
192
193 if (!maybe_setutent ())
194 {
195 /* Not available. */
196 *result = NULL;
197 return -1;
198 }
199
200 if (try_file_lock (file_fd, F_RDLCK))
201 return -1;
202
203 ssize_t nbytes = read_last_entry ();
204 file_unlock (file_fd);
205
206 if (nbytes <= 0) /* Read error or EOF. */
207 {
208 if (nbytes == 0)
209 /* errno should be unchanged to indicate success. A premature
210 EOF is treated like an EOF (missing complete record at the
211 end). */
212 __set_errno (saved_errno);
213 *result = NULL;
214 return -1;
215 }
216
217 memcpy (buffer, &last_entry, sizeof (struct utmp));
218 *result = buffer;
219
220 return 0;
221}
222
223
224/* Search for *ID, updating last_entry and file_offset. Return 0 on
225 success and -1 on failure. Does not perform locking; for that see
226 internal_getut_r below. */
227static int
228internal_getut_nolock (const struct utmp *id)
229{
230 while (1)
231 {
232 ssize_t nbytes = read_last_entry ();
233 if (nbytes < 0)
234 return -1;
235 if (nbytes == 0)
236 {
237 /* End of file reached. */
238 __set_errno (ESRCH);
239 return -1;
240 }
241
242 if (matches_last_entry (id))
243 break;
244 }
245
246 return 0;
247}
248
249/* Search for *ID, updating last_entry and file_offset. Return 0 on
250 success and -1 on failure. If the locking operation failed, write
251 true to *LOCK_FAILED. */
252static int
253internal_getut_r (const struct utmp *id, bool *lock_failed)
254{
255 if (try_file_lock (file_fd, F_RDLCK))
256 {
257 *lock_failed = true;
258 return -1;
259 }
260
261 int result = internal_getut_nolock (id);
262 file_unlock (file_fd);
263 return result;
264}
265
266/* For implementing this function we don't use the getutent_r function
267 because we can avoid the reposition on every new entry this way. */
268int
269__libc_getutid_r (const struct utmp *id, struct utmp *buffer,
270 struct utmp **result)
271{
272 if (!maybe_setutent ())
273 {
274 *result = NULL;
275 return -1;
276 }
277
278 /* We don't have to distinguish whether we can lock the file or
279 whether there is no entry. */
280 bool lock_failed = false;
281 if (internal_getut_r (id, &lock_failed) < 0)
282 {
283 *result = NULL;
284 return -1;
285 }
286
287 memcpy (buffer, &last_entry, sizeof (struct utmp));
288 *result = buffer;
289
290 return 0;
291}
292
293/* For implementing this function we don't use the getutent_r function
294 because we can avoid the reposition on every new entry this way. */
295int
296__libc_getutline_r (const struct utmp *line, struct utmp *buffer,
297 struct utmp **result)
298{
299 if (!maybe_setutent ())
300 {
301 *result = NULL;
302 return -1;
303 }
304
305 if (try_file_lock (file_fd, F_RDLCK))
306 {
307 *result = NULL;
308 return -1;
309 }
310
311 while (1)
312 {
313 ssize_t nbytes = read_last_entry ();
314 if (nbytes < 0)
315 {
316 file_unlock (file_fd);
317 *result = NULL;
318 return -1;
319 }
320 if (nbytes == 0)
321 {
322 /* End of file reached. */
323 file_unlock (file_fd);
324 __set_errno (ESRCH);
325 *result = NULL;
326 return -1;
327 }
328
329 /* Stop if we found a user or login entry. */
330 if ((last_entry.ut_type == USER_PROCESS
331 || last_entry.ut_type == LOGIN_PROCESS)
332 && (strncmp (line->ut_line, last_entry.ut_line, sizeof line->ut_line)
333 == 0))
334 break;
335 }
336
337 file_unlock (file_fd);
338 memcpy (buffer, &last_entry, sizeof (struct utmp));
339 *result = buffer;
340
341 return 0;
342}
343
344
345struct utmp *
346__libc_pututline (const struct utmp *data)
347{
348 if (!maybe_setutent ())
349 return NULL;
350
351 struct utmp *pbuf;
352
353 if (! file_writable)
354 {
355 /* We must make the file descriptor writable before going on. */
356 const char *file_name = TRANSFORM_UTMP_FILE_NAME (__libc_utmp_file_name);
357
358 int new_fd = __open_nocancel
359 (file_name, O_RDWR | O_LARGEFILE | O_CLOEXEC);
360 if (new_fd == -1)
361 return NULL;
362
363 if (__dup2 (new_fd, file_fd) < 0)
364 {
365 __close_nocancel_nostatus (new_fd);
366 return NULL;
367 }
368 __close_nocancel_nostatus (new_fd);
369 file_writable = true;
370 }
371
372 /* Exclude other writers before validating the cache. */
373 if (try_file_lock (file_fd, F_WRLCK))
374 return NULL;
375
376 /* Find the correct place to insert the data. */
377 bool found = false;
378 if (matches_last_entry (data))
379 {
380 /* Read back the entry under the write lock. */
381 file_offset -= sizeof (last_entry);
382 ssize_t nbytes = read_last_entry ();
383 if (nbytes < 0)
384 {
385 file_unlock (file_fd);
386 return NULL;
387 }
388
389 if (nbytes == 0)
390 /* End of file reached. */
391 found = false;
392 else
393 found = matches_last_entry (data);
394 }
395
396 if (!found)
397 /* Search forward for the entry. */
398 found = internal_getut_nolock (data) >= 0;
399
400 off64_t write_offset;
401 if (!found)
402 {
403 /* We append the next entry. */
404 write_offset = __lseek64 (file_fd, 0, SEEK_END);
405
406 /* Round down to the next multiple of the entry size. This
407 ensures any partially-written record is overwritten by the
408 new record. */
409 write_offset = (write_offset / sizeof (struct utmp)
410 * sizeof (struct utmp));
411 }
412 else
413 /* Overwrite last_entry. */
414 write_offset = file_offset - sizeof (struct utmp);
415
416 /* Write the new data. */
417 ssize_t nbytes;
418 if (__lseek64 (file_fd, write_offset, SEEK_SET) < 0
419 || (nbytes = __write_nocancel (file_fd, data, sizeof (struct utmp))) < 0)
420 {
421 /* There is no need to recover the file position because all
422 reads use pread64, and any future write is preceded by
423 another seek. */
424 file_unlock (file_fd);
425 return NULL;
426 }
427
428 if (nbytes != sizeof (struct utmp))
429 {
430 /* If we appended a new record this is only partially written.
431 Remove it. */
432 if (!found)
433 (void) __ftruncate64 (file_fd, write_offset);
434 file_unlock (file_fd);
435 /* Assume that the write failure was due to missing disk
436 space. */
437 __set_errno (ENOSPC);
438 return NULL;
439 }
440
441 file_unlock (file_fd);
442 file_offset = write_offset + sizeof (struct utmp);
443 pbuf = (struct utmp *) data;
444
445 return pbuf;
446}
447
448
449void
450__libc_endutent (void)
451{
452 if (file_fd >= 0)
453 {
454 __close_nocancel_nostatus (file_fd);
455 file_fd = -1;
456 }
457}
458
459
460int
461__libc_updwtmp (const char *file, const struct utmp *utmp)
462{
463 int result = -1;
464 off64_t offset;
465 int fd;
466
467 /* Open WTMP file. */
468 fd = __open_nocancel (file, O_WRONLY | O_LARGEFILE);
469 if (fd < 0)
470 return -1;
471
472 if (try_file_lock (fd, F_WRLCK))
473 {
474 __close_nocancel_nostatus (fd);
475 return -1;
476 }
477
478 /* Remember original size of log file. */
479 offset = __lseek64 (fd, 0, SEEK_END);
480 if (offset % sizeof (struct utmp) != 0)
481 {
482 offset -= offset % sizeof (struct utmp);
483 __ftruncate64 (fd, offset);
484
485 if (__lseek64 (fd, 0, SEEK_END) < 0)
486 goto unlock_return;
487 }
488
489 /* Write the entry. If we can't write all the bytes, reset the file
490 size back to the original size. That way, no partial entries
491 will remain. */
492 if (__write_nocancel (fd, utmp, sizeof (struct utmp))
493 != sizeof (struct utmp))
494 {
495 __ftruncate64 (fd, offset);
496 goto unlock_return;
497 }
498
499 result = 0;
500
501unlock_return:
502 file_unlock (fd);
503
504 /* Close WTMP file. */
505 __close_nocancel_nostatus (fd);
506
507 return result;
508}
509