Revert "Drop mic raw image format support"
[tools/mic.git] / mic / bootstrap.py
1 #!/usr/bin/python -tt
2 #
3 # Copyright (c) 2009, 2010, 2011 Intel, Inc.
4 #
5 # This program is free software; you can redistribute it and/or modify it
6 # under the terms of the GNU General Public License as published by the Free
7 # Software Foundation; version 2 of the License
8 #
9 # This program is distributed in the hope that it will be useful, but
10 # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
11 # or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12 # for more details.
13 #
14 # You should have received a copy of the GNU General Public License along
15 # with this program; if not, write to the Free Software Foundation, Inc., 59
16 # Temple Place - Suite 330, Boston, MA 02111-1307, USA.
17
18 from __future__ import with_statement
19 import os
20 import sys
21 import tempfile
22 import shutil
23 import subprocess
24 import rpm
25 from mic import msger
26 from mic.utils import errors, proxy, misc
27 from mic.utils.rpmmisc import readRpmHeader, RPMInstallCallback
28 from mic.chroot import cleanup_mounts, setup_chrootenv, cleanup_chrootenv
29
30 PATH_BOOTSTRAP = "/usr/sbin:/usr/bin:/sbin:/bin"
31
32 RPMTRANS_FLAGS = [
33                    rpm.RPMTRANS_FLAG_ALLFILES,
34                    rpm.RPMTRANS_FLAG_NOSCRIPTS,
35                    rpm.RPMTRANS_FLAG_NOTRIGGERS,
36                  ]
37
38 RPMVSF_FLAGS = [
39                  rpm._RPMVSF_NOSIGNATURES,
40                  rpm._RPMVSF_NODIGESTS
41                ]
42
43 RPMPROB_FLAGS = [
44                   rpm.RPMPROB_FILTER_OLDPACKAGE,
45                   rpm.RPMPROB_FILTER_REPLACEPKG,
46                   rpm.RPMPROB_FILTER_IGNOREARCH
47                 ]
48
49 class MiniBackend(object):
50     def __init__(self, rootdir, arch=None, repomd=None):
51         self._ts = None
52         self.rootdir = os.path.abspath(rootdir)
53         self.arch = arch
54         self.repomd = repomd
55         self.dlpkgs = []
56         self.localpkgs = {}
57         self.optionals = []
58         self.preins = {}
59         self.postins = {}
60         self.scriptlets = False
61
62     def __del__(self):
63         try:
64             del self.ts
65         except:
66             pass
67
68     def get_ts(self):
69         if not self._ts:
70             self._ts = rpm.TransactionSet(self.rootdir)
71             self._ts.setFlags(reduce(lambda x, y: x|y, RPMTRANS_FLAGS))
72             self._ts.setVSFlags(reduce(lambda x, y: x|y, RPMVSF_FLAGS))
73             self._ts.setProbFilter(reduce(lambda x, y: x|y, RPMPROB_FLAGS))
74
75         return self._ts
76
77     def del_ts(self):
78         if self._ts:
79             self._ts.closeDB()
80             self._ts = None
81
82     ts = property(fget = lambda self: self.get_ts(),
83                   fdel = lambda self: self.del_ts(),
84                   doc="TransactionSet object")
85
86     def selectPackage(self, pkg):
87         if not pkg in self.dlpkgs:
88             self.dlpkgs.append(pkg)
89
90     def runInstall(self):
91         # FIXME: check space
92         self.downloadPkgs()
93         self.installPkgs()
94
95         if not self.scriptlets:
96             return
97
98         for pkg in self.preins.keys():
99             prog, script = self.preins[pkg]
100             self.run_pkg_script(pkg, prog, script, '0')
101         for pkg in self.postins.keys():
102             prog, script = self.postins[pkg]
103             self.run_pkg_script(pkg, prog, script, '1')
104
105     def downloadPkgs(self):
106         nonexist = []
107         for pkg in self.dlpkgs:
108             localpth = misc.get_package(pkg, self.repomd, self.arch)
109             if localpth:
110                 self.localpkgs[pkg] = localpth
111             elif pkg in self.optionals:
112                 # skip optional rpm
113                 continue
114             else:
115                 # mark nonexist rpm
116                 nonexist.append(pkg)
117
118         if nonexist:
119             raise errors.BootstrapError("Can't get rpm binary: %s" %
120                                         ','.join(nonexist))
121
122     def installPkgs(self):
123         for pkg in self.localpkgs.keys():
124             rpmpath = self.localpkgs[pkg]
125
126             hdr = readRpmHeader(self.ts, rpmpath)
127
128             # save prein and postin scripts
129             self.preins[pkg] = (hdr['PREINPROG'], hdr['PREIN'])
130             self.postins[pkg] = (hdr['POSTINPROG'], hdr['POSTIN'])
131
132             # mark pkg as install
133             self.ts.addInstall(hdr, rpmpath, 'u')
134
135         # run transaction
136         self.ts.order()
137         cb = RPMInstallCallback(self.ts)
138         errs = self.ts.run(cb.callback, '')
139
140         # ts.run() exit codes are, hmm, "creative": None means all ok, empty 
141         # list means some errors happened in the transaction and non-empty 
142         # list that there were errors preventing the ts from starting...
143         if errs is not None:
144              raise errors.BootstrapError("Transaction couldn't start: %s" % '\n'.join(errs))
145
146     def run_pkg_script(self, pkg, prog, script, arg):
147         mychroot = lambda: os.chroot(self.rootdir)
148
149         if not script:
150             return
151
152         if prog == "<lua>":
153             prog = "/usr/bin/lua"
154
155         tmpdir = os.path.join(self.rootdir, "tmp")
156         if not os.path.exists(tmpdir):
157             os.makedirs(tmpdir)
158         tmpfd, tmpfp = tempfile.mkstemp(dir=tmpdir, prefix="%s.pre-" % pkg)
159         script = script.replace('\r', '')
160         os.write(tmpfd, script)
161         os.close(tmpfd)
162         os.chmod(tmpfp, 0700)
163
164         try:
165             script_fp = os.path.join('/tmp', os.path.basename(tmpfp))
166             subprocess.call([prog, script_fp, arg], preexec_fn=mychroot)
167         except (OSError, IOError), err:
168             msger.warning(str(err))
169         finally:
170             os.unlink(tmpfp)
171
172 class Bootstrap(object):
173     def __init__(self, rootdir, distro, arch=None):
174         self.rootdir = misc.mkdtemp(dir=rootdir, prefix=distro)
175         self.distro = distro
176         self.arch = arch
177         self.logfile = None
178         self.pkgslist = []
179         self.repomd = None
180
181     def __del__(self):
182         self.cleanup()
183
184     def get_rootdir(self):
185         if os.path.exists(self.rootdir):
186             shutil.rmtree(self.rootdir, ignore_errors=True)
187         os.makedirs(self.rootdir)
188         return self.rootdir
189
190     def dirsetup(self, rootdir=None):
191         _path = lambda pth: os.path.join(rootdir, pth.lstrip('/'))
192
193         if not rootdir:
194             rootdir = self.rootdir
195
196         try:
197             # make /tmp and /etc path
198             tmpdir = _path('/tmp')
199             if not os.path.exists(tmpdir):
200                 os.makedirs(tmpdir)
201             etcdir = _path('/etc')
202             if not os.path.exists(etcdir):
203                 os.makedirs(etcdir)
204
205             # touch distro file
206             tzdist = _path('/etc/%s-release' % self.distro)
207             if not os.path.exists(tzdist):
208                 with open(tzdist, 'w') as wf:
209                     wf.write("bootstrap")
210         except:
211             pass
212
213     def create(self, repomd, pkglist, optlist=()):
214         try:
215             pkgmgr = MiniBackend(self.get_rootdir())
216             pkgmgr.arch = self.arch
217             pkgmgr.repomd = repomd
218             pkgmgr.optionals = list(optlist)
219             map(pkgmgr.selectPackage, pkglist + list(optlist))
220             pkgmgr.runInstall()
221         except (OSError, IOError, errors.CreatorError), err:
222             raise errors.BootstrapError("%s" % err)
223
224     def run(self, cmd, chdir, rootdir=None, bindmounts=None):
225         def mychroot():
226             os.chroot(rootdir)
227             os.chdir(chdir)
228
229         def sync_timesetting(rootdir):
230             try:
231                 # sync time and zone info to bootstrap
232                 if os.path.exists(rootdir + "/etc/localtime"):
233                     os.unlink(rootdir + "/etc/localtime")
234                 shutil.copyfile("/etc/localtime", rootdir + "/etc/localtime")
235             except:
236                 pass
237
238         def sync_passwdfile(rootdir):
239             try:
240                 # sync passwd file to bootstrap, saving the user info
241                 if os.path.exists(rootdir + "/etc/passwd"):
242                     os.unlink(rootdir + "/etc/passwd")
243                 shutil.copyfile("/etc/passwd", rootdir + "/etc/passwd")
244             except:
245                 pass
246
247         if not rootdir:
248             rootdir = self.rootdir
249
250         if isinstance(cmd, list):
251             shell = False
252         else:
253             shell = True
254
255         env = os.environ
256         env['PATH'] = "%s:%s" % (PATH_BOOTSTRAP, env['PATH'])
257
258         retcode = 0
259         gloablmounts = None
260         try:
261             proxy.set_proxy_environ()
262             gloablmounts = setup_chrootenv(rootdir, bindmounts)
263             sync_timesetting(rootdir)
264             sync_passwdfile(rootdir)
265             retcode = subprocess.call(cmd, preexec_fn=mychroot, env=env, shell=shell)
266         except (OSError, IOError):
267             # add additional information to original exception
268             value, tb = sys.exc_info()[1:]
269             value = '%s: %s' % (value, ' '.join(cmd))
270             raise RuntimeError, value, tb
271         finally:
272             #if self.logfile and os.path.isfile(self.logfile):
273             #    msger.log(file(self.logfile).read())
274             cleanup_chrootenv(rootdir, bindmounts, gloablmounts)
275             proxy.unset_proxy_environ()
276         return retcode
277
278     def cleanup(self):
279         try:
280             # clean mounts
281             cleanup_mounts(self.rootdir)
282             # remove rootdir
283             shutil.rmtree(self.rootdir, ignore_errors=True)
284         except:
285             pass