[gvfs] sftp: Copy file timestamps on push/pull



commit 8e75d14569f90b1832a06b2f9d386e2030bd67ae
Author: Maxim Mikityanskiy <maxtram95 gmail com>
Date:   Sat Apr 18 15:31:49 2020 +0300

    sftp: Copy file timestamps on push/pull
    
    Copy and move operations preserve file attributes (such as modification
    time) in most of existing scenarios: local copy/move, remote copy/move,
    file_copy_fallback in glib. However, one case remains special: copy/move
    between local and remote (gvfs) locations. It's implemented by push and
    pull operations in backends, which don't attempt to preserve the usual
    attributes (e.g., mtime and atime).
    
    This commit implements the missing piece of functionality in sftp
    backend. Modification time is preserved on copy and move, and access
    time is preserved on move only, complying to the settable attributes
    list of sftp backend.
    
    Signed-off-by: Maxim Mikityanskiy <maxtram95 gmail com>

 daemon/gvfsbackendsftp.c | 112 +++++++++++++++++++++++++++++++++++++----------
 1 file changed, 89 insertions(+), 23 deletions(-)
---
diff --git a/daemon/gvfsbackendsftp.c b/daemon/gvfsbackendsftp.c
index cde9cd85..1bb4a673 100644
--- a/daemon/gvfsbackendsftp.c
+++ b/daemon/gvfsbackendsftp.c
@@ -5491,6 +5491,8 @@ typedef struct {
   /* fstat information */
   goffset size;
   guint32 permissions;
+  guint64 mtime;
+  guint64 atime;
 
   /* state */
   goffset offset;
@@ -5640,8 +5642,15 @@ push_close_deleted_file (GVfsBackendSftp *backend,
 }
 
 static void
-push_close_delete_or_succeed (SftpPushHandle *handle)
+push_close_delete_or_succeed (GVfsBackendSftp *backend,
+                              int reply_type,
+                              GDataInputStream *reply,
+                              guint32 len,
+                              GVfsJob *job,
+                              gpointer user_data)
 {
+  SftpPushHandle *handle = user_data;
+
   if (handle->tempname)
     {
       /* If we wrote to a temp file, do delete then rename. */
@@ -5662,15 +5671,52 @@ push_close_delete_or_succeed (SftpPushHandle *handle)
 }
 
 static void
-push_close_restore_permissions (GVfsBackendSftp *backend,
-                                int reply_type,
-                                GDataInputStream *reply,
-                                guint32 len,
-                                GVfsJob *job,
-                                gpointer user_data)
+push_close_restore_permissions (SftpPushHandle *handle)
+{
+  gboolean default_perms = (handle->op_job->flags & G_FILE_COPY_TARGET_DEFAULT_PERMS);
+  guint32 flags = SSH_FILEXFER_ATTR_ACMODTIME;
+
+  if (!default_perms)
+    flags |= SSH_FILEXFER_ATTR_PERMISSIONS;
+
+  /* Restore the source file's permissions and timestamps. */
+  GDataOutputStream *command = new_command_stream (handle->backend, SSH_FXP_SETSTAT);
+  put_string (command, handle->tempname ? handle->tempname : handle->op_job->destination);
+  g_data_output_stream_put_uint32 (command, flags, NULL, NULL);
+  if (!default_perms)
+    g_data_output_stream_put_uint32 (command, handle->permissions, NULL, NULL);
+  g_data_output_stream_put_uint32 (command, handle->atime, NULL, NULL);
+  g_data_output_stream_put_uint32 (command, handle->mtime, NULL, NULL);
+  queue_command_stream_and_free (&handle->backend->command_connection, command,
+                                 push_close_delete_or_succeed,
+                                 handle->job, handle);
+}
+
+static void
+push_close_stat_reply (GVfsBackendSftp *backend,
+                       int reply_type,
+                       GDataInputStream *reply,
+                       guint32 len,
+                       GVfsJob *job,
+                       gpointer user_data)
 {
-  /* We don't care if setting the permissions succeeded or not. */
-  push_close_delete_or_succeed (user_data);
+  SftpPushHandle *handle = user_data;
+
+  if (reply_type == SSH_FXP_ATTRS)
+    {
+      GFileInfo *info = g_file_info_new ();
+
+      parse_attributes (backend, info, NULL, reply, NULL);
+
+      /* Don't fail on error, but fall back to the local atime
+       * (assigned in push_source_fstat_cb). */
+      if (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_TIME_ACCESS))
+        handle->atime = g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_TIME_ACCESS);
+
+      g_object_unref (info);
+    }
+
+  push_close_restore_permissions (handle);
 }
 
 static void
@@ -5688,19 +5734,19 @@ push_close_write_reply (GVfsBackendSftp *backend,
       guint32 code = read_status_code (reply);
       if (code == SSH_FX_OK)
         {
-          if (handle->op_job->flags & G_FILE_COPY_TARGET_DEFAULT_PERMS)
-            push_close_delete_or_succeed (handle);
-          else
+          /* Atime is COPY_WHEN_MOVED, but not COPY_WITH_FILE. */
+          if (!handle->op_job->remove_source &&
+              !(handle->op_job->flags & G_FILE_COPY_ALL_METADATA))
             {
-              /* Restore the source file's permissions. */
-              GDataOutputStream *command = new_command_stream (backend, SSH_FXP_SETSTAT);
-              put_string (command, handle->tempname ? handle->tempname : handle->op_job->destination);
-              g_data_output_stream_put_uint32 (command, SSH_FILEXFER_ATTR_PERMISSIONS, NULL, NULL);
-              g_data_output_stream_put_uint32 (command, handle->permissions, NULL, NULL);
+              GDataOutputStream *command = new_command_stream (backend, SSH_FXP_LSTAT);
+              put_string (command, handle->op_job->destination);
               queue_command_stream_and_free (&backend->command_connection, command,
-                                             push_close_restore_permissions,
+                                             push_close_stat_reply,
                                              job, handle);
+              return;
             }
+
+          push_close_restore_permissions (handle);
           return;
         }
       else
@@ -6070,6 +6116,8 @@ push_source_fstat_cb (GObject *source, GAsyncResult *res, gpointer user_data)
     {
       handle->permissions = g_file_info_get_attribute_uint32 (info, G_FILE_ATTRIBUTE_UNIX_MODE) & 0777;
       handle->size = g_file_info_get_size (info);
+      handle->mtime = g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_TIME_MODIFIED);
+      handle->atime = g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_TIME_ACCESS);
 
       command = new_command_stream (handle->backend, SSH_FXP_OPEN);
       put_string (command, handle->op_job->destination);
@@ -6102,7 +6150,9 @@ push_source_open_cb (GObject *source, GAsyncResult *res, gpointer user_data)
 
       g_file_input_stream_query_info_async (fin,
                                             G_FILE_ATTRIBUTE_STANDARD_SIZE ","
-                                            G_FILE_ATTRIBUTE_UNIX_MODE,
+                                            G_FILE_ATTRIBUTE_UNIX_MODE ","
+                                            G_FILE_ATTRIBUTE_TIME_MODIFIED ","
+                                            G_FILE_ATTRIBUTE_TIME_ACCESS,
                                             0, NULL,
                                             push_source_fstat_cb, handle);
     }
@@ -6211,6 +6261,8 @@ typedef struct {
   /* fstat information */
   goffset size;
   guint32 mode;
+  guint64 mtime;
+  guint64 atime;
 
   /* state */
   goffset offset;
@@ -6317,12 +6369,22 @@ pull_close_cb (GObject *source, GAsyncResult *res, gpointer user_data)
     {
       g_vfs_job_progress_callback (handle->n_written, handle->n_written, handle->job);
 
-      if (handle->size >= 0 && !(handle->op_job->flags & G_FILE_COPY_TARGET_DEFAULT_PERMS))
+      if (handle->size >= 0)
         {
           GFileInfo *info = g_file_info_new ();
-          g_file_info_set_attribute_uint32 (info,
-                                            G_FILE_ATTRIBUTE_UNIX_MODE,
-                                            handle->mode);
+          if (!(handle->op_job->flags & G_FILE_COPY_TARGET_DEFAULT_PERMS))
+            g_file_info_set_attribute_uint32 (info,
+                                              G_FILE_ATTRIBUTE_UNIX_MODE,
+                                              handle->mode);
+          g_file_info_set_attribute_uint64 (info,
+                                            G_FILE_ATTRIBUTE_TIME_MODIFIED,
+                                            handle->mtime);
+          /* Atime is COPY_WHEN_MOVED, but not COPY_WITH_FILE. */
+          if (handle->op_job->remove_source ||
+              (handle->op_job->flags & G_FILE_COPY_ALL_METADATA))
+            g_file_info_set_attribute_uint64 (info,
+                                              G_FILE_ATTRIBUTE_TIME_ACCESS,
+                                              handle->atime);
           g_file_set_attributes_async (handle->dest,
                                        info,
                                        G_FILE_QUERY_INFO_NONE,
@@ -6573,6 +6635,10 @@ pull_fstat_reply (GVfsBackendSftp *backend,
       handle->size = g_file_info_get_size (info);
       handle->mode = g_file_info_get_attribute_uint32 (info,
                                                        G_FILE_ATTRIBUTE_UNIX_MODE);
+      handle->mtime = g_file_info_get_attribute_uint64 (info,
+                                                        G_FILE_ATTRIBUTE_TIME_MODIFIED);
+      handle->atime = g_file_info_get_attribute_uint64 (info,
+                                                        G_FILE_ATTRIBUTE_TIME_ACCESS);
       g_object_unref (info);
     }
   else


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]