1 #! /usr/bin/python2.6
   2 #
   3 # This file and its contents are supplied under the terms of the
   4 # Common Development and Distribution License ("CDDL"), version 1.0.
   5 # You may only use this file in accordance with the terms of version
   6 # 1.0 of the CDDL.
   7 #
   8 # A full copy of the text of the CDDL should have accompanied this
   9 # source.  A copy of the CDDL is also available via the Internet at
  10 # http://www.illumos.org/license/CDDL.
  11 #
  12 
  13 #
  14 # Copyright 2014 Nexenta Systems, Inc.  All rights reserved.
  15 #
  16 
  17 #
  18 # Convert an ips manifest into Debian package source files.
  19 #
  20 
  21 import optparse
  22 import sys
  23 
  24 from pkg.manifest import Manifest
  25 from pkg.fmri import PkgFmri, IllegalFmri
  26 from os import makedirs, chown, chmod, listdir
  27 from os.path import join, basename, dirname, exists, realpath
  28 from shutil import rmtree, copyfile
  29 from time import strftime
  30 from pwd import getpwnam
  31 from grp import getgrnam
  32 
  33 # 
  34 # These packages depend on sunwcs in our debian world, but this isn't 
  35 # reflected in the manifests provided. This signifies the dependency.
  36 # TODO: Pull into conffile?
  37 #
  38 spec = {'system-file-system-zfs':1,
  39         'system-file-system-zfs-tests':1,
  40         'system-file-system-udfs':1,
  41         'system-floating-point-scrubber':1,
  42         'system-tnf':1,
  43         'diagnostic-cpu-counters':1,
  44         'driver-network-eri':1,
  45         'compatibility-ucb':1,
  46         'diagnostic-powertop':1,
  47         'diagnostic-latencytop':1,
  48         'network-ipfilter':1,
  49         'developer-debug-mdb':1,
  50         'system-extended-system-utilities':1,
  51         'storage-library-network-array':1,
  52         'service-resource-cap':1,
  53         'developer-linker':1,
  54         'developer-dtrace':1,
  55         'driver-network-eri':1}
  56 
  57 # 
  58 # These packages will be ignored.
  59 # TODO: Debian meta packages for consolidations?
  60 #
  61 ign = {'consolidation-osnet-osnet-message-files':1,
  62        'consolidation-osnet-osnet-incorporation':1,
  63        'consolidation-osnet-osnet-redist':1}
  64 
  65 drep, dver, dpri = {}, {}, {}
  66 #
  67 # TODO: Is this better as a single def or a class?
  68 # If this expands to do an any-all-or-none style build, it would probably 
  69 # be better suited as a class definition.
  70 #
  71 class IPSConvert:
  72     """class for converting from ips packages"""
  73     def __init__(self, ips, args):
  74 
  75         self.dir = dirname(ips)
  76         self.name = basename(ips).split('.', 1)[0]
  77         self.args = args
  78         
  79         #
  80         # Suck the manifest into a class structure for parsing
  81         #
  82         self.mf = Manifest()
  83         self.mf.set_content(pathname=ips)
  84         
  85         self.depends = ['${shlibs:Depends}', '${misc:Depends}']
  86         self.post = [] # Holds str lines for pkgname.postinst
  87         self.pre = [] # '' pkgname.preinst
  88         self.rm = [] # '' pkgname.prerm
  89         self.fix = [] # '' pkgname.fixperms
  90         self.svc = {}
  91 
  92         pfmri = self.mf.get('pkg.fmri', None)
  93         self.pkgfmri = PkgFmri(pfmri) if pfmri else None
  94         self.pkgname = None
  95         self.process = False
  96 
  97         # Debian attrs (for pylint)
  98         self.origname = None
  99         self.rep = None
 100         self.pri = None
 101         self.wd = None
 102         self.ver = None
 103         self.arch = None
 104         self.origver = None           
 105 
 106     def initdebpkg(self):
 107         """Initialize the object for conversion to Debian format"""
 108         
 109         #
 110         # Some packages won't have fmri attributes. Old code skipped them, so we
 111         # will, too.
 112         #
 113 
 114         if not self.pkgfmri:
 115             if self.args.v:
 116                 print "%s: No fmri attribute found. Skipping." % (self.name)
 117             return False
 118 
 119         #
 120         # We get our package names from the pkg.fmri attribute after
 121         # a little post processing
 122         #
 123         self.pkgname = self.pkgfmri.pkg_name.replace('/','-').replace('_','-') \
 124             .lower()
 125  
 126         #
 127         # This is mostly taken straight from the perl code.
 128         # There, we skipped any obsoleted or renamed packages, consolidations,
 129         # or packages we specifically mark to be ignored.
 130         #
 131         if self.mf.get('pkg.obsolete', None):
 132             if self.args.v:
 133                 print "%s: Package is obsolete. Ignoring." % (self.pkgname)
 134             return
 135         elif 'consolidation' in self.pkgname:
 136             if self.args.v:
 137                 print "%s: Package is a consolidation. Ignoring." \
 138                     % (self.pkgname)
 139             return
 140         elif ign.get(self.pkgname):
 141             if self.args.v:
 142                 print "%s: Package is marked to be ignored." % (self.pkgname)
 143             return
 144 
 145         if self.mf.get('pkg.renamed', None):
 146             for rdep in self.mf.gen_actions_by_type('depend'):
 147                 try:
 148                     repfmri = PkgFmri(rdep.attrs.get('fmri'))
 149                 except(IllegalFmri) as err:
 150                     if 'build version' in str(err):
 151                         repfmri = PkgFmri(rdep.attrs.get('fmri').split('@')[0])
 152                     else:
 153                         raise
 154                 rname = repfmri.pkg_name.replace('/','-') \
 155                     .replace('_','-').lower()
 156                 if rname == "consolidation-osnet-osnet-incorporation":
 157                     continue
 158                 reps = drep.get(rname) or []
 159                 if self.pkgname not in reps:
 160                     reps.append(self.pkgname)
 161                     drep[rname] = reps
 162 
 163             return
 164 
 165         self.process = True
 166 
 167     def gendebpkg(self):
 168         """Generate a debian style package source from an ips manifest"""
 169  
 170         #
 171         # origver is XBS-ORIGINAL-VERSION, taken from part of 
 172         # the fmri's version string. 
 173         # origname is the debian "Provides" field. It comes from
 174         # everything after the last '/' in the fmri.
 175         #
 176         self.origver = self.args.origver or str(self.pkgfmri.version.release)
 177         self.origname = self.pkgfmri.pkg_name.rsplit('/', 1)[-1].lower()
 178 
 179         self.arch = self.mf.get('variant.arch', None)
 180         if (type(self.arch) == list and len(self.arch) != 0):
 181             self.arch = self.arch[0]
 182         self.arch = 'solaris-%s' % (self.arch or 'i386')
 183 
 184         #
 185         # Get the debian Replaces, Priorities, and Version fields, either from
 186         # CLI or ips2deb conf files.
 187         #
 188         self.rep = self.args.rep or ', '.join(drep.get(self.pkgname) or [])
 189         self.pri = self.args.pri or dpri.get(self.pkgname, [''])[0]
 190         self.ver = self.args.ver or dver.get(self.pkgname, [''])[0]
 191 
 192         # Dependencies
 193         
 194         #
 195         # Some packages were set to be dependent on sunwcs, even though it's not
 196         # directly listed in the manifest. We replicate that behavior. here.
 197         # There's other extra behavior when we generate sunwcs later on.
 198         # TODO: perhaps find a better way to handle this.
 199         #
 200         if self.args.spec or spec.get(self.pkgname):
 201             self.depends.append('sunwcs')
 202 
 203         for dep in self.mf.gen_actions_by_type('depend'):
 204             fmri = dep.attrs.get('fmri')
 205 
 206             #
 207             # Ignore dependencies we don't care about.
 208             # TODO: there has to be a cleaner way to do this...
 209             # TODO2: if this is 'cleaner', figure out which of these are
 210             # whole-string matches and clean up the REGEX.
 211             #
 212             if not fmri or dep.attrs['type'] != 'require':
 213                 continue
 214             try:
 215                 dfmri = PkgFmri(fmri)
 216             except(IllegalFmri) as err:
 217                 if 'build version' in str(err):
 218                     dfmri = PkgFmri(fmri.split('@')[0])
 219                 else:
 220                     raise
 221             if dfmri.is_name_match(
 222                 '.*__TBD.*|.*consolidation.*|.*release/name.*|'
 223                 '.*compatibility-packages-sunwxwplt.*|.*runtime/perl.*'):
 224                 continue
 225             
 226             #
 227             # Same deal as pkgfmri.
 228             #
 229             depstr = dfmri.pkg_name.replace('/','-').replace('_','-').lower()
 230 
 231             if dep.attrs.get('pkg.debug.depend.file') and self.ver:
 232                 if "driver-serial-usbsksp-usbs49-fw" in depstr or \
 233                     depstr in self.depends:
 234                     #
 235                     # the perl code filters out "sunwcsd" as a possible
 236                     # source of dependencies. Apparently, that's handled
 237                     # elsewhere in the new process, because removing the check
 238                     # for SUNWcsd here doesn't change anything, and we don't
 239                     # miss any dependencies without it.
 240                     #
 241                     continue
 242                 depstr += ' (>= %s)' % (self.ver)
 243 
 244             self.depends.append(depstr)
 245         #
 246         # Get directory name for holding output, based either on CLI args or
 247         # The path to the file we're reading from
 248         #
 249         self.wd = join(self.args.wd or self.dir,
 250                        self.name) 
 251         #
 252         # Create the output directories, removing it if it already exists.
 253         #
 254         if exists(self.wd):
 255             rmtree(self.wd)
 256         makedirs(join(self.wd, 'debian'))
 257 
 258         bwd = join(self.wd, 'debian', self.pkgname)
 259 
 260         #
 261         # Create the directories for every dir action in the manifest.
 262         # Also adds related commands to package files.
 263         #
 264         for d in self.mf.gen_actions_by_type('dir'):
 265             pth = d.attrs['path']
 266             dest = join(bwd, pth)
 267             if not exists(dest):
 268                 makedirs(dest, int(d.attrs['mode'], 8))
 269 
 270             #
 271             # Solaris, by default, won't let you chown a file as non-root.
 272             # However, we still want to build the package if we can't 
 273             # chown files.
 274             #
 275             try:
 276                 chown(dest, getpwnam(d.attrs['owner']).pw_uid, 
 277                       getgrnam(d.attrs['group']).gr_gid)
 278             except(OSError) as err:
 279                 if 'Not owner' in str(err):
 280                     pass
 281                 else:
 282                     raise
 283             self.fix.append("chmod %s $DEST/%s" % (d.attrs['mode'], pth))
 284             self.fix.append("chown %s:%s $DEST/%s" % (d.attrs['owner'], 
 285                                                 d.attrs['group'], pth))
 286             
 287 
 288         #
 289         # Copy all of the files from the file actions into their appropriate
 290         # directories. Also adds related commands to package files.
 291         #
 292         if self.pkgname == 'sunwcs' and self.mf.actions_bytype.get('file'):
 293             self.post.append(
 294 """cp -f $BASEDIR/usr/bin/cp $BASEDIR/usr/bin/ln
 295         cp -f $BASEDIR/usr/bin/cp $BASEDIR/usr/bin/mv
 296         cp -f $BASEDIR/usr/lib/isaexec $BASEDIR/usr/bin/ksh
 297         cp -f $BASEDIR/usr/lib/isaexec $BASEDIR/usr/bin/ksh93
 298         cp -f $BASEDIR/usr/lib/isaexec $BASEDIR/usr/sbin/rem_drv
 299         cp -f $BASEDIR/usr/lib/isaexec $BASEDIR/usr/sbin/update_drv""")
 300 
 301         for f in self.mf.gen_actions_by_type('file'):
 302             pth = f.attrs['path']
 303             
 304             #
 305             # From perl code:
 306             # 'use etc/motd from base-files package'
 307             #
 308             if 'etc/motd' in pth:
 309                 continue
 310             self.fix.append("mkdir -p $DEST/" + dirname(pth))
 311 
 312             #
 313             # Some packages don't include a dir action for every directory in
 314             # the file actions. That's probably a bug, but we don't want to 
 315             # error out just because of that, so we might as well check to make
 316             # sure the directory exists.
 317             #
 318             dest = join(bwd, pth)
 319             if not exists(dirname(dest)):
 320                 makedirs(dirname(dest))
 321 
 322             #
 323             # Try to find this file in the directories supplied via CLI.
 324             # If we can't, throw an error.
 325             #
 326             cpth = pth if f.hash == 'NOHASH' or f.attrs.get('chash') else f.hash
 327             src = ''
 328             for d in self.args.dir:
 329                 if exists(join(d, cpth)):
 330                     src = join(d, cpth)
 331 
 332             if not src:
 333                 raise Exception("Couldn't find %s in supplied directories" 
 334                     % (cpth))
 335 
 336             copyfile(src, dest)
 337             chmod(dest, int(f.attrs['mode'], 8))
 338             try:
 339                 chown(dest, getpwnam(f.attrs['owner']).pw_uid, 
 340                       getgrnam(f.attrs['group']).gr_gid)
 341             except(OSError) as err:
 342                 if 'Not owner' in str(err):
 343                     pass
 344                 else:
 345                     raise
 346 
 347             self.fix.append(
 348 """test -f "$DEST/%s" || echo '== Missing: %s'
 349 test -f "$DEST/%s" || exit 1
 350 chmod %s "$DEST/%s"
 351 chown %s:%s "$DEST/%s" """.strip(' ') % (pth, pth, pth, f.attrs['mode'], pth, 
 352                              f.attrs['owner'], f.attrs['group'], pth))
 353     
 354             pres = f.attrs.get('preserve')
 355             if pres == 'renamenew':
 356                 self.fix.append("mv $DEST/%s $DEST/%s.new" % (pth, pth))
 357                 self.post.append(
 358                     "([ -f $BASEDIR/%s ] || mv -f $BASEDIR/%s.new $BASEDIR/%s)" 
 359                     % (pth,pth,pth))
 360 
 361             elif pres == 'renameold':
 362                 self.fix.append("mv $DEST/%s $DEST/%s.%s"
 363                     % (pth,pth,self.pkgname))
 364                 self.post.append(
 365 """([ -f $BASEDIR/%s ] && cp -f $BASEDIR/%s $BASEDIR/%s.old )
 366         ([ -f $BASEDIR/%s.%s ] && mv -f $BASEDIR/%s.%s $BASEDIR/%s )"""
 367         % (pth,pth,pth,pth,self.pkgname,pth,self.pkgname,pth))
 368 
 369             elif pres == 'legacy':
 370                 self.fix.append("mv $DEST/%s $DEST/%s.%s"
 371                            % (pth,pth,self.pkgname))
 372                 self.post.append(
 373 """([ -f $BASEDIR/%s ] || rm -f $BASEDIR/%s.%s )
 374         ([ -f $BASEDIR/%s ] && mv -f $BASEDIR/%s $BASEDIR/%s.legacy )
 375         ([ -f $BASEDIR/%s.%s ] && mv -f $BASEDIR/%s.%s $BASEDIR/%s )"""
 376         % (pth,pth,self.pkgname,pth,pth,pth,pth,pth,self.pkgname,pth))
 377 
 378             elif pres == 'true':
 379                 self.fix.append("mv $DEST/%s $DEST/%s.%s"
 380                     % (pth,pth,self.pkgname))
 381                 self.post.append(
 382 """([ -f $BASEDIR/%s.saved ] && mv -f $BASEDIR/%s.saved $BASEDIR/%s )
 383         ([ -f $BASEDIR/%s ] || mv -f $BASEDIR/%s.%s $BASEDIR/%s )
 384         ([ -f $BASEDIR/%s ] && rm -f $BASEDIR/%s.%s)"""
 385         % (pth,pth,pth,pth,pth,self.pkgname,pth,pth,pth,self.pkgname))
 386                 self.rm.append(
 387                     "([ -f $BASEDIR/%s ] && "
 388                     "mv -f $BASEDIR/%s $BASEDIR/%s.saved)" % (pth,pth,pth))
 389 
 390             if f.attrs.get('variant.opensolaris.zone') == 'global':
 391                 self.post.append(
 392                     '[ "$ZONEINST" = "1" ] && ([ -f $BASEDIR/%s ] && '
 393                     'rm -f $BASEDIR/%s)' % (pth,pth))
 394 
 395             rfmri = f.attrs.get('restart_fmri')
 396             if rfmri and not self.svc.get(rfmri):
 397                 self.svc[rfmri] = 1
 398                 self.post.append(
 399                     '[ "${BASEDIR}" = "/" ] && ( /usr/sbin/svcadm restart %s '
 400                     '|| true )' % (rfmri))
 401 
 402 
 403         # 
 404         # Add commands for each hardlink action into
 405         # the appropriate package files.
 406         #
 407         if self.pkgname == 'sunwcs' and self.mf.actions_bytype.get('hardlink'):
 408             self.fix.append(
 409 """mkdir -p $DEST/usr/bin && cp -f $DEST/usr/bin/cp $DEST/usr/bin/ln
 410 mkdir -p $DEST/usr/bin && cp -f $DEST/usr/bin/cp $DEST/usr/bin/mv
 411 mkdir -p $DEST/usr/bin && cp -f $DEST/usr/lib/isaexec $DEST/usr/bin/ksh
 412 mkdir -p $DEST/usr/bin && cp -f $DEST/usr/lib/isaexec $DEST/usr/bin/ksh93
 413 mkdir -p $DEST/usr/bin && cp -f $DEST/usr/lib/isaexec $DEST/usr/sbin/rem_drv
 414 mkdir -p $DEST/usr/bin && cp -f $DEST/usr/lib/isaexec $DEST/usr/sbin/update_drv\
 415 """)
 416 
 417         hlskip = ['usr/bin/ln', 'usr/bin/mv', 'usr/bin/ksh', 'usr/bin/ksh93', 
 418                   'usr/sbin/rem_drv', 'usr/sbin/update_drv']
 419         for hl in self.mf.gen_actions_by_type('hardlink'):
 420             pth = hl.attrs['path']
 421             #
 422             # There are some hardlinks we don't care about, so skip them.
 423             #
 424             if pth in hlskip: 
 425                 continue
 426             
 427             if hl.attrs.get('variant.opensolaris.zone') == 'global':
 428                 self.post.append(
 429                     '[ "$ZONEINST" = "1" ] || (mkdir -p $BASEDIR/%s && '
 430                     'cd $BASEDIR/%s && ln -f %s %s)'
 431                     % (dirname(pth), dirname(pth), 
 432                       hl.attrs['target'], basename(pth)))
 433                 self.rm.append('[ "$ZONEINST" = "1" ] || rm -f $BASEDIR/%s'
 434                     % (pth))
 435             else:
 436                 self.post.append(
 437                     "mkdir -p $BASEDIR/%s && (cd $BASEDIR/%s && ln -f %s %s)"
 438                     % (dirname(pth), dirname(pth), 
 439                       hl.attrs['target'], basename(pth)))
 440                 self.rm.append('rm -f $BASEDIR/%s' % (pth))
 441 
 442         # 
 443         # Add appropriate commands for each driver action 
 444         # to the appropriate package files.
 445         # TODO: Do it "the IPS way"?
 446         #
 447         for drv in self.mf.gen_actions_by_type('driver'): 
 448             name = drv.attrs.get('name')
 449             privs = drv.attrs.get('privs')
 450             policy = drv.attrs.get('policy')
 451             devlink = drv.attrs.get('devlink')
 452             oPerm = drv.attrs.get('perms')
 453             oClPerm = drv.attrs.get('clone_perms')
 454             cls = drv.attrs.get('class')
 455             alias = drv.attrs.get('alias')
 456             opts = ''
 457 
 458             if type(cls) == list:
 459                 cls = ' '.join(cls)
 460             if type(alias) == list:
 461                 alias = '" "'.join(alias)
 462             if type(policy) == list:
 463                 policy = ", ".join(policy)
 464             if type(oPerm) == list:
 465                 oPerm = "','".join(oPerm)
 466             if type(privs) == list:
 467                 privs = privs[0]
 468                 
 469             if privs:
 470                 opts += ' -P %s' % (privs)
 471             if policy:
 472                 policy = policy.replace('"',"")
 473                 opts += " -p \'%s\'" % (policy)
 474             
 475             clopts = opts
 476             if cls:
 477                 opts += " -c '%s'" % (cls)
 478             if alias:
 479                 opts += " -i '\"%s\"'" % (alias)
 480 
 481             if not oPerm:
 482                 clopts = opts
 483             else:
 484                 opts += " -m \'%s\'" % (oPerm.replace('"',"'"))
 485 
 486             self.post.append('[ "$ZONEINST" = "1" ] || '
 487                 '(grep -c "^%s " $BASEDIR/etc/name_to_major >/dev/null '
 488                 '|| ( add_drv -n  $BASEDIR_OPT %s %s ) )' 
 489                 % (name, opts, name))
 490             self.rm.append(
 491                 '[ "$ZONEINST" = "1" ] || ( rem_drv -n $BASEDIR_OPT %s )'
 492                 % (name))
 493             
 494             if oClPerm:
 495                 if type(oClPerm) != list:
 496                     oClPerm = [oClPerm]
 497                 for perm in oClPerm:
 498                     perm = perm.replace('"','')
 499                     self.post.append('[ "$ZONEINST" = "1" ] || '
 500                         '(grep -c "^clone:%s" $BASEDIR/etc/minor_perm '
 501                         '>/dev/null || update_drv -n  -a $BASEDIR_OPT %s '
 502                         '-m \'%s\' clone)' % (perm, clopts, perm))
 503                     self.rm.append('[ "$ZONEINST" = "1" ] || '
 504                         '(grep -c "^clone:%s" $BASEDIR/etc/minor_perm '
 505                         '>/dev/null && update_drv -n  -d $BASEDIR_OPT %s '
 506                         '-m \'%s\' clone)' % (perm, clopts, perm))
 507 
 508             if devlink:
 509                 devlink = devlink.replace('\\t','\t')
 510                 fields = devlink.split()
 511                 self.post.append('[ "$ZONEINST" = "1" ] || '
 512                     '(grep -c "^%s" $BASEDIR/etc/devlink.tab '
 513                     '>/dev/null || (echo "%s" >> $BASEDIR/etc/devlink.tab))'
 514                             % (fields[0], devlink))
 515                 self.rm.append('[ "$ZONEINST" = "1" ] || '
 516                     '( cat $BASEDIR/etc/devlink.tab | sed -e \'/^%s/d\' '
 517                     '> $BASEDIR/etc/devlink.tab.new; '
 518                     'mv $BASEDIR/etc/devlink.tab.new '
 519                     '$BASEDIR/etc/devlink.tab )' % (fields[0]))
 520 
 521         # TODO: do we need this?
 522         if self.mf.actions_bytype.get('driver'):
 523             if len(self.post):
 524                 self.post.append('')
 525             if len(self.rm):
 526                 self.rm.append('')
 527 
 528 
 529         #
 530         # Add commands for each link action to the appropriate package files.
 531         #
 532         mediator = [] # temporary location for mediator command
 533         old_mtr = None;
 534         
 535         for link in self.mf.gen_actions_by_type('link'):
 536             pth = link.attrs['path']
 537             target = link.attrs['target']
 538             mtr = link.attrs.get('mediator')
 539             mtr_ver = link.attrs.get('mediator-version')
 540             mtr_pri = link.attrs.get('mediator-priority')
 541 
 542             if old_mtr and mtr != old_mtr:
 543                 raise Exception("%s: multiple mediator groups per manifest "
 544                                 "not supported"
 545                                 % (self.pkgname))
 546 
 547             dire = dirname(pth)
 548             if not dire:
 549                 dire = '.'
 550 
 551             alt = pth.replace("/","-")
 552 
 553             if mtr:
 554                 alt_pri = 99
 555             elif mtr_pri == 'vendor':
 556                 alt_pri = 10
 557             else:
 558                 alt_pri = 0
 559 
 560             if mtr and link.attrs.get('variant.opensolaris.zone'):
 561                 raise Exception("%s: mediated links using "
 562                                 "variant.opensolaris.zone not supported"
 563                                 % (self.pkgname))
 564 
 565             if mtr and len(mediator) == 0:
 566                 makedirs(join(bwd, "var/mediator"))
 567                 copyfile("/dev/null", join(bwd, "var/mediator/", self.pkgname))
 568                 self.fix.append("mkdir -p $DEST/var/mediator")
 569 
 570                 # The following will "just work" as our update-alternatives
 571                 # honors the BASEDIR environment variable.
 572                 mediator.append(
 573                     '(update-alternatives --quiet '
 574                     '--install /var/mediator/%s %s /var/mediator/%s %s'
 575                     %(mtr, mtr, self.pkgname, alt_pri))
 576                 self.rm.append(
 577 """if [ "$1" != "upgrade" ]; then
 578         (update-alternatives --quiet --remove %s /var/mediator/%s)
 579 fi
 580 """ %(mtr, self.pkgname))
 581 
 582             if link.attrs.get('variant.opensolaris.zone') == 'global':
 583                 self.post.append(
 584                     '[ "$ZONEINST" = "1" ] || (mkdir -p $BASEDIR/%s && '
 585                     'ln -f -s %s $BASEDIR/%s)' % (dire, target, pth))
 586 
 587             else: # != 'global'
 588                 if mtr:
 589                     mediator.append('--slave /%s %s /%s/%s'
 590                                 %(pth, alt, dire, target))
 591                 else:
 592                     self.fix.append('mkdir -p $DEST/%s && ln -f -s %s $DEST/%s' 
 593                                % (dire, target, pth))
 594 
 595         if len(mediator):
 596             mediator.append(')')
 597             self.post.append(' '.join(mediator))
 598 
 599         # Groups
 600         for grp in self.mf.gen_actions_by_type('group'):
 601             self.post.append("""
 602         if ! getent group %s >/dev/null 2>&1 ; then
 603                 groupadd -g %s %s
 604         fi""" % (grp.attrs['groupname'], grp.attrs['gid'], 
 605                 grp.attrs['groupname']))    
 606 
 607 
 608         # Users
 609         for usr in self.mf.gen_actions_by_type('user'):
 610             name = usr.attrs['username']
 611             uid = usr.attrs['uid']
 612             gcos = usr.attrs.get('gcos-field')
 613             shell = usr.attrs.get('login-shell')
 614             grp = usr.attrs.get('group')
 615             self.post.append("""
 616         if ! getent passwd %s >/dev/null 2>&1 ; then
 617                useradd %s%s%s%s%s
 618         fi""" % (name, ' -c "%s"' % (gcos) if gcos else '',
 619                 ' -s %s' % (shell) if shell else '',
 620                 ' -u ' + uid, ' -g %s' % (grp) if grp else '', ' -m ' + name))
 621 
 622 
 623         return True
 624 
 625     def savedebpkg(self):
 626         """Create source files for a debian package"""
 627         #
 628         # If there are no actions we care about,
 629         # don't bother creating any files.
 630         #
 631         if not (len(self.depends) or self.mf.actions_bytype.get('dir') \
 632                     or self.mf.actions_bytype.get('file') \
 633                     or self.mf.actions_bytype.get('hardlink') \
 634                     or self.mf.actions_bytype.get('user') \
 635                     or self.mf.actions_bytype.get('group')):
 636             print "%s: Nothing to save. Exiting." % (self.pkgname)
 637             return
 638 
 639         control = open(join(self.wd, 'debian', 'control'), 'w')
 640         control.write("""Source: %s
 641 Section: %s
 642 Priority: %s
 643 XBS-Original-Version: %s
 644 XBS-Category: %s
 645 Maintainer: %s
 646 
 647 Package: %s
 648 Architecture: %s
 649 Depends: %s
 650 Provides: %s%s
 651 Description: %s
 652  %s
 653 """         % (self.pkgname, self.args.sect, self.pri, self.origver,
 654                self.args.sect, self.args.main, self.pkgname,
 655                self.arch, ', '.join(self.depends),
 656                self.origname.replace('_','-'), 
 657                '\nReplaces: %s' % (self.rep) if self.rep else '',
 658                self.mf.get('pkg.summary', 'none'),
 659                self.mf.get('pkg.description', 'none')))
 660         control.close()
 661 
 662         change = open(join(self.wd, 'debian', 'changelog'), 'w')
 663         change.write("""%s (%s) unstable; urgency=low
 664 
 665   * Temporary file, need only for package generation process
 666 
 667  -- %s  %s
 668 """ % (self.pkgname, self.ver, self.args.main,
 669        strftime("%a, %d %h %Y %H:%M:%S %z")))
 670         change.close()
 671 
 672         compat = open(join(self.wd, 'debian', 'compat'),'w')
 673         compat.write('7\n')
 674         compat.close()
 675 
 676         copy = open(join(self.wd, 'debian', 'copyright'),'w')
 677         copy.write("""
 678 Copyright:
 679 
 680 Copyright (c) 2011 illumian.  All rights reserved.
 681 Use is subject to license terms.
 682 
 683 """)
 684         copy.close()
 685 
 686 
 687         fixperms = open(join(self.wd, 'debian',
 688                              self.pkgname + '.fixperms'), 'w')
 689         fixperms.write(
 690 """#!/bin/sh
 691 
 692 export PATH=%s
 693 
 694 %s
 695 """ % ("/usr/bin:/sbin:/usr/sbin" if len(self.fix) else '', 
 696       '\n'.join(self.fix) if len(self.fix) else ''))
 697         fixperms.close()
 698 
 699         if len(self.post):
 700             postinst = open(join(self.wd, 'debian',
 701                                  self.pkgname + '.postinst'), 'w')
 702             postinst.write(
 703 """#!/bin/sh
 704 
 705 # postinst script for %s
 706 #
 707 # see: dh_installdeb(1)
 708 
 709 #set -e
 710 
 711 # summary of how this script can be called:
 712 #        * <postinst> \`configure\' <most-recently-configured-version>
 713 #        * <old-postinst> \`abort-upgrade\' <new version>
 714 #        * <conflictor\'s-postinst> \`abort-remove\' \`in-favour\' <package>
 715 #          <new-version>
 716 #        * <postinst> \`abort-remove\'
 717 #        * <deconfigured\'s-postinst> \`abort-deconfigure\' \`in-favour\'
 718 #          <failed-install-package> <version> \`removing\'
 719 #          <conflicting-package> <version>
 720 # for details, see http://www.debian.org/doc/debian-policy/ or
 721 # the debian-policy package
 722 
 723 PATH=/usr/bin:/sbin:/usr/sbin:/usr/local/bin:/usr/local/sbin
 724 export PATH
 725 
 726 if [ "${BASEDIR:=/}" != "/" ]; then
 727     BASEDIR_OPT="-b $BASEDIR"
 728 fi
 729 
 730 case "$1" in
 731     configure)
 732         %s
 733     ;;
 734 
 735     abort-upgrade|abort-remove|abort-deconfigure)
 736     ;;
 737 
 738     *)
 739         echo "postinst called with unknown argument \'$1\'" >&2
 740         exit 1
 741     ;;
 742 esac
 743 
 744 # dh_installdeb will replace this with shell code automatically
 745 # generated by other debhelper scripts.
 746 
 747 #DEBHELPER#
 748 
 749 exit 0
 750 """ % (self.pkgname, '\n\t'.join(self.post)))
 751             postinst.close()
 752 
 753 
 754         if len(self.pre):
 755             preinst = open(join(self.wd, 'debian',
 756                                 self.pkgname + '.preinst'), 'w')
 757             preinst.write(
 758 """#!/bin/sh
 759 # preinst script for sunwcs
 760 #
 761 # see: dh_installdeb(1)
 762 
 763 #set -e
 764 
 765 # summary of how this script can be called:
 766 #        * <new-preinst> \`install'
 767 #        * <new-preinst> \`install' <old-version>
 768 #        * <new-preinst> \`upgrade' <old-version>
 769 #        * <old-preinst> \`abort-upgrade' <new-version>
 770 # for details, see http://www.debian.org/doc/debian-policy/ or
 771 # the debian-policy package
 772 
 773 PATH=/usr/bin:/sbin:/usr/sbin:/usr/local/bin:/usr/local/sbin
 774 export PATH
 775 
 776 if [ "${BASEDIR:=/}" != "/" ]; then
 777     BASEDIR_OPT="-b $BASEDIR"
 778 fi
 779 
 780 case "$1" in
 781     install|upgrade)
 782         %s
 783     ;;
 784 
 785     abort-upgrade)
 786     ;;
 787 
 788     *)
 789         echo "preinst called with unknown argument '$1'" >&2
 790         exit 1
 791     ;;
 792 esac
 793 
 794 # dh_installdeb will replace this with shell code automatically
 795 # generated by other debhelper scripts.
 796 
 797 #DEBHELPER#
 798 
 799 exit 0
 800 """ % ('\n\t'.join(self.pre)))
 801             preinst.close()
 802 
 803         if len(self.rm):
 804             prerm = open(join(self.wd, 'debian',
 805                               self.pkgname + '.prerm'), 'w')
 806             prerm.write(
 807 """#!/bin/sh
 808 # prerm script for sunwcs
 809 #
 810 # see: dh_installdeb(1)
 811 
 812 #set -e
 813 
 814 # summary of how this script can be called:
 815 #        * <prerm> \`remove\'
 816 #        * <old-prerm> \`upgrade' <new-version>
 817 #        * <new-prerm> \`failed-upgrade' <old-version>
 818 #        * <conflictor's-prerm> \`remove' \`in-favour' <package> <new-version>
 819 #        * <deconfigured\'s-prerm> \`deconfigure' \`in-favour'
 820 #          <package-being-installed> <version> \`removing'
 821 #          <conflicting-package> <version>
 822 # for details, see http://www.debian.org/doc/debian-policy/ or
 823 # the debian-policy package
 824 
 825 PATH=/usr/bin:/sbin:/usr/sbin:/usr/local/bin:/usr/local/sbin
 826 export PATH
 827 
 828 if [ "${BASEDIR:=/}" != "/" ]; then
 829     BASEDIR_OPT="-b $BASEDIR"
 830 fi
 831 
 832 case "$1" in
 833     remove|upgrade|deconfigure)
 834         %s
 835     ;;
 836 
 837     failed-upgrade)
 838     ;;
 839 
 840     *)
 841         echo "prerm called with unknown argument '$1'" >&2
 842         exit 1
 843     ;;
 844 esac
 845 
 846 # dh_installdeb will replace this with shell code automatically
 847 # generated by other debhelper scripts.
 848 
 849 #DEBHELPER#
 850 
 851 exit 0
 852 """ % ('\n\t'.join(self.rm)))
 853             prerm.close()
 854         rpath = join(self.wd, 'debian', 'rules')
 855         rules = open(rpath, 'w')
 856         rules.write(
 857 """#!/usr/bin/gmake -f
 858 # -*- makefile -*-
 859 # Sample debian/rules that uses debhelper.
 860 # This file was originally written by Joey Hess and Craig Small.
 861 # As a special exception, when this file is copied by dh-make into a
 862 # dh-make output file, you may use that output file without restriction.
 863 # This special exception was added by Craig Small in version 0.37 of dh-make.
 864 
 865 # Uncomment this to turn on verbose mode.
 866 #export DH_VERBOSE=1
 867 
 868 #MYGATE := ${BASEGATE}
 869 DEST := $(CURDIR)/debian/%s
 870 
 871 ##configure: configure-stamp
 872 ##configure-stamp:
 873 ##      dh_testdir
 874         # Add here commands to configure the package.
 875 ##      touch configure-stamp
 876 
 877 
 878 ##build: build-stamp
 879 build:
 880 
 881 ##build-stamp: configure-stamp 
 882 #       dh_testdir
 883         # Add here commands to compile the package.
 884 #       touch $@
 885 
 886 clean:
 887         dh_testdir
 888         dh_testroot
 889         -rm -f build-stamp configure-stamp
 890 
 891         # Add here commands to clean up after the build process.
 892 #       -$(MAKE) clean
 893 #       dh_clean
 894 
 895 ##install: build
 896 install:
 897 ##      dh_testdir
 898 ##      dh_testroot
 899 ##      dh_clean -k 
 900 ##      dh_installdirs
 901 
 902         # Add here commands to install the package into debian/tmp.
 903 #       mkdir -p $(CURDIR)/debian/tmp
 904 #       mv proto/* $(CURDIR)/debian/tmp
 905 
 906 
 907 # Build architecture-independent files here.
 908 ##binary-indep: build install
 909 # We have nothing to do by default.
 910 
 911 # Build architecture-dependent files here.
 912 ##binary-arch: build install
 913 binary-arch:
 914         dh_testdir
 915         dh_testroot
 916 #       test -f $(CURDIR)/debian/%s.fixperms && MYSRCDIR=$(MYGATE) DEST=$(DEST)\
 917  /bin/sh $(CURDIR)/debian/%s.fixperms
 918         test -f $(CURDIR)/debian/%s.fixperms && DEST=$(DEST)\
 919  /bin/sh $(CURDIR)/debian/%s.fixperms
 920 #       dh_makeshlibs -p%s
 921         dh_makeshlibs
 922         dh_installdeb
 923         rm -f $(CURDIR)/debian/%s/DEBIAN/conffiles
 924 #       dh_shlibdeps debian/tmp/lib/* debian/tmp/usr/lib/*
 925         -dh_shlibdeps -Xdebian/sunwcs/usr/kernel/* -- --ignore-missing-info
 926         dh_gencontrol
 927         dh_md5sums
 928         dh_builddeb -- -Zbzip2 -z9
 929 
 930 ##binary: binary-indep binary-arch
 931 binary: binary-arch
 932 ##.PHONY: build clean binary-indep binary-arch binary install configure
 933 .PHONY: clean binary-arch binary
 934 """ % (self.pkgname, self.pkgname, self.pkgname, self.pkgname, self.pkgname,
 935        self.pkgname, self.pkgname))
 936         rules.close()
 937         chmod(rpath, 0777)
 938         conffiles = open(join(self.wd, 'debian',
 939                               self.pkgname + '.conffiles'), 'w')
 940         conffiles.write('\n')
 941         conffiles.close()
 942 
 943 
 944 def parsespec(d, f):
 945     """create a dict() that contains the key/value pairs \
 946 represented in a conffile"""
 947     d.update([(lambda a, b:[a, [b]])(*line.rstrip().split(':')) for line in f 
 948               if not line.startswith('#') and ':' in line])
 949 
 950 def createparser():
 951     """create an OptionParser object"""
 952     cwd = dirname(realpath(__file__))
 953     parser = optparse.OptionParser(
 954         description='Convert a mogrified pkg manifest to Debian format')
 955     parser.add_option('-p', '--pkg', dest = 'path', action = 'append',
 956                         help = 'location of the manifest')
 957     parser.add_option('-v', '--verbose', dest = 'v', action = 'store_true',
 958                         help = 'output verbose stuff')
 959     parser.add_option('--pv', dest = 'ver', default = '1.0.0-deb',
 960                         help = 'package version')
 961     parser.add_option('--cv', dest = 'origver',
 962                         help = 'XBS-Original-Version Debian field')
 963     parser.add_option('-o', '--wd', dest = 'wd',
 964                         help = 'Where to store this package')
 965     parser.add_option('--mg', '--mfg', '--manifest_gate', dest = 'gate',
 966                         help = 'location of all manifests')
 967     parser.add_option('--maintainer', dest = 'main',
 968                         default = 'Nexenta Systems <maintainer@nexenta.com>',
 969                         help = 'Debian Maintainer Field')
 970     parser.add_option('--me', '--mfe', dest = 'ext', default = 'res', 
 971                         help = 'Extension of manifests (i.e. .mog, .mf)')
 972     parser.add_option('-s', '--section', '--category', dest = 'sect', 
 973                       default = 'undef', help = 'Debian Section field')
 974     parser.add_option('-r', '--priority', dest = 'pri', default = 'optional',
 975                         help = 'Debian Priority field')
 976     parser.add_option('--spec', dest = 'spec', action = 'store_true',
 977                         help = 'Whether this is a "special" package')
 978     parser.add_option('--rep', dest = 'rep',
 979                         help = 'Debian Replace field')
 980     parser.add_option('-d', '--dir', dest = 'dir', action = 'append', 
 981                       help = 'Path to top of tree of files included in packages'
 982                       '(i.e. proto/root_i386)')
 983     parser.add_option('--conf', dest = 'conf', default = '%s/../etc/' % (cwd),
 984                       help = 'Directory where the ips conf files are stored')
 985     return parser
 986 
 987 # Start
 988 def main(arglist):
 989     """main"""
 990     errs = False
 991     parser = createparser()
 992     (args, thing)  = parser.parse_args(arglist)
 993 
 994     #
 995     # Find the conffiles relative to the directory this script is in.
 996     #
 997     frep = open(join(args.conf, 'ips2deb.replaces'),'r')
 998     fver = open(join(args.conf, 'ips2deb.versions'),'r')
 999     fpri = open(join(args.conf, 'ips2deb.priorities'),'r')
1000 
1001 
1002     parsespec(drep, frep)
1003     parsespec(dver, fver)
1004     parsespec(dpri, fpri)
1005 
1006     if not args.dir:
1007         raise Exception("Need at least one -d/--dir option...")
1008 
1009     #
1010     # If a set of packages are specified, build those. 
1011     # Otherwise, build all of the packages in a directory.
1012     #
1013     if not args.path or args.path == 'all':
1014         if not args.gate:
1015             raise Exception('Manifest Gate path needed if no package specified')
1016         pkglist = [join(args.gate, f) for f in listdir(args.gate) 
1017                    if f.endswith('.%s' % (args.ext))]
1018     else:
1019         pkglist = args.path
1020 
1021     pkgs = []
1022     for pkg in pkglist:
1023         try:
1024             tmp = IPSConvert(pkg, args)
1025             tmp.initdebpkg()
1026             if tmp.process:
1027                 pkgs.append(tmp)
1028         except Exception as err:
1029             print "\n\nError: %s\n%s: %s\n\n" % (pkg, type(err), err)
1030             errs = True
1031 
1032     for pkg in pkgs:
1033         try:
1034             pkg.gendebpkg()
1035             pkg.savedebpkg()
1036         except Exception as err:
1037             print "\n\nError: %s\n%s: %s\n\n" % (pkg.pkgname, type(err), err)
1038             errs = True
1039 
1040     frep.close()
1041     fver.close()
1042     fpri.close()
1043 
1044     return errs
1045 
1046 if __name__ == '__main__':
1047     errors = main(sys.argv[1:])
1048     if errors:
1049         raise Exception("some errors occured during processing;\n"
1050             "Packages that didn't error out were still generated.\n")