fix conflict name
[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 None:
144             pass
145         elif len(errs) == 0:
146              msger.warning("Warning: scriptlet or other non-fatal errors occurred")
147         else:
148              raise errors.BootstrapError("Transaction couldn't start: %s" % '\n'.join(errs))
149
150     def run_pkg_script(self, pkg, prog, script, arg):
151         mychroot = lambda: os.chroot(self.rootdir)
152
153         if not script:
154             return
155
156         if prog == "<lua>":
157             prog = "/usr/bin/lua"
158
159         tmpdir = os.path.join(self.rootdir, "tmp")
160         if not os.path.exists(tmpdir):
161             os.makedirs(tmpdir)
162         tmpfd, tmpfp = tempfile.mkstemp(dir=tmpdir, prefix="%s.pre-" % pkg)
163         script = script.replace('\r', '')
164         os.write(tmpfd, script)
165         os.close(tmpfd)
166         os.chmod(tmpfp, 0700)
167
168         try:
169             script_fp = os.path.join('/tmp', os.path.basename(tmpfp))
170             subprocess.call([prog, script_fp, arg], preexec_fn=mychroot)
171         except (OSError, IOError), err:
172             msger.warning(str(err))
173         finally:
174             os.unlink(tmpfp)
175
176 class Bootstrap(object):
177     def __init__(self, rootdir, distro, arch=None):
178         self.rootdir = misc.mkdtemp(dir=rootdir, prefix=distro)
179         self.distro = distro
180         self.arch = arch
181         self.logfile = None
182         self.pkgslist = []
183         self.repomd = None
184
185     def __del__(self):
186         self.cleanup()
187
188     def get_rootdir(self):
189         if os.path.exists(self.rootdir):
190             shutil.rmtree(self.rootdir, ignore_errors=True)
191         os.makedirs(self.rootdir)
192         return self.rootdir
193
194     def dirsetup(self, rootdir=None):
195         _path = lambda pth: os.path.join(rootdir, pth.lstrip('/'))
196
197         if not rootdir:
198             rootdir = self.rootdir
199
200         try:
201             # make /tmp and /etc path
202             tmpdir = _path('/tmp')
203             if not os.path.exists(tmpdir):
204                 os.makedirs(tmpdir)
205             etcdir = _path('/etc')
206             if not os.path.exists(etcdir):
207                 os.makedirs(etcdir)
208
209             # touch distro file
210             tzdist = _path('/etc/%s-release' % self.distro)
211             if not os.path.exists(tzdist):
212                 with open(tzdist, 'w') as wf:
213                     wf.write("bootstrap")
214         except:
215             pass
216
217     def create(self, repomd, pkglist, optlist=()):
218         try:
219             pkgmgr = MiniBackend(self.get_rootdir())
220             pkgmgr.arch = self.arch
221             pkgmgr.repomd = repomd
222             pkgmgr.optionals = list(optlist)
223             map(pkgmgr.selectPackage, pkglist + list(optlist))
224             pkgmgr.runInstall()
225         except (OSError, IOError, errors.CreatorError), err:
226             raise errors.BootstrapError("%s" % err)
227
228     def run(self, cmd, chdir, rootdir=None, bindmounts=None):
229         def mychroot():
230             os.chroot(rootdir)
231             os.chdir(chdir)
232
233         def sync_timesetting(rootdir):
234             try:
235                 # sync time and zone info to bootstrap
236                 if os.path.exists(rootdir + "/etc/localtime"):
237                     os.unlink(rootdir + "/etc/localtime")
238                 shutil.copyfile("/etc/localtime", rootdir + "/etc/localtime")
239             except:
240                 pass
241
242         def sync_passwdfile(rootdir):
243             try:
244                 # sync passwd file to bootstrap, saving the user info
245                 if os.path.exists(rootdir + "/etc/passwd"):
246                     os.unlink(rootdir + "/etc/passwd")
247                 shutil.copyfile("/etc/passwd", rootdir + "/etc/passwd")
248             except:
249                 pass
250
251         if not rootdir:
252             rootdir = self.rootdir
253
254         if isinstance(cmd, list):
255             shell = False
256         else:
257             shell = True
258
259         env = os.environ
260         env['PATH'] = "%s:%s" % (PATH_BOOTSTRAP, env['PATH'])
261
262         retcode = 0
263         gloablmounts = None
264         try:
265             proxy.set_proxy_environ()
266             gloablmounts = setup_chrootenv(rootdir, bindmounts)
267             sync_timesetting(rootdir)
268             sync_passwdfile(rootdir)
269             retcode = subprocess.call(cmd, preexec_fn=mychroot, env=env, shell=shell)
270         except (OSError, IOError):
271             # add additional information to original exception
272             value, tb = sys.exc_info()[1:]
273             value = '%s: %s' % (value, ' '.join(cmd))
274             raise RuntimeError, value, tb
275         finally:
276             #if self.logfile and os.path.isfile(self.logfile):
277             #    msger.log(file(self.logfile).read())
278             cleanup_chrootenv(rootdir, bindmounts, gloablmounts)
279             proxy.unset_proxy_environ()
280         return retcode
281
282     def cleanup(self):
283         try:
284             # clean mounts
285             cleanup_mounts(self.rootdir)
286             # remove rootdir
287             shutil.rmtree(self.rootdir, ignore_errors=True)
288         except:
289             pass