Example #1
0
/**
 * 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);
    }
}
Example #2
0
/**
 * 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;
}
Example #3
0
/**
 * 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);
}
Example #4
0
/**
 * 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;
}
Example #5
0
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;
}
Example #6
0
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;
}