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