1 /* GIO - GLib Input, Output and Streaming Library
2 *
3 * Copyright (C) 2006-2007 Red Hat, Inc.
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General
16 * Public License along with this library; if not, write to the
17 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18 * Boston, MA 02110-1301, USA.
19 *
20 * Author: Alexander Larsson <alexl@redhat.com>
21 */
22
23
24 #include <config.h>
25
26 #include <stdlib.h>
27 #include <sys/poll.h>
28 #include <sys/types.h>
29 #include <sys/stat.h>
30 #include <sys/time.h>
31 #include <errno.h>
32 #include <unistd.h>
33 #include <fcntl.h>
34 #include <string.h>
35
36 #include <glib/gstdio.h>
37 #include <glib/gi18n.h>
38 #include <gio/gio.h>
39 #include <gio/gunixinputstream.h>
40 #include <gio/gunixoutputstream.h>
41
42 #include "gvfsicon.h"
43
44 #include "gvfsbackendsftp.h"
45 #include "gvfsjobopenforread.h"
46 #include "gvfsjobopeniconforread.h"
47 #include "gvfsjobmount.h"
48 #include "gvfsjobread.h"
49 #include "gvfsjobseekread.h"
50 #include "gvfsjobopenforwrite.h"
51 #include "gvfsjobwrite.h"
52 #include "gvfsjobclosewrite.h"
53 #include "gvfsjobseekwrite.h"
54 #include "gvfsjobtruncate.h"
55 #include "gvfsjobsetdisplayname.h"
56 #include "gvfsjobqueryinfo.h"
57 #include "gvfsjobqueryinforead.h"
58 #include "gvfsjobqueryinfowrite.h"
59 #include "gvfsjobmove.h"
60 #include "gvfsjobdelete.h"
61 #include "gvfsjobqueryfsinfo.h"
62 #include "gvfsjobqueryattributes.h"
63 #include "gvfsjobenumerate.h"
64 #include "gvfsjobmakedirectory.h"
65 #include "gvfsjobprogress.h"
66 #include "gvfsjobpush.h"
67 #include "gvfsjobpull.h"
68 #include "gvfsjobsetattribute.h"
69 #include "gvfsdaemonprotocol.h"
70 #include "gvfsutils.h"
71 #include "gvfskeyring.h"
72 #include <gvfsutils.h>
73 #include "sftp.h"
74 #include "pty_open.h"
75
76 /* TODO for sftp:
77 * Implement can_delete & can_rename
78 * fstat
79 */
80
81 #if defined(HAVE_GRANTPT) || defined(HAVE_OPENPTY)
82 /* We only use this on systems with unix98 or BSD ptys */
83 #define USE_PTY 1
84 #endif
85
86 #define SFTP_READ_TIMEOUT 40 /* seconds */
87
88 /*
89 * All servers SHOULD support packets of at least 34000 bytes (where the packet
90 * size refers to the full length, including the header above). This should
91 * allow for reads and writes of at most 32768 bytes. For more details, see
92 * draft-ietf-secsh-filexfer-02.txt.
93 */
94 #define MAX_BUFFER_SIZE 32768
95
96 static GQuark id_q;
97
98 typedef enum {
99 SFTP_EXT_OPENSSH_STATVFS,
100 } SFTPServerExtensions;
101
102 typedef enum {
103 SFTP_VENDOR_INVALID = 0,
104 SFTP_VENDOR_OPENSSH,
105 SFTP_VENDOR_SSH
106 } SFTPClientVendor;
107
108 typedef struct _MultiReply MultiReply;
109
110 typedef void (*ReplyCallback) (GVfsBackendSftp *backend,
111 int reply_type,
112 GDataInputStream *reply,
113 guint32 len,
114 GVfsJob *job,
115 gpointer user_data);
116
117 typedef void (*MultiReplyCallback) (GVfsBackendSftp *backend,
118 MultiReply *replies,
119 int n_replies,
120 GVfsJob *job,
121 gpointer user_data);
122
123
124 typedef struct {
125 MultiReply *replies;
126 int n_replies;
127 int n_outstanding;
128 gpointer user_data;
129 MultiReplyCallback callback;
130 } MultiRequest;
131
132 struct _MultiReply {
133 int type;
134 GDataInputStream *data;
135 guint32 data_len;
136
137 MultiRequest *request;
138 };
139
140
141
142 typedef struct {
143 guchar *data;
144 gsize size;
145 } DataBuffer;
146
147 typedef struct {
148 DataBuffer *raw_handle;
149 goffset offset;
150 char *filename;
151 char *tempname;
152 guint32 permissions;
153 gboolean set_permissions;
154 gboolean make_backup;
155 } SftpHandle;
156
157
158 typedef struct {
159 ReplyCallback callback;
160 GVfsJob *job;
161 gpointer user_data;
162 } ExpectedReply;
163
164 typedef struct {
165 GVfsBackendSftp *op_backend;
166
167 GOutputStream *command_stream;
168 GInputStream *reply_stream;
169 GDataInputStream *error_stream;
170
171 GCancellable *reply_stream_cancellable;
172
173 /* Output Queue */
174
175 gsize command_bytes_written;
176 GList *command_queue;
177
178 /* Reply reading: */
179 GHashTable *expected_replies;
180 guint32 reply_size;
181 guint32 reply_size_read;
182 guint8 *reply;
183 } Connection;
184
185 struct _GVfsBackendSftp
186 {
187 GVfsBackend parent_instance;
188
189 SFTPClientVendor client_vendor;
190 char *host;
191 int port;
192 gboolean user_specified;
193 gboolean user_specified_in_uri;
194 char *user;
195 char *tmp_password;
196 GPasswordSave password_save;
197
198 guint32 my_uid;
199 guint32 my_gid;
200
201 int protocol_version;
202 SFTPServerExtensions extensions;
203
204 guint32 current_id;
205
206 Connection command_connection;
207 Connection data_connection;
208
209 gboolean force_unmounted;
210 };
211
212 static void parse_attributes (GVfsBackendSftp *backend,
213 GFileInfo *info,
214 const char *basename,
215 GDataInputStream *reply,
216 GFileAttributeMatcher *attribute_matcher);
217
G_DEFINE_TYPE(GVfsBackendSftp,g_vfs_backend_sftp,G_VFS_TYPE_BACKEND)218 G_DEFINE_TYPE (GVfsBackendSftp, g_vfs_backend_sftp, G_VFS_TYPE_BACKEND)
219
220 static void
221 data_buffer_free (DataBuffer *buffer)
222 {
223 if (buffer)
224 {
225 g_free (buffer->data);
226 g_slice_free (DataBuffer, buffer);
227 }
228 }
229
230 static void
make_fd_nonblocking(int fd)231 make_fd_nonblocking (int fd)
232 {
233 fcntl (fd, F_SETFL, O_NONBLOCK | fcntl (fd, F_GETFL));
234 }
235
236 static SFTPClientVendor
get_sftp_client_vendor(void)237 get_sftp_client_vendor (void)
238 {
239 char *ssh_stderr;
240 char *args[3];
241 gint ssh_exitcode;
242 SFTPClientVendor res = SFTP_VENDOR_INVALID;
243
244 args[0] = g_strdup (SSH_PROGRAM);
245 args[1] = g_strdup ("-V");
246 args[2] = NULL;
247 if (g_spawn_sync (NULL, args, NULL,
248 G_SPAWN_SEARCH_PATH | G_SPAWN_STDOUT_TO_DEV_NULL,
249 NULL, NULL,
250 NULL, &ssh_stderr,
251 &ssh_exitcode, NULL))
252 {
253 if (ssh_stderr == NULL)
254 res = SFTP_VENDOR_INVALID;
255 else if ((strstr (ssh_stderr, "OpenSSH") != NULL) ||
256 (strstr (ssh_stderr, "Sun_SSH") != NULL))
257 res = SFTP_VENDOR_OPENSSH;
258 else if (strstr (ssh_stderr, "SSH Secure Shell") != NULL)
259 res = SFTP_VENDOR_SSH;
260 else
261 res = SFTP_VENDOR_INVALID;
262 }
263
264 g_free (ssh_stderr);
265 g_free (args[0]);
266 g_free (args[1]);
267
268 return res;
269 }
270
271 static gboolean
has_extension(GVfsBackendSftp * backend,SFTPServerExtensions extension)272 has_extension (GVfsBackendSftp *backend, SFTPServerExtensions extension)
273 {
274 return (backend->extensions & (1 << extension)) != 0;
275 }
276
277 static void
destroy_connection(Connection * conn)278 destroy_connection (Connection *conn)
279 {
280 if (conn->expected_replies)
281 {
282 g_hash_table_destroy (conn->expected_replies);
283 conn->expected_replies = NULL;
284 }
285
286 g_clear_object (&conn->command_stream);
287 g_clear_object (&conn->reply_stream_cancellable);
288 g_clear_object (&conn->reply_stream);
289 g_clear_object (&conn->error_stream);
290 }
291
292 static gboolean
connection_is_usable(Connection * conn)293 connection_is_usable (Connection *conn)
294 {
295 return conn->command_stream != NULL;
296 }
297
298 static void
g_vfs_backend_sftp_finalize(GObject * object)299 g_vfs_backend_sftp_finalize (GObject *object)
300 {
301 GVfsBackendSftp *backend;
302
303 backend = G_VFS_BACKEND_SFTP (object);
304 destroy_connection (&backend->command_connection);
305 destroy_connection (&backend->data_connection);
306
307 if (G_OBJECT_CLASS (g_vfs_backend_sftp_parent_class)->finalize)
308 (*G_OBJECT_CLASS (g_vfs_backend_sftp_parent_class)->finalize) (object);
309 }
310
311 static void
expected_reply_free(ExpectedReply * reply)312 expected_reply_free (ExpectedReply *reply)
313 {
314 g_object_unref (reply->job);
315 g_slice_free (ExpectedReply, reply);
316 }
317
318 static void
g_vfs_backend_sftp_init(GVfsBackendSftp * backend)319 g_vfs_backend_sftp_init (GVfsBackendSftp *backend)
320 {
321 }
322
323 static void
look_for_stderr_errors(Connection * conn,GError ** error)324 look_for_stderr_errors (Connection *conn, GError **error)
325 {
326 char *line;
327
328 while (1)
329 {
330 line = g_data_input_stream_read_line (conn->error_stream, NULL, NULL, NULL);
331
332 if (line == NULL)
333 {
334 /* Error (real or WOULDBLOCK) or EOF */
335 g_set_error_literal (error,
336 G_IO_ERROR, G_IO_ERROR_FAILED,
337 _("Connection failed"));
338 return;
339 }
340
341 g_debug ("stderr: %s\n", line);
342 if (strstr (line, "Permission denied") != NULL)
343 {
344 g_set_error_literal (error,
345 G_IO_ERROR, G_IO_ERROR_PERMISSION_DENIED,
346 _("Permission denied"));
347 return;
348 }
349 else if (strstr (line, "Name or service not known") != NULL)
350 {
351 g_set_error_literal (error,
352 G_IO_ERROR, G_IO_ERROR_HOST_NOT_FOUND,
353 _("Hostname not known"));
354 return;
355 }
356 else if (strstr (line, "No route to host") != NULL)
357 {
358 g_set_error_literal (error,
359 G_IO_ERROR, G_IO_ERROR_HOST_NOT_FOUND,
360 _("No route to host"));
361 return;
362 }
363 else if (strstr (line, "Connection refused") != NULL ||
364 strstr (line, "subsystem request failed") != NULL)
365 {
366 g_set_error_literal (error,
367 G_IO_ERROR, G_IO_ERROR_CONNECTION_REFUSED,
368 _("Connection refused by server"));
369 return;
370 }
371 else if (strstr (line, "Host key verification failed") != NULL)
372 {
373 g_set_error_literal (error,
374 G_IO_ERROR, G_IO_ERROR_FAILED,
375 _("Host key verification failed"));
376 return;
377 }
378 else if (strstr (line, "Too many authentication failures") != NULL)
379 {
380 g_set_error_literal (error,
381 G_IO_ERROR, G_IO_ERROR_FAILED,
382 _("Too many authentication failures"));
383 return;
384 }
385
386 g_free (line);
387 }
388 }
389
390 static gchar*
read_dbus_string_dict_value(GVariant * args,const gchar * key)391 read_dbus_string_dict_value (GVariant *args, const gchar *key)
392 {
393 GVariant *a;
394 GVariantIter iter;
395 const gchar *str, *val;
396 gchar *res;
397
398 if (! g_variant_is_of_type (args, G_VARIANT_TYPE ("(a{ss})")))
399 return NULL;
400
401 g_variant_get (args, "(@a{ss})", &a);
402
403 res = NULL;
404 g_variant_iter_init (&iter, a);
405 while (g_variant_iter_next (&iter, "{&s&s}", &str, &val))
406 {
407 if (g_strcmp0 (str, key) == 0)
408 {
409 res = g_strdup (val);
410 break;
411 }
412 }
413
414 g_variant_unref (a);
415
416 return res;
417 }
418
419 static void
setup_ssh_environment(void)420 setup_ssh_environment (void)
421 {
422 GDBusConnection *conn;
423 GError *error;
424 GVariant *iter;
425 gchar *env;
426
427 error = NULL;
428 conn = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error);
429 if (! conn)
430 {
431 g_warning ("Failed to setup SSH evironment: %s (%s, %d)",
432 error->message, g_quark_to_string (error->domain), error->code);
433 g_error_free (error);
434 return;
435 }
436
437 iter = g_dbus_connection_call_sync (conn,
438 "org.gnome.keyring",
439 "/org/gnome/keyring/daemon",
440 "org.gnome.keyring.Daemon",
441 "GetEnvironment",
442 NULL,
443 NULL,
444 G_DBUS_CALL_FLAGS_NONE,
445 -1,
446 NULL,
447 &error);
448 if (! iter)
449 {
450 g_dbus_error_strip_remote_error (error);
451 g_warning ("Failed to setup SSH evironment: %s (%s, %d)",
452 error->message, g_quark_to_string (error->domain), error->code);
453 g_error_free (error);
454 }
455 else
456 {
457 env = read_dbus_string_dict_value (iter, "SSH_AUTH_SOCK");
458 if (env && env[0])
459 g_setenv ("SSH_AUTH_SOCK", env, TRUE);
460 g_free (env);
461 g_variant_unref (iter);
462 }
463
464 g_object_unref (conn);
465 }
466
467 static char **
setup_ssh_commandline(GVfsBackend * backend)468 setup_ssh_commandline (GVfsBackend *backend)
469 {
470 GVfsBackendSftp *op_backend = G_VFS_BACKEND_SFTP (backend);
471 guint last_arg;
472 gchar **args;
473
474 args = g_new0 (gchar *, 20); /* 20 is enought for now, bump size if code below changes */
475
476 /* Fill in the first few args */
477 last_arg = 0;
478 args[last_arg++] = g_strdup (SSH_PROGRAM);
479
480 if (op_backend->client_vendor == SFTP_VENDOR_OPENSSH)
481 {
482 args[last_arg++] = g_strdup ("-oForwardX11 no");
483 args[last_arg++] = g_strdup ("-oForwardAgent no");
484 args[last_arg++] = g_strdup ("-oPermitLocalCommand no");
485 args[last_arg++] = g_strdup ("-oClearAllForwardings yes");
486 args[last_arg++] = g_strdup ("-oProtocol 2");
487 args[last_arg++] = g_strdup ("-oNoHostAuthenticationForLocalhost yes");
488 #ifndef USE_PTY
489 args[last_arg++] = g_strdup ("-oBatchMode yes");
490 #endif
491
492 }
493 else if (op_backend->client_vendor == SFTP_VENDOR_SSH)
494 args[last_arg++] = g_strdup ("-x");
495
496 if (op_backend->port != -1)
497 {
498 args[last_arg++] = g_strdup ("-p");
499 args[last_arg++] = g_strdup_printf ("%d", op_backend->port);
500 }
501
502
503 if (op_backend->user_specified)
504 {
505 args[last_arg++] = g_strdup ("-l");
506 args[last_arg++] = g_strdup (op_backend->user);
507 }
508
509 args[last_arg++] = g_strdup ("-s");
510
511 if (op_backend->client_vendor == SFTP_VENDOR_SSH)
512 {
513 args[last_arg++] = g_strdup ("sftp");
514 args[last_arg++] = g_strdup (op_backend->host);
515 }
516 else
517 {
518 args[last_arg++] = g_strdup (op_backend->host);
519 args[last_arg++] = g_strdup ("sftp");
520 }
521
522 args[last_arg++] = NULL;
523
524 return args;
525 }
526
527 static gboolean
spawn_ssh(GVfsBackend * backend,char * args[],pid_t * pid,int * tty_fd,int * stdin_fd,int * stdout_fd,int * stderr_fd,int * slave_fd,GError ** error)528 spawn_ssh (GVfsBackend *backend,
529 char *args[],
530 pid_t *pid,
531 int *tty_fd,
532 int *stdin_fd,
533 int *stdout_fd,
534 int *stderr_fd,
535 int *slave_fd,
536 GError **error)
537 {
538 if (gvfs_get_debug ())
539 {
540 const char **arg;
541 GString *cmd;
542
543 cmd = g_string_new (NULL);
544 for (arg = (const char **)args; *arg != NULL; arg++)
545 {
546 g_string_append (cmd, *arg);
547 g_string_append (cmd, " ");
548 }
549
550 g_debug ("spawn_ssh: %s\n", cmd->str);
551 g_string_free (cmd, TRUE);
552 }
553
554 #ifdef USE_PTY
555 *tty_fd = pty_open(pid, PTY_REAP_CHILD, NULL,
556 args[0], args, NULL,
557 300, 300,
558 stdin_fd, stdout_fd, stderr_fd, slave_fd);
559 if (*tty_fd == -1)
560 {
561 g_set_error_literal (error,
562 G_IO_ERROR, G_IO_ERROR_FAILED,
563 _("Unable to spawn SSH program"));
564 return FALSE;
565 }
566 #else
567 GError *my_error;
568 GPid gpid;
569
570 *tty_fd = -1;
571
572 my_error = NULL;
573 if (!g_spawn_async_with_pipes (NULL, args, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL,
574 &gpid,
575 stdin_fd, stdout_fd, stderr_fd, &my_error))
576 {
577 g_set_error (error,
578 G_IO_ERROR, G_IO_ERROR_FAILED,
579 _("Unable to spawn SSH program: %s"), my_error->message);
580 g_error_free (my_error);
581 return FALSE;
582 }
583 *pid = gpid;
584 #endif
585
586 return TRUE;
587 }
588
589 static guint32
get_new_id(GVfsBackendSftp * backend)590 get_new_id (GVfsBackendSftp *backend)
591 {
592 return backend->current_id++;
593 }
594
595 static GDataOutputStream *
new_command_stream(GVfsBackendSftp * backend,int type)596 new_command_stream (GVfsBackendSftp *backend, int type)
597 {
598 GOutputStream *mem_stream;
599 GDataOutputStream *data_stream;
600 guint32 id;
601
602 mem_stream = g_memory_output_stream_new (NULL, 0, (GReallocFunc)g_realloc, NULL);
603 data_stream = g_data_output_stream_new (mem_stream);
604 g_object_unref (mem_stream);
605
606 g_data_output_stream_put_int32 (data_stream, 0, NULL, NULL); /* LEN */
607 g_data_output_stream_put_byte (data_stream, type, NULL, NULL);
608 if (type != SSH_FXP_INIT)
609 {
610 id = get_new_id (backend);
611 g_data_output_stream_put_uint32 (data_stream, id, NULL, NULL);
612 g_object_set_qdata (G_OBJECT (data_stream), id_q, GUINT_TO_POINTER (id));
613 }
614
615 return data_stream;
616 }
617
618 static gpointer
get_data_from_command_stream(GDataOutputStream * command_stream,gsize * len)619 get_data_from_command_stream (GDataOutputStream *command_stream, gsize *len)
620 {
621 GOutputStream *mem_stream;
622 gpointer data;
623 guint32 *len_ptr;
624
625 mem_stream = g_filter_output_stream_get_base_stream (G_FILTER_OUTPUT_STREAM (command_stream));
626 *len = g_memory_output_stream_get_data_size (G_MEMORY_OUTPUT_STREAM (mem_stream));
627 data = g_memory_output_stream_get_data (G_MEMORY_OUTPUT_STREAM (mem_stream));
628
629 len_ptr = (guint32 *)data;
630 *len_ptr = GUINT32_TO_BE (*len - 4);
631
632 return data;
633 }
634
635 static gboolean
send_command_sync_and_unref_command(Connection * conn,GDataOutputStream * command_stream,GCancellable * cancellable,GError ** error)636 send_command_sync_and_unref_command (Connection *conn,
637 GDataOutputStream *command_stream,
638 GCancellable *cancellable,
639 GError **error)
640 {
641 gpointer data;
642 gsize len;
643 gsize bytes_written;
644 gboolean res;
645
646 data = get_data_from_command_stream (command_stream, &len);
647
648 res = g_output_stream_write_all (conn->command_stream,
649 data, len,
650 &bytes_written,
651 cancellable, error);
652
653 if (error == NULL && !res)
654 g_warning ("Ignored send_command error\n");
655
656 g_free (data);
657 g_object_unref (command_stream);
658
659 return res;
660 }
661
662 static gboolean
wait_for_reply(GVfsBackend * backend,int stdout_fd,GError ** error)663 wait_for_reply (GVfsBackend *backend, int stdout_fd, GError **error)
664 {
665 fd_set ifds;
666 struct timeval tv;
667 int ret;
668
669 FD_ZERO (&ifds);
670 FD_SET (stdout_fd, &ifds);
671
672 tv.tv_sec = SFTP_READ_TIMEOUT;
673 tv.tv_usec = 0;
674
675 ret = select (stdout_fd+1, &ifds, NULL, NULL, &tv);
676
677 if (ret <= 0)
678 {
679 g_set_error_literal (error,
680 G_IO_ERROR, G_IO_ERROR_TIMED_OUT,
681 _("Timed out when logging in"));
682 return FALSE;
683 }
684 return TRUE;
685 }
686
687 static GDataInputStream *
make_reply_stream(guint8 * data,gsize len)688 make_reply_stream (guint8 *data, gsize len)
689 {
690 GInputStream *mem_stream;
691 GDataInputStream *data_stream;
692
693 mem_stream = g_memory_input_stream_new_from_data (data, len, g_free);
694 data_stream = g_data_input_stream_new (mem_stream);
695 g_object_unref (mem_stream);
696
697 return data_stream;
698 }
699
700 static GDataInputStream *
read_reply_sync(Connection * conn,gsize * len_out,GError ** error)701 read_reply_sync (Connection *conn, gsize *len_out, GError **error)
702 {
703 guint32 len;
704 gsize bytes_read;
705 GByteArray *array;
706 guint8 *data;
707
708 if (!g_input_stream_read_all (conn->reply_stream,
709 &len, 4,
710 &bytes_read, NULL, error))
711 return NULL;
712
713 /* Make sure we handle SSH exiting early, e.g. if no further
714 authentication methods */
715 if (bytes_read == 0)
716 {
717 g_set_error_literal (error,
718 G_IO_ERROR, G_IO_ERROR_FAILED,
719 _("Connection failed"));
720 return NULL;
721 }
722
723 len = GUINT32_FROM_BE (len);
724
725 array = g_byte_array_sized_new (len);
726
727 if (!g_input_stream_read_all (conn->reply_stream,
728 array->data, len,
729 &bytes_read, NULL, error))
730 {
731 g_byte_array_free (array, TRUE);
732 return NULL;
733 }
734
735 if (len_out)
736 *len_out = len;
737
738 data = array->data;
739 g_byte_array_free (array, FALSE);
740
741 return make_reply_stream (data, len);
742 }
743
744 static void
put_string(GDataOutputStream * stream,const char * str)745 put_string (GDataOutputStream *stream, const char *str)
746 {
747 g_data_output_stream_put_uint32 (stream, strlen (str), NULL, NULL);
748 g_data_output_stream_put_string (stream, str, NULL, NULL);
749 }
750
751 static void
put_data_buffer(GDataOutputStream * stream,DataBuffer * buffer)752 put_data_buffer (GDataOutputStream *stream, DataBuffer *buffer)
753 {
754 g_data_output_stream_put_uint32 (stream, buffer->size, NULL, NULL);
755 g_output_stream_write_all (G_OUTPUT_STREAM (stream),
756 buffer->data, buffer->size,
757 NULL,
758 NULL, NULL);
759 }
760
761 static char *
read_string(GDataInputStream * stream,gsize * len_out)762 read_string (GDataInputStream *stream, gsize *len_out)
763 {
764 guint32 len;
765 char *data;
766 GError *error;
767
768 error = NULL;
769 len = g_data_input_stream_read_uint32 (stream, NULL, &error);
770 if (error)
771 {
772 g_error_free (error);
773 return NULL;
774 }
775
776 data = g_malloc (len + 1);
777
778 if (!g_input_stream_read_all (G_INPUT_STREAM (stream), data, len, NULL, NULL, NULL))
779 {
780 g_free (data);
781 return NULL;
782 }
783
784 data[len] = 0;
785
786 if (len_out)
787 *len_out = len;
788
789 return data;
790 }
791
792 static DataBuffer *
read_data_buffer(GDataInputStream * stream)793 read_data_buffer (GDataInputStream *stream)
794 {
795 DataBuffer *buffer;
796
797 buffer = g_slice_new (DataBuffer);
798 buffer->data = (guchar *)read_string (stream, &buffer->size);
799
800 return buffer;
801 }
802
803 static gboolean
get_hostname_from_line(const gchar * buffer,gchar ** hostname_out)804 get_hostname_from_line (const gchar *buffer,
805 gchar **hostname_out)
806 {
807 gchar *startpos;
808 gchar *endpos;
809
810 /* Parse a line that looks like: "username@hostname's password:". */
811
812 startpos = strchr (buffer, '@');
813 if (!startpos)
814 return FALSE;
815
816 endpos = strchr (buffer, '\'');
817 if (!endpos)
818 return FALSE;
819
820 *hostname_out = g_strndup (startpos + 1, endpos - startpos - 1);
821
822 return TRUE;
823 }
824
825 static gboolean
get_hostname_and_fingerprint_from_line(const gchar * buffer,gchar ** hostname_out,gchar ** fingerprint_out)826 get_hostname_and_fingerprint_from_line (const gchar *buffer,
827 gchar **hostname_out,
828 gchar **fingerprint_out)
829 {
830 gchar *pos;
831 gchar *startpos;
832 gchar *endpos;
833 gchar *hostname = NULL;
834 gchar *fingerprint = NULL;
835
836 if (g_str_has_prefix (buffer, "The authenticity of host '"))
837 {
838 /* OpenSSH */
839 pos = strchr (&buffer[26], '\'');
840 if (pos == NULL)
841 return FALSE;
842
843 hostname = g_strndup (&buffer[26], pos - (&buffer[26]));
844
845 startpos = strstr (pos, " key fingerprint is ");
846 if (startpos == NULL)
847 {
848 g_free (hostname);
849 return FALSE;
850 }
851
852 startpos = startpos + 20;
853 endpos = strchr (startpos, '.');
854 if (endpos == NULL)
855 {
856 g_free (hostname);
857 return FALSE;
858 }
859
860 fingerprint = g_strndup (startpos, endpos - startpos);
861 }
862 else if (strstr (buffer, "Key fingerprint:") != NULL)
863 {
864 /* SSH.com*/
865 startpos = strstr (buffer, "Key fingerprint:");
866 if (startpos == NULL)
867 {
868 g_free (hostname);
869 return FALSE;
870 }
871
872 startpos = startpos + 18;
873 endpos = strchr (startpos, '\r');
874 fingerprint = g_strndup (startpos, endpos - startpos);
875 }
876
877 *hostname_out = hostname;
878 *fingerprint_out = fingerprint;
879
880 return TRUE;
881 }
882
883 static gboolean
get_hostname_and_ip_address(const gchar * buffer,gchar ** hostname_out,gchar ** ip_address_out)884 get_hostname_and_ip_address (const gchar *buffer,
885 gchar **hostname_out,
886 gchar **ip_address_out)
887 {
888 char *startpos, *endpos, *hostname;
889
890 /* Parse a line that looks like:
891 * Warning: the ECDSA/RSA host key for 'hostname' differs from the key for the IP address '...'
892 * First get the hostname.
893 */
894 startpos = strchr (buffer, '\'');
895 if (!startpos)
896 return FALSE;
897 startpos++;
898
899 endpos = strchr (startpos, '\'');
900 if (!endpos)
901 return FALSE;
902
903 hostname = g_strndup (startpos, endpos - startpos);
904
905 /* Then get the ip address. */
906 startpos = strchr (endpos + 1, '\'');
907 if (!startpos)
908 {
909 g_free (hostname);
910 return FALSE;
911 }
912 startpos++;
913
914 endpos = strchr (startpos, '\'');
915 if (!endpos)
916 {
917 g_free (hostname);
918 return FALSE;
919 }
920
921 *hostname_out = hostname;
922 *ip_address_out = g_strndup (startpos, endpos - startpos);
923
924 return TRUE;
925 }
926
927 static gboolean
login_answer_yes_no(GMountSource * mount_source,char * message,GOutputStream * reply_stream,GError ** error)928 login_answer_yes_no (GMountSource *mount_source,
929 char *message,
930 GOutputStream *reply_stream,
931 GError **error)
932 {
933 const char *choices[] = {_("Log In Anyway"), _("Cancel Login"), NULL};
934 const char *choice_string;
935 int choice;
936 gboolean aborted = FALSE;
937 gsize bytes_written;
938
939 if (!g_mount_source_ask_question (mount_source,
940 message,
941 choices,
942 &aborted,
943 &choice) ||
944 aborted)
945 {
946 g_set_error_literal (error,
947 G_IO_ERROR, G_IO_ERROR_FAILED,
948 _("Login dialog cancelled"));
949 g_free (message);
950 return FALSE;
951 }
952 g_free (message);
953
954 choice_string = (choice == 0) ? "yes" : "no";
955 if (!g_output_stream_write_all (reply_stream,
956 choice_string,
957 strlen (choice_string),
958 &bytes_written,
959 NULL, NULL) ||
960 !g_output_stream_write_all (reply_stream,
961 "\n", 1,
962 &bytes_written,
963 NULL, NULL))
964 {
965 g_set_error_literal (error,
966 G_IO_ERROR, G_IO_ERROR_FAILED,
967 _("Can’t send host identity confirmation"));
968 return FALSE;
969 }
970
971 return TRUE;
972 }
973
974 static const gchar *
get_authtype_from_password_line(const char * password_line)975 get_authtype_from_password_line (const char *password_line)
976 {
977 return g_str_has_prefix (password_line, "Enter passphrase for key") ?
978 "publickey" : "password";
979 }
980
981 static char *
get_object_from_password_line(const char * password_line)982 get_object_from_password_line (const char *password_line)
983 {
984 char *chr, *ptr, *object = NULL;
985
986 if (g_str_has_prefix (password_line, "Enter passphrase for key"))
987 {
988 ptr = strchr (password_line, '\'');
989 if (ptr != NULL)
990 {
991 ptr += 1;
992 chr = strchr (ptr, '\'');
993 if (chr != NULL)
994 {
995 object = g_strndup (ptr, chr - ptr);
996 }
997 else
998 {
999 object = g_strdup (ptr);
1000 }
1001 }
1002 }
1003 return object;
1004 }
1005
1006 static gboolean
handle_login(GVfsBackend * backend,GMountSource * mount_source,int tty_fd,int stdout_fd,int stderr_fd,gboolean initial_connection,GError ** error)1007 handle_login (GVfsBackend *backend,
1008 GMountSource *mount_source,
1009 int tty_fd, int stdout_fd, int stderr_fd,
1010 gboolean initial_connection,
1011 GError **error)
1012 {
1013 GVfsBackendSftp *op_backend = G_VFS_BACKEND_SFTP (backend);
1014 GInputStream *prompt_stream;
1015 GOutputStream *reply_stream;
1016 int ret;
1017 int prompt_fd;
1018 struct pollfd fds[2];
1019 char buffer[1024];
1020 gsize len;
1021 gboolean ret_val;
1022 char *new_password = NULL;
1023 char *new_user = NULL;
1024 gboolean password_in_keyring = FALSE;
1025 const gchar *authtype = NULL;
1026 gchar *object = NULL;
1027 char *prompt;
1028 int attempts = 0;
1029 static int i = 0;
1030
1031 i++;
1032 g_debug ("handle_login #%d initial_connection = %d - user: %s, host: %s, port: %d\n",
1033 i, initial_connection, op_backend->user, op_backend->host, op_backend->port);
1034
1035 if (op_backend->client_vendor == SFTP_VENDOR_SSH)
1036 prompt_fd = stderr_fd;
1037 else
1038 prompt_fd = tty_fd;
1039
1040 prompt_stream = g_unix_input_stream_new (prompt_fd, FALSE);
1041 reply_stream = g_unix_output_stream_new (tty_fd, FALSE);
1042
1043 ret_val = TRUE;
1044 while (1)
1045 {
1046 fds[0].fd = stdout_fd;
1047 fds[0].events = POLLIN;
1048 fds[1].fd = prompt_fd;
1049 fds[1].events = POLLIN;
1050
1051 ret = poll(fds, 2, SFTP_READ_TIMEOUT * 1000);
1052
1053 if (ret <= 0)
1054 {
1055 g_set_error_literal (error,
1056 G_IO_ERROR, G_IO_ERROR_TIMED_OUT,
1057 _("Timed out when logging in"));
1058 ret_val = FALSE;
1059 break;
1060 }
1061
1062 if (fds[0].revents)
1063 break; /* Got reply to initial INIT request */
1064
1065 if (!(fds[1].revents & POLLIN))
1066 continue;
1067
1068 len = g_input_stream_read (prompt_stream,
1069 buffer, sizeof (buffer) - 1,
1070 NULL, error);
1071
1072 if (len == -1)
1073 {
1074 ret_val = FALSE;
1075 break;
1076 }
1077
1078 buffer[len] = 0;
1079 g_strchug (buffer);
1080
1081 g_debug ("handle_login #%d - prompt: \"%s\"\n", i, buffer);
1082
1083 /*
1084 * If logging in on a second connection (e.g. the data connection), use
1085 * the user and password stored in the backend and don't retry if it
1086 * fails.
1087 *
1088 * If the input URI contains a username
1089 * if the input URI contains a password, we attempt one login and return GNOME_VFS_ERROR_ACCESS_DENIED on failure.
1090 * if the input URI contains no password, we query the user until he provides a correct one, or he cancels.
1091 *
1092 * If the input URI contains no username
1093 * (a) the user is queried for a user name and a password, with the default login being his
1094 * local login name.
1095 *
1096 * (b) if the user decides to change his remote login name, we go to tty_retry because we need a
1097 * new SSH session, attempting one login with his provided credentials, and if that fails proceed
1098 * with (a), but use his desired remote login name as default.
1099 *
1100 * The "password" variable is only used for the very first login attempt,
1101 * or for the first re-login attempt when the user decided to change his name.
1102 * Otherwise, we "new_password" and "new_user_name" is used, as output variable
1103 * for user and keyring input.
1104 */
1105 if (g_str_has_suffix (buffer, "password: ") ||
1106 g_str_has_suffix (buffer, "Password: ") ||
1107 g_str_has_suffix (buffer, "Password:") ||
1108 g_str_has_prefix (buffer, "Password for ") ||
1109 g_str_has_prefix (buffer, "Enter Kerberos password") ||
1110 g_str_has_prefix (buffer, "Enter passphrase for key") ||
1111 g_str_has_prefix (buffer, "Enter PASSCODE"))
1112 {
1113 gboolean aborted = FALSE;
1114 gsize bytes_written;
1115
1116 attempts++;
1117 authtype = get_authtype_from_password_line (buffer);
1118 object = get_object_from_password_line (buffer);
1119
1120 if (!initial_connection && attempts > 1)
1121 {
1122 g_set_error_literal (error,
1123 G_IO_ERROR, G_IO_ERROR_PERMISSION_DENIED,
1124 _("Permission denied"));
1125 ret_val = FALSE;
1126 break;
1127 }
1128
1129 /* If password is in keyring at this point is because it failed */
1130 if (!op_backend->tmp_password && (password_in_keyring ||
1131 !g_vfs_keyring_lookup_password (op_backend->user,
1132 op_backend->host,
1133 NULL,
1134 "sftp",
1135 object,
1136 authtype,
1137 op_backend->port != -1 ?
1138 op_backend->port
1139 :
1140 0,
1141 &new_user,
1142 NULL,
1143 &new_password)))
1144 {
1145 GAskPasswordFlags flags = G_ASK_PASSWORD_NEED_PASSWORD;
1146 gchar *hostname = NULL;
1147
1148 g_debug ("handle_login #%d - asking for password...\n", i);
1149
1150 if (g_vfs_keyring_is_available ())
1151 flags |= G_ASK_PASSWORD_SAVING_SUPPORTED;
1152 if (strcmp (authtype, "password") == 0 &&
1153 !op_backend->user_specified)
1154 flags |= G_ASK_PASSWORD_NEED_USERNAME;
1155
1156 g_free (new_password);
1157
1158 get_hostname_from_line (buffer, &hostname);
1159
1160 if (op_backend->user_specified)
1161 if (strcmp (authtype, "publickey") == 0)
1162 /* Translators: the first %s is the username, the second the host name */
1163 prompt = g_strdup_printf (_("Enter passphrase for secure key for %s on %s"), op_backend->user, op_backend->host);
1164 else
1165 /* Translators: the first %s is the username, the second the host name */
1166 prompt = g_strdup_printf (_("Enter password for %s on %s"), op_backend->user, hostname ? hostname : op_backend->host);
1167 else
1168 if (strcmp (authtype, "publickey") == 0)
1169 /* Translators: %s is the hostname */
1170 prompt = g_strdup_printf (_("Enter passphrase for secure key for %s"), op_backend->host);
1171 else
1172 /* Translators: %s is the hostname */
1173 prompt = g_strdup_printf (_("Enter password for %s"), hostname ? hostname : op_backend->host);
1174
1175 if (!g_mount_source_ask_password (mount_source,
1176 prompt,
1177 op_backend->user,
1178 NULL,
1179 flags,
1180 &aborted,
1181 &new_password,
1182 &new_user,
1183 NULL,
1184 NULL,
1185 &op_backend->password_save) ||
1186 aborted)
1187 {
1188 g_set_error_literal (error, G_IO_ERROR,
1189 aborted ? G_IO_ERROR_FAILED_HANDLED : G_IO_ERROR_PERMISSION_DENIED,
1190 _("Password dialog cancelled"));
1191 ret_val = FALSE;
1192 break;
1193 }
1194 g_free (prompt);
1195 }
1196 else if (op_backend->tmp_password)
1197 {
1198 /* I already have a password of a previous login attempt
1199 * (either because this is a second connection or because the
1200 * user provided a new user name).
1201 */
1202 new_password = op_backend->tmp_password;
1203 op_backend->tmp_password = NULL;
1204
1205 g_debug ("handle_login #%d - using credentials from previous login attempt...\n", i);
1206 }
1207 else
1208 {
1209 password_in_keyring = TRUE;
1210
1211 g_debug ("handle_login #%d - using credentials from keyring...\n", i);
1212 }
1213
1214 if (new_user &&
1215 (op_backend->user == NULL ||
1216 strcmp (new_user, op_backend->user) != 0))
1217 {
1218 g_debug ("handle_login #%d - new_user: %s\n", i, new_user);
1219
1220 g_free (op_backend->user);
1221 op_backend->user = new_user;
1222
1223 op_backend->user_specified = TRUE;
1224
1225 g_free (op_backend->tmp_password);
1226 op_backend->tmp_password = new_password;
1227 new_password = NULL;
1228
1229 g_set_error_literal (error,
1230 G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT,
1231 "Invalid user name");
1232 ret_val = FALSE;
1233 break;
1234 }
1235 else if (new_user)
1236 {
1237 g_free (new_user);
1238 }
1239
1240 if (new_password == NULL)
1241 {
1242 /* This really should not happen, but was seen as bug #569203
1243 * avoid crash and ask for info
1244 */
1245 g_warning ("Got NULL password but no error in sftp login request. "
1246 "This should not happen, if you can reproduce this, please "
1247 "add information to http://bugzilla.gnome.org/show_bug.cgi?id=569203");
1248 new_password = g_strdup ("");
1249 }
1250
1251 if (!g_output_stream_write_all (reply_stream,
1252 new_password, strlen (new_password),
1253 &bytes_written,
1254 NULL, NULL) ||
1255 !g_output_stream_write_all (reply_stream,
1256 "\n", 1,
1257 &bytes_written,
1258 NULL, NULL))
1259 {
1260 g_set_error_literal (error,
1261 G_IO_ERROR, G_IO_ERROR_PERMISSION_DENIED,
1262 _("Can’t send password"));
1263 ret_val = FALSE;
1264 break;
1265 }
1266 }
1267 else if (g_str_has_prefix (buffer, "The authenticity of host '") ||
1268 strstr (buffer, "Key fingerprint:") != NULL)
1269 {
1270 gchar *hostname = NULL;
1271 gchar *fingerprint = NULL;
1272 gchar *message;
1273
1274 g_debug ("handle_login #%d - confirming authenticity of host...\n", i);
1275
1276 get_hostname_and_fingerprint_from_line (buffer, &hostname, &fingerprint);
1277
1278 message = g_strdup_printf (_("Can’t verify the identity of “%s”.\n"
1279 "This happens when you log in to a computer the first time.\n\n"
1280 "The identity sent by the remote computer is “%s”. "
1281 "If you want to be absolutely sure it is safe to continue, "
1282 "contact the system administrator."),
1283 hostname ? hostname : op_backend->host,
1284 fingerprint ? fingerprint : "???");
1285
1286 g_free (hostname);
1287 g_free (fingerprint);
1288
1289 if (!login_answer_yes_no (mount_source, message, reply_stream, error))
1290 {
1291 ret_val = FALSE;
1292 break;
1293 }
1294 }
1295 else if (strstr (buffer, "differs from the key for the IP address"))
1296 {
1297 gchar *hostname = NULL;
1298 gchar *ip_address = NULL;
1299 gchar *message;
1300
1301 g_debug ("handle_login #%d - host key / IP mismatch ...\n", i);
1302
1303 get_hostname_and_ip_address (buffer, &hostname, &ip_address);
1304
1305 message = g_strdup_printf (_("The host key for “%s” differs from the key for the IP address “%s”\n"
1306 "If you want to be absolutely sure it is safe to continue, "
1307 "contact the system administrator."),
1308 hostname ? hostname : op_backend->host,
1309 ip_address ? ip_address : "???");
1310
1311 g_free (hostname);
1312 g_free (ip_address);
1313
1314 if (!login_answer_yes_no (mount_source, message, reply_stream, error))
1315 {
1316 ret_val = FALSE;
1317 break;
1318 }
1319 }
1320 }
1321
1322 if (ret_val && initial_connection)
1323 {
1324 g_debug ("handle_login #%d - password_save: %d\n", i, op_backend->password_save);
1325
1326 /* Login succeed, save password in keyring */
1327 g_vfs_keyring_save_password (op_backend->user,
1328 op_backend->host,
1329 NULL,
1330 "sftp",
1331 object,
1332 authtype,
1333 op_backend->port != -1 ?
1334 op_backend->port
1335 :
1336 0,
1337 new_password,
1338 op_backend->password_save);
1339
1340 /* Keep the successful password for subsequent connections. */
1341 op_backend->tmp_password = new_password;
1342 new_password = NULL;
1343 }
1344
1345 g_debug ("handle_login #%d - ret_val: %d\n", i, ret_val);
1346
1347 g_free (object);
1348 g_free (new_password);
1349 g_object_unref (prompt_stream);
1350 g_object_unref (reply_stream);
1351 return ret_val;
1352 }
1353
1354 static void
fail_jobs(Connection * conn,GError * error)1355 fail_jobs (Connection *conn, GError *error)
1356 {
1357 GHashTableIter iter;
1358 gpointer key, value;
1359
1360 if (!connection_is_usable (conn))
1361 return;
1362
1363 g_hash_table_iter_init (&iter, conn->expected_replies);
1364 while (g_hash_table_iter_next (&iter, &key, &value))
1365 {
1366 ExpectedReply *expected_reply = (ExpectedReply *) value;
1367 g_vfs_job_failed_from_error (expected_reply->job, error);
1368 }
1369 }
1370
1371 static void
fail_jobs_and_unmount(GVfsBackendSftp * backend,GError * error)1372 fail_jobs_and_unmount (GVfsBackendSftp *backend, GError *error)
1373 {
1374 if (backend->force_unmounted)
1375 return;
1376
1377 backend->force_unmounted = TRUE;
1378
1379 fail_jobs (&backend->command_connection, error);
1380 fail_jobs (&backend->data_connection, error);
1381
1382 g_error_free (error);
1383
1384 g_vfs_backend_force_unmount ((GVfsBackend*)backend);
1385 }
1386
1387 static int
check_input_stream_read_result(Connection * conn,gssize res,GError * error)1388 check_input_stream_read_result (Connection *conn, gssize res, GError *error)
1389 {
1390 if (G_UNLIKELY (res <= 0))
1391 {
1392 if (res == 0 || error == NULL)
1393 {
1394 g_clear_error (&error);
1395 g_set_error (&error, G_IO_ERROR, G_IO_ERROR_FAILED,
1396 res == 0 ? _("The connection is closed (the underlying SSH process exited)")
1397 : _("Internal error: Unknown Error"));
1398 }
1399
1400 fail_jobs_and_unmount (conn->op_backend, error);
1401 return -1;
1402 }
1403
1404 return 0;
1405 }
1406
1407 static void read_reply_async (Connection *conn);
1408
1409 static void
read_reply_async_got_data(GObject * source_object,GAsyncResult * result,gpointer user_data)1410 read_reply_async_got_data (GObject *source_object,
1411 GAsyncResult *result,
1412 gpointer user_data)
1413 {
1414 Connection *conn = user_data;
1415 gssize res;
1416 GDataInputStream *reply;
1417 ExpectedReply *expected_reply;
1418 guint32 id;
1419 int type;
1420 GError *error;
1421
1422 error = NULL;
1423 res = g_input_stream_read_finish (G_INPUT_STREAM (source_object), result, &error);
1424
1425 /* If we got an error, we've already called force_unmount so don't do
1426 * anything further. */
1427 if (check_input_stream_read_result (conn, res, error) == -1)
1428 return;
1429
1430 conn->reply_size_read += res;
1431
1432 if (conn->reply_size_read < conn->reply_size)
1433 {
1434 g_input_stream_read_async (conn->reply_stream,
1435 conn->reply + conn->reply_size_read, conn->reply_size - conn->reply_size_read,
1436 0, NULL, read_reply_async_got_data, conn);
1437 return;
1438 }
1439
1440 reply = make_reply_stream (conn->reply, conn->reply_size);
1441 conn->reply = NULL;
1442
1443 type = g_data_input_stream_read_byte (reply, NULL, NULL);
1444 id = g_data_input_stream_read_uint32 (reply, NULL, NULL);
1445
1446 expected_reply = g_hash_table_lookup (conn->expected_replies, GINT_TO_POINTER (id));
1447 if (expected_reply)
1448 {
1449 if (expected_reply->callback != NULL)
1450 (expected_reply->callback) (conn->op_backend, type, reply, conn->reply_size,
1451 expected_reply->job, expected_reply->user_data);
1452 g_hash_table_remove (conn->expected_replies, GINT_TO_POINTER (id));
1453 }
1454 else
1455 g_warning ("Got unhandled reply of size %"G_GUINT32_FORMAT" for id %"G_GUINT32_FORMAT"\n", conn->reply_size, id);
1456
1457 g_object_unref (reply);
1458
1459 read_reply_async (conn);
1460
1461 }
1462
1463 static void
read_reply_async_got_len(GObject * source_object,GAsyncResult * result,gpointer user_data)1464 read_reply_async_got_len (GObject *source_object,
1465 GAsyncResult *result,
1466 gpointer user_data)
1467 {
1468 Connection *conn = user_data;
1469 gssize res;
1470 GError *error;
1471
1472 error = NULL;
1473 res = g_input_stream_read_finish (G_INPUT_STREAM (source_object), result, &error);
1474
1475 /* Bail out if cancelled */
1476 if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
1477 {
1478 g_error_free (error);
1479 g_object_unref (conn->op_backend);
1480 return;
1481 }
1482
1483 /* If we got an error, we've already called force_unmount so don't do
1484 * anything further. */
1485 if (check_input_stream_read_result (conn, res, error) == -1)
1486 return;
1487
1488 conn->reply_size_read += res;
1489
1490 if (conn->reply_size_read < 4)
1491 {
1492 g_input_stream_read_async (conn->reply_stream,
1493 (char *)&conn->reply_size + conn->reply_size_read, 4 - conn->reply_size_read,
1494 0, conn->reply_stream_cancellable, read_reply_async_got_len,
1495 conn);
1496 return;
1497 }
1498 conn->reply_size = GUINT32_FROM_BE (conn->reply_size);
1499
1500 conn->reply_size_read = 0;
1501 conn->reply = g_malloc (conn->reply_size);
1502 g_input_stream_read_async (conn->reply_stream,
1503 conn->reply, conn->reply_size,
1504 0, NULL, read_reply_async_got_data, conn);
1505 }
1506
1507 static void
read_reply_async(Connection * conn)1508 read_reply_async (Connection *conn)
1509 {
1510 conn->reply_size_read = 0;
1511 g_input_stream_read_async (conn->reply_stream,
1512 &conn->reply_size, 4,
1513 0, conn->reply_stream_cancellable,
1514 read_reply_async_got_len,
1515 conn);
1516 }
1517
1518 static void send_command (Connection *conn);
1519
1520 static void
send_command_data(GObject * source_object,GAsyncResult * result,gpointer user_data)1521 send_command_data (GObject *source_object,
1522 GAsyncResult *result,
1523 gpointer user_data)
1524 {
1525 Connection *conn = user_data;
1526 gssize res;
1527 DataBuffer *buffer;
1528
1529 res = g_output_stream_write_finish (G_OUTPUT_STREAM (source_object), result, NULL);
1530
1531 if (res <= 0)
1532 {
1533 g_warning ("Error sending command");
1534 fail_jobs_and_unmount (conn->op_backend, NULL);
1535 return;
1536 }
1537
1538 buffer = conn->command_queue->data;
1539
1540 conn->command_bytes_written += res;
1541
1542 if (conn->command_bytes_written < buffer->size)
1543 {
1544 g_output_stream_write_async (conn->command_stream,
1545 buffer->data + conn->command_bytes_written,
1546 buffer->size - conn->command_bytes_written,
1547 0,
1548 NULL,
1549 send_command_data,
1550 conn);
1551 return;
1552 }
1553
1554 data_buffer_free (buffer);
1555
1556 conn->command_queue = g_list_delete_link (conn->command_queue, conn->command_queue);
1557
1558 if (conn->command_queue != NULL)
1559 send_command (conn);
1560 }
1561
1562 static void
send_command(Connection * conn)1563 send_command (Connection *conn)
1564 {
1565 DataBuffer *buffer;
1566
1567 buffer = conn->command_queue->data;
1568
1569 conn->command_bytes_written = 0;
1570 g_output_stream_write_async (conn->command_stream,
1571 buffer->data,
1572 buffer->size,
1573 0,
1574 NULL,
1575 send_command_data,
1576 conn);
1577 }
1578
1579 static void
expect_reply(Connection * conn,guint32 id,ReplyCallback callback,GVfsJob * job,gpointer user_data)1580 expect_reply (Connection *conn,
1581 guint32 id,
1582 ReplyCallback callback,
1583 GVfsJob *job,
1584 gpointer user_data)
1585 {
1586 ExpectedReply *expected;
1587
1588 expected = g_slice_new (ExpectedReply);
1589 expected->callback = callback;
1590 expected->job = g_object_ref (job);
1591 expected->user_data = user_data;
1592
1593 g_hash_table_replace (conn->expected_replies, GINT_TO_POINTER (id), expected);
1594 }
1595
1596 static DataBuffer *
data_buffer_new(guchar * data,gsize len)1597 data_buffer_new (guchar *data, gsize len)
1598 {
1599 DataBuffer *buffer;
1600
1601 buffer = g_slice_new (DataBuffer);
1602 buffer->data = data;
1603 buffer->size = len;
1604
1605 return buffer;
1606 }
1607
1608 static void
queue_command_buffer(Connection * conn,DataBuffer * buffer)1609 queue_command_buffer (Connection *conn,
1610 DataBuffer *buffer)
1611 {
1612 gboolean first;
1613
1614 first = conn->command_queue == NULL;
1615
1616 conn->command_queue = g_list_append (conn->command_queue, buffer);
1617
1618 if (first)
1619 send_command (conn);
1620 }
1621
1622 static void
queue_command_stream_and_free(Connection * conn,GDataOutputStream * command_stream,ReplyCallback callback,GVfsJob * job,gpointer user_data)1623 queue_command_stream_and_free (Connection *conn,
1624 GDataOutputStream *command_stream,
1625 ReplyCallback callback,
1626 GVfsJob *job,
1627 gpointer user_data)
1628 {
1629 gpointer data;
1630 gsize len;
1631 DataBuffer *buffer;
1632 guint32 id;
1633
1634 id = GPOINTER_TO_UINT (g_object_get_qdata (G_OBJECT (command_stream), id_q));
1635 data = get_data_from_command_stream (command_stream, &len);
1636
1637 buffer = data_buffer_new (data, len);
1638 g_object_unref (command_stream);
1639
1640 expect_reply (conn, id, callback, job, user_data);
1641 queue_command_buffer (conn, buffer);
1642 }
1643
1644
1645 static void
multi_request_cb(GVfsBackendSftp * backend,int reply_type,GDataInputStream * reply_stream,guint32 len,GVfsJob * job,gpointer user_data)1646 multi_request_cb (GVfsBackendSftp *backend,
1647 int reply_type,
1648 GDataInputStream *reply_stream,
1649 guint32 len,
1650 GVfsJob *job,
1651 gpointer user_data)
1652 {
1653 MultiReply *reply;
1654 MultiRequest *request;
1655 int i;
1656
1657 reply = user_data;
1658 request = reply->request;
1659
1660 reply->type = reply_type;
1661 reply->data = g_object_ref (reply_stream);
1662 reply->data_len = len;
1663
1664 if (--request->n_outstanding == 0)
1665 {
1666 /* Call callback */
1667 if (request->callback != NULL)
1668 (request->callback) (backend,
1669 request->replies,
1670 request->n_replies,
1671 job,
1672 request->user_data);
1673
1674 /* Free request data */
1675
1676 for (i = 0; i < request->n_replies; i++)
1677 {
1678 reply = &request->replies[i];
1679 if (reply->data)
1680 g_object_unref (reply->data);
1681 }
1682 g_free (request->replies);
1683
1684 g_free (request);
1685
1686 }
1687 }
1688
1689 typedef struct
1690 {
1691 Connection *connection;
1692 GDataOutputStream *cmd;
1693 } Command;
1694
1695 static void
queue_command_streams_and_free(Command * commands,int n_commands,MultiReplyCallback callback,GVfsJob * job,gpointer user_data)1696 queue_command_streams_and_free (Command *commands,
1697 int n_commands,
1698 MultiReplyCallback callback,
1699 GVfsJob *job,
1700 gpointer user_data)
1701 {
1702 MultiRequest *data;
1703 MultiReply *reply;
1704
1705 int i;
1706
1707 data = g_new0 (MultiRequest, 1);
1708
1709 data->user_data = user_data;
1710 data->n_replies = n_commands;
1711 data->n_outstanding = n_commands;
1712 data->replies = g_new0 (MultiReply, n_commands);
1713 data->callback = callback;
1714
1715 for (i = 0; i < n_commands; i++)
1716 {
1717 reply = &data->replies[i];
1718 reply->request = data;
1719 queue_command_stream_and_free (commands[i].connection,
1720 commands[i].cmd,
1721 multi_request_cb,
1722 job,
1723 reply);
1724 }
1725 }
1726
1727 static gboolean
get_uid_sync(GVfsBackendSftp * backend)1728 get_uid_sync (GVfsBackendSftp *backend)
1729 {
1730 GDataOutputStream *command;
1731 GDataInputStream *reply;
1732 int type;
1733
1734 command = new_command_stream (backend, SSH_FXP_STAT);
1735 put_string (command, ".");
1736 send_command_sync_and_unref_command (&backend->command_connection,
1737 command,
1738 NULL, NULL);
1739
1740 reply = read_reply_sync (&backend->command_connection, NULL, NULL);
1741 if (reply == NULL)
1742 return FALSE;
1743
1744 type = g_data_input_stream_read_byte (reply, NULL, NULL);
1745 /*id =*/ (void) g_data_input_stream_read_uint32 (reply, NULL, NULL);
1746
1747 /* On error, set uid to -1 and ignore */
1748 backend->my_uid = (guint32)-1;
1749 backend->my_gid = (guint32)-1;
1750 if (type == SSH_FXP_ATTRS)
1751 {
1752 GFileInfo *info;
1753
1754 info = g_file_info_new ();
1755 parse_attributes (backend, info, NULL, reply, NULL);
1756 if (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_UNIX_UID))
1757 {
1758 /* Both are always set if set */
1759 backend->my_uid = g_file_info_get_attribute_uint32 (info,
1760 G_FILE_ATTRIBUTE_UNIX_UID);
1761 backend->my_gid = g_file_info_get_attribute_uint32 (info,
1762 G_FILE_ATTRIBUTE_UNIX_GID);
1763 }
1764
1765 g_object_unref (info);
1766 }
1767
1768 g_object_unref (reply);
1769
1770 return TRUE;
1771 }
1772
1773 static gboolean
get_home_sync(GVfsBackendSftp * backend)1774 get_home_sync (GVfsBackendSftp *backend)
1775 {
1776 GDataOutputStream *command;
1777 GDataInputStream *reply;
1778 char *home_path;
1779 int type;
1780
1781 command = new_command_stream (backend, SSH_FXP_REALPATH);
1782 put_string (command, ".");
1783 send_command_sync_and_unref_command (&backend->command_connection,
1784 command,
1785 NULL, NULL);
1786
1787 reply = read_reply_sync (&backend->command_connection, NULL, NULL);
1788 if (reply == NULL)
1789 return FALSE;
1790
1791 type = g_data_input_stream_read_byte (reply, NULL, NULL);
1792 /*id =*/ (void) g_data_input_stream_read_uint32 (reply, NULL, NULL);
1793
1794 /* On error, set home to NULL and ignore */
1795 if (type == SSH_FXP_NAME)
1796 {
1797 /* count = */ (void) g_data_input_stream_read_uint32 (reply, NULL, NULL);
1798
1799 home_path = read_string (reply, NULL);
1800 g_vfs_backend_set_default_location (G_VFS_BACKEND (backend), home_path);
1801 g_free (home_path);
1802 }
1803
1804 g_object_unref (reply);
1805
1806 return TRUE;
1807 }
1808
1809 static void
1810 do_mount (GVfsBackend *backend,
1811 GVfsJobMount *job,
1812 GMountSpec *mount_spec,
1813 GMountSource *mount_source,
1814 gboolean is_automount);
1815
1816 static gboolean
setup_connection(GVfsBackend * backend,GVfsJobMount * job,GMountSpec * mount_spec,GMountSource * mount_source,gboolean is_automount,Connection * connection,gboolean initial_connection,GError ** error)1817 setup_connection (GVfsBackend *backend,
1818 GVfsJobMount *job,
1819 GMountSpec *mount_spec,
1820 GMountSource *mount_source,
1821 gboolean is_automount,
1822 Connection *connection,
1823 gboolean initial_connection,
1824 GError **error)
1825 {
1826 const struct {
1827 const char *name; /* extension_name field */
1828 const char *data; /* extension_data field */
1829 SFTPServerExtensions enable; /* flag to enable this extension */
1830 } extensions[] = {
1831 { "statvfs@openssh.com", "2", SFTP_EXT_OPENSSH_STATVFS },
1832 };
1833
1834 GVfsBackendSftp *op_backend = G_VFS_BACKEND_SFTP (backend);
1835 gchar **args;
1836 pid_t pid;
1837 int tty_fd, stdout_fd, stdin_fd, stderr_fd, slave_fd;
1838 GInputStream *is;
1839 GDataOutputStream *command;
1840 GDataInputStream *reply;
1841 gboolean res;
1842 char *extension_name, *extension_data;
1843 int i;
1844
1845 args = setup_ssh_commandline (backend);
1846
1847 if (!spawn_ssh (backend,
1848 args, &pid,
1849 &tty_fd, &stdin_fd, &stdout_fd, &stderr_fd, &slave_fd,
1850 error))
1851 {
1852 g_strfreev (args);
1853 return FALSE;
1854 }
1855
1856 g_strfreev (args);
1857
1858 connection->op_backend = op_backend;
1859 connection->command_stream = g_unix_output_stream_new (stdin_fd, TRUE);
1860 connection->expected_replies = g_hash_table_new_full (NULL, NULL, NULL,
1861 (GDestroyNotify)expected_reply_free);
1862
1863 command = new_command_stream (op_backend, SSH_FXP_INIT);
1864 g_data_output_stream_put_int32 (command,
1865 SSH_FILEXFER_VERSION, NULL, NULL);
1866 send_command_sync_and_unref_command (connection, command, NULL, NULL);
1867
1868 if (tty_fd == -1)
1869 res = wait_for_reply (backend, stdout_fd, error);
1870 else
1871 {
1872 res = handle_login (backend,
1873 mount_source,
1874 tty_fd, stdout_fd, stderr_fd,
1875 initial_connection,
1876 error);
1877 close (slave_fd);
1878 }
1879
1880 if (!res)
1881 {
1882 if (error && (*error)->code == G_IO_ERROR_INVALID_ARGUMENT)
1883 {
1884 /* New username provided by the user,
1885 * we need to re-spawn the ssh command
1886 */
1887 g_clear_error (error);
1888 do_mount (backend, job, mount_spec, mount_source, is_automount);
1889 }
1890
1891 return FALSE;
1892 }
1893
1894 connection->reply_stream = g_unix_input_stream_new (stdout_fd, TRUE);
1895 connection->reply_stream_cancellable = g_cancellable_new ();
1896
1897 make_fd_nonblocking (stderr_fd);
1898 is = g_unix_input_stream_new (stderr_fd, TRUE);
1899 connection->error_stream = g_data_input_stream_new (is);
1900 g_object_unref (is);
1901
1902 reply = read_reply_sync (connection, NULL, NULL);
1903 if (reply == NULL)
1904 {
1905 look_for_stderr_errors (connection, error);
1906 return FALSE;
1907 }
1908
1909 if (g_data_input_stream_read_byte (reply, NULL, NULL) != SSH_FXP_VERSION)
1910 {
1911 g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, _("Protocol error"));
1912 return FALSE;
1913 }
1914
1915 op_backend->protocol_version = g_data_input_stream_read_uint32 (reply, NULL, NULL);
1916
1917 while ((extension_name = read_string (reply, NULL)) != NULL)
1918 {
1919 extension_data = read_string (reply, NULL);
1920 if (extension_data)
1921 {
1922 for (i = 0; i < G_N_ELEMENTS (extensions); i++)
1923 {
1924 if (!strcmp (extension_name, extensions[i].name) &&
1925 !strcmp (extension_data, extensions[i].data))
1926 op_backend->extensions |= 1 << extensions[i].enable;
1927 }
1928 }
1929 g_free (extension_name);
1930 g_free (extension_data);
1931 }
1932
1933 g_object_unref (reply);
1934
1935 if (initial_connection &&
1936 (!get_uid_sync (op_backend) || !get_home_sync (op_backend)))
1937 {
1938 g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, _("Protocol error"));
1939 return FALSE;
1940 }
1941
1942 g_object_ref (op_backend);
1943 read_reply_async (connection);
1944
1945 return TRUE;
1946 }
1947
1948 static void
do_mount(GVfsBackend * backend,GVfsJobMount * job,GMountSpec * mount_spec,GMountSource * mount_source,gboolean is_automount)1949 do_mount (GVfsBackend *backend,
1950 GVfsJobMount *job,
1951 GMountSpec *mount_spec,
1952 GMountSource *mount_source,
1953 gboolean is_automount)
1954 {
1955 GVfsBackendSftp *op_backend = G_VFS_BACKEND_SFTP (backend);
1956 GError *error = NULL;
1957 GMountSpec *sftp_mount_spec;
1958 char *display_name;
1959
1960 if (!setup_connection (backend,
1961 job,
1962 mount_spec,
1963 mount_source,
1964 is_automount,
1965 &op_backend->command_connection,
1966 TRUE,
1967 &error))
1968 {
1969 if (error)
1970 {
1971 g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
1972 g_error_free (error);
1973 }
1974 /* When a new user is specified, do_mount is called recursively so we
1975 * need to return here without finishing the job. */
1976 return;
1977 }
1978
1979 if (!setup_connection (backend,
1980 job,
1981 mount_spec,
1982 mount_source,
1983 is_automount,
1984 &op_backend->data_connection,
1985 FALSE,
1986 NULL))
1987 {
1988 g_warning ("Setting up data connection failed\n");
1989 destroy_connection (&op_backend->data_connection);
1990 }
1991
1992 g_clear_pointer (&op_backend->tmp_password, g_free);
1993
1994 sftp_mount_spec = g_mount_spec_new ("sftp");
1995 if (op_backend->user_specified_in_uri)
1996 g_mount_spec_set (sftp_mount_spec, "user", op_backend->user);
1997 g_mount_spec_set (sftp_mount_spec, "host", op_backend->host);
1998 if (op_backend->port != -1)
1999 {
2000 char *v;
2001 v = g_strdup_printf ("%d", op_backend->port);
2002 g_mount_spec_set (sftp_mount_spec, "port", v);
2003 g_free (v);
2004 }
2005
2006 g_vfs_backend_set_mount_spec (backend, sftp_mount_spec);
2007 g_mount_spec_unref (sftp_mount_spec);
2008
2009 if (op_backend->user_specified_in_uri)
2010 /* Translators: This is the name of an SFTP share, like "<user> on <hostname>" */
2011 display_name = g_strdup_printf (_("%s on %s"), op_backend->user, op_backend->host);
2012 else
2013 display_name = g_strdup (op_backend->host);
2014 g_vfs_backend_set_display_name (backend, display_name);
2015 g_free (display_name);
2016
2017 g_vfs_backend_set_icon_name (G_VFS_BACKEND (backend), "folder-remote");
2018 g_vfs_backend_set_symbolic_icon_name (G_VFS_BACKEND (backend), "folder-remote-symbolic");
2019 g_vfs_job_succeeded (G_VFS_JOB (job));
2020 }
2021
2022 static void
real_do_mount(GVfsBackend * backend,GVfsJobMount * job,GMountSpec * mount_spec,GMountSource * mount_source,gboolean is_automount)2023 real_do_mount (GVfsBackend *backend,
2024 GVfsJobMount *job,
2025 GMountSpec *mount_spec,
2026 GMountSource *mount_source,
2027 gboolean is_automount)
2028 {
2029 GVfsBackendSftp *op_backend = G_VFS_BACKEND_SFTP (backend);
2030
2031 setup_ssh_environment ();
2032
2033 op_backend->password_save = G_PASSWORD_SAVE_NEVER;
2034 do_mount (backend, job, mount_spec, mount_source, is_automount);
2035 }
2036
2037 static gboolean
try_mount(GVfsBackend * backend,GVfsJobMount * job,GMountSpec * mount_spec,GMountSource * mount_source,gboolean is_automount)2038 try_mount (GVfsBackend *backend,
2039 GVfsJobMount *job,
2040 GMountSpec *mount_spec,
2041 GMountSource *mount_source,
2042 gboolean is_automount)
2043 {
2044 GVfsBackendSftp *op_backend = G_VFS_BACKEND_SFTP (backend);
2045 const char *user, *host, *port;
2046
2047 op_backend->client_vendor = get_sftp_client_vendor ();
2048
2049 if (op_backend->client_vendor == SFTP_VENDOR_INVALID)
2050 {
2051 g_vfs_job_failed (G_VFS_JOB (job),
2052 G_IO_ERROR, G_IO_ERROR_FAILED,
2053 _("Unable to find supported SSH command"));
2054 return TRUE;
2055 }
2056
2057 host = g_mount_spec_get (mount_spec, "host");
2058
2059 if (host == NULL)
2060 {
2061 g_vfs_job_failed (G_VFS_JOB (job),
2062 G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT,
2063 _("No hostname specified"));
2064 return TRUE;
2065 }
2066
2067 port = g_mount_spec_get (mount_spec, "port");
2068 op_backend->port = -1;
2069 if (port != NULL)
2070 {
2071 int p = atoi (port);
2072 if (p != 22)
2073 op_backend->port = p;
2074 }
2075
2076 user = g_mount_spec_get (mount_spec, "user");
2077
2078 op_backend->host = g_strdup (host);
2079 op_backend->user = g_strdup (user);
2080 if (op_backend->user)
2081 {
2082 op_backend->user_specified = TRUE;
2083 op_backend->user_specified_in_uri = TRUE;
2084 }
2085
2086
2087 return FALSE;
2088 }
2089
2090 static gboolean
try_unmount(GVfsBackend * backend,GVfsJobUnmount * job,GMountUnmountFlags flags,GMountSource * mount_source)2091 try_unmount (GVfsBackend *backend,
2092 GVfsJobUnmount *job,
2093 GMountUnmountFlags flags,
2094 GMountSource *mount_source)
2095 {
2096 GVfsBackendSftp *op_backend = G_VFS_BACKEND_SFTP (backend);
2097
2098 if (op_backend->command_connection.reply_stream &&
2099 op_backend->command_connection.reply_stream_cancellable)
2100 g_cancellable_cancel (op_backend->command_connection.reply_stream_cancellable);
2101 if (op_backend->data_connection.reply_stream &&
2102 op_backend->data_connection.reply_stream_cancellable)
2103 g_cancellable_cancel (op_backend->data_connection.reply_stream_cancellable);
2104 g_vfs_job_succeeded (G_VFS_JOB (job));
2105
2106 return TRUE;
2107 }
2108
2109 static int
io_error_code_for_sftp_error(guint32 code,int failure_error)2110 io_error_code_for_sftp_error (guint32 code, int failure_error)
2111 {
2112 int error_code;
2113
2114 error_code = G_IO_ERROR_FAILED;
2115
2116 switch (code)
2117 {
2118 default:
2119 case SSH_FX_EOF:
2120 case SSH_FX_BAD_MESSAGE:
2121 case SSH_FX_NO_CONNECTION:
2122 case SSH_FX_CONNECTION_LOST:
2123 break;
2124
2125 case SSH_FX_FAILURE:
2126 error_code = failure_error;
2127 break;
2128
2129 case SSH_FX_NO_SUCH_FILE:
2130 error_code = G_IO_ERROR_NOT_FOUND;
2131 break;
2132
2133 case SSH_FX_PERMISSION_DENIED:
2134 error_code = G_IO_ERROR_PERMISSION_DENIED;
2135 break;
2136
2137 case SSH_FX_OP_UNSUPPORTED:
2138 error_code = G_IO_ERROR_NOT_SUPPORTED;
2139 break;
2140 }
2141 return error_code;
2142 }
2143
2144 static char *
error_message(GIOErrorEnum error)2145 error_message (GIOErrorEnum error)
2146 {
2147 switch (error)
2148 {
2149 case G_IO_ERROR_NOT_EMPTY:
2150 return _("Directory not empty");
2151 case G_IO_ERROR_EXISTS:
2152 return _("Target file exists");
2153 case G_IO_ERROR_NOT_SUPPORTED:
2154 return _("Operation not supported");
2155 case G_IO_ERROR_PERMISSION_DENIED:
2156 return _("Permission denied");
2157 case G_IO_ERROR_NOT_FOUND:
2158 return _("No such file or directory");
2159 default:
2160 return _("Unknown reason");
2161 }
2162 }
2163
2164 static gboolean
error_from_status_code(GVfsJob * job,guint32 code,int failure_error,int allowed_sftp_error,GError ** error)2165 error_from_status_code (GVfsJob *job,
2166 guint32 code,
2167 int failure_error,
2168 int allowed_sftp_error,
2169 GError **error)
2170 {
2171 gint error_code;
2172
2173 if (failure_error == -1)
2174 failure_error = G_IO_ERROR_FAILED;
2175
2176 if (code == SSH_FX_OK ||
2177 (allowed_sftp_error != -1 &&
2178 code == allowed_sftp_error))
2179 return TRUE;
2180
2181 if (error)
2182 {
2183 error_code = io_error_code_for_sftp_error (code, failure_error);
2184 *error = g_error_new_literal (G_IO_ERROR, error_code,
2185 error_message (error_code));
2186 }
2187
2188 return FALSE;
2189 }
2190
2191 static gboolean
failure_from_status_code(GVfsJob * job,guint32 code,int failure_error,int allowed_sftp_error)2192 failure_from_status_code (GVfsJob *job,
2193 guint32 code,
2194 int failure_error,
2195 int allowed_sftp_error)
2196 {
2197 GError *error;
2198
2199 error = NULL;
2200 if (error_from_status_code (job, code, failure_error, allowed_sftp_error, &error))
2201 return TRUE;
2202 else
2203 {
2204 g_vfs_job_failed_from_error (job, error);
2205 g_error_free (error);
2206 }
2207 return FALSE;
2208 }
2209
2210 static gboolean
result_from_status_code(GVfsJob * job,guint32 code,int failure_error,int allowed_sftp_error)2211 result_from_status_code (GVfsJob *job,
2212 guint32 code,
2213 int failure_error,
2214 int allowed_sftp_error)
2215 {
2216 gboolean res;
2217
2218 res = failure_from_status_code (job, code,
2219 failure_error,
2220 allowed_sftp_error);
2221 if (res)
2222 g_vfs_job_succeeded (job);
2223
2224 return res;
2225 }
2226
2227 static guint32
read_status_code(GDataInputStream * status_reply)2228 read_status_code (GDataInputStream *status_reply)
2229 {
2230 return g_data_input_stream_read_uint32 (status_reply, NULL, NULL);
2231 }
2232
2233 static gboolean
error_from_status(GVfsJob * job,GDataInputStream * reply,int failure_error,int allowed_sftp_error,GError ** error)2234 error_from_status (GVfsJob *job,
2235 GDataInputStream *reply,
2236 int failure_error,
2237 int allowed_sftp_error,
2238 GError **error)
2239 {
2240 guint32 code;
2241
2242 code = read_status_code (reply);
2243
2244 return error_from_status_code (job, code,
2245 failure_error, allowed_sftp_error,
2246 error);
2247 }
2248
2249 static gboolean
failure_from_status(GVfsJob * job,GDataInputStream * reply,int failure_error,int allowed_sftp_error)2250 failure_from_status (GVfsJob *job,
2251 GDataInputStream *reply,
2252 int failure_error,
2253 int allowed_sftp_error)
2254 {
2255 GError *error;
2256
2257 error = NULL;
2258 if (error_from_status (job, reply, failure_error, allowed_sftp_error, &error))
2259 return TRUE;
2260 else
2261 {
2262 g_vfs_job_failed_from_error (job, error);
2263 g_error_free (error);
2264 }
2265 return FALSE;
2266 }
2267
2268 static gboolean
result_from_status(GVfsJob * job,GDataInputStream * reply,int failure_error,int allowed_sftp_error)2269 result_from_status (GVfsJob *job,
2270 GDataInputStream *reply,
2271 int failure_error,
2272 int allowed_sftp_error)
2273 {
2274 gboolean res;
2275
2276 res = failure_from_status (job, reply,
2277 failure_error,
2278 allowed_sftp_error);
2279 if (res)
2280 g_vfs_job_succeeded (job);
2281
2282 return res;
2283 }
2284
2285
2286
2287 typedef struct _ErrorFromStatData ErrorFromStatData;
2288
2289 typedef void (*ErrorFromStatCallback) (GVfsBackendSftp *backend,
2290 GVfsJob *job,
2291 gint original_error,
2292 gint stat_error,
2293 GFileInfo *info,
2294 gpointer user_data);
2295
2296
2297 struct _ErrorFromStatData {
2298 gint original_error;
2299 ErrorFromStatCallback callback;
2300 gpointer user_data;
2301 };
2302
2303 static void
error_from_lstat_reply(GVfsBackendSftp * backend,int reply_type,GDataInputStream * reply,guint32 len,GVfsJob * job,gpointer user_data)2304 error_from_lstat_reply (GVfsBackendSftp *backend,
2305 int reply_type,
2306 GDataInputStream *reply,
2307 guint32 len,
2308 GVfsJob *job,
2309 gpointer user_data)
2310 {
2311 ErrorFromStatData *data = user_data;
2312 GFileInfo *info;
2313 gint stat_error;
2314
2315 stat_error = 0;
2316 info = NULL;
2317 if (reply_type == SSH_FXP_STATUS)
2318 stat_error = read_status_code (reply);
2319 else if (reply_type == SSH_FXP_ATTRS)
2320 {
2321 info = g_file_info_new ();
2322 parse_attributes (backend,
2323 info,
2324 NULL,
2325 reply,
2326 NULL);
2327 }
2328 else
2329 {
2330 g_vfs_job_failed (job, G_IO_ERROR, G_IO_ERROR_FAILED,
2331 _("Invalid reply received"));
2332 goto out;
2333 }
2334
2335 data->callback (backend, job,
2336 data->original_error,
2337 stat_error,
2338 info,
2339 data->user_data);
2340
2341 out:
2342 g_slice_free (ErrorFromStatData, data);
2343 }
2344
2345 static void
error_from_lstat(GVfsBackendSftp * backend,GVfsJob * job,guint32 original_error,const char * path,ErrorFromStatCallback callback,gpointer user_data)2346 error_from_lstat (GVfsBackendSftp *backend,
2347 GVfsJob *job,
2348 guint32 original_error,
2349 const char *path,
2350 ErrorFromStatCallback callback,
2351 gpointer user_data)
2352 {
2353 GVfsBackendSftp *op_backend = G_VFS_BACKEND_SFTP (backend);
2354 GDataOutputStream *command;
2355 ErrorFromStatData *data;
2356
2357 command = new_command_stream (op_backend, SSH_FXP_LSTAT);
2358 put_string (command, path);
2359
2360 data = g_slice_new (ErrorFromStatData);
2361 data->original_error = original_error;
2362 data->callback = callback;
2363 data->user_data = user_data;
2364 queue_command_stream_and_free (&op_backend->command_connection, command,
2365 error_from_lstat_reply,
2366 G_VFS_JOB (job), data);
2367 }
2368
2369 static void
set_access_attributes_trusted(GFileInfo * info,guint32 perm)2370 set_access_attributes_trusted (GFileInfo *info,
2371 guint32 perm)
2372 {
2373 g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_READ,
2374 perm & 0x4);
2375 g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE,
2376 perm & 0x2);
2377 g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_EXECUTE,
2378 perm & 0x1);
2379 }
2380
2381 /* For files we don't own we can't trust a negative response to this check, as
2382 something else could allow us to do the operation, for instance an ACL
2383 or some sticky bit thing */
2384 static void
set_access_attributes(GFileInfo * info,guint32 perm)2385 set_access_attributes (GFileInfo *info,
2386 guint32 perm)
2387 {
2388 if (perm & 0x4)
2389 g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_READ,
2390 TRUE);
2391 if (perm & 0x2)
2392 g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE,
2393 TRUE);
2394 if (perm & 0x1)
2395 g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_EXECUTE,
2396 TRUE);
2397 }
2398
2399
2400 static void
parse_attributes(GVfsBackendSftp * backend,GFileInfo * info,const char * basename,GDataInputStream * reply,GFileAttributeMatcher * matcher)2401 parse_attributes (GVfsBackendSftp *backend,
2402 GFileInfo *info,
2403 const char *basename,
2404 GDataInputStream *reply,
2405 GFileAttributeMatcher *matcher)
2406 {
2407 guint32 flags;
2408 GFileType type;
2409 guint32 uid, gid;
2410 guint32 mode;
2411 gboolean has_uid, free_mimetype;
2412 char *mimetype;
2413 gboolean uncertain_content_type;
2414
2415 flags = g_data_input_stream_read_uint32 (reply, NULL, NULL);
2416
2417 if (basename != NULL && basename[0] == '.')
2418 g_file_info_set_is_hidden (info, TRUE);
2419
2420 if (basename != NULL)
2421 g_file_info_set_name (info, basename);
2422 else
2423 g_file_info_set_name (info, "/");
2424
2425 if (basename != NULL && basename[strlen (basename) -1] == '~')
2426 g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_STANDARD_IS_BACKUP, TRUE);
2427
2428 if (flags & SSH_FILEXFER_ATTR_SIZE)
2429 {
2430 guint64 size = g_data_input_stream_read_uint64 (reply, NULL, NULL);
2431 g_file_info_set_size (info, size);
2432 }
2433
2434 has_uid = FALSE;
2435 uid = gid = 0; /* Avoid warnings */
2436 if (flags & SSH_FILEXFER_ATTR_UIDGID)
2437 {
2438 has_uid = TRUE;
2439 uid = g_data_input_stream_read_uint32 (reply, NULL, NULL);
2440 g_file_info_set_attribute_uint32 (info, G_FILE_ATTRIBUTE_UNIX_UID, uid);
2441 gid = g_data_input_stream_read_uint32 (reply, NULL, NULL);
2442 g_file_info_set_attribute_uint32 (info, G_FILE_ATTRIBUTE_UNIX_GID, gid);
2443 }
2444
2445 type = G_FILE_TYPE_UNKNOWN;
2446
2447 if (flags & SSH_FILEXFER_ATTR_PERMISSIONS)
2448 {
2449 mode = g_data_input_stream_read_uint32 (reply, NULL, NULL);
2450 g_file_info_set_attribute_uint32 (info, G_FILE_ATTRIBUTE_UNIX_MODE, mode);
2451
2452 mimetype = NULL;
2453 uncertain_content_type = FALSE;
2454 if (S_ISREG (mode))
2455 type = G_FILE_TYPE_REGULAR;
2456 else if (S_ISDIR (mode))
2457 {
2458 type = G_FILE_TYPE_DIRECTORY;
2459 mimetype = "inode/directory";
2460 }
2461 else if (S_ISFIFO (mode))
2462 {
2463 type = G_FILE_TYPE_SPECIAL;
2464 mimetype = "inode/fifo";
2465 }
2466 else if (S_ISSOCK (mode))
2467 {
2468 type = G_FILE_TYPE_SPECIAL;
2469 mimetype = "inode/socket";
2470 }
2471 else if (S_ISCHR (mode))
2472 {
2473 type = G_FILE_TYPE_SPECIAL;
2474 mimetype = "inode/chardevice";
2475 }
2476 else if (S_ISBLK (mode))
2477 {
2478 type = G_FILE_TYPE_SPECIAL;
2479 mimetype = "inode/blockdevice";
2480 }
2481 else if (S_ISLNK (mode))
2482 {
2483 type = G_FILE_TYPE_SYMBOLIC_LINK;
2484 g_file_info_set_is_symlink (info, TRUE);
2485 mimetype = "inode/symlink";
2486 }
2487
2488 free_mimetype = FALSE;
2489 if (mimetype == NULL)
2490 {
2491 if (basename)
2492 {
2493 mimetype = g_content_type_guess (basename, NULL, 0, &uncertain_content_type);
2494 free_mimetype = TRUE;
2495 }
2496 else
2497 mimetype = "application/octet-stream";
2498 }
2499
2500 if (!uncertain_content_type)
2501 g_file_info_set_content_type (info, mimetype);
2502 g_file_info_set_attribute_string (info, G_FILE_ATTRIBUTE_STANDARD_FAST_CONTENT_TYPE, mimetype);
2503
2504 if (g_file_attribute_matcher_matches (matcher,
2505 G_FILE_ATTRIBUTE_STANDARD_ICON)
2506 || g_file_attribute_matcher_matches (matcher,
2507 G_FILE_ATTRIBUTE_STANDARD_SYMBOLIC_ICON))
2508 {
2509 GIcon *icon = NULL;
2510 GIcon *symbolic_icon = NULL;
2511
2512 icon = g_content_type_get_icon (mimetype);
2513 symbolic_icon = g_content_type_get_symbolic_icon (mimetype);
2514
2515 if (icon == NULL)
2516 icon = g_themed_icon_new ("text-x-generic");
2517 if (symbolic_icon == NULL)
2518 symbolic_icon = g_themed_icon_new ("text-x-generic-symbolic");
2519
2520 g_file_info_set_icon (info, icon);
2521 g_file_info_set_symbolic_icon (info, symbolic_icon);
2522 g_object_unref (icon);
2523 g_object_unref (symbolic_icon);
2524 }
2525
2526
2527 if (free_mimetype)
2528 g_free (mimetype);
2529
2530 if (has_uid && backend->my_uid != (guint32)-1)
2531 {
2532 if (uid == backend->my_uid)
2533 set_access_attributes_trusted (info, (mode >> 6) & 0x7);
2534 else if (gid == backend->my_gid)
2535 set_access_attributes (info, (mode >> 3) & 0x7);
2536 else
2537 set_access_attributes (info, (mode >> 0) & 0x7);
2538 }
2539
2540 }
2541
2542 g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_TRASH, FALSE);
2543
2544 g_file_info_set_file_type (info, type);
2545
2546 if (flags & SSH_FILEXFER_ATTR_ACMODTIME)
2547 {
2548 guint32 v;
2549 char *etag;
2550
2551 v = g_data_input_stream_read_uint32 (reply, NULL, NULL);
2552 g_file_info_set_attribute_uint64 (info, G_FILE_ATTRIBUTE_TIME_ACCESS, v);
2553 v = g_data_input_stream_read_uint32 (reply, NULL, NULL);
2554 g_file_info_set_attribute_uint64 (info, G_FILE_ATTRIBUTE_TIME_MODIFIED, v);
2555
2556 etag = g_strdup_printf ("%lu", (long unsigned int)v);
2557 g_file_info_set_attribute_string (info, G_FILE_ATTRIBUTE_ETAG_VALUE, etag);
2558 g_free (etag);
2559 }
2560
2561 if (flags & SSH_FILEXFER_ATTR_EXTENDED)
2562 {
2563 guint32 count, i;
2564 char *name, *val;
2565 count = g_data_input_stream_read_uint32 (reply, NULL, NULL);
2566 for (i = 0; i < count; i++)
2567 {
2568 name = read_string (reply, NULL);
2569 val = read_string (reply, NULL);
2570
2571 g_free (name);
2572 g_free (val);
2573 }
2574 }
2575
2576 /* We use the same setting as for local files. Can't really
2577 * do better, since there is no way in this version of sftp to find out
2578 * the remote charset encoding
2579 */
2580 if (g_file_attribute_matcher_matches (matcher,
2581 G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME))
2582 {
2583 if (basename != NULL)
2584 {
2585 char *display_name = g_filename_display_name (basename);
2586
2587 if (strstr (display_name, "\357\277\275") != NULL)
2588 {
2589 char *p = display_name;
2590 display_name = g_strconcat (display_name, _(" (invalid encoding)"), NULL);
2591 g_free (p);
2592 }
2593 g_file_info_set_display_name (info, display_name);
2594 g_free (display_name);
2595 }
2596 else
2597 {
2598 char *name;
2599
2600
2601 /* Translators: This is the name of the root of an SFTP share, like "/ on <hostname>" */
2602 name = g_strdup_printf (_("/ on %s"), G_VFS_BACKEND_SFTP (backend)->host);
2603 g_file_info_set_display_name (info, name);
2604 g_free (name);
2605 }
2606 }
2607
2608 if (basename != NULL &&
2609 g_file_attribute_matcher_matches (matcher,
2610 G_FILE_ATTRIBUTE_STANDARD_EDIT_NAME))
2611 {
2612 char *edit_name = g_filename_display_name (basename);
2613 g_file_info_set_edit_name (info, edit_name);
2614 g_free (edit_name);
2615 }
2616 }
2617
2618 static SftpHandle *
sftp_handle_new(GDataInputStream * reply)2619 sftp_handle_new (GDataInputStream *reply)
2620 {
2621 SftpHandle *handle;
2622
2623 handle = g_slice_new0 (SftpHandle);
2624 handle->raw_handle = read_data_buffer (reply);
2625 handle->offset = 0;
2626
2627 return handle;
2628 }
2629
2630 static void
sftp_handle_free(SftpHandle * handle)2631 sftp_handle_free (SftpHandle *handle)
2632 {
2633 data_buffer_free (handle->raw_handle);
2634 g_free (handle->filename);
2635 g_free (handle->tempname);
2636 g_slice_free (SftpHandle, handle);
2637 }
2638
2639 static void
open_stat_reply(GVfsBackendSftp * backend,int reply_type,GDataInputStream * reply,guint32 len,GVfsJob * job,gpointer user_data)2640 open_stat_reply (GVfsBackendSftp *backend,
2641 int reply_type,
2642 GDataInputStream *reply,
2643 guint32 len,
2644 GVfsJob *job,
2645 gpointer user_data)
2646 {
2647 if (g_vfs_job_is_finished (job))
2648 {
2649 /* Handled in stat reply */
2650 return;
2651 }
2652
2653 if (reply_type == SSH_FXP_ATTRS)
2654 {
2655 GFileType type;
2656 GFileInfo *info = g_file_info_new ();
2657
2658 parse_attributes (backend, info, NULL,
2659 reply, NULL);
2660 type = g_file_info_get_file_type (info);
2661 g_object_unref (info);
2662
2663 if (type == G_FILE_TYPE_DIRECTORY)
2664 {
2665 g_vfs_job_failed (job, G_IO_ERROR, G_IO_ERROR_IS_DIRECTORY,
2666 _("File is directory"));
2667 return;
2668 }
2669 }
2670
2671 if (GPOINTER_TO_INT (G_VFS_JOB(job)->backend_data) == 1)
2672 {
2673 /* We ran the read_reply and it was a generic failure */
2674 g_vfs_job_failed (job, G_IO_ERROR, G_IO_ERROR_FAILED,
2675 _("Failure"));
2676 }
2677 }
2678
2679 static void
open_for_read_reply(GVfsBackendSftp * backend,int reply_type,GDataInputStream * reply,guint32 len,GVfsJob * job,gpointer user_data)2680 open_for_read_reply (GVfsBackendSftp *backend,
2681 int reply_type,
2682 GDataInputStream *reply,
2683 guint32 len,
2684 GVfsJob *job,
2685 gpointer user_data)
2686 {
2687 SftpHandle *handle;
2688
2689 if (g_vfs_job_is_finished (job))
2690 {
2691 /* Handled in stat reply */
2692
2693 /* Normally this should not happen as we
2694 sent an is_dir error. But i guess we can
2695 race */
2696 if (reply_type == SSH_FXP_HANDLE)
2697 {
2698 GDataOutputStream *command;
2699 DataBuffer *bhandle;
2700
2701 bhandle = read_data_buffer (reply);
2702
2703 command = new_command_stream (backend, SSH_FXP_CLOSE);
2704 put_data_buffer (command, bhandle);
2705 queue_command_stream_and_free (&backend->command_connection, command,
2706 NULL,
2707 G_VFS_JOB (job), NULL);
2708
2709 data_buffer_free (bhandle);
2710 }
2711
2712 return;
2713 }
2714
2715 if (reply_type == SSH_FXP_STATUS)
2716 {
2717 if (failure_from_status (job,
2718 reply,
2719 -1,
2720 SSH_FX_FAILURE))
2721 {
2722 /* Unknown failure type, mark that we got this and
2723 return result from stat result */
2724 G_VFS_JOB(job)->backend_data = GINT_TO_POINTER (1);
2725 }
2726
2727 return;
2728 }
2729
2730 if (reply_type != SSH_FXP_HANDLE)
2731 {
2732 g_vfs_job_failed (job, G_IO_ERROR, G_IO_ERROR_FAILED,
2733 _("Invalid reply received"));
2734 return;
2735 }
2736
2737 handle = sftp_handle_new (reply);
2738
2739 g_vfs_job_open_for_read_set_handle (G_VFS_JOB_OPEN_FOR_READ (job), handle);
2740 g_vfs_job_open_for_read_set_can_seek (G_VFS_JOB_OPEN_FOR_READ (job), TRUE);
2741 g_vfs_job_succeeded (job);
2742 }
2743
2744 static gboolean
try_open_for_read(GVfsBackend * backend,GVfsJobOpenForRead * job,const char * filename)2745 try_open_for_read (GVfsBackend *backend,
2746 GVfsJobOpenForRead *job,
2747 const char *filename)
2748 {
2749 GVfsBackendSftp *op_backend = G_VFS_BACKEND_SFTP (backend);
2750 GDataOutputStream *command;
2751
2752 G_VFS_JOB(job)->backend_data = GINT_TO_POINTER (0);
2753
2754 command = new_command_stream (op_backend,
2755 SSH_FXP_STAT);
2756 put_string (command, filename);
2757 queue_command_stream_and_free (&op_backend->command_connection, command,
2758 open_stat_reply,
2759 G_VFS_JOB (job), NULL);
2760
2761 command = new_command_stream (op_backend,
2762 SSH_FXP_OPEN);
2763 put_string (command, filename);
2764 g_data_output_stream_put_uint32 (command, SSH_FXF_READ, NULL, NULL); /* open flags */
2765 g_data_output_stream_put_uint32 (command, 0, NULL, NULL); /* Attr flags */
2766
2767 queue_command_stream_and_free (&op_backend->command_connection, command,
2768 open_for_read_reply,
2769 G_VFS_JOB (job), NULL);
2770
2771 return TRUE;
2772 }
2773
2774 static void
read_reply(GVfsBackendSftp * backend,int reply_type,GDataInputStream * reply,guint32 len,GVfsJob * job,gpointer user_data)2775 read_reply (GVfsBackendSftp *backend,
2776 int reply_type,
2777 GDataInputStream *reply,
2778 guint32 len,
2779 GVfsJob *job,
2780 gpointer user_data)
2781 {
2782 SftpHandle *handle;
2783 guint32 count;
2784
2785 handle = user_data;
2786
2787 if (reply_type == SSH_FXP_STATUS)
2788 {
2789 result_from_status (job, reply, -1, SSH_FX_EOF);
2790 return;
2791 }
2792
2793 if (reply_type != SSH_FXP_DATA)
2794 {
2795 g_vfs_job_failed (job, G_IO_ERROR, G_IO_ERROR_FAILED,
2796 _("Invalid reply received"));
2797 return;
2798 }
2799
2800 count = g_data_input_stream_read_uint32 (reply, NULL, NULL);
2801
2802 if (!g_input_stream_read_all (G_INPUT_STREAM (reply),
2803 G_VFS_JOB_READ (job)->buffer, count,
2804 NULL, NULL, NULL))
2805 {
2806 g_vfs_job_failed (job, G_IO_ERROR, G_IO_ERROR_FAILED,
2807 _("Invalid reply received"));
2808 return;
2809 }
2810
2811 handle->offset += count;
2812
2813 g_vfs_job_read_set_size (G_VFS_JOB_READ (job), count);
2814 g_vfs_job_succeeded (job);
2815 }
2816
2817 static gboolean
try_read(GVfsBackend * backend,GVfsJobRead * job,GVfsBackendHandle _handle,char * buffer,gsize bytes_requested)2818 try_read (GVfsBackend *backend,
2819 GVfsJobRead *job,
2820 GVfsBackendHandle _handle,
2821 char *buffer,
2822 gsize bytes_requested)
2823 {
2824 SftpHandle *handle = _handle;
2825 GVfsBackendSftp *op_backend = G_VFS_BACKEND_SFTP (backend);
2826 GDataOutputStream *command;
2827
2828 command = new_command_stream (op_backend,
2829 SSH_FXP_READ);
2830 put_data_buffer (command, handle->raw_handle);
2831 g_data_output_stream_put_uint64 (command, handle->offset, NULL, NULL);
2832 g_data_output_stream_put_uint32 (command, bytes_requested, NULL, NULL);
2833
2834 queue_command_stream_and_free (&op_backend->command_connection, command,
2835 read_reply,
2836 G_VFS_JOB (job), handle);
2837
2838 return TRUE;
2839 }
2840
2841 static void
seek_read_fstat_reply(GVfsBackendSftp * backend,int reply_type,GDataInputStream * reply,guint32 len,GVfsJob * job,gpointer user_data)2842 seek_read_fstat_reply (GVfsBackendSftp *backend,
2843 int reply_type,
2844 GDataInputStream *reply,
2845 guint32 len,
2846 GVfsJob *job,
2847 gpointer user_data)
2848 {
2849 SftpHandle *handle;
2850 GFileInfo *info;
2851 goffset file_size;
2852 GVfsJobSeekRead *op_job;
2853
2854 handle = user_data;
2855
2856 if (reply_type == SSH_FXP_STATUS)
2857 {
2858 result_from_status (job, reply, -1, -1);
2859 return;
2860 }
2861
2862 if (reply_type != SSH_FXP_ATTRS)
2863 {
2864 g_vfs_job_failed (job, G_IO_ERROR, G_IO_ERROR_FAILED,
2865 _("Invalid reply received"));
2866 return;
2867 }
2868
2869 info = g_file_info_new ();
2870 parse_attributes (backend, info, NULL,
2871 reply, NULL);
2872 file_size = g_file_info_get_size (info);
2873 g_object_unref (info);
2874
2875 op_job = G_VFS_JOB_SEEK_READ (job);
2876
2877 handle->offset = file_size + op_job->requested_offset;
2878
2879 if (handle->offset < 0)
2880 handle->offset = 0;
2881
2882 g_vfs_job_seek_read_set_offset (op_job, handle->offset);
2883 g_vfs_job_succeeded (job);
2884 }
2885
2886 static gboolean
try_seek_on_read(GVfsBackend * backend,GVfsJobSeekRead * job,GVfsBackendHandle _handle,goffset offset,GSeekType type)2887 try_seek_on_read (GVfsBackend *backend,
2888 GVfsJobSeekRead *job,
2889 GVfsBackendHandle _handle,
2890 goffset offset,
2891 GSeekType type)
2892 {
2893 SftpHandle *handle = _handle;
2894 GVfsBackendSftp *op_backend = G_VFS_BACKEND_SFTP (backend);
2895 GDataOutputStream *command;
2896
2897 switch (job->seek_type)
2898 {
2899 case G_SEEK_CUR:
2900 handle->offset += job->requested_offset;
2901 break;
2902 case G_SEEK_SET:
2903 handle->offset = job->requested_offset;
2904 break;
2905 case G_SEEK_END:
2906 command = new_command_stream (op_backend,
2907 SSH_FXP_FSTAT);
2908 put_data_buffer (command, handle->raw_handle);
2909 queue_command_stream_and_free (&op_backend->command_connection, command,
2910 seek_read_fstat_reply,
2911 G_VFS_JOB (job), handle);
2912 return TRUE;
2913 }
2914
2915 if (handle->offset < 0)
2916 handle->offset = 0;
2917
2918 g_vfs_job_seek_read_set_offset (job, handle->offset);
2919 g_vfs_job_succeeded (G_VFS_JOB (job));
2920
2921 return TRUE;
2922 }
2923
2924 static void
delete_temp_file(GVfsBackendSftp * backend,SftpHandle * handle,GVfsJob * job)2925 delete_temp_file (GVfsBackendSftp *backend,
2926 SftpHandle *handle,
2927 GVfsJob *job)
2928 {
2929 GDataOutputStream *command;
2930
2931 if (handle->tempname)
2932 {
2933 command = new_command_stream (backend,
2934 SSH_FXP_REMOVE);
2935 put_string (command, handle->tempname);
2936 queue_command_stream_and_free (&backend->command_connection, command,
2937 NULL,
2938 job, NULL);
2939 }
2940 }
2941
2942 static void
close_moved_tempfile(GVfsBackendSftp * backend,int reply_type,GDataInputStream * reply,guint32 len,GVfsJob * job,gpointer user_data)2943 close_moved_tempfile (GVfsBackendSftp *backend,
2944 int reply_type,
2945 GDataInputStream *reply,
2946 guint32 len,
2947 GVfsJob *job,
2948 gpointer user_data)
2949 {
2950 SftpHandle *handle;
2951
2952 handle = user_data;
2953
2954 if (reply_type == SSH_FXP_STATUS)
2955 result_from_status (job, reply, -1, -1);
2956 else
2957 g_vfs_job_failed (job, G_IO_ERROR, G_IO_ERROR_FAILED,
2958 _("Invalid reply received"));
2959
2960 /* On failure, don't remove tempfile, since we removed the new original file */
2961 sftp_handle_free (handle);
2962 }
2963
2964 static void
close_restore_permissions(GVfsBackendSftp * backend,int reply_type,GDataInputStream * reply,guint32 len,GVfsJob * job,gpointer user_data)2965 close_restore_permissions (GVfsBackendSftp *backend,
2966 int reply_type,
2967 GDataInputStream *reply,
2968 guint32 len,
2969 GVfsJob *job,
2970 gpointer user_data)
2971 {
2972 GDataOutputStream *command;
2973 SftpHandle *handle;
2974
2975 handle = user_data;
2976
2977 /* Here we don't really care whether or not setting the permissions succeeded
2978 or not. We just take the last step and rename the temp file to the
2979 actual file */
2980 command = new_command_stream (backend,
2981 SSH_FXP_RENAME);
2982 put_string (command, handle->tempname);
2983 put_string (command, handle->filename);
2984 queue_command_stream_and_free (&backend->command_connection, command,
2985 close_moved_tempfile,
2986 G_VFS_JOB (job), handle);
2987 }
2988
2989 static void
close_deleted_file(GVfsBackendSftp * backend,int reply_type,GDataInputStream * reply,guint32 len,GVfsJob * job,gpointer user_data)2990 close_deleted_file (GVfsBackendSftp *backend,
2991 int reply_type,
2992 GDataInputStream *reply,
2993 guint32 len,
2994 GVfsJob *job,
2995 gpointer user_data)
2996 {
2997 GDataOutputStream *command;
2998 GError *error;
2999 gboolean res;
3000 SftpHandle *handle;
3001
3002 handle = user_data;
3003
3004 error = NULL;
3005 res = FALSE;
3006 if (reply_type == SSH_FXP_STATUS)
3007 res = error_from_status (job, reply, -1, -1, &error);
3008 else
3009 g_set_error_literal (&error, G_IO_ERROR, G_IO_ERROR_FAILED,
3010 _("Invalid reply received"));
3011
3012 if (res)
3013 {
3014 /* Removed original file, now first try to restore permissions */
3015 if (handle->set_permissions)
3016 {
3017 command = new_command_stream (backend, SSH_FXP_SETSTAT);
3018 put_string (command, handle->tempname);
3019 g_data_output_stream_put_uint32 (command, SSH_FILEXFER_ATTR_PERMISSIONS, NULL, NULL);
3020 g_data_output_stream_put_uint32 (command, handle->permissions, NULL, NULL);
3021 queue_command_stream_and_free (&backend->command_connection, command,
3022 close_restore_permissions,
3023 G_VFS_JOB (job), handle);
3024 }
3025 else
3026 {
3027 /* Skip restoring permissions by calling the callback directly */
3028 close_restore_permissions(backend, 0, NULL, 0, job, user_data);
3029 }
3030 }
3031 else
3032 {
3033 /* The delete failed, remove any temporary files */
3034 delete_temp_file (backend,
3035 handle,
3036 G_VFS_JOB (job));
3037
3038 g_vfs_job_failed_from_error (job, error);
3039 g_error_free (error);
3040 sftp_handle_free (handle);
3041 }
3042 }
3043
3044 static void
close_moved_file(GVfsBackendSftp * backend,int reply_type,GDataInputStream * reply,guint32 len,GVfsJob * job,gpointer user_data)3045 close_moved_file (GVfsBackendSftp *backend,
3046 int reply_type,
3047 GDataInputStream *reply,
3048 guint32 len,
3049 GVfsJob *job,
3050 gpointer user_data)
3051 {
3052 GDataOutputStream *command;
3053 GError *error;
3054 gboolean res;
3055 SftpHandle *handle;
3056
3057 handle = user_data;
3058
3059 error = NULL;
3060 res = FALSE;
3061 if (reply_type == SSH_FXP_STATUS)
3062 res = error_from_status (job, reply, -1, -1, &error);
3063 else
3064 g_set_error_literal (&error, G_IO_ERROR, G_IO_ERROR_FAILED,
3065 _("Invalid reply received"));
3066
3067 if (res)
3068 {
3069 /* moved original file to backup, now move new file in place */
3070
3071 command = new_command_stream (backend,
3072 SSH_FXP_RENAME);
3073 put_string (command, handle->tempname);
3074 put_string (command, handle->filename);
3075 queue_command_stream_and_free (&backend->command_connection, command,
3076 close_moved_tempfile,
3077 G_VFS_JOB (job), handle);
3078 }
3079 else
3080 {
3081 /* Move original file to backup name failed, remove any temporary files */
3082 delete_temp_file (backend,
3083 handle,
3084 G_VFS_JOB (job));
3085
3086 g_vfs_job_failed (job, G_IO_ERROR, G_IO_ERROR_CANT_CREATE_BACKUP,
3087 _("Error creating backup file: %s"), error->message);
3088 g_error_free (error);
3089 sftp_handle_free (handle);
3090 }
3091 }
3092
3093 static void
close_deleted_backup(GVfsBackendSftp * backend,int reply_type,GDataInputStream * reply,guint32 len,GVfsJob * job,gpointer user_data)3094 close_deleted_backup (GVfsBackendSftp *backend,
3095 int reply_type,
3096 GDataInputStream *reply,
3097 guint32 len,
3098 GVfsJob *job,
3099 gpointer user_data)
3100 {
3101 SftpHandle *handle;
3102 GDataOutputStream *command;
3103 char *backup_name;
3104
3105 /* Ignore result here, if it failed we'll just get a new error when moving over it
3106 * This is simpler than ignoring NOEXIST errors
3107 */
3108
3109 handle = user_data;
3110
3111 command = new_command_stream (backend,
3112 SSH_FXP_RENAME);
3113 backup_name = g_strconcat (handle->filename, "~", NULL);
3114 put_string (command, handle->filename);
3115 put_string (command, backup_name);
3116 g_free (backup_name);
3117 queue_command_stream_and_free (&backend->command_connection, command,
3118 close_moved_file,
3119 G_VFS_JOB (job), handle);
3120 }
3121
3122 static void
close_write_reply(GVfsBackendSftp * backend,int reply_type,GDataInputStream * reply,guint32 len,GVfsJob * job,gpointer user_data)3123 close_write_reply (GVfsBackendSftp *backend,
3124 int reply_type,
3125 GDataInputStream *reply,
3126 guint32 len,
3127 GVfsJob *job,
3128 gpointer user_data)
3129 {
3130 GDataOutputStream *command;
3131 GError *error;
3132 gboolean res;
3133 char *backup_name;
3134 SftpHandle *handle;
3135
3136 handle = user_data;
3137
3138 error = NULL;
3139 res = FALSE;
3140 if (reply_type == SSH_FXP_STATUS)
3141 res = error_from_status (job, reply, -1, -1, &error);
3142 else
3143 g_set_error_literal (&error, G_IO_ERROR, G_IO_ERROR_FAILED,
3144 _("Invalid reply received"));
3145
3146 if (res)
3147 {
3148 if (handle->tempname)
3149 {
3150 if (handle->make_backup)
3151 {
3152 command = new_command_stream (backend,
3153 SSH_FXP_REMOVE);
3154 backup_name = g_strconcat (handle->filename, "~", NULL);
3155 put_string (command, backup_name);
3156 g_free (backup_name);
3157 queue_command_stream_and_free (&backend->command_connection,
3158 command,
3159 close_deleted_backup,
3160 G_VFS_JOB (job), handle);
3161 }
3162 else
3163 {
3164 command = new_command_stream (backend,
3165 SSH_FXP_REMOVE);
3166 put_string (command, handle->filename);
3167 queue_command_stream_and_free (&backend->command_connection,
3168 command,
3169 close_deleted_file,
3170 G_VFS_JOB (job), handle);
3171 }
3172 }
3173 else
3174 {
3175 g_vfs_job_succeeded (job);
3176 sftp_handle_free (handle);
3177 }
3178 }
3179 else
3180 {
3181 /* The close failed, remove any temporary files */
3182 delete_temp_file (backend,
3183 handle,
3184 G_VFS_JOB (job));
3185
3186 g_vfs_job_failed_from_error (job, error);
3187 g_error_free (error);
3188
3189 sftp_handle_free (handle);
3190 }
3191 }
3192
3193 static void
close_write_fstat_reply(GVfsBackendSftp * backend,int reply_type,GDataInputStream * reply,guint32 len,GVfsJob * job,gpointer user_data)3194 close_write_fstat_reply (GVfsBackendSftp *backend,
3195 int reply_type,
3196 GDataInputStream *reply,
3197 guint32 len,
3198 GVfsJob *job,
3199 gpointer user_data)
3200 {
3201 SftpHandle *handle = user_data;
3202 GDataOutputStream *command;
3203 GFileInfo *info;
3204 const char *etag;
3205
3206 if (reply_type == SSH_FXP_ATTRS)
3207 {
3208 info = g_file_info_new ();
3209 parse_attributes (backend, info, NULL,
3210 reply, NULL);
3211 etag = g_file_info_get_etag (info);
3212 if (etag)
3213 g_vfs_job_close_write_set_etag (G_VFS_JOB_CLOSE_WRITE (job), etag);
3214 g_object_unref (info);
3215 }
3216
3217 command = new_command_stream (backend, SSH_FXP_CLOSE);
3218 put_data_buffer (command, handle->raw_handle);
3219
3220 queue_command_stream_and_free (&backend->command_connection, command,
3221 close_write_reply,
3222 G_VFS_JOB (job), handle);
3223 }
3224
3225 static gboolean
try_close_write(GVfsBackend * backend,GVfsJobCloseWrite * job,GVfsBackendHandle _handle)3226 try_close_write (GVfsBackend *backend,
3227 GVfsJobCloseWrite *job,
3228 GVfsBackendHandle _handle)
3229 {
3230 SftpHandle *handle = _handle;
3231 GVfsBackendSftp *op_backend = G_VFS_BACKEND_SFTP (backend);
3232 GDataOutputStream *command;
3233
3234 command = new_command_stream (op_backend, SSH_FXP_FSTAT);
3235 put_data_buffer (command, handle->raw_handle);
3236
3237 queue_command_stream_and_free (&op_backend->command_connection, command,
3238 close_write_fstat_reply,
3239 G_VFS_JOB (job), handle);
3240
3241 return TRUE;
3242 }
3243
3244
3245 static void
close_read_reply(GVfsBackendSftp * backend,int reply_type,GDataInputStream * reply,guint32 len,GVfsJob * job,gpointer user_data)3246 close_read_reply (GVfsBackendSftp *backend,
3247 int reply_type,
3248 GDataInputStream *reply,
3249 guint32 len,
3250 GVfsJob *job,
3251 gpointer user_data)
3252 {
3253 SftpHandle *handle;
3254
3255 handle = user_data;
3256
3257 if (reply_type == SSH_FXP_STATUS)
3258 result_from_status (job, reply, -1, -1);
3259 else
3260 g_vfs_job_failed (job, G_IO_ERROR, G_IO_ERROR_FAILED,
3261 _("Invalid reply received"));
3262 sftp_handle_free (handle);
3263 }
3264
3265 static gboolean
try_close_read(GVfsBackend * backend,GVfsJobCloseRead * job,GVfsBackendHandle _handle)3266 try_close_read (GVfsBackend *backend,
3267 GVfsJobCloseRead *job,
3268 GVfsBackendHandle _handle)
3269 {
3270 SftpHandle *handle = _handle;
3271 GVfsBackendSftp *op_backend = G_VFS_BACKEND_SFTP (backend);
3272 GDataOutputStream *command;
3273
3274 command = new_command_stream (op_backend, SSH_FXP_CLOSE);
3275 put_data_buffer (command, handle->raw_handle);
3276
3277 queue_command_stream_and_free (&op_backend->command_connection, command,
3278 close_read_reply,
3279 G_VFS_JOB (job), handle);
3280
3281 return TRUE;
3282 }
3283
3284 static void
put_mode(GDataOutputStream * command,GFileCreateFlags flags)3285 put_mode (GDataOutputStream *command, GFileCreateFlags flags)
3286 {
3287 if (flags & G_FILE_CREATE_PRIVATE)
3288 {
3289 g_data_output_stream_put_uint32 (command, SSH_FILEXFER_ATTR_PERMISSIONS, NULL, NULL);
3290 g_data_output_stream_put_uint32 (command, 0600, NULL, NULL);
3291 }
3292 else
3293 {
3294 g_data_output_stream_put_uint32 (command, 0, NULL, NULL);
3295 }
3296 }
3297
3298 static void not_dir_or_not_exist_error (GVfsBackendSftp *backend,
3299 GVfsJob *job,
3300 char *filename);
3301
3302 static void
not_dir_or_not_exist_error_cb(GVfsBackendSftp * backend,GVfsJob * job,gint original_error,gint stat_error,GFileInfo * info,gpointer user_data)3303 not_dir_or_not_exist_error_cb (GVfsBackendSftp *backend,
3304 GVfsJob *job,
3305 gint original_error,
3306 gint stat_error,
3307 GFileInfo *info,
3308 gpointer user_data)
3309 {
3310 char *path;
3311
3312 path = user_data;
3313 if (info != NULL)
3314 {
3315 if (g_file_info_get_file_type (info) == G_FILE_TYPE_DIRECTORY)
3316 /* Parent is a directory, so must not have found child */
3317 g_vfs_job_failed (job, G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
3318 _("No such file or directory"));
3319 else /* Some path element was not a directory */
3320 g_vfs_job_failed (job, G_IO_ERROR, G_IO_ERROR_NOT_DIRECTORY,
3321 _("Not a directory"));
3322 }
3323 else if (stat_error == SSH_FX_NO_SUCH_FILE)
3324 {
3325 not_dir_or_not_exist_error (backend, job, path);
3326 }
3327 else
3328 {
3329 /* Some other weird error, lets say "not found" */
3330 g_vfs_job_failed (job, G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
3331 _("No such file or directory"));
3332 }
3333
3334 g_free (path);
3335 }
3336
3337 static void
not_dir_or_not_exist_error(GVfsBackendSftp * backend,GVfsJob * job,char * filename)3338 not_dir_or_not_exist_error (GVfsBackendSftp *backend,
3339 GVfsJob *job,
3340 char *filename)
3341 {
3342 char *parent;
3343
3344 parent = g_path_get_dirname (filename);
3345 if (strcmp (parent, ".") == 0)
3346 {
3347 g_free (parent);
3348 /* Root not found? Weird, but at least not
3349 NOT_DIRECTORY, so lets report not found */
3350 g_vfs_job_failed (job, G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
3351 _("No such file or directory"));
3352 return;
3353 }
3354
3355 error_from_lstat (backend,
3356 job,
3357 SSH_FX_NO_SUCH_FILE,
3358 filename,
3359 not_dir_or_not_exist_error_cb,
3360 parent);
3361 }
3362
3363 static void
create_reply(GVfsBackendSftp * backend,int reply_type,GDataInputStream * reply,guint32 len,GVfsJob * job,gpointer user_data)3364 create_reply (GVfsBackendSftp *backend,
3365 int reply_type,
3366 GDataInputStream *reply,
3367 guint32 len,
3368 GVfsJob *job,
3369 gpointer user_data)
3370 {
3371 SftpHandle *handle;
3372 guint32 code;
3373
3374 if (reply_type == SSH_FXP_STATUS)
3375 {
3376 code = read_status_code (reply);
3377
3378 if (code == SSH_FX_NO_SUCH_FILE)
3379 {
3380 /* openssh sftp returns NO_SUCH_FILE for both ENOTDIR and ENOENT,
3381 we need to stat-walk the hierarchy to see what the error was */
3382 not_dir_or_not_exist_error (backend, job,
3383 G_VFS_JOB_OPEN_FOR_WRITE (job)->filename);
3384 return;
3385 }
3386
3387 result_from_status_code (job, code, G_IO_ERROR_EXISTS, -1);
3388 return;
3389 }
3390
3391 if (reply_type != SSH_FXP_HANDLE)
3392 {
3393 g_vfs_job_failed (job, G_IO_ERROR, G_IO_ERROR_FAILED,
3394 _("Invalid reply received"));
3395 return;
3396 }
3397
3398 handle = sftp_handle_new (reply);
3399
3400 g_vfs_job_open_for_write_set_handle (G_VFS_JOB_OPEN_FOR_WRITE (job), handle);
3401 g_vfs_job_open_for_write_set_can_seek (G_VFS_JOB_OPEN_FOR_WRITE (job), TRUE);
3402 g_vfs_job_open_for_write_set_can_truncate (G_VFS_JOB_OPEN_FOR_WRITE (job), TRUE);
3403 g_vfs_job_succeeded (job);
3404 }
3405
3406 static gboolean
try_create(GVfsBackend * backend,GVfsJobOpenForWrite * job,const char * filename,GFileCreateFlags flags)3407 try_create (GVfsBackend *backend,
3408 GVfsJobOpenForWrite *job,
3409 const char *filename,
3410 GFileCreateFlags flags)
3411 {
3412 GVfsBackendSftp *op_backend = G_VFS_BACKEND_SFTP (backend);
3413 GDataOutputStream *command;
3414
3415 command = new_command_stream (op_backend,
3416 SSH_FXP_OPEN);
3417 put_string (command, filename);
3418 g_data_output_stream_put_uint32 (command, SSH_FXF_WRITE|SSH_FXF_CREAT|SSH_FXF_EXCL, NULL, NULL); /* open flags */
3419 put_mode (command, flags);
3420
3421 queue_command_stream_and_free (&op_backend->command_connection, command,
3422 create_reply,
3423 G_VFS_JOB (job), NULL);
3424
3425 return TRUE;
3426 }
3427
3428 static void
append_to_error(GVfsBackendSftp * backend,GVfsJob * job,gint original_error,gint stat_error,GFileInfo * info,gpointer user_data)3429 append_to_error (GVfsBackendSftp *backend,
3430 GVfsJob *job,
3431 gint original_error,
3432 gint stat_error,
3433 GFileInfo *info,
3434 gpointer user_data)
3435 {
3436 if ((original_error == SSH_FX_FAILURE) &&
3437 info != NULL &&
3438 g_file_info_get_file_type (info) == G_FILE_TYPE_DIRECTORY)
3439 {
3440 g_vfs_job_failed (job, G_IO_ERROR, G_IO_ERROR_IS_DIRECTORY,
3441 _("File is directory"));
3442 return;
3443 }
3444
3445 if (original_error == SSH_FX_NO_SUCH_FILE)
3446 {
3447 not_dir_or_not_exist_error (backend, job,
3448 G_VFS_JOB_OPEN_FOR_WRITE (job)->filename);
3449 return;
3450 }
3451
3452
3453 result_from_status_code (job, original_error, -1, -1);
3454 }
3455
3456 static void
append_to_reply(GVfsBackendSftp * backend,int reply_type,GDataInputStream * reply,guint32 len,GVfsJob * job,gpointer user_data)3457 append_to_reply (GVfsBackendSftp *backend,
3458 int reply_type,
3459 GDataInputStream *reply,
3460 guint32 len,
3461 GVfsJob *job,
3462 gpointer user_data)
3463 {
3464 SftpHandle *handle;
3465
3466 if (reply_type == SSH_FXP_STATUS)
3467 {
3468 error_from_lstat (backend, job, read_status_code (reply),
3469 G_VFS_JOB_OPEN_FOR_WRITE (job)->filename,
3470 append_to_error,
3471 NULL);
3472 return;
3473 }
3474
3475 if (reply_type != SSH_FXP_HANDLE)
3476 {
3477 g_vfs_job_failed (job, G_IO_ERROR, G_IO_ERROR_FAILED,
3478 _("Invalid reply received"));
3479 return;
3480 }
3481
3482 handle = sftp_handle_new (reply);
3483
3484 g_vfs_job_open_for_write_set_handle (G_VFS_JOB_OPEN_FOR_WRITE (job), handle);
3485 g_vfs_job_open_for_write_set_can_seek (G_VFS_JOB_OPEN_FOR_WRITE (job), TRUE);
3486 g_vfs_job_open_for_write_set_can_truncate (G_VFS_JOB_OPEN_FOR_WRITE (job), TRUE);
3487 g_vfs_job_succeeded (job);
3488 }
3489
3490 static gboolean
try_append_to(GVfsBackend * backend,GVfsJobOpenForWrite * job,const char * filename,GFileCreateFlags flags)3491 try_append_to (GVfsBackend *backend,
3492 GVfsJobOpenForWrite *job,
3493 const char *filename,
3494 GFileCreateFlags flags)
3495 {
3496 GVfsBackendSftp *op_backend = G_VFS_BACKEND_SFTP (backend);
3497 GDataOutputStream *command;
3498
3499 command = new_command_stream (op_backend,
3500 SSH_FXP_OPEN);
3501 put_string (command, filename);
3502 g_data_output_stream_put_uint32 (command, SSH_FXF_WRITE|SSH_FXF_CREAT|SSH_FXF_APPEND, NULL, NULL); /* open flags */
3503 put_mode (command, flags);
3504
3505 queue_command_stream_and_free (&op_backend->command_connection, command,
3506 append_to_reply,
3507 G_VFS_JOB (job), NULL);
3508
3509 return TRUE;
3510 }
3511
3512 typedef struct {
3513 guint32 permissions;
3514 gboolean set_permissions;
3515 guint32 uid;
3516 guint32 gid;
3517 gboolean set_ownership;
3518 char *tempname;
3519 int temp_count;
3520 } ReplaceData;
3521
3522 static void
replace_data_free(ReplaceData * data)3523 replace_data_free (ReplaceData *data)
3524 {
3525 g_free (data->tempname);
3526 g_slice_free (ReplaceData, data);
3527 }
3528
3529 static void replace_create_temp (GVfsBackendSftp *backend,
3530 GVfsJobOpenForWrite *job);
3531
3532 static void
replace_truncate_original_reply(GVfsBackendSftp * backend,int reply_type,GDataInputStream * reply,guint32 len,GVfsJob * job,gpointer user_data)3533 replace_truncate_original_reply (GVfsBackendSftp *backend,
3534 int reply_type,
3535 GDataInputStream *reply,
3536 guint32 len,
3537 GVfsJob *job,
3538 gpointer user_data)
3539 {
3540 GVfsJobOpenForWrite *op_job;
3541 SftpHandle *handle;
3542 ReplaceData *data;
3543 GError *error = NULL;
3544
3545 op_job = G_VFS_JOB_OPEN_FOR_WRITE (job);
3546 data = G_VFS_JOB (job)->backend_data;
3547
3548 if (reply_type == SSH_FXP_STATUS)
3549 {
3550 error = NULL;
3551 if (error_from_status (job, reply, G_IO_ERROR_EXISTS, -1, &error))
3552 /* Open should not return OK */
3553 g_vfs_job_failed (job, G_IO_ERROR, G_IO_ERROR_FAILED,
3554 _("Invalid reply received"));
3555 else
3556 {
3557 g_vfs_job_failed_from_error (job, error);
3558 g_error_free (error);
3559 }
3560 return;
3561 }
3562
3563 if (reply_type != SSH_FXP_HANDLE)
3564 {
3565 g_vfs_job_failed (job, G_IO_ERROR, G_IO_ERROR_FAILED,
3566 _("Invalid reply received"));
3567 return;
3568 }
3569
3570 handle = sftp_handle_new (reply);
3571 handle->filename = g_strdup (op_job->filename);
3572 handle->tempname = NULL;
3573 handle->permissions = data->permissions;
3574 handle->set_permissions = data->set_permissions;
3575 handle->make_backup = op_job->make_backup;
3576
3577 g_vfs_job_open_for_write_set_handle (op_job, handle);
3578 g_vfs_job_open_for_write_set_can_seek (op_job, TRUE);
3579 g_vfs_job_open_for_write_set_can_truncate (op_job, TRUE);
3580
3581 g_vfs_job_succeeded (job);
3582 }
3583
3584 static void
replace_truncate_original(GVfsBackendSftp * backend,GVfsJob * job)3585 replace_truncate_original (GVfsBackendSftp *backend,
3586 GVfsJob *job)
3587 {
3588 GVfsJobOpenForWrite *op_job;
3589 GDataOutputStream *command;
3590
3591 op_job = G_VFS_JOB_OPEN_FOR_WRITE (job);
3592
3593 command = new_command_stream (backend,
3594 SSH_FXP_OPEN);
3595 put_string (command, op_job->filename);
3596 g_data_output_stream_put_uint32 (command, SSH_FXF_WRITE|SSH_FXF_CREAT|SSH_FXF_TRUNC, NULL, NULL); /* open flags */
3597 put_mode (command, op_job->flags);
3598
3599 queue_command_stream_and_free (&backend->command_connection, command,
3600 replace_truncate_original_reply,
3601 job, NULL);
3602 }
3603
3604 static void
close_temp_file(GVfsBackendSftp * backend,SftpHandle * handle,GVfsJob * job)3605 close_temp_file (GVfsBackendSftp *backend,
3606 SftpHandle *handle,
3607 GVfsJob *job)
3608 {
3609 GDataOutputStream *command;
3610
3611 command = new_command_stream (backend,
3612 SSH_FXP_CLOSE);
3613 put_data_buffer (command, handle->raw_handle);
3614 queue_command_stream_and_free (&backend->command_connection, command,
3615 NULL,
3616 G_VFS_JOB (job), NULL);
3617 }
3618
3619 static void
replace_create_temp_fsetstat_reply(GVfsBackendSftp * backend,int reply_type,GDataInputStream * reply,guint32 len,GVfsJob * job,gpointer user_data)3620 replace_create_temp_fsetstat_reply (GVfsBackendSftp *backend,
3621 int reply_type,
3622 GDataInputStream *reply,
3623 guint32 len,
3624 GVfsJob *job,
3625 gpointer user_data)
3626 {
3627 GVfsJobOpenForWrite *op_job = G_VFS_JOB_OPEN_FOR_WRITE (job);
3628 ReplaceData *data = job->backend_data;
3629 SftpHandle *handle = user_data;
3630 GError *error = NULL;
3631
3632 if (reply_type == SSH_FXP_STATUS)
3633 error_from_status (job, reply, -1, -1, &error);
3634 else
3635 g_vfs_job_failed (job, G_IO_ERROR, G_IO_ERROR_FAILED,
3636 _("Invalid reply received"));
3637
3638 if (error != NULL)
3639 {
3640 if (data->set_ownership &&
3641 error->code == G_IO_ERROR_PERMISSION_DENIED)
3642 {
3643 g_error_free (error);
3644
3645 close_temp_file (backend, handle, job);
3646 delete_temp_file (backend, handle, job);
3647 sftp_handle_free (handle);
3648
3649 /* This was probably due to the fact that the ownership could not be
3650 set properly. In this case we change our strategy altogether and
3651 simply open/truncate the original file. This is not as secure
3652 as the atomit tempfile/move approach, but at least ownership
3653 doesn't change */
3654 if (!op_job->make_backup)
3655 replace_truncate_original (backend, job);
3656 else
3657 {
3658 /* We only do this if make_backup is FALSE, as this version breaks
3659 the backup code. Would like to handle the backup case too by
3660 backing up before truncating, but for now error out... */
3661 g_vfs_job_failed (job, G_IO_ERROR,
3662 G_IO_ERROR_CANT_CREATE_BACKUP,
3663 _("Backups not supported"));
3664 }
3665 }
3666 else
3667 {
3668 g_vfs_job_failed_from_error (job, error);
3669 g_error_free (error);
3670 }
3671 return;
3672 }
3673
3674 g_vfs_job_open_for_write_set_handle (op_job, handle);
3675 g_vfs_job_open_for_write_set_can_seek (op_job, TRUE);
3676 g_vfs_job_open_for_write_set_can_truncate (op_job, TRUE);
3677 g_vfs_job_succeeded (job);
3678 }
3679
3680 static void
replace_create_temp_fstat_reply(GVfsBackendSftp * backend,int reply_type,GDataInputStream * reply,guint32 len,GVfsJob * job,gpointer user_data)3681 replace_create_temp_fstat_reply (GVfsBackendSftp *backend,
3682 int reply_type,
3683 GDataInputStream *reply,
3684 guint32 len,
3685 GVfsJob *job,
3686 gpointer user_data)
3687 {
3688 GVfsJobOpenForWrite *op_job = G_VFS_JOB_OPEN_FOR_WRITE (job);
3689 ReplaceData *data = job->backend_data;
3690 SftpHandle *handle = user_data;
3691 GFileInfo *info;
3692 guint32 uid = -1;
3693 guint32 gid = -1;
3694
3695 if (reply_type == SSH_FXP_STATUS ||
3696 reply_type != SSH_FXP_ATTRS)
3697 {
3698 close_temp_file (backend, handle, job);
3699 delete_temp_file (backend, handle, job);
3700 sftp_handle_free (handle);
3701
3702 if (reply_type == SSH_FXP_STATUS)
3703 result_from_status (job, reply, -1, -1);
3704 else
3705 g_vfs_job_failed (job, G_IO_ERROR, G_IO_ERROR_FAILED,
3706 _("Invalid reply received"));
3707 return;
3708 }
3709
3710 info = g_file_info_new ();
3711 parse_attributes (backend, info, NULL, reply, NULL);
3712
3713 if (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_UNIX_UID) &&
3714 g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_UNIX_GID))
3715 {
3716 uid = g_file_info_get_attribute_uint32 (info, G_FILE_ATTRIBUTE_UNIX_UID);
3717 gid = g_file_info_get_attribute_uint32 (info, G_FILE_ATTRIBUTE_UNIX_GID);
3718 }
3719
3720 g_object_unref (info);
3721
3722 if (uid != data->uid || gid != data->gid)
3723 {
3724 GDataOutputStream *command;
3725
3726 command = new_command_stream (backend, SSH_FXP_FSETSTAT);
3727 put_data_buffer (command, handle->raw_handle);
3728 g_data_output_stream_put_uint32 (command, SSH_FILEXFER_ATTR_UIDGID, NULL, NULL);
3729 g_data_output_stream_put_uint32 (command, data->uid, NULL, NULL);
3730 g_data_output_stream_put_uint32 (command, data->gid, NULL, NULL);
3731 queue_command_stream_and_free (&backend->command_connection, command,
3732 replace_create_temp_fsetstat_reply, job, handle);
3733 return;
3734 }
3735
3736 g_vfs_job_open_for_write_set_handle (op_job, handle);
3737 g_vfs_job_open_for_write_set_can_seek (op_job, TRUE);
3738 g_vfs_job_open_for_write_set_can_truncate (op_job, TRUE);
3739 g_vfs_job_succeeded (job);
3740 }
3741
3742 static void
replace_create_temp_reply(GVfsBackendSftp * backend,int reply_type,GDataInputStream * reply,guint32 len,GVfsJob * job,gpointer user_data)3743 replace_create_temp_reply (GVfsBackendSftp *backend,
3744 int reply_type,
3745 GDataInputStream *reply,
3746 guint32 len,
3747 GVfsJob *job,
3748 gpointer user_data)
3749 {
3750 GVfsJobOpenForWrite *op_job;
3751 SftpHandle *handle;
3752 ReplaceData *data;
3753 GError *error;
3754
3755 op_job = G_VFS_JOB_OPEN_FOR_WRITE (job);
3756 data = G_VFS_JOB (job)->backend_data;
3757
3758 if (reply_type == SSH_FXP_STATUS)
3759 {
3760 error = NULL;
3761 if (error_from_status (job, reply, G_IO_ERROR_EXISTS, -1, &error))
3762 /* Open should not return OK */
3763 g_vfs_job_failed (job, G_IO_ERROR, G_IO_ERROR_FAILED,
3764 _("Invalid reply received"));
3765 else if (error->code == G_IO_ERROR_EXISTS)
3766 {
3767 /* It was *probably* the EXCL flag failing. I wish we had
3768 an actual real error code for that, grumble */
3769 g_error_free (error);
3770
3771 replace_create_temp (backend, op_job);
3772 }
3773 else
3774 {
3775 g_vfs_job_failed_from_error (job, error);
3776 g_error_free (error);
3777 }
3778 return;
3779 }
3780
3781 if (reply_type != SSH_FXP_HANDLE)
3782 {
3783 g_vfs_job_failed (job, G_IO_ERROR, G_IO_ERROR_FAILED,
3784 _("Invalid reply received"));
3785 return;
3786 }
3787
3788 handle = sftp_handle_new (reply);
3789 handle->filename = g_strdup (op_job->filename);
3790 handle->tempname = g_strdup (data->tempname);
3791 handle->permissions = data->permissions;
3792 handle->set_permissions = data->set_permissions;
3793 handle->make_backup = op_job->make_backup;
3794
3795 if (data->set_ownership)
3796 {
3797 GDataOutputStream *command;
3798
3799 command = new_command_stream (backend, SSH_FXP_FSTAT);
3800 put_data_buffer (command, handle->raw_handle);
3801 queue_command_stream_and_free (&backend->command_connection, command,
3802 replace_create_temp_fstat_reply, job, handle);
3803 return;
3804 }
3805
3806 g_vfs_job_open_for_write_set_handle (op_job, handle);
3807 g_vfs_job_open_for_write_set_can_seek (op_job, TRUE);
3808 g_vfs_job_open_for_write_set_can_truncate (op_job, TRUE);
3809
3810 g_vfs_job_succeeded (job);
3811 }
3812
3813 static void
replace_create_temp(GVfsBackendSftp * backend,GVfsJobOpenForWrite * job)3814 replace_create_temp (GVfsBackendSftp *backend,
3815 GVfsJobOpenForWrite *job)
3816 {
3817 GVfsBackendSftp *op_backend = G_VFS_BACKEND_SFTP (backend);
3818 GDataOutputStream *command;
3819 char *dirname;
3820 ReplaceData *data;
3821 char basename[] = ".giosaveXXXXXX";
3822
3823 data = G_VFS_JOB (job)->backend_data;
3824
3825 data->temp_count++;
3826
3827 if (data->temp_count == 100)
3828 {
3829 g_vfs_job_failed (G_VFS_JOB (job),
3830 G_IO_ERROR, G_IO_ERROR_FAILED,
3831 _("Unable to create temporary file"));
3832 return;
3833 }
3834
3835 g_free (data->tempname);
3836
3837 dirname = g_path_get_dirname (job->filename);
3838 gvfs_randomize_string (basename + 8, 6);
3839 data->tempname = g_build_filename (dirname, basename, NULL);
3840 g_free (dirname);
3841
3842 command = new_command_stream (op_backend,
3843 SSH_FXP_OPEN);
3844 put_string (command, data->tempname);
3845 g_data_output_stream_put_uint32 (command, SSH_FXF_WRITE|SSH_FXF_CREAT|SSH_FXF_EXCL, NULL, NULL); /* open flags */
3846 g_data_output_stream_put_uint32 (command, (data->set_permissions ? SSH_FILEXFER_ATTR_PERMISSIONS : 0), NULL, NULL);
3847
3848 if (data->set_permissions)
3849 g_data_output_stream_put_uint32 (command, data->permissions, NULL, NULL);
3850 queue_command_stream_and_free (&op_backend->command_connection, command,
3851 replace_create_temp_reply,
3852 G_VFS_JOB (job), NULL);
3853 }
3854
3855 static void
replace_stat_reply(GVfsBackendSftp * backend,int reply_type,GDataInputStream * reply,guint32 len,GVfsJob * job,gpointer user_data)3856 replace_stat_reply (GVfsBackendSftp *backend,
3857 int reply_type,
3858 GDataInputStream *reply,
3859 guint32 len,
3860 GVfsJob *job,
3861 gpointer user_data)
3862 {
3863 GFileInfo *info;
3864 GVfsJobOpenForWrite *op_job;
3865 const char *current_etag;
3866 guint32 permissions;
3867 guint32 uid;
3868 guint32 gid;
3869 gboolean set_permissions;
3870 gboolean set_ownership = FALSE;
3871 gboolean is_regular = FALSE;
3872 ReplaceData *data;
3873
3874 op_job = G_VFS_JOB_OPEN_FOR_WRITE (job);
3875
3876 set_permissions = op_job->flags & G_FILE_CREATE_PRIVATE ? TRUE : FALSE;
3877 permissions = 0600;
3878
3879 if (reply_type == SSH_FXP_ATTRS)
3880 {
3881 info = g_file_info_new ();
3882 parse_attributes (backend, info, NULL,
3883 reply, NULL);
3884
3885 if (g_file_info_get_file_type (info) == G_FILE_TYPE_DIRECTORY)
3886 {
3887 g_object_unref (info);
3888 g_vfs_job_failed (job, G_IO_ERROR, G_IO_ERROR_IS_DIRECTORY,
3889 _("File is directory"));
3890 return;
3891 }
3892 else if (g_file_info_get_file_type (info) == G_FILE_TYPE_REGULAR)
3893 is_regular = TRUE;
3894
3895 if (op_job->etag != NULL)
3896 {
3897 current_etag = g_file_info_get_attribute_string (info, G_FILE_ATTRIBUTE_ETAG_VALUE);
3898
3899 if (current_etag == NULL ||
3900 strcmp (op_job->etag, current_etag) != 0)
3901 {
3902 g_vfs_job_failed (job,
3903 G_IO_ERROR, G_IO_ERROR_WRONG_ETAG,
3904 _("The file was externally modified"));
3905 g_object_unref (info);
3906 return;
3907 }
3908 }
3909
3910 if (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_UNIX_MODE) &&
3911 !(op_job->flags & G_FILE_CREATE_REPLACE_DESTINATION))
3912 {
3913 set_permissions = TRUE;
3914 permissions = g_file_info_get_attribute_uint32 (info, G_FILE_ATTRIBUTE_UNIX_MODE) & 0777;
3915 }
3916
3917 if (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_UNIX_UID) &&
3918 g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_UNIX_GID) &&
3919 !(op_job->flags & G_FILE_CREATE_REPLACE_DESTINATION))
3920 {
3921 set_ownership = TRUE;
3922 uid = g_file_info_get_attribute_uint32 (info, G_FILE_ATTRIBUTE_UNIX_UID);
3923 gid = g_file_info_get_attribute_uint32 (info, G_FILE_ATTRIBUTE_UNIX_GID);
3924 }
3925 }
3926
3927 data = g_slice_new0 (ReplaceData);
3928 data->set_permissions = set_permissions;
3929 if (set_permissions)
3930 data->permissions = permissions;
3931
3932 data->set_ownership = set_ownership;
3933 if (set_ownership)
3934 {
3935 data->uid = uid;
3936 data->gid = gid;
3937 }
3938
3939 g_vfs_job_set_backend_data (job, data, (GDestroyNotify)replace_data_free);
3940
3941 /* If G_FILE_CREATE_REPLACE_DESTINATION is specified or the destination
3942 * is a regular file, replace by writing to a temp file and renaming rather
3943 * than truncating. */
3944 if (op_job->flags & G_FILE_CREATE_REPLACE_DESTINATION || is_regular)
3945 replace_create_temp (backend, op_job);
3946 else
3947 {
3948 if (op_job->make_backup)
3949 g_vfs_job_failed (job, G_IO_ERROR, G_IO_ERROR_CANT_CREATE_BACKUP,
3950 _("Backups not supported"));
3951 else
3952 replace_truncate_original (backend, job);
3953 }
3954 }
3955
3956 static void
replace_exclusive_reply(GVfsBackendSftp * backend,int reply_type,GDataInputStream * reply,guint32 len,GVfsJob * job,gpointer user_data)3957 replace_exclusive_reply (GVfsBackendSftp *backend,
3958 int reply_type,
3959 GDataInputStream *reply,
3960 guint32 len,
3961 GVfsJob *job,
3962 gpointer user_data)
3963 {
3964 GVfsJobOpenForWrite *op_job;
3965 GDataOutputStream *command;
3966 SftpHandle *handle;
3967 GError *error;
3968
3969 op_job = G_VFS_JOB_OPEN_FOR_WRITE (job);
3970 if (reply_type == SSH_FXP_STATUS)
3971 {
3972 error = NULL;
3973 if (error_from_status (job, reply, G_IO_ERROR_EXISTS, -1, &error))
3974 /* Open should not return OK */
3975 g_vfs_job_failed (job, G_IO_ERROR, G_IO_ERROR_FAILED,
3976 _("Invalid reply received"));
3977 else if (error->code == G_IO_ERROR_NOT_FOUND)
3978 {
3979 g_error_free (error);
3980 /* openssh sftp returns NO_SUCH_FILE for ENOTDIR */
3981 not_dir_or_not_exist_error (backend, job,
3982 G_VFS_JOB_OPEN_FOR_WRITE (job)->filename);
3983 }
3984 else if (error->code == G_IO_ERROR_EXISTS)
3985 {
3986 /* It was *probably* the EXCL flag failing. I wish we had
3987 an actual real error code for that, grumble */
3988 g_error_free (error);
3989
3990 /* Replace existing file code: */
3991
3992 command = new_command_stream (backend,
3993 SSH_FXP_LSTAT);
3994 put_string (command, op_job->filename);
3995 queue_command_stream_and_free (&backend->command_connection, command,
3996 replace_stat_reply,
3997 G_VFS_JOB (job), NULL);
3998 }
3999 else
4000 {
4001 g_vfs_job_failed_from_error (job, error);
4002 g_error_free (error);
4003 }
4004 return;
4005 }
4006
4007 if (reply_type != SSH_FXP_HANDLE)
4008 {
4009 g_vfs_job_failed (job, G_IO_ERROR, G_IO_ERROR_FAILED,
4010 _("Invalid reply received"));
4011 return;
4012 }
4013
4014 handle = sftp_handle_new (reply);
4015
4016 g_vfs_job_open_for_write_set_handle (op_job, handle);
4017 g_vfs_job_open_for_write_set_can_seek (op_job, TRUE);
4018 g_vfs_job_open_for_write_set_can_truncate (op_job, TRUE);
4019
4020 g_vfs_job_succeeded (job);
4021 }
4022
4023 static gboolean
try_replace(GVfsBackend * backend,GVfsJobOpenForWrite * job,const char * filename,const char * etag,gboolean make_backup,GFileCreateFlags flags)4024 try_replace (GVfsBackend *backend,
4025 GVfsJobOpenForWrite *job,
4026 const char *filename,
4027 const char *etag,
4028 gboolean make_backup,
4029 GFileCreateFlags flags)
4030 {
4031 GVfsBackendSftp *op_backend = G_VFS_BACKEND_SFTP (backend);
4032 GDataOutputStream *command;
4033
4034 command = new_command_stream (op_backend,
4035 SSH_FXP_OPEN);
4036 put_string (command, filename);
4037 g_data_output_stream_put_uint32 (command, SSH_FXF_WRITE|SSH_FXF_CREAT|SSH_FXF_EXCL, NULL, NULL); /* open flags */
4038 put_mode (command, flags);
4039
4040 queue_command_stream_and_free (&op_backend->command_connection, command,
4041 replace_exclusive_reply,
4042 G_VFS_JOB (job), NULL);
4043
4044 return TRUE;
4045 }
4046
4047 static void
write_reply(GVfsBackendSftp * backend,int reply_type,GDataInputStream * reply,guint32 len,GVfsJob * job,gpointer user_data)4048 write_reply (GVfsBackendSftp *backend,
4049 int reply_type,
4050 GDataInputStream *reply,
4051 guint32 len,
4052 GVfsJob *job,
4053 gpointer user_data)
4054 {
4055 SftpHandle *handle;
4056
4057 handle = user_data;
4058
4059 if (reply_type == SSH_FXP_STATUS)
4060 {
4061 if (result_from_status (job, reply, -1, -1))
4062 {
4063 handle->offset += G_VFS_JOB_WRITE (job)->written_size;
4064 }
4065 }
4066 else
4067 g_vfs_job_failed (job, G_IO_ERROR, G_IO_ERROR_FAILED,
4068 _("Invalid reply received"));
4069 }
4070
4071 static gboolean
try_write(GVfsBackend * backend,GVfsJobWrite * job,GVfsBackendHandle _handle,char * buffer,gsize buffer_size)4072 try_write (GVfsBackend *backend,
4073 GVfsJobWrite *job,
4074 GVfsBackendHandle _handle,
4075 char *buffer,
4076 gsize buffer_size)
4077 {
4078 SftpHandle *handle = _handle;
4079 GVfsBackendSftp *op_backend = G_VFS_BACKEND_SFTP (backend);
4080 GDataOutputStream *command;
4081 gsize size;
4082
4083 size = MIN (buffer_size, MAX_BUFFER_SIZE);
4084
4085 command = new_command_stream (op_backend,
4086 SSH_FXP_WRITE);
4087 put_data_buffer (command, handle->raw_handle);
4088 g_data_output_stream_put_uint64 (command, handle->offset, NULL, NULL);
4089 g_data_output_stream_put_uint32 (command, size, NULL, NULL);
4090 /* Ideally we shouldn't do this copy, but doing the writes as multiple writes
4091 caused problems on the read side in openssh */
4092 g_output_stream_write_all (G_OUTPUT_STREAM (command),
4093 buffer, size,
4094 NULL, NULL, NULL);
4095
4096 queue_command_stream_and_free (&op_backend->command_connection, command,
4097 write_reply,
4098 G_VFS_JOB (job), handle);
4099
4100 /* We always write the full size (on success) */
4101 g_vfs_job_write_set_written_size (job, size);
4102
4103 return TRUE;
4104 }
4105
4106 static void
seek_write_fstat_reply(GVfsBackendSftp * backend,int reply_type,GDataInputStream * reply,guint32 len,GVfsJob * job,gpointer user_data)4107 seek_write_fstat_reply (GVfsBackendSftp *backend,
4108 int reply_type,
4109 GDataInputStream *reply,
4110 guint32 len,
4111 GVfsJob *job,
4112 gpointer user_data)
4113 {
4114 SftpHandle *handle;
4115 GFileInfo *info;
4116 goffset file_size;
4117 GVfsJobSeekWrite *op_job;
4118
4119 handle = user_data;
4120
4121 if (reply_type == SSH_FXP_STATUS)
4122 {
4123 result_from_status (job, reply, -1, -1);
4124 return;
4125 }
4126
4127 if (reply_type != SSH_FXP_ATTRS)
4128 {
4129 g_vfs_job_failed (job, G_IO_ERROR, G_IO_ERROR_FAILED,
4130 _("Invalid reply received"));
4131 return;
4132 }
4133
4134 info = g_file_info_new ();
4135 parse_attributes (backend, info, NULL,
4136 reply, NULL);
4137 file_size = g_file_info_get_size (info);
4138 g_object_unref (info);
4139
4140 op_job = G_VFS_JOB_SEEK_WRITE (job);
4141
4142 handle->offset = file_size + op_job->requested_offset;
4143
4144 if (handle->offset < 0)
4145 handle->offset = 0;
4146
4147 g_vfs_job_seek_write_set_offset (op_job, handle->offset);
4148 g_vfs_job_succeeded (job);
4149 }
4150
4151 static gboolean
try_seek_on_write(GVfsBackend * backend,GVfsJobSeekWrite * job,GVfsBackendHandle _handle,goffset offset,GSeekType type)4152 try_seek_on_write (GVfsBackend *backend,
4153 GVfsJobSeekWrite *job,
4154 GVfsBackendHandle _handle,
4155 goffset offset,
4156 GSeekType type)
4157 {
4158 SftpHandle *handle = _handle;
4159 GVfsBackendSftp *op_backend = G_VFS_BACKEND_SFTP (backend);
4160 GDataOutputStream *command;
4161
4162 switch (job->seek_type)
4163 {
4164 case G_SEEK_CUR:
4165 handle->offset += job->requested_offset;
4166 break;
4167 case G_SEEK_SET:
4168 handle->offset = job->requested_offset;
4169 break;
4170 case G_SEEK_END:
4171 command = new_command_stream (op_backend,
4172 SSH_FXP_FSTAT);
4173 put_data_buffer (command, handle->raw_handle);
4174 queue_command_stream_and_free (&op_backend->command_connection, command,
4175 seek_write_fstat_reply,
4176 G_VFS_JOB (job), handle);
4177 return TRUE;
4178 }
4179
4180 if (handle->offset < 0)
4181 handle->offset = 0;
4182
4183 g_vfs_job_seek_write_set_offset (job, handle->offset);
4184 g_vfs_job_succeeded (G_VFS_JOB (job));
4185
4186 return TRUE;
4187 }
4188
4189 static void
truncate_reply(GVfsBackendSftp * backend,int reply_type,GDataInputStream * reply,guint32 len,GVfsJob * job,gpointer user_data)4190 truncate_reply (GVfsBackendSftp *backend,
4191 int reply_type,
4192 GDataInputStream *reply,
4193 guint32 len,
4194 GVfsJob *job,
4195 gpointer user_data)
4196 {
4197 if (reply_type == SSH_FXP_STATUS)
4198 result_from_status (job, reply, -1, -1);
4199 else
4200 g_vfs_job_failed (job, G_IO_ERROR, G_IO_ERROR_FAILED,
4201 _("Invalid reply received"));
4202 }
4203
4204 static gboolean
try_truncate(GVfsBackend * backend,GVfsJobTruncate * job,GVfsBackendHandle _handle,goffset size)4205 try_truncate (GVfsBackend *backend,
4206 GVfsJobTruncate *job,
4207 GVfsBackendHandle _handle,
4208 goffset size)
4209 {
4210 SftpHandle *handle = _handle;
4211 GVfsBackendSftp *op_backend = G_VFS_BACKEND_SFTP (backend);
4212 GDataOutputStream *command;
4213
4214 command = new_command_stream (op_backend, SSH_FXP_FSETSTAT);
4215 put_data_buffer (command, handle->raw_handle);
4216 g_data_output_stream_put_uint32 (command, SSH_FILEXFER_ATTR_SIZE, NULL, NULL);
4217 g_data_output_stream_put_uint64 (command, size, NULL, NULL);
4218 queue_command_stream_and_free (&op_backend->command_connection, command,
4219 truncate_reply,
4220 G_VFS_JOB (job), NULL);
4221
4222 return TRUE;
4223 }
4224
4225 typedef struct {
4226 DataBuffer *handle;
4227 int outstanding_requests;
4228 } ReadDirData;
4229
4230 static
4231 void
read_dir_data_free(ReadDirData * data)4232 read_dir_data_free (ReadDirData *data)
4233 {
4234 data_buffer_free (data->handle);
4235 g_slice_free (ReadDirData, data);
4236 }
4237
4238 static void
read_dir_readlink_reply(GVfsBackendSftp * backend,int reply_type,GDataInputStream * reply,guint32 len,GVfsJob * job,gpointer user_data)4239 read_dir_readlink_reply (GVfsBackendSftp *backend,
4240 int reply_type,
4241 GDataInputStream *reply,
4242 guint32 len,
4243 GVfsJob *job,
4244 gpointer user_data)
4245 {
4246 ReadDirData *data;
4247 GFileInfo *info = user_data;
4248 char *target;
4249
4250 data = job->backend_data;
4251
4252 if (reply_type == SSH_FXP_NAME)
4253 {
4254 /* count = */ (void) g_data_input_stream_read_uint32 (reply, NULL, NULL);
4255
4256 target = read_string (reply, NULL);
4257 if (target)
4258 {
4259 g_file_info_set_symlink_target (info, target);
4260 g_free (target);
4261 }
4262 }
4263
4264 g_vfs_job_enumerate_add_info (G_VFS_JOB_ENUMERATE (job), info);
4265 g_object_unref (info);
4266
4267 if (--data->outstanding_requests == 0)
4268 g_vfs_job_enumerate_done (G_VFS_JOB_ENUMERATE (job));
4269 }
4270
4271 static void
read_dir_got_stat_info(GVfsBackendSftp * backend,GVfsJob * job,GFileInfo * info)4272 read_dir_got_stat_info (GVfsBackendSftp *backend,
4273 GVfsJob *job,
4274 GFileInfo *info)
4275 {
4276 GVfsJobEnumerate *enum_job;
4277 GDataOutputStream *command;
4278 ReadDirData *data;
4279 char *abs_name;
4280
4281 data = job->backend_data;
4282
4283 enum_job = G_VFS_JOB_ENUMERATE (job);
4284
4285 if (g_file_attribute_matcher_matches (enum_job->attribute_matcher,
4286 G_FILE_ATTRIBUTE_STANDARD_SYMLINK_TARGET))
4287 {
4288 data->outstanding_requests++;
4289 command = new_command_stream (backend,
4290 SSH_FXP_READLINK);
4291 abs_name = g_build_filename (enum_job->filename, g_file_info_get_name (info), NULL);
4292 put_string (command, abs_name);
4293 g_free (abs_name);
4294 queue_command_stream_and_free (&backend->command_connection, command,
4295 read_dir_readlink_reply,
4296 G_VFS_JOB (job), g_object_ref (info));
4297 }
4298 else
4299 g_vfs_job_enumerate_add_info (enum_job, info);
4300 }
4301
4302
4303 static void
read_dir_symlink_reply(GVfsBackendSftp * backend,int reply_type,GDataInputStream * reply,guint32 len,GVfsJob * job,gpointer user_data)4304 read_dir_symlink_reply (GVfsBackendSftp *backend,
4305 int reply_type,
4306 GDataInputStream *reply,
4307 guint32 len,
4308 GVfsJob *job,
4309 gpointer user_data)
4310 {
4311 const char *name;
4312 GFileInfo *info;
4313 GFileInfo *lstat_info;
4314 ReadDirData *data;
4315
4316 lstat_info = user_data;
4317 name = g_file_info_get_name (lstat_info);
4318 data = job->backend_data;
4319
4320 if (reply_type == SSH_FXP_ATTRS)
4321 {
4322 info = g_file_info_new ();
4323 g_file_info_set_name (info, name);
4324 g_file_info_set_is_symlink (info, TRUE);
4325
4326 parse_attributes (backend, info, name, reply, G_VFS_JOB_ENUMERATE (job)->attribute_matcher);
4327
4328 read_dir_got_stat_info (backend, job, info);
4329
4330 g_object_unref (info);
4331 }
4332 else
4333 read_dir_got_stat_info (backend, job, lstat_info);
4334
4335 g_object_unref (lstat_info);
4336
4337 if (--data->outstanding_requests == 0)
4338 g_vfs_job_enumerate_done (G_VFS_JOB_ENUMERATE (job));
4339 }
4340
4341 static void
read_dir_reply(GVfsBackendSftp * backend,int reply_type,GDataInputStream * reply,guint32 len,GVfsJob * job,gpointer user_data)4342 read_dir_reply (GVfsBackendSftp *backend,
4343 int reply_type,
4344 GDataInputStream *reply,
4345 guint32 len,
4346 GVfsJob *job,
4347 gpointer user_data)
4348 {
4349 GVfsJobEnumerate *enum_job;
4350 guint32 count;
4351 int i;
4352 GDataOutputStream *command;
4353 ReadDirData *data;
4354
4355 data = job->backend_data;
4356 enum_job = G_VFS_JOB_ENUMERATE (job);
4357
4358 if (reply_type != SSH_FXP_NAME)
4359 {
4360 /* Ignore all error, including the expected END OF FILE.
4361 * Real errors are expected in open_dir anyway */
4362
4363 /* Close handle */
4364
4365 command = new_command_stream (backend,
4366 SSH_FXP_CLOSE);
4367 put_data_buffer (command, data->handle);
4368 queue_command_stream_and_free (&backend->command_connection, command,
4369 NULL,
4370 G_VFS_JOB (job), NULL);
4371
4372 if (--data->outstanding_requests == 0)
4373 g_vfs_job_enumerate_done (enum_job);
4374
4375 return;
4376 }
4377
4378 count = g_data_input_stream_read_uint32 (reply, NULL, NULL);
4379 for (i = 0; i < count; i++)
4380 {
4381 GFileInfo *info;
4382 char *name;
4383 char *longname;
4384 char *abs_name;
4385
4386 info = g_file_info_new ();
4387 name = read_string (reply, NULL);
4388 g_file_info_set_name (info, name);
4389
4390 longname = read_string (reply, NULL);
4391 g_free (longname);
4392
4393 parse_attributes (backend, info, name, reply, enum_job->attribute_matcher);
4394
4395 if (g_file_info_get_file_type (info) == G_FILE_TYPE_SYMBOLIC_LINK &&
4396 ! (enum_job->flags & G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS))
4397 {
4398 /* Default (at least for openssh) is for readdir to not follow symlinks.
4399 This was a symlink, and follow links was requested, so we need to manually follow it */
4400 command = new_command_stream (backend,
4401 SSH_FXP_STAT);
4402 abs_name = g_build_filename (enum_job->filename, name, NULL);
4403 put_string (command, abs_name);
4404 g_free (abs_name);
4405
4406 queue_command_stream_and_free (&backend->command_connection, command,
4407 read_dir_symlink_reply,
4408 G_VFS_JOB (job), g_object_ref (info));
4409 data->outstanding_requests ++;
4410 }
4411 else if (strcmp (".", name) != 0 &&
4412 strcmp ("..", name) != 0)
4413 read_dir_got_stat_info (backend, job, info);
4414
4415 g_object_unref (info);
4416 g_free (name);
4417 }
4418
4419 command = new_command_stream (backend,
4420 SSH_FXP_READDIR);
4421 put_data_buffer (command, data->handle);
4422 queue_command_stream_and_free (&backend->command_connection, command,
4423 read_dir_reply,
4424 G_VFS_JOB (job), NULL);
4425 }
4426
4427 static void
open_dir_error(GVfsBackendSftp * backend,GVfsJob * job,gint original_error,gint stat_error,GFileInfo * info,gpointer user_data)4428 open_dir_error (GVfsBackendSftp *backend,
4429 GVfsJob *job,
4430 gint original_error,
4431 gint stat_error,
4432 GFileInfo *info,
4433 gpointer user_data)
4434 {
4435 if ((original_error == SSH_FX_FAILURE ||
4436 original_error == SSH_FX_NO_SUCH_FILE) &&
4437 info != NULL &&
4438 g_file_info_get_file_type (info) != G_FILE_TYPE_DIRECTORY)
4439 {
4440 g_vfs_job_failed (G_VFS_JOB (job),
4441 G_IO_ERROR,
4442 G_IO_ERROR_NOT_DIRECTORY,
4443 _("The file is not a directory"));
4444 return;
4445 }
4446
4447 result_from_status_code (job, original_error, -1, -1);
4448 }
4449
4450 static void
open_dir_reply(GVfsBackendSftp * backend,int reply_type,GDataInputStream * reply,guint32 len,GVfsJob * job,gpointer user_data)4451 open_dir_reply (GVfsBackendSftp *backend,
4452 int reply_type,
4453 GDataInputStream *reply,
4454 guint32 len,
4455 GVfsJob *job,
4456 gpointer user_data)
4457 {
4458 GVfsBackendSftp *op_backend = G_VFS_BACKEND_SFTP (backend);
4459 GDataOutputStream *command;
4460 ReadDirData *data;
4461
4462 data = job->backend_data;
4463
4464 if (reply_type == SSH_FXP_STATUS)
4465 {
4466 error_from_lstat (backend, job, read_status_code (reply),
4467 G_VFS_JOB_ENUMERATE (job)->filename,
4468 open_dir_error,
4469 NULL);
4470 return;
4471 }
4472
4473 if (reply_type != SSH_FXP_HANDLE)
4474 {
4475 g_vfs_job_failed (job, G_IO_ERROR, G_IO_ERROR_FAILED,
4476 _("Invalid reply received"));
4477 return;
4478 }
4479
4480 g_vfs_job_succeeded (G_VFS_JOB (job));
4481
4482 data->handle = read_data_buffer (reply);
4483
4484 command = new_command_stream (op_backend,
4485 SSH_FXP_READDIR);
4486 put_data_buffer (command, data->handle);
4487
4488 data->outstanding_requests = 1;
4489
4490 queue_command_stream_and_free (&op_backend->command_connection, command,
4491 read_dir_reply,
4492 G_VFS_JOB (job), NULL);
4493 }
4494
4495 static gboolean
try_enumerate(GVfsBackend * backend,GVfsJobEnumerate * job,const char * filename,GFileAttributeMatcher * attribute_matcher,GFileQueryInfoFlags flags)4496 try_enumerate (GVfsBackend *backend,
4497 GVfsJobEnumerate *job,
4498 const char *filename,
4499 GFileAttributeMatcher *attribute_matcher,
4500 GFileQueryInfoFlags flags)
4501 {
4502 GVfsBackendSftp *op_backend = G_VFS_BACKEND_SFTP (backend);
4503 GDataOutputStream *command;
4504 ReadDirData *data;
4505
4506 data = g_slice_new0 (ReadDirData);
4507
4508 g_vfs_job_set_backend_data (G_VFS_JOB (job), data, (GDestroyNotify)read_dir_data_free);
4509 command = new_command_stream (op_backend,
4510 SSH_FXP_OPENDIR);
4511 put_string (command, filename);
4512
4513 queue_command_stream_and_free (&op_backend->command_connection, command,
4514 open_dir_reply,
4515 G_VFS_JOB (job), NULL);
4516
4517 return TRUE;
4518 }
4519
4520 static void
query_info_reply(GVfsBackendSftp * backend,MultiReply * replies,int n_replies,GVfsJob * job,gpointer user_data)4521 query_info_reply (GVfsBackendSftp *backend,
4522 MultiReply *replies,
4523 int n_replies,
4524 GVfsJob *job,
4525 gpointer user_data)
4526 {
4527 char *basename;
4528 int i;
4529 MultiReply *lstat_reply, *reply;
4530 GFileInfo *lstat_info;
4531 GVfsJobQueryInfo *op_job;
4532
4533 op_job = G_VFS_JOB_QUERY_INFO (job);
4534
4535 i = 0;
4536 lstat_reply = &replies[i++];
4537
4538 if (lstat_reply->type == SSH_FXP_STATUS)
4539 {
4540 result_from_status (job, lstat_reply->data, -1, -1);
4541 return;
4542 }
4543 else if (lstat_reply->type != SSH_FXP_ATTRS)
4544 {
4545 g_vfs_job_failed (job,
4546 G_IO_ERROR, G_IO_ERROR_FAILED,
4547 "%s", _("Invalid reply received"));
4548 return;
4549 }
4550
4551 basename = NULL;
4552 if (strcmp (op_job->filename, "/") != 0)
4553 basename = g_path_get_basename (op_job->filename);
4554
4555 if (op_job->flags & G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS)
4556 {
4557 parse_attributes (backend, op_job->file_info, basename,
4558 lstat_reply->data, op_job->attribute_matcher);
4559 }
4560 else
4561 {
4562 /* Look at stat results */
4563 reply = &replies[i++];
4564
4565 if (reply->type == SSH_FXP_ATTRS)
4566 {
4567 parse_attributes (backend, op_job->file_info, basename,
4568 reply->data, op_job->attribute_matcher);
4569
4570
4571 lstat_info = g_file_info_new ();
4572 parse_attributes (backend, lstat_info, basename,
4573 lstat_reply->data, op_job->attribute_matcher);
4574 if (g_file_info_get_is_symlink (lstat_info))
4575 g_file_info_set_is_symlink (op_job->file_info, TRUE);
4576 g_object_unref (lstat_info);
4577 }
4578 else
4579 {
4580 /* Broken symlink, use lstat data */
4581 parse_attributes (backend, op_job->file_info, basename,
4582 lstat_reply->data, op_job->attribute_matcher);
4583 }
4584
4585 }
4586
4587 g_free (basename);
4588
4589 if (g_file_attribute_matcher_matches (op_job->attribute_matcher,
4590 G_FILE_ATTRIBUTE_STANDARD_SYMLINK_TARGET))
4591 {
4592 /* Look at readlink results */
4593 reply = &replies[i++];
4594
4595 if (reply->type == SSH_FXP_NAME)
4596 {
4597 char *symlink_target;
4598
4599 /* Skip count (always 1 for replies to SSH_FXP_READLINK) */
4600 g_data_input_stream_read_uint32 (reply->data, NULL, NULL);
4601 symlink_target = read_string (reply->data, NULL);
4602 g_file_info_set_symlink_target (op_job->file_info, symlink_target);
4603 g_free (symlink_target);
4604 }
4605 }
4606
4607 g_vfs_job_succeeded (G_VFS_JOB (job));
4608 }
4609
4610 static gboolean
try_query_info(GVfsBackend * backend,GVfsJobQueryInfo * job,const char * filename,GFileQueryInfoFlags flags,GFileInfo * info,GFileAttributeMatcher * matcher)4611 try_query_info (GVfsBackend *backend,
4612 GVfsJobQueryInfo *job,
4613 const char *filename,
4614 GFileQueryInfoFlags flags,
4615 GFileInfo *info,
4616 GFileAttributeMatcher *matcher)
4617 {
4618 GVfsBackendSftp *op_backend = G_VFS_BACKEND_SFTP (backend);
4619 Command commands[3];
4620 GDataOutputStream *command;
4621 int n_commands;
4622
4623 n_commands = 0;
4624
4625 commands[n_commands].connection = &op_backend->command_connection;
4626 command = commands[n_commands++].cmd =
4627 new_command_stream (op_backend,
4628 SSH_FXP_LSTAT);
4629 put_string (command, filename);
4630
4631 if (! (job->flags & G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS))
4632 {
4633 commands[n_commands].connection = &op_backend->command_connection;
4634 command = commands[n_commands++].cmd =
4635 new_command_stream (op_backend,
4636 SSH_FXP_STAT);
4637 put_string (command, filename);
4638 }
4639
4640 if (g_file_attribute_matcher_matches (job->attribute_matcher,
4641 G_FILE_ATTRIBUTE_STANDARD_SYMLINK_TARGET))
4642 {
4643 commands[n_commands].connection = &op_backend->command_connection;
4644 command = commands[n_commands++].cmd =
4645 new_command_stream (op_backend,
4646 SSH_FXP_READLINK);
4647 put_string (command, filename);
4648 }
4649
4650 queue_command_streams_and_free (commands, n_commands,
4651 query_info_reply,
4652 G_VFS_JOB (job), NULL);
4653
4654 return TRUE;
4655 }
4656
4657 static void
query_fs_info_reply(GVfsBackendSftp * backend,int reply_type,GDataInputStream * reply,guint32 len,GVfsJob * job,gpointer user_data)4658 query_fs_info_reply (GVfsBackendSftp *backend,
4659 int reply_type,
4660 GDataInputStream *reply,
4661 guint32 len,
4662 GVfsJob *job,
4663 gpointer user_data)
4664 {
4665 GFileInfo *info = user_data;
4666 guint64 frsize, blocks, bfree, bavail, flags;
4667
4668 if (reply_type == SSH_FXP_STATUS)
4669 {
4670 guint32 code = read_status_code (reply);
4671
4672 if (code == SSH_FX_NO_SUCH_FILE)
4673 not_dir_or_not_exist_error (backend, job, G_VFS_JOB_QUERY_FS_INFO (job)->filename);
4674 else
4675 result_from_status_code (job, code, -1, -1);
4676 return;
4677 }
4678 else if (reply_type != SSH_FXP_EXTENDED_REPLY)
4679 {
4680 g_vfs_job_failed (job,
4681 G_IO_ERROR, G_IO_ERROR_FAILED,
4682 "%s", _("Invalid reply received"));
4683 return;
4684 }
4685
4686 g_data_input_stream_read_uint64 (reply, NULL, NULL); /* bsize */
4687 frsize = g_data_input_stream_read_uint64 (reply, NULL, NULL);
4688 blocks = g_data_input_stream_read_uint64 (reply, NULL, NULL);
4689 bfree = g_data_input_stream_read_uint64 (reply, NULL, NULL);
4690 bavail = g_data_input_stream_read_uint64 (reply, NULL, NULL);
4691 g_data_input_stream_read_uint64 (reply, NULL, NULL); /* files */
4692 g_data_input_stream_read_uint64 (reply, NULL, NULL); /* ffree */
4693 g_data_input_stream_read_uint64 (reply, NULL, NULL); /* favail */
4694 g_data_input_stream_read_uint64 (reply, NULL, NULL); /* fsid */
4695 flags = g_data_input_stream_read_uint64 (reply, NULL, NULL);
4696 g_data_input_stream_read_uint64 (reply, NULL, NULL); /* namemax */
4697
4698 /* If free and available are both 0, treat it like the size information is
4699 * missing.
4700 * */
4701 if (bfree || bavail)
4702 {
4703 g_file_info_set_attribute_uint64 (info,
4704 G_FILE_ATTRIBUTE_FILESYSTEM_FREE,
4705 frsize * bavail);
4706
4707 g_file_info_set_attribute_uint64 (info,
4708 G_FILE_ATTRIBUTE_FILESYSTEM_SIZE,
4709 frsize * blocks);
4710
4711 g_file_info_set_attribute_uint64 (info,
4712 G_FILE_ATTRIBUTE_FILESYSTEM_USED,
4713 frsize * (blocks - bfree));
4714 }
4715
4716 g_file_info_set_attribute_boolean (info,
4717 G_FILE_ATTRIBUTE_FILESYSTEM_READONLY,
4718 flags & SSH_FXE_STATVFS_ST_RDONLY);
4719
4720 g_vfs_job_succeeded (job);
4721 }
4722
4723 static gboolean
try_query_fs_info(GVfsBackend * backend,GVfsJobQueryFsInfo * job,const char * filename,GFileInfo * info,GFileAttributeMatcher * matcher)4724 try_query_fs_info (GVfsBackend *backend,
4725 GVfsJobQueryFsInfo *job,
4726 const char *filename,
4727 GFileInfo *info,
4728 GFileAttributeMatcher *matcher)
4729 {
4730 GVfsBackendSftp *op_backend = G_VFS_BACKEND_SFTP (backend);
4731 GDataOutputStream *command;
4732
4733 g_file_info_set_attribute_string (info,
4734 G_FILE_ATTRIBUTE_FILESYSTEM_TYPE, "sftp");
4735 g_file_info_set_attribute_boolean (info,
4736 G_FILE_ATTRIBUTE_FILESYSTEM_REMOTE, TRUE);
4737
4738 if (has_extension (op_backend, SFTP_EXT_OPENSSH_STATVFS) &&
4739 (g_file_attribute_matcher_matches (matcher,
4740 G_FILE_ATTRIBUTE_FILESYSTEM_SIZE) ||
4741 g_file_attribute_matcher_matches (matcher,
4742 G_FILE_ATTRIBUTE_FILESYSTEM_FREE) ||
4743 g_file_attribute_matcher_matches (matcher,
4744 G_FILE_ATTRIBUTE_FILESYSTEM_USED) ||
4745 g_file_attribute_matcher_matches (matcher,
4746 G_FILE_ATTRIBUTE_FILESYSTEM_READONLY)))
4747 {
4748 command = new_command_stream (op_backend, SSH_FXP_EXTENDED);
4749 put_string (command, "statvfs@openssh.com");
4750 put_string (command, filename);
4751
4752 queue_command_stream_and_free (&op_backend->command_connection, command,
4753 query_fs_info_reply,
4754 G_VFS_JOB (job), info);
4755 }
4756 else
4757 g_vfs_job_succeeded (G_VFS_JOB (job));
4758
4759 return TRUE;
4760 }
4761
4762 typedef struct {
4763 GFileInfo *info;
4764 GFileAttributeMatcher *attribute_matcher;
4765 } QueryInfoFStatData;
4766
4767 static void
query_info_fstat_reply(GVfsBackendSftp * backend,int reply_type,GDataInputStream * reply,guint32 len,GVfsJob * job,gpointer user_data)4768 query_info_fstat_reply (GVfsBackendSftp *backend,
4769 int reply_type,
4770 GDataInputStream *reply,
4771 guint32 len,
4772 GVfsJob *job,
4773 gpointer user_data)
4774 {
4775 QueryInfoFStatData *data = user_data;
4776 GFileInfo *file_info;
4777 GFileAttributeMatcher *attribute_matcher;
4778
4779 file_info = data->info;
4780 attribute_matcher = data->attribute_matcher;
4781 g_slice_free (QueryInfoFStatData, data);
4782
4783 if (reply_type == SSH_FXP_STATUS)
4784 {
4785 result_from_status (job, reply, -1, -1);
4786 return;
4787 }
4788
4789 if (reply_type != SSH_FXP_ATTRS)
4790 {
4791 g_vfs_job_failed (job, G_IO_ERROR, G_IO_ERROR_FAILED,
4792 _("Invalid reply received"));
4793 return;
4794 }
4795
4796 parse_attributes (backend,
4797 file_info,
4798 NULL,
4799 reply,
4800 attribute_matcher);
4801
4802 g_vfs_job_succeeded (G_VFS_JOB (job));
4803 }
4804
4805 static gboolean
try_query_info_fstat(GVfsBackend * backend,GVfsJob * job,GVfsBackendHandle _handle,GFileInfo * info,GFileAttributeMatcher * attribute_matcher)4806 try_query_info_fstat (GVfsBackend *backend,
4807 GVfsJob *job,
4808 GVfsBackendHandle _handle,
4809 GFileInfo *info,
4810 GFileAttributeMatcher *attribute_matcher)
4811 {
4812 SftpHandle *handle = _handle;
4813 GVfsBackendSftp *op_backend = G_VFS_BACKEND_SFTP (backend);
4814 GDataOutputStream *command;
4815 QueryInfoFStatData *data;
4816
4817 command = new_command_stream (op_backend, SSH_FXP_FSTAT);
4818 put_data_buffer (command, handle->raw_handle);
4819
4820 data = g_slice_new (QueryInfoFStatData);
4821 data->info = info;
4822 data->attribute_matcher = attribute_matcher;
4823 queue_command_stream_and_free (&op_backend->command_connection, command,
4824 query_info_fstat_reply,
4825 G_VFS_JOB (job), data);
4826
4827 return TRUE;
4828 }
4829
4830 static void
move_reply(GVfsBackendSftp * backend,int reply_type,GDataInputStream * reply,guint32 len,GVfsJob * job,gpointer user_data)4831 move_reply (GVfsBackendSftp *backend,
4832 int reply_type,
4833 GDataInputStream *reply,
4834 guint32 len,
4835 GVfsJob *job,
4836 gpointer user_data)
4837 {
4838 goffset *file_size;
4839
4840 /* on any unknown error, return NOT_SUPPORTED to get the fallback implementation */
4841 if (reply_type == SSH_FXP_STATUS)
4842 {
4843 if (failure_from_status (job, reply, G_IO_ERROR_NOT_SUPPORTED, -1))
4844 {
4845 /* Succeeded, report file size */
4846 file_size = job->backend_data;
4847 if (file_size != NULL)
4848 g_vfs_job_progress_callback (*file_size, *file_size, job);
4849 g_vfs_job_succeeded (job);
4850 }
4851 }
4852 else
4853 g_vfs_job_failed (job, G_IO_ERROR, G_IO_ERROR_FAILED,
4854 _("Invalid reply received"));
4855 }
4856
4857 static void
move_do_rename(GVfsBackendSftp * backend,GVfsJob * job)4858 move_do_rename (GVfsBackendSftp *backend,
4859 GVfsJob *job)
4860 {
4861 GVfsJobMove *op_job;
4862 GDataOutputStream *command;
4863
4864 op_job = G_VFS_JOB_MOVE (job);
4865
4866 command = new_command_stream (backend,
4867 SSH_FXP_RENAME);
4868 put_string (command, op_job->source);
4869 put_string (command, op_job->destination);
4870
4871 queue_command_stream_and_free (&backend->command_connection, command,
4872 move_reply,
4873 G_VFS_JOB (job), NULL);
4874 }
4875
4876 static void
move_delete_target_reply(GVfsBackendSftp * backend,int reply_type,GDataInputStream * reply,guint32 len,GVfsJob * job,gpointer user_data)4877 move_delete_target_reply (GVfsBackendSftp *backend,
4878 int reply_type,
4879 GDataInputStream *reply,
4880 guint32 len,
4881 GVfsJob *job,
4882 gpointer user_data)
4883 {
4884 if (reply_type == SSH_FXP_STATUS)
4885 {
4886 if (failure_from_status (job, reply, -1, -1))
4887 move_do_rename (backend, job);
4888 }
4889 else
4890 g_vfs_job_failed (job, G_IO_ERROR, G_IO_ERROR_FAILED,
4891 _("Invalid reply received"));
4892 }
4893
4894
4895 static void
move_lstat_reply(GVfsBackendSftp * backend,MultiReply * replies,int n_replies,GVfsJob * job,gpointer user_data)4896 move_lstat_reply (GVfsBackendSftp *backend,
4897 MultiReply *replies,
4898 int n_replies,
4899 GVfsJob *job,
4900 gpointer user_data)
4901 {
4902 GVfsJobMove *op_job;
4903 gboolean destination_exist, source_is_dir, dest_is_dir;
4904 GDataOutputStream *command;
4905 GFileInfo *info;
4906 goffset *file_size;
4907
4908 op_job = G_VFS_JOB_MOVE (job);
4909
4910 if (replies[0].type == SSH_FXP_STATUS)
4911 {
4912 result_from_status (job, replies[0].data, -1, -1);
4913 return;
4914 }
4915 else if (replies[0].type != SSH_FXP_ATTRS)
4916 {
4917 g_vfs_job_failed (job,
4918 G_IO_ERROR, G_IO_ERROR_FAILED,
4919 "%s", _("Invalid reply received"));
4920 return;
4921 }
4922
4923 info = g_file_info_new ();
4924 parse_attributes (backend, info, NULL,
4925 replies[0].data, NULL);
4926 source_is_dir = g_file_info_get_file_type (info) == G_FILE_TYPE_DIRECTORY;
4927 file_size = g_new (goffset, 1);
4928 *file_size = g_file_info_get_size (info);
4929 g_vfs_job_set_backend_data (G_VFS_JOB (job), file_size, g_free);
4930 g_object_unref (info);
4931
4932 destination_exist = FALSE;
4933 if (replies[1].type == SSH_FXP_ATTRS)
4934 {
4935 destination_exist = TRUE; /* Target file exists */
4936
4937 info = g_file_info_new ();
4938 parse_attributes (backend, info, NULL,
4939 replies[1].data, NULL);
4940 dest_is_dir = g_file_info_get_file_type (info) == G_FILE_TYPE_DIRECTORY;
4941 g_object_unref (info);
4942
4943 if (op_job->flags & G_FILE_COPY_OVERWRITE)
4944 {
4945
4946 /* Always fail on dirs, even with overwrite */
4947 if (dest_is_dir)
4948 {
4949 if (source_is_dir)
4950 g_vfs_job_failed (job,
4951 G_IO_ERROR,
4952 G_IO_ERROR_WOULD_MERGE,
4953 _("Can’t move directory over directory"));
4954 else
4955 g_vfs_job_failed (job,
4956 G_IO_ERROR,
4957 G_IO_ERROR_IS_DIRECTORY,
4958 _("File is directory"));
4959 return;
4960 }
4961 }
4962 else
4963 {
4964 g_vfs_job_failed (G_VFS_JOB (job),
4965 G_IO_ERROR,
4966 G_IO_ERROR_EXISTS,
4967 _("Target file already exists"));
4968 return;
4969 }
4970 }
4971
4972 /* TODO: Check flags & G_FILE_COPY_BACKUP */
4973
4974 if (destination_exist && (op_job->flags & G_FILE_COPY_OVERWRITE))
4975 {
4976 command = new_command_stream (backend,
4977 SSH_FXP_REMOVE);
4978 put_string (command, op_job->destination);
4979 queue_command_stream_and_free (&backend->command_connection, command,
4980 move_delete_target_reply,
4981 G_VFS_JOB (job), NULL);
4982 return;
4983 }
4984
4985 move_do_rename (backend, job);
4986 }
4987
4988
4989 static gboolean
try_move(GVfsBackend * backend,GVfsJobMove * job,const char * source,const char * destination,GFileCopyFlags flags,GFileProgressCallback progress_callback,gpointer progress_callback_data)4990 try_move (GVfsBackend *backend,
4991 GVfsJobMove *job,
4992 const char *source,
4993 const char *destination,
4994 GFileCopyFlags flags,
4995 GFileProgressCallback progress_callback,
4996 gpointer progress_callback_data)
4997 {
4998 GVfsBackendSftp *op_backend = G_VFS_BACKEND_SFTP (backend);
4999 GDataOutputStream *command;
5000 Command commands[2];
5001
5002 commands[0].connection = &op_backend->command_connection;
5003 command = commands[0].cmd =
5004 new_command_stream (op_backend,
5005 SSH_FXP_LSTAT);
5006 put_string (command, source);
5007
5008 commands[1].connection = &op_backend->command_connection;
5009 command = commands[1].cmd =
5010 new_command_stream (op_backend,
5011 SSH_FXP_LSTAT);
5012 put_string (command, destination);
5013
5014 queue_command_streams_and_free (commands, 2,
5015 move_lstat_reply,
5016 G_VFS_JOB (job), NULL);
5017
5018 return TRUE;
5019 }
5020
5021 static void
set_display_name_reply(GVfsBackendSftp * backend,int reply_type,GDataInputStream * reply,guint32 len,GVfsJob * job,gpointer user_data)5022 set_display_name_reply (GVfsBackendSftp *backend,
5023 int reply_type,
5024 GDataInputStream *reply,
5025 guint32 len,
5026 GVfsJob *job,
5027 gpointer user_data)
5028 {
5029 if (reply_type == SSH_FXP_STATUS)
5030 result_from_status (job, reply, -1, -1);
5031 else
5032 g_vfs_job_failed (job, G_IO_ERROR, G_IO_ERROR_FAILED,
5033 _("Invalid reply received"));
5034 }
5035
5036 static gboolean
try_set_display_name(GVfsBackend * backend,GVfsJobSetDisplayName * job,const char * filename,const char * display_name)5037 try_set_display_name (GVfsBackend *backend,
5038 GVfsJobSetDisplayName *job,
5039 const char *filename,
5040 const char *display_name)
5041 {
5042 GVfsBackendSftp *op_backend = G_VFS_BACKEND_SFTP (backend);
5043 GDataOutputStream *command;
5044 char *dirname, *basename, *new_name;
5045
5046 /* We use the same setting as for local files. Can't really
5047 * do better, since there is no way in this version of sftp to find out
5048 * the remote charset encoding
5049 */
5050
5051 dirname = g_path_get_dirname (filename);
5052 basename = g_filename_from_utf8 (display_name, -1, NULL, NULL, NULL);
5053 if (basename == NULL)
5054 basename = g_strdup (display_name);
5055 new_name = g_build_filename (dirname, basename, NULL);
5056 g_free (dirname);
5057 g_free (basename);
5058
5059 g_vfs_job_set_display_name_set_new_path (job,
5060 new_name);
5061
5062 command = new_command_stream (op_backend,
5063 SSH_FXP_RENAME);
5064 put_string (command, filename);
5065 put_string (command, new_name);
5066
5067 queue_command_stream_and_free (&op_backend->command_connection, command,
5068 set_display_name_reply,
5069 G_VFS_JOB (job), NULL);
5070
5071 g_free (new_name);
5072
5073 return TRUE;
5074 }
5075
5076
5077 static void
make_symlink_reply(GVfsBackendSftp * backend,int reply_type,GDataInputStream * reply,guint32 len,GVfsJob * job,gpointer user_data)5078 make_symlink_reply (GVfsBackendSftp *backend,
5079 int reply_type,
5080 GDataInputStream *reply,
5081 guint32 len,
5082 GVfsJob *job,
5083 gpointer user_data)
5084 {
5085 if (reply_type == SSH_FXP_STATUS)
5086 result_from_status (job, reply, G_IO_ERROR_EXISTS, -1);
5087 else
5088 g_vfs_job_failed (job, G_IO_ERROR, G_IO_ERROR_FAILED,
5089 _("Invalid reply received"));
5090 }
5091
5092 static gboolean
try_make_symlink(GVfsBackend * backend,GVfsJobMakeSymlink * job,const char * filename,const char * symlink_value)5093 try_make_symlink (GVfsBackend *backend,
5094 GVfsJobMakeSymlink *job,
5095 const char *filename,
5096 const char *symlink_value)
5097 {
5098 GVfsBackendSftp *op_backend = G_VFS_BACKEND_SFTP (backend);
5099 GDataOutputStream *command;
5100
5101 command = new_command_stream (op_backend,
5102 SSH_FXP_SYMLINK);
5103 /* Note: This is the reverse order of how this is documented in
5104 draft-ietf-secsh-filexfer-02.txt, but its how openssh does it. */
5105 put_string (command, symlink_value);
5106 put_string (command, filename);
5107
5108 queue_command_stream_and_free (&op_backend->command_connection, command,
5109 make_symlink_reply,
5110 G_VFS_JOB (job), NULL);
5111
5112 return TRUE;
5113 }
5114
5115 static void
mkdir_stat_reply(GVfsBackendSftp * backend,int reply_type,GDataInputStream * reply,guint32 len,GVfsJob * job,gpointer user_data)5116 mkdir_stat_reply (GVfsBackendSftp *backend,
5117 int reply_type,
5118 GDataInputStream *reply,
5119 guint32 len,
5120 GVfsJob *job,
5121 gpointer user_data)
5122 {
5123 if (reply_type == SSH_FXP_STATUS)
5124 /* We got some error, but lets report the original FAILURE, as
5125 these extra errors are not really mkdir errors, we just wanted
5126 to implement EEXISTS */
5127 result_from_status_code (job, SSH_FX_FAILURE, -1, -1);
5128 else if (reply_type != SSH_FXP_ATTRS)
5129 g_vfs_job_failed (job, G_IO_ERROR, G_IO_ERROR_FAILED,
5130 _("Invalid reply received"));
5131 else
5132 g_vfs_job_failed (job, G_IO_ERROR, G_IO_ERROR_EXISTS,
5133 _("Target file exists"));
5134 }
5135
5136 static void
make_directory_reply(GVfsBackendSftp * backend,int reply_type,GDataInputStream * reply,guint32 len,GVfsJob * job,gpointer user_data)5137 make_directory_reply (GVfsBackendSftp *backend,
5138 int reply_type,
5139 GDataInputStream *reply,
5140 guint32 len,
5141 GVfsJob *job,
5142 gpointer user_data)
5143 {
5144 if (reply_type == SSH_FXP_STATUS)
5145 {
5146 gint stat_error;
5147
5148 stat_error = read_status_code (reply);
5149 if (stat_error == SSH_FX_FAILURE)
5150 {
5151 /* Generic SFTP error, let's stat the target */
5152 GDataOutputStream *command;
5153
5154 command = new_command_stream (backend,
5155 SSH_FXP_LSTAT);
5156 put_string (command, G_VFS_JOB_MAKE_DIRECTORY (job)->filename);
5157 queue_command_stream_and_free (&backend->command_connection, command,
5158 mkdir_stat_reply,
5159 G_VFS_JOB (job), NULL);
5160 }
5161 else
5162 result_from_status_code (job, stat_error, -1, -1);
5163 }
5164 else
5165 g_vfs_job_failed (job, G_IO_ERROR, G_IO_ERROR_FAILED,
5166 _("Invalid reply received"));
5167 }
5168
5169 static gboolean
try_make_directory(GVfsBackend * backend,GVfsJobMakeDirectory * job,const char * filename)5170 try_make_directory (GVfsBackend *backend,
5171 GVfsJobMakeDirectory *job,
5172 const char *filename)
5173 {
5174 GVfsBackendSftp *op_backend = G_VFS_BACKEND_SFTP (backend);
5175 GDataOutputStream *command;
5176
5177 command = new_command_stream (op_backend,
5178 SSH_FXP_MKDIR);
5179 put_string (command, filename);
5180 /* No file info - flag 0 */
5181 g_data_output_stream_put_uint32 (command, 0, NULL, NULL);
5182
5183 queue_command_stream_and_free (&op_backend->command_connection, command,
5184 make_directory_reply,
5185 G_VFS_JOB (job), NULL);
5186
5187 return TRUE;
5188 }
5189
5190 static void
delete_remove_reply(GVfsBackendSftp * backend,int reply_type,GDataInputStream * reply,guint32 len,GVfsJob * job,gpointer user_data)5191 delete_remove_reply (GVfsBackendSftp *backend,
5192 int reply_type,
5193 GDataInputStream *reply,
5194 guint32 len,
5195 GVfsJob *job,
5196 gpointer user_data)
5197 {
5198 if (reply_type == SSH_FXP_STATUS)
5199 result_from_status (job, reply, -1, -1);
5200 else
5201 g_vfs_job_failed (job, G_IO_ERROR, G_IO_ERROR_FAILED,
5202 _("Invalid reply received"));
5203 }
5204
5205 static void
delete_rmdir_reply(GVfsBackendSftp * backend,int reply_type,GDataInputStream * reply,guint32 len,GVfsJob * job,gpointer user_data)5206 delete_rmdir_reply (GVfsBackendSftp *backend,
5207 int reply_type,
5208 GDataInputStream *reply,
5209 guint32 len,
5210 GVfsJob *job,
5211 gpointer user_data)
5212 {
5213 if (reply_type == SSH_FXP_STATUS)
5214 result_from_status (job, reply, G_IO_ERROR_NOT_EMPTY, -1);
5215 else
5216 g_vfs_job_failed (job, G_IO_ERROR, G_IO_ERROR_FAILED,
5217 _("Invalid reply received"));
5218 }
5219
5220 static void
delete_lstat_reply(GVfsBackendSftp * backend,int reply_type,GDataInputStream * reply,guint32 len,GVfsJob * job,gpointer user_data)5221 delete_lstat_reply (GVfsBackendSftp *backend,
5222 int reply_type,
5223 GDataInputStream *reply,
5224 guint32 len,
5225 GVfsJob *job,
5226 gpointer user_data)
5227 {
5228 if (reply_type == SSH_FXP_STATUS)
5229 result_from_status (job, reply, -1, -1);
5230 else if (reply_type != SSH_FXP_ATTRS)
5231 g_vfs_job_failed (job, G_IO_ERROR, G_IO_ERROR_FAILED,
5232 _("Invalid reply received"));
5233 else
5234 {
5235 GFileInfo *info;
5236 GDataOutputStream *command;
5237
5238 info = g_file_info_new ();
5239 parse_attributes (backend, info, NULL, reply, NULL);
5240
5241 if (g_file_info_get_file_type (info) == G_FILE_TYPE_DIRECTORY)
5242 {
5243 command = new_command_stream (backend,
5244 SSH_FXP_RMDIR);
5245 put_string (command, G_VFS_JOB_DELETE (job)->filename);
5246 queue_command_stream_and_free (&backend->command_connection, command,
5247 delete_rmdir_reply,
5248 G_VFS_JOB (job), NULL);
5249 }
5250 else
5251 {
5252 command = new_command_stream (backend,
5253 SSH_FXP_REMOVE);
5254 put_string (command, G_VFS_JOB_DELETE (job)->filename);
5255 queue_command_stream_and_free (&backend->command_connection, command,
5256 delete_remove_reply,
5257 G_VFS_JOB (job), NULL);
5258 }
5259
5260 g_object_unref (info);
5261 }
5262 }
5263
5264 static gboolean
try_delete(GVfsBackend * backend,GVfsJobDelete * job,const char * filename)5265 try_delete (GVfsBackend *backend,
5266 GVfsJobDelete *job,
5267 const char *filename)
5268 {
5269 GVfsBackendSftp *op_backend = G_VFS_BACKEND_SFTP (backend);
5270 GDataOutputStream *command;
5271
5272 command = new_command_stream (op_backend,
5273 SSH_FXP_LSTAT);
5274 put_string (command, filename);
5275 queue_command_stream_and_free (&op_backend->command_connection, command,
5276 delete_lstat_reply,
5277 G_VFS_JOB (job), NULL);
5278
5279 return TRUE;
5280 }
5281
5282 static gboolean
try_query_settable_attributes(GVfsBackend * backend,GVfsJobQueryAttributes * job,const char * filename)5283 try_query_settable_attributes (GVfsBackend *backend,
5284 GVfsJobQueryAttributes *job,
5285 const char *filename)
5286 {
5287 GFileAttributeInfoList *list;
5288
5289 list = g_file_attribute_info_list_new ();
5290
5291 g_file_attribute_info_list_add (list,
5292 G_FILE_ATTRIBUTE_UNIX_MODE,
5293 G_FILE_ATTRIBUTE_TYPE_UINT32,
5294 G_FILE_ATTRIBUTE_INFO_COPY_WITH_FILE |
5295 G_FILE_ATTRIBUTE_INFO_COPY_WHEN_MOVED);
5296
5297 g_file_attribute_info_list_add (list,
5298 G_FILE_ATTRIBUTE_TIME_MODIFIED,
5299 G_FILE_ATTRIBUTE_TYPE_UINT64,
5300 G_FILE_ATTRIBUTE_INFO_COPY_WITH_FILE |
5301 G_FILE_ATTRIBUTE_INFO_COPY_WHEN_MOVED);
5302
5303 g_file_attribute_info_list_add (list,
5304 G_FILE_ATTRIBUTE_TIME_ACCESS,
5305 G_FILE_ATTRIBUTE_TYPE_UINT64,
5306 G_FILE_ATTRIBUTE_INFO_COPY_WHEN_MOVED);
5307
5308 g_vfs_job_query_attributes_set_list (job, list);
5309 g_vfs_job_succeeded (G_VFS_JOB (job));
5310 g_file_attribute_info_list_unref (list);
5311
5312 return TRUE;
5313 }
5314
5315 static void
set_attribute_reply(GVfsBackendSftp * backend,int reply_type,GDataInputStream * reply,guint32 len,GVfsJob * job,gpointer user_data)5316 set_attribute_reply (GVfsBackendSftp *backend,
5317 int reply_type,
5318 GDataInputStream *reply,
5319 guint32 len,
5320 GVfsJob *job,
5321 gpointer user_data)
5322 {
5323 if (reply_type == SSH_FXP_STATUS)
5324 result_from_status (job, reply, -1, -1);
5325 else
5326 g_vfs_job_failed (job, G_IO_ERROR, G_IO_ERROR_FAILED,
5327 _("Invalid reply received"));
5328 }
5329
5330 static void
set_attribute_stat_reply(GVfsBackendSftp * backend,int reply_type,GDataInputStream * reply,guint32 len,GVfsJob * job,gpointer user_data)5331 set_attribute_stat_reply (GVfsBackendSftp *backend,
5332 int reply_type,
5333 GDataInputStream *reply,
5334 guint32 len,
5335 GVfsJob *job,
5336 gpointer user_data)
5337 {
5338 GVfsBackendSftp *op_backend = G_VFS_BACKEND_SFTP (backend);
5339 GDataOutputStream *command;
5340 GVfsJobSetAttribute *op_job = G_VFS_JOB_SET_ATTRIBUTE (job);
5341
5342 if (reply_type == SSH_FXP_ATTRS)
5343 {
5344 guint32 mtime;
5345 guint32 atime;
5346 GFileInfo *info = g_file_info_new ();
5347
5348 parse_attributes (backend, info, NULL, reply, NULL);
5349
5350 /* parse_attributes sets either both timestamps or none
5351 * so checking one of them is enough. */
5352 if (!g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_TIME_MODIFIED))
5353 {
5354 g_vfs_job_failed (job, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
5355 _("Operation not supported"));
5356 g_object_unref (info);
5357 return;
5358 }
5359 /* Timestamps must be read as uint64 but sftp only supports uint32 */
5360 if (op_job->value.uint64 > G_MAXUINT32)
5361 {
5362 g_vfs_job_failed (job, G_IO_ERROR, G_IO_ERROR_FAILED,
5363 _("Value out of range, sftp only supports 32bit timestamps"));
5364 g_object_unref (info);
5365 return;
5366 }
5367
5368 if (g_strcmp0 (op_job->attribute, G_FILE_ATTRIBUTE_TIME_ACCESS) == 0)
5369 {
5370 mtime = g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_TIME_MODIFIED);
5371 atime = op_job->value.uint64;
5372 }
5373 else
5374 {
5375 atime = g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_TIME_ACCESS);
5376 mtime = op_job->value.uint64;
5377 }
5378
5379 g_object_unref (info);
5380
5381 command = new_command_stream (op_backend, SSH_FXP_SETSTAT);
5382 put_string (command, op_job->filename);
5383
5384 g_data_output_stream_put_uint32 (command, SSH_FILEXFER_ATTR_ACMODTIME, NULL, NULL);
5385 g_data_output_stream_put_uint32 (command, atime, NULL, NULL);
5386 g_data_output_stream_put_uint32 (command, mtime, NULL, NULL);
5387 queue_command_stream_and_free (&op_backend->command_connection, command,
5388 set_attribute_reply,
5389 G_VFS_JOB (job), NULL);
5390 }
5391 else if (reply_type == SSH_FXP_STATUS)
5392 result_from_status (job, reply, -1, -1);
5393 else
5394 g_vfs_job_failed (job, G_IO_ERROR, G_IO_ERROR_FAILED,
5395 _("Invalid reply received"));
5396 }
5397
5398 static gboolean
try_set_attribute(GVfsBackend * backend,GVfsJobSetAttribute * job,const char * filename,const char * attribute,GFileAttributeType type,gpointer value_p,GFileQueryInfoFlags flags)5399 try_set_attribute (GVfsBackend *backend,
5400 GVfsJobSetAttribute *job,
5401 const char *filename,
5402 const char *attribute,
5403 GFileAttributeType type,
5404 gpointer value_p,
5405 GFileQueryInfoFlags flags)
5406 {
5407 GVfsBackendSftp *op_backend = G_VFS_BACKEND_SFTP (backend);
5408 GDataOutputStream *command;
5409
5410 if (g_strcmp0 (attribute, G_FILE_ATTRIBUTE_UNIX_MODE) == 0)
5411 {
5412 if (type != G_FILE_ATTRIBUTE_TYPE_UINT32)
5413 {
5414 g_vfs_job_failed (G_VFS_JOB (job),
5415 G_IO_ERROR,
5416 G_IO_ERROR_INVALID_ARGUMENT,
5417 _("Invalid attribute type (uint32 expected)"));
5418 return TRUE;
5419 }
5420
5421 command = new_command_stream (op_backend,
5422 SSH_FXP_SETSTAT);
5423 put_string (command, filename);
5424 g_data_output_stream_put_uint32 (command, SSH_FILEXFER_ATTR_PERMISSIONS, NULL, NULL);
5425 g_data_output_stream_put_uint32 (command, (*(guint32 *)value_p) & 0777, NULL, NULL);
5426 queue_command_stream_and_free (&op_backend->command_connection, command,
5427 set_attribute_reply,
5428 G_VFS_JOB (job), NULL);
5429 }
5430 else if (g_strcmp0 (attribute, G_FILE_ATTRIBUTE_TIME_MODIFIED) == 0 ||
5431 g_strcmp0 (attribute, G_FILE_ATTRIBUTE_TIME_ACCESS) == 0)
5432 {
5433 if (type != G_FILE_ATTRIBUTE_TYPE_UINT64)
5434 {
5435 g_vfs_job_failed (G_VFS_JOB (job),
5436 G_IO_ERROR,
5437 G_IO_ERROR_INVALID_ARGUMENT,
5438 _("Invalid attribute type (uint64 expected)"));
5439 return TRUE;
5440 }
5441
5442 command = new_command_stream (op_backend, SSH_FXP_LSTAT);
5443 put_string (command, filename);
5444
5445 queue_command_stream_and_free (&op_backend->command_connection, command,
5446 set_attribute_stat_reply,
5447 G_VFS_JOB (job), NULL);
5448 }
5449 else
5450 {
5451 g_vfs_job_failed (G_VFS_JOB (job),
5452 G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
5453 _("Operation not supported"));
5454 }
5455
5456 return TRUE;
5457 }
5458
5459 /* Return true if the job is finished or cancelled, failing it if needed. */
5460 static gboolean
check_finished_or_cancelled_job(GVfsJob * job)5461 check_finished_or_cancelled_job (GVfsJob *job)
5462 {
5463 if (g_vfs_job_is_finished (job))
5464 return TRUE;
5465
5466 if (g_vfs_job_is_cancelled (job))
5467 {
5468 g_vfs_job_failed (job, G_IO_ERROR, G_IO_ERROR_CANCELLED,
5469 _("Operation was cancelled"));
5470 return TRUE;
5471 }
5472
5473 return FALSE;
5474 }
5475
5476 /* The push sliding window mechanism is based on the one in the OpenSSH sftp
5477 * client. */
5478
5479 #define PUSH_MAX_REQUESTS 64
5480
5481 typedef struct {
5482 /* Job context */
5483 GVfsBackendSftp *backend;
5484 GVfsJobPush *op_job;
5485 GVfsJob *job;
5486
5487 /* Open files */
5488 DataBuffer *raw_handle;
5489 GInputStream *in;
5490
5491 /* fstat information */
5492 goffset size;
5493 guint32 permissions;
5494 guint64 mtime;
5495 guint64 atime;
5496
5497 /* state */
5498 goffset offset;
5499 goffset n_written;
5500 int num_req;
5501
5502 /* replace data */
5503 char *tempname;
5504 int temp_count;
5505
5506 char buffer[MAX_BUFFER_SIZE];
5507 } SftpPushHandle;
5508
5509 typedef struct {
5510 SftpPushHandle *handle;
5511 gssize count;
5512 } PushWriteRequest;
5513
5514 static void
sftp_push_handle_free(SftpPushHandle * handle)5515 sftp_push_handle_free (SftpPushHandle *handle)
5516 {
5517 GDataOutputStream *command;
5518
5519 /* Only free the handle if there are no write requests outstanding and no
5520 * asynchronous reads pending. */
5521 if (handle->num_req == 0 && (!handle->in || !g_input_stream_has_pending (handle->in)))
5522 {
5523 if (handle->in)
5524 {
5525 g_input_stream_close_async (handle->in, 0, NULL, NULL, NULL);
5526 g_object_unref (handle->in);
5527 }
5528
5529 /* If raw_handle is non-NULL, it means destination is still open. Close
5530 * it. */
5531 if (handle->raw_handle)
5532 {
5533 command = new_command_stream (handle->backend, SSH_FXP_CLOSE);
5534 put_data_buffer (command, handle->raw_handle);
5535 queue_command_stream_and_free (&handle->backend->data_connection,
5536 command,
5537 NULL,
5538 handle->job, NULL);
5539 data_buffer_free (handle->raw_handle);
5540 }
5541
5542 /* If tempname is non-NULL, it means we failed and should delete the temp
5543 * file. */
5544 if (handle->tempname)
5545 {
5546 command = new_command_stream (handle->backend, SSH_FXP_REMOVE);
5547 put_string (command, handle->tempname);
5548 queue_command_stream_and_free (&handle->backend->command_connection,
5549 command,
5550 NULL,
5551 handle->job, NULL);
5552
5553 g_free (handle->tempname);
5554 }
5555
5556 g_object_unref (handle->backend);
5557 g_object_unref (handle->job);
5558 g_slice_free (SftpPushHandle, handle);
5559 }
5560 }
5561
5562 static void
5563 push_read_cb (GObject *source, GAsyncResult *res, gpointer user_data);
5564
5565 static void
push_enqueue_request(SftpPushHandle * handle)5566 push_enqueue_request (SftpPushHandle *handle)
5567 {
5568 g_input_stream_read_async (handle->in,
5569 handle->buffer,
5570 MAX_BUFFER_SIZE,
5571 G_PRIORITY_DEFAULT,
5572 NULL,
5573 push_read_cb, handle);
5574 handle->num_req++;
5575 }
5576
5577 static void
push_close_moved_file(GVfsBackendSftp * backend,int reply_type,GDataInputStream * reply,guint32 len,GVfsJob * job,gpointer user_data)5578 push_close_moved_file (GVfsBackendSftp *backend,
5579 int reply_type,
5580 GDataInputStream *reply,
5581 guint32 len,
5582 GVfsJob *job,
5583 gpointer user_data)
5584 {
5585 SftpPushHandle *handle = user_data;
5586
5587 if (reply_type == SSH_FXP_STATUS)
5588 {
5589 guint32 code = read_status_code (reply);
5590 if (code == SSH_FX_OK)
5591 {
5592 if (handle->op_job->remove_source)
5593 g_unlink (handle->op_job->local_path);
5594
5595 g_vfs_job_succeeded (job);
5596 }
5597 else
5598 result_from_status_code (job, code, -1, -1);
5599 }
5600 else
5601 g_vfs_job_failed (job, G_IO_ERROR, G_IO_ERROR_FAILED,
5602 _("Invalid reply received"));
5603
5604 sftp_push_handle_free (handle);
5605 }
5606
5607 static void
push_close_deleted_file(GVfsBackendSftp * backend,int reply_type,GDataInputStream * reply,guint32 len,GVfsJob * job,gpointer user_data)5608 push_close_deleted_file (GVfsBackendSftp *backend,
5609 int reply_type,
5610 GDataInputStream *reply,
5611 guint32 len,
5612 GVfsJob *job,
5613 gpointer user_data)
5614 {
5615 SftpPushHandle *handle = user_data;
5616
5617 if (reply_type == SSH_FXP_STATUS)
5618 {
5619 guint32 code = read_status_code (reply);
5620 if (code == SSH_FX_OK)
5621 {
5622 /* The delete completed successfully, now rename. */
5623 GDataOutputStream *command = new_command_stream (backend, SSH_FXP_RENAME);
5624 put_string (command, handle->tempname);
5625 put_string (command, handle->op_job->destination);
5626 queue_command_stream_and_free (&backend->command_connection, command,
5627 push_close_moved_file,
5628 job, handle);
5629
5630 g_free (handle->tempname);
5631 handle->tempname = NULL;
5632 return;
5633 }
5634 else
5635 result_from_status_code (job, code, -1, -1);
5636 }
5637 else
5638 g_vfs_job_failed (job, G_IO_ERROR, G_IO_ERROR_FAILED,
5639 _("Invalid reply received"));
5640
5641 sftp_push_handle_free (handle);
5642 }
5643
5644 static void
push_close_delete_or_succeed(GVfsBackendSftp * backend,int reply_type,GDataInputStream * reply,guint32 len,GVfsJob * job,gpointer user_data)5645 push_close_delete_or_succeed (GVfsBackendSftp *backend,
5646 int reply_type,
5647 GDataInputStream *reply,
5648 guint32 len,
5649 GVfsJob *job,
5650 gpointer user_data)
5651 {
5652 SftpPushHandle *handle = user_data;
5653
5654 if (handle->tempname)
5655 {
5656 /* If we wrote to a temp file, do delete then rename. */
5657 GDataOutputStream *command = new_command_stream (handle->backend, SSH_FXP_REMOVE);
5658 put_string (command, handle->op_job->destination);
5659 queue_command_stream_and_free (&handle->backend->command_connection, command,
5660 push_close_deleted_file,
5661 handle->job, handle);
5662 }
5663 else
5664 {
5665 if (handle->op_job->remove_source)
5666 g_unlink (handle->op_job->local_path);
5667
5668 g_vfs_job_succeeded (handle->job);
5669 sftp_push_handle_free (handle);
5670 }
5671 }
5672
5673 static void
push_close_restore_permissions(SftpPushHandle * handle)5674 push_close_restore_permissions (SftpPushHandle *handle)
5675 {
5676 gboolean default_perms = (handle->op_job->flags & G_FILE_COPY_TARGET_DEFAULT_PERMS);
5677 guint32 flags = SSH_FILEXFER_ATTR_ACMODTIME;
5678
5679 if (!default_perms)
5680 flags |= SSH_FILEXFER_ATTR_PERMISSIONS;
5681
5682 /* Restore the source file's permissions and timestamps. */
5683 GDataOutputStream *command = new_command_stream (handle->backend, SSH_FXP_SETSTAT);
5684 put_string (command, handle->tempname ? handle->tempname : handle->op_job->destination);
5685 g_data_output_stream_put_uint32 (command, flags, NULL, NULL);
5686 if (!default_perms)
5687 g_data_output_stream_put_uint32 (command, handle->permissions, NULL, NULL);
5688 g_data_output_stream_put_uint32 (command, handle->atime, NULL, NULL);
5689 g_data_output_stream_put_uint32 (command, handle->mtime, NULL, NULL);
5690 queue_command_stream_and_free (&handle->backend->command_connection, command,
5691 push_close_delete_or_succeed,
5692 handle->job, handle);
5693 }
5694
5695 static void
push_close_stat_reply(GVfsBackendSftp * backend,int reply_type,GDataInputStream * reply,guint32 len,GVfsJob * job,gpointer user_data)5696 push_close_stat_reply (GVfsBackendSftp *backend,
5697 int reply_type,
5698 GDataInputStream *reply,
5699 guint32 len,
5700 GVfsJob *job,
5701 gpointer user_data)
5702 {
5703 SftpPushHandle *handle = user_data;
5704
5705 if (reply_type == SSH_FXP_ATTRS)
5706 {
5707 GFileInfo *info = g_file_info_new ();
5708
5709 parse_attributes (backend, info, NULL, reply, NULL);
5710
5711 /* Don't fail on error, but fall back to the local atime
5712 * (assigned in push_source_fstat_cb). */
5713 if (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_TIME_ACCESS))
5714 handle->atime = g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_TIME_ACCESS);
5715
5716 g_object_unref (info);
5717 }
5718
5719 push_close_restore_permissions (handle);
5720 }
5721
5722 static void
push_close_write_reply(GVfsBackendSftp * backend,int reply_type,GDataInputStream * reply,guint32 len,GVfsJob * job,gpointer user_data)5723 push_close_write_reply (GVfsBackendSftp *backend,
5724 int reply_type,
5725 GDataInputStream *reply,
5726 guint32 len,
5727 GVfsJob *job,
5728 gpointer user_data)
5729 {
5730 SftpPushHandle *handle = user_data;
5731
5732 if (reply_type == SSH_FXP_STATUS)
5733 {
5734 guint32 code = read_status_code (reply);
5735 if (code == SSH_FX_OK)
5736 {
5737 /* Atime is COPY_WHEN_MOVED, but not COPY_WITH_FILE. */
5738 if (!handle->op_job->remove_source &&
5739 !(handle->op_job->flags & G_FILE_COPY_ALL_METADATA))
5740 {
5741 GDataOutputStream *command = new_command_stream (backend, SSH_FXP_LSTAT);
5742 put_string (command, handle->op_job->destination);
5743 queue_command_stream_and_free (&backend->command_connection, command,
5744 push_close_stat_reply,
5745 job, handle);
5746 return;
5747 }
5748
5749 push_close_restore_permissions (handle);
5750 return;
5751 }
5752 else
5753 result_from_status_code (job, code, -1, -1);
5754 }
5755 else
5756 g_vfs_job_failed (job, G_IO_ERROR, G_IO_ERROR_FAILED,
5757 _("Invalid reply received"));
5758
5759 sftp_push_handle_free (handle);
5760 }
5761
5762 static void
push_finish(SftpPushHandle * handle)5763 push_finish (SftpPushHandle *handle)
5764 {
5765 GDataOutputStream *command = new_command_stream (handle->backend, SSH_FXP_CLOSE);
5766 put_data_buffer (command, handle->raw_handle);
5767 queue_command_stream_and_free (&handle->backend->data_connection, command,
5768 push_close_write_reply,
5769 handle->job, handle);
5770
5771 data_buffer_free (handle->raw_handle);
5772 handle->raw_handle = NULL;
5773 }
5774
5775 static void
push_source_close_cb(GObject * source,GAsyncResult * res,gpointer user_data)5776 push_source_close_cb (GObject *source, GAsyncResult *res, gpointer user_data)
5777 {
5778 SftpPushHandle *handle = user_data;
5779
5780 g_input_stream_close_finish (handle->in, res, NULL);
5781 g_clear_object (&handle->in);
5782
5783 if (check_finished_or_cancelled_job (handle->job))
5784 {
5785 sftp_push_handle_free (handle);
5786 return;
5787 }
5788
5789 /* If there are no write requests outstanding, we are done. */
5790 if (handle->num_req == 0)
5791 push_finish(handle);
5792 }
5793
5794 static void
push_write_reply(GVfsBackendSftp * backend,int reply_type,GDataInputStream * reply,guint32 len,GVfsJob * job,gpointer user_data)5795 push_write_reply (GVfsBackendSftp *backend,
5796 int reply_type,
5797 GDataInputStream *reply,
5798 guint32 len,
5799 GVfsJob *job,
5800 gpointer user_data)
5801 {
5802 PushWriteRequest *request = user_data;
5803 SftpPushHandle *handle = request->handle;
5804 gssize count = request->count;
5805
5806 g_slice_free (PushWriteRequest, request);
5807
5808 handle->num_req--;
5809
5810 if (check_finished_or_cancelled_job (job))
5811 {
5812 sftp_push_handle_free (handle);
5813 return;
5814 }
5815
5816 if (reply_type == SSH_FXP_STATUS)
5817 {
5818 guint32 code = read_status_code (reply);
5819
5820 if (code == SSH_FX_OK)
5821 {
5822 handle->n_written += count;
5823 g_vfs_job_progress_callback (handle->n_written, handle->size, job);
5824
5825 /* Enqueue a read op if the file is still open, and there isn't
5826 * already one pending. */
5827 if (handle->in && !g_input_stream_has_pending (handle->in))
5828 push_enqueue_request (handle);
5829
5830 /* We are done if the file is closed and there are no write requests
5831 * oustanding. */
5832 if (!handle->in && handle->num_req == 0)
5833 {
5834 push_finish (handle);
5835 return;
5836 }
5837 }
5838 else
5839 result_from_status_code (handle->job, code, -1, -1);
5840 }
5841 else
5842 g_vfs_job_failed (job, G_IO_ERROR, G_IO_ERROR_FAILED,
5843 _("Invalid reply received"));
5844
5845 sftp_push_handle_free (handle);
5846 }
5847
5848 static void
push_read_cb(GObject * source,GAsyncResult * res,gpointer user_data)5849 push_read_cb (GObject *source, GAsyncResult *res, gpointer user_data)
5850 {
5851 PushWriteRequest *request;
5852 GDataOutputStream *command;
5853 gssize count;
5854
5855 SftpPushHandle *handle = user_data;
5856 GError *error = NULL;
5857
5858 count = g_input_stream_read_finish (handle->in, res, &error);
5859
5860 if (check_finished_or_cancelled_job (handle->job))
5861 {
5862 g_clear_error (&error);
5863 sftp_push_handle_free (handle);
5864 return;
5865 }
5866
5867 if (error)
5868 {
5869 g_vfs_job_failed_from_error (handle->job, error);
5870 g_error_free (error);
5871
5872 sftp_push_handle_free (handle);
5873 return;
5874 }
5875
5876 if (count == 0)
5877 {
5878 handle->num_req--;
5879
5880 g_input_stream_close_async (handle->in,
5881 0, NULL,
5882 push_source_close_cb, handle);
5883 return;
5884 }
5885
5886 request = g_slice_new (PushWriteRequest);
5887 request->handle = handle;
5888 request->count = count;
5889
5890 command = new_command_stream (handle->backend, SSH_FXP_WRITE);
5891 put_data_buffer (command, handle->raw_handle);
5892 g_data_output_stream_put_uint64 (command, handle->offset, NULL, NULL);
5893 g_data_output_stream_put_uint32 (command, count, NULL, NULL);
5894 g_output_stream_write_all (G_OUTPUT_STREAM (command),
5895 handle->buffer, count,
5896 NULL, NULL, NULL);
5897 queue_command_stream_and_free (&handle->backend->data_connection, command,
5898 push_write_reply,
5899 handle->job, request);
5900 handle->offset += count;
5901
5902 if (handle->num_req < PUSH_MAX_REQUESTS)
5903 push_enqueue_request (handle);
5904 }
5905
5906 static void push_create_temp (SftpPushHandle *handle);
5907
5908 static void
push_truncate_original_reply(GVfsBackendSftp * backend,int reply_type,GDataInputStream * reply,guint32 len,GVfsJob * job,gpointer user_data)5909 push_truncate_original_reply (GVfsBackendSftp *backend,
5910 int reply_type,
5911 GDataInputStream *reply,
5912 guint32 len,
5913 GVfsJob *job,
5914 gpointer user_data)
5915 {
5916 SftpPushHandle *handle = user_data;
5917
5918 if (reply_type == SSH_FXP_HANDLE)
5919 {
5920 handle->raw_handle = read_data_buffer (reply);
5921 push_enqueue_request (handle);
5922 }
5923 else if (reply_type == SSH_FXP_STATUS)
5924 result_from_status (job, reply, -1, -1);
5925 else
5926 g_vfs_job_failed (job, G_IO_ERROR, G_IO_ERROR_FAILED,
5927 _("Invalid reply received"));
5928
5929 sftp_push_handle_free (handle);
5930 }
5931
5932 static void
push_create_temp_reply(GVfsBackendSftp * backend,int reply_type,GDataInputStream * reply,guint32 len,GVfsJob * job,gpointer user_data)5933 push_create_temp_reply (GVfsBackendSftp *backend,
5934 int reply_type,
5935 GDataInputStream *reply,
5936 guint32 len,
5937 GVfsJob *job,
5938 gpointer user_data)
5939 {
5940 SftpPushHandle *handle = user_data;
5941
5942 if (reply_type == SSH_FXP_STATUS)
5943 {
5944 guint32 code = read_status_code (reply);
5945 if (code == SSH_FX_PERMISSION_DENIED)
5946 {
5947 /* The temp file creation failed. Try truncating the existing file. */
5948 GDataOutputStream *command;
5949
5950 g_free (handle->tempname);
5951 handle->tempname = NULL;
5952
5953 command = new_command_stream (backend, SSH_FXP_OPEN);
5954 put_string (command, handle->op_job->destination);
5955 g_data_output_stream_put_uint32 (command, SSH_FXF_WRITE|SSH_FXF_CREAT|SSH_FXF_TRUNC, NULL, NULL);
5956 g_data_output_stream_put_uint32 (command, 0, NULL, NULL);
5957 queue_command_stream_and_free (&backend->data_connection, command,
5958 push_truncate_original_reply,
5959 job, handle);
5960
5961 return;
5962 }
5963 else if (code == SSH_FX_FAILURE)
5964 {
5965 push_create_temp (handle);
5966 return;
5967 }
5968 else
5969 g_vfs_job_failed (job, G_IO_ERROR, G_IO_ERROR_FAILED,
5970 _("Invalid reply received"));
5971 }
5972 else if (reply_type != SSH_FXP_HANDLE)
5973 g_vfs_job_failed (job, G_IO_ERROR, G_IO_ERROR_FAILED,
5974 _("Invalid reply received"));
5975 else
5976 {
5977 handle->raw_handle = read_data_buffer (reply);
5978 push_enqueue_request (handle);
5979 }
5980
5981 sftp_push_handle_free (handle);
5982 }
5983
5984 static void
push_create_temp(SftpPushHandle * handle)5985 push_create_temp (SftpPushHandle *handle)
5986 {
5987 GDataOutputStream *command;
5988 char *dirname;
5989 char basename[] = ".giosaveXXXXXX";
5990
5991 /* Write to a temp file and then rename to replace. */
5992
5993 handle->temp_count++;
5994
5995 if (handle->temp_count == 100)
5996 {
5997 g_vfs_job_failed (handle->job, G_IO_ERROR, G_IO_ERROR_FAILED,
5998 _("Unable to create temporary file"));
5999 sftp_push_handle_free (handle);
6000 return;
6001 }
6002
6003 g_free (handle->tempname);
6004 dirname = g_path_get_dirname (handle->op_job->destination);
6005 gvfs_randomize_string (basename + 8, 6);
6006 handle->tempname = g_build_filename (dirname, basename, NULL);
6007 g_free (dirname);
6008
6009 command = new_command_stream (handle->backend, SSH_FXP_OPEN);
6010 put_string (command, handle->tempname);
6011 g_data_output_stream_put_uint32 (command, SSH_FXF_WRITE|SSH_FXF_CREAT|SSH_FXF_EXCL, NULL, NULL);
6012 g_data_output_stream_put_uint32 (command, 0, NULL, NULL);
6013 queue_command_stream_and_free (&handle->backend->data_connection, command,
6014 push_create_temp_reply,
6015 handle->job, handle);
6016 }
6017
6018 static void
push_open_stat_reply(GVfsBackendSftp * backend,int reply_type,GDataInputStream * reply,guint32 len,GVfsJob * job,gpointer user_data)6019 push_open_stat_reply (GVfsBackendSftp *backend,
6020 int reply_type,
6021 GDataInputStream *reply,
6022 guint32 len,
6023 GVfsJob *job,
6024 gpointer user_data)
6025 {
6026 SftpPushHandle *handle = user_data;
6027
6028 if (reply_type == SSH_FXP_ATTRS)
6029 {
6030 GFileInfo *info;
6031 GFileType type;
6032
6033 info = g_file_info_new ();
6034 parse_attributes (backend, info, NULL, reply, NULL);
6035 type = g_file_info_get_file_type (info);
6036 g_object_unref (info);
6037
6038 if (type == G_FILE_TYPE_DIRECTORY)
6039 {
6040 /* We cannot overwrite a directory. */
6041 if (handle->op_job->flags & G_FILE_COPY_OVERWRITE)
6042 g_vfs_job_failed (job, G_IO_ERROR, G_IO_ERROR_IS_DIRECTORY,
6043 _("File is directory"));
6044
6045 sftp_push_handle_free (handle);
6046 return;
6047 }
6048
6049 push_create_temp (handle);
6050 }
6051 else
6052 {
6053 result_from_status (job, reply, -1, -1);
6054 sftp_push_handle_free (handle);
6055 }
6056 }
6057
6058 static void
push_open_reply(GVfsBackendSftp * backend,int reply_type,GDataInputStream * reply,guint32 len,GVfsJob * job,gpointer user_data)6059 push_open_reply (GVfsBackendSftp *backend,
6060 int reply_type,
6061 GDataInputStream *reply,
6062 guint32 len,
6063 GVfsJob *job,
6064 gpointer user_data)
6065 {
6066 SftpPushHandle *handle = user_data;
6067
6068 if (reply_type == SSH_FXP_STATUS)
6069 {
6070 guint32 code = read_status_code (reply);
6071 if (code == SSH_FX_NO_SUCH_FILE)
6072 not_dir_or_not_exist_error (backend, job, handle->op_job->destination);
6073 else if (code == SSH_FX_FAILURE)
6074 {
6075 if (handle->op_job->flags & G_FILE_COPY_OVERWRITE)
6076 {
6077 /* The destination probably exists. Let's see if we can overwrite
6078 * it. */
6079 GDataOutputStream *command = new_command_stream (backend, SSH_FXP_LSTAT);
6080 put_string (command, handle->op_job->destination);
6081 queue_command_stream_and_free (&backend->command_connection, command,
6082 push_open_stat_reply,
6083 job, handle);
6084 return;
6085 }
6086 else
6087 result_from_status_code (job, code, G_IO_ERROR_EXISTS, -1);
6088 }
6089 else
6090 g_vfs_job_failed (job, G_IO_ERROR, G_IO_ERROR_FAILED,
6091 _("Invalid reply received"));
6092 }
6093 else if (reply_type != SSH_FXP_HANDLE)
6094 g_vfs_job_failed (job, G_IO_ERROR, G_IO_ERROR_FAILED,
6095 _("Invalid reply received"));
6096 else
6097 {
6098 handle->raw_handle = read_data_buffer (reply);
6099 push_enqueue_request (handle);
6100 }
6101
6102 sftp_push_handle_free (handle);
6103 }
6104
6105 static void
push_source_fstat_cb(GObject * source,GAsyncResult * res,gpointer user_data)6106 push_source_fstat_cb (GObject *source, GAsyncResult *res, gpointer user_data)
6107 {
6108 GFileInputStream *fin = G_FILE_INPUT_STREAM (source);
6109 SftpPushHandle *handle = user_data;
6110 GError *error = NULL;
6111 GFileInfo *info;
6112 GDataOutputStream *command;
6113
6114 info = g_file_input_stream_query_info_finish (fin, res, &error);
6115 if (info)
6116 {
6117 handle->permissions = g_file_info_get_attribute_uint32 (info, G_FILE_ATTRIBUTE_UNIX_MODE) & 0777;
6118 handle->size = g_file_info_get_size (info);
6119 handle->mtime = g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_TIME_MODIFIED);
6120 handle->atime = g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_TIME_ACCESS);
6121
6122 command = new_command_stream (handle->backend, SSH_FXP_OPEN);
6123 put_string (command, handle->op_job->destination);
6124 g_data_output_stream_put_uint32 (command, SSH_FXF_WRITE|SSH_FXF_CREAT|SSH_FXF_EXCL, NULL, NULL);
6125 g_data_output_stream_put_uint32 (command, 0, NULL, NULL);
6126 queue_command_stream_and_free (&handle->backend->data_connection, command,
6127 push_open_reply,
6128 handle->job, handle);
6129 }
6130 else
6131 {
6132 g_vfs_job_failed_from_error (handle->job, error);
6133 g_error_free (error);
6134 sftp_push_handle_free (handle);
6135 }
6136 }
6137
6138 static void
push_source_open_cb(GObject * source,GAsyncResult * res,gpointer user_data)6139 push_source_open_cb (GObject *source, GAsyncResult *res, gpointer user_data)
6140 {
6141 GFile *source_file = G_FILE (source);
6142 SftpPushHandle *handle = user_data;
6143 GError *error = NULL;
6144 GFileInputStream *fin;
6145
6146 fin = g_file_read_finish (source_file, res, &error);
6147 if (fin)
6148 {
6149 handle->in = G_INPUT_STREAM (fin);
6150
6151 g_file_input_stream_query_info_async (fin,
6152 G_FILE_ATTRIBUTE_STANDARD_SIZE ","
6153 G_FILE_ATTRIBUTE_UNIX_MODE ","
6154 G_FILE_ATTRIBUTE_TIME_MODIFIED ","
6155 G_FILE_ATTRIBUTE_TIME_ACCESS,
6156 0, NULL,
6157 push_source_fstat_cb, handle);
6158 }
6159 else
6160 {
6161 if (error->domain == G_IO_ERROR && error->code == G_IO_ERROR_IS_DIRECTORY)
6162 {
6163 /* Fall back to default implementation to improve the error message */
6164 g_vfs_job_failed (handle->job, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
6165 _("Operation not supported"));
6166 }
6167 else
6168 g_vfs_job_failed_from_error (handle->job, error);
6169
6170 g_error_free (error);
6171 sftp_push_handle_free (handle);
6172 }
6173 }
6174
6175 static void
push_source_lstat_cb(GObject * source,GAsyncResult * res,gpointer user_data)6176 push_source_lstat_cb (GObject *source, GAsyncResult *res, gpointer user_data)
6177 {
6178 GFile *source_file = G_FILE (source);
6179 SftpPushHandle *handle = user_data;
6180 GError *error = NULL;
6181 GFileInfo *info;
6182
6183 info = g_file_query_info_finish (source_file, res, &error);
6184 if (!info)
6185 {
6186 g_vfs_job_failed_from_error (handle->job, error);
6187 g_error_free (error);
6188 sftp_push_handle_free (handle);
6189 return;
6190 }
6191
6192 if ((handle->op_job->flags & G_FILE_COPY_NOFOLLOW_SYMLINKS) &&
6193 g_file_info_get_file_type (info) == G_FILE_TYPE_SYMBOLIC_LINK)
6194 {
6195 /* Fall back to default implementation to copy symlink */
6196 g_vfs_job_failed (handle->job, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
6197 _("Operation not supported"));
6198 sftp_push_handle_free (handle);
6199 return;
6200 }
6201
6202 g_file_read_async (source_file, 0, NULL, push_source_open_cb, handle);
6203 }
6204
6205 static gboolean
try_push(GVfsBackend * backend,GVfsJobPush * op_job,const char * destination,const char * local_path,GFileCopyFlags flags,gboolean remove_source,GFileProgressCallback progress_callback,gpointer progress_callback_data)6206 try_push (GVfsBackend *backend,
6207 GVfsJobPush *op_job,
6208 const char *destination,
6209 const char *local_path,
6210 GFileCopyFlags flags,
6211 gboolean remove_source,
6212 GFileProgressCallback progress_callback,
6213 gpointer progress_callback_data)
6214 {
6215 GVfsBackendSftp *op_backend = G_VFS_BACKEND_SFTP (backend);
6216 GFile *source;
6217 SftpPushHandle *handle;
6218
6219 if (remove_source && (flags & G_FILE_COPY_NO_FALLBACK_FOR_MOVE))
6220 {
6221 g_vfs_job_failed (G_VFS_JOB (op_job),
6222 G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
6223 _("Operation not supported"));
6224 return TRUE;
6225 }
6226
6227 if (!connection_is_usable (&op_backend->data_connection))
6228 {
6229 g_vfs_job_failed (G_VFS_JOB (op_job),
6230 G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
6231 _("Operation not supported"));
6232 return TRUE;
6233 }
6234
6235 handle = g_slice_new0 (SftpPushHandle);
6236 handle->backend = g_object_ref (op_backend);
6237 handle->job = g_object_ref (G_VFS_JOB (op_job));
6238 handle->op_job = op_job;
6239
6240 source = g_file_new_for_path (local_path);
6241 g_file_query_info_async (source,
6242 G_FILE_ATTRIBUTE_STANDARD_TYPE,
6243 G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
6244 0, NULL,
6245 push_source_lstat_cb, handle);
6246 g_object_unref (source);
6247
6248 return TRUE;
6249 }
6250
6251 /* The pull sliding window mechanism is based on the one from the OpenSSH sftp
6252 * client. It is complicated because requests can be returned out of order. */
6253
6254 #define PULL_MAX_REQUESTS 64 /* Never have more than this many requests outstanding */
6255 #define PULL_SIZE_INCOMPLETE -1 /* Indicates an incomplete fstat() request */
6256 #define PULL_SIZE_INVALID -2 /* Indicates that no fstat() request is in progress */
6257
6258 typedef struct {
6259 /* initial job information */
6260 GVfsBackendSftp *backend;
6261 GVfsJob *job;
6262 GVfsJobPull *op_job;
6263 GFile *dest;
6264
6265 /* Open files */
6266 DataBuffer *raw_handle;
6267 GOutputStream *output;
6268
6269 /* fstat information */
6270 goffset size;
6271 guint32 mode;
6272 guint64 mtime;
6273 guint64 atime;
6274
6275 /* state */
6276 goffset offset;
6277 goffset n_written;
6278 int num_req; /* Number of outstanding read requests */
6279 int max_req; /* Current maximum number of outstanding read requests */
6280 GList *queued_writes;
6281 } SftpPullHandle;
6282
6283 typedef struct {
6284 SftpPullHandle *handle;
6285 guint32 request_len; /* number of bytes requested */
6286 guint64 request_offset; /* offset of requested bytes */
6287 gssize response_len; /* number of bytes returned */
6288 gssize write_offset; /* offset in buffer of bytes written so far */
6289 char *buffer;
6290 } PullRequest;
6291
6292 static void
6293 pull_enqueue_next_request (SftpPullHandle *handle);
6294
6295 static void
6296 pull_enqueue_request (SftpPullHandle *handle, guint64 offset, guint32 len);
6297
6298 static void
6299 pull_try_start_write (SftpPullHandle *handle);
6300
6301 static void
pull_request_free(PullRequest * request)6302 pull_request_free (PullRequest *request)
6303 {
6304 if (request->buffer)
6305 g_slice_free1 (request->response_len, request->buffer);
6306 g_slice_free (PullRequest, request);
6307 }
6308
6309 static void
sftp_pull_handle_free(SftpPullHandle * handle)6310 sftp_pull_handle_free (SftpPullHandle *handle)
6311 {
6312 if (handle->size != PULL_SIZE_INCOMPLETE && /* fstat complete */
6313 (!handle->output || !g_output_stream_has_pending (handle->output)) && /* no writes outstanding */
6314 handle->num_req == 0) /* no reads oustanding */
6315 {
6316 if (handle->raw_handle)
6317 {
6318 GDataOutputStream *command = new_command_stream (handle->backend, SSH_FXP_CLOSE);
6319 put_data_buffer (command, handle->raw_handle);
6320 queue_command_stream_and_free (&handle->backend->data_connection, command,
6321 NULL,
6322 handle->job, NULL);
6323 data_buffer_free (handle->raw_handle);
6324 }
6325 g_clear_object (&handle->output);
6326 g_object_unref(handle->backend);
6327 g_object_unref(handle->op_job);
6328 g_object_unref(handle->dest);
6329 g_list_free_full (handle->queued_writes, (GDestroyNotify)pull_request_free);
6330 g_slice_free (SftpPullHandle, handle);
6331 }
6332 }
6333
6334 static void
pull_remove_source_reply(GVfsBackendSftp * backend,int reply_type,GDataInputStream * reply,guint32 len,GVfsJob * job,gpointer user_data)6335 pull_remove_source_reply (GVfsBackendSftp *backend,
6336 int reply_type,
6337 GDataInputStream *reply,
6338 guint32 len,
6339 GVfsJob *job,
6340 gpointer user_data)
6341 {
6342 if (reply_type == SSH_FXP_STATUS)
6343 result_from_status (job, reply, -1, -1);
6344 else
6345 g_vfs_job_failed (job, G_IO_ERROR, G_IO_ERROR_FAILED,
6346 _("Invalid reply received"));
6347 }
6348
6349 static void
pull_set_perms_cb(GObject * source,GAsyncResult * res,gpointer user_data)6350 pull_set_perms_cb (GObject *source, GAsyncResult *res, gpointer user_data)
6351 {
6352 SftpPullHandle *handle = user_data;
6353
6354 if (handle->op_job->remove_source)
6355 {
6356 GDataOutputStream *command = new_command_stream (handle->backend, SSH_FXP_REMOVE);
6357 put_string (command, handle->op_job->source);
6358 queue_command_stream_and_free (&handle->backend->command_connection,
6359 command,
6360 pull_remove_source_reply,
6361 handle->job,
6362 NULL);
6363 }
6364 else
6365 g_vfs_job_succeeded (handle->job);
6366
6367 sftp_pull_handle_free (handle);
6368 }
6369
6370 static void
pull_close_cb(GObject * source,GAsyncResult * res,gpointer user_data)6371 pull_close_cb (GObject *source, GAsyncResult *res, gpointer user_data)
6372 {
6373 SftpPullHandle *handle = user_data;
6374 GError *error = NULL;
6375
6376 if (g_output_stream_close_finish(handle->output, res, &error))
6377 {
6378 g_vfs_job_progress_callback (handle->n_written, handle->n_written, handle->job);
6379
6380 if (handle->size >= 0)
6381 {
6382 GFileInfo *info = g_file_info_new ();
6383 if (!(handle->op_job->flags & G_FILE_COPY_TARGET_DEFAULT_PERMS))
6384 g_file_info_set_attribute_uint32 (info,
6385 G_FILE_ATTRIBUTE_UNIX_MODE,
6386 handle->mode);
6387 g_file_info_set_attribute_uint64 (info,
6388 G_FILE_ATTRIBUTE_TIME_MODIFIED,
6389 handle->mtime);
6390 /* Atime is COPY_WHEN_MOVED, but not COPY_WITH_FILE. */
6391 if (handle->op_job->remove_source ||
6392 (handle->op_job->flags & G_FILE_COPY_ALL_METADATA))
6393 g_file_info_set_attribute_uint64 (info,
6394 G_FILE_ATTRIBUTE_TIME_ACCESS,
6395 handle->atime);
6396 g_file_set_attributes_async (handle->dest,
6397 info,
6398 G_FILE_QUERY_INFO_NONE,
6399 G_PRIORITY_DEFAULT,
6400 NULL,
6401 pull_set_perms_cb, handle);
6402 g_object_unref (info);
6403 return;
6404 }
6405
6406 if (handle->op_job->remove_source)
6407 {
6408 GDataOutputStream *command = new_command_stream (handle->backend, SSH_FXP_REMOVE);
6409 put_string (command, handle->op_job->source);
6410 queue_command_stream_and_free (&handle->backend->command_connection,
6411 command,
6412 pull_remove_source_reply,
6413 handle->job,
6414 NULL);
6415 }
6416 else
6417 g_vfs_job_succeeded (handle->job);
6418 }
6419 else
6420 {
6421 g_vfs_job_failed_from_error (handle->job, error);
6422 g_error_free (error);
6423 }
6424
6425 sftp_pull_handle_free (handle);
6426 }
6427
6428 static void
pull_try_finish(SftpPullHandle * handle)6429 pull_try_finish (SftpPullHandle *handle)
6430 {
6431 if (handle->max_req == 0 && /* received EOF */
6432 handle->size != PULL_SIZE_INCOMPLETE && /* fstat complete */
6433 !g_output_stream_has_pending (handle->output) && /* no writes outstanding */
6434 handle->num_req == 0) /* no reads oustanding */
6435 {
6436 g_output_stream_close_async (handle->output,
6437 G_PRIORITY_DEFAULT,
6438 NULL,
6439 pull_close_cb, handle);
6440 }
6441 }
6442
6443 static void
pull_write_cb(GObject * source,GAsyncResult * res,gpointer user_data)6444 pull_write_cb (GObject *source, GAsyncResult *res, gpointer user_data)
6445 {
6446 PullRequest *request = user_data;
6447 SftpPullHandle *handle = request->handle;
6448 GError *error = NULL;
6449 gssize n_written;
6450
6451 n_written = g_output_stream_write_finish (handle->output, res, &error);
6452 if (n_written == -1)
6453 {
6454 g_vfs_job_failed_from_error (handle->job, error);
6455 g_error_free (error);
6456 pull_request_free (request);
6457 sftp_pull_handle_free (handle);
6458 return;
6459 }
6460
6461 handle->n_written += n_written;
6462 request->write_offset += n_written;
6463
6464 /* If we didn't write everything, do another write */
6465 if (request->write_offset < request->response_len)
6466 {
6467 g_output_stream_write_async (handle->output,
6468 request->buffer + request->write_offset,
6469 request->response_len - request->write_offset,
6470 G_PRIORITY_DEFAULT,
6471 NULL,
6472 pull_write_cb, request);
6473 return;
6474 }
6475
6476 if (handle->size >= 0)
6477 g_vfs_job_progress_callback (handle->n_written, handle->size, handle->job);
6478
6479 pull_try_start_write (handle);
6480
6481 /* If we read short, issue another request for the remaining data. */
6482 if (request->response_len < request->request_len)
6483 pull_enqueue_request (handle,
6484 request->request_offset + request->response_len,
6485 request->request_len - request->response_len);
6486 else if (handle->max_req == 0)
6487 pull_try_finish (handle);
6488 else
6489 {
6490 /* Once we have requested past the estimated EOF, request one at a
6491 * time. Otherwise try increase the number of concurrent requests. */
6492 if (handle->offset > handle->size)
6493 handle->max_req = 1;
6494 else if (handle->max_req < PULL_MAX_REQUESTS)
6495 handle->max_req++;
6496
6497 while (handle->num_req < handle->max_req)
6498 pull_enqueue_next_request (handle);
6499 }
6500
6501 pull_request_free (request);
6502 }
6503
6504 static void
pull_try_start_write(SftpPullHandle * handle)6505 pull_try_start_write (SftpPullHandle *handle)
6506 {
6507 GError *error = NULL;
6508 PullRequest *request;
6509
6510 if (g_output_stream_has_pending (handle->output))
6511 return;
6512
6513 if (!handle->queued_writes)
6514 return;
6515
6516 request = handle->queued_writes->data;
6517 handle->queued_writes = g_list_delete_link (handle->queued_writes, handle->queued_writes);
6518
6519 if (!g_seekable_seek (G_SEEKABLE (handle->output),
6520 request->request_offset, G_SEEK_SET,
6521 NULL, &error))
6522 {
6523 g_vfs_job_failed_from_error (handle->job, error);
6524 g_error_free (error);
6525 pull_request_free (request);
6526 sftp_pull_handle_free (handle);
6527 return;
6528 }
6529
6530 g_output_stream_write_async (handle->output,
6531 request->buffer, request->response_len,
6532 G_PRIORITY_DEFAULT,
6533 NULL,
6534 pull_write_cb, request);
6535 }
6536
6537 static void
pull_read_reply(GVfsBackendSftp * backend,int reply_type,GDataInputStream * reply,guint32 len,GVfsJob * job,gpointer user_data)6538 pull_read_reply (GVfsBackendSftp *backend,
6539 int reply_type,
6540 GDataInputStream *reply,
6541 guint32 len,
6542 GVfsJob *job,
6543 gpointer user_data)
6544 {
6545 PullRequest *request = user_data;
6546 SftpPullHandle *handle = request->handle;
6547
6548 handle->num_req--;
6549
6550 if (check_finished_or_cancelled_job (job))
6551 {
6552 }
6553 else if (reply_type == SSH_FXP_STATUS)
6554 {
6555 guint32 code = read_status_code (reply);
6556 if (code == SSH_FX_EOF)
6557 {
6558 pull_request_free (request);
6559 handle->max_req = 0;
6560 pull_try_finish (handle);
6561 return;
6562 }
6563 else
6564 result_from_status_code (job, code, -1, -1);
6565 }
6566 else if (reply_type != SSH_FXP_DATA)
6567 {
6568 g_vfs_job_failed (job, G_IO_ERROR, G_IO_ERROR_FAILED,
6569 _("Invalid reply received"));
6570 }
6571 else
6572 {
6573 request->response_len = g_data_input_stream_read_uint32 (reply, NULL, NULL);
6574 request->buffer = g_slice_alloc (request->response_len);
6575
6576 if (g_input_stream_read_all (G_INPUT_STREAM (reply),
6577 request->buffer, request->response_len,
6578 NULL, NULL, NULL))
6579 {
6580 handle->queued_writes = g_list_append (handle->queued_writes, request);
6581 pull_try_start_write (handle);
6582 return;
6583 }
6584 else
6585 g_vfs_job_failed (job, G_IO_ERROR, G_IO_ERROR_FAILED,
6586 _("Invalid reply received"));
6587 }
6588
6589 pull_request_free (request);
6590 sftp_pull_handle_free (handle);
6591 }
6592
6593 static void
pull_enqueue_request(SftpPullHandle * handle,guint64 offset,guint32 len)6594 pull_enqueue_request (SftpPullHandle *handle, guint64 offset, guint32 len)
6595 {
6596 PullRequest *request;
6597 GDataOutputStream *command;
6598
6599 request = g_slice_new0 (PullRequest);
6600 request->handle = handle;
6601 request->request_len = len;
6602 request->request_offset = offset;
6603
6604 command = new_command_stream (handle->backend, SSH_FXP_READ);
6605 put_data_buffer (command, handle->raw_handle);
6606 g_data_output_stream_put_uint64 (command, offset, NULL, NULL);
6607 g_data_output_stream_put_uint32 (command, len, NULL, NULL);
6608 queue_command_stream_and_free (&handle->backend->data_connection, command,
6609 pull_read_reply,
6610 handle->job, request);
6611
6612 handle->num_req++;
6613 }
6614
6615 static void
pull_enqueue_next_request(SftpPullHandle * handle)6616 pull_enqueue_next_request (SftpPullHandle *handle)
6617 {
6618 pull_enqueue_request (handle, handle->offset, MAX_BUFFER_SIZE);
6619 handle->offset += MAX_BUFFER_SIZE;
6620 }
6621
6622 static void
pull_fstat_reply(GVfsBackendSftp * backend,int reply_type,GDataInputStream * reply,guint32 len,GVfsJob * job,gpointer user_data)6623 pull_fstat_reply (GVfsBackendSftp *backend,
6624 int reply_type,
6625 GDataInputStream *reply,
6626 guint32 len,
6627 GVfsJob *job,
6628 gpointer user_data)
6629 {
6630 SftpPullHandle *handle = user_data;
6631
6632 if (check_finished_or_cancelled_job (job))
6633 {
6634 handle->size = PULL_SIZE_INVALID;
6635 sftp_pull_handle_free (handle);
6636 return;
6637 }
6638
6639 if (reply_type == SSH_FXP_ATTRS)
6640 {
6641 GFileInfo *info = g_file_info_new ();
6642 parse_attributes (backend, info, NULL, reply, NULL);
6643 handle->size = g_file_info_get_size (info);
6644 handle->mode = g_file_info_get_attribute_uint32 (info,
6645 G_FILE_ATTRIBUTE_UNIX_MODE);
6646 handle->mtime = g_file_info_get_attribute_uint64 (info,
6647 G_FILE_ATTRIBUTE_TIME_MODIFIED);
6648 handle->atime = g_file_info_get_attribute_uint64 (info,
6649 G_FILE_ATTRIBUTE_TIME_ACCESS);
6650 g_object_unref (info);
6651 }
6652 else
6653 handle->size = PULL_SIZE_INVALID;
6654
6655 pull_try_finish (handle);
6656 }
6657
6658 static void
pull_dest_open_cb(GObject * source,GAsyncResult * res,gpointer user_data)6659 pull_dest_open_cb (GObject *source, GAsyncResult *res, gpointer user_data)
6660 {
6661 SftpPullHandle *handle = user_data;
6662 GError *error = NULL;
6663
6664 if (handle->op_job->flags & G_FILE_COPY_OVERWRITE)
6665 handle->output = G_OUTPUT_STREAM (g_file_replace_finish (handle->dest,
6666 res,
6667 &error));
6668 else
6669 handle->output = G_OUTPUT_STREAM (g_file_create_finish (handle->dest,
6670 res,
6671 &error));
6672 if (handle->output)
6673 {
6674 /* Do an fstat() to find out the size and mode of the file. */
6675 GDataOutputStream *command = new_command_stream (handle->backend,
6676 SSH_FXP_FSTAT);
6677 put_data_buffer (command, handle->raw_handle);
6678 queue_command_stream_and_free (&handle->backend->data_connection,
6679 command,
6680 pull_fstat_reply,
6681 handle->job,
6682 handle);
6683 handle->size = PULL_SIZE_INCOMPLETE;
6684
6685 while (handle->num_req < handle->max_req)
6686 pull_enqueue_next_request (handle);
6687 }
6688 else
6689 {
6690 g_vfs_job_failed_from_error (handle->job, error);
6691 g_error_free (error);
6692 sftp_pull_handle_free (handle);
6693 }
6694 }
6695
6696 static void
pull_open_reply(GVfsBackendSftp * backend,MultiReply * replies,int n_replies,GVfsJob * job,gpointer user_data)6697 pull_open_reply (GVfsBackendSftp *backend,
6698 MultiReply *replies,
6699 int n_replies,
6700 GVfsJob *job,
6701 gpointer user_data)
6702 {
6703 SftpPullHandle *handle = user_data;
6704
6705 if (replies[0].type == SSH_FXP_ATTRS)
6706 {
6707 GFileType type;
6708 GFileInfo *info = g_file_info_new ();
6709
6710 parse_attributes (backend, info, NULL, replies[0].data, NULL);
6711 type = g_file_info_get_file_type (info);
6712 g_object_unref (info);
6713
6714 if (type != G_FILE_TYPE_REGULAR)
6715 {
6716 /* Fall back to default implementation to copy non-regular files */
6717 g_vfs_job_failed (job, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
6718 _("Operation not supported"));
6719 }
6720 else if (replies[1].type == SSH_FXP_STATUS)
6721 result_from_status (job, replies[1].data, -1, -1);
6722 else if (replies[1].type != SSH_FXP_HANDLE)
6723 g_vfs_job_failed (job, G_IO_ERROR, G_IO_ERROR_FAILED,
6724 _("Invalid reply received"));
6725 else
6726 {
6727 /* We got a valid file handle. */
6728 handle->raw_handle = read_data_buffer (replies[1].data);
6729
6730 if (handle->op_job->flags & G_FILE_COPY_OVERWRITE)
6731 g_file_replace_async (handle->dest,
6732 NULL,
6733 handle->op_job->flags & G_FILE_COPY_BACKUP ? TRUE : FALSE,
6734 G_FILE_CREATE_REPLACE_DESTINATION,
6735 G_PRIORITY_DEFAULT,
6736 NULL,
6737 pull_dest_open_cb, handle);
6738 else
6739 g_file_create_async (handle->dest,
6740 G_FILE_CREATE_NONE,
6741 G_PRIORITY_DEFAULT,
6742 NULL,
6743 pull_dest_open_cb, handle);
6744 return;
6745 }
6746 }
6747 else if (replies[0].type == SSH_FXP_STATUS)
6748 result_from_status (job, replies[0].data, -1, -1);
6749 else
6750 g_vfs_job_failed (job,
6751 G_IO_ERROR, G_IO_ERROR_FAILED,
6752 "%s", _("Invalid reply received"));
6753
6754 /* If we got a file handle, store it. It will be closed when the
6755 * SftpPushHandle is freed. */
6756 if (replies[1].type == SSH_FXP_HANDLE && !handle->raw_handle)
6757 handle->raw_handle = read_data_buffer (replies[1].data);
6758
6759 sftp_pull_handle_free (handle);
6760 }
6761
6762 static gboolean
try_pull(GVfsBackend * backend,GVfsJobPull * job,const char * source,const char * local_path,GFileCopyFlags flags,gboolean remove_source,GFileProgressCallback progress_callback,gpointer progress_callback_data)6763 try_pull (GVfsBackend *backend,
6764 GVfsJobPull *job,
6765 const char *source,
6766 const char *local_path,
6767 GFileCopyFlags flags,
6768 gboolean remove_source,
6769 GFileProgressCallback progress_callback,
6770 gpointer progress_callback_data)
6771 {
6772 GVfsBackendSftp *op_backend = G_VFS_BACKEND_SFTP (backend);
6773 SftpPullHandle *handle;
6774 Command commands[2];
6775
6776 if (remove_source && (flags & G_FILE_COPY_NO_FALLBACK_FOR_MOVE))
6777 {
6778 g_vfs_job_failed (G_VFS_JOB (job),
6779 G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
6780 _("Operation not supported"));
6781 return TRUE;
6782 }
6783
6784 if (!connection_is_usable (&op_backend->data_connection))
6785 {
6786 g_vfs_job_failed (G_VFS_JOB (job),
6787 G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
6788 _("Operation not supported"));
6789 return TRUE;
6790 }
6791
6792 handle = g_slice_new0 (SftpPullHandle);
6793 handle->backend = g_object_ref (op_backend);
6794 handle->op_job = g_object_ref (job);
6795 handle->job = G_VFS_JOB (job);
6796 handle->dest = g_file_new_for_path (local_path);
6797 handle->size = PULL_SIZE_INVALID;
6798 handle->max_req = 1;
6799
6800 commands[0].connection = &op_backend->command_connection;
6801 commands[0].cmd = new_command_stream (op_backend,
6802 flags & G_FILE_COPY_NOFOLLOW_SYMLINKS ? SSH_FXP_LSTAT : SSH_FXP_STAT);
6803 put_string (commands[0].cmd, source);
6804
6805 commands[1].connection = &op_backend->data_connection;
6806 commands[1].cmd = new_command_stream (op_backend, SSH_FXP_OPEN);
6807 put_string (commands[1].cmd, source);
6808 g_data_output_stream_put_uint32 (commands[1].cmd, SSH_FXF_READ, NULL, NULL);
6809 g_data_output_stream_put_uint32 (commands[1].cmd, 0, NULL, NULL);
6810
6811 queue_command_streams_and_free (commands, 2,
6812 pull_open_reply,
6813 G_VFS_JOB(job),
6814 handle);
6815
6816 return TRUE;
6817 }
6818
6819 static void
g_vfs_backend_sftp_class_init(GVfsBackendSftpClass * klass)6820 g_vfs_backend_sftp_class_init (GVfsBackendSftpClass *klass)
6821 {
6822 GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
6823 GVfsBackendClass *backend_class = G_VFS_BACKEND_CLASS (klass);
6824
6825 id_q = g_quark_from_static_string ("command-id");
6826
6827 gobject_class->finalize = g_vfs_backend_sftp_finalize;
6828
6829 backend_class->mount = real_do_mount;
6830 backend_class->try_mount = try_mount;
6831 backend_class->try_unmount = try_unmount;
6832 backend_class->try_open_for_read = try_open_for_read;
6833 backend_class->try_read = try_read;
6834 backend_class->try_seek_on_read = try_seek_on_read;
6835 backend_class->try_close_read = try_close_read;
6836 backend_class->try_close_write = try_close_write;
6837 backend_class->try_query_info = try_query_info;
6838 backend_class->try_query_fs_info = try_query_fs_info;
6839 backend_class->try_query_info_on_read = (gpointer) try_query_info_fstat;
6840 backend_class->try_query_info_on_write = (gpointer) try_query_info_fstat;
6841 backend_class->try_enumerate = try_enumerate;
6842 backend_class->try_create = try_create;
6843 backend_class->try_append_to = try_append_to;
6844 backend_class->try_replace = try_replace;
6845 backend_class->try_write = try_write;
6846 backend_class->try_seek_on_write = try_seek_on_write;
6847 backend_class->try_truncate = try_truncate;
6848 backend_class->try_move = try_move;
6849 backend_class->try_make_symlink = try_make_symlink;
6850 backend_class->try_make_directory = try_make_directory;
6851 backend_class->try_delete = try_delete;
6852 backend_class->try_set_display_name = try_set_display_name;
6853 backend_class->try_query_settable_attributes = try_query_settable_attributes;
6854 backend_class->try_set_attribute = try_set_attribute;
6855 backend_class->try_push = try_push;
6856 backend_class->try_pull = try_pull;
6857 }
6858