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 p = gettok(&cp);
132 if (*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 == '\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
156 p = gettok(&cp);
157 if (strlen(p) >= MAXPATHLEN) {
158 /* very long paths are not allowed */
159 continue;
160 }
161 (void) strlcpy(ze->zone_path, p, MAXPATHLEN);
162
163 p = gettok(&cp);
164 if (uuid_parse(p, ze->zone_uuid) == -1)
165 uuid_clear(ze->zone_uuid);
166
167 break;
168 }
169
170 return (ze);
171 }
172
173 static boolean_t
174 path_common(char *path, size_t path_size, const char *stem)
175 {
176 const char *native_root = zone_get_nroot();
177
178 if (native_root == NULL || zonecfg_in_alt_root()) {
179 /*
180 * Do not prepend the native system root (e.g. "/native") if an
181 * alternative configuration root has been selected.
182 */
183 native_root = "";
184 }
185
186 return (snprintf(path, path_size, "%s%s%s", native_root, zonecfg_root,
187 stem) < path_size);
188 }
189
190 static boolean_t
191 get_index_path(char *path, size_t path_size)
192 {
193 return (path_common(path, path_size, ZONE_INDEX_FILE));
194 }
195
196 static boolean_t
197 get_temp_path(char *path, size_t path_size)
198 {
199 return (path_common(path, path_size, _PATH_TMPFILE));
200 }
201
202 FILE *
203 setzoneent(void)
204 {
205 char path[MAXPATHLEN];
206
207 if (!get_index_path(path, sizeof (path))) {
208 errno = EINVAL;
209 return (NULL);
210 }
211 return (fopen(path, "r"));
212 }
213
214 void
215 endzoneent(FILE *cookie)
216 {
217 if (cookie != NULL)
218 (void) fclose(cookie);
219 }
220
221 static int
222 lock_index_file(void)
223 {
224 int lock_fd;
225 struct flock lock;
226 char path[MAXPATHLEN];
227
228 if (!path_common(path, sizeof (path), ZONE_INDEX_LOCK_DIR))
229 return (-1);
230 if ((mkdir(path, S_IRWXU) == -1) && errno != EEXIST)
231 return (-1);
232 if (strlcat(path, ZONE_INDEX_LOCK_FILE, sizeof (path)) >=
233 sizeof (path))
234 return (-1);
235 lock_fd = open(path, O_CREAT|O_RDWR, 0644);
236 if (lock_fd == -1)
237 return (-1);
238
239 lock.l_type = F_WRLCK;
240 lock.l_whence = SEEK_SET;
241 lock.l_start = 0;
242 lock.l_len = 0;
243
244 if (fcntl(lock_fd, F_SETLKW, &lock) == -1) {
245 (void) close(lock_fd);
246 return (-1);
247 }
248
249 return (lock_fd);
250 }
251
252 static int
253 unlock_index_file(int lock_fd)
254 {
255 struct flock lock;
256
257 lock.l_type = F_UNLCK;
258 lock.l_whence = SEEK_SET;
259 lock.l_start = 0;
260 lock.l_len = 0;
261
262 if (fcntl(lock_fd, F_SETLK, &lock) == -1)
263 return (Z_UNLOCKING_FILE);
264
265 if (close(lock_fd) == -1)
266 return (Z_UNLOCKING_FILE);
267
268 return (Z_OK);
269 }
270
271 /*
272 * This function adds or removes a zone name et al. to the index file.
273 *
274 * If ze->zone_state is < 0, it means leave the
275 * existing value unchanged; this is only meaningful when operation ==
276 * PZE_MODIFY (i.e., it's bad on PZE_ADD and a no-op on PZE_REMOVE).
277 *
278 * A zero-length ze->zone_path means leave the existing value
279 * unchanged; this is only meaningful when operation == PZE_MODIFY
280 * (i.e., it's bad on PZE_ADD and a no-op on PZE_REMOVE).
281 *
282 * A zero-length ze->zone_newname means leave the existing name
283 * unchanged; otherwise the zone is renamed to zone_newname. This is
284 * only meaningful when operation == PZE_MODIFY.
285 *
286 * Locking and unlocking is done via the functions above.
287 * The file itself is not modified in place; rather, a copy is made which
288 * is modified, then the copy is atomically renamed back to the main file.
289 */
290 int
291 putzoneent(struct zoneent *ze, zoneent_op_t operation)
292 {
293 FILE *index_file, *tmp_file;
294 char buf[MAX_INDEX_LEN];
295 int tmp_file_desc, lock_fd, err;
296 boolean_t exist, need_quotes;
297 char *cp;
298 char tmp_path[MAXPATHLEN];
299 char path[MAXPATHLEN];
300 char uuidstr[UUID_PRINTABLE_STRING_LENGTH];
301 size_t namelen;
302 const char *zone_name, *zone_state, *zone_path, *zone_uuid;
303
304 assert(ze != NULL);
305
306 /*
307 * Don't allow modification of Global Zone entry
308 * in index file
309 */
310 if ((operation == PZE_MODIFY) &&
311 (strcmp(ze->zone_name, GLOBAL_ZONENAME) == 0)) {
312 return (Z_OK);
313 }
314
315 if (operation == PZE_ADD &&
316 (ze->zone_state < 0 || strlen(ze->zone_path) == 0))
317 return (Z_INVAL);
318
319 if (operation != PZE_MODIFY && strlen(ze->zone_newname) != 0)
320 return (Z_INVAL);
321
322 if ((lock_fd = lock_index_file()) == -1)
323 return (Z_LOCKING_FILE);
324
325 if (!get_temp_path(tmp_path, sizeof (tmp_path))) {
326 (void) unlock_index_file(lock_fd);
327 return (Z_NOMEM);
328 }
329
330 tmp_file_desc = mkstemp(tmp_path);
331 if (tmp_file_desc == -1) {
332 (void) unlink(tmp_path);
333 (void) unlock_index_file(lock_fd);
334 return (Z_TEMP_FILE);
335 }
336 (void) fchmod(tmp_file_desc, ZONE_INDEX_MODE);
337 (void) fchown(tmp_file_desc, ZONE_INDEX_UID, ZONE_INDEX_GID);
338 if ((tmp_file = fdopen(tmp_file_desc, "w")) == NULL) {
339 (void) close(tmp_file_desc);
340 err = Z_MISC_FS;
341 goto error;
342 }
343 if (!get_index_path(path, sizeof (path))) {
344 err = Z_MISC_FS;
345 goto error;
346 }
347 if ((index_file = fopen(path, "r")) == NULL) {
348 err = Z_MISC_FS;
349 goto error;
350 }
351
352 exist = B_FALSE;
353 zone_name = ze->zone_name;
354 namelen = strlen(zone_name);
355 for (;;) {
356 if (fgets(buf, sizeof (buf), index_file) == NULL) {
357 if (operation == PZE_ADD && !exist) {
358 zone_state = zone_state_str(ze->zone_state);
359 zone_path = ze->zone_path;
360 zone_uuid = "";
361 goto add_entry;
362 }
363 /*
364 * It's not considered an error to delete something
365 * that doesn't exist, but we can't modify a missing
366 * record.
367 */
368 if (operation == PZE_MODIFY && !exist) {
369 err = Z_UPDATING_INDEX;
370 goto error;
371 }
372 break;
373 }
374
375 if (buf[0] == '#') {
376 /* skip and preserve comment lines */
377 (void) fputs(buf, tmp_file);
378 continue;
379 }
380
381 if (strncmp(buf, zone_name, namelen) != 0 ||
382 buf[namelen] != ':') {
383 /* skip and preserve non-target lines */
384 (void) fputs(buf, tmp_file);
385 continue;
386 }
387
388 if ((cp = strpbrk(buf, "\r\n")) == NULL) {
389 /* this represents a line that's too long; delete */
390 continue;
391 }
392 *cp = '\0';
393
394 /*
395 * Skip over the zone name. Because we've already matched the
396 * target zone (above), we know for certain here that the zone
397 * name is present and correctly formed. No need to check.
398 */
399 cp = strchr(buf, ':') + 1;
400
401 zone_state = gettok(&cp);
402 if (*zone_state == '\0') {
403 /* state field should not be empty */
404 err = Z_UPDATING_INDEX;
405 goto error;
406 }
407 zone_path = gettok(&cp);
408 zone_uuid = gettok(&cp);
409
410 switch (operation) {
411 case PZE_ADD:
412 /* can't add same zone */
413 err = Z_UPDATING_INDEX;
414 goto error;
415
416 case PZE_MODIFY:
417 /*
418 * If the caller specified a new state for the zone,
419 * then use that. Otherwise, use the current state.
420 */
421 if (ze->zone_state >= 0) {
422 zone_state = zone_state_str(ze->zone_state);
423
424 /*
425 * If the caller is uninstalling this zone,
426 * then wipe out the uuid. The zone's contents
427 * are no longer known.
428 */
429 if (ze->zone_state < ZONE_STATE_INSTALLED)
430 zone_uuid = "";
431 }
432
433 /* If a new name is supplied, use it. */
434 if (ze->zone_newname[0] != '\0')
435 zone_name = ze->zone_newname;
436
437 if (ze->zone_path[0] != '\0')
438 zone_path = ze->zone_path;
439 break;
440
441 case PZE_REMOVE:
442 default:
443 continue;
444 }
445
446 add_entry:
447 /*
448 * If the entry in the file is in greater than configured
449 * state, then we must have a UUID. Make sure that we do.
450 * (Note that the file entry is only tokenized, not fully
451 * parsed, so we need to do a string comparison here.)
452 */
453 if (strcmp(zone_state, ZONE_STATE_STR_CONFIGURED) != 0 &&
454 *zone_uuid == '\0') {
455 if (uuid_is_null(ze->zone_uuid))
456 uuid_generate(ze->zone_uuid);
457 uuid_unparse(ze->zone_uuid, uuidstr);
458 zone_uuid = uuidstr;
459 }
460 /*
461 * We need to quote a path that contains a ":"; this should
462 * only affect the zonepath, as zone names do not allow such
463 * characters, and zone states do not have them either. Same
464 * with double-quotes themselves: they are not allowed in zone
465 * names, and do not occur in zone states, and in theory should
466 * never occur in a zonepath since zonecfg does not support a
467 * method for escaping them.
468 */
469 need_quotes = (strchr(zone_path, ':') != NULL);
470 (void) fprintf(tmp_file, "%s:%s:%s%s%s:%s\n", zone_name,
471 zone_state, need_quotes ? "\"" : "", zone_path,
472 need_quotes ? "\"" : "", zone_uuid);
473 exist = B_TRUE;
474 }
475
476 (void) fclose(index_file);
477 index_file = NULL;
478 if (fclose(tmp_file) != 0) {
479 tmp_file = NULL;
480 err = Z_MISC_FS;
481 goto error;
482 }
483 tmp_file = NULL;
484 if (rename(tmp_path, path) == -1) {
485 err = errno == EACCES ? Z_ACCES : Z_MISC_FS;
486 goto error;
487 }
488 if (unlock_index_file(lock_fd) != Z_OK)
489 return (Z_UNLOCKING_FILE);
490 return (Z_OK);
491
492 error:
493 if (index_file != NULL)
494 (void) fclose(index_file);
495 if (tmp_file != NULL)
496 (void) fclose(tmp_file);
497 (void) unlink(tmp_path);
498 (void) unlock_index_file(lock_fd);
499 return (err);
500 }