xref: /illumos-gate/usr/src/lib/libzonecfg/common/getzoneent.c (revision 581cede61ac9c14d8d4ea452562a567189eead78)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 /*
22  * Copyright 2006 Sun Microsystems, Inc.  All rights reserved.
23  * Use is subject to license terms.
24  */
25 
26 #pragma ident	"%Z%%M%	%I%	%E% SMI"
27 
28 /*
29  * This module contains functions used for reading and writing the index file.
30  * setzoneent() opens the file.  getzoneent() parses the file, doing the usual
31  * skipping of comment lines, etc., and using gettok() to deal with the ":"
32  * delimiters.  endzoneent() closes the file.  putzoneent() updates the file,
33  * adding, deleting or modifying lines, locking and unlocking appropriately.
34  */
35 
36 #include <stdlib.h>
37 #include <string.h>
38 #include <errno.h>
39 #include <libzonecfg.h>
40 #include <unistd.h>
41 #include <fcntl.h>
42 #include <sys/stat.h>
43 #include <assert.h>
44 #include <uuid/uuid.h>
45 #include "zonecfg_impl.h"
46 
47 
48 #define	_PATH_TMPFILE	ZONE_CONFIG_ROOT "/zonecfg.XXXXXX"
49 
50 /*
51  * gettok() is a helper function for parsing the index file, used to split
52  * the lines by their ":" delimiters.  Note that an entry may contain a ":"
53  * inside double quotes; this should only affect the zonepath, as zone names
54  * do not allow such characters, and zone states do not have them either.
55  * Same with double-quotes themselves: they are not allowed in zone names,
56  * and do not occur in zone states, and in theory should never occur in a
57  * zonepath since zonecfg does not support a method for escaping them.
58  *
59  * It never returns NULL.
60  */
61 
62 static char *
63 gettok(char **cpp)
64 {
65 	char *cp = *cpp, *retv;
66 	boolean_t quoted = B_FALSE;
67 
68 	if (cp == NULL)
69 		return ("");
70 	if (*cp == '"') {
71 		quoted = B_TRUE;
72 		cp++;
73 	}
74 	retv = cp;
75 	if (quoted) {
76 		while (*cp != '\0' && *cp != '"')
77 			cp++;
78 		if (*cp == '"')
79 			*cp++ = '\0';
80 	}
81 	while (*cp != '\0' && *cp != ':')
82 		cp++;
83 	if (*cp == '\0') {
84 		*cpp = NULL;
85 	} else {
86 		*cp++ = '\0';
87 		*cpp = cp;
88 	}
89 	return (retv);
90 }
91 
92 char *
93 getzoneent(FILE *cookie)
94 {
95 	struct zoneent *ze;
96 	char *name;
97 
98 	if ((ze = getzoneent_private(cookie)) == NULL)
99 		return (NULL);
100 	name = strdup(ze->zone_name);
101 	free(ze);
102 	return (name);
103 }
104 
105 struct zoneent *
106 getzoneent_private(FILE *cookie)
107 {
108 	char *cp, buf[MAX_INDEX_LEN], *p;
109 	struct zoneent *ze;
110 
111 	if (cookie == NULL)
112 		return (NULL);
113 
114 	if ((ze = malloc(sizeof (struct zoneent))) == NULL)
115 		return (NULL);
116 
117 	for (;;) {
118 		if (fgets(buf, sizeof (buf), cookie) == NULL) {
119 			free(ze);
120 			return (NULL);
121 		}
122 		if ((cp = strpbrk(buf, "\r\n")) == NULL) {
123 			/* this represents a line that's too long */
124 			continue;
125 		}
126 		*cp = '\0';
127 		cp = buf;
128 		if (*cp == '#') {
129 			/* skip comment lines */
130 			continue;
131 		}
132 		p = gettok(&cp);
133 		if (*p == '\0' || strlen(p) >= ZONENAME_MAX) {
134 			/*
135 			 * empty or very long zone names are not allowed
136 			 */
137 			continue;
138 		}
139 		(void) strlcpy(ze->zone_name, p, ZONENAME_MAX);
140 
141 		p = gettok(&cp);
142 		if (*p == '\0') {
143 			/* state field should not be empty */
144 			continue;
145 		}
146 		errno = 0;
147 		if (strcmp(p, ZONE_STATE_STR_CONFIGURED) == 0) {
148 			ze->zone_state = ZONE_STATE_CONFIGURED;
149 		} else if (strcmp(p, ZONE_STATE_STR_INCOMPLETE) == 0) {
150 			ze->zone_state = ZONE_STATE_INCOMPLETE;
151 		} else if (strcmp(p, ZONE_STATE_STR_INSTALLED) == 0) {
152 			ze->zone_state = ZONE_STATE_INSTALLED;
153 		} else {
154 			continue;
155 		}
156 
157 		p = gettok(&cp);
158 		if (strlen(p) >= MAXPATHLEN) {
159 			/* very long paths are not allowed */
160 			continue;
161 		}
162 		(void) strlcpy(ze->zone_path, p, MAXPATHLEN);
163 
164 		p = gettok(&cp);
165 		if (uuid_parse(p, ze->zone_uuid) == -1)
166 			uuid_clear(ze->zone_uuid);
167 
168 		break;
169 	}
170 
171 	return (ze);
172 }
173 
174 static boolean_t
175 get_index_path(char *path)
176 {
177 	return (snprintf(path, MAXPATHLEN, "%s%s", zonecfg_root,
178 	    ZONE_INDEX_FILE) < MAXPATHLEN);
179 }
180 
181 FILE *
182 setzoneent(void)
183 {
184 	char path[MAXPATHLEN];
185 
186 	if (!get_index_path(path)) {
187 		errno = EINVAL;
188 		return (NULL);
189 	}
190 	return (fopen(path, "r"));
191 }
192 
193 void
194 endzoneent(FILE *cookie)
195 {
196 	if (cookie != NULL)
197 		(void) fclose(cookie);
198 }
199 
200 static int
201 lock_index_file(void)
202 {
203 	int lock_fd;
204 	struct flock lock;
205 	char path[MAXPATHLEN];
206 
207 	if (snprintf(path, sizeof (path), "%s%s", zonecfg_root,
208 	    ZONE_INDEX_LOCK_DIR) >= sizeof (path))
209 		return (-1);
210 	if ((mkdir(path, S_IRWXU) == -1) && errno != EEXIST)
211 		return (-1);
212 	if (strlcat(path, ZONE_INDEX_LOCK_FILE, sizeof (path)) >=
213 	    sizeof (path))
214 		return (-1);
215 	lock_fd = open(path, O_CREAT|O_RDWR, 0644);
216 	if (lock_fd == -1)
217 		return (-1);
218 
219 	lock.l_type = F_WRLCK;
220 	lock.l_whence = SEEK_SET;
221 	lock.l_start = 0;
222 	lock.l_len = 0;
223 
224 	if (fcntl(lock_fd, F_SETLKW, &lock) == -1) {
225 		(void) close(lock_fd);
226 		return (-1);
227 	}
228 
229 	return (lock_fd);
230 }
231 
232 static int
233 unlock_index_file(int lock_fd)
234 {
235 	struct flock lock;
236 
237 	lock.l_type = F_UNLCK;
238 	lock.l_whence = SEEK_SET;
239 	lock.l_start = 0;
240 	lock.l_len = 0;
241 
242 	if (fcntl(lock_fd, F_SETLK, &lock) == -1)
243 		return (Z_UNLOCKING_FILE);
244 
245 	if (close(lock_fd) == -1)
246 		return (Z_UNLOCKING_FILE);
247 
248 	return (Z_OK);
249 }
250 
251 /*
252  * This function adds or removes a zone name et al. to the index file.
253  *
254  * If ze->zone_state is < 0, it means leave the
255  * existing value unchanged; this is only meaningful when operation ==
256  * PZE_MODIFY (i.e., it's bad on PZE_ADD and a no-op on PZE_REMOVE).
257  *
258  * A zero-length ze->zone_path means leave the existing value
259  * unchanged; this is only meaningful when operation == PZE_MODIFY
260  * (i.e., it's bad on PZE_ADD and a no-op on PZE_REMOVE).
261  *
262  * A zero-length ze->zone_newname means leave the existing name
263  * unchanged; otherwise the zone is renamed to zone_newname.  This is
264  * only meaningful when operation == PZE_MODIFY.
265  *
266  * Locking and unlocking is done via the functions above.
267  * The file itself is not modified in place; rather, a copy is made which
268  * is modified, then the copy is atomically renamed back to the main file.
269  */
270 int
271 putzoneent(struct zoneent *ze, zoneent_op_t operation)
272 {
273 	FILE *index_file, *tmp_file;
274 	char *tmp_file_name, buf[MAX_INDEX_LEN];
275 	int tmp_file_desc, lock_fd, err;
276 	boolean_t exist, need_quotes;
277 	char *cp;
278 	char path[MAXPATHLEN];
279 	char uuidstr[UUID_PRINTABLE_STRING_LENGTH];
280 	size_t tlen, namelen;
281 	const char *zone_name, *zone_state, *zone_path, *zone_uuid;
282 
283 	assert(ze != NULL);
284 	if (operation == PZE_ADD &&
285 	    (ze->zone_state < 0 || strlen(ze->zone_path) == 0))
286 		return (Z_INVAL);
287 
288 	if (operation != PZE_MODIFY && strlen(ze->zone_newname) != 0)
289 		return (Z_INVAL);
290 
291 	if ((lock_fd = lock_index_file()) == -1)
292 		return (Z_LOCKING_FILE);
293 
294 	/* using sizeof gives us room for the terminating NUL byte as well */
295 	tlen = sizeof (_PATH_TMPFILE) + strlen(zonecfg_root);
296 	tmp_file_name = malloc(tlen);
297 	if (tmp_file_name == NULL) {
298 		(void) unlock_index_file(lock_fd);
299 		return (Z_NOMEM);
300 	}
301 	(void) snprintf(tmp_file_name, tlen, "%s%s", zonecfg_root,
302 	    _PATH_TMPFILE);
303 
304 	tmp_file_desc = mkstemp(tmp_file_name);
305 	if (tmp_file_desc == -1) {
306 		(void) unlink(tmp_file_name);
307 		free(tmp_file_name);
308 		(void) unlock_index_file(lock_fd);
309 		return (Z_TEMP_FILE);
310 	}
311 	(void) fchmod(tmp_file_desc, ZONE_INDEX_MODE);
312 	(void) fchown(tmp_file_desc, ZONE_INDEX_UID, ZONE_INDEX_GID);
313 	if ((tmp_file = fdopen(tmp_file_desc, "w")) == NULL) {
314 		(void) close(tmp_file_desc);
315 		err = Z_MISC_FS;
316 		goto error;
317 	}
318 	if (!get_index_path(path)) {
319 		err = Z_MISC_FS;
320 		goto error;
321 	}
322 	if ((index_file = fopen(path, "r")) == NULL) {
323 		err = Z_MISC_FS;
324 		goto error;
325 	}
326 
327 	exist = B_FALSE;
328 	zone_name = ze->zone_name;
329 	namelen = strlen(zone_name);
330 	for (;;) {
331 		if (fgets(buf, sizeof (buf), index_file) == NULL) {
332 			if (operation == PZE_ADD && !exist) {
333 				zone_state = zone_state_str(ze->zone_state);
334 				zone_path = ze->zone_path;
335 				zone_uuid = "";
336 				goto add_entry;
337 			}
338 			/*
339 			 * It's not considered an error to delete something
340 			 * that doesn't exist, but we can't modify a missing
341 			 * record.
342 			 */
343 			if (operation == PZE_MODIFY && !exist) {
344 				err = Z_UPDATING_INDEX;
345 				goto error;
346 			}
347 			break;
348 		}
349 
350 		if (buf[0] == '#') {
351 			/* skip and preserve comment lines */
352 			(void) fputs(buf, tmp_file);
353 			continue;
354 		}
355 
356 		if (strncmp(buf, zone_name, namelen) != 0 ||
357 		    buf[namelen] != ':') {
358 			/* skip and preserve non-target lines */
359 			(void) fputs(buf, tmp_file);
360 			continue;
361 		}
362 
363 		if ((cp = strpbrk(buf, "\r\n")) == NULL) {
364 			/* this represents a line that's too long; delete */
365 			continue;
366 		}
367 		*cp = '\0';
368 
369 		/*
370 		 * Skip over the zone name.  Because we've already matched the
371 		 * target zone (above), we know for certain here that the zone
372 		 * name is present and correctly formed.  No need to check.
373 		 */
374 		cp = strchr(buf, ':') + 1;
375 
376 		zone_state = gettok(&cp);
377 		if (*zone_state == '\0') {
378 			/* state field should not be empty */
379 			err = Z_UPDATING_INDEX;
380 			goto error;
381 		}
382 		zone_path = gettok(&cp);
383 		zone_uuid = gettok(&cp);
384 
385 		switch (operation) {
386 		case PZE_ADD:
387 			/* can't add same zone */
388 			err = Z_UPDATING_INDEX;
389 			goto error;
390 
391 		case PZE_MODIFY:
392 			/*
393 			 * If the caller specified a new state for the zone,
394 			 * then use that.  Otherwise, use the current state.
395 			 */
396 			if (ze->zone_state >= 0) {
397 				zone_state = zone_state_str(ze->zone_state);
398 
399 				/*
400 				 * If the caller is uninstalling this zone,
401 				 * then wipe out the uuid.  The zone's contents
402 				 * are no longer known.
403 				 */
404 				if (ze->zone_state < ZONE_STATE_INSTALLED)
405 					zone_uuid = "";
406 			}
407 
408 			/* If a new name is supplied, use it. */
409 			if (ze->zone_newname[0] != '\0')
410 				zone_name = ze->zone_newname;
411 
412 			if (ze->zone_path[0] != '\0')
413 				zone_path = ze->zone_path;
414 			break;
415 
416 		case PZE_REMOVE:
417 		default:
418 			continue;
419 		}
420 
421 	add_entry:
422 		/*
423 		 * If the entry in the file is in greater than configured
424 		 * state, then we must have a UUID.  Make sure that we do.
425 		 * (Note that the file entry is only tokenized, not fully
426 		 * parsed, so we need to do a string comparison here.)
427 		 */
428 		if (strcmp(zone_state, ZONE_STATE_STR_CONFIGURED) != 0 &&
429 		    *zone_uuid == '\0') {
430 			if (uuid_is_null(ze->zone_uuid))
431 				uuid_generate(ze->zone_uuid);
432 			uuid_unparse(ze->zone_uuid, uuidstr);
433 			zone_uuid = uuidstr;
434 		}
435 		/*
436 		 * We need to quote a path that contains a ":"; this should
437 		 * only affect the zonepath, as zone names do not allow such
438 		 * characters, and zone states do not have them either.  Same
439 		 * with double-quotes themselves: they are not allowed in zone
440 		 * names, and do not occur in zone states, and in theory should
441 		 * never occur in a zonepath since zonecfg does not support a
442 		 * method for escaping them.
443 		 */
444 		need_quotes = (strchr(zone_path, ':') != NULL);
445 		(void) fprintf(tmp_file, "%s:%s:%s%s%s:%s\n", zone_name,
446 		    zone_state, need_quotes ? "\"" : "", zone_path,
447 		    need_quotes ? "\"" : "", zone_uuid);
448 		exist = B_TRUE;
449 	}
450 
451 	(void) fclose(index_file);
452 	index_file = NULL;
453 	if (fclose(tmp_file) != 0) {
454 		tmp_file = NULL;
455 		err = Z_MISC_FS;
456 		goto error;
457 	}
458 	tmp_file = NULL;
459 	if (rename(tmp_file_name, path) == -1) {
460 		err = errno == EACCES ? Z_ACCES : Z_MISC_FS;
461 		goto error;
462 	}
463 	free(tmp_file_name);
464 	if (unlock_index_file(lock_fd) != Z_OK)
465 		return (Z_UNLOCKING_FILE);
466 	return (Z_OK);
467 
468 error:
469 	if (index_file != NULL)
470 		(void) fclose(index_file);
471 	if (tmp_file != NULL)
472 		(void) fclose(tmp_file);
473 	(void) unlink(tmp_file_name);
474 	free(tmp_file_name);
475 	(void) unlock_index_file(lock_fd);
476 	return (err);
477 }
478