/** * 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); } }
/** * 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; } 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_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); 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_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; gint64 end_time; 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) { static GThread *last_thread = NULL; /* 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++; last_thread = g_thread_self (); 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_connection_free (task->conn); task->conn = NULL; g_mutex_lock (&ftp->mutex); ftp->connections--; /* If this value is still equal to our thread it means there were no races * trying to open connections and the maybe_max_connections value is * reliable. */ if (last_thread == g_thread_self () && !g_vfs_ftp_task_error_matches (task, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { g_print ("maybe: %u, max %u (due to %s)\n", maybe_max_connections, ftp->max_connections, task->error->message); ftp->max_connections = MIN (ftp->max_connections, maybe_max_connections); if (ftp->max_connections == 0) { g_debug ("no more connections left, exiting...\n"); /* FIXME: shut down properly */ exit (0); } } g_vfs_ftp_task_clear_error (task); continue; } end_time = g_get_monotonic_time () + G_VFS_FTP_TIMEOUT_IN_SECONDS * G_TIME_SPAN_SECOND; if (ftp->busy_connections >= ftp->connections || !g_cond_wait_until (&ftp->cond, &ftp->mutex, end_time)) { 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; }
static GFileInfo * g_vfs_ftp_dir_cache_funcs_lookup_uncached (GVfsFtpTask * task, const GVfsFtpFile *file) { GFileInfo *info; char **reply; if (g_vfs_ftp_file_is_root (file)) return create_root_file_info (task->backend); /* the directory cache fails when the parent directory of the file is not readable. * This cannot happen on Unix, but it can happen on FTP. * In this case we try to figure out as much as possible about the file (does it even exist?) * using standard ftp commands. */ if (g_vfs_ftp_task_send (task, 0, "CWD %s", g_vfs_ftp_file_get_ftp_path (file))) { char *tmp; info = g_file_info_new (); tmp = g_path_get_basename (g_vfs_ftp_file_get_gvfs_path (file)); g_file_info_set_name (info, tmp); g_free (tmp); gvfs_file_info_populate_default (info, g_vfs_ftp_file_get_gvfs_path (file), G_FILE_TYPE_DIRECTORY); g_file_info_set_is_hidden (info, TRUE); return info; } g_vfs_ftp_task_clear_error (task); 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; info = g_file_info_new (); tmp = g_path_get_basename (g_vfs_ftp_file_get_gvfs_path (file)); g_file_info_set_name (info, tmp); g_free (tmp); gvfs_file_info_populate_default (info, g_vfs_ftp_file_get_gvfs_path (file), G_FILE_TYPE_REGULAR); 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); return info; } 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 * don't support SIZE. * If you have ways to improve file detection, patches are welcome. */ return NULL; }
static GFileInfo * g_vfs_ftp_dir_cache_resolve_symlink (GVfsFtpDirCache * cache, GVfsFtpTask * task, const GVfsFtpFile *file, GFileInfo * original, guint stamp) { static const char *copy_attributes[] = { G_FILE_ATTRIBUTE_STANDARD_IS_SYMLINK, G_FILE_ATTRIBUTE_STANDARD_IS_HIDDEN, G_FILE_ATTRIBUTE_STANDARD_NAME, G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME, G_FILE_ATTRIBUTE_STANDARD_EDIT_NAME, G_FILE_ATTRIBUTE_STANDARD_COPY_NAME, G_FILE_ATTRIBUTE_STANDARD_SYMLINK_TARGET }; GFileInfo *info, *result; GVfsFtpFile *tmp, *link; guint i, lookups = 0; const char *target; if (!g_file_info_get_is_symlink (original) || g_vfs_ftp_task_is_in_error (task)) return original; info = g_object_ref (original); link = g_vfs_ftp_file_copy (file); do { target = g_file_info_get_symlink_target (info); if (target == NULL) { /* This happens when bad servers don't report a symlink target. * We now want to figure out if this is a directory or regular file, * so we can at least report something useful. */ g_object_unref (info); info = cache->funcs->lookup_uncached (task, file); break; } tmp = link; link = cache->funcs->resolve_symlink (task, tmp, g_file_info_get_symlink_target (info)); g_vfs_ftp_file_free (tmp); g_object_unref (info); if (link == NULL) { g_vfs_ftp_task_clear_error (task); return original; } info = g_vfs_ftp_dir_cache_lookup_file_internal (cache, task, link, stamp); if (info == NULL) { g_vfs_ftp_file_free (link); g_vfs_ftp_task_clear_error (task); return original; } } while (g_file_info_get_is_symlink (info) && lookups++ < 8); g_vfs_ftp_file_free (link); if (g_file_info_get_is_symlink (info)) { /* too many recursions */ g_object_unref (info); return original; } result = g_file_info_dup (info); g_object_unref (info); for (i = 0; i < G_N_ELEMENTS (copy_attributes); i++) { GFileAttributeType type; gpointer value; if (!g_file_info_get_attribute_data (original, copy_attributes[i], &type, &value, NULL)) continue; g_file_info_set_attribute (result, copy_attributes[i], type, value); } g_object_unref (original); return result; }