15676436 pkg verify option to verify individual files s12b106
authorMingrui Lyu <mingrui.lyu@oracle.com>
Tue, 16 Aug 2016 10:18:24 -0700
changeset 3407 36ba7afc32e2
parent 3406 691263690c87
child 3408 7d3c73d68250
15676436 pkg verify option to verify individual files
src/client.py
src/man/pkg.1
src/modules/client/api.py
src/modules/client/client_api.py
src/modules/client/image.py
src/modules/client/imageplan.py
src/modules/client/options.py
src/tests/cli/t_fix.py
src/tests/cli/t_pkg_mediated.py
src/tests/cli/t_pkg_verify.py
--- a/src/client.py	Mon Aug 08 17:36:55 2016 -0700
+++ b/src/client.py	Tue Aug 16 10:18:24 2016 -0700
@@ -2214,12 +2214,13 @@
         return __handle_client_json_api_output(out_json, op)
 
 def verify(op, api_inst, pargs, omit_headers, parsable_version, quiet, verbose,
-    unpackaged, unpackaged_only):
+    unpackaged, unpackaged_only, verify_paths):
         """Determine if installed packages match manifests."""
 
         out_json = client_api._verify(op, api_inst, pargs, omit_headers,
             parsable_version, quiet, verbose, unpackaged, unpackaged_only,
-            display_plan_cb=display_plan_cb, logger=logger)
+            display_plan_cb=display_plan_cb, logger=logger,
+            verify_paths=verify_paths)
 
         # Print error messages.
         if "errors" in out_json:
@@ -3468,11 +3469,11 @@
                         for o, e in err.failed:
                                 refresh_errstr += "\n"
                                 refresh_errstr += str(e)
-                                
+
                         refresh_errstr += "\n"
                 else:
                         refresh_errstr += "\n   \n" + str(err)
-                 
+
 
         partial_str = ":"
         if partial:
@@ -3531,7 +3532,7 @@
                 else:
                         repo_str = _("{0} of {1} repositories").format(
                             len(failed["errors"]), total)
-                        
+
                 outstr += _("Errors were encountered when attempting to " \
                     "contact {0} for publisher '{1}'.\n").format(repo_str, pub)
                 for err in failed["errors"]:
@@ -5169,7 +5170,8 @@
     "info_local":             ("l", ""),
     "info_remote":            ("r", ""),
     "display_license":        ("", "license"),
-    "publisher_a":            ("a", "")
+    "publisher_a":            ("a", ""),
+    "verify_paths":           ("p", "")
 }
 
 #
--- a/src/man/pkg.1	Mon Aug 08 17:36:55 2016 -0700
+++ b/src/man/pkg.1	Tue Aug 16 10:18:24 2016 -0700
@@ -66,7 +66,8 @@
     [<replaceable>pkg_fmri_pattern</replaceable> ...]</synopsis>
 <synopsis>/usr/bin/pkg search [-HIaflpr]
     [-o <replaceable>attribute</replaceable>[,<replaceable>attribute</replaceable>]...]... [-s <replaceable>repo_uri</replaceable>] <replaceable>query</replaceable></synopsis>
-<synopsis>/usr/bin/pkg verify [-Hqv] [--parsable <replaceable>version</replaceable>] [--unpackaged] [--unpackaged-only] [<replaceable>pkg_fmri_pattern</replaceable> ...]</synopsis>
+<synopsis>/usr/bin/pkg verify [-Hqv] [-p <replaceable>path</replaceable>]... [--parsable <replaceable>version</replaceable>]
+    [--unpackaged] [--unpackaged-only] [<replaceable>pkg_fmri_pattern</replaceable> ...]</synopsis>
 <synopsis>/usr/bin/pkg fix [-Hnvq] [--no-be-activate]
     [--no-backup-be | --require-backup-be]
     [--backup-be-name <replaceable>name</replaceable>]
@@ -900,8 +901,7 @@
 </variablelist>
 </listitem>
 </varlistentry>
-<varlistentry><term><command>pkg verify</command> [<option>Hqv</option>]
-[<option>-parsable</option> <replaceable>version</replaceable>] [<option>-unpackaged</option>]
+<varlistentry><term><command>pkg verify</command> [<option>Hqv</option>] [<option>p</option> <replaceable>path</replaceable>]... [<option>-parsable</option> <replaceable>version</replaceable>] [<option>-unpackaged</option>]
 [<option>-unpackaged-only</option>] [<replaceable>pkg_fmri_pattern</replaceable> ...]</term>
 <listitem><para>Validate the installation of all packages installed in the
 current image. If current signature policy for related publishers is not <literal>
@@ -911,7 +911,8 @@
 <variablelist termlength="wholeline">
 <varlistentry><term><replaceable>pkg_fmri_pattern</replaceable></term>
 <listitem><para>Validate the installation of only the specified packages installed
-in the current image.</para>
+in the current image. When used with <option>p</option>, only the matching actions 
+from the specified packages will be verified.</para>
 </listitem>
 </varlistentry>
 <varlistentry><term><option>H</option></term>
@@ -923,6 +924,15 @@
 description of the <option>v</option> option for the install command above.</para>
 </listitem>
 </varlistentry>
+<varlistentry><term><option>p</option> <replaceable>path</replaceable></term>
+<listitem><para>Validate individual files, links or directories by specifying the
+paths. Paths specified are assumed to be relative to the <literal>/</literal> of 
+the image on which the verify is performed. If a directory or a link is specified, 
+only the matching action for the directory or the link will be verified.</para>
+<para>If no <replaceable>pkg_fmri_pattern</replaceable>s are provided when specifying
+paths, all matching actions from packages installed in the image will be verified.</para>
+</listitem>
+</varlistentry>
 <varlistentry><term><option>-parsable</option> <replaceable>version</replaceable></term>
 <listitem><para>Parsable output. The supported version is 0. Use of this option
 implies <option>-q</option>.</para>
@@ -2259,6 +2269,10 @@
 <para>The following command rehydrates only files and hardlinks delivered by the <literal>test1</literal> publisher.</para>
 <screen>$ <userinput>pkg -R /tmp/test_image rehydrate -p test1</userinput></screen>
 </example>
+<example><title>Verify an Individual Path in an Image</title>
+<para>Verify an individual file at path <literal>/tmp/test_image/usr/bin/ls</literal> and display verbose result.</para>
+<screen>$ <userinput>pkg -R /tmp/test_image verify -v -p /usr/bin/ls</userinput></screen>
+</example>
 </refsect1>
 <refsect1 role="environment-variables"><title></title>
 <variablelist termlength="wholeline">
--- a/src/modules/client/api.py	Mon Aug 08 17:36:55 2016 -0700
+++ b/src/modules/client/api.py	Tue Aug 16 10:18:24 2016 -0700
@@ -2257,7 +2257,7 @@
                     publishers=publishers)
 
         def gen_plan_verify(self, args, noexecute=True, unpackaged=False,
-            unpackaged_only=False):
+            unpackaged_only=False, verify_paths=misc.EmptyI):
                 """This is a generator function that yields a PlanDescription
                 object.
 
@@ -2272,7 +2272,8 @@
                 op = API_OP_VERIFY
                 return self.__plan_op(op, args=args, _noexecute=noexecute,
                     _refresh_catalogs=False, _update_index=False, _new_be=None,
-                    unpackaged=unpackaged, unpackaged_only=unpackaged_only)
+                    unpackaged=unpackaged, unpackaged_only=unpackaged_only,
+                    verify_paths=verify_paths)
 
         def gen_plan_fix(self, args, backup_be=None, backup_be_name=None,
             be_activate=True, be_name=None, new_be=None, noexecute=True,
--- a/src/modules/client/client_api.py	Mon Aug 08 17:36:55 2016 -0700
+++ b/src/modules/client/client_api.py	Tue Aug 16 10:18:24 2016 -0700
@@ -565,7 +565,7 @@
                         refresh_errstr += "\n"
                 else:
                         refresh_errstr += "\n\n" + str(err)
-                 
+
 
         partial_str = ":"
         if partial:
@@ -1171,7 +1171,7 @@
     _omit_headers=False, _origins=None, _parsable_version=None, _quiet=False,
     _quiet_plan=False, _show_licenses=False, _stage=API_STAGE_DEFAULT,
     _verbose=0, display_plan_cb=None, logger=None, _unpackaged=False,
-    _unpackaged_only=False, **kwargs):
+    _unpackaged_only=False, _verify_paths=EmptyI, **kwargs):
 
         # All the api interface functions that we invoke have some
         # common arguments.  Set those up now.
@@ -1181,6 +1181,7 @@
         if _op == PKG_OP_VERIFY:
                 kwargs["unpackaged"] = _unpackaged
                 kwargs["unpackaged_only"] = _unpackaged_only
+                kwargs["verify_paths"] = _verify_paths
         elif _op == PKG_OP_FIX:
                 kwargs["unpackaged"] = _unpackaged
 
@@ -1381,8 +1382,8 @@
 def __api_op(_op, _api_inst, _accept=False, _li_ignore=None, _noexecute=False,
     _origins=None, _parsable_version=None, _quiet=False, _quiet_plan=False,
     _show_licenses=False, _stage=API_STAGE_DEFAULT, _verbose=0,
-    _unpackaged=False, _unpackaged_only=False, display_plan_cb=None,
-    logger=None, **kwargs):
+    _unpackaged=False, _unpackaged_only=False, _verify_paths=EmptyI,
+    display_plan_cb=None, logger=None, **kwargs):
         """Do something that involves the api.
 
         Arguments prefixed with '_' are primarily used within this
@@ -1400,7 +1401,8 @@
                     _show_licenses=_show_licenses, _stage=_stage,
                     _verbose=_verbose, _quiet_plan=_quiet_plan,
                     _unpackaged=_unpackaged, _unpackaged_only=_unpackaged_only,
-                    display_plan_cb=display_plan_cb, logger=logger, **kwargs)
+                    _verify_paths=_verify_paths, display_plan_cb=display_plan_cb,
+                    logger=logger, **kwargs)
 
                 if "_failures" in _api_inst._img.transport.repo_status:
                         ret.setdefault("data", {}).update(
@@ -2467,7 +2469,7 @@
         return __prepare_json(err, errors=errors_json, data=data)
 
 def _verify(op, api_inst, pargs, omit_headers, parsable_version, quiet, verbose,
-    unpackaged, unpackaged_only, display_plan_cb=None, logger=None):
+    unpackaged, unpackaged_only, verify_paths, display_plan_cb=None, logger=None):
         """Determine if installed packages match manifests."""
 
         errors_json = []
@@ -2481,7 +2483,8 @@
             _omit_headers=omit_headers, _quiet=quiet, _quiet_plan=True,
             _verbose=verbose, _parsable_version=parsable_version,
             _unpackaged=unpackaged, _unpackaged_only=unpackaged_only,
-            display_plan_cb=display_plan_cb, logger=logger)
+            _verify_paths=verify_paths, display_plan_cb=display_plan_cb,
+            logger=logger)
 
 def _fix(op, api_inst, pargs, accept, backup_be, backup_be_name, be_activate,
     be_name, new_be, noexecute, omit_headers, parsable_version, quiet,
--- a/src/modules/client/image.py	Mon Aug 08 17:36:55 2016 -0700
+++ b/src/modules/client/image.py	Tue Aug 16 10:18:24 2016 -0700
@@ -1738,7 +1738,8 @@
                 # Only after success should the configuration be saved.
                 self.save_config()
 
-        def verify(self, fmri, progresstracker, **kwargs):
+        def verify(self, fmri, progresstracker, verifypaths=None,
+            overlaypaths=None, **kwargs):
                 """Generator that returns a tuple of the form (action, errors,
                 warnings, info) if there are any error, warning, or other
                 messages about an action contained within the specified
@@ -1750,9 +1751,15 @@
 
                 'progresstracker' is a ProgressTracker object.
 
+                'verifypaths' is the set of paths to verify.
+
+                'overlaypaths' is the set of overlaying path to verify.
+
                 'kwargs' is a dict of additional keyword arguments to be passed
                 to each action verification routine."""
 
+                path_only = bool(verifypaths or overlaypaths)
+
                 try:
                         pub = self.get_publisher(prefix=fmri.publisher)
                 except apx.UnknownPublisher:
@@ -1764,8 +1771,10 @@
                         sig_pol = self.signature_policy.combine(
                             pub.signature_policy)
 
-                progresstracker.plan_add_progress(
-                    progresstracker.PLAN_PKG_VERIFY)
+                if not path_only:
+                        progresstracker.plan_add_progress(
+                            progresstracker.PLAN_PKG_VERIFY)
+
                 manf = self.get_manifest(fmri, ignore_excludes=True)
                 sigs = list(manf.gen_actions_by_type("signature",
                     excludes=self.list_excludes()))
@@ -1786,8 +1795,6 @@
                         except apx.InvalidResourceLocation as e:
                                 yield None, [e], [], []
 
-                progresstracker.plan_add_progress(
-                    progresstracker.PLAN_PKG_VERIFY, nitems=0)
                 def mediation_allowed(act):
                         """Helper function to determine if the mediation
                         delivered by a link is allowed.  If it is, then
@@ -1824,8 +1831,11 @@
                         vardrate_excludes.append(func)
 
                 for act in manf.gen_actions():
+                        path = act.attrs.get("path")
+
                         progresstracker.plan_add_progress(
                             progresstracker.PLAN_PKG_VERIFY, nitems=0)
+
                         if (act.name == "link" or
                             act.name == "hardlink") and \
                             not mediation_allowed(act):
@@ -1837,14 +1847,29 @@
                         warnings = []
                         info = []
                         if act.include_this(excludes, publisher=fmri.publisher):
-                                errors, warnings, info = act.verify(
-                                    self, pfmri=fmri, **kwargs)
+                                if not path_only:
+                                        errors, warnings, info = act.verify(
+                                            self, pfmri=fmri, **kwargs)
+                                elif path in verifypaths or path in overlaypaths:
+                                        if path in verifypaths:
+                                            progresstracker.plan_add_progress(
+                                                progresstracker.PLAN_PKG_VERIFY)
+
+                                        errors, warnings, info = act.verify(
+                                            self, pfmri=fmri, **kwargs)
+                                        # It's safe to immediately discard this
+                                        # match as only one action can deliver a
+                                        # path with overlay=allow and only one with
+                                        # overlay=true.
+                                        overlaypaths.discard(path)
+                                        if act.attrs.get("overlay") == "allow":
+                                                overlaypaths.add(path)
+                                        verifypaths.discard(path)
                         elif act.include_this(vardrate_excludes,
                             publisher=fmri.publisher) and not act.refcountable:
                                 # Verify that file that is faceted out does not
                                 # exist. Exclude actions which may be delivered
                                 # from multiple packages.
-                                path = act.attrs.get("path", None)
                                 if path is not None and os.path.exists(
                                     os.path.join(self.root, path)):
                                         errors.append(
@@ -4036,14 +4061,14 @@
                 progtrack.plan_all_done()
 
         def make_fix_plan(self, op, progtrack, check_cancel, noexecute, args,
-            unpackaged=False, unpackaged_only=False):
+            unpackaged=False, unpackaged_only=False, verify_paths=EmptyI):
                 """Create an image plan to fix the image. Note: verify shares
                 the same routine."""
 
                 progtrack.plan_all_start()
                 self.__make_plan_common(op, progtrack, check_cancel, noexecute,
                     args=args, unpackaged=unpackaged,
-                    unpackaged_only=unpackaged_only)
+                    unpackaged_only=unpackaged_only, verify_paths=verify_paths)
                 progtrack.plan_all_done()
 
         def make_noop_plan(self, op, progtrack, check_cancel,
--- a/src/modules/client/imageplan.py	Mon Aug 08 17:36:55 2016 -0700
+++ b/src/modules/client/imageplan.py	Tue Aug 16 10:18:24 2016 -0700
@@ -1771,49 +1771,10 @@
                 # Clean up the BE used for verify.
                 bootenv.BootEnv.cleanup_be(dup_be_name)
 
-        def plan_fix(self, args, unpackaged=False, unpackaged_only=False):
-                """Determine the changes needed to fix the image."""
-
-                self.__plan_op()
-                self.__evaluate_excludes()
-
-                pt = self.__progtrack
-                pt.plan_all_start()
-
-                if args:
-                        proposed_dict, self.__match_rm = self.__match_user_fmris(
-                            self.image, args, self.MATCH_INST_VERSIONS)
-
-                        # merge patterns together
-                        proposed_fixes = sorted(set([
-                            f
-                            for each in proposed_dict.values()
-                            for f in each
-                        ]))
-                else:
-                        proposed_fixes = [
-                            f
-                            for f in self.image.gen_installed_pkgs(ordered=True)
-                        ]
-
-                if proposed_fixes:
-                        pt.plan_start(pt.PLAN_PKG_VERIFY, goal=len(proposed_fixes))
-                        # Verify unpackaged contents.
-                        if unpackaged or unpackaged_only:
-                                self.__process_unpackaged(proposed_fixes,
-                                    pt=pt)
-                                pt.plan_done(pt.PLAN_PKG_VERIFY)
-                                if unpackaged_only:
-                                        self.__finish_plan(plandesc.EVALUATED_PKGS)
-                                        return
-                                # Otherwise we reset the goals for packaged
-                                # contents.
-                                pt.plan_start(pt.PLAN_PKG_VERIFY, goal=len(
-                                    proposed_fixes))
-
-                repairs = []
-
-                for pfmri in proposed_fixes:
+        def __verify_fmris(self, repairs, args, proposed_fmris, pt, verifypaths,
+            overlaypaths):
+                path_only = bool(verifypaths or overlaypaths)
+                for pfmri in proposed_fmris:
                         entries = []
                         needs_fix = []
                         result = "OK"
@@ -1828,8 +1789,11 @@
                         # related messages output for it.
                         process_overlay = False
                         errs = set()
+                        verify_path_count = len(verifypaths)
+                        overlay_path_count = len(overlaypaths)
                         for act, errors, warnings, pinfo in self.image.verify(
-                            pfmri, pt, verbose=True, forever=True):
+                            pfmri, pt, verifypaths=verifypaths,
+                            overlaypaths=overlaypaths, verbose=True, forever=True):
                                 # determine the package's status and message
                                 # type
                                 if errors:
@@ -1840,16 +1804,29 @@
                                         # signature policy) and not a specific
                                         # action, so act may be None.
                                         needs_fix.append(act)
-                                        if act and not process_overlay:
+                                        if (not path_only and act and
+                                            not process_overlay):
+                                                # We only do this for package
+                                                # verification. In path verification,
+                                                # if the action has overlay=allow,
+                                                # its path will be in 'overlaypaths'
+                                                # and we will later look for the
+                                                # overlaying file anyway.
                                                 process_overlay = \
                                                     self.__process_verify_result(
-                                                    args, self.image, act, errs,
-                                                    pfmri)
+                                                        args, self.image,
+                                                        act, errs, pfmri)
                                 elif not failed and warnings:
                                         result = "WARNING"
                                         msg_level = MSG_WARNING
 
                                 entries.append((act, errors, warnings, pinfo))
+
+                        if (path_only and verify_path_count == len(verifypaths)
+                            and overlay_path_count == len(overlaypaths)):
+                                # When verifying paths, omit packages without any
+                                # matches from output.
+                                continue
                         timestamp = misc.time_to_timestamp(time.time())
                         self.pd.add_item_message(ffmri, timestamp,
                             msg_level, _("{pkg_name:70} {result:>7}").format(
@@ -1883,14 +1860,92 @@
                                             _("{0}").format(x),
                                             parent=parent)
 
-                        if not needs_fix:
-                                continue
-
-                        # Eliminate policy-based entries with no repair action.
-                        needs_fix = [x for x in needs_fix if x is not None]
-                        repairs.append((pfmri, needs_fix))
-                if proposed_fixes:
-                        pt.plan_done(pt.PLAN_PKG_VERIFY)
+                        if needs_fix:
+                                # Eliminate policy-based entries with no repair
+                                # action.
+                                needs_fix = [x for x in needs_fix
+                                             if x is not None]
+                                repairs.append((pfmri, needs_fix))
+
+                        if path_only and not overlaypaths and not verifypaths:
+                                return
+
+        def plan_fix(self, args, unpackaged=False, unpackaged_only=False,
+                verify_paths=misc.EmptyI):
+                """Determine the changes needed to fix the image."""
+
+                self.__plan_op()
+                self.__evaluate_excludes()
+
+                pt = self.__progtrack
+                pt.plan_all_start()
+
+                if args:
+                        proposed_dict, self.__match_rm = self.__match_user_fmris(
+                            self.image, args, self.MATCH_INST_VERSIONS)
+
+                        # merge patterns together
+                        proposed_fixes = sorted(set([
+                            f
+                            for each in proposed_dict.values()
+                            for f in each
+                        ]))
+                else:
+                        # No FMRIs specified, verify all packages
+                        proposed_fixes = list(self.image.gen_installed_pkgs(
+                            ordered=True))
+
+                repairs = []
+                overlaypaths = set()
+                verifypaths = set(a.lstrip(os.path.sep) for a in verify_paths)
+
+                if not verify_paths:
+                        pt.plan_start(pt.PLAN_PKG_VERIFY, goal=len(proposed_fixes))
+
+                        # Verify unpackaged contents.
+                        if unpackaged or unpackaged_only:
+                                self.__process_unpackaged(proposed_fixes,
+                                    pt=pt)
+                                pt.plan_done(pt.PLAN_PKG_VERIFY)
+                                if unpackaged_only:
+                                        self.__finish_plan(plandesc.EVALUATED_PKGS)
+                                        return
+                                # Otherwise we reset the goals for packaged
+                                # contents.
+                                pt.plan_start(pt.PLAN_PKG_VERIFY, goal=len(
+                                    proposed_fixes))
+                        self.__verify_fmris(repairs, args, proposed_fixes, pt,
+                            verifypaths, overlaypaths)
+                else:
+                        pt.plan_start(pt.PLAN_PKG_VERIFY, goal=len(verifypaths))
+
+                        self.__verify_fmris(repairs, args, proposed_fixes, pt,
+                            verifypaths, overlaypaths)
+
+                        timestamp = misc.time_to_timestamp(time.time())
+                        for path_not_found in verifypaths:
+                                pt.plan_add_progress(pt.PLAN_PKG_VERIFY)
+                                self.pd.add_item_message("path not found",
+                                    timestamp, MSG_WARNING,
+                                    _("{path} is not found in the image").format(
+                                        path=path_not_found))
+
+                        if args and overlaypaths:
+                                # Only perform verification for the rest of packages
+                                # if FMRIs are provided and there are actions with
+                                # overlay=allow found in those FMRIs. In the second
+                                # pass, only look for actions with overlay=true.
+                                pfixes = set(proposed_fixes)
+                                path_fmri = [
+                                    f
+                                    for f in self.image.gen_installed_pkgs(
+                                        ordered=True)
+                                        if f not in pfixes
+                                ]
+                                self.__verify_fmris(repairs, args, path_fmri, pt,
+                                    set(), overlaypaths)
+
+                pt.plan_done(pt.PLAN_PKG_VERIFY)
 
                 # If no repairs, finish the plan.
                 if not repairs:
--- a/src/modules/client/options.py	Mon Aug 08 17:36:55 2016 -0700
+++ b/src/modules/client/options.py	Tue Aug 16 10:18:24 2016 -0700
@@ -85,6 +85,7 @@
 UPDATE_INDEX          = "update_index"
 UNPACKAGED            = "unpackaged"
 UNPACKAGED_ONLY       = "unpackaged_only"
+VERIFY_PATHS          = "verify_paths"
 VERBOSE               = "verbose"
 SYNC_ACT              = "sync_act"
 ACT_TIMEOUT           = "act_timeout"
@@ -452,6 +453,18 @@
                 raise InvalidOptionError(InvalidOptionError.INCOMPAT,
                     [UNPACKAGED, UNPACKAGED_ONLY])
 
+def opts_table_cb_path_no_unpackaged(api_inst, opts, opts_new):
+        # Check whether path options is used with either unpackaged
+        # or unpackaged_only options.
+
+        if opts[VERIFY_PATHS] and opts[UNPACKAGED]:
+                raise InvalidOptionError(InvalidOptionError.INCOMPAT,
+                    [VERIFY_PATHS, UNPACKAGED])
+
+        if opts[VERIFY_PATHS] and opts[UNPACKAGED_ONLY]:
+                raise InvalidOptionError(InvalidOptionError.INCOMPAT,
+                    [VERIFY_PATHS, UNPACKAGED_ONLY])
+
 def __parse_linked_props(args):
         """"Parse linked image property options that were specified on the
         command line into a dictionary.  Make sure duplicate properties were
@@ -1262,7 +1275,10 @@
     [
     opts_table_cb_nqv,
     opts_table_cb_unpackaged,
+    opts_table_cb_path_no_unpackaged,
     (UNPACKAGED_ONLY,  False, [], {"type": "boolean"}),
+    (VERIFY_PATHS, [], [], {"type": "array",
+                            "items": {"type": "string"}}),
 ]
 
 opts_publisher = \
--- a/src/tests/cli/t_fix.py	Mon Aug 08 17:36:55 2016 -0700
+++ b/src/tests/cli/t_fix.py	Tue Aug 16 10:18:24 2016 -0700
@@ -622,11 +622,25 @@
                 # First, only install the package that has a file with
                 # attribute overlay=allow.
                 self.pkg("install gss")
+
+                # Path verification should report ok.
+                self.pkg("verify -v -p {0}".format(file_path))
+                self.assertTrue("OK" in self.output and file_path not in self.output
+                    and pfmri_gss.get_pkg_stem() in self.output)
+
                 self.file_exists(file_path)
                 self.file_remove(file_path)
                 self.file_doesnt_exist(file_path)
+
                 # Verify should report an error if the file is missing.
                 self.pkg("verify -v gss", exit=1)
+
+                # Path verification should report error.
+                self.pkg("verify -v -p {0}".format(file_path), exit=1)
+                self.assertTrue("OK" not in self.output and "ERROR" in self.output)
+                self.assertTrue(file_path in self.output and \
+                    pfmri_gss.get_pkg_stem() in self.output)
+
                 # Fix should be able to repair the file.
                 self.pkg("fix -v gss")
                 self.file_exists(file_path)
@@ -634,10 +648,39 @@
 
                 # Install the overlaying package.
                 self.pkg("install krb5")
+
+                # Path verification should report ok for both the overlaid package
+                # and the overlaying package.
+                self.pkg("verify -v -p {0}".format(file_path))
+                self.assertTrue(self.output.count("OK") == 2
+                    and "ERROR" not in self.output)
+                self.assertTrue(pfmri_krb.get_pkg_stem() in self.output
+                    and pfmri_gss.get_pkg_stem() in self.output)
+
+                self.pkg("verify -v -p {0} gss".format(file_path))
+                self.assertTrue(self.output.count("OK") == 2
+                    and "ERROR" not in self.output)
+                self.assertTrue(pfmri_krb.get_pkg_stem() in self.output
+                    and pfmri_gss.get_pkg_stem() in self.output)
+
                 self.file_exists(file_path)
                 self.file_remove(file_path)
                 self.file_doesnt_exist(file_path)
 
+                # Path verification should report error for both the overlaid package
+                # and the overlaying package.
+                self.pkg("verify -v -p {0}".format(file_path), exit=1)
+                self.assertTrue("OK" not in self.output
+                    and self.output.count("ERROR") == 4)
+                self.assertTrue(pfmri_krb.get_pkg_stem() in self.output
+                    and pfmri_gss.get_pkg_stem() in self.output)
+
+                self.pkg("verify -v -p {0} gss".format(file_path), exit=1)
+                self.assertTrue("OK" not in self.output
+                    and self.output.count("ERROR") == 4)
+                self.assertTrue(pfmri_krb.get_pkg_stem() in self.output
+                    and pfmri_gss.get_pkg_stem() in self.output)
+
                 # Now pkg verify should still report an error on the overlaid
                 # package and tell the users to verify the overlaying package.
                 self.pkg("verify gss", exit=1)
--- a/src/tests/cli/t_pkg_mediated.py	Mon Aug 08 17:36:55 2016 -0700
+++ b/src/tests/cli/t_pkg_mediated.py	Tue Aug 16 10:18:24 2016 -0700
@@ -546,6 +546,7 @@
                 # debugging tests when they fail.
                 self.pkg("install -vvv [email protected]")
                 self.pkg("mediator") # If tests fail, this is helpful.
+                self.pkg("verify -v -p /usr/sbin/sendmail -p /usr/bin/mailq")
 
                 # Verify that /usr/bin/mailq and /usr/lib/sendmail are files.
                 check_files(gen_mta_files())
@@ -555,6 +556,8 @@
                 self.pkg("install -vvv sendmail@1")
                 self.pkg("mediator") # If tests fail, this is helpful.
                 self.pkg("verify -v")
+                self.pkg("verify -v -p /usr/sbin/sendmail -p /usr/bin/mailq")
+                self.pkg("verify -v -p /usr/sbin/sendmail -p /usr/bin/mailq sendmail@1")
 
                 # Check that installed links point to sendmail and that
                 # verify passes.
@@ -569,6 +572,8 @@
                 self.pkg("mediator") # If tests fail, this is helpful.
                 check_target(gen_mta_links(), "sendmail3-mta")
                 self.pkg("verify -v")
+                self.pkg("verify -v -p /usr/sbin/sendmail -p /usr/bin/mailq")
+                self.pkg("verify -v -p /usr/sbin/sendmail -p /usr/bin/mailq sendmail@3")
 
                 # Downgrading to 0.5 should change sendmail and mailq links back
                 # to a file.
@@ -993,6 +998,7 @@
                 self.pkg("verify -v python-unladen-swallow-27 "
                     "python-unladen-swallow-35")
                 self.pkg("verify -v python-unladen-swallow-34", exit=1)
+                self.pkg("verify -v -p /usr/bin/python", exit=1)
                 self.pkg("fix")
                 self.pkg("verify -v")
 
--- a/src/tests/cli/t_pkg_verify.py	Mon Aug 08 17:36:55 2016 -0700
+++ b/src/tests/cli/t_pkg_verify.py	Tue Aug 16 10:18:24 2016 -0700
@@ -48,6 +48,7 @@
             add dir mode=0755 owner=root group=bin path=/usr/bin
             add file bobcat mode=0644 owner=root group=bin path=/usr/bin/bobcat
             add file ls path=/usr/bin/ls mode=755 owner=root group=sys
+            add link path=/usr/bin/bobcat_link target=/usr/bin/bobcat
             add file bobcat path=/etc/preserved mode=644 owner=root group=sys preserve=true timestamp="20080731T024051Z"
             add file dricon_maj path=/etc/name_to_major mode=644 owner=root group=sys preserve=true
             add file dricon_da path=/etc/driver_aliases mode=644 owner=root group=sys preserve=true
@@ -59,6 +60,15 @@
             add driver name=zigit alias=pci8086,1234
             close
             """
+        bar10 = """
+            open [email protected],5.11-0:20110908T004546Z
+            add dir mode=0755 owner=root group=sys path=/usr
+            add dir mode=0755 owner=root group=bin path=/usr/bin
+            add link path=/usr/bin/bobcat_link target=/usr/bin/bobcat
+            add file bronze1 mode=644 owner=root group=sys path=/etc/bronze1
+            add file bronze2 mode=644 owner=root group=sys path=/etc/bronze2
+            close
+            """
 
         sysattr = """
             open [email protected]
@@ -74,7 +84,9 @@
            "dricon_mp": """\n""",
            "dricon_dp": """\n""",
            "dricon_ep": """\n""",
-           "permission": ""
+           "permission": "",
+           "bronze1": "",
+           "bronze2": ""
         }
 
         def setUp(self):
@@ -123,6 +135,20 @@
                 self.pkg_verify("-q foo")
                 assert(self.output == "")
 
+                # Should not fail since the path exists in the package
+                # and is intact.
+                self.pkg_verify("-v -p /etc/name_to_major")
+                self.assertTrue("foo" in self.output
+                    and "etc/name_to_major" not in self.output)
+                self.pkg_verify("-v -p /usr/bin/bobcat_link")
+                self.assertTrue("OK" in self.output)
+                self.pkg_verify("-v -p /usr")
+                self.assertTrue(self.output.count("OK") == 1)
+
+                # Should output path not found.
+                self.pkg_verify("-p nonexist")
+                self.assertTrue("not found" in self.output)
+
                 # Should not fail if publisher is disabled and package is ok.
                 self.pkg("set-publisher -d test")
                 self.pkg_verify("foo")
@@ -139,6 +165,17 @@
                 self.assertTrue("Unexpected Exception" not in self.output)
                 self.assertTrue("PACKAGE" in self.output and "STATUS" in self.output)
 
+                # Should fail with exit code 2 because of invalid option combo.
+                self.pkg_verify("-p /usr/bin/bobcat --unpackaged", exit=2)
+                self.pkg_verify("-p /usr/bin/bobcat --unpackaged-only", exit=2)
+
+                # Should fail with exit code 1 because the file is removed
+                # and the package is not ok.
+                self.pkg_verify("-p /usr/bin/bobcat", exit=1)
+                self.assertTrue("PACKAGE" in self.output
+                    and self.output.count("ERROR") == 2)
+                self.assertTrue("usr/bin/bobcat" in self.output)
+
                 # Test that "-H" works as expected.
                 self.pkg_verify("foo -H", exit=1)
                 self.assertTrue("PACKAGE" not in self.output and
@@ -199,6 +236,155 @@
                 self.pkg_verify("")
                 self.assertTrue("4321" in self.output and "WARNING" in self.output)
 
+        def test_multiple_paths_input(self):
+                """Test that when input is multiple paths, results returned are as
+                expected."""
+
+                self.pkgsend_bulk(self.rurl, self.bar10)
+
+                self.image_create(self.rurl)
+                self.pkg("install foo bar")
+
+                # Test verification of multiple paths in a package.
+                # Should not fail since files specified by paths are all intact.
+                self.pkg_verify("-v -p /etc/driver_aliases -p /etc/minor_perm \
+                    -p /etc/security/extra_privs")
+
+                # Test verification of multiple paths in different packages.
+                # Should not fail since files specified by paths are all intact.
+                self.pkg_verify("-v -p /etc/driver_aliases -p /etc/bronze1 -p /usr/bin/bobcat")
+                self.assertTrue("ERROR" not in self.output
+                    and self.output.count("OK") == 2)
+                self.pkg_verify("-v -p /usr -p /etc/driver_aliases")
+                self.assertTrue("ERROR" not in self.output
+                    and self.output.count("OK") == 2)
+                self.pkg_verify("-v -p /usr -p /usr/bin")
+                self.assertTrue("ERROR" not in self.output
+                    and self.output.count("OK") == 1)
+                self.pkg_verify("-v -p /usr -p /usr/bin/bobcat_link")
+                self.assertTrue("ERROR" not in self.output
+                    and self.output.count("OK") == 1)
+
+
+                # When multiple paths are given to pkg verify, if any of them
+                # are not packaged in the image it should report the file not found.
+                self.pkg_verify("-v -p /etc/driver_aliases -p nonexist")
+                self.assertTrue("ERROR" not in self.output
+                    and self.output.count("OK") == 1)
+                self.assertTrue("nonexist is not found" in self.output)
+
+                fd = open(os.path.join(self.get_img_path(), "usr", "bin", "bobcat"), "w+")
+                fd.write("Bobcats are here")
+                fd.close()
+
+                # When verify multilple paths in a package, should output
+                # ok for one package, error for the other.
+                self.pkg_verify("-v -p /etc/driver_aliases \
+                    -p /usr/bin/bobcat", exit=1)
+                self.assertTrue("usr/bin/bobcat" in self.output
+                    and "etc/driver_aliases" not in self.output)
+                self.assertTrue("foo" in self.output)
+                self.assertTrue("OK" not in self.output
+                    and self.output.count("ERROR") == 3
+                    and "Hash" in self.output)
+
+                # Even though the target file is modified, the link and dir
+                # verification should pass.
+                self.pkg_verify("-v -p /usr -p /usr/bin -p /usr/bin/bobcat_link")
+                self.assertTrue("ERROR" not in self.output
+                    and self.output.count("OK") == 1)
+
+                # When verifying multiple paths in different packages, should fail
+                # the package whose manifest contains the path. Should not
+                # fail the other package.
+                self.pkg_verify("-v -p /usr/bin/bobcat -p /etc/bronze1", exit=1)
+                self.assertTrue("usr/bin/bobcat" in self.output
+                    and "etc/bronze1" not in self.output
+                    and "foo" in self.output and "bar" in self.output)
+                self.assertTrue(self.output.count("OK") == 1
+                    and self.output.count("ERROR") == 3
+                    and "Hash" in self.output)
+                self.pkg_verify("-v -p /usr -p /usr/bin/bobcat", exit=1)
+                self.assertTrue("usr/bin/bobcat" in self.output
+                    and "foo" in self.output and "bar" in self.output)
+                self.assertTrue("OK" in self.output
+                    and self.output.count("ERROR") == 3
+                    and "Hash" in self.output)
+
+                self.pkg("uninstall foo bar")
+
+        def test_mix_verify_input(self):
+                """Test that when input is mix of FMRIs and paths, verbose output
+                is correct"""
+
+                self.pkgsend_bulk(self.rurl, self.bar10)
+                self.image_create(self.rurl)
+                self.pkg("install foo bar")
+
+                # Should verify the package when only FMRI is provided.
+                self.pkg_verify("-v foo")
+                self.assertTrue("foo" in self.output and "OK" in self.output)
+
+                # Should verify the path when no FMRI is provided.
+                self.pkg_verify("-v -p /etc/name_to_major")
+                self.assertTrue("foo" in self.output and "OK" in self.output)
+
+                # Should verify only the path when both path and FMRI are
+                # provided and an action of the FMRI matches the path.
+                self.pkg_verify("-v -p /etc/name_to_major foo")
+                self.assertTrue("foo" in self.output
+                    and "etc/name_to_major" not in self.output)
+                self.assertTrue(self.output.count("OK") == 1)
+
+                # Should verify only the path when the path and more than
+                # one FMRIs are provided.
+                self.pkg_verify("-v -p /etc/name_to_major foo bar")
+                self.assertTrue("foo" in self.output
+                    and "bar" not in self.output
+                    and "etc/name_to_major" not in self.output)
+                self.assertTrue(self.output.count("OK") == 1)
+                self.pkg_verify("-v -p /usr foo bar")
+                self.assertTrue("bar" in self.output
+                    and "foo" not in self.output)
+                self.assertTrue(self.output.count("OK") == 1)
+                self.pkg_verify("-v -p /usr/bin/bobcat_link foo bar")
+                self.assertTrue("bar" in self.output
+                    and "foo" not in self.output)
+                self.assertTrue(self.output.count("OK") == 1)
+
+                # Should verify the path when both the path and the FMRI are
+                # provided but the path is not in the manifest of the package.
+                self.pkg_verify("-v -p /etc/name_to_major bar")
+                self.assertTrue("foo" not in self.output and
+                    "bar" not in self.output and "not found" in self.output)
+                self.assertTrue("OK" not in self.output)
+
+                fd = open(os.path.join(self.get_img_path(), "usr", "bin", "bobcat"), "w+")
+                fd.write("Bobcats are here")
+                fd.close()
+
+                # Should not output error for the package if the modified file
+                # is not verified.
+                self.pkg_verify("-v -p /etc/bronze1 bar")
+                self.assertTrue("bar" in self.output
+                    and "OK" in self.output and "ERROR" not in self.output)
+
+                # When the path belongs to the manifest of FMRI, should
+                # fail and report error for the path and the FMRI.
+                self.pkg_verify("-v -p /usr/bin/bobcat foo bar", exit=1)
+                self.assertTrue("foo" in self.output and "bar" not in self.output
+                    and "usr/bin/bobcat" in self.output)
+                self.assertTrue(self.output.count("ERROR") == 3
+                    and "OK" not in self.output)
+
+                # Even though the target file is modified, the link and dir
+                # verification should pass.
+                self.pkg_verify("-v -p /usr/bin -p /usr/bin/bobcat_link foo bar")
+                self.assertTrue("ERROR" not in self.output
+                    and self.output.count("OK") == 1)
+
+                self.pkg("uninstall foo bar")
+
         def test_02_installed(self):
                 """When multiple FMRIs are given to pkg verify, if any of them
                 aren't installed it should fail."""