2N/A/*
2N/A * CDDL HEADER START
2N/A *
2N/A * The contents of this file are subject to the terms of the
2N/A * Common Development and Distribution License (the "License").
2N/A * You may not use this file except in compliance with the License.
2N/A *
2N/A * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
2N/A * or http://www.opensolaris.org/os/licensing.
2N/A * See the License for the specific language governing permissions
2N/A * and limitations under the License.
2N/A *
2N/A * When distributing Covered Code, include this CDDL HEADER in each
2N/A * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
2N/A * If applicable, add the following below this CDDL HEADER, with the
2N/A * fields enclosed by brackets "[]" replaced with your own identifying
2N/A * information: Portions Copyright [yyyy] [name of copyright owner]
2N/A *
2N/A * CDDL HEADER END
2N/A */
2N/A/*
2N/A * Copyright (c) 2003, 2012, Oracle and/or its affiliates. All rights reserved.
2N/A */
2N/A
2N/A
2N/A/*
2N/A * This module contains functions used for reading and writing the index file.
2N/A * setzoneent() opens the file. getzoneent() parses the file, doing the usual
2N/A * skipping of comment lines, etc., and using gettok() to deal with the ":"
2N/A * delimiters. endzoneent() closes the file. putzoneent() updates the file,
2N/A * adding, deleting or modifying lines, locking and unlocking appropriately.
2N/A */
2N/A
2N/A#include <stdlib.h>
2N/A#include <string.h>
2N/A#include <errno.h>
2N/A#include <libzonecfg.h>
2N/A#include <unistd.h>
2N/A#include <fcntl.h>
2N/A#include <sys/stat.h>
2N/A#include <assert.h>
2N/A#include <uuid/uuid.h>
2N/A#include "zonecfg_impl.h"
2N/A
2N/A
2N/A#define _PATH_TMPFILE ZONE_CONFIG_ROOT "/zonecfg.XXXXXX"
2N/A
2N/A/*
2N/A * gettok() is a helper function for parsing the index file, used to split
2N/A * the lines by their ":" delimiters. Note that an entry may contain a ":"
2N/A * inside double quotes; this should only affect the zonepath, as zone names
2N/A * do not allow such characters, and zone states do not have them either.
2N/A * Same with double-quotes themselves: they are not allowed in zone names,
2N/A * and do not occur in zone states, and in theory should never occur in a
2N/A * zonepath since zonecfg does not support a method for escaping them.
2N/A *
2N/A * It never returns NULL.
2N/A */
2N/A
2N/Astatic char *
2N/Agettok(char **cpp)
2N/A{
2N/A char *cp = *cpp, *retv;
2N/A boolean_t quoted = B_FALSE;
2N/A
2N/A if (cp == NULL)
2N/A return ("");
2N/A if (*cp == '"') {
2N/A quoted = B_TRUE;
2N/A cp++;
2N/A }
2N/A retv = cp;
2N/A if (quoted) {
2N/A while (*cp != '\0' && *cp != '"')
2N/A cp++;
2N/A if (*cp == '"')
2N/A *cp++ = '\0';
2N/A }
2N/A while (*cp != '\0' && *cp != ':')
2N/A cp++;
2N/A if (*cp == '\0') {
2N/A *cpp = NULL;
2N/A } else {
2N/A *cp++ = '\0';
2N/A *cpp = cp;
2N/A }
2N/A return (retv);
2N/A}
2N/A
2N/Achar *
2N/Agetzoneent(FILE *cookie)
2N/A{
2N/A struct zoneent *ze;
2N/A char *name;
2N/A
2N/A if ((ze = getzoneent_private(cookie)) == NULL)
2N/A return (NULL);
2N/A name = strdup(ze->zone_name);
2N/A free(ze);
2N/A return (name);
2N/A}
2N/A
2N/Astruct zoneent *
2N/Agetzoneent_private(FILE *cookie)
2N/A{
2N/A char *cp, buf[MAX_INDEX_LEN], *p;
2N/A struct zoneent *ze;
2N/A
2N/A if (cookie == NULL)
2N/A return (NULL);
2N/A
2N/A if ((ze = malloc(sizeof (struct zoneent))) == NULL)
2N/A return (NULL);
2N/A
2N/A for (;;) {
2N/A if (fgets(buf, sizeof (buf), cookie) == NULL) {
2N/A free(ze);
2N/A return (NULL);
2N/A }
2N/A if ((cp = strpbrk(buf, "\r\n")) == NULL) {
2N/A /* this represents a line that's too long */
2N/A continue;
2N/A }
2N/A *cp = '\0';
2N/A cp = buf;
2N/A if (*cp == '#') {
2N/A /* skip comment lines */
2N/A continue;
2N/A }
2N/A p = gettok(&cp);
2N/A if (*p == '\0' || strlen(p) >= ZONENAME_MAX) {
2N/A /*
2N/A * empty or very long zone names are not allowed
2N/A */
2N/A continue;
2N/A }
2N/A (void) strlcpy(ze->zone_name, p, ZONENAME_MAX);
2N/A
2N/A p = gettok(&cp);
2N/A if (*p == '\0') {
2N/A /* state field should not be empty */
2N/A continue;
2N/A }
2N/A if ((ze->zone_state = zone_state_num(p)) > ZONE_STATE_INSTALLED)
2N/A continue;
2N/A
2N/A p = gettok(&cp);
2N/A if (strlen(p) >= MAXPATHLEN) {
2N/A /* very long paths are not allowed */
2N/A continue;
2N/A }
2N/A (void) strlcpy(ze->zone_path, p, MAXPATHLEN);
2N/A
2N/A p = gettok(&cp);
2N/A if (uuid_parse(p, ze->zone_uuid) == -1)
2N/A uuid_clear(ze->zone_uuid);
2N/A
2N/A break;
2N/A }
2N/A
2N/A return (ze);
2N/A}
2N/A
2N/Astatic boolean_t
2N/Aget_index_path(char *path)
2N/A{
2N/A return (snprintf(path, MAXPATHLEN, "%s%s", zonecfg_root,
2N/A ZONE_INDEX_FILE) < MAXPATHLEN);
2N/A}
2N/A
2N/AFILE *
2N/Asetzoneent(void)
2N/A{
2N/A char path[MAXPATHLEN];
2N/A
2N/A if (!get_index_path(path)) {
2N/A errno = EINVAL;
2N/A return (NULL);
2N/A }
2N/A return (fopen(path, "r"));
2N/A}
2N/A
2N/Avoid
2N/Aendzoneent(FILE *cookie)
2N/A{
2N/A if (cookie != NULL)
2N/A (void) fclose(cookie);
2N/A}
2N/A
2N/Astatic int
2N/Alock_index_file(void)
2N/A{
2N/A int lock_fd;
2N/A struct flock lock;
2N/A char path[MAXPATHLEN];
2N/A
2N/A if (snprintf(path, sizeof (path), "%s%s", zonecfg_root,
2N/A ZONE_INDEX_LOCK_DIR) >= sizeof (path))
2N/A return (-1);
2N/A if ((mkdir(path, S_IRWXU) == -1) && errno != EEXIST)
2N/A return (-1);
2N/A if (strlcat(path, ZONE_INDEX_LOCK_FILE, sizeof (path)) >=
2N/A sizeof (path))
2N/A return (-1);
2N/A lock_fd = open(path, O_CREAT|O_RDWR, 0644);
2N/A if (lock_fd == -1)
2N/A return (-1);
2N/A
2N/A lock.l_type = F_WRLCK;
2N/A lock.l_whence = SEEK_SET;
2N/A lock.l_start = 0;
2N/A lock.l_len = 0;
2N/A
2N/A if (fcntl(lock_fd, F_SETLKW, &lock) == -1) {
2N/A (void) close(lock_fd);
2N/A return (-1);
2N/A }
2N/A
2N/A return (lock_fd);
2N/A}
2N/A
2N/Astatic int
2N/Aunlock_index_file(int lock_fd)
2N/A{
2N/A struct flock lock;
2N/A
2N/A lock.l_type = F_UNLCK;
2N/A lock.l_whence = SEEK_SET;
2N/A lock.l_start = 0;
2N/A lock.l_len = 0;
2N/A
2N/A if (fcntl(lock_fd, F_SETLK, &lock) == -1)
2N/A return (Z_UNLOCKING_FILE);
2N/A
2N/A if (close(lock_fd) == -1)
2N/A return (Z_UNLOCKING_FILE);
2N/A
2N/A return (Z_OK);
2N/A}
2N/A
2N/A/*
2N/A * This function adds or removes a zone name et al. to the index file.
2N/A *
2N/A * If ze->zone_state is < 0, it means leave the
2N/A * existing value unchanged; this is only meaningful when operation ==
2N/A * PZE_MODIFY (i.e., it's bad on PZE_ADD and a no-op on PZE_REMOVE).
2N/A *
2N/A * A zero-length ze->zone_path means leave the existing value
2N/A * unchanged; this is only meaningful when operation == PZE_MODIFY
2N/A * (i.e., it's bad on PZE_ADD and a no-op on PZE_REMOVE).
2N/A *
2N/A * A zero-length ze->zone_newname means leave the existing name
2N/A * unchanged; otherwise the zone is renamed to zone_newname. This is
2N/A * only meaningful when operation == PZE_MODIFY.
2N/A *
2N/A * Locking and unlocking is done via the functions above.
2N/A * The file itself is not modified in place; rather, a copy is made which
2N/A * is modified, then the copy is atomically renamed back to the main file.
2N/A */
2N/Aint
2N/Aputzoneent(struct zoneent *ze, zoneent_op_t operation)
2N/A{
2N/A FILE *index_file, *tmp_file;
2N/A char *tmp_file_name, buf[MAX_INDEX_LEN];
2N/A int tmp_file_desc, lock_fd, err;
2N/A boolean_t exist, need_quotes;
2N/A char *cp;
2N/A char path[MAXPATHLEN];
2N/A char uuidstr[UUID_PRINTABLE_STRING_LENGTH];
2N/A size_t tlen, namelen;
2N/A const char *zone_name, *zone_state, *zone_path, *zone_uuid;
2N/A
2N/A assert(ze != NULL);
2N/A
2N/A /*
2N/A * Don't allow modification of Global Zone entry
2N/A * in index file
2N/A */
2N/A if ((operation == PZE_MODIFY) &&
2N/A (strcmp(ze->zone_name, GLOBAL_ZONENAME) == 0)) {
2N/A return (Z_OK);
2N/A }
2N/A
2N/A if (operation == PZE_ADD &&
2N/A (ze->zone_state < 0 || strlen(ze->zone_path) == 0))
2N/A return (Z_INVAL);
2N/A
2N/A if (operation != PZE_MODIFY && strlen(ze->zone_newname) != 0)
2N/A return (Z_INVAL);
2N/A
2N/A if ((lock_fd = lock_index_file()) == -1)
2N/A return (Z_LOCKING_FILE);
2N/A
2N/A /* using sizeof gives us room for the terminating NUL byte as well */
2N/A tlen = sizeof (_PATH_TMPFILE) + strlen(zonecfg_root);
2N/A tmp_file_name = malloc(tlen);
2N/A if (tmp_file_name == NULL) {
2N/A (void) unlock_index_file(lock_fd);
2N/A return (Z_NOMEM);
2N/A }
2N/A (void) snprintf(tmp_file_name, tlen, "%s%s", zonecfg_root,
2N/A _PATH_TMPFILE);
2N/A
2N/A tmp_file_desc = mkstemp(tmp_file_name);
2N/A if (tmp_file_desc == -1) {
2N/A (void) unlink(tmp_file_name);
2N/A free(tmp_file_name);
2N/A (void) unlock_index_file(lock_fd);
2N/A return (Z_TEMP_FILE);
2N/A }
2N/A (void) fchmod(tmp_file_desc, ZONE_INDEX_MODE);
2N/A (void) fchown(tmp_file_desc, ZONE_INDEX_UID, ZONE_INDEX_GID);
2N/A if ((tmp_file = fdopen(tmp_file_desc, "w")) == NULL) {
2N/A (void) close(tmp_file_desc);
2N/A err = Z_MISC_FS;
2N/A goto error;
2N/A }
2N/A if (!get_index_path(path)) {
2N/A err = Z_MISC_FS;
2N/A goto error;
2N/A }
2N/A if ((index_file = fopen(path, "r")) == NULL) {
2N/A err = Z_MISC_FS;
2N/A goto error;
2N/A }
2N/A
2N/A exist = B_FALSE;
2N/A zone_name = ze->zone_name;
2N/A namelen = strlen(zone_name);
2N/A for (;;) {
2N/A if (fgets(buf, sizeof (buf), index_file) == NULL) {
2N/A if (operation == PZE_ADD && !exist) {
2N/A zone_state = zone_state_str(ze->zone_state);
2N/A zone_path = ze->zone_path;
2N/A zone_uuid = "";
2N/A goto add_entry;
2N/A }
2N/A /*
2N/A * It's not considered an error to delete something
2N/A * that doesn't exist, but we can't modify a missing
2N/A * record.
2N/A */
2N/A if (operation == PZE_MODIFY && !exist) {
2N/A err = Z_UPDATING_INDEX;
2N/A goto error;
2N/A }
2N/A break;
2N/A }
2N/A
2N/A if (buf[0] == '#') {
2N/A /* skip and preserve comment lines */
2N/A (void) fputs(buf, tmp_file);
2N/A continue;
2N/A }
2N/A
2N/A if (strncmp(buf, zone_name, namelen) != 0 ||
2N/A buf[namelen] != ':') {
2N/A /* skip and preserve non-target lines */
2N/A (void) fputs(buf, tmp_file);
2N/A continue;
2N/A }
2N/A
2N/A if ((cp = strpbrk(buf, "\r\n")) == NULL) {
2N/A /* this represents a line that's too long; delete */
2N/A continue;
2N/A }
2N/A *cp = '\0';
2N/A
2N/A /*
2N/A * Skip over the zone name. Because we've already matched the
2N/A * target zone (above), we know for certain here that the zone
2N/A * name is present and correctly formed. No need to check.
2N/A */
2N/A cp = strchr(buf, ':') + 1;
2N/A
2N/A zone_state = gettok(&cp);
2N/A if (*zone_state == '\0') {
2N/A /* state field should not be empty */
2N/A err = Z_UPDATING_INDEX;
2N/A goto error;
2N/A }
2N/A zone_path = gettok(&cp);
2N/A zone_uuid = gettok(&cp);
2N/A
2N/A switch (operation) {
2N/A case PZE_ADD:
2N/A /* can't add same zone */
2N/A err = Z_UPDATING_INDEX;
2N/A goto error;
2N/A
2N/A case PZE_MODIFY:
2N/A /*
2N/A * If the caller specified a new state for the zone,
2N/A * then use that. Otherwise, use the current state.
2N/A */
2N/A if (ze->zone_state >= 0) {
2N/A zone_state = zone_state_str(ze->zone_state);
2N/A
2N/A /*
2N/A * If the caller is uninstalling this zone,
2N/A * then wipe out the uuid. The zone's contents
2N/A * are no longer known.
2N/A */
2N/A if (ze->zone_state < ZONE_STATE_INSTALLED)
2N/A zone_uuid = "";
2N/A }
2N/A
2N/A /* If a new name is supplied, use it. */
2N/A if (ze->zone_newname[0] != '\0')
2N/A zone_name = ze->zone_newname;
2N/A
2N/A if (ze->zone_path[0] != '\0')
2N/A zone_path = ze->zone_path;
2N/A break;
2N/A
2N/A case PZE_REMOVE:
2N/A default:
2N/A continue;
2N/A }
2N/A
2N/A add_entry:
2N/A /*
2N/A * If the entry in the file is in greater than configured
2N/A * state, then we must have a UUID. Make sure that we do.
2N/A * (Note that the file entry is only tokenized, not fully
2N/A * parsed, so we need to do a string comparison here.)
2N/A */
2N/A if (strcmp(zone_state,
2N/A zone_state_str(ZONE_STATE_CONFIGURED)) != 0 &&
2N/A *zone_uuid == '\0') {
2N/A if (uuid_is_null(ze->zone_uuid))
2N/A uuid_generate(ze->zone_uuid);
2N/A uuid_unparse(ze->zone_uuid, uuidstr);
2N/A zone_uuid = uuidstr;
2N/A }
2N/A /*
2N/A * We need to quote a path that contains a ":"; this should
2N/A * only affect the zonepath, as zone names do not allow such
2N/A * characters, and zone states do not have them either. Same
2N/A * with double-quotes themselves: they are not allowed in zone
2N/A * names, and do not occur in zone states, and in theory should
2N/A * never occur in a zonepath since zonecfg does not support a
2N/A * method for escaping them.
2N/A */
2N/A need_quotes = (strchr(zone_path, ':') != NULL);
2N/A (void) fprintf(tmp_file, "%s:%s:%s%s%s:%s\n", zone_name,
2N/A zone_state, need_quotes ? "\"" : "", zone_path,
2N/A need_quotes ? "\"" : "", zone_uuid);
2N/A exist = B_TRUE;
2N/A }
2N/A
2N/A (void) fclose(index_file);
2N/A index_file = NULL;
2N/A if (fclose(tmp_file) != 0) {
2N/A tmp_file = NULL;
2N/A err = Z_MISC_FS;
2N/A goto error;
2N/A }
2N/A tmp_file = NULL;
2N/A if (rename(tmp_file_name, path) == -1) {
2N/A err = errno == EACCES ? Z_ACCES : Z_MISC_FS;
2N/A goto error;
2N/A }
2N/A free(tmp_file_name);
2N/A if (unlock_index_file(lock_fd) != Z_OK)
2N/A return (Z_UNLOCKING_FILE);
2N/A return (Z_OK);
2N/A
2N/Aerror:
2N/A if (index_file != NULL)
2N/A (void) fclose(index_file);
2N/A if (tmp_file != NULL)
2N/A (void) fclose(tmp_file);
2N/A (void) unlink(tmp_file_name);
2N/A free(tmp_file_name);
2N/A (void) unlock_index_file(lock_fd);
2N/A return (err);
2N/A}