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 * Copyright 2015 Joyent, Inc.
24 */
25
26
27 /*
28 * This module contains functions used for reading and writing the index file.
29 * setzoneent() opens the file. getzoneent() parses the file, doing the usual
30 * skipping of comment lines, etc., and using gettok() to deal with the ":"
31 * delimiters. endzoneent() closes the file. putzoneent() updates the file,
32 * adding, deleting or modifying lines, locking and unlocking appropriately.
33 */
34
35 #include <stdlib.h>
36 #include <string.h>
37 #include <errno.h>
38 #include <libzonecfg.h>
39 #include <unistd.h>
40 #include <fcntl.h>
41 #include <sys/stat.h>
42 #include <assert.h>
43 #include <uuid/uuid.h>
44 #include "zonecfg_impl.h"
45
46
47 #define _PATH_TMPFILE ZONE_CONFIG_ROOT "/zonecfg.XXXXXX"
48
49 /*
50 * gettok() is a helper function for parsing the index file, used to split
51 * the lines by their ":" delimiters. Note that an entry may contain a ":"
52 * inside double quotes; this should only affect the zonepath, as zone names
53 * do not allow such characters, and zone states do not have them either.
54 * Same with double-quotes themselves: they are not allowed in zone names,
55 * and do not occur in zone states, and in theory should never occur in a
56 * zonepath since zonecfg does not support a method for escaping them.
57 *
58 * It never returns NULL.
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
132 /* zonename */
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 /* state */
143 p = gettok(&cp);
144 if (*p == '\0') {
145 /* state field should not be empty */
146 continue;
147 }
148 errno = 0;
149 if (strcmp(p, ZONE_STATE_STR_CONFIGURED) == 0) {
150 ze->zone_state = ZONE_STATE_CONFIGURED;
151 } else if (strcmp(p, ZONE_STATE_STR_INCOMPLETE) == 0) {
152 ze->zone_state = ZONE_STATE_INCOMPLETE;
153 } else if (strcmp(p, ZONE_STATE_STR_INSTALLED) == 0) {
154 ze->zone_state = ZONE_STATE_INSTALLED;
155 } else {
156 continue;
157 }
158
159 /* zonepath */
160 p = gettok(&cp);
161 if (strlen(p) >= MAXPATHLEN) {
162 /* very long paths are not allowed */
163 continue;
164 }
165 (void) strlcpy(ze->zone_path, p, MAXPATHLEN);
166
167 /* uuid */
168 p = gettok(&cp);
169 if (uuid_parse(p, ze->zone_uuid) == -1)
170 uuid_clear(ze->zone_uuid);
171
172 /* brand [optional] */
173 p = gettok(&cp);
174 if (strlen(p) >= MAXNAMELEN) {
175 /* very long names are not allowed */
176 continue;
177 }
178 (void) strlcpy(ze->zone_brand, p, MAXNAMELEN);
179
180 /* IP type [optional] */
181 p = gettok(&cp);
182 if (strlen(p) >= MAXNAMELEN) {
183 /* very long names are not allowed */
184 continue;
185 }
186 ze->zone_iptype = ZS_SHARED;
187 if (*p == 'e') {
188 ze->zone_iptype = ZS_EXCLUSIVE;
189 }
190
191 /* debug ID [optional] */
192 p = gettok(&cp);
193 if (*p != '\0')
194 ze->zone_did = atoi(p);
195
196 break;
197 }
198
199 return (ze);
200 }
201
202 static boolean_t
203 path_common(char *path, size_t path_size, const char *stem)
204 {
205 const char *native_root = zone_get_nroot();
206
207 if (native_root == NULL || zonecfg_in_alt_root()) {
208 /*
209 * Do not prepend the native system root (e.g. "/native") if an
210 * alternative configuration root has been selected.
211 */
212 native_root = "";
213 }
214
215 return (snprintf(path, path_size, "%s%s%s", native_root, zonecfg_root,
216 stem) < path_size);
217 }
218
219 static boolean_t
220 get_index_path(char *path, size_t path_size)
221 {
222 return (path_common(path, path_size, ZONE_INDEX_FILE));
223 }
224
225 static boolean_t
226 get_temp_path(char *path, size_t path_size)
227 {
228 return (path_common(path, path_size, _PATH_TMPFILE));
229 }
230
231 FILE *
232 setzoneent(void)
233 {
234 char path[MAXPATHLEN];
235
236 if (!get_index_path(path, sizeof (path))) {
237 errno = EINVAL;
238 return (NULL);
239 }
240 return (fopen(path, "r"));
241 }
242
243 void
244 endzoneent(FILE *cookie)
245 {
246 if (cookie != NULL)
247 (void) fclose(cookie);
248 }
249
250 static int
251 lock_index_file(void)
252 {
253 int lock_fd;
254 struct flock lock;
255 char path[MAXPATHLEN];
256
257 if (!path_common(path, sizeof (path), ZONE_INDEX_LOCK_DIR))
258 return (-1);
259 if ((mkdir(path, S_IRWXU) == -1) && errno != EEXIST)
260 return (-1);
261 if (strlcat(path, ZONE_INDEX_LOCK_FILE, sizeof (path)) >=
262 sizeof (path))
263 return (-1);
264 lock_fd = open(path, O_CREAT|O_RDWR, 0644);
265 if (lock_fd == -1)
266 return (-1);
267
268 lock.l_type = F_WRLCK;
269 lock.l_whence = SEEK_SET;
270 lock.l_start = 0;
271 lock.l_len = 0;
272
273 if (fcntl(lock_fd, F_SETLKW, &lock) == -1) {
274 (void) close(lock_fd);
275 return (-1);
276 }
277
278 return (lock_fd);
279 }
280
281 static int
282 unlock_index_file(int lock_fd)
283 {
284 struct flock lock;
285
286 lock.l_type = F_UNLCK;
287 lock.l_whence = SEEK_SET;
288 lock.l_start = 0;
289 lock.l_len = 0;
290
291 if (fcntl(lock_fd, F_SETLK, &lock) == -1)
292 return (Z_UNLOCKING_FILE);
293
294 if (close(lock_fd) == -1)
295 return (Z_UNLOCKING_FILE);
296
297 return (Z_OK);
298 }
299
300 /*
301 * This function adds or removes a zone name et al. to the index file.
302 *
303 * If ze->zone_state is < 0, it means leave the
304 * existing value unchanged; this is only meaningful when operation ==
305 * PZE_MODIFY (i.e., it's bad on PZE_ADD and a no-op on PZE_REMOVE).
306 *
307 * A zero-length ze->zone_path means leave the existing value
308 * unchanged; this is only meaningful when operation == PZE_MODIFY
309 * (i.e., it's bad on PZE_ADD and a no-op on PZE_REMOVE).
310 *
311 * A zero-length ze->zone_newname means leave the existing name
312 * unchanged; otherwise the zone is renamed to zone_newname. This is
313 * only meaningful when operation == PZE_MODIFY.
314 *
315 * Locking and unlocking is done via the functions above.
316 * The file itself is not modified in place; rather, a copy is made which
317 * is modified, then the copy is atomically renamed back to the main file.
318 */
319 int
320 putzoneent(struct zoneent *ze, zoneent_op_t operation)
321 {
322 FILE *index_file, *tmp_file;
323 char buf[MAX_INDEX_LEN];
324 int tmp_file_desc, lock_fd, err;
325 boolean_t exist, need_quotes;
326 char *cp, *tmpp;
327 char tmp_path[MAXPATHLEN];
328 char path[MAXPATHLEN];
329 char uuidstr[UUID_PRINTABLE_STRING_LENGTH];
330 size_t namelen;
331 const char *zone_name, *zone_state, *zone_path, *zone_uuid,
332 *zone_brand = "", *zone_iptype;
333 zoneid_t zone_did;
334
335 assert(ze != NULL);
336
337 /*
338 * Don't allow modification of Global Zone entry
339 * in index file
340 */
341 if ((operation == PZE_MODIFY) &&
342 (strcmp(ze->zone_name, GLOBAL_ZONENAME) == 0)) {
343 return (Z_OK);
344 }
345
346 if (operation == PZE_ADD &&
347 (ze->zone_state < 0 || strlen(ze->zone_path) == 0))
348 return (Z_INVAL);
349
350 if (operation != PZE_MODIFY && strlen(ze->zone_newname) != 0)
351 return (Z_INVAL);
352
353 if ((lock_fd = lock_index_file()) == -1)
354 return (Z_LOCKING_FILE);
355
356 if (!get_temp_path(tmp_path, sizeof (tmp_path))) {
357 (void) unlock_index_file(lock_fd);
358 return (Z_NOMEM);
359 }
360
361 tmp_file_desc = mkstemp(tmp_path);
362 if (tmp_file_desc == -1) {
363 (void) unlink(tmp_path);
364 (void) unlock_index_file(lock_fd);
365 return (Z_TEMP_FILE);
366 }
367 (void) fchmod(tmp_file_desc, ZONE_INDEX_MODE);
368 (void) fchown(tmp_file_desc, ZONE_INDEX_UID, ZONE_INDEX_GID);
369 if ((tmp_file = fdopen(tmp_file_desc, "w")) == NULL) {
370 (void) close(tmp_file_desc);
371 err = Z_MISC_FS;
372 goto error;
373 }
374 if (!get_index_path(path, sizeof (path))) {
375 err = Z_MISC_FS;
376 goto error;
377 }
378 if ((index_file = fopen(path, "r")) == NULL) {
379 err = Z_MISC_FS;
380 goto error;
381 }
382
383 exist = B_FALSE;
384 zone_name = ze->zone_name;
385 namelen = strlen(zone_name);
386 zone_brand = ze->zone_brand;
387 zone_iptype = (ze->zone_iptype == ZS_SHARED ? "sh" : "ex");
388 zone_did = ze->zone_did;
389 for (;;) {
390 if (fgets(buf, sizeof (buf), index_file) == NULL) {
391 if (operation == PZE_ADD && !exist) {
392 zone_state = zone_state_str(ze->zone_state);
393 zone_path = ze->zone_path;
394 zone_uuid = "";
395 goto add_entry;
396 }
397 /*
398 * It's not considered an error to delete something
399 * that doesn't exist, but we can't modify a missing
400 * record.
401 */
402 if (operation == PZE_MODIFY && !exist) {
403 err = Z_UPDATING_INDEX;
404 goto error;
405 }
406 break;
407 }
408
409 if (buf[0] == '#') {
410 /* skip and preserve comment lines */
411 (void) fputs(buf, tmp_file);
412 continue;
413 }
414
415 if (strncmp(buf, zone_name, namelen) != 0 ||
416 buf[namelen] != ':') {
417 /* skip and preserve non-target lines */
418 (void) fputs(buf, tmp_file);
419 continue;
420 }
421
422 if ((cp = strpbrk(buf, "\r\n")) == NULL) {
423 /* this represents a line that's too long; delete */
424 continue;
425 }
426 *cp = '\0';
427
428 /*
429 * Skip over the zone name. Because we've already matched the
430 * target zone (above), we know for certain here that the zone
431 * name is present and correctly formed. No need to check.
432 */
433 cp = strchr(buf, ':') + 1;
434
435 zone_state = gettok(&cp);
436 if (*zone_state == '\0') {
437 /* state field should not be empty */
438 err = Z_UPDATING_INDEX;
439 goto error;
440 }
441 zone_path = gettok(&cp);
442 zone_uuid = gettok(&cp);
443 zone_brand = gettok(&cp);
444 zone_iptype = gettok(&cp);
445 tmpp = gettok(&cp);
446 if (*tmpp != '\0')
447 zone_did = atoi(tmpp);
448
449 switch (operation) {
450 case PZE_ADD:
451 /* can't add same zone */
452 err = Z_UPDATING_INDEX;
453 goto error;
454
455 case PZE_MODIFY:
456 /*
457 * If the caller specified a new state for the zone,
458 * then use that. Otherwise, use the current state.
459 */
460 if (ze->zone_state >= 0) {
461 zone_state = zone_state_str(ze->zone_state);
462 }
463
464 /* If a new name is supplied, use it. */
465 if (ze->zone_newname[0] != '\0')
466 zone_name = ze->zone_newname;
467
468 if (ze->zone_path[0] != '\0')
469 zone_path = ze->zone_path;
470
471 /* If new UUID provided, replace it */
472 if (!uuid_is_null(ze->zone_uuid)) {
473 uuid_unparse(ze->zone_uuid, uuidstr);
474 zone_uuid = uuidstr;
475 }
476
477 /* If a brand is supplied, use it. */
478 if (ze->zone_brand[0] != '\0') {
479 zone_brand = ze->zone_brand;
480
481 /*
482 * Since the brand, iptype and did are optional,
483 * we we only reset the iptype and did if the
484 * brand is provided.
485 */
486 zone_iptype = (ze->zone_iptype == ZS_SHARED ?
487 "sh" : "ex");
488 zone_did = ze->zone_did;
489 }
490
491 break;
492
493 case PZE_REMOVE:
494 default:
495 continue;
496 }
497
498 add_entry:
499 /*
500 * If the entry in the file is in greater than configured
501 * state, then we must have a UUID. Make sure that we do.
502 * (Note that the file entry is only tokenized, not fully
503 * parsed, so we need to do a string comparison here.)
504 */
505 if (strcmp(zone_state, ZONE_STATE_STR_CONFIGURED) != 0 &&
506 *zone_uuid == '\0') {
507 if (uuid_is_null(ze->zone_uuid))
508 uuid_generate(ze->zone_uuid);
509 uuid_unparse(ze->zone_uuid, uuidstr);
510 zone_uuid = uuidstr;
511 }
512 /*
513 * We need to quote a path that contains a ":"; this should
514 * only affect the zonepath, as zone names do not allow such
515 * characters, and zone states do not have them either. Same
516 * with double-quotes themselves: they are not allowed in zone
517 * names, and do not occur in zone states, and in theory should
518 * never occur in a zonepath since zonecfg does not support a
519 * method for escaping them.
520 */
521 need_quotes = (strchr(zone_path, ':') != NULL);
522
523 if (*zone_brand != '\0') {
524 (void) fprintf(tmp_file, "%s:%s:%s%s%s:%s:%s:%s:%d\n",
525 zone_name, zone_state, need_quotes ? "\"" : "",
526 zone_path, need_quotes ? "\"" : "", zone_uuid,
527 zone_brand, zone_iptype, zone_did);
528 } else {
529 (void) fprintf(tmp_file, "%s:%s:%s%s%s:%s\n", zone_name,
530 zone_state, need_quotes ? "\"" : "", zone_path,
531 need_quotes ? "\"" : "", zone_uuid);
532 }
533 exist = B_TRUE;
534 }
535
536 (void) fclose(index_file);
537 index_file = NULL;
538 if (fclose(tmp_file) != 0) {
539 tmp_file = NULL;
540 err = Z_MISC_FS;
541 goto error;
542 }
543 tmp_file = NULL;
544 if (rename(tmp_path, path) == -1) {
545 err = errno == EACCES ? Z_ACCES : Z_MISC_FS;
546 goto error;
547 }
548 if (unlock_index_file(lock_fd) != Z_OK)
549 return (Z_UNLOCKING_FILE);
550 return (Z_OK);
551
552 error:
553 if (index_file != NULL)
554 (void) fclose(index_file);
555 if (tmp_file != NULL)
556 (void) fclose(tmp_file);
557 (void) unlink(tmp_path);
558 (void) unlock_index_file(lock_fd);
559 return (err);
560 }