Merge release-0.28.17 from 'tools/mic'
[platform/upstream/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 import glob
26 import errno
27
28 from mic import msger
29 from mic.utils import errors, proxy, misc
30 from mic.utils.rpmmisc import readRpmHeader, RPMInstallCallback
31 from mic.chroot import cleanup_mounts, setup_chrootenv, cleanup_chrootenv
32 from mic.conf import configmgr
33
34 PATH_BOOTSTRAP = "/usr/sbin:/usr/bin:/sbin:/bin"
35
36 RPMTRANS_FLAGS = [
37                    rpm.RPMTRANS_FLAG_ALLFILES,
38                    rpm.RPMTRANS_FLAG_NOSCRIPTS,
39                    rpm.RPMTRANS_FLAG_NOTRIGGERS,
40                  ]
41
42 RPMVSF_FLAGS = [
43                  rpm._RPMVSF_NOSIGNATURES,
44                  rpm._RPMVSF_NODIGESTS
45                ]
46
47 RPMPROB_FLAGS = [
48                   rpm.RPMPROB_FILTER_OLDPACKAGE,
49                   rpm.RPMPROB_FILTER_REPLACEPKG,
50                   rpm.RPMPROB_FILTER_IGNOREARCH
51                 ]
52
53 class MiniBackend(object):
54     def __init__(self, rootdir, arch=None, repomd=None):
55         self._ts = None
56         self.rootdir = os.path.abspath(rootdir)
57         self.arch = arch
58         self.repomd = repomd
59         self.dlpkgs = []
60         self.localpkgs = {}
61         self.optionals = []
62         self.preins = {}
63         self.postins = {}
64         self.scriptlets = False
65
66     def __del__(self):
67         try:
68             del self.ts
69         except:
70             pass
71
72     def get_ts(self):
73         if not self._ts:
74             self._ts = rpm.TransactionSet(self.rootdir)
75             self._ts.setFlags(reduce(lambda x, y: x|y, RPMTRANS_FLAGS))
76             self._ts.setVSFlags(reduce(lambda x, y: x|y, RPMVSF_FLAGS))
77             self._ts.setProbFilter(reduce(lambda x, y: x|y, RPMPROB_FLAGS))
78
79         return self._ts
80
81     def del_ts(self):
82         if self._ts:
83             self._ts.closeDB()
84             self._ts = None
85
86     ts = property(fget = lambda self: self.get_ts(),
87                   fdel = lambda self: self.del_ts(),
88                   doc="TransactionSet object")
89
90     def selectPackage(self, pkg):
91         if not pkg in self.dlpkgs:
92             self.dlpkgs.append(pkg)
93
94     def _get_local_packages(self, pkg):
95         """Return local mic-bootstrap rpm path."""
96         cropts = configmgr.create
97         if cropts['local_pkgs_path']:
98             if os.path.isdir(cropts['local_pkgs_path']):
99                 pkglist = glob.glob(
100                            os.path.join(cropts['local_pkgs_path'], pkg + '*.rpm'))
101                 if len(pkglist) > 1:
102                     raise errors.BootstrapError("Many %s packages in folder, put only one %s package in it." % (pkg, pkg))
103                 elif len(pkglist) == 1:
104                     return ''.join(pkglist)
105             elif os.path.splitext(cropts['local_pkgs_path'])[-1] == '.rpm':
106                 if cropts['local_pkgs_path'].find(pkg) >= 0:
107                     return cropts['local_pkgs_path']
108         return None
109     def runInstall(self):
110         # FIXME: check space
111         self.downloadPkgs()
112         self.installPkgs()
113
114         if not self.scriptlets:
115             return
116
117         for pkg in self.preins.keys():
118             prog, script = self.preins[pkg]
119             self.run_pkg_script(pkg, prog, script, '0')
120         for pkg in self.postins.keys():
121             prog, script = self.postins[pkg]
122             self.run_pkg_script(pkg, prog, script, '1')
123
124     def downloadPkgs(self):
125         nonexist = []
126         for pkg in self.dlpkgs:
127             localpth = self._get_local_packages(pkg)
128             if localpth is None:
129                 localpth = misc.get_package(pkg, self.repomd, self.arch)
130             if localpth:
131                 self.localpkgs[pkg] = localpth
132             elif pkg in self.optionals:
133                 # skip optional rpm
134                 continue
135             else:
136                 # mark nonexist rpm
137                 nonexist.append(pkg)
138
139         if nonexist:
140             raise errors.BootstrapError("Can't get rpm binary: %s" %
141                                         ','.join(nonexist))
142
143     def installPkgs(self):
144         for pkg in self.localpkgs.keys():
145             rpmpath = self.localpkgs[pkg]
146
147             hdr = readRpmHeader(self.ts, rpmpath)
148
149             # save prein and postin scripts
150             self.preins[pkg] = (hdr['PREINPROG'], hdr['PREIN'])
151             self.postins[pkg] = (hdr['POSTINPROG'], hdr['POSTIN'])
152
153             # mark pkg as install
154             self.ts.addInstall(hdr, rpmpath, 'u')
155
156         # run transaction
157         self.ts.order()
158         cb = RPMInstallCallback(self.ts)
159         errs = self.ts.run(cb.callback, '')
160
161         # ts.run() exit codes are, hmm, "creative": None means all ok, empty 
162         # list means some errors happened in the transaction and non-empty 
163         # list that there were errors preventing the ts from starting...
164         if errs is not None:
165              raise errors.BootstrapError("Transaction couldn't start: %s" % errs)
166
167     def run_pkg_script(self, pkg, prog, script, arg):
168         mychroot = lambda: os.chroot(self.rootdir)
169
170         if not script:
171             return
172
173         if prog == "<lua>":
174             prog = "/usr/bin/lua"
175
176         tmpdir = os.path.join(self.rootdir, "tmp")
177         if not os.path.exists(tmpdir):
178             os.makedirs(tmpdir)
179         tmpfd, tmpfp = tempfile.mkstemp(dir=tmpdir, prefix="%s.pre-" % pkg)
180         script = script.replace('\r', '')
181         os.write(tmpfd, script)
182         os.close(tmpfd)
183         os.chmod(tmpfp, 0o700)
184
185         try:
186             script_fp = os.path.join('/tmp', os.path.basename(tmpfp))
187             subprocess.call([prog, script_fp, arg], preexec_fn=mychroot)
188         except (OSError, IOError) as err:
189             msger.warning(str(err))
190         finally:
191             os.unlink(tmpfp)
192
193 class Bootstrap(object):
194     def __init__(self, rootdir, distro, arch=None):
195         self.rootdir = misc.mkdtemp(dir=rootdir, prefix=distro)
196         self.distro = distro
197         self.arch = arch
198         self.logfile = None
199         self.pkgslist = []
200         self.repomd = None
201
202     def __del__(self):
203         self.cleanup()
204
205     def get_rootdir(self):
206         if os.path.exists(self.rootdir):
207             shutil.rmtree(self.rootdir, ignore_errors=True)
208         os.makedirs(self.rootdir)
209         return self.rootdir
210
211     def dirsetup(self, rootdir=None):
212         _path = lambda pth: os.path.join(rootdir, pth.lstrip('/'))
213
214         if not rootdir:
215             rootdir = self.rootdir
216
217         try:
218             # make /tmp and /etc path
219             tmpdir = _path('/tmp')
220             if not os.path.exists(tmpdir):
221                 os.makedirs(tmpdir)
222             etcdir = _path('/etc')
223             if not os.path.exists(etcdir):
224                 os.makedirs(etcdir)
225
226             # touch distro file
227             tzdist = _path('/etc/%s-release' % self.distro)
228             if not os.path.exists(tzdist):
229                 with open(tzdist, 'w') as wf:
230                     wf.write("bootstrap")
231         except:
232             pass
233
234     def create(self, repomd, pkglist, optlist=()):
235         try:
236             pkgmgr = MiniBackend(self.get_rootdir())
237             pkgmgr.arch = self.arch
238             pkgmgr.repomd = repomd
239             pkgmgr.optionals = list(optlist)
240             map(pkgmgr.selectPackage, pkglist + list(optlist))
241             pkgmgr.runInstall()
242         except (OSError, IOError, errors.CreatorError) as err:
243             raise errors.BootstrapError("%s" % err)
244
245     def run(self, cmd, chdir, rootdir=None, bindmounts=None):
246         def mychroot():
247             os.chroot(rootdir)
248             os.chdir(chdir)
249
250         def sync_timesetting(rootdir):
251             try:
252                 # sync time and zone info to bootstrap
253                 if os.path.exists(rootdir + "/etc/localtime"):
254                     os.unlink(rootdir + "/etc/localtime")
255                 shutil.copyfile("/etc/localtime", rootdir + "/etc/localtime")
256             except:
257                 pass
258
259         def sync_passwdfile(rootdir):
260             try:
261                 # sync passwd file to bootstrap, saving the user info
262                 if os.path.exists(rootdir + "/etc/passwd"):
263                     os.unlink(rootdir + "/etc/passwd")
264                 shutil.copyfile("/etc/passwd", rootdir + "/etc/passwd")
265             except:
266                 pass
267
268         def sync_hostfile(rootdir):
269             try:
270                 # sync host info to bootstrap
271                 if os.path.exists(rootdir + "/etc/hosts"):
272                     os.unlink(rootdir + "/etc/hosts")
273                 shutil.copyfile("/etc/hosts", rootdir + "/etc/hosts")
274             except:
275                 pass
276
277         def sync_signfile(rootdir,homedir):
278             if os.path.exists(homedir + "/.sign"):
279                 signfile = rootdir + homedir + "/.sign"
280                 try:
281                     # sync sign info to bootstrap
282                     if os.path.exists(signfile):
283                         if os.path.isdir(signfile):
284                             shutil.rmtree(signfile)
285                         else:
286                             os.unlink(signfile)
287                     try:
288                         shutil.copytree(homedir + "/.sign", signfile)
289                     except OSError as exc:
290                         if exc.errno == errno.ENOTDIR:
291                             shutil.copy(homedir + "/.sign", signfile)
292                         else: raise
293                 except:
294                     pass
295
296         if not rootdir:
297             rootdir = self.rootdir
298
299         if isinstance(cmd, list):
300             shell = False
301         else:
302             shell = True
303
304         env = os.environ
305         env['PATH'] = "%s:%s" % (PATH_BOOTSTRAP, env['PATH'])
306         cropts = configmgr.create
307         if cropts["ks"]:
308             lang = cropts["ks"].handler.lang.lang
309             env['LANG'] = lang
310             env['LC_ALL'] = lang
311
312         retcode = 0
313         gloablmounts = None
314         try:
315             proxy.set_proxy_environ()
316             #globalmounts is no useless, remove it, currently function always return none.
317             setup_chrootenv(rootdir, bindmounts)
318             sync_timesetting(rootdir)
319             sync_passwdfile(rootdir)
320             sync_hostfile(rootdir)
321             env['SUDO_USER'] = 'root'
322             if 'SUDO_USER' in env:
323                 sync_signfile(rootdir, os.path.expanduser('~' + env['SUDO_USER']))
324             else:
325                 sync_signfile(rootdir, os.path.expanduser('~'))
326             retcode = subprocess.call(cmd, preexec_fn=mychroot, env=env, shell=shell)
327         except (OSError, IOError):
328             # add additional information to original exception
329             value, tb = sys.exc_info()[1:]
330             value = '%s: %s' % (value, ' '.join(cmd))
331             raise RuntimeError (value, tb)
332         finally:
333             #if self.logfile and os.path.isfile(self.logfile):
334             #    msger.log(file(self.logfile).read())
335             cleanup_chrootenv(rootdir, bindmounts, gloablmounts)
336             proxy.unset_proxy_environ()
337         return retcode
338
339     def cleanup(self):
340         try:
341             # clean mounts
342             cleanup_mounts(self.rootdir)
343             # remove rootdir
344             shutil.rmtree(self.rootdir, ignore_errors=True)
345         except:
346             pass