Add the perl file editing, mostly based in the virt-edit implementation. This introduces a mild code duplication with edit_file_editor; will deal with it in a later commit. --- fish/file-edit.c | 110 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ fish/file-edit.h | 11 ++++++ 2 files changed, 121 insertions(+) diff --git a/fish/file-edit.c b/fish/file-edit.c index e2298eb..fa68626 100644 --- a/fish/file-edit.c +++ b/fish/file-edit.c @@ -159,6 +159,116 @@ edit_file_editor (guestfs_h *g, const char *filename, const char *editor, return 0; } +int +edit_file_perl (guestfs_h *g, const char *filename, const char *perl_expr, + const char *backup_extension) +{ + CLEANUP_FREE char *tmpdir = guestfs_get_tmpdir (g); + CLEANUP_UNLINK_FREE char *tmpfilename = NULL; + char buf[256]; + CLEANUP_FREE char *newname = NULL; + CLEANUP_FREE char *cmd = NULL; + CLEANUP_FREE char *outfile = NULL; + int r, fd; + + /* Download the file and write it to a temporary. */ + if (asprintf (&tmpfilename, "%s/libguestfsXXXXXX", tmpdir) == -1) { + perror ("asprintf"); + return -1; + } + + fd = mkstemp (tmpfilename); + if (fd == -1) { + perror ("mkstemp"); + return -1; + } + + snprintf (buf, sizeof buf, "/dev/fd/%d", fd); + + if (guestfs_download (g, filename, buf) == -1) { + close (fd); + return -1; + } + + if (close (fd) == -1) { + perror (tmpfilename); + return -1; + } + + if (asprintf (&outfile, "%s.out", tmpfilename) == -1) { + perror ("asprintf"); + return -1; + } + + /* Pass the expression to Perl via the environment. This sidesteps + * any quoting problems with the already complex Perl command line. + */ + setenv ("virt_edit_expr", perl_expr, 1); + + /* Call out to a canned Perl script. */ + if (asprintf (&cmd, + "perl -e '" + "$lineno = 0; " + "$expr = $ENV{virt_edit_expr}; " + "while (<STDIN>) { " + " $lineno++; " + " eval $expr; " + " die if $@; " + " print STDOUT $_ or die \"print: $!\"; " + "} " + "close STDOUT or die \"close: $!\"; " + "' < %s > %s", + tmpfilename, outfile) == -1) { + perror ("asprintf"); + return -1; + } + + r = system (cmd); + if (r == -1 || WEXITSTATUS (r) != 0) + return -1; + + if (rename (outfile, tmpfilename) == -1) { + perror ("rename"); + return -1; + } + + /* Upload to a new file in the same directory, 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. + */ + newname = generate_random_name (filename); + if (!newname) + return -1; + + /* Write new content. */ + if (guestfs_upload (g, tmpfilename, newname) == -1) + return -1; + + /* Set the permissions, UID, GID and SELinux context of the new + * file to match the old file (RHBZ#788641). + */ + if (guestfs_copy_attributes (g, filename, newname, + GUESTFS_COPY_ATTRIBUTES_ALL, 1, -1) == -1) + return -1; + + /* Backup or overwrite the file. */ + if (backup_extension) { + CLEANUP_FREE char *backupname = NULL; + + backupname = generate_backup_name (filename, backup_extension); + if (backupname == NULL) + return -1; + + if (guestfs_mv (g, filename, backupname) == -1) + return -1; + } + if (guestfs_mv (g, newname, filename) == -1) + return -1; + + return 0; +} + static char random_char (void) { diff --git a/fish/file-edit.h b/fish/file-edit.h index 1d401c6..98fc453 100644 --- a/fish/file-edit.h +++ b/fish/file-edit.h @@ -32,4 +32,15 @@ extern int edit_file_editor (guestfs_h *g, const char *filename, const char *editor, const char *backup_extension); +/** + * Edit 'filename' running the specified 'perl_expr' using Perl. + * If 'backup_extension' is not null, then a copy of 'filename' is saved + * with 'backup_extension' appended to its file name. + * + * Returns -1 for failure, 0 otherwise (on success). + */ +extern int edit_file_perl (guestfs_h *g, const char *filename, + const char *perl_expr, + const char *backup_extension); + #endif -- 1.9.3