3 # Copyright (C) 2009 Red Hat Inc.
5 # This program is free software; you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation; either version 2 of the License, or
8 # (at your option) any later version.
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
15 # You should have received a copy of the GNU General Public License
16 # along with this program; if not, write to the Free Software
17 # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
28 eval "use Sys::Virt;";
34 virt-inspector - Display OS version, kernel, drivers, mount points, applications, etc. in a virtual machine
38 virt-inspector [--connect URI] domname
40 virt-inspector guest.img [guest.img ...]
44 B<virt-inspector> examines a virtual machine and tries to determine
45 the version of the OS, the kernel version, what drivers are installed,
46 whether the virtual machine is fully virtualized (FV) or
47 para-virtualized (PV), what applications are installed and more.
49 Virt-inspector can produce output in several formats, including a
50 readable text report, and XML for feeding into other programs.
52 Virt-inspector should only be run on I<inactive> virtual machines.
53 The program tries to determine that the machine is inactive and will
54 refuse to run if it thinks you are trying to inspect a running domain.
56 In the normal usage, use C<virt-inspector domname> where C<domname> is
57 the libvirt domain (see: C<virsh list --all>).
59 You can also run virt-inspector directly on disk images from a single
60 virtual machine. Use C<virt-inspector guest.img>. In rare cases a
61 domain has several block devices, in which case you should list them
62 one after another, with the first corresponding to the guest's
63 C</dev/sda>, the second to the guest's C</dev/sdb> and so on.
65 Virt-inspector can only inspect and report upon I<one domain at a
66 time>. To inspect several virtual machines, you have to run
67 virt-inspector several times (for example, from a shell script
70 Because virt-inspector needs direct access to guest images, it won't
71 normally work over remote libvirt connections.
89 =item B<--connect URI> | B<-c URI>
91 If using libvirt, connect to the given I<URI>. If omitted,
92 then we connect to the default libvirt hypervisor.
94 Libvirt is only used if you specify a C<domname> on the
95 command line. If you specify guest block devices directly,
96 then libvirt is not used at all.
104 Force reading a particular guest even if it appears to be active. In
105 earlier versions of virt-inspector, this could be dangerous (for
106 example, corrupting the guest's disk image). However in more recent
107 versions, it should not cause corruption, but might cause
108 virt-inspector to crash or produce incorrect results.
116 The following options select the output format. Use only one of them.
117 The default is a readable text report.
121 =item B<--text> (default)
127 Produce no output at all.
131 If you select I<--xml> then you get XML output which can be fed
136 If you select I<--perl> then you get Perl structures output which
137 can be used directly in another Perl program.
143 If you select I<--fish> then we print a L<guestfish(1)> command
144 line which will automatically mount up the filesystems on the
145 correct mount points. Try this for example:
147 eval `virt-inspector --fish guest.img`
149 I<--ro-fish> is the same, but the I<--ro> option is passed to
150 guestfish so that the filesystems are mounted read-only.
154 In "query mode" we answer common questions about the guest, such
155 as whether it is fullvirt or needs a Xen hypervisor to run.
157 See section I<QUERY MODE> below.
163 GetOptions ("help|?" => \$help,
164 "connect|c=s" => \$uri,
166 "text" => sub { $output = "text" },
167 "none" => sub { $output = "none" },
168 "xml" => sub { $output = "xml" },
169 "perl" => sub { $output = "perl" },
170 "fish" => sub { $output = "fish" },
171 "guestfish" => sub { $output = "fish" },
172 "ro-fish" => sub { $output = "ro-fish" },
173 "ro-guestfish" => sub { $output = "ro-fish" },
174 "query" => sub { $output = "query" },
176 pod2usage (1) if $help;
177 pod2usage ("$0: no image or VM names given") if @ARGV == 0;
179 # Domain name or guest image(s)?
187 die "guest image $_ does not exist or is not readable\n"
191 die "no libvirt support (install Sys::Virt)"
192 unless exists $INC{"Sys/Virt.pm"};
194 pod2usage ("$0: too many domains listed on command line") if @ARGV > 1;
198 $vmm = Sys::Virt->new (uri => $uri, readonly => 1);
200 $vmm = Sys::Virt->new (readonly => 1);
202 die "cannot connect to libvirt $uri\n" unless $vmm;
204 my @doms = $vmm->list_defined_domains ();
207 if ($_->get_name () eq $ARGV[0]) {
212 die "$ARGV[0] is not the name of an inactive libvirt domain\n"
215 # Get the names of the image(s).
216 my $xml = $dom->get_xml_description ();
218 my $p = new XML::XPath::XMLParser (xml => $xml);
219 my $disks = $p->find ("//devices/disk");
221 foreach ($disks->get_nodelist) {
222 print XML::XPath::XMLParser::as_string($_);
228 # We've now got the list of @images, so feed them to libguestfs.
229 my $g = Sys::Guestfs->new ();
230 $g->add_drive_ro ($_) foreach @images;
234 # We want to get the list of LVs and partitions (ie. anything that
235 # could contain a filesystem). Discard any partitions which are PVs.
236 my @partitions = $g->list_partitions ();
237 my @pvs = $g->pvs ();
241 return 1 if $_ eq $t;
245 @partitions = grep { ! is_pv ($_) } @partitions;
247 my @lvs = $g->lvs ();
253 Linux (distro + version)
257 +--- Filesystems ---------- Installed apps --- Kernel & drivers
258 ----------- -------------- ----------------
259 mount point => device List of apps Extra information
260 mount point => device and versions about kernel(s)
263 (plus lots of extra information
264 about each filesystem)
266 The output of virt-inspector is a complex two-level data structure.
268 At the top level is a list of the operating systems installed on the
269 guest. (For the vast majority of guests, only a single OS is
270 installed.) The data returned for the OS includes the name (Linux,
271 Windows), the distribution and version.
273 The diagram above shows what we return for each OS.
275 With the I<--xml> option the output is mapped into an XML document.
276 Unfortunately there is no clear schema for this document
277 (contributions welcome) but you can get an idea of the format by
278 looking at other documents and as a last resort the source for this
281 With the I<--fish> or I<--ro-fish> option the mount points are mapped to
282 L<guestfish(1)> command line parameters, so that you can go in
283 afterwards and inspect the guest with everything mounted in the
284 right place. For example:
286 eval `virt-inspector --ro-fish guest.img`
287 ==> guestfish --ro -a guest.img -m /dev/VG/LV:/ -m /dev/sda1:/boot
291 # List of possible filesystems.
292 my @devices = sort (@lvs, @partitions);
294 # Now query each one to build up a picture of what's in it.
295 my %fses = map { $_ => check_fs ($_) } @devices;
297 # Now the complex checking code itself.
298 # check_fs takes a device name (LV or partition name) and returns
299 # a hashref containing everything we can find out about the device.
302 my $dev = shift; # LV or partition name.
304 my %r; # Result hash.
306 # First try 'file(1)' on it.
307 my $file = $g->file ($dev);
308 if ($file =~ /ext2 filesystem data/) {
311 } elsif ($file =~ /ext3 filesystem data/) {
314 } elsif ($file =~ /ext4 filesystem data/) {
317 } elsif ($file =~ m{Linux/i386 swap file}) {
323 # If it's ext2/3/4, then we want the UUID and label.
324 if (exists $r{fstype} && $r{fstype} =~ /^ext/) {
325 $r{uuid} = $g->get_e2uuid ($dev);
326 $r{label} = $g->get_e2label ($dev);
329 # Try mounting it, fnarrr.
331 $r{is_mountable} = 1;
332 eval { $g->mount_ro ($dev, "/") };
334 # It's not mountable, probably empty or some format
335 # we don't understand.
336 $r{is_mountable} = 0;
341 if ($g->is_file ("/grub/menu.lst") ||
342 $g->is_file ("/grub/grub.conf")) {
343 $r{content} = "linux-grub";
349 if ($g->is_dir ("/etc") && $g->is_dir ("/bin") &&
350 $g->is_file ("/etc/fstab")) {
351 $r{content} = "linux-root";
353 check_linux_root (\%r);
358 if ($g->is_dir ("/etc") && $g->is_dir ("/bin") &&
359 $g->is_dir ("/share") && !$g->exists ("/local") &&
360 !$g->is_file ("/etc/fstab")) {
361 $r{content} = "linux-usrlocal";
366 if ($g->is_dir ("/etc") && $g->is_dir ("/bin") &&
367 $g->is_dir ("/share") && $g->exists ("/local") &&
368 !$g->is_file ("/etc/fstab")) {
369 $r{content} = "linux-usr";
374 if ($g->is_file ("/AUTOEXEC.BAT") ||
375 $g->is_file ("/autoexec.bat") ||
376 $g->is_dir ("/Program Files") ||
377 $g->is_dir ("/WINDOWS") ||
378 $g->is_file ("/ntldr")) {
379 $r{fstype} = "ntfs"; # XXX this is a guess
380 $r{fsos} = "windows";
381 $r{content} = "windows-root";
383 check_windows_root (\%r);
398 # Look into /etc to see if we recognise the operating system.
399 if ($g->is_file ("/etc/redhat-release")) {
400 $_ = $g->cat ("/etc/redhat-release");
401 if (/Fedora release (\d+\.\d+)/) {
402 $r->{osdistro} = "fedora";
403 $r->{osversion} = "$1"
404 } elsif (/(Red Hat Enterprise Linux|CentOS|Scientific Linux).*release (\d+).*Update (\d+)/) {
405 $r->{osdistro} = "redhat";
406 $r->{osversion} = "$2.$3";
407 } elsif (/(Red Hat Enterprise Linux|CentOS|Scientific Linux).*release (\d+(?:\.(\d+))?)/) {
408 $r->{osdistro} = "redhat";
409 $r->{osversion} = "$2";
411 $r->{osdistro} = "redhat";
413 } elsif ($g->is_file ("/etc/debian_version")) {
414 $_ = $g->cat ("/etc/debian_version");
416 $r->{osdistro} = "debian";
417 $r->{osversion} = "$1";
419 $r->{osdistro} = "debian";
423 # Parse the contents of /etc/fstab. This is pretty vital so
424 # we can determine where filesystems are supposed to be mounted.
425 eval "\$_ = \$g->cat ('/etc/fstab');";
427 my @lines = split /\n/;
430 my @fields = split /[ \t]+/;
432 my $spec = $fields[0]; # first column (dev/label/uuid)
433 my $file = $fields[1]; # second column (mountpoint)
434 if ($spec =~ m{^/} ||
435 $spec =~ m{^LABEL=} ||
436 $spec =~ m{^UUID=} ||
438 push @fstab, [$spec, $file]
442 $r->{fstab} = \@fstab if @fstab;
446 sub check_windows_root
459 # Grub version, if we care.
462 #print Dumper (\%fses);
464 #----------------------------------------------------------------------
465 # Now find out how many operating systems we've got. Usually just one.
469 foreach (sort keys %fses) {
470 if ($fses{$_}->{is_root}) {
475 get_os_version (\%r);
476 assign_mount_points (\%r);
486 $r->{os} = $r->{root}->{fsos} if exists $r->{root}->{fsos};
487 $r->{distro} = $r->{root}->{osdistro} if exists $r->{root}->{osdistro};
488 $r->{version} = $r->{root}->{osversion} if exists $r->{root}->{osversion};
491 sub assign_mount_points
496 $r->{mounts} = { "/" => $r->{root_device} };
497 $r->{filesystems} = { $r->{root_device} => $r->{root} };
499 # Use /etc/fstab if we have it to mount the rest.
500 if (exists $r->{root}->{fstab}) {
501 my @fstab = @{$r->{root}->{fstab}};
503 my ($spec, $file) = @$_;
505 my ($dev, $fs) = find_filesystem ($spec);
507 $r->{mounts}->{$file} = $dev;
508 $r->{filesystems}->{$dev} = $fs;
509 if (exists $fs->{used}) {
519 # Find filesystem by device name, LABEL=.. or UUID=..
526 foreach (sort keys %fses) {
527 if (exists $fses{$_}->{label} &&
528 $fses{$_}->{label} eq $label) {
529 return ($_, $fses{$_});
532 warn "unknown filesystem label $label\n";
534 } elsif (/^UUID=(.*)/) {
536 foreach (sort keys %fses) {
537 if (exists $fses{$_}->{uuid} &&
538 $fses{$_}->{uuid} eq $uuid) {
539 return ($_, $fses{$_});
542 warn "unknown filesystem UUID $uuid\n";
545 return ($_, $fses{$_}) if exists $fses{$_};
547 if (m{^/dev/hd(.*)} && exists $fses{"/dev/sd$1"}) {
548 return ("/dev/sd$1", $fses{"/dev/sd$1"});
550 if (m{^/dev/xvd(.*)} && exists $fses{"/dev/sd$1"}) {
551 return ("/dev/sd$1", $fses{"/dev/sd$1"});
554 return () if m{/dev/cdrom};
556 warn "unknown filesystem $_\n";
561 #print Dumper(\%oses);
563 #----------------------------------------------------------------------
564 # Mount up the disks so we can check for applications
565 # and kernels. Skip this if the output is "*fish" because
566 # we don't need to know.
568 if ($output !~ /.*fish$/) {
570 foreach $root_dev (sort keys %oses) {
571 my $mounts = $oses{$root_dev}->{mounts};
572 # Have to mount / first. Luckily '/' is early in the ASCII
573 # character set, so this should be OK.
574 foreach (sort keys %$mounts) {
575 $g->mount_ro ($mounts->{$_}, $_)
576 if $_ ne "swap" && ($_ eq '/' || $g->is_dir ($_));
579 check_for_applications ($root_dev);
580 check_for_kernels ($root_dev);
586 sub check_for_applications
589 my $root_dev = shift;
593 my $os = $oses{$root_dev}->{os};
594 if ($os eq "linux") {
595 my $distro = $oses{$root_dev}->{distro};
596 if ($distro eq "redhat") {
597 my @lines = $g->command_lines
598 (["rpm", "-q", "-a", "--qf",
599 "%{name} %{epoch} %{version} %{release} %{arch}\n"]);
601 if (m/^(.*) (.*) (.*) (.*) (.*)$/) {
603 $epoch = "" if $epoch eq "(none)";
615 } elsif ($os eq "windows") {
617 # I worked out a general plan for this, but haven't
618 # implemented it yet. We can iterate over /Program Files
619 # looking for *.EXE files, which we download, then use
620 # i686-pc-mingw32-windres on, to find the VERSIONINFO
621 # section, which has a lot of useful information.
624 $oses{$root_dev}->{apps} = \@apps;
627 sub check_for_kernels
630 my $root_dev = shift;
634 my $os = $oses{$root_dev}->{os};
635 if ($os eq "linux") {
636 # Installed kernels will have a corresponding /lib/modules/<version>
637 # directory, which is the easiest way to find out what kernels
638 # are installed, and what modules are available.
639 foreach ($g->ls ("/lib/modules")) {
640 if ($g->is_dir ("/lib/modules/$_")) {
642 $kernel{version} = $_;
646 foreach ($g->find ("/lib/modules/$_")) {
647 if (m,/([^/]+)\.ko$, || m,([^/]+)\.o$,) {
652 $kernel{modules} = \@modules;
654 push @kernels, \%kernel;
658 } elsif ($os eq "windows") {
662 $oses{$root_dev}->{kernels} = \@kernels;
665 #----------------------------------------------------------------------
668 if ($output eq "fish" || $output eq "ro-fish") {
669 my @osdevs = keys %oses;
670 # This only works if there is a single OS.
671 die "--fish output is only possible with a single OS\n" if @osdevs != 1;
673 my $root_dev = $osdevs[0];
676 if ($output eq "ro-fish") {
680 print " -a $_" foreach @images;
682 my $mounts = $oses{$root_dev}->{mounts};
683 # Have to mount / first. Luckily '/' is early in the ASCII
684 # character set, so this should be OK.
685 foreach (sort keys %$mounts) {
686 print " -m $mounts->{$_}:$_" if $_ ne "swap";
692 elsif ($output eq "perl") {
693 print Dumper(\%oses);
696 # Plain text output (the default).
697 elsif ($output eq "text") {
702 elsif ($output eq "xml") {
707 elsif ($output eq "query") {
713 output_text_os ($oses{$_}) foreach sort keys %oses;
720 print $os->{os}, " " if exists $os->{os};
721 print $os->{distro}, " " if exists $os->{distro};
722 print $os->{version}, " " if exists $os->{version};
723 print "on ", $os->{root_device}, ":\n";
725 print " Mountpoints:\n";
726 my $mounts = $os->{mounts};
727 foreach (sort keys %$mounts) {
728 printf " %-30s %s\n", $mounts->{$_}, $_
731 print " Filesystems:\n";
732 my $filesystems = $os->{filesystems};
733 foreach (sort keys %$filesystems) {
735 print " label: $filesystems->{$_}{label}\n"
736 if exists $filesystems->{$_}{label};
737 print " UUID: $filesystems->{$_}{uuid}\n"
738 if exists $filesystems->{$_}{uuid};
739 print " type: $filesystems->{$_}{fstype}\n"
740 if exists $filesystems->{$_}{fstype};
741 print " content: $filesystems->{$_}{content}\n"
742 if exists $filesystems->{$_}{content};
745 print " Applications:\n";
746 my @apps = @{$os->{apps}};
748 print " $_->{name} $_->{version}\n"
752 my @kernels = @{$os->{kernels}};
754 print " $_->{version}\n";
755 my @modules = @{$_->{modules}};
764 print "<operatingsystems>\n";
765 output_xml_os ($oses{$_}) foreach sort keys %oses;
766 print "</operatingsystems>\n";
773 print "<operatingsystem>\n";
775 print "<os>", $os->{os}, "</os>\n" if exists $os->{os};
776 print "<distro>", $os->{distro}, "</distro>\n" if exists $os->{distro};
777 print "<version>", $os->{version}, "</version>\n" if exists $os->{version};
778 print "<root>", $os->{root_device}, "</root>\n";
780 print "<mountpoints>\n";
781 my $mounts = $os->{mounts};
782 foreach (sort keys %$mounts) {
783 printf "<mountpoint dev='%s'>%s</mountpoint>\n",
786 print "</mountpoints>\n";
788 print "<filesystems>\n";
789 my $filesystems = $os->{filesystems};
790 foreach (sort keys %$filesystems) {
791 print "<filesystem dev='$_'>\n";
792 print "<label>$filesystems->{$_}{label}</label>\n"
793 if exists $filesystems->{$_}{label};
794 print "<uuid>$filesystems->{$_}{uuid}</uuid>\n"
795 if exists $filesystems->{$_}{uuid};
796 print "<type>$filesystems->{$_}{fstype}</type>\n"
797 if exists $filesystems->{$_}{fstype};
798 print "<content>$filesystems->{$_}{content}</content>\n"
799 if exists $filesystems->{$_}{content};
800 print "</filesystem>\n";
802 print "</filesystems>\n";
804 print "<applications>\n";
805 my @apps = @{$os->{apps}};
807 print "<application>\n";
808 print "<name>$_->{name}</name><version>$_->{version}</version>\n";
809 print "</application>\n";
811 print "</applications>\n";
814 my @kernels = @{$os->{kernels}};
817 print "<version>$_->{version}</version>\n";
819 my @modules = @{$_->{modules}};
821 print "<module>$_</module>\n";
823 print "</modules>\n";
826 print "</kernels>\n";
828 print "</operatingsystem>\n";
833 When you use C<virt-inspector --query>, the output is a series of
841 (each answer is usually C<yes> or C<no>, or the line is completely
842 missing if we could not determine the answer at all).
844 If the guest is multiboot, you can get apparently conflicting answers
845 (eg. C<windows=yes> and C<linux=yes>, or a guest which is both
846 fullvirt and has a Xen PV kernel). This is normal, and just means
847 that the guest can do both things, although it might require operator
848 intervention such as selecting a boot option when the guest is
851 This section describes the full range of answers possible.
859 output_query_windows ();
860 output_query_linux ();
861 output_query_rhel ();
862 output_query_fedora ();
863 output_query_debian ();
864 output_query_fullvirt ();
865 output_query_xen_domU_kernel ();
866 output_query_xen_pv_drivers ();
867 output_query_virtio_drivers ();
870 =item windows=(yes|no)
872 Answer C<yes> if Microsoft Windows is installed in the guest.
876 sub output_query_windows
879 foreach my $os (keys %oses) {
880 $windows="yes" if $oses{$os}->{os} eq "windows";
882 print "windows=$windows\n";
887 Answer C<yes> if a Linux kernel is installed in the guest.
891 sub output_query_linux
894 foreach my $os (keys %oses) {
895 $linux="yes" if $oses{$os}->{os} eq "linux";
897 print "linux=$linux\n";
902 Answer C<yes> if the guest contains Red Hat Enterprise Linux.
906 sub output_query_rhel
909 foreach my $os (keys %oses) {
910 $rhel="yes" if $oses{$os}->{os} eq "linux" && $oses{$os}->{distro} eq "redhat";
912 print "rhel=$rhel\n";
915 =item fedora=(yes|no)
917 Answer C<yes> if the guest contains the Fedora Linux distribution.
921 sub output_query_fedora
924 foreach my $os (keys %oses) {
925 $fedora="yes" if $oses{$os}->{os} eq "linux" && $oses{$os}->{distro} eq "fedora";
927 print "fedora=$fedora\n";
930 =item debian=(yes|no)
932 Answer C<yes> if the guest contains the Debian Linux distribution.
936 sub output_query_debian
939 foreach my $os (keys %oses) {
940 $debian="yes" if $oses{$os}->{os} eq "linux" && $oses{$os}->{distro} eq "debian";
942 print "debian=$debian\n";
945 =item fullvirt=(yes|no)
947 Answer C<yes> if there is at least one operating system kernel
948 installed in the guest which runs fully virtualized. Such a guest
949 would require a hypervisor which supports full system virtualization.
953 sub output_query_fullvirt
955 # The assumption is full-virt, unless all installed kernels
956 # are identified as paravirt.
957 # XXX Fails on Windows guests.
958 foreach my $os (keys %oses) {
959 foreach my $kernel (@{$oses{$os}->{kernels}}) {
960 my $is_pv = $kernel->{version} =~ m/xen/;
962 print "fullvirt=yes\n";
967 print "fullvirt=no\n";
970 =item xen_domU_kernel=(yes|no)
972 Answer C<yes> if there is at least one Linux kernel installed in
973 the guest which is compiled as a Xen DomU (a Xen paravirtualized
978 sub output_query_xen_domU_kernel
980 foreach my $os (keys %oses) {
981 foreach my $kernel (@{$oses{$os}->{kernels}}) {
982 my $is_xen = $kernel->{version} =~ m/xen/;
984 print "xen_domU_kernel=yes\n";
989 print "xen_domU_kernel=no\n";
992 =item xen_pv_drivers=(yes|no)
994 Answer C<yes> if the guest has Xen paravirtualized drivers installed
995 (usually the kernel itself will be fully virtualized, but the PV
996 drivers have been installed by the administrator for performance
1001 sub output_query_xen_pv_drivers
1003 foreach my $os (keys %oses) {
1004 foreach my $kernel (@{$oses{$os}->{kernels}}) {
1005 foreach my $module (@{$kernel->{modules}}) {
1006 if ($module =~ m/xen-/) {
1007 print "xen_pv_drivers=yes\n";
1013 print "xen_pv_drivers=no\n";
1016 =item virtio_drivers=(yes|no)
1018 Answer C<yes> if the guest has virtio paravirtualized drivers
1019 installed. Virtio drivers are commonly used to improve the
1024 sub output_query_virtio_drivers
1026 foreach my $os (keys %oses) {
1027 foreach my $kernel (@{$oses{$os}->{kernels}}) {
1028 foreach my $module (@{$kernel->{modules}}) {
1029 if ($module =~ m/virtio_/) {
1030 print "virtio_drivers=yes\n";
1036 print "virtio_drivers=no\n";
1050 Richard W.M. Jones L<http://et.redhat.com/~rjones/>
1054 Copyright (C) 2009 Red Hat Inc.
1056 This program is free software; you can redistribute it and/or modify
1057 it under the terms of the GNU General Public License as published by
1058 the Free Software Foundation; either version 2 of the License, or
1059 (at your option) any later version.
1061 This program is distributed in the hope that it will be useful,
1062 but WITHOUT ANY WARRANTY; without even the implied warranty of
1063 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1064 GNU General Public License for more details.
1066 You should have received a copy of the GNU General Public License
1067 along with this program; if not, write to the Free Software
1068 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.