pututxline.c revision 215310
1/*-
2 * Copyright (c) 2010 Ed Schouten <ed@FreeBSD.org>
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 *    notice, this list of conditions and the following disclaimer in the
12 *    documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24 * SUCH DAMAGE.
25 */
26
27#include <sys/cdefs.h>
28__FBSDID("$FreeBSD: head/lib/libc/gen/pututxline.c 215310 2010-11-14 18:42:39Z ed $");
29
30#include "namespace.h"
31#include <sys/endian.h>
32#include <sys/stat.h>
33#include <sys/uio.h>
34#include <errno.h>
35#include <fcntl.h>
36#include <stdio.h>
37#include <string.h>
38#include <unistd.h>
39#include <utmpx.h>
40#include "utxdb.h"
41#include "un-namespace.h"
42
43static FILE *
44futx_open(const char *file)
45{
46	int fd;
47	FILE *fp;
48	struct stat sb;
49
50	fd = _open(file, O_CREAT|O_RDWR|O_EXLOCK, 0644);
51	if (fd < 0)
52		return (NULL);
53
54	/* Safety check: never use broken files. */
55	if (_fstat(fd, &sb) != -1 && sb.st_size % sizeof(struct futx) != 0) {
56		_close(fd);
57		errno = EINVAL;
58		return (NULL);
59	}
60
61	fp = fdopen(fd, "r+");
62	if (fp == NULL) {
63		_close(fd);
64		return (NULL);
65	}
66
67	return (fp);
68}
69
70static int
71utx_active_add(const struct futx *fu)
72{
73	FILE *fp;
74	struct futx fe;
75	off_t partial = -1;
76
77	/*
78	 * Register user login sessions.  Overwrite entries of sessions
79	 * that have already been terminated.
80	 */
81	fp = futx_open(_PATH_UTX_ACTIVE);
82	if (fp == NULL)
83		return (1);
84	while (fread(&fe, sizeof fe, 1, fp) == 1) {
85		switch (fe.fu_type) {
86		case USER_PROCESS:
87		case INIT_PROCESS:
88		case LOGIN_PROCESS:
89		case DEAD_PROCESS:
90			/* Overwrite when ut_id matches. */
91			if (memcmp(fu->fu_id, fe.fu_id, sizeof fe.fu_id) == 0) {
92				fseeko(fp, -(off_t)sizeof fe, SEEK_CUR);
93				goto exact;
94			}
95			if (fe.fu_type != DEAD_PROCESS)
96				break;
97			/* FALLTHROUGH */
98		default:
99			/* Allow us to overwrite unused records. */
100			if (partial == -1)
101				partial = ftello(fp) - (off_t)sizeof fe;
102			break;
103		}
104	}
105
106	/*
107	 * No exact match found.  Use the partial match.  If no partial
108	 * match was found, just append a new record.
109	 */
110	if (partial != -1)
111		fseeko(fp, partial, SEEK_SET);
112exact:
113	fwrite(fu, sizeof *fu, 1, fp);
114	fclose(fp);
115	return (0);
116}
117
118static int
119utx_active_remove(struct futx *fu)
120{
121	FILE *fp;
122	struct futx fe;
123
124	/*
125	 * Remove user login sessions, having the same ut_id.
126	 */
127	fp = futx_open(_PATH_UTX_ACTIVE);
128	if (fp == NULL)
129		return (1);
130	while (fread(&fe, sizeof fe, 1, fp) == 1) {
131		switch (fe.fu_type) {
132		case USER_PROCESS:
133		case INIT_PROCESS:
134		case LOGIN_PROCESS:
135			if (memcmp(fu->fu_id, fe.fu_id, sizeof fe.fu_id) != 0)
136				continue;
137
138			/* Terminate session. */
139			fseeko(fp, -(off_t)sizeof fe, SEEK_CUR);
140			fwrite(fu, sizeof *fu, 1, fp);
141			fclose(fp);
142			return (0);
143		}
144	}
145
146	fclose(fp);
147	errno = ESRCH;
148	return (1);
149}
150
151static void
152utx_active_purge(void)
153{
154
155	truncate(_PATH_UTX_ACTIVE, 0);
156}
157
158static int
159utx_lastlogin_add(const struct futx *fu)
160{
161	FILE *fp;
162	struct futx fe;
163
164	/*
165	 * Write an entry to lastlogin.  Overwrite the entry if the
166	 * current user already has an entry.  If not, append a new
167	 * entry.
168	 */
169	fp = futx_open(_PATH_UTX_LASTLOGIN);
170	if (fp == NULL)
171		return (1);
172	while (fread(&fe, sizeof fe, 1, fp) == 1) {
173		if (strncmp(fu->fu_user, fe.fu_user, sizeof fe.fu_user) != 0)
174			continue;
175
176		/* Found a previous lastlogin entry for this user. */
177		fseeko(fp, -(off_t)sizeof fe, SEEK_CUR);
178		break;
179	}
180	fwrite(fu, sizeof *fu, 1, fp);
181	fclose(fp);
182	return (0);
183}
184
185static void
186utx_lastlogin_upgrade(void)
187{
188	int fd;
189	struct stat sb;
190
191	fd = _open(_PATH_UTX_LASTLOGIN, O_RDWR, 0644);
192	if (fd < 0)
193		return;
194
195	/*
196	 * Truncate broken lastlogin files.  In the future we should
197	 * check for older versions of the file format here and try to
198	 * upgrade it.
199	 */
200	if (_fstat(fd, &sb) != -1 && sb.st_size % sizeof(struct futx) != 0)
201		ftruncate(fd, 0);
202	_close(fd);
203}
204
205static int
206utx_log_add(const struct futx *fu)
207{
208	int fd;
209	uint16_t l;
210	struct iovec vec[2];
211
212	/*
213	 * Append an entry to the log file.  We only need to append
214	 * records to this file, so to conserve space, trim any trailing
215	 * zero-bytes.  Prepend a length field, indicating the length of
216	 * the record, excluding the length field itself.
217	 */
218	for (l = sizeof *fu; l > 0 && ((const char *)fu)[l - 1] == '\0'; l--);
219	vec[0].iov_base = &l;
220	vec[0].iov_len = sizeof l;
221	vec[1].iov_base = __DECONST(void *, fu);
222	vec[1].iov_len = l;
223	l = htobe16(l);
224
225	fd = _open(_PATH_UTX_LOG, O_CREAT|O_WRONLY|O_APPEND, 0644);
226	if (fd < 0)
227		return (1);
228	_writev(fd, vec, 2);
229	_close(fd);
230	return (0);
231}
232
233struct utmpx *
234pututxline(const struct utmpx *utmpx)
235{
236	struct futx fu;
237	int bad = 0;
238
239	utx_to_futx(utmpx, &fu);
240
241	switch (fu.fu_type) {
242	case BOOT_TIME:
243	case SHUTDOWN_TIME:
244		utx_active_purge();
245		utx_lastlogin_upgrade();
246		break;
247	case OLD_TIME:
248	case NEW_TIME:
249		break;
250	case USER_PROCESS:
251		bad |= utx_active_add(&fu);
252		bad |= utx_lastlogin_add(&fu);
253		break;
254#if 0 /* XXX: Are these records of any use to us? */
255	case INIT_PROCESS:
256	case LOGIN_PROCESS:
257		bad |= utx_active_add(&fu);
258		break;
259#endif
260	case DEAD_PROCESS:
261		/*
262		 * In case writing a logout entry fails, never attempt
263		 * to write it to utx.log.  The logout entry's ut_id
264		 * might be invalid.
265		 */
266		if (utx_active_remove(&fu) != 0)
267			return (NULL);
268		break;
269	default:
270		return (NULL);
271	}
272
273	bad |= utx_log_add(&fu);
274	return (bad ? NULL : futx_to_utx(&fu));
275}
276