xref: /titanic_41/usr/src/tools/scripts/validate_pkg.py (revision f3acf9920334d127db190e20b0d66d3d23aee111)
1ead1f93eSLiane Praza#!/usr/bin/python2.6
2ead1f93eSLiane Praza#
3ead1f93eSLiane Praza# CDDL HEADER START
4ead1f93eSLiane Praza#
5ead1f93eSLiane Praza# The contents of this file are subject to the terms of the
6ead1f93eSLiane Praza# Common Development and Distribution License (the "License").
7ead1f93eSLiane Praza# You may not use this file except in compliance with the License.
8ead1f93eSLiane Praza#
9ead1f93eSLiane Praza# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10ead1f93eSLiane Praza# or http://www.opensolaris.org/os/licensing.
11ead1f93eSLiane Praza# See the License for the specific language governing permissions
12ead1f93eSLiane Praza# and limitations under the License.
13ead1f93eSLiane Praza#
14ead1f93eSLiane Praza# When distributing Covered Code, include this CDDL HEADER in each
15ead1f93eSLiane Praza# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16ead1f93eSLiane Praza# If applicable, add the following below this CDDL HEADER, with the
17ead1f93eSLiane Praza# fields enclosed by brackets "[]" replaced with your own identifying
18ead1f93eSLiane Praza# information: Portions Copyright [yyyy] [name of copyright owner]
19ead1f93eSLiane Praza#
20ead1f93eSLiane Praza# CDDL HEADER END
21ead1f93eSLiane Praza#
22ead1f93eSLiane Praza
23ead1f93eSLiane Praza#
24ead1f93eSLiane Praza# Copyright 2010 Sun Microsystems, Inc.  All rights reserved.
25ead1f93eSLiane Praza# Use is subject to license terms.
26ead1f93eSLiane Praza#
27ead1f93eSLiane Praza
28ead1f93eSLiane Praza#
29ead1f93eSLiane Praza# Compare the content generated by a build to a set of manifests
30ead1f93eSLiane Praza# describing how that content is to be delivered.
31ead1f93eSLiane Praza#
32ead1f93eSLiane Praza
33ead1f93eSLiane Praza
34ead1f93eSLiane Prazaimport getopt
35ead1f93eSLiane Prazaimport os
36ead1f93eSLiane Prazaimport stat
37ead1f93eSLiane Prazaimport sys
38ead1f93eSLiane Praza
39ead1f93eSLiane Prazafrom pkg import actions
40ead1f93eSLiane Prazafrom pkg import manifest
41ead1f93eSLiane Praza
42ead1f93eSLiane Praza
43ead1f93eSLiane Praza#
44ead1f93eSLiane Praza# Dictionary used to map action names to output format.  Each entry is
45ead1f93eSLiane Praza# indexed by action name, and consists of a list of tuples that map
46ead1f93eSLiane Praza# FileInfo class members to output labels.
47ead1f93eSLiane Praza#
48ead1f93eSLiane PrazaOUTPUTMAP = {
49ead1f93eSLiane Praza    "dir": [
50ead1f93eSLiane Praza        ("group", "group="),
51ead1f93eSLiane Praza        ("mode", "mode="),
52ead1f93eSLiane Praza        ("owner", "owner="),
53ead1f93eSLiane Praza        ("path", "path=")
54ead1f93eSLiane Praza    ],
55ead1f93eSLiane Praza    "file": [
56ead1f93eSLiane Praza        ("hash", ""),
57ead1f93eSLiane Praza        ("group", "group="),
58ead1f93eSLiane Praza        ("mode", "mode="),
59ead1f93eSLiane Praza        ("owner", "owner="),
60ead1f93eSLiane Praza        ("path", "path=")
61ead1f93eSLiane Praza    ],
62ead1f93eSLiane Praza    "link": [
63*f3acf992SAlexander Pyhalov        ("mediator", "mediator="),
64ead1f93eSLiane Praza        ("path", "path="),
65ead1f93eSLiane Praza        ("target", "target=")
66ead1f93eSLiane Praza    ],
67ead1f93eSLiane Praza    "hardlink": [
68ead1f93eSLiane Praza        ("path", "path="),
69ead1f93eSLiane Praza        ("hardkey", "target=")
70ead1f93eSLiane Praza    ],
71ead1f93eSLiane Praza}
72ead1f93eSLiane Praza
73ead1f93eSLiane Praza# Mode checks used to validate safe file and directory permissions
74ead1f93eSLiane PrazaALLMODECHECKS = frozenset(("m", "w", "s", "o"))
75ead1f93eSLiane PrazaDEFAULTMODECHECKS = frozenset(("m", "w", "o"))
76ead1f93eSLiane Praza
77ead1f93eSLiane Prazaclass FileInfo(object):
78ead1f93eSLiane Praza    """Base class to represent a file.
79ead1f93eSLiane Praza
80ead1f93eSLiane Praza    Subclassed according to whether the file represents an actual filesystem
81ead1f93eSLiane Praza    object (RealFileInfo) or an IPS manifest action (ActionInfo).
82ead1f93eSLiane Praza    """
83ead1f93eSLiane Praza
84ead1f93eSLiane Praza    def __init__(self):
85ead1f93eSLiane Praza        self.path = None
86ead1f93eSLiane Praza        self.isdir = False
87ead1f93eSLiane Praza        self.target = None
88ead1f93eSLiane Praza        self.owner = None
89ead1f93eSLiane Praza        self.group = None
90ead1f93eSLiane Praza        self.mode = None
91ead1f93eSLiane Praza        self.hardkey = None
92ead1f93eSLiane Praza        self.hardpaths = set()
93ead1f93eSLiane Praza        self.editable = False
94ead1f93eSLiane Praza
95ead1f93eSLiane Praza    def name(self):
96ead1f93eSLiane Praza        """Return the IPS action name of a FileInfo object.
97ead1f93eSLiane Praza        """
98ead1f93eSLiane Praza        if self.isdir:
99ead1f93eSLiane Praza            return "dir"
100ead1f93eSLiane Praza
101ead1f93eSLiane Praza        if self.target:
102ead1f93eSLiane Praza            return "link"
103ead1f93eSLiane Praza
104ead1f93eSLiane Praza        if self.hardkey:
105ead1f93eSLiane Praza            return "hardlink"
106ead1f93eSLiane Praza
107ead1f93eSLiane Praza        return "file"
108ead1f93eSLiane Praza
109ead1f93eSLiane Praza    def checkmodes(self, modechecks):
110ead1f93eSLiane Praza        """Check for and report on unsafe permissions.
111ead1f93eSLiane Praza
112ead1f93eSLiane Praza        Returns a potentially empty list of warning strings.
113ead1f93eSLiane Praza        """
114ead1f93eSLiane Praza        w = []
115ead1f93eSLiane Praza
116ead1f93eSLiane Praza        t = self.name()
117ead1f93eSLiane Praza        if t in ("link", "hardlink"):
118ead1f93eSLiane Praza            return w
119ead1f93eSLiane Praza        m = int(self.mode, 8)
120ead1f93eSLiane Praza        o = self.owner
121ead1f93eSLiane Praza        p = self.path
122ead1f93eSLiane Praza
123ead1f93eSLiane Praza        if "s" in modechecks and t == "file":
124ead1f93eSLiane Praza            if m & (stat.S_ISUID | stat.S_ISGID):
125ead1f93eSLiane Praza                if m & (stat.S_IRGRP | stat.S_IROTH):
126ead1f93eSLiane Praza                    w.extend(["%s: 0%o: setuid/setgid file should not be " \
127ead1f93eSLiane Praza                        "readable by group or other" % (p, m)])
128ead1f93eSLiane Praza
129ead1f93eSLiane Praza        if "o" in modechecks and o != "root" and ((m & stat.S_ISUID) == 0):
130ead1f93eSLiane Praza            mu = (m & stat.S_IRWXU) >> 6
131ead1f93eSLiane Praza            mg = (m & stat.S_IRWXG) >> 3
132ead1f93eSLiane Praza            mo = m & stat.S_IRWXO
133ead1f93eSLiane Praza            e = self.editable
134ead1f93eSLiane Praza
135ead1f93eSLiane Praza            if (((mu & 02) == 0 and (mo & mg & 04) == 04) or
136ead1f93eSLiane Praza                (t == "file" and mo & 01 == 1) or
137ead1f93eSLiane Praza                (mg, mo) == (mu, mu) or
138ead1f93eSLiane Praza                ((t == "file" and not e or t == "dir" and o == "bin") and
139ead1f93eSLiane Praza                (mg & 05 == mo & 05)) or
140ead1f93eSLiane Praza                (t == "file" and o == "bin" and mu & 01 == 01) or
141ead1f93eSLiane Praza                (m & 0105 != 0 and p.startswith("etc/security/dev/"))):
142ead1f93eSLiane Praza                w.extend(["%s: owner \"%s\" may be safely " \
143ead1f93eSLiane Praza                    "changed to \"root\"" % (p, o)])
144ead1f93eSLiane Praza
145ead1f93eSLiane Praza        if "w" in modechecks and t == "file" and o != "root":
146ead1f93eSLiane Praza            uwx = stat.S_IWUSR | stat.S_IXUSR
147ead1f93eSLiane Praza            if m & uwx == uwx:
148ead1f93eSLiane Praza                w.extend(["%s: non-root-owned executable should not " \
149ead1f93eSLiane Praza                    "also be writable by owner." % p])
150ead1f93eSLiane Praza
151ead1f93eSLiane Praza        if ("m" in modechecks and
152ead1f93eSLiane Praza            m & (stat.S_IWGRP | stat.S_IWOTH) != 0 and
153ead1f93eSLiane Praza            m & stat.S_ISVTX == 0):
154ead1f93eSLiane Praza            w.extend(["%s: 0%o: should not be writable by group or other" %
155ead1f93eSLiane Praza                (p, m)])
156ead1f93eSLiane Praza
157ead1f93eSLiane Praza        return w
158ead1f93eSLiane Praza
159ead1f93eSLiane Praza    def __ne__(self, other):
160ead1f93eSLiane Praza        """Compare two FileInfo objects.
161ead1f93eSLiane Praza
162ead1f93eSLiane Praza        Note this is the "not equal" comparison, so a return value of False
163ead1f93eSLiane Praza        indicates that the objects are functionally equivalent.
164ead1f93eSLiane Praza        """
165ead1f93eSLiane Praza        #
166ead1f93eSLiane Praza        # Map the objects such that the lhs is always the ActionInfo,
167ead1f93eSLiane Praza        # and the rhs is always the RealFileInfo.
168ead1f93eSLiane Praza        #
169ead1f93eSLiane Praza        # It's only really important that the rhs not be an
170ead1f93eSLiane Praza        # ActionInfo; if we're comparing FileInfo the RealFileInfo, it
171ead1f93eSLiane Praza        # won't actually matter what we choose.
172ead1f93eSLiane Praza        #
173ead1f93eSLiane Praza        if isinstance(self, ActionInfo):
174ead1f93eSLiane Praza            lhs = self
175ead1f93eSLiane Praza            rhs = other
176ead1f93eSLiane Praza        else:
177ead1f93eSLiane Praza            lhs = other
178ead1f93eSLiane Praza            rhs = self
179ead1f93eSLiane Praza
180ead1f93eSLiane Praza        #
181ead1f93eSLiane Praza        # Because the manifest may legitimately translate a relative
182ead1f93eSLiane Praza        # path from the proto area into a different path on the installed
183ead1f93eSLiane Praza        # system, we don't compare paths here.  We only expect this comparison
184ead1f93eSLiane Praza        # to be invoked on items with identical relative paths in
185ead1f93eSLiane Praza        # first place.
186ead1f93eSLiane Praza        #
187ead1f93eSLiane Praza
188ead1f93eSLiane Praza        #
189ead1f93eSLiane Praza        # All comparisons depend on type.  For symlink and directory, they
190ead1f93eSLiane Praza        # must be the same.  For file and hardlink, see below.
191ead1f93eSLiane Praza        #
192ead1f93eSLiane Praza        typelhs = lhs.name()
193ead1f93eSLiane Praza        typerhs = rhs.name()
194ead1f93eSLiane Praza        if typelhs in ("link", "dir"):
195ead1f93eSLiane Praza            if typelhs != typerhs:
196ead1f93eSLiane Praza                return True
197ead1f93eSLiane Praza
198ead1f93eSLiane Praza        #
199ead1f93eSLiane Praza        # For symlinks, all that's left is the link target.
200*f3acf992SAlexander Pyhalov        # For mediated symlinks targets can differ.
201ead1f93eSLiane Praza        #
202ead1f93eSLiane Praza        if typelhs == "link":
203*f3acf992SAlexander Pyhalov            return (lhs.mediator is None) and (lhs.target != rhs.target)
204ead1f93eSLiane Praza
205ead1f93eSLiane Praza        #
206ead1f93eSLiane Praza        # For a directory, it's important that both be directories,
207ead1f93eSLiane Praza        # the modes be identical, and the paths are identical.  We already
208ead1f93eSLiane Praza        # checked all but the modes above.
209ead1f93eSLiane Praza        #
210ead1f93eSLiane Praza        # If both objects are files, then we're in the same boat.
211ead1f93eSLiane Praza        #
212ead1f93eSLiane Praza        if typelhs == "dir" or (typelhs == "file" and typerhs == "file"):
213ead1f93eSLiane Praza            return lhs.mode != rhs.mode
214ead1f93eSLiane Praza
215ead1f93eSLiane Praza        #
216ead1f93eSLiane Praza        # For files or hardlinks:
217ead1f93eSLiane Praza        #
218ead1f93eSLiane Praza        # Since the key space is different (inodes for real files and
219ead1f93eSLiane Praza        # actual link targets for hard links), and since the proto area will
220ead1f93eSLiane Praza        # identify all N occurrences as hardlinks, but the manifests as one
221ead1f93eSLiane Praza        # file and N-1 hardlinks, we have to compare files to hardlinks.
222ead1f93eSLiane Praza        #
223ead1f93eSLiane Praza
224ead1f93eSLiane Praza        #
225ead1f93eSLiane Praza        # If they're both hardlinks, we just make sure that
226ead1f93eSLiane Praza        # the same target path appears in both sets of
227ead1f93eSLiane Praza        # possible targets.
228ead1f93eSLiane Praza        #
229ead1f93eSLiane Praza        if typelhs == "hardlink" and typerhs == "hardlink":
230ead1f93eSLiane Praza            return len(lhs.hardpaths.intersection(rhs.hardpaths)) == 0
231ead1f93eSLiane Praza
232ead1f93eSLiane Praza        #
233ead1f93eSLiane Praza        # Otherwise, we have a mix of file and hardlink, so we
234ead1f93eSLiane Praza        # need to make sure that the file path appears in the
235ead1f93eSLiane Praza        # set of possible target paths for the hardlink.
236ead1f93eSLiane Praza        #
237ead1f93eSLiane Praza        # We already know that the ActionInfo, if present, is the lhs
238ead1f93eSLiane Praza        # operator.  So it's the rhs operator that's guaranteed to
239ead1f93eSLiane Praza        # have a set of hardpaths.
240ead1f93eSLiane Praza        #
241ead1f93eSLiane Praza        return lhs.path not in rhs.hardpaths
242ead1f93eSLiane Praza
243ead1f93eSLiane Praza    def __str__(self):
244ead1f93eSLiane Praza        """Return an action-style representation of a FileInfo object.
245ead1f93eSLiane Praza
246ead1f93eSLiane Praza        We don't currently quote items with embedded spaces.  If we
247ead1f93eSLiane Praza        ever decide to parse this output, we'll want to revisit that.
248ead1f93eSLiane Praza        """
249ead1f93eSLiane Praza        name = self.name()
250ead1f93eSLiane Praza        out = name
251ead1f93eSLiane Praza
252ead1f93eSLiane Praza        for member, label in OUTPUTMAP[name]:
253ead1f93eSLiane Praza            out += " " + label + str(getattr(self, member))
254ead1f93eSLiane Praza
255ead1f93eSLiane Praza        return out
256ead1f93eSLiane Praza
257ead1f93eSLiane Praza    def protostr(self):
258ead1f93eSLiane Praza        """Return a protolist-style representation of a FileInfo object.
259ead1f93eSLiane Praza        """
260ead1f93eSLiane Praza        target = "-"
261ead1f93eSLiane Praza        major = "-"
262ead1f93eSLiane Praza        minor = "-"
263ead1f93eSLiane Praza
264ead1f93eSLiane Praza        mode = self.mode
265ead1f93eSLiane Praza        owner = self.owner
266ead1f93eSLiane Praza        group = self.group
267ead1f93eSLiane Praza
268ead1f93eSLiane Praza        name = self.name()
269ead1f93eSLiane Praza        if name == "dir":
270ead1f93eSLiane Praza            ftype = "d"
271ead1f93eSLiane Praza        elif name in ("file", "hardlink"):
272ead1f93eSLiane Praza            ftype = "f"
273ead1f93eSLiane Praza        elif name == "link":
274ead1f93eSLiane Praza            ftype = "s"
275ead1f93eSLiane Praza            target = self.target
276ead1f93eSLiane Praza            mode = "777"
277ead1f93eSLiane Praza            owner = "root"
278ead1f93eSLiane Praza            group = "other"
279ead1f93eSLiane Praza
280ead1f93eSLiane Praza        out = "%c %-30s %-20s %4s %-5s %-5s %6d %2ld  -  -" % \
281ead1f93eSLiane Praza            (ftype, self.path, target, mode, owner, group, 0, 1)
282ead1f93eSLiane Praza
283ead1f93eSLiane Praza        return out
284ead1f93eSLiane Praza
285ead1f93eSLiane Praza
286ead1f93eSLiane Prazaclass ActionInfo(FileInfo):
287ead1f93eSLiane Praza    """Object to track information about manifest actions.
288ead1f93eSLiane Praza
289ead1f93eSLiane Praza    This currently understands file, link, dir, and hardlink actions.
290ead1f93eSLiane Praza    """
291ead1f93eSLiane Praza
292ead1f93eSLiane Praza    def __init__(self, action):
293ead1f93eSLiane Praza        FileInfo.__init__(self)
294ead1f93eSLiane Praza        #
295ead1f93eSLiane Praza        # Currently, all actions that we support have a "path"
296ead1f93eSLiane Praza        # attribute.  If that changes, then we'll need to
297ead1f93eSLiane Praza        # catch a KeyError from this assignment.
298ead1f93eSLiane Praza        #
299ead1f93eSLiane Praza        self.path = action.attrs["path"]
300ead1f93eSLiane Praza
301ead1f93eSLiane Praza        if action.name == "file":
302ead1f93eSLiane Praza            self.owner = action.attrs["owner"]
303ead1f93eSLiane Praza            self.group = action.attrs["group"]
304ead1f93eSLiane Praza            self.mode = action.attrs["mode"]
305ead1f93eSLiane Praza            self.hash = action.hash
306ead1f93eSLiane Praza            if "preserve" in action.attrs:
307ead1f93eSLiane Praza                self.editable = True
308ead1f93eSLiane Praza        elif action.name == "link":
309ead1f93eSLiane Praza            target = action.attrs["target"]
310ead1f93eSLiane Praza            self.target = os.path.normpath(target)
311*f3acf992SAlexander Pyhalov            self.mediator = action.attrs.get("mediator")
312ead1f93eSLiane Praza        elif action.name == "dir":
313ead1f93eSLiane Praza            self.owner = action.attrs["owner"]
314ead1f93eSLiane Praza            self.group = action.attrs["group"]
315ead1f93eSLiane Praza            self.mode = action.attrs["mode"]
316ead1f93eSLiane Praza            self.isdir = True
317ead1f93eSLiane Praza        elif action.name == "hardlink":
318ead1f93eSLiane Praza            target = os.path.normpath(action.get_target_path())
319ead1f93eSLiane Praza            self.hardkey = target
320ead1f93eSLiane Praza            self.hardpaths.add(target)
321ead1f93eSLiane Praza
322ead1f93eSLiane Praza    @staticmethod
323ead1f93eSLiane Praza    def supported(action):
324ead1f93eSLiane Praza        """Indicates whether the specified IPS action time is
325ead1f93eSLiane Praza        correctly handled by the ActionInfo constructor.
326ead1f93eSLiane Praza        """
327ead1f93eSLiane Praza        return action in frozenset(("file", "dir", "link", "hardlink"))
328ead1f93eSLiane Praza
329ead1f93eSLiane Praza
330ead1f93eSLiane Prazaclass UnsupportedFileFormatError(Exception):
331ead1f93eSLiane Praza    """This means that the stat.S_IFMT returned something we don't
332ead1f93eSLiane Praza    support, ie a pipe or socket.  If it's appropriate for such an
333ead1f93eSLiane Praza    object to be in the proto area, then the RealFileInfo constructor
334ead1f93eSLiane Praza    will need to evolve to support it, or it will need to be in the
335ead1f93eSLiane Praza    exception list.
336ead1f93eSLiane Praza    """
337ead1f93eSLiane Praza    def __init__(self, path, mode):
338ead1f93eSLiane Praza        Exception.__init__(self)
339ead1f93eSLiane Praza        self.path = path
340ead1f93eSLiane Praza        self.mode = mode
341ead1f93eSLiane Praza
342ead1f93eSLiane Praza    def __str__(self):
343ead1f93eSLiane Praza        return '%s: unsupported S_IFMT %07o' % (self.path, self.mode)
344ead1f93eSLiane Praza
345ead1f93eSLiane Praza
346ead1f93eSLiane Prazaclass RealFileInfo(FileInfo):
347ead1f93eSLiane Praza    """Object to track important-to-packaging file information.
348ead1f93eSLiane Praza
349ead1f93eSLiane Praza    This currently handles regular files, directories, and symbolic links.
350ead1f93eSLiane Praza
351ead1f93eSLiane Praza    For multiple RealFileInfo objects with identical hardkeys, there
352ead1f93eSLiane Praza    is no way to determine which of the hard links should be
353ead1f93eSLiane Praza    delivered as a file, and which as hardlinks.
354ead1f93eSLiane Praza    """
355ead1f93eSLiane Praza
356ead1f93eSLiane Praza    def __init__(self, root=None, path=None):
357ead1f93eSLiane Praza        FileInfo.__init__(self)
358ead1f93eSLiane Praza        self.path = path
359ead1f93eSLiane Praza        path = os.path.join(root, path)
360ead1f93eSLiane Praza        lstat = os.lstat(path)
361ead1f93eSLiane Praza        mode = lstat.st_mode
362ead1f93eSLiane Praza
363ead1f93eSLiane Praza        #
364ead1f93eSLiane Praza        # Per stat.py, these cases are mutually exclusive.
365ead1f93eSLiane Praza        #
366ead1f93eSLiane Praza        if stat.S_ISREG(mode):
367ead1f93eSLiane Praza            self.hash = self.path
368ead1f93eSLiane Praza        elif stat.S_ISDIR(mode):
369ead1f93eSLiane Praza            self.isdir = True
370ead1f93eSLiane Praza        elif stat.S_ISLNK(mode):
371ead1f93eSLiane Praza            self.target = os.path.normpath(os.readlink(path))
372*f3acf992SAlexander Pyhalov            self.mediator = None
373ead1f93eSLiane Praza        else:
374ead1f93eSLiane Praza            raise UnsupportedFileFormatError(path, mode)
375ead1f93eSLiane Praza
376ead1f93eSLiane Praza        if not stat.S_ISLNK(mode):
377ead1f93eSLiane Praza            self.mode = "%04o" % stat.S_IMODE(mode)
378ead1f93eSLiane Praza            #
379ead1f93eSLiane Praza            # Instead of reading the group and owner from the proto area after
380ead1f93eSLiane Praza            # a non-root build, just drop in dummy values.  Since we don't
381ead1f93eSLiane Praza            # compare them anywhere, this should allow at least marginally
382ead1f93eSLiane Praza            # useful comparisons of protolist-style output.
383ead1f93eSLiane Praza            #
384ead1f93eSLiane Praza            self.owner = "owner"
385ead1f93eSLiane Praza            self.group = "group"
386ead1f93eSLiane Praza
387ead1f93eSLiane Praza        #
388ead1f93eSLiane Praza        # refcount > 1 indicates a hard link
389ead1f93eSLiane Praza        #
390ead1f93eSLiane Praza        if lstat.st_nlink > 1:
391ead1f93eSLiane Praza            #
392ead1f93eSLiane Praza            # This could get ugly if multiple proto areas reside
393ead1f93eSLiane Praza            # on different filesystems.
394ead1f93eSLiane Praza            #
395ead1f93eSLiane Praza            self.hardkey = lstat.st_ino
396ead1f93eSLiane Praza
397ead1f93eSLiane Praza
398ead1f93eSLiane Prazaclass DirectoryTree(dict):
399ead1f93eSLiane Praza    """Meant to be subclassed according to population method.
400ead1f93eSLiane Praza    """
401ead1f93eSLiane Praza    def __init__(self, name):
402ead1f93eSLiane Praza        dict.__init__(self)
403ead1f93eSLiane Praza        self.name = name
404ead1f93eSLiane Praza
405ead1f93eSLiane Praza    def compare(self, other):
406ead1f93eSLiane Praza        """Compare two different sets of FileInfo objects.
407ead1f93eSLiane Praza        """
408ead1f93eSLiane Praza        keys1 = frozenset(self.keys())
409ead1f93eSLiane Praza        keys2 = frozenset(other.keys())
410ead1f93eSLiane Praza
411ead1f93eSLiane Praza        common = keys1.intersection(keys2)
412ead1f93eSLiane Praza        onlykeys1 = keys1.difference(common)
413ead1f93eSLiane Praza        onlykeys2 = keys2.difference(common)
414ead1f93eSLiane Praza
415ead1f93eSLiane Praza        if onlykeys1:
416ead1f93eSLiane Praza            print "Entries present in %s but not %s:" % \
417ead1f93eSLiane Praza                (self.name, other.name)
418ead1f93eSLiane Praza            for path in sorted(onlykeys1):
419ead1f93eSLiane Praza                print("\t%s" % str(self[path]))
420ead1f93eSLiane Praza            print ""
421ead1f93eSLiane Praza
422ead1f93eSLiane Praza        if onlykeys2:
423ead1f93eSLiane Praza            print "Entries present in %s but not %s:" % \
424ead1f93eSLiane Praza                (other.name, self.name)
425ead1f93eSLiane Praza            for path in sorted(onlykeys2):
426ead1f93eSLiane Praza                print("\t%s" % str(other[path]))
427ead1f93eSLiane Praza            print ""
428ead1f93eSLiane Praza
429ead1f93eSLiane Praza        nodifferences = True
430ead1f93eSLiane Praza        for path in sorted(common):
431ead1f93eSLiane Praza            if self[path] != other[path]:
432ead1f93eSLiane Praza                if nodifferences:
433ead1f93eSLiane Praza                    nodifferences = False
434ead1f93eSLiane Praza                    print "Entries that differ between %s and %s:" \
435ead1f93eSLiane Praza                        % (self.name, other.name)
436ead1f93eSLiane Praza                print("%14s %s" % (self.name, self[path]))
437ead1f93eSLiane Praza                print("%14s %s" % (other.name, other[path]))
438ead1f93eSLiane Praza        if not nodifferences:
439ead1f93eSLiane Praza            print ""
440ead1f93eSLiane Praza
441ead1f93eSLiane Praza
442ead1f93eSLiane Prazaclass BadProtolistFormat(Exception):
443ead1f93eSLiane Praza    """This means that the user supplied a file via -l, but at least
444ead1f93eSLiane Praza    one line from that file doesn't have the right number of fields to
445ead1f93eSLiane Praza    parse as protolist output.
446ead1f93eSLiane Praza    """
447ead1f93eSLiane Praza    def __str__(self):
448ead1f93eSLiane Praza        return 'bad proto list entry: "%s"' % Exception.__str__(self)
449ead1f93eSLiane Praza
450ead1f93eSLiane Praza
451ead1f93eSLiane Prazaclass ProtoTree(DirectoryTree):
452ead1f93eSLiane Praza    """Describes one or more proto directories as a dictionary of
453ead1f93eSLiane Praza    RealFileInfo objects, indexed by relative path.
454ead1f93eSLiane Praza    """
455ead1f93eSLiane Praza
456ead1f93eSLiane Praza    def adddir(self, proto, exceptions):
457ead1f93eSLiane Praza        """Extends the ProtoTree dictionary with RealFileInfo
458ead1f93eSLiane Praza        objects describing the proto dir, indexed by relative
459ead1f93eSLiane Praza        path.
460ead1f93eSLiane Praza        """
461ead1f93eSLiane Praza        newentries = {}
462ead1f93eSLiane Praza
463ead1f93eSLiane Praza        pdir = os.path.normpath(proto)
464ead1f93eSLiane Praza        strippdir = lambda r, n: os.path.join(r, n)[len(pdir)+1:]
465ead1f93eSLiane Praza        for root, dirs, files in os.walk(pdir):
466ead1f93eSLiane Praza            for name in dirs + files:
467ead1f93eSLiane Praza                path = strippdir(root, name)
468ead1f93eSLiane Praza                if path not in exceptions:
469ead1f93eSLiane Praza                    try:
470ead1f93eSLiane Praza                        newentries[path] = RealFileInfo(pdir, path)
471ead1f93eSLiane Praza                    except OSError, e:
472ead1f93eSLiane Praza                        sys.stderr.write("Warning: unable to stat %s: %s\n" %
473ead1f93eSLiane Praza                            (path, e))
474ead1f93eSLiane Praza                        continue
475ead1f93eSLiane Praza                else:
476ead1f93eSLiane Praza                    exceptions.remove(path)
477ead1f93eSLiane Praza                    if name in dirs:
478ead1f93eSLiane Praza                        dirs.remove(name)
479ead1f93eSLiane Praza
480ead1f93eSLiane Praza        #
481ead1f93eSLiane Praza        # Find the sets of paths in this proto dir that are hardlinks
482ead1f93eSLiane Praza        # to the same inode.
483ead1f93eSLiane Praza        #
484ead1f93eSLiane Praza        # It seems wasteful to store this in each FileInfo, but we
485ead1f93eSLiane Praza        # otherwise need a linking mechanism.  With this information
486ead1f93eSLiane Praza        # here, FileInfo object comparison can be self contained.
487ead1f93eSLiane Praza        #
488ead1f93eSLiane Praza        # We limit this aggregation to a single proto dir, as
489ead1f93eSLiane Praza        # represented by newentries.  That means we don't need to care
490ead1f93eSLiane Praza        # about proto dirs on separate filesystems, or about hardlinks
491ead1f93eSLiane Praza        # that cross proto dir boundaries.
492ead1f93eSLiane Praza        #
493ead1f93eSLiane Praza        hk2path = {}
494ead1f93eSLiane Praza        for path, fileinfo in newentries.iteritems():
495ead1f93eSLiane Praza            if fileinfo.hardkey:
496ead1f93eSLiane Praza                hk2path.setdefault(fileinfo.hardkey, set()).add(path)
497ead1f93eSLiane Praza        for fileinfo in newentries.itervalues():
498ead1f93eSLiane Praza            if fileinfo.hardkey:
499ead1f93eSLiane Praza                fileinfo.hardpaths.update(hk2path[fileinfo.hardkey])
500ead1f93eSLiane Praza        self.update(newentries)
501ead1f93eSLiane Praza
502ead1f93eSLiane Praza    def addprotolist(self, protolist, exceptions):
503ead1f93eSLiane Praza        """Read in the specified file, assumed to be the
504ead1f93eSLiane Praza        output of protolist.
505ead1f93eSLiane Praza
506ead1f93eSLiane Praza        This has been tested minimally, and is potentially useful for
507ead1f93eSLiane Praza        comparing across the transition period, but should ultimately
508ead1f93eSLiane Praza        go away.
509ead1f93eSLiane Praza        """
510ead1f93eSLiane Praza
511ead1f93eSLiane Praza        try:
512ead1f93eSLiane Praza            plist = open(protolist)
513ead1f93eSLiane Praza        except IOError, exc:
514ead1f93eSLiane Praza            raise IOError("cannot open proto list: %s" % str(exc))
515ead1f93eSLiane Praza
516ead1f93eSLiane Praza        newentries = {}
517ead1f93eSLiane Praza
518ead1f93eSLiane Praza        for pline in plist:
519ead1f93eSLiane Praza            pline = pline.split()
520ead1f93eSLiane Praza            #
521ead1f93eSLiane Praza            # Use a FileInfo() object instead of a RealFileInfo()
522ead1f93eSLiane Praza            # object because we want to avoid the RealFileInfo
523ead1f93eSLiane Praza            # constructor, because there's nothing to actually stat().
524ead1f93eSLiane Praza            #
525ead1f93eSLiane Praza            fileinfo = FileInfo()
526ead1f93eSLiane Praza            try:
527ead1f93eSLiane Praza                if pline[1] in exceptions:
528ead1f93eSLiane Praza                    exceptions.remove(pline[1])
529ead1f93eSLiane Praza                    continue
530ead1f93eSLiane Praza                if pline[0] == "d":
531ead1f93eSLiane Praza                    fileinfo.isdir = True
532ead1f93eSLiane Praza                fileinfo.path = pline[1]
533ead1f93eSLiane Praza                if pline[2] != "-":
534ead1f93eSLiane Praza                    fileinfo.target = os.path.normpath(pline[2])
535ead1f93eSLiane Praza                fileinfo.mode = int("0%s" % pline[3])
536ead1f93eSLiane Praza                fileinfo.owner = pline[4]
537ead1f93eSLiane Praza                fileinfo.group = pline[5]
538ead1f93eSLiane Praza                if pline[6] != "0":
539ead1f93eSLiane Praza                    fileinfo.hardkey = pline[6]
540ead1f93eSLiane Praza                newentries[pline[1]] = fileinfo
541ead1f93eSLiane Praza            except IndexError:
542ead1f93eSLiane Praza                raise BadProtolistFormat(pline)
543ead1f93eSLiane Praza
544ead1f93eSLiane Praza        plist.close()
545ead1f93eSLiane Praza        hk2path = {}
546ead1f93eSLiane Praza        for path, fileinfo in newentries.iteritems():
547ead1f93eSLiane Praza            if fileinfo.hardkey:
548ead1f93eSLiane Praza                hk2path.setdefault(fileinfo.hardkey, set()).add(path)
549ead1f93eSLiane Praza        for fileinfo in newentries.itervalues():
550ead1f93eSLiane Praza            if fileinfo.hardkey:
551ead1f93eSLiane Praza                fileinfo.hardpaths.update(hk2path[fileinfo.hardkey])
552ead1f93eSLiane Praza        self.update(newentries)
553ead1f93eSLiane Praza
554ead1f93eSLiane Praza
555ead1f93eSLiane Prazaclass ManifestParsingError(Exception):
556ead1f93eSLiane Praza    """This means that the Manifest.set_content() raised an
557ead1f93eSLiane Praza    ActionError.  We raise this, instead, to tell us which manifest
558ead1f93eSLiane Praza    could not be parsed, rather than what action error we hit.
559ead1f93eSLiane Praza    """
560ead1f93eSLiane Praza    def __init__(self, mfile, error):
561ead1f93eSLiane Praza        Exception.__init__(self)
562ead1f93eSLiane Praza        self.mfile = mfile
563ead1f93eSLiane Praza        self.error = error
564ead1f93eSLiane Praza
565ead1f93eSLiane Praza    def __str__(self):
566ead1f93eSLiane Praza        return "unable to parse manifest %s: %s" % (self.mfile, self.error)
567ead1f93eSLiane Praza
568ead1f93eSLiane Praza
569ead1f93eSLiane Prazaclass ManifestTree(DirectoryTree):
570ead1f93eSLiane Praza    """Describes one or more directories containing arbitrarily
571ead1f93eSLiane Praza    many manifests as a dictionary of ActionInfo objects, indexed
572ead1f93eSLiane Praza    by the relative path of the data source within the proto area.
573ead1f93eSLiane Praza    That path may or may not be the same as the path attribute of the
574ead1f93eSLiane Praza    given action.
575ead1f93eSLiane Praza    """
576ead1f93eSLiane Praza
577ead1f93eSLiane Praza    def addmanifest(self, root, mfile, arch, modechecks, exceptions):
578ead1f93eSLiane Praza        """Treats the specified input file as a pkg(5) package
579ead1f93eSLiane Praza        manifest, and extends the ManifestTree dictionary with entries
580ead1f93eSLiane Praza        for the actions therein.
581ead1f93eSLiane Praza        """
582ead1f93eSLiane Praza        mfest = manifest.Manifest()
583ead1f93eSLiane Praza        try:
584ead1f93eSLiane Praza            mfest.set_content(open(os.path.join(root, mfile)).read())
585ead1f93eSLiane Praza        except IOError, exc:
586ead1f93eSLiane Praza            raise IOError("cannot read manifest: %s" % str(exc))
587ead1f93eSLiane Praza        except actions.ActionError, exc:
588ead1f93eSLiane Praza            raise ManifestParsingError(mfile, str(exc))
589ead1f93eSLiane Praza
590ead1f93eSLiane Praza        #
591ead1f93eSLiane Praza        # Make sure the manifest is applicable to the user-specified
592ead1f93eSLiane Praza        # architecture.  Assumption: if variant.arch is not an
593ead1f93eSLiane Praza        # attribute of the manifest, then the package should be
594ead1f93eSLiane Praza        # installed on all architectures.
595ead1f93eSLiane Praza        #
596ead1f93eSLiane Praza        if arch not in mfest.attributes.get("variant.arch", (arch,)):
597ead1f93eSLiane Praza            return
598ead1f93eSLiane Praza
599ead1f93eSLiane Praza        modewarnings = set()
600ead1f93eSLiane Praza        for action in mfest.gen_actions():
601ead1f93eSLiane Praza            if "path" not in action.attrs or \
602ead1f93eSLiane Praza                not ActionInfo.supported(action.name):
603ead1f93eSLiane Praza                continue
604ead1f93eSLiane Praza
605ead1f93eSLiane Praza            #
606ead1f93eSLiane Praza            # The dir action is currently fully specified, in that it
607ead1f93eSLiane Praza            # lists owner, group, and mode attributes.  If that
608ead1f93eSLiane Praza            # changes in pkg(5) code, we'll need to revisit either this
609ead1f93eSLiane Praza            # code or the ActionInfo() constructor.  It's possible
610ead1f93eSLiane Praza            # that the pkg(5) system could be extended to provide a
611ead1f93eSLiane Praza            # mechanism for specifying directory permissions outside
612ead1f93eSLiane Praza            # of the individual manifests that deliver files into
613ead1f93eSLiane Praza            # those directories.  Doing so at time of manifest
614ead1f93eSLiane Praza            # processing would mean that validate_pkg continues to work,
615ead1f93eSLiane Praza            # but doing so at time of publication would require updates.
616ead1f93eSLiane Praza            #
617ead1f93eSLiane Praza
618ead1f93eSLiane Praza            #
619ead1f93eSLiane Praza            # See pkgsend(1) for the use of NOHASH for objects with
620ead1f93eSLiane Praza            # datastreams.  Currently, that means "files," but this
621ead1f93eSLiane Praza            # should work for any other such actions.
622ead1f93eSLiane Praza            #
623ead1f93eSLiane Praza            if getattr(action, "hash", "NOHASH") != "NOHASH":
624ead1f93eSLiane Praza                path = action.hash
625ead1f93eSLiane Praza            else:
626ead1f93eSLiane Praza                path = action.attrs["path"]
627ead1f93eSLiane Praza
628ead1f93eSLiane Praza            #
629ead1f93eSLiane Praza            # This is the wrong tool in which to enforce consistency
630ead1f93eSLiane Praza            # on a set of manifests.  So instead of comparing the
631ead1f93eSLiane Praza            # different actions with the same "path" attribute, we
632ead1f93eSLiane Praza            # use the first one.
633ead1f93eSLiane Praza            #
634ead1f93eSLiane Praza            if path in self:
635ead1f93eSLiane Praza                continue
636ead1f93eSLiane Praza
637ead1f93eSLiane Praza            #
638ead1f93eSLiane Praza            # As with the manifest itself, if an action has specified
639ead1f93eSLiane Praza            # variant.arch, we look for the target architecture
640ead1f93eSLiane Praza            # therein.
641ead1f93eSLiane Praza            #
642fe89515eSRichard Lowe            var = None
643fe89515eSRichard Lowe
644fe89515eSRichard Lowe            #
645fe89515eSRichard Lowe            # The name of this method changed in pkg(5) build 150, we need to
646fe89515eSRichard Lowe            # work with both sets.
647fe89515eSRichard Lowe            #
648fe89515eSRichard Lowe            if hasattr(action, 'get_variants'):
649ead1f93eSLiane Praza                var = action.get_variants()
650fe89515eSRichard Lowe            else:
651fe89515eSRichard Lowe                var = action.get_variant_template()
652ead1f93eSLiane Praza            if "variant.arch" in var and arch not in var["variant.arch"]:
653ead1f93eSLiane Praza                return
654ead1f93eSLiane Praza
655ead1f93eSLiane Praza            self[path] = ActionInfo(action)
656ead1f93eSLiane Praza            if modechecks is not None and path not in exceptions:
657ead1f93eSLiane Praza                modewarnings.update(self[path].checkmodes(modechecks))
658ead1f93eSLiane Praza
659ead1f93eSLiane Praza        if len(modewarnings) > 0:
660ead1f93eSLiane Praza            print "warning: unsafe permissions in %s" % mfile
661ead1f93eSLiane Praza            for w in sorted(modewarnings):
662ead1f93eSLiane Praza                print w
663ead1f93eSLiane Praza            print ""
664ead1f93eSLiane Praza
665ead1f93eSLiane Praza    def adddir(self, mdir, arch, modechecks, exceptions):
666ead1f93eSLiane Praza        """Walks the specified directory looking for pkg(5) manifests.
667ead1f93eSLiane Praza        """
668ead1f93eSLiane Praza        for mfile in os.listdir(mdir):
669ead1f93eSLiane Praza            if (mfile.endswith(".mog") and
670ead1f93eSLiane Praza                stat.S_ISREG(os.lstat(os.path.join(mdir, mfile)).st_mode)):
671ead1f93eSLiane Praza                try:
672ead1f93eSLiane Praza                    self.addmanifest(mdir, mfile, arch, modechecks, exceptions)
673ead1f93eSLiane Praza                except IOError, exc:
674ead1f93eSLiane Praza                    sys.stderr.write("warning: %s\n" % str(exc))
675ead1f93eSLiane Praza
676ead1f93eSLiane Praza    def resolvehardlinks(self):
677ead1f93eSLiane Praza        """Populates mode, group, and owner for resolved (ie link target
678ead1f93eSLiane Praza        is present in the manifest tree) hard links.
679ead1f93eSLiane Praza        """
680ead1f93eSLiane Praza        for info in self.values():
681ead1f93eSLiane Praza            if info.name() == "hardlink":
682ead1f93eSLiane Praza                tgt = info.hardkey
683ead1f93eSLiane Praza                if tgt in self:
684ead1f93eSLiane Praza                    tgtinfo = self[tgt]
685ead1f93eSLiane Praza                    info.owner = tgtinfo.owner
686ead1f93eSLiane Praza                    info.group = tgtinfo.group
687ead1f93eSLiane Praza                    info.mode = tgtinfo.mode
688ead1f93eSLiane Praza
689ead1f93eSLiane Prazaclass ExceptionList(set):
690ead1f93eSLiane Praza    """Keep track of an exception list as a set of paths to be excluded
691ead1f93eSLiane Praza    from any other lists we build.
692ead1f93eSLiane Praza    """
693ead1f93eSLiane Praza
694ead1f93eSLiane Praza    def __init__(self, files, arch):
695ead1f93eSLiane Praza        set.__init__(self)
696ead1f93eSLiane Praza        for fname in files:
697ead1f93eSLiane Praza            try:
698ead1f93eSLiane Praza                self.readexceptionfile(fname, arch)
699ead1f93eSLiane Praza            except IOError, exc:
700ead1f93eSLiane Praza                sys.stderr.write("warning: cannot read exception file: %s\n" %
701ead1f93eSLiane Praza                    str(exc))
702ead1f93eSLiane Praza
703ead1f93eSLiane Praza    def readexceptionfile(self, efile, arch):
704ead1f93eSLiane Praza        """Build a list of all pathnames from the specified file that
705ead1f93eSLiane Praza        either apply to all architectures (ie which have no trailing
706ead1f93eSLiane Praza        architecture tokens), or to the specified architecture (ie
707ead1f93eSLiane Praza        which have the value of the arch arg as a trailing
708ead1f93eSLiane Praza        architecture token.)
709ead1f93eSLiane Praza        """
710ead1f93eSLiane Praza
711ead1f93eSLiane Praza        excfile = open(efile)
712ead1f93eSLiane Praza
713ead1f93eSLiane Praza        for exc in excfile:
714ead1f93eSLiane Praza            exc = exc.split()
715ead1f93eSLiane Praza            if len(exc) and exc[0][0] != "#":
716ead1f93eSLiane Praza                if arch in (exc[1:] or arch):
717ead1f93eSLiane Praza                    self.add(os.path.normpath(exc[0]))
718ead1f93eSLiane Praza
719ead1f93eSLiane Praza        excfile.close()
720ead1f93eSLiane Praza
721ead1f93eSLiane Praza
722ead1f93eSLiane PrazaUSAGE = """%s [-v] -a arch [-e exceptionfile]... [-L|-M [-X check]...] input_1 [input_2]
723ead1f93eSLiane Praza
724ead1f93eSLiane Prazawhere input_1 and input_2 may specify proto lists, proto areas,
725ead1f93eSLiane Prazaor manifest directories.  For proto lists, use one or more
726ead1f93eSLiane Praza
727ead1f93eSLiane Praza    -l file
728ead1f93eSLiane Praza
729ead1f93eSLiane Prazaarguments.  For proto areas, use one or more
730ead1f93eSLiane Praza
731ead1f93eSLiane Praza    -p dir
732ead1f93eSLiane Praza
733ead1f93eSLiane Prazaarguments.  For manifest directories, use one or more
734ead1f93eSLiane Praza
735ead1f93eSLiane Praza    -m dir
736ead1f93eSLiane Praza
737ead1f93eSLiane Prazaarguments.
738ead1f93eSLiane Praza
739ead1f93eSLiane PrazaIf -L or -M is specified, then only one input source is allowed, and
740ead1f93eSLiane Prazait should be one or more manifest directories.  These two options are
741ead1f93eSLiane Prazamutually exclusive.
742ead1f93eSLiane Praza
743ead1f93eSLiane PrazaThe -L option is used to generate a proto list to stdout.
744ead1f93eSLiane Praza
745ead1f93eSLiane PrazaThe -M option is used to check for safe file and directory modes.
746ead1f93eSLiane PrazaBy default, this causes all mode checks to be performed.  Individual
747ead1f93eSLiane Prazamode checks may be turned off using "-X check," where "check" comes
748ead1f93eSLiane Prazafrom the following set of checks:
749ead1f93eSLiane Praza
750ead1f93eSLiane Praza    m   check for group or other write permissions
751ead1f93eSLiane Praza    w   check for user write permissions on files and directories
752ead1f93eSLiane Praza        not owned by root
753ead1f93eSLiane Praza    s   check for group/other read permission on executable files
754ead1f93eSLiane Praza        that have setuid/setgid bit(s)
755ead1f93eSLiane Praza    o   check for files that could be safely owned by root
756ead1f93eSLiane Praza""" % sys.argv[0]
757ead1f93eSLiane Praza
758ead1f93eSLiane Praza
759ead1f93eSLiane Prazadef usage(msg=None):
760ead1f93eSLiane Praza    """Try to give the user useful information when they don't get the
761ead1f93eSLiane Praza    command syntax right.
762ead1f93eSLiane Praza    """
763ead1f93eSLiane Praza    if msg:
764ead1f93eSLiane Praza        sys.stderr.write("%s: %s\n" % (sys.argv[0], msg))
765ead1f93eSLiane Praza    sys.stderr.write(USAGE)
766ead1f93eSLiane Praza    sys.exit(2)
767ead1f93eSLiane Praza
768ead1f93eSLiane Praza
769ead1f93eSLiane Prazadef main(argv):
770ead1f93eSLiane Praza    """Compares two out of three possible data sources: a proto list, a
771ead1f93eSLiane Praza    set of proto areas, and a set of manifests.
772ead1f93eSLiane Praza    """
773ead1f93eSLiane Praza    try:
774ead1f93eSLiane Praza        opts, args = getopt.getopt(argv, 'a:e:Ll:Mm:p:vX:')
775ead1f93eSLiane Praza    except getopt.GetoptError, exc:
776ead1f93eSLiane Praza        usage(str(exc))
777ead1f93eSLiane Praza
778ead1f93eSLiane Praza    if args:
779ead1f93eSLiane Praza        usage()
780ead1f93eSLiane Praza
781ead1f93eSLiane Praza    arch = None
782ead1f93eSLiane Praza    exceptionlists = []
783ead1f93eSLiane Praza    listonly = False
784ead1f93eSLiane Praza    manifestdirs = []
785ead1f93eSLiane Praza    manifesttree = ManifestTree("manifests")
786ead1f93eSLiane Praza    protodirs = []
787ead1f93eSLiane Praza    prototree = ProtoTree("proto area")
788ead1f93eSLiane Praza    protolists = []
789ead1f93eSLiane Praza    protolist = ProtoTree("proto list")
790ead1f93eSLiane Praza    modechecks = set()
791ead1f93eSLiane Praza    togglemodechecks = set()
792ead1f93eSLiane Praza    trees = []
793ead1f93eSLiane Praza    comparing = set()
794ead1f93eSLiane Praza    verbose = False
795ead1f93eSLiane Praza
796ead1f93eSLiane Praza    for opt, arg in opts:
797ead1f93eSLiane Praza        if opt == "-a":
798ead1f93eSLiane Praza            if arch:
799ead1f93eSLiane Praza                usage("may only specify one architecture")
800ead1f93eSLiane Praza            else:
801ead1f93eSLiane Praza                arch = arg
802ead1f93eSLiane Praza        elif opt == "-e":
803ead1f93eSLiane Praza            exceptionlists.append(arg)
804ead1f93eSLiane Praza        elif opt == "-L":
805ead1f93eSLiane Praza            listonly = True
806ead1f93eSLiane Praza        elif opt == "-l":
807ead1f93eSLiane Praza            comparing.add("protolist")
808ead1f93eSLiane Praza            protolists.append(os.path.normpath(arg))
809ead1f93eSLiane Praza        elif opt == "-M":
810ead1f93eSLiane Praza            modechecks.update(DEFAULTMODECHECKS)
811ead1f93eSLiane Praza        elif opt == "-m":
812ead1f93eSLiane Praza            comparing.add("manifests")
813ead1f93eSLiane Praza            manifestdirs.append(os.path.normpath(arg))
814ead1f93eSLiane Praza        elif opt == "-p":
815ead1f93eSLiane Praza            comparing.add("proto area")
816ead1f93eSLiane Praza            protodirs.append(os.path.normpath(arg))
817ead1f93eSLiane Praza        elif opt == "-v":
818ead1f93eSLiane Praza            verbose = True
819ead1f93eSLiane Praza        elif opt == "-X":
820ead1f93eSLiane Praza            togglemodechecks.add(arg)
821ead1f93eSLiane Praza
822ead1f93eSLiane Praza    if listonly or len(modechecks) > 0:
823ead1f93eSLiane Praza        if len(comparing) != 1 or "manifests" not in comparing:
824ead1f93eSLiane Praza            usage("-L and -M require one or more -m args, and no -l or -p")
825ead1f93eSLiane Praza        if listonly and len(modechecks) > 0:
826ead1f93eSLiane Praza            usage("-L and -M are mutually exclusive")
827ead1f93eSLiane Praza    elif len(comparing) != 2:
828ead1f93eSLiane Praza        usage("must specify exactly two of -l, -m, and -p")
829ead1f93eSLiane Praza
830ead1f93eSLiane Praza    if len(togglemodechecks) > 0 and len(modechecks) == 0:
831ead1f93eSLiane Praza        usage("-X requires -M")
832ead1f93eSLiane Praza
833ead1f93eSLiane Praza    for s in togglemodechecks:
834ead1f93eSLiane Praza        if s not in ALLMODECHECKS:
835ead1f93eSLiane Praza            usage("unknown mode check %s" % s)
836ead1f93eSLiane Praza        modechecks.symmetric_difference_update((s))
837ead1f93eSLiane Praza
838ead1f93eSLiane Praza    if len(modechecks) == 0:
839ead1f93eSLiane Praza        modechecks = None
840ead1f93eSLiane Praza
841ead1f93eSLiane Praza    if not arch:
842ead1f93eSLiane Praza        usage("must specify architecture")
843ead1f93eSLiane Praza
844ead1f93eSLiane Praza    exceptions = ExceptionList(exceptionlists, arch)
845ead1f93eSLiane Praza    originalexceptions = exceptions.copy()
846ead1f93eSLiane Praza
847ead1f93eSLiane Praza    if len(manifestdirs) > 0:
848ead1f93eSLiane Praza        for mdir in manifestdirs:
849ead1f93eSLiane Praza            manifesttree.adddir(mdir, arch, modechecks, exceptions)
850ead1f93eSLiane Praza        if listonly:
851ead1f93eSLiane Praza            manifesttree.resolvehardlinks()
852ead1f93eSLiane Praza            for info in manifesttree.values():
853ead1f93eSLiane Praza                print "%s" % info.protostr()
854ead1f93eSLiane Praza            sys.exit(0)
855ead1f93eSLiane Praza        if modechecks is not None:
856ead1f93eSLiane Praza            sys.exit(0)
857ead1f93eSLiane Praza        trees.append(manifesttree)
858ead1f93eSLiane Praza
859ead1f93eSLiane Praza    if len(protodirs) > 0:
860ead1f93eSLiane Praza        for pdir in protodirs:
861ead1f93eSLiane Praza            prototree.adddir(pdir, exceptions)
862ead1f93eSLiane Praza        trees.append(prototree)
863ead1f93eSLiane Praza
864ead1f93eSLiane Praza    if len(protolists) > 0:
865ead1f93eSLiane Praza        for plist in protolists:
866ead1f93eSLiane Praza            try:
867ead1f93eSLiane Praza                protolist.addprotolist(plist, exceptions)
868ead1f93eSLiane Praza            except IOError, exc:
869ead1f93eSLiane Praza                sys.stderr.write("warning: %s\n" % str(exc))
870ead1f93eSLiane Praza        trees.append(protolist)
871ead1f93eSLiane Praza
872ead1f93eSLiane Praza    if verbose and exceptions:
873ead1f93eSLiane Praza        print "Entries present in exception list but missing from proto area:"
874ead1f93eSLiane Praza        for exc in sorted(exceptions):
875ead1f93eSLiane Praza            print "\t%s" % exc
876ead1f93eSLiane Praza        print ""
877ead1f93eSLiane Praza
878ead1f93eSLiane Praza    usedexceptions = originalexceptions.difference(exceptions)
879ead1f93eSLiane Praza    harmfulexceptions = usedexceptions.intersection(manifesttree)
880ead1f93eSLiane Praza    if harmfulexceptions:
881ead1f93eSLiane Praza        print "Entries present in exception list but also in manifests:"
882ead1f93eSLiane Praza        for exc in sorted(harmfulexceptions):
883ead1f93eSLiane Praza            print "\t%s" % exc
884ead1f93eSLiane Praza            del manifesttree[exc]
885ead1f93eSLiane Praza        print ""
886ead1f93eSLiane Praza
887ead1f93eSLiane Praza    trees[0].compare(trees[1])
888ead1f93eSLiane Praza
889ead1f93eSLiane Prazaif __name__ == '__main__':
890ead1f93eSLiane Praza    try:
891ead1f93eSLiane Praza        main(sys.argv[1:])
892ead1f93eSLiane Praza    except KeyboardInterrupt:
893ead1f93eSLiane Praza        sys.exit(1)
894ead1f93eSLiane Praza    except IOError:
895ead1f93eSLiane Praza        sys.exit(1)
896