e7743bfac86ff6fcccf6762ff71726f8b55da22b
[kopensolaris-gnu/glibc.git] / login / utmp_file.c
1 /* Copyright (C) 1996-2002, 2003, 2004 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, write to the Free
18    Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
19    02111-1307 USA.  */
20
21 #include <assert.h>
22 #include <errno.h>
23 #include <fcntl.h>
24 #include <signal.h>
25 #include <stdio.h>
26 #include <string.h>
27 #include <unistd.h>
28 #include <utmp.h>
29 #include <not-cancel.h>
30
31 #include "utmp-private.h"
32 #include "utmp-equal.h"
33
34
35 /* Descriptor for the file and position.  */
36 static int file_fd = -1;
37 static off64_t file_offset;
38
39 /* Cache for the last read entry.  */
40 static struct utmp last_entry;
41
42
43 /* Locking timeout.  */
44 #ifndef TIMEOUT
45 # define TIMEOUT 1
46 #endif
47
48 /* Do-nothing handler for locking timeout.  */
49 static void timeout_handler (int signum) {};
50
51 /* LOCK_FILE(fd, type) failure_statement
52      attempts to get a lock on the utmp file referenced by FD.  If it fails,
53      the failure_statement is executed, otherwise it is skipped.
54    LOCKING_FAILED()
55      jumps into the UNLOCK_FILE macro and ensures cleanup of LOCK_FILE.
56    UNLOCK_FILE(fd)
57      unlocks the utmp file referenced by FD and performs the cleanup of
58      LOCK_FILE.
59  */
60 #define LOCK_FILE(fd, type) \
61 {                                                                             \
62   struct flock fl;                                                            \
63   struct sigaction action, old_action;                                        \
64   unsigned int old_timeout;                                                   \
65                                                                               \
66   /* Cancel any existing alarm.  */                                           \
67   old_timeout = alarm (0);                                                    \
68                                                                               \
69   /* Establish signal handler.  */                                            \
70   action.sa_handler = timeout_handler;                                        \
71   __sigemptyset (&action.sa_mask);                                            \
72   action.sa_flags = 0;                                                        \
73   __sigaction (SIGALRM, &action, &old_action);                                \
74                                                                               \
75   alarm (TIMEOUT);                                                            \
76                                                                               \
77   /* Try to get the lock.  */                                                 \
78   memset (&fl, '\0', sizeof (struct flock));                                  \
79   fl.l_type = (type);                                                         \
80   fl.l_whence = SEEK_SET;                                                     \
81   if (fcntl_not_cancel ((fd), F_SETLKW, &fl) < 0)
82
83 #define LOCKING_FAILED() \
84   goto unalarm_return
85
86 #define UNLOCK_FILE(fd) \
87   /* Unlock the file.  */                                                     \
88   fl.l_type = F_UNLCK;                                                        \
89   fcntl_not_cancel ((fd), F_SETLKW, &fl);                                     \
90                                                                               \
91  unalarm_return:                                                              \
92   /* Reset the signal handler and alarm.  We must reset the alarm             \
93      before resetting the handler so our alarm does not generate a            \
94      spurious SIGALRM seen by the user.  However, we cannot just set          \
95      the user's old alarm before restoring the handler, because then          \
96      it's possible our handler could catch the user alarm's SIGARLM           \
97      and then the user would never see the signal he expected.  */            \
98   alarm (0);                                                                  \
99   __sigaction (SIGALRM, &old_action, NULL);                                   \
100   if (old_timeout != 0)                                                       \
101     alarm (old_timeout);                                                      \
102 } while (0)
103
104
105 /* Functions defined here.  */
106 static int setutent_file (void);
107 static int getutent_r_file (struct utmp *buffer, struct utmp **result);
108 static int getutid_r_file (const struct utmp *key, struct utmp *buffer,
109                            struct utmp **result);
110 static int getutline_r_file (const struct utmp *key, struct utmp *buffer,
111                              struct utmp **result);
112 static struct utmp *pututline_file (const struct utmp *data);
113 static void endutent_file (void);
114 static int updwtmp_file (const char *file, const struct utmp *utmp);
115
116 /* Jump table for file functions.  */
117 const struct utfuncs __libc_utmp_file_functions =
118 {
119   setutent_file,
120   getutent_r_file,
121   getutid_r_file,
122   getutline_r_file,
123   pututline_file,
124   endutent_file,
125   updwtmp_file
126 };
127
128
129 #ifndef TRANSFORM_UTMP_FILE_NAME
130 # define TRANSFORM_UTMP_FILE_NAME(file_name) (file_name)
131 #endif
132
133 static int
134 setutent_file (void)
135 {
136   if (file_fd < 0)
137     {
138       const char *file_name;
139       int result;
140
141       file_name = TRANSFORM_UTMP_FILE_NAME (__libc_utmp_file_name);
142
143       file_fd = open_not_cancel_2 (file_name, O_RDWR);
144       if (file_fd == -1)
145         {
146           /* Hhm, read-write access did not work.  Try read-only.  */
147           file_fd = open_not_cancel_2 (file_name, O_RDONLY);
148           if (file_fd == -1)
149             return 0;
150         }
151
152       /* We have to make sure the file is `closed on exec'.  */
153       result = fcntl_not_cancel (file_fd, F_GETFD, 0);
154       if (result >= 0)
155         result = fcntl_not_cancel (file_fd, F_SETFD, result | FD_CLOEXEC);
156       if (result == -1)
157         {
158           close_not_cancel_no_status (file_fd);
159           return 0;
160         }
161     }
162
163   __lseek64 (file_fd, 0, SEEK_SET);
164   file_offset = 0;
165
166   /* Make sure the entry won't match.  */
167 #if _HAVE_UT_TYPE - 0
168   last_entry.ut_type = -1;
169 #else
170   last_entry.ut_line[0] = '\177';
171 # if _HAVE_UT_ID - 0
172   last_entry.ut_id[0] = '\0';
173 # endif
174 #endif
175
176   return 1;
177 }
178
179
180 static int
181 getutent_r_file (struct utmp *buffer, struct utmp **result)
182 {
183   ssize_t nbytes;
184
185   assert (file_fd >= 0);
186
187   if (file_offset == -1l)
188     {
189       /* Not available.  */
190       *result = NULL;
191       return -1;
192     }
193
194   LOCK_FILE (file_fd, F_RDLCK)
195     {
196       nbytes = 0;
197       LOCKING_FAILED ();
198     }
199
200   /* Read the next entry.  */
201   nbytes = read_not_cancel (file_fd, &last_entry, sizeof (struct utmp));
202
203   UNLOCK_FILE (file_fd);
204
205   if (nbytes != sizeof (struct utmp))
206     {
207       if (nbytes != 0)
208         file_offset = -1l;
209       *result = NULL;
210       return -1;
211     }
212
213   /* Update position pointer.  */
214   file_offset += sizeof (struct utmp);
215
216   memcpy (buffer, &last_entry, sizeof (struct utmp));
217   *result = buffer;
218
219   return 0;
220 }
221
222
223 static int
224 internal_getut_r (const struct utmp *id, struct utmp *buffer)
225 {
226   int result = -1;
227
228   LOCK_FILE (file_fd, F_RDLCK)
229     LOCKING_FAILED ();
230
231 #if _HAVE_UT_TYPE - 0
232   if (id->ut_type == RUN_LVL || id->ut_type == BOOT_TIME
233       || id->ut_type == OLD_TIME || id->ut_type == NEW_TIME)
234     {
235       /* Search for next entry with type RUN_LVL, BOOT_TIME,
236          OLD_TIME, or NEW_TIME.  */
237
238       while (1)
239         {
240           /* Read the next entry.  */
241           if (read_not_cancel (file_fd, buffer, sizeof (struct utmp))
242               != sizeof (struct utmp))
243             {
244               __set_errno (ESRCH);
245               file_offset = -1l;
246               goto unlock_return;
247             }
248           file_offset += sizeof (struct utmp);
249
250           if (id->ut_type == buffer->ut_type)
251             break;
252         }
253     }
254   else
255 #endif /* _HAVE_UT_TYPE */
256     {
257       /* Search for the next entry with the specified ID and with type
258          INIT_PROCESS, LOGIN_PROCESS, USER_PROCESS, or DEAD_PROCESS.  */
259
260       while (1)
261         {
262           /* Read the next entry.  */
263           if (read_not_cancel (file_fd, buffer, sizeof (struct utmp))
264               != sizeof (struct utmp))
265             {
266               __set_errno (ESRCH);
267               file_offset = -1l;
268               goto unlock_return;
269             }
270           file_offset += sizeof (struct utmp);
271
272           if (__utmp_equal (buffer, id))
273             break;
274         }
275     }
276
277   result = 0;
278
279 unlock_return:
280   UNLOCK_FILE (file_fd);
281
282   return result;
283 }
284
285
286 /* For implementing this function we don't use the getutent_r function
287    because we can avoid the reposition on every new entry this way.  */
288 static int
289 getutid_r_file (const struct utmp *id, struct utmp *buffer,
290                 struct utmp **result)
291 {
292   assert (file_fd >= 0);
293
294   if (file_offset == -1l)
295     {
296       *result = NULL;
297       return -1;
298     }
299
300   if (internal_getut_r (id, &last_entry) < 0)
301     {
302       *result = NULL;
303       return -1;
304     }
305
306   memcpy (buffer, &last_entry, sizeof (struct utmp));
307   *result = buffer;
308
309   return 0;
310 }
311
312
313 /* For implementing this function we don't use the getutent_r function
314    because we can avoid the reposition on every new entry this way.  */
315 static int
316 getutline_r_file (const struct utmp *line, struct utmp *buffer,
317                   struct utmp **result)
318 {
319   assert (file_fd >= 0);
320
321   if (file_offset == -1l)
322     {
323       *result = NULL;
324       return -1;
325     }
326
327   LOCK_FILE (file_fd, F_RDLCK)
328     {
329       *result = NULL;
330       LOCKING_FAILED ();
331     }
332
333   while (1)
334     {
335       /* Read the next entry.  */
336       if (read_not_cancel (file_fd, &last_entry, sizeof (struct utmp))
337           != sizeof (struct utmp))
338         {
339           __set_errno (ESRCH);
340           file_offset = -1l;
341           *result = NULL;
342           goto unlock_return;
343         }
344       file_offset += sizeof (struct utmp);
345
346       /* Stop if we found a user or login entry.  */
347       if (
348 #if _HAVE_UT_TYPE - 0
349           (last_entry.ut_type == USER_PROCESS
350            || last_entry.ut_type == LOGIN_PROCESS)
351           &&
352 #endif
353           !strncmp (line->ut_line, last_entry.ut_line, sizeof line->ut_line))
354         break;
355     }
356
357   memcpy (buffer, &last_entry, sizeof (struct utmp));
358   *result = buffer;
359
360 unlock_return:
361   UNLOCK_FILE (file_fd);
362
363   return ((*result == NULL) ? -1 : 0);
364 }
365
366
367 static struct utmp *
368 pututline_file (const struct utmp *data)
369 {
370   struct utmp buffer;
371   struct utmp *pbuf;
372   int found;
373
374   assert (file_fd >= 0);
375
376   /* Find the correct place to insert the data.  */
377   if (file_offset > 0
378       && (
379 #if _HAVE_UT_TYPE - 0
380           (last_entry.ut_type == data->ut_type
381            && (last_entry.ut_type == RUN_LVL
382                || last_entry.ut_type == BOOT_TIME
383                || last_entry.ut_type == OLD_TIME
384                || last_entry.ut_type == NEW_TIME))
385           ||
386 #endif
387           __utmp_equal (&last_entry, data)))
388     found = 1;
389   else
390     found = internal_getut_r (data, &buffer);
391
392   LOCK_FILE (file_fd, F_WRLCK)
393     {
394       pbuf = NULL;
395       LOCKING_FAILED ();
396     }
397
398   if (found < 0)
399     {
400       /* We append the next entry.  */
401       file_offset = __lseek64 (file_fd, 0, SEEK_END);
402       if (file_offset % sizeof (struct utmp) != 0)
403         {
404           file_offset -= file_offset % sizeof (struct utmp);
405           __ftruncate64 (file_fd, file_offset);
406
407           if (__lseek64 (file_fd, 0, SEEK_END) < 0)
408             {
409               pbuf = NULL;
410               goto unlock_return;
411             }
412         }
413     }
414   else
415     {
416       /* We replace the just read entry.  */
417       file_offset -= sizeof (struct utmp);
418       __lseek64 (file_fd, file_offset, SEEK_SET);
419     }
420
421   /* Write the new data.  */
422   if (write_not_cancel (file_fd, data, sizeof (struct utmp))
423       != sizeof (struct utmp))
424     {
425       /* If we appended a new record this is only partially written.
426          Remove it.  */
427       if (found < 0)
428         (void) __ftruncate64 (file_fd, file_offset);
429       pbuf = NULL;
430     }
431   else
432     {
433       file_offset += sizeof (struct utmp);
434       pbuf = (struct utmp *) data;
435     }
436
437  unlock_return:
438   UNLOCK_FILE (file_fd);
439
440   return pbuf;
441 }
442
443
444 static void
445 endutent_file (void)
446 {
447   assert (file_fd >= 0);
448
449   close_not_cancel_no_status (file_fd);
450   file_fd = -1;
451 }
452
453
454 static int
455 updwtmp_file (const char *file, const struct utmp *utmp)
456 {
457   int result = -1;
458   off64_t offset;
459   int fd;
460
461   /* Open WTMP file.  */
462   fd = open_not_cancel_2 (file, O_WRONLY);
463   if (fd < 0)
464     return -1;
465
466   LOCK_FILE (fd, F_WRLCK)
467     LOCKING_FAILED ();
468
469   /* Remember original size of log file.  */
470   offset = __lseek64 (fd, 0, SEEK_END);
471   if (offset % sizeof (struct utmp) != 0)
472     {
473       offset -= offset % sizeof (struct utmp);
474       __ftruncate64 (fd, offset);
475
476       if (__lseek64 (fd, 0, SEEK_END) < 0)
477         goto unlock_return;
478     }
479
480   /* Write the entry.  If we can't write all the bytes, reset the file
481      size back to the original size.  That way, no partial entries
482      will remain.  */
483   if (write_not_cancel (fd, utmp, sizeof (struct utmp))
484       != sizeof (struct utmp))
485     {
486       __ftruncate64 (fd, offset);
487       goto unlock_return;
488     }
489
490   result = 0;
491
492 unlock_return:
493   UNLOCK_FILE (fd);
494
495   /* Close WTMP file.  */
496   close_not_cancel_no_status (fd);
497
498   return result;
499 }