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 *
gettok(char ** cpp)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 *
getzoneent(FILE * cookie)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 *
getzoneent_private(FILE * cookie)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
get_index_path(char * path)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 *
setzoneent(void)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
endzoneent(FILE * cookie)192 endzoneent(FILE *cookie)
193 {
194 if (cookie != NULL)
195 (void) fclose(cookie);
196 }
197
198 static int
lock_index_file(void)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
unlock_index_file(int lock_fd)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
putzoneent(struct zoneent * ze,zoneent_op_t operation)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