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