mirror of
https://git.kernel.org/pub/scm/bluetooth/bluez.git
synced 2024-12-04 01:24:42 +08:00
46e41cbc06
The authorization request of a queued transfer could fail, and this needs to be reported to the transfer initiator. Otherwise it would likely result in D-Bus timeouts.
1440 lines
31 KiB
C
1440 lines
31 KiB
C
/*
|
|
*
|
|
* OBEX Client
|
|
*
|
|
* Copyright (C) 2007-2010 Marcel Holtmann <marcel@holtmann.org>
|
|
*
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program 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 General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include <config.h>
|
|
#endif
|
|
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <unistd.h>
|
|
#include <string.h>
|
|
#include <sys/stat.h>
|
|
|
|
#include <glib.h>
|
|
#include <gdbus.h>
|
|
#include <gobex.h>
|
|
|
|
#include "log.h"
|
|
#include "transfer.h"
|
|
#include "session.h"
|
|
#include "agent.h"
|
|
#include "driver.h"
|
|
#include "transport.h"
|
|
|
|
#define SESSION_INTERFACE "org.openobex.Session"
|
|
#define SESSION_BASEPATH "/org/openobex"
|
|
|
|
#define OBEX_IO_ERROR obex_io_error_quark()
|
|
#define OBEX_IO_ERROR_FIRST (0xff + 1)
|
|
|
|
enum {
|
|
OBEX_IO_DISCONNECTED = OBEX_IO_ERROR_FIRST,
|
|
OBEX_IO_BUSY,
|
|
};
|
|
|
|
static guint64 counter = 0;
|
|
|
|
struct callback_data {
|
|
struct obc_session *session;
|
|
session_callback_t func;
|
|
void *data;
|
|
};
|
|
|
|
struct session_callback {
|
|
session_callback_t func;
|
|
void *data;
|
|
};
|
|
|
|
struct pending_request {
|
|
guint id;
|
|
guint req_id;
|
|
struct obc_session *session;
|
|
struct obc_transfer *transfer;
|
|
GFunc auth_complete;
|
|
session_callback_t func;
|
|
void *data;
|
|
};
|
|
|
|
struct setpath_data {
|
|
char **remaining;
|
|
int index;
|
|
session_callback_t func;
|
|
void *user_data;
|
|
};
|
|
|
|
struct obc_session {
|
|
guint id;
|
|
gint refcount;
|
|
char *source;
|
|
char *destination;
|
|
uint8_t channel;
|
|
struct obc_transport *transport;
|
|
struct obc_driver *driver;
|
|
gchar *path; /* Session path */
|
|
DBusConnection *conn;
|
|
GObex *obex;
|
|
struct obc_agent *agent;
|
|
struct pending_request *p;
|
|
gchar *owner; /* Session owner */
|
|
guint watch;
|
|
GQueue *queue;
|
|
};
|
|
|
|
static GSList *sessions = NULL;
|
|
|
|
static void session_prepare_put(gpointer data, gpointer user_data);
|
|
static void session_terminate_transfer(struct obc_session *session,
|
|
struct obc_transfer *transfer,
|
|
GError *gerr);
|
|
|
|
GQuark obex_io_error_quark(void)
|
|
{
|
|
return g_quark_from_static_string("obex-io-error-quark");
|
|
}
|
|
|
|
struct obc_session *obc_session_ref(struct obc_session *session)
|
|
{
|
|
g_atomic_int_inc(&session->refcount);
|
|
|
|
DBG("%p: ref=%d", session, session->refcount);
|
|
|
|
return session;
|
|
}
|
|
|
|
static void session_unregistered(struct obc_session *session)
|
|
{
|
|
char *path;
|
|
|
|
if (session->driver && session->driver->remove)
|
|
session->driver->remove(session);
|
|
|
|
path = session->path;
|
|
session->path = NULL;
|
|
|
|
g_dbus_unregister_interface(session->conn, path, SESSION_INTERFACE);
|
|
|
|
DBG("Session(%p) unregistered %s", session, path);
|
|
|
|
g_free(path);
|
|
}
|
|
|
|
static struct pending_request *pending_request_new(struct obc_session *session,
|
|
struct obc_transfer *transfer,
|
|
GFunc auth_complete,
|
|
session_callback_t func,
|
|
void *data)
|
|
{
|
|
struct pending_request *p;
|
|
static guint id = 0;
|
|
|
|
p = g_new0(struct pending_request, 1);
|
|
p->id = ++id;
|
|
p->session = obc_session_ref(session);
|
|
p->transfer = transfer;
|
|
p->auth_complete = auth_complete;
|
|
p->func = func;
|
|
p->data = data;
|
|
|
|
return p;
|
|
}
|
|
|
|
static void pending_request_free(struct pending_request *p)
|
|
{
|
|
if (p->transfer)
|
|
obc_transfer_unregister(p->transfer);
|
|
|
|
if (p->session)
|
|
obc_session_unref(p->session);
|
|
|
|
g_free(p);
|
|
}
|
|
|
|
static void session_free(struct obc_session *session)
|
|
{
|
|
DBG("%p", session);
|
|
|
|
if (session->agent) {
|
|
obc_agent_release(session->agent);
|
|
obc_agent_free(session->agent);
|
|
}
|
|
|
|
if (session->queue) {
|
|
g_queue_foreach(session->queue, (GFunc) pending_request_free,
|
|
NULL);
|
|
g_queue_free(session->queue);
|
|
}
|
|
|
|
if (session->watch)
|
|
g_dbus_remove_watch(session->conn, session->watch);
|
|
|
|
if (session->obex != NULL)
|
|
g_obex_unref(session->obex);
|
|
|
|
if (session->id > 0 && session->transport != NULL)
|
|
session->transport->disconnect(session->id);
|
|
|
|
if (session->path)
|
|
session_unregistered(session);
|
|
|
|
if (session->conn)
|
|
dbus_connection_unref(session->conn);
|
|
|
|
if (session->p)
|
|
pending_request_free(session->p);
|
|
|
|
sessions = g_slist_remove(sessions, session);
|
|
|
|
g_free(session->path);
|
|
g_free(session->owner);
|
|
g_free(session->source);
|
|
g_free(session->destination);
|
|
g_free(session);
|
|
}
|
|
|
|
void obc_session_unref(struct obc_session *session)
|
|
{
|
|
gboolean ret;
|
|
|
|
ret = g_atomic_int_dec_and_test(&session->refcount);
|
|
|
|
DBG("%p: ref=%d", session, session->refcount);
|
|
|
|
if (ret == FALSE)
|
|
return;
|
|
|
|
session_free(session);
|
|
}
|
|
|
|
static void connect_cb(GObex *obex, GError *err, GObexPacket *rsp,
|
|
gpointer user_data)
|
|
{
|
|
struct callback_data *callback = user_data;
|
|
GError *gerr = NULL;
|
|
uint8_t rsp_code;
|
|
|
|
if (err != NULL) {
|
|
error("connect_cb: %s", err->message);
|
|
gerr = g_error_copy(err);
|
|
goto done;
|
|
}
|
|
|
|
rsp_code = g_obex_packet_get_operation(rsp, NULL);
|
|
if (rsp_code != G_OBEX_RSP_SUCCESS)
|
|
gerr = g_error_new(OBEX_IO_ERROR, -EIO,
|
|
"OBEX Connect failed with 0x%02x", rsp_code);
|
|
|
|
done:
|
|
callback->func(callback->session, gerr, callback->data);
|
|
if (gerr != NULL)
|
|
g_error_free(gerr);
|
|
obc_session_unref(callback->session);
|
|
g_free(callback);
|
|
}
|
|
|
|
static void transport_func(GIOChannel *io, GError *err, gpointer user_data)
|
|
{
|
|
struct callback_data *callback = user_data;
|
|
struct obc_session *session = callback->session;
|
|
struct obc_driver *driver = session->driver;
|
|
struct obc_transport *transport = session->transport;
|
|
GObex *obex;
|
|
GObexTransportType type;
|
|
int tx_mtu = -1;
|
|
int rx_mtu = -1;
|
|
|
|
DBG("");
|
|
|
|
if (err != NULL) {
|
|
error("%s", err->message);
|
|
goto done;
|
|
}
|
|
|
|
g_io_channel_set_close_on_unref(io, FALSE);
|
|
|
|
if (transport->getpacketopt &&
|
|
transport->getpacketopt(io, &tx_mtu, &rx_mtu) == 0)
|
|
type = G_OBEX_TRANSPORT_PACKET;
|
|
else
|
|
type = G_OBEX_TRANSPORT_STREAM;
|
|
|
|
obex = g_obex_new(io, type, tx_mtu, rx_mtu);
|
|
if (obex == NULL)
|
|
goto done;
|
|
|
|
g_io_channel_set_close_on_unref(io, TRUE);
|
|
|
|
if (driver->target != NULL)
|
|
g_obex_connect(obex, connect_cb, callback, &err,
|
|
G_OBEX_HDR_TARGET, driver->target, driver->target_len,
|
|
G_OBEX_HDR_INVALID);
|
|
else
|
|
g_obex_connect(obex, connect_cb, callback, &err,
|
|
G_OBEX_HDR_INVALID);
|
|
|
|
if (err != NULL) {
|
|
error("%s", err->message);
|
|
g_obex_unref(obex);
|
|
goto done;
|
|
}
|
|
|
|
session->obex = obex;
|
|
sessions = g_slist_prepend(sessions, session);
|
|
|
|
return;
|
|
done:
|
|
callback->func(callback->session, err, callback->data);
|
|
obc_session_unref(callback->session);
|
|
g_free(callback);
|
|
}
|
|
|
|
static void owner_disconnected(DBusConnection *connection, void *user_data)
|
|
{
|
|
struct obc_session *session = user_data;
|
|
|
|
DBG("");
|
|
|
|
obc_session_shutdown(session);
|
|
}
|
|
|
|
int obc_session_set_owner(struct obc_session *session, const char *name,
|
|
GDBusWatchFunction func)
|
|
{
|
|
if (session == NULL)
|
|
return -EINVAL;
|
|
|
|
if (session->watch)
|
|
g_dbus_remove_watch(session->conn, session->watch);
|
|
|
|
session->watch = g_dbus_add_disconnect_watch(session->conn, name, func,
|
|
session, NULL);
|
|
if (session->watch == 0)
|
|
return -EINVAL;
|
|
|
|
session->owner = g_strdup(name);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static struct obc_session *session_find(const char *source,
|
|
const char *destination,
|
|
const char *service,
|
|
uint8_t channel,
|
|
const char *owner)
|
|
{
|
|
GSList *l;
|
|
|
|
for (l = sessions; l; l = l->next) {
|
|
struct obc_session *session = l->data;
|
|
|
|
if (g_strcmp0(session->destination, destination))
|
|
continue;
|
|
|
|
if (g_strcmp0(service, session->driver->service))
|
|
continue;
|
|
|
|
if (source && g_strcmp0(session->source, source))
|
|
continue;
|
|
|
|
if (channel && session->channel != channel)
|
|
continue;
|
|
|
|
if (g_strcmp0(owner, session->owner))
|
|
continue;
|
|
|
|
return session;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static gboolean connection_complete(gpointer data)
|
|
{
|
|
struct callback_data *cb = data;
|
|
|
|
cb->func(cb->session, 0, cb->data);
|
|
|
|
obc_session_unref(cb->session);
|
|
|
|
g_free(cb);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static int session_connect(struct obc_session *session,
|
|
session_callback_t function, void *user_data)
|
|
{
|
|
struct callback_data *callback;
|
|
struct obc_transport *transport = session->transport;
|
|
struct obc_driver *driver = session->driver;
|
|
|
|
callback = g_try_malloc0(sizeof(*callback));
|
|
if (callback == NULL)
|
|
return -ENOMEM;
|
|
|
|
callback->func = function;
|
|
callback->data = user_data;
|
|
callback->session = obc_session_ref(session);
|
|
|
|
/* Connection completed */
|
|
if (session->obex) {
|
|
g_idle_add(connection_complete, callback);
|
|
return 0;
|
|
}
|
|
|
|
/* Ongoing connection */
|
|
if (session->id > 0)
|
|
return 0;
|
|
|
|
session->id = transport->connect(session->source, session->destination,
|
|
driver->uuid, session->channel,
|
|
transport_func, callback);
|
|
if (session->id == 0) {
|
|
obc_session_unref(callback->session);
|
|
g_free(callback);
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
struct obc_session *obc_session_create(const char *source,
|
|
const char *destination,
|
|
const char *service,
|
|
uint8_t channel,
|
|
const char *owner,
|
|
session_callback_t function,
|
|
void *user_data)
|
|
{
|
|
DBusConnection *conn;
|
|
struct obc_session *session;
|
|
struct obc_transport *transport;
|
|
struct obc_driver *driver;
|
|
|
|
if (destination == NULL)
|
|
return NULL;
|
|
|
|
session = session_find(source, destination, service, channel, owner);
|
|
if (session != NULL)
|
|
goto proceed;
|
|
|
|
/* FIXME: Do proper transport lookup when the API supports it */
|
|
transport = obc_transport_find("Bluetooth");
|
|
if (transport == NULL)
|
|
return NULL;
|
|
|
|
driver = obc_driver_find(service);
|
|
if (driver == NULL)
|
|
return NULL;
|
|
|
|
conn = dbus_bus_get(DBUS_BUS_SESSION, NULL);
|
|
if (conn == NULL)
|
|
return NULL;
|
|
|
|
session = g_try_malloc0(sizeof(*session));
|
|
if (session == NULL)
|
|
return NULL;
|
|
|
|
session->refcount = 1;
|
|
session->transport = transport;
|
|
session->driver = driver;
|
|
session->conn = conn;
|
|
session->source = g_strdup(source);
|
|
session->destination = g_strdup(destination);
|
|
session->channel = channel;
|
|
session->queue = g_queue_new();
|
|
|
|
if (owner)
|
|
obc_session_set_owner(session, owner, owner_disconnected);
|
|
|
|
proceed:
|
|
if (session_connect(session, function, user_data) < 0) {
|
|
obc_session_unref(session);
|
|
return NULL;
|
|
}
|
|
|
|
DBG("session %p transport %s driver %s", session,
|
|
session->transport->name, session->driver->service);
|
|
|
|
return session;
|
|
}
|
|
|
|
void obc_session_shutdown(struct obc_session *session)
|
|
{
|
|
DBG("%p", session);
|
|
|
|
obc_session_ref(session);
|
|
|
|
/* Unregister any pending transfer */
|
|
g_queue_foreach(session->queue, (GFunc) pending_request_free, NULL);
|
|
|
|
/* Unregister interfaces */
|
|
if (session->path)
|
|
session_unregistered(session);
|
|
|
|
/* Disconnect transport */
|
|
if (session->id > 0 && session->transport != NULL) {
|
|
session->transport->disconnect(session->id);
|
|
session->id = 0;
|
|
}
|
|
|
|
obc_session_unref(session);
|
|
}
|
|
|
|
static DBusMessage *assign_agent(DBusConnection *connection,
|
|
DBusMessage *message, void *user_data)
|
|
{
|
|
struct obc_session *session = user_data;
|
|
const gchar *sender, *path;
|
|
|
|
if (dbus_message_get_args(message, NULL,
|
|
DBUS_TYPE_OBJECT_PATH, &path,
|
|
DBUS_TYPE_INVALID) == FALSE)
|
|
return g_dbus_create_error(message,
|
|
"org.openobex.Error.InvalidArguments",
|
|
"Invalid arguments in method call");
|
|
|
|
sender = dbus_message_get_sender(message);
|
|
|
|
if (obc_session_set_agent(session, sender, path) < 0)
|
|
return g_dbus_create_error(message,
|
|
"org.openobex.Error.AlreadyExists",
|
|
"Already exists");
|
|
|
|
return dbus_message_new_method_return(message);
|
|
}
|
|
|
|
static DBusMessage *release_agent(DBusConnection *connection,
|
|
DBusMessage *message, void *user_data)
|
|
{
|
|
struct obc_session *session = user_data;
|
|
struct obc_agent *agent = session->agent;
|
|
const gchar *sender;
|
|
gchar *path;
|
|
|
|
if (dbus_message_get_args(message, NULL,
|
|
DBUS_TYPE_OBJECT_PATH, &path,
|
|
DBUS_TYPE_INVALID) == FALSE)
|
|
return g_dbus_create_error(message,
|
|
"org.openobex.Error.InvalidArguments",
|
|
"Invalid arguments in method call");
|
|
|
|
sender = dbus_message_get_sender(message);
|
|
|
|
if (agent == NULL)
|
|
return dbus_message_new_method_return(message);
|
|
|
|
if (g_str_equal(sender, obc_agent_get_name(agent)) == FALSE ||
|
|
g_str_equal(path, obc_agent_get_path(agent)) == FALSE)
|
|
return g_dbus_create_error(message,
|
|
"org.openobex.Error.NotAuthorized",
|
|
"Not Authorized");
|
|
|
|
obc_agent_free(agent);
|
|
|
|
return dbus_message_new_method_return(message);
|
|
}
|
|
|
|
static void append_entry(DBusMessageIter *dict,
|
|
const char *key, int type, void *val)
|
|
{
|
|
DBusMessageIter entry, value;
|
|
const char *signature;
|
|
|
|
dbus_message_iter_open_container(dict, DBUS_TYPE_DICT_ENTRY,
|
|
NULL, &entry);
|
|
|
|
dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &key);
|
|
|
|
switch (type) {
|
|
case DBUS_TYPE_STRING:
|
|
signature = DBUS_TYPE_STRING_AS_STRING;
|
|
break;
|
|
case DBUS_TYPE_BYTE:
|
|
signature = DBUS_TYPE_BYTE_AS_STRING;
|
|
break;
|
|
case DBUS_TYPE_UINT64:
|
|
signature = DBUS_TYPE_UINT64_AS_STRING;
|
|
break;
|
|
default:
|
|
signature = DBUS_TYPE_VARIANT_AS_STRING;
|
|
break;
|
|
}
|
|
|
|
dbus_message_iter_open_container(&entry, DBUS_TYPE_VARIANT,
|
|
signature, &value);
|
|
dbus_message_iter_append_basic(&value, type, val);
|
|
dbus_message_iter_close_container(&entry, &value);
|
|
|
|
dbus_message_iter_close_container(dict, &entry);
|
|
}
|
|
|
|
static DBusMessage *session_get_properties(DBusConnection *connection,
|
|
DBusMessage *message, void *user_data)
|
|
{
|
|
struct obc_session *session = user_data;
|
|
DBusMessage *reply;
|
|
DBusMessageIter iter, dict;
|
|
|
|
reply = dbus_message_new_method_return(message);
|
|
if (!reply)
|
|
return NULL;
|
|
|
|
dbus_message_iter_init_append(reply, &iter);
|
|
|
|
dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY,
|
|
DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING
|
|
DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING
|
|
DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict);
|
|
|
|
if (session->source != NULL)
|
|
append_entry(&dict, "Source", DBUS_TYPE_STRING,
|
|
&session->source);
|
|
|
|
append_entry(&dict, "Destination", DBUS_TYPE_STRING,
|
|
&session->destination);
|
|
|
|
append_entry(&dict, "Channel", DBUS_TYPE_BYTE, &session->channel);
|
|
|
|
dbus_message_iter_close_container(&iter, &dict);
|
|
|
|
return reply;
|
|
}
|
|
|
|
static GDBusMethodTable session_methods[] = {
|
|
{ "GetProperties", "", "a{sv}", session_get_properties },
|
|
{ "AssignAgent", "o", "", assign_agent },
|
|
{ "ReleaseAgent", "o", "", release_agent },
|
|
{ }
|
|
};
|
|
|
|
static void session_request_reply(DBusPendingCall *call, gpointer user_data)
|
|
{
|
|
struct obc_session *session = user_data;
|
|
struct pending_request *p = session->p;
|
|
struct obc_transfer *transfer = p->transfer;
|
|
DBusMessage *reply = dbus_pending_call_steal_reply(call);
|
|
const char *name;
|
|
DBusError derr;
|
|
|
|
dbus_error_init(&derr);
|
|
if (dbus_set_error_from_message(&derr, reply)) {
|
|
GError *gerr = NULL;
|
|
|
|
error("Replied with an error: %s, %s",
|
|
derr.name, derr.message);
|
|
dbus_error_free(&derr);
|
|
dbus_message_unref(reply);
|
|
|
|
g_set_error(&gerr, OBEX_IO_ERROR, -ECANCELED, "%s",
|
|
derr.message);
|
|
session_terminate_transfer(session, transfer, gerr);
|
|
g_clear_error(&gerr);
|
|
|
|
return;
|
|
}
|
|
|
|
dbus_message_get_args(reply, NULL,
|
|
DBUS_TYPE_STRING, &name,
|
|
DBUS_TYPE_INVALID);
|
|
|
|
DBG("Agent.Request() reply: %s", name);
|
|
|
|
if (strlen(name))
|
|
obc_transfer_set_name(transfer, name);
|
|
|
|
if (p->auth_complete)
|
|
p->auth_complete(session, transfer);
|
|
|
|
dbus_message_unref(reply);
|
|
|
|
return;
|
|
}
|
|
|
|
static gboolean session_request_proceed(gpointer data)
|
|
{
|
|
struct obc_session *session = data;
|
|
struct pending_request *p = session->p;
|
|
struct obc_transfer *transfer = p->transfer;
|
|
|
|
if (p->auth_complete)
|
|
p->auth_complete(p->session, transfer);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static int pending_request_auth(struct pending_request *p)
|
|
{
|
|
struct obc_session *session = p->session;
|
|
struct obc_agent *agent = session->agent;
|
|
const char *path;
|
|
|
|
path = obc_transfer_get_path(p->transfer);
|
|
|
|
if (agent == NULL || path == NULL) {
|
|
g_idle_add(session_request_proceed, session);
|
|
return 0;
|
|
}
|
|
|
|
return obc_agent_request(agent, path, session_request_reply, session,
|
|
NULL);
|
|
}
|
|
|
|
static int session_request(struct obc_session *session,
|
|
struct obc_transfer *transfer,
|
|
GFunc auth_complete,
|
|
session_callback_t func,
|
|
void *data)
|
|
{
|
|
struct pending_request *p;
|
|
int err;
|
|
|
|
p = pending_request_new(session, transfer, auth_complete, func, data);
|
|
|
|
if (session->p) {
|
|
g_queue_push_tail(session->queue, p);
|
|
return 0;
|
|
}
|
|
|
|
err = pending_request_auth(p);
|
|
if (err < 0) {
|
|
pending_request_free(p);
|
|
return err;
|
|
}
|
|
|
|
session->p = p;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void session_process_queue(struct obc_session *session)
|
|
{
|
|
struct pending_request *p;
|
|
|
|
if (session->queue == NULL || g_queue_is_empty(session->queue))
|
|
return;
|
|
|
|
obc_session_ref(session);
|
|
|
|
while ((p = g_queue_pop_head(session->queue))) {
|
|
int err;
|
|
|
|
err = pending_request_auth(p);
|
|
if (err == 0) {
|
|
session->p = p;
|
|
break;
|
|
}
|
|
|
|
if (p->func) {
|
|
GError *gerr = NULL;
|
|
|
|
g_set_error(&gerr, OBEX_IO_ERROR, err,
|
|
"Authorization failed");
|
|
p->func(session, gerr, p->data);
|
|
g_error_free(gerr);
|
|
}
|
|
|
|
pending_request_free(p);
|
|
}
|
|
|
|
obc_session_unref(session);
|
|
}
|
|
|
|
static void session_terminate_transfer(struct obc_session *session,
|
|
struct obc_transfer *transfer,
|
|
GError *gerr)
|
|
{
|
|
struct pending_request *p = session->p;
|
|
|
|
if (p == NULL || p->transfer != transfer)
|
|
return;
|
|
|
|
obc_session_ref(session);
|
|
|
|
if (p->func)
|
|
p->func(session, gerr, p->data);
|
|
|
|
pending_request_free(p);
|
|
session->p = NULL;
|
|
|
|
session_process_queue(session);
|
|
|
|
obc_session_unref(session);
|
|
}
|
|
|
|
static void session_notify_complete(struct obc_session *session,
|
|
struct obc_transfer *transfer)
|
|
{
|
|
struct obc_agent *agent = session->agent;
|
|
const char *path;
|
|
|
|
path = obc_transfer_get_path(transfer);
|
|
|
|
if (agent == NULL || path == NULL)
|
|
goto done;
|
|
|
|
obc_agent_notify_complete(agent, path);
|
|
|
|
done:
|
|
|
|
DBG("Transfer(%p) complete", transfer);
|
|
|
|
session_terminate_transfer(session, transfer, NULL);
|
|
}
|
|
|
|
static void session_notify_error(struct obc_session *session,
|
|
struct obc_transfer *transfer,
|
|
GError *err)
|
|
{
|
|
struct obc_agent *agent = session->agent;
|
|
const char *path;
|
|
|
|
path = obc_transfer_get_path(transfer);
|
|
if (agent == NULL || path == NULL)
|
|
goto done;
|
|
|
|
obc_agent_notify_error(agent, path, err->message);
|
|
|
|
done:
|
|
error("Transfer(%p) Error: %s", transfer, err->message);
|
|
|
|
session_terminate_transfer(session, transfer, err);
|
|
}
|
|
|
|
static void session_notify_progress(struct obc_session *session,
|
|
struct obc_transfer *transfer,
|
|
gint64 transferred)
|
|
{
|
|
struct obc_agent *agent = session->agent;
|
|
const char *path;
|
|
|
|
path = obc_transfer_get_path(transfer);
|
|
if (agent == NULL || path == NULL)
|
|
goto done;
|
|
|
|
obc_agent_notify_progress(agent, path, transferred);
|
|
|
|
done:
|
|
DBG("Transfer(%p) progress: %ld bytes", transfer,
|
|
(long int ) transferred);
|
|
|
|
if (transferred == obc_transfer_get_size(transfer))
|
|
session_notify_complete(session, transfer);
|
|
}
|
|
|
|
static void transfer_progress(struct obc_transfer *transfer,
|
|
gint64 transferred, GError *err,
|
|
void *user_data)
|
|
{
|
|
struct obc_session *session = user_data;
|
|
|
|
if (err != 0)
|
|
goto fail;
|
|
|
|
session_notify_progress(session, transfer, transferred);
|
|
|
|
return;
|
|
|
|
fail:
|
|
session_notify_error(session, transfer, err);
|
|
}
|
|
|
|
static void session_prepare_get(gpointer data, gpointer user_data)
|
|
{
|
|
struct obc_session *session = data;
|
|
struct obc_transfer *transfer = user_data;
|
|
int ret;
|
|
|
|
ret = obc_transfer_get(transfer, transfer_progress, session);
|
|
if (ret < 0) {
|
|
GError *gerr = NULL;
|
|
|
|
g_set_error(&gerr, OBEX_IO_ERROR, ret, "%s", strerror(-ret));
|
|
session_notify_error(session, transfer, gerr);
|
|
g_clear_error(&gerr);
|
|
return;
|
|
}
|
|
|
|
DBG("Transfer(%p) started", transfer);
|
|
}
|
|
|
|
int obc_session_get(struct obc_session *session, const char *type,
|
|
const char *filename, const char *targetname,
|
|
const guint8 *apparam, gint apparam_size,
|
|
session_callback_t func, void *user_data)
|
|
{
|
|
struct obc_transfer *transfer;
|
|
struct obc_transfer_params *params = NULL;
|
|
const char *agent;
|
|
|
|
if (session->obex == NULL)
|
|
return -ENOTCONN;
|
|
|
|
if (apparam != NULL) {
|
|
params = g_new0(struct obc_transfer_params, 1);
|
|
params->data = g_new(guint8, apparam_size);
|
|
memcpy(params->data, apparam, apparam_size);
|
|
params->size = apparam_size;
|
|
}
|
|
|
|
if (session->agent)
|
|
agent = obc_agent_get_name(session->agent);
|
|
else
|
|
agent = NULL;
|
|
|
|
transfer = obc_transfer_register(session->conn, session->obex,
|
|
agent, filename,
|
|
targetname, type,
|
|
params);
|
|
if (transfer == NULL) {
|
|
if (params != NULL) {
|
|
g_free(params->data);
|
|
g_free(params);
|
|
}
|
|
return -EIO;
|
|
}
|
|
|
|
return session_request(session, transfer, session_prepare_get,
|
|
func, user_data);
|
|
}
|
|
|
|
int obc_session_send(struct obc_session *session, const char *filename,
|
|
const char *targetname)
|
|
{
|
|
struct obc_transfer *transfer;
|
|
const char *agent;
|
|
int err;
|
|
|
|
if (session->obex == NULL)
|
|
return -ENOTCONN;
|
|
|
|
agent = obc_agent_get_name(session->agent);
|
|
|
|
transfer = obc_transfer_register(session->conn, session->obex,
|
|
agent, filename,
|
|
targetname, NULL,
|
|
NULL);
|
|
if (transfer == NULL)
|
|
return -EINVAL;
|
|
|
|
err = obc_transfer_set_file(transfer);
|
|
if (err < 0) {
|
|
obc_transfer_unregister(transfer);
|
|
return err;
|
|
}
|
|
|
|
return session_request(session, transfer, session_prepare_put,
|
|
NULL, NULL);
|
|
}
|
|
|
|
int obc_session_pull(struct obc_session *session,
|
|
const char *type, const char *filename,
|
|
session_callback_t function, void *user_data)
|
|
{
|
|
struct obc_transfer *transfer;
|
|
const char *agent;
|
|
|
|
if (session->obex == NULL)
|
|
return -ENOTCONN;
|
|
|
|
if (session->agent != NULL)
|
|
agent = obc_agent_get_name(session->agent);
|
|
else
|
|
agent = NULL;
|
|
|
|
transfer = obc_transfer_register(session->conn, session->obex,
|
|
agent, NULL,
|
|
filename, type,
|
|
NULL);
|
|
if (transfer == NULL) {
|
|
return -EIO;
|
|
}
|
|
|
|
return session_request(session, transfer, session_prepare_get,
|
|
function, user_data);
|
|
}
|
|
|
|
const char *obc_session_register(struct obc_session *session,
|
|
GDBusDestroyFunction destroy)
|
|
{
|
|
if (session->path)
|
|
return session->path;
|
|
|
|
session->path = g_strdup_printf("%s/session%ju",
|
|
SESSION_BASEPATH, counter++);
|
|
|
|
if (g_dbus_register_interface(session->conn, session->path,
|
|
SESSION_INTERFACE, session_methods,
|
|
NULL, NULL, session, destroy) == FALSE)
|
|
goto fail;
|
|
|
|
if (session->driver->probe && session->driver->probe(session) < 0) {
|
|
g_dbus_unregister_interface(session->conn, session->path,
|
|
SESSION_INTERFACE);
|
|
goto fail;
|
|
}
|
|
|
|
DBG("Session(%p) registered %s", session, session->path);
|
|
|
|
return session->path;
|
|
|
|
fail:
|
|
g_free(session->path);
|
|
session->path = NULL;
|
|
return NULL;
|
|
}
|
|
|
|
static void session_prepare_put(gpointer data, gpointer user_data)
|
|
{
|
|
struct obc_session *session = data;
|
|
struct obc_transfer *transfer = user_data;
|
|
int ret;
|
|
|
|
ret = obc_transfer_put(transfer, transfer_progress, session);
|
|
if (ret < 0) {
|
|
GError *gerr = NULL;
|
|
|
|
g_set_error(&gerr, OBEX_IO_ERROR, ret, "%s (%d)",
|
|
strerror(-ret), -ret);
|
|
session_notify_error(session, transfer, gerr);
|
|
g_clear_error(&gerr);
|
|
return;
|
|
}
|
|
|
|
DBG("Transfer(%p) started", transfer);
|
|
}
|
|
|
|
int obc_session_put(struct obc_session *session, char *buf, const char *targetname)
|
|
{
|
|
struct obc_transfer *transfer;
|
|
const char *agent;
|
|
|
|
if (session->obex == NULL) {
|
|
g_free(buf);
|
|
return -ENOTCONN;
|
|
}
|
|
|
|
agent = obc_agent_get_name(session->agent);
|
|
|
|
transfer = obc_transfer_register(session->conn, session->obex,
|
|
agent, NULL,
|
|
targetname, NULL,
|
|
NULL);
|
|
if (transfer == NULL) {
|
|
g_free(buf);
|
|
return -EIO;
|
|
}
|
|
|
|
obc_transfer_set_buffer(transfer, buf);
|
|
|
|
return session_request(session, transfer, session_prepare_put,
|
|
NULL, NULL);
|
|
}
|
|
|
|
static void agent_destroy(gpointer data, gpointer user_data)
|
|
{
|
|
struct obc_session *session = user_data;
|
|
|
|
session->agent = NULL;
|
|
}
|
|
|
|
int obc_session_set_agent(struct obc_session *session, const char *name,
|
|
const char *path)
|
|
{
|
|
struct obc_agent *agent;
|
|
|
|
if (session == NULL)
|
|
return -EINVAL;
|
|
|
|
if (session->agent)
|
|
return -EALREADY;
|
|
|
|
agent = obc_agent_create(session->conn, name, path, agent_destroy,
|
|
session);
|
|
|
|
if (session->watch == 0)
|
|
obc_session_set_owner(session, name, owner_disconnected);
|
|
|
|
session->agent = agent;
|
|
|
|
return 0;
|
|
}
|
|
|
|
const char *obc_session_get_agent(struct obc_session *session)
|
|
{
|
|
struct obc_agent *agent;
|
|
|
|
if (session == NULL)
|
|
return NULL;
|
|
|
|
agent = session->agent;
|
|
if (agent == NULL)
|
|
return NULL;
|
|
|
|
return obc_agent_get_name(session->agent);
|
|
}
|
|
|
|
const char *obc_session_get_owner(struct obc_session *session)
|
|
{
|
|
if (session == NULL)
|
|
return NULL;
|
|
|
|
return session->owner;
|
|
}
|
|
|
|
const char *obc_session_get_path(struct obc_session *session)
|
|
{
|
|
return session->path;
|
|
}
|
|
|
|
const char *obc_session_get_target(struct obc_session *session)
|
|
{
|
|
return session->driver->target;
|
|
}
|
|
|
|
static struct obc_transfer *obc_session_get_transfer(
|
|
struct obc_session *session)
|
|
{
|
|
if (session->p == NULL)
|
|
return NULL;
|
|
|
|
return session->p->transfer;
|
|
}
|
|
|
|
const char *obc_session_get_buffer(struct obc_session *session, size_t *size)
|
|
{
|
|
struct obc_transfer *transfer;
|
|
const char *buf;
|
|
|
|
transfer = obc_session_get_transfer(session);
|
|
if (transfer == NULL)
|
|
return NULL;
|
|
|
|
buf = obc_transfer_get_buffer(transfer, size);
|
|
|
|
obc_transfer_clear_buffer(transfer);
|
|
|
|
return buf;
|
|
}
|
|
|
|
void *obc_session_get_params(struct obc_session *session, size_t *size)
|
|
{
|
|
struct obc_transfer *transfer;
|
|
struct obc_transfer_params params;
|
|
|
|
transfer= obc_session_get_transfer(session);
|
|
if (transfer == NULL)
|
|
return NULL;
|
|
|
|
if (obc_transfer_get_params(transfer, ¶ms) < 0)
|
|
return NULL;
|
|
|
|
if (size)
|
|
*size = params.size;
|
|
|
|
return params.data;
|
|
}
|
|
|
|
static void setpath_complete(struct obc_session *session, GError *err,
|
|
void *user_data)
|
|
{
|
|
struct pending_request *p = user_data;
|
|
struct setpath_data *data = p->data;
|
|
|
|
if (data->func)
|
|
data->func(session, err, data->user_data);
|
|
|
|
g_strfreev(data->remaining);
|
|
g_free(data);
|
|
|
|
if (session->p == p)
|
|
session->p = NULL;
|
|
|
|
pending_request_free(p);
|
|
|
|
session_process_queue(session);
|
|
}
|
|
|
|
static void setpath_cb(GObex *obex, GError *err, GObexPacket *rsp,
|
|
gpointer user_data)
|
|
{
|
|
struct pending_request *p = user_data;
|
|
struct setpath_data *data = p->data;
|
|
char *next;
|
|
guint8 code;
|
|
|
|
p->req_id = 0;
|
|
|
|
if (err != NULL) {
|
|
setpath_complete(p->session, err, user_data);
|
|
return;
|
|
}
|
|
|
|
code = g_obex_packet_get_operation(rsp, NULL);
|
|
if (code != G_OBEX_RSP_SUCCESS) {
|
|
GError *gerr = NULL;
|
|
g_set_error(&gerr, OBEX_IO_ERROR, code, "%s",
|
|
g_obex_strerror(code));
|
|
setpath_complete(p->session, err, user_data);
|
|
g_clear_error(&gerr);
|
|
return;
|
|
}
|
|
|
|
next = data->remaining[data->index];
|
|
if (next == NULL) {
|
|
setpath_complete(p->session, NULL, user_data);
|
|
return;
|
|
}
|
|
|
|
data->index++;
|
|
|
|
p->req_id = g_obex_setpath(obex, next, setpath_cb, p, &err);
|
|
if (err != NULL) {
|
|
setpath_complete(p->session, err, data);
|
|
g_error_free(err);
|
|
}
|
|
}
|
|
|
|
guint obc_session_setpath(struct obc_session *session, const char *path,
|
|
session_callback_t func, void *user_data,
|
|
GError **err)
|
|
{
|
|
struct setpath_data *data;
|
|
struct pending_request *p;
|
|
const char *first = "";
|
|
|
|
if (session->obex == NULL) {
|
|
g_set_error(err, OBEX_IO_ERROR, OBEX_IO_DISCONNECTED,
|
|
"Session disconnected");
|
|
return 0;
|
|
}
|
|
|
|
if (session->p != NULL) {
|
|
g_set_error(err, OBEX_IO_ERROR, OBEX_IO_BUSY,
|
|
"Session busy");
|
|
return 0;
|
|
}
|
|
|
|
data = g_new0(struct setpath_data, 1);
|
|
data->func = func;
|
|
data->user_data = user_data;
|
|
data->remaining = g_strsplit(path, "/", 3);
|
|
|
|
p = pending_request_new(session, NULL, NULL, setpath_complete, data);
|
|
|
|
/* Relative path */
|
|
if (path[0] != '/') {
|
|
first = data->remaining[data->index];
|
|
data->index++;
|
|
}
|
|
|
|
p->req_id = g_obex_setpath(session->obex, first, setpath_cb, p, err);
|
|
if (*err != NULL)
|
|
goto fail;
|
|
|
|
session->p = p;
|
|
|
|
return p->id;
|
|
|
|
fail:
|
|
g_strfreev(data->remaining);
|
|
g_free(data);
|
|
pending_request_free(p);
|
|
return 0;
|
|
}
|
|
|
|
static void async_cb(GObex *obex, GError *err, GObexPacket *rsp,
|
|
gpointer user_data)
|
|
{
|
|
struct pending_request *p = user_data;
|
|
struct obc_session *session = p->session;
|
|
GError *gerr = NULL;
|
|
uint8_t code;
|
|
|
|
p->req_id = 0;
|
|
|
|
if (err != NULL) {
|
|
if (p->func)
|
|
p->func(p->session, err, p->data);
|
|
goto done;
|
|
}
|
|
|
|
code = g_obex_packet_get_operation(rsp, NULL);
|
|
if (code != G_OBEX_RSP_SUCCESS)
|
|
g_set_error(&gerr, OBEX_IO_ERROR, code, "%s",
|
|
g_obex_strerror(code));
|
|
|
|
if (p->func)
|
|
p->func(p->session, gerr, p->data);
|
|
|
|
if (gerr != NULL)
|
|
g_clear_error(&gerr);
|
|
|
|
done:
|
|
pending_request_free(p);
|
|
session->p = NULL;
|
|
|
|
session_process_queue(session);
|
|
}
|
|
|
|
guint obc_session_mkdir(struct obc_session *session, const char *folder,
|
|
session_callback_t func, void *user_data,
|
|
GError **err)
|
|
{
|
|
struct pending_request *p;
|
|
|
|
if (session->obex == NULL) {
|
|
g_set_error(err, OBEX_IO_ERROR, OBEX_IO_DISCONNECTED,
|
|
"Session disconnected");
|
|
return 0;
|
|
}
|
|
|
|
if (session->p != NULL) {
|
|
g_set_error(err, OBEX_IO_ERROR, OBEX_IO_BUSY, "Session busy");
|
|
return 0;
|
|
}
|
|
|
|
|
|
p = pending_request_new(session, NULL, NULL, func, user_data);
|
|
|
|
p->req_id = g_obex_mkdir(session->obex, folder, async_cb, p, err);
|
|
if (*err != NULL) {
|
|
pending_request_free(p);
|
|
return 0;
|
|
}
|
|
|
|
session->p = p;
|
|
return p->id;
|
|
}
|
|
|
|
guint obc_session_copy(struct obc_session *session, const char *filename,
|
|
const char *destname, session_callback_t func,
|
|
void *user_data, GError **err)
|
|
{
|
|
struct pending_request *p;
|
|
|
|
if (session->obex == NULL) {
|
|
g_set_error(err, OBEX_IO_ERROR, OBEX_IO_DISCONNECTED,
|
|
"Session disconnected");
|
|
return 0;
|
|
}
|
|
|
|
if (session->p != NULL) {
|
|
g_set_error(err, OBEX_IO_ERROR, OBEX_IO_BUSY, "Session busy");
|
|
return 0;
|
|
}
|
|
|
|
p = pending_request_new(session, NULL, NULL, func, user_data);
|
|
|
|
p->req_id = g_obex_copy(session->obex, filename, destname, async_cb, p,
|
|
err);
|
|
if (*err != NULL) {
|
|
pending_request_free(p);
|
|
return 0;
|
|
}
|
|
|
|
session->p = p;
|
|
return p->id;
|
|
}
|
|
|
|
guint obc_session_move(struct obc_session *session, const char *filename,
|
|
const char *destname, session_callback_t func,
|
|
void *user_data, GError **err)
|
|
{
|
|
struct pending_request *p;
|
|
|
|
if (session->obex == NULL) {
|
|
g_set_error(err, OBEX_IO_ERROR, OBEX_IO_DISCONNECTED,
|
|
"Session disconnected");
|
|
return 0;
|
|
}
|
|
|
|
if (session->p != NULL) {
|
|
g_set_error(err, OBEX_IO_ERROR, OBEX_IO_BUSY, "Session busy");
|
|
return 0;
|
|
}
|
|
|
|
p = pending_request_new(session, NULL, NULL, func, user_data);
|
|
|
|
p->req_id = g_obex_move(session->obex, filename, destname, async_cb, p,
|
|
err);
|
|
if (*err != NULL) {
|
|
pending_request_free(p);
|
|
return 0;
|
|
}
|
|
|
|
session->p = p;
|
|
return p->id;
|
|
}
|
|
|
|
guint obc_session_delete(struct obc_session *session, const char *file,
|
|
session_callback_t func, void *user_data,
|
|
GError **err)
|
|
{
|
|
struct pending_request *p;
|
|
|
|
if (session->obex == NULL) {
|
|
g_set_error(err, OBEX_IO_ERROR, OBEX_IO_DISCONNECTED,
|
|
"Session disconnected");
|
|
return 0;
|
|
}
|
|
|
|
if (session->p != NULL) {
|
|
g_set_error(err, OBEX_IO_ERROR, OBEX_IO_BUSY, "Session busy");
|
|
return 0;
|
|
}
|
|
|
|
p = pending_request_new(session, NULL, NULL, func, user_data);
|
|
|
|
p->req_id = g_obex_delete(session->obex, file, async_cb, p, err);
|
|
if (*err != NULL) {
|
|
pending_request_free(p);
|
|
return 0;
|
|
}
|
|
|
|
session->p = p;
|
|
return p->id;
|
|
}
|
|
|
|
void obc_session_cancel(struct obc_session *session, guint id,
|
|
gboolean remove)
|
|
{
|
|
struct pending_request *p = session->p;
|
|
|
|
if (p == NULL || p->id != id)
|
|
return;
|
|
|
|
if (p->req_id == 0)
|
|
return;
|
|
|
|
g_obex_cancel_req(session->obex, p->req_id, remove);
|
|
if (!remove)
|
|
return;
|
|
|
|
pending_request_free(p);
|
|
session->p = NULL;
|
|
|
|
session_process_queue(session);
|
|
}
|