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