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*3be32116SAlexander 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*3be32116SAlexander Pyhalov # For mediated symlinks targets can differ. 201ead1f93eSLiane Praza # 202ead1f93eSLiane Praza if typelhs == "link": 203*3be32116SAlexander 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*3be32116SAlexander 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*3be32116SAlexander 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