New tools: virt-copy-in, virt-copy-out, virt-tar-in, virt-tar-out.
[libguestfs.git] / tools / virt-edit
index e00e4cf..c8ee159 100755 (executable)
@@ -1,6 +1,6 @@
 #!/usr/bin/perl -w
 # virt-edit
 #!/usr/bin/perl -w
 # virt-edit
-# Copyright (C) 2009 Red Hat Inc.
+# Copyright (C) 2009-2010 Red Hat Inc.
 #
 # 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
 #
 # 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
@@ -20,12 +20,11 @@ use warnings;
 use strict;
 
 use Sys::Guestfs;
 use strict;
 
 use Sys::Guestfs;
-use Sys::Guestfs::Lib qw(open_guest get_partitions resolve_windows_path
-  inspect_all_partitions inspect_partition
-  inspect_operating_systems mount_operating_system);
+use Sys::Guestfs::Lib qw(open_guest);
 use Pod::Usage;
 use Getopt::Long;
 use File::Temp qw/tempfile/;
 use Pod::Usage;
 use Getopt::Long;
 use File::Temp qw/tempfile/;
+use File::Basename;
 use Locale::TextDomain 'libguestfs';
 
 =encoding utf8
 use Locale::TextDomain 'libguestfs';
 
 =encoding utf8
@@ -40,6 +39,8 @@ virt-edit - Edit a file in a virtual machine
 
  virt-edit [--options] disk.img [disk.img ...] file
 
 
  virt-edit [--options] disk.img [disk.img ...] file
 
+ virt-edit [domname|disk.img] file -e 'expr'
+
 =head1 WARNING
 
 You must I<not> use C<virt-edit> on live virtual machines.  If you do
 =head1 WARNING
 
 You must I<not> use C<virt-edit> on live virtual machines.  If you do
@@ -56,10 +57,18 @@ cases you should look at the L<guestfish(1)> tool.
 
 =head1 EXAMPLES
 
 
 =head1 EXAMPLES
 
+Edit the named files interactively:
+
  virt-edit mydomain /boot/grub/grub.conf
 
  virt-edit mydomain /etc/passwd
 
  virt-edit mydomain /boot/grub/grub.conf
 
  virt-edit mydomain /etc/passwd
 
+You can also edit files non-interactively (see
+L</NON-INTERACTIVE EDITING> below).
+To change the init default level to 5:
+
+ virt-edit mydomain /etc/inittab -e 's/^id:.*/id:5:initdefault:/'
+
 =head1 OPTIONS
 
 =over 4
 =head1 OPTIONS
 
 =over 4
@@ -82,6 +91,22 @@ Display version number and exit.
 
 =cut
 
 
 =cut
 
+my $backup;
+
+=item B<--backup extension> | B<-b extension>
+
+Create a backup of the original file I<in the guest disk image>.
+The backup has the original filename with C<extension> added.
+
+Usually the first character of C<extension> would be a dot C<.>
+so you would write:
+
+ virt-edit -b .orig [etc]
+
+By default, no backup file is made.
+
+=cut
+
 my $uri;
 
 =item B<--connect URI> | B<-c URI>
 my $uri;
 
 =item B<--connect URI> | B<-c URI>
@@ -92,6 +117,36 @@ connect to the default libvirt hypervisor.
 If you specify guest block devices directly, then libvirt is not used
 at all.
 
 If you specify guest block devices directly, then libvirt is not used
 at all.
 
+=cut
+
+my $format;
+
+=item B<--format> raw
+
+Specify the format of disk images given on the command line.  If this
+is omitted then the format is autodetected from the content of the
+disk image.
+
+If disk images are requested from libvirt, then this program asks
+libvirt for this information.  In this case, the value of the format
+parameter is ignored.
+
+If working with untrusted raw-format guest disk images, you should
+ensure the format is always specified.
+
+=cut
+
+my $expr;
+
+=item B<--expr EXPR> | B<-e EXPR>
+
+Instead of launching the external editor, non-interactively
+apply the Perl expression C<EXPR> to each line in the file.
+See L</NON-INTERACTIVE EDITING> below.
+
+Be careful to properly quote the expression to prevent it from
+being altered by the shell.
+
 =back
 
 =cut
 =back
 
 =cut
@@ -99,6 +154,9 @@ at all.
 GetOptions ("help|?" => \$help,
             "version" => \$version,
             "connect|c=s" => \$uri,
 GetOptions ("help|?" => \$help,
             "version" => \$version,
             "connect|c=s" => \$uri,
+            "format=s" => \$format,
+            "expr|e=s" => \$expr,
+            "backup|b=s" => \$backup,
     ) or pod2usage (2);
 pod2usage (1) if $help;
 if ($version) {
     ) or pod2usage (2);
 pod2usage (1) if $help;
 if ($version) {
@@ -115,57 +173,160 @@ my $filename = pop @ARGV;
 
 my $g;
 if ($uri) {
 
 my $g;
 if ($uri) {
-    $g = open_guest (\@ARGV, address => $uri, rw => 1);
+    $g = open_guest (\@ARGV, address => $uri, rw => 1, format => $format);
 } else {
 } else {
-    $g = open_guest (\@ARGV, rw => 1);
+    $g = open_guest (\@ARGV, rw => 1, format => $format);
 }
 
 $g->launch ();
 
 }
 
 $g->launch ();
 
-# List of possible filesystems.
-my @partitions = get_partitions ($g);
-
-# Now query each one to build up a picture of what's in it.
-my %fses =
-    inspect_all_partitions ($g, \@partitions,
-      use_windows_registry => 0);
-
-my $oses = inspect_operating_systems ($g, \%fses);
+my @roots = $g->inspect_os ();
+if (@roots == 0) {
+    die __x("{prog}: No operating system could be detected inside this disk image.\n\nThis may be because the file is not a disk image, or is not a virtual machine\nimage, or because the OS type is not understood by libguestfs.\n\nIf you feel this is an error, please file a bug report including as much\ninformation about the disk image as possible.\n",
+            prog => basename ($0));
+}
+if (@roots > 1) {
+    die __x("{prog}: multiboot operating systems are not supported.\n",
+            prog => basename ($0))
+}
+my %fses = $g->inspect_get_mountpoints ($roots[0]);
+my @fses = sort { length $a <=> length $b } keys %fses;
+foreach (@fses) {
+    $g->mount_options ("", $fses{$_}, $_);
+}
 
 
-my @roots = keys %$oses;
-die __"multiboot operating systems are not supported by virt-edit" if @roots > 1;
-my $root_dev = $roots[0];
+my ($fh, $tempname) = tempfile (UNLINK => 1);
+my $fddev = "/dev/fd/" . fileno ($fh);
 
 
-my $os = $oses->{$root_dev};
-mount_operating_system ($g, $os, 0);
+# Allow this to fail in case eg. the file does not exist.
+$g->download ($filename, $fddev);
 
 
-my ($fh, $tempname) = tempfile ();
+close $fh or die "close: $!";
 
 
-# Allow this to fail in case eg. the file does not exist.
-$g->download($filename, $tempname);
+my $do_upload = $tempname;
 
 
-my $oldctime = (stat ($tempname))[10];
+if (!defined $expr) {
+    # Interactively edit the file.
+    my $oldctime = (stat ($tempname))[10];
 
 
-my $editor = $ENV{EDITOR};
-$editor ||= "vi";
-system ("$editor $tempname") == 0
-    or die "edit failed: $editor: $?";
+    my $editor = $ENV{EDITOR};
+    $editor ||= "vi";
+    system ("$editor $tempname") == 0
+        or die "edit failed: $editor: $?";
 
 
-my $newctime = (stat ($tempname))[10];
+    my $newctime = (stat ($tempname))[10];
 
 
-if ($oldctime != $newctime) {
-    $g->upload ($tempname, $filename)
+    if ($oldctime == $newctime) {
+        $do_upload = undef;
+        print __"File not changed.\n";
+    }
 } else {
 } else {
-    print __"File not changed.\n";
+    my ($fh, $tempout) = tempfile (UNLINK => 1);
+
+    # Apply a Perl expression to the lines of the file.
+    open IFILE, $tempname or die "$tempname: $!";
+    my $lineno = 0;
+    while (<IFILE>) {
+        $lineno++;
+        eval $expr;
+        die if $@;
+        print $fh $_ or die "print: $!";
+    }
+    close $fh or die "close: $!";
+
+    $do_upload = $tempout;
 }
 
 }
 
-$g->sync ();
-$g->umount_all ();
+if (defined $do_upload) {
+    # Upload to a new file, so if it fails we don't end up with
+    # a partially written file.  Give the new file a completely
+    # random name so we have only a tiny chance of overwriting
+    # some existing file.
+    my $dirname = $filename;
+    $dirname =~ s{/[^/]+$}{/};
+
+    my @chars = ('a'..'z', 'A'..'Z', '0'..'9');
+    my $newname = $dirname;
+    foreach (0..7) {
+        $newname .= $chars[rand @chars];
+    }
+
+    $g->upload ($do_upload, $newname);
+
+    # Backup or overwrite?
+    $g->mv ($filename, "$filename$backup") if defined $backup;
+    $g->mv ($newname, $filename);
+
+    $g->umount_all ();
+    $g->sync ();
+}
 
 undef $g;
 
 exit 0;
 
 
 undef $g;
 
 exit 0;
 
+=head1 NON-INTERACTIVE EDITING
+
+C<virt-edit> normally calls out to C<$EDITOR> (or vi) so
+the system administrator can interactively edit the file.
+
+There are two ways also to use C<virt-edit> from scripts in order to
+make automated edits to files.  (Note that although you I<can> use
+C<virt-edit> like this, it's less error-prone to write scripts
+directly using the libguestfs API and Augeas for configuration file
+editing.)
+
+The first method is to temporarily set C<$EDITOR> to any script or
+program you want to run.  The script is invoked as C<$EDITOR tmpfile>
+and it should update C<tmpfile> in place however it likes.
+
+The second method is to use the C<-e> parameter of C<virt-edit> to run
+a short Perl snippet in the style of L<sed(1)>.  For example to
+replace all instances of C<foo> with C<bar> in a file:
+
+ virt-edit domname filename -e 's/foo/bar/'
+
+The full power of Perl regular expressions can be used (see
+L<perlre(1)>).  For example to delete root's password you could do:
+
+ virt-edit domname /etc/passwd -e 's/^root:.*?:/root::/'
+
+What really happens is that the snippet is evaluated as a Perl
+expression for each line of the file.  The line, including the final
+C<\n>, is passed in C<$_> and the expression should update C<$_> or
+leave it unchanged.
+
+To delete a line, set C<$_> to the empty string.  For example, to
+delete the C<apache> user account from the password file you can do:
+
+ virt-edit mydomain /etc/passwd -e '$_ = "" if /^apache:/'
+
+To insert a line, prepend or append it to C<$_>.  However appending
+lines to the end of the file is rather difficult this way since there
+is no concept of "last line of the file" - your expression just
+doesn't get called again.  You might want to use the first method
+(setting C<$EDITOR>) if you want to do this.
+
+The variable C<$lineno> contains the current line number.
+As is traditional, the first line in the file is number C<1>.
+
+The return value from the expression is ignored, but the expression
+may call C<die> in order to abort the whole program, leaving the
+original file untouched.
+
+Remember when matching the end of a line that C<$_> may contain the
+final C<\n>, or (for DOS files) C<\r\n>, or if the file does not end
+with a newline then neither of these.  Thus to match or substitute
+some text at the end of a line, use this regular expression:
+
+ /some text(\r?\n)?$/
+
+Alternately, use the perl C<chomp> function, being careful not to
+chomp C<$_> itself (since that would remove all newlines from the
+file):
+
+ my $m = $_; chomp $m; $m =~ /some text$/
+
 =head1 ENVIRONMENT VARIABLES
 
 =over 4
 =head1 ENVIRONMENT VARIABLES
 
 =over 4
@@ -179,15 +340,26 @@ If not set, C<vi> is used.
 
 =back
 
 
 =back
 
+=head1 SHELL QUOTING
+
+Libvirt guest names can contain arbitrary characters, some of which
+have meaning to the shell such as C<#> and space.  You may need to
+quote or escape these characters on the command line.  See the shell
+manual page L<sh(1)> for details.
+
 =head1 SEE ALSO
 
 L<guestfs(3)>,
 L<guestfish(1)>,
 L<virt-cat(1)>,
 =head1 SEE ALSO
 
 L<guestfs(3)>,
 L<guestfish(1)>,
 L<virt-cat(1)>,
+L<virt-copy-in(1)>,
+L<virt-tar-in(1)>,
 L<Sys::Guestfs(3)>,
 L<Sys::Guestfs::Lib(3)>,
 L<Sys::Virt(3)>,
 L<Sys::Guestfs(3)>,
 L<Sys::Guestfs::Lib(3)>,
 L<Sys::Virt(3)>,
-L<http://libguestfs.org/>.
+L<http://libguestfs.org/>,
+L<perl(1)>,
+L<perlre(1)>.
 
 =head1 AUTHOR
 
 
 =head1 AUTHOR
 
@@ -195,7 +367,7 @@ Richard W.M. Jones L<http://people.redhat.com/~rjones/>
 
 =head1 COPYRIGHT
 
 
 =head1 COPYRIGHT
 
-Copyright (C) 2009 Red Hat Inc.
+Copyright (C) 2009-2010 Red Hat Inc.
 
 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
 
 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