static int write_or_append (guestfs_h *g, const char *path, const char *content, size_t size, int append) { CLEANUP_UNLINK_FREE char *tmpfile = NULL; int fd = -1; int64_t filesize; /* If the content is small enough, use guestfs_internal_write{,_append} * since that call is more efficient. */ if (size <= 2*1024*1024) return (!append ? guestfs_internal_write : guestfs_internal_write_append) (g, path, content, size); if (guestfs_int_lazy_make_tmpdir (g) == -1) goto err; /* Write the content out to a temporary file. */ tmpfile = safe_asprintf (g, "%s/write%d", g->tmpdir, ++g->unique); fd = open (tmpfile, O_WRONLY|O_CREAT|O_NOCTTY|O_CLOEXEC, 0600); if (fd == -1) { perrorf (g, "open: %s", tmpfile); goto err; } if (full_write (fd, content, size) != size) { perrorf (g, "write: %s", tmpfile); goto err; } if (close (fd) == -1) { perrorf (g, "close: %s", tmpfile); goto err; } fd = -1; if (!append) { if (guestfs_upload (g, tmpfile, path) == -1) goto err; } else { /* XXX Should have an 'upload-append' call to make this atomic. */ filesize = guestfs_filesize (g, path); if (filesize == -1) goto err; if (guestfs_upload_offset (g, tmpfile, path, filesize) == -1) goto err; } return 0; err: if (fd >= 0) close (fd); return -1; }
static void edit (const char *filename, const char *root) { char *filename_to_free = NULL; const char *tmpdir = guestfs_tmpdir (); char tmpfile[strlen (tmpdir) + 32]; sprintf (tmpfile, "%s/virteditXXXXXX", tmpdir); int fd; char fdbuf[32]; char *upload_from = NULL; char *newname = NULL; char *backupname = NULL; /* Windows? Special handling is required. */ if (is_windows (g, root)) filename = filename_to_free = windows_path (g, root, filename); /* Download the file to a temporary. */ fd = mkstemp (tmpfile); if (fd == -1) { perror ("mkstemp"); exit (EXIT_FAILURE); } snprintf (fdbuf, sizeof fdbuf, "/dev/fd/%d", fd); if (guestfs_download (g, filename, fdbuf) == -1) goto error; if (close (fd) == -1) { perror (tmpfile); goto error; } if (!perl_expr) upload_from = edit_interactively (tmpfile); else upload_from = edit_non_interactively (tmpfile); /* We don't always need to upload: upload_from could be NULL because * the user closed the editor without changing the file. */ if (upload_from) { /* 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 (guestfs_upload (g, upload_from, newname) == -1) goto error; /* Backup or overwrite the file. */ if (backup_extension) { backupname = generate_backup_name (filename); if (guestfs_mv (g, filename, backupname) == -1) goto error; } if (guestfs_mv (g, newname, filename) == -1) goto error; } unlink (tmpfile); free (filename_to_free); free (upload_from); free (newname); free (backupname); return; error: unlink (tmpfile); exit (EXIT_FAILURE); }
int run_edit (const char *cmd, size_t argc, char *argv[]) { CLEANUP_FREE char *tmpdir = guestfs_get_tmpdir (g); CLEANUP_UNLINK_FREE char *filename = NULL; char buf[256]; const char *editor; CLEANUP_FREE char *remotefilename = NULL, *newname = NULL; struct stat oldstat, newstat; int r, fd; if (argc != 1) { fprintf (stderr, _("use '%s filename' to edit a file\n"), cmd); return -1; } /* Choose an editor. */ if (STRCASEEQ (cmd, "vi")) editor = "vi"; else if (STRCASEEQ (cmd, "emacs")) editor = "emacs -nw"; else { editor = getenv ("EDITOR"); if (editor == NULL) editor = "vi"; /* could be cruel here and choose ed(1) */ } /* Handle 'win:...' prefix. */ remotefilename = win_prefix (argv[0]); if (remotefilename == NULL) return -1; /* Download the file and write it to a temporary. */ if (asprintf (&filename, "%s/guestfishXXXXXX", tmpdir) == -1) { perror ("asprintf"); return -1; } fd = mkstemp (filename); if (fd == -1) { perror ("mkstemp"); return -1; } snprintf (buf, sizeof buf, "/dev/fd/%d", fd); if (guestfs_download (g, remotefilename, buf) == -1) { close (fd); return -1; } if (close (fd) == -1) { perror (filename); return -1; } /* Get the old stat. */ if (stat (filename, &oldstat) == -1) { perror (filename); return -1; } /* Edit it. */ /* XXX Safe? */ snprintf (buf, sizeof buf, "%s %s", editor, filename); r = system (buf); if (r != 0) { perror (buf); return -1; } /* Get the new stat. */ if (stat (filename, &newstat) == -1) { perror (filename); return -1; } /* Changed? */ if (oldstat.st_ctime == newstat.st_ctime && oldstat.st_size == newstat.st_size) return 0; /* 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 (remotefilename); if (!newname) return -1; /* Write new content. */ if (guestfs_upload (g, filename, 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, remotefilename, newname, GUESTFS_COPY_ATTRIBUTES_ALL, 1, -1) == -1) return -1; if (guestfs_mv (g, newname, remotefilename) == -1) return -1; return 0; }
static void test_virtio_serial (void) { int fd, r, eh; char tmpfile[] = "/tmp/speedtestXXXXXX"; struct sigaction sa, old_sa; if (!virtio_serial_upload && !virtio_serial_download) return; /* Create a sparse file. We could upload from /dev/zero, but we * won't get progress messages because libguestfs tests if the * source file is a regular file. */ fd = mkstemp (tmpfile); if (fd == -1) error (EXIT_FAILURE, errno, "mkstemp: %s", tmpfile); if (ftruncate (fd, TEST_SERIAL_MAX_SIZE) == -1) error (EXIT_FAILURE, errno, "ftruncate"); if (close (fd) == -1) error (EXIT_FAILURE, errno, "close"); g = guestfs_create (); if (!g) error (EXIT_FAILURE, errno, "guestfs_create"); if (guestfs_add_drive_scratch (g, INT64_C (100*1024*1024), -1) == -1) exit (EXIT_FAILURE); if (guestfs_launch (g) == -1) exit (EXIT_FAILURE); /* Make and mount a filesystem which will be used by the download test. */ if (guestfs_mkfs (g, "ext4", "/dev/sda") == -1) exit (EXIT_FAILURE); if (guestfs_mount (g, "/dev/sda", "/") == -1) exit (EXIT_FAILURE); /* Time out the upload after TEST_SERIAL_MAX_TIME seconds have passed. */ memset (&sa, 0, sizeof sa); sa.sa_handler = stop_transfer; sa.sa_flags = SA_RESTART; sigaction (SIGALRM, &sa, &old_sa); /* Get progress messages, which will tell us how much data has been * transferred. */ eh = guestfs_set_event_callback (g, progress_cb, GUESTFS_EVENT_PROGRESS, 0, NULL); if (eh == -1) exit (EXIT_FAILURE); if (virtio_serial_upload) { gettimeofday (&start, NULL); rate = -1; operation = "upload"; alarm (max_time_override > 0 ? max_time_override : TEST_SERIAL_MAX_TIME); /* For the upload test, upload the sparse file to /dev/null in the * appliance. Hopefully this is mostly testing just virtio-serial. */ guestfs_push_error_handler (g, NULL, NULL); r = guestfs_upload (g, tmpfile, "/dev/null"); alarm (0); unlink (tmpfile); guestfs_pop_error_handler (g); /* It's possible that the upload will finish before the alarm fires, * or that the upload will be stopped by the alarm. */ if (r == -1 && guestfs_last_errno (g) != EINTR) { fprintf (stderr, "%s: expecting upload command to return EINTR\n%s\n", guestfs_int_program_name, guestfs_last_error (g)); exit (EXIT_FAILURE); } if (rate == -1) { rate_error: fprintf (stderr, "%s: internal error: progress callback was not called! (r=%d, errno=%d)\n", guestfs_int_program_name, r, guestfs_last_errno (g)); exit (EXIT_FAILURE); } print_rate ("virtio-serial upload rate:", rate); } if (virtio_serial_download) { /* For the download test, download a sparse file within the * appliance to /dev/null on the host. */ if (guestfs_touch (g, "/sparse") == -1) exit (EXIT_FAILURE); if (guestfs_truncate_size (g, "/sparse", TEST_SERIAL_MAX_SIZE) == -1) exit (EXIT_FAILURE); gettimeofday (&start, NULL); rate = -1; operation = "download"; alarm (max_time_override > 0 ? max_time_override : TEST_SERIAL_MAX_TIME); guestfs_push_error_handler (g, NULL, NULL); r = guestfs_download (g, "/sparse", "/dev/null"); alarm (0); guestfs_pop_error_handler (g); if (r == -1 && guestfs_last_errno (g) != EINTR) { fprintf (stderr, "%s: expecting download command to return EINTR\n%s\n", guestfs_int_program_name, guestfs_last_error (g)); exit (EXIT_FAILURE); } if (rate == -1) goto rate_error; print_rate ("virtio-serial download rate:", rate); } if (guestfs_shutdown (g) == -1) exit (EXIT_FAILURE); guestfs_close (g); /* Restore SIGALRM signal handler. */ sigaction (SIGALRM, &old_sa, NULL); }