xref: /illumos-gate/usr/src/lib/libzonecfg/common/getzoneent.c (revision bdcaf82257ab2deb6b46efaaa4bc93a1a44b3885)
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 <ctype.h>
39 #include <string.h>
40 #include <errno.h>
41 #include <libzonecfg.h>
42 #include <unistd.h>
43 #include <fcntl.h>
44 #include <sys/stat.h>
45 #include <assert.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 
61 static char *
62 gettok(char **cpp)
63 {
64 	char *cp = *cpp, *retv;
65 	boolean_t quoted = B_FALSE;
66 
67 	if (cp == NULL)
68 		return ("");
69 	if (*cp == '"') {
70 		quoted = B_TRUE;
71 		cp++;
72 	}
73 	retv = cp;
74 	if (quoted) {
75 		while (*cp != '\0' && *cp != '"')
76 			cp++;
77 		if (*cp == '"')
78 			*cp++ = '\0';
79 	}
80 	while (*cp != '\0' && *cp != ':')
81 		cp++;
82 	if (*cp == '\0') {
83 		*cpp = NULL;
84 	} else {
85 		*cp++ = '\0';
86 		*cpp = cp;
87 	}
88 	return (retv);
89 }
90 
91 char *
92 getzoneent(FILE *cookie)
93 {
94 	struct zoneent *ze;
95 	char *name;
96 
97 	if ((ze = getzoneent_private(cookie)) == NULL)
98 		return (NULL);
99 	name = strdup(ze->zone_name);
100 	free(ze);
101 	return (name);
102 }
103 
104 struct zoneent *
105 getzoneent_private(FILE *cookie)
106 {
107 	char *cp, buf[MAX_INDEX_LEN], *p;
108 	struct zoneent *ze;
109 
110 	if (cookie == NULL)
111 		return (NULL);
112 
113 	if ((ze = malloc(sizeof (struct zoneent))) == NULL)
114 		return (NULL);
115 
116 	for (;;) {
117 		if (fgets(buf, sizeof (buf), cookie) == NULL) {
118 			free(ze);
119 			return (NULL);
120 		}
121 		if ((cp = strpbrk(buf, "\r\n")) == NULL) {
122 			/* this represents a line that's too long */
123 			continue;
124 		}
125 		*cp = '\0';
126 		cp = buf;
127 		if (*cp == '#') {
128 			/* skip comment lines */
129 			continue;
130 		}
131 		p = gettok(&cp);
132 		if (p == NULL || *p == '\0' || strlen(p) > ZONENAME_MAX) {
133 			/*
134 			 * empty or very long zone names are not allowed
135 			 */
136 			continue;
137 		}
138 		(void) strlcpy(ze->zone_name, p, ZONENAME_MAX);
139 
140 		p = gettok(&cp);
141 		if (p == NULL || *p == '\0') {
142 			/* state field should not be empty */
143 			continue;
144 		}
145 		errno = 0;
146 		if (strcmp(p, ZONE_STATE_STR_CONFIGURED) == 0) {
147 			ze->zone_state = ZONE_STATE_CONFIGURED;
148 		} else if (strcmp(p, ZONE_STATE_STR_INCOMPLETE) == 0) {
149 			ze->zone_state = ZONE_STATE_INCOMPLETE;
150 		} else if (strcmp(p, ZONE_STATE_STR_INSTALLED) == 0) {
151 			ze->zone_state = ZONE_STATE_INSTALLED;
152 		} else
153 			continue;
154 
155 		p = gettok(&cp);
156 		if (strlen(p) > MAXPATHLEN) {
157 			/* very long paths are not allowed */
158 			continue;
159 		}
160 		if (p == NULL) {
161 			/* empty paths accepted for backwards compatibility */
162 			p = "";
163 		}
164 		(void) strlcpy(ze->zone_path, p, MAXPATHLEN);
165 
166 		break;
167 	}
168 
169 	return (ze);
170 }
171 
172 FILE *
173 setzoneent(void)
174 {
175 	return (fopen(ZONE_INDEX_FILE, "r"));
176 }
177 
178 void
179 endzoneent(FILE *cookie)
180 {
181 	if (cookie != NULL)
182 		(void) fclose(cookie);
183 }
184 
185 static int
186 lock_index_file(int *lock_fd)
187 {
188 	struct flock lock;
189 
190 	if ((mkdir(ZONE_SNAPSHOT_ROOT, S_IRWXU) == -1) && errno != EEXIST)
191 		return (Z_LOCKING_FILE);
192 	*lock_fd = open(ZONE_INDEX_LOCK_FILE, O_CREAT|O_RDWR, 0644);
193 	if (*lock_fd < 0)
194 		return (Z_LOCKING_FILE);
195 
196 	lock.l_type = F_WRLCK;
197 	lock.l_whence = SEEK_SET;
198 	lock.l_start = 0;
199 	lock.l_len = 0;
200 
201 	if (fcntl(*lock_fd, F_SETLKW, &lock) == -1)
202 		return (Z_LOCKING_FILE);
203 
204 	return (Z_OK);
205 }
206 
207 static int
208 unlock_index_file(int lock_fd)
209 {
210 	struct flock lock;
211 
212 	lock.l_type = F_UNLCK;
213 	lock.l_whence = SEEK_SET;
214 	lock.l_start = 0;
215 	lock.l_len = 0;
216 
217 	if (fcntl(lock_fd, F_SETLK, &lock) == -1)
218 		return (Z_UNLOCKING_FILE);
219 
220 	if (close(lock_fd) == -1)
221 		return (Z_UNLOCKING_FILE);
222 
223 	return (Z_OK);
224 }
225 
226 /*
227  * This function adds or removes a zone name et al. to the index file.
228  *
229  * If ze->zone_state is < 0, it means leave the
230  * existing value unchanged; this is only meaningful when operation ==
231  * PZE_MODIFY (i.e., it's bad on PZE_ADD and a no-op on PZE_REMOVE).
232  *
233  * A zero-length ze->zone_path means leave the existing value
234  * unchanged; this is only meaningful when operation == PZE_MODIFY
235  * (i.e., it's bad on PZE_ADD and a no-op on PZE_REMOVE).
236  *
237  * A zero-length ze->zone_newname means leave the existing name
238  * unchanged; otherwise the zone is renamed to zone_newname.  This is
239  * only meaningful when operation == PZE_MODIFY.
240  *
241  * Locking and unlocking is done via the functions above.
242  * The file itself is not modified in place; rather, a copy is made which
243  * is modified, then the copy is atomically renamed back to the main file.
244  */
245 int
246 putzoneent(struct zoneent *ze, zoneent_op_t operation)
247 {
248 	FILE *index_file, *tmp_file;
249 	char *tmp_file_name, buf[MAX_INDEX_LEN], orig_buf[MAX_INDEX_LEN];
250 	char zone[ZONENAME_MAX + 1];		/* name plus newline */
251 	char line[MAX_INDEX_LEN];
252 	int tmp_file_desc, lock_fd, err;
253 	boolean_t exists = B_FALSE, need_quotes;
254 	char *cp, *p;
255 
256 	assert(ze != NULL);
257 	if (operation == PZE_ADD &&
258 	    (ze->zone_state < 0 || strlen(ze->zone_path) == 0))
259 		return (Z_INVAL);
260 
261 	if (operation != PZE_MODIFY && strlen(ze->zone_newname) != 0)
262 		return (Z_INVAL);
263 
264 	if ((err = lock_index_file(&lock_fd)) != Z_OK)
265 		return (err);
266 	tmp_file_name = strdup(_PATH_TMPFILE);
267 	if (tmp_file_name == NULL) {
268 		(void) unlock_index_file(lock_fd);
269 		return (Z_NOMEM);
270 	}
271 	tmp_file_desc = mkstemp(tmp_file_name);
272 	if (tmp_file_desc == -1) {
273 		(void) unlink(tmp_file_name);
274 		free(tmp_file_name);
275 		(void) unlock_index_file(lock_fd);
276 		return (Z_TEMP_FILE);
277 	}
278 	if ((tmp_file = fdopen(tmp_file_desc, "w")) == NULL) {
279 		(void) close(tmp_file_desc);
280 		(void) unlink(tmp_file_name);
281 		free(tmp_file_name);
282 		(void) unlock_index_file(lock_fd);
283 		return (Z_MISC_FS);
284 	}
285 	if ((index_file = fopen(ZONE_INDEX_FILE, "r")) == NULL) {
286 		(void) fclose(tmp_file);
287 		(void) unlink(tmp_file_name);
288 		free(tmp_file_name);
289 		(void) unlock_index_file(lock_fd);
290 		return (Z_MISC_FS);
291 	}
292 
293 	/*
294 	 * We need to quote a path which contains a ":"; this should only
295 	 * affect the zonepath, as zone names do not allow such characters,
296 	 * and zone states do not have them either.  Same with double-quotes
297 	 * themselves: they are not allowed in zone names, and do not occur
298 	 * in zone states, and in theory should never occur in a zonepath
299 	 * since zonecfg does not support a method for escaping them.
300 	 */
301 	need_quotes = (strchr(ze->zone_path, ':') != NULL);
302 
303 	(void) snprintf(line, sizeof (line), "%s:%s:%s%s%s\n", ze->zone_name,
304 	    zone_state_str(ze->zone_state), need_quotes ? "\"" : "",
305 	    ze->zone_path, need_quotes ? "\"" : "");
306 	for (;;) {
307 		if (fgets(buf, sizeof (buf), index_file) == NULL) {
308 			if (operation == PZE_ADD && !exists)
309 				(void) fputs(line, tmp_file);
310 			break;
311 		}
312 		(void) strlcpy(orig_buf, buf, sizeof (orig_buf));
313 
314 		if ((cp = strpbrk(buf, "\r\n")) == NULL) {
315 			/* this represents a line that's too long */
316 			continue;
317 		}
318 		*cp = '\0';
319 		cp = buf;
320 		if (*cp == '#') {
321 			/* skip comment lines */
322 			(void) fputs(orig_buf, tmp_file);
323 			continue;
324 		}
325 		p = gettok(&cp);
326 		if (p == NULL || *p == '\0' || strlen(p) > ZONENAME_MAX) {
327 			/*
328 			 * empty or very long zone names are not allowed
329 			 */
330 			continue;
331 		}
332 		(void) strlcpy(zone, p, ZONENAME_MAX);
333 
334 		if (strcmp(zone, ze->zone_name) == 0) {
335 			exists = B_TRUE;		/* already there */
336 			if (operation == PZE_ADD) {
337 				/* can't add same zone */
338 				goto error;
339 			} else if (operation == PZE_MODIFY) {
340 				char tmp_state[ZONE_STATE_MAXSTRLEN + 1];
341 				char *tmp_name;
342 
343 				if (ze->zone_state >= 0 &&
344 				    strlen(ze->zone_path) > 0) {
345 					/* use specified values */
346 					(void) fputs(line, tmp_file);
347 					continue;
348 				}
349 				/* use existing value for state */
350 				p = gettok(&cp);
351 				if (p == NULL || *p == '\0') {
352 					/* state field should not be empty */
353 					goto error;
354 				}
355 				(void) strlcpy(tmp_state,
356 				    (ze->zone_state < 0) ? p :
357 				    zone_state_str(ze->zone_state),
358 				    sizeof (tmp_state));
359 
360 				p = gettok(&cp);
361 
362 				/*
363 				 * If a new name is supplied, use it.
364 				 */
365 				if (strlen(ze->zone_newname) != 0)
366 					tmp_name = ze->zone_newname;
367 				else
368 					tmp_name = ze->zone_name;
369 
370 				(void) fprintf(tmp_file, "%s:%s:%s%s%s\n",
371 				    tmp_name, tmp_state,
372 				    need_quotes ? "\"" : "",
373 				    (strlen(ze->zone_path) == 0) ? p :
374 				    ze->zone_path, need_quotes ? "\"" : "");
375 			}
376 			/* else if (operation == PZE_REMOVE) { no-op } */
377 		} else {
378 			(void) fputs(orig_buf, tmp_file);
379 		}
380 	}
381 
382 	(void) fclose(index_file);
383 	if (fclose(tmp_file) != 0) {
384 		(void) unlink(tmp_file_name);
385 		free(tmp_file_name);
386 		(void) unlock_index_file(lock_fd);
387 		return (Z_MISC_FS);
388 	}
389 	(void) chmod(tmp_file_name, 0644);
390 	if (rename(tmp_file_name, ZONE_INDEX_FILE) == -1) {
391 		(void) unlink(tmp_file_name);
392 		free(tmp_file_name);
393 		(void) unlock_index_file(lock_fd);
394 		if (errno == EACCES)
395 			return (Z_ACCES);
396 		return (Z_MISC_FS);
397 	}
398 	free(tmp_file_name);
399 	if (unlock_index_file(lock_fd) != Z_OK)
400 		return (Z_UNLOCKING_FILE);
401 	return (Z_OK);
402 error:
403 	(void) fclose(index_file);
404 	(void) fclose(tmp_file);
405 	(void) unlink(tmp_file_name);
406 	free(tmp_file_name);
407 	if (unlock_index_file(lock_fd) != Z_OK)
408 		return (Z_UNLOCKING_FILE);
409 	return (Z_UPDATING_INDEX);
410 }
411