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