Beispiel #1
0
/*!	Unregisters an image from the specified team.
*/
status_t
unregister_image(Team *team, image_id id)
{
	status_t status = B_ENTRY_NOT_FOUND;

	mutex_lock(&sImageMutex);

	struct image *image = sImageTable->Lookup(id);
	if (image != NULL && image->team == team->id) {
		list_remove_link(image);
		sImageTable->Remove(image);
		status = B_OK;
	}

	mutex_unlock(&sImageMutex);

	if (status == B_OK) {
		// notify the debugger
		user_debug_image_deleted(&image->info.basic_info);

		// notify listeners
		sNotificationService.Notify(IMAGE_REMOVED, image);

		free(image);
	}

	return status;
}
Beispiel #2
0
status_t
set_sem_owner(sem_id id, team_id newTeamID)
{
	if (sSemsActive == false)
		return B_NO_MORE_SEMS;
	if (id < 0)
		return B_BAD_SEM_ID;
	if (newTeamID < 0)
		return B_BAD_TEAM_ID;

	int32 slot = id % sMaxSems;

	// get the new team
	Team* newTeam = Team::Get(newTeamID);
	if (newTeam == NULL)
		return B_BAD_TEAM_ID;
	BReference<Team> newTeamReference(newTeam, true);

	InterruptsSpinLocker semListLocker(sSemsSpinlock);
	SpinLocker semLocker(sSems[slot].lock);

	if (sSems[slot].id != id) {
		TRACE(("set_sem_owner: invalid sem_id %ld\n", id));
		return B_BAD_SEM_ID;
	}

	list_remove_link(&sSems[slot].u.used.team_link);
	list_add_item(&newTeam->sem_list, &sSems[slot].u.used.team_link);

	sSems[slot].u.used.owner = newTeam->id;
	return B_OK;
}
Beispiel #3
0
status_t
set_port_owner(port_id id, team_id newTeamID)
{
	TRACE(("set_port_owner(id = %ld, team = %ld)\n", id, newTeamID));

	if (id < 0)
		return B_BAD_PORT_ID;

	int32 slot = id % sMaxPorts;

	MutexLocker locker(sPorts[slot].lock);

	if (sPorts[slot].id != id) {
		TRACE(("set_port_owner: invalid port_id %ld\n", id));
		return B_BAD_PORT_ID;
	}

	InterruptsSpinLocker teamLocker(gTeamSpinlock);

	struct team* team = team_get_team_struct_locked(newTeamID);
	if (team == NULL) {
		T(OwnerChange(sPorts[slot], newTeamID, B_BAD_TEAM_ID));
		return B_BAD_TEAM_ID;
	}

	// transfer ownership to other team
	list_remove_link(&sPorts[slot].team_link);
	list_add_item(&team->port_list, &sPorts[slot].team_link);
	sPorts[slot].owner = newTeamID;

	T(OwnerChange(sPorts[slot], newTeamID, B_OK));
	return B_OK;
}
Beispiel #4
0
static status_t
delete_sem_internal(sem_id id, bool checkPermission)
{
	if (sSemsActive == false)
		return B_NO_MORE_SEMS;
	if (id < 0)
		return B_BAD_SEM_ID;

	int32 slot = id % sMaxSems;

	cpu_status state = disable_interrupts();
	GRAB_SEM_LIST_LOCK();
	GRAB_SEM_LOCK(sSems[slot]);

	if (sSems[slot].id != id) {
		RELEASE_SEM_LOCK(sSems[slot]);
		RELEASE_SEM_LIST_LOCK();
		restore_interrupts(state);
		TRACE(("delete_sem: invalid sem_id %ld\n", id));
		return B_BAD_SEM_ID;
	}

	if (checkPermission
		&& sSems[slot].u.used.owner == team_get_kernel_team_id()) {
		RELEASE_SEM_LOCK(sSems[slot]);
		RELEASE_SEM_LIST_LOCK();
		restore_interrupts(state);
		dprintf("thread %ld tried to delete kernel semaphore %ld.\n",
			thread_get_current_thread_id(), id);
		return B_NOT_ALLOWED;
	}

	if (sSems[slot].u.used.owner >= 0) {
		list_remove_link(&sSems[slot].u.used.team_link);
		sSems[slot].u.used.owner = -1;
	} else
		panic("sem %ld has no owner", id);

	RELEASE_SEM_LIST_LOCK();

	char* name;
	uninit_sem_locked(sSems[slot], &name);

	SpinLocker schedulerLocker(gSchedulerLock);
	scheduler_reschedule_if_necessary_locked();
	schedulerLocker.Unlock();

	restore_interrupts(state);

	free(name);
	return B_OK;
}
/* Remove connection from current position in connection list and move it to
 * new correct position according to conn_cmp_func. O(2n).
 */
static void conn_reinsert (ASDownload *dl, ASDownConn *conn)
{
	List *link;

	/* Find link. */
	link = list_find (dl->conns, conn);
	assert (link);

	/* Remove entry. */
	dl->conns = list_remove_link (dl->conns, link);

	/* Reinsert at correct pos. */
	dl->conns = list_insert_sorted (dl->conns, (CompareFunc) conn_cmp_func,
	                                conn);

#ifdef VERIFY_CONN_LIST
	verify_connections (dl);
#endif
}
Beispiel #6
0
status_t
delete_port(port_id id)
{
	TRACE(("delete_port(id = %ld)\n", id));

	if (!sPortsActive || id < 0)
		return B_BAD_PORT_ID;

	int32 slot = id % sMaxPorts;

	MutexLocker locker(sPorts[slot].lock);

	if (sPorts[slot].id != id) {
		TRACE(("delete_port: invalid port_id %ld\n", id));
		return B_BAD_PORT_ID;
	}

	T(Delete(sPorts[slot]));

	{
		InterruptsSpinLocker teamLocker(gTeamSpinlock);
		list_remove_link(&sPorts[slot].team_link);
	}

	uninit_port_locked(sPorts[slot]);

	locker.Unlock();

	MutexLocker _(sPortsLock);

	// update the first free slot hint in the array
	if (slot < sFirstFreeSlot)
		sFirstFreeSlot = slot;

	sUsedPorts--;
	return B_OK;
}
static int fst_plugin_session_callback (FSTSession *session,
										FSTSessionMsg msg_type,
										FSTPacket *msg_data)
{
	switch (msg_type)
	{
	/* session management messages */
	case SessMsgConnected:
	{
		/* determine local ip */
		FST_PLUGIN->local_ip = net_local_ip (session->tcpcon->fd, NULL);
		FST_DBG_3 ("CONNECTED to %s:%d, local ip: %s",
		           session->node->host, session->node->port,			
		           net_ip_str (FST_PLUGIN->local_ip));
		break;
	}

	case SessMsgEstablished:
	{
		FST_PLUGIN->stats->sessions++;

		FST_DBG_3 ("ESTABLISHED session to %s:%d (total sessions: %d)",
				   session->node->host, session->node->port, 
		           FST_PLUGIN->stats->sessions);
		break;
	}

	case SessMsgDisconnected:
	{
		List *item;

		/* zero stats */
		if (session->was_established)
		{
			assert(FST_PLUGIN->stats->sessions > 0);

			FST_PLUGIN->stats->sessions--;

			FST_DBG_3 ("DISCONNECTED session to %s:%d (total sessions: %d)",
					   session->node->host, session->node->port, 
			           FST_PLUGIN->stats->sessions);

			if (FST_PLUGIN->stats->sessions == 0)
			{
				FST_PLUGIN->stats->users = 0;
				FST_PLUGIN->stats->files = 0;
				FST_PLUGIN->stats->size = 0;
			}

			/* Terminate all queries sent to this session */
			fst_searchlist_session_disconnected (FST_PLUGIN->searches, session);
		}

		/* close old session */
		if (FST_PLUGIN->session == session)
		{
			FST_PLUGIN->session = NULL;
		}
		else if ((item = list_find (FST_PLUGIN->sessions, session)))
		{
			FST_PLUGIN->sessions = list_remove_link (FST_PLUGIN->sessions, item);
		}
		else
		{
			/* We have no record of this session yet it was disconnected. This
			 * is not good! */
			assert (0);
		}

		/* remove old node from node cache */
		assert (session->node);

		if (session->node)
		{
			fst_nodecache_remove (FST_PLUGIN->nodecache, session->node);
		}

		/* free session */
		fst_session_free (session);

		fst_plugin_connect_next ();
		return FALSE;
	}

	/* FastTrack messages */
	case SessMsgNodeList:
	{
		int added_nodes = 0;
		time_t now = time (NULL); 

		while (fst_packet_remaining (msg_data) >= 8)
		{
			unsigned long ip		= fst_packet_get_uint32 (msg_data);			
			unsigned short port		= ntohs (fst_packet_get_uint16 (msg_data));	
			unsigned int last_seen	= fst_packet_get_uint8 (msg_data);			
			unsigned int load		= fst_packet_get_uint8 (msg_data);		

			FSTNode *node;
#if 0
			FST_DBG_4 ("node: %s:%d   load: %d%% last_seen: %d mins ago",
					   net_ip_str(ip), port, load, last_seen);
#else
#ifdef DUMP_NODES
			fprintf (stderr, "%08x %08x:%d %08x:%d %d %d\n",
			         now, session->tcpcon->host, session->tcpcon->port,
			         ip, port, load, last_seen);

#endif
#endif

			/* Only add routable ips to cache */
			if (fst_utils_ip_routable ((in_addr_t)ip))
			{
				node = fst_nodecache_add (FST_PLUGIN->nodecache,
				                          NodeKlassSuper,
				                          net_ip_str (ip), port, load,
				                          now - last_seen * 60);
				      
				if (node && last_seen == 0)
					fst_peer_insert (FST_PLUGIN->peers, session->node,
					                 &session->peers, node);
	
				added_nodes++;
			}
		}

#ifdef DUMP_NODES
		fprintf (stderr, "\n");
#endif

		/* sort the cache again */
		fst_nodecache_sort (FST_PLUGIN->nodecache);

		FST_DBG_1 ("added %d received supernode IPs to nodes list", added_nodes);

		/* save some new nodes for next time (but not too often) */
		if (FST_PLUGIN->session == session)
			save_nodes ();

		/* now that we have some more nodes, try to continue connecting */
		fst_plugin_connect_next ();

		/* if we got this from an index node disconnect now and use a supernode */
		if (session->node->klass == NodeKlassIndex)
		{
			FST_DBG ("disconnecting from index node");
			/* this calls us back with SessMsgDisconnected */
			fst_session_disconnect (session);
			return FALSE;
		}
		break;
	}

	case SessMsgNetworkStats:
	{
		unsigned int mantissa, exponent;
		unsigned int prev_users = FST_PLUGIN->stats->users;

		/* get stats only from primary supernode */
		if (session != FST_PLUGIN->session)
			break;

		if (fst_packet_remaining (msg_data) < 12)
			break;

		FST_PLUGIN->stats->users = ntohl (fst_packet_get_uint32 (msg_data));
		FST_PLUGIN->stats->files = ntohl (fst_packet_get_uint32 (msg_data));

		mantissa = ntohs(fst_packet_get_uint16 (msg_data));	/* mantissa of size */
		exponent = ntohs(fst_packet_get_uint16 (msg_data));	/* exponent of size */

		if (exponent >= 30)
			FST_PLUGIN->stats->size = mantissa << (exponent - 30);
		else
			FST_PLUGIN->stats->size = mantissa >> (30 - exponent);

		/* what follows in the packet is the number of files and their size
		 * per media type (6 times).
		 * Then optionally the different network names and the number of
		 * their users.
		 * we do not currently care for those
		 */

		FST_DBG_3 ("received network stats: %d users, %d files, %d GB",
				   FST_PLUGIN->stats->users,
				   FST_PLUGIN->stats->files,
				   FST_PLUGIN->stats->size);

		/* if we connected to a splitted network segment move on */
		if (FST_PLUGIN->stats->users < FST_MIN_USERS_ON_CONNECT &&
		    prev_users == 0)
		{
			FST_DBG ("disconnecting from splitted network segment");
			/* this calls us back with SessMsgDisconnected */
			fst_session_disconnect (session);
			return FALSE;
		}

#if 0
		fst_udp_discover_ping_node (FST_PLUGIN->discover, session->node);
#endif

		break;
	}

	case SessMsgNetworkName:
	{
		FSTPacket *packet;
		char *net_name = STRDUP_N (msg_data->data, fst_packet_size(msg_data));

		FST_DBG_2 ("received network name: \"%s\", sending ours: \"%s\"",
				   net_name ,FST_NETWORK_NAME);
		free (net_name);

		if (! (packet = fst_packet_create ()))
			break; /* not overly important, just don't send it */

		fst_packet_put_ustr (packet, FST_NETWORK_NAME, strlen (FST_NETWORK_NAME));

		if (!fst_session_send_message (session, SessMsgNetworkName, packet))
		{
			fst_packet_free (packet);
			fst_session_disconnect (session);
			return FALSE;
		}

		fst_packet_free (packet);
		break;
	}

	case SessMsgExternalIp:
	{
		FST_PLUGIN->external_ip = fst_packet_get_uint32 (msg_data);
		FST_DBG_1 ("received external ip: %s",
				   net_ip_str (FST_PLUGIN->external_ip));

		/* resend our info with new external ip */
		fst_session_send_info (session);

		/* upload our shares to supernode.
		 * we do this here because we have to make sure we are accessible
		 * from the outside since we don't push yet.
		 */
		if (fst_share_do_share (session))
		{
			FST_DBG_1 ("registering shares with new supernode %s",
			           session->node->host);
			if (!fst_share_register_all (session))
				FST_DBG ("registering shares with new supernode failed");
		}

		/* resend queries for all running searches */
		fst_searchlist_send_queries (FST_PLUGIN->searches, session);

		break;
	}

	case SessMsgProtocolVersion:
	{
		/* Note: We are not really sure if this is the protocol version. */
		FSTPacket *packet;
		fst_uint32 version;

		if ((packet = fst_packet_create ()))
		{
			version = ntohl (fst_packet_get_uint32 (msg_data));

			FST_HEAVY_DBG_1 ("received protocol version: 0x%02X", version);

			fst_packet_put_uint32 (packet, htonl (version));
			fst_session_send_message (session, SessMsgProtocolVersion, packet);		
			fst_packet_free (packet);
		}

		break;
	}

	case SessMsgQueryReply:
	{
		/* forward results from all sessions */ 
		fst_searchlist_process_reply (FST_PLUGIN->searches, session,
		                              msg_type, msg_data);
		break;
	}

	case SessMsgQueryEnd:
	{
		fst_searchlist_process_reply (FST_PLUGIN->searches, session,
		                              msg_type, msg_data);
		break;
	}

	default:
/*
		FST_DBG_2 ("unhandled message: type = 0x%02x, length = %d",
				   msg_type, fst_packet_size(msg_data));
		printf("\nunhandled message: type = 0x%02x, length = %d",
			   msg_type, fst_packet_size(msg_data));
		print_bin_data(msg_data->data, fst_packet_remaining(msg_data));
*/
		break;
	}

	return TRUE;
}
/* take necessary steps to maintain man->connections sessions */
static as_bool sessman_maintain (ASSessMan *man)
{
	ASSession *session;
	unsigned int connected = list_length (man->connected);
	unsigned int connecting = list_length (man->connecting);
	int len;
		
	if (man->connections == 0)
	{
		/* disconnect everything */
		man->connecting = list_foreach_remove (man->connecting,
		                     (ListForeachFunc)sessman_disconnect_itr, man);
		man->connected = list_foreach_remove (man->connected,
		                     (ListForeachFunc)sessman_disconnect_itr, man);
	}
	else if (man->connections <= connected)
	{
		/* We have more connections than needed. First stop all discovery. */
		man->connecting = list_foreach_remove (man->connecting,
		                     (ListForeachFunc)sessman_disconnect_itr, man);

		/* Now remove superfluous connections.
		 * TODO: Be smart about which connections we remove.
		 */
		len = connected - man->connections;
		
		while (len > 0)
		{
			session = (ASSession *) man->connected->data;

			as_session_disconnect (session, FALSE);
			/* notify node manager */
			as_nodeman_update_disconnected (AS->nodeman, session->host);
			as_session_free (session);

			man->connected = list_remove_link (man->connected, man->connected);
			len--;
		}
	}
	else if (man->connections > connected)
	{
		/* We need more connections. Fill up discovery queue. */
		len = AS_SESSION_PARALLEL_ATTEMPTS - connecting;

		while (len > 0)
		{
			ASSession *session;
			ASNode *node;

			/* Get next node */
			if (!(node = as_nodeman_next (AS->nodeman)))
			{
				/* FIXME: Use Ares http cache by adding download code to
				 * node manager and calling it from here.
				 */
				
				/* only warn if this is likely to be a
				 * problem, to avoid confusion
				 */
				if (!man->connected)
					AS_ERR ("Ran out of nodes");

				return FALSE;	
			}

			/* Create session */
			if (!(session = as_session_create (session_state_cb,
			                                   session_packet_cb)))
			{
				AS_ERR ("Insufficient memory");
				as_nodeman_update_failed (AS->nodeman, node->host);
				return FALSE; /* hmm */
			}

			session->udata = man;

#if 0
			AS_HEAVY_DBG_3 ("Trying node %s:%d, weight: %.02f",
			                net_ip_str (node->host), node->port, node->weight);
#endif

			/* Connect to node */
			if (!(as_session_connect (session, node->host, node->port)))
			{
				as_nodeman_update_failed (AS->nodeman, node->host);
				as_session_free (session);
				continue; /* try next node */
			}

			/* Add session to connecting list */
			man->connecting = list_prepend (man->connecting, session);
			len--;
		}
	}

	connected = list_length (man->connected);
	connecting = list_length (man->connecting);

	AS_HEAVY_DBG_3 ("session_maintain: requested: %d, connected: %d, connecting: %d",
	                man->connections, connected, connecting);

	/* Let NetInfo know what's going on */
	as_netinfo_handle_connect (AS->netinfo, man->connections, connected);

	return TRUE;
}
/* This mangels chunks as follows:
 *   - Leave active chunks alone
 *   - Split half complete chunks in a complete and an empty chunk
 *   - Merge consecutive complete chunks
 *   - Merge consecutive empty chunks
 */
static as_bool consolidate_chunks (ASDownload *dl)
{
	List *link, *last_link, *new_link;
	ASDownChunk *chunk, *last_chunk, *new_chunk;
	size_t new_start, new_size;

	link = dl->chunks;
	last_link = NULL;
	last_chunk = NULL;

	while (link)
	{
		chunk = link->data;

		/* Split chunk if it is not active and half complete */
		if (!chunk->udata &&
		    chunk->received > 0 &&
		    chunk->received < chunk->size)
		{
			AS_HEAVY_DBG_3 ("Splitting half complete chunk (%u, %u, %u)",
			                chunk->start, chunk->size, chunk->received);

			/* create the new empty chunk */
			new_start = chunk->start + chunk->received;
			new_size  = chunk->size - chunk->received;

			if (!(new_chunk = as_downchunk_create (new_start, new_size)))
			{
				/* This is not recoverable if we rely on chunk downloads always
				 * beginning at chunk->received == 0 so quit here.
				 */
				AS_ERR_2 ("Couldn't create chunk (%u,%u)", new_start, new_size);
				return FALSE;						
			}

			/* Shorten old chunk. */
			chunk->size = chunk->received;

			/* Insert new chunk after old one. */
			new_link = list_prepend (NULL, new_chunk);
			list_insert_link (link, new_link);

			assert (chunk->received == chunk->size);
			assert (new_chunk->received == 0);

			/* Adjust last_link and link. */
			last_link = link;
			link = new_link;

			last_chunk = last_link->data;
			chunk = link->data;
		}

		/* Merge last_chunk and chunk if they are either both complete or both
		 * empty.
		 */
		if (last_chunk && !chunk->udata && !last_chunk->udata &&
		    ((last_chunk->received == last_chunk->size &&
		     chunk->received == chunk->size) ||
			(last_chunk->received == 0 && chunk->received == 0)))
		{	
			AS_HEAVY_DBG_5 ("Merging %s chunks (%u,%u) and (%u,%u)",
			                (chunk->received == 0) ? "empty" : "complete",
			                last_chunk->start, last_chunk->size,
			                chunk->start, chunk->size);

			/* increase last chunk by size of chunk */
			last_chunk->size += chunk->size;
			last_chunk->received += chunk->received;

			/* free chunk and remove link */
			as_downchunk_free (chunk);
			last_link = list_remove_link (last_link, link);

			/* adjust link so loop can continue */
			link = last_link;
		}

		/* Move on to next chunk */
		last_link = link;
		link = link->next;
		last_chunk = last_link->data;
	};

	return TRUE;
}
/* Assign new connections to unused chunks. Split large chunks if there are
 * unused connections. Since our connection list is sorted by bandwidth this
 * will pick fast connections first.
 */
static as_bool start_chunks (ASDownload *dl)
{
	List *chunk_l;
	List *conn_l, *tmp_l, *slow_conn_l;
	ASDownChunk *chunk, *new_chunk;
	ASDownConn *conn, *slow_conn;
	time_t now = time (NULL);
	size_t remaining, new_start, new_size;
	int active_conns, pending_conns, max_new_conns;
	unsigned int speed;

	if (!(conn_l = dl->conns))
		return TRUE; /* nothing to do */

	/* We do nothing if there are already enough connections. If not we limit
	 * the number of our attempts to (maximum - active) * 2, assuming half of
	 * the connection attempts will fail.
	 * TODO: Is 2 a good factor? Does it reflect the real failure rate?
	 */
	active_conns = active_conns_from_list (dl, &pending_conns);

	if (active_conns >= AS_CONF_INT (AS_DOWNLOAD_MAX_ACTIVE_SOURCES))
		return TRUE;

	max_new_conns = (AS_CONF_INT (AS_DOWNLOAD_MAX_ACTIVE_SOURCES) -
	                 active_conns) * 2;

	if (pending_conns >= max_new_conns)
		return TRUE;

	max_new_conns -= pending_conns;

	AS_HEAVY_DBG_1 ("Attempting a maximum of %d new connections",
	                max_new_conns);

	/* First assign connections to unused chunks */

	for (chunk_l = dl->chunks; chunk_l; chunk_l = chunk_l->next)
	{
		chunk = chunk_l->data;

		/* skip active and complete chunks */
		if (chunk->udata || chunk->received == chunk->size)
			continue;
		
		/* find a connection for this unused chunk */
		while (conn_l)
		{
			conn = conn_l->data;

			if (!conn->udata2 &&
			    (conn->state != DOWNCONN_QUEUED || conn->queue_next_try <= now))
			{
				/* associate chunk with connection */
				chunk->udata = conn;
				conn->udata2 = chunk;

				/* We always split half complete chunks in consolidate_chunks
				 * before starting them.
				 */
				assert (chunk->received == 0);

				/* use the connection we found with this chunk */
				if (!as_downconn_start (conn, dl->hash, chunk->start,
				                        chunk->size))
				{
					/* download start failed, remove connection. */
					AS_DBG_2 ("Failed to start download from %s:%d, removing source.",
					          net_ip_str (conn->source->host),
					          conn->source->port);

					as_downconn_free (conn);
					chunk->udata = NULL;

					tmp_l = conn_l->next;
					dl->conns = list_remove_link (dl->conns, conn_l);
					conn_l = tmp_l;

					/* try next connection */
					continue;
				}

				max_new_conns--;

				AS_HEAVY_DBG_5 ("Started unused chunk (%u,%u) of \"%s\" with %s:%d",
			                    chunk->start, chunk->size, dl->filename,
				                net_ip_str (conn->source->host),
				                conn->source->port);

				/* move on to next chunk */
				conn_l = conn_l->next;
				break;
			}

			conn_l = conn_l->next;
		}

		if (!conn_l || max_new_conns <= 0)
			break; /* no more suitable connections */
	}

	/* Now loop through remaining connections and create new chunks for them
	 * by splitting large ones.
	 */
	while (conn_l && max_new_conns > 0)
	{
		conn = conn_l->data;

		/* if we see a used connection we can stop since there are no more
		 * unused ones
		 */
		if (conn->udata2)
			break;

		/* skip queued connections */
		if (conn->state == DOWNCONN_QUEUED && conn->queue_next_try > now)
		{
			conn_l = conn_l->next;
			continue;
		}

		/* Find chunk with largest remaining size */
		remaining = 0;
		tmp_l = NULL;
		for (chunk_l = dl->chunks; chunk_l; chunk_l = chunk_l->next)
		{
			chunk = chunk_l->data;
			if (chunk->size - chunk->received > remaining)
			{
				remaining = chunk->size - chunk->received;
				tmp_l = chunk_l;
			}
		}

		chunk_l = tmp_l;

		if (!chunk_l || remaining <= AS_DOWNLOAD_MIN_CHUNK_SIZE * 2)
		{
			/* No more chunks to break up */
			break;
		}

		/* break up this chunk in middle of remaining size */
		chunk = chunk_l->data;
		new_size = (chunk->size - chunk->received) / 2;
		new_start = chunk->start + chunk->size - new_size;

		if (!(new_chunk = as_downchunk_create (new_start, new_size)))
		{
			/* Nothing we can do but bail out. Overall state should still be
			 * consistent and other connections and chunks can go on.
			 */
			AS_ERR_2 ("Couldn't create chunk (%u,%u)", new_start, new_size);
			return FALSE;						
		}

		/* associate new chunk with connection */
		new_chunk->udata = conn;
		conn->udata2 = new_chunk;

		/* Start new connection */
		if (!as_downconn_start (conn, dl->hash, new_chunk->start,
		                        new_chunk->size))
		{
			/* download start failed, remove connection. */
			AS_WARN_2 ("Failed to start download from %s:%d, removing source.",
			           net_ip_str (conn->source->host),
			           conn->source->port);

			as_downchunk_free (new_chunk);
			as_downconn_free (conn);

			tmp_l = conn_l->next;
			dl->conns = list_remove_link (dl->conns, conn_l);
			conn_l = tmp_l;

			/* We now have a chunk in the list which has no source. Fixing
			 * this here would be complicated so we break and leave it to
			 * a later maintenance timer invokation.
			 */
			break;
		}

		max_new_conns--;

		AS_HEAVY_DBG_5 ("Started new chunk (%u,%u) of \"%s\" with %s:%d",
	                    new_chunk->start, new_chunk->size, dl->filename,
		                net_ip_str (conn->source->host),
		                conn->source->port);

		/* Shorten old chunk. */
		chunk->size -= new_size;

		AS_HEAVY_DBG_4 ("Reduced old chunk from (%u,%u) to (%u,%u)",
	                    chunk->start, chunk->size + new_size,
	                    chunk->start, chunk->size);

		/* Insert new chunk after old one */
		tmp_l = list_prepend (NULL, new_chunk);
		list_insert_link (chunk_l, tmp_l);

		/* go on with next connection */
		conn_l = conn_l->next;
	}

	/* If there are still unused sources left at this point we are in endgame 
	 * mode. Replace slow connections with unused faster ones. The 
	 * max_new_conns check prevents looping through a lot of unused sources
	 * if we are not yet in endgame mode.
	 */
	while (conn_l && max_new_conns > 0)
	{
		conn = conn_l->data;

		/* if we see a used connection we can stop since there are no more
		 * unused ones
		 */
		if (conn->udata2)
			break;

		/* Skip queued sources. */
		if (conn->state == DOWNCONN_QUEUED && conn->queue_next_try > now)
		{
			conn_l = conn_l->next;
			continue;
		}
		
		/* Find slowest connection to replace. Ideally we would start at the
		 * end of the list since that's where the slowest connections are. Use
		 * current b/w of sources instead of historical one so we have a value
		 * other than 0 for first time requests.
		 */
		speed = 0xFFFFFFFF;
		slow_conn_l = NULL;

		for (tmp_l = conn_l->next; tmp_l; tmp_l = tmp_l->next)
		{
			slow_conn = tmp_l->data;

			if (slow_conn->udata2 && as_downconn_speed (slow_conn) < speed)
			{
				speed = as_downconn_speed (slow_conn);
				slow_conn_l = tmp_l;
			}
		}

		/* Changing the connection must be worth it so only do it if we
		 * can become at least 1 kb/s faster.
		 */
		if (!slow_conn_l || speed + 1024 >= as_downconn_speed (conn))
			break;

		slow_conn = slow_conn_l->data;

		AS_HEAVY_DBG_3 ("Removing slow source %s (%2.2f kb/s) for potentially faster %2.2f kb/s",
		                net_ip_str (slow_conn->source->host),
		                (float)speed / 1024,
		                (float)as_downconn_speed (conn) / 1024);

		/* cancel slow connection */
		as_downconn_cancel (slow_conn);

		/* disassociate chunk */
		chunk = slow_conn->udata2;
		slow_conn->udata2 = NULL;
		chunk->udata = NULL;

		/* Remove old connection, don't need it anymore. This is safe because
		 * the old connection comes after conn_l.
		 */
		dl->conns = list_remove_link (dl->conns, slow_conn_l);
		as_downconn_free (slow_conn);

		/* Start chunk again with faster connection. This is the only point
		 * where we start a chunk without chunk->received being 0.
		 */
		chunk->udata = conn;
		conn->udata2 = chunk;

		if (!as_downconn_start (conn, dl->hash,
		                        chunk->start + chunk->received,
		                        chunk->size - chunk->received))
		{
			/* Download start failed, remove connection. This leaves the chunk
			 * list in a non-consolidated state which is bad form but still
			 * works since consolidate_chunks will be called later before we
			 * do anything else.
			 */
			AS_WARN_2 ("Failed to start replacement download from %s:%d, removing source.",
			           net_ip_str (conn->source->host),
			           conn->source->port);

			/* We removed a slow connection and couldn't replace it. */
			max_new_conns++;

			as_downconn_free (conn);
			chunk->udata = NULL;

			tmp_l = conn_l->next;
			dl->conns = list_remove_link (dl->conns, conn_l);
			conn_l = tmp_l;

			/* What we should do now is use the next unused source for the
			 * chunk we just disconnected and couldn't reconnect. Since that
			 * would become pretty ugly to do here and this is an unlikely
			 * error case we just break the loop and let the maintenance timer
			 * handle it later.
			 */
			break;
		}

		conn_l = conn_l->next;
	}

	/* Get entire connection list in order. A bit inefficient. Would be better
	 * to have two connection lists, one unsorted for used and one sorted for
	 * unused connections.
	 */
	dl->conns = list_sort (dl->conns, (CompareFunc) conn_cmp_func);

	return TRUE;
}