summaryrefslogtreecommitdiff
path: root/gnome-2-24/daemon/gvfsbackendsftp.c
diff options
context:
space:
mode:
Diffstat (limited to 'gnome-2-24/daemon/gvfsbackendsftp.c')
-rw-r--r--gnome-2-24/daemon/gvfsbackendsftp.c3913
1 files changed, 3913 insertions, 0 deletions
diff --git a/gnome-2-24/daemon/gvfsbackendsftp.c b/gnome-2-24/daemon/gvfsbackendsftp.c
new file mode 100644
index 00000000..3527ebe7
--- /dev/null
+++ b/gnome-2-24/daemon/gvfsbackendsftp.c
@@ -0,0 +1,3913 @@
+/* GIO - GLib Input, Output and Streaming Library
+ *
+ * Copyright (C) 2006-2007 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Alexander Larsson <alexl@redhat.com>
+ */
+
+
+#include <config.h>
+
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <errno.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <string.h>
+
+#include <glib/gstdio.h>
+#include <glib/gi18n.h>
+#include <gio/gio.h>
+#include <gio/gunixinputstream.h>
+#include <gio/gunixoutputstream.h>
+
+#include "gvfsbackendsftp.h"
+#include "gvfsjobopenforread.h"
+#include "gvfsjobread.h"
+#include "gvfsjobseekread.h"
+#include "gvfsjobopenforwrite.h"
+#include "gvfsjobwrite.h"
+#include "gvfsjobclosewrite.h"
+#include "gvfsjobseekwrite.h"
+#include "gvfsjobsetdisplayname.h"
+#include "gvfsjobqueryinfo.h"
+#include "gvfsjobmove.h"
+#include "gvfsjobdelete.h"
+#include "gvfsjobqueryfsinfo.h"
+#include "gvfsjobqueryattributes.h"
+#include "gvfsjobenumerate.h"
+#include "gvfsdaemonprotocol.h"
+#include "gvfskeyring.h"
+#include "sftp.h"
+#include "pty_open.h"
+
+/* TODO for sftp:
+ * Implement can_delete & can_rename
+ * fstat
+ */
+
+#ifdef HAVE_GRANTPT
+/* We only use this on systems with unix98 ptys */
+#define USE_PTY 1
+#endif
+
+static GQuark id_q;
+
+typedef enum {
+ SFTP_VENDOR_INVALID = 0,
+ SFTP_VENDOR_OPENSSH,
+ SFTP_VENDOR_SSH
+} SFTPClientVendor;
+
+typedef struct _MultiReply MultiReply;
+
+typedef void (*ReplyCallback) (GVfsBackendSftp *backend,
+ int reply_type,
+ GDataInputStream *reply,
+ guint32 len,
+ GVfsJob *job,
+ gpointer user_data);
+
+typedef void (*MultiReplyCallback) (GVfsBackendSftp *backend,
+ MultiReply *replies,
+ int n_replies,
+ GVfsJob *job,
+ gpointer user_data);
+
+
+typedef struct {
+ MultiReply *replies;
+ int n_replies;
+ int n_outstanding;
+ gpointer user_data;
+ MultiReplyCallback callback;
+} MultiRequest;
+
+struct _MultiReply {
+ int type;
+ GDataInputStream *data;
+ guint32 data_len;
+
+ MultiRequest *request;
+};
+
+
+
+typedef struct {
+ guchar *data;
+ gsize size;
+} DataBuffer;
+
+typedef struct {
+ DataBuffer *raw_handle;
+ goffset offset;
+ char *filename;
+ char *tempname;
+ guint32 permissions;
+ gboolean make_backup;
+} SftpHandle;
+
+
+typedef struct {
+ ReplyCallback callback;
+ GVfsJob *job;
+ gpointer user_data;
+} ExpectedReply;
+
+struct _GVfsBackendSftp
+{
+ GVfsBackend parent_instance;
+
+ SFTPClientVendor client_vendor;
+ char *host;
+ int port;
+ gboolean user_specified;
+ char *user;
+ char *tmp_password;
+
+ guint32 my_uid;
+ guint32 my_gid;
+
+ int protocol_version;
+
+ GOutputStream *command_stream;
+ GInputStream *reply_stream;
+ GDataInputStream *error_stream;
+
+ guint32 current_id;
+
+ /* Output Queue */
+
+ gsize command_bytes_written;
+ GList *command_queue;
+
+ /* Reply reading: */
+ GHashTable *expected_replies;
+ guint32 reply_size;
+ guint32 reply_size_read;
+ guint8 *reply;
+
+ GMountSource *mount_source; /* Only used/set during mount */
+ int mount_try;
+ gboolean mount_try_again;
+};
+
+static void parse_attributes (GVfsBackendSftp *backend,
+ GFileInfo *info,
+ const char *basename,
+ GDataInputStream *reply,
+ GFileAttributeMatcher *attribute_matcher);
+
+
+G_DEFINE_TYPE (GVfsBackendSftp, g_vfs_backend_sftp, G_VFS_TYPE_BACKEND)
+
+static void
+data_buffer_free (DataBuffer *buffer)
+{
+ if (buffer)
+ {
+ g_free (buffer->data);
+ g_slice_free (DataBuffer, buffer);
+ }
+}
+
+static void
+make_fd_nonblocking (int fd)
+{
+ fcntl (fd, F_SETFL, O_NONBLOCK | fcntl (fd, F_GETFL));
+}
+
+static SFTPClientVendor
+get_sftp_client_vendor (void)
+{
+ char *ssh_stderr;
+ char *args[3];
+ gint ssh_exitcode;
+ SFTPClientVendor res = SFTP_VENDOR_INVALID;
+
+ args[0] = g_strdup (SSH_PROGRAM);
+ args[1] = g_strdup ("-V");
+ args[2] = NULL;
+ if (g_spawn_sync (NULL, args, NULL,
+ G_SPAWN_SEARCH_PATH | G_SPAWN_STDOUT_TO_DEV_NULL,
+ NULL, NULL,
+ NULL, &ssh_stderr,
+ &ssh_exitcode, NULL))
+ {
+ if (ssh_stderr == NULL)
+ res = SFTP_VENDOR_INVALID;
+ else if ((strstr (ssh_stderr, "OpenSSH") != NULL) ||
+ (strstr (ssh_stderr, "Sun_SSH") != NULL))
+ res = SFTP_VENDOR_OPENSSH;
+ else if (strstr (ssh_stderr, "SSH Secure Shell") != NULL)
+ res = SFTP_VENDOR_SSH;
+ else
+ res = SFTP_VENDOR_INVALID;
+ }
+
+ g_free (ssh_stderr);
+ g_free (args[0]);
+ g_free (args[1]);
+
+ return res;
+}
+
+static void
+g_vfs_backend_sftp_finalize (GObject *object)
+{
+ GVfsBackendSftp *backend;
+
+ backend = G_VFS_BACKEND_SFTP (object);
+
+ g_hash_table_destroy (backend->expected_replies);
+
+ if (backend->command_stream)
+ g_object_unref (backend->command_stream);
+
+ if (backend->reply_stream)
+ g_object_unref (backend->reply_stream);
+
+ if (backend->error_stream)
+ g_object_unref (backend->error_stream);
+
+ if (G_OBJECT_CLASS (g_vfs_backend_sftp_parent_class)->finalize)
+ (*G_OBJECT_CLASS (g_vfs_backend_sftp_parent_class)->finalize) (object);
+}
+
+static void
+expected_reply_free (ExpectedReply *reply)
+{
+ g_object_unref (reply->job);
+ g_slice_free (ExpectedReply, reply);
+}
+
+static void
+g_vfs_backend_sftp_init (GVfsBackendSftp *backend)
+{
+ backend->expected_replies = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify)expected_reply_free);
+}
+
+static void
+look_for_stderr_errors (GVfsBackend *backend, GError **error)
+{
+ GVfsBackendSftp *op_backend = G_VFS_BACKEND_SFTP (backend);
+ char *line;
+
+ while (1)
+ {
+ line = g_data_input_stream_read_line (op_backend->error_stream, NULL, NULL, NULL);
+
+ if (line == NULL)
+ {
+ /* Error (real or WOULDBLOCK) or EOF */
+ g_set_error_literal (error,
+ G_IO_ERROR, G_IO_ERROR_FAILED,
+ _("ssh program unexpectedly exited"));
+ return;
+ }
+
+ if (strstr (line, "Permission denied") != NULL)
+ {
+ g_set_error_literal (error,
+ G_IO_ERROR, G_IO_ERROR_PERMISSION_DENIED,
+ _("Permission denied"));
+ return;
+ }
+ else if (strstr (line, "Name or service not known") != NULL)
+ {
+ g_set_error_literal (error,
+ G_IO_ERROR, G_IO_ERROR_HOST_NOT_FOUND,
+ _("Hostname not known"));
+ return;
+ }
+ else if (strstr (line, "No route to host") != NULL)
+ {
+ g_set_error_literal (error,
+ G_IO_ERROR, G_IO_ERROR_HOST_NOT_FOUND,
+ _("No route to host"));
+ return;
+ }
+ else if (strstr (line, "Connection refused") != NULL)
+ {
+ g_set_error_literal (error,
+ G_IO_ERROR, G_IO_ERROR_PERMISSION_DENIED,
+ _("Connection refused by server"));
+ return;
+ }
+ else if (strstr (line, "Host key verification failed") != NULL)
+ {
+ g_set_error_literal (error,
+ G_IO_ERROR, G_IO_ERROR_FAILED,
+ _("Host key verification failed"));
+ return;
+ }
+
+ g_free (line);
+ }
+}
+
+static char **
+setup_ssh_commandline (GVfsBackend *backend)
+{
+ GVfsBackendSftp *op_backend = G_VFS_BACKEND_SFTP (backend);
+ guint last_arg;
+ gchar **args;
+
+ args = g_new0 (gchar *, 20); /* 20 is enought for now, bump size if code below changes */
+
+ /* Fill in the first few args */
+ last_arg = 0;
+ args[last_arg++] = g_strdup (SSH_PROGRAM);
+
+ if (op_backend->client_vendor == SFTP_VENDOR_OPENSSH)
+ {
+ args[last_arg++] = g_strdup ("-oForwardX11 no");
+ args[last_arg++] = g_strdup ("-oForwardAgent no");
+ args[last_arg++] = g_strdup ("-oClearAllForwardings yes");
+ args[last_arg++] = g_strdup ("-oProtocol 2");
+ args[last_arg++] = g_strdup ("-oNoHostAuthenticationForLocalhost yes");
+#ifndef USE_PTY
+ args[last_arg++] = g_strdup ("-oBatchMode yes");
+#endif
+
+ }
+ else if (op_backend->client_vendor == SFTP_VENDOR_SSH)
+ args[last_arg++] = g_strdup ("-x");
+
+ if (op_backend->port != -1)
+ {
+ args[last_arg++] = g_strdup ("-p");
+ args[last_arg++] = g_strdup_printf ("%d", op_backend->port);
+ }
+
+
+ args[last_arg++] = g_strdup ("-l");
+ args[last_arg++] = g_strdup (op_backend->user);
+
+ args[last_arg++] = g_strdup ("-s");
+
+ if (op_backend->client_vendor == SFTP_VENDOR_SSH)
+ {
+ args[last_arg++] = g_strdup ("sftp");
+ args[last_arg++] = g_strdup (op_backend->host);
+ }
+ else
+ {
+ args[last_arg++] = g_strdup (op_backend->host);
+ args[last_arg++] = g_strdup ("sftp");
+ }
+
+ args[last_arg++] = NULL;
+
+ return args;
+}
+
+static gboolean
+spawn_ssh (GVfsBackend *backend,
+ char *args[],
+ pid_t *pid,
+ int *tty_fd,
+ int *stdin_fd,
+ int *stdout_fd,
+ int *stderr_fd,
+ GError **error)
+{
+#ifdef USE_PTY
+ *tty_fd = pty_open(pid, PTY_REAP_CHILD, NULL,
+ args[0], args, NULL,
+ 300, 300,
+ stdin_fd, stdout_fd, stderr_fd);
+ if (*tty_fd == -1)
+ {
+ g_set_error_literal (error,
+ G_IO_ERROR, G_IO_ERROR_FAILED,
+ _("Unable to spawn ssh program"));
+ return FALSE;
+ }
+#else
+ GError *my_error;
+ GPid gpid;
+
+ *tty_fd = -1;
+
+ my_error = NULL;
+ if (!g_spawn_async_with_pipes (NULL, args, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL,
+ &gpid,
+ stdin_fd, stdout_fd, stderr_fd, &my_error))
+ {
+ g_set_error (error,
+ G_IO_ERROR, G_IO_ERROR_FAILED,
+ _("Unable to spawn ssh program: %s"), my_error->message);
+ g_error_free (my_error);
+ return FALSE;
+ }
+ *pid = gpid;
+#endif
+
+ return TRUE;
+}
+
+static guint32
+get_new_id (GVfsBackendSftp *backend)
+{
+ return backend->current_id++;
+}
+
+static GDataOutputStream *
+new_command_stream (GVfsBackendSftp *backend, int type)
+{
+ GOutputStream *mem_stream;
+ GDataOutputStream *data_stream;
+ guint32 id;
+
+ mem_stream = g_memory_output_stream_new (NULL, 0, (GReallocFunc)g_realloc, NULL);
+ data_stream = g_data_output_stream_new (mem_stream);
+ g_object_unref (mem_stream);
+
+ g_data_output_stream_put_int32 (data_stream, 0, NULL, NULL); /* LEN */
+ g_data_output_stream_put_byte (data_stream, type, NULL, NULL);
+ if (type != SSH_FXP_INIT)
+ {
+ id = get_new_id (backend);
+ g_data_output_stream_put_uint32 (data_stream, id, NULL, NULL);
+ g_object_set_qdata (G_OBJECT (data_stream), id_q, GUINT_TO_POINTER (id));
+ }
+
+ return data_stream;
+}
+
+static gsize
+get_data_size (GMemoryOutputStream *stream)
+{
+ g_seekable_seek (G_SEEKABLE (stream),
+ 0,
+ G_SEEK_END,
+ NULL, NULL);
+ return g_seekable_tell (G_SEEKABLE (stream));
+}
+
+static gpointer
+get_data_from_command_stream (GDataOutputStream *command_stream, gsize *len)
+{
+ GOutputStream *mem_stream;
+ gpointer data;
+ guint32 *len_ptr;
+
+ mem_stream = g_filter_output_stream_get_base_stream (G_FILTER_OUTPUT_STREAM (command_stream));
+ data = g_memory_output_stream_get_data (G_MEMORY_OUTPUT_STREAM (mem_stream));
+ *len = get_data_size (G_MEMORY_OUTPUT_STREAM (mem_stream));
+
+ len_ptr = (guint32 *)data;
+ *len_ptr = GUINT32_TO_BE (*len - 4);
+
+ return data;
+}
+
+static gboolean
+send_command_sync_and_unref_command (GVfsBackendSftp *backend,
+ GDataOutputStream *command_stream,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gpointer data;
+ gsize len;
+ gsize bytes_written;
+ gboolean res;
+
+ data = get_data_from_command_stream (command_stream, &len);
+
+ res = g_output_stream_write_all (backend->command_stream,
+ data, len,
+ &bytes_written,
+ cancellable, error);
+
+ if (error == NULL && !res)
+ g_warning ("Ignored send_command error\n");
+
+ g_free (data);
+ g_object_unref (command_stream);
+
+ return res;
+}
+
+static gboolean
+wait_for_reply (GVfsBackend *backend, int stdout_fd, GError **error)
+{
+ fd_set ifds;
+ struct timeval tv;
+ int ret;
+
+ FD_ZERO (&ifds);
+ FD_SET (stdout_fd, &ifds);
+
+ tv.tv_sec = 20;
+ tv.tv_usec = 0;
+
+ ret = select (stdout_fd+1, &ifds, NULL, NULL, &tv);
+
+ if (ret <= 0)
+ {
+ g_set_error_literal (error,
+ G_IO_ERROR, G_IO_ERROR_TIMED_OUT,
+ _("Timed out when logging in"));
+ return FALSE;
+ }
+ return TRUE;
+}
+
+static GDataInputStream *
+make_reply_stream (guint8 *data, gsize len)
+{
+ GInputStream *mem_stream;
+ GDataInputStream *data_stream;
+
+ mem_stream = g_memory_input_stream_new_from_data (data, len, g_free);
+ data_stream = g_data_input_stream_new (mem_stream);
+ g_object_unref (mem_stream);
+
+ return data_stream;
+}
+
+static GDataInputStream *
+read_reply_sync (GVfsBackendSftp *backend, gsize *len_out, GError **error)
+{
+ guint32 len;
+ gsize bytes_read;
+ GByteArray *array;
+ guint8 *data;
+
+ if (!g_input_stream_read_all (backend->reply_stream,
+ &len, 4,
+ &bytes_read, NULL, error))
+ return NULL;
+
+
+ len = GUINT32_FROM_BE (len);
+
+ array = g_byte_array_sized_new (len);
+
+ if (!g_input_stream_read_all (backend->reply_stream,
+ array->data, len,
+ &bytes_read, NULL, error))
+ {
+ g_byte_array_free (array, TRUE);
+ return NULL;
+ }
+
+ if (len_out)
+ *len_out = len;
+
+ data = array->data;
+ g_byte_array_free (array, FALSE);
+
+ return make_reply_stream (data, len);
+}
+
+static void
+put_string (GDataOutputStream *stream, const char *str)
+{
+ g_data_output_stream_put_uint32 (stream, strlen (str), NULL, NULL);
+ g_data_output_stream_put_string (stream, str, NULL, NULL);
+}
+
+static void
+put_data_buffer (GDataOutputStream *stream, DataBuffer *buffer)
+{
+ g_data_output_stream_put_uint32 (stream, buffer->size, NULL, NULL);
+ g_output_stream_write_all (G_OUTPUT_STREAM (stream),
+ buffer->data, buffer->size,
+ NULL,
+ NULL, NULL);
+}
+
+static char *
+read_string (GDataInputStream *stream, gsize *len_out)
+{
+ guint32 len;
+ char *data;
+ GError *error;
+
+ error = NULL;
+ len = g_data_input_stream_read_uint32 (stream, NULL, &error);
+ if (error)
+ {
+ g_error_free (error);
+ return NULL;
+ }
+
+ data = g_malloc (len + 1);
+
+ if (!g_input_stream_read_all (G_INPUT_STREAM (stream), data, len, NULL, NULL, NULL))
+ {
+ g_free (data);
+ return NULL;
+ }
+
+ data[len] = 0;
+
+ if (len_out)
+ *len_out = len;
+
+ return data;
+}
+
+static DataBuffer *
+read_data_buffer (GDataInputStream *stream)
+{
+ DataBuffer *buffer;
+
+ buffer = g_slice_new (DataBuffer);
+ buffer->data = (guchar *)read_string (stream, &buffer->size);
+
+ return buffer;
+}
+
+static gboolean
+get_hostname_and_fingerprint_from_line (const gchar *buffer,
+ gchar **hostname_out,
+ gchar **fingerprint_out)
+{
+ gchar *pos;
+ gchar *startpos;
+ gchar *endpos;
+ gchar *hostname = NULL;
+ gchar *fingerprint = NULL;
+
+ if (g_str_has_prefix (buffer, "The authenticity of host '"))
+ {
+ /* OpenSSH */
+ pos = strchr (&buffer[26], '\'');
+ if (pos == NULL)
+ return FALSE;
+
+ hostname = g_strndup (&buffer[26], pos - (&buffer[26]));
+
+ startpos = strstr (pos, " key fingerprint is ");
+ if (startpos == NULL)
+ {
+ g_free (hostname);
+ return FALSE;
+ }
+
+ startpos = startpos + 20;
+ endpos = strchr (startpos, '.');
+ if (endpos == NULL)
+ {
+ g_free (hostname);
+ return FALSE;
+ }
+
+ fingerprint = g_strndup (startpos, endpos - startpos);
+ }
+ else if (strstr (buffer, "Key fingerprint:") != NULL)
+ {
+ /* SSH.com*/
+ startpos = strstr (buffer, "Key fingerprint:");
+ if (startpos == NULL)
+ {
+ g_free (hostname);
+ return FALSE;
+ }
+
+ startpos = startpos + 18;
+ endpos = strchr (startpos, '\r');
+ fingerprint = g_strndup (startpos, endpos - startpos);
+ }
+
+ *hostname_out = hostname;
+ *fingerprint_out = fingerprint;
+
+ return TRUE;
+}
+
+static const gchar *
+get_authtype_from_password_line (const char *password_line)
+{
+ return g_str_has_prefix (password_line, "Enter passphrase for key") ?
+ "publickey" : "password";
+}
+
+static char *
+get_object_from_password_line (const char *password_line)
+{
+ char *chr, *ptr, *object = NULL;
+
+ if (g_str_has_prefix (password_line, "Enter passphrase for key"))
+ {
+ ptr = strchr (password_line, '\'');
+ if (ptr != NULL)
+ {
+ ptr += 1;
+ chr = strchr (ptr, '\'');
+ if (chr != NULL)
+ {
+ object = g_strndup (ptr, chr - ptr);
+ }
+ else
+ {
+ object = g_strdup (ptr);
+ }
+ }
+ }
+ return object;
+}
+
+static gboolean
+handle_login (GVfsBackend *backend,
+ GMountSource *mount_source,
+ int tty_fd, int stdout_fd, int stderr_fd,
+ GError **error)
+{
+ GVfsBackendSftp *op_backend = G_VFS_BACKEND_SFTP (backend);
+ GInputStream *prompt_stream;
+ GOutputStream *reply_stream;
+ fd_set ifds;
+ struct timeval tv;
+ int ret;
+ int prompt_fd;
+ char buffer[1024];
+ gsize len;
+ gboolean aborted = FALSE;
+ gboolean ret_val;
+ char *new_password = NULL;
+ char *new_user = NULL;
+ GPasswordSave password_save = G_PASSWORD_SAVE_NEVER;
+ gsize bytes_written;
+ gboolean password_in_keyring = FALSE;
+ const gchar *authtype = NULL;
+ gchar *object = NULL;
+
+ if (op_backend->client_vendor == SFTP_VENDOR_SSH)
+ prompt_fd = stderr_fd;
+ else
+ prompt_fd = tty_fd;
+
+ prompt_stream = g_unix_input_stream_new (prompt_fd, FALSE);
+ reply_stream = g_unix_output_stream_new (tty_fd, FALSE);
+
+ ret_val = TRUE;
+ while (1)
+ {
+ FD_ZERO (&ifds);
+ FD_SET (stdout_fd, &ifds);
+ FD_SET (prompt_fd, &ifds);
+
+ tv.tv_sec = 20;
+ tv.tv_usec = 0;
+
+ ret = select (MAX (stdout_fd, prompt_fd)+1, &ifds, NULL, NULL, &tv);
+
+ if (ret <= 0)
+ {
+ g_set_error_literal (error,
+ G_IO_ERROR, G_IO_ERROR_TIMED_OUT,
+ _("Timed out when logging in"));
+ ret_val = FALSE;
+ break;
+ }
+
+ if (FD_ISSET (stdout_fd, &ifds))
+ break; /* Got reply to initial INIT request */
+
+ g_assert (FD_ISSET (prompt_fd, &ifds));
+
+
+ len = g_input_stream_read (prompt_stream,
+ buffer, sizeof (buffer) - 1,
+ NULL, error);
+
+ if (len == -1)
+ {
+ ret_val = FALSE;
+ break;
+ }
+
+ buffer[len] = 0;
+
+ /*
+ * If the input URI contains a username
+ * if the input URI contains a password, we attempt one login and return GNOME_VFS_ERROR_ACCESS_DENIED on failure.
+ * if the input URI contains no password, we query the user until he provides a correct one, or he cancels.
+ *
+ * If the input URI contains no username
+ * (a) the user is queried for a user name and a password, with the default login being his
+ * local login name.
+ *
+ * (b) if the user decides to change his remote login name, we go to tty_retry because we need a
+ * new SSH session, attempting one login with his provided credentials, and if that fails proceed
+ * with (a), but use his desired remote login name as default.
+ *
+ * The "password" variable is only used for the very first login attempt,
+ * or for the first re-login attempt when the user decided to change his name.
+ * Otherwise, we "new_password" and "new_user_name" is used, as output variable
+ * for user and keyring input.
+ */
+ if (g_str_has_suffix (buffer, "password: ") ||
+ g_str_has_suffix (buffer, "Password: ") ||
+ g_str_has_suffix (buffer, "Password:") ||
+ g_str_has_prefix (buffer, "Password for ") ||
+ g_str_has_prefix (buffer, "Enter Kerberos password") ||
+ g_str_has_prefix (buffer, "Enter passphrase for key"))
+ {
+ authtype = get_authtype_from_password_line (buffer);
+ object = get_object_from_password_line (buffer);
+
+ /* If password is in keyring at this point is because it failed */
+ if (!op_backend->tmp_password && (password_in_keyring ||
+ !g_vfs_keyring_lookup_password (op_backend->user,
+ op_backend->host,
+ NULL,
+ "sftp",
+ object,
+ authtype,
+ op_backend->port != -1 ?
+ op_backend->port
+ :
+ 0,
+ NULL,
+ NULL,
+ &new_password)))
+ {
+ GAskPasswordFlags flags = G_ASK_PASSWORD_NEED_PASSWORD;
+
+ if (g_vfs_keyring_is_available ())
+ flags |= G_ASK_PASSWORD_SAVING_SUPPORTED;
+ if (!op_backend->user_specified)
+ flags |= G_ASK_PASSWORD_NEED_USERNAME;
+
+ g_free (new_password);
+
+ if (!g_mount_source_ask_password (mount_source,
+ g_str_has_prefix (buffer, "Enter passphrase for key") ?
+ _("Enter passphrase for key")
+ :
+ _("Enter password"),
+ op_backend->user,
+ NULL,
+ flags,
+ &aborted,
+ &new_password,
+ &new_user,
+ NULL,
+ NULL,
+ &password_save) ||
+ aborted)
+ {
+ g_set_error_literal (error,
+ G_IO_ERROR, G_IO_ERROR_PERMISSION_DENIED,
+ _("Password dialog cancelled"));
+ ret_val = FALSE;
+ break;
+ }
+ }
+ else if (op_backend->tmp_password)
+ {
+ /* I already a have a password of a previous login attempt
+ * that failed because the user provided a new user name
+ */
+ new_password = op_backend->tmp_password;
+ op_backend->tmp_password = NULL;
+ }
+ else
+ password_in_keyring = TRUE;
+
+ if (new_user && strcmp (new_user, op_backend->user) != 0)
+ {
+ g_free (op_backend->user);
+ op_backend->user = new_user;
+
+ g_free (op_backend->tmp_password);
+ op_backend->tmp_password = new_password;
+ new_password = NULL;
+
+ g_set_error_literal (error,
+ G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT,
+ "Invalid user name");
+ ret_val = FALSE;
+ break;
+ }
+ else if (new_user)
+ {
+ g_free (new_user);
+ }
+
+ if (!g_output_stream_write_all (reply_stream,
+ new_password, strlen (new_password),
+ &bytes_written,
+ NULL, NULL) ||
+ !g_output_stream_write_all (reply_stream,
+ "\n", 1,
+ &bytes_written,
+ NULL, NULL))
+ {
+ g_set_error_literal (error,
+ G_IO_ERROR, G_IO_ERROR_PERMISSION_DENIED,
+ _("Can't send password"));
+ ret_val = FALSE;
+ break;
+ }
+ }
+ else if (g_str_has_prefix (buffer, "The authenticity of host '") ||
+ strstr (buffer, "Key fingerprint:") != NULL)
+ {
+ const gchar *choices[] = {_("Log In Anyway"), _("Cancel Login")};
+ const gchar *v_choices = (const gchar *)choices;
+ const gchar *choice_string;
+ gchar *hostname = NULL;
+ gchar *fingerprint = NULL;
+ gint choice;
+ gchar *message;
+
+ get_hostname_and_fingerprint_from_line (buffer, &hostname, &fingerprint);
+
+ message = g_strdup_printf (_("The identity of the remote computer (%s) is unknown.\n"
+ "This happens when you log in to a computer the first time.\n\n"
+ "The identity sent by the remote computer is %s. "
+ "If you want to be absolutely sure it is safe to continue, "
+ "contact the system administrator."),
+ hostname ? hostname : op_backend->host, fingerprint);
+
+ g_free (hostname);
+ g_free (fingerprint);
+
+ if (!g_mount_source_ask_question (mount_source,
+ message,
+ &v_choices,
+ 2,
+ &aborted,
+ &choice) ||
+ aborted)
+ {
+ g_set_error_literal (error,
+ G_IO_ERROR, G_IO_ERROR_PERMISSION_DENIED,
+ _("Login dialog cancelled"));
+ g_free (message);
+ ret_val = FALSE;
+ break;
+ }
+ g_free (message);
+
+ choice_string = (choice == 0) ? "yes" : "no";
+ if (!g_output_stream_write_all (reply_stream,
+ choice_string,
+ strlen (choice_string),
+ &bytes_written,
+ NULL, NULL) ||
+ !g_output_stream_write_all (reply_stream,
+ "\n", 1,
+ &bytes_written,
+ NULL, NULL))
+ {
+ g_set_error_literal (error,
+ G_IO_ERROR, G_IO_ERROR_PERMISSION_DENIED,
+ _("Can't send host identity confirmation"));
+ ret_val = FALSE;
+ break;
+ }
+ }
+ }
+
+ if (ret_val)
+ {
+ /* Login succeed, save password in keyring */
+ g_vfs_keyring_save_password (op_backend->user,
+ op_backend->host,
+ NULL,
+ "sftp",
+ object,
+ authtype,
+ op_backend->port != -1 ?
+ op_backend->port
+ :
+ 0,
+ new_password,
+ password_save);
+ }
+
+ g_free (object);
+ g_free (new_password);
+ g_object_unref (prompt_stream);
+ g_object_unref (reply_stream);
+ return ret_val;
+}
+
+static void
+fail_jobs_and_die (GVfsBackendSftp *backend, GError *error)
+{
+ GHashTableIter iter;
+ gpointer key, value;
+
+ g_hash_table_iter_init (&iter, backend->expected_replies);
+ while (g_hash_table_iter_next (&iter, &key, &value))
+ {
+ ExpectedReply *expected_reply = (ExpectedReply *) value;
+ g_vfs_job_failed_from_error (expected_reply->job, error);
+ }
+
+ g_error_free (error);
+
+ _exit (1);
+}
+
+static void
+check_input_stream_read_result (GVfsBackendSftp *backend, gssize res, GError *error)
+{
+ if (G_UNLIKELY (res <= 0))
+ {
+ if (res == 0 || error == NULL)
+ {
+ g_clear_error (&error);
+ g_set_error (&error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ _("Internal error: %s"),
+ res == 0 ? "The underlying ssh process died" : "Unkown Error");
+ }
+
+ fail_jobs_and_die (backend, error);
+ }
+}
+
+static void read_reply_async (GVfsBackendSftp *backend);
+
+static void
+read_reply_async_got_data (GObject *source_object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ GVfsBackendSftp *backend = user_data;
+ gssize res;
+ GDataInputStream *reply;
+ ExpectedReply *expected_reply;
+ guint32 id;
+ int type;
+ GError *error;
+
+ error = NULL;
+ res = g_input_stream_read_finish (G_INPUT_STREAM (source_object), result, &error);
+
+ check_input_stream_read_result (backend, res, error);
+
+ backend->reply_size_read += res;
+
+ if (backend->reply_size_read < backend->reply_size)
+ {
+ g_input_stream_read_async (backend->reply_stream,
+ backend->reply + backend->reply_size_read, backend->reply_size - backend->reply_size_read,
+ 0, NULL, read_reply_async_got_data, backend);
+ return;
+ }
+
+ reply = make_reply_stream (backend->reply, backend->reply_size);
+ backend->reply = NULL;
+
+ type = g_data_input_stream_read_byte (reply, NULL, NULL);
+ id = g_data_input_stream_read_uint32 (reply, NULL, NULL);
+
+ expected_reply = g_hash_table_lookup (backend->expected_replies, GINT_TO_POINTER (id));
+ if (expected_reply)
+ {
+ if (expected_reply->callback != NULL)
+ (expected_reply->callback) (backend, type, reply, backend->reply_size,
+ expected_reply->job, expected_reply->user_data);
+ g_hash_table_remove (backend->expected_replies, GINT_TO_POINTER (id));
+ }
+ else
+ g_warning ("Got unhandled reply of size %"G_GUINT32_FORMAT" for id %"G_GUINT32_FORMAT"\n", backend->reply_size, id);
+
+ g_object_unref (reply);
+
+ read_reply_async (backend);
+
+}
+
+static void
+read_reply_async_got_len (GObject *source_object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ GVfsBackendSftp *backend = user_data;
+ gssize res;
+ GError *error;
+
+ error = NULL;
+ res = g_input_stream_read_finish (G_INPUT_STREAM (source_object), result, &error);
+
+ check_input_stream_read_result (backend, res, error);
+
+ backend->reply_size_read += res;
+
+ if (backend->reply_size_read < 4)
+ {
+ g_input_stream_read_async (backend->reply_stream,
+ &backend->reply_size + backend->reply_size_read, 4 - backend->reply_size_read,
+ 0, NULL, read_reply_async_got_len, backend);
+ return;
+ }
+ backend->reply_size = GUINT32_FROM_BE (backend->reply_size);
+
+ backend->reply_size_read = 0;
+ backend->reply = g_malloc (backend->reply_size);
+ g_input_stream_read_async (backend->reply_stream,
+ backend->reply, backend->reply_size,
+ 0, NULL, read_reply_async_got_data, backend);
+}
+
+static void
+read_reply_async (GVfsBackendSftp *backend)
+{
+ backend->reply_size_read = 0;
+ g_input_stream_read_async (backend->reply_stream,
+ &backend->reply_size, 4,
+ 0, NULL, read_reply_async_got_len, backend);
+}
+
+static void send_command (GVfsBackendSftp *backend);
+
+static void
+send_command_data (GObject *source_object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ GVfsBackendSftp *backend = user_data;
+ gssize res;
+ DataBuffer *buffer;
+
+ res = g_output_stream_write_finish (G_OUTPUT_STREAM (source_object), result, NULL);
+
+ if (res <= 0)
+ {
+ /* TODO: unmount, etc */
+ g_warning ("Error sending command");
+ return;
+ }
+
+ buffer = backend->command_queue->data;
+
+ backend->command_bytes_written += res;
+
+ if (backend->command_bytes_written < buffer->size)
+ {
+ g_output_stream_write_async (backend->command_stream,
+ buffer->data + backend->command_bytes_written,
+ buffer->size - backend->command_bytes_written,
+ 0,
+ NULL,
+ send_command_data,
+ backend);
+ return;
+ }
+
+ data_buffer_free (buffer);
+
+ backend->command_queue = g_list_delete_link (backend->command_queue, backend->command_queue);
+
+ if (backend->command_queue != NULL)
+ send_command (backend);
+}
+
+static void
+send_command (GVfsBackendSftp *backend)
+{
+ DataBuffer *buffer;
+
+ buffer = backend->command_queue->data;
+
+ backend->command_bytes_written = 0;
+ g_output_stream_write_async (backend->command_stream,
+ buffer->data,
+ buffer->size,
+ 0,
+ NULL,
+ send_command_data,
+ backend);
+}
+
+static void
+expect_reply (GVfsBackendSftp *backend,
+ guint32 id,
+ ReplyCallback callback,
+ GVfsJob *job,
+ gpointer user_data)
+{
+ ExpectedReply *expected;
+
+ expected = g_slice_new (ExpectedReply);
+ expected->callback = callback;
+ expected->job = g_object_ref (job);
+ expected->user_data = user_data;
+
+ g_hash_table_replace (backend->expected_replies, GINT_TO_POINTER (id), expected);
+}
+
+static DataBuffer *
+data_buffer_new (guchar *data, gsize len)
+{
+ DataBuffer *buffer;
+
+ buffer = g_slice_new (DataBuffer);
+ buffer->data = data;
+ buffer->size = len;
+
+ return buffer;
+}
+
+static void
+queue_command_buffer (GVfsBackendSftp *backend,
+ DataBuffer *buffer)
+{
+ gboolean first;
+
+ first = backend->command_queue == NULL;
+
+ backend->command_queue = g_list_append (backend->command_queue, buffer);
+
+ if (first)
+ send_command (backend);
+}
+
+static void
+queue_command_stream_and_free (GVfsBackendSftp *backend,
+ GDataOutputStream *command_stream,
+ ReplyCallback callback,
+ GVfsJob *job,
+ gpointer user_data)
+{
+ gpointer data;
+ gsize len;
+ DataBuffer *buffer;
+ guint32 id;
+
+ id = GPOINTER_TO_UINT (g_object_get_qdata (G_OBJECT (command_stream), id_q));
+ data = get_data_from_command_stream (command_stream, &len);
+
+ buffer = data_buffer_new (data, len);
+ g_object_unref (command_stream);
+
+ expect_reply (backend, id, callback, job, user_data);
+ queue_command_buffer (backend, buffer);
+}
+
+
+static void
+multi_request_cb (GVfsBackendSftp *backend,
+ int reply_type,
+ GDataInputStream *reply_stream,
+ guint32 len,
+ GVfsJob *job,
+ gpointer user_data)
+{
+ MultiReply *reply;
+ MultiRequest *request;
+ int i;
+
+ reply = user_data;
+ request = reply->request;
+
+ reply->type = reply_type;
+ reply->data = g_object_ref (reply_stream);
+ reply->data_len = len;
+
+ if (--request->n_outstanding == 0)
+ {
+ /* Call callback */
+ if (request->callback != NULL)
+ (request->callback) (backend,
+ request->replies,
+ request->n_replies,
+ job,
+ request->user_data);
+
+ /* Free request data */
+
+ for (i = 0; i < request->n_replies; i++)
+ {
+ reply = &request->replies[i];
+ if (reply->data)
+ g_object_unref (reply->data);
+ }
+ g_free (request->replies);
+
+ g_free (request);
+
+ }
+}
+
+static void
+queue_command_streams_and_free (GVfsBackendSftp *backend,
+ GDataOutputStream **commands,
+ int n_commands,
+ MultiReplyCallback callback,
+ GVfsJob *job,
+ gpointer user_data)
+{
+ MultiRequest *data;
+ MultiReply *reply;
+
+ int i;
+
+ data = g_new0 (MultiRequest, 1);
+
+ data->user_data = user_data;
+ data->n_replies = n_commands;
+ data->n_outstanding = n_commands;
+ data->replies = g_new0 (MultiReply, n_commands);
+ data->callback = callback;
+
+ for (i = 0; i < n_commands; i++)
+ {
+ reply = &data->replies[i];
+ reply->request = data;
+ queue_command_stream_and_free (backend,
+ commands[i],
+ multi_request_cb,
+ job,
+ reply);
+ }
+}
+
+static gboolean
+get_uid_sync (GVfsBackendSftp *backend)
+{
+ GDataOutputStream *command;
+ GDataInputStream *reply;
+ int type;
+
+ command = new_command_stream (backend, SSH_FXP_STAT);
+ put_string (command, ".");
+ send_command_sync_and_unref_command (backend, command, NULL, NULL);
+
+ reply = read_reply_sync (backend, NULL, NULL);
+ if (reply == NULL)
+ return FALSE;
+
+ type = g_data_input_stream_read_byte (reply, NULL, NULL);
+ /*id =*/ (void) g_data_input_stream_read_uint32 (reply, NULL, NULL);
+
+ /* On error, set uid to -1 and ignore */
+ backend->my_uid = (guint32)-1;
+ backend->my_gid = (guint32)-1;
+ if (type == SSH_FXP_ATTRS)
+ {
+ GFileInfo *info;
+
+ info = g_file_info_new ();
+ parse_attributes (backend, info, NULL, reply, NULL);
+ if (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_UNIX_UID))
+ {
+ /* Both are always set if set */
+ backend->my_uid = g_file_info_get_attribute_uint32 (info,
+ G_FILE_ATTRIBUTE_UNIX_UID);
+ backend->my_gid = g_file_info_get_attribute_uint32 (info,
+ G_FILE_ATTRIBUTE_UNIX_GID);
+ }
+
+ g_object_unref (info);
+ }
+
+ g_object_unref (reply);
+
+ return TRUE;
+}
+
+static void
+do_mount (GVfsBackend *backend,
+ GVfsJobMount *job,
+ GMountSpec *mount_spec,
+ GMountSource *mount_source,
+ gboolean is_automount)
+{
+ GVfsBackendSftp *op_backend = G_VFS_BACKEND_SFTP (backend);
+ gchar **args; /* Enough for now, extend if you add more args */
+ pid_t pid;
+ int tty_fd, stdout_fd, stdin_fd, stderr_fd;
+ GError *error;
+ GInputStream *is;
+ GDataOutputStream *command;
+ GDataInputStream *reply;
+ gboolean res;
+ GMountSpec *sftp_mount_spec;
+ char *extension_name, *extension_data;
+ char *display_name;
+
+ args = setup_ssh_commandline (backend);
+
+ error = NULL;
+ if (!spawn_ssh (backend,
+ args, &pid,
+ &tty_fd, &stdin_fd, &stdout_fd, &stderr_fd,
+ &error))
+ {
+ g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
+ g_error_free (error);
+ g_strfreev (args);
+ return;
+ }
+
+ g_strfreev (args);
+
+ op_backend->command_stream = g_unix_output_stream_new (stdin_fd, TRUE);
+
+ command = new_command_stream (op_backend, SSH_FXP_INIT);
+ g_data_output_stream_put_int32 (command,
+ SSH_FILEXFER_VERSION, NULL, NULL);
+ send_command_sync_and_unref_command (op_backend, command, NULL, NULL);
+
+ if (tty_fd == -1)
+ res = wait_for_reply (backend, stdout_fd, &error);
+ else
+ res = handle_login (backend, mount_source, tty_fd, stdout_fd, stderr_fd, &error);
+
+ if (!res)
+ {
+ if (error->code == G_IO_ERROR_INVALID_ARGUMENT)
+ {
+ /* New username provided by the user,
+ * we need to re-spawn the ssh command
+ */
+ g_error_free (error);
+ do_mount (backend, job, mount_spec, mount_source, is_automount);
+ }
+ else
+ {
+ g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
+ g_error_free (error);
+ }
+
+ return;
+ }
+
+ op_backend->reply_stream = g_unix_input_stream_new (stdout_fd, TRUE);
+
+ make_fd_nonblocking (stderr_fd);
+ is = g_unix_input_stream_new (stderr_fd, TRUE);
+ op_backend->error_stream = g_data_input_stream_new (is);
+ g_object_unref (is);
+
+ reply = read_reply_sync (op_backend, NULL, NULL);
+ if (reply == NULL)
+ {
+ look_for_stderr_errors (backend, &error);
+ g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
+ g_error_free (error);
+ return;
+ }
+
+ if (g_data_input_stream_read_byte (reply, NULL, NULL) != SSH_FXP_VERSION)
+ {
+ g_set_error_literal (&error, G_IO_ERROR, G_IO_ERROR_FAILED, _("Protocol error"));
+ g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
+ g_error_free (error);
+ return;
+ }
+
+ op_backend->protocol_version = g_data_input_stream_read_uint32 (reply, NULL, NULL);
+
+ while ((extension_name = read_string (reply, NULL)) != NULL)
+ {
+ extension_data = read_string (reply, NULL);
+ if (extension_data)
+ {
+ /* TODO: Do something with this */
+ }
+ g_free (extension_name);
+ g_free (extension_data);
+ }
+
+ g_object_unref (reply);
+
+ if (!get_uid_sync (op_backend))
+ {
+ g_set_error_literal (&error, G_IO_ERROR, G_IO_ERROR_FAILED, _("Protocol error"));
+ g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
+ g_error_free (error);
+ return;
+ }
+
+ read_reply_async (op_backend);
+
+ sftp_mount_spec = g_mount_spec_new ("sftp");
+ if (op_backend->user_specified)
+ g_mount_spec_set (sftp_mount_spec, "user", op_backend->user);
+ g_mount_spec_set (sftp_mount_spec, "host", op_backend->host);
+ if (op_backend->port != -1)
+ {
+ char *v;
+ v = g_strdup_printf ("%d", op_backend->port);
+ g_mount_spec_set (sftp_mount_spec, "port", v);
+ g_free (v);
+ }
+
+ g_vfs_backend_set_mount_spec (backend, sftp_mount_spec);
+ g_mount_spec_unref (sftp_mount_spec);
+
+ /* Translators: This is the name of an sftp share, like "sftp on <hostname>" */
+ display_name = g_strdup_printf (_("sftp on %s"), op_backend->host);
+ g_vfs_backend_set_display_name (backend, display_name);
+ g_free (display_name);
+ g_vfs_backend_set_icon_name (backend, "folder-remote");
+
+ g_vfs_job_succeeded (G_VFS_JOB (job));
+}
+
+static gboolean
+try_mount (GVfsBackend *backend,
+ GVfsJobMount *job,
+ GMountSpec *mount_spec,
+ GMountSource *mount_source,
+ gboolean is_automount)
+{
+ GVfsBackendSftp *op_backend = G_VFS_BACKEND_SFTP (backend);
+ const char *user, *host, *port;
+
+ op_backend->client_vendor = get_sftp_client_vendor ();
+
+ if (op_backend->client_vendor == SFTP_VENDOR_INVALID)
+ {
+ g_vfs_job_failed (G_VFS_JOB (job),
+ G_IO_ERROR, G_IO_ERROR_FAILED,
+ _("Unable to find supported ssh command"));
+ return TRUE;
+ }
+
+ host = g_mount_spec_get (mount_spec, "host");
+
+ if (host == NULL)
+ {
+ g_vfs_job_failed (G_VFS_JOB (job),
+ G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT,
+ _("No hostname specified"));
+ return TRUE;
+ }
+
+ port = g_mount_spec_get (mount_spec, "port");
+ op_backend->port = -1;
+ if (port != NULL)
+ {
+ int p = atoi (port);
+ if (p != 22)
+ op_backend->port = p;
+ }
+
+ user = g_mount_spec_get (mount_spec, "user");
+
+ op_backend->host = g_strdup (host);
+ op_backend->user = g_strdup (user);
+ if (op_backend->user)
+ op_backend->user_specified = TRUE;
+ else
+ op_backend->user = g_strdup (g_get_user_name ());
+
+ return FALSE;
+}
+
+static int
+io_error_code_for_sftp_error (guint32 code, int failure_error)
+{
+ int error_code;
+
+ error_code = G_IO_ERROR_FAILED;
+
+ switch (code)
+ {
+ default:
+ case SSH_FX_EOF:
+ case SSH_FX_BAD_MESSAGE:
+ case SSH_FX_NO_CONNECTION:
+ case SSH_FX_CONNECTION_LOST:
+ break;
+
+ case SSH_FX_FAILURE:
+ error_code = failure_error;
+ break;
+
+ case SSH_FX_NO_SUCH_FILE:
+ error_code = G_IO_ERROR_NOT_FOUND;
+ break;
+
+ case SSH_FX_PERMISSION_DENIED:
+ error_code = G_IO_ERROR_PERMISSION_DENIED;
+ break;
+
+ case SSH_FX_OP_UNSUPPORTED:
+ error_code = G_IO_ERROR_NOT_SUPPORTED;
+ break;
+ }
+ return error_code;
+}
+
+static gboolean
+error_from_status (GVfsJob *job,
+ GDataInputStream *reply,
+ int failure_error,
+ int allowed_sftp_error,
+ GError **error)
+{
+ guint32 code;
+ gint error_code;
+ char *message;
+
+ if (failure_error == -1)
+ failure_error = G_IO_ERROR_FAILED;
+
+ code = g_data_input_stream_read_uint32 (reply, NULL, NULL);
+
+ if (code == SSH_FX_OK ||
+ (allowed_sftp_error != -1 &&
+ code == allowed_sftp_error))
+ return TRUE;
+
+ if (error)
+ {
+ error_code = io_error_code_for_sftp_error (code, failure_error);
+ message = read_string (reply, NULL);
+ if (message == NULL)
+ message = g_strdup ("Unknown reason");
+
+ *error = g_error_new_literal (G_IO_ERROR, error_code, message);
+ g_free (message);
+ }
+
+ return FALSE;
+}
+
+static gboolean
+failure_from_status (GVfsJob *job,
+ GDataInputStream *reply,
+ int failure_error,
+ int allowed_sftp_error)
+{
+ GError *error;
+
+ error = NULL;
+ if (error_from_status (job, reply, failure_error, allowed_sftp_error, &error))
+ return TRUE;
+ else
+ {
+ g_vfs_job_failed_from_error (job, error);
+ g_error_free (error);
+ }
+ return FALSE;
+}
+
+
+static gboolean
+result_from_status (GVfsJob *job,
+ GDataInputStream *reply,
+ int failure_error,
+ int allowed_sftp_error)
+{
+ gboolean res;
+
+ res = failure_from_status (job, reply,
+ failure_error,
+ allowed_sftp_error);
+ if (res)
+ g_vfs_job_succeeded (job);
+
+ return res;
+}
+
+static void
+set_access_attributes (GFileInfo *info,
+ guint32 perm)
+{
+ g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_READ,
+ perm & 0x4);
+ g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE,
+ perm & 0x2);
+ g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_EXECUTE,
+ perm & 0x1);
+}
+
+
+static void
+parse_attributes (GVfsBackendSftp *backend,
+ GFileInfo *info,
+ const char *basename,
+ GDataInputStream *reply,
+ GFileAttributeMatcher *matcher)
+{
+ guint32 flags;
+ GFileType type;
+ guint32 uid, gid;
+ guint32 mode;
+ gboolean has_uid, free_mimetype;
+ char *mimetype;
+ GIcon *icon;
+
+ flags = g_data_input_stream_read_uint32 (reply, NULL, NULL);
+
+ if (basename != NULL && basename[0] == '.')
+ g_file_info_set_is_hidden (info, TRUE);
+
+ if (basename != NULL)
+ g_file_info_set_name (info, basename);
+ else
+ g_file_info_set_name (info, "/");
+
+ if (basename != NULL && basename[strlen (basename) -1] == '~')
+ g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_STANDARD_IS_BACKUP, TRUE);
+
+ if (flags & SSH_FILEXFER_ATTR_SIZE)
+ {
+ guint64 size = g_data_input_stream_read_uint64 (reply, NULL, NULL);
+ g_file_info_set_size (info, size);
+ }
+
+ has_uid = FALSE;
+ uid = gid = 0; /* Avoid warnings */
+ if (flags & SSH_FILEXFER_ATTR_UIDGID)
+ {
+ has_uid = TRUE;
+ uid = g_data_input_stream_read_uint32 (reply, NULL, NULL);
+ g_file_info_set_attribute_uint32 (info, G_FILE_ATTRIBUTE_UNIX_UID, uid);
+ gid = g_data_input_stream_read_uint32 (reply, NULL, NULL);
+ g_file_info_set_attribute_uint32 (info, G_FILE_ATTRIBUTE_UNIX_GID, gid);
+ }
+
+ type = G_FILE_TYPE_UNKNOWN;
+
+ if (flags & SSH_FILEXFER_ATTR_PERMISSIONS)
+ {
+ mode = g_data_input_stream_read_uint32 (reply, NULL, NULL);
+ g_file_info_set_attribute_uint32 (info, G_FILE_ATTRIBUTE_UNIX_MODE, mode);
+
+ mimetype = NULL;
+ if (S_ISREG (mode))
+ type = G_FILE_TYPE_REGULAR;
+ else if (S_ISDIR (mode))
+ {
+ type = G_FILE_TYPE_DIRECTORY;
+ mimetype = "inode/directory";
+ }
+ else if (S_ISFIFO (mode))
+ {
+ type = G_FILE_TYPE_SPECIAL;
+ mimetype = "inode/fifo";
+ }
+ else if (S_ISSOCK (mode))
+ {
+ type = G_FILE_TYPE_SPECIAL;
+ mimetype = "inode/socket";
+ }
+ else if (S_ISCHR (mode))
+ {
+ type = G_FILE_TYPE_SPECIAL;
+ mimetype = "inode/chardevice";
+ }
+ else if (S_ISBLK (mode))
+ {
+ type = G_FILE_TYPE_SPECIAL;
+ mimetype = "inode/blockdevice";
+ }
+ else if (S_ISLNK (mode))
+ {
+ type = G_FILE_TYPE_SYMBOLIC_LINK;
+ g_file_info_set_is_symlink (info, TRUE);
+ mimetype = "inode/symlink";
+ }
+
+ free_mimetype = FALSE;
+ if (mimetype == NULL)
+ {
+ if (basename)
+ {
+ mimetype = g_content_type_guess (basename, NULL, 0, NULL);
+ free_mimetype = TRUE;
+ }
+ else
+ mimetype = "application/octet-stream";
+ }
+
+ g_file_info_set_content_type (info, mimetype);
+ g_file_info_set_attribute_string (info, G_FILE_ATTRIBUTE_STANDARD_FAST_CONTENT_TYPE, mimetype);
+
+ if (g_file_attribute_matcher_matches (matcher,
+ G_FILE_ATTRIBUTE_STANDARD_ICON))
+ {
+ icon = NULL;
+ if (S_ISDIR(mode))
+ icon = g_themed_icon_new ("folder");
+ else if (mimetype)
+ {
+ icon = g_content_type_get_icon (mimetype);
+ if (G_IS_THEMED_ICON (icon))
+ g_themed_icon_append_name (G_THEMED_ICON (icon), "text-x-generic");
+ }
+
+ if (icon == NULL)
+ icon = g_themed_icon_new ("text-x-generic");
+
+ g_file_info_set_icon (info, icon);
+ g_object_unref (icon);
+ }
+
+
+ if (free_mimetype)
+ g_free (mimetype);
+
+ if (has_uid && backend->my_uid != (guint32)-1)
+ {
+ if (uid == backend->my_uid)
+ set_access_attributes (info, (mode >> 6) & 0x7);
+ else if (gid == backend->my_gid)
+ set_access_attributes (info, (mode >> 3) & 0x7);
+ else
+ set_access_attributes (info, (mode >> 0) & 0x7);
+ }
+
+ }
+
+ g_file_info_set_file_type (info, type);
+
+ if (flags & SSH_FILEXFER_ATTR_ACMODTIME)
+ {
+ guint32 v;
+ char *etag;
+
+ v = g_data_input_stream_read_uint32 (reply, NULL, NULL);
+ g_file_info_set_attribute_uint64 (info, G_FILE_ATTRIBUTE_TIME_ACCESS, v);
+ v = g_data_input_stream_read_uint32 (reply, NULL, NULL);
+ g_file_info_set_attribute_uint64 (info, G_FILE_ATTRIBUTE_TIME_MODIFIED, v);
+
+ etag = g_strdup_printf ("%lu", (long unsigned int)v);
+ g_file_info_set_attribute_string (info, G_FILE_ATTRIBUTE_ETAG_VALUE, etag);
+ g_free (etag);
+ }
+
+ if (flags & SSH_FILEXFER_ATTR_EXTENDED)
+ {
+ guint32 count, i;
+ char *name, *val;
+ count = g_data_input_stream_read_uint32 (reply, NULL, NULL);
+ for (i = 0; i < count; i++)
+ {
+ name = read_string (reply, NULL);
+ val = read_string (reply, NULL);
+
+ g_free (name);
+ g_free (val);
+ }
+ }
+
+ /* We use the same setting as for local files. Can't really
+ * do better, since there is no way in this version of sftp to find out
+ * the remote charset encoding
+ */
+ if (g_file_attribute_matcher_matches (matcher,
+ G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME))
+ {
+ if (basename != NULL)
+ {
+ char *display_name = g_filename_display_name (basename);
+
+ if (strstr (display_name, "\357\277\275") != NULL)
+ {
+ char *p = display_name;
+ display_name = g_strconcat (display_name, _(" (invalid encoding)"), NULL);
+ g_free (p);
+ }
+ g_file_info_set_display_name (info, display_name);
+ g_free (display_name);
+ }
+ else
+ {
+ char *name;
+
+
+ /* Translators: This is the name of the root of an sftp share, like "/ on <hostname>" */
+ name = g_strdup_printf (_("/ on %s"), G_VFS_BACKEND_SFTP (backend)->host);
+ g_file_info_set_display_name (info, name);
+ g_free (name);
+ }
+ }
+
+ if (basename != NULL &&
+ g_file_attribute_matcher_matches (matcher,
+ G_FILE_ATTRIBUTE_STANDARD_EDIT_NAME))
+ {
+ char *edit_name = g_filename_display_name (basename);
+ g_file_info_set_edit_name (info, edit_name);
+ g_free (edit_name);
+ }
+}
+
+static SftpHandle *
+sftp_handle_new (GDataInputStream *reply)
+{
+ SftpHandle *handle;
+
+ handle = g_slice_new0 (SftpHandle);
+ handle->raw_handle = read_data_buffer (reply);
+ handle->offset = 0;
+
+ return handle;
+}
+
+static void
+sftp_handle_free (SftpHandle *handle)
+{
+ data_buffer_free (handle->raw_handle);
+ g_free (handle->filename);
+ g_free (handle->tempname);
+ g_slice_free (SftpHandle, handle);
+}
+
+static void
+open_stat_reply (GVfsBackendSftp *backend,
+ int reply_type,
+ GDataInputStream *reply,
+ guint32 len,
+ GVfsJob *job,
+ gpointer user_data)
+{
+ if (g_vfs_job_is_finished (job))
+ {
+ /* Handled in stat reply */
+ return;
+ }
+
+ if (reply_type == SSH_FXP_ATTRS)
+ {
+ GFileType type;
+ GFileInfo *info = g_file_info_new ();
+
+ parse_attributes (backend, info, NULL,
+ reply, NULL);
+ type = g_file_info_get_file_type (info);
+ g_object_unref (info);
+
+ if (type == G_FILE_TYPE_DIRECTORY)
+ {
+ g_vfs_job_failed (job, G_IO_ERROR, G_IO_ERROR_IS_DIRECTORY,
+ _("File is directory"));
+ return;
+ }
+ }
+
+ if (GPOINTER_TO_INT (G_VFS_JOB(job)->backend_data) == 1)
+ {
+ /* We ran the read_reply and it was a generic failure */
+ g_vfs_job_failed (job, G_IO_ERROR, G_IO_ERROR_FAILED,
+ _("Failure"));
+ }
+}
+
+static void
+open_for_read_reply (GVfsBackendSftp *backend,
+ int reply_type,
+ GDataInputStream *reply,
+ guint32 len,
+ GVfsJob *job,
+ gpointer user_data)
+{
+ SftpHandle *handle;
+
+ if (g_vfs_job_is_finished (job))
+ {
+ /* Handled in stat reply */
+
+ /* Normally this should not happen as we
+ sent an is_dir error. But i guess we can
+ race */
+ if (reply_type == SSH_FXP_HANDLE)
+ {
+ GDataOutputStream *command;
+ DataBuffer *bhandle;
+
+ bhandle = read_data_buffer (reply);
+
+ command = new_command_stream (backend, SSH_FXP_CLOSE);
+ put_data_buffer (command, bhandle);
+ queue_command_stream_and_free (backend, command, NULL, G_VFS_JOB (job), NULL);
+
+ data_buffer_free (bhandle);
+ }
+
+ return;
+ }
+
+ if (reply_type == SSH_FXP_STATUS)
+ {
+ if (failure_from_status (job,
+ reply,
+ -1,
+ SSH_FX_FAILURE))
+ {
+ /* Unknown failure type, mark that we got this and
+ return result from stat result */
+ G_VFS_JOB(job)->backend_data = GINT_TO_POINTER (1);
+ }
+
+ return;
+ }
+
+ if (reply_type != SSH_FXP_HANDLE)
+ {
+ g_vfs_job_failed (job, G_IO_ERROR, G_IO_ERROR_FAILED,
+ _("Invalid reply received"));
+ return;
+ }
+
+ handle = sftp_handle_new (reply);
+
+ g_vfs_job_open_for_read_set_handle (G_VFS_JOB_OPEN_FOR_READ (job), handle);
+ g_vfs_job_open_for_read_set_can_seek (G_VFS_JOB_OPEN_FOR_READ (job), TRUE);
+ g_vfs_job_succeeded (job);
+}
+
+static gboolean
+try_open_for_read (GVfsBackend *backend,
+ GVfsJobOpenForRead *job,
+ const char *filename)
+{
+ GVfsBackendSftp *op_backend = G_VFS_BACKEND_SFTP (backend);
+ GDataOutputStream *command;
+
+ G_VFS_JOB(job)->backend_data = GINT_TO_POINTER (0);
+
+ command = new_command_stream (op_backend,
+ SSH_FXP_STAT);
+ put_string (command, filename);
+ queue_command_stream_and_free (op_backend, command, open_stat_reply, G_VFS_JOB (job), NULL);
+
+ command = new_command_stream (op_backend,
+ SSH_FXP_OPEN);
+ put_string (command, filename);
+ g_data_output_stream_put_uint32 (command, SSH_FXF_READ, NULL, NULL); /* open flags */
+ g_data_output_stream_put_uint32 (command, 0, NULL, NULL); /* Attr flags */
+
+ queue_command_stream_and_free (op_backend, command, open_for_read_reply, G_VFS_JOB (job), NULL);
+
+ return TRUE;
+}
+
+static void
+read_reply (GVfsBackendSftp *backend,
+ int reply_type,
+ GDataInputStream *reply,
+ guint32 len,
+ GVfsJob *job,
+ gpointer user_data)
+{
+ SftpHandle *handle;
+ guint32 count;
+
+ handle = user_data;
+
+ if (reply_type == SSH_FXP_STATUS)
+ {
+ result_from_status (job, reply, -1, SSH_FX_EOF);
+ return;
+ }
+
+ if (reply_type != SSH_FXP_DATA)
+ {
+ g_vfs_job_failed (job, G_IO_ERROR, G_IO_ERROR_FAILED,
+ _("Invalid reply received"));
+ return;
+ }
+
+ count = g_data_input_stream_read_uint32 (reply, NULL, NULL);
+
+ if (!g_input_stream_read_all (G_INPUT_STREAM (reply),
+ G_VFS_JOB_READ (job)->buffer, count,
+ NULL, NULL, NULL))
+ {
+ g_vfs_job_failed (job, G_IO_ERROR, G_IO_ERROR_FAILED,
+ _("Invalid reply received"));
+ return;
+ }
+
+ handle->offset += count;
+
+ g_vfs_job_read_set_size (G_VFS_JOB_READ (job), count);
+ g_vfs_job_succeeded (job);
+}
+
+static gboolean
+try_read (GVfsBackend *backend,
+ GVfsJobRead *job,
+ GVfsBackendHandle _handle,
+ char *buffer,
+ gsize bytes_requested)
+{
+ SftpHandle *handle = _handle;
+ GVfsBackendSftp *op_backend = G_VFS_BACKEND_SFTP (backend);
+ GDataOutputStream *command;
+
+ command = new_command_stream (op_backend,
+ SSH_FXP_READ);
+ put_data_buffer (command, handle->raw_handle);
+ g_data_output_stream_put_uint64 (command, handle->offset, NULL, NULL);
+ g_data_output_stream_put_uint32 (command, bytes_requested, NULL, NULL);
+
+ queue_command_stream_and_free (op_backend, command, read_reply, G_VFS_JOB (job), handle);
+
+ return TRUE;
+}
+
+static void
+seek_read_fstat_reply (GVfsBackendSftp *backend,
+ int reply_type,
+ GDataInputStream *reply,
+ guint32 len,
+ GVfsJob *job,
+ gpointer user_data)
+{
+ SftpHandle *handle;
+ GFileInfo *info;
+ goffset file_size;
+ GVfsJobSeekRead *op_job;
+
+ handle = user_data;
+
+ if (reply_type == SSH_FXP_STATUS)
+ {
+ result_from_status (job, reply, -1, -1);
+ return;
+ }
+
+ if (reply_type != SSH_FXP_ATTRS)
+ {
+ g_vfs_job_failed (job, G_IO_ERROR, G_IO_ERROR_FAILED,
+ _("Invalid reply received"));
+ return;
+ }
+
+ info = g_file_info_new ();
+ parse_attributes (backend, info, NULL,
+ reply, NULL);
+ file_size = g_file_info_get_size (info);
+ g_object_unref (info);
+
+ op_job = G_VFS_JOB_SEEK_READ (job);
+
+ switch (op_job->seek_type)
+ {
+ case G_SEEK_CUR:
+ handle->offset += op_job->requested_offset;
+ break;
+ case G_SEEK_SET:
+ handle->offset = op_job->requested_offset;
+ break;
+ case G_SEEK_END:
+ handle->offset = file_size + op_job->requested_offset;
+ break;
+ }
+
+ if (handle->offset < 0)
+ handle->offset = 0;
+ if (handle->offset > file_size)
+ handle->offset = file_size;
+
+ g_vfs_job_seek_read_set_offset (op_job, handle->offset);
+ g_vfs_job_succeeded (job);
+}
+
+static gboolean
+try_seek_on_read (GVfsBackend *backend,
+ GVfsJobSeekRead *job,
+ GVfsBackendHandle _handle,
+ goffset offset,
+ GSeekType type)
+{
+ SftpHandle *handle = _handle;
+ GVfsBackendSftp *op_backend = G_VFS_BACKEND_SFTP (backend);
+ GDataOutputStream *command;
+
+ command = new_command_stream (op_backend,
+ SSH_FXP_FSTAT);
+ put_data_buffer (command, handle->raw_handle);
+
+ queue_command_stream_and_free (op_backend, command, seek_read_fstat_reply, G_VFS_JOB (job), handle);
+
+ return TRUE;
+}
+
+static void
+delete_temp_file (GVfsBackendSftp *backend,
+ SftpHandle *handle,
+ GVfsJob *job)
+{
+ GDataOutputStream *command;
+
+ if (handle->tempname)
+ {
+ command = new_command_stream (backend,
+ SSH_FXP_REMOVE);
+ put_string (command, handle->tempname);
+ queue_command_stream_and_free (backend, command, NULL, job, NULL);
+ }
+}
+
+static void
+close_moved_tempfile (GVfsBackendSftp *backend,
+ int reply_type,
+ GDataInputStream *reply,
+ guint32 len,
+ GVfsJob *job,
+ gpointer user_data)
+{
+ SftpHandle *handle;
+
+ handle = user_data;
+
+ if (reply_type == SSH_FXP_STATUS)
+ result_from_status (job, reply, -1, -1);
+ else
+ g_vfs_job_failed (job, G_IO_ERROR, G_IO_ERROR_FAILED,
+ _("Invalid reply received"));
+
+ /* On failure, don't remove tempfile, since we removed the new original file */
+ sftp_handle_free (handle);
+}
+
+static void
+close_restore_permissions (GVfsBackendSftp *backend,
+ int reply_type,
+ GDataInputStream *reply,
+ guint32 len,
+ GVfsJob *job,
+ gpointer user_data)
+{
+ GDataOutputStream *command;
+ SftpHandle *handle;
+
+ handle = user_data;
+
+ /* Here we don't really care whether or not setting the permissions succeeded
+ or not. We just take the last step and rename the temp file to the
+ actual file */
+ command = new_command_stream (backend,
+ SSH_FXP_RENAME);
+ put_string (command, handle->tempname);
+ put_string (command, handle->filename);
+ queue_command_stream_and_free (backend, command, close_moved_tempfile, G_VFS_JOB (job), handle);
+}
+
+static void
+close_deleted_file (GVfsBackendSftp *backend,
+ int reply_type,
+ GDataInputStream *reply,
+ guint32 len,
+ GVfsJob *job,
+ gpointer user_data)
+{
+ GDataOutputStream *command;
+ GError *error;
+ gboolean res;
+ SftpHandle *handle;
+
+ handle = user_data;
+
+ error = NULL;
+ res = FALSE;
+ if (reply_type == SSH_FXP_STATUS)
+ res = error_from_status (job, reply, -1, -1, &error);
+ else
+ g_set_error_literal (&error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ _("Invalid reply received"));
+
+ if (res)
+ {
+ /* Removed original file, now first try to restore permissions */
+ command = new_command_stream (backend,
+ SSH_FXP_SETSTAT);
+ put_string (command, handle->tempname);
+ g_data_output_stream_put_uint32 (command, SSH_FILEXFER_ATTR_PERMISSIONS, NULL, NULL);
+ g_data_output_stream_put_uint32 (command, handle->permissions, NULL, NULL);
+ queue_command_stream_and_free (backend, command, close_restore_permissions, G_VFS_JOB (job), handle);
+ }
+ else
+ {
+ /* The delete failed, remove any temporary files */
+ delete_temp_file (backend,
+ handle,
+ G_VFS_JOB (job));
+
+ g_vfs_job_failed_from_error (job, error);
+ g_error_free (error);
+ sftp_handle_free (handle);
+ }
+}
+
+static void
+close_moved_file (GVfsBackendSftp *backend,
+ int reply_type,
+ GDataInputStream *reply,
+ guint32 len,
+ GVfsJob *job,
+ gpointer user_data)
+{
+ GDataOutputStream *command;
+ GError *error;
+ gboolean res;
+ SftpHandle *handle;
+
+ handle = user_data;
+
+ error = NULL;
+ res = FALSE;
+ if (reply_type == SSH_FXP_STATUS)
+ res = error_from_status (job, reply, -1, -1, &error);
+ else
+ g_set_error_literal (&error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ _("Invalid reply received"));
+
+ if (res)
+ {
+ /* moved original file to backup, now move new file in place */
+
+ command = new_command_stream (backend,
+ SSH_FXP_RENAME);
+ put_string (command, handle->tempname);
+ put_string (command, handle->filename);
+ queue_command_stream_and_free (backend, command, close_moved_tempfile, G_VFS_JOB (job), handle);
+ }
+ else
+ {
+ /* Move original file to backup name failed, remove any temporary files */
+ delete_temp_file (backend,
+ handle,
+ G_VFS_JOB (job));
+
+ g_vfs_job_failed (job, G_IO_ERROR, G_IO_ERROR_CANT_CREATE_BACKUP,
+ _("Error creating backup file: %s"), error->message);
+ g_error_free (error);
+ sftp_handle_free (handle);
+ }
+}
+
+static void
+close_deleted_backup (GVfsBackendSftp *backend,
+ int reply_type,
+ GDataInputStream *reply,
+ guint32 len,
+ GVfsJob *job,
+ gpointer user_data)
+{
+ SftpHandle *handle;
+ GDataOutputStream *command;
+ char *backup_name;
+
+ /* Ignore result here, if it failed we'll just get a new error when moving over it
+ * This is simpler than ignoring NOEXIST errors
+ */
+
+ handle = user_data;
+
+ command = new_command_stream (backend,
+ SSH_FXP_RENAME);
+ backup_name = g_strconcat (handle->filename, "~", NULL);
+ put_string (command, handle->filename);
+ put_string (command, backup_name);
+ g_free (backup_name);
+ queue_command_stream_and_free (backend, command, close_moved_file, G_VFS_JOB (job), handle);
+}
+
+static void
+close_write_reply (GVfsBackendSftp *backend,
+ int reply_type,
+ GDataInputStream *reply,
+ guint32 len,
+ GVfsJob *job,
+ gpointer user_data)
+{
+ GDataOutputStream *command;
+ GError *error;
+ gboolean res;
+ char *backup_name;
+ SftpHandle *handle;
+
+ handle = user_data;
+
+ error = NULL;
+ res = FALSE;
+ if (reply_type == SSH_FXP_STATUS)
+ res = error_from_status (job, reply, -1, -1, &error);
+ else
+ g_set_error_literal (&error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ _("Invalid reply received"));
+
+ if (res)
+ {
+ if (handle->tempname)
+ {
+ if (handle->make_backup)
+ {
+ command = new_command_stream (backend,
+ SSH_FXP_REMOVE);
+ backup_name = g_strconcat (handle->filename, "~", NULL);
+ put_string (command, backup_name);
+ g_free (backup_name);
+ queue_command_stream_and_free (backend, command, close_deleted_backup, G_VFS_JOB (job), handle);
+ }
+ else
+ {
+ command = new_command_stream (backend,
+ SSH_FXP_REMOVE);
+ put_string (command, handle->filename);
+ queue_command_stream_and_free (backend, command, close_deleted_file, G_VFS_JOB (job), handle);
+ }
+ }
+ else
+ {
+ g_vfs_job_succeeded (job);
+ sftp_handle_free (handle);
+ }
+ }
+ else
+ {
+ /* The close failed, remove any temporary files */
+ delete_temp_file (backend,
+ handle,
+ G_VFS_JOB (job));
+
+ g_vfs_job_failed_from_error (job, error);
+ g_error_free (error);
+
+ sftp_handle_free (handle);
+ }
+}
+
+static void
+close_write_fstat_reply (GVfsBackendSftp *backend,
+ int reply_type,
+ GDataInputStream *reply,
+ guint32 len,
+ GVfsJob *job,
+ gpointer user_data)
+{
+ SftpHandle *handle = user_data;
+ GDataOutputStream *command;
+ GFileInfo *info;
+ const char *etag;
+
+ if (reply_type == SSH_FXP_ATTRS)
+ {
+ info = g_file_info_new ();
+ parse_attributes (backend, info, NULL,
+ reply, NULL);
+ etag = g_file_info_get_etag (info);
+ if (etag)
+ g_vfs_job_close_write_set_etag (G_VFS_JOB_CLOSE_WRITE (job), etag);
+ g_object_unref (info);
+ }
+
+ command = new_command_stream (backend, SSH_FXP_CLOSE);
+ put_data_buffer (command, handle->raw_handle);
+
+ queue_command_stream_and_free (backend, command, close_write_reply, G_VFS_JOB (job), handle);
+}
+
+static gboolean
+try_close_write (GVfsBackend *backend,
+ GVfsJobCloseWrite *job,
+ GVfsBackendHandle _handle)
+{
+ SftpHandle *handle = _handle;
+ GVfsBackendSftp *op_backend = G_VFS_BACKEND_SFTP (backend);
+ GDataOutputStream *command;
+
+ command = new_command_stream (op_backend, SSH_FXP_FSTAT);
+ put_data_buffer (command, handle->raw_handle);
+
+ queue_command_stream_and_free (op_backend, command, close_write_fstat_reply, G_VFS_JOB (job), handle);
+
+ return TRUE;
+}
+
+
+static void
+close_read_reply (GVfsBackendSftp *backend,
+ int reply_type,
+ GDataInputStream *reply,
+ guint32 len,
+ GVfsJob *job,
+ gpointer user_data)
+{
+ SftpHandle *handle;
+
+ handle = user_data;
+
+ if (reply_type == SSH_FXP_STATUS)
+ result_from_status (job, reply, -1, -1);
+ else
+ g_vfs_job_failed (job, G_IO_ERROR, G_IO_ERROR_FAILED,
+ _("Invalid reply received"));
+ sftp_handle_free (handle);
+}
+
+static gboolean
+try_close_read (GVfsBackend *backend,
+ GVfsJobCloseRead *job,
+ GVfsBackendHandle _handle)
+{
+ SftpHandle *handle = _handle;
+ GVfsBackendSftp *op_backend = G_VFS_BACKEND_SFTP (backend);
+ GDataOutputStream *command;
+
+ command = new_command_stream (op_backend, SSH_FXP_CLOSE);
+ put_data_buffer (command, handle->raw_handle);
+
+ queue_command_stream_and_free (op_backend, command, close_read_reply, G_VFS_JOB (job), handle);
+
+ return TRUE;
+}
+
+static void
+create_reply (GVfsBackendSftp *backend,
+ int reply_type,
+ GDataInputStream *reply,
+ guint32 len,
+ GVfsJob *job,
+ gpointer user_data)
+{
+ SftpHandle *handle;
+
+ if (reply_type == SSH_FXP_STATUS)
+ {
+ result_from_status (job, reply, G_IO_ERROR_EXISTS, -1);
+ return;
+ }
+
+ if (reply_type != SSH_FXP_HANDLE)
+ {
+ g_vfs_job_failed (job, G_IO_ERROR, G_IO_ERROR_FAILED,
+ _("Invalid reply received"));
+ return;
+ }
+
+ handle = sftp_handle_new (reply);
+
+ g_vfs_job_open_for_write_set_handle (G_VFS_JOB_OPEN_FOR_WRITE (job), handle);
+ g_vfs_job_open_for_write_set_can_seek (G_VFS_JOB_OPEN_FOR_WRITE (job), TRUE);
+ g_vfs_job_succeeded (job);
+}
+
+static gboolean
+try_create (GVfsBackend *backend,
+ GVfsJobOpenForWrite *job,
+ const char *filename,
+ GFileCreateFlags flags)
+{
+ GVfsBackendSftp *op_backend = G_VFS_BACKEND_SFTP (backend);
+ GDataOutputStream *command;
+
+ command = new_command_stream (op_backend,
+ SSH_FXP_OPEN);
+ put_string (command, filename);
+ g_data_output_stream_put_uint32 (command, SSH_FXF_WRITE|SSH_FXF_CREAT|SSH_FXF_EXCL, NULL, NULL); /* open flags */
+ g_data_output_stream_put_uint32 (command, 0, NULL, NULL); /* Attr flags */
+
+ queue_command_stream_and_free (op_backend, command, create_reply, G_VFS_JOB (job), NULL);
+
+ return TRUE;
+}
+
+static void
+append_to_reply (GVfsBackendSftp *backend,
+ int reply_type,
+ GDataInputStream *reply,
+ guint32 len,
+ GVfsJob *job,
+ gpointer user_data)
+{
+ SftpHandle *handle;
+
+ if (reply_type == SSH_FXP_STATUS)
+ {
+ result_from_status (job, reply, -1, -1);
+ return;
+ }
+
+ if (reply_type != SSH_FXP_HANDLE)
+ {
+ g_vfs_job_failed (job, G_IO_ERROR, G_IO_ERROR_FAILED,
+ _("Invalid reply received"));
+ return;
+ }
+
+ handle = sftp_handle_new (reply);
+
+ g_vfs_job_open_for_write_set_handle (G_VFS_JOB_OPEN_FOR_WRITE (job), handle);
+ g_vfs_job_open_for_write_set_can_seek (G_VFS_JOB_OPEN_FOR_WRITE (job), FALSE);
+ g_vfs_job_succeeded (job);
+}
+
+static gboolean
+try_append_to (GVfsBackend *backend,
+ GVfsJobOpenForWrite *job,
+ const char *filename,
+ GFileCreateFlags flags)
+{
+ GVfsBackendSftp *op_backend = G_VFS_BACKEND_SFTP (backend);
+ GDataOutputStream *command;
+
+ command = new_command_stream (op_backend,
+ SSH_FXP_OPEN);
+ put_string (command, filename);
+ g_data_output_stream_put_uint32 (command, SSH_FXF_WRITE|SSH_FXF_CREAT|SSH_FXF_APPEND, NULL, NULL); /* open flags */
+ g_data_output_stream_put_uint32 (command, 0, NULL, NULL); /* Attr flags */
+
+ queue_command_stream_and_free (op_backend, command, append_to_reply, G_VFS_JOB (job), NULL);
+
+ return TRUE;
+}
+
+typedef struct {
+ guint32 permissions;
+ char *tempname;
+ int temp_count;
+} ReplaceData;
+
+static void
+replace_data_free (ReplaceData *data)
+{
+ g_free (data->tempname);
+ g_slice_free (ReplaceData, data);
+}
+
+static void replace_create_temp (GVfsBackendSftp *backend,
+ GVfsJobOpenForWrite *job);
+
+static void
+replace_create_temp_reply (GVfsBackendSftp *backend,
+ int reply_type,
+ GDataInputStream *reply,
+ guint32 len,
+ GVfsJob *job,
+ gpointer user_data)
+{
+ GVfsJobOpenForWrite *op_job;
+ SftpHandle *handle;
+ ReplaceData *data;
+ GError *error;
+
+ op_job = G_VFS_JOB_OPEN_FOR_WRITE (job);
+ data = G_VFS_JOB (job)->backend_data;
+
+ if (reply_type == SSH_FXP_STATUS)
+ {
+ error = NULL;
+ if (error_from_status (job, reply, G_IO_ERROR_EXISTS, -1, &error))
+ /* Open should not return OK */
+ g_vfs_job_failed (job, G_IO_ERROR, G_IO_ERROR_FAILED,
+ _("Invalid reply received"));
+ else if (error->code == G_IO_ERROR_EXISTS)
+ {
+ /* It was *probably* the EXCL flag failing. I wish we had
+ an actual real error code for that, grumble */
+ g_error_free (error);
+
+ replace_create_temp (backend, op_job);
+ }
+ else
+ {
+ g_vfs_job_failed_from_error (job, error);
+ g_error_free (error);
+ }
+ return;
+ }
+
+ if (reply_type != SSH_FXP_HANDLE)
+ {
+ g_vfs_job_failed (job, G_IO_ERROR, G_IO_ERROR_FAILED,
+ _("Invalid reply received"));
+ return;
+ }
+
+ handle = sftp_handle_new (reply);
+ handle->filename = g_strdup (op_job->filename);
+ handle->tempname = g_strdup (data->tempname);
+ handle->permissions = data->permissions;
+ handle->make_backup = op_job->make_backup;
+
+ g_vfs_job_open_for_write_set_handle (op_job, handle);
+ g_vfs_job_open_for_write_set_can_seek (op_job, TRUE);
+
+ g_vfs_job_succeeded (job);
+}
+
+static void
+random_text (char *s)
+{
+ static const char letters[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
+ static const int NLETTERS = sizeof (letters) - 1;
+ static int counter = 0;
+
+ GTimeVal tv;
+ glong value;
+
+ /* Get some more or less random data. */
+ g_get_current_time (&tv);
+ value = (tv.tv_usec ^ tv.tv_sec) + counter++;
+
+ /* Fill in the random bits. */
+ s[0] = letters[value % NLETTERS];
+ value /= NLETTERS;
+ s[1] = letters[value % NLETTERS];
+ value /= NLETTERS;
+ s[2] = letters[value % NLETTERS];
+ value /= NLETTERS;
+ s[3] = letters[value % NLETTERS];
+ value /= NLETTERS;
+ s[4] = letters[value % NLETTERS];
+ value /= NLETTERS;
+ s[5] = letters[value % NLETTERS];
+}
+
+static void
+replace_create_temp (GVfsBackendSftp *backend,
+ GVfsJobOpenForWrite *job)
+{
+ GVfsBackendSftp *op_backend = G_VFS_BACKEND_SFTP (backend);
+ GDataOutputStream *command;
+ char *dirname;
+ ReplaceData *data;
+ char basename[] = ".giosaveXXXXXX";
+
+ data = G_VFS_JOB (job)->backend_data;
+
+ data->temp_count++;
+
+ if (data->temp_count == 100)
+ {
+ g_vfs_job_failed (G_VFS_JOB (job),
+ G_IO_ERROR, G_IO_ERROR_FAILED,
+ _("Unable to create temporary file"));
+ return;
+ }
+
+ g_free (data->tempname);
+
+ dirname = g_path_get_dirname (job->filename);
+ random_text (basename + 8);
+ data->tempname = g_build_filename (dirname, basename, NULL);
+ g_free (dirname);
+
+ command = new_command_stream (op_backend,
+ SSH_FXP_OPEN);
+ put_string (command, data->tempname);
+ g_data_output_stream_put_uint32 (command, SSH_FXF_WRITE|SSH_FXF_CREAT|SSH_FXF_EXCL, NULL, NULL); /* open flags */
+ g_data_output_stream_put_uint32 (command, SSH_FILEXFER_ATTR_PERMISSIONS, NULL, NULL); /* Attr flags */
+ g_data_output_stream_put_uint32 (command, data->permissions, NULL, NULL);
+ queue_command_stream_and_free (op_backend, command, replace_create_temp_reply, G_VFS_JOB (job), NULL);
+}
+
+static void
+replace_stat_reply (GVfsBackendSftp *backend,
+ int reply_type,
+ GDataInputStream *reply,
+ guint32 len,
+ GVfsJob *job,
+ gpointer user_data)
+{
+ GFileInfo *info;
+ GVfsJobOpenForWrite *op_job;
+ const char *current_etag;
+ guint32 permissions;
+ ReplaceData *data;
+
+ op_job = G_VFS_JOB_OPEN_FOR_WRITE (job);
+
+ permissions = 0644;
+
+ if (reply_type == SSH_FXP_ATTRS)
+ {
+ info = g_file_info_new ();
+ parse_attributes (backend, info, NULL,
+ reply, NULL);
+
+ if (op_job->etag != NULL)
+ {
+ current_etag = g_file_info_get_attribute_string (info, G_FILE_ATTRIBUTE_ETAG_VALUE);
+
+ if (current_etag == NULL ||
+ strcmp (op_job->etag, current_etag) != 0)
+ {
+ g_vfs_job_failed (job,
+ G_IO_ERROR, G_IO_ERROR_WRONG_ETAG,
+ _("The file was externally modified"));
+ g_object_unref (info);
+ return;
+ }
+ }
+
+ if (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_UNIX_MODE))
+ permissions = g_file_info_get_attribute_uint32 (info, G_FILE_ATTRIBUTE_UNIX_MODE) & 0777;
+ }
+
+ data = g_slice_new0 (ReplaceData);
+ data->permissions = permissions;
+ g_vfs_job_set_backend_data (job, data, (GDestroyNotify)replace_data_free);
+
+ replace_create_temp (backend, op_job);
+}
+
+static void
+replace_exclusive_reply (GVfsBackendSftp *backend,
+ int reply_type,
+ GDataInputStream *reply,
+ guint32 len,
+ GVfsJob *job,
+ gpointer user_data)
+{
+ GVfsJobOpenForWrite *op_job;
+ GDataOutputStream *command;
+ SftpHandle *handle;
+ GError *error;
+
+ op_job = G_VFS_JOB_OPEN_FOR_WRITE (job);
+ if (reply_type == SSH_FXP_STATUS)
+ {
+ error = NULL;
+ if (error_from_status (job, reply, G_IO_ERROR_EXISTS, -1, &error))
+ /* Open should not return OK */
+ g_vfs_job_failed (job, G_IO_ERROR, G_IO_ERROR_FAILED,
+ _("Invalid reply received"));
+ else if (error->code == G_IO_ERROR_EXISTS)
+ {
+ /* It was *probably* the EXCL flag failing. I wish we had
+ an actual real error code for that, grumble */
+ g_error_free (error);
+
+ /* Replace existing file code: */
+
+ command = new_command_stream (backend,
+ SSH_FXP_STAT);
+ put_string (command, op_job->filename);
+ queue_command_stream_and_free (backend, command, replace_stat_reply, G_VFS_JOB (job), NULL);
+ }
+ else
+ {
+ g_vfs_job_failed_from_error (job, error);
+ g_error_free (error);
+ }
+ return;
+ }
+
+ if (reply_type != SSH_FXP_HANDLE)
+ {
+ g_vfs_job_failed (job, G_IO_ERROR, G_IO_ERROR_FAILED,
+ _("Invalid reply received"));
+ return;
+ }
+
+ handle = sftp_handle_new (reply);
+
+ g_vfs_job_open_for_write_set_handle (op_job, handle);
+ g_vfs_job_open_for_write_set_can_seek (op_job, TRUE);
+
+ g_vfs_job_succeeded (job);
+}
+
+static gboolean
+try_replace (GVfsBackend *backend,
+ GVfsJobOpenForWrite *job,
+ const char *filename,
+ const char *etag,
+ gboolean make_backup,
+ GFileCreateFlags flags)
+{
+ GVfsBackendSftp *op_backend = G_VFS_BACKEND_SFTP (backend);
+ GDataOutputStream *command;
+
+ command = new_command_stream (op_backend,
+ SSH_FXP_OPEN);
+ put_string (command, filename);
+ g_data_output_stream_put_uint32 (command, SSH_FXF_WRITE|SSH_FXF_CREAT|SSH_FXF_EXCL, NULL, NULL); /* open flags */
+ g_data_output_stream_put_uint32 (command, 0, NULL, NULL); /* Attr flags */
+
+ queue_command_stream_and_free (op_backend, command, replace_exclusive_reply, G_VFS_JOB (job), NULL);
+
+ return TRUE;
+}
+
+static void
+write_reply (GVfsBackendSftp *backend,
+ int reply_type,
+ GDataInputStream *reply,
+ guint32 len,
+ GVfsJob *job,
+ gpointer user_data)
+{
+ SftpHandle *handle;
+
+ handle = user_data;
+
+ if (reply_type == SSH_FXP_STATUS)
+ {
+ if (result_from_status (job, reply, -1, -1))
+ {
+ handle->offset += G_VFS_JOB_WRITE (job)->data_size;
+ }
+ }
+ else
+ g_vfs_job_failed (job, G_IO_ERROR, G_IO_ERROR_FAILED,
+ _("Invalid reply received"));
+}
+
+static gboolean
+try_write (GVfsBackend *backend,
+ GVfsJobWrite *job,
+ GVfsBackendHandle _handle,
+ char *buffer,
+ gsize buffer_size)
+{
+ SftpHandle *handle = _handle;
+ GVfsBackendSftp *op_backend = G_VFS_BACKEND_SFTP (backend);
+ GDataOutputStream *command;
+
+ command = new_command_stream (op_backend,
+ SSH_FXP_WRITE);
+ put_data_buffer (command, handle->raw_handle);
+ g_data_output_stream_put_uint64 (command, handle->offset, NULL, NULL);
+ g_data_output_stream_put_uint32 (command, buffer_size, NULL, NULL);
+ /* Ideally we shouldn't do this copy, but doing the writes as multiple writes
+ caused problems on the read side in openssh */
+ g_output_stream_write_all (G_OUTPUT_STREAM (command),
+ buffer, buffer_size,
+ NULL, NULL, NULL);
+
+ queue_command_stream_and_free (op_backend, command, write_reply, G_VFS_JOB (job), handle);
+
+ /* We always write the full size (on success) */
+ g_vfs_job_write_set_written_size (job, buffer_size);
+
+ return TRUE;
+}
+
+static void
+seek_write_fstat_reply (GVfsBackendSftp *backend,
+ int reply_type,
+ GDataInputStream *reply,
+ guint32 len,
+ GVfsJob *job,
+ gpointer user_data)
+{
+ SftpHandle *handle;
+ GFileInfo *info;
+ goffset file_size;
+ GVfsJobSeekWrite *op_job;
+
+ handle = user_data;
+
+ if (reply_type == SSH_FXP_STATUS)
+ {
+ result_from_status (job, reply, -1, -1);
+ return;
+ }
+
+ if (reply_type != SSH_FXP_ATTRS)
+ {
+ g_vfs_job_failed (job, G_IO_ERROR, G_IO_ERROR_FAILED,
+ _("Invalid reply received"));
+ return;
+ }
+
+ info = g_file_info_new ();
+ parse_attributes (backend, info, NULL,
+ reply, NULL);
+ file_size = g_file_info_get_size (info);
+ g_object_unref (info);
+
+ op_job = G_VFS_JOB_SEEK_WRITE (job);
+
+ switch (op_job->seek_type)
+ {
+ case G_SEEK_CUR:
+ handle->offset += op_job->requested_offset;
+ break;
+ case G_SEEK_SET:
+ handle->offset = op_job->requested_offset;
+ break;
+ case G_SEEK_END:
+ handle->offset = file_size + op_job->requested_offset;
+ break;
+ }
+
+ if (handle->offset < 0)
+ handle->offset = 0;
+ if (handle->offset > file_size)
+ handle->offset = file_size;
+
+ g_vfs_job_seek_write_set_offset (op_job, handle->offset);
+ g_vfs_job_succeeded (job);
+}
+
+static gboolean
+try_seek_on_write (GVfsBackend *backend,
+ GVfsJobSeekWrite *job,
+ GVfsBackendHandle _handle,
+ goffset offset,
+ GSeekType type)
+{
+ SftpHandle *handle = _handle;
+ GVfsBackendSftp *op_backend = G_VFS_BACKEND_SFTP (backend);
+ GDataOutputStream *command;
+
+ command = new_command_stream (op_backend,
+ SSH_FXP_FSTAT);
+ put_data_buffer (command, handle->raw_handle);
+
+ queue_command_stream_and_free (op_backend, command, seek_write_fstat_reply, G_VFS_JOB (job), handle);
+
+ return TRUE;
+}
+
+typedef struct {
+ DataBuffer *handle;
+ int outstanding_requests;
+} ReadDirData;
+
+static
+void
+read_dir_data_free (ReadDirData *data)
+{
+ data_buffer_free (data->handle);
+ g_slice_free (ReadDirData, data);
+}
+
+static void
+read_dir_readlink_reply (GVfsBackendSftp *backend,
+ int reply_type,
+ GDataInputStream *reply,
+ guint32 len,
+ GVfsJob *job,
+ gpointer user_data)
+{
+ ReadDirData *data;
+ GFileInfo *info = user_data;
+ char *target;
+
+ data = job->backend_data;
+
+ if (reply_type == SSH_FXP_NAME)
+ {
+ /* count = */ (void) g_data_input_stream_read_uint32 (reply, NULL, NULL);
+
+ target = read_string (reply, NULL);
+ if (target)
+ {
+ g_file_info_set_symlink_target (info, target);
+ g_free (target);
+ }
+ }
+
+ g_vfs_job_enumerate_add_info (G_VFS_JOB_ENUMERATE (job), info);
+ g_object_unref (info);
+
+ if (--data->outstanding_requests == 0)
+ g_vfs_job_enumerate_done (G_VFS_JOB_ENUMERATE (job));
+}
+
+static void
+read_dir_got_stat_info (GVfsBackendSftp *backend,
+ GVfsJob *job,
+ GFileInfo *info)
+{
+ GVfsJobEnumerate *enum_job;
+ GDataOutputStream *command;
+ ReadDirData *data;
+ char *abs_name;
+
+ data = job->backend_data;
+
+ enum_job = G_VFS_JOB_ENUMERATE (job);
+
+ if (g_file_attribute_matcher_matches (enum_job->attribute_matcher,
+ G_FILE_ATTRIBUTE_STANDARD_SYMLINK_TARGET))
+ {
+ data->outstanding_requests++;
+ command = new_command_stream (backend,
+ SSH_FXP_READLINK);
+ abs_name = g_build_filename (enum_job->filename, g_file_info_get_name (info), NULL);
+ put_string (command, abs_name);
+ g_free (abs_name);
+ queue_command_stream_and_free (backend, command, read_dir_readlink_reply, G_VFS_JOB (job), g_object_ref (info));
+ }
+ else
+ g_vfs_job_enumerate_add_info (enum_job, info);
+}
+
+
+static void
+read_dir_symlink_reply (GVfsBackendSftp *backend,
+ int reply_type,
+ GDataInputStream *reply,
+ guint32 len,
+ GVfsJob *job,
+ gpointer user_data)
+{
+ const char *name;
+ GFileInfo *info;
+ GFileInfo *lstat_info;
+ ReadDirData *data;
+
+ lstat_info = user_data;
+ name = g_file_info_get_name (lstat_info);
+ data = job->backend_data;
+
+ if (reply_type == SSH_FXP_ATTRS)
+ {
+ info = g_file_info_new ();
+ g_file_info_set_name (info, name);
+ g_file_info_set_is_symlink (info, TRUE);
+
+ parse_attributes (backend, info, name, reply, G_VFS_JOB_ENUMERATE (job)->attribute_matcher);
+
+ read_dir_got_stat_info (backend, job, info);
+
+ g_object_unref (info);
+ }
+ else
+ read_dir_got_stat_info (backend, job, lstat_info);
+
+ g_object_unref (lstat_info);
+
+ if (--data->outstanding_requests == 0)
+ g_vfs_job_enumerate_done (G_VFS_JOB_ENUMERATE (job));
+}
+
+static void
+read_dir_reply (GVfsBackendSftp *backend,
+ int reply_type,
+ GDataInputStream *reply,
+ guint32 len,
+ GVfsJob *job,
+ gpointer user_data)
+{
+ GVfsJobEnumerate *enum_job;
+ guint32 count;
+ int i;
+ GDataOutputStream *command;
+ ReadDirData *data;
+
+ data = job->backend_data;
+ enum_job = G_VFS_JOB_ENUMERATE (job);
+
+ if (reply_type != SSH_FXP_NAME)
+ {
+ /* Ignore all error, including the expected END OF FILE.
+ * Real errors are expected in open_dir anyway */
+
+ /* Close handle */
+
+ command = new_command_stream (backend,
+ SSH_FXP_CLOSE);
+ put_data_buffer (command, data->handle);
+ queue_command_stream_and_free (backend, command, NULL, G_VFS_JOB (job), NULL);
+
+ if (--data->outstanding_requests == 0)
+ g_vfs_job_enumerate_done (enum_job);
+
+ return;
+ }
+
+ count = g_data_input_stream_read_uint32 (reply, NULL, NULL);
+ for (i = 0; i < count; i++)
+ {
+ GFileInfo *info;
+ char *name;
+ char *longname;
+ char *abs_name;
+
+ info = g_file_info_new ();
+ name = read_string (reply, NULL);
+ g_file_info_set_name (info, name);
+
+ longname = read_string (reply, NULL);
+ g_free (longname);
+
+ parse_attributes (backend, info, name, reply, enum_job->attribute_matcher);
+
+ if (g_file_info_get_file_type (info) == G_FILE_TYPE_SYMBOLIC_LINK &&
+ ! (enum_job->flags & G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS))
+ {
+ /* Default (at least for openssh) is for readdir to not follow symlinks.
+ This was a symlink, and follow links was requested, so we need to manually follow it */
+ command = new_command_stream (backend,
+ SSH_FXP_STAT);
+ abs_name = g_build_filename (enum_job->filename, name, NULL);
+ put_string (command, abs_name);
+ g_free (abs_name);
+
+ queue_command_stream_and_free (backend, command, read_dir_symlink_reply, G_VFS_JOB (job), g_object_ref (info));
+ data->outstanding_requests ++;
+ }
+ else if (strcmp (".", name) != 0 &&
+ strcmp ("..", name) != 0)
+ read_dir_got_stat_info (backend, job, info);
+
+ g_object_unref (info);
+ g_free (name);
+ }
+
+ command = new_command_stream (backend,
+ SSH_FXP_READDIR);
+ put_data_buffer (command, data->handle);
+ queue_command_stream_and_free (backend, command, read_dir_reply, G_VFS_JOB (job), NULL);
+}
+
+static void
+open_dir_reply (GVfsBackendSftp *backend,
+ int reply_type,
+ GDataInputStream *reply,
+ guint32 len,
+ GVfsJob *job,
+ gpointer user_data)
+{
+ GVfsBackendSftp *op_backend = G_VFS_BACKEND_SFTP (backend);
+ GDataOutputStream *command;
+ ReadDirData *data;
+
+ data = job->backend_data;
+
+ if (reply_type == SSH_FXP_STATUS)
+ {
+ result_from_status (job, reply, -1, -1);
+ return;
+ }
+
+ if (reply_type != SSH_FXP_HANDLE)
+ {
+ g_vfs_job_failed (job, G_IO_ERROR, G_IO_ERROR_FAILED,
+ _("Invalid reply received"));
+ return;
+ }
+
+ g_vfs_job_succeeded (G_VFS_JOB (job));
+
+ data->handle = read_data_buffer (reply);
+
+ command = new_command_stream (op_backend,
+ SSH_FXP_READDIR);
+ put_data_buffer (command, data->handle);
+
+ data->outstanding_requests = 1;
+
+ queue_command_stream_and_free (op_backend, command, read_dir_reply, G_VFS_JOB (job), NULL);
+}
+
+static gboolean
+try_enumerate (GVfsBackend *backend,
+ GVfsJobEnumerate *job,
+ const char *filename,
+ GFileAttributeMatcher *attribute_matcher,
+ GFileQueryInfoFlags flags)
+{
+ GVfsBackendSftp *op_backend = G_VFS_BACKEND_SFTP (backend);
+ GDataOutputStream *command;
+ ReadDirData *data;
+
+ data = g_slice_new0 (ReadDirData);
+
+ g_vfs_job_set_backend_data (G_VFS_JOB (job), data, (GDestroyNotify)read_dir_data_free);
+ command = new_command_stream (op_backend,
+ SSH_FXP_OPENDIR);
+ put_string (command, filename);
+
+ queue_command_stream_and_free (op_backend, command, open_dir_reply, G_VFS_JOB (job), NULL);
+
+ return TRUE;
+}
+
+static void
+query_info_reply (GVfsBackendSftp *backend,
+ MultiReply *replies,
+ int n_replies,
+ GVfsJob *job,
+ gpointer user_data)
+{
+ char *basename;
+ int i;
+ MultiReply *lstat_reply, *reply;
+ GFileInfo *lstat_info;
+ GVfsJobQueryInfo *op_job;
+
+ op_job = G_VFS_JOB_QUERY_INFO (job);
+
+ i = 0;
+ lstat_reply = &replies[i++];
+
+ if (lstat_reply->type == SSH_FXP_STATUS)
+ {
+ result_from_status (job, lstat_reply->data, -1, -1);
+ return;
+ }
+ else if (lstat_reply->type != SSH_FXP_ATTRS)
+ {
+ g_vfs_job_failed (job,
+ G_IO_ERROR, G_IO_ERROR_FAILED,
+ "%s", _("Invalid reply received"));
+ return;
+ }
+
+ basename = NULL;
+ if (strcmp (op_job->filename, "/") != 0)
+ basename = g_path_get_basename (op_job->filename);
+
+ if (op_job->flags & G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS)
+ {
+ parse_attributes (backend, op_job->file_info, basename,
+ lstat_reply->data, op_job->attribute_matcher);
+ }
+ else
+ {
+ /* Look at stat results */
+ reply = &replies[i++];
+
+ if (reply->type == SSH_FXP_ATTRS)
+ {
+ parse_attributes (backend, op_job->file_info, basename,
+ reply->data, op_job->attribute_matcher);
+
+
+ lstat_info = g_file_info_new ();
+ parse_attributes (backend, lstat_info, basename,
+ lstat_reply->data, op_job->attribute_matcher);
+ if (g_file_info_get_is_symlink (lstat_info))
+ g_file_info_set_is_symlink (op_job->file_info, TRUE);
+ g_object_unref (lstat_info);
+ }
+ else
+ {
+ /* Broken symlink, use lstat data */
+ parse_attributes (backend, op_job->file_info, basename,
+ lstat_reply->data, op_job->attribute_matcher);
+ }
+
+ }
+
+ g_free (basename);
+
+ if (g_file_attribute_matcher_matches (op_job->attribute_matcher,
+ G_FILE_ATTRIBUTE_STANDARD_SYMLINK_TARGET))
+ {
+ /* Look at readlink results */
+ reply = &replies[i++];
+
+ if (reply->type == SSH_FXP_NAME)
+ {
+ char *symlink_target;
+ guint32 count;
+
+ count = g_data_input_stream_read_uint32 (reply->data, NULL, NULL);
+ symlink_target = read_string (reply->data, NULL);
+ g_file_info_set_symlink_target (op_job->file_info, symlink_target);
+ g_free (symlink_target);
+ }
+ }
+
+ g_vfs_job_succeeded (G_VFS_JOB (job));
+}
+
+static gboolean
+try_query_info (GVfsBackend *backend,
+ GVfsJobQueryInfo *job,
+ const char *filename,
+ GFileQueryInfoFlags flags,
+ GFileInfo *info,
+ GFileAttributeMatcher *matcher)
+{
+ GVfsBackendSftp *op_backend = G_VFS_BACKEND_SFTP (backend);
+ GDataOutputStream *commands[3];
+ GDataOutputStream *command;
+ int n_commands;
+
+ n_commands = 0;
+
+ command = commands[n_commands++] =
+ new_command_stream (op_backend,
+ SSH_FXP_LSTAT);
+ put_string (command, filename);
+
+ if (! (job->flags & G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS))
+ {
+ command = commands[n_commands++] =
+ new_command_stream (op_backend,
+ SSH_FXP_STAT);
+ put_string (command, filename);
+ }
+
+ if (g_file_attribute_matcher_matches (job->attribute_matcher,
+ G_FILE_ATTRIBUTE_STANDARD_SYMLINK_TARGET))
+ {
+ command = commands[n_commands++] =
+ new_command_stream (op_backend,
+ SSH_FXP_READLINK);
+ put_string (command, filename);
+ }
+
+ queue_command_streams_and_free (op_backend, commands, n_commands, query_info_reply, G_VFS_JOB (job), NULL);
+
+ return TRUE;
+}
+
+static void
+move_reply (GVfsBackendSftp *backend,
+ int reply_type,
+ GDataInputStream *reply,
+ guint32 len,
+ GVfsJob *job,
+ gpointer user_data)
+{
+ goffset *file_size;
+
+ /* on any unknown error, return NOT_SUPPORTED to get the fallback implementation */
+ if (reply_type == SSH_FXP_STATUS)
+ {
+ if (failure_from_status (job, reply, G_IO_ERROR_NOT_SUPPORTED, -1))
+ {
+ /* Succeeded, report file size */
+ file_size = job->backend_data;
+ if (file_size != NULL)
+ g_vfs_job_move_progress_callback (*file_size, *file_size, job);
+ g_vfs_job_succeeded (job);
+ }
+ }
+ else
+ g_vfs_job_failed (job, G_IO_ERROR, G_IO_ERROR_FAILED,
+ _("Invalid reply received"));
+}
+
+static void
+move_do_rename (GVfsBackendSftp *backend,
+ GVfsJob *job)
+{
+ GVfsJobMove *op_job;
+ GDataOutputStream *command;
+
+ op_job = G_VFS_JOB_MOVE (job);
+
+ command = new_command_stream (backend,
+ SSH_FXP_RENAME);
+ put_string (command, op_job->source);
+ put_string (command, op_job->destination);
+
+ queue_command_stream_and_free (backend, command, move_reply, G_VFS_JOB (job), NULL);
+}
+
+static void
+move_delete_target_reply (GVfsBackendSftp *backend,
+ int reply_type,
+ GDataInputStream *reply,
+ guint32 len,
+ GVfsJob *job,
+ gpointer user_data)
+{
+ if (reply_type == SSH_FXP_STATUS)
+ {
+ if (failure_from_status (job, reply, -1, -1))
+ move_do_rename (backend, job);
+ }
+ else
+ g_vfs_job_failed (job, G_IO_ERROR, G_IO_ERROR_FAILED,
+ _("Invalid reply received"));
+}
+
+
+static void
+move_lstat_reply (GVfsBackendSftp *backend,
+ MultiReply *replies,
+ int n_replies,
+ GVfsJob *job,
+ gpointer user_data)
+{
+ GVfsJobMove *op_job;
+ gboolean destination_exist, source_is_dir, dest_is_dir;
+ GDataOutputStream *command;
+ GFileInfo *info;
+ goffset *file_size;
+
+ op_job = G_VFS_JOB_MOVE (job);
+
+ if (replies[0].type == SSH_FXP_STATUS)
+ {
+ result_from_status (job, replies[0].data, -1, -1);
+ return;
+ }
+ else if (replies[0].type != SSH_FXP_ATTRS)
+ {
+ g_vfs_job_failed (job,
+ G_IO_ERROR, G_IO_ERROR_FAILED,
+ "%s", _("Invalid reply received"));
+ return;
+ }
+
+ info = g_file_info_new ();
+ parse_attributes (backend, info, NULL,
+ replies[0].data, NULL);
+ source_is_dir = g_file_info_get_file_type (info) == G_FILE_TYPE_DIRECTORY;
+ file_size = g_new (goffset, 1);
+ *file_size = g_file_info_get_size (info);
+ g_vfs_job_set_backend_data (G_VFS_JOB (job), file_size, g_free);
+ g_object_unref (info);
+
+ destination_exist = FALSE;
+ if (replies[1].type == SSH_FXP_ATTRS)
+ {
+ destination_exist = TRUE; /* Target file exists */
+
+ info = g_file_info_new ();
+ parse_attributes (backend, info, NULL,
+ replies[1].data, NULL);
+ dest_is_dir = g_file_info_get_file_type (info) == G_FILE_TYPE_DIRECTORY;
+ g_object_unref (info);
+
+ if (op_job->flags & G_FILE_COPY_OVERWRITE)
+ {
+
+ /* Always fail on dirs, even with overwrite */
+ if (dest_is_dir)
+ {
+ if (source_is_dir)
+ g_vfs_job_failed (job,
+ G_IO_ERROR,
+ G_IO_ERROR_WOULD_MERGE,
+ _("Can't move directory over directory"));
+ else
+ g_vfs_job_failed (job,
+ G_IO_ERROR,
+ G_IO_ERROR_IS_DIRECTORY,
+ _("File is directory"));
+ return;
+ }
+ }
+ else
+ {
+ g_vfs_job_failed (G_VFS_JOB (job),
+ G_IO_ERROR,
+ G_IO_ERROR_EXISTS,
+ _("Target file already exists"));
+ return;
+ }
+ }
+
+ /* TODO: Check flags & G_FILE_COPY_BACKUP */
+
+ if (destination_exist && (op_job->flags & G_FILE_COPY_OVERWRITE))
+ {
+ command = new_command_stream (backend,
+ SSH_FXP_REMOVE);
+ put_string (command, op_job->destination);
+ queue_command_stream_and_free (backend, command, move_delete_target_reply, G_VFS_JOB (job), NULL);
+ return;
+ }
+
+ move_do_rename (backend, job);
+}
+
+
+static gboolean
+try_move (GVfsBackend *backend,
+ GVfsJobMove *job,
+ const char *source,
+ const char *destination,
+ GFileCopyFlags flags,
+ GFileProgressCallback progress_callback,
+ gpointer progress_callback_data)
+{
+ GVfsBackendSftp *op_backend = G_VFS_BACKEND_SFTP (backend);
+ GDataOutputStream *command;
+ GDataOutputStream *commands[2];
+
+ command = commands[0] =
+ new_command_stream (op_backend,
+ SSH_FXP_LSTAT);
+ put_string (command, source);
+
+ command = commands[1] =
+ new_command_stream (op_backend,
+ SSH_FXP_LSTAT);
+ put_string (command, destination);
+
+ queue_command_streams_and_free (op_backend, commands, 2, move_lstat_reply, G_VFS_JOB (job), NULL);
+
+ return TRUE;
+}
+
+static void
+set_display_name_reply (GVfsBackendSftp *backend,
+ int reply_type,
+ GDataInputStream *reply,
+ guint32 len,
+ GVfsJob *job,
+ gpointer user_data)
+{
+ if (reply_type == SSH_FXP_STATUS)
+ result_from_status (job, reply, -1, -1);
+ else
+ g_vfs_job_failed (job, G_IO_ERROR, G_IO_ERROR_FAILED,
+ _("Invalid reply received"));
+}
+
+static gboolean
+try_set_display_name (GVfsBackend *backend,
+ GVfsJobSetDisplayName *job,
+ const char *filename,
+ const char *display_name)
+{
+ GVfsBackendSftp *op_backend = G_VFS_BACKEND_SFTP (backend);
+ GDataOutputStream *command;
+ char *dirname, *basename, *new_name;
+
+ /* We use the same setting as for local files. Can't really
+ * do better, since there is no way in this version of sftp to find out
+ * the remote charset encoding
+ */
+
+ dirname = g_path_get_dirname (filename);
+ basename = g_filename_from_utf8 (display_name, -1, NULL, NULL, NULL);
+ if (basename == NULL)
+ basename = g_strdup (display_name);
+ new_name = g_build_filename (dirname, basename, NULL);
+ g_free (dirname);
+ g_free (basename);
+
+ g_vfs_job_set_display_name_set_new_path (job,
+ new_name);
+
+ command = new_command_stream (op_backend,
+ SSH_FXP_RENAME);
+ put_string (command, filename);
+ put_string (command, new_name);
+
+ queue_command_stream_and_free (op_backend, command, set_display_name_reply, G_VFS_JOB (job), NULL);
+
+ g_free (new_name);
+
+ return TRUE;
+}
+
+
+static void
+make_symlink_reply (GVfsBackendSftp *backend,
+ int reply_type,
+ GDataInputStream *reply,
+ guint32 len,
+ GVfsJob *job,
+ gpointer user_data)
+{
+ if (reply_type == SSH_FXP_STATUS)
+ result_from_status (job, reply, -1, -1);
+ else
+ g_vfs_job_failed (job, G_IO_ERROR, G_IO_ERROR_FAILED,
+ _("Invalid reply received"));
+}
+
+static gboolean
+try_make_symlink (GVfsBackend *backend,
+ GVfsJobMakeSymlink *job,
+ const char *filename,
+ const char *symlink_value)
+{
+ GVfsBackendSftp *op_backend = G_VFS_BACKEND_SFTP (backend);
+ GDataOutputStream *command;
+
+ command = new_command_stream (op_backend,
+ SSH_FXP_SYMLINK);
+ put_string (command, filename);
+ put_string (command, symlink_value);
+
+ queue_command_stream_and_free (op_backend, command, make_symlink_reply, G_VFS_JOB (job), NULL);
+
+ return TRUE;
+}
+
+static void
+make_directory_reply (GVfsBackendSftp *backend,
+ int reply_type,
+ GDataInputStream *reply,
+ guint32 len,
+ GVfsJob *job,
+ gpointer user_data)
+{
+ if (reply_type == SSH_FXP_STATUS)
+ result_from_status (job, reply, -1, -1);
+ else
+ g_vfs_job_failed (job, G_IO_ERROR, G_IO_ERROR_FAILED,
+ _("Invalid reply received"));
+}
+
+static gboolean
+try_make_directory (GVfsBackend *backend,
+ GVfsJobMakeDirectory *job,
+ const char *filename)
+{
+ GVfsBackendSftp *op_backend = G_VFS_BACKEND_SFTP (backend);
+ GDataOutputStream *command;
+
+ command = new_command_stream (op_backend,
+ SSH_FXP_MKDIR);
+ put_string (command, filename);
+ /* No file info - flag 0 */
+ g_data_output_stream_put_uint32 (command, 0, NULL, NULL);
+
+ queue_command_stream_and_free (op_backend, command, make_directory_reply, G_VFS_JOB (job), NULL);
+
+ return TRUE;
+}
+
+static void
+delete_remove_reply (GVfsBackendSftp *backend,
+ int reply_type,
+ GDataInputStream *reply,
+ guint32 len,
+ GVfsJob *job,
+ gpointer user_data)
+{
+ if (reply_type == SSH_FXP_STATUS)
+ result_from_status (job, reply, -1, -1);
+ else
+ g_vfs_job_failed (job, G_IO_ERROR, G_IO_ERROR_FAILED,
+ _("Invalid reply received"));
+}
+
+static void
+delete_rmdir_reply (GVfsBackendSftp *backend,
+ int reply_type,
+ GDataInputStream *reply,
+ guint32 len,
+ GVfsJob *job,
+ gpointer user_data)
+{
+ if (reply_type == SSH_FXP_STATUS)
+ result_from_status (job, reply, G_IO_ERROR_NOT_EMPTY, -1);
+ else
+ g_vfs_job_failed (job, G_IO_ERROR, G_IO_ERROR_FAILED,
+ _("Invalid reply received"));
+}
+
+static void
+delete_lstat_reply (GVfsBackendSftp *backend,
+ int reply_type,
+ GDataInputStream *reply,
+ guint32 len,
+ GVfsJob *job,
+ gpointer user_data)
+{
+ if (reply_type == SSH_FXP_STATUS)
+ result_from_status (job, reply, -1, -1);
+ else if (reply_type != SSH_FXP_ATTRS)
+ g_vfs_job_failed (job, G_IO_ERROR, G_IO_ERROR_FAILED,
+ _("Invalid reply received"));
+ else
+ {
+ GFileInfo *info;
+ GDataOutputStream *command;
+
+ info = g_file_info_new ();
+ parse_attributes (backend, info, NULL, reply, NULL);
+
+ if (g_file_info_get_file_type (info) == G_FILE_TYPE_DIRECTORY)
+ {
+ command = new_command_stream (backend,
+ SSH_FXP_RMDIR);
+ put_string (command, G_VFS_JOB_DELETE (job)->filename);
+ queue_command_stream_and_free (backend, command, delete_rmdir_reply, G_VFS_JOB (job), NULL);
+ }
+ else
+ {
+ command = new_command_stream (backend,
+ SSH_FXP_REMOVE);
+ put_string (command, G_VFS_JOB_DELETE (job)->filename);
+ queue_command_stream_and_free (backend, command, delete_remove_reply, G_VFS_JOB (job), NULL);
+ }
+
+ g_object_unref (info);
+ }
+}
+
+static gboolean
+try_delete (GVfsBackend *backend,
+ GVfsJobDelete *job,
+ const char *filename)
+{
+ GVfsBackendSftp *op_backend = G_VFS_BACKEND_SFTP (backend);
+ GDataOutputStream *command;
+
+ command = new_command_stream (op_backend,
+ SSH_FXP_LSTAT);
+ put_string (command, filename);
+ queue_command_stream_and_free (op_backend, command, delete_lstat_reply, G_VFS_JOB (job), NULL);
+
+ return TRUE;
+}
+
+static gboolean
+try_query_settable_attributes (GVfsBackend *backend,
+ GVfsJobQueryAttributes *job,
+ const char *filename)
+{
+ GFileAttributeInfoList *list;
+
+ list = g_file_attribute_info_list_new ();
+
+ g_file_attribute_info_list_add (list,
+ G_FILE_ATTRIBUTE_UNIX_MODE,
+ G_FILE_ATTRIBUTE_TYPE_UINT32,
+ G_FILE_ATTRIBUTE_INFO_COPY_WITH_FILE |
+ G_FILE_ATTRIBUTE_INFO_COPY_WHEN_MOVED);
+
+ g_vfs_job_query_attributes_set_list (job, list);
+ g_vfs_job_succeeded (G_VFS_JOB (job));
+ g_file_attribute_info_list_unref (list);
+
+ return TRUE;
+}
+
+static void
+set_attribute_reply (GVfsBackendSftp *backend,
+ int reply_type,
+ GDataInputStream *reply,
+ guint32 len,
+ GVfsJob *job,
+ gpointer user_data)
+{
+ if (reply_type == SSH_FXP_STATUS)
+ result_from_status (job, reply, -1, -1);
+ else
+ g_vfs_job_failed (job, G_IO_ERROR, G_IO_ERROR_FAILED,
+ _("Invalid reply received"));
+}
+
+static gboolean
+try_set_attribute (GVfsBackend *backend,
+ GVfsJobSetAttribute *job,
+ const char *filename,
+ const char *attribute,
+ GFileAttributeType type,
+ gpointer value_p,
+ GFileQueryInfoFlags flags)
+{
+ GVfsBackendSftp *op_backend = G_VFS_BACKEND_SFTP (backend);
+ GDataOutputStream *command;
+
+ if (strcmp (attribute, G_FILE_ATTRIBUTE_UNIX_MODE) != 0)
+ {
+ g_vfs_job_failed (G_VFS_JOB (job),
+ G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
+ _("Operation unsupported"));
+ return TRUE;
+ }
+
+ command = new_command_stream (op_backend,
+ SSH_FXP_SETSTAT);
+ put_string (command, filename);
+ g_data_output_stream_put_uint32 (command, SSH_FILEXFER_ATTR_PERMISSIONS, NULL, NULL);
+ g_data_output_stream_put_uint32 (command, (*(guint32 *)value_p) & 0777, NULL, NULL);
+ queue_command_stream_and_free (op_backend, command, set_attribute_reply, G_VFS_JOB (job), NULL);
+
+ return TRUE;
+}
+
+static void
+g_vfs_backend_sftp_class_init (GVfsBackendSftpClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+ GVfsBackendClass *backend_class = G_VFS_BACKEND_CLASS (klass);
+
+ id_q = g_quark_from_static_string ("command-id");
+
+ gobject_class->finalize = g_vfs_backend_sftp_finalize;
+
+ backend_class->mount = do_mount;
+ backend_class->try_mount = try_mount;
+ backend_class->try_open_for_read = try_open_for_read;
+ backend_class->try_read = try_read;
+ backend_class->try_seek_on_read = try_seek_on_read;
+ backend_class->try_close_read = try_close_read;
+ backend_class->try_close_write = try_close_write;
+ backend_class->try_query_info = try_query_info;
+ backend_class->try_enumerate = try_enumerate;
+ backend_class->try_create = try_create;
+ backend_class->try_append_to = try_append_to;
+ backend_class->try_replace = try_replace;
+ backend_class->try_write = try_write;
+ backend_class->try_seek_on_write = try_seek_on_write;
+ backend_class->try_move = try_move;
+ backend_class->try_make_symlink = try_make_symlink;
+ backend_class->try_make_directory = try_make_directory;
+ backend_class->try_delete = try_delete;
+ backend_class->try_set_display_name = try_set_display_name;
+ backend_class->try_query_settable_attributes = try_query_settable_attributes;
+ backend_class->try_set_attribute = try_set_attribute;
+}