diff options
author | Benjamin Otte <otte@gnome.org> | 2009-06-02 13:19:51 +0200 |
---|---|---|
committer | Benjamin Otte <otte@gnome.org> | 2009-06-11 10:05:40 +0200 |
commit | e62142b001e218d268a85f2f90993cdf42148fae (patch) | |
tree | 4948320f6778cb04118a1c6541291eac338fed73 | |
parent | 2f5c4dcfd579f9b9cceb8eb08b71d4318e9c03b6 (diff) | |
download | gvfs-e62142b001e218d268a85f2f90993cdf42148fae.tar.gz |
[FTP] introduce GVfsFtpTask
split out the old FtpConnection struct into a separate GVfsFtpTask
structure that acts as a one-stop solution to vfuncs. It keeps track of
all important structures:
- the backend
- the current job
- the potential connection to the server
- the error state
during the lifetime of a backend vfunc and supplies convenience
functions to ease implementing these vfuncs. The API of gvfsftptask.h is
documented.
-rw-r--r-- | daemon/Makefile.am | 1 | ||||
-rw-r--r-- | daemon/gvfsbackendftp.c | 1611 | ||||
-rw-r--r-- | daemon/gvfsbackendftp.h | 70 | ||||
-rw-r--r-- | daemon/gvfsftpconnection.c | 27 | ||||
-rw-r--r-- | daemon/gvfsftpconnection.h | 1 | ||||
-rw-r--r-- | daemon/gvfsftptask.c | 888 | ||||
-rw-r--r-- | daemon/gvfsftptask.h | 99 |
7 files changed, 1509 insertions, 1188 deletions
diff --git a/daemon/Makefile.am b/daemon/Makefile.am index af8d663b..c9e17ae7 100644 --- a/daemon/Makefile.am +++ b/daemon/Makefile.am @@ -232,6 +232,7 @@ gvfsd_obexftp_LDADD = $(OBEXFTP_LIBS) $(XML_LIBS) $(HAL_LIBS) $(libraries) gvfsd_ftp_SOURCES = \ gvfsftpconnection.c gvfsftpconnection.h \ gvfsftpfile.c gvfsftpfile.h \ + gvfsftptask.c gvfsftptask.h \ gvfsbackendftp.c gvfsbackendftp.h \ ParseFTPList.c ParseFTPList.h \ daemon-main.c daemon-main.h \ diff --git a/daemon/gvfsbackendftp.c b/daemon/gvfsbackendftp.c index 26462463..03834e44 100644 --- a/daemon/gvfsbackendftp.c +++ b/daemon/gvfsbackendftp.c @@ -51,9 +51,7 @@ #include "ParseFTPList.h" #include "gvfsftpconnection.h" #include "gvfsftpfile.h" - -/* timeout for network connect/send/receive (use 0 for none) */ -#define TIMEOUT_IN_SECONDS 30 +#include "gvfsftptask.h" /* * about filename interpretation in the ftp backend @@ -69,9 +67,6 @@ * paths exactly match those of a TVFS-using FTP server. */ -/* unsinged char is on purpose, so we get warnings when we misuse them */ -typedef struct _FtpConnection FtpConnection; - typedef struct _FtpDirEntry FtpDirEntry; struct _FtpDirEntry { gsize size; @@ -79,13 +74,12 @@ struct _FtpDirEntry { gchar data[1]; }; -typedef struct FtpDirReader FtpDirReader; struct FtpDirReader { - void (* init_data) (FtpConnection * conn, + void (* init_data) (GVfsFtpTask * task, const GVfsFtpFile *dir); - gpointer (* iter_new) (FtpConnection * conn); + gpointer (* iter_new) (GVfsFtpTask * task); GFileInfo * (* iter_process)(gpointer iter, - FtpConnection * conn, + GVfsFtpTask * task, const GVfsFtpFile *dirname, const GVfsFtpFile *must_match_file, const char * line, @@ -93,452 +87,33 @@ struct FtpDirReader { void (* iter_free) (gpointer iter); }; -typedef enum { - FTP_FEATURE_MDTM = (1 << 0), - FTP_FEATURE_SIZE = (1 << 1), - FTP_FEATURE_TVFS = (1 << 2), - FTP_FEATURE_EPSV = (1 << 3), - FTP_FEATURE_UTF8 = (1 << 4), -} FtpFeatures; -#define FTP_FEATURES_DEFAULT (FTP_FEATURE_EPSV) - -typedef enum { - FTP_SYSTEM_UNKNOWN = 0, - FTP_SYSTEM_UNIX, - FTP_SYSTEM_WINDOWS -} FtpSystem; - -typedef enum { - /* Server advertises support for EPSV (or we assume that it supports it), - * but it does fail to do so, we set this flag so we can fall back to - * PASV. */ - FTP_WORKAROUND_BROKEN_EPSV = (1 << 0), - /* Server replies with a wrong address in PASV, we use connection IP - * instead */ - FTP_WORKAROUND_PASV_ADDR = (1 << 1), - /* server does not allow querying features before login, so we try after - * logging in instead. */ - FTP_WORKAROUND_FEAT_AFTER_LOGIN = (1 << 2), -} FtpWorkarounds; - -struct _GVfsBackendFtp -{ - GVfsBackend backend; - - GSocketConnectable * addr; - GSocketClient * connection_factory; - char * user; - gboolean has_initial_user; - char * password; /* password or NULL for anonymous */ - char * host_display_name; - - /* vfuncs */ - const FtpDirReader * dir_ops; - - /* connection collection */ - GQueue * queue; - GMutex * mutex; - GCond * cond; - guint connections; - guint max_connections; - - /* caching results from dir queries */ - GStaticRWLock directory_cache_lock; - GHashTable * directory_cache; -}; - G_DEFINE_TYPE (GVfsBackendFtp, g_vfs_backend_ftp, G_VFS_TYPE_BACKEND) -#define STATUS_GROUP(status) ((status) / 100) - -typedef void (* Ftp550Handler) (FtpConnection *conn, const GVfsFtpFile *file); - -/*** FTP CONNECTION ***/ - -struct _FtpConnection -{ - /* per-job data */ - GError * error; - GVfsJob * job; - - FtpFeatures features; - FtpSystem system; - FtpWorkarounds workarounds; - - GVfsFtpConnection * conn; /* the connection in use */ - char ** read_buffer; -}; - -static void -ftp_connection_free (FtpConnection *conn) -{ - g_assert (conn->job == NULL); - - if (conn->conn) - g_vfs_ftp_connection_free (conn->conn); - g_strfreev (conn->read_buffer); - - g_slice_free (FtpConnection, conn); -} - -#define ftp_connection_in_error(conn) ((conn)->error != NULL) -#define ftp_connection_clear_error(conn) (g_clear_error (&(conn)->error)) +/*** CODE ***/ static gboolean -ftp_connection_pop_job (FtpConnection *conn) +gvfs_backend_ftp_determine_features (GVfsFtpTask *task) { - gboolean result; - GError *error; - GVfsJob *job; - - g_return_val_if_fail (conn->job != NULL, FALSE); - - /* sending a reply is racy after the reply is sent. The connection may be - * reused in a different thread before the reply sending returns. This is - * racy in particular when the connection is used as a read/write handle. - */ - error = conn->error; - conn->error = NULL; - job = conn->job; - conn->job = NULL; - - if (error) - { - g_vfs_job_failed_from_error (job, error); - g_clear_error (&error); - result = FALSE; - } - else - { - g_vfs_job_succeeded (job); - result = TRUE; - } - - return result; -} - -static void -ftp_connection_push_job (FtpConnection *conn, GVfsJob *job) -{ - g_return_if_fail (conn->job == NULL); - - /* FIXME: ref the job? */ - conn->job = job; -} - -/** - * ftp_error_set_from_response: - * @error: pointer to an error to be set or %NULL - * @response: an FTP response code to use as the error message - * - * Sets an error based on an FTP response code. - **/ -static void -ftp_connection_set_error_from_response (FtpConnection *conn, guint response) -{ - const char *msg; - int code; - - /* Please keep this list ordered by response code, - * but group responses with the same message. */ - switch (response) - { - case 332: /* Need account for login. */ - case 532: /* Need account for storing files. */ - /* FIXME: implement a sane way to handle accounts. */ - code = G_IO_ERROR_NOT_SUPPORTED; - msg = _("Accounts are unsupported"); - break; - case 421: /* Service not available, closing control connection. */ - code = G_IO_ERROR_FAILED; - msg = _("Host closed connection"); - break; - case 425: /* Can't open data connection. */ - code = G_IO_ERROR_CLOSED; - msg = _("Cannot open data connection. Maybe your firewall prevents this?"); - break; - case 426: /* Connection closed; transfer aborted. */ - code = G_IO_ERROR_CLOSED; - msg = _("Data connection closed"); - break; - case 450: /* Requested file action not taken. File unavailable (e.g., file busy). */ - case 550: /* Requested action not taken. File unavailable (e.g., file not found, no access). */ - /* FIXME: This is a lot of different errors. So we have to pretend to - * be smart here. */ - code = G_IO_ERROR_FAILED; - msg = _("Operation failed"); - break; - case 451: /* Requested action aborted: local error in processing. */ - code = G_IO_ERROR_FAILED; - msg = _("Operation failed"); - break; - case 452: /* Requested action not taken. Insufficient storage space in system. */ - case 552: - code = G_IO_ERROR_NO_SPACE; - msg = _("No space left on server"); - break; - case 500: /* Syntax error, command unrecognized. */ - case 501: /* Syntax error in parameters or arguments. */ - case 502: /* Command not implemented. */ - case 503: /* Bad sequence of commands. */ - case 504: /* Command not implemented for that parameter. */ - code = G_IO_ERROR_NOT_SUPPORTED; - msg = _("Operation unsupported"); - break; - case 530: /* Not logged in. */ - code = G_IO_ERROR_PERMISSION_DENIED; - msg = _("Permission denied"); - break; - case 551: /* Requested action aborted: page type unknown. */ - code = G_IO_ERROR_FAILED; - msg = _("Page type unknown"); - break; - case 553: /* Requested action not taken. File name not allowed. */ - code = G_IO_ERROR_INVALID_FILENAME; - msg = _("Invalid filename"); - break; - default: - code = G_IO_ERROR_FAILED; - msg = _("Invalid reply"); - break; - } - - DEBUG ("error: %s\n", msg); - g_set_error_literal (&conn->error, G_IO_ERROR, code, msg); -} - -/** - * ResponseFlags: - * RESPONSE_PASS_100: Don't treat 1XX responses, but return them - * RESPONSE_PASS_300: Don't treat 3XX responses, but return them - * RESPONSE_PASS_400: Don't treat 4XX responses, but return them - * RESPONSE_PASS_500: Don't treat 5XX responses, but return them - * RESPONSE_PASS_550: Don't treat 550 responses, but return them - * RESPONSE_FAIL_200: Fail on a 2XX response - */ - -typedef enum { - RESPONSE_PASS_100 = (1 << 0), - RESPONSE_PASS_300 = (1 << 1), - RESPONSE_PASS_400 = (1 << 2), - RESPONSE_PASS_500 = (1 << 3), - RESPONSE_PASS_550 = (1 << 4), - RESPONSE_FAIL_200 = (1 << 5) -} ResponseFlags; - -/** - * ftp_connection_receive: - * @conn: connection to receive from - * @flags: flags for handling the response - * @error: pointer to error message - * - * Reads a command and stores it in @conn->read_buffer. The read buffer will be - * null-terminated and contain @conn->read_bytes bytes. Afterwards, the response - * will be parsed and processed according to @flags. By default, all responses - * but 2xx will cause an error. - * - * Returns: 0 on error, the ftp code otherwise - **/ -static guint -ftp_connection_receive (FtpConnection *conn, - ResponseFlags flags) -{ - guint response; - - g_assert (conn->job != NULL); - - if (ftp_connection_in_error (conn)) - return 0; - - g_strfreev (conn->read_buffer); - conn->read_buffer = NULL; - - response = g_vfs_ftp_connection_receive (conn->conn, - &conn->read_buffer, - conn->job->cancellable, - &conn->error); - - switch (STATUS_GROUP (response)) - { - case 0: - return 0; - case 1: - if (flags & RESPONSE_PASS_100) - break; - ftp_connection_set_error_from_response (conn, response); - return 0; - case 2: - if (flags & RESPONSE_FAIL_200) - { - ftp_connection_set_error_from_response (conn, response); - return 0; - } - break; - case 3: - if (flags & RESPONSE_PASS_300) - break; - ftp_connection_set_error_from_response (conn, response); - return 0; - case 4: - if (flags & RESPONSE_PASS_400) - break; - ftp_connection_set_error_from_response (conn, response); - return 0; - break; - case 5: - if ((flags & RESPONSE_PASS_500) || (response == 550 && (flags & RESPONSE_PASS_550))) - break; - ftp_connection_set_error_from_response (conn, response); - return 0; - default: - g_assert_not_reached (); - break; - } - - return response; -} - -/** - * ftp_connection_send: - * @conn: the connection to send to - * @flags: #ResponseFlags to use - * @error: pointer to take an error - * @format: format string to construct command from - * (without trailing \r\n) - * @...: arguments to format string - * - * Takes a command, waits for an answer and parses it. Without any @flags, FTP - * codes other than 2xx cause an error. The last read ftp command will be put - * into @conn->read_buffer. - * - * Returns: 0 on error or the received FTP code otherwise. - * - **/ -static guint -ftp_connection_sendv (FtpConnection *conn, - ResponseFlags flags, - const char * format, - va_list varargs) -{ - GString *command; - - g_assert (conn->job != NULL); - - if (ftp_connection_in_error (conn)) - return 0; - - command = g_string_new (""); - g_string_append_vprintf (command, format, varargs); -#ifdef PRINT_DEBUG - if (g_str_has_prefix (command->str, "PASS")) - DEBUG ("--> PASS ***\n"); - else - DEBUG ("--> %s\n", command->str); -#endif - g_string_append (command, "\r\n"); - g_vfs_ftp_connection_send (conn->conn, - command->str, - command->len, - conn->job->cancellable, - &conn->error); - - g_string_free (command, TRUE); - - return ftp_connection_receive (conn, flags); -} - -static guint -ftp_connection_send (FtpConnection *conn, - ResponseFlags flags, - const char * format, - ...) G_GNUC_PRINTF (3, 4); -static guint -ftp_connection_send (FtpConnection *conn, - ResponseFlags flags, - const char * format, - ...) -{ - va_list varargs; - guint response; - - va_start (varargs, format); - response = ftp_connection_sendv (conn, - flags, - format, - varargs); - va_end (varargs); - return response; -} - -static void -ftp_connection_check_file (FtpConnection *conn, - const Ftp550Handler *handlers, - const GVfsFtpFile *file) -{ - while (*handlers && !ftp_connection_in_error (conn)) - { - (*handlers) (conn, file); - handlers++; - } -} - -static guint -ftp_connection_send_and_check (FtpConnection *conn, - ResponseFlags flags, - const Ftp550Handler *handlers, - const GVfsFtpFile *file, - const char *format, - ...) G_GNUC_PRINTF (5, 6); -static guint -ftp_connection_send_and_check (FtpConnection *conn, - ResponseFlags flags, - const Ftp550Handler *handlers, - const GVfsFtpFile *file, - const char *format, - ...) -{ - va_list varargs; - guint response; - - /* check that there's no 550 handling used - don't allow bad use of API */ - g_return_val_if_fail ((flags & RESPONSE_PASS_550) == 0, 0); - g_return_val_if_fail (handlers != NULL, 0); - g_return_val_if_fail (file != NULL, 0); - - va_start (varargs, format); - response = ftp_connection_sendv (conn, - flags | RESPONSE_PASS_550, - format, - varargs); - va_end (varargs); - if (response == 550) - { - ftp_connection_check_file (conn, handlers, file); - if (!ftp_connection_in_error (conn)) - ftp_connection_set_error_from_response (conn, response); - response = 0; - } - return response; -} - -static void -ftp_connection_parse_features (FtpConnection *conn) -{ - struct { + const struct { const char * name; /* name of feature */ - FtpFeatures enable; /* flags to enable with this feature */ + GVfsFtpFeature enable; /* flags to enable with this feature */ } features[] = { - { "MDTM", FTP_FEATURE_MDTM }, - { "SIZE", FTP_FEATURE_SIZE }, - { "TVFS", FTP_FEATURE_TVFS }, - { "EPSV", FTP_FEATURE_EPSV }, - { "UTF8", FTP_FEATURE_UTF8 }, + { "MDTM", G_VFS_FTP_FEATURE_MDTM }, + { "SIZE", G_VFS_FTP_FEATURE_SIZE }, + { "TVFS", G_VFS_FTP_FEATURE_TVFS }, + { "EPSV", G_VFS_FTP_FEATURE_EPSV }, + { "UTF8", G_VFS_FTP_FEATURE_UTF8 }, }; guint i, j; + char **reply; + + if (!g_vfs_ftp_task_send_and_check (task, 0, NULL, NULL, &reply, "FEAT")) + return FALSE; - for (i = 1; conn->read_buffer[i]; i++) + task->backend->features = 0; + for (i = 1; reply[i]; i++) { - char *feature = conn->read_buffer[i]; + char *feature = reply[i]; if (feature[0] != ' ') continue; @@ -555,353 +130,84 @@ ftp_connection_parse_features (FtpConnection *conn) if (g_ascii_strcasecmp (feature, features[j].name) == 0) { DEBUG ("feature %s supported\n", features[j].name); - conn->features |= features[j].enable; + task->backend->features |= 1 << features[j].enable; } } } -} - -/* NB: you must free the connection if it's in error returning from here */ -static FtpConnection * -ftp_connection_create (GSocketConnectable *addr, - GVfsJob * job) -{ - FtpConnection *conn; - - conn = g_slice_new0 (FtpConnection); - ftp_connection_push_job (conn, job); - - conn->conn = g_vfs_ftp_connection_new (addr, - conn->job->cancellable, - &conn->error); - - ftp_connection_receive (conn, 0); - return conn; -} - -static guint -ftp_connection_login (FtpConnection *conn, - const char * username, - const char * password) -{ - guint status; - if (ftp_connection_in_error (conn)) - return 0; + g_strfreev (reply); - status = ftp_connection_send (conn, RESPONSE_PASS_300, - "USER %s", username); - - if (STATUS_GROUP (status) == 3) - { - /* rationale for choosing the default password: - * - some ftp servers expect something that looks like an email address - * - we don't want to send the user's name or address, as that would be - * a privacy problem - * - we want to give ftp server administrators a chance to notify us of - * problems with our client. - * - we don't want to drown in spam. - */ - if (password == NULL || password[0] == 0) - password = "gvfsd-ftp-" VERSION "@example.com"; - status = ftp_connection_send (conn, 0, - "PASS %s", password); - } - - return status; + return TRUE; } static void -ftp_connection_parse_system (FtpConnection *conn) +gvfs_backend_ftp_determine_system (GVfsFtpTask *task) { static const struct { - const char *id; - FtpSystem system; + const char * id; + GVfsFtpSystem system; } known_systems[] = { /* NB: the first entry that matches is taken, so order matters */ - { "UNIX ", FTP_SYSTEM_UNIX }, - { "WINDOWS_NT ", FTP_SYSTEM_WINDOWS } + { "UNIX ", G_VFS_FTP_SYSTEM_UNIX }, + { "WINDOWS_NT ", G_VFS_FTP_SYSTEM_WINDOWS } }; guint i; - char *system_name = conn->read_buffer[0] + 4; + char *system_name; + char **reply; + + if (g_vfs_ftp_task_is_in_error (task)) + return; + + if (!g_vfs_ftp_task_send_and_check (task, 0, NULL, NULL, &reply, "SYST")) + { + g_vfs_ftp_task_clear_error (task); + return; + } + system_name = reply[0] + 4; for (i = 0; i < G_N_ELEMENTS (known_systems); i++) { if (g_ascii_strncasecmp (system_name, known_systems[i].id, strlen (known_systems[i].id)) == 0) { - conn->system = known_systems[i].system; - DEBUG ("system is %u\n", conn->system); + task->backend->system = known_systems[i].system; + DEBUG ("system is %u\n", task->backend->system); break; } } -} - -static void -ftp_connection_prepare (FtpConnection *conn) -{ - /* check supported features */ - if (ftp_connection_send (conn, 0, "FEAT") != 0) { - ftp_connection_parse_features (conn); - } else { - ftp_connection_clear_error(conn); - conn->workarounds |= FTP_WORKAROUND_FEAT_AFTER_LOGIN; - conn->features = 0; - } -} - -static gboolean -ftp_connection_use (FtpConnection *conn) -{ - /* only binary transfers please */ - ftp_connection_send (conn, 0, "TYPE I"); - if (ftp_connection_in_error (conn)) - return FALSE; - -#if 0 - /* RFC 2428 suggests to send this to make NAT routers happy */ - /* XXX: Disabled for the following reasons: - * - most ftp clients don't use it - * - lots of broken ftp servers can't see the difference between - * "EPSV" and "EPSV ALL" - * - impossible to dynamically fall back to regular PASV in case - * EPSV doesn't work for some reason. - * If this makes your ftp connection fail, please file a bug and we will - * try to invent a way to make this all work. Until then, we'll just - * ignore the RFC. - */ - if (conn->features & FTP_FEATURE_EPSV) - ftp_connection_send (conn, 0, "EPSV ALL"); - ftp_connection_clear_error(conn); -#endif - - if (conn->workarounds & FTP_WORKAROUND_FEAT_AFTER_LOGIN) { - if (ftp_connection_send (conn, 0, "FEAT") != 0) { - ftp_connection_parse_features (conn); - } else { - ftp_connection_clear_error (conn); - conn->features = FTP_FEATURES_DEFAULT; - } - } - - /* instruct server that we'll give and assume we get utf8 */ - if (conn->features & FTP_FEATURE_UTF8) - { - if (!ftp_connection_send (conn, 0, "OPTS UTF8 ON")) - ftp_connection_clear_error (conn); - } - - if (ftp_connection_send (conn, 0, "SYST")) - ftp_connection_parse_system (conn); - ftp_connection_clear_error (conn); - - return TRUE; -} - -static GSocketAddress * -ftp_connection_create_remote_address (FtpConnection *conn, guint port) -{ - GSocketAddress *old, *new; - - old = g_vfs_ftp_connection_get_address (conn->conn, &conn->error); - if (old == NULL) - return NULL; - g_assert (G_IS_INET_SOCKET_ADDRESS (old)); - new = g_inet_socket_address_new (g_inet_socket_address_get_address (G_INET_SOCKET_ADDRESS (old)), port); - - return new; -} -static gboolean -ftp_connection_ensure_data_connection_epsv (FtpConnection *conn) -{ - const char *s; - guint port; - GSocketAddress *addr; - guint status; - - g_assert (conn->error == NULL); - - if ((conn->features & FTP_FEATURE_EPSV) == 0) - return FALSE; - - if (conn->workarounds & FTP_WORKAROUND_BROKEN_EPSV) - return FALSE; - - status = ftp_connection_send (conn, RESPONSE_PASS_500, "EPSV"); - if (STATUS_GROUP (status) != 2) - return FALSE; - - /* FIXME: parse multiple lines? */ - s = strrchr (conn->read_buffer[0], '('); - if (!s) - return FALSE; - - s += 4; - port = strtoul (s, NULL, 10); - if (port == 0) - return FALSE; - - addr = ftp_connection_create_remote_address (conn, port); - if (addr == NULL) - return FALSE; - - if (!g_vfs_ftp_connection_open_data_connection (conn->conn, - addr, - conn->job->cancellable, - &conn->error)) - { - g_object_unref (addr); - DEBUG ("Successful EPSV response code, but data connection failed. Enabling FTP_WORKAROUND_BROKEN_EPSV.\n"); - conn->workarounds |= FTP_WORKAROUND_BROKEN_EPSV; - ftp_connection_clear_error (conn); - return FALSE; - } - - g_object_unref (addr); - return TRUE; -} - -static gboolean -ftp_connection_ensure_data_connection_pasv (FtpConnection *conn) -{ - guint ip1, ip2, ip3, ip4, port1, port2; - const char *s; - GSocketAddress *addr; - guint status; - - /* only binary transfers please */ - status = ftp_connection_send (conn, 0, "PASV"); - if (status == 0) - return FALSE; - - /* parse response and try to find the address to connect to. - * This code does the same as curl. - */ - for (s = conn->read_buffer[0]; *s; s++) - { - if (sscanf (s, "%u,%u,%u,%u,%u,%u", - &ip1, &ip2, &ip3, &ip4, - &port1, &port2) == 6) - break; - } - if (*s == 0) - { - g_set_error_literal (&conn->error, G_IO_ERROR, G_IO_ERROR_FAILED, - _("Invalid reply")); - return FALSE; - } - - if (!(conn->workarounds & FTP_WORKAROUND_PASV_ADDR)) - { - guint8 ip[4]; - GInetAddress *inet_addr; - - ip[0] = ip1; - ip[1] = ip2; - ip[2] = ip3; - ip[3] = ip4; - inet_addr = g_inet_address_new_from_bytes (ip, G_SOCKET_FAMILY_IPV4); - addr = g_inet_socket_address_new (inet_addr, port1 << 8 | port2); - g_object_unref (inet_addr); - - if (g_vfs_ftp_connection_open_data_connection (conn->conn, - addr, - conn->job->cancellable, - &conn->error)) - { - g_object_unref (addr); - return TRUE; - } - - g_object_unref (addr); - /* set workaround flag (see below), so we don't try this again */ - DEBUG ("Successfull PASV response but data connection failed. Enabling FTP_WORKAROUND_PASV_ADDR.\n"); - conn->workarounds |= FTP_WORKAROUND_PASV_ADDR; - ftp_connection_clear_error (conn); - } - - /* Workaround code: - * Various ftp servers aren;t setup correctly when behind a NAT. They report - * their own IP address (like 10.0.0.4) and not the address in front of the - * NAT. But this is likely the same address that we connected to with our - * command connetion. So if the address given by PASV fails, we fall back - * to the address of the command stream. - */ - addr = ftp_connection_create_remote_address (conn, port1 << 8 | port2); - if (addr == NULL) - return FALSE; - if (!g_vfs_ftp_connection_open_data_connection (conn->conn, - addr, - conn->job->cancellable, - &conn->error)) - { - g_object_unref (addr); - return FALSE; - } - - g_object_unref (addr); - return TRUE; -} - -static gboolean -ftp_connection_ensure_data_connection (FtpConnection *conn) -{ - if (ftp_connection_in_error (conn)) - return FALSE; - - if (ftp_connection_ensure_data_connection_epsv (conn)) - return TRUE; - - if (ftp_connection_in_error (conn)) - return FALSE; - - if (ftp_connection_ensure_data_connection_pasv (conn)) - return TRUE; - - return FALSE; -} - -static void -ftp_connection_close_data_connection (FtpConnection *conn) -{ - g_vfs_ftp_connection_close_data_connection (conn->conn); + g_strfreev (reply); } /*** COMMON FUNCTIONS WITH SPECIAL HANDLING ***/ static gboolean -ftp_connection_cd (FtpConnection *conn, const GVfsFtpFile *file) +g_vfs_ftp_task_cd (GVfsFtpTask *task, const GVfsFtpFile *file) { - guint response = ftp_connection_send (conn, - RESPONSE_PASS_500, + guint response = g_vfs_ftp_task_send (task, + G_VFS_FTP_PASS_550, "CWD %s", g_vfs_ftp_file_get_ftp_path (file)); if (response == 550) { - g_set_error_literal (&conn->error, + g_set_error_literal (&task->error, G_IO_ERROR, G_IO_ERROR_NOT_DIRECTORY, _("The file is not a directory")); response = 0; } - else if (STATUS_GROUP (response) == 5) - { - ftp_connection_set_error_from_response (conn, response); - response = 0; - } return response != 0; } static gboolean -ftp_connection_try_cd (FtpConnection *conn, const GVfsFtpFile *file) +g_vfs_ftp_task_try_cd (GVfsFtpTask *task, const GVfsFtpFile *file) { - if (ftp_connection_in_error (conn)) + if (g_vfs_ftp_task_is_in_error (task)) return FALSE; - if (!ftp_connection_cd (conn, file)) + if (!g_vfs_ftp_task_cd (task, file)) { - ftp_connection_clear_error (conn); + g_vfs_ftp_task_clear_error (task); return FALSE; } @@ -911,25 +217,25 @@ ftp_connection_try_cd (FtpConnection *conn, const GVfsFtpFile *file) /*** default directory reading ***/ static void -dir_default_init_data (FtpConnection *conn, const GVfsFtpFile *dir) +dir_default_init_data (GVfsFtpTask *task, const GVfsFtpFile *dir) { - ftp_connection_cd (conn, dir); - ftp_connection_ensure_data_connection (conn); + g_vfs_ftp_task_cd (task, dir); + g_vfs_ftp_task_open_data_connection (task); - ftp_connection_send (conn, - RESPONSE_PASS_100 | RESPONSE_FAIL_200, - (conn->system == FTP_SYSTEM_UNIX) ? "LIST -a" : "LIST"); + g_vfs_ftp_task_send (task, + G_VFS_FTP_PASS_100 | G_VFS_FTP_FAIL_200, + (task->backend->system == G_VFS_FTP_SYSTEM_UNIX) ? "LIST -a" : "LIST"); } static gpointer -dir_default_iter_new (FtpConnection *conn) +dir_default_iter_new (GVfsFtpTask *task) { return g_slice_new0 (struct list_state); } static GFileInfo * dir_default_iter_process (gpointer iter, - FtpConnection *conn, + GVfsFtpTask * task, const GVfsFtpFile *dir, const GVfsFtpFile *must_match_file, const char *line, @@ -970,10 +276,7 @@ dir_default_iter_process (gpointer iter, } else { - name = NULL; - g_assert_not_reached (); - // FIXME: - //name = (GVfsFtpFile *) t; + name = g_vfs_ftp_file_new_from_ftp (task->backend, t); g_free (t); } if (name == NULL) @@ -1041,7 +344,7 @@ dir_default_iter_process (gpointer iter, type == 'l' ? G_FILE_TYPE_SYMBOLIC_LINK : G_FILE_TYPE_DIRECTORY); - if (conn->system == FTP_SYSTEM_UNIX) + if (task->backend->system == G_VFS_FTP_SYSTEM_UNIX) g_file_info_set_is_hidden (info, result.fe_fnlen > 0 && result.fe_fname[0] == '.'); @@ -1077,126 +380,6 @@ static const FtpDirReader dir_default = { /*** BACKEND ***/ static void -g_vfs_backend_ftp_push_connection (GVfsBackendFtp *ftp, FtpConnection *conn) -{ - /* we allow conn == NULL to ease error cases */ - if (conn == NULL) - return; - - if (conn->job) - ftp_connection_pop_job (conn); - - g_mutex_lock (ftp->mutex); - if (ftp->queue) - { - g_queue_push_tail (ftp->queue, conn); - g_cond_signal (ftp->cond); - } - else - ftp_connection_free (conn); - g_mutex_unlock (ftp->mutex); -} - -static void -do_broadcast (GCancellable *cancellable, GCond *cond) -{ - g_cond_broadcast (cond); -} - -static FtpConnection * -g_vfs_backend_ftp_pop_connection (GVfsBackendFtp *ftp, - GVfsJob * job) -{ - FtpConnection *conn = NULL; - GTimeVal now; - gulong id; - - if (g_cancellable_is_cancelled (job->cancellable)) - return NULL; - - g_mutex_lock (ftp->mutex); - id = g_cancellable_connect (job->cancellable, - G_CALLBACK (do_broadcast), - ftp->cond, NULL); - while (conn == NULL && ftp->queue != NULL) - { - if (g_cancellable_is_cancelled (job->cancellable)) - break; - - conn = g_queue_pop_head (ftp->queue); - if (conn != NULL) - { - /* Figure out if this connection had a timeout sent. If so, skip it. */ - g_mutex_unlock (ftp->mutex); - ftp_connection_push_job (conn, job); - if (ftp_connection_send (conn, 0, "NOOP")) - break; - - ftp_connection_clear_error (conn); - conn->job = NULL; - ftp_connection_free (conn); - conn = NULL; - g_mutex_lock (ftp->mutex); - ftp->connections--; - continue; - } - - if (ftp->connections < ftp->max_connections) - { - /* Save current number of connections here, so we can limit maximum - * connections later. - * This is necessary for threading reasons (connections can be - * opened or closed while we are still in the opening process. */ - guint maybe_max_connections = ftp->connections; - - ftp->connections++; - g_mutex_unlock (ftp->mutex); - conn = ftp_connection_create (ftp->addr, job); - if (G_LIKELY (!ftp_connection_in_error (conn))) - { - ftp_connection_prepare (conn); - ftp_connection_login (conn, ftp->user, ftp->password); - ftp_connection_use (conn); - if (G_LIKELY (!ftp_connection_in_error (conn))) - break; - } - - ftp_connection_clear_error (conn); - /* Don't call ftp_connection_pop_job () here, the job isn't done yet */ - conn->job = NULL; - ftp_connection_free (conn); - conn = NULL; - g_mutex_lock (ftp->mutex); - ftp->connections--; - ftp->max_connections = MIN (ftp->max_connections, maybe_max_connections); - if (ftp->max_connections == 0) - { - DEBUG ("no more connections left, exiting..."); - /* FIXME: shut down properly */ - exit (0); - } - - continue; - } - - g_get_current_time (&now); - g_time_val_add (&now, TIMEOUT_IN_SECONDS * 1000 * 1000); - if (!g_cond_timed_wait (ftp->cond, ftp->mutex, &now)) - { - g_vfs_job_failed (G_VFS_JOB (job), - G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK, - /* defeat string freeze! */ - /* _("Resource temporarily unavailable")); */ - "%s", g_strerror (EAGAIN)); - break; - } - } - g_cancellable_disconnect (job->cancellable, id); - - return conn; -} - -static void g_vfs_backend_ftp_finalize (GObject *object) { GVfsBackendFtp *ftp = G_VFS_BACKEND_FTP (object); @@ -1270,7 +453,7 @@ do_mount (GVfsBackend *backend, gboolean is_automount) { GVfsBackendFtp *ftp = G_VFS_BACKEND_FTP (backend); - FtpConnection *conn; + GVfsFtpTask task = G_VFS_FTP_TASK_INIT (ftp, G_VFS_JOB (job)); char *prompt = NULL; char *username; char *password; @@ -1280,19 +463,24 @@ do_mount (GVfsBackend *backend, GNetworkAddress *addr; guint port; - conn = ftp_connection_create (ftp->addr, - G_VFS_JOB (job)); + task.conn = g_vfs_ftp_connection_new (ftp->addr, task.cancellable, &task.error); /* fail fast here. No need to ask for a password if we know the hostname * doesn't exist or the given host/port doesn't have an ftp server running. */ - if (ftp_connection_in_error (conn)) + if (task.conn == NULL) { - ftp_connection_pop_job (conn); - ftp_connection_free (conn); + g_vfs_ftp_task_done (&task); return; } - ftp_connection_prepare (conn); + /* send pre-login commands */ + g_vfs_ftp_task_receive (&task, 0, NULL); + if (!gvfs_backend_ftp_determine_features (&task)) + { + g_vfs_ftp_task_clear_error (&task); + g_vfs_backend_ftp_use_workaround (ftp, G_VFS_FTP_WORKAROUND_FEAT_AFTER_LOGIN); + ftp->features = 0; + } addr = G_NETWORK_ADDRESS (ftp->addr); port = g_network_address_get_port (addr); @@ -1357,7 +545,7 @@ do_mount (GVfsBackend *backend, &password_save) || aborted) { - g_set_error_literal (&conn->error, G_IO_ERROR, G_IO_ERROR_PERMISSION_DENIED, + g_set_error_literal (&task.error, G_IO_ERROR, G_IO_ERROR_PERMISSION_DENIED, _("Password dialog cancelled")); break; } @@ -1374,7 +562,7 @@ try_login: g_free (ftp->password); if (anonymous) { - if (ftp_connection_login (conn, "anonymous", "") != 0) + if (g_vfs_ftp_task_login (&task, "anonymous", "") != 0) { ftp->user = g_strdup ("anonymous"); ftp->password = g_strdup (""); @@ -1387,41 +575,53 @@ try_login: { ftp->user = username ? g_strdup (username) : g_strdup (""); ftp->password = g_strdup (password); - if (ftp_connection_login (conn, username, password) != 0) + if (g_vfs_ftp_task_login (&task, username, password) != 0) break; } g_free (username); g_free (password); if (break_on_fail || - !g_error_matches (conn->error, G_IO_ERROR, G_IO_ERROR_PERMISSION_DENIED)) + !g_error_matches (task.error, G_IO_ERROR, G_IO_ERROR_PERMISSION_DENIED)) break; - ftp_connection_clear_error (conn); + g_vfs_ftp_task_clear_error (&task); } - ftp_connection_use (conn); + /* send post-login commands */ + if (g_vfs_backend_ftp_uses_workaround (ftp, G_VFS_FTP_WORKAROUND_FEAT_AFTER_LOGIN) && + !g_vfs_ftp_task_is_in_error (&task)) + { + if (!gvfs_backend_ftp_determine_features (&task)) + { + g_vfs_ftp_task_clear_error (&task); + ftp->features = G_VFS_FTP_FEATURES_DEFAULT; + } + } + g_vfs_ftp_task_setup_connection (&task); + gvfs_backend_ftp_determine_system (&task); /* Save the address of the current connection, so that for future connections, * we are sure to connect to the same machine. * The idea here is to avoid using mirrors that have a different state, which * might cause Heisenbugs. */ - if (!ftp_connection_in_error (conn)) + if (!g_vfs_ftp_task_is_in_error (&task)) { - ftp->addr = G_SOCKET_CONNECTABLE (g_vfs_ftp_connection_get_address (conn->conn, &conn->error)); + ftp->addr = G_SOCKET_CONNECTABLE (g_vfs_ftp_connection_get_address (task.conn, &task.error)); if (ftp->addr == NULL) { - DEBUG ("error querying remote address: %s\nUsing original address instead.", conn->error->message); - ftp_connection_clear_error (conn); + DEBUG ("error querying remote address: %s\nUsing original address instead.", task.error->message); + g_vfs_ftp_task_clear_error (&task); ftp->addr = g_object_ref (addr); } } - if (ftp_connection_in_error (conn)) + if (g_vfs_ftp_task_is_in_error (&task)) { - ftp_connection_pop_job (conn); - ftp_connection_free (conn); + g_vfs_ftp_connection_free (task.conn); + task.conn = NULL; + g_vfs_ftp_task_done (&task); g_object_unref (addr); return; } @@ -1470,9 +670,9 @@ try_login: ftp->connections = 1; ftp->max_connections = G_MAXUINT; ftp->queue = g_queue_new (); - g_vfs_backend_ftp_push_connection (ftp, conn); g_object_unref (addr); + g_vfs_ftp_task_done (&task); } static gboolean @@ -1521,13 +721,13 @@ do_unmount (GVfsBackend * backend, GVfsJobUnmount *job) { GVfsBackendFtp *ftp = G_VFS_BACKEND_FTP (backend); - FtpConnection *conn; + GVfsFtpConnection *conn; g_mutex_lock (ftp->mutex); while ((conn = g_queue_pop_head (ftp->queue))) { /* FIXME: properly quit */ - ftp_connection_free (conn); + g_vfs_ftp_connection_free (conn); } g_queue_free (ftp->queue); ftp->queue = NULL; @@ -1537,54 +737,58 @@ do_unmount (GVfsBackend * backend, } static void -error_550_exists (FtpConnection *conn, const GVfsFtpFile *file) +error_550_exists (GVfsFtpTask *task, gpointer file) { /* FIXME: * What we should do here is look at the cache to figure out if the file - * exists, but as cache access is currently only exposed via the backend + * exists, but as cache access is only exposed via the backend * structure (it should be properly abstracted into an opaque thread-safe - * structure and then be available per-connection), we cannot do that. + * structure and then be available per-connection), we could not do that. * So instead, we use the same code we use when trying to find hidden * directories. */ - if (ftp_connection_try_cd (conn, file) || - ftp_connection_send (conn, 0, "SIZE %s", g_vfs_ftp_file_get_ftp_path (file))) + if (g_vfs_ftp_task_try_cd (task, file) || + g_vfs_ftp_task_send (task, 0, "SIZE %s", g_vfs_ftp_file_get_ftp_path (file))) { - g_set_error_literal (&conn->error, + g_set_error_literal (&task->error, G_IO_ERROR, G_IO_ERROR_EXISTS, _("Target file already exists")); } else { - /* clear potential error from ftp_connection_send() in else if line above */ - ftp_connection_clear_error (conn); + /* clear potential error from g_vfs_ftp_task_send() above */ + g_vfs_ftp_task_clear_error (task); } } static void -error_550_is_directory (FtpConnection *conn, const GVfsFtpFile *file) +error_550_is_directory (GVfsFtpTask *task, gpointer file) { - guint response = ftp_connection_send (conn, - RESPONSE_PASS_550, - "CWD %s", g_vfs_ftp_file_get_ftp_path (file)); - - if (STATUS_GROUP (response) == 2) + if (g_vfs_ftp_task_send (task, + G_VFS_FTP_PASS_550, + "CWD %s", g_vfs_ftp_file_get_ftp_path (file))) { - g_set_error_literal (&conn->error, G_IO_ERROR, + g_set_error_literal (&task->error, G_IO_ERROR, G_IO_ERROR_IS_DIRECTORY, _("File is directory")); } + else + { + /* clear potential error from g_vfs_ftp_task_send() above */ + g_vfs_ftp_task_clear_error (task); + } } static void -error_550_parent_not_found (FtpConnection *conn, const GVfsFtpFile *file) +error_550_parent_not_found (GVfsFtpTask *task, gpointer file) { GVfsFtpFile *dir = g_vfs_ftp_file_new_parent (file); - if (!g_vfs_ftp_file_equal (file, dir) && !ftp_connection_try_cd (conn, dir)) + if (!g_vfs_ftp_file_equal (file, dir) && !g_vfs_ftp_task_try_cd (task, dir)) { - g_set_error_literal (&conn->error, G_IO_ERROR, + /* Yes, this is a weird error for a missing parent directory */ + g_set_error_literal (&task->error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, _("No such file or directory")); } @@ -1598,33 +802,31 @@ do_open_for_read (GVfsBackend *backend, const char *filename) { GVfsBackendFtp *ftp = G_VFS_BACKEND_FTP (backend); - FtpConnection *conn; + GVfsFtpTask task = G_VFS_FTP_TASK_INIT (ftp, G_VFS_JOB (job)); GVfsFtpFile *file; - static const Ftp550Handler open_read_handlers[] = { error_550_is_directory, NULL }; - - conn = g_vfs_backend_ftp_pop_connection (ftp, G_VFS_JOB (job)); - if (!conn) - return; - - ftp_connection_ensure_data_connection (conn); + static const GVfsFtpErrorFunc open_read_handlers[] = { error_550_is_directory, NULL }; + g_vfs_ftp_task_open_data_connection (&task); file = g_vfs_ftp_file_new_from_gvfs (ftp, filename); - ftp_connection_send_and_check (conn, - RESPONSE_PASS_100 | RESPONSE_FAIL_200, + + g_vfs_ftp_task_send_and_check (&task, + G_VFS_FTP_PASS_100 | G_VFS_FTP_FAIL_200, &open_read_handlers[0], file, + NULL, "RETR %s", g_vfs_ftp_file_get_ftp_path (file)); g_vfs_ftp_file_free (file); - if (ftp_connection_in_error (conn)) - g_vfs_backend_ftp_push_connection (ftp, conn); - else + if (!g_vfs_ftp_task_is_in_error (&task)) { /* don't push the connection back, it's our handle now */ + GVfsFtpConnection *conn = g_vfs_ftp_task_take_connection (&task); + g_vfs_job_open_for_read_set_handle (job, conn); g_vfs_job_open_for_read_set_can_seek (job, FALSE); - ftp_connection_pop_job (conn); } + + g_vfs_ftp_task_done (&task); } static void @@ -1633,12 +835,14 @@ do_close_read (GVfsBackend * backend, GVfsBackendHandle handle) { GVfsBackendFtp *ftp = G_VFS_BACKEND_FTP (backend); - FtpConnection *conn = handle; + GVfsFtpTask task = G_VFS_FTP_TASK_INIT (ftp, G_VFS_JOB (job)); + GVfsFtpConnection *conn = handle; + + g_vfs_ftp_task_give_connection (&task, conn); + g_vfs_ftp_task_close_data_connection (&task); + g_vfs_ftp_task_receive (&task, 0, NULL); - ftp_connection_push_job (conn, G_VFS_JOB (job)); - ftp_connection_close_data_connection (conn); - ftp_connection_receive (conn, 0); - g_vfs_backend_ftp_push_connection (ftp, conn); + g_vfs_ftp_task_done (&task); } static void @@ -1648,63 +852,60 @@ do_read (GVfsBackend * backend, char * buffer, gsize bytes_requested) { - FtpConnection *conn = handle; + GVfsBackendFtp *ftp = G_VFS_BACKEND_FTP (backend); + GVfsFtpTask task = G_VFS_FTP_TASK_INIT (ftp, G_VFS_JOB (job)); + GVfsFtpConnection *conn = handle; gssize n_bytes; - ftp_connection_push_job (conn, G_VFS_JOB (job)); - - n_bytes = g_vfs_ftp_connection_read_data (conn->conn, + n_bytes = g_vfs_ftp_connection_read_data (conn, buffer, bytes_requested, - conn->job->cancellable, - &conn->error); + task.cancellable, + &task.error); if (n_bytes >= 0) g_vfs_job_read_set_size (job, n_bytes); - ftp_connection_pop_job (conn); + + g_vfs_ftp_task_done (&task); } static void -do_start_write (GVfsBackendFtp *ftp, - FtpConnection *conn, +do_start_write (GVfsFtpTask *task, GFileCreateFlags flags, const char *format, - ...) G_GNUC_PRINTF (4, 5); + ...) G_GNUC_PRINTF (3, 4); static void -do_start_write (GVfsBackendFtp *ftp, - FtpConnection *conn, +do_start_write (GVfsFtpTask *task, GFileCreateFlags flags, const char *format, ...) { va_list varargs; - guint status; /* FIXME: can we honour the flags? */ - ftp_connection_ensure_data_connection (conn); + g_vfs_ftp_task_open_data_connection (task); va_start (varargs, format); - status = ftp_connection_sendv (conn, - RESPONSE_PASS_100 | RESPONSE_FAIL_200, - format, - varargs); + g_vfs_ftp_task_sendv (task, + G_VFS_FTP_PASS_100 | G_VFS_FTP_FAIL_200, + NULL, + format, + varargs); va_end (varargs); - if (ftp_connection_in_error (conn)) - g_vfs_backend_ftp_push_connection (ftp, conn); - else + if (!g_vfs_ftp_task_is_in_error (task)) { /* don't push the connection back, it's our handle now */ - g_vfs_job_open_for_write_set_handle (G_VFS_JOB_OPEN_FOR_WRITE (conn->job), conn); - g_vfs_job_open_for_write_set_can_seek (G_VFS_JOB_OPEN_FOR_WRITE (conn->job), FALSE); - ftp_connection_pop_job (conn); + GVfsFtpConnection *conn = g_vfs_ftp_task_take_connection (task); + g_vfs_job_open_for_write_set_handle (G_VFS_JOB_OPEN_FOR_WRITE (task->job), conn); + g_vfs_job_open_for_write_set_can_seek (G_VFS_JOB_OPEN_FOR_WRITE (task->job), FALSE); } } static void -gvfs_backend_ftp_purge_cache_directory (GVfsBackendFtp *ftp, - const GVfsFtpFile * dir) +gvfs_backend_ftp_purge_cache_directory (GVfsBackendFtp * ftp, + const GVfsFtpFile *dir) { g_static_rw_lock_writer_lock (&ftp->directory_cache_lock); g_hash_table_remove (ftp->directory_cache, dir); @@ -1712,9 +913,8 @@ gvfs_backend_ftp_purge_cache_directory (GVfsBackendFtp *ftp, } static void -gvfs_backend_ftp_purge_cache_of_file (GVfsBackendFtp *ftp, - FtpConnection * conn, - const GVfsFtpFile * file) +gvfs_backend_ftp_purge_cache_of_file (GVfsBackendFtp * ftp, + const GVfsFtpFile *file) { GVfsFtpFile *dir = g_vfs_ftp_file_new_parent (file); @@ -1726,7 +926,7 @@ gvfs_backend_ftp_purge_cache_of_file (GVfsBackendFtp *ftp, /* forward declaration */ static GFileInfo * -create_file_info (GVfsBackendFtp *ftp, FtpConnection *conn, const char *filename, char **symlink); +create_file_info (GVfsFtpTask *task, const char *filename, char **symlink); static void do_create (GVfsBackend *backend, @@ -1735,32 +935,27 @@ do_create (GVfsBackend *backend, GFileCreateFlags flags) { GVfsBackendFtp *ftp = G_VFS_BACKEND_FTP (backend); - FtpConnection *conn; + GVfsFtpTask task = G_VFS_FTP_TASK_INIT (ftp, G_VFS_JOB (job)); GFileInfo *info; GVfsFtpFile *file; - conn = g_vfs_backend_ftp_pop_connection (ftp, G_VFS_JOB (job)); - if (conn == NULL) - return; - - info = create_file_info (ftp, conn, filename, NULL); + info = create_file_info (&task, filename, NULL); if (info) { g_object_unref (info); - g_set_error_literal (&conn->error, + g_set_error_literal (&task.error, G_IO_ERROR, G_IO_ERROR_EXISTS, _("Target file already exists")); - goto error; + g_vfs_ftp_task_done (&task); + return; } file = g_vfs_ftp_file_new_from_gvfs (ftp, filename); - do_start_write (ftp, conn, flags, "STOR %s", g_vfs_ftp_file_get_ftp_path (file)); - gvfs_backend_ftp_purge_cache_of_file (ftp, conn, file); + do_start_write (&task, flags, "STOR %s", g_vfs_ftp_file_get_ftp_path (file)); + gvfs_backend_ftp_purge_cache_of_file (ftp, file); g_vfs_ftp_file_free (file); - return; -error: - g_vfs_backend_ftp_push_connection (ftp, conn); + g_vfs_ftp_task_done (&task); } static void @@ -1770,18 +965,15 @@ do_append (GVfsBackend *backend, GFileCreateFlags flags) { GVfsBackendFtp *ftp = G_VFS_BACKEND_FTP (backend); - FtpConnection *conn; + GVfsFtpTask task = G_VFS_FTP_TASK_INIT (ftp, G_VFS_JOB (job)); GVfsFtpFile *file; - conn = g_vfs_backend_ftp_pop_connection (ftp, G_VFS_JOB (job)); - if (conn == NULL) - return; - file = g_vfs_ftp_file_new_from_gvfs (ftp, filename); - do_start_write (ftp, conn, flags, "APPE %s", g_vfs_ftp_file_get_ftp_path (file)); - gvfs_backend_ftp_purge_cache_of_file (ftp, conn, file); + do_start_write (&task, flags, "APPE %s", g_vfs_ftp_file_get_ftp_path (file)); + gvfs_backend_ftp_purge_cache_of_file (ftp, file); g_vfs_ftp_file_free (file); - return; + + g_vfs_ftp_task_done (&task); } static void @@ -1793,28 +985,26 @@ do_replace (GVfsBackend *backend, GFileCreateFlags flags) { GVfsBackendFtp *ftp = G_VFS_BACKEND_FTP (backend); - FtpConnection *conn; + GVfsFtpTask task = G_VFS_FTP_TASK_INIT (ftp, G_VFS_JOB (job)); GVfsFtpFile *file; if (make_backup) { /* FIXME: implement! */ - g_vfs_job_failed (G_VFS_JOB (job), - G_IO_ERROR, - G_IO_ERROR_CANT_CREATE_BACKUP, - _("backups not supported yet")); + g_set_error_literal (&task.error, + G_IO_ERROR, + G_IO_ERROR_CANT_CREATE_BACKUP, + _("backups not supported yet")); + g_vfs_ftp_task_done (&task); return; } - conn = g_vfs_backend_ftp_pop_connection (ftp, G_VFS_JOB (job)); - if (conn == NULL) - return; - file = g_vfs_ftp_file_new_from_gvfs (ftp, filename); - do_start_write (ftp, conn, flags, "STOR %s", g_vfs_ftp_file_get_ftp_path (file)); - gvfs_backend_ftp_purge_cache_of_file (ftp, conn, file); + do_start_write (&task, flags, "STOR %s", g_vfs_ftp_file_get_ftp_path (file)); + gvfs_backend_ftp_purge_cache_of_file (ftp, file); g_vfs_ftp_file_free (file); - return; + + g_vfs_ftp_task_done (&task); } static void @@ -1823,14 +1013,14 @@ do_close_write (GVfsBackend *backend, GVfsBackendHandle handle) { GVfsBackendFtp *ftp = G_VFS_BACKEND_FTP (backend); - FtpConnection *conn = handle; + GVfsFtpTask task = G_VFS_FTP_TASK_INIT (ftp, G_VFS_JOB (job)); - ftp_connection_push_job (conn, G_VFS_JOB (job)); + g_vfs_ftp_task_give_connection (&task, handle); - ftp_connection_close_data_connection (conn); - ftp_connection_receive (conn, 0); + g_vfs_ftp_task_close_data_connection (&task); + g_vfs_ftp_task_receive (&task, 0, NULL); - g_vfs_backend_ftp_push_connection (ftp, conn); + g_vfs_ftp_task_done (&task); } static void @@ -1840,21 +1030,22 @@ do_write (GVfsBackend *backend, char *buffer, gsize buffer_size) { - FtpConnection *conn = handle; + GVfsBackendFtp *ftp = G_VFS_BACKEND_FTP (backend); + GVfsFtpTask task = G_VFS_FTP_TASK_INIT (ftp, G_VFS_JOB (job)); + GVfsFtpConnection *conn = handle; gssize n_bytes; - ftp_connection_push_job (conn, G_VFS_JOB (job)); - /* FIXME: use write_all here? */ - n_bytes = g_vfs_ftp_connection_write_data (conn->conn, + n_bytes = g_vfs_ftp_connection_write_data (conn, buffer, buffer_size, - G_VFS_JOB (job)->cancellable, - &conn->error); + task.cancellable, + &task.error); if (n_bytes >= 0) g_vfs_job_write_set_written_size (job, n_bytes); - ftp_connection_pop_job (conn); + + g_vfs_ftp_task_done (&task); } static GFileInfo * @@ -1884,12 +1075,12 @@ create_file_info_for_root (GVfsBackendFtp *ftp) } static FtpDirEntry * -do_enumerate_directory (FtpConnection *conn) +do_enumerate_directory (GVfsFtpTask *task) { gssize n_bytes; FtpDirEntry *entry; - if (ftp_connection_in_error (conn)) + if (g_vfs_ftp_task_is_in_error (task)) return NULL; entry = ftp_dir_entry_new (); @@ -1901,16 +1092,16 @@ do_enumerate_directory (FtpConnection *conn) entry = ftp_dir_entry_grow (entry); if (entry == NULL) { - g_set_error_literal (&conn->error, G_IO_ERROR, G_IO_ERROR_FAILED, + g_set_error_literal (&task->error, G_IO_ERROR, G_IO_ERROR_FAILED, _("Out of memory while reading directory contents")); return NULL; } } - n_bytes = g_vfs_ftp_connection_read_data (conn->conn, + n_bytes = g_vfs_ftp_connection_read_data (task->conn, entry->data + entry->length, entry->size - entry->length - 1, - conn->job->cancellable, - &conn->error); + task->cancellable, + &task->error); if (n_bytes < 0) { @@ -1922,9 +1113,9 @@ do_enumerate_directory (FtpConnection *conn) } while (n_bytes > 0); - ftp_connection_close_data_connection (conn); - ftp_connection_receive (conn, 0); - if (ftp_connection_in_error (conn)) + g_vfs_ftp_task_close_data_connection (task); + g_vfs_ftp_task_receive (task, 0, NULL); + if (g_vfs_ftp_task_is_in_error (task)) { ftp_dir_entry_free (entry); return NULL; @@ -1938,11 +1129,11 @@ do_enumerate_directory (FtpConnection *conn) /* IMPORTANT: SUCK ALARM! * locks ftp->directory_cache_lock but only iff it returns !NULL */ static const FtpDirEntry * -enumerate_directory (GVfsBackendFtp *ftp, - FtpConnection * conn, - const GVfsFtpFile * dir, - gboolean use_cache) +enumerate_directory (GVfsFtpTask * task, + const GVfsFtpFile *dir, + gboolean use_cache) { + GVfsBackendFtp *ftp = task->backend; FtpDirEntry *entry; g_static_rw_lock_reader_lock (&ftp->directory_cache_lock); @@ -1957,8 +1148,8 @@ enumerate_directory (GVfsBackendFtp *ftp, if (entry == NULL) { g_static_rw_lock_reader_unlock (&ftp->directory_cache_lock); - ftp->dir_ops->init_data (conn, dir); - entry = do_enumerate_directory (conn); + ftp->dir_ops->init_data (task, dir); + entry = do_enumerate_directory (task); if (entry == NULL) return NULL; g_static_rw_lock_writer_lock (&ftp->directory_cache_lock); @@ -1973,44 +1164,47 @@ enumerate_directory (GVfsBackendFtp *ftp, } static GFileInfo * -create_file_info_from_parent (GVfsBackendFtp *ftp, FtpConnection *conn, - const GVfsFtpFile *dir, const GVfsFtpFile *file, char **symlink) +create_file_info_from_parent (GVfsFtpTask * task, + const GVfsFtpFile *dir, + const GVfsFtpFile *file, + char ** symlink) { GFileInfo *info = NULL; gpointer iter; const FtpDirEntry *entry; const char *sol, *eol; - entry = enumerate_directory (ftp, conn, dir, TRUE); + entry = enumerate_directory (task, dir, TRUE); if (entry == NULL) return NULL; - iter = ftp->dir_ops->iter_new (conn); + iter = task->backend->dir_ops->iter_new (task); for (sol = eol = entry->data; eol; sol = eol + 1) { eol = memchr (sol, '\n', entry->length - (sol - entry->data)); - info = ftp->dir_ops->iter_process (iter, - conn, - dir, - file, - sol, - symlink); + info = task->backend->dir_ops->iter_process (iter, + task, + dir, + file, + sol, + symlink); if (info) break; } - ftp->dir_ops->iter_free (iter); - g_static_rw_lock_reader_unlock (&ftp->directory_cache_lock); + task->backend->dir_ops->iter_free (iter); + g_static_rw_lock_reader_unlock (&task->backend->directory_cache_lock); return info; } static GFileInfo * -create_file_info_from_file (GVfsBackendFtp *ftp, FtpConnection *conn, - const GVfsFtpFile *file, const char *filename, char **symlink) +create_file_info_from_file (GVfsFtpTask *task, const GVfsFtpFile *file, + const char *filename, char **symlink) { GFileInfo *info; + char **reply; - if (ftp_connection_try_cd (conn, file)) + if (g_vfs_ftp_task_try_cd (task, file)) { char *tmp; @@ -2024,7 +1218,7 @@ create_file_info_from_file (GVfsBackendFtp *ftp, FtpConnection *conn, g_file_info_set_is_hidden (info, TRUE); } - else if (ftp_connection_send (conn, 0, "SIZE %s", g_vfs_ftp_file_get_ftp_path (file))) + else if (g_vfs_ftp_task_send_and_check (task, 0, NULL, NULL, &reply, "SIZE %s", g_vfs_ftp_file_get_ftp_path (file))) { char *tmp; @@ -2036,7 +1230,8 @@ create_file_info_from_file (GVfsBackendFtp *ftp, FtpConnection *conn, gvfs_file_info_populate_default (info, filename, G_FILE_TYPE_REGULAR); - g_file_info_set_size (info, g_ascii_strtoull (conn->read_buffer[0] + 4, NULL, 0)); + g_file_info_set_size (info, g_ascii_strtoull (reply[0] + 4, NULL, 0)); + g_strfreev (reply); g_file_info_set_is_hidden (info, TRUE); } @@ -2044,7 +1239,7 @@ create_file_info_from_file (GVfsBackendFtp *ftp, FtpConnection *conn, { info = NULL; /* clear error from ftp_connection_send() in else if line above */ - ftp_connection_clear_error (conn); + g_vfs_ftp_task_clear_error (task); /* note that there might still be a file/directory, we just have * no way to figure this out (in particular on ftp servers that @@ -2058,7 +1253,7 @@ create_file_info_from_file (GVfsBackendFtp *ftp, FtpConnection *conn, /* NB: This gets a file info for the given object, no matter if it's a dir * or a file */ static GFileInfo * -create_file_info (GVfsBackendFtp *ftp, FtpConnection *conn, const char *filename, char **symlink) +create_file_info (GVfsFtpTask *task, const char *filename, char **symlink) { GVfsFtpFile *dir, *file; GFileInfo *info; @@ -2067,14 +1262,14 @@ create_file_info (GVfsBackendFtp *ftp, FtpConnection *conn, const char *filename *symlink = NULL; if (g_str_equal (filename, "/")) - return create_file_info_for_root (ftp); + return create_file_info_for_root (task->backend); - file = g_vfs_ftp_file_new_from_gvfs (ftp, filename); + file = g_vfs_ftp_file_new_from_gvfs (task->backend, filename); dir = g_vfs_ftp_file_new_parent (file); - info = create_file_info_from_parent (ftp, conn, dir, file, symlink); + info = create_file_info_from_parent (task, dir, file, symlink); if (info == NULL) - info = create_file_info_from_file (ftp, conn, file, filename, symlink); + info = create_file_info_from_file (task, file, filename, symlink); g_vfs_ftp_file_free (dir); g_vfs_ftp_file_free (file); @@ -2082,7 +1277,7 @@ create_file_info (GVfsBackendFtp *ftp, FtpConnection *conn, const char *filename } static GFileInfo * -resolve_symlink (GVfsBackendFtp *ftp, FtpConnection *conn, GFileInfo *original, const char *filename) +resolve_symlink (GVfsFtpTask *task, GFileInfo *original, const char *filename) { GFileInfo *info = NULL; char *symlink, *newlink; @@ -2097,7 +1292,7 @@ resolve_symlink (GVfsBackendFtp *ftp, FtpConnection *conn, GFileInfo *original, G_FILE_ATTRIBUTE_STANDARD_SYMLINK_TARGET }; - if (ftp_connection_in_error (conn)) + if (g_vfs_ftp_task_is_in_error (task)) return original; /* How many symlinks should we follow? @@ -2106,8 +1301,7 @@ resolve_symlink (GVfsBackendFtp *ftp, FtpConnection *conn, GFileInfo *original, symlink = g_strdup (filename); for (i = 0; i < 8 && symlink; i++) { - info = create_file_info (ftp, - conn, + info = create_file_info (task, symlink, &newlink); if (!newlink) @@ -2118,10 +1312,10 @@ resolve_symlink (GVfsBackendFtp *ftp, FtpConnection *conn, GFileInfo *original, } g_free (symlink); - if (ftp_connection_in_error (conn)) + if (g_vfs_ftp_task_is_in_error (task)) { g_assert (info == NULL); - ftp_connection_clear_error (conn); + g_vfs_ftp_task_clear_error (task); return original; } if (info == NULL) @@ -2158,30 +1352,24 @@ do_query_info (GVfsBackend *backend, GFileAttributeMatcher *matcher) { GVfsBackendFtp *ftp = G_VFS_BACKEND_FTP (backend); - FtpConnection *conn; + GVfsFtpTask task = G_VFS_FTP_TASK_INIT (ftp, G_VFS_JOB (job)); GFileInfo *real; char *symlink; - conn = g_vfs_backend_ftp_pop_connection (ftp, G_VFS_JOB (job)); - if (conn == NULL) - return; - if (query_flags & G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS) { - real = create_file_info (ftp, - conn, + real = create_file_info (&task, filename, NULL); } else { - real = create_file_info (ftp, - conn, + real = create_file_info (&task, filename, &symlink); if (symlink) { - real = resolve_symlink (ftp, conn, real, symlink); + real = resolve_symlink (&task, real, symlink); g_free (symlink); } } @@ -2191,13 +1379,15 @@ do_query_info (GVfsBackend *backend, g_file_info_copy_into (real, info); g_object_unref (real); } - else if (!ftp_connection_in_error (conn)) - g_set_error_literal (&conn->error, - G_IO_ERROR, - G_IO_ERROR_NOT_FOUND, - _("File doesn't exist")); + else if (!g_vfs_ftp_task_is_in_error (&task)) + { + g_set_error_literal (&task.error, + G_IO_ERROR, + G_IO_ERROR_NOT_FOUND, + _("File doesn't exist")); + } - g_vfs_backend_ftp_push_connection (ftp, conn); + g_vfs_ftp_task_done (&task); } static void @@ -2208,7 +1398,7 @@ do_enumerate (GVfsBackend *backend, GFileQueryInfoFlags query_flags) { GVfsBackendFtp *ftp = G_VFS_BACKEND_FTP (backend); - FtpConnection *conn; + GVfsFtpTask task = G_VFS_FTP_TASK_INIT (ftp, G_VFS_JOB (job)); GVfsFtpFile *dir; gpointer iter; GSList *symlink_targets = NULL; @@ -2218,69 +1408,60 @@ do_enumerate (GVfsBackend *backend, const FtpDirEntry *entry; const char *sol, *eol; - conn = g_vfs_backend_ftp_pop_connection (ftp, G_VFS_JOB (job)); - if (conn == NULL) - return; - /* no need to check for IS_DIR, because the enumeration code will return that * automatically. */ dir = g_vfs_ftp_file_new_from_gvfs (ftp, dirname); - entry = enumerate_directory (ftp, conn, dir, FALSE); - if (ftp_connection_pop_job (conn)) + entry = enumerate_directory (&task, dir, FALSE); + if (entry != NULL) { - ftp_connection_push_job (conn, G_VFS_JOB (job)); - if (entry != NULL) - { - iter = ftp->dir_ops->iter_new (conn); - for (sol = eol = entry->data; eol; sol = eol + 1) + g_vfs_job_succeeded (task.job); + task.job = NULL; + + iter = ftp->dir_ops->iter_new (&task); + for (sol = eol = entry->data; eol; sol = eol + 1) + { + char *symlink = NULL; + + eol = memchr (sol, '\n', entry->length - (sol - entry->data)); + info = ftp->dir_ops->iter_process (iter, + &task, + dir, + NULL, + sol, + query_flags & G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS ? NULL : &symlink); + if (symlink) { - char *symlink = NULL; - - eol = memchr (sol, '\n', entry->length - (sol - entry->data)); - info = ftp->dir_ops->iter_process (iter, - conn, - dir, - NULL, - sol, - query_flags & G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS ? NULL : &symlink); - if (symlink) - { - /* This is necessary due to our locking. - * And we must not unlock here because it might invalidate the list we iterate */ - symlink_targets = g_slist_prepend (symlink_targets, symlink); - symlink_fileinfos = g_slist_prepend (symlink_fileinfos, info); - } - else if (info) - { - g_vfs_job_enumerate_add_info (job, info); - g_object_unref (info); - } - } - ftp->dir_ops->iter_free (iter); - g_static_rw_lock_reader_unlock (&ftp->directory_cache_lock); - for (twalk = symlink_targets, fwalk = symlink_fileinfos; twalk; - twalk = twalk->next, fwalk = fwalk->next) - { - info = resolve_symlink (ftp, conn, fwalk->data, twalk->data); - g_free (twalk->data); - g_vfs_job_enumerate_add_info (job, info); - g_object_unref (info); - } - g_slist_free (symlink_targets); - g_slist_free (symlink_fileinfos); - } - + /* This is necessary due to our locking. + * And we must not unlock here because it might invalidate the list we iterate */ + symlink_targets = g_slist_prepend (symlink_targets, symlink); + symlink_fileinfos = g_slist_prepend (symlink_fileinfos, info); + } + else if (info) + { + g_vfs_job_enumerate_add_info (job, info); + g_object_unref (info); + } + } + ftp->dir_ops->iter_free (iter); + g_static_rw_lock_reader_unlock (&ftp->directory_cache_lock); + for (twalk = symlink_targets, fwalk = symlink_fileinfos; twalk; + twalk = twalk->next, fwalk = fwalk->next) + { + info = resolve_symlink (&task, fwalk->data, twalk->data); + g_free (twalk->data); + g_vfs_job_enumerate_add_info (job, info); + g_object_unref (info); + } + g_slist_free (symlink_targets); + g_slist_free (symlink_fileinfos); + g_vfs_job_enumerate_done (job); - conn->job = NULL; - ftp_connection_clear_error (conn); } - else - g_assert (entry == NULL); - - g_vfs_backend_ftp_push_connection (ftp, conn); + g_vfs_ftp_file_free (dir); + g_vfs_ftp_task_done (&task); } static void @@ -2290,30 +1471,27 @@ do_set_display_name (GVfsBackend *backend, const char *display_name) { GVfsBackendFtp *ftp = G_VFS_BACKEND_FTP (backend); - FtpConnection *conn; + GVfsFtpTask task = G_VFS_FTP_TASK_INIT (ftp, G_VFS_JOB (job)); GVfsFtpFile *original, *dir, *now; - conn = g_vfs_backend_ftp_pop_connection (ftp, G_VFS_JOB (job)); - if (conn == NULL) - return; - original = g_vfs_ftp_file_new_from_gvfs (ftp, filename); dir = g_vfs_ftp_file_new_parent (original); - now = g_vfs_ftp_file_new_child (dir, display_name, &conn->error); - ftp_connection_send (conn, - RESPONSE_PASS_300 | RESPONSE_FAIL_200, + now = g_vfs_ftp_file_new_child (dir, display_name, &task.error); + g_vfs_ftp_task_send (&task, + G_VFS_FTP_PASS_300 | G_VFS_FTP_FAIL_200, "RNFR %s", g_vfs_ftp_file_get_ftp_path (original)); - ftp_connection_send (conn, + g_vfs_ftp_task_send (&task, 0, "RNTO %s", g_vfs_ftp_file_get_ftp_path (now)); /* FIXME: parse result of RNTO here? */ g_vfs_job_set_display_name_set_new_path (job, g_vfs_ftp_file_get_gvfs_path (now)); gvfs_backend_ftp_purge_cache_directory (ftp, dir); - g_vfs_backend_ftp_push_connection (ftp, conn); g_vfs_ftp_file_free (now); g_vfs_ftp_file_free (dir); g_vfs_ftp_file_free (original); + + g_vfs_ftp_task_done (&task); } static void @@ -2322,51 +1500,44 @@ do_delete (GVfsBackend *backend, const char *filename) { GVfsBackendFtp *ftp = G_VFS_BACKEND_FTP (backend); - FtpConnection *conn; + GVfsFtpTask task = G_VFS_FTP_TASK_INIT (ftp, G_VFS_JOB (job)); GVfsFtpFile *file; guint response; - conn = g_vfs_backend_ftp_pop_connection (ftp, G_VFS_JOB (job)); - if (conn == NULL) - return; - /* We try file deletion first. If that fails, we try directory deletion. * The file-first-then-directory order has been decided by coin-toss. */ file = g_vfs_ftp_file_new_from_gvfs (ftp, filename); - response = ftp_connection_send (conn, - RESPONSE_PASS_500, + response = g_vfs_ftp_task_send (&task, + G_VFS_FTP_PASS_500, "DELE %s", g_vfs_ftp_file_get_ftp_path (file)); - if (STATUS_GROUP (response) == 5) + if (G_VFS_FTP_RESPONSE_GROUP (response) == 5) { - response = ftp_connection_send (conn, - RESPONSE_PASS_500, + response = g_vfs_ftp_task_send (&task, + G_VFS_FTP_PASS_550, "RMD %s", g_vfs_ftp_file_get_ftp_path (file)); if (response == 550) { - const FtpDirEntry *entry = enumerate_directory (ftp, conn, file, FALSE); + const FtpDirEntry *entry = enumerate_directory (&task, file, FALSE); if (entry) { g_static_rw_lock_reader_unlock (&ftp->directory_cache_lock); - g_set_error_literal (&conn->error, + g_set_error_literal (&task.error, G_IO_ERROR, G_IO_ERROR_NOT_EMPTY, g_strerror (ENOTEMPTY)); } else { - ftp_connection_clear_error (conn); - ftp_connection_set_error_from_response (conn, response); + g_vfs_ftp_task_clear_error (&task); + g_vfs_ftp_task_set_error_from_response (&task, response); } } - else if (STATUS_GROUP (response) == 5) - { - ftp_connection_set_error_from_response (conn, response); - } } - gvfs_backend_ftp_purge_cache_of_file (ftp, conn, file); + gvfs_backend_ftp_purge_cache_of_file (ftp, file); g_vfs_ftp_file_free (file); - g_vfs_backend_ftp_push_connection (ftp, conn); + + g_vfs_ftp_task_done (&task); } static void @@ -2375,27 +1546,24 @@ do_make_directory (GVfsBackend *backend, const char *filename) { GVfsBackendFtp *ftp = G_VFS_BACKEND_FTP (backend); - FtpConnection *conn; + GVfsFtpTask task = G_VFS_FTP_TASK_INIT (ftp, G_VFS_JOB (job)); GVfsFtpFile *file; - static const Ftp550Handler make_directory_handlers[] = { error_550_exists, error_550_parent_not_found, NULL }; - - conn = g_vfs_backend_ftp_pop_connection (ftp, G_VFS_JOB (job)); - if (conn == NULL) - return; + static const GVfsFtpErrorFunc make_directory_handlers[] = { error_550_exists, error_550_parent_not_found, NULL }; file = g_vfs_ftp_file_new_from_gvfs (ftp, filename); - ftp_connection_send_and_check (conn, + g_vfs_ftp_task_send_and_check (&task, 0, make_directory_handlers, file, + NULL, "MKD %s", g_vfs_ftp_file_get_ftp_path (file)); /* FIXME: Compare created file with name from server result to be sure * it's correct and otherwise fail. */ - gvfs_backend_ftp_purge_cache_of_file (ftp, conn, file); + gvfs_backend_ftp_purge_cache_of_file (ftp, file); g_vfs_ftp_file_free (file); - g_vfs_backend_ftp_push_connection (ftp, conn); + g_vfs_ftp_task_done (&task); } static void @@ -2408,35 +1576,34 @@ do_move (GVfsBackend *backend, gpointer progress_callback_data) { GVfsBackendFtp *ftp = G_VFS_BACKEND_FTP (backend); - FtpConnection *conn; + GVfsFtpTask task = G_VFS_FTP_TASK_INIT (ftp, G_VFS_JOB (job)); GVfsFtpFile *srcfile, *destfile; /* FIXME: what about G_FILE_COPY_NOFOLLOW_SYMLINKS and G_FILE_COPY_ALL_METADATA? */ if (flags & G_FILE_COPY_BACKUP) { - /* FIXME: implement! */ - g_vfs_job_failed (G_VFS_JOB (job), - G_IO_ERROR, - G_IO_ERROR_CANT_CREATE_BACKUP, - _("backups not supported yet")); + /* FIXME: implement? */ + g_set_error_literal (&task.error, + G_IO_ERROR, + G_IO_ERROR_CANT_CREATE_BACKUP, + _("backups not supported yet")); + g_vfs_ftp_task_done (&task); return; } - conn = g_vfs_backend_ftp_pop_connection (ftp, G_VFS_JOB (job)); - if (conn == NULL) - return; - srcfile = g_vfs_ftp_file_new_from_gvfs (ftp, source); destfile = g_vfs_ftp_file_new_from_gvfs (ftp, destination); - if (ftp_connection_try_cd (conn, destfile)) + if (g_vfs_ftp_task_try_cd (&task, destfile)) { char *basename = g_path_get_basename (source); - GVfsFtpFile *real = g_vfs_ftp_file_new_child (destfile, basename, &conn->error); + GVfsFtpFile *real = g_vfs_ftp_file_new_child (destfile, basename, &task.error); g_free (basename); if (real == NULL) - goto out; + { + goto out; + } else { g_vfs_ftp_file_free (destfile); @@ -2446,15 +1613,14 @@ do_move (GVfsBackend *backend, if (!(flags & G_FILE_COPY_OVERWRITE)) { - GFileInfo *info = create_file_info (ftp, - conn, + GFileInfo *info = create_file_info (&task, g_vfs_ftp_file_get_gvfs_path (destfile), NULL); if (info) { g_object_unref (info); - g_set_error_literal (&conn->error, + g_set_error_literal (&task.error, G_IO_ERROR, G_IO_ERROR_EXISTS, _("Target file already exists")); @@ -2462,19 +1628,20 @@ do_move (GVfsBackend *backend, } } - ftp_connection_send (conn, - RESPONSE_PASS_300 | RESPONSE_FAIL_200, + g_vfs_ftp_task_send (&task, + G_VFS_FTP_PASS_300 | G_VFS_FTP_FAIL_200, "RNFR %s", g_vfs_ftp_file_get_ftp_path (srcfile)); - ftp_connection_send (conn, + g_vfs_ftp_task_send (&task, 0, "RNTO %s", g_vfs_ftp_file_get_ftp_path (destfile)); - gvfs_backend_ftp_purge_cache_of_file (ftp, conn, srcfile); - gvfs_backend_ftp_purge_cache_of_file (ftp, conn, destfile); + gvfs_backend_ftp_purge_cache_of_file (ftp, srcfile); + gvfs_backend_ftp_purge_cache_of_file (ftp, destfile); out: g_vfs_ftp_file_free (srcfile); g_vfs_ftp_file_free (destfile); - g_vfs_backend_ftp_push_connection (ftp, conn); + + g_vfs_ftp_task_done (&task); } static void @@ -2503,3 +1670,71 @@ g_vfs_backend_ftp_class_init (GVfsBackendFtpClass *klass) backend_class->make_directory = do_make_directory; backend_class->move = do_move; } + +/*** PUBLIC API ***/ + +/** + * g_vfs_backend_ftp_has_feature: + * @ftp: the backend + * @feature: feature to check + * + * Checks if the FTP server supports a given @feature. Features are determined + * once during the mount phase and are not queried again. + * + * Returns: %TRUE if @feature is supported. + **/ +gboolean +g_vfs_backend_ftp_has_feature (GVfsBackendFtp *ftp, + GVfsFtpFeature feature) +{ + g_return_val_if_fail (G_VFS_IS_BACKEND_FTP (ftp), FALSE); + g_return_val_if_fail (feature < 32, FALSE); + + return (ftp->features & (1 << feature)) != 0; +} + +/** + * g_vfs_backend_ftp_uses_workaround: + * @ftp: the backend + * @workaround: workaround to check + * + * Checks if the given @workaround was enabled previously using + * g_vfs_backend_ftp_use_workaround(). See that function for a discussion + * of the purpose of workarounds. + * + * Returns: %TRUE if the workaround is enabled + **/ +gboolean +g_vfs_backend_ftp_uses_workaround (GVfsBackendFtp * ftp, + GVfsFtpWorkaround workaround) +{ + g_return_val_if_fail (G_VFS_IS_BACKEND_FTP (ftp), FALSE); + g_return_val_if_fail (workaround < 32, FALSE); + + return (g_atomic_int_get (&ftp->workarounds) & (1 << workaround)) != 0; +} + +/** + * g_vfs_backend_ftp_use_workaround: + * @ftp: the backend + * @workaround: workaround to set + * + * Sets the given @workaround to be used on the backend. Workarounds are flags + * set on the backend to ensure a special behavior in the client to work + * around problems with servers. See the existing workarounds for examples. + **/ +void +g_vfs_backend_ftp_use_workaround (GVfsBackendFtp * ftp, + GVfsFtpWorkaround workaround) +{ + int cur, set; + + g_return_if_fail (G_VFS_IS_BACKEND_FTP (ftp)); + g_return_if_fail (workaround < 32); + + set = 1 << workaround; + while (((cur = g_atomic_int_get (&ftp->workarounds)) & set) == 0 && + !g_atomic_int_compare_and_exchange (&ftp->workarounds, cur, cur | set)); + +} + diff --git a/daemon/gvfsbackendftp.h b/daemon/gvfsbackendftp.h index 23538342..e1df8b0b 100644 --- a/daemon/gvfsbackendftp.h +++ b/daemon/gvfsbackendftp.h @@ -28,6 +28,38 @@ G_BEGIN_DECLS +#define G_VFS_FTP_TIMEOUT_IN_SECONDS 30 + +typedef enum { + G_VFS_FTP_FEATURE_MDTM, + G_VFS_FTP_FEATURE_SIZE, + G_VFS_FTP_FEATURE_TVFS, + G_VFS_FTP_FEATURE_EPSV, + G_VFS_FTP_FEATURE_UTF8 +} GVfsFtpFeature; +#define G_VFS_FTP_FEATURES_DEFAULT (1 << G_VFS_FTP_FEATURE_EPSV) + +typedef enum { + G_VFS_FTP_SYSTEM_UNKNOWN = 0, + G_VFS_FTP_SYSTEM_UNIX, + G_VFS_FTP_SYSTEM_WINDOWS +} GVfsFtpSystem; + +typedef enum { + /* Server advertises support for EPSV (or we assume that it supports it), + * but it does fail to do so, we set this flag so we can fall back to + * PASV. */ + G_VFS_FTP_WORKAROUND_BROKEN_EPSV, + /* Server replies with a wrong address in PASV, we use connection IP + * instead */ + G_VFS_FTP_WORKAROUND_PASV_ADDR, + /* server does not allow querying features before login, so we try after + * logging in instead. */ + G_VFS_FTP_WORKAROUND_FEAT_AFTER_LOGIN, +} GVfsFtpWorkaround; + +typedef struct FtpDirReader FtpDirReader; + #define G_VFS_TYPE_BACKEND_FTP (g_vfs_backend_ftp_get_type ()) #define G_VFS_BACKEND_FTP(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), G_VFS_TYPE_BACKEND_FTP, GVfsBackendFtp)) #define G_VFS_BACKEND_FTP_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), G_VFS_TYPE_BACKEND_FTP, GVfsBackendFtpClass)) @@ -38,6 +70,36 @@ G_BEGIN_DECLS typedef struct _GVfsBackendFtp GVfsBackendFtp; typedef struct _GVfsBackendFtpClass GVfsBackendFtpClass; +struct _GVfsBackendFtp +{ + GVfsBackend backend; + + GSocketConnectable * addr; + GSocketClient * connection_factory; + char * user; + gboolean has_initial_user; + char * password; /* password or NULL for anonymous */ + char * host_display_name; + + GVfsFtpSystem system; /* the system from the SYST response */ + int features; /* GVfsFtpFeatures that are supported */ + int workarounds; /* GVfsFtpWorkarounds in use - int because it's atomic */ + + /* vfuncs */ + const FtpDirReader * dir_ops; + + /* connection collection - accessed from gvfsftptask.c */ + GMutex * mutex; /* mutex protecting the following variables */ + GCond * cond; /* cond used to signal tasks waiting on the mutex */ + GQueue * queue; /* queue containing the connections */ + guint connections; /* current number of connections */ + guint max_connections; /* upper server limit for number of connections - dynamically generated */ + + /* caching results from dir queries */ + GStaticRWLock directory_cache_lock; + GHashTable * directory_cache; +}; + struct _GVfsBackendFtpClass { GVfsBackendClass parent_class; @@ -45,6 +107,14 @@ struct _GVfsBackendFtpClass GType g_vfs_backend_ftp_get_type (void) G_GNUC_CONST; +gboolean g_vfs_backend_ftp_has_feature (GVfsBackendFtp * ftp, + GVfsFtpFeature feature); +gboolean g_vfs_backend_ftp_uses_workaround (GVfsBackendFtp * ftp, + GVfsFtpWorkaround workaround); +void g_vfs_backend_ftp_use_workaround (GVfsBackendFtp * ftp, + GVfsFtpWorkaround workaround); + + G_END_DECLS #endif /* __G_VFS_BACKEND_FTP_H__ */ diff --git a/daemon/gvfsftpconnection.c b/daemon/gvfsftpconnection.c index 7a28fb89..485ad68d 100644 --- a/daemon/gvfsftpconnection.c +++ b/daemon/gvfsftpconnection.c @@ -275,3 +275,30 @@ g_vfs_ftp_connection_read_contents (GVfsFtpConnection *conn, g_assert_not_reached (); } +/** + * g_vfs_ftp_connection_is_usable: + * @conn: a connection + * + * Checks if this connection can still be used to send new commands. For + * example, if the connection was closed, this is not possible and this + * function will return %FALSE. + * + * Returns: %TRUE if the connection is still usable + **/ +gboolean +g_vfs_ftp_connection_is_usable (GVfsFtpConnection *conn) +{ + GIOCondition cond; + + g_return_val_if_fail (conn != NULL, FALSE); + + /* FIXME: return FALSE here if a send or receive failed irrecoverably */ + + cond = G_IO_ERR | G_IO_HUP; + cond = g_socket_condition_check (g_socket_connection_get_socket (G_SOCKET_CONNECTION (conn->commands)), cond); + if (cond) + return FALSE; + + return TRUE; +} + diff --git a/daemon/gvfsftpconnection.h b/daemon/gvfsftpconnection.h index 4e9e7ecc..234f4e4d 100644 --- a/daemon/gvfsftpconnection.h +++ b/daemon/gvfsftpconnection.h @@ -53,6 +53,7 @@ guint g_vfs_ftp_connection_receive (GVfsFtpConnection GCancellable * cancellable, GError ** error); +gboolean g_vfs_ftp_connection_is_usable (GVfsFtpConnection * conn); GSocketAddress * g_vfs_ftp_connection_get_address (GVfsFtpConnection * conn, GError ** error); diff --git a/daemon/gvfsftptask.c b/daemon/gvfsftptask.c new file mode 100644 index 00000000..af889fe0 --- /dev/null +++ b/daemon/gvfsftptask.c @@ -0,0 +1,888 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright (C) 2009 Benjamin Otte <otte@gnome.org> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Benjamin Otte <otte@gnome.org> + */ + +#include <config.h> + +#include <stdio.h> /* for sscanf() */ +#include <stdlib.h> /* for exit() */ + +#include <glib/gi18n.h> + +#include "gvfsftptask.h" + +/*** DOCS ***/ + +/** + * GVfsFtpResponseFlags: + * @G_VFS_FTP_PASS_100: Don't treat 1XX responses, but return them + * @G_VFS_FTP_PASS_300: Don't treat 3XX responses, but return them + * @G_VFS_FTP_PASS_400: Don't treat 4XX responses, but return them + * @G_VFS_FTP_PASS_500: Don't treat 5XX responses, but return them + * @G_VFS_FTP_PASS_550: Don't treat 550 responses, but return them + * @G_VFS_FTP_FAIL_200: Fail on a 2XX response + * + * These flags can be passed to gvfs_ftp_task_receive() (and in + * turn gvfs_ftp_task_send()) to influence the behavior of the functions. + */ + +/** + * G_VFS_FTP_G_VFS_FTP_GROUP: + * @response: a valid ftp response + * + * Determines the group the given @response belongs to. The group is the first + * digit of the reply. + * + * Returns: The group the response code belonged to from 1-5 + */ + +/** + * G_VFS_FTP_TASK_INIT: + * @backend: the backend used by this task + * @job: the job that initiated the task or %NULL if none + * + * Initializes a new task structure for the given backend and job. + */ + +/** + * GVfsFtpErrorFunc: + * @task: task to handle + * @data: data argument provided to g_vfs_ftp_task_send_and_check() + * + * Function prototype for error checking functions used by + * g_vfs_ftp_task_send_and_check(). When called, these functions are supposed + * to check a specific error condition and if met, set an error on the passed + * @task. + */ + +/*** CODE ***/ + +gboolean +g_vfs_ftp_task_login (GVfsFtpTask *task, + const char * username, + const char * password) +{ + guint status; + + g_return_val_if_fail (task != NULL, FALSE); + g_return_val_if_fail (username != NULL, FALSE); + g_return_val_if_fail (password != NULL, FALSE); + + if (g_vfs_ftp_task_is_in_error (task)) + return FALSE; + + status = g_vfs_ftp_task_send (task, G_VFS_FTP_PASS_300, + "USER %s", username); + + if (G_VFS_FTP_RESPONSE_GROUP (status) == 3) + { + /* rationale for choosing the default password: + * - some ftp servers expect something that looks like an email address + * - we don't want to send the user's name or address, as that would be + * a privacy problem + * - we want to give ftp server administrators a chance to notify us of + * problems with our client. + * - we don't want to drown in spam. + */ + if (password == NULL || password[0] == 0) + password = "gvfsd-ftp-" VERSION "@example.com"; + status = g_vfs_ftp_task_send (task, 0, + "PASS %s", password); + } + + return status; +} + +/** + * g_vfs_ftp_task_setup_connection: + * @task: the task + * + * Sends all commands necessary to put the connection into a usable state, + * like setting the transfer mode to binary. Note that passive mode will + * will be set on a case-by-case basis when opening a data connection. + **/ +void +g_vfs_ftp_task_setup_connection (GVfsFtpTask *task) +{ + g_return_if_fail (task != NULL); + + /* only binary transfers please */ + g_vfs_ftp_task_send (task, 0, "TYPE I"); + if (g_vfs_ftp_task_is_in_error (task)) + return; + +#if 0 + /* RFC 2428 suggests to send this to make NAT routers happy */ + /* XXX: Disabled for the following reasons: + * - most ftp clients don't use it + * - lots of broken ftp servers can't see the difference between + * "EPSV" and "EPSV ALL" + * - impossible to dynamically fall back to regular PASV in case + * EPSV doesn't work for some reason. + * If this makes your ftp connection fail, please file a bug and we will + * try to invent a way to make this all work. Until then, we'll just + * ignore the RFC. + */ + if (g_vfs_backend_ftp_has_feature (task->backend, g_VFS_FTP_FEATURE_EPSV)) + g_vfs_ftp_task_send (task, 0, "EPSV ALL"); + g_vfs_ftp_task_clear_error (task); +#endif + + /* instruct server that we'll give and assume we get utf8 */ + if (g_vfs_backend_ftp_has_feature (task->backend, G_VFS_FTP_FEATURE_UTF8)) + { + if (!g_vfs_ftp_task_send (task, 0, "OPTS UTF8 ON")) + g_vfs_ftp_task_clear_error (task); + } +} + + +static void +do_broadcast (GCancellable *cancellable, GCond *cond) +{ + g_cond_broadcast (cond); +} + +/** + * g_vfs_ftp_task_acquire_connection: + * @task: a task without an associated connection + * + * Acquires a new connection for use by this @task. This uses the connection + * pool of @task's backend, so it reuses previously opened connections and + * does not reopen new connections unnecessarily. If all connections are busy, + * it waits %G_VFS_FTP_TIMEOUT_IN_SECONDS seconds for a new connection to + * become available. Keep in mind that a newly acquired connection might have + * timed out and therefore closed by the FTP server. You must account for + * this when sending the first command to the server. + * + * Returns: %TRUE if a connection could be acquired, %FALSE if an error + * occured + **/ +static gboolean +g_vfs_ftp_task_acquire_connection (GVfsFtpTask *task) +{ + GVfsBackendFtp *ftp; + GTimeVal now; + gulong id; + + g_return_val_if_fail (task != NULL, FALSE); + g_return_val_if_fail (task->conn == NULL, FALSE); + + if (g_vfs_ftp_task_is_in_error (task)) + return FALSE; + + ftp = task->backend; + g_mutex_lock (ftp->mutex); + id = g_cancellable_connect (task->cancellable, + G_CALLBACK (do_broadcast), + ftp->cond, NULL); + while (task->conn == NULL && ftp->queue != NULL) + { + if (g_cancellable_is_cancelled (task->cancellable)) + { + task->error = g_error_new_literal (G_IO_ERROR, G_IO_ERROR_CANCELLED, + _("Operation was cancelled")); + break; + } + + task->conn = g_queue_pop_head (ftp->queue); + if (task->conn != NULL) + break; + + if (ftp->connections < ftp->max_connections) + { + /* Save current number of connections here, so we can limit maximum + * connections later. + * This is necessary for threading reasons (connections can be + * opened or closed while we are still in the opening process. */ + guint maybe_max_connections = ftp->connections; + + ftp->connections++; + g_mutex_unlock (ftp->mutex); + task->conn = g_vfs_ftp_connection_new (ftp->addr, task->cancellable, &task->error); + if (G_LIKELY (task->conn != NULL)) + { + g_vfs_ftp_task_receive (task, 0, NULL); + g_vfs_ftp_task_login (task, ftp->user, ftp->password); + g_vfs_ftp_task_setup_connection (task); + if (G_LIKELY (!g_vfs_ftp_task_is_in_error (task))) + break; + } + + g_vfs_ftp_task_clear_error (task); + g_vfs_ftp_connection_free (task->conn); + task->conn = NULL; + g_mutex_lock (ftp->mutex); + ftp->connections--; + ftp->max_connections = MIN (ftp->max_connections, maybe_max_connections); + if (ftp->max_connections == 0) + { + DEBUG ("no more connections left, exiting..."); + /* FIXME: shut down properly */ + exit (0); + } + + continue; + } + + g_get_current_time (&now); + g_time_val_add (&now, G_VFS_FTP_TIMEOUT_IN_SECONDS * 1000 * 1000); + if (!g_cond_timed_wait (ftp->cond, ftp->mutex, &now)) + { + task->error = g_error_new_literal (G_IO_ERROR, G_IO_ERROR_BUSY, + _("The FTP server is busy. Try again later")); + break; + } + } + g_cancellable_disconnect (task->cancellable, id); + g_mutex_unlock (ftp->mutex); + + return task->conn != NULL; +} + +/** + * g_vfs_ftp_task_release_connection: + * @task: a task + * + * Releases the connection in use by @task to the backend's connection pool, + * or frees it if it is in an error state. You must use this function to free + * a @task's connection, never use g_vfs_ftp_connection_free() directly. If + * the task does not have a current connection, this function just returns. + **/ +static void +g_vfs_ftp_task_release_connection (GVfsFtpTask *task) +{ + g_return_if_fail (task != NULL); + + /* we allow task->conn == NULL to ease error cases */ + if (task->conn == NULL) + return; + + g_mutex_lock (task->backend->mutex); + if (task->backend->queue && g_vfs_ftp_connection_is_usable (task->conn)) + { + g_queue_push_tail (task->backend->queue, task->conn); + g_cond_signal (task->backend->cond); + } + else + g_vfs_ftp_connection_free (task->conn); + g_mutex_unlock (task->backend->mutex); + task->conn = NULL; +} + +/** + * g_vfs_ftp_task_done: + * @task: the task to finalize + * + * Finalizes the given task and clears all memory in use. It also marks the + * associated job as success or failure depending on the error state of the + * task. + **/ +void +g_vfs_ftp_task_done (GVfsFtpTask *task) +{ + g_return_if_fail (task != NULL); + + if (task->conn) + g_vfs_ftp_task_release_connection (task); + + if (task->job) + { + if (g_vfs_ftp_task_is_in_error (task)) + g_vfs_job_failed_from_error (task->job, task->error); + else + g_vfs_job_succeeded (task->job); + } + + g_vfs_ftp_task_clear_error (task); +} + +/** + * g_vfs_ftp_task_set_error_from_response: + * @task: the task + * @response: the response code + * + * Sets the @task into an error state. The exact error is determined from the + * @response code. + **/ +void +g_vfs_ftp_task_set_error_from_response (GVfsFtpTask *task, guint response) +{ + const char *msg; + int code; + + g_return_if_fail (task != NULL); + g_return_if_fail (task->error == NULL); + + /* Please keep this list ordered by response code, + * but group responses with the same message. */ + switch (response) + { + case 332: /* Need account for login. */ + case 532: /* Need account for storing files. */ + /* FIXME: implement a sane way to handle accounts. */ + code = G_IO_ERROR_NOT_SUPPORTED; + msg = _("Accounts are unsupported"); + break; + case 421: /* Service not available, closing control connection. */ + code = G_IO_ERROR_FAILED; + msg = _("Host closed connection"); + break; + case 425: /* Can't open data connection. */ + code = G_IO_ERROR_CLOSED; + msg = _("Cannot open data connection. Maybe your firewall prevents this?"); + break; + case 426: /* Connection closed; transfer aborted. */ + code = G_IO_ERROR_CLOSED; + msg = _("Data connection closed"); + break; + case 450: /* Requested file action not taken. File unavailable (e.g., file busy). */ + case 550: /* Requested action not taken. File unavailable (e.g., file not found, no access). */ + /* FIXME: This is a lot of different errors. So we have to pretend to + * be smart here. */ + code = G_IO_ERROR_FAILED; + msg = _("Operation failed"); + break; + case 451: /* Requested action aborted: local error in processing. */ + code = G_IO_ERROR_FAILED; + msg = _("Operation failed"); + break; + case 452: /* Requested action not taken. Insufficient storage space in system. */ + case 552: + code = G_IO_ERROR_NO_SPACE; + msg = _("No space left on server"); + break; + case 500: /* Syntax error, command unrecognized. */ + case 501: /* Syntax error in parameters or arguments. */ + case 502: /* Command not implemented. */ + case 503: /* Bad sequence of commands. */ + case 504: /* Command not implemented for that parameter. */ + code = G_IO_ERROR_NOT_SUPPORTED; + msg = _("Operation unsupported"); + break; + case 530: /* Not logged in. */ + code = G_IO_ERROR_PERMISSION_DENIED; + msg = _("Permission denied"); + break; + case 551: /* Requested action aborted: page type unknown. */ + code = G_IO_ERROR_FAILED; + msg = _("Page type unknown"); + break; + case 553: /* Requested action not taken. File name not allowed. */ + code = G_IO_ERROR_INVALID_FILENAME; + msg = _("Invalid filename"); + break; + default: + code = G_IO_ERROR_FAILED; + msg = _("Invalid reply"); + break; + } + + g_set_error_literal (&task->error, G_IO_ERROR, code, msg); +} + +/** + * g_vfs_ftp_task_give_connection: + * @task: the task + * @conn: the connection that the @task should use + * + * Forces a given @task to do I/O using the given connection. The @task must + * not have a connection associated with itself. The @task will take + * ownership of @conn. + **/ +void +g_vfs_ftp_task_give_connection (GVfsFtpTask * task, + GVfsFtpConnection *conn) +{ + g_return_if_fail (task != NULL); + g_return_if_fail (task->conn == NULL); + + task->conn = conn; +} + +/** + * g_vfs_ftp_task_take_connection: + * @task: the task + * + * Acquires the connection in use by the @task, so it can later be used with + * g_vfs_ftp_task_give_connection(). This or any other task will not use the + * connection anymore. The @task must have a connection in use. + * + * Returns: The connection that @task was using. You acquire ownership of + * the connection. + **/ +GVfsFtpConnection * +g_vfs_ftp_task_take_connection (GVfsFtpTask *task) +{ + GVfsFtpConnection *conn; + + g_return_val_if_fail (task != NULL, NULL); + g_return_val_if_fail (task->conn != NULL, NULL); + + conn = task->conn; + task->conn = NULL; + + return conn; +} + +/** + * g_vfs_ftp_task_send: + * @task: the sending task + * @flags: response flags to use when sending + * @format: format string to construct command from + * (without trailing \r\n) + * @...: arguments to format string + * + * Shortcut to calling g_vfs_ftp_task_send_and_check() with the reply, funcs + * and data arguments set to %NULL. See that function for details. + * + * Returns: 0 on error or the received FTP code otherwise. + **/ +guint +g_vfs_ftp_task_send (GVfsFtpTask * task, + GVfsFtpResponseFlags flags, + const char * format, + ...) +{ + va_list varargs; + guint response; + + g_return_val_if_fail (task != NULL, 0); + g_return_val_if_fail (format != NULL, 0); + + va_start (varargs, format); + response = g_vfs_ftp_task_sendv (task, + flags, + NULL, + format, + varargs); + va_end (varargs); + return response; +} + +/** + * g_vfs_ftp_task_send_and_check: + * @task: the sending task + * @flags: response flags to use when sending + * @funcs: %NULL or %NULL-terminated array of functions used to determine the + * exact failure case upon a "550 Operation Failed" reply. This is + * often necessary + * @data: data to pass to @funcs. + * @reply: %NULL or pointer to take a char array containing the full reply of + * the ftp server upon successful reply. Use g_strfreev() to free + * after use. + * @format: format string to construct command from + * (without trailing \r\n) + * @...: arguments to format string + * + * Takes an ftp command in printf-style @format, potentially acquires a + * connection automatically, sends the command and waits for an answer from + * the ftp server. Without any @flags, FTP response codes other than 2xx cause + * an error. If @reply is not %NULL, the full reply will be put into a + * %NULL-terminated string array that must be freed with g_strfreev() after + * use. + * If @funcs is set, the 550 response code will cause all of these functions to + * be called in order passing them the @task and @data arguments given to this + * function until one of them sets an error on @task. This error will then be + * returned from this function. If none of those functions sets an error, the + * generic error for the 550 response will be used. + * If an error has been set on @task previously, this function will do nothing. + * + * Returns: 0 on error or the received FTP code otherwise. + **/ +guint +g_vfs_ftp_task_send_and_check (GVfsFtpTask * task, + GVfsFtpResponseFlags flags, + const GVfsFtpErrorFunc *funcs, + gpointer data, + char *** reply, + const char * format, + ...) +{ + va_list varargs; + guint response; + + g_return_val_if_fail (task != NULL, 0); + g_return_val_if_fail (format != NULL, 0); + g_return_val_if_fail (funcs == NULL || funcs[0] != NULL, 0); + + if (funcs) + { + g_return_val_if_fail ((flags & G_VFS_FTP_PASS_550) == 0, 0); + flags |= G_VFS_FTP_PASS_550; + } + + va_start (varargs, format); + response = g_vfs_ftp_task_sendv (task, + flags, + reply, + format, + varargs); + va_end (varargs); + + if (response == 550 && funcs) + { + while (*funcs && !g_vfs_ftp_task_is_in_error (task)) + { + (*funcs) (task, data); + funcs++; + } + if (!g_vfs_ftp_task_is_in_error (task)) + g_vfs_ftp_task_set_error_from_response (task, response); + response = 0; + } + + return response; +} + +/** + * g_vfs_ftp_task_sendv: + * @task: the sending task + * @flags: response flags to use when receiving the reply + * @reply: %NULL or pointer to char array that takes the full reply from the + * server + * @format: format string to construct command from + * (without trailing \r\n) + * @varargs: arguments to format string + * + * This is the varargs version of g_vfs_ftp_task_send(). See that function + * for details. + * + * Returns: the received FTP code or 0 on error. + **/ +guint +g_vfs_ftp_task_sendv (GVfsFtpTask * task, + GVfsFtpResponseFlags flags, + char *** reply, + const char * format, + va_list varargs) +{ + GString *command; + gboolean retry_on_timeout = FALSE; + guint response; + + if (g_vfs_ftp_task_is_in_error (task)) + return 0; + + command = g_string_new (""); + g_string_append_vprintf (command, format, varargs); + g_string_append (command, "\r\n"); + +retry: + if (task->conn == NULL) + { + if (!g_vfs_ftp_task_acquire_connection (task)) + { + g_string_free (command, TRUE); + return 0; + } + retry_on_timeout = TRUE; + } + +#ifdef PRINT_DEBUG + if (g_str_has_prefix (command->str, "PASS")) + DEBUG ("--> PASS ***\n"); + else + { + command->str[command->len - 2] = 0; + DEBUG ("--> %s\n", command->str); + command->str[command->len - 2] = '\r'; + } +#endif + g_vfs_ftp_connection_send (task->conn, + command->str, + command->len, + task->cancellable, + &task->error); + + response = g_vfs_ftp_task_receive (task, flags, reply); + + /* NB: requires adaption if we allow passing 4xx responses */ + if (retry_on_timeout && + g_vfs_ftp_task_is_in_error (task) && + !g_vfs_ftp_connection_is_usable (task->conn)) + { + g_vfs_ftp_task_clear_error (task); + g_vfs_ftp_task_release_connection (task); + goto retry; + } + + g_string_free (command, TRUE); + return response; +} + +/** + * g_vfs_ftp_task_receive: + * @task: the receiving task + * @flags: response flags to use + * @reply: %NULL or pointer to char array that takes the full reply from the + * server + * + * Unless @task is in an error state, this function receives a reply from + * the @task's connection. The @task must have a connection set, which will + * happen when either g_vfs_ftp_task_send() or + * g_vfs_ftp_task_give_connection() have been called on the @task before. + * Unless @flags are given, all reply codes not in the 200s cause an error. + * If @task is in an error state when calling this function, nothing will + * happen and the function will just return. + * + * Returns: the received FTP code or 0 on error. + **/ +guint +g_vfs_ftp_task_receive (GVfsFtpTask * task, + GVfsFtpResponseFlags flags, + char *** reply) +{ + guint response; + + g_return_val_if_fail (task != NULL, 0); + if (g_vfs_ftp_task_is_in_error (task)) + return 0; + g_return_val_if_fail (task->conn != NULL, 0); + + response = g_vfs_ftp_connection_receive (task->conn, + reply, + task->cancellable, + &task->error); + + switch (G_VFS_FTP_RESPONSE_GROUP (response)) + { + case 0: + return 0; + case 1: + if (flags & G_VFS_FTP_PASS_100) + break; + g_vfs_ftp_task_set_error_from_response (task, response); + return 0; + case 2: + if (flags & G_VFS_FTP_FAIL_200) + { + g_vfs_ftp_task_set_error_from_response (task, response); + return 0; + } + break; + case 3: + if (flags & G_VFS_FTP_PASS_300) + break; + g_vfs_ftp_task_set_error_from_response (task, response); + return 0; + case 4: + g_vfs_ftp_task_set_error_from_response (task, response); + return 0; + case 5: + if ((flags & G_VFS_FTP_PASS_500) || + (response == 550 && (flags & G_VFS_FTP_PASS_550))) + break; + g_vfs_ftp_task_set_error_from_response (task, response); + return 0; + default: + g_assert_not_reached (); + break; + } + + return response; +} + +static GSocketAddress * +g_vfs_ftp_task_create_remote_address (GVfsFtpTask *task, guint port) +{ + GSocketAddress *old, *new; + + old = g_vfs_ftp_connection_get_address (task->conn, &task->error); + if (old == NULL) + return NULL; + g_assert (G_IS_INET_SOCKET_ADDRESS (old)); + new = g_inet_socket_address_new (g_inet_socket_address_get_address (G_INET_SOCKET_ADDRESS (old)), port); + + return new; +} + +static gboolean +g_vfs_ftp_task_open_data_connection_epsv (GVfsFtpTask *task) +{ + const char *s; + char **reply; + guint port; + GSocketAddress *addr; + guint status; + + g_assert (task->error == NULL); + + if (!g_vfs_backend_ftp_has_feature (task->backend, G_VFS_FTP_FEATURE_EPSV) || + g_vfs_backend_ftp_uses_workaround (task->backend, G_VFS_FTP_WORKAROUND_BROKEN_EPSV)) + return FALSE; + + status = g_vfs_ftp_task_send_and_check (task, G_VFS_FTP_PASS_500, NULL, NULL, &reply, "EPSV"); + if (G_VFS_FTP_RESPONSE_GROUP (status) != 2) + goto fail; + + /* FIXME: parse multiple lines? */ + s = strrchr (reply[0], '('); + if (!s) + goto fail; + + s += 4; + port = strtoul (s, NULL, 10); + if (port == 0) + goto fail; + + g_strfreev (reply); + addr = g_vfs_ftp_task_create_remote_address (task, port); + if (addr == NULL) + return FALSE; + + if (!g_vfs_ftp_connection_open_data_connection (task->conn, + addr, + task->cancellable, + &task->error)) + { + g_object_unref (addr); + DEBUG ("Successful EPSV response code, but data connection failed. Enabling FTP_WORKAROUND_BROKEN_EPSV.\n"); + g_vfs_backend_ftp_use_workaround (task->backend, G_VFS_FTP_WORKAROUND_BROKEN_EPSV); + g_vfs_ftp_task_clear_error (task); + return FALSE; + } + + g_object_unref (addr); + return TRUE; + +fail: + g_strfreev (reply); + return FALSE; +} + +static gboolean +g_vfs_ftp_task_open_data_connection_pasv (GVfsFtpTask *task) +{ + guint ip1, ip2, ip3, ip4, port1, port2; + char **reply; + const char *s; + GSocketAddress *addr; + guint status; + + /* only binary transfers please */ + status = g_vfs_ftp_task_send_and_check (task, 0, NULL, NULL, &reply, "PASV"); + if (status == 0) + return FALSE; + + /* parse response and try to find the address to connect to. + * This code does the same as curl. + */ + for (s = reply[0]; *s; s++) + { + if (sscanf (s, "%u,%u,%u,%u,%u,%u", + &ip1, &ip2, &ip3, &ip4, + &port1, &port2) == 6) + break; + } + g_strfreev (reply); + if (*s == 0) + { + g_set_error_literal (&task->error, G_IO_ERROR, G_IO_ERROR_FAILED, + _("Invalid reply")); + return FALSE; + } + + if (!g_vfs_backend_ftp_uses_workaround (task->backend, G_VFS_FTP_WORKAROUND_PASV_ADDR)) + { + guint8 ip[4]; + GInetAddress *inet_addr; + + ip[0] = ip1; + ip[1] = ip2; + ip[2] = ip3; + ip[3] = ip4; + inet_addr = g_inet_address_new_from_bytes (ip, G_SOCKET_FAMILY_IPV4); + addr = g_inet_socket_address_new (inet_addr, port1 << 8 | port2); + g_object_unref (inet_addr); + + if (g_vfs_ftp_connection_open_data_connection (task->conn, + addr, + task->cancellable, + &task->error)) + { + g_object_unref (addr); + return TRUE; + } + + g_object_unref (addr); + /* set workaround flag (see below), so we don't try this again */ + DEBUG ("Successfull PASV response but data connection failed. Enabling FTP_WORKAROUND_PASV_ADDR.\n"); + g_vfs_backend_ftp_use_workaround (task->backend, G_VFS_FTP_WORKAROUND_PASV_ADDR); + g_vfs_ftp_task_clear_error (task); + } + + /* Workaround code: + * Various ftp servers aren't setup correctly when behind a NAT. They report + * their own IP address (like 10.0.0.4) and not the address in front of the + * NAT. But this is likely the same address that we connected to with our + * command connetion. So if the address given by PASV fails, we fall back + * to the address of the command stream. + */ + addr = g_vfs_ftp_task_create_remote_address (task, port1 << 8 | port2); + if (addr == NULL) + return FALSE; + if (!g_vfs_ftp_connection_open_data_connection (task->conn, + addr, + task->cancellable, + &task->error)) + { + g_object_unref (addr); + return FALSE; + } + + g_object_unref (addr); + return TRUE; +} + +/** + * g_vfs_ftp_task_close_data_connection: + * @task: a task potentially having an open data connection + * + * Closes any data connection @task might have opened. + */ +/** + * g_vfs_ftp_task_open_data_connection: + * @task: a task not having an open data connection + * + * Tries to open a data connection to the ftp server. If the operation fails, + * @task will be set into an error state. + **/ +void +g_vfs_ftp_task_open_data_connection (GVfsFtpTask *task) +{ + g_return_if_fail (task != NULL); + + if (g_vfs_ftp_task_is_in_error (task)) + return; + + /* only check this here, erroneous connection might have failed to acquire a connection. */ + g_return_if_fail (task->conn != NULL); + + if (g_vfs_ftp_task_open_data_connection_epsv (task)) + return; + + if (g_vfs_ftp_task_is_in_error (task)) + return; + + g_vfs_ftp_task_open_data_connection_pasv (task); +} + diff --git a/daemon/gvfsftptask.h b/daemon/gvfsftptask.h new file mode 100644 index 00000000..2bd9bd20 --- /dev/null +++ b/daemon/gvfsftptask.h @@ -0,0 +1,99 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright (C) 2009 Benjamin Otte <otte@gnome.org> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Benjamin Otte <otte@gnome.org> + */ + +#ifndef __G_VFS_FTP_TASK_H__ +#define __G_VFS_FTP_TASK_H__ + +#include "gvfsbackendftp.h" +#include "gvfsftpconnection.h" + +G_BEGIN_DECLS + +typedef enum { + G_VFS_FTP_PASS_100 = (1 << 0), + G_VFS_FTP_PASS_300 = (1 << 1), + G_VFS_FTP_PASS_500 = (1 << 2), + G_VFS_FTP_PASS_550 = (1 << 3), + G_VFS_FTP_FAIL_200 = (1 << 4) +} GVfsFtpResponseFlags; + +#define G_VFS_FTP_RESPONSE_GROUP(response) ((response) / 100) + +typedef struct _GVfsFtpTask GVfsFtpTask; +struct _GVfsFtpTask +{ + GVfsBackendFtp * backend; /* backend this task is running on */ + GVfsJob * job; /* job that is processed or NULL if not bound to a task */ + GCancellable * cancellable; /* cancellable in use */ + + GError * error; /* NULL or current error - will be propagated to task */ + GVfsFtpConnection * conn; /* connection in use by this task or NULL if none */ +}; + +typedef void (* GVfsFtpErrorFunc) (GVfsFtpTask *task, gpointer data); + +#define G_VFS_FTP_TASK_INIT(backend,job) { (backend), (job), (job)->cancellable, } +void g_vfs_ftp_task_done (GVfsFtpTask * task); + +#define g_vfs_ftp_task_is_in_error(task) ((task)->error != NULL) +#define g_vfs_ftp_task_clear_error(task) (g_clear_error (&(task)->error)) +void g_vfs_ftp_task_set_error_from_response (GVfsFtpTask * task, + guint response); + +void g_vfs_ftp_task_give_connection (GVfsFtpTask * task, + GVfsFtpConnection * conn); +GVfsFtpConnection * g_vfs_ftp_task_take_connection (GVfsFtpTask * task); + +guint g_vfs_ftp_task_send (GVfsFtpTask * task, + GVfsFtpResponseFlags flags, + const char * format, + ...) G_GNUC_PRINTF (3, 4); +guint g_vfs_ftp_task_send_and_check (GVfsFtpTask * task, + GVfsFtpResponseFlags flags, + const GVfsFtpErrorFunc *funcs, + gpointer data, + char *** reply, + const char * format, + ...) G_GNUC_PRINTF (6, 7); +guint g_vfs_ftp_task_sendv (GVfsFtpTask * task, + GVfsFtpResponseFlags flags, + char *** reply, + const char * format, + va_list varargs); +guint g_vfs_ftp_task_receive (GVfsFtpTask * task, + GVfsFtpResponseFlags flags, + char *** reply); +void g_vfs_ftp_task_open_data_connection (GVfsFtpTask * task); +#define g_vfs_ftp_task_close_data_connection(task) G_STMT_START{\ + if ((task)->conn) \ + g_vfs_ftp_connection_close_data_connection((task)->conn);\ +}G_STMT_END + +gboolean g_vfs_ftp_task_login (GVfsFtpTask * task, + const char * username, + const char * password); +void g_vfs_ftp_task_setup_connection (GVfsFtpTask * task); + + +G_END_DECLS + +#endif /* __G_VFS_FTP_TASK_H__ */ |