static GVfsFtpDirCacheEntry * g_vfs_ftp_dir_cache_lookup_entry (GVfsFtpDirCache * cache, GVfsFtpTask * task, const GVfsFtpFile *dir, guint stamp) { GVfsFtpDirCacheEntry *entry; g_mutex_lock (cache->lock); entry = g_hash_table_lookup (cache->directories, dir); if (entry) g_vfs_ftp_dir_cache_entry_ref (entry); g_mutex_unlock (cache->lock); if (entry && entry->stamp < stamp) g_vfs_ftp_dir_cache_entry_unref (entry); else if (entry) return entry; if (g_vfs_ftp_task_send (task, G_VFS_FTP_PASS_550, "CWD %s", g_vfs_ftp_file_get_ftp_path (dir)) == 550) { g_set_error_literal (&task->error, G_IO_ERROR, G_IO_ERROR_NOT_DIRECTORY, _("The file is not a directory")); } g_vfs_ftp_task_setup_data_connection (task); g_vfs_ftp_task_send (task, G_VFS_FTP_PASS_100 | G_VFS_FTP_FAIL_200, "%s", cache->funcs->command); g_vfs_ftp_task_open_data_connection (task); if (g_vfs_ftp_task_is_in_error (task)) return NULL; entry = g_vfs_ftp_dir_cache_entry_new (stamp); cache->funcs->process (g_io_stream_get_input_stream (g_vfs_ftp_connection_get_data_stream (task->conn)), g_vfs_ftp_connection_get_debug_id (task->conn), dir, entry, task->cancellable, &task->error); 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)) { g_vfs_ftp_dir_cache_entry_unref (entry); return NULL; } g_mutex_lock (cache->lock); g_hash_table_insert (cache->directories, g_vfs_ftp_file_copy (dir), g_vfs_ftp_dir_cache_entry_ref (entry)); g_mutex_unlock (cache->lock); return entry; }
/** * 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_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) { /* close a potentially open data connection, the error handlers * might try to open new ones and that would cause assertions */ g_vfs_ftp_task_close_data_connection (task); 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; }
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 = "******" 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); } }
GList * g_vfs_ftp_dir_cache_lookup_dir (GVfsFtpDirCache * cache, GVfsFtpTask * task, const GVfsFtpFile *dir, gboolean flush, gboolean resolve_symlinks) { GVfsFtpDirCacheEntry *entry; GHashTableIter iter; gpointer file, info; guint stamp; GList *result = NULL; g_return_val_if_fail (cache != NULL, NULL); g_return_val_if_fail (task != NULL, NULL); g_return_val_if_fail (dir != NULL, NULL); if (g_vfs_ftp_task_is_in_error (task)) return NULL; if (flush) { g_mutex_lock (cache->lock); g_assert (cache->stamp != G_MAXUINT); stamp = ++cache->stamp; g_mutex_unlock (cache->lock); } else stamp = 0; entry = g_vfs_ftp_dir_cache_lookup_entry (cache, task, dir, stamp); if (entry == NULL) return NULL; g_hash_table_iter_init (&iter, entry->files); while (g_hash_table_iter_next (&iter, &file, &info)) { g_object_ref (info); if (resolve_symlinks) info = g_vfs_ftp_dir_cache_resolve_symlink (cache, task, file, info, stamp); g_assert (!g_vfs_ftp_task_is_in_error (task)); result = g_list_prepend (result, info); } g_vfs_ftp_dir_cache_entry_unref (entry); return result; }
static GFileInfo * g_vfs_ftp_dir_cache_lookup_file_internal (GVfsFtpDirCache * cache, GVfsFtpTask * task, const GVfsFtpFile *file, guint stamp) { GVfsFtpDirCacheEntry *entry; GVfsFtpFile *dir; GFileInfo *info; if (g_vfs_ftp_task_is_in_error (task)) return NULL; if (!g_vfs_ftp_file_is_root (file)) { dir = g_vfs_ftp_file_new_parent (file); entry = g_vfs_ftp_dir_cache_lookup_entry (cache, task, dir, stamp); g_vfs_ftp_file_free (dir); if (entry == NULL) return NULL; info = g_hash_table_lookup (entry->files, file); if (info != NULL) { /* NB: the order of ref/unref is important here */ g_object_ref (info); g_vfs_ftp_dir_cache_entry_unref (entry); return info; } g_vfs_ftp_dir_cache_entry_unref (entry); } if (g_vfs_ftp_task_is_in_error (task)) return NULL; return cache->funcs->lookup_uncached (task, file); }
/** * 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_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); break; case 2: if (flags & G_VFS_FTP_FAIL_200) g_vfs_ftp_task_set_error_from_response (task, response); break; case 3: if (flags & G_VFS_FTP_PASS_300) break; g_vfs_ftp_task_set_error_from_response (task, response); break; case 4: g_vfs_ftp_task_set_error_from_response (task, response); break; 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); break; default: g_assert_not_reached (); break; } if (g_vfs_ftp_task_is_in_error (task)) { if (response != 0 && reply) { g_strfreev (*reply); *reply = NULL; } response = 0; } return response; }
/** * 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_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; }