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