inspector: Determine guest architecture.
[libguestfs.git] / inspector / virt-inspector.pl
1 #!/usr/bin/perl -w
2 # virt-inspector
3 # Copyright (C) 2009 Red Hat Inc.
4 #
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.
9 #
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.
14 #
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.
18
19 use warnings;
20 use strict;
21
22 use Sys::Guestfs;
23 use Sys::Guestfs::Lib qw(open_guest get_partitions resolve_windows_path
24   inspect_all_partitions inspect_partition
25   inspect_operating_systems mount_operating_system inspect_in_detail);
26 use Pod::Usage;
27 use Getopt::Long;
28 use Data::Dumper;
29 use XML::Writer;
30 use Locale::TextDomain 'libguestfs';
31
32 # Optional:
33 eval "use YAML::Any;";
34
35 =encoding utf8
36
37 =head1 NAME
38
39 virt-inspector - Display OS version, kernel, drivers, mount points, applications, etc. in a virtual machine
40
41 =head1 SYNOPSIS
42
43  virt-inspector [--connect URI] domname
44
45  virt-inspector guest.img [guest.img ...]
46
47 =head1 DESCRIPTION
48
49 B<virt-inspector> examines a virtual machine and tries to determine
50 the version of the OS, the kernel version, what drivers are installed,
51 whether the virtual machine is fully virtualized (FV) or
52 para-virtualized (PV), what applications are installed and more.
53
54 Virt-inspector can produce output in several formats, including a
55 readable text report, and XML for feeding into other programs.
56
57 Virt-inspector should only be run on I<inactive> virtual machines.
58 The program tries to determine that the machine is inactive and will
59 refuse to run if it thinks you are trying to inspect a running domain.
60
61 In the normal usage, use C<virt-inspector domname> where C<domname> is
62 the libvirt domain (see: C<virsh list --all>).
63
64 You can also run virt-inspector directly on disk images from a single
65 virtual machine.  Use C<virt-inspector guest.img>.  In rare cases a
66 domain has several block devices, in which case you should list them
67 one after another, with the first corresponding to the guest's
68 C</dev/sda>, the second to the guest's C</dev/sdb> and so on.
69
70 Virt-inspector can only inspect and report upon I<one domain at a
71 time>.  To inspect several virtual machines, you have to run
72 virt-inspector several times (for example, from a shell script
73 for-loop).
74
75 Because virt-inspector needs direct access to guest images, it won't
76 normally work over remote libvirt connections.
77
78 =head1 OPTIONS
79
80 =over 4
81
82 =cut
83
84 my $help;
85
86 =item B<--help>
87
88 Display brief help.
89
90 =cut
91
92 my $version;
93
94 =item B<--version>
95
96 Display version number and exit.
97
98 =cut
99
100 my $uri;
101
102 =item B<--connect URI> | B<-c URI>
103
104 If using libvirt, connect to the given I<URI>.  If omitted,
105 then we connect to the default libvirt hypervisor.
106
107 Libvirt is only used if you specify a C<domname> on the
108 command line.  If you specify guest block devices directly,
109 then libvirt is not used at all.
110
111 =cut
112
113 my $output = "text";
114
115 =back
116
117 The following options select the output format.  Use only one of them.
118 The default is a readable text report.
119
120 =over 4
121
122 =item B<--text> (default)
123
124 Plain text report.
125
126 =item B<--none>
127
128 Produce no output at all.
129
130 =item B<--xml>
131
132 If you select I<--xml> then you get XML output which can be fed
133 to other programs.
134
135 =item B<--yaml>
136
137 If you select I<--yaml> then you get YAML output which can be fed
138 to other programs.
139
140 =item B<--perl>
141
142 If you select I<--perl> then you get Perl structures output which
143 can be used directly in another Perl program.
144
145 =item B<--fish>
146
147 =item B<--ro-fish>
148
149 If you select I<--fish> then we print a L<guestfish(1)> command
150 line which will automatically mount up the filesystems on the
151 correct mount points.  Try this for example:
152
153  guestfish $(virt-inspector --fish guest.img)
154
155 I<--ro-fish> is the same, but the I<--ro> option is passed to
156 guestfish so that the filesystems are mounted read-only.
157
158 =item B<--query>
159
160 In "query mode" we answer common questions about the guest, such
161 as whether it is fullvirt or needs a Xen hypervisor to run.
162
163 See section I<QUERY MODE> below.
164
165 =cut
166
167 my $windows_registry;
168
169 =item B<--windows-registry>
170
171 If this item is passed, I<and> the guest is Windows, I<and> the
172 external program C<reged> is available (see SEE ALSO section), then we
173 attempt to parse the Windows registry.  This allows much more
174 information to be gathered for Windows guests.
175
176 This is quite an expensive and slow operation, so we don't do it by
177 default.
178
179 =back
180
181 =cut
182
183 GetOptions ("help|?" => \$help,
184             "version" => \$version,
185             "connect|c=s" => \$uri,
186             "text" => sub { $output = "text" },
187             "none" => sub { $output = "none" },
188             "xml" => sub { $output = "xml" },
189             "yaml" => sub { $output = "yaml" },
190             "perl" => sub { $output = "perl" },
191             "fish" => sub { $output = "fish" },
192             "guestfish" => sub { $output = "fish" },
193             "ro-fish" => sub { $output = "ro-fish" },
194             "ro-guestfish" => sub { $output = "ro-fish" },
195             "query" => sub { $output = "query" },
196             "windows-registry" => \$windows_registry,
197     ) or pod2usage (2);
198 pod2usage (1) if $help;
199 if ($version) {
200     my $g = Sys::Guestfs->new ();
201     my %h = $g->version ();
202     print "$h{major}.$h{minor}.$h{release}$h{extra}\n";
203     exit
204 }
205 pod2usage (__"virt-inspector: no image or VM names given") if @ARGV == 0;
206
207 my $rw = 0;
208 $rw = 1 if $output eq "fish";
209 my $g;
210 my @images;
211 if ($uri) {
212     my ($conn, $dom);
213     ($g, $conn, $dom, @images) =
214         open_guest (\@ARGV, rw => $rw, address => $uri);
215 } else {
216     my ($conn, $dom);
217     ($g, $conn, $dom, @images) =
218         open_guest (\@ARGV, rw => $rw);
219 }
220
221 $g->launch ();
222 $g->wait_ready ();
223
224 =head1 OUTPUT FORMAT
225
226  Operating system(s)
227  -------------------
228  Linux (distro + version)
229  Windows (version)
230     |
231     |
232     +--- Filesystems ---------- Installed apps --- Kernel & drivers
233          -----------            --------------     ----------------
234          mount point => device  List of apps       Extra information
235          mount point => device  and versions       about kernel(s)
236               ...                                  and drivers
237          swap => swap device
238          (plus lots of extra information
239          about each filesystem)
240
241 The output of virt-inspector is a complex two-level data structure.
242
243 At the top level is a list of the operating systems installed on the
244 guest.  (For the vast majority of guests, only a single OS is
245 installed.)  The data returned for the OS includes the name (Linux,
246 Windows), the distribution and version.
247
248 The diagram above shows what we return for each OS.
249
250 With the I<--xml> option the output is mapped into an XML document.
251 Unfortunately there is no clear schema for this document
252 (contributions welcome) but you can get an idea of the format by
253 looking at other documents and as a last resort the source for this
254 program.
255
256 With the I<--fish> or I<--ro-fish> option the mount points are mapped to
257 L<guestfish(1)> command line parameters, so that you can go in
258 afterwards and inspect the guest with everything mounted in the
259 right place.  For example:
260
261  guestfish $(virt-inspector --ro-fish guest.img)
262  ==> guestfish --ro -a guest.img -m /dev/VG/LV:/ -m /dev/sda1:/boot
263
264 =cut
265
266 # List of possible filesystems.
267 my @partitions = get_partitions ($g);
268
269 # Now query each one to build up a picture of what's in it.
270 my %fses =
271     inspect_all_partitions ($g, \@partitions,
272       use_windows_registry => $windows_registry);
273
274 #print "fses -----------\n";
275 #print Dumper(\%fses);
276
277 my $oses = inspect_operating_systems ($g, \%fses);
278
279 #print "oses -----------\n";
280 #print Dumper($oses);
281
282 # Mount up the disks so we can check for applications
283 # and kernels.  Skip this if the output is "*fish" because
284 # we don't need to know.
285
286 if ($output !~ /.*fish$/) {
287     my $root_dev;
288     foreach $root_dev (sort keys %$oses) {
289         my $os = $oses->{$root_dev};
290         mount_operating_system ($g, $os);
291         inspect_in_detail ($g, $os);
292         $g->umount_all ();
293     }
294 }
295
296 #----------------------------------------------------------------------
297 # Output.
298
299 if ($output eq "fish" || $output eq "ro-fish") {
300     my @osdevs = keys %$oses;
301     # This only works if there is a single OS.
302     die __"--fish output is only possible with a single OS\n" if @osdevs != 1;
303
304     my $root_dev = $osdevs[0];
305
306     if ($output eq "ro-fish") {
307         print "--ro ";
308     }
309
310     print "-a $_ " foreach @images;
311
312     my $mounts = $oses->{$root_dev}->{mounts};
313     # Have to mount / first.  Luckily '/' is early in the ASCII
314     # character set, so this should be OK.
315     foreach (sort keys %$mounts) {
316         print "-m $mounts->{$_}:$_ " if $_ ne "swap" && $_ ne "none";
317     }
318     print "\n"
319 }
320
321 # Perl output.
322 elsif ($output eq "perl") {
323     print Dumper(%$oses);
324 }
325
326 # YAML output
327 elsif ($output eq "yaml") {
328     die __"virt-inspector: no YAML support\n"
329         unless exists $INC{"YAML/Any.pm"};
330
331     print Dump(%$oses);
332 }
333
334 # Plain text output (the default).
335 elsif ($output eq "text") {
336     output_text ();
337 }
338
339 # XML output.
340 elsif ($output eq "xml") {
341     output_xml ();
342 }
343
344 # Query mode.
345 elsif ($output eq "query") {
346     output_query ();
347 }
348
349 sub output_text
350 {
351     output_text_os ($oses->{$_}) foreach sort keys %$oses;
352 }
353
354 sub output_text_os
355 {
356     my $os = shift;
357
358     print $os->{os}, " " if exists $os->{os};
359     print $os->{distro}, " " if exists $os->{distro};
360     print $os->{arch}, " " if exists $os->{arch};
361     print $os->{major_version} if exists $os->{major_version};
362     print ".", $os->{minor_version} if exists $os->{minor_version};
363     print " ";
364     print "on ", $os->{root_device}, ":\n";
365
366     print __"  Mountpoints:\n";
367     my $mounts = $os->{mounts};
368     foreach (sort keys %$mounts) {
369         printf "    %-30s %s\n", $mounts->{$_}, $_
370     }
371
372     print __"  Filesystems:\n";
373     my $filesystems = $os->{filesystems};
374     foreach (sort keys %$filesystems) {
375         print "    $_:\n";
376         print "      label: $filesystems->{$_}{label}\n"
377             if exists $filesystems->{$_}{label};
378         print "      UUID: $filesystems->{$_}{uuid}\n"
379             if exists $filesystems->{$_}{uuid};
380         print "      type: $filesystems->{$_}{fstype}\n"
381             if exists $filesystems->{$_}{fstype};
382         print "      content: $filesystems->{$_}{content}\n"
383             if exists $filesystems->{$_}{content};
384     }
385
386     if (exists $os->{modprobe_aliases}) {
387         my %aliases = %{$os->{modprobe_aliases}};
388         my @keys = sort keys %aliases;
389         if (@keys) {
390             print __"  Modprobe aliases:\n";
391             foreach (@keys) {
392                 printf "    %-30s %s\n", $_, $aliases{$_}->{modulename}
393             }
394         }
395     }
396
397     if (exists $os->{initrd_modules}) {
398         my %modvers = %{$os->{initrd_modules}};
399         my @keys = sort keys %modvers;
400         if (@keys) {
401             print __"  Initrd modules:\n";
402             foreach (@keys) {
403                 my @modules = @{$modvers{$_}};
404                 print "    $_:\n";
405                 print "      $_\n" foreach @modules;
406             }
407         }
408     }
409
410     print __"  Applications:\n";
411     my @apps =  @{$os->{apps}};
412     foreach (@apps) {
413         print "    $_->{name} $_->{version}\n"
414     }
415
416     print __"  Kernels:\n";
417     my @kernels = @{$os->{kernels}};
418     foreach (@kernels) {
419         print "    $_->{version} ($_->{arch})\n";
420         my @modules = @{$_->{modules}};
421         foreach (@modules) {
422             print "      $_\n";
423         }
424     }
425
426     if (exists $os->{root}->{registry}) {
427         print __"  Windows Registry entries:\n";
428         # These are just lumps of text - dump them out.
429         foreach (@{$os->{root}->{registry}}) {
430             print "$_\n";
431         }
432     }
433 }
434
435 sub output_xml
436 {
437     my $xml = new XML::Writer(DATA_MODE => 1, DATA_INDENT => 2);
438
439     $xml->startTag("operatingsystems");
440     output_xml_os ($oses->{$_}, $xml) foreach sort keys %$oses;
441     $xml->endTag("operatingsystems");
442
443     $xml->end();
444 }
445
446 sub output_xml_os
447 {
448     my ($os, $xml) = @_;
449
450     $xml->startTag("operatingsystem");
451
452     foreach ( [ "name" => "os" ],
453               [ "distro" => "distro" ],
454               [ "arch" => "arch" ],
455               [ "major_version" => "major_version" ],
456               [ "minor_version" => "minor_version" ],
457               [ "package_format" => "package_format" ],
458               [ "package_management" => "package_management" ],
459               [ "root" => "root_device" ] ) {
460         $xml->dataElement($_->[0], $os->{$_->[1]}) if exists $os->{$_->[1]};
461     }
462
463     $xml->startTag("mountpoints");
464     my $mounts = $os->{mounts};
465     foreach (sort keys %$mounts) {
466         $xml->dataElement("mountpoint", $_, "dev" => $mounts->{$_});
467     }
468     $xml->endTag("mountpoints");
469
470     $xml->startTag("filesystems");
471     my $filesystems = $os->{filesystems};
472     foreach (sort keys %$filesystems) {
473         $xml->startTag("filesystem", "dev" => $_);
474
475         foreach my $field ( [ "label" => "label" ],
476                             [ "uuid" => "uuid" ],
477                             [ "type" => "fstype" ],
478                             [ "content" => "content" ],
479                             [ "spec" => "spec" ] ) {
480             $xml->dataElement($field->[0], $filesystems->{$_}{$field->[1]})
481                 if exists $filesystems->{$_}{$field->[1]};
482         }
483
484         $xml->endTag("filesystem");
485     }
486     $xml->endTag("filesystems");
487
488     if (exists $os->{modprobe_aliases}) {
489         my %aliases = %{$os->{modprobe_aliases}};
490         my @keys = sort keys %aliases;
491         if (@keys) {
492             $xml->startTag("modprobealiases");
493             foreach (@keys) {
494                 $xml->startTag("alias", "device" => $_);
495
496                 foreach my $field ( [ "modulename" => "modulename" ],
497                                     [ "augeas" => "augeas" ],
498                                     [ "file" => "file" ] ) {
499                     $xml->dataElement($field->[0], $aliases{$_}->{$field->[1]});
500                 }
501
502                 $xml->endTag("alias");
503             }
504             $xml->endTag("modprobealiases");
505         }
506     }
507
508     if (exists $os->{initrd_modules}) {
509         my %modvers = %{$os->{initrd_modules}};
510         my @keys = sort keys %modvers;
511         if (@keys) {
512             $xml->startTag("initrds");
513             foreach (@keys) {
514                 my @modules = @{$modvers{$_}};
515                 $xml->startTag("initrd", "version" => $_);
516                 $xml->dataElement("module", $_) foreach @modules;
517                 $xml->endTag("initrd");
518             }
519             $xml->endTag("initrds");
520         }
521     }
522
523     $xml->startTag("applications");
524     my @apps =  @{$os->{apps}};
525     foreach (@apps) {
526         $xml->startTag("application");
527         $xml->dataElement("name", $_->{name});
528         $xml->dataElement("version", $_->{version});
529         $xml->endTag("application");
530     }
531     $xml->endTag("applications");
532
533     $xml->startTag("kernels");
534     my @kernels = @{$os->{kernels}};
535     foreach (@kernels) {
536         $xml->startTag("kernel",
537                        "version" => $_->{version},
538                        "arch" => $_->{arch});
539         $xml->startTag("modules");
540         my @modules = @{$_->{modules}};
541         foreach (@modules) {
542             $xml->dataElement("module", $_);
543         }
544         $xml->endTag("modules");
545         $xml->endTag("kernel");
546     }
547     $xml->endTag("kernels");
548
549     if (exists $os->{root}->{registry}) {
550         $xml->startTag("windowsregistryentries");
551         # These are just lumps of text - dump them out.
552         foreach (@{$os->{root}->{registry}}) {
553             $xml->dataElement("windowsregistryentry", $_);
554         }
555         $xml->endTag("windowsregistryentries");
556     }
557
558     $xml->endTag("operatingsystem");
559 }
560
561 =head1 QUERY MODE
562
563 When you use C<virt-inspector --query>, the output is a series of
564 lines of the form:
565
566  windows=no
567  linux=yes
568  fullvirt=yes
569  xen_pv_drivers=no
570
571 (each answer is usually C<yes> or C<no>, or the line is completely
572 missing if we could not determine the answer at all).
573
574 If the guest is multiboot, you can get apparently conflicting answers
575 (eg. C<windows=yes> and C<linux=yes>, or a guest which is both
576 fullvirt and has a Xen PV kernel).  This is normal, and just means
577 that the guest can do both things, although it might require operator
578 intervention such as selecting a boot option when the guest is
579 booting.
580
581 This section describes the full range of answers possible.
582
583 =over 4
584
585 =cut
586
587 sub output_query
588 {
589     output_query_windows ();
590     output_query_linux ();
591     output_query_rhel ();
592     output_query_fedora ();
593     output_query_debian ();
594     output_query_fullvirt ();
595     output_query_xen_domU_kernel ();
596     output_query_xen_pv_drivers ();
597     output_query_virtio_drivers ();
598     output_query_kernel_arch ();
599     output_query_userspace_arch ();
600 }
601
602 =item windows=(yes|no)
603
604 Answer C<yes> if Microsoft Windows is installed in the guest.
605
606 =cut
607
608 sub output_query_windows
609 {
610     my $windows = "no";
611     foreach my $os (keys %$oses) {
612         $windows="yes" if $oses->{$os}->{os} eq "windows";
613     }
614     print "windows=$windows\n";
615 }
616
617 =item linux=(yes|no)
618
619 Answer C<yes> if a Linux kernel is installed in the guest.
620
621 =cut
622
623 sub output_query_linux
624 {
625     my $linux = "no";
626     foreach my $os (keys %$oses) {
627         $linux="yes" if $oses->{$os}->{os} eq "linux";
628     }
629     print "linux=$linux\n";
630 }
631
632 =item rhel=(yes|no)
633
634 Answer C<yes> if the guest contains Red Hat Enterprise Linux.
635
636 =cut
637
638 sub output_query_rhel
639 {
640     my $rhel = "no";
641     foreach my $os (keys %$oses) {
642         $rhel="yes" if ($oses->{$os}->{os} eq "linux" &&
643                         $oses->{$os}->{distro} eq "rhel");
644     }
645     print "rhel=$rhel\n";
646 }
647
648 =item fedora=(yes|no)
649
650 Answer C<yes> if the guest contains the Fedora Linux distribution.
651
652 =cut
653
654 sub output_query_fedora
655 {
656     my $fedora = "no";
657     foreach my $os (keys %$oses) {
658         $fedora="yes" if $oses->{$os}->{os} eq "linux" && $oses->{$os}->{distro} eq "fedora";
659     }
660     print "fedora=$fedora\n";
661 }
662
663 =item debian=(yes|no)
664
665 Answer C<yes> if the guest contains the Debian Linux distribution.
666
667 =cut
668
669 sub output_query_debian
670 {
671     my $debian = "no";
672     foreach my $os (keys %$oses) {
673         $debian="yes" if $oses->{$os}->{os} eq "linux" && $oses->{$os}->{distro} eq "debian";
674     }
675     print "debian=$debian\n";
676 }
677
678 =item fullvirt=(yes|no)
679
680 Answer C<yes> if there is at least one operating system kernel
681 installed in the guest which runs fully virtualized.  Such a guest
682 would require a hypervisor which supports full system virtualization.
683
684 =cut
685
686 sub output_query_fullvirt
687 {
688     # The assumption is full-virt, unless all installed kernels
689     # are identified as paravirt.
690     # XXX Fails on Windows guests.
691     foreach my $os (keys %$oses) {
692         foreach my $kernel (@{$oses->{$os}->{kernels}}) {
693             my $is_pv = $kernel->{version} =~ m/xen/;
694             unless ($is_pv) {
695                 print "fullvirt=yes\n";
696                 return;
697             }
698         }
699     }
700     print "fullvirt=no\n";
701 }
702
703 =item xen_domU_kernel=(yes|no)
704
705 Answer C<yes> if there is at least one Linux kernel installed in
706 the guest which is compiled as a Xen DomU (a Xen paravirtualized
707 guest).
708
709 =cut
710
711 sub output_query_xen_domU_kernel
712 {
713     foreach my $os (keys %$oses) {
714         foreach my $kernel (@{$oses->{$os}->{kernels}}) {
715             my $is_xen = $kernel->{version} =~ m/xen/;
716             if ($is_xen) {
717                 print "xen_domU_kernel=yes\n";
718                 return;
719             }
720         }
721     }
722     print "xen_domU_kernel=no\n";
723 }
724
725 =item xen_pv_drivers=(yes|no)
726
727 Answer C<yes> if the guest has Xen paravirtualized drivers installed
728 (usually the kernel itself will be fully virtualized, but the PV
729 drivers have been installed by the administrator for performance
730 reasons).
731
732 =cut
733
734 sub output_query_xen_pv_drivers
735 {
736     foreach my $os (keys %$oses) {
737         foreach my $kernel (@{$oses->{$os}->{kernels}}) {
738             foreach my $module (@{$kernel->{modules}}) {
739                 if ($module =~ m/xen-/) {
740                     print "xen_pv_drivers=yes\n";
741                     return;
742                 }
743             }
744         }
745     }
746     print "xen_pv_drivers=no\n";
747 }
748
749 =item virtio_drivers=(yes|no)
750
751 Answer C<yes> if the guest has virtio paravirtualized drivers
752 installed.  Virtio drivers are commonly used to improve the
753 performance of KVM.
754
755 =cut
756
757 sub output_query_virtio_drivers
758 {
759     foreach my $os (keys %$oses) {
760         foreach my $kernel (@{$oses->{$os}->{kernels}}) {
761             foreach my $module (@{$kernel->{modules}}) {
762                 if ($module =~ m/virtio_/) {
763                     print "virtio_drivers=yes\n";
764                     return;
765                 }
766             }
767         }
768     }
769     print "virtio_drivers=no\n";
770 }
771
772 =item userspace_arch=(x86_64|...)
773
774 Print the architecture of userspace.
775
776 NB. For multi-boot VMs this can print several lines.
777
778 =cut
779
780 sub output_query_userspace_arch
781 {
782     my %arches;
783
784     foreach my $os (keys %$oses) {
785         $arches{$oses->{$os}->{arch}} = 1 if exists $oses->{$os}->{arch};
786     }
787
788     foreach (sort keys %arches) {
789         print "userspace_arch=$_\n";
790     }
791 }
792
793 =item kernel_arch=(x86_64|...)
794
795 Print the architecture of the kernel.
796
797 NB. For multi-boot VMs this can print several lines.
798
799 =cut
800
801 sub output_query_kernel_arch
802 {
803     my %arches;
804
805     foreach my $os (keys %$oses) {
806         foreach my $kernel (@{$oses->{$os}->{kernels}}) {
807             $arches{$kernel->{arch}} = 1 if exists $kernel->{arch};
808         }
809     }
810
811     foreach (sort keys %arches) {
812         print "kernel_arch=$_\n";
813     }
814 }
815
816 =back
817
818 =head1 SEE ALSO
819
820 L<guestfs(3)>,
821 L<guestfish(1)>,
822 L<Sys::Guestfs(3)>,
823 L<Sys::Guestfs::Lib(3)>,
824 L<Sys::Virt(3)>,
825 L<http://libguestfs.org/>.
826
827 For Windows registry parsing we require the C<reged> program
828 from L<http://home.eunet.no/~pnordahl/ntpasswd/>.
829
830 =head1 AUTHOR
831
832 Richard W.M. Jones L<http://et.redhat.com/~rjones/>
833
834 Matthew Booth L<mbooth@redhat.com>
835
836 =head1 COPYRIGHT
837
838 Copyright (C) 2009 Red Hat Inc.
839
840 This program is free software; you can redistribute it and/or modify
841 it under the terms of the GNU General Public License as published by
842 the Free Software Foundation; either version 2 of the License, or
843 (at your option) any later version.
844
845 This program is distributed in the hope that it will be useful,
846 but WITHOUT ANY WARRANTY; without even the implied warranty of
847 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
848 GNU General Public License for more details.
849
850 You should have received a copy of the GNU General Public License
851 along with this program; if not, write to the Free Software
852 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.