/* GIO - GLib Input, Output and Streaming Library * * Copyright (C) Carl-Anton Ingmarsson 2011 * * 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., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. * * Author: Carl-Anton Ingmarsson */ #include #include #include #include #include "gvfsafpconnection.h" /* * GVfsAfpName */ struct _GVfsAfpName { guint32 text_encoding; gchar *str; gsize len; gint ref_count; }; static void _g_vfs_afp_name_free (GVfsAfpName *afp_name) { g_free (afp_name->str); g_slice_free (GVfsAfpName, afp_name); } void g_vfs_afp_name_unref (GVfsAfpName *afp_name) { if (g_atomic_int_dec_and_test (&afp_name->ref_count)) _g_vfs_afp_name_free (afp_name); } void g_vfs_afp_name_ref (GVfsAfpName *afp_name) { g_atomic_int_inc (&afp_name->ref_count); } char * g_vfs_afp_name_get_string (GVfsAfpName *afp_name) { return g_utf8_normalize (afp_name->str, afp_name->len, G_NORMALIZE_DEFAULT_COMPOSE); } GVfsAfpName * g_vfs_afp_name_new (guint32 text_encoding, gchar *str, gsize len) { GVfsAfpName *afp_name; afp_name = g_slice_new (GVfsAfpName); afp_name->ref_count = 1; afp_name->text_encoding = text_encoding; afp_name->str = str; afp_name->len = len; return afp_name; } /* * GVfsAfpReply */ struct _GVfsAfpReplyClass { GObjectClass parent_class; }; struct _GVfsAfpReply { GObject parent_instance; AfpResultCode result_code; char *data; gsize len; gboolean free_data; goffset pos; }; G_DEFINE_TYPE (GVfsAfpReply, g_vfs_afp_reply, G_TYPE_OBJECT); static void g_vfs_afp_reply_init (GVfsAfpReply *reply) { reply->data = NULL; reply->len = 0; reply->pos = 0; } static void g_vfs_afp_reply_finalize (GObject *object) { GVfsAfpReply *reply = (GVfsAfpReply *)object; if (reply->free_data) g_free (reply->data); } static void g_vfs_afp_reply_class_init (GVfsAfpReplyClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->finalize = g_vfs_afp_reply_finalize; } static GVfsAfpReply * g_vfs_afp_reply_new (AfpResultCode result_code, char *data, gsize len, gboolean take_data) { GVfsAfpReply *reply; reply = g_object_new (G_VFS_TYPE_AFP_REPLY, NULL); reply->result_code = result_code; reply->len = len; reply->data = data; reply->free_data = take_data; return reply; } gboolean g_vfs_afp_reply_read_byte (GVfsAfpReply *reply, guint8 *byte) { if ((reply->len - reply->pos) < 1) return FALSE; if (byte) *byte = reply->data[reply->pos]; reply->pos++; return TRUE; } gboolean g_vfs_afp_reply_read_int64 (GVfsAfpReply *reply, gint64 *val) { if ((reply->len - reply->pos) < 8) return FALSE; if (val) *val = GINT64_FROM_BE (*((gint64 *)(reply->data + reply->pos))); reply->pos += 8; return TRUE; } gboolean g_vfs_afp_reply_read_int32 (GVfsAfpReply *reply, gint32 *val) { if ((reply->len - reply->pos) < 4) return FALSE; if (val) *val = GINT32_FROM_BE (*((gint32 *)(reply->data + reply->pos))); reply->pos += 4; return TRUE; } gboolean g_vfs_afp_reply_read_int16 (GVfsAfpReply *reply, gint16 *val) { if ((reply->len - reply->pos) < 2) return FALSE; if (val) *val = GINT16_FROM_BE (*((gint16 *)(reply->data + reply->pos))); reply->pos += 2; return TRUE; } gboolean g_vfs_afp_reply_read_uint64 (GVfsAfpReply *reply, guint64 *val) { if ((reply->len - reply->pos) < 8) return FALSE; if (val) *val = GUINT64_FROM_BE (*((guint64 *)(reply->data + reply->pos))); reply->pos += 8; return TRUE; } gboolean g_vfs_afp_reply_read_uint32 (GVfsAfpReply *reply, guint32 *val) { if ((reply->len - reply->pos) < 4) return FALSE; if (val) *val = GUINT32_FROM_BE (*((guint32 *)(reply->data + reply->pos))); reply->pos += 4; return TRUE; } gboolean g_vfs_afp_reply_read_uint16 (GVfsAfpReply *reply, guint16 *val) { if ((reply->len - reply->pos) < 2) return FALSE; if (val) *val = GUINT16_FROM_BE (*((guint16 *)(reply->data + reply->pos))); reply->pos += 2; return TRUE; } gboolean g_vfs_afp_reply_get_data (GVfsAfpReply *reply, gsize size, guint8 **data) { if ((reply->len - reply->pos) < size) return FALSE; if (data) *data = (guint8 *)(reply->data + reply->pos); reply->pos += size; return TRUE; } gboolean g_vfs_afp_reply_dup_data (GVfsAfpReply *reply, gsize size, guint8 **data) { if ((reply->len - reply->pos) < size) return FALSE; if (data) { *data = g_malloc (size); memcpy (*data, reply->data + reply->pos, size); } reply->pos += size; return TRUE; } gboolean g_vfs_afp_reply_read_pascal (GVfsAfpReply *reply, gboolean is_utf8, char **str) { guint8 strsize; if (!g_vfs_afp_reply_read_byte (reply, &strsize)) return FALSE; if (strsize > (reply->len - reply->pos)) { reply->pos--; return FALSE; } if (str) { if (is_utf8) { char *tmp; if (!g_vfs_afp_reply_get_data (reply, strsize, (guint8 **)&tmp)) { reply->pos--; return FALSE; } *str = g_utf8_normalize (tmp, strsize, G_NORMALIZE_DEFAULT_COMPOSE); } else { *str = g_convert (reply->data + reply->pos, strsize, "UTF-8", "MACINTOSH", NULL, NULL, NULL); reply->pos += strsize; } } else { reply->pos += strsize; } return TRUE; } gboolean g_vfs_afp_reply_read_afp_name (GVfsAfpReply *reply, gboolean read_text_encoding, GVfsAfpName **afp_name) { gint old_pos; guint32 text_encoding; guint16 len; gchar *str; old_pos = reply->pos; if (read_text_encoding) { if (!g_vfs_afp_reply_read_uint32 (reply, &text_encoding)) return FALSE; } else text_encoding = 0; if (!g_vfs_afp_reply_read_uint16 (reply, &len)) { reply->pos = old_pos; return FALSE; } if (!g_vfs_afp_reply_get_data (reply, len, (guint8 **)&str)) { reply->pos = old_pos; return FALSE; } if (afp_name) *afp_name = g_vfs_afp_name_new (text_encoding, g_strndup (str, len), len); return TRUE; } gboolean g_vfs_afp_reply_seek (GVfsAfpReply *reply, goffset offset, GSeekType type) { goffset absolute; switch (type) { case G_SEEK_CUR: absolute = reply->pos + offset; break; case G_SEEK_SET: absolute = offset; break; case G_SEEK_END: absolute = reply->len + offset; break; default: return FALSE; } if (absolute < 0 || absolute >= reply->len) return FALSE; reply->pos = absolute; return TRUE; } gboolean g_vfs_afp_reply_skip_to_even (GVfsAfpReply *reply) { if ((reply->pos % 2) == 0) return TRUE; if ((reply->len - reply->pos) < 1) return FALSE; reply->pos++; return TRUE; } goffset g_vfs_afp_reply_get_pos (GVfsAfpReply *reply) { return reply->pos; } gsize g_vfs_afp_reply_get_size (GVfsAfpReply *reply) { return reply->len; } AfpResultCode g_vfs_afp_reply_get_result_code (GVfsAfpReply *reply) { return reply->result_code; } /* * GVfsAfpCommand */ struct _GVfsAfpCommandClass { GDataOutputStreamClass parent_class; }; struct _GVfsAfpCommand { GDataOutputStream parent_instance; AfpCommandType type; char *buf; gsize buf_size; }; G_DEFINE_TYPE (GVfsAfpCommand, g_vfs_afp_command, G_TYPE_DATA_OUTPUT_STREAM); static void g_vfs_afp_command_init (GVfsAfpCommand *comm) { } static void g_vfs_afp_command_class_init (GVfsAfpCommandClass *klass) { } GVfsAfpCommand * g_vfs_afp_command_new (AfpCommandType type) { GOutputStream *mem_stream; GVfsAfpCommand *comm; mem_stream = g_memory_output_stream_new (NULL, 0, g_realloc, g_free); comm = g_object_new (G_VFS_TYPE_AFP_COMMAND, "base-stream", mem_stream, NULL); g_object_unref (mem_stream); comm->type = type; g_vfs_afp_command_put_byte (comm, type); return comm; } void g_vfs_afp_command_put_byte (GVfsAfpCommand *comm, guint8 byte) { g_data_output_stream_put_byte (G_DATA_OUTPUT_STREAM (comm), byte, NULL, NULL); } void g_vfs_afp_command_put_int16 (GVfsAfpCommand *comm, gint16 val) { g_data_output_stream_put_int16 (G_DATA_OUTPUT_STREAM (comm), val, NULL, NULL); } void g_vfs_afp_command_put_int32 (GVfsAfpCommand *comm, gint32 val) { g_data_output_stream_put_int32 (G_DATA_OUTPUT_STREAM (comm), val, NULL, NULL); } void g_vfs_afp_command_put_int64 (GVfsAfpCommand *comm, gint64 val) { g_data_output_stream_put_int64 (G_DATA_OUTPUT_STREAM (comm), val, NULL, NULL); } void g_vfs_afp_command_put_uint16 (GVfsAfpCommand *comm, guint16 val) { g_data_output_stream_put_uint16 (G_DATA_OUTPUT_STREAM (comm), val, NULL, NULL); } void g_vfs_afp_command_put_uint32 (GVfsAfpCommand *comm, guint32 val) { g_data_output_stream_put_uint32 (G_DATA_OUTPUT_STREAM (comm), val, NULL, NULL); } void g_vfs_afp_command_put_uint64 (GVfsAfpCommand *comm, guint64 val) { g_data_output_stream_put_uint64 (G_DATA_OUTPUT_STREAM (comm), val, NULL, NULL); } void g_vfs_afp_command_put_pascal (GVfsAfpCommand *comm, const char *str) { size_t len; if (str == NULL) { g_vfs_afp_command_put_byte (comm, 0); return; } len = MIN (strlen (str), 256); g_vfs_afp_command_put_byte (comm, len); g_output_stream_write (G_OUTPUT_STREAM (comm), str, len, NULL, NULL); } void g_vfs_afp_command_put_afp_name (GVfsAfpCommand *comm, GVfsAfpName *afp_name) { g_vfs_afp_command_put_uint32 (comm, afp_name->text_encoding); g_vfs_afp_command_put_uint16 (comm, afp_name->len); if (afp_name->len > 0) { g_output_stream_write_all (G_OUTPUT_STREAM (comm), afp_name->str, afp_name->len, NULL, NULL, NULL); } } static GVfsAfpName * filename_to_afp_pathname (const char *filename) { gsize len; char *str; gint i; while (*filename == '/') filename++; len = strlen (filename); str = g_malloc (len); for (i = 0; i < len; i++) { if (filename[i] == '/') str[i] = '\0'; else str[i] = filename[i]; } return g_vfs_afp_name_new (0x08000103, str, len); } void g_vfs_afp_command_put_pathname (GVfsAfpCommand *comm, const char *filename) { GVfsAfpName *pathname; /* PathType */ g_vfs_afp_command_put_byte (comm, AFP_PATH_TYPE_UTF8_NAME); /* Pathname */ pathname = filename_to_afp_pathname (filename); g_vfs_afp_command_put_afp_name (comm, pathname); g_vfs_afp_name_unref (pathname); } void g_vfs_afp_command_pad_to_even (GVfsAfpCommand *comm) { if (g_vfs_afp_command_get_size (comm) % 2 == 1) g_vfs_afp_command_put_byte (comm, 0); } gsize g_vfs_afp_command_get_size (GVfsAfpCommand *comm) { GMemoryOutputStream *mem_stream; mem_stream = G_MEMORY_OUTPUT_STREAM (g_filter_output_stream_get_base_stream (G_FILTER_OUTPUT_STREAM (comm))); return g_memory_output_stream_get_data_size (mem_stream); } char * g_vfs_afp_command_get_data (GVfsAfpCommand *comm) { GMemoryOutputStream *mem_stream; mem_stream = G_MEMORY_OUTPUT_STREAM (g_filter_output_stream_get_base_stream (G_FILTER_OUTPUT_STREAM (comm))); return g_memory_output_stream_get_data (mem_stream); } void g_vfs_afp_command_set_buffer (GVfsAfpCommand *comm, char *buf, gsize size) { g_return_if_fail (buf != NULL); g_return_if_fail (size > 0); comm->buf = buf; comm->buf_size = size; } /* * GVfsAfpConnection */ enum { ATTENTION, LAST_SIGNAL }; static guint signals[LAST_SIGNAL] = {0,}; typedef struct { guint8 flags; guint8 command; guint16 requestID; union { guint32 errorCode; guint32 writeOffset; }; guint32 totalDataLength; guint32 reserved; } DSIHeader; enum { STATE_INITIAL, STATE_CONNECTED, STATE_PENDING_CLOSE, STATE_CLOSED }; struct _GVfsAfpConnectionPrivate { GSocketConnectable *addr; GIOStream *stream; /* State */ volatile gint atomic_state; guint16 request_id; guint16 tickle_id; guint32 kRequestQuanta; guint32 kServerReplayCacheSize; GThread *worker_thread; GMainContext *worker_context; GMainLoop *worker_loop; GMutex mutex; GQueue *request_queue; GHashTable *request_hash; /* send loop */ gboolean send_loop_running; DSIHeader write_dsi_header; /* read loop */ GCancellable *read_cancellable; DSIHeader read_dsi_header; char *reply_buf; gboolean free_reply_buf; GSList *pending_closes; }; G_DEFINE_TYPE_WITH_PRIVATE (GVfsAfpConnection, g_vfs_afp_connection, G_TYPE_OBJECT); typedef enum { DSI_CLOSE_SESSION = 1, DSI_COMMAND = 2, DSI_GET_STATUS = 3, DSI_OPEN_SESSION = 4, DSI_TICKLE = 5, DSI_WRITE = 6, DSI_ATTENTION = 8 } DsiCommand; typedef enum { REQUEST_TYPE_COMMAND, REQUEST_TYPE_TICKLE } RequestType; typedef struct { RequestType type; GVfsAfpCommand *command; char *reply_buf; GTask *task; GVfsAfpConnection *conn; } RequestData; typedef struct { GMutex mutex; GCond cond; GVfsAfpConnection *conn; GCancellable *cancellable; gboolean res; GError **error; void *data; } SyncData; static void sync_data_init (SyncData *data, GVfsAfpConnection *conn, GError **error) { g_mutex_init (&data->mutex); g_cond_init (&data->cond); data->conn = conn; data->error = error; data->res = FALSE; } static void sync_data_clear (SyncData *data) { g_mutex_clear (&data->mutex); g_cond_clear (&data->cond); } static void sync_data_signal (SyncData *data) { g_mutex_lock (&data->mutex); g_cond_signal (&data->cond); g_mutex_unlock (&data->mutex); } static void sync_data_wait (SyncData *data) { g_mutex_lock (&data->mutex); g_cond_wait (&data->cond, &data->mutex); g_mutex_unlock (&data->mutex); } static void send_request_unlocked (GVfsAfpConnection *afp_connection); static void close_connection (GVfsAfpConnection *conn); static void read_reply (GVfsAfpConnection *afp_connection); static void free_request_data (RequestData *req_data) { if (req_data->command) g_object_unref (req_data->command); if (req_data->task) g_object_unref (req_data->task); g_slice_free (RequestData, req_data); } static gboolean check_open (GVfsAfpConnection *conn, GError **error) { GVfsAfpConnectionPrivate *priv = conn->priv; /* Acts as memory barrier */ gint state = g_atomic_int_get (&priv->atomic_state); if (state == STATE_INITIAL) { g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_INITIALIZED, _("The connection is not opened")); return FALSE; } else if (state == STATE_CLOSED) { g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_CLOSED, _("The connection is closed")); return FALSE; } return TRUE; } static void g_vfs_afp_connection_init (GVfsAfpConnection *afp_connection) { GVfsAfpConnectionPrivate *priv; afp_connection->priv = priv = g_vfs_afp_connection_get_instance_private (afp_connection); priv->kRequestQuanta = -1; priv->kServerReplayCacheSize = -1; g_mutex_init (&priv->mutex); priv->request_queue = g_queue_new (); priv->request_hash = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, (GDestroyNotify)free_request_data); priv->read_cancellable = g_cancellable_new (); priv->send_loop_running = FALSE; } static void g_vfs_afp_connection_finalize (GObject *object) { GVfsAfpConnection *afp_connection = (GVfsAfpConnection *)object; GVfsAfpConnectionPrivate *priv = afp_connection->priv; g_clear_object (&priv->addr); g_clear_object (&priv->stream); g_clear_object (&priv->read_cancellable); g_mutex_clear (&priv->mutex); G_OBJECT_CLASS (g_vfs_afp_connection_parent_class)->finalize (object); } static void g_vfs_afp_connection_class_init (GVfsAfpConnectionClass *klass) { GObjectClass* object_class = G_OBJECT_CLASS (klass); object_class->finalize = g_vfs_afp_connection_finalize; signals[ATTENTION] = g_signal_new ("attention", G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS, 0, NULL, NULL, g_cclosure_marshal_VOID__UINT, G_TYPE_NONE, 1, G_TYPE_UINT); } static guint16 get_request_id (GVfsAfpConnection *afp_connection) { GVfsAfpConnectionPrivate *priv = afp_connection->priv; return priv->request_id++; } static guint16 get_tickle_id (GVfsAfpConnection *afp_connection) { GVfsAfpConnectionPrivate *priv = afp_connection->priv; return priv->tickle_id++; } typedef struct { void *buffer; gsize count; gsize bytes_read; } ReadAllData; static void free_read_all_data (ReadAllData *read_data) { g_slice_free (ReadAllData, read_data); } static void read_all_cb (GObject *source_object, GAsyncResult *res, gpointer user_data) { GInputStream *stream = G_INPUT_STREAM (source_object); GTask *task = G_TASK (user_data); gssize bytes_read; GError *err = NULL; ReadAllData *read_data = g_task_get_task_data (task); bytes_read = g_input_stream_read_finish (stream, res, &err); if (bytes_read == -1) { g_task_return_error (task, err); g_object_unref (task); return; } else if (bytes_read == 0) { g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_CLOSED, _("Got EOS")); g_object_unref (task); return; } read_data->bytes_read += bytes_read; if (read_data->bytes_read < read_data->count) { g_input_stream_read_async (stream, (guint8 *)read_data->buffer + read_data->bytes_read, read_data->count - read_data->bytes_read, g_task_get_priority (task), g_task_get_cancellable (task), read_all_cb, task); return; } g_task_return_boolean (task, TRUE); g_object_unref (task); } static void read_all_async (GInputStream *stream, void *buffer, gsize count, int io_priority, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) { ReadAllData *read_data; GTask *task; task = g_task_new (stream, cancellable, callback, user_data); g_task_set_source_tag (task, read_all_async); g_task_set_priority (task, io_priority); read_data = g_slice_new0 (ReadAllData); read_data->buffer = buffer; read_data->count = count; g_task_set_task_data (task, read_data, (GDestroyNotify)free_read_all_data); g_input_stream_read_async (stream, buffer, count, io_priority, cancellable, read_all_cb, task); } static gboolean read_all_finish (GInputStream *stream, GAsyncResult *res, gsize *bytes_read, GError **error) { g_return_val_if_fail (g_task_is_valid (res, stream), FALSE); g_return_val_if_fail (g_async_result_is_tagged (res, read_all_async), FALSE); if (bytes_read) { ReadAllData *read_data; read_data = g_task_get_task_data (G_TASK (res)); *bytes_read = read_data->bytes_read; } return g_task_propagate_boolean (G_TASK (res), error); } static void dispatch_reply (GVfsAfpConnection *afp_connection) { GVfsAfpConnectionPrivate *priv = afp_connection->priv; DSIHeader *dsi_header = &priv->read_dsi_header; switch (dsi_header->command) { case DSI_CLOSE_SESSION: { g_warning ("Server closed session\n"); break; } case DSI_TICKLE: { RequestData *req_data; /* Send back a tickle message */ req_data = g_slice_new0 (RequestData); req_data->type = REQUEST_TYPE_TICKLE; req_data->conn = afp_connection; /* take lock */ g_mutex_lock (&priv->mutex); g_queue_push_head (priv->request_queue, req_data); if (!priv->send_loop_running) { priv->send_loop_running = TRUE; send_request_unlocked (afp_connection); } /* release lock */ g_mutex_unlock (&priv->mutex); break; } case DSI_ATTENTION: { guint8 attention_code; attention_code = priv->reply_buf[0] >> 4; g_signal_emit (afp_connection, signals[ATTENTION], 0, attention_code); break; } case DSI_COMMAND: case DSI_WRITE: { RequestData *req_data; req_data = g_hash_table_lookup (priv->request_hash, GUINT_TO_POINTER ((guint)dsi_header->requestID)); if (req_data) { GVfsAfpReply *reply; reply = g_vfs_afp_reply_new (dsi_header->errorCode, priv->reply_buf, dsi_header->totalDataLength, priv->free_reply_buf); priv->free_reply_buf = FALSE; g_task_return_pointer (req_data->task, reply, g_object_unref); g_hash_table_remove (priv->request_hash, GUINT_TO_POINTER ((guint)dsi_header->requestID)); } break; } default: g_assert_not_reached (); } } static void read_data_cb (GObject *object, GAsyncResult *res, gpointer user_data) { GInputStream *input = G_INPUT_STREAM (object); GVfsAfpConnection *afp_connection = G_VFS_AFP_CONNECTION (user_data); GVfsAfpConnectionPrivate *priv = afp_connection->priv; gboolean result; GError *err = NULL; if (g_atomic_int_get (&priv->atomic_state) == STATE_PENDING_CLOSE) { if (!priv->send_loop_running) close_connection (afp_connection); return; } result = read_all_finish (input, res, NULL, &err); if (!result) { if (g_error_matches (err, G_IO_ERROR, G_IO_ERROR_CLOSED) || g_error_matches (err, G_IO_ERROR, G_IO_ERROR_CONNECTION_CLOSED)) { g_message (_("Host closed connection")); } else { g_warning ("FAIL!!! \"%s\"\n", err->message); } exit (0); } dispatch_reply (afp_connection); if (priv->free_reply_buf) g_free (priv->reply_buf); priv->reply_buf = NULL; read_reply (afp_connection); } static void read_dsi_header_cb (GObject *object, GAsyncResult *res, gpointer user_data) { GInputStream *input = G_INPUT_STREAM (object); GVfsAfpConnection *afp_conn = G_VFS_AFP_CONNECTION (user_data); GVfsAfpConnectionPrivate *priv = afp_conn->priv; gboolean result; GError *err = NULL; DSIHeader *dsi_header; if (g_atomic_int_get (&priv->atomic_state) == STATE_PENDING_CLOSE) { if (!priv->send_loop_running) close_connection (afp_conn); return; } result = read_all_finish (input, res, NULL, &err); if (!result) { if (g_error_matches (err, G_IO_ERROR, G_IO_ERROR_CLOSED) || g_error_matches (err, G_IO_ERROR, G_IO_ERROR_CONNECTION_CLOSED)) { g_message (_("Host closed connection")); } else { g_warning ("FAIL!!! \"%s\"\n", err->message); } exit (0); } dsi_header = &priv->read_dsi_header; dsi_header->requestID = GUINT16_FROM_BE (dsi_header->requestID); dsi_header->errorCode = GUINT32_FROM_BE (dsi_header->errorCode); dsi_header->totalDataLength = GUINT32_FROM_BE (dsi_header->totalDataLength); if (dsi_header->totalDataLength > 0) { RequestData *req_data; req_data = g_hash_table_lookup (priv->request_hash, GUINT_TO_POINTER ((guint)dsi_header->requestID)); if (req_data && req_data->reply_buf) { priv->reply_buf = req_data->reply_buf; priv->free_reply_buf = FALSE; } else { priv->reply_buf = g_malloc (dsi_header->totalDataLength); priv->free_reply_buf = TRUE; } read_all_async (input, priv->reply_buf, dsi_header->totalDataLength, 0, priv->read_cancellable, read_data_cb, afp_conn); return; } dispatch_reply (afp_conn); read_reply (afp_conn); } static void read_reply (GVfsAfpConnection *afp_connection) { GVfsAfpConnectionPrivate *priv = afp_connection->priv; GInputStream *input; if (g_atomic_int_get (&priv->atomic_state) & STATE_PENDING_CLOSE) { if (!priv->send_loop_running) close_connection (afp_connection); return; } input = g_io_stream_get_input_stream (priv->stream); read_all_async (input, &priv->read_dsi_header, sizeof (DSIHeader), 0, priv->read_cancellable, read_dsi_header_cb, afp_connection); } typedef struct { const void *buffer; gsize count; gsize bytes_written; } WriteAllData; inline static void free_write_all_data (WriteAllData *write_data) { g_slice_free (WriteAllData, write_data); } static void write_all_cb (GObject *source_object, GAsyncResult *res, gpointer user_data) { GOutputStream *stream = G_OUTPUT_STREAM (source_object); GTask *task = G_TASK (user_data); gssize bytes_written; GError *err = NULL; WriteAllData *write_data = g_task_get_task_data (task); bytes_written = g_output_stream_write_finish (stream, res, &err); if (bytes_written == -1) { g_task_return_error (task, err); g_object_unref (task); return; } write_data->bytes_written += bytes_written; if (write_data->bytes_written < write_data->count) { g_output_stream_write_async (stream, (const guint8 *)write_data->buffer + write_data->bytes_written, write_data->count - write_data->bytes_written, g_task_get_priority (task), g_task_get_cancellable (task), write_all_cb, task); return; } g_task_return_boolean (task, TRUE); g_object_unref (task); } static void write_all_async (GOutputStream *stream, const void *buffer, gsize count, int io_priority, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) { GTask *task; WriteAllData *write_data; task = g_task_new (stream, cancellable, callback, user_data); g_task_set_source_tag (task, write_all_async); g_task_set_priority (task, io_priority); write_data = g_slice_new0 (WriteAllData); write_data->buffer = buffer; write_data->count = count; g_task_set_task_data (task, write_data, (GDestroyNotify)free_write_all_data); g_output_stream_write_async (stream, buffer, count, io_priority, cancellable, write_all_cb, task); } static gboolean write_all_finish (GOutputStream *stream, GAsyncResult *res, gsize *bytes_written, GError **error) { g_return_val_if_fail (g_task_is_valid (res, stream), FALSE); g_return_val_if_fail (g_async_result_is_tagged (res, write_all_async), FALSE); if (bytes_written) { WriteAllData *write_data; write_data = g_task_get_task_data (G_TASK (res)); *bytes_written = write_data->bytes_written; } return g_task_propagate_boolean (G_TASK (res), error); } #define HANDLE_RES() { \ gboolean result; \ GError *err = NULL; \ \ result = write_all_finish (output, res, NULL, &err); \ if (!result) \ { \ if (req_data->task) \ g_task_return_error (req_data->task, err); \ else \ g_error_free (err); \ \ g_hash_table_remove (priv->request_hash, \ GUINT_TO_POINTER ((guint)GUINT16_FROM_BE (priv->write_dsi_header.requestID))); \ free_request_data (req_data); \ \ g_mutex_lock (&priv->mutex); \ send_request_unlocked (afp_conn); \ g_mutex_unlock (&priv->mutex); \ return; \ } \ } static void write_buf_cb (GObject *object, GAsyncResult *res, gpointer user_data) { GOutputStream *output = G_OUTPUT_STREAM (object); RequestData *req_data = user_data; GVfsAfpConnection *afp_conn = req_data->conn; GVfsAfpConnectionPrivate *priv = afp_conn->priv; HANDLE_RES (); g_mutex_lock (&priv->mutex); send_request_unlocked (afp_conn); g_mutex_unlock (&priv->mutex); } static void write_command_cb (GObject *object, GAsyncResult *res, gpointer user_data) { GOutputStream *output = G_OUTPUT_STREAM (object); RequestData *req_data = user_data; GVfsAfpConnection *afp_conn = req_data->conn; GVfsAfpConnectionPrivate *priv = afp_conn->priv; HANDLE_RES (); if (priv->write_dsi_header.command == DSI_WRITE && req_data->command->buf) { write_all_async (output, req_data->command->buf, req_data->command->buf_size, 0, NULL, write_buf_cb, req_data); return; } g_mutex_lock (&priv->mutex); send_request_unlocked (afp_conn); g_mutex_unlock (&priv->mutex); } static void write_dsi_header_cb (GObject *object, GAsyncResult *res, gpointer user_data) { GOutputStream *output = G_OUTPUT_STREAM (object); RequestData *req_data = user_data; GVfsAfpConnection *afp_conn = req_data->conn; GVfsAfpConnectionPrivate *priv = afp_conn->priv; char *data; gsize size; HANDLE_RES (); if (req_data->type == REQUEST_TYPE_TICKLE) { g_mutex_lock (&priv->mutex); send_request_unlocked (afp_conn); g_mutex_unlock (&priv->mutex); return; } data = g_vfs_afp_command_get_data (req_data->command); size = g_vfs_afp_command_get_size (req_data->command); write_all_async (output, data, size, 0, NULL, write_command_cb, req_data); } static void send_request_unlocked (GVfsAfpConnection *afp_connection) { GVfsAfpConnectionPrivate *priv = afp_connection->priv; RequestData *req_data; guint32 writeOffset; guint8 dsi_command; while ((req_data = g_queue_pop_head (priv->request_queue))) { if (!req_data->task || !g_task_return_error_if_cancelled (req_data->task)) break; } if (!req_data) { priv->send_loop_running = FALSE; return; } switch (req_data->type) { case REQUEST_TYPE_TICKLE: priv->write_dsi_header.flags = 0x00; priv->write_dsi_header.command = DSI_TICKLE; priv->write_dsi_header.requestID = GUINT16_TO_BE (get_tickle_id (afp_connection)); priv->write_dsi_header.writeOffset = 0; priv->write_dsi_header.totalDataLength = 0; priv->write_dsi_header.reserved = 0; break; case REQUEST_TYPE_COMMAND: { gsize size; switch (req_data->command->type) { case AFP_COMMAND_WRITE: writeOffset = 8; dsi_command = DSI_WRITE; break; case AFP_COMMAND_WRITE_EXT: writeOffset = 20; dsi_command = DSI_WRITE; break; default: writeOffset = 0; dsi_command = DSI_COMMAND; break; } priv->write_dsi_header.flags = 0x00; priv->write_dsi_header.command = dsi_command; priv->write_dsi_header.requestID = GUINT16_TO_BE (get_request_id (afp_connection)); priv->write_dsi_header.writeOffset = GUINT32_TO_BE (writeOffset); /* totalDataLength */ size = g_vfs_afp_command_get_size (req_data->command); if (dsi_command == DSI_WRITE && req_data->command->buf) size += req_data->command->buf_size; priv->write_dsi_header.totalDataLength = GUINT32_TO_BE (size); priv->write_dsi_header.reserved = 0; break; } default: g_assert_not_reached (); } if (req_data->type != REQUEST_TYPE_TICKLE) g_hash_table_insert (priv->request_hash, GUINT_TO_POINTER ((guint)GUINT16_FROM_BE (priv->write_dsi_header.requestID)), req_data); write_all_async (g_io_stream_get_output_stream (priv->stream), &priv->write_dsi_header, sizeof (DSIHeader), 0, NULL, write_dsi_header_cb, req_data); } static gboolean start_send_loop_func (gpointer data) { GVfsAfpConnection *conn = data; GVfsAfpConnectionPrivate *priv = conn->priv; g_mutex_lock (&priv->mutex); if (priv->send_loop_running) goto out; priv->send_loop_running = TRUE; send_request_unlocked (conn); out: g_mutex_unlock (&priv->mutex); return G_SOURCE_REMOVE; } void g_vfs_afp_connection_send_command (GVfsAfpConnection *afp_connection, GVfsAfpCommand *command, char *reply_buf, GAsyncReadyCallback callback, GCancellable *cancellable, gpointer user_data) { GVfsAfpConnectionPrivate *priv = afp_connection->priv; GTask *task; GError *err = NULL; RequestData *req_data; task = g_task_new (afp_connection, cancellable, callback, user_data); g_task_set_source_tag (task, g_vfs_afp_connection_send_command); if (!check_open (afp_connection, &err)) { g_task_return_error (task, err); g_object_unref (task); return; } req_data = g_slice_new0 (RequestData); req_data->type = REQUEST_TYPE_COMMAND; req_data->command = g_object_ref (command); req_data->reply_buf = reply_buf; req_data->conn = afp_connection; req_data->task = task; /* Take lock */ g_mutex_lock (&priv->mutex); g_queue_push_tail (priv->request_queue, req_data); if (!priv->send_loop_running) { g_main_context_invoke (priv->worker_context, start_send_loop_func, afp_connection); } /* Release lock */ g_mutex_unlock (&priv->mutex); } GVfsAfpReply * g_vfs_afp_connection_send_command_finish (GVfsAfpConnection *afp_connection, GAsyncResult *res, GError **error) { g_return_val_if_fail (g_task_is_valid (res, afp_connection), NULL); g_return_val_if_fail (g_async_result_is_tagged (res, g_vfs_afp_connection_send_command), NULL); return g_task_propagate_pointer (G_TASK (res), error); } static gboolean read_reply_sync (GInputStream *input, DSIHeader *dsi_header, char **data, GCancellable *cancellable, GError **error) { gboolean res; gsize read_count, bytes_read; g_assert (dsi_header != NULL); read_count = sizeof (DSIHeader); res = g_input_stream_read_all (input, dsi_header, read_count, &bytes_read, cancellable, error); if (!res) return FALSE; if (bytes_read < read_count) { g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, _("Connection unexpectedly went down")); return FALSE; } dsi_header->requestID = GUINT16_FROM_BE (dsi_header->requestID); dsi_header->errorCode = GUINT32_FROM_BE (dsi_header->errorCode); dsi_header->totalDataLength = GUINT32_FROM_BE (dsi_header->totalDataLength); if (dsi_header->totalDataLength == 0) { *data = NULL; return TRUE; } *data = g_malloc (dsi_header->totalDataLength); read_count = dsi_header->totalDataLength; res = g_input_stream_read_all (input, *data, read_count, &bytes_read, cancellable, error); if (!res) { g_free (*data); return FALSE; } if (bytes_read < read_count) { g_free (*data); g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, _("Got unexpected end of stream")); return FALSE; } return TRUE; } static gboolean send_request_sync (GOutputStream *output, DsiCommand command, guint16 request_id, guint32 writeOffset, gsize len, const char *data, GCancellable *cancellable, GError **error) { DSIHeader dsi_header; gboolean res; gsize write_count, bytes_written; dsi_header.flags = 0x00; dsi_header.command = command; dsi_header.requestID = GUINT16_TO_BE (request_id); dsi_header.writeOffset = GUINT32_TO_BE (writeOffset); dsi_header.totalDataLength = GUINT32_TO_BE (len); dsi_header.reserved = 0; write_count = sizeof (DSIHeader); res = g_output_stream_write_all (output, &dsi_header, write_count, &bytes_written, cancellable, error); if (!res) return FALSE; if (data == NULL) return TRUE; write_count = len; res = g_output_stream_write_all (output, data, write_count, &bytes_written, cancellable, error); if (!res) return FALSE; return TRUE; } static void send_command_sync_cb (GObject *source_object, GAsyncResult *res, gpointer user_data) { SyncData *sync_data = user_data; sync_data->data = g_object_ref (res); sync_data_signal (sync_data); } GVfsAfpReply * g_vfs_afp_connection_send_command_sync (GVfsAfpConnection *afp_connection, GVfsAfpCommand *command, GCancellable *cancellable, GError **error) { SyncData sync_data; GVfsAfpReply *reply; if (!check_open (afp_connection, error)) return FALSE; sync_data_init (&sync_data, afp_connection, NULL); g_vfs_afp_connection_send_command (afp_connection, command, NULL, send_command_sync_cb, cancellable, &sync_data); sync_data_wait (&sync_data); reply = g_vfs_afp_connection_send_command_finish (afp_connection, sync_data.data, error); g_object_unref (sync_data.data); sync_data_clear (&sync_data); return reply; } static void close_connection (GVfsAfpConnection *conn) { GVfsAfpConnectionPrivate *priv = conn->priv; guint16 req_id; gboolean res; GError *err = NULL; GQueue *request_queue; GSList *pending_closes, *siter; GHashTable *request_hash; GHashTableIter iter; RequestData *req_data; /* Take lock */ g_mutex_lock (&priv->mutex); /* Set closed flag */ g_atomic_int_set (&priv->atomic_state, STATE_CLOSED); request_queue = priv->request_queue; priv->request_queue = NULL; request_hash = priv->request_hash; priv->request_hash = NULL; pending_closes = priv->pending_closes; priv->pending_closes = NULL; /* Release lock */ g_mutex_unlock (&priv->mutex); /* close DSI session */ req_id = get_request_id (conn); res = send_request_sync (g_io_stream_get_output_stream (priv->stream), DSI_CLOSE_SESSION, req_id, 0, 0, NULL, NULL, &err); if (!res) g_io_stream_close (priv->stream, NULL, NULL); else res = g_io_stream_close (priv->stream, NULL, &err); g_clear_object (&priv->stream); while ((req_data = g_queue_pop_head (request_queue))) { g_task_return_new_error (req_data->task, G_IO_ERROR, G_IO_ERROR_CLOSED, "Connection was closed"); free_request_data (req_data); } g_hash_table_iter_init (&iter, request_hash); while (g_hash_table_iter_next (&iter, NULL, (void **)&req_data)) { g_task_return_new_error (req_data->task, G_IO_ERROR, G_IO_ERROR_CLOSED, "Connection was closed"); free_request_data (req_data); } /* quit main_loop */ g_main_loop_quit (priv->worker_loop); g_main_loop_unref (priv->worker_loop); g_main_context_unref (priv->worker_context); for (siter = pending_closes; siter != NULL; siter = siter->next) { SyncData *close_data = siter->data; close_data->res = TRUE; sync_data_signal (close_data); } g_slist_free (pending_closes); } gboolean g_vfs_afp_connection_close_sync (GVfsAfpConnection *afp_connection, GCancellable *cancellable, GError **error) { GVfsAfpConnectionPrivate *priv = afp_connection->priv; SyncData close_data; /* Take lock */ g_mutex_lock (&priv->mutex); if (!check_open (afp_connection, error)) { g_mutex_unlock (&priv->mutex); return FALSE; } sync_data_init (&close_data, afp_connection, error); priv->pending_closes = g_slist_prepend (priv->pending_closes, &close_data); /* Release lock */ g_mutex_unlock (&priv->mutex); if (g_atomic_int_compare_and_exchange (&priv->atomic_state, STATE_CONNECTED, STATE_PENDING_CLOSE)) g_cancellable_cancel (priv->read_cancellable); sync_data_wait (&close_data); return close_data.res; } static gpointer open_thread_func (gpointer user_data) { SyncData *data = user_data; GVfsAfpConnection *conn = data->conn; GVfsAfpConnectionPrivate *priv = conn->priv; GSocketClient *client; GSocketConnection *connection; GSocket *socket; GError *error = NULL; guint16 req_id; gboolean res = FALSE; char *reply; DSIHeader dsi_header; guint pos; client = g_socket_client_new (); connection = g_socket_client_connect (client, priv->addr, data->cancellable, data->error); g_object_unref (client); if (!connection) goto out; socket = g_socket_connection_get_socket (connection); if (!g_socket_set_option (socket, IPPROTO_TCP, TCP_NODELAY, TRUE, &error)) { g_warning ("Could not set TCP_NODELAY: %s\n", error->message); g_error_free (error); } priv->stream = G_IO_STREAM (connection); req_id = get_request_id (conn); res = send_request_sync (g_io_stream_get_output_stream (priv->stream), DSI_OPEN_SESSION, req_id, 0, 0, NULL, data->cancellable, data->error); if (!res) goto out; res = read_reply_sync (g_io_stream_get_input_stream (priv->stream), &dsi_header, &reply, data->cancellable, data->error); if (!res) goto out; pos = 0; while ((dsi_header.totalDataLength - pos) > 2) { guint8 optionType; guint8 optionLength; optionType = reply[pos++]; optionLength = reply[pos++]; switch (optionType) { case 0x00: if (optionLength == 4 && (dsi_header.totalDataLength - pos) >= 4) priv->kRequestQuanta = GUINT32_FROM_BE (*(guint32 *)(reply + pos)); break; case 0x02: if (optionLength == 4 && (dsi_header.totalDataLength - pos) >= 4) priv->kServerReplayCacheSize = GUINT32_FROM_BE (*(guint32 *)(reply + pos)); break; default: g_debug ("Unknown DSI option\n"); } pos += optionLength; } g_free (reply); out: if (res) g_atomic_int_set (&priv->atomic_state, STATE_CONNECTED); /* Signal sync call thread */ data->res = res; sync_data_signal (data); /* Return from thread on failure */ if (!res) { g_clear_object (&priv->stream); return NULL; } /* Create MainLoop */ priv->worker_context = g_main_context_new (); priv->worker_loop = g_main_loop_new (priv->worker_context, TRUE); read_reply (conn); /* Run mainloop */ g_main_loop_run (priv->worker_loop); return NULL; } gboolean g_vfs_afp_connection_open_sync (GVfsAfpConnection *afp_connection, GCancellable *cancellable, GError **error) { GVfsAfpConnectionPrivate *priv = afp_connection->priv; SyncData data; sync_data_init (&data, afp_connection, error); data.cancellable = cancellable; priv->worker_thread = g_thread_new ("AFP Worker Thread", open_thread_func, &data); sync_data_wait (&data); sync_data_clear (&data); return data.res; } GVfsAfpConnection * g_vfs_afp_connection_new (GSocketConnectable *addr) { GVfsAfpConnection *afp_connection; GVfsAfpConnectionPrivate *priv; afp_connection = g_object_new (G_VFS_TYPE_AFP_CONNECTION, NULL); priv = afp_connection->priv; priv->addr = g_object_ref (addr); return afp_connection; } GVfsAfpReply * g_vfs_afp_query_server_info (GSocketConnectable *addr, GCancellable *cancellable, GError **error) { GSocketClient *client; GIOStream *conn; gboolean res; DSIHeader dsi_header; char *data; client = g_socket_client_new (); conn = G_IO_STREAM (g_socket_client_connect (client, addr, cancellable, error)); g_object_unref (client); if (!conn) return NULL; res = send_request_sync (g_io_stream_get_output_stream (conn), DSI_GET_STATUS, 0, 0, 0, NULL, cancellable, error); if (!res) { g_object_unref (conn); return NULL; } res = read_reply_sync (g_io_stream_get_input_stream (conn), &dsi_header, &data, cancellable, error); if (!res) { g_object_unref (conn); return NULL; } g_object_unref (conn); return g_vfs_afp_reply_new (dsi_header.errorCode, data, dsi_header.totalDataLength, TRUE); } guint32 g_vfs_afp_connection_get_max_request_size (GVfsAfpConnection *afp_connection) { g_return_val_if_fail (G_VFS_IS_AFP_CONNECTION (afp_connection), 0); return afp_connection->priv->kRequestQuanta; }