xen-devel.lists.xenproject.org archive mirror
 help / color / mirror / Atom feed
* [PATCH] add new bootloader xenpvnetboot for pv guest
@ 2012-02-22 14:06 Zhigang Wang
  2012-02-24 11:04 ` Ian Campbell
  0 siblings, 1 reply; 7+ messages in thread
From: Zhigang Wang @ 2012-02-22 14:06 UTC (permalink / raw)
  To: xen-devel; +Cc: Zhigang Wang

 tools/misc/Makefile     |    2 +-
 tools/misc/xenpvnetboot |  293 ++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 294 insertions(+), 1 deletions(-)


# HG changeset patch
# User Zhigang Wang <zhigang.x.wang@oracle.com>
# Date 1329919344 18000
# Node ID 6770476b814532bb36a3b00cba16e5cd8a6b4585
# Parent  ca80eca9cfa39d1b60d1216948dac5711d550b83
add new bootloader xenpvnetboot for pv guest

`xenpvnetboot` supports getting boot images from following locations:

 - http://host/path
 - https://host/path
 - ftp://host/path
 - file:///path
 - tftp://host/path
 - nfs:host:/path
 - /path
 - /path/file.iso
 - /path/filesystem.img
 - /dev/sda1
 - nfs+iso:host:/path/file.iso
 - nfs+iso:host:/path/filesystem.img

To use it, make `xenpvnetboot` as bootloader for PV guest::

    bootloader = '/usr/bin/xenpvnetboot'

To get boot images from various locations, set the right bootloader arguments,
e.g.::

    bootloarder_args = ['--location=http://192.168.0.1/fedora/x86_64']
    bootloarder_args = ['--location=ftp://192.168.0.1/fedora/x86_64']
    bootloarder_args = ['--location=file:///fedora/x86_64']
    bootloarder_args = ['--location=tftp://192.168.0.1/fedora/x86_64']
    bootloarder_args = ['--location=/fedora/x86_64']
    bootloarder_args = ['--location=/fedora/Fedora-16-x86_64-DVD.iso']
    bootloarder_args = ['--location=nfs:192.168.0.1:/fedora/x86_64']
    bootloarder_args = ['--location=nfs+iso:192.168.0.1:/fedora/Fedora-16-x86_64-DVD.iso']

You can use `kernel` and `ramdisk` to specify the relative path of boot
kernel and ramdisk. `xenpvnetboot` will join them with the location to find the
boot kernel and ramdisk, e.g.::

    kernel = 'images/pxeboot/vmlinuz'
    ramdisk = 'images/pxeboot/initrd.img'
    bootloarder_args = ['--location=http://192.168.0.1/fedora/x86_64']

    kernel = 'fedora/x86_64/images/pxeboot/vmlinuz'
    ramdisk = 'fedora/x86_64/images/pxeboot/initrd.img'
    bootloarder_args = ['--location=http://192.168.0.1/']

You can also omit the `--location` option and specify the full URL for `kernel`
and `ramdisk` directly, e.g.:

    kernel = 'http://192.168.0.1/fedora/x86_64/images/pxeboot/vmlinuz'
    ramdisk = 'http://192.168.0.1/fedora/x86_64/images/pxeboot/initrd.img'

If only `--location` is specified and `kernel` and `ramdisk` are not specified,
`xenpvnetboot` will search the following places for boot images from the
location::

    ('images/xen/vmlinuz', 'images/xen/initrd.img'), # Fedora <= 10 and OL = 5
    ('boot/i386/vmlinuz-xen', 'boot/i386/initrd-xen'), # openSUSE >= 10.2 and SLES >= 10
    ('boot/x86_64/vmlinuz-xen', 'boot/x86_64/initrd-xen'), # openSUSE >= 10.2 and SLES >= 10
    ('current/images/netboot/xen/vmlinuz', 'current/images/netboot/xen/initrd.gz'), # Debian
    ('images/pxeboot/vmlinuz', 'images/pxeboot/initrd.img'), # Fedora >=10 and OL >= 6
    ('isolinux/vmlinuz', 'isolinux/initrd.img'), # Fedora >= 10 and OL >= 6

`xenpvnetboot` requires python module `urlgrabber <http://urlgrabber.baseurl.org/>`.

Signed-off-by: Zhigang Wang <zhigang.x.wang@oracle.com>

diff -r ca80eca9cfa3 -r 6770476b8145 tools/misc/Makefile
--- a/tools/misc/Makefile	Mon Feb 20 18:34:14 2012 +0000
+++ b/tools/misc/Makefile	Wed Feb 22 09:02:24 2012 -0500
@@ -17,7 +17,7 @@ SUBDIRS-$(CONFIG_LOMOUNT) += lomount
 SUBDIRS-$(CONFIG_MINITERM) += miniterm
 SUBDIRS := $(SUBDIRS-y)
 
-INSTALL_BIN-y := xencons
+INSTALL_BIN-y := xencons xenpvnetboot
 INSTALL_BIN-$(CONFIG_X86) += xen-detect
 INSTALL_BIN := $(INSTALL_BIN-y)
 
diff -r ca80eca9cfa3 -r 6770476b8145 tools/misc/xenpvnetboot
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tools/misc/xenpvnetboot	Wed Feb 22 09:02:24 2012 -0500
@@ -0,0 +1,293 @@
+#!/usr/bin/env python
+#
+# Copyright (C) 2010 Oracle. All rights reserved.
+#
+# This program is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free Software
+# Foundation, version 2.  This program is distributed in the hope that it will be
+# useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
+# Public License for more details.  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., 59 Temple Place - Suite 330, Boston, MA 021110-1307,
+# USA.
+
+import sys
+import os
+import stat
+import time
+import string
+import random
+import tempfile
+import commands
+import subprocess
+import urlgrabber
+from optparse import OptionParser
+
+
+XEN_PATHS = [
+    ('images/xen/vmlinuz', 'images/xen/initrd.img'), # Fedora <= 10 and OL = 5
+    ('boot/i386/vmlinuz-xen', 'boot/i386/initrd-xen'), # openSUSE >= 10.2 and SLES >= 10
+    ('boot/x86_64/vmlinuz-xen', 'boot/x86_64/initrd-xen'), # openSUSE >= 10.2 and SLES >= 10
+    ('current/images/netboot/xen/vmlinuz', 'current/images/netboot/xen/initrd.gz'), # Debian
+    ('images/pxeboot/vmlinuz', 'images/pxeboot/initrd.img'), # Fedora >=10 and OL >= 6
+    ('isolinux/vmlinuz', 'isolinux/initrd.img'), # Fedora >= 10 and OL >= 6
+]
+
+
+def format_sxp(kernel, ramdisk, args):
+    s = 'linux (kernel %s)' % kernel
+    if ramdisk:
+        s += '(ramdisk %s)' % ramdisk
+    if args:
+        s += '(args "%s")' % args
+    return s
+
+
+def format_simple(kernel, ramdisk, args, sep):
+    s = ('kernel %s' % kernel) + sep
+    if ramdisk:
+        s += ('ramdisk %s' % ramdisk) + sep
+    if args:
+        s += ('args %s' % args) + sep
+    s += sep
+    return s
+
+
+def mount(dev, path, option=''):
+    if os.uname()[0] == 'SunOS':
+        mountcmd = '/usr/sbin/mount'
+    else:
+        mountcmd = '/bin/mount'
+    cmd = ' '.join([mountcmd, option, dev, path])
+    (status, output) = commands.getstatusoutput(cmd)
+    if status != 0:
+        raise RuntimeError('Command: (%s) failed: (%s) %s' % (cmd, status, output))
+
+
+def umount(path):
+    if os.uname()[0] == 'SunOS':
+        cmd = ['/usr/sbin/umount', path]
+    else:
+        cmd = ['/bin/umount', path]
+    subprocess.call(cmd)
+
+
+class Fetcher:
+    def __init__(self, location, tmpdir):
+        self.location = location
+        self.tmpdir = tmpdir
+        self.srcdir = location
+
+    def prepare(self):
+        if not os.path.exists(self.tmpdir):
+            os.makedirs(self.tmpdir, 0750)
+
+    def cleanup(self):
+        pass
+
+    def get_file(self, filename):
+        url = os.path.join(self.srcdir, filename)
+        suffix = ''.join(random.sample(string.ascii_letters, 6))
+        local_name = os.path.join(self.tmpdir, 'xenpvboot.%s.%s' % (os.path.basename(filename), suffix))
+        try:
+            return urlgrabber.urlgrab(url, local_name, copy_local=1)
+        except Exception, err:
+            raise RuntimeError('Cannot get file %s: %s' % (url, err))
+
+
+class MountedFetcher(Fetcher):
+    def prepare(self):
+        Fetcher.prepare(self)
+        self.srcdir = tempfile.mkdtemp(prefix='xenpvboot.', dir=self.tmpdir)
+        if self.location.startswith('nfs:'):
+            mount(self.location[4:], self.srcdir, '-o ro')
+        else:
+            if stat.S_ISBLK(os.stat(self.location)[stat.ST_MODE]):
+                option = '-o ro'
+            else:
+                option = '-o ro,loop'
+            if os.uname()[0] == 'SunOS':
+                option += ' -F hsfs'
+            mount(self.location, self.srcdir, option)
+
+    def cleanup(self):
+        umount(self.srcdir)
+        try:
+            os.rmdir(self.srcdir)
+        except:
+            pass
+
+
+class NFSISOFetcher(MountedFetcher):
+    def __init__(self, location, tmpdir):
+        self.nfsdir = None
+        MountedFetcher.__init__(self, location, tmpdir)
+
+    def prepare(self):
+        Fetcher.prepare(self)
+        self.nfsdir = tempfile.mkdtemp(prefix='xenpvboot.', dir=self.tmpdir)
+        self.srcdir = tempfile.mkdtemp(prefix='xenpvboot.', dir=self.tmpdir)
+        nfs = os.path.dirname(self.location[8:])
+        iso = os.path.basename(self.location[8:])
+        mount(nfs, self.nfsdir, '-o ro')
+        option = '-o ro,loop'
+        if os.uname()[0] == 'SunOS':
+            option += ' -F hsfs'
+        mount(os.path.join(self.nfsdir, iso), self.srcdir, option)
+
+    def cleanup(self):
+        MountedFetcher.cleanup(self)
+        time.sleep(1)
+        umount(self.nfsdir)
+        try:
+            os.rmdir(self.nfsdir)
+        except:
+            pass
+
+
+class TFTPFetcher(Fetcher):
+    def get_file(self, filename):
+        if '/' in self.location[7:]:
+            host = self.location[7:].split('/', 1)[0].replace(':', ' ')
+            basedir = self.location[7:].split('/', 1)[1]
+        else:
+            host = self.location[7:].replace(':', ' ')
+            basedir = ''
+        suffix = ''.join(random.sample(string.ascii_letters, 6))
+        local_name = os.path.join(self.tmpdir, 'xenpvboot.%s.%s' % (os.path.basename(filename), suffix))
+        cmd = '/usr/bin/tftp %s -c get %s %s' % (host, os.path.join(basedir, filename), local_name)
+        (status, output) = commands.getstatusoutput(cmd)
+        if status != 0:
+            raise RuntimeError('Command: (%s) failed: (%s) %s' % (cmd, status, output))
+        return local_name
+
+
+def main():
+    usage = '''%prog [option]
+
+Get boot images from the given location and prepare for Xen to use.
+
+Supported locations:
+
+ - http://host/path
+ - https://host/path
+ - ftp://host/path
+ - file:///path
+ - tftp://host/path
+ - nfs:host:/path
+ - /path
+ - /path/file.iso
+ - /path/filesystem.img
+ - /dev/sda1
+ - nfs+iso:host:/path/file.iso
+ - nfs+iso:host:/path/filesystem.img'''
+    version = '%prog version 0.1'
+    parser = OptionParser(usage=usage, version=version)
+    parser.add_option('', '--location',
+                      help='The base url for kernel and ramdisk files.')
+    parser.add_option('', '--kernel',
+                      help='The kernel image file.')
+    parser.add_option('', '--ramdisk',
+                      help='The initial ramdisk file.')
+    parser.add_option('', '--args',
+                      help='Arguments pass to the kernel.')
+    parser.add_option('', '--output',
+                      help='Redirect output to this file instead of stdout.')
+    parser.add_option('', '--output-directory', default='/var/run/libxl',
+                      help='Output directory.')
+    parser.add_option('', '--output-format', default='sxp',
+                      help='Output format: sxp, simple or simple0.')
+    parser.add_option('-q', '--quiet', action='store_true',
+                      help='Be quiet.')
+    (opts, args) = parser.parse_args()
+
+    if not opts.location and not opts.kernel and not opts.ramdisk:
+        if not opts.quiet:
+            print >> sys.stderr, 'You should at least specify a location or kernel/ramdisk.'
+            parser.print_help(sys.stderr)
+        sys.exit(1)
+
+    if not opts.output or opts.output == '-':
+        fd = sys.stdout.fileno()
+    else:
+        fd = os.open(opts.output, os.O_WRONLY)
+
+    if opts.location:
+        location = opts.location
+    else:
+        location = ''
+    if (location == ''
+        or location.startswith('http://') or location.startswith('https://')
+        or location.startswith('ftp://') or location.startswith('file://')
+        or (os.path.exists(location) and os.path.isdir(location))):
+        fetcher = Fetcher(location, opts.output_directory)
+    elif location.startswith('nfs:') or (os.path.exists(location) and not os.path.isdir(location)):
+        fetcher = MountedFetcher(location, opts.output_directory)
+    elif location.startswith('nfs+iso:'):
+        fetcher = NFSISOFetcher(location, opts.output_directory)
+    elif location.startswith('tftp://'):
+        fetcher = TFTPFetcher(location, opts.output_directory)
+    else:
+        if not opts.quiet:
+            print >> sys.stderr, 'Unsupported location: %s' % location
+        sys.exit(1)
+
+    try:
+        fetcher.prepare()
+    except Exception, err:
+        if not opts.quiet:
+            print >> sys.stderr, str(err)
+        fetcher.cleanup()
+        sys.exit(1)
+
+    try:
+        kernel = None
+        if opts.kernel:
+            kernel = fetcher.get_file(opts.kernel)
+        else:
+            for (kernel_path, _) in XEN_PATHS:
+                try:
+                    kernel = fetcher.get_file(kernel_path)
+                except Exception, err:
+                    if not opts.quiet:
+                        print >> sys.stderr, str(err)
+                    continue
+                break
+
+        if not kernel:
+            if not opts.quiet:
+                print >> sys.stderr, 'Cannot get kernel from loacation: %s' % location
+            sys.exit(1)
+
+        ramdisk = None
+        if opts.ramdisk:
+            ramdisk = fetcher.get_file(opts.ramdisk)
+        else:
+            for (_, ramdisk_path) in XEN_PATHS:
+                try:
+                    ramdisk = fetcher.get_file(ramdisk_path)
+                except Exception, err:
+                    if not opts.quiet:
+                        print >> sys.stderr, str(err)
+                    continue
+                break
+    finally:
+        fetcher.cleanup()
+
+    if opts.output_format == 'sxp':
+        output = format_sxp(kernel, ramdisk, opts.args)
+    elif opts.output_format == 'simple':
+        output = format_simple(kernel, ramdisk, opts.args, '\n')
+    elif opts.output_format == 'simple0':
+        output = format_simple(kernel, ramdisk, opts.args, '\0')
+    else:
+        print >> sys.stderr, 'Unknown output format: %s' % opts.output_format
+        sys.exit(1)
+
+    sys.stdout.flush()
+    os.write(fd, output)
+
+
+if __name__ == '__main__':
+    main()

^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: [PATCH] add new bootloader xenpvnetboot for pv guest
  2012-02-22 14:06 [PATCH] add new bootloader xenpvnetboot for pv guest Zhigang Wang
@ 2012-02-24 11:04 ` Ian Campbell
  2012-03-01 18:56   ` [PATCH] add new bootloader xenpvnetboot for pv guest [and 1 more messages] Ian Jackson
  0 siblings, 1 reply; 7+ messages in thread
From: Ian Campbell @ 2012-02-24 11:04 UTC (permalink / raw)
  To: Zhigang Wang; +Cc: xen-devel

On Wed, 2012-02-22 at 14:06 +0000, Zhigang Wang wrote:
> tools/misc/Makefile     |    2 +-
>  tools/misc/xenpvnetboot |  293 ++++++++++++++++++++++++++++++++++++++++++++++++
>  2 files changed, 294 insertions(+), 1 deletions(-)
> 
> 
> # HG changeset patch
> # User Zhigang Wang <zhigang.x.wang@oracle.com>
> # Date 1329919344 18000
> # Node ID 6770476b814532bb36a3b00cba16e5cd8a6b4585
> # Parent  ca80eca9cfa39d1b60d1216948dac5711d550b83
> add new bootloader xenpvnetboot for pv guest
> 
> `xenpvnetboot` supports getting boot images from following locations:

I think this is a really nice idea -- it's been floating around the
bottom of my TODO list for ages now.

Would it be possible to add a man page by adding
docs/man/xenpvnetboot.1.pod using the Perl POD syntax (there's a bunch
of existing docs you could take inspiration from) and perhaps to update
docs/man/xl.cfg.5.pod to reference this new bootloader (you might need
to add a list of possible options, since pyrub was previously the only
thing)

Since you have, I guess, reverse engineered the PV bootloader protocol
from pygrub in order to implement this I wonder if you would consider
writing up a specification for the required interface or such
bootloaders? e.g. docs/misc/pvbootloader.markdown.

By happy coincidence there is a Xen documentation day on Monday ;-)
http://lists.xen.org/archives/html/xen-devel/2012-02/msg01468.html and
http://wiki.xen.org/wiki/Xen_Document_Days
http://blog.xen.org/index.php/2012/02/24/next-xen-document-day-feb-27/

Do you have any further plans for this bootloader? e.g. I think it would
be really cool if it could present a curses based wizard, e.g.
	* Select your distro
	* Provide a URL
	* Tweak command line options
	* Go!

Ian.

^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: [PATCH] add new bootloader xenpvnetboot for pv guest [and 1 more messages]
  2012-02-24 11:04 ` Ian Campbell
@ 2012-03-01 18:56   ` Ian Jackson
  2012-03-03 22:46     ` Zhigang Wang
  0 siblings, 1 reply; 7+ messages in thread
From: Ian Jackson @ 2012-03-01 18:56 UTC (permalink / raw)
  To: Ian Campbell, Zhigang Wang; +Cc: xen-devel

Zhigang Wang writes ("[Xen-devel] [PATCH] add new bootloader xenpvnetboot for pv guest"):
> add new bootloader xenpvnetboot for pv guest

Great, thanks.  I agree with Ian's comments but this is a clear
improvement so I have applied it.  But, improving the documentation is
something we should definitely do.

Ian writes:
> Since you have, I guess, reverse engineered the PV bootloader protocol
> >from pygrub in order to implement this I wonder if you would consider
> writing up a specification for the required interface or such
> bootloaders? e.g. docs/misc/pvbootloader.markdown.

That is also an excellent suggestion.

Ian.

^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: [PATCH] add new bootloader xenpvnetboot for pv guest [and 1 more messages]
  2012-03-01 18:56   ` [PATCH] add new bootloader xenpvnetboot for pv guest [and 1 more messages] Ian Jackson
@ 2012-03-03 22:46     ` Zhigang Wang
  2012-03-05 15:12       ` Ian Campbell
  2012-03-12 11:11       ` Ian Jackson
  0 siblings, 2 replies; 7+ messages in thread
From: Zhigang Wang @ 2012-03-03 22:46 UTC (permalink / raw)
  To: Ian Jackson; +Cc: Ian Campbell, xen-devel

On 03/01/2012 01:56 PM, Ian Jackson wrote:
> Zhigang Wang writes ("[Xen-devel] [PATCH] add new bootloader xenpvnetboot for pv guest"):
>> add new bootloader xenpvnetboot for pv guest
> 
> Great, thanks.  I agree with Ian's comments but this is a clear
> improvement so I have applied it.  But, improving the documentation is
> something we should definitely do.
> 
> Ian writes:
>> Since you have, I guess, reverse engineered the PV bootloader protocol
>> >from pygrub in order to implement this I wonder if you would consider
>> writing up a specification for the required interface or such
>> bootloaders? e.g. docs/misc/pvbootloader.markdown.
> 
> That is also an excellent suggestion.
> 
> Ian.

Thanks Ian C and Ian J.

Sorry I missed Ian C's mail a few days ago.

I add what you suggested to my TODO list.

Another thing you may help about this is: PV guest diskless boot using this boot
loader. Current for PV guest, we require at least one disk. I will work on it
and probably ask for help later.

Thanks,

Zhigang

^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: [PATCH] add new bootloader xenpvnetboot for pv guest [and 1 more messages]
  2012-03-03 22:46     ` Zhigang Wang
@ 2012-03-05 15:12       ` Ian Campbell
  2012-03-05 15:20         ` Zhigang Wang
  2012-03-12 11:11       ` Ian Jackson
  1 sibling, 1 reply; 7+ messages in thread
From: Ian Campbell @ 2012-03-05 15:12 UTC (permalink / raw)
  To: Zhigang Wang; +Cc: Ian Jackson, xen-devel

On Sat, 2012-03-03 at 17:46 -0500, Zhigang Wang wrote:
> Another thing you may help about this is: PV guest diskless boot using this boot
> loader. Current for PV guest, we require at least one disk.

You mean the toolstack errors out if you don't configure a disk? I think
that's just a bug, or perhaps a misfeature.

Ian.

^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: [PATCH] add new bootloader xenpvnetboot for pv guest [and 1 more messages]
  2012-03-05 15:12       ` Ian Campbell
@ 2012-03-05 15:20         ` Zhigang Wang
  0 siblings, 0 replies; 7+ messages in thread
From: Zhigang Wang @ 2012-03-05 15:20 UTC (permalink / raw)
  To: Ian Campbell; +Cc: Ian Jackson, xen-devel

On 03/05/2012 10:12 AM, Ian Campbell wrote:
> On Sat, 2012-03-03 at 17:46 -0500, Zhigang Wang wrote:
>> Another thing you may help about this is: PV guest diskless boot using this boot
>> loader. Current for PV guest, we require at least one disk.
> You mean the toolstack errors out if you don't configure a disk? I think
> that's just a bug, or perhaps a misfeature.
Yes. I mean that. Then let's fix that.

Zhigang

>
> Ian.
>
>
> _______________________________________________
> Xen-devel mailing list
> Xen-devel@lists.xen.org
> http://lists.xen.org/xen-devel

^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: [PATCH] add new bootloader xenpvnetboot for pv guest [and 1 more messages]
  2012-03-03 22:46     ` Zhigang Wang
  2012-03-05 15:12       ` Ian Campbell
@ 2012-03-12 11:11       ` Ian Jackson
  1 sibling, 0 replies; 7+ messages in thread
From: Ian Jackson @ 2012-03-12 11:11 UTC (permalink / raw)
  To: Zhigang Wang; +Cc: Ian Campbell, xen-devel

Zhigang Wang writes ("Re: [Xen-devel] [PATCH] add new bootloader xenpvnetboot for pv guest [and 1 more messages]"):
> I add what you suggested to my TODO list.

Thanks.

> Another thing you may help about this is: PV guest diskless boot
> using this boot loader. Current for PV guest, we require at least
> one disk. I will work on it and probably ask for help later.

Great.  Let us know if you have questions.

Ian.

^ permalink raw reply	[flat|nested] 7+ messages in thread

end of thread, other threads:[~2012-03-12 11:11 UTC | newest]

Thread overview: 7+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2012-02-22 14:06 [PATCH] add new bootloader xenpvnetboot for pv guest Zhigang Wang
2012-02-24 11:04 ` Ian Campbell
2012-03-01 18:56   ` [PATCH] add new bootloader xenpvnetboot for pv guest [and 1 more messages] Ian Jackson
2012-03-03 22:46     ` Zhigang Wang
2012-03-05 15:12       ` Ian Campbell
2012-03-05 15:20         ` Zhigang Wang
2012-03-12 11:11       ` Ian Jackson

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).