/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #pragma ident "%Z%%M% %I% %E% SMI" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define DTD_ELEM_BOOT ((const xmlChar *) "boot") #define DTD_ELEM_BRAND ((const xmlChar *) "brand") #define DTD_ELEM_COMMENT ((const xmlChar *) "comment") #define DTD_ELEM_DEVICE ((const xmlChar *) "device") #define DTD_ELEM_GLOBAL_MOUNT ((const xmlChar *) "global_mount") #define DTD_ELEM_HALT ((const xmlChar *) "halt") #define DTD_ELEM_INITNAME ((const xmlChar *) "initname") #define DTD_ELEM_INSTALL ((const xmlChar *) "install") #define DTD_ELEM_INSTALLOPTS ((const xmlChar *) "installopts") #define DTD_ELEM_LOGIN_CMD ((const xmlChar *) "login_cmd") #define DTD_ELEM_MODNAME ((const xmlChar *) "modname") #define DTD_ELEM_MOUNT ((const xmlChar *) "mount") #define DTD_ELEM_POSTCLONE ((const xmlChar *) "postclone") #define DTD_ELEM_PRIVILEGE ((const xmlChar *) "privilege") #define DTD_ELEM_SYMLINK ((const xmlChar *) "symlink") #define DTD_ELEM_VERIFY_CFG ((const xmlChar *) "verify_cfg") #define DTD_ELEM_VERIFY_ADM ((const xmlChar *) "verify_adm") #define DTD_ATTR_ALLOWEXCL ((const xmlChar *) "allow-exclusive-ip") #define DTD_ATTR_ARCH ((const xmlChar *) "arch") #define DTD_ATTR_DIRECTORY ((const xmlChar *) "directory") #define DTD_ATTR_IPTYPE ((const xmlChar *) "ip-type") #define DTD_ATTR_MATCH ((const xmlChar *) "match") #define DTD_ATTR_MODE ((const xmlChar *) "mode") #define DTD_ATTR_NAME ((const xmlChar *) "name") #define DTD_ATTR_OPT ((const xmlChar *) "opt") #define DTD_ATTR_PATH ((const xmlChar *) "path") #define DTD_ATTR_SET ((const xmlChar *) "set") #define DTD_ATTR_SOURCE ((const xmlChar *) "source") #define DTD_ATTR_SPECIAL ((const xmlChar *) "special") #define DTD_ATTR_TARGET ((const xmlChar *) "target") #define DTD_ATTR_TYPE ((const xmlChar *) "type") #define DTD_ENTITY_TRUE "true" static volatile boolean_t libbrand_initialized = B_FALSE; static char i_curr_arch[MAXNAMELEN]; static char i_curr_zone[ZONENAME_MAX]; /*ARGSUSED*/ static void brand_error_func(void *ctx, const char *msg, ...) { /* * Ignore error messages from libxml */ } static boolean_t libbrand_initialize() { static mutex_t initialize_lock = DEFAULTMUTEX; (void) mutex_lock(&initialize_lock); if (libbrand_initialized) { (void) mutex_unlock(&initialize_lock); return (B_TRUE); } if (sysinfo(SI_ARCHITECTURE, i_curr_arch, sizeof (i_curr_arch)) < 0) { (void) mutex_unlock(&initialize_lock); return (B_FALSE); } if (getzonenamebyid(getzoneid(), i_curr_zone, sizeof (i_curr_zone)) < 0) { (void) mutex_unlock(&initialize_lock); return (B_FALSE); } /* * Note that here we're initializing per-process libxml2 * state. By doing so we're implicitly assuming that * no other code in this process is also trying to * use libxml2. But in most case we know this not to * be true since we're almost always used in conjunction * with libzonecfg, which also uses libxml2. Lucky for * us, libzonecfg initializes libxml2 to essentially * the same defaults as we're using below. */ xmlLineNumbersDefault(1); xmlLoadExtDtdDefaultValue |= XML_DETECT_IDS; xmlDoValidityCheckingDefaultValue = 1; (void) xmlKeepBlanksDefault(0); xmlGetWarningsDefaultValue = 0; xmlSetGenericErrorFunc(NULL, brand_error_func); libbrand_initialized = B_TRUE; (void) mutex_unlock(&initialize_lock); return (B_TRUE); } static const char * get_curr_arch(void) { if (!libbrand_initialize()) return (NULL); return (i_curr_arch); } static const char * get_curr_zone(void) { if (!libbrand_initialize()) return (NULL); return (i_curr_zone); } /* * Internal function to open an XML file * * Returns the XML doc pointer, or NULL on failure. It will validate the * document, as well as removing any comments from the document structure. */ static xmlDocPtr open_xml_file(const char *file) { xmlDocPtr doc; xmlValidCtxtPtr cvp; int valid; if (!libbrand_initialize()) return (NULL); /* * Parse the file */ if ((doc = xmlParseFile(file)) == NULL) return (NULL); /* * Validate the file */ if ((cvp = xmlNewValidCtxt()) == NULL) { xmlFreeDoc(doc); return (NULL); } cvp->error = brand_error_func; cvp->warning = brand_error_func; valid = xmlValidateDocument(cvp, doc); xmlFreeValidCtxt(cvp); if (valid == 0) { xmlFreeDoc(doc); return (NULL); } return (doc); } /* * Open a handle to the named brand. * * Returns a handle to the named brand, which is used for all subsequent brand * interaction, or NULL if unable to open or initialize the brand. */ brand_handle_t brand_open(const char *name) { struct brand_handle *bhp; char path[MAXPATHLEN]; xmlNodePtr node; xmlChar *property; struct stat statbuf; /* * Make sure brand name isn't too long */ if (strlen(name) >= MAXNAMELEN) return (NULL); /* * Check that the brand exists */ (void) snprintf(path, sizeof (path), "%s/%s", BRAND_DIR, name); if (stat(path, &statbuf) != 0) return (NULL); /* * Allocate brand handle */ if ((bhp = malloc(sizeof (struct brand_handle))) == NULL) return (NULL); bzero(bhp, sizeof (struct brand_handle)); (void) strcpy(bhp->bh_name, name); /* * Open the configuration file */ (void) snprintf(path, sizeof (path), "%s/%s/%s", BRAND_DIR, name, BRAND_CONFIG); if ((bhp->bh_config = open_xml_file(path)) == NULL) { brand_close((brand_handle_t)bhp); return (NULL); } /* * Verify that the name of the brand matches the directory in which it * is installed. */ if ((node = xmlDocGetRootElement(bhp->bh_config)) == NULL) { brand_close((brand_handle_t)bhp); return (NULL); } if (xmlStrcmp(node->name, DTD_ELEM_BRAND) != 0) { brand_close((brand_handle_t)bhp); return (NULL); } if ((property = xmlGetProp(node, DTD_ATTR_NAME)) == NULL) { brand_close((brand_handle_t)bhp); return (NULL); } if (strcmp((char *)property, name) != 0) { xmlFree(property); brand_close((brand_handle_t)bhp); return (NULL); } xmlFree(property); /* * Open handle to platform configuration file. */ (void) snprintf(path, sizeof (path), "%s/%s/%s", BRAND_DIR, name, BRAND_PLATFORM); if ((bhp->bh_platform = open_xml_file(path)) == NULL) { brand_close((brand_handle_t)bhp); return (NULL); } return ((brand_handle_t)bhp); } /* * Closes the given brand handle */ void brand_close(brand_handle_t bh) { struct brand_handle *bhp = (struct brand_handle *)bh; if (bhp->bh_platform != NULL) xmlFreeDoc(bhp->bh_platform); if (bhp->bh_config != NULL) xmlFreeDoc(bhp->bh_config); free(bhp); } static int i_substitute_tokens(const char *sbuf, char *dbuf, int dbuf_size, const char *zonename, const char *zoneroot, const char *username, const char *curr_zone, int argc, char **argv) { int dst, src, i; assert(argc >= 0); assert((argc == 0) || (argv != NULL)); /* * Walk through the characters, substituting values as needed. */ dbuf[0] = '\0'; dst = 0; for (src = 0; src < strlen((char *)sbuf) && dst < dbuf_size; src++) { if (sbuf[src] != '%') { dbuf[dst++] = sbuf[src]; continue; } switch (sbuf[++src]) { case '%': dst += strlcpy(dbuf + dst, "%", dbuf_size - dst); break; case 'R': if (zoneroot == NULL) break; dst += strlcpy(dbuf + dst, zoneroot, dbuf_size - dst); break; case 'u': if (username == NULL) break; dst += strlcpy(dbuf + dst, username, dbuf_size - dst); break; case 'Z': if (curr_zone == NULL) break; /* name of the zone we're running in */ dst += strlcpy(dbuf + dst, curr_zone, dbuf_size - dst); break; case 'z': /* name of the zone we're operating on */ if (zonename == NULL) break; dst += strlcpy(dbuf + dst, zonename, dbuf_size - dst); break; case '*': if (argv == NULL) break; for (i = 0; i < argc; i++) dst += snprintf(dbuf + dst, dbuf_size - dst, " \"%s\"", argv[i]); break; } } if (dst >= dbuf_size) return (-1); dbuf[dst] = '\0'; return (0); } /* * Retrieve the given tag from the brand. * Perform the following substitutions as necessary: * * %% % * %u Username * %z Name of target zone * %Z Name of current zone * %R Root of zone * %* Additional arguments (argc, argv) * * Returns 0 on success, -1 on failure. */ static int brand_get_value(struct brand_handle *bhp, const char *zonename, const char *zoneroot, const char *username, const char *curr_zone, char *buf, size_t len, int argc, char **argv, const xmlChar *tagname, boolean_t substitute, boolean_t optional) { xmlNodePtr node; xmlChar *content; int err = 0; /* * Retrieve the specified value from the XML doc */ if ((node = xmlDocGetRootElement(bhp->bh_config)) == NULL) return (-1); if (xmlStrcmp(node->name, DTD_ELEM_BRAND) != 0) return (-1); for (node = node->xmlChildrenNode; node != NULL; node = node->next) { if (xmlStrcmp(node->name, tagname) == 0) break; } if (node == NULL) return (-1); if ((content = xmlNodeGetContent(node)) == NULL) return (-1); if (strlen((char *)content) == 0) { /* * If the entry in the config file is empty, check to see * whether this is an optional field. If so, we return the * empty buffer. If not, we return an error. */ if (optional) { buf[0] = '\0'; } else { err = -1; } } else { /* Substitute token values as needed. */ if (substitute) { if (i_substitute_tokens((char *)content, buf, len, zonename, zoneroot, username, curr_zone, argc, argv) != 0) err = -1; } else { if (strlcpy(buf, (char *)content, len) >= len) err = -1; } } xmlFree(content); return (err); } int brand_get_boot(brand_handle_t bh, const char *zonename, const char *zoneroot, char *buf, size_t len, int argc, char **argv) { struct brand_handle *bhp = (struct brand_handle *)bh; return (brand_get_value(bhp, zonename, zoneroot, NULL, NULL, buf, len, argc, argv, DTD_ELEM_BOOT, B_TRUE, B_TRUE)); } int brand_get_brandname(brand_handle_t bh, char *buf, size_t len) { struct brand_handle *bhp = (struct brand_handle *)bh; if (len <= strlen(bhp->bh_name)) return (-1); (void) strcpy(buf, bhp->bh_name); return (0); } int brand_get_halt(brand_handle_t bh, const char *zonename, const char *zoneroot, char *buf, size_t len, int argc, char **argv) { struct brand_handle *bhp = (struct brand_handle *)bh; return (brand_get_value(bhp, zonename, zoneroot, NULL, NULL, buf, len, argc, argv, DTD_ELEM_HALT, B_TRUE, B_TRUE)); } int brand_get_initname(brand_handle_t bh, char *buf, size_t len) { struct brand_handle *bhp = (struct brand_handle *)bh; return (brand_get_value(bhp, NULL, NULL, NULL, NULL, buf, len, 0, NULL, DTD_ELEM_INITNAME, B_FALSE, B_FALSE)); } int brand_get_login_cmd(brand_handle_t bh, const char *username, char *buf, size_t len) { struct brand_handle *bhp = (struct brand_handle *)bh; const char *curr_zone = get_curr_zone(); return (brand_get_value(bhp, NULL, NULL, username, curr_zone, buf, len, 0, NULL, DTD_ELEM_LOGIN_CMD, B_TRUE, B_FALSE)); } int brand_get_install(brand_handle_t bh, const char *zonename, const char *zoneroot, char *buf, size_t len, int argc, char **argv) { struct brand_handle *bhp = (struct brand_handle *)bh; return (brand_get_value(bhp, zonename, zoneroot, NULL, NULL, buf, len, argc, argv, DTD_ELEM_INSTALL, B_TRUE, B_FALSE)); } int brand_get_installopts(brand_handle_t bh, char *buf, size_t len) { struct brand_handle *bhp = (struct brand_handle *)bh; return (brand_get_value(bhp, NULL, NULL, NULL, NULL, buf, len, 0, NULL, DTD_ELEM_INSTALLOPTS, B_FALSE, B_TRUE)); } int brand_get_modname(brand_handle_t bh, char *buf, size_t len) { struct brand_handle *bhp = (struct brand_handle *)bh; return (brand_get_value(bhp, NULL, NULL, NULL, NULL, buf, len, 0, NULL, DTD_ELEM_MODNAME, B_FALSE, B_TRUE)); } int brand_get_postclone(brand_handle_t bh, const char *zonename, const char *zoneroot, char *buf, size_t len, int argc, char **argv) { struct brand_handle *bhp = (struct brand_handle *)bh; return (brand_get_value(bhp, zonename, zoneroot, NULL, NULL, buf, len, argc, argv, DTD_ELEM_POSTCLONE, B_TRUE, B_TRUE)); } int brand_get_verify_cfg(brand_handle_t bh, char *buf, size_t len) { struct brand_handle *bhp = (struct brand_handle *)bh; return (brand_get_value(bhp, NULL, NULL, NULL, NULL, buf, len, 0, NULL, DTD_ELEM_VERIFY_CFG, B_FALSE, B_TRUE)); } int brand_get_verify_adm(brand_handle_t bh, const char *zonename, const char *zoneroot, char *buf, size_t len, int argc, char **argv) { struct brand_handle *bhp = (struct brand_handle *)bh; return (brand_get_value(bhp, zonename, zoneroot, NULL, NULL, buf, len, argc, argv, DTD_ELEM_VERIFY_ADM, B_TRUE, B_TRUE)); } int brand_is_native(brand_handle_t bh) { struct brand_handle *bhp = (struct brand_handle *)bh; return ((strcmp(bhp->bh_name, NATIVE_BRAND_NAME) == 0) ? 1 : 0); } boolean_t brand_allow_exclusive_ip(brand_handle_t bh) { struct brand_handle *bhp = (struct brand_handle *)bh; xmlNodePtr node; xmlChar *allow_excl; boolean_t ret; assert(bhp != NULL); if ((node = xmlDocGetRootElement(bhp->bh_platform)) == NULL) return (B_FALSE); allow_excl = xmlGetProp(node, DTD_ATTR_ALLOWEXCL); if (allow_excl == NULL) return (B_FALSE); /* Note: only return B_TRUE if it's "true" */ if (strcmp((char *)allow_excl, DTD_ENTITY_TRUE) == 0) ret = B_TRUE; else ret = B_FALSE; xmlFree(allow_excl); return (ret); } /* * Iterate over brand privileges * * Walks the brand config, searching for elements, calling the * specified callback for each. Returns 0 on success, or -1 on failure. */ int brand_config_iter_privilege(brand_handle_t bh, int (*func)(void *, priv_iter_t *), void *data) { struct brand_handle *bhp = (struct brand_handle *)bh; xmlNodePtr node; xmlChar *name, *set, *iptype; priv_iter_t priv_iter; int ret; if ((node = xmlDocGetRootElement(bhp->bh_config)) == NULL) return (-1); for (node = node->xmlChildrenNode; node != NULL; node = node->next) { if (xmlStrcmp(node->name, DTD_ELEM_PRIVILEGE) != 0) continue; name = xmlGetProp(node, DTD_ATTR_NAME); set = xmlGetProp(node, DTD_ATTR_SET); iptype = xmlGetProp(node, DTD_ATTR_IPTYPE); if (name == NULL || set == NULL || iptype == NULL) { if (name != NULL) xmlFree(name); if (set != NULL) xmlFree(set); if (iptype != NULL) xmlFree(iptype); return (-1); } priv_iter.pi_name = (char *)name; priv_iter.pi_set = (char *)set; priv_iter.pi_iptype = (char *)iptype; ret = func(data, &priv_iter); xmlFree(name); xmlFree(set); xmlFree(iptype); if (ret != 0) return (-1); } return (0); } static int i_brand_platform_iter_mounts(struct brand_handle *bhp, const char *zoneroot, int (*func)(void *, const char *, const char *, const char *, const char *), void *data, const xmlChar *mount_type) { xmlNodePtr node; xmlChar *special, *dir, *type, *opt; char special_exp[MAXPATHLEN]; char opt_exp[MAXPATHLEN]; int ret; if ((node = xmlDocGetRootElement(bhp->bh_platform)) == NULL) return (-1); for (node = node->xmlChildrenNode; node != NULL; node = node->next) { if (xmlStrcmp(node->name, mount_type) != 0) continue; special = xmlGetProp(node, DTD_ATTR_SPECIAL); dir = xmlGetProp(node, DTD_ATTR_DIRECTORY); type = xmlGetProp(node, DTD_ATTR_TYPE); opt = xmlGetProp(node, DTD_ATTR_OPT); if ((special == NULL) || (dir == NULL) || (type == NULL) || (opt == NULL)) { ret = -1; goto next; } /* Substitute token values as needed. */ if ((ret = i_substitute_tokens((char *)special, special_exp, sizeof (special_exp), NULL, zoneroot, NULL, NULL, 0, NULL)) != 0) goto next; /* opt might not be defined */ if (strlen((const char *)opt) == 0) { xmlFree(opt); opt = NULL; } else { if ((ret = i_substitute_tokens((char *)opt, opt_exp, sizeof (opt_exp), NULL, zoneroot, NULL, NULL, 0, NULL)) != 0) goto next; } ret = func(data, (char *)special_exp, (char *)dir, (char *)type, ((opt != NULL) ? opt_exp : NULL)); next: if (special != NULL) xmlFree(special); if (dir != NULL) xmlFree(dir); if (type != NULL) xmlFree(type); if (opt != NULL) xmlFree(opt); if (ret != 0) return (-1); } return (0); } /* * Iterate over global platform filesystems * * Walks the platform, searching for elements, calling the * specified callback for each. Returns 0 on success, or -1 on failure. * * Perform the following substitutions as necessary: * * %R Root of zone */ int brand_platform_iter_gmounts(brand_handle_t bh, const char *zoneroot, int (*func)(void *, const char *, const char *, const char *, const char *), void *data) { struct brand_handle *bhp = (struct brand_handle *)bh; return (i_brand_platform_iter_mounts(bhp, zoneroot, func, data, DTD_ELEM_GLOBAL_MOUNT)); } /* * Iterate over non-global zone platform filesystems * * Walks the platform, searching for elements, calling the * specified callback for each. Returns 0 on success, or -1 on failure. */ int brand_platform_iter_mounts(brand_handle_t bh, int (*func)(void *, const char *, const char *, const char *, const char *), void *data) { struct brand_handle *bhp = (struct brand_handle *)bh; return (i_brand_platform_iter_mounts(bhp, NULL, func, data, DTD_ELEM_MOUNT)); } /* * Iterate over platform symlinks * * Walks the platform, searching for elements, calling the * specified callback for each. Returns 0 on success, or -1 on failure. */ int brand_platform_iter_link(brand_handle_t bh, int (*func)(void *, const char *, const char *), void *data) { struct brand_handle *bhp = (struct brand_handle *)bh; xmlNodePtr node; xmlChar *source, *target; int ret; if ((node = xmlDocGetRootElement(bhp->bh_platform)) == NULL) return (-1); for (node = node->xmlChildrenNode; node != NULL; node = node->next) { if (xmlStrcmp(node->name, DTD_ELEM_SYMLINK) != 0) continue; source = xmlGetProp(node, DTD_ATTR_SOURCE); target = xmlGetProp(node, DTD_ATTR_TARGET); if (source == NULL || target == NULL) { if (source != NULL) xmlFree(source); if (target != NULL) xmlFree(target); return (-1); } ret = func(data, (char *)source, (char *)target); xmlFree(source); xmlFree(target); if (ret != 0) return (-1); } return (0); } /* * Iterate over platform devices * * Walks the platform, searching for elements, calling the * specified callback for each. Returns 0 on success, or -1 on failure. */ int brand_platform_iter_devices(brand_handle_t bh, const char *zonename, int (*func)(void *, const char *, const char *), void *data, const char *curr_iptype) { struct brand_handle *bhp = (struct brand_handle *)bh; const char *curr_arch = get_curr_arch(); xmlNodePtr node; xmlChar *match, *name, *arch, *iptype; char match_exp[MAXPATHLEN]; boolean_t err = B_FALSE; int ret = 0; assert(bhp != NULL); assert(zonename != NULL); assert(func != NULL); assert(curr_iptype != NULL); if ((node = xmlDocGetRootElement(bhp->bh_platform)) == NULL) return (-1); for (node = node->xmlChildrenNode; node != NULL; node = node->next) { if (xmlStrcmp(node->name, DTD_ELEM_DEVICE) != 0) continue; match = xmlGetProp(node, DTD_ATTR_MATCH); name = xmlGetProp(node, DTD_ATTR_NAME); arch = xmlGetProp(node, DTD_ATTR_ARCH); iptype = xmlGetProp(node, DTD_ATTR_IPTYPE); if ((match == NULL) || (name == NULL) || (arch == NULL) || (iptype == NULL)) { err = B_TRUE; goto next; } /* check if the arch matches */ if ((strcmp((char *)arch, "all") != 0) && (strcmp((char *)arch, curr_arch) != 0)) goto next; /* check if the iptype matches */ if ((strcmp((char *)iptype, "all") != 0) && (strcmp((char *)iptype, curr_iptype) != 0)) goto next; /* Substitute token values as needed. */ if ((ret = i_substitute_tokens((char *)match, match_exp, sizeof (match_exp), zonename, NULL, NULL, NULL, 0, NULL)) != 0) { err = B_TRUE; goto next; } /* name might not be defined */ if (strlen((const char *)name) == 0) { xmlFree(name); name = NULL; } /* invoke the callback */ ret = func(data, (const char *)match_exp, (const char *)name); next: if (match != NULL) xmlFree(match); if (name != NULL) xmlFree(name); if (arch != NULL) xmlFree(arch); if (iptype != NULL) xmlFree(iptype); if (err) return (-1); if (ret != 0) return (-1); } return (0); }