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 }