#include <stdio.h>
#include <stdlib.h>
+#include <stdint.h>
#include <string.h>
#include <fcntl.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
-#include "../src/guestfs_protocol.h"
+#include "guestfs_protocol.h"
#include "daemon.h"
#include "actions.h"
+struct write_cb_data {
+ int fd; /* file descriptor */
+ uint64_t written; /* bytes written so far */
+};
+
static int
-write_cb (void *fd_ptr, const void *buf, int len)
+write_cb (void *data_vp, const void *buf, size_t len)
{
- int fd = *(int *)fd_ptr;
- return xwrite (fd, buf, len);
+ struct write_cb_data *data = data_vp;
+ int r;
+
+ r = xwrite (data->fd, buf, len);
+ if (r == -1)
+ return -1;
+
+ data->written += len;
+
+ if (progress_hint > 0)
+ notify_progress (data->written, progress_hint);
+
+ return 0;
}
/* Has one FileIn parameter. */
-int
-do_upload (char *filename)
+static int
+upload (const char *filename, int flags, int64_t offset)
{
- int err, fd, r, is_dev;
+ struct write_cb_data data = { .written = 0 };
+ int err, r, is_dev;
- is_dev = strncmp (filename, "/dev/", 5) == 0;
- if (!is_dev) {
- if (!root_mounted || filename[0] != '/') {
- cancel_receive ();
- reply_with_error ("upload: root must be mounted and path must be absolute");
- return -1;
- }
- }
+ is_dev = STRPREFIX (filename, "/dev/");
if (!is_dev) CHROOT_IN;
- fd = open (filename, O_WRONLY|O_CREAT|O_TRUNC|O_NOCTTY, 0666);
+ data.fd = open (filename, flags, 0666);
if (!is_dev) CHROOT_OUT;
- if (fd == -1) {
+ if (data.fd == -1) {
err = errno;
- cancel_receive ();
+ r = cancel_receive ();
errno = err;
reply_with_perror ("%s", filename);
return -1;
}
- r = receive_file (write_cb, &fd);
+ if (offset) {
+ if (lseek (data.fd, offset, SEEK_SET) == -1) {
+ err = errno;
+ r = cancel_receive ();
+ errno = err;
+ reply_with_perror ("lseek: %s", filename);
+ return -1;
+ }
+ }
+
+ r = receive_file (write_cb, &data.fd);
if (r == -1) { /* write error */
err = errno;
- cancel_receive ();
+ r = cancel_receive ();
errno = err;
- reply_with_perror ("write: %s", filename);
- close (fd);
+ reply_with_error ("write error: %s", filename);
+ close (data.fd);
return -1;
}
if (r == -2) { /* cancellation from library */
- close (fd);
- /* Do NOT send any error. */
+ /* This error is ignored by the library since it initiated the
+ * cancel. Nevertheless we must send an error reply here.
+ */
+ reply_with_error ("file upload cancelled");
+ close (data.fd);
return -1;
}
- if (close (fd) == -1) {
+ if (close (data.fd) == -1) {
err = errno;
- cancel_receive ();
+ if (r == -1) /* if r == 0, file transfer ended already */
+ r = cancel_receive ();
errno = err;
reply_with_perror ("close: %s", filename);
return -1;
return 0;
}
+/* Has one FileIn parameter. */
+int
+do_upload (const char *filename)
+{
+ return upload (filename, O_WRONLY|O_CREAT|O_TRUNC|O_NOCTTY, 0);
+}
+
+/* Has one FileIn parameter. */
+int
+do_upload_offset (const char *filename, int64_t offset)
+{
+ if (offset < 0) {
+ reply_with_perror ("%s: offset in file is negative", filename);
+ return -1;
+ }
+
+ return upload (filename, O_WRONLY|O_CREAT|O_NOCTTY, offset);
+}
+
/* Has one FileOut parameter. */
int
-do_download (char *filename)
+do_download (const char *filename)
{
int fd, r, is_dev;
char buf[GUESTFS_MAX_CHUNK_SIZE];
- NEED_ROOT_OR_IS_DEVICE (filename, return -1);
-
- is_dev = strncmp (filename, "/dev/", 5) == 0;
+ is_dev = STRPREFIX (filename, "/dev/");
if (!is_dev) CHROOT_IN;
fd = open (filename, O_RDONLY);
return -1;
}
+ /* Calculate the size of the file or device for notification messages. */
+ uint64_t total, sent = 0;
+ if (!is_dev) {
+ struct stat statbuf;
+ if (fstat (fd, &statbuf) == -1) {
+ reply_with_perror ("%s", filename);
+ close (fd);
+ return -1;
+ }
+ total = statbuf.st_size;
+ } else {
+ int64_t size = do_blockdev_getsize64 (filename);
+ if (size == -1) {
+ /* do_blockdev_getsize64 has already sent a reply. */
+ close (fd);
+ return -1;
+ }
+ total = (uint64_t) size;
+ }
+
/* Now we must send the reply message, before the file contents. After
* this there is no opportunity in the protocol to send any error
* message back. Instead we can only cancel the transfer.
close (fd);
return -1;
}
+
+ sent += r;
+ notify_progress (sent, total);
}
if (r == -1) {
return -1;
}
- send_file_end (0); /* Normal end of file. */
+ if (send_file_end (0)) /* Normal end of file. */
+ return -1;
+
+ return 0;
+}
+
+/* Has one FileOut parameter. */
+int
+do_download_offset (const char *filename, int64_t offset, int64_t size)
+{
+ int fd, r, is_dev;
+ char buf[GUESTFS_MAX_CHUNK_SIZE];
+
+ if (offset < 0) {
+ reply_with_perror ("%s: offset in file is negative", filename);
+ return -1;
+ }
+
+ if (size < 0) {
+ reply_with_perror ("%s: size is negative", filename);
+ return -1;
+ }
+ uint64_t usize = (uint64_t) size;
+
+ is_dev = STRPREFIX (filename, "/dev/");
+
+ if (!is_dev) CHROOT_IN;
+ fd = open (filename, O_RDONLY);
+ if (!is_dev) CHROOT_OUT;
+ if (fd == -1) {
+ reply_with_perror ("%s", filename);
+ return -1;
+ }
+
+ if (offset) {
+ if (lseek (fd, offset, SEEK_SET) == -1) {
+ reply_with_perror ("lseek: %s", filename);
+ return -1;
+ }
+ }
+
+ uint64_t total = usize, sent = 0;
+
+ /* Now we must send the reply message, before the file contents. After
+ * this there is no opportunity in the protocol to send any error
+ * message back. Instead we can only cancel the transfer.
+ */
+ reply (NULL, NULL);
+
+ while (usize > 0) {
+ r = read (fd, buf, usize > sizeof buf ? sizeof buf : usize);
+ if (r == -1) {
+ perror (filename);
+ send_file_end (1); /* Cancel. */
+ close (fd);
+ return -1;
+ }
+
+ if (r == 0)
+ /* The documentation leaves this case undefined. Currently we
+ * just read fewer bytes than requested.
+ */
+ break;
+
+ if (send_file_write (buf, r) < 0) {
+ close (fd);
+ return -1;
+ }
+
+ sent += r;
+ usize -= r;
+ notify_progress (sent, total);
+ }
+
+ if (close (fd) == -1) {
+ perror (filename);
+ send_file_end (1); /* Cancel. */
+ return -1;
+ }
+
+ if (send_file_end (0)) /* Normal end of file. */
+ return -1;
+
return 0;
}