Print this page
5189 validate_pkg should support mediated links
Reviewed by: Richard Lowe <richlowe@richlowe.net>
Reviewed by: Josef 'Jeff' Sipek <josef.sipek@nexenta.com>


  43 #
  44 # Dictionary used to map action names to output format.  Each entry is
  45 # indexed by action name, and consists of a list of tuples that map
  46 # FileInfo class members to output labels.
  47 #
  48 OUTPUTMAP = {
  49     "dir": [
  50         ("group", "group="),
  51         ("mode", "mode="),
  52         ("owner", "owner="),
  53         ("path", "path=")
  54     ],
  55     "file": [
  56         ("hash", ""),
  57         ("group", "group="),
  58         ("mode", "mode="),
  59         ("owner", "owner="),
  60         ("path", "path=")
  61     ],
  62     "link": [

  63         ("path", "path="),
  64         ("target", "target=")
  65     ],
  66     "hardlink": [
  67         ("path", "path="),
  68         ("hardkey", "target=")
  69     ],
  70 }
  71 
  72 # Mode checks used to validate safe file and directory permissions
  73 ALLMODECHECKS = frozenset(("m", "w", "s", "o"))
  74 DEFAULTMODECHECKS = frozenset(("m", "w", "o"))
  75 
  76 class FileInfo(object):
  77     """Base class to represent a file.
  78 
  79     Subclassed according to whether the file represents an actual filesystem
  80     object (RealFileInfo) or an IPS manifest action (ActionInfo).
  81     """
  82 


 179         #
 180         # Because the manifest may legitimately translate a relative
 181         # path from the proto area into a different path on the installed
 182         # system, we don't compare paths here.  We only expect this comparison
 183         # to be invoked on items with identical relative paths in
 184         # first place.
 185         #
 186 
 187         #
 188         # All comparisons depend on type.  For symlink and directory, they
 189         # must be the same.  For file and hardlink, see below.
 190         #
 191         typelhs = lhs.name()
 192         typerhs = rhs.name()
 193         if typelhs in ("link", "dir"):
 194             if typelhs != typerhs:
 195                 return True
 196 
 197         #
 198         # For symlinks, all that's left is the link target.

 199         #
 200         if typelhs == "link":
 201             return lhs.target != rhs.target
 202 
 203         #
 204         # For a directory, it's important that both be directories,
 205         # the modes be identical, and the paths are identical.  We already
 206         # checked all but the modes above.
 207         #
 208         # If both objects are files, then we're in the same boat.
 209         #
 210         if typelhs == "dir" or (typelhs == "file" and typerhs == "file"):
 211             return lhs.mode != rhs.mode
 212 
 213         #
 214         # For files or hardlinks:
 215         #
 216         # Since the key space is different (inodes for real files and
 217         # actual link targets for hard links), and since the proto area will
 218         # identify all N occurrences as hardlinks, but the manifests as one
 219         # file and N-1 hardlinks, we have to compare files to hardlinks.
 220         #
 221 


 289 
 290     def __init__(self, action):
 291         FileInfo.__init__(self)
 292         #
 293         # Currently, all actions that we support have a "path"
 294         # attribute.  If that changes, then we'll need to
 295         # catch a KeyError from this assignment.
 296         #
 297         self.path = action.attrs["path"]
 298 
 299         if action.name == "file":
 300             self.owner = action.attrs["owner"]
 301             self.group = action.attrs["group"]
 302             self.mode = action.attrs["mode"]
 303             self.hash = action.hash
 304             if "preserve" in action.attrs:
 305                 self.editable = True
 306         elif action.name == "link":
 307             target = action.attrs["target"]
 308             self.target = os.path.normpath(target)

 309         elif action.name == "dir":
 310             self.owner = action.attrs["owner"]
 311             self.group = action.attrs["group"]
 312             self.mode = action.attrs["mode"]
 313             self.isdir = True
 314         elif action.name == "hardlink":
 315             target = os.path.normpath(action.get_target_path())
 316             self.hardkey = target
 317             self.hardpaths.add(target)
 318 
 319     @staticmethod
 320     def supported(action):
 321         """Indicates whether the specified IPS action time is
 322         correctly handled by the ActionInfo constructor.
 323         """
 324         return action in frozenset(("file", "dir", "link", "hardlink"))
 325 
 326 
 327 class UnsupportedFileFormatError(Exception):
 328     """This means that the stat.S_IFMT returned something we don't


 349     is no way to determine which of the hard links should be
 350     delivered as a file, and which as hardlinks.
 351     """
 352 
 353     def __init__(self, root=None, path=None):
 354         FileInfo.__init__(self)
 355         self.path = path
 356         path = os.path.join(root, path)
 357         lstat = os.lstat(path)
 358         mode = lstat.st_mode
 359 
 360         #
 361         # Per stat.py, these cases are mutually exclusive.
 362         #
 363         if stat.S_ISREG(mode):
 364             self.hash = self.path
 365         elif stat.S_ISDIR(mode):
 366             self.isdir = True
 367         elif stat.S_ISLNK(mode):
 368             self.target = os.path.normpath(os.readlink(path))

 369         else:
 370             raise UnsupportedFileFormatError(path, mode)
 371 
 372         if not stat.S_ISLNK(mode):
 373             self.mode = "%04o" % stat.S_IMODE(mode)
 374             #
 375             # Instead of reading the group and owner from the proto area after
 376             # a non-root build, just drop in dummy values.  Since we don't
 377             # compare them anywhere, this should allow at least marginally
 378             # useful comparisons of protolist-style output.
 379             #
 380             self.owner = "owner"
 381             self.group = "group"
 382 
 383         #
 384         # refcount > 1 indicates a hard link
 385         #
 386         if lstat.st_nlink > 1:
 387             #
 388             # This could get ugly if multiple proto areas reside




  43 #
  44 # Dictionary used to map action names to output format.  Each entry is
  45 # indexed by action name, and consists of a list of tuples that map
  46 # FileInfo class members to output labels.
  47 #
  48 OUTPUTMAP = {
  49     "dir": [
  50         ("group", "group="),
  51         ("mode", "mode="),
  52         ("owner", "owner="),
  53         ("path", "path=")
  54     ],
  55     "file": [
  56         ("hash", ""),
  57         ("group", "group="),
  58         ("mode", "mode="),
  59         ("owner", "owner="),
  60         ("path", "path=")
  61     ],
  62     "link": [
  63         ("mediator", "mediator="),
  64         ("path", "path="),
  65         ("target", "target=")
  66     ],
  67     "hardlink": [
  68         ("path", "path="),
  69         ("hardkey", "target=")
  70     ],
  71 }
  72 
  73 # Mode checks used to validate safe file and directory permissions
  74 ALLMODECHECKS = frozenset(("m", "w", "s", "o"))
  75 DEFAULTMODECHECKS = frozenset(("m", "w", "o"))
  76 
  77 class FileInfo(object):
  78     """Base class to represent a file.
  79 
  80     Subclassed according to whether the file represents an actual filesystem
  81     object (RealFileInfo) or an IPS manifest action (ActionInfo).
  82     """
  83 


 180         #
 181         # Because the manifest may legitimately translate a relative
 182         # path from the proto area into a different path on the installed
 183         # system, we don't compare paths here.  We only expect this comparison
 184         # to be invoked on items with identical relative paths in
 185         # first place.
 186         #
 187 
 188         #
 189         # All comparisons depend on type.  For symlink and directory, they
 190         # must be the same.  For file and hardlink, see below.
 191         #
 192         typelhs = lhs.name()
 193         typerhs = rhs.name()
 194         if typelhs in ("link", "dir"):
 195             if typelhs != typerhs:
 196                 return True
 197 
 198         #
 199         # For symlinks, all that's left is the link target.
 200         # For mediated symlinks targets can differ.
 201         #
 202         if typelhs == "link":
 203             return (lhs.mediator is None) and (lhs.target != rhs.target)
 204 
 205         #
 206         # For a directory, it's important that both be directories,
 207         # the modes be identical, and the paths are identical.  We already
 208         # checked all but the modes above.
 209         #
 210         # If both objects are files, then we're in the same boat.
 211         #
 212         if typelhs == "dir" or (typelhs == "file" and typerhs == "file"):
 213             return lhs.mode != rhs.mode
 214 
 215         #
 216         # For files or hardlinks:
 217         #
 218         # Since the key space is different (inodes for real files and
 219         # actual link targets for hard links), and since the proto area will
 220         # identify all N occurrences as hardlinks, but the manifests as one
 221         # file and N-1 hardlinks, we have to compare files to hardlinks.
 222         #
 223 


 291 
 292     def __init__(self, action):
 293         FileInfo.__init__(self)
 294         #
 295         # Currently, all actions that we support have a "path"
 296         # attribute.  If that changes, then we'll need to
 297         # catch a KeyError from this assignment.
 298         #
 299         self.path = action.attrs["path"]
 300 
 301         if action.name == "file":
 302             self.owner = action.attrs["owner"]
 303             self.group = action.attrs["group"]
 304             self.mode = action.attrs["mode"]
 305             self.hash = action.hash
 306             if "preserve" in action.attrs:
 307                 self.editable = True
 308         elif action.name == "link":
 309             target = action.attrs["target"]
 310             self.target = os.path.normpath(target)
 311             self.mediator = action.attrs.get("mediator")
 312         elif action.name == "dir":
 313             self.owner = action.attrs["owner"]
 314             self.group = action.attrs["group"]
 315             self.mode = action.attrs["mode"]
 316             self.isdir = True
 317         elif action.name == "hardlink":
 318             target = os.path.normpath(action.get_target_path())
 319             self.hardkey = target
 320             self.hardpaths.add(target)
 321 
 322     @staticmethod
 323     def supported(action):
 324         """Indicates whether the specified IPS action time is
 325         correctly handled by the ActionInfo constructor.
 326         """
 327         return action in frozenset(("file", "dir", "link", "hardlink"))
 328 
 329 
 330 class UnsupportedFileFormatError(Exception):
 331     """This means that the stat.S_IFMT returned something we don't


 352     is no way to determine which of the hard links should be
 353     delivered as a file, and which as hardlinks.
 354     """
 355 
 356     def __init__(self, root=None, path=None):
 357         FileInfo.__init__(self)
 358         self.path = path
 359         path = os.path.join(root, path)
 360         lstat = os.lstat(path)
 361         mode = lstat.st_mode
 362 
 363         #
 364         # Per stat.py, these cases are mutually exclusive.
 365         #
 366         if stat.S_ISREG(mode):
 367             self.hash = self.path
 368         elif stat.S_ISDIR(mode):
 369             self.isdir = True
 370         elif stat.S_ISLNK(mode):
 371             self.target = os.path.normpath(os.readlink(path))
 372             self.mediator = None
 373         else:
 374             raise UnsupportedFileFormatError(path, mode)
 375 
 376         if not stat.S_ISLNK(mode):
 377             self.mode = "%04o" % stat.S_IMODE(mode)
 378             #
 379             # Instead of reading the group and owner from the proto area after
 380             # a non-root build, just drop in dummy values.  Since we don't
 381             # compare them anywhere, this should allow at least marginally
 382             # useful comparisons of protolist-style output.
 383             #
 384             self.owner = "owner"
 385             self.group = "group"
 386 
 387         #
 388         # refcount > 1 indicates a hard link
 389         #
 390         if lstat.st_nlink > 1:
 391             #
 392             # This could get ugly if multiple proto areas reside