summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJoshua M. Clulow <jmc@joyent.com>2015-06-02 22:54:11 +0000
committerJoshua M. Clulow <jmc@joyent.com>2015-06-08 20:46:20 +0000
commit3780e6e7444648fd996068beb45fc98096c1a525 (patch)
treef4fab5a33c4261bfb92711f1fd3be6aa55fd9276
parent747826ee744212d4556f07404b550534e0c023f7 (diff)
downloadillumos-joyent-3780e6e7444648fd996068beb45fc98096c1a525.tar.gz
OS-4361 libzonecfg should be aware of branded zone native root
Reviewed by: Robert Mustacchi <rm@joyent.com>
-rw-r--r--usr/src/lib/libzonecfg/Makefile.com17
-rw-r--r--usr/src/lib/libzonecfg/common/getzoneent.c59
-rw-r--r--usr/src/lib/libzonecfg/common/libzonecfg.c126
-rw-r--r--usr/src/lib/xml/os_dtd.c238
-rw-r--r--usr/src/lib/xml/os_dtd.h49
5 files changed, 405 insertions, 84 deletions
diff --git a/usr/src/lib/libzonecfg/Makefile.com b/usr/src/lib/libzonecfg/Makefile.com
index 0bee2fc908..6bec7141cb 100644
--- a/usr/src/lib/libzonecfg/Makefile.com
+++ b/usr/src/lib/libzonecfg/Makefile.com
@@ -20,11 +20,14 @@
#
#
# Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
+# Copyright 2015 Joyent, Inc.
#
LIBRARY= libzonecfg.a
VERS= .1
-OBJECTS= libzonecfg.o getzoneent.o scratchops.o
+LIB_OBJS= libzonecfg.o getzoneent.o scratchops.o
+XML_OBJS= os_dtd.o
+OBJECTS= $(LIB_OBJS) $(XML_OBJS)
include ../../Makefile.lib
@@ -35,15 +38,27 @@ LDLIBS += -lc -lsocket -lnsl -luuid -lnvpair -lsysevent -lsec -lbrand \
$(DYNLIB) := LDLIBS += -lxml2
SRCDIR = ../common
+
+XMLDIR = $(SRC)/lib/xml
+SRCS = \
+ $(LIB_OBJS:%.o=$(SRCDIR)/%.c) \
+ $(XML_OBJS:%.o=$(XMLDIR)/%.c) \
+
CPPFLAGS += -I$(ADJUNCT_PROTO)/usr/include/libxml2 -I$(SRCDIR) -D_REENTRANT
CERRWARN += -_gcc=-Wno-uninitialized
CERRWARN += -_gcc=-Wno-parentheses
$(LINTLIB) := SRCS= $(SRCDIR)/$(LINTSRC)
+CPPFLAGS += -I$(XMLDIR)
+
.KEEP_STATE:
all: $(LIBS)
lint: lintcheck
+pics/%.o: $(XMLDIR)/%.c
+ $(COMPILE.c) -o $@ $<
+ $(POST_PROCESS_O)
+
include ../../Makefile.targ
diff --git a/usr/src/lib/libzonecfg/common/getzoneent.c b/usr/src/lib/libzonecfg/common/getzoneent.c
index 76664fcc92..2113ecd44b 100644
--- a/usr/src/lib/libzonecfg/common/getzoneent.c
+++ b/usr/src/lib/libzonecfg/common/getzoneent.c
@@ -20,6 +20,7 @@
*/
/*
* Copyright (c) 2003, 2010, Oracle and/or its affiliates. All rights reserved.
+ * Copyright 2015 Joyent, Inc.
*/
@@ -170,10 +171,32 @@ getzoneent_private(FILE *cookie)
}
static boolean_t
-get_index_path(char *path)
+path_common(char *path, size_t path_size, const char *stem)
{
- return (snprintf(path, MAXPATHLEN, "%s%s", zonecfg_root,
- ZONE_INDEX_FILE) < MAXPATHLEN);
+ const char *native_root = zone_get_nroot();
+
+ if (native_root == NULL || zonecfg_in_alt_root()) {
+ /*
+ * Do not prepend the native system root (e.g. "/native") if an
+ * alternative configuration root has been selected.
+ */
+ native_root = "";
+ }
+
+ return (snprintf(path, path_size, "%s%s%s", native_root, zonecfg_root,
+ stem) < path_size);
+}
+
+static boolean_t
+get_index_path(char *path, size_t path_size)
+{
+ return (path_common(path, path_size, ZONE_INDEX_FILE));
+}
+
+static boolean_t
+get_temp_path(char *path, size_t path_size)
+{
+ return (path_common(path, path_size, _PATH_TMPFILE));
}
FILE *
@@ -181,7 +204,7 @@ setzoneent(void)
{
char path[MAXPATHLEN];
- if (!get_index_path(path)) {
+ if (!get_index_path(path, sizeof (path))) {
errno = EINVAL;
return (NULL);
}
@@ -202,8 +225,7 @@ lock_index_file(void)
struct flock lock;
char path[MAXPATHLEN];
- if (snprintf(path, sizeof (path), "%s%s", zonecfg_root,
- ZONE_INDEX_LOCK_DIR) >= sizeof (path))
+ if (!path_common(path, sizeof (path), ZONE_INDEX_LOCK_DIR))
return (-1);
if ((mkdir(path, S_IRWXU) == -1) && errno != EEXIST)
return (-1);
@@ -269,13 +291,14 @@ int
putzoneent(struct zoneent *ze, zoneent_op_t operation)
{
FILE *index_file, *tmp_file;
- char *tmp_file_name, buf[MAX_INDEX_LEN];
+ char buf[MAX_INDEX_LEN];
int tmp_file_desc, lock_fd, err;
boolean_t exist, need_quotes;
char *cp;
+ char tmp_path[MAXPATHLEN];
char path[MAXPATHLEN];
char uuidstr[UUID_PRINTABLE_STRING_LENGTH];
- size_t tlen, namelen;
+ size_t namelen;
const char *zone_name, *zone_state, *zone_path, *zone_uuid;
assert(ze != NULL);
@@ -299,20 +322,14 @@ putzoneent(struct zoneent *ze, zoneent_op_t operation)
if ((lock_fd = lock_index_file()) == -1)
return (Z_LOCKING_FILE);
- /* using sizeof gives us room for the terminating NUL byte as well */
- tlen = sizeof (_PATH_TMPFILE) + strlen(zonecfg_root);
- tmp_file_name = malloc(tlen);
- if (tmp_file_name == NULL) {
+ if (!get_temp_path(tmp_path, sizeof (tmp_path))) {
(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);
+ tmp_file_desc = mkstemp(tmp_path);
if (tmp_file_desc == -1) {
- (void) unlink(tmp_file_name);
- free(tmp_file_name);
+ (void) unlink(tmp_path);
(void) unlock_index_file(lock_fd);
return (Z_TEMP_FILE);
}
@@ -323,7 +340,7 @@ putzoneent(struct zoneent *ze, zoneent_op_t operation)
err = Z_MISC_FS;
goto error;
}
- if (!get_index_path(path)) {
+ if (!get_index_path(path, sizeof (path))) {
err = Z_MISC_FS;
goto error;
}
@@ -462,11 +479,10 @@ putzoneent(struct zoneent *ze, zoneent_op_t operation)
goto error;
}
tmp_file = NULL;
- if (rename(tmp_file_name, path) == -1) {
+ if (rename(tmp_path, 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);
@@ -476,8 +492,7 @@ error:
(void) fclose(index_file);
if (tmp_file != NULL)
(void) fclose(tmp_file);
- (void) unlink(tmp_file_name);
- free(tmp_file_name);
+ (void) unlink(tmp_path);
(void) unlock_index_file(lock_fd);
return (err);
}
diff --git a/usr/src/lib/libzonecfg/common/libzonecfg.c b/usr/src/lib/libzonecfg/common/libzonecfg.c
index 3162390931..6e368a164f 100644
--- a/usr/src/lib/libzonecfg/common/libzonecfg.c
+++ b/usr/src/lib/libzonecfg/common/libzonecfg.c
@@ -22,7 +22,7 @@
/*
* Copyright 2014 Gary Mills
* Copyright (c) 2003, 2010, Oracle and/or its affiliates. All rights reserved.
- * Copyright 2015, Joyent Inc. All rights reserved.
+ * Copyright 2015 Joyent Inc.
*/
#include <libsysevent.h>
@@ -59,6 +59,8 @@
#include <secdb.h>
#include <user_attr.h>
#include <prof_attr.h>
+#include <sys/debug.h>
+#include <os_dtd.h>
#include <arpa/inet.h>
#include <netdb.h>
@@ -279,17 +281,35 @@ zonecfg_in_alt_root(void)
*/
static boolean_t
-config_file_path(const char *zonename, char *answer)
+file_path_common(const char *zonename, const char *subdir, const char *stem,
+ char *answer, size_t answer_size)
{
- return (snprintf(answer, MAXPATHLEN, "%s%s/%s.xml", zonecfg_root,
- ZONE_CONFIG_ROOT, zonename) < MAXPATHLEN);
+ const char *native_root = zone_get_nroot();
+
+ if (native_root == NULL || zonecfg_in_alt_root()) {
+ /*
+ * Do not prepend the native system root (e.g. "/native") if an
+ * alternative configuration root has been selected.
+ */
+ native_root = "";
+ }
+
+ return (snprintf(answer, answer_size, "%s%s%s/%s.%s", native_root,
+ zonecfg_root, subdir, zonename, stem) < answer_size);
}
static boolean_t
-snap_file_path(const char *zonename, char *answer)
+config_file_path(const char *zonename, char *answer, size_t answer_size)
{
- return (snprintf(answer, MAXPATHLEN, "%s%s/%s.snapshot.xml",
- zonecfg_root, ZONE_SNAPSHOT_ROOT, zonename) < MAXPATHLEN);
+ return (file_path_common(zonename, ZONE_CONFIG_ROOT, "xml", answer,
+ answer_size));
+}
+
+static boolean_t
+snap_file_path(const char *zonename, char *answer, size_t answer_size)
+{
+ return (file_path_common(zonename, ZONE_SNAPSHOT_ROOT, "snapshot.xml",
+ answer, answer_size));
}
/*ARGSUSED*/
@@ -360,7 +380,7 @@ zonecfg_destroy(const char *zonename, boolean_t force)
int err, state_err;
zone_state_t state;
- if (!config_file_path(zonename, path))
+ if (!config_file_path(zonename, path, sizeof (path)))
return (Z_MISC_FS);
state_err = zone_get_state((char *)zonename, &state);
@@ -417,7 +437,7 @@ zonecfg_destroy_snapshot(const char *zonename)
{
char path[MAXPATHLEN];
- if (!snap_file_path(zonename, path))
+ if (!snap_file_path(zonename, path, sizeof (path)))
return (Z_MISC_FS);
return (zonecfg_destroy_impl(path));
}
@@ -584,9 +604,8 @@ static int
zonecfg_get_handle_impl(const char *zonename, const char *filename,
zone_dochandle_t handle)
{
- xmlValidCtxtPtr cvp;
struct stat statbuf;
- int valid;
+ boolean_t valid;
if (zonename == NULL)
return (Z_NO_ZONE);
@@ -597,14 +616,13 @@ zonecfg_get_handle_impl(const char *zonename, const char *filename,
return (Z_INVALID_DOCUMENT);
return (Z_NO_ZONE);
}
- if ((cvp = xmlNewValidCtxt()) == NULL)
+
+ if (os_dtd_validate(handle->zone_dh_doc, B_FALSE, &valid) != 0) {
return (Z_NOMEM);
- cvp->error = zonecfg_error_func;
- cvp->warning = zonecfg_error_func;
- valid = xmlValidateDocument(cvp, handle->zone_dh_doc);
- xmlFreeValidCtxt(cvp);
- if (valid == 0)
+ }
+ if (!valid) {
return (Z_INVALID_DOCUMENT);
+ }
/* delete any comments such as inherited Sun copyright / ident str */
stripcomments(handle);
@@ -616,7 +634,7 @@ zonecfg_get_handle(const char *zonename, zone_dochandle_t handle)
{
char path[MAXPATHLEN];
- if (!config_file_path(zonename, path))
+ if (!config_file_path(zonename, path, sizeof (path)))
return (Z_MISC_FS);
handle->zone_dh_newzone = B_FALSE;
@@ -660,7 +678,7 @@ zonecfg_get_snapshot_handle(const char *zonename, zone_dochandle_t handle)
{
char path[MAXPATHLEN];
- if (!snap_file_path(zonename, path))
+ if (!snap_file_path(zonename, path, sizeof (path)))
return (Z_MISC_FS);
handle->zone_dh_newzone = B_FALSE;
return (zonecfg_get_handle_impl(zonename, path, handle));
@@ -673,7 +691,7 @@ zonecfg_get_template_handle(const char *template, const char *zonename,
char path[MAXPATHLEN];
int err;
- if (!config_file_path(template, path))
+ if (!config_file_path(template, path, sizeof (path)))
return (Z_MISC_FS);
if ((err = zonecfg_get_handle_impl(template, path, handle)) != Z_OK)
@@ -707,21 +725,19 @@ int
zonecfg_attach_manifest(int fd, zone_dochandle_t local_handle,
zone_dochandle_t rem_handle)
{
- xmlValidCtxtPtr cvp;
- int valid;
+ boolean_t valid;
/* load the manifest into the handle for the remote system */
if ((rem_handle->zone_dh_doc = xmlReadFd(fd, NULL, NULL, 0)) == NULL) {
return (Z_INVALID_DOCUMENT);
}
- if ((cvp = xmlNewValidCtxt()) == NULL)
+
+ if (os_dtd_validate(rem_handle->zone_dh_doc, B_FALSE, &valid) != 0) {
return (Z_NOMEM);
- cvp->error = zonecfg_error_func;
- cvp->warning = zonecfg_error_func;
- valid = xmlValidateDocument(cvp, rem_handle->zone_dh_doc);
- xmlFreeValidCtxt(cvp);
- if (valid == 0)
+ }
+ if (!valid) {
return (Z_INVALID_DOCUMENT);
+ }
/* delete any comments such as inherited Sun copyright / ident str */
stripcomments(rem_handle);
@@ -742,14 +758,12 @@ zonecfg_attach_manifest(int fd, zone_dochandle_t local_handle,
* We need to re-run xmlValidateDocument on local_handle to properly
* update the in-core representation of the configuration.
*/
- if ((cvp = xmlNewValidCtxt()) == NULL)
+ if (os_dtd_validate(local_handle->zone_dh_doc, B_FALSE, &valid) != 0) {
return (Z_NOMEM);
- cvp->error = zonecfg_error_func;
- cvp->warning = zonecfg_error_func;
- valid = xmlValidateDocument(cvp, local_handle->zone_dh_doc);
- xmlFreeValidCtxt(cvp);
- if (valid == 0)
+ }
+ if (!valid) {
return (Z_INVALID_DOCUMENT);
+ }
strip_sw_inv(local_handle);
@@ -1203,9 +1217,9 @@ zonecfg_save_impl(zone_dochandle_t handle, char *filename)
{
char tmpfile[MAXPATHLEN];
char bakdir[MAXPATHLEN], bakbase[MAXPATHLEN], bakfile[MAXPATHLEN];
- int tmpfd, err, valid;
- xmlValidCtxt cvp = { NULL };
+ int tmpfd, err;
boolean_t backup;
+ boolean_t valid;
(void) strlcpy(tmpfile, filename, sizeof (tmpfile));
(void) dirname(tmpfile);
@@ -1218,16 +1232,13 @@ zonecfg_save_impl(zone_dochandle_t handle, char *filename)
}
(void) close(tmpfd);
- cvp.error = zonecfg_error_func;
- cvp.warning = zonecfg_error_func;
-
/*
* We do a final validation of the document. Since the library has
* malfunctioned if it fails to validate, we follow-up with an
* assert() that the doc is valid.
*/
- valid = xmlValidateDocument(&cvp, handle->zone_dh_doc);
- assert(valid != 0);
+ VERIFY0(os_dtd_validate(handle->zone_dh_doc, B_FALSE, &valid));
+ VERIFY(valid == B_TRUE);
if (xmlSaveFormatFile(tmpfile, handle->zone_dh_doc, 1) <= 0)
goto err;
@@ -1324,7 +1335,7 @@ zonecfg_save(zone_dochandle_t handle)
if ((err = zonecfg_get_name(handle, zname, sizeof (zname))) != Z_OK)
return (err);
- if (!config_file_path(zname, path))
+ if (!config_file_path(zname, path, sizeof (path)))
return (Z_MISC_FS);
addcomment(handle, "\n DO NOT EDIT THIS "
@@ -1345,8 +1356,10 @@ zonecfg_save(zone_dochandle_t handle)
handle->zone_dh_newzone = B_FALSE;
if (is_renaming(handle)) {
- if (config_file_path(handle->zone_dh_delete_name, delpath))
+ if (config_file_path(handle->zone_dh_delete_name, delpath,
+ sizeof (delpath))) {
(void) unlink(delpath);
+ }
handle->zone_dh_delete_name[0] = '\0';
}
@@ -1356,23 +1369,18 @@ zonecfg_save(zone_dochandle_t handle)
int
zonecfg_verify_save(zone_dochandle_t handle, char *filename)
{
- int valid;
-
- xmlValidCtxt cvp = { NULL };
+ boolean_t valid;
if (zonecfg_check_handle(handle) != Z_OK)
return (Z_BAD_HANDLE);
- cvp.error = zonecfg_error_func;
- cvp.warning = zonecfg_error_func;
-
/*
* We do a final validation of the document. Since the library has
* malfunctioned if it fails to validate, we follow-up with an
* assert() that the doc is valid.
*/
- valid = xmlValidateDocument(&cvp, handle->zone_dh_doc);
- assert(valid != 0);
+ VERIFY0(os_dtd_validate(handle->zone_dh_doc, B_FALSE, &valid));
+ VERIFY(valid == B_TRUE);
if (xmlSaveFormatFile(filename, handle->zone_dh_doc, 1) <= 0)
return (Z_SAVING_FILE);
@@ -1386,9 +1394,8 @@ zonecfg_detach_save(zone_dochandle_t handle, uint_t flags)
char zname[ZONENAME_MAX];
char path[MAXPATHLEN];
char migpath[MAXPATHLEN];
- xmlValidCtxt cvp = { NULL };
int err = Z_SAVING_FILE;
- int valid;
+ boolean_t valid;
if (zonecfg_check_handle(handle) != Z_OK)
return (Z_BAD_HANDLE);
@@ -1415,16 +1422,13 @@ zonecfg_detach_save(zone_dochandle_t handle, uint_t flags)
addcomment(handle, "\n DO NOT EDIT THIS FILE. "
"Use zonecfg(1M) and zoneadm(1M) attach.\n");
- cvp.error = zonecfg_error_func;
- cvp.warning = zonecfg_error_func;
-
/*
* We do a final validation of the document. Since the library has
* malfunctioned if it fails to validate, we follow-up with an
* assert() that the doc is valid.
*/
- valid = xmlValidateDocument(&cvp, handle->zone_dh_doc);
- assert(valid != 0);
+ VERIFY0(os_dtd_validate(handle->zone_dh_doc, B_FALSE, &valid));
+ VERIFY(valid == B_TRUE);
if (xmlSaveFormatFile(migpath, handle->zone_dh_doc, 1) <= 0)
return (Z_SAVING_FILE);
@@ -1497,7 +1501,7 @@ zonecfg_access(const char *zonename, int amode)
{
char path[MAXPATHLEN];
- if (!config_file_path(zonename, path))
+ if (!config_file_path(zonename, path, sizeof (path)))
return (Z_INVAL);
if (access(path, amode) == 0)
return (Z_OK);
@@ -1562,7 +1566,7 @@ zonecfg_create_snapshot(const char *zonename)
goto out;
}
- if (!snap_file_path(zonename, path)) {
+ if (!snap_file_path(zonename, path, sizeof (path))) {
error = Z_MISC_FS;
goto out;
}
@@ -8032,7 +8036,7 @@ zonecfg_update_userauths(zone_dochandle_t handle, char *zonename)
(void) fclose(uaf);
return (Z_MISC_FS);
}
- if (!config_file_path(zonename, config_file)) {
+ if (!config_file_path(zonename, config_file, sizeof (config_file))) {
(void) fclose(uaf);
return (Z_MISC_FS);
}
diff --git a/usr/src/lib/xml/os_dtd.c b/usr/src/lib/xml/os_dtd.c
new file mode 100644
index 0000000000..579e99ba3c
--- /dev/null
+++ b/usr/src/lib/xml/os_dtd.c
@@ -0,0 +1,238 @@
+/*
+ * This file and its contents are supplied under the terms of the
+ * Common Development and Distribution License ("CDDL"), version 1.0.
+ * You may only use this file in accordance with the terms of version
+ * 1.0 of the CDDL.
+ *
+ * A full copy of the text of the CDDL should have accompanied this
+ * source. A copy of the CDDL is also available via the Internet at
+ * http://www.illumos.org/license/CDDL.
+ */
+
+/*
+ * Copyright 2015 Joyent, Inc.
+ */
+
+/*
+ * Utility functions for working with XML documents that are validated against
+ * Document Type Definitions (DTD) shipped in the operating system.
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <strings.h>
+#include <errno.h>
+#include <zone.h>
+
+#include <libxml/parser.h>
+#include <libxml/xmlmemory.h>
+
+#include "os_dtd.h"
+
+struct os_dtd_path {
+ os_dtd_id_t odp_id;
+ const char *odp_name;
+ const char *odp_public_ident;
+ const char *odp_path;
+};
+
+static os_dtd_path_t os_dtd_paths[] = {
+ /*
+ * DTDs for Zones infrastructure:
+ */
+ { OS_DTD_ZONES_BRAND, "brand",
+ "-//Sun Microsystems Inc//DTD Brands//EN",
+ "/usr/share/lib/xml/dtd/brand.dtd.1" },
+ { OS_DTD_ZONES_ZONE, "zone",
+ "-//Sun Microsystems Inc//DTD Zones//EN",
+ "/usr/share/lib/xml/dtd/zonecfg.dtd.1" },
+ { OS_DTD_ZONES_PLATFORM, "platform",
+ "-//Sun Microsystems Inc//Zones Platform//EN",
+ "/usr/share/lib/xml/dtd/zone_platform.dtd.1" },
+
+ /*
+ * DTDs for smf(5):
+ */
+ { OS_DTD_SMF_SERVICE_BUNDLE, "service_bundle",
+ NULL,
+ "/usr/share/lib/xml/dtd/service_bundle.dtd.1" },
+
+ { OS_DTD_UNKNOWN, NULL, NULL, NULL }
+};
+
+/*
+ * Check this document to see if it references the public identifier of a
+ * well-known DTD that we ship with the operating system. If there is no DTD,
+ * or the public identifier is unknown to us, return OS_DTD_UNKNOWN.
+ */
+os_dtd_id_t
+os_dtd_identify(xmlDocPtr doc)
+{
+ xmlDtdPtr dp;
+ int i;
+
+ if ((dp = xmlGetIntSubset(doc)) == NULL) {
+ /*
+ * This document does not have an internal subset pointing
+ * to a DTD.
+ */
+ errno = EIO;
+ return (OS_DTD_UNKNOWN);
+ }
+
+ /*
+ * The use of a symbolic name via the public identifier is preferred.
+ * Check to see if the document refers to a public identifier for any
+ * well-known DTD:
+ */
+ for (i = 0; os_dtd_paths[i].odp_id != OS_DTD_UNKNOWN; i++) {
+ os_dtd_path_t *odp = &os_dtd_paths[i];
+ const xmlChar *pubid = (const xmlChar *)odp->odp_public_ident;
+
+ if (dp->ExternalID == NULL || odp->odp_public_ident == NULL) {
+ continue;
+ }
+
+ if (xmlStrEqual(pubid, dp->ExternalID)) {
+ return (odp->odp_id);
+ }
+ }
+
+ /*
+ * If a public identifier is not present, or does not match any known
+ * DTD, fall back to inspecting the system identifier.
+ */
+ for (i = 0; os_dtd_paths[i].odp_id != OS_DTD_UNKNOWN; i++) {
+ os_dtd_path_t *odp = &os_dtd_paths[i];
+ char uri[sizeof ("file://") + MAXPATHLEN];
+ const xmlChar *path = (const xmlChar *)odp->odp_path;
+
+ if (dp->SystemID == NULL || odp->odp_path == NULL) {
+ continue;
+ }
+
+ /*
+ * The system identifier may be a regular path.
+ */
+ if (xmlStrEqual(path, dp->SystemID)) {
+ return (odp->odp_id);
+ }
+
+ /*
+ * The system identifier may also be a "file://"
+ * URI referring to a path:
+ */
+ (void) snprintf(uri, sizeof (uri), "file://%s", odp->odp_path);
+ if (xmlStrEqual((const xmlChar *)uri, dp->SystemID)) {
+ return (odp->odp_id);
+ }
+ }
+
+ errno = ENOENT;
+ return (OS_DTD_UNKNOWN);
+}
+
+static os_dtd_path_t *
+os_dtd_lookup(os_dtd_id_t id)
+{
+ int i;
+
+ for (i = 0; os_dtd_paths[i].odp_id != OS_DTD_UNKNOWN; i++) {
+ os_dtd_path_t *odp = &os_dtd_paths[i];
+
+ if (odp->odp_id == id) {
+ return (odp);
+ }
+ }
+
+ errno = ENOENT;
+ return (NULL);
+}
+
+/*
+ * If this document references a DTD, remove that reference (the "internal
+ * subset"). Install a new internal subset reference to the well-known
+ * operating system DTD passed by the caller. The URI in this reference will
+ * respect the current native system prefix (e.g. "/native") if there is one,
+ * such as when running in a branded zone.
+ */
+int
+os_dtd_attach(xmlDocPtr doc, os_dtd_id_t id)
+{
+ xmlDtdPtr dp;
+ os_dtd_path_t *odp;
+ const char *zroot = zone_get_nroot();
+ char uri[sizeof ("file://") + MAXPATHLEN];
+
+ if ((odp = os_dtd_lookup(id)) == NULL) {
+ return (-1);
+ }
+
+ if ((dp = xmlGetIntSubset(doc)) != NULL) {
+ /*
+ * This document already has an internal subset. Remove it
+ * before attaching the new one.
+ */
+ xmlUnlinkNode((xmlNodePtr)dp);
+ xmlFreeNode((xmlNodePtr)dp);
+ }
+
+ /*
+ * The "system identifier" of this internal subset must refer to the
+ * path in the filesystem where the DTD file (the external subset) is
+ * stored. If we are running in a branded zone, that file may be at a
+ * different path (e.g. under "/native").
+ */
+ (void) snprintf(uri, sizeof (uri), "file://%s%s", zroot != NULL ?
+ zroot : "", odp->odp_path);
+
+ if (xmlCreateIntSubset(doc, (const xmlChar *)odp->odp_name,
+ (const xmlChar *)odp->odp_public_ident,
+ (const xmlChar *)uri) == NULL) {
+ errno = EIO;
+ return (-1);
+ }
+
+ return (0);
+}
+
+/*ARGSUSED*/
+static void
+os_dtd_print_nothing(void *ctx, const char *msg, ...)
+{
+}
+
+int
+os_dtd_validate(xmlDocPtr doc, boolean_t emit_messages, boolean_t *valid)
+{
+ int ret;
+ xmlValidCtxtPtr cvp;
+ os_dtd_id_t dtdid;
+
+ if ((dtdid = os_dtd_identify(doc)) != OS_DTD_UNKNOWN) {
+ /*
+ * This document refers to a well-known DTD shipped with
+ * the operating system. Ensure that it points to the
+ * correct local path for validation in the current context.
+ */
+ if (os_dtd_attach(doc, dtdid) != 0) {
+ return (-1);
+ }
+ }
+
+ if ((cvp = xmlNewValidCtxt()) == NULL) {
+ return (-1);
+ }
+
+ if (!emit_messages) {
+ cvp->error = os_dtd_print_nothing;
+ cvp->warning = os_dtd_print_nothing;
+ }
+
+ ret = xmlValidateDocument(cvp, doc);
+ xmlFreeValidCtxt(cvp);
+
+ *valid = (ret == 1 ? B_TRUE : B_FALSE);
+ return (0);
+}
diff --git a/usr/src/lib/xml/os_dtd.h b/usr/src/lib/xml/os_dtd.h
new file mode 100644
index 0000000000..c6fe24a293
--- /dev/null
+++ b/usr/src/lib/xml/os_dtd.h
@@ -0,0 +1,49 @@
+/*
+ * This file and its contents are supplied under the terms of the
+ * Common Development and Distribution License ("CDDL"), version 1.0.
+ * You may only use this file in accordance with the terms of version
+ * 1.0 of the CDDL.
+ *
+ * A full copy of the text of the CDDL should have accompanied this
+ * source. A copy of the CDDL is also available via the Internet at
+ * http://www.illumos.org/license/CDDL.
+ */
+
+/*
+ * Copyright 2015 Joyent, Inc.
+ */
+
+#ifndef _OS_DTD_H
+#define _OS_DTD_H
+
+/*
+ * Utility functions for working with XML documents that are validated against
+ * Document Type Definitions (DTD) shipped in the operating system.
+ */
+
+#include <libxml/parser.h>
+#include <libxml/xmlmemory.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef enum os_dtd_id {
+ OS_DTD_UNKNOWN = 0,
+ OS_DTD_ZONES_BRAND,
+ OS_DTD_ZONES_ZONE,
+ OS_DTD_ZONES_PLATFORM,
+ OS_DTD_SMF_SERVICE_BUNDLE
+} os_dtd_id_t;
+
+typedef struct os_dtd_path os_dtd_path_t;
+
+extern os_dtd_id_t os_dtd_identify(xmlDocPtr);
+extern int os_dtd_attach(xmlDocPtr, os_dtd_id_t);
+extern int os_dtd_validate(xmlDocPtr, boolean_t, boolean_t *);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _OS_DTD_H */