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