[Spice-devel] [PATCH spice-vdagent v4] file-xfer: Check free space before file transfer

Jakub Janků janku.jakub.jj at gmail.com
Sun May 21 13:19:40 UTC 2017


Add function get_free_space_available that retrieves amount of free
space in the given directory. The statvfs may fail even when there's
enough free space (e.g. when not supported by system), in this case
return G_MAXUINT64 so that the transfer isn't terminated groundlessly.

When the file is too big, send VDAgentFileXferStatusMessage with
result VD_AGENT_FILE_XFER_STATUS_NOT_ENOUGH_SPACE and amount of free
space available, if the result isn't supported by client, send
VD_AGENT_FILE_XFER_STATUS_ERROR. Client then terminates the transfer.

Change send_file_xfer_status() to support sending file xfer status
messages with additional (error) data, can be used for reporting
more file xfer errors.
---
 src/vdagent/file-xfers.c | 39 ++++++++++++++++++++++++++
 src/vdagentd/vdagentd.c  | 71 ++++++++++++++++++++++++++++++++----------------
 2 files changed, 86 insertions(+), 24 deletions(-)

diff --git a/src/vdagent/file-xfers.c b/src/vdagent/file-xfers.c
index b3937a4..3510760 100644
--- a/src/vdagent/file-xfers.c
+++ b/src/vdagent/file-xfers.c
@@ -32,6 +32,7 @@
 #include <fcntl.h>
 #include <errno.h>
 #include <sys/stat.h>
+#include <sys/statvfs.h>
 #include <sys/types.h>
 #include <spice/vd_agent.h>
 #include <glib.h>
@@ -168,6 +169,17 @@ error:
     return NULL;
 }
 
+static uint64_t get_free_space_available(const char *path)
+{
+    struct statvfs stat;
+    if (statvfs(path, &stat) != 0) {
+        syslog(LOG_WARNING, "file-xfer: failed to get free space, statvfs error: %s",
+               strerror(errno));
+        return G_MAXUINT64;
+    }
+    return stat.f_bsize * stat.f_bavail;
+}
+
 void vdagent_file_xfers_start(struct vdagent_file_xfers *xfers,
     VDAgentFileXferStartMessage *msg)
 {
@@ -175,6 +187,7 @@ void vdagent_file_xfers_start(struct vdagent_file_xfers *xfers,
     char *dir = NULL, *path = NULL, *file_path = NULL;
     struct stat st;
     int i;
+    uint64_t free_space;
 
     g_return_if_fail(xfers != NULL);
 
@@ -193,6 +206,32 @@ void vdagent_file_xfers_start(struct vdagent_file_xfers *xfers,
 
     file_path = g_build_filename(xfers->save_dir, task->file_name, NULL);
 
+    free_space = get_free_space_available(xfers->save_dir);
+    if (task->file_size > free_space) {
+        if (GLIB_CHECK_VERSION(2, 30, 0)) {
+            gchar *free_space_str = g_format_size(free_space);
+            gchar *file_size_str = g_format_size(task->file_size);
+            syslog(LOG_ERR, "file-xfer: not enough free space (%s to copy, %s free)",
+                file_size_str, free_space_str);
+            g_free(free_space_str);
+            g_free(file_size_str);
+        } else {
+            syslog(LOG_ERR, "file-xfer: not enough free space (%lu B to copy, %lu B free)",
+                task->file_size, free_space);
+        }
+
+        udscs_write(xfers->vdagentd,
+                    VDAGENTD_FILE_XFER_STATUS,
+                    msg->id,
+                    VD_AGENT_FILE_XFER_STATUS_NOT_ENOUGH_SPACE,
+                    (uint8_t *)&free_space,
+                    sizeof(free_space));
+        vdagent_file_xfer_task_free(task);
+        g_free(file_path);
+        g_free(dir);
+        return;
+    }
+
     dir = g_path_get_dirname(file_path);
     if (g_mkdir_with_parents(dir, S_IRWXU) == -1) {
         syslog(LOG_ERR, "file-xfer: Failed to create dir %s", dir);
diff --git a/src/vdagentd/vdagentd.c b/src/vdagentd/vdagentd.c
index f3ac606..7664142 100644
--- a/src/vdagentd/vdagentd.c
+++ b/src/vdagentd/vdagentd.c
@@ -301,20 +301,38 @@ static void do_client_clipboard(struct vdagent_virtio_port *vport,
                 data, size);
 }
 
-/* To be used by vdagentd for failures in file-xfer such as when file-xfer was
- * cancelled or an error happened */
+/* Send file-xfer status to the client. In the case status is an error,
+ * optional data for the client and log message may be specified. */
 static void send_file_xfer_status(struct vdagent_virtio_port *vport,
-                                  const char *msg, uint32_t id, uint32_t xfer_status)
+                                  const char *msg, uint32_t id, uint32_t xfer_status,
+                                  uint8_t *data, uint32_t data_size)
 {
-    VDAgentFileXferStatusMessage status = {
-        .id = GUINT32_TO_LE(id),
-        .result = GUINT32_TO_LE(xfer_status),
-    };
-    syslog(LOG_WARNING, msg, id);
+    VDAgentFileXferStatusMessage *status;
+
+    /* Replace new detailed errors with older generic VD_AGENT_FILE_XFER_STATUS_ERROR
+     * when not supported by client */
+    if (xfer_status > VD_AGENT_FILE_XFER_STATUS_SUCCESS &&
+        !VD_AGENT_HAS_CAPABILITY(capabilities, capabilities_size,
+                                VD_AGENT_CAP_FILE_XFER_DETAILED_ERRORS)) {
+        xfer_status = VD_AGENT_FILE_XFER_STATUS_ERROR;
+        data_size = 0;
+    }
+
+    status = malloc(sizeof(*status) + data_size);
+    status->id = GUINT32_TO_LE(id);
+    status->result = GUINT32_TO_LE(xfer_status);
+    if (data)
+        memcpy(status->data, data, data_size);
+
+    if (msg)
+        syslog(LOG_WARNING, "file-xfer %u: %s", id, msg);
+
     if (vport)
         vdagent_virtio_port_write(vport, VDP_CLIENT_PORT,
                                   VD_AGENT_FILE_XFER_STATUS, 0,
-                                  (uint8_t *)&status, sizeof(status));
+                                  (uint8_t *)status, sizeof(*status) + data_size);
+
+    free(status);
 }
 
 static void do_client_file_xfer(struct vdagent_virtio_port *vport,
@@ -330,15 +348,15 @@ static void do_client_file_xfer(struct vdagent_virtio_port *vport,
         if (!active_session_conn) {
             send_file_xfer_status(vport,
                "Could not find an agent connection belonging to the "
-               "active session, cancelling client file-xfer request %u",
-               s->id, VD_AGENT_FILE_XFER_STATUS_CANCELLED);
+               "active session, cancelling file-xfer",
+               s->id, VD_AGENT_FILE_XFER_STATUS_CANCELLED, NULL, 0);
             return;
         } else if (session_info_session_is_locked(session_info)) {
             syslog(LOG_DEBUG, "Session is locked, skipping file-xfer-start");
             send_file_xfer_status(vport,
                "User's session is locked and cannot start file transfer. "
-               "Cancelling client file-xfer request %u",
-               s->id, VD_AGENT_FILE_XFER_STATUS_ERROR);
+               "Cancelling file-xfer",
+               s->id, VD_AGENT_FILE_XFER_STATUS_ERROR, NULL, 0);
             return;
         }
         udscs_write(active_session_conn, VDAGENTD_FILE_XFER_START, 0, 0,
@@ -808,9 +826,9 @@ static gboolean remove_active_xfers(gpointer key, gpointer value, gpointer conn)
 {
     if (value == conn) {
         send_file_xfer_status(virtio_port,
-                              "Agent disc; cancelling file-xfer %u",
+                              "Agent disc; cancelling file-xfer",
                               GPOINTER_TO_UINT(key),
-                              VD_AGENT_FILE_XFER_STATUS_CANCELLED);
+                              VD_AGENT_FILE_XFER_STATUS_CANCELLED, NULL, 0);
         return 1;
     } else
         return 0;
@@ -903,17 +921,22 @@ static void agent_read_complete(struct udscs_connection **connp,
         }
         break;
     case VDAGENTD_FILE_XFER_STATUS:{
-        VDAgentFileXferStatusMessage status;
-        status.id = GUINT32_TO_LE(header->arg1);
-        status.result = GUINT32_TO_LE(header->arg2);
-        vdagent_virtio_port_write(virtio_port, VDP_CLIENT_PORT,
-                                  VD_AGENT_FILE_XFER_STATUS, 0,
-                                  (uint8_t *)&status, sizeof(status));
-        if (status.result == VD_AGENT_FILE_XFER_STATUS_CAN_SEND_DATA)
-            g_hash_table_insert(active_xfers, GUINT_TO_POINTER(status.id),
+        /* header->arg1 = file xfer task id, header->arg2 = file xfer status */
+        switch (header->arg2) {
+            case VD_AGENT_FILE_XFER_STATUS_NOT_ENOUGH_SPACE:
+                send_file_xfer_status(virtio_port, "Not enough free space. Cancelling file-xfer",
+                                      header->arg1, header->arg2, data, sizeof(uint64_t));
+                break;
+            default:
+                send_file_xfer_status(virtio_port, NULL, header->arg1, header->arg2, NULL, 0);
+        }
+
+        if (header->arg2 == VD_AGENT_FILE_XFER_STATUS_CAN_SEND_DATA)
+            g_hash_table_insert(active_xfers, GUINT_TO_POINTER(GUINT32_TO_LE(header->arg1)),
                                 *connp);
         else
-            g_hash_table_remove(active_xfers, GUINT_TO_POINTER(status.id));
+            g_hash_table_remove(active_xfers, GUINT_TO_POINTER(GUINT32_TO_LE(header->arg1)));
+
         break;
     }
 
-- 
2.9.4



More information about the Spice-devel mailing list