# This is a BitKeeper generated diff -Nru style patch. # # ChangeSet # 2005/04/25 18:25:47-04:00 katzj@xxxxxxxxxxxxxx # Add passing of booting a specific entry by label or by index # # tools/python/xen/xm/create.py # 2005/04/25 18:25:45-04:00 katzj@xxxxxxxxxxxxxx +6 -2 # allow specifying the default entry to boot # # tools/python/xen/xend/XendBootloader.py # 2005/04/25 18:25:45-04:00 katzj@xxxxxxxxxxxxxx +9 -3 # support passing the default entry to boot # # tools/pygrub/src/pygrub # 2005/04/25 18:25:45-04:00 katzj@xxxxxxxxxxxxxx +35 -20 # support passing an entry to boot either by label or by index. also # remove some dead code and fix a few minor bugs # # ChangeSet # 2005/04/14 12:16:27-04:00 katzj@xxxxxxxxxxxxxx # Minor fixes based on feedback from Mike Wray # # tools/python/xen/xm/create.py # 2005/04/14 12:16:25-04:00 katzj@xxxxxxxxxxxxxx +2 -3 # Use method in blkif to do uname -> file conversion # # tools/python/xen/xend/server/blkif.py # 2005/04/14 12:16:25-04:00 katzj@xxxxxxxxxxxxxx +9 -0 # Add method to convert from a uname to a real file # # tools/python/xen/xend/XendDomainInfo.py # 2005/04/14 12:16:25-04:00 katzj@xxxxxxxxxxxxxx +6 -8 # Make lack of a disk a fatal error. Use the method in blkif # for going from a disk uname to a filename # # tools/python/xen/xend/XendBootloader.py # 2005/04/14 12:16:25-04:00 katzj@xxxxxxxxxxxxxx +14 -4 # Improve error handling. Log errors and raise an error on a problem # instead of just printing to stderr and exiting # # ChangeSet # 2005/04/13 23:02:35-04:00 katzj@xxxxxxxxxxxxxx # Add pygrub, a simple boot loader for use to read a boot loader # config out of a domU's filesystem and then read kernels out as well. # Currently only has support for ext[23] via libext2fs, but it should # be easy to add other filesystems. # # Still needs work to complete the interface and to support full disk # images instead of just partitions. # # tools/pygrub/src/pygrub # 2005/04/13 23:02:33-04:00 katzj@xxxxxxxxxxxxxx +255 -0 # # tools/pygrub/src/fsys/ext2/test.py # 2005/04/13 23:02:33-04:00 katzj@xxxxxxxxxxxxxx +15 -0 # # tools/pygrub/src/fsys/ext2/ext2module.c # 2005/04/13 23:02:33-04:00 katzj@xxxxxxxxxxxxxx +332 -0 # # tools/pygrub/src/fsys/ext2/__init__.py # 2005/04/13 23:02:33-04:00 katzj@xxxxxxxxxxxxxx +38 -0 # # tools/pygrub/src/fsys/__init__.py # 2005/04/13 23:02:33-04:00 katzj@xxxxxxxxxxxxxx +61 -0 # # tools/pygrub/src/GrubConf.py # 2005/04/13 23:02:33-04:00 katzj@xxxxxxxxxxxxxx +229 -0 # # tools/pygrub/setup.py # 2005/04/13 23:02:33-04:00 katzj@xxxxxxxxxxxxxx +25 -0 # # tools/pygrub/Makefile # 2005/04/13 23:02:33-04:00 katzj@xxxxxxxxxxxxxx +18 -0 # # tools/pygrub/src/pygrub # 2005/04/13 23:02:33-04:00 katzj@xxxxxxxxxxxxxx +0 -0 # BitKeeper file /home/katzj/cvs/xen/xen-pygrub/tools/pygrub/src/pygrub # # tools/pygrub/src/fsys/ext2/test.py # 2005/04/13 23:02:33-04:00 katzj@xxxxxxxxxxxxxx +0 -0 # BitKeeper file /home/katzj/cvs/xen/xen-pygrub/tools/pygrub/src/fsys/ext2/test.py # # tools/pygrub/src/fsys/ext2/ext2module.c # 2005/04/13 23:02:33-04:00 katzj@xxxxxxxxxxxxxx +0 -0 # BitKeeper file /home/katzj/cvs/xen/xen-pygrub/tools/pygrub/src/fsys/ext2/ext2module.c # # tools/pygrub/src/fsys/ext2/__init__.py # 2005/04/13 23:02:33-04:00 katzj@xxxxxxxxxxxxxx +0 -0 # BitKeeper file /home/katzj/cvs/xen/xen-pygrub/tools/pygrub/src/fsys/ext2/__init__.py # # tools/pygrub/src/fsys/__init__.py # 2005/04/13 23:02:33-04:00 katzj@xxxxxxxxxxxxxx +0 -0 # BitKeeper file /home/katzj/cvs/xen/xen-pygrub/tools/pygrub/src/fsys/__init__.py # # tools/pygrub/src/GrubConf.py # 2005/04/13 23:02:33-04:00 katzj@xxxxxxxxxxxxxx +0 -0 # BitKeeper file /home/katzj/cvs/xen/xen-pygrub/tools/pygrub/src/GrubConf.py # # tools/pygrub/setup.py # 2005/04/13 23:02:33-04:00 katzj@xxxxxxxxxxxxxx +0 -0 # BitKeeper file /home/katzj/cvs/xen/xen-pygrub/tools/pygrub/setup.py # # tools/pygrub/Makefile # 2005/04/13 23:02:33-04:00 katzj@xxxxxxxxxxxxxx +0 -0 # BitKeeper file /home/katzj/cvs/xen/xen-pygrub/tools/pygrub/Makefile # # tools/Makefile # 2005/04/13 23:02:33-04:00 katzj@xxxxxxxxxxxxxx +1 -0 # Add pygrub subdirectory # # ChangeSet # 2005/04/13 23:01:42-04:00 katzj@xxxxxxxxxxxxxx # Add framework for running a bootloader from within xend and xm. To # specify the executable to run, set in your domain config like # bootloader="/usr/bin/pygrub" # # The bootloader will get executed on both domain creation and domain # reboot to get the default kernel/initrd specified in the bootloader # config for the domain. If you auto-connect the console on domain # creation, then your bootloader will be run in an interactive mode. # # BitKeeper/etc/logging_ok # 2005/04/13 23:01:42-04:00 katzj@xxxxxxxxxxxxxx +1 -0 # Logging to logging@xxxxxxxxxxxxxxx accepted # # tools/python/xen/xend/XendBootloader.py # 2005/04/13 23:01:37-04:00 katzj@xxxxxxxxxxxxxx +79 -0 # # tools/python/xen/xm/create.py # 2005/04/13 23:01:37-04:00 katzj@xxxxxxxxxxxxxx +29 -2 # Run bootloader on domain creation. If console is being connected, # then run in an interactive mode, else just go with the default. # # tools/python/xen/xend/XendDomainInfo.py # 2005/04/13 23:01:37-04:00 katzj@xxxxxxxxxxxxxx +41 -1 # Add running of a boot loader from xend on domain reboot. Runs in # an uninteractive mode to get the default kernel/initrd for the # domain. Also removes any temporary kernels created by the # bootloader. # # tools/python/xen/xend/XendBootloader.py # 2005/04/13 23:01:37-04:00 katzj@xxxxxxxxxxxxxx +0 -0 # BitKeeper file /home/katzj/cvs/xen/xen-pygrub/tools/python/xen/xend/XendBootloader.py # diff -Nru a/tools/Makefile b/tools/Makefile --- a/tools/Makefile 2005-04-25 18:49:05 -04:00 +++ b/tools/Makefile 2005-04-25 18:49:05 -04:00 @@ -11,6 +11,7 @@ SUBDIRS += xfrd SUBDIRS += xcs SUBDIRS += ioemu +SUBDIRS += pygrub .PHONY: all install clean check check_clean diff -Nru a/tools/pygrub/Makefile b/tools/pygrub/Makefile --- /dev/null Wed Dec 31 16:00:00 196900 +++ b/tools/pygrub/Makefile 2005-04-25 18:49:05 -04:00 @@ -0,0 +1,18 @@ + +XEN_ROOT = ../.. +include $(XEN_ROOT)/tools/Rules.mk + +all: build +build: + CFLAGS="$(CFLAGS)" python setup.py build + +ifndef XEN_PYTHON_NATIVE_INSTALL +install: all + CFLAGS="$(CFLAGS)" python setup.py install --home="$(DESTDIR)/usr" +else +install: all + CFLAGS="$(CFLAGS)" python setup.py install --root="$(DESTDIR)" +endif + +clean: + rm -rf build *.pyc *.pyo *.o *.a *~ diff -Nru a/tools/pygrub/setup.py b/tools/pygrub/setup.py --- /dev/null Wed Dec 31 16:00:00 196900 +++ b/tools/pygrub/setup.py 2005-04-25 18:49:05 -04:00 @@ -0,0 +1,25 @@ +from distutils.core import setup, Extension +import os + +extra_compile_args = [ "-fno-strict-aliasing", "-Wall", "-Werror" ] + +# in a perfect world, we'd figure out the fsys modules dynamically +ext2 = Extension("grub.fsys.ext2._pyext2", + extra_compile_args = extra_compile_args, + libraries = ["ext2fs"], + sources = ["src/fsys/ext2/ext2module.c"]) + +setup(name='pygrub', + version='0.1', + description='Boot loader that looks a lot like grub for Xen', + author='Jeremy Katz', + author_email='katzj@xxxxxxxxxx', + license='GPL', + package_dir={'grub': 'src'}, + scripts = ["src/pygrub"], + packages=['grub', + 'grub.fsys', + 'grub.fsys.ext2'], + ext_modules = [ext2] + ) + diff -Nru a/tools/pygrub/src/GrubConf.py b/tools/pygrub/src/GrubConf.py --- /dev/null Wed Dec 31 16:00:00 196900 +++ b/tools/pygrub/src/GrubConf.py 2005-04-25 18:49:05 -04:00 @@ -0,0 +1,229 @@ +# +# GrubConf.py - Simple grub.conf parsing +# +# Copyright 2005 Red Hat, Inc. +# Jeremy Katz +# +# This software may be freely redistributed under the terms of the GNU +# general public license. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +# + +import os, sys +import logging + +def grub_split(s, maxsplit = -1): + """Split a grub option screen separated with either '=' or whitespace.""" + eq = s.find('=') + if eq == -1: + return s.split(None, maxsplit) + + # see which of a space or tab is first + sp = s.find(' ') + tab = s.find('\t') + if (tab != -1 and tab < sp) or (tab != -1 and sp == -1): + sp = tab + + if eq != -1 and eq < sp or (eq != -1 and sp == -1): + return s.split('=', maxsplit) + else: + return s.split(None, maxsplit) + +def get_path(s): + """Returns a tuple of (GrubDiskPart, path) corresponding to string.""" + if not s.startswith('('): + return (None, s) + idx = s.find(')') + if idx == -1: + raise ValueError, "Unable to find matching ')'" + d = s[:idx] + return (GrubDiskPart(d), s[idx + 1:]) + +class GrubDiskPart(object): + def __init__(self, str): + if str.find(',') != -1: + (self.disk, self.part) = str.split(",", 2) + else: + self.disk = str + self.part = None + + def __repr__(self): + if self.part is not None: + return "d%dp%d" %(self.disk, self.part) + else: + return "d%d" %(self,disk,) + + def get_disk(self): + return self._disk + def set_disk(self, val): + val = val.replace("(", "").replace(")", "") + self._disk = int(val[2:]) + disk = property(get_disk, set_disk) + + def get_part(self): + return self._part + def set_part(self, val): + if val is None: + self._part = val + return + val = val.replace("(", "").replace(")", "") + self._part = int(val) + part = property(get_part, set_part) + +class GrubImage(object): + def __init__(self, lines): + self._root = self._initrd = self._kernel = self._args = None + for l in lines: + (com, arg) = grub_split(l, 1) + + if self.commands.has_key(com): + if self.commands[com] is not None: + exec("%s = r\"%s\"" %(self.commands[com], arg.strip())) + else: + logging.info("Ignored image directive %s" %(com,)) + else: + logging.warning("Unknown image directive %s" %(com,)) + + def __repr__(self): + return ("title: %s\n" + " root: %s\n" + " kernel: %s\n" + " args: %s\n" + " initrd: %s" %(self.title, self.root, self.kernel, + self.args, self.initrd)) + + def set_root(self, val): + self._root = GrubDiskPart(val) + def get_root(self): + return self._root + root = property(get_root, set_root) + + def set_kernel(self, val): + if val.find(" ") == -1: + self._kernel = get_path(val) + self._args = None + return + (kernel, args) = val.split(None, 1) + self._kernel = get_path(kernel) + self._args = args + def get_kernel(self): + return self._kernel + def get_args(self): + return self._args + kernel = property(get_kernel, set_kernel) + args = property(get_args) + + def set_initrd(self, val): + self._initrd = get_path(val) + def get_initrd(self): + return self._initrd + initrd = property(get_initrd, set_initrd) + + # set up command handlers + commands = { "title": "self.title", + "root": "self.root", + "rootnoverify": "self.root", + "kernel": "self.kernel", + "initrd": "self.initrd", + "chainloader": None, + "module": None} + + +class GrubConfigFile(object): + def __init__(self, fn = None): + self.filename = fn + self.images = [] + self.timeout = -1 + + if fn is not None: + self.parse() + + def parse(self, buf = None): + if buf is None: + if self.filename is None: + raise ValueError, "No config file defined to parse!" + + f = open(self.filename, 'r') + lines = f.readlines() + f.close() + else: + lines = buf.split("\n") + + img = [] + for l in lines: + l = l.strip() + # skip blank lines + if len(l) == 0: + continue + # skip comments + if l.startswith('#'): + continue + # new image + if l.startswith("title"): + if len(img) > 0: + self.images.append(GrubImage(img)) + img = [l] + continue + + if len(img) > 0: + img.append(l) + continue + + try: + (com, arg) = grub_split(l, 1) + except ValueError: + com = l + arg = "" + + if self.commands.has_key(com): + if self.commands[com] is not None: + exec("%s = r\"%s\"" %(self.commands[com], arg.strip())) + else: + logging.info("Ignored directive %s" %(com,)) + else: + logging.warning("Unknown directive %s" %(com,)) + + if len(img) > 0: + self.images.append(GrubImage(img)) + + def _get_default(self): + return self._default + def _set_default(self, val): + if val == "saved": + self._default = -1 + else: + self._default = int(val) + + if self._default < 0: + raise ValueError, "default must be positive number" + default = property(_get_default, _set_default) + + def set_splash(self, val): + self._splash = get_path(val) + def get_splash(self): + return self._splash + splash = property(get_splash, set_splash) + + # set up command handlers + commands = { "default": "self.default", + "timeout": "self.timeout", + "fallback": "self.fallback", + "hiddenmenu": "self.hiddenmenu", + "splashimage": "self.splash", + "password": "self.password" } + for c in ("bootp", "color", "device", "dhcp", "hide", "ifconfig", + "pager", "partnew", "parttype", "rarp", "serial", + "setkey", "terminal", "terminfo", "tftpserver", "unhide"): + commands[c] = None + del c + + +if __name__ == "__main__": + if sys.argv < 2: + raise RuntimeError, "Need a grub.conf to read" + g = GrubConfigFile(sys.argv[1]) + for i in g.images: + print i #, i.title, i.root, i.kernel, i.args, i.initrd diff -Nru a/tools/pygrub/src/fsys/__init__.py b/tools/pygrub/src/fsys/__init__.py --- /dev/null Wed Dec 31 16:00:00 196900 +++ b/tools/pygrub/src/fsys/__init__.py 2005-04-25 18:49:05 -04:00 @@ -0,0 +1,61 @@ +# +# Copyright 2005 Red Hat, Inc. +# Jeremy Katz +# +# This software may be freely redistributed under the terms of the GNU +# general public license. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +# + +import os +import sys + +fstypes = {} + +def register_fstype(x): + if x.name in fstypes.keys(): + return + fstypes[x.name] = x + +class FileSystemType(object): + """A simple representation for a file system that gives a fs name + and a method for sniffing a file to see if it's of the given fstype.""" + def __init__(self): + self.name = "" + + def sniff_magic(self, fn, offset = 0): + """Look at the filesystem at fn for the appropriate magic starting at + offset offset.""" + raise RuntimeError, "sniff_magic not implemented" + + def open_fs(self, fn, offset = 0): + """Open the given filesystem and return a filesystem object.""" + raise RuntimeError, "open_fs not implemented" + +class FileSystem(object): + def open(self, name, flags = 0, block_size = 0): + """Open the fsys on name with given flags and block_size.""" + raise RuntimeError, "open not implemented" + + def close(self): + """Close the fsys.""" + raise RuntimeError, "close not implemented" + + def open_file(self, file, flags = None): + """Open the file 'name' with the given flags. The returned object + should look similar to a native file object.""" + raise RuntimeError, "open_file not implemented" + + + +mydir = sys.modules['grub.fsys'].__path__[0] +for f in os.listdir(mydir): + if not os.path.isdir("%s/%s" %(mydir, f)): + continue + try: + exec "import grub.fsys.%s" %(f,) + except ImportError, e: + pass diff -Nru a/tools/pygrub/src/fsys/ext2/__init__.py b/tools/pygrub/src/fsys/ext2/__init__.py --- /dev/null Wed Dec 31 16:00:00 196900 +++ b/tools/pygrub/src/fsys/ext2/__init__.py 2005-04-25 18:49:05 -04:00 @@ -0,0 +1,38 @@ +# Copyright 2005 Red Hat, Inc. +# Jeremy Katz +# +# This software may be freely redistributed under the terms of the GNU +# general public license. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +# + +from grub.fsys import register_fstype, FileSystemType +from _pyext2 import * + +import os, struct + +class Ext2FileSystemType(FileSystemType): + def __init__(self): + FileSystemType.__init__(self) + self.name = "ext2" + + def sniff_magic(self, fn, offset = 0): + fd = os.open(fn, os.O_RDONLY) + os.lseek(fd, offset, 0) + buf = os.read(fd, 2048) + + if len(buf) > 1082 and \ + struct.unpack(" + * + * This software may be freely redistributed under the terms of the GNU + * general public license. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include + +#include +#include +#include + +#if (PYTHON_API_VERSION >= 1011) +#define PY_PAD 0L,0L,0L,0L,0L,0L,0L,0L,0L,0L,0L,0L,0L,0L,0L,0L,0L,0L,0L,0L,0L,0L,0L,0L +#else +#define PY_PAD 0L,0L,0L,0L +#endif + + +/* global error object */ +PyObject *Ext2Error; + +typedef struct _Ext2Fs Ext2Fs; +struct _Ext2Fs { + PyObject_HEAD; + ext2_filsys fs; +}; + +typedef struct _Ext2File Ext2File; +struct _Ext2File { + PyObject_HEAD; + ext2_file_t file; +}; + +/* ext2 file object */ + +static PyObject * +ext2_file_close (Ext2File *file, PyObject *args) +{ + if (file->file != NULL) + ext2fs_file_close(file->file); + Py_INCREF(Py_None); + return Py_None; +} + +static PyObject * +ext2_file_read (Ext2File *file, PyObject *args) +{ + int err, size = 0; + size_t n, total = 0; + PyObject * buffer = NULL; + + if (file->file == NULL) { + PyErr_SetString(PyExc_ValueError, "Cannot read from closed file"); + return NULL; + } + + if (!PyArg_ParseTuple(args, "|i", &size)) + return NULL; + + buffer = PyString_FromStringAndSize((char *) NULL, (size) ? size : 4096); + if (buffer == NULL) + return buffer; + + while (1) { + err = ext2fs_file_read(file->file, PyString_AS_STRING(buffer) + total, + (size) ? size : 4096, &n); + if (err) { + if (buffer != NULL) { Py_DECREF(buffer); } + Py_DECREF(buffer); + PyErr_SetString(PyExc_ValueError, "read error"); + return NULL; + } + + total += n; + if (n == 0) + break; + + if (size && size == total) + break; + + if (!size) { + _PyString_Resize(&buffer, total + 4096); + } + } + + _PyString_Resize(&buffer, total); + return buffer; +} + +static void +ext2_file_dealloc (Ext2File * file) +{ + if (file->file != NULL) + ext2fs_file_close(file->file); + PyMem_DEL(file); +} + +static struct PyMethodDef Ext2FileMethods[] = { + { "close", + (PyCFunction) ext2_file_close, + METH_VARARGS, NULL }, + { "read", + (PyCFunction) ext2_file_read, + METH_VARARGS, NULL }, + { NULL, NULL, 0, NULL } +}; + +static PyObject * +ext2_file_getattr (Ext2File * file, char * name) +{ + return Py_FindMethod (Ext2FileMethods, (PyObject *) file, name); +} + +static char Ext2FileType__doc__[] = "This is the ext2 filesystem object"; +PyTypeObject Ext2FileType = { + PyObject_HEAD_INIT(&PyType_Type) + 0, /* ob_size */ + "Ext2File", /* tp_name */ + sizeof(Ext2File), /* tp_size */ + 0, /* tp_itemsize */ + (destructor) ext2_file_dealloc, /* tp_dealloc */ + 0, /* tp_print */ + (getattrfunc) ext2_file_getattr, /* tp_getattr */ + 0, /* tp_setattr */ + 0, /* tp_compare */ + 0, /* tp_repr */ + 0, /* tp_as_number */ + 0, /* tp_as_sequence */ + 0, /* tp_as_mapping */ + 0, /* tp_hash */ + 0, /* tp_call */ + 0, /* tp_str */ + 0, /* tp_getattro */ + 0, /* tp_setattro */ + 0, /* tp_as_buffer */ + 0L, /* tp_flags */ + Ext2FileType__doc__, + PY_PAD +}; + +static PyObject * +ext2_file_open (Ext2Fs *fs, char * name, int flags) +{ + int err; + ext2_file_t f; + ext2_ino_t ino; + Ext2File * file; + + file = (Ext2File *) PyObject_NEW(Ext2File, &Ext2FileType); + file->file = NULL; + + err = ext2fs_namei_follow(fs->fs, EXT2_ROOT_INO, EXT2_ROOT_INO, name, &ino); + if (err) { + PyErr_SetString(PyExc_ValueError, "unable to open file"); + return NULL; + } + + err = ext2fs_file_open(fs->fs, ino, flags, &f); + if (err) { + PyErr_SetString(PyExc_ValueError, "unable to open file"); + return NULL; + } + + file->file = f; + return (PyObject *) file; +} + +/* ext2fs object */ + +static PyObject * +ext2_fs_close (Ext2Fs *fs, PyObject *args) +{ + if (fs->fs != NULL) + ext2fs_close(fs->fs); + Py_INCREF(Py_None); + return Py_None; +} + +static PyObject * +ext2_fs_open (Ext2Fs *fs, PyObject *args, PyObject *kwargs) +{ + static char *kwlist[] = { "name", "flags", "superblock", + "block_size", NULL }; + char * name; + int flags = 0, superblock = 0, err; + unsigned int block_size = 0; + ext2_filsys efs; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "s|iii", kwlist, + &name, &flags, &superblock, &block_size)) + return NULL; + + if (fs->fs != NULL) { + PyErr_SetString(PyExc_ValueError, "already have an fs object"); + return NULL; + } + + err = ext2fs_open(name, flags, superblock, block_size, + unix_io_manager, &efs); + if (err) { + PyErr_SetString(PyExc_ValueError, "unable to open file"); + return NULL; + } + + fs->fs = efs; + + Py_INCREF(Py_None); + return Py_None; +} + +static PyObject * +ext2_fs_open_file (Ext2Fs *fs, PyObject *args, PyObject *kwargs) +{ + static char *kwlist[] = { "name", "flags", NULL }; + char * name; + int flags = 0; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "s|i", kwlist, + &name, &flags)) + return NULL; + + return ext2_file_open(fs, name, flags); +} + +static void +ext2_fs_dealloc (Ext2Fs * fs) +{ + if (fs->fs != NULL) + ext2fs_close(fs->fs); + PyMem_DEL(fs); +} + +static struct PyMethodDef Ext2FsMethods[] = { + { "close", + (PyCFunction) ext2_fs_close, + METH_VARARGS, NULL }, + { "open", + (PyCFunction) ext2_fs_open, + METH_VARARGS|METH_KEYWORDS, NULL }, + { "open_file", + (PyCFunction) ext2_fs_open_file, + METH_VARARGS|METH_KEYWORDS, NULL }, + { NULL, NULL, 0, NULL } +}; + +static PyObject * +ext2_fs_getattr (Ext2Fs * fs, char * name) +{ + return Py_FindMethod (Ext2FsMethods, (PyObject *) fs, name); +} + +static char Ext2FsType__doc__[] = "This is the ext2 filesystem object"; +PyTypeObject Ext2FsType = { + PyObject_HEAD_INIT(&PyType_Type) + 0, /* ob_size */ + "Ext2Fs", /* tp_name */ + sizeof(Ext2Fs), /* tp_size */ + 0, /* tp_itemsize */ + (destructor) ext2_fs_dealloc, /* tp_dealloc */ + 0, /* tp_print */ + (getattrfunc) ext2_fs_getattr, /* tp_getattr */ + 0, /* tp_setattr */ + 0, /* tp_compare */ + 0, /* tp_repr */ + 0, /* tp_as_number */ + 0, /* tp_as_sequence */ + 0, /* tp_as_mapping */ + 0, /* tp_hash */ + 0, /* tp_call */ + 0, /* tp_str */ + 0, /* tp_getattro */ + 0, /* tp_setattro */ + 0, /* tp_as_buffer */ + 0L, /* tp_flags */ + Ext2FsType__doc__, + PY_PAD +}; + +static PyObject * +ext2_fs_new(PyObject *o, PyObject *args, PyObject *kwargs) +{ + static char *kwlist[] = { "name", "flags", "superblock", + "block_size", NULL }; + char * name; + int flags = 0, superblock = 0; + unsigned int block_size = 0; + Ext2Fs *pfs; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "s|iii", kwlist, + &name, &flags, &superblock, &block_size)) + return NULL; + + pfs = (Ext2Fs *) PyObject_NEW(Ext2Fs, &Ext2FsType); + if (pfs == NULL) + return NULL; + pfs->fs = NULL; + + if (!ext2_fs_open(pfs, + Py_BuildValue("siii", name, flags, superblock, block_size), + NULL)) + return NULL; + + return (PyObject *)pfs; +} + + +static struct PyMethodDef Ext2ModuleMethods[] = { + { "Ext2Fs", (PyCFunction) ext2_fs_new, METH_VARARGS|METH_KEYWORDS, NULL }, + { NULL, NULL, 0, NULL } +}; + + +void init_pyext2(void) { + PyObject *m, *d; + + m = Py_InitModule("_pyext2", Ext2ModuleMethods); + d = PyModule_GetDict(m); + + /* o = PyObject_NEW(PyObject, yExt2FsConstructorType); + PyDict_SetItemString(d, "PyExt2Fs", o); + Py_DECREF(o);*/ + +} diff -Nru a/tools/pygrub/src/fsys/ext2/test.py b/tools/pygrub/src/fsys/ext2/test.py --- /dev/null Wed Dec 31 16:00:00 196900 +++ b/tools/pygrub/src/fsys/ext2/test.py 2005-04-25 18:49:05 -04:00 @@ -0,0 +1,15 @@ +#!/usr/bin/python + + +import _pyext2 +import struct, os, sys + +fs = _pyext2.Ext2Fs("test.img") + +f = fs.open_file("/boot/vmlinuz-2.6.11-1.1177_FC4") +buf = f.read() +o = open("vmlinuz", "wb+") +o.write(buf) +o.close() + +f.close() diff -Nru a/tools/pygrub/src/pygrub b/tools/pygrub/src/pygrub --- /dev/null Wed Dec 31 16:00:00 196900 +++ b/tools/pygrub/src/pygrub 2005-04-25 18:49:05 -04:00 @@ -0,0 +1,270 @@ +#!/usr/bin/python +# +# pygrub - simple python-based bootloader for Xen +# +# Copyright 2005 Red Hat, Inc. +# Jeremy Katz +# +# This software may be freely redistributed under the terms of the GNU +# general public license. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +# + +import os, sys, string, struct, tempfile +import logging + +import curses, _curses, curses.wrapper +import getopt + +import grub.GrubConf +import grub.fsys + +PYGRUB_VER = 0.02 + + +def draw_window(): + stdscr = curses.initscr() + curses.use_default_colors() + try: + curses.curs_set(0) + except _curses.error: + pass + + stdscr.addstr(1, 4, "pyGRUB version %s" %(PYGRUB_VER,)) + + win = curses.newwin(10, 74, 2, 1) + win.box() + win.refresh() + + stdscr.addstr(12, 5, "Use the U and D keys to select which entry is highlighted.") + stdscr.addstr(13, 5, "Press enter to boot the selected OS. 'e' to edit the") + stdscr.addstr(14, 5, "commands before booting, 'a' to modify the kernel arguments ") + stdscr.addstr(15, 5, "before booting, or 'c' for a command line.") + stdscr.addch(12, 13, curses.ACS_UARROW) + stdscr.addch(12, 19, curses.ACS_DARROW) + (y, x) = stdscr.getmaxyx() + stdscr.move(y - 1, x - 1) + + stdscr.refresh() + return (stdscr, win) + +def fill_entries(win, cfg, selected): + y = 0 + + for i in cfg.images: + if (0, y) > win.getmaxyx(): + break + if y == selected: + attr = curses.A_REVERSE + else: + attr = 0 + win.addstr(y + 1, 2, i.title.ljust(70), attr) + y += 1 + win.refresh() + +def select(win, line): + win.attron(curses.A_REVERSE) + win.redrawln(line + 1, 1) + win.refresh() + +def is_disk_image(file): + fd = os.open(file, os.O_RDONLY) + buf = os.read(fd, 512) + os.close(fd) + + if len(buf) >= 512 and struct.unpack("H", buf[0x1fe: 0x200]) == (0xaaff): + return True + return False + + +def get_config(fn): + if not os.access(fn, os.R_OK): + raise RuntimeError, "Unable to access %s" %(fn,) + + cf = grub.GrubConf.GrubConfigFile() + + if is_disk_image(fn): + raise RuntimeError, "appears to be a full disk image... unable to handle this yet" + + # open the image and read the grub config + fs = None + for fstype in grub.fsys.fstypes.values(): + if fstype.sniff_magic(fn): + fs = fstype.open_fs(fn) + break + + if fs is not None: + f = fs.open_file("/boot/grub/grub.conf") + buf = f.read() + f.close() + fs.close() + # then parse the grub config + cf.parse(buf) + else: + # set the config file and parse it + cf.filename = fn + cf.parse() + + return cf + +def get_entry_idx(cf, entry): + # first, see if the given entry is numeric + try: + idx = string.atoi(entry) + return idx + except ValueError: + pass + + # it's not, now check the labels for a match + for i in range(len(cf.images)): + if entry == cf.images[i].title: + return i + + return None + +def main(cf = None): + mytime = 0 + + (stdscr, win) = draw_window() + stdscr.timeout(1000) + selected = cf.default + + while (mytime < int(cf.timeout)): + if cf.timeout != -1 and mytime != -1: + stdscr.addstr(20, 5, "Will boot selected entry in %2d seconds" + %(int(cf.timeout) - mytime)) + else: + stdscr.addstr(20, 5, " " * 80) + + fill_entries(win, cf, selected) + c = stdscr.getch() + if mytime != -1: + mytime += 1 +# if c == ord('q'): +# selected = -1 +# break + elif c == ord('c'): + # FIXME: needs to go to command line mode + continue + elif c == ord('a'): + # FIXME: needs to go to append mode + continue + elif c == ord('e'): + # FIXME: needs to go to edit mode + continue + elif c in (curses.KEY_ENTER, ord('\n'), ord('\r')): + break + elif c == curses.KEY_UP: + mytime = -1 + selected -= 1 + elif c == curses.KEY_DOWN: + mytime = -1 + selected += 1 + else: + pass + + # bound at the top and bottom + if selected < 0: + selected = 0 + elif selected >= len(cf.images): + selected = len(cf.images) - 1 + + if selected >= 0: + return selected + +if __name__ == "__main__": + sel = None + + def run_main(scr, *args): + global sel + sel = main(cf) + + def usage(): + print >> sys.stderr, "Usage: %s [-q|--quiet] [--output=] [--entry=] " %(sys.argv[0],) + + try: + opts, args = getopt.gnu_getopt(sys.argv[1:], 'qh::', + ["quiet", "help", "output=", "entry="]) + except getopt.GetoptError: + usage() + sys.exit(1) + + if len(args) < 1: + usage() + sys.exit(1) + file = args[0] + + output = None + entry = None + interactive = True + for o, a in opts: + if o in ("-q", "--quiet"): + interactive = False + elif o in ("-h", "--help"): + usage() + sys.exit() + elif o in ("--output",): + output = a + elif o in ("--entry",): + entry = a + # specifying the entry to boot implies non-interactive + interactive = False + + if output is None or output == "-": + fd = sys.stdout.fileno() + else: + fd = os.open(output, os.O_WRONLY) + + cf = get_config(file) + if interactive: + curses.wrapper(run_main) + else: + sel = cf.default + + # set the entry to boot as requested + if entry is not None: + idx = get_entry_idx(cf, entry) + if idx is not None and idx > 0 and idx < len(cf.images): + sel = idx + + img = cf.images[sel] + print "Going to boot %s" %(img.title) + print " kernel: %s" %(img.kernel[1],) + if img.initrd: + print " initrd: %s" %(img.initrd[1],) + + if is_disk_image(file): + raise RuntimeError, "unable to handle full disk images yet" + + # read the kernel and initrd onto the hostfs + fs = None + for fstype in grub.fsys.fstypes.values(): + if fstype.sniff_magic(file): + fs = fstype.open_fs(file) + break + + if fs is None: + raise RuntimeError, "Unable to open filesystem" + + kernel = fs.open_file(img.kernel[1],).read() + (tfd, fn) = tempfile.mkstemp(prefix="vmlinuz.") + os.write(tfd, kernel) + os.close(tfd) + sxp = "linux (kernel %s)" %(fn,) + + if img.initrd: + initrd = fs.open_file(img.initrd[1],).read() + (tfd, fn) = tempfile.mkstemp(prefix="initrd.") + os.write(tfd, initrd) + os.close(tfd) + sxp += "(ramdisk %s)" %(fn,) + else: + initrd = None + sxp += "(args '%s')" %(img.args,) + + sys.stdout.flush() + os.write(fd, sxp) + diff -Nru a/tools/python/xen/xend/XendBootloader.py b/tools/python/xen/xend/XendBootloader.py --- /dev/null Wed Dec 31 16:00:00 196900 +++ b/tools/python/xen/xend/XendBootloader.py 2005-04-25 18:49:05 -04:00 @@ -0,0 +1,95 @@ +# +# XendBootloader.py - Framework to run a boot loader for picking the kernel +# +# Copyright 2005 Red Hat, Inc. +# Jeremy Katz +# +# This software may be freely redistributed under the terms of the GNU +# general public license. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +# + +import os, sys, select +import sxp + +from XendLogging import log +from XendError import VmError + +BL_FIFO = "/var/lib/xen/xenbl" + +def bootloader(blexec, disk, quiet = 0, vcpus = None, entry = None): + """Run the boot loader executable on the given disk and return a + config image. + @param blexec Binary to use as the boot loader + @param disk Disk to run the boot loader on. + @param quiet Run in non-interactive mode, just booting the default. + @param vcpus Number of vcpus for the domain. + @param entry Default entry to boot.""" + + if not os.access(blexec, os.X_OK): + msg = "Bootloader isn't executable" + log.error(msg) + raise VmError, msg + if not os.access(disk, os.R_OK): + msg = "Disk isn't accessible" + log.error(msg) + raise VmError, msg + + os.mkfifo(BL_FIFO, 0600) + + child = os.fork() + if (not child): + args = [ blexec ] + if quiet: + args.append("-q") + args.append("--output=%s" %(BL_FIFO,)) + if entry is not None: + args.append("--entry=%s" %(entry,)) + args.append(disk) + + try: + os.execvp(args[0], args) + except OSError, e: + print e + pass + os._exit(1) + + while 1: + try: + r = os.open(BL_FIFO, os.O_RDONLY) + except OSError, e: + if e.errno == 4: + continue + break + ret = "" + while 1: + select.select([r], [], []) + s = os.read(r, 1024) + ret = ret + s + if len(s) == 0: + break + + (pid, status) = os.waitpid(child, 0) + os.close(r) + os.unlink(BL_FIFO) + + if len(ret) == 0: + msg = "Boot loader didn't return any data!" + log.error(msg) + raise VmError, msg + + pin = sxp.Parser() + pin.input(ret) + pin.input_eof() + + config_image = pin.val + if vcpus and sxp.child_value(config_image, "vcpus") is None: + config_image.append(['vcpus', vcpus]) + + config = [['image', pin.val]] + config.append(['bootloader', blexec]) + return config + diff -Nru a/tools/python/xen/xend/XendDomainInfo.py b/tools/python/xen/xend/XendDomainInfo.py --- a/tools/python/xen/xend/XendDomainInfo.py 2005-04-25 18:49:05 -04:00 +++ b/tools/python/xen/xend/XendDomainInfo.py 2005-04-25 18:49:05 -04:00 @@ -20,7 +20,8 @@ import xen.lowlevel.xc; xc = xen.lowlevel.xc.new() import xen.util.ip from xen.util.ip import _readline, _readlines -from xen.xend.server import channel +from xen.xend.server import channel, blkif +from xen.xend.XendBootloader import bootloader import sxp @@ -323,6 +324,7 @@ self.image_handler = None self.is_vmx = 0 self.vcpus = 1 + self.bootloader = None def setdom(self, dom): """Set the domain id. @@ -458,6 +460,7 @@ self.find_image_handler() self.init_domain() + self.configure_bootloader() self.configure_console() self.configure_backends() self.construct_image() @@ -795,7 +798,7 @@ ramdisk = ramdisk, flags = flags) else: - log.warning('building dom with %d vcpus', self.vcpus) + log.warning('building dom with %d vcpus' %(self.vcpus,)) err = buildfn(dom = dom, image = kernel, control_evtchn = self.console.getRemotePort(), @@ -803,6 +806,14 @@ ramdisk = ramdisk, flags = flags, vcpus = self.vcpus) + + if self.bootloader: + try: + if kernel: os.unlink(kernel) + if ramdisk: os.unlink(ramdisk) + except OSError, e: + log.warning('unable to unlink kernel/ramdisk: %s' %(e,)) + if err != 0: raise VmError('Building domain failed: type=%s dom=%d err=%d' % (ostype, dom, err)) @@ -961,6 +972,13 @@ maxmem = self.memory xc.domain_setmaxmem(self.dom, maxmem_kb = maxmem * 1024) + def configure_bootloader(self): + """Configure boot loader. + """ + bl = sxp.child_value(self.config, "bootloader") + if bl is not None: + self.bootloader = bl + def configure_console(self): """Configure the vm console port. """ @@ -1034,6 +1052,24 @@ try: self.restart_check() self.restart_state = STATE_RESTART_BOOTING + # if we're restarting with a bootloader, we need to run it + if self.bootloader: + # FIXME: this assumes the disk is the first device and + # that we're booting from the first disk + blcfg = None + # FIXME: this assumes that we want to use the first disk + dev = sxp.child_value(self.config, "device") + if dev: + disk = sxp.child_value(dev, "uname") + fn = blkif.blkdev_uname_to_file(disk) + blcfg = bootloader(self.bootloader, fn, 1, self.vcpus) + if blcfg is None: + msg = "Had a bootloader specified, but can't find disk" + log.error(msg) + raise VmError(msg) + else: + s = "(vm %s)" %(sxp.to_string(blcfg[0]),) + self.config = sxp.merge(sxp.from_string(s), self.config) d = self.construct(self.config) finally: self.restart_state = None @@ -1163,6 +1199,7 @@ if args: cmdline += " " + args ramdisk = sxp.child_value(image, "ramdisk", '') + log.debug("creating linux domain with cmdline: %s" %(cmdline,)) vm.create_domain("linux", kernel, ramdisk, cmdline) return vm @@ -1377,6 +1414,7 @@ add_config_handler('device', vm_field_ignore) add_config_handler('backend', vm_field_ignore) add_config_handler('vcpus', vm_field_ignore) +add_config_handler('bootloader', vm_field_ignore) # Register other config handlers. add_config_handler('maxmem', vm_field_maxmem) diff -Nru a/tools/python/xen/xend/server/blkif.py b/tools/python/xen/xend/server/blkif.py --- a/tools/python/xen/xend/server/blkif.py 2005-04-25 18:49:05 -04:00 +++ b/tools/python/xen/xend/server/blkif.py 2005-04-25 18:49:05 -04:00 @@ -96,6 +96,15 @@ 'type' : 'Disk' } return val +def blkdev_uname_to_file(uname): + """Take a blkdev uname and return the corresponding filename.""" + fn = None + if uname.find(":") != -1: + (typ, fn) = uname.split(":") + if typ == "phy" and not fn.startswith("/dev/"): + fn = "/dev/%s" %(fn,) + return fn + class BlkifBackendController(controller.BackendController): """ Handler for the 'back-end' channel to a block device driver domain. """ diff -Nru a/tools/python/xen/xm/create.py b/tools/python/xen/xm/create.py --- a/tools/python/xen/xm/create.py 2005-04-25 18:49:05 -04:00 +++ b/tools/python/xen/xm/create.py 2005-04-25 18:49:05 -04:00 @@ -10,6 +10,8 @@ from xen.xend import sxp from xen.xend import PrettyPrint from xen.xend.XendClient import server, XendError +from xen.xend.XendBootloader import bootloader +from xen.xend.server import blkif from xen.util import console_client @@ -94,6 +96,14 @@ fn=set_value, default=None, use="Domain name. Must be unique.") +gopts.var('bootloader', val='FILE', + fn=set_value, default=None, + use="Path to bootloader.") + +gopts.var('bootentry', val='NAME', + fn=set_value, default=None, + use="Entry to boot via boot loader") + gopts.var('kernel', val='FILE', fn=set_value, default=None, use="Path to kernel image.") @@ -375,6 +385,21 @@ config_devs.append(['device_model', device_model]) config_devs.append(['device_config', device_config]) +def run_bootloader(config, vals): + if not os.access(vals.bootloader, os.X_OK): + print >> sys.stderr, "Bootloader isn't executable" + sys.exit(1) + if len(vals.disk) < 1: + print >> sys.stderr, "No disks configured and boot loader requested" + sys.exit(1) + (uname, dev, mode, backend) = vals.disk[0] + file = blkif.blkdev_uname_to_file(uname) + + blcfg = bootloader(vals.bootloader, file, not vals.console_autoconnect, + vals.vcpus, vals.blentry) + + config.extend(blcfg) + def make_config(vals): """Create the domain configuration. """ @@ -396,8 +421,11 @@ config.append(['restart', vals.restart]) if vals.console: config.append(['console', vals.console]) - - configure_image(config, vals) + + if vals.bootloader: + run_bootloader(config, vals) + else: + configure_image(config, vals) config_devs = [] configure_disks(config_devs, vals) configure_pci(config_devs, vals) @@ -405,6 +433,7 @@ configure_usb(config_devs, vals) configure_vmx(config_devs, vals) config += config_devs + return config def preprocess_disk(opts, vals): @@ -588,6 +617,7 @@ if not opts.getopt('name') and opts.getopt('defconfig'): opts.setopt('name', os.path.basename(opts.getopt('defconfig'))) config = make_config(opts.vals) + if opts.vals.dryrun: PrettyPrint.prettyprint(config) else: