1#!/usr/bin/env python
2#
3# Copyright (C) 2010 Oracle. All rights reserved.
4#
5# This program is free software; you can redistribute it and/or modify it under
6# the terms of the GNU General Public License as published by the Free Software
7# Foundation, version 2.  This program is distributed in the hope that it will be
8# useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
9# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
10# Public License for more details.  You should have received a copy of the GNU
11# General Public License along with this program; If not, see <http://www.gnu.org/licenses/>.
12
13import sys
14import os
15import stat
16import time
17import string
18import random
19import tempfile
20import commands
21import subprocess
22import urlgrabber
23from optparse import OptionParser
24
25
26XEN_PATHS = [
27    ('images/xen/vmlinuz', 'images/xen/initrd.img'), # Fedora <= 10 and OL = 5
28    ('boot/i386/vmlinuz-xen', 'boot/i386/initrd-xen'), # openSUSE >= 10.2 and SLES >= 10
29    ('boot/x86_64/vmlinuz-xen', 'boot/x86_64/initrd-xen'), # openSUSE >= 10.2 and SLES >= 10
30    ('current/images/netboot/xen/vmlinuz', 'current/images/netboot/xen/initrd.gz'), # Debian
31    ('images/pxeboot/vmlinuz', 'images/pxeboot/initrd.img'), # Fedora >=10 and OL >= 6
32    ('isolinux/vmlinuz', 'isolinux/initrd.img'), # Fedora >= 10 and OL >= 6
33]
34
35
36def format_sxp(kernel, ramdisk, args):
37    s = 'linux (kernel %s)' % kernel
38    if ramdisk:
39        s += '(ramdisk %s)' % ramdisk
40    if args:
41        s += '(args "%s")' % args
42    return s
43
44
45def format_simple(kernel, ramdisk, args, sep):
46    s = ('kernel %s' % kernel) + sep
47    if ramdisk:
48        s += ('ramdisk %s' % ramdisk) + sep
49    if args:
50        s += ('args %s' % args) + sep
51    s += sep
52    return s
53
54
55def mount(dev, path, option=''):
56    if os.uname()[0] == 'SunOS':
57        mountcmd = '/usr/sbin/mount'
58    else:
59        mountcmd = '/bin/mount'
60    cmd = ' '.join([mountcmd, option, dev, path])
61    (status, output) = commands.getstatusoutput(cmd)
62    if status != 0:
63        raise RuntimeError('Command: (%s) failed: (%s) %s' % (cmd, status, output))
64
65
66def umount(path):
67    if os.uname()[0] == 'SunOS':
68        cmd = ['/usr/sbin/umount', path]
69    else:
70        cmd = ['/bin/umount', path]
71    subprocess.call(cmd)
72
73
74class Fetcher:
75    def __init__(self, location, tmpdir):
76        self.location = location
77        self.tmpdir = tmpdir
78        self.srcdir = location
79
80    def prepare(self):
81        if not os.path.exists(self.tmpdir):
82            os.makedirs(self.tmpdir, 0750)
83
84    def cleanup(self):
85        pass
86
87    def get_file(self, filename):
88        url = os.path.join(self.srcdir, filename)
89        suffix = ''.join(random.sample(string.ascii_letters, 6))
90        local_name = os.path.join(self.tmpdir, 'xenpvboot.%s.%s' % (os.path.basename(filename), suffix))
91        try:
92            return urlgrabber.urlgrab(url, local_name, copy_local=1)
93        except Exception, err:
94            raise RuntimeError('Cannot get file %s: %s' % (url, err))
95
96
97class MountedFetcher(Fetcher):
98    def prepare(self):
99        Fetcher.prepare(self)
100        self.srcdir = tempfile.mkdtemp(prefix='xenpvboot.', dir=self.tmpdir)
101        if self.location.startswith('nfs:'):
102            mount(self.location[4:], self.srcdir, '-o ro')
103        else:
104            if stat.S_ISBLK(os.stat(self.location)[stat.ST_MODE]):
105                option = '-o ro'
106            else:
107                option = '-o ro,loop'
108            if os.uname()[0] == 'SunOS':
109                option += ' -F hsfs'
110            mount(self.location, self.srcdir, option)
111
112    def cleanup(self):
113        umount(self.srcdir)
114        try:
115            os.rmdir(self.srcdir)
116        except:
117            pass
118
119
120class NFSISOFetcher(MountedFetcher):
121    def __init__(self, location, tmpdir):
122        self.nfsdir = None
123        MountedFetcher.__init__(self, location, tmpdir)
124
125    def prepare(self):
126        Fetcher.prepare(self)
127        self.nfsdir = tempfile.mkdtemp(prefix='xenpvboot.', dir=self.tmpdir)
128        self.srcdir = tempfile.mkdtemp(prefix='xenpvboot.', dir=self.tmpdir)
129        nfs = os.path.dirname(self.location[8:])
130        iso = os.path.basename(self.location[8:])
131        mount(nfs, self.nfsdir, '-o ro')
132        option = '-o ro,loop'
133        if os.uname()[0] == 'SunOS':
134            option += ' -F hsfs'
135        mount(os.path.join(self.nfsdir, iso), self.srcdir, option)
136
137    def cleanup(self):
138        MountedFetcher.cleanup(self)
139        time.sleep(1)
140        umount(self.nfsdir)
141        try:
142            os.rmdir(self.nfsdir)
143        except:
144            pass
145
146
147class TFTPFetcher(Fetcher):
148    def get_file(self, filename):
149        if '/' in self.location[7:]:
150            host = self.location[7:].split('/', 1)[0].replace(':', ' ')
151            basedir = self.location[7:].split('/', 1)[1]
152        else:
153            host = self.location[7:].replace(':', ' ')
154            basedir = ''
155        suffix = ''.join(random.sample(string.ascii_letters, 6))
156        local_name = os.path.join(self.tmpdir, 'xenpvboot.%s.%s' % (os.path.basename(filename), suffix))
157        cmd = '/usr/bin/tftp %s -c get %s %s' % (host, os.path.join(basedir, filename), local_name)
158        (status, output) = commands.getstatusoutput(cmd)
159        if status != 0:
160            raise RuntimeError('Command: (%s) failed: (%s) %s' % (cmd, status, output))
161        return local_name
162
163
164def main():
165    usage = '''%prog [option]
166
167Get boot images from the given location and prepare for Xen to use.
168
169Supported locations:
170
171 - http://host/path
172 - https://host/path
173 - ftp://host/path
174 - file:///path
175 - tftp://host/path
176 - nfs:host:/path
177 - /path
178 - /path/file.iso
179 - /path/filesystem.img
180 - /dev/sda1
181 - nfs+iso:host:/path/file.iso
182 - nfs+iso:host:/path/filesystem.img'''
183    version = '%prog version 0.1'
184    parser = OptionParser(usage=usage, version=version)
185    parser.add_option('', '--location',
186                      help='The base url for kernel and ramdisk files.')
187    parser.add_option('', '--kernel',
188                      help='The kernel image file.')
189    parser.add_option('', '--ramdisk',
190                      help='The initial ramdisk file.')
191    parser.add_option('', '--args',
192                      help='Arguments pass to the kernel.')
193    parser.add_option('', '--output',
194                      help='Redirect output to this file instead of stdout.')
195    parser.add_option('', '--output-directory', default='/var/run/libxl',
196                      help='Output directory.')
197    parser.add_option('', '--output-format', default='sxp',
198                      help='Output format: sxp, simple or simple0.')
199    parser.add_option('-q', '--quiet', action='store_true',
200                      help='Be quiet.')
201    (opts, args) = parser.parse_args()
202
203    if not opts.location and not opts.kernel and not opts.ramdisk:
204        if not opts.quiet:
205            print >> sys.stderr, 'You should at least specify a location or kernel/ramdisk.'
206            parser.print_help(sys.stderr)
207        sys.exit(1)
208
209    if not opts.output or opts.output == '-':
210        fd = sys.stdout.fileno()
211    else:
212        fd = os.open(opts.output, os.O_WRONLY)
213
214    if opts.location:
215        location = opts.location
216    else:
217        location = ''
218    if (location == ''
219        or location.startswith('http://') or location.startswith('https://')
220        or location.startswith('ftp://') or location.startswith('file://')
221        or (os.path.exists(location) and os.path.isdir(location))):
222        fetcher = Fetcher(location, opts.output_directory)
223    elif location.startswith('nfs:') or (os.path.exists(location) and not os.path.isdir(location)):
224        fetcher = MountedFetcher(location, opts.output_directory)
225    elif location.startswith('nfs+iso:'):
226        fetcher = NFSISOFetcher(location, opts.output_directory)
227    elif location.startswith('tftp://'):
228        fetcher = TFTPFetcher(location, opts.output_directory)
229    else:
230        if not opts.quiet:
231            print >> sys.stderr, 'Unsupported location: %s' % location
232        sys.exit(1)
233
234    try:
235        fetcher.prepare()
236    except Exception, err:
237        if not opts.quiet:
238            print >> sys.stderr, str(err)
239        fetcher.cleanup()
240        sys.exit(1)
241
242    try:
243        kernel = None
244        if opts.kernel:
245            kernel = fetcher.get_file(opts.kernel)
246        else:
247            for (kernel_path, _) in XEN_PATHS:
248                try:
249                    kernel = fetcher.get_file(kernel_path)
250                except Exception, err:
251                    if not opts.quiet:
252                        print >> sys.stderr, str(err)
253                    continue
254                break
255
256        if not kernel:
257            if not opts.quiet:
258                print >> sys.stderr, 'Cannot get kernel from loacation: %s' % location
259            sys.exit(1)
260
261        ramdisk = None
262        if opts.ramdisk:
263            ramdisk = fetcher.get_file(opts.ramdisk)
264        else:
265            for (_, ramdisk_path) in XEN_PATHS:
266                try:
267                    ramdisk = fetcher.get_file(ramdisk_path)
268                except Exception, err:
269                    if not opts.quiet:
270                        print >> sys.stderr, str(err)
271                    continue
272                break
273    finally:
274        fetcher.cleanup()
275
276    if opts.output_format == 'sxp':
277        output = format_sxp(kernel, ramdisk, opts.args)
278    elif opts.output_format == 'simple':
279        output = format_simple(kernel, ramdisk, opts.args, '\n')
280    elif opts.output_format == 'simple0':
281        output = format_simple(kernel, ramdisk, opts.args, '\0')
282    else:
283        print >> sys.stderr, 'Unknown output format: %s' % opts.output_format
284        sys.exit(1)
285
286    sys.stdout.flush()
287    os.write(fd, output)
288
289
290if __name__ == '__main__':
291    main()
292