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")