/* Jungle Monkey * Copyright (C) 1999-2001 The Regents of the University of Michigan * * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include #include #include #include #include #include #include "mtp_client.h" #include "mtp_server.h" #include "mtp_debug.h" #include "util/elf.h" #include "util/util.h" #define TIMEOUT 30000 #define MAX_LINE_LEN 64000 #define BUF_LEN 2048 /** Call graph: mtp_client_get_mirrors mtp_client_get_info mtp_client_get_info_conn mtp_client_get_mirror mtp_client_get_mirrors mtp_client_get_info_conn mtp_client_get_file_mirror mtp_client_get_file_rendezvous */ static MtpClient* mtp_client_new (const GURL* url, GConn* conn, gpointer func, gpointer user_data); static void mtp_client_connect (MtpClient* client, gchar* message, GConnFunc func); static MtpClient* mtp_client_get_info_conn (const GURL* url, GConn* conn, MtpClientInfoFunc func, gpointer user_data); static MtpClient* mtp_client_get_file_mirror_conn (const GURL* url, GConn* conn, MtpClientRecvFunc func, gpointer user_data); /* **************************************** */ static MtpClient* mtp_client_new (const GURL* url, GConn* conn, gpointer func, gpointer user_data) { MtpClient* client = NULL; client = g_new0 (MtpClient, 1); if (url) client->url = gnet_url_clone (url); if (conn) client->conn = conn; else if (url) client->conn = gnet_conn_new (url->hostname, url->port ? url->port : MTP_SERVER_PORT, NULL, NULL); client->func = func; client->user_data = user_data; return client; } static void mtp_client_connect (MtpClient* client, gchar* message, GConnFunc func) { ElfNode* jmpa; gchar* buffer; gint length; g_return_if_fail (client); g_return_if_fail (message); g_return_if_fail (func); g_return_if_fail (client->conn); g_return_if_fail (client->url); g_return_if_fail (client->func); client->conn->func = func; client->conn->user_data = client; /* Connect if not connected */ if (!gnet_conn_is_connected (client->conn)) gnet_conn_connect (client->conn, MTP_SERVER_TIMEOUT); /* Create the announcement */ jmpa = elf_new (message); elf_set_attribute (jmpa, "name", client->url->resource); elf_write (jmpa, &buffer, &length); elf_delete (jmpa); /* Send the announcement */ gnet_conn_write (client->conn, buffer, length, MTP_SERVER_TIMEOUT); } void mtp_client_delete (MtpClient* client) { if (client) { GSList* i; gnet_url_delete (client->url); gnet_conn_unref (client->conn, TRUE); g_free (client->buffer); if (client->file) { fclose (client->file); unlink (client->path); } g_free (client->path); gnet_sha_delete (client->sha); for (i = client->children; i != NULL; i = i->next) { MtpClient* c = (MtpClient*) i->data; mtp_client_delete (c); } g_free (client); } } /* ************************************************************ */ static gboolean mirror_gconn_func (GConn* conn, GConnStatus status, gchar* buffer, gint length, gpointer user_data); MtpClient* mtp_client_get_mirrors (const GURL* url, MtpClientMirrorsFunc func, gpointer user_data) { MtpClient* client = NULL; g_return_val_if_fail (url, NULL); g_return_val_if_fail (func, NULL); client = mtp_client_new (url, NULL, func, user_data); g_return_val_if_fail (client, NULL); mtp_client_connect (client, "GET_MIRRORS", mirror_gconn_func); return client; } static gboolean mirror_gconn_func (GConn* conn, GConnStatus status, gchar* buffer, gint length, gpointer user_data) { MtpClient* client = (MtpClient*) user_data; g_return_val_if_fail (client, FALSE); switch (status) { case GNET_CONN_STATUS_CONNECT: { /* Nothing */ break; } case GNET_CONN_STATUS_READ: { GList* as = NULL; ElfNode* a; gchar* mirrors; gchar** mirrors_split = NULL; gint i; GSList* conns = NULL; gchar* cname = NULL; g_return_val_if_fail (conn->inetaddr, FALSE); /* Read, hopefully, MIRRORS mirrors="blah..." */ as = elf_read_list (buffer, length); if (!as) goto done; a = (ElfNode*) as->data; g_return_val_if_fail (a && a->name, FALSE); /* name should be MIRRORS or ERROR */ if (strcmp (a->name, "MIRRORS")) goto done; mirrors = elf_get_attribute(a, "mirrors"); if (!mirrors) goto done; /* Parse the mirrors */ mirrors_split = g_strsplit (mirrors, ";", 16000); if (!mirrors_split) goto done; cname = gnet_inetaddr_get_canonical_name (conn->inetaddr); /* Examine each mirror */ for (i = 0; mirrors_split[i]; ++i) { gchar** pair_split; /* Parse the mirror. There may be additional arguments in the future - we allow up to 10. For now, we expect a just a hostname-port pair. */ pair_split = g_strsplit (mirrors_split[i], ",", 10); if (pair_split) { if (pair_split[0] && *pair_split[0] && pair_split[1] && *pair_split[1]) { gint port; port = atoi (pair_split[1]); /* If the mirror is the same as the rendezvous, reference the connection - we may reuse it. */ if (port == conn->port && !strcmp (pair_split[0], cname)) { gnet_conn_ref (conn); conns = g_slist_prepend (conns, conn); } /* Otherwise, create a new conn. */ else { GConn* new_conn; new_conn = gnet_conn_new (pair_split[0], port, NULL, NULL); conns = g_slist_prepend (conns, new_conn); } } g_strfreev (pair_split); } } /* Reverse the list. Now they are in the order they were sent. */ conns = g_slist_reverse (conns); done: if (as) elf_delete_list(as); /* Delete the mirrors */ if (mirrors_split) { for (i = 0; mirrors_split[i]; ++i) g_free (mirrors_split[i]); g_free (mirrors_split); } if (cname) g_free (cname); if (conns) ((MtpClientMirrorsFunc) client->func)(client, MTP_CLIENT_STATUS_OK, conns, client->user_data); else ((MtpClientMirrorsFunc) client->func)(client, MTP_CLIENT_STATUS_ERROR, NULL, client->user_data); return FALSE; break; } case GNET_CONN_STATUS_WRITE: { g_free (buffer); /* Read response */ gnet_conn_readline (conn, NULL, MAX_LINE_LEN, MTP_SERVER_TIMEOUT); break; } case GNET_CONN_STATUS_CLOSE: case GNET_CONN_STATUS_TIMEOUT: case GNET_CONN_STATUS_ERROR: { gnet_conn_disconnect (client->conn, TRUE); ((MtpClientMirrorsFunc) client->func)(client, MTP_CLIENT_STATUS_ERROR, NULL, client->user_data); break; } } return FALSE; } /* ************************************************************ */ static gboolean info_gconn_func (GConn* conn, GConnStatus status, gchar* buffer, gint length, gpointer user_data); MtpClient* mtp_client_get_info (const GURL* url, MtpClientInfoFunc func, gpointer user_data) { return mtp_client_get_info_conn (url, NULL, func, user_data); } static MtpClient* mtp_client_get_info_conn (const GURL* url, GConn* conn, MtpClientInfoFunc func, gpointer user_data) { MtpClient* client = NULL; g_return_val_if_fail (url, NULL); g_return_val_if_fail (func, NULL); client = mtp_client_new (url, conn, func, user_data); g_return_val_if_fail (client, NULL); mtp_client_connect (client, "GET_INFO", info_gconn_func); return client; } static gboolean info_gconn_func (GConn* conn, GConnStatus status, gchar* buffer, gint length, gpointer user_data) { MtpClient* client = (MtpClient*) user_data; g_return_val_if_fail (client, FALSE); switch (status) { case GNET_CONN_STATUS_CONNECT: { /* Nothing */ break; } case GNET_CONN_STATUS_READ: { GList* as = NULL; ElfNode* a; gboolean ok = FALSE; gchar* length_str; gint len = -1; /* Read, hopefully, INFO length="#" */ as = elf_read_list (buffer, length); if (!as) goto done; a = (ElfNode*) as->data; g_return_val_if_fail (a && a->name, FALSE); /* name should be INFO or ERROR */ if (strcmp (a->name, "INFO")) goto done; length_str = elf_get_attribute(a, "length"); if (length_str) len = atoi (length_str); if (len < 0) goto done; ok = TRUE; done: if (as) elf_delete_list (as); if (ok) ((MtpClientInfoFunc) client->func)(client, MTP_CLIENT_STATUS_OK, len, client->user_data); else ((MtpClientInfoFunc) client->func)(client, MTP_CLIENT_STATUS_ERROR, -1, client->user_data); break; } case GNET_CONN_STATUS_WRITE: { g_free (buffer); /* Read reponse */ gnet_conn_readline (conn, NULL, MAX_LINE_LEN, MTP_SERVER_TIMEOUT); break; } case GNET_CONN_STATUS_CLOSE: case GNET_CONN_STATUS_TIMEOUT: case GNET_CONN_STATUS_ERROR: { gnet_conn_disconnect (client->conn, TRUE); ((MtpClientInfoFunc) client->func)(client, MTP_CLIENT_STATUS_ERROR, -1, client->user_data); break; } } return FALSE; } /* ************************************************************ */ static void mirror_mirrors_func (MtpClient* client, MtpClientStatus status, GSList* conns, gpointer user_data); static void mirror_info_func (MtpClient* iclient, MtpClientStatus status, gint length, gpointer user_data); MtpClient* mtp_client_get_mirror (const GURL* url, MtpClientMirrorFunc func, gpointer user_data) { MtpClient* client; MtpClient* mclient; g_return_val_if_fail (url, NULL); g_return_val_if_fail (func, NULL); client = mtp_client_new (url, NULL, func, user_data); mclient = mtp_client_get_mirrors (url, mirror_mirrors_func, client); if (!mclient) { g_free (client); return NULL; } client->children = g_slist_prepend (client->children, mclient); return client; } static void mirror_mirrors_func (MtpClient* mclient, MtpClientStatus status, GSList* conns, gpointer user_data) { MtpClient* client = (MtpClient*) user_data; /* Done with client */ client->children = g_slist_remove (client->children, mclient); mtp_client_delete (mclient); if (status == MTP_CLIENT_STATUS_OK && conns) { /* If there's only one mirror, use just that mirror */ if (!conns->next) { GConn* conn = (GConn*) conns->data; /* conn is refed already */ ((MtpClientMirrorFunc) client->func)(client, MTP_CLIENT_STATUS_OK, conn, client->user_data); } /* Otherwise, ping each mirror to find the best. We have a ref to each connection already. */ else { GSList* i; for (i = conns; i != NULL; i = i->next) { GConn* conn; MtpClient* iclient; conn = (GConn*) i->data; iclient = mtp_client_get_info_conn (client->url, conn, mirror_info_func, client); g_return_if_fail (iclient); client->children = g_slist_prepend (client->children, iclient); } } g_slist_free (conns); } else { ((MtpClientMirrorFunc) client->func)(client, MTP_CLIENT_STATUS_ERROR, NULL, client->user_data); } } static void mirror_info_func (MtpClient* iclient, MtpClientStatus status, gint length, gpointer user_data) { MtpClient* client = (MtpClient*) user_data; /* Done with client */ client->children = g_slist_remove (client->children, iclient); if (status == MTP_CLIENT_STATUS_OK) { GSList* i; /* Remove all other info request children */ for (i = client->children; i != NULL; i = i->next) { MtpClient* c = (MtpClient*) i->data; mtp_client_delete (c); } g_slist_free (client->children); client->children = NULL; gnet_conn_ref (iclient->conn); ((MtpClientMirrorFunc) client->func)(client, MTP_CLIENT_STATUS_OK, iclient->conn, client->user_data); } else { /* If not children left, that's an error */ if (!client->children) { ((MtpClientMirrorFunc) client->func)(client, MTP_CLIENT_STATUS_ERROR, NULL, client->user_data); } /* Otherwise, hopefully another client/child will work. */ } mtp_client_delete (iclient); } /* ************************************************************ */ static gboolean mirror_file_gconn_func (GConn* conn, GConnStatus status, gchar* buffer, gint length, gpointer user_data); MtpClient* mtp_client_get_file_mirror (const GURL* url, MtpClientRecvFunc func, gpointer user_data) { return mtp_client_get_file_mirror_conn (url, NULL, func, user_data); } static MtpClient* mtp_client_get_file_mirror_conn (const GURL* url, GConn* conn, MtpClientRecvFunc func, gpointer user_data) { MtpClient* client = NULL; g_return_val_if_fail (url, NULL); g_return_val_if_fail (func, NULL); client = mtp_client_new (url, conn, func, user_data); g_return_val_if_fail (client, NULL); client->buffer = g_new0 (gchar, BUF_LEN); mtp_client_connect (client, "GET", mirror_file_gconn_func); return client; } static gboolean mirror_file_gconn_func (GConn* conn, GConnStatus status, gchar* buffer, gint length, gpointer user_data) { MtpClient* client = (MtpClient*) user_data; g_return_val_if_fail (client, FALSE); switch (status) { case GNET_CONN_STATUS_CONNECT: { /* Nothing */ break; } case GNET_CONN_STATUS_READ: { /* Read header */ if (!client->length) { GList* as = NULL; ElfNode* a; gboolean ok = FALSE; gchar* length_str; gint len; as = elf_read_list (buffer, length); if (!as) goto done; a = (ElfNode*) as->data; g_return_val_if_fail (a && a->name, FALSE); /* Name shoudl be FILE or ERROR */ if (strcmp (a->name, "FILE")) goto done; length_str = elf_get_attribute(a, "length"); if (!length_str) goto done; len = atoi (length_str); if (len < 0) goto done; client->length = len; ok = TRUE; done: if (as) elf_delete_list (as); if (ok) { /* Read file if there is a file */ if (client->length) { gnet_conn_readany (client->conn, client->buffer, MIN(BUF_LEN, client->length), TIMEOUT); } /* Otherwise, file is 0-length - we are done */ else { gnet_conn_disconnect (client->conn, TRUE); } /* Buffer == NULL signals the start of the file */ ((MtpClientRecvFunc) client->func)(client, MTP_CLIENT_STATUS_OK, NULL, 0, client->user_data); } else { ((MtpClientRecvFunc) client->func)(client, MTP_CLIENT_STATUS_ERROR, NULL, 0, client->user_data); } } else { guint rv; guint len; gboolean done; /* Don't allow sender to send more than the length of the file */ len = MIN (client->length - client->offset, length); done = (client->offset + len) == client->length; /* Pass file up to user */ rv = ((MtpClientRecvFunc) client->func)(client, MTP_CLIENT_STATUS_OK, buffer, len, client->user_data); /* Upper level deletes me when done or on error */ if (rv != len || done) return FALSE; client->offset += len; /* Close connection if done */ if (client->offset == client->length) { gnet_conn_disconnect (client->conn, TRUE); } /* Otherwise, read more */ else { gint len; len = MIN(client->length - client->offset, BUF_LEN); gnet_conn_readany (client->conn, client->buffer, len, TIMEOUT); } } break; } case GNET_CONN_STATUS_WRITE: { g_free (buffer); /* Read reponse */ gnet_conn_readline (conn, NULL, MAX_LINE_LEN, MTP_SERVER_TIMEOUT); break; } case GNET_CONN_STATUS_CLOSE: case GNET_CONN_STATUS_TIMEOUT: case GNET_CONN_STATUS_ERROR: { gnet_conn_disconnect (client->conn, TRUE); ((MtpClientRecvFunc) client->func)(client, MTP_CLIENT_STATUS_ERROR, NULL, 0, client->user_data); break; } } return FALSE; } /* ************************************************************ */ static void file_mirror_func (MtpClient* mclient, MtpClientStatus status, GConn* conn, gpointer user_data); static guint file_file_func (MtpClient* fclient, MtpClientStatus status, const void* buffer, guint length, gpointer user_data); /** Get a file from via a rendezvous. We connect to the rendezvous and get the best mirror (using mtp_client_get_mirror). Then we connect to the mirror and get the file (using mtp_client_get_file_mirror). */ MtpClient* mtp_client_get_file_rendezvous (const GURL* url, MtpClientRecvFunc func, gpointer user_data) { MtpClient* client; MtpClient* mclient; g_return_val_if_fail (url, NULL); g_return_val_if_fail (func, NULL); client = mtp_client_new (url, NULL, func, user_data); g_return_val_if_fail (client, NULL); /* Get the best mirror */ mclient = mtp_client_get_mirror (url, file_mirror_func, client); if (!mclient) { g_free (client); return NULL; } client->children = g_slist_prepend (client->children, mclient); return client; } /* Called when we have a mirror. We now connect to the mirror and get the file. */ static void file_mirror_func (MtpClient* mclient, MtpClientStatus status, GConn* conn /* have ref */, gpointer user_data) { MtpClient* client = (MtpClient*) user_data; /* Done with client */ client->children = g_slist_remove (client->children, mclient); mtp_client_delete (mclient); /* Make sure we found a mirror */ if (status == MTP_CLIENT_STATUS_OK) { MtpClient* fclient; /* Copy the conn of the mirror - we will reuse it */ gnet_conn_ref (conn); gnet_conn_unref (client->conn, TRUE); client->conn = conn; /* Get the file from the mirror */ fclient = mtp_client_get_file_mirror_conn (client->url, conn, file_file_func, client); g_return_if_fail (fclient); client->children = g_slist_prepend (client->children, fclient); } else { ((MtpClientRecvFunc) client->func)(client, MTP_CLIENT_STATUS_ERROR, NULL, 0, client->user_data); } } /** Called when we get a file from the mirror. */ static guint file_file_func (MtpClient* fclient, MtpClientStatus status, const void* buffer, guint length, gpointer user_data) { MtpClient* client = (MtpClient*) user_data; /* Fake we are the file client (we copied the conn above) */ client->length = fclient->length; client->offset = fclient->offset; return ((MtpClientRecvFunc) client->func)(client, status, buffer, length, client->user_data); } /* ************************************************************ */ static guint recv_func (MtpClient* rclient, MtpClientStatus status, const void* buf, guint len, gpointer user_data); MtpClient* mtp_client_get_file (const GURL* url, const gchar* path, MtpClientFileFunc func, gpointer user_data) { MtpClient* client; MtpClient* mclient; g_return_val_if_fail (url, NULL); g_return_val_if_fail (path, NULL); g_return_val_if_fail (func, NULL); client = mtp_client_new (url, NULL, func, user_data); g_return_val_if_fail (client, NULL); /* Get the best mirror */ mclient = mtp_client_get_file_rendezvous (url, recv_func, client); if (!mclient) { g_free (client); return NULL; } client->children = g_slist_prepend (client->children, mclient); client->path = g_strdup (path); return client; } /* TODO. Support random access recvs using an offset. */ static guint recv_func (MtpClient* rclient, MtpClientStatus status, const void* buf, guint len, gpointer user_data) { MtpClient* client = (MtpClient*) user_data; /* If there was an error, reset myself */ if (status != MTP_CLIENT_STATUS_OK) /* Error */ { MTPP (2, "recv_func error\n"); /* Close the file */ if (client->file) { fclose (client->file); client->file = NULL; } unlink (client->path); /* Error upcall */ ((MtpClientFileFunc) client->func)(client, MTP_CLIENT_STATUS_ERROR, client->user_data); return 0; } /* If the is the first time, then there will be no buffer */ else if (!buf) /* Create */ { /* Open the file */ client->file = fopen (client->path, "w"); if (!client->file) { g_warning ("Could not open file %s: %s\n", client->path, strerror(errno)); /* Error upcall */ ((MtpClientFileFunc) client->func)(client, MTP_CLIENT_STATUS_ERROR, client->user_data); return 0; } client->offset = 0; client->length = rclient->length; client->sha = gnet_sha_new_incremental (); /* Check if zero length file */ if (client->length == 0) { /* Close the file */ fclose (client->file); client->file = NULL; /* Finish the SHA */ gnet_sha_final (client->sha); /* Success upcall */ ((MtpClientFileFunc) client->func)(client, MTP_CLIENT_STATUS_OK, client->user_data); return 0; } /* Update upcall */ ((MtpClientFileFunc) client->func)(client, MTP_CLIENT_STATUS_UPDATE, client->user_data); } /* Otherwise, this is data - append it */ else /* Write */ { guint rv; MTPP (2, "recv_func %s (%d bytes at %d, need %d)\n", client->path, len, client->offset, client->length); g_return_val_if_fail (len + client->offset <= client->length, 0); /* Append the data */ rv = fwrite (buf, len, 1, client->file); if (rv != 1) { g_warning ("fwrite failed: %s\n", strerror(errno)); return 0; } /* Update SHA */ gnet_sha_update (client->sha, buf, len); /* Update offset */ client->offset += len; /* Check if done */ if (client->offset == client->length) { MTPP (2, "recv_func %s done\n", client->path); /* Close the file */ fclose (client->file); client->file = NULL; /* Finish the SHA */ gnet_sha_final (client->sha); /* Success upcall */ ((MtpClientFileFunc) client->func)(client, MTP_CLIENT_STATUS_OK, client->user_data); return 0; } /* Update upcall */ ((MtpClientFileFunc) client->func)(client, MTP_CLIENT_STATUS_UPDATE, client->user_data); } return len; }