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 * 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 * 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 * 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 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 * 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 192 endzoneent(FILE *cookie) 193 { 194 if (cookie != NULL) 195 (void) fclose(cookie); 196 } 197 198 static int 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 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 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