diff options
Diffstat (limited to 'usr/src/lib/libzonecfg/common/libzonecfg.c')
-rw-r--r-- | usr/src/lib/libzonecfg/common/libzonecfg.c | 500 |
1 files changed, 435 insertions, 65 deletions
diff --git a/usr/src/lib/libzonecfg/common/libzonecfg.c b/usr/src/lib/libzonecfg/common/libzonecfg.c index 87d2e45c52..b9d80dbefa 100644 --- a/usr/src/lib/libzonecfg/common/libzonecfg.c +++ b/usr/src/lib/libzonecfg/common/libzonecfg.c @@ -38,6 +38,7 @@ #include <ctype.h> #include <sys/mntio.h> #include <sys/mnttab.h> +#include <sys/types.h> #include <arpa/inet.h> #include <netdb.h> @@ -99,6 +100,9 @@ struct zone_dochandle { xmlDocPtr zone_dh_doc; xmlNodePtr zone_dh_cur; xmlNodePtr zone_dh_top; + boolean_t zone_dh_newzone; + boolean_t zone_dh_snapshot; + char zone_dh_delete_name[ZONENAME_MAX]; }; /* @@ -140,14 +144,11 @@ zonecfg_error_func(void *ctx, const char *msg, ...) zone_dochandle_t zonecfg_init_handle(void) { - zone_dochandle_t handle = malloc(sizeof (struct zone_dochandle)); + zone_dochandle_t handle = calloc(1, sizeof (struct zone_dochandle)); if (handle == NULL) { errno = Z_NOMEM; return (NULL); } - handle->zone_dh_doc = NULL; - handle->zone_dh_cur = NULL; - handle->zone_dh_top = NULL; /* generic libxml initialization */ xmlLineNumbersDefault(1); @@ -191,12 +192,62 @@ zonecfg_destroy_impl(char *filename) } int -zonecfg_destroy(const char *zonename) +zonecfg_destroy(const char *zonename, boolean_t force) { char path[MAXPATHLEN]; + struct zoneent ze; + int err, state_err; + zone_state_t state; config_file_path(zonename, path); - return (zonecfg_destroy_impl(path)); + + state_err = zone_get_state((char *)zonename, &state); + err = access(path, W_OK); + + /* + * If there is no file, and no index entry, reliably indicate that no + * such zone exists. + */ + if ((state_err == Z_NO_ZONE) && (err == -1) && (errno == ENOENT)) + return (Z_NO_ZONE); + + /* + * Handle any other filesystem related errors (except if the XML + * file is missing, which we treat silently), unless we're forcing, + * in which case we plow on. + */ + if (err == -1 && errno != ENOENT) { + if (errno == EACCES) + return (Z_ACCES); + else if (!force) + return (Z_MISC_FS); + } + + if (state > ZONE_STATE_INSTALLED) + return (Z_BAD_ZONE_STATE); + + if (!force && state > ZONE_STATE_CONFIGURED) + return (Z_BAD_ZONE_STATE); + + /* + * Index deletion succeeds even if the entry doesn't exist. So this + * will fail only if we've had some more severe problem. + */ + bzero(&ze, sizeof (ze)); + (void) strlcpy(ze.zone_name, zonename, sizeof (ze.zone_name)); + if ((err = putzoneent(&ze, PZE_REMOVE)) != Z_OK) + if (!force) + return (err); + + err = zonecfg_destroy_impl(path); + + /* + * Treat failure to find the XML file silently, since, well, it's + * gone, and with the index file cleaned up, we're done. + */ + if (err == Z_OK || err == Z_NO_ZONE) + return (Z_OK); + return (err); } int @@ -277,12 +328,39 @@ setrootattr(zone_dochandle_t handle, const xmlChar *propname, char *propval) return (Z_OK); } +static void +addcomment(zone_dochandle_t handle, const char *comment) +{ + xmlNodePtr node; + node = xmlNewComment((xmlChar *) comment); + + if (node != NULL) + (void) xmlAddPrevSibling(handle->zone_dh_top, node); +} + +static void +stripcomments(zone_dochandle_t handle) +{ + xmlDocPtr top; + xmlNodePtr child, next; + + top = handle->zone_dh_doc; + for (child = top->xmlChildrenNode; child != NULL; child = next) { + next = child->next; + if (child->name == NULL) + continue; + if (xmlStrcmp(child->name, DTD_ELEM_COMMENT) == 0) { + next = child->next; + xmlUnlinkNode(child); + xmlFreeNode(child); + } + } +} + static int zonecfg_get_handle_impl(char *zonename, char *filename, zone_dochandle_t handle) { xmlValidCtxtPtr cvp; - xmlDocPtr top; - xmlNodePtr child, next; struct stat statbuf; int valid; @@ -302,18 +380,9 @@ zonecfg_get_handle_impl(char *zonename, char *filename, zone_dochandle_t handle) xmlFreeValidCtxt(cvp); if (valid == 0) return (Z_INVALID_DOCUMENT); + /* delete any comments such as inherited Sun copyright / ident str */ - top = handle->zone_dh_doc; - for (child = top->xmlChildrenNode; child != NULL; child = next) { - next = child->next; - if (child->name == NULL) - continue; - if (xmlStrcmp(child->name, DTD_ELEM_COMMENT) == 0) { - next = child->next; - xmlUnlinkNode(child); - xmlFreeNode(child); - } - } + stripcomments(handle); return (Z_OK); } @@ -323,6 +392,8 @@ zonecfg_get_handle(char *zonename, zone_dochandle_t handle) char path[MAXPATHLEN]; config_file_path(zonename, path); + handle->zone_dh_newzone = B_FALSE; + return (zonecfg_get_handle_impl(zonename, path, handle)); } @@ -332,10 +403,84 @@ zonecfg_get_snapshot_handle(char *zonename, zone_dochandle_t handle) char path[MAXPATHLEN]; snap_file_path(zonename, path); + handle->zone_dh_newzone = B_FALSE; return (zonecfg_get_handle_impl(zonename, path, handle)); } int +zonecfg_get_template_handle(char *template, char *zonename, + zone_dochandle_t handle) +{ + char path[MAXPATHLEN]; + int err; + + config_file_path(template, path); + + if ((err = zonecfg_get_handle_impl(template, path, handle)) != Z_OK) + return (err); + handle->zone_dh_newzone = B_TRUE; + return (setrootattr(handle, DTD_ATTR_NAME, zonename)); +} + +static boolean_t +is_renaming(zone_dochandle_t handle) +{ + if (handle->zone_dh_newzone) + return (B_FALSE); + if (strlen(handle->zone_dh_delete_name) > 0) + return (B_TRUE); + return (B_FALSE); +} + +static boolean_t +is_new(zone_dochandle_t handle) +{ + return (handle->zone_dh_newzone || handle->zone_dh_snapshot); +} + +static boolean_t +is_snapshot(zone_dochandle_t handle) +{ + return (handle->zone_dh_snapshot); +} + +/* + * It would be great to be able to use libc's ctype(3c) macros, but we + * can't, as they are locale sensitive, and it would break our limited thread + * safety if this routine had to change the app locale on the fly. + */ +int +zonecfg_validate_zonename(char *zone) +{ + int i; + + if (strcmp(zone, GLOBAL_ZONENAME) == 0) + return (Z_BOGUS_ZONE_NAME); + + if (strlen(zone) >= ZONENAME_MAX) + return (Z_BOGUS_ZONE_NAME); + + if (!((zone[0] >= 'a' && zone[0] <= 'z') || + (zone[0] >= 'A' && zone[0] <= 'Z') || + (zone[0] >= '0' && zone[0] <= '9'))) + return (Z_BOGUS_ZONE_NAME); + + for (i = 1; zone[i] != '\0'; i++) { + if (!((zone[i] >= 'a' && zone[i] <= 'z') || + (zone[i] >= 'A' && zone[i] <= 'Z') || + (zone[i] >= '0' && zone[i] <= '9') || + (zone[i] == '-') || (zone[i] == '_') || (zone[i] == '.'))) + return (Z_BOGUS_ZONE_NAME); + } + + return (Z_OK); +} + +/* + * Changing the zone name requires us to track both the old and new + * name of the zone until commit time. + */ +int zonecfg_get_name(zone_dochandle_t handle, char *name, size_t namesize) { return (getrootattr(handle, DTD_ATTR_NAME, name, namesize)); @@ -344,7 +489,85 @@ zonecfg_get_name(zone_dochandle_t handle, char *name, size_t namesize) int zonecfg_set_name(zone_dochandle_t handle, char *name) { - return (setrootattr(handle, DTD_ATTR_NAME, name)); + zone_state_t state; + char curname[ZONENAME_MAX], old_delname[ZONENAME_MAX]; + int err; + + if ((err = getrootattr(handle, DTD_ATTR_NAME, curname, + sizeof (curname))) != Z_OK) + return (err); + + if (strcmp(name, curname) == 0) + return (Z_OK); + + /* + * Switching zone names to one beginning with SUNW is not permitted. + */ + if (strncmp(name, "SUNW", 4) == 0) + return (Z_BOGUS_ZONE_NAME); + + if ((err = zonecfg_validate_zonename(name)) != Z_OK) + return (err); + + /* + * Setting the name back to the original name (effectively a revert of + * the name) is fine. But if we carry on, we'll falsely identify the + * name as "in use," so special case here. + */ + if (strcmp(name, handle->zone_dh_delete_name) == 0) { + err = setrootattr(handle, DTD_ATTR_NAME, name); + handle->zone_dh_delete_name[0] = '\0'; + return (err); + } + + /* Check to see if new name chosen is already in use */ + if (zone_get_state(name, &state) != Z_NO_ZONE) + return (Z_NAME_IN_USE); + + /* + * If this isn't already "new" or in a renaming transition, then + * we're initiating a rename here; so stash the "delete name" + * (i.e. the name of the zone we'll be removing) for the rename. + */ + (void) strlcpy(old_delname, handle->zone_dh_delete_name, + sizeof (old_delname)); + if (!is_new(handle) && !is_renaming(handle)) { + /* + * Name change is allowed only when the zone we're altering + * is not ready or running. + */ + err = zone_get_state(curname, &state); + if (err == Z_OK) { + if (state > ZONE_STATE_INSTALLED) + return (Z_BAD_ZONE_STATE); + } else if (err != Z_NO_ZONE) { + return (err); + } + + (void) strlcpy(handle->zone_dh_delete_name, curname, + sizeof (handle->zone_dh_delete_name)); + assert(is_renaming(handle)); + } else if (is_renaming(handle)) { + err = zone_get_state(handle->zone_dh_delete_name, &state); + if (err == Z_OK) { + if (state > ZONE_STATE_INSTALLED) + return (Z_BAD_ZONE_STATE); + } else if (err != Z_NO_ZONE) { + return (err); + } + } + + if ((err = setrootattr(handle, DTD_ATTR_NAME, name)) != Z_OK) { + /* + * Restore the deletename to whatever it was at the + * top of the routine, since we've had a failure. + */ + (void) strlcpy(handle->zone_dh_delete_name, old_delname, + sizeof (handle->zone_dh_delete_name)); + return (err); + } + + return (Z_OK); } int @@ -402,6 +625,11 @@ zonecfg_set_pool(zone_dochandle_t handle, char *pool) * in the <zonename>.xml file: the path to the zone. This is for performance, * since we need to walk all zonepath's in order to be able to detect conflicts * (see crosscheck_zonepaths() in the zoneadm command). + * + * An additional complexity is that when doing a rename, we'd like the entire + * index update operation (rename, and potential state changes) to be atomic. + * In general, the operation of this function should succeed or fail as + * a unit. */ int zonecfg_refresh_index_file(zone_dochandle_t handle) @@ -409,25 +637,94 @@ zonecfg_refresh_index_file(zone_dochandle_t handle) char name[ZONENAME_MAX], zonepath[MAXPATHLEN]; struct zoneent ze; int err; + int opcode; + char *zn; + + bzero(&ze, sizeof (ze)); + ze.zone_state = -1; /* Preserve existing state in index */ if ((err = zonecfg_get_name(handle, name, sizeof (name))) != Z_OK) return (err); + (void) strlcpy(ze.zone_name, name, sizeof (ze.zone_name)); + if ((err = zonecfg_get_zonepath(handle, zonepath, sizeof (zonepath))) != Z_OK) return (err); - (void) strlcpy(ze.zone_name, name, sizeof (ze.zone_name)); - ze.zone_state = -1; (void) strlcpy(ze.zone_path, zonepath, sizeof (ze.zone_path)); - return (putzoneent(&ze, PZE_MODIFY)); + + if (is_renaming(handle)) { + opcode = PZE_MODIFY; + (void) strlcpy(ze.zone_name, handle->zone_dh_delete_name, + sizeof (ze.zone_name)); + (void) strlcpy(ze.zone_newname, name, sizeof (ze.zone_newname)); + } else if (is_new(handle)) { + FILE *cookie; + /* + * Be tolerant of the zone already existing in the index file, + * since we might be forcibly overwriting an existing + * configuration with a new one (for example 'create -F' + * in zonecfg). + */ + opcode = PZE_ADD; + cookie = setzoneent(); + while ((zn = getzoneent(cookie)) != NULL) { + if (strcmp(zn, name) == 0) { + opcode = PZE_MODIFY; + free(zn); + break; + } + free(zn); + } + endzoneent(cookie); + ze.zone_state = ZONE_STATE_CONFIGURED; + } else { + opcode = PZE_MODIFY; + } + + if ((err = putzoneent(&ze, opcode)) != Z_OK) + return (err); + + return (Z_OK); } +/* + * The goal of this routine is to cause the index file update and the + * document save to happen as an atomic operation. We do the document + * first, saving a backup copy using a hard link; if that succeeds, we go + * on to the index. If that fails, we roll the document back into place. + * + * Strategy: + * + * New zone 'foo' configuration: + * Create tmpfile (zonecfg.xxxxxx) + * Write XML to tmpfile + * Rename tmpfile to xmlfile (zonecfg.xxxxxx -> foo.xml) + * Add entry to index file + * If it fails, delete foo.xml, leaving nothing behind. + * + * Save existing zone 'foo': + * Make backup of foo.xml -> .backup + * Create tmpfile (zonecfg.xxxxxx) + * Write XML to tmpfile + * Rename tmpfile to xmlfile (zonecfg.xxxxxx -> foo.xml) + * Modify index file as needed + * If it fails, recover from .backup -> foo.xml + * + * Rename 'foo' to 'bar': + * Create tmpfile (zonecfg.xxxxxx) + * Write XML to tmpfile + * Rename tmpfile to xmlfile (zonecfg.xxxxxx -> bar.xml) + * Add entry for 'bar' to index file, Remove entry for 'foo' (refresh) + * If it fails, delete bar.xml; foo.xml is left behind. + */ static int zonecfg_save_impl(zone_dochandle_t handle, char *filename) { char tmpfile[MAXPATHLEN]; - int tmpfd; + char bakdir[MAXPATHLEN], bakbase[MAXPATHLEN], bakfile[MAXPATHLEN]; + int tmpfd, err; xmlValidCtxt cvp = { NULL }; - xmlNodePtr comment; + boolean_t backup; (void) strlcpy(tmpfile, filename, sizeof (tmpfile)); (void) dirname(tmpfile); @@ -443,28 +740,82 @@ zonecfg_save_impl(zone_dochandle_t handle, char *filename) cvp.error = zonecfg_error_func; cvp.warning = zonecfg_error_func; - if ((comment = xmlNewComment((xmlChar *) "\n DO NOT EDIT THIS " - "FILE. Use zonecfg(1M) instead.\n")) == NULL) - goto err; - if (xmlAddPrevSibling(handle->zone_dh_top, comment) == 0) - goto err; - - if (xmlValidateDocument(&cvp, handle->zone_dh_doc) == 0) - goto err; + /* + * We do a final validation of the document-- but the library has + * malfunctioned if it fails to validate, so it's an assert. + */ + assert(xmlValidateDocument(&cvp, handle->zone_dh_doc) != 0); if (xmlSaveFormatFile(tmpfile, handle->zone_dh_doc, 1) <= 0) goto err; + (void) chmod(tmpfile, 0644); + /* + * In the event we are doing a standard save, hard link a copy of the + * original file in .backup.<pid>.filename so we can restore it if + * something goes wrong. + */ + if (!is_new(handle) && !is_renaming(handle)) { + backup = B_TRUE; + + (void) strlcpy(bakdir, filename, sizeof (bakdir)); + (void) strlcpy(bakbase, filename, sizeof (bakbase)); + (void) snprintf(bakfile, sizeof (bakfile), "%s/.backup.%d.%s", + dirname(bakdir), getpid(), basename(bakbase)); + + if (link(filename, bakfile) == -1) { + err = errno; + (void) unlink(tmpfile); + if (errno == EACCES) + return (Z_ACCES); + return (Z_MISC_FS); + } + } + + /* + * Move the new document over top of the old. + * i.e.: zonecfg.XXXXXX -> myzone.xml + */ if (rename(tmpfile, filename) == -1) { + err = errno; (void) unlink(tmpfile); - if (errno == EACCES) + if (backup) + (void) unlink(bakfile); + if (err == EACCES) return (Z_ACCES); return (Z_MISC_FS); } - /* now update the cached copy of the zone path in the index file */ - return (zonecfg_refresh_index_file(handle)); + /* + * If this is a snapshot, we're done-- don't add an index entry. + */ + if (is_snapshot(handle)) + return (Z_OK); + + /* now update the index file to reflect whatever we just did */ + if ((err = zonecfg_refresh_index_file(handle)) != Z_OK) { + if (backup) { + /* + * Try to restore from our backup. + */ + (void) unlink(filename); + (void) rename(bakfile, filename); + } else { + /* + * Either the zone is new, in which case we can delete + * new.xml, or we're doing a rename, so ditto. + */ + assert(is_new(handle) || is_renaming(handle)); + (void) unlink(filename); + } + return (Z_UPDATING_INDEX); + } + + if (backup) + (void) unlink(bakfile); + + return (Z_OK); err: (void) unlink(tmpfile); @@ -474,14 +825,43 @@ err: int zonecfg_save(zone_dochandle_t handle) { - char zname[MAXPATHLEN], path[MAXPATHLEN]; - int err; + char zname[ZONENAME_MAX], path[MAXPATHLEN]; + char delpath[MAXPATHLEN]; + int err = Z_SAVING_FILE; + + if (zonecfg_check_handle(handle) != Z_OK) + return (Z_BAD_HANDLE); - if ((err = zonecfg_get_name(handle, zname, sizeof (zname))) != Z_OK) { + /* + * We don't support saving snapshots at this time. + */ + if (handle->zone_dh_snapshot) + return (Z_INVAL); + + if ((err = zonecfg_get_name(handle, zname, sizeof (zname))) != Z_OK) return (err); - } + config_file_path(zname, path); - return (zonecfg_save_impl(handle, path)); + + addcomment(handle, "\n DO NOT EDIT THIS " + "FILE. Use zonecfg(1M) instead.\n"); + + err = zonecfg_save_impl(handle, path); + + stripcomments(handle); + + if (err != Z_OK) + return (err); + + handle->zone_dh_newzone = B_FALSE; + + if (is_renaming(handle)) { + config_file_path(handle->zone_dh_delete_name, delpath); + (void) unlink(delpath); + handle->zone_dh_delete_name[0] = '\0'; + } + + return (Z_OK); } /* @@ -519,6 +899,9 @@ zonecfg_create_snapshot(char *zonename) return (Z_NOMEM); } + handle->zone_dh_newzone = B_TRUE; + handle->zone_dh_snapshot = B_TRUE; + if ((error = zonecfg_get_handle(zonename, handle)) != Z_OK) goto out; if ((error = operation_prep(handle)) != Z_OK) @@ -548,8 +931,14 @@ zonecfg_create_snapshot(char *zonename) } snap_file_path(zonename, path); + + addcomment(handle, "\n DO NOT EDIT THIS FILE. " + "It is a snapshot of running zone state.\n"); + error = zonecfg_save_impl(handle, path); + stripcomments(handle); + out: zonecfg_fini_handle(handle); return (error); @@ -2142,13 +2531,13 @@ zonecfg_strerror(int errnum) return (dgettext(TEXT_DOMAIN, "No such property type")); case Z_NO_PROPERTY_ID: return (dgettext(TEXT_DOMAIN, "No such property with that id")); - case Z_RESOURCE_EXISTS: + case Z_BAD_ZONE_STATE: return (dgettext(TEXT_DOMAIN, - "Resource already exists with that id")); + "Zone state is invalid for the requested operation")); case Z_INVALID_DOCUMENT: return (dgettext(TEXT_DOMAIN, "Invalid document")); - case Z_ID_IN_USE: - return (dgettext(TEXT_DOMAIN, "Zone ID in use")); + case Z_NAME_IN_USE: + return (dgettext(TEXT_DOMAIN, "Zone name already in use")); case Z_NO_SUCH_ID: return (dgettext(TEXT_DOMAIN, "No such zone ID")); case Z_UPDATING_INDEX: @@ -2578,26 +2967,6 @@ zonecfg_get_privset(priv_set_t *privs) } int -zonecfg_add_index(char *zone, char *path) -{ - struct zoneent ze; - - (void) strlcpy(ze.zone_name, zone, sizeof (ze.zone_name)); - ze.zone_state = ZONE_STATE_CONFIGURED; - (void) strlcpy(ze.zone_path, path, sizeof (ze.zone_path)); - return (putzoneent(&ze, PZE_ADD)); -} - -int -zonecfg_delete_index(char *zone) -{ - struct zoneent ze; - - (void) strlcpy(ze.zone_name, zone, sizeof (ze.zone_name)); - return (putzoneent(&ze, PZE_REMOVE)); -} - -int zone_get_zonepath(char *zone_name, char *zonepath, size_t rp_sz) { zone_dochandle_t handle; @@ -2733,6 +3102,7 @@ zone_set_state(char *zone, zone_state_t state) state != ZONE_STATE_INCOMPLETE) return (Z_INVAL); + bzero(&ze, sizeof (ze)); (void) strlcpy(ze.zone_name, zone, sizeof (ze.zone_name)); ze.zone_state = state; (void) strlcpy(ze.zone_path, "", sizeof (ze.zone_path)); |