Version 1.7.1.
[libguestfs.git] / tools / virt-edit
1 #!/usr/bin/perl -w
2 # virt-edit
3 # Copyright (C) 2009-2010 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);
24 use Pod::Usage;
25 use Getopt::Long;
26 use File::Temp qw/tempfile/;
27 use File::Basename;
28 use Locale::TextDomain 'libguestfs';
29
30 =encoding utf8
31
32 =head1 NAME
33
34 virt-edit - Edit a file in a virtual machine
35
36 =head1 SYNOPSIS
37
38  virt-edit [--options] domname file
39
40  virt-edit [--options] disk.img [disk.img ...] file
41
42  virt-edit [domname|disk.img] file -e 'expr'
43
44 =head1 WARNING
45
46 You must I<not> use C<virt-edit> on live virtual machines.  If you do
47 this, you risk disk corruption in the VM.  C<virt-edit> tries to stop
48 you from doing this, but doesn't catch all cases.
49
50 =head1 DESCRIPTION
51
52 C<virt-edit> is a command line tool to edit C<file> where C<file>
53 exists in the named virtual machine (or disk image).
54
55 If you want to just view a file, use L<virt-cat(1)>.  For more complex
56 cases you should look at the L<guestfish(1)> tool.
57
58 =head1 EXAMPLES
59
60 Edit the named files interactively:
61
62  virt-edit mydomain /boot/grub/grub.conf
63
64  virt-edit mydomain /etc/passwd
65
66 You can also edit files non-interactively (see
67 L</NON-INTERACTIVE EDITING> below).
68 To change the init default level to 5:
69
70  virt-edit mydomain /etc/inittab -e 's/^id:.*/id:5:initdefault:/'
71
72 =head1 OPTIONS
73
74 =over 4
75
76 =cut
77
78 my $help;
79
80 =item B<--help>
81
82 Display brief help.
83
84 =cut
85
86 my $version;
87
88 =item B<--version>
89
90 Display version number and exit.
91
92 =cut
93
94 my $backup;
95
96 =item B<--backup extension> | B<-b extension>
97
98 Create a backup of the original file I<in the guest disk image>.
99 The backup has the original filename with C<extension> added.
100
101 Usually the first character of C<extension> would be a dot C<.>
102 so you would write:
103
104  virt-edit -b .orig [etc]
105
106 By default, no backup file is made.
107
108 =cut
109
110 my $uri;
111
112 =item B<--connect URI> | B<-c URI>
113
114 If using libvirt, connect to the given I<URI>.  If omitted, then we
115 connect to the default libvirt hypervisor.
116
117 If you specify guest block devices directly, then libvirt is not used
118 at all.
119
120 =cut
121
122 my $format;
123
124 =item B<--format> raw
125
126 Specify the format of disk images given on the command line.  If this
127 is omitted then the format is autodetected from the content of the
128 disk image.
129
130 If disk images are requested from libvirt, then this program asks
131 libvirt for this information.  In this case, the value of the format
132 parameter is ignored.
133
134 If working with untrusted raw-format guest disk images, you should
135 ensure the format is always specified.
136
137 =cut
138
139 my $expr;
140
141 =item B<--expr EXPR> | B<-e EXPR>
142
143 Instead of launching the external editor, non-interactively
144 apply the Perl expression C<EXPR> to each line in the file.
145 See L</NON-INTERACTIVE EDITING> below.
146
147 Be careful to properly quote the expression to prevent it from
148 being altered by the shell.
149
150 =back
151
152 =cut
153
154 GetOptions ("help|?" => \$help,
155             "version" => \$version,
156             "connect|c=s" => \$uri,
157             "format=s" => \$format,
158             "expr|e=s" => \$expr,
159             "backup|b=s" => \$backup,
160     ) or pod2usage (2);
161 pod2usage (1) if $help;
162 if ($version) {
163     my $g = Sys::Guestfs->new ();
164     my %h = $g->version ();
165     print "$h{major}.$h{minor}.$h{release}$h{extra}\n";
166     exit
167 }
168
169 pod2usage (__"virt-edit: no image, VM names or filenames to edit given")
170     if @ARGV <= 1;
171
172 my $filename = pop @ARGV;
173
174 my $g;
175 if ($uri) {
176     $g = open_guest (\@ARGV, address => $uri, rw => 1, format => $format);
177 } else {
178     $g = open_guest (\@ARGV, rw => 1, format => $format);
179 }
180
181 $g->launch ();
182
183 my @roots = $g->inspect_os ();
184 if (@roots == 0) {
185     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",
186             prog => basename ($0));
187 }
188 if (@roots > 1) {
189     die __x("{prog}: multiboot operating systems are not supported.\n",
190             prog => basename ($0))
191 }
192 my %fses = $g->inspect_get_mountpoints ($roots[0]);
193 my @fses = sort { length $a <=> length $b } keys %fses;
194 foreach (@fses) {
195     $g->mount_options ("", $fses{$_}, $_);
196 }
197
198 my ($fh, $tempname) = tempfile (UNLINK => 1);
199 my $fddev = "/dev/fd/" . fileno ($fh);
200
201 # Allow this to fail in case eg. the file does not exist.
202 $g->download ($filename, $fddev);
203
204 close $fh or die "close: $!";
205
206 my $do_upload = $tempname;
207
208 if (!defined $expr) {
209     # Interactively edit the file.
210     my $oldctime = (stat ($tempname))[10];
211
212     my $editor = $ENV{EDITOR};
213     $editor ||= "vi";
214     system ("$editor $tempname") == 0
215         or die "edit failed: $editor: $?";
216
217     my $newctime = (stat ($tempname))[10];
218
219     if ($oldctime == $newctime) {
220         $do_upload = undef;
221         print __"File not changed.\n";
222     }
223 } else {
224     my ($fh, $tempout) = tempfile (UNLINK => 1);
225
226     # Apply a Perl expression to the lines of the file.
227     open IFILE, $tempname or die "$tempname: $!";
228     my $lineno = 0;
229     while (<IFILE>) {
230         $lineno++;
231         eval $expr;
232         die if $@;
233         print $fh $_ or die "print: $!";
234     }
235     close $fh or die "close: $!";
236
237     $do_upload = $tempout;
238 }
239
240 if (defined $do_upload) {
241     # Upload to a new file, so if it fails we don't end up with
242     # a partially written file.  Give the new file a completely
243     # random name so we have only a tiny chance of overwriting
244     # some existing file.
245     my $dirname = $filename;
246     $dirname =~ s{/[^/]+$}{/};
247
248     my @chars = ('a'..'z', 'A'..'Z', '0'..'9');
249     my $newname = $dirname;
250     foreach (0..7) {
251         $newname .= $chars[rand @chars];
252     }
253
254     $g->upload ($do_upload, $newname);
255
256     # Backup or overwrite?
257     $g->mv ($filename, "$filename$backup") if defined $backup;
258     $g->mv ($newname, $filename);
259
260     $g->umount_all ();
261     $g->sync ();
262 }
263
264 undef $g;
265
266 exit 0;
267
268 =head1 NON-INTERACTIVE EDITING
269
270 C<virt-edit> normally calls out to C<$EDITOR> (or vi) so
271 the system administrator can interactively edit the file.
272
273 There are two ways also to use C<virt-edit> from scripts in order to
274 make automated edits to files.  (Note that although you I<can> use
275 C<virt-edit> like this, it's less error-prone to write scripts
276 directly using the libguestfs API and Augeas for configuration file
277 editing.)
278
279 The first method is to temporarily set C<$EDITOR> to any script or
280 program you want to run.  The script is invoked as C<$EDITOR tmpfile>
281 and it should update C<tmpfile> in place however it likes.
282
283 The second method is to use the C<-e> parameter of C<virt-edit> to run
284 a short Perl snippet in the style of L<sed(1)>.  For example to
285 replace all instances of C<foo> with C<bar> in a file:
286
287  virt-edit domname filename -e 's/foo/bar/'
288
289 The full power of Perl regular expressions can be used (see
290 L<perlre(1)>).  For example to delete root's password you could do:
291
292  virt-edit domname /etc/passwd -e 's/^root:.*?:/root::/'
293
294 What really happens is that the snippet is evaluated as a Perl
295 expression for each line of the file.  The line, including the final
296 C<\n>, is passed in C<$_> and the expression should update C<$_> or
297 leave it unchanged.
298
299 To delete a line, set C<$_> to the empty string.  For example, to
300 delete the C<apache> user account from the password file you can do:
301
302  virt-edit mydomain /etc/passwd -e '$_ = "" if /^apache:/'
303
304 To insert a line, prepend or append it to C<$_>.  However appending
305 lines to the end of the file is rather difficult this way since there
306 is no concept of "last line of the file" - your expression just
307 doesn't get called again.  You might want to use the first method
308 (setting C<$EDITOR>) if you want to do this.
309
310 The variable C<$lineno> contains the current line number.
311 As is traditional, the first line in the file is number C<1>.
312
313 The return value from the expression is ignored, but the expression
314 may call C<die> in order to abort the whole program, leaving the
315 original file untouched.
316
317 Remember when matching the end of a line that C<$_> may contain the
318 final C<\n>, or (for DOS files) C<\r\n>, or if the file does not end
319 with a newline then neither of these.  Thus to match or substitute
320 some text at the end of a line, use this regular expression:
321
322  /some text(\r?\n)?$/
323
324 Alternately, use the perl C<chomp> function, being careful not to
325 chomp C<$_> itself (since that would remove all newlines from the
326 file):
327
328  my $m = $_; chomp $m; $m =~ /some text$/
329
330 =head1 ENVIRONMENT VARIABLES
331
332 =over 4
333
334 =item C<EDITOR>
335
336 If set, this string is used as the editor.  It may contain arguments,
337 eg. C<"emacs -nw">
338
339 If not set, C<vi> is used.
340
341 =back
342
343 =head1 SHELL QUOTING
344
345 Libvirt guest names can contain arbitrary characters, some of which
346 have meaning to the shell such as C<#> and space.  You may need to
347 quote or escape these characters on the command line.  See the shell
348 manual page L<sh(1)> for details.
349
350 =head1 SEE ALSO
351
352 L<guestfs(3)>,
353 L<guestfish(1)>,
354 L<virt-cat(1)>,
355 L<Sys::Guestfs(3)>,
356 L<Sys::Guestfs::Lib(3)>,
357 L<Sys::Virt(3)>,
358 L<http://libguestfs.org/>,
359 L<perl(1)>,
360 L<perlre(1)>.
361
362 =head1 AUTHOR
363
364 Richard W.M. Jones L<http://people.redhat.com/~rjones/>
365
366 =head1 COPYRIGHT
367
368 Copyright (C) 2009-2010 Red Hat Inc.
369
370 This program is free software; you can redistribute it and/or modify
371 it under the terms of the GNU General Public License as published by
372 the Free Software Foundation; either version 2 of the License, or
373 (at your option) any later version.
374
375 This program is distributed in the hope that it will be useful,
376 but WITHOUT ANY WARRANTY; without even the implied warranty of
377 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
378 GNU General Public License for more details.
379
380 You should have received a copy of the GNU General Public License
381 along with this program; if not, write to the Free Software
382 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.