#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <libzonecfg.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <assert.h>
#include <uuid/uuid.h>
#include "zonecfg_impl.h"
#define _PATH_TMPFILE ZONE_CONFIG_ROOT "/zonecfg.XXXXXX"
static char *
gettok(char **cpp)
{
char *cp = *cpp, *retv;
boolean_t quoted = B_FALSE;
if (cp == NULL)
return ("");
if (*cp == '"') {
quoted = B_TRUE;
cp++;
}
retv = cp;
if (quoted) {
while (*cp != '\0' && *cp != '"')
cp++;
if (*cp == '"')
*cp++ = '\0';
}
while (*cp != '\0' && *cp != ':')
cp++;
if (*cp == '\0') {
*cpp = NULL;
} else {
*cp++ = '\0';
*cpp = cp;
}
return (retv);
}
char *
getzoneent(FILE *cookie)
{
struct zoneent *ze;
char *name;
if ((ze = getzoneent_private(cookie)) == NULL)
return (NULL);
name = strdup(ze->zone_name);
free(ze);
return (name);
}
struct zoneent *
getzoneent_private(FILE *cookie)
{
char *cp, buf[MAX_INDEX_LEN], *p;
struct zoneent *ze;
if (cookie == NULL)
return (NULL);
if ((ze = malloc(sizeof (struct zoneent))) == NULL)
return (NULL);
for (;;) {
if (fgets(buf, sizeof (buf), cookie) == NULL) {
free(ze);
return (NULL);
}
if ((cp = strpbrk(buf, "\r\n")) == NULL) {
continue;
}
*cp = '\0';
cp = buf;
if (*cp == '#') {
continue;
}
p = gettok(&cp);
if (*p == '\0' || strlen(p) >= ZONENAME_MAX) {
continue;
}
(void) strlcpy(ze->zone_name, p, ZONENAME_MAX);
p = gettok(&cp);
if (*p == '\0') {
continue;
}
errno = 0;
if (strcmp(p, ZONE_STATE_STR_CONFIGURED) == 0) {
ze->zone_state = ZONE_STATE_CONFIGURED;
} else if (strcmp(p, ZONE_STATE_STR_INCOMPLETE) == 0) {
ze->zone_state = ZONE_STATE_INCOMPLETE;
} else if (strcmp(p, ZONE_STATE_STR_INSTALLED) == 0) {
ze->zone_state = ZONE_STATE_INSTALLED;
} else {
continue;
}
p = gettok(&cp);
if (strlen(p) >= MAXPATHLEN) {
continue;
}
(void) strlcpy(ze->zone_path, p, MAXPATHLEN);
p = gettok(&cp);
if (uuid_parse(p, ze->zone_uuid) == -1)
uuid_clear(ze->zone_uuid);
break;
}
return (ze);
}
static boolean_t
get_index_path(char *path)
{
return (snprintf(path, MAXPATHLEN, "%s%s", zonecfg_root,
ZONE_INDEX_FILE) < MAXPATHLEN);
}
FILE *
setzoneent(void)
{
char path[MAXPATHLEN];
if (!get_index_path(path)) {
errno = EINVAL;
return (NULL);
}
return (fopen(path, "r"));
}
void
endzoneent(FILE *cookie)
{
if (cookie != NULL)
(void) fclose(cookie);
}
static int
lock_index_file(void)
{
int lock_fd;
struct flock lock;
char path[MAXPATHLEN];
if (snprintf(path, sizeof (path), "%s%s", zonecfg_root,
ZONE_INDEX_LOCK_DIR) >= sizeof (path))
return (-1);
if ((mkdir(path, S_IRWXU) == -1) && errno != EEXIST)
return (-1);
if (strlcat(path, ZONE_INDEX_LOCK_FILE, sizeof (path)) >=
sizeof (path))
return (-1);
lock_fd = open(path, O_CREAT|O_RDWR, 0644);
if (lock_fd == -1)
return (-1);
lock.l_type = F_WRLCK;
lock.l_whence = SEEK_SET;
lock.l_start = 0;
lock.l_len = 0;
if (fcntl(lock_fd, F_SETLKW, &lock) == -1) {
(void) close(lock_fd);
return (-1);
}
return (lock_fd);
}
static int
unlock_index_file(int lock_fd)
{
struct flock lock;
lock.l_type = F_UNLCK;
lock.l_whence = SEEK_SET;
lock.l_start = 0;
lock.l_len = 0;
if (fcntl(lock_fd, F_SETLK, &lock) == -1)
return (Z_UNLOCKING_FILE);
if (close(lock_fd) == -1)
return (Z_UNLOCKING_FILE);
return (Z_OK);
}
int
putzoneent(struct zoneent *ze, zoneent_op_t operation)
{
FILE *index_file, *tmp_file;
char *tmp_file_name, buf[MAX_INDEX_LEN];
int tmp_file_desc, lock_fd, err;
boolean_t exist, need_quotes;
char *cp;
char path[MAXPATHLEN];
char uuidstr[UUID_PRINTABLE_STRING_LENGTH];
size_t tlen, namelen;
const char *zone_name, *zone_state, *zone_path, *zone_uuid;
assert(ze != NULL);
if ((operation == PZE_MODIFY) &&
(strcmp(ze->zone_name, GLOBAL_ZONENAME) == 0)) {
return (Z_OK);
}
if (operation == PZE_ADD &&
(ze->zone_state < 0 || strlen(ze->zone_path) == 0))
return (Z_INVAL);
if (operation != PZE_MODIFY && strlen(ze->zone_newname) != 0)
return (Z_INVAL);
if ((lock_fd = lock_index_file()) == -1)
return (Z_LOCKING_FILE);
tlen = sizeof (_PATH_TMPFILE) + strlen(zonecfg_root);
tmp_file_name = malloc(tlen);
if (tmp_file_name == NULL) {
(void) unlock_index_file(lock_fd);
return (Z_NOMEM);
}
(void) snprintf(tmp_file_name, tlen, "%s%s", zonecfg_root,
_PATH_TMPFILE);
tmp_file_desc = mkstemp(tmp_file_name);
if (tmp_file_desc == -1) {
(void) unlink(tmp_file_name);
free(tmp_file_name);
(void) unlock_index_file(lock_fd);
return (Z_TEMP_FILE);
}
(void) fchmod(tmp_file_desc, ZONE_INDEX_MODE);
(void) fchown(tmp_file_desc, ZONE_INDEX_UID, ZONE_INDEX_GID);
if ((tmp_file = fdopen(tmp_file_desc, "w")) == NULL) {
(void) close(tmp_file_desc);
err = Z_MISC_FS;
goto error;
}
if (!get_index_path(path)) {
err = Z_MISC_FS;
goto error;
}
if ((index_file = fopen(path, "r")) == NULL) {
err = Z_MISC_FS;
goto error;
}
exist = B_FALSE;
zone_name = ze->zone_name;
namelen = strlen(zone_name);
for (;;) {
if (fgets(buf, sizeof (buf), index_file) == NULL) {
if (operation == PZE_ADD && !exist) {
zone_state = zone_state_str(ze->zone_state);
zone_path = ze->zone_path;
zone_uuid = "";
goto add_entry;
}
if (operation == PZE_MODIFY && !exist) {
err = Z_UPDATING_INDEX;
goto error;
}
break;
}
if (buf[0] == '#') {
(void) fputs(buf, tmp_file);
continue;
}
if (strncmp(buf, zone_name, namelen) != 0 ||
buf[namelen] != ':') {
(void) fputs(buf, tmp_file);
continue;
}
if ((cp = strpbrk(buf, "\r\n")) == NULL) {
continue;
}
*cp = '\0';
cp = strchr(buf, ':') + 1;
zone_state = gettok(&cp);
if (*zone_state == '\0') {
err = Z_UPDATING_INDEX;
goto error;
}
zone_path = gettok(&cp);
zone_uuid = gettok(&cp);
switch (operation) {
case PZE_ADD:
err = Z_UPDATING_INDEX;
goto error;
case PZE_MODIFY:
if (ze->zone_state >= 0) {
zone_state = zone_state_str(ze->zone_state);
if (ze->zone_state < ZONE_STATE_INSTALLED)
zone_uuid = "";
}
if (ze->zone_newname[0] != '\0')
zone_name = ze->zone_newname;
if (ze->zone_path[0] != '\0')
zone_path = ze->zone_path;
break;
case PZE_REMOVE:
default:
continue;
}
add_entry:
if (strcmp(zone_state, ZONE_STATE_STR_CONFIGURED) != 0 &&
*zone_uuid == '\0') {
if (uuid_is_null(ze->zone_uuid))
uuid_generate(ze->zone_uuid);
uuid_unparse(ze->zone_uuid, uuidstr);
zone_uuid = uuidstr;
}
need_quotes = (strchr(zone_path, ':') != NULL);
(void) fprintf(tmp_file, "%s:%s:%s%s%s:%s\n", zone_name,
zone_state, need_quotes ? "\"" : "", zone_path,
need_quotes ? "\"" : "", zone_uuid);
exist = B_TRUE;
}
(void) fclose(index_file);
index_file = NULL;
if (fclose(tmp_file) != 0) {
tmp_file = NULL;
err = Z_MISC_FS;
goto error;
}
tmp_file = NULL;
if (rename(tmp_file_name, path) == -1) {
err = errno == EACCES ? Z_ACCES : Z_MISC_FS;
goto error;
}
free(tmp_file_name);
if (unlock_index_file(lock_fd) != Z_OK)
return (Z_UNLOCKING_FILE);
return (Z_OK);
error:
if (index_file != NULL)
(void) fclose(index_file);
if (tmp_file != NULL)
(void) fclose(tmp_file);
(void) unlink(tmp_file_name);
free(tmp_file_name);
(void) unlock_index_file(lock_fd);
return (err);
}