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