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