3 # Copyright (c) 2009, 2010, 2011 Intel, Inc.
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
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
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.
18 from __future__ import with_statement
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
34 PATH_BOOTSTRAP = "/usr/sbin:/usr/bin:/sbin:/bin"
37 rpm.RPMTRANS_FLAG_ALLFILES,
38 rpm.RPMTRANS_FLAG_NOSCRIPTS,
39 rpm.RPMTRANS_FLAG_NOTRIGGERS,
43 rpm._RPMVSF_NOSIGNATURES,
48 rpm.RPMPROB_FILTER_OLDPACKAGE,
49 rpm.RPMPROB_FILTER_REPLACEPKG,
50 rpm.RPMPROB_FILTER_IGNOREARCH
53 class MiniBackend(object):
54 def __init__(self, rootdir, arch=None, repomd=None):
56 self.rootdir = os.path.abspath(rootdir)
64 self.scriptlets = False
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))
86 ts = property(fget = lambda self: self.get_ts(),
87 fdel = lambda self: self.del_ts(),
88 doc="TransactionSet object")
90 def selectPackage(self, pkg):
91 if not pkg in self.dlpkgs:
92 self.dlpkgs.append(pkg)
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']):
100 os.path.join(cropts['local_pkgs_path'], pkg + '*.rpm'))
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']
109 def runInstall(self):
114 if not self.scriptlets:
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')
124 def downloadPkgs(self):
126 for pkg in self.dlpkgs:
127 localpth = self._get_local_packages(pkg)
129 localpth = misc.get_package(pkg, self.repomd, self.arch)
131 self.localpkgs[pkg] = localpth
132 elif pkg in self.optionals:
140 raise errors.BootstrapError("Can't get rpm binary: %s" %
143 def installPkgs(self):
144 for pkg in self.localpkgs.keys():
145 rpmpath = self.localpkgs[pkg]
147 hdr = readRpmHeader(self.ts, rpmpath)
149 # save prein and postin scripts
150 self.preins[pkg] = (hdr['PREINPROG'], hdr['PREIN'])
151 self.postins[pkg] = (hdr['POSTINPROG'], hdr['POSTIN'])
153 # mark pkg as install
154 self.ts.addInstall(hdr, rpmpath, 'u')
158 cb = RPMInstallCallback(self.ts)
159 errs = self.ts.run(cb.callback, '')
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...
165 raise errors.BootstrapError("Transaction couldn't start: %s" % errs)
167 def run_pkg_script(self, pkg, prog, script, arg):
168 mychroot = lambda: os.chroot(self.rootdir)
174 prog = "/usr/bin/lua"
176 tmpdir = os.path.join(self.rootdir, "tmp")
177 if not os.path.exists(tmpdir):
179 tmpfd, tmpfp = tempfile.mkstemp(dir=tmpdir, prefix="%s.pre-" % pkg)
180 script = script.replace('\r', '')
181 os.write(tmpfd, script)
183 os.chmod(tmpfp, 0o700)
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))
193 class Bootstrap(object):
194 def __init__(self, rootdir, distro, arch=None):
195 self.rootdir = misc.mkdtemp(dir=rootdir, prefix=distro)
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)
211 def dirsetup(self, rootdir=None):
212 _path = lambda pth: os.path.join(rootdir, pth.lstrip('/'))
215 rootdir = self.rootdir
218 # make /tmp and /etc path
219 tmpdir = _path('/tmp')
220 if not os.path.exists(tmpdir):
222 etcdir = _path('/etc')
223 if not os.path.exists(etcdir):
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")
234 def create(self, repomd, pkglist, optlist=()):
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))
242 except (OSError, IOError, errors.CreatorError) as err:
243 raise errors.BootstrapError("%s" % err)
245 def run(self, cmd, chdir, rootdir=None, bindmounts=None):
250 def sync_timesetting(rootdir):
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")
259 def sync_passwdfile(rootdir):
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")
268 def sync_hostfile(rootdir):
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")
277 def sync_signfile(rootdir,homedir):
278 if os.path.exists(homedir + "/.sign"):
279 signfile = rootdir + homedir + "/.sign"
281 # sync sign info to bootstrap
282 if os.path.exists(signfile):
283 if os.path.isdir(signfile):
284 shutil.rmtree(signfile)
288 shutil.copytree(homedir + "/.sign", signfile)
289 except OSError as exc:
290 if exc.errno == errno.ENOTDIR:
291 shutil.copy(homedir + "/.sign", signfile)
297 rootdir = self.rootdir
299 if isinstance(cmd, list):
305 env['PATH'] = "%s:%s" % (PATH_BOOTSTRAP, env['PATH'])
306 cropts = configmgr.create
308 lang = cropts["ks"].handler.lang.lang
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']))
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)
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()
342 cleanup_mounts(self.rootdir)
344 shutil.rmtree(self.rootdir, ignore_errors=True)