pututxline.c revision 218846
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 218846 2011-02-19 11:31:56Z 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	struct stat sb;
47	FILE *fp;
48	int fd;
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 = EFTYPE;
58		return (NULL);
59	}
60
61	fp = fdopen(fd, "r+");
62	if (fp == NULL) {
63		_close(fd);
64		return (NULL);
65	}
66	return (fp);
67}
68
69static int
70utx_active_add(const struct futx *fu)
71{
72	struct futx fe;
73	FILE *fp;
74	off_t partial = -1;
75	int error, ret;
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)) ==
92			    0) {
93				ret = fseeko(fp, -(off_t)sizeof(fe), SEEK_CUR);
94				goto exact;
95			}
96			if (fe.fu_type != DEAD_PROCESS)
97				break;
98			/* FALLTHROUGH */
99		default:
100			/* Allow us to overwrite unused records. */
101			if (partial == -1) {
102				partial = ftello(fp);
103				/*
104				 * Distinguish errors from valid values so we
105				 * don't overwrite good data by accident.
106				 */
107				if (partial != -1)
108					partial -= (off_t)sizeof(fe);
109			}
110			break;
111		}
112	}
113
114	/*
115	 * No exact match found.  Use the partial match.  If no partial
116	 * match was found, just append a new record.
117	 */
118	if (partial != -1)
119		ret = fseeko(fp, partial, SEEK_SET);
120exact:
121	if (ret == -1)
122		error = errno;
123	else if (fwrite(fu, sizeof(*fu), 1, fp) < 1)
124		error = errno;
125	else
126		error = 0;
127	fclose(fp);
128	errno = error;
129	return (error == 0 ? 0 : 1);
130}
131
132static int
133utx_active_remove(struct futx *fu)
134{
135	struct futx fe;
136	FILE *fp;
137	int error, ret;
138
139	/*
140	 * Remove user login sessions, having the same ut_id.
141	 */
142	fp = futx_open(_PATH_UTX_ACTIVE);
143	if (fp == NULL)
144		return (-1);
145	error = ESRCH;
146	ret = -1;
147	while (fread(&fe, sizeof(fe), 1, fp) == 1 && ret != 0)
148		switch (fe.fu_type) {
149		case USER_PROCESS:
150		case INIT_PROCESS:
151		case LOGIN_PROCESS:
152			if (memcmp(fu->fu_id, fe.fu_id, sizeof(fe.fu_id)) != 0)
153				continue;
154
155			/* Terminate session. */
156			if (fseeko(fp, -(off_t)sizeof(fe), SEEK_CUR) == -1)
157				error = errno;
158			else if (fwrite(fu, sizeof(*fu), 1, fp) < 1)
159				error = errno;
160			else
161				ret = 0;
162
163		}
164
165	fclose(fp);
166	errno = error;
167	return (ret);
168}
169
170static void
171utx_active_purge(void)
172{
173
174	truncate(_PATH_UTX_ACTIVE, 0);
175}
176
177static int
178utx_lastlogin_add(const struct futx *fu)
179{
180	struct futx fe;
181	FILE *fp;
182	int error, ret;
183
184	ret = 0;
185
186	/*
187	 * Write an entry to lastlogin.  Overwrite the entry if the
188	 * current user already has an entry.  If not, append a new
189	 * entry.
190	 */
191	fp = futx_open(_PATH_UTX_LASTLOGIN);
192	if (fp == NULL)
193		return (-1);
194	while (fread(&fe, sizeof fe, 1, fp) == 1) {
195		if (strncmp(fu->fu_user, fe.fu_user, sizeof fe.fu_user) != 0)
196			continue;
197
198		/* Found a previous lastlogin entry for this user. */
199		ret = fseeko(fp, -(off_t)sizeof fe, SEEK_CUR);
200		break;
201	}
202	if (ret == -1)
203		error = errno;
204	else if (fwrite(fu, sizeof *fu, 1, fp) < 1) {
205		error = errno;
206		ret = -1;
207	}
208	fclose(fp);
209	errno = error;
210	return (ret);
211}
212
213static void
214utx_lastlogin_upgrade(void)
215{
216	struct stat sb;
217	int fd;
218
219	fd = _open(_PATH_UTX_LASTLOGIN, O_RDWR, 0644);
220	if (fd < 0)
221		return;
222
223	/*
224	 * Truncate broken lastlogin files.  In the future we should
225	 * check for older versions of the file format here and try to
226	 * upgrade it.
227	 */
228	if (_fstat(fd, &sb) != -1 && sb.st_size % sizeof(struct futx) != 0)
229		ftruncate(fd, 0);
230	_close(fd);
231}
232
233static int
234utx_log_add(const struct futx *fu)
235{
236	struct iovec vec[2];
237	int error, fd;
238	uint16_t l;
239
240	/*
241	 * Append an entry to the log file.  We only need to append
242	 * records to this file, so to conserve space, trim any trailing
243	 * zero-bytes.  Prepend a length field, indicating the length of
244	 * the record, excluding the length field itself.
245	 */
246	for (l = sizeof(*fu); l > 0 && ((const char *)fu)[l - 1] == '\0'; l--) ;
247	vec[0].iov_base = &l;
248	vec[0].iov_len = sizeof(l);
249	vec[1].iov_base = __DECONST(void *, fu);
250	vec[1].iov_len = l;
251	l = htobe16(l);
252
253	fd = _open(_PATH_UTX_LOG, O_CREAT|O_WRONLY|O_APPEND, 0644);
254	if (fd < 0)
255		return (-1);
256	if (_writev(fd, vec, 2) == -1)
257		error = errno;
258	else
259		error = 0;
260	_close(fd);
261	errno = error;
262	return (error == 0 ? 0 : 1);
263}
264
265struct utmpx *
266pututxline(const struct utmpx *utmpx)
267{
268	struct futx fu;
269	int bad = 0;
270
271	utx_to_futx(utmpx, &fu);
272
273	switch (fu.fu_type) {
274	case BOOT_TIME:
275	case SHUTDOWN_TIME:
276		utx_active_purge();
277		utx_lastlogin_upgrade();
278		break;
279	case OLD_TIME:
280	case NEW_TIME:
281		break;
282	case USER_PROCESS:
283		bad |= utx_active_add(&fu);
284		bad |= utx_lastlogin_add(&fu);
285		break;
286#if 0 /* XXX: Are these records of any use to us? */
287	case INIT_PROCESS:
288	case LOGIN_PROCESS:
289		bad |= utx_active_add(&fu);
290		break;
291#endif
292	case DEAD_PROCESS:
293		/*
294		 * In case writing a logout entry fails, never attempt
295		 * to write it to utx.log.  The logout entry's ut_id
296		 * might be invalid.
297		 */
298		if (utx_active_remove(&fu) != 0)
299			return (NULL);
300		break;
301	default:
302		errno = EINVAL;
303		return (NULL);
304	}
305
306	bad |= utx_log_add(&fu);
307	return (bad ? NULL : futx_to_utx(&fu));
308}
309