15811694 filesystem actions should support system attributes
authorErik Trauschke <Erik.Trauschke@oracle.com>
Fri, 28 Feb 2014 10:34:59 -0800
changeset 3026 5e62d70bdfaa
parent 3025 c2f7cdceff60
child 3027 31883fa1a828
15811694 filesystem actions should support system attributes
src/man/pkg.5
src/modules/actions/file.py
src/modules/portable/__init__.py
src/modules/portable/os_sunos.py
src/modules/sysattr.c
src/pkg/manifests/package:pkg.p5m
src/setup.py
src/tests/api/t_sysattr.py
src/tests/cli/t_pkg_install.py
src/tests/cli/t_pkg_verify.py
--- a/src/man/pkg.5	Fri Feb 28 09:57:49 2014 -0800
+++ b/src/man/pkg.5	Fri Feb 28 10:34:59 2014 -0800
@@ -1,6 +1,6 @@
 '\" te
-.\" Copyright (c) 2009, 2013, Oracle and/or its affiliates. All rights reserved.
-.TH pkg 5 "07 Aug 2013" "SunOS 5.12" "Standards, Environments, and Macros"
+.\" Copyright (c) 2009, 2014, Oracle and/or its affiliates. All rights reserved.
+.TH pkg 5 "07 Feb 2014" "SunOS 5.12" "Standards, Environments, and Macros"
 .SH NAME
 pkg \- Image Packaging System
 .SH DESCRIPTION
@@ -353,6 +353,28 @@
 .ne 2
 .mk
 .na
+\fB\fBsysattr\fR\fR
+.ad
+.sp .6
+.RS 4n
+This attribute is used to specify any system attributes which should be set for this file. The value of the \fBsysattr\fR attribute can be a comma-separated list of verbose system attributes or a string sequence of compact system attribute options. Supported system attributes are explained in chmod(1). System attributes specified in the manifest are set additionally to system attributes which might have been set by other subsystems of the operating system.
+.sp
+.in +2
+.nf
+file path=opt/secret_file sysattr=hidden,sensitive
+.sp
+or
+.sp
+file path=opt/secret_file sysattr=HT
+.fi
+.in -2
+
+.RE
+
+.sp
+.ne 2
+.mk
+.na
 \fB\fBtimestamp\fR\fR
 .ad
 .sp .6
--- a/src/modules/actions/file.py	Fri Feb 28 09:57:49 2014 -0800
+++ b/src/modules/actions/file.py	Fri Feb 28 10:34:59 2014 -0800
@@ -21,7 +21,7 @@
 #
 
 #
-# Copyright (c) 2007, 2013, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2007, 2014, Oracle and/or its affiliates. All rights reserved.
 #
 
 """module describing a file packaging object
@@ -59,7 +59,7 @@
 
         name = "file"
         key_attr = "path"
-        unique_attrs = "path", "mode", "owner", "group", "preserve"
+        unique_attrs = "path", "mode", "owner", "group", "preserve", "sysattr"
         globally_identical = True
         namespace_group = "path"
         ordinality = generic._orderdict[name]
@@ -262,6 +262,33 @@
                                 os.utime(final_path, (t, t))
                                 os.chmod(final_path, mode)
 
+                # Handle system attributes.
+                sattr = self.attrs.get("sysattr")
+                if sattr:
+                        sattrs = sattr.split(",")
+                        if len(sattrs) == 1 and \
+                            sattrs[0] not in portable.get_sysattr_dict():
+                                # not a verbose attr, try as a compact attr seq
+                                arg = sattrs[0]
+                        else:
+                                arg = sattrs
+
+                        try:
+                                portable.fsetattr(final_path, arg)
+                        except OSError, e:
+                                if e.errno != errno.EINVAL:
+                                        raise
+                                raise ActionExecutionError(self,
+                                    details=_("System attributes are not "
+                                    "supported on the target filesystem."))
+                        except ValueError, e:
+                                raise ActionExecutionError(self,
+                                    details=_("Could not set system attributes "
+                                    "'%(attrlist)s': %(err)s") % {
+                                        "attrlist": sattr,
+                                        "err": e
+                                    })
+
         def verify(self, img, **args):
                 """Returns a tuple of lists of the form (errors, warnings,
                 info).  The error list will be empty if the action has been
@@ -391,6 +418,31 @@
                                                     "found": sha_hash,
                                                     "expected": hash_val })
                                         self.replace_required = True
+
+                        # Check system attributes.
+                        # Since some attributes like 'archive' or 'av_modified'
+                        # are set automatically by the FS, it makes no sense to
+                        # check for 1:1 matches. So we only check that the
+                        # system attributes specified in the action are still
+                        # set on the file.
+                        sattr = self.attrs.get("sysattr", None)
+                        if sattr:
+                                sattrs = sattr.split(",")
+                                if len(sattrs) == 1 and \
+                                    sattrs[0] not in portable.get_sysattr_dict():
+                                        # not a verbose attr, try as a compact
+                                        set_attrs = portable.fgetattr(path,
+                                            compact=True)
+                                        sattrs = sattrs[0]
+                                else:
+                                        set_attrs = portable.fgetattr(path)
+
+                                for a in sattrs:
+                                        if a not in set_attrs:
+                                                errors.append(
+                                                    _("System attribute '%s' "
+                                                    "not set") % a)
+
                 except EnvironmentError, e:
                         if e.errno == errno.EACCES:
                                 errors.append(_("Skipping: Permission Denied"))
--- a/src/modules/portable/__init__.py	Fri Feb 28 09:57:49 2014 -0800
+++ b/src/modules/portable/__init__.py	Fri Feb 28 10:34:59 2014 -0800
@@ -19,7 +19,7 @@
 #
 # CDDL HEADER END
 #
-# Copyright (c) 2008, 2011, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2008, 2014, Oracle and/or its affiliates. All rights reserved.
 #
 
 # The portable module provide access to methods that require operating system-
@@ -206,6 +206,22 @@
         mode of the file."""
         raise NotImplementedError
 
+def fsetattr(path, attrs):
+        """ Set system attributes for file specified by 'path'. 'attrs' can be
+        a list of verbose system attributes or a string containing a sequence of
+        short options."""
+        raise NotImplementedError
+
+def fgetattr(path, compact=False):
+        """ Get system attributes for file specified by 'path'. If 'compact' is
+        True, it returns a string of short attribute options, otherwise a list
+        of verbose attributes."""
+        raise NotImplementedError
+
+def get_sysattr_dict():
+        """ Returns a dictionary containing all supported system attributes. The
+        keys of the dict are verbose attributes, the values short options."""
+        raise NotImplementedError
 
 # File type constants
 # -------------------
--- a/src/modules/portable/os_sunos.py	Fri Feb 28 09:57:49 2014 -0800
+++ b/src/modules/portable/os_sunos.py	Fri Feb 28 10:34:59 2014 -0800
@@ -19,7 +19,7 @@
 #
 # CDDL HEADER END
 #
-# Copyright (c) 2008, 2010, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2008, 2014, Oracle and/or its affiliates. All rights reserved.
 #
 
 """
@@ -39,6 +39,7 @@
 from pkg.portable import ELF, EXEC, PD_LOCAL_PATH, UNFOUND, SMF_MANIFEST
 
 import pkg.arch as arch
+import pkg.sysattr as sysattr
 
 def get_isainfo():
         return arch.get_isainfo()
@@ -85,4 +86,12 @@
                                 yield joined_ft
                 else:
                         yield joined_ft
-                
+
+def fsetattr(path, attrs):
+        return sysattr.fsetattr(path, attrs)
+
+def fgetattr(path, compact=False):
+        return sysattr.fgetattr(path, compact=compact)
+
+def get_sysattr_dict():
+        return sysattr.get_attr_dict()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/modules/sysattr.c	Fri Feb 28 10:34:59 2014 -0800
@@ -0,0 +1,360 @@
+/*
+ * 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 (c) 2014, Oracle and/or its affiliates. All rights reserved.
+ */
+
+#include <attr.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdbool.h>
+#include <sys/nvpair.h>
+
+#include <Python.h>
+
+/*
+ * Test if a sys attr is not in the list of ignored attributes.
+ */
+
+static bool
+is_supported(int attr)
+{
+	int ignore[] = {F_OWNERSID, F_GROUPSID, F_AV_SCANSTAMP,
+	    F_OPAQUE, F_CRTIME, F_FSID, F_GEN, F_REPARSE};
+
+	for (int i = 0; i < (sizeof (ignore) / sizeof (int)); i++)
+		if (ignore[i] == attr)
+			return (false);
+	return (true);
+}
+
+/*
+ * Decref a list and all included elements.
+ */
+
+static void
+clear_list(PyObject *list)
+{
+	PyObject *p;
+	Py_ssize_t size;
+
+	if ((size = PyList_Size(list)) == 0) {
+		Py_CLEAR(list);
+		return;
+	}
+
+	for (Py_ssize_t i = 0; i < size; i++) {
+		p = PyList_GetItem(list, i);
+		Py_CLEAR(p);
+	}
+	Py_CLEAR(list);
+}
+
+/*
+ * Get a dictionary containing all supported system attributes in the form:
+ *
+ *   { <verbose_name>: <compact_option>,
+ *     ...
+ *   }
+ */
+
+static char py_get_attr_dict_doc[] = "\n\
+Get a dictionary containing all supported system attributes.\n\
+\n\
+@return: dictionary of supported system attribute in the form:\n\
+    { <verbose_name>: <compact_option>,\n\
+        ... \n\
+    }\n\
+";
+
+/*ARGSUSED*/
+static PyObject *
+py_get_attr_dict(PyObject *self)
+{
+
+	PyObject *sys_attrs;
+
+	if ((sys_attrs = PyDict_New()) == NULL)
+		return (NULL);
+
+	for (int i = 0; i < F_ATTR_ALL; i++) {
+		if (!is_supported(i))
+			continue;
+
+		PyObject *str;
+		if ((str = PyString_FromString(
+		    attr_to_option(i))) == NULL) {
+			PyDict_Clear(sys_attrs);
+			Py_CLEAR(sys_attrs);
+			return (NULL);
+		}
+		if (PyDict_SetItemString(
+		    sys_attrs, attr_to_name(i), str) != 0) {
+			PyDict_Clear(sys_attrs);
+			Py_CLEAR(sys_attrs);
+			return (NULL);
+		}
+	}
+
+	return (sys_attrs);
+}
+
+/*
+ * Set system attributes for a file specified by 'path'. The system attributes
+ * can either be passed as a list of verbose attribute names or a string that
+ * consists of a sequence of compact attribute options.
+ *
+ * Raises ValueError for invalid system attributes or OSError (with errno set)
+ * if any of the library calls fail.
+ *
+ * Input examples:
+ *   verbose attributes example: ['hidden', 'archive', 'sensitive', ... ]
+ *
+ *   compact attributes example: 'HAT'
+ *
+ */
+
+static char py_fsetattr_doc[] = "\n\
+Set system attributes for a file. The system attributes can either be passed \n\
+as a list of verbose attribute names or a string that consists of a sequence \n\
+of compact attribute options.\n\
+\n\
+@param path: path of file to be modified\n\
+@param attrs: attributes to set\n\
+\n\
+@return: None\n\
+";
+
+/*ARGSUSED*/
+static PyObject *
+py_fsetattr(PyObject *self, PyObject *args)
+{
+	char *path;
+	bool compact = false;
+	int f;
+	int sys_attr = -1;
+	nvlist_t *request;
+	PyObject *attrs;
+	PyObject *attrs_iter;
+	PyObject *attr = NULL;
+
+	if (PyArg_ParseTuple(args, "sO", &path, &attrs) == 0) {
+		return (NULL);
+	}
+
+	if (nvlist_alloc(&request, NV_UNIQUE_NAME, 0) != 0) {
+		PyErr_SetFromErrno(PyExc_OSError);
+		return (NULL);
+	}
+
+	/*
+	 * A single string indicates system attributes are passed in compact
+	 * form (e.g. AHi), verbose attributes are read as a list of strings.
+	 */
+	if (PyString_Check(attrs)) {
+		compact = true;
+	}
+
+	if ((attrs_iter = PyObject_GetIter(attrs)) == NULL)
+		goto out;
+
+	while (attr = PyIter_Next(attrs_iter)) {
+		char *attr_str = PyString_AsString(attr);
+		if (attr_str == NULL) {
+			goto out;
+		}
+
+		if (compact)
+			sys_attr = option_to_attr(attr_str);
+		else
+			sys_attr = name_to_attr(attr_str);
+
+		if (sys_attr == F_ATTR_INVAL) {
+			PyObject *tstr = compact ?
+			    PyString_FromString(" is not a valid compact "
+			    "system attribute") :
+			    PyString_FromString(" is not a valid verbose "
+			    "system attribute");
+			PyString_ConcatAndDel(&attr, tstr);
+			PyErr_SetObject(PyExc_ValueError, attr);
+			goto out;
+		}
+
+		if (!is_supported(sys_attr)) {
+			PyObject *tstr = compact ?
+			    PyString_FromString(" is not a supported compact "
+			    "system attribute") :
+			    PyString_FromString(" is not a supported verbose "
+			    "system attribute");
+			PyString_ConcatAndDel(&attr, tstr);
+			PyErr_SetObject(PyExc_ValueError, attr);
+			goto out;
+		}
+
+		if (nvlist_add_boolean_value(request, attr_to_name(sys_attr),
+		    1) != 0) {
+			PyErr_SetFromErrno(PyExc_OSError);
+			goto out;
+		}
+		Py_CLEAR(attr);
+	}
+	Py_CLEAR(attrs_iter);
+
+	if ((f = open(path, O_RDONLY)) == -1) {
+		PyErr_SetFromErrno(PyExc_OSError);
+		goto out;
+	}
+
+	if (fsetattr(f, XATTR_VIEW_READWRITE, request)) {
+		PyErr_SetFromErrno(PyExc_OSError);
+		close(f);
+		goto out;
+	}
+	(void) close(f);
+	nvlist_free(request);
+
+	Py_RETURN_NONE;
+
+out:
+	nvlist_free(request);
+	Py_XDECREF(attrs_iter);
+	Py_XDECREF(attr);
+	return (NULL);
+
+}
+
+/*
+ * Get the list of set system attributes for file specified by 'path'.
+ * Returns a list of verbose attributes by default. If 'compact' is True,
+ * return a string consisting of compact option identifiers.
+ *
+ */
+
+static char py_fgetattr_doc[] = "\n\
+Get the list of set system attributes for a file.\n\
+\n\
+@param path: path of file\n\
+@param compact: if true, return system attributes in compact form\n\
+\n\
+@return: list of verbose system attributes or string sequence of compact\n\
+attributes\n\
+";
+
+/*ARGSUSED*/
+static PyObject *
+py_fgetattr(PyObject *self, PyObject *args, PyObject *kwds)
+{
+	char cattrs[F_ATTR_ALL];
+	char *path;
+	bool compact = false;
+	int f;
+	boolean_t bval;
+	nvlist_t *response;
+	nvpair_t *pair = NULL;
+	PyObject *attr_list = NULL;
+
+	/* Python based arguments to this function */
+	static char *kwlist[] = {"path", "compact", NULL};
+
+	if (PyArg_ParseTupleAndKeywords(args, kwds, "s|i", kwlist,
+	    &path, &compact) == 0) {
+		return (NULL);
+	}
+
+	if ((f = open(path, O_RDONLY)) == -1) {
+		PyErr_SetFromErrno(PyExc_OSError);
+		return (NULL);
+	}
+
+	if (fgetattr(f, XATTR_VIEW_READWRITE, &response)) {
+		PyErr_SetFromErrno(PyExc_OSError);
+		close(f);
+		return (NULL);
+	}
+	(void) close(f);
+
+	if (!compact) {
+		if ((attr_list = PyList_New(0)) == NULL)
+			return (NULL);
+	}
+
+	int count = 0;
+	while (pair = nvlist_next_nvpair(response, pair)) {
+		char *name = nvpair_name(pair);
+		/* we ignore all non-boolean attrs */
+		if (nvpair_type(pair) != DATA_TYPE_BOOLEAN_VALUE)
+			continue;
+
+		if (nvpair_value_boolean_value(pair, &bval) != 0) {
+			PyErr_SetString(PyExc_OSError,
+			    "could not read attr value");
+			clear_list(attr_list);
+			return (NULL);
+		}
+
+		if (bval) {
+			if (compact) {
+				if (count >= F_ATTR_ALL) {
+					clear_list(attr_list);
+					PyErr_SetString(PyExc_OSError, "Too "
+					    "many system attributes found");
+					return (NULL);
+				}
+				cattrs[count++] = attr_to_option(name_to_attr(
+				    name))[0];
+			} else {
+				PyObject *str;
+				if ((str = PyString_FromString(name)) == NULL) {
+					clear_list(attr_list);
+					return (NULL);
+				}
+				if (PyList_Append(attr_list, str) != 0) {
+					Py_CLEAR(str);
+					clear_list(attr_list);
+					return (NULL);
+				}
+				Py_CLEAR(str);
+			}
+		}
+	}
+	nvlist_free(response);
+
+	if (compact) {
+		cattrs[count] = '\0';
+		return (PyString_FromString(cattrs));
+	}
+
+	return (attr_list);
+}
+
+static PyMethodDef methods[] = {
+	{ "fsetattr", (PyCFunction)py_fsetattr, METH_VARARGS, py_fsetattr_doc },
+	{ "fgetattr", (PyCFunction)py_fgetattr, METH_KEYWORDS,
+	    py_fgetattr_doc },
+	{ "get_attr_dict", (PyCFunction)py_get_attr_dict, METH_NOARGS,
+	    py_get_attr_dict_doc },
+	{ NULL, NULL }
+};
+
+PyMODINIT_FUNC
+initsysattr() {
+	(void) Py_InitModule("sysattr", methods);
+}
--- a/src/pkg/manifests/package:pkg.p5m	Fri Feb 28 09:57:49 2014 -0800
+++ b/src/pkg/manifests/package:pkg.p5m	Fri Feb 28 10:34:59 2014 -0800
@@ -18,7 +18,7 @@
 #
 # CDDL HEADER END
 #
-# Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2010, 2014, Oracle and/or its affiliates. All rights reserved.
 #
 
 set name=pkg.fmri value=pkg:/package/pkg@$(PKGVERS)
@@ -34,6 +34,7 @@
 file path=$(PYDIRVP)/pkg-0.1-py2.6.egg-info
 dir  path=$(PYDIRVP)/pkg/64
 file path=$(PYDIRVP)/pkg/64/_varcet.so
+file path=$(PYDIRVP)/pkg/64/sysattr.so
 file path=$(PYDIRVP)/pkg/__init__.py
 file path=$(PYDIRVP)/pkg/_varcet.so
 dir  path=$(PYDIRVP)/pkg/actions
@@ -194,6 +195,7 @@
 file path=$(PYDIRVP)/pkg/server/transaction.py
 file path=$(PYDIRVP)/pkg/smf.py
 file path=$(PYDIRVP)/pkg/solver.so
+file path=$(PYDIRVP)/pkg/sysattr.so
 file path=$(PYDIRVP)/pkg/syscallat.so
 file path=$(PYDIRVP)/pkg/sysvpkg.py
 file path=$(PYDIRVP)/pkg/updatelog.py
--- a/src/setup.py	Fri Feb 28 09:57:49 2014 -0800
+++ b/src/setup.py	Fri Feb 28 10:34:59 2014 -0800
@@ -19,7 +19,7 @@
 #
 # CDDL HEADER END
 #
-# Copyright (c) 2008, 2013, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2008, 2014, Oracle and/or its affiliates. All rights reserved.
 #
 
 import errno
@@ -383,6 +383,9 @@
 pkg_locales = \
     'ar ca cs de es fr he hu id it ja ko nl pl pt_BR ru sk sv zh_CN zh_HK zh_TW'.split()
 
+sysattr_srcs = [
+        'modules/sysattr.c'
+        ]
 syscallat_srcs = [
         'modules/syscallat.c'
         ]
@@ -564,6 +567,12 @@
                             ["%s%s" % ("-I", k) for k in include_dirs] + \
                             ['-I' + self.escape(get_python_inc())] + \
                             syscallat_srcs
+                        sysattrcmd = lint + lint_flags + \
+                            ['-D_FILE_OFFSET_BITS=64'] + \
+                            ["%s%s" % ("-I", k) for k in include_dirs] + \
+                            ['-I' + self.escape(get_python_inc())] + \
+                            ["%s%s" % ("-l", k) for k in sysattr_libraries] + \
+                            sysattr_srcs
 
                         print(" ".join(archcmd))
                         os.system(" ".join(archcmd))
@@ -579,6 +588,8 @@
                         os.system(" ".join(pspawncmd))
                         print(" ".join(syscallatcmd))
                         os.system(" ".join(syscallatcmd))
+                        print(" ".join(sysattrcmd))
+                        os.system(" ".join(sysattrcmd))
 
 
 # Runs both C and Python lint
@@ -1427,6 +1438,7 @@
                 ),
         ]
 elf_libraries = None
+sysattr_libraries = None
 data_files = web_files
 cmdclasses = {
         'install': install_func,
@@ -1512,6 +1524,7 @@
         # All others use OpenSSL and cross-platform arch module
         if osname == 'sunos':
             elf_libraries += [ 'md' ]
+            sysattr_libraries = [ 'nvpair' ]
             ext_modules += [
                     Extension(
                             'arch',
@@ -1537,6 +1550,16 @@
                             extra_link_args = link_args,
                             define_macros = [('_FILE_OFFSET_BITS', '64')]
                             ),
+                    Extension(
+                            'sysattr',
+                            sysattr_srcs,
+                            include_dirs = include_dirs,
+                            libraries = sysattr_libraries,
+                            extra_compile_args = compile_args,
+                            extra_link_args = link_args,
+                            define_macros = [('_FILE_OFFSET_BITS', '64')],
+                            build_64 = True
+                            ),
                     ]
         else:
             elf_libraries += [ 'ssl' ]
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/tests/api/t_sysattr.py	Fri Feb 28 10:34:59 2014 -0800
@@ -0,0 +1,150 @@
+#!/usr/bin/python
+#
+# 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 (c) 2014, Oracle and/or its affiliates. All rights reserved.
+
+import testutils
+if __name__ == "__main__":
+        testutils.setup_environment("../../../proto")
+import pkg5unittest
+
+import pkg.portable as portable
+import os
+import re
+import tempfile
+import unittest
+
+class TestSysattr(pkg5unittest.Pkg5TestCase):
+
+        def __check_sysattrs(self, path, expected=[]):
+                """Use ls -/ to check if sysattrs specified by expected are
+                   set."""
+
+                p_re = re.compile("{(?P<attrs>.*)}")
+
+                self.cmdline_run("/usr/bin/ls -/ v %s" % path)
+                m = re.search(p_re, self.output)
+
+                self.assertTrue(m is not None)
+
+                attrs = m.groupdict()["attrs"].split(",")
+                for e in expected:
+                        self.assertTrue(e in attrs)
+
+        def __reset_file(self):
+                """Remove and recreate test file to clear sys attrs."""
+                portable.remove(self.test_fn)
+                self.test_fh, self.test_fn = tempfile.mkstemp(
+                    dir=self.test_path)
+
+        def __get_supported(self):
+                supported = portable.get_sysattr_dict()
+                # remove "immutable" and "nounlink"
+                # We can't unset system attributes and we can't use chmod
+                # for unsetting them due to the missing sys_linkdir privilege
+                # which gets removed in run.py.
+                del supported["immutable"]
+                del supported["nounlink"]
+
+                return supported
+
+        def setUp(self):
+                if portable.osname != "sunos":
+                        raise pkg5unittest.TestSkippedException(
+                            "System attributes unsupported on this platform.")
+
+                self.test_path = tempfile.mkdtemp(prefix="test-suite",
+                    dir="/var/tmp")
+                self.test_fh, self.test_fn = tempfile.mkstemp(
+                    dir=self.test_path)
+
+        def tearDown(self):
+                portable.remove(self.test_fn)
+                os.rmdir(self.test_path)
+
+        def test_0_bad_input(self):
+                # fsetattr
+                self.assertRaises(TypeError, portable.fsetattr, self.test_fn,
+                    None)
+                self.assertRaises(ValueError, portable.fsetattr, self.test_fn,
+                    ["octopus"])
+                self.assertRaises(ValueError, portable.fsetattr, self.test_fn,
+                    "xyz")
+                self.assertRaises(OSError, portable.fsetattr, "/nofile",
+                    "H")
+
+                # fgetattr
+                self.assertRaises(OSError, portable.fgetattr, "/nofile")
+
+        def test_1_supported_dict(self):
+                """Check if the supported sys attr dictionary can be retrieved
+                   and contains some attributes."""
+
+                supported = portable.get_sysattr_dict()
+                self.assertTrue(len(supported))
+
+        def test_2_fsetattr(self):
+                """Check if the fsetattr works with all supported attrs."""
+                supported = self.__get_supported()
+
+                # try to set all supported verbose attrs
+                for a in supported:
+                        portable.fsetattr(self.test_fn, [a])
+                        self.__check_sysattrs(self.test_fn, [a])
+                        self.__reset_file()
+
+                # try to set all supported compact attrs
+                for a in supported:
+                        portable.fsetattr(self.test_fn, supported[a])
+                        self.__check_sysattrs(self.test_fn, [a])
+                        self.__reset_file()
+
+                # set all at once using verbose
+                portable.fsetattr(self.test_fn, supported)
+                self.__check_sysattrs(self.test_fn, supported)
+                self.__reset_file()
+
+                # set all at once using compact
+                cattrs = ""
+                for a in supported:
+                        cattrs += supported[a]
+                portable.fsetattr(self.test_fn, cattrs)
+                self.__check_sysattrs(self.test_fn, supported)
+                self.__reset_file()
+
+        def test_3_fgetattr(self):
+                """Check if the fgetattr works with all supported attrs.""" 
+                supported = self.__get_supported()
+                for a in supported:
+                        # av_quarantined file becomes unreadable, skip
+                        if a == "av_quarantined":
+                                continue
+                        portable.fsetattr(self.test_fn, [a])
+                        vattrs = portable.fgetattr(self.test_fn, compact=False)
+                        cattrs = portable.fgetattr(self.test_fn, compact=True)
+                        self.assertTrue(a in vattrs)
+                        self.assertTrue(supported[a] in cattrs)
+                        self.__reset_file()
+
+
+if __name__ == "__main__":
+        unittest.main()
--- a/src/tests/cli/t_pkg_install.py	Fri Feb 28 09:57:49 2014 -0800
+++ b/src/tests/cli/t_pkg_install.py	Fri Feb 28 10:34:59 2014 -0800
@@ -21,7 +21,7 @@
 #
 
 #
-# Copyright (c) 2008, 2013, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2008, 2014, Oracle and/or its affiliates. All rights reserved.
 #
 
 import testutils
@@ -35,7 +35,9 @@
 import re
 import shutil
 import socket
+import subprocess
 import stat
+import tempfile
 import time
 import unittest
 import urllib2
@@ -192,6 +194,30 @@
             add link path=etc/cat_link target="../opt/dir with white\tspace/cat in a hat"
             close """
 
+        secret1 = """
+            open [email protected]
+            add dir mode=0755 owner=root group=bin path=/p1
+            add file tmp/cat mode=0555 owner=root group=bin sysattr=TH path=/p1/cat
+            close """
+
+        secret2 = """
+            open [email protected]
+            add dir mode=0755 owner=root group=bin path=/p2
+            add file tmp/cat mode=0555 owner=root group=bin sysattr=hidden,sensitive path=/p2/cat
+            close """
+
+        secret3 = """
+            open [email protected]
+            add dir mode=0755 owner=root group=bin path=/p3
+            add file tmp/cat mode=0555 owner=root group=bin sysattr=horst path=/p3/cat
+            close """
+
+        secret4 = """
+            open [email protected]
+            add dir mode=0755 owner=root group=bin path=/p3
+            add file tmp/cat mode=0555 owner=root group=bin sysattr=hidden,horst path=/p3/cat
+            close """
+
         misc_files = [ "tmp/libc.so.1", "tmp/cat", "tmp/baz" ]
 
         def setUp(self):
@@ -781,6 +807,50 @@
 
                 self.pkg("uninstall -vvv fuzzy")
 
+        def test_sysattrs(self):
+                """Test install with setting system attributes."""
+
+                if portable.osname != "sunos":
+                        raise pkg5unittest.TestSkippedException(
+                            "System attributes unsupported on this platform.")
+
+                plist = self.pkgsend_bulk(self.rurl, [self.secret1,
+                    self.secret2, self.secret3, self.secret4])
+                
+                # Need to create an image in /var/tmp since sysattrs don't work
+                # in tmpfs.
+                self.debug(self.rurl)
+                old_img_path = self.img_path()
+                self.set_img_path(tempfile.mkdtemp(dir="/var/tmp"))
+
+                self.image_create(self.rurl)
+
+                # test without permission for setting sensitive system attribute
+                self.pkg("install secret1", su_wrap=True, exit=1)
+
+                # now some tests which should succeed
+                self.pkg("install secret1")
+                fpath = os.path.join(self.img_path(),"p1/cat")
+                p = subprocess.Popen(["/usr/bin/ls", "-/", "c", fpath],
+                    stdout=subprocess.PIPE)
+                out, err = p.communicate()
+                expected = "{AH-----m----T}"
+                self.assertTrue(expected in out, out)
+
+                self.pkg("install secret2")
+                fpath = os.path.join(self.img_path(),"p2/cat")
+                p = subprocess.Popen(["/usr/bin/ls", "-/", "c", fpath],
+                    stdout=subprocess.PIPE)
+                out, err = p.communicate()
+                expected = "{AH-----m----T}"
+                self.assertTrue(expected in out, out)
+
+                # test some packages with invalid sysattrs
+                self.pkg("install secret3", exit=1)
+                self.pkg("install secret4", exit=1)
+                shutil.rmtree(self.img_path())
+                self.set_img_path(old_img_path)
+
 
 class TestPkgInstallApache(pkg5unittest.ApacheDepotTestCase):
 
--- a/src/tests/cli/t_pkg_verify.py	Fri Feb 28 09:57:49 2014 -0800
+++ b/src/tests/cli/t_pkg_verify.py	Fri Feb 28 10:34:59 2014 -0800
@@ -20,7 +20,7 @@
 # CDDL HEADER END
 #
 
-# Copyright (c) 2008, 2013, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2008, 2014, Oracle and/or its affiliates. All rights reserved.
 
 import testutils
 if __name__ == "__main__":
@@ -29,6 +29,9 @@
 
 import os
 import pkg.portable as portable
+import shutil
+import subprocess
+import tempfile
 import time
 import unittest
 
@@ -56,6 +59,12 @@
             close
             """
 
+        sysattr = """
+            open [email protected]
+            add dir mode=0755 owner=root group=bin path=/p1
+            add file bobcat mode=0555 owner=root group=bin sysattr=TH path=/p1/bobcat
+            close """
+
         misc_files = {
            "bobcat": "",
            "dricon_da": """zigit "pci8086,1234"\n""",
@@ -224,6 +233,44 @@
 
                 self.pkg_verify("", exit=1)
 
+        def test_sysattrs(self):
+                """Test that system attributes are verified correctly."""
+
+                if portable.osname != "sunos":
+                        raise pkg5unittest.TestSkippedException(
+                            "System attributes unsupported on this platform.")
+
+                self.pkgsend_bulk(self.rurl, [self.sysattr])
+
+                # Need to create an image in /var/tmp since sysattrs don't work
+                # in tmpfs.
+                old_img_path = self.img_path()
+                self.set_img_path(tempfile.mkdtemp(prefix="test-suite",
+                    dir="/var/tmp"))
+
+                self.image_create(self.rurl)
+                self.pkg("install sysattr")
+                self.pkg("verify")
+                fpath = os.path.join(self.img_path(),"p1/bobcat")
+
+                # Need to get creative here to remove the system attributes
+                # since you need the sys_linkdir privilege which we don't have:
+                # see run.py:393
+                # So we re-create the file with correct owner and mode and the
+                # only thing missing are the sysattrs.
+                portable.remove(fpath)
+                portable.copyfile(os.path.join(self.test_root, "bobcat"), fpath)
+                os.chmod(fpath, 0555)
+                os.chown(fpath, -1, 2)
+                self.pkg("verify", exit=1)
+                for sattr in ('H','T'):
+                        expected = "System attribute '%s' not set" % sattr
+                        self.assertTrue(expected in self.output,
+                            "Missing in verify output:  %s" % expected)
+
+                shutil.rmtree(self.img_path())
+                self.set_img_path(old_img_path)
+
 
 if __name__ == "__main__":
         unittest.main()