long stats_handle (const char *mount) { stats_source_t *src_stats; if (mount == NULL) return 0; avl_tree_wlock (_stats.source_tree); src_stats = _find_source(_stats.source_tree, mount); if (src_stats == NULL) { src_stats = (stats_source_t *)calloc (1, sizeof (stats_source_t)); if (src_stats == NULL) abort(); DEBUG1 ("new source stat %s", mount); src_stats->source = (char *)strdup (mount); src_stats->stats_tree = avl_tree_new (_compare_stats, NULL); src_stats->flags = STATS_SLAVE|STATS_GENERAL|STATS_HIDDEN; avl_insert (_stats.source_tree, (void *)src_stats); } avl_tree_wlock (src_stats->stats_tree); avl_tree_unlock (_stats.source_tree); return (long)src_stats; }
/* This removes any source stats from virtual mountpoints, ie mountpoints * where no source_t exists. This function requires the global sources lock * to be held before calling. */ void stats_clear_virtual_mounts (void) { avl_node *snode; avl_tree_wlock (_stats.source_tree); snode = avl_get_first(_stats.source_tree); while (snode) { stats_source_t *src = (stats_source_t *)snode->key; source_t *source = source_find_mount_raw (src->source); if (source == NULL) { stats_node_t *node; avl_tree_wlock (src->stats_tree); node = _find_node (src->stats_tree, "fallback"); if (node == NULL) { /* no source_t and no fallback file stat, so delete */ snode = avl_get_next (snode); avl_delete (_stats.source_tree, src, _free_source_stats); continue; } avl_tree_unlock (src->stats_tree); } snode = avl_get_next (snode); } avl_tree_unlock (_stats.source_tree); }
void *my_calloc (const char *file, int line, size_t num, size_t size) { alloc_node match, *result; snprintf (match.name, sizeof (match.name), "%s:%d", file, line); avl_tree_wlock (global.alloc_tree); if (avl_get_by_key (global.alloc_tree, &match, (void**)&result) == 0) { allocheader *block = calloc (1, (num*size)+sizeof(allocheader)); result->count++; result->allocated += (num*size); block->info = result; block->len = num*size; avl_tree_unlock (global.alloc_tree); return block+1; } result = calloc (1, sizeof(alloc_node)); if (result) { allocheader *block = calloc (1, (num*size)+sizeof(allocheader)); snprintf (result->name, sizeof (result->name), "%s:%d", file, line); result->count = 1; result->allocated = (num * size); avl_insert (global.alloc_tree, result); block->info = result; block->len = num*size; avl_tree_unlock (global.alloc_tree); return block+1; } avl_tree_unlock (global.alloc_tree); return NULL; }
int fserve_query_count (fbinfo *finfo) { int ret = -1; fh_node *fh; if (finfo->flags & FS_FALLBACK && finfo->limit) { avl_tree_wlock (fh_cache); fh = open_fh (finfo); if (fh) { ret = fh->refcount; thread_mutex_unlock (&fh->lock); } } else { avl_tree_rlock (fh_cache); fh = find_fh (finfo); if (fh) { thread_mutex_lock (&fh->lock); ret = fh->refcount; thread_mutex_unlock (&fh->lock); } avl_tree_unlock (fh_cache); } return ret; }
long stats_lock (long handle, const char *mount) { stats_source_t *src_stats = (stats_source_t *)handle; if (src_stats == NULL) src_stats = (stats_source_t*)stats_handle (mount); else avl_tree_wlock (src_stats->stats_tree); return (long)src_stats; }
static void process_source_event (stats_event_t *event) { stats_source_t *snode; avl_tree_wlock (_stats.source_tree); snode = _find_source(_stats.source_tree, event->source); if (snode == NULL) { if (event->action == STATS_EVENT_REMOVE) { avl_tree_unlock (_stats.source_tree); return; } snode = (stats_source_t *)calloc(1,sizeof(stats_source_t)); if (snode == NULL) abort(); DEBUG1 ("new source stat %s", event->source); snode->source = (char *)strdup(event->source); snode->stats_tree = avl_tree_new(_compare_stats, NULL); snode->flags = STATS_SLAVE|STATS_GENERAL|STATS_HIDDEN; avl_insert(_stats.source_tree, (void *)snode); } if (event->action == STATS_EVENT_REMOVE && event->name == NULL) { int fallback_stream = 0; avl_tree_wlock (snode->stats_tree); fallback_stream = _find_node (snode->stats_tree, "fallback") == NULL ? 1 : 0; if (fallback_stream) avl_delete(_stats.source_tree, (void *)snode, _free_source_stats); else avl_tree_unlock (snode->stats_tree); avl_tree_unlock (_stats.source_tree); return; } avl_tree_wlock (snode->stats_tree); avl_tree_unlock (_stats.source_tree); process_source_stat (snode, event); avl_tree_unlock (snode->stats_tree); }
/* if 0 is returned then the client should not be touched, however if -1 * is returned then the caller is responsible for handling the client */ static int add_listener_to_source (source_t *source, client_t *client) { int loop = 10; do { ICECAST_LOG_DEBUG("max on %s is %ld (cur %lu)", source->mount, source->max_listeners, source->listeners); if (source->max_listeners == -1) break; if (source->listeners < (unsigned long)source->max_listeners) break; if (loop && source->fallback_when_full && source->fallback_mount) { source_t *next = source_find_mount (source->fallback_mount); if (!next) { ICECAST_LOG_ERROR("Fallback '%s' for full source '%s' not found", source->mount, source->fallback_mount); return -1; } ICECAST_LOG_INFO("stream full trying %s", next->mount); source = next; loop--; continue; } /* now we fail the client */ return -1; } while (1); client->write_to_client = format_generic_write_to_client; client->check_buffer = format_check_http_buffer; client->refbuf->len = PER_CLIENT_REFBUF_SIZE; memset (client->refbuf->data, 0, PER_CLIENT_REFBUF_SIZE); /* lets add the client to the active list */ avl_tree_wlock (source->pending_tree); avl_insert (source->pending_tree, client); avl_tree_unlock (source->pending_tree); if (source->running == 0 && source->on_demand) { /* enable on-demand relay to start, wake up the slave thread */ ICECAST_LOG_DEBUG("kicking off on-demand relay"); source->on_demand_req = 1; } ICECAST_LOG_DEBUG("Added client to %s", source->mount); return 0; }
void my_free (void *freeblock) { allocheader *block; alloc_node *info; if (freeblock == NULL) return; block = (allocheader*)freeblock -1; info = block->info; avl_tree_wlock (global.alloc_tree); info->count--; info->allocated -= block->len; avl_tree_unlock (global.alloc_tree); free (block); }
/* Remove the provided source from the global tree and free it */ void source_free_source (source_t *source) { DEBUG1 ("freeing source \"%s\"", source->mount); avl_tree_wlock (global.source_tree); avl_delete (global.source_tree, source, NULL); avl_tree_unlock (global.source_tree); avl_tree_free(source->pending_tree, _free_client); avl_tree_free(source->client_tree, _free_client); free (source->mount); free (source); return; }
int fserve_set_override (const char *mount, const char *dest, format_type_t type) { fh_node fh, *result; fh.finfo.flags = FS_FALLBACK; fh.finfo.mount = (char *)mount; fh.finfo.fallback = NULL; fh.finfo.type = type; avl_tree_wlock (fh_cache); result = find_fh (&fh.finfo); if (result) { thread_mutex_lock (&result->lock); if (result->refcount > 0) { fh_node *copy = calloc (1, sizeof (*copy)); avl_delete (fh_cache, result, NULL); copy->finfo = result->finfo; copy->finfo.mount = strdup (copy->finfo.mount); copy->prev_count = -1; // trigger stats update copy->expire = (time_t)-1; copy->stats = result->stats; copy->format = result->format; copy->f = result->f; thread_mutex_create (©->lock); copy->out_bitrate = rate_setup (10000, 1000); copy->clients = avl_tree_new (client_compare, NULL); avl_insert (fh_cache, copy); result->finfo.flags |= FS_DELETE; result->finfo.flags &= ~FS_FALLBACK; result->format = NULL; result->stats = 0; result->f = SOCK_ERROR; result->finfo.fallback = strdup (dest); result->finfo.type = type; } avl_tree_unlock (fh_cache); thread_mutex_unlock (&result->lock); INFO2 ("move clients from %s to %s", mount, dest); return 1; } avl_tree_unlock (fh_cache); return 0; }
/* Remove the provided source from the global tree and free it */ void source_free_source (source_t *source) { DEBUG1 ("freeing source \"%s\"", source->mount); avl_tree_wlock (global.source_tree); avl_delete (global.source_tree, source, NULL); avl_tree_unlock (global.source_tree); avl_tree_free(source->pending_tree, _free_client); avl_tree_free(source->client_tree, _free_client); /* make sure all YP entries have gone */ yp_remove (source->mount); free (source->mount); free (source); return; }
void *my_realloc (const char *file, int line, void *ptr, size_t size) { allocheader *block, *newblock; alloc_node *info; if (ptr == NULL) return my_calloc (file, line, 1, size); if (size == 0) { my_free (ptr); return NULL; } block = (allocheader*)ptr -1; avl_tree_wlock (global.alloc_tree); newblock = realloc (block, sizeof (allocheader)+size); info = newblock->info; info->allocated -= newblock->len; info->allocated += size; newblock->len = size; avl_tree_unlock (global.alloc_tree); return newblock+1; }
static void process_global_event (stats_event_t *event) { stats_node_t *node = NULL; avl_tree_wlock (_stats.global_tree); /* DEBUG3("global event %s %s %d", event->name, event->value, event->action); */ if (event->action == STATS_EVENT_REMOVE) { /* we're deleting */ node = _find_node(_stats.global_tree, event->name); if (node != NULL) { stats_listener_send (node->flags, "DELETE global %s\n", event->name); avl_delete(_stats.global_tree, (void *)node, _free_stats); } avl_tree_unlock (_stats.global_tree); return; } node = _find_node(_stats.global_tree, event->name); if (node) { modify_node_event (node, event); if ((node->flags & STATS_REGULAR) == 0) stats_listener_send (node->flags, "EVENT global %s %s\n", node->name, node->value); } else { /* add node */ node = (stats_node_t *)calloc(1, sizeof(stats_node_t)); node->name = (char *)strdup(event->name); node->value = (char *)strdup(event->value); node->flags = event->flags; avl_insert(_stats.global_tree, (void *)node); stats_listener_send (node->flags, "EVENT global %s %s\n", event->name, event->value); } avl_tree_unlock (_stats.global_tree); }
/* Allocate a new source with the stated mountpoint, if one already * exists with that mountpoint in the global source tree then return * NULL. */ source_t *source_reserve (const char *mount) { source_t *src = NULL; if(mount[0] != '/') WARN1("Source at \"%s\" does not start with '/', clients will be " "unable to connect", mount); do { avl_tree_wlock (global.source_tree); src = source_find_mount_raw (mount); if (src) { src = NULL; break; } src = calloc (1, sizeof(source_t)); if (src == NULL) break; src->client_tree = avl_tree_new(_compare_clients, NULL); src->pending_tree = avl_tree_new(_compare_clients, NULL); /* make duplicates for strings or similar */ src->mount = strdup (mount); src->max_listeners = -1; avl_insert (global.source_tree, src); } while (0); avl_tree_unlock (global.source_tree); return src; }
/* Move clients from source to dest provided dest is running * and that the stream format is the same. * The only lock that should be held when this is called is the * source tree lock */ void source_move_clients (source_t *source, source_t *dest) { /* we don't want the two write locks to deadlock in here */ thread_mutex_lock (&move_clients_mutex); /* if the destination is not running then we can't move clients */ if (dest->running == 0) { WARN1 ("destination mount %s not running, unable to move clients ", dest->mount); thread_mutex_unlock (&move_clients_mutex); return; } avl_tree_wlock (dest->pending_tree); do { client_t *client; /* we need to move the client and pending trees */ avl_tree_wlock (source->pending_tree); if (source->format == NULL) { INFO1 ("source mount %s is not available", source->mount); break; } if (source->format->type != dest->format->type) { WARN2 ("stream %s and %s are of different types, ignored", source->mount, dest->mount); break; } while (1) { avl_node *node = avl_get_first (source->pending_tree); if (node == NULL) break; client = (client_t *)(node->key); avl_delete (source->pending_tree, client, NULL); /* switch client to different queue */ client_set_queue (client, dest->stream_data_tail); avl_insert (dest->pending_tree, (void *)client); } avl_tree_wlock (source->client_tree); while (1) { avl_node *node = avl_get_first (source->client_tree); if (node == NULL) break; client = (client_t *)(node->key); avl_delete (source->client_tree, client, NULL); /* switch client to different queue */ client_set_queue (client, dest->stream_data_tail); avl_insert (dest->pending_tree, (void *)client); } source->listeners = 0; stats_event (source->mount, "listeners", "0"); avl_tree_unlock (source->client_tree); } while (0); avl_tree_unlock (source->pending_tree); avl_tree_unlock (dest->pending_tree); thread_mutex_unlock (&move_clients_mutex); }
int fserve_setup_client_fb (client_t *client, fbinfo *finfo) { fh_node *fh = &no_file; int ret = 0; if (finfo) { mount_proxy *minfo; if (finfo->flags & FS_FALLBACK && finfo->limit == 0) return -1; avl_tree_wlock (fh_cache); fh = find_fh (finfo); minfo = config_find_mount (config_get_config(), finfo->mount); if (fh) { thread_mutex_lock (&fh->lock); avl_tree_unlock (fh_cache); client->shared_data = NULL; if (minfo) { if (minfo->max_listeners >= 0 && fh->refcount > minfo->max_listeners) { thread_mutex_unlock (&fh->lock); config_release_config(); return client_send_403redirect (client, finfo->mount, "max listeners reached"); } if (check_duplicate_logins (finfo->mount, fh->clients, client, minfo->auth) == 0) { thread_mutex_unlock (&fh->lock); config_release_config(); return client_send_403 (client, "Account already in use"); } } config_release_config(); } else { if (minfo && minfo->max_listeners == 0) { avl_tree_unlock (fh_cache); config_release_config(); client->shared_data = NULL; return client_send_403redirect (client, finfo->mount, "max listeners reached"); } config_release_config(); fh = open_fh (finfo); if (fh == NULL) return client_send_404 (client, NULL); } if (fh->finfo.limit) { client->timer_start = client->worker->current_time.tv_sec; if (client->connection.sent_bytes == 0) client->timer_start -= 2; client->counter = 0; client->intro_offset = 0; global_reduce_bitrate_sampling (global.out_bitrate); } } else thread_mutex_lock (&fh->lock); client->mount = fh->finfo.mount; if (fh->finfo.type == FORMAT_TYPE_UNDEFINED) { if (client->respcode == 0) { client->refbuf->len = 0; ret = format_general_headers (fh->format, client); } } else { if (fh->format->create_client_data && client->format_data == NULL) ret = fh->format->create_client_data (fh->format, client); if (fh->format->write_buf_to_client) client->check_buffer = fh->format->write_buf_to_client; } if (ret < 0) { thread_mutex_unlock (&fh->lock); return client_send_416 (client); } fh_add_client (fh, client); thread_mutex_unlock (&fh->lock); client->shared_data = fh; if (client->check_buffer == NULL) client->check_buffer = format_generic_write_to_client; client->ops = &buffer_content_ops; client->flags &= ~CLIENT_HAS_INTRO_CONTENT; client->flags |= CLIENT_IN_FSERVE; if (client->flags & CLIENT_ACTIVE) { client->schedule_ms = client->worker->time_ms; if (finfo && finfo->flags & FS_FALLBACK) return 0; // prevent a recursive loop return client->ops->process (client); } else { worker_t *worker = client->worker; client->flags |= CLIENT_ACTIVE; worker_wakeup (worker); /* worker may of already processed client but make sure */ } return 0; }
void source_clear_source (source_t *source) { int c; DEBUG1 ("clearing source \"%s\"", source->mount); avl_tree_wlock (source->pending_tree); client_destroy(source->client); source->client = NULL; source->parser = NULL; source->con = NULL; /* log bytes read in access log */ if (source->client && source->format) source->client->con->sent_bytes = source->format->read_bytes; if (source->dumpfile) { INFO1 ("Closing dumpfile for %s", source->mount); fclose (source->dumpfile); source->dumpfile = NULL; } /* lets kick off any clients that are left on here */ avl_tree_wlock (source->client_tree); c=0; while (1) { avl_node *node = avl_get_first (source->client_tree); if (node) { client_t *client = node->key; if (client->respcode == 200) c++; /* only count clients that have had some processing */ avl_delete (source->client_tree, client, _free_client); continue; } break; } if (c) { stats_event_sub (NULL, "listeners", source->listeners); INFO2 ("%d active listeners on %s released", c, source->mount); } avl_tree_unlock (source->client_tree); while (avl_get_first (source->pending_tree)) { avl_delete (source->pending_tree, avl_get_first(source->pending_tree)->key, _free_client); } if (source->format && source->format->free_plugin) source->format->free_plugin (source->format); source->format = NULL; /* Lets clear out the source queue too */ while (source->stream_data) { refbuf_t *p = source->stream_data; source->stream_data = p->next; p->next = NULL; /* can be referenced by burst handler as well */ while (p->_count > 1) refbuf_release (p); refbuf_release (p); } source->stream_data_tail = NULL; source->burst_point = NULL; source->burst_size = 0; source->burst_offset = 0; source->queue_size = 0; source->queue_size_limit = 0; source->listeners = 0; source->max_listeners = -1; source->prev_listeners = 0; source->hidden = 0; source->shoutcast_compat = 0; source->client_stats_update = 0; util_dict_free (source->audio_info); source->audio_info = NULL; free(source->fallback_mount); source->fallback_mount = NULL; free(source->dumpfilename); source->dumpfilename = NULL; if (source->intro_file) { fclose (source->intro_file); source->intro_file = NULL; } source->on_demand_req = 0; avl_tree_unlock (source->pending_tree); }
/* Move clients from source to dest provided dest is running * and that the stream format is the same. * The only lock that should be held when this is called is the * source tree lock */ void source_move_clients (source_t *source, source_t *dest) { unsigned long count = 0; if (strcmp (source->mount, dest->mount) == 0) { WARN1 ("src and dst are the same \"%s\", skipping", source->mount); return; } /* we don't want the two write locks to deadlock in here */ thread_mutex_lock (&move_clients_mutex); /* if the destination is not running then we can't move clients */ avl_tree_wlock (dest->pending_tree); if (dest->running == 0 && dest->on_demand == 0) { WARN1 ("destination mount %s not running, unable to move clients ", dest->mount); avl_tree_unlock (dest->pending_tree); thread_mutex_unlock (&move_clients_mutex); return; } do { client_t *client; /* we need to move the client and pending trees - we must take the * locks in this order to avoid deadlocks */ avl_tree_wlock (source->pending_tree); avl_tree_wlock (source->client_tree); if (source->on_demand == 0 && source->format == NULL) { INFO1 ("source mount %s is not available", source->mount); break; } if (source->format && dest->format) { if (source->format->type != dest->format->type) { WARN2 ("stream %s and %s are of different types, ignored", source->mount, dest->mount); break; } } while (1) { avl_node *node = avl_get_first (source->pending_tree); if (node == NULL) break; client = (client_t *)(node->key); avl_delete (source->pending_tree, client, NULL); /* when switching a client to a different queue, be wary of the * refbuf it's referring to, if it's http headers then we need * to write them so don't release it. */ if (client->check_buffer != format_check_http_buffer) { client_set_queue (client, NULL); client->check_buffer = format_check_file_buffer; if (source->con == NULL) client->intro_offset = -1; } avl_insert (dest->pending_tree, (void *)client); count++; } while (1) { avl_node *node = avl_get_first (source->client_tree); if (node == NULL) break; client = (client_t *)(node->key); avl_delete (source->client_tree, client, NULL); /* when switching a client to a different queue, be wary of the * refbuf it's referring to, if it's http headers then we need * to write them so don't release it. */ if (client->check_buffer != format_check_http_buffer) { client_set_queue (client, NULL); client->check_buffer = format_check_file_buffer; if (source->con == NULL) client->intro_offset = -1; } avl_insert (dest->pending_tree, (void *)client); count++; } INFO2 ("passing %lu listeners to \"%s\"", count, dest->mount); source->listeners = 0; stats_event (source->mount, "listeners", "0"); } while (0); avl_tree_unlock (source->pending_tree); avl_tree_unlock (source->client_tree); /* see if we need to wake up an on-demand relay */ if (dest->running == 0 && dest->on_demand && count) dest->on_demand_req = 1; avl_tree_unlock (dest->pending_tree); thread_mutex_unlock (&move_clients_mutex); }
static void process_source_stat (stats_source_t *src_stats, stats_event_t *event) { if (event->name) { stats_node_t *node = _find_node (src_stats->stats_tree, event->name); if (node == NULL) { /* adding node */ if (event->action != STATS_EVENT_REMOVE && event->value) { DEBUG3 ("new node on %s \"%s\" (%s)", src_stats->source, event->name, event->value); node = (stats_node_t *)calloc (1,sizeof(stats_node_t)); node->name = (char *)strdup (event->name); node->value = (char *)strdup (event->value); node->flags = event->flags; if (src_stats->flags & STATS_HIDDEN) node->flags |= STATS_HIDDEN; stats_listener_send (node->flags, "EVENT %s %s %s\n", src_stats->source, event->name, event->value); avl_insert (src_stats->stats_tree, (void *)node); } return; } if (event->action == STATS_EVENT_REMOVE) { DEBUG2 ("delete node %s from %s", event->name, src_stats->source); stats_listener_send (node->flags, "DELETE %s %s\n", src_stats->source, event->name); avl_delete (src_stats->stats_tree, (void *)node, _free_stats); return; } modify_node_event (node, event); stats_listener_send (node->flags, "EVENT %s %s %s\n", src_stats->source, node->name, node->value); return; } if (event->action == STATS_EVENT_REMOVE && event->name == NULL) { avl_tree_unlock (src_stats->stats_tree); avl_tree_wlock (_stats.source_tree); avl_tree_wlock (src_stats->stats_tree); avl_delete (_stats.source_tree, (void *)src_stats, _free_source_stats); avl_tree_unlock (_stats.source_tree); return; } /* change source flags status */ if (event->action & STATS_EVENT_HIDDEN) { avl_node *node = avl_get_first (src_stats->stats_tree); int visible = 0; if ((event->flags&STATS_HIDDEN) == (src_stats->flags&STATS_HIDDEN)) return; if (src_stats->flags & STATS_HIDDEN) { stats_node_t *ct = _find_node (src_stats->stats_tree, "server_type"); const char *type = "audio/mpeg"; if (ct) type = ct->value; src_stats->flags &= ~STATS_HIDDEN; stats_listener_send (src_stats->flags, "NEW %s %s\n", type, src_stats->source); visible = 1; } else { stats_listener_send (src_stats->flags, "DELETE %s\n", src_stats->source); src_stats->flags |= STATS_HIDDEN; } while (node) { stats_node_t *stats = (stats_node_t*)node->key; if (visible) { stats->flags &= ~STATS_HIDDEN; stats_listener_send (stats->flags, "EVENT %s %s %s\n", src_stats->source, stats->name, stats->value); } else stats->flags |= STATS_HIDDEN; node = avl_get_next (node); } } }
void source_main (source_t *source) { refbuf_t *refbuf; client_t *client; avl_node *client_node; source_init (source); while (global.running == ICE_RUNNING && source->running) { int remove_from_q; refbuf = get_next_buffer (source); remove_from_q = 0; source->short_delay = 0; if (refbuf) { /* append buffer to the in-flight data queue, */ if (source->stream_data == NULL) { source->stream_data = refbuf; source->burst_point = refbuf; } if (source->stream_data_tail) source->stream_data_tail->next = refbuf; source->stream_data_tail = refbuf; source->queue_size += refbuf->len; /* new buffer is referenced for burst */ refbuf_addref (refbuf); /* new data on queue, so check the burst point */ source->burst_offset += refbuf->len; while (source->burst_offset > source->burst_size) { refbuf_t *to_release = source->burst_point; if (to_release->next) { source->burst_point = to_release->next; source->burst_offset -= to_release->len; refbuf_release (to_release); continue; } break; } /* save stream to file */ if (source->dumpfile && source->format->write_buf_to_file) source->format->write_buf_to_file (source, refbuf); } /* lets see if we have too much data in the queue, but don't remove it until later */ if (source->queue_size > source->queue_size_limit) remove_from_q = 1; /* acquire write lock on pending_tree */ avl_tree_wlock(source->pending_tree); /* acquire write lock on client_tree */ avl_tree_wlock(source->client_tree); client_node = avl_get_first(source->client_tree); while (client_node) { client = (client_t *)client_node->key; send_to_listener (source, client, remove_from_q); if (client->con->error) { client_node = avl_get_next(client_node); if (client->respcode == 200) stats_event_dec (NULL, "listeners"); avl_delete(source->client_tree, (void *)client, _free_client); source->listeners--; DEBUG0("Client removed"); continue; } client_node = avl_get_next(client_node); } /** add pending clients **/ client_node = avl_get_first(source->pending_tree); while (client_node) { if(source->max_listeners != -1 && source->listeners >= (unsigned long)source->max_listeners) { /* The common case is caught in the main connection handler, * this deals with rarer cases (mostly concerning fallbacks) * and doesn't give the listening client any information about * why they were disconnected */ client = (client_t *)client_node->key; client_node = avl_get_next(client_node); avl_delete(source->pending_tree, (void *)client, _free_client); INFO0("Client deleted, exceeding maximum listeners for this " "mountpoint."); continue; } /* Otherwise, the client is accepted, add it */ avl_insert(source->client_tree, client_node->key); source->listeners++; DEBUG0("Client added"); stats_event_inc(source->mount, "connections"); client_node = avl_get_next(client_node); } /** clear pending tree **/ while (avl_get_first(source->pending_tree)) { avl_delete(source->pending_tree, avl_get_first(source->pending_tree)->key, source_remove_client); } /* release write lock on pending_tree */ avl_tree_unlock(source->pending_tree); /* update the stats if need be */ if (source->listeners != source->prev_listeners) { source->prev_listeners = source->listeners; INFO2("listener count on %s now %lu", source->mount, source->listeners); if (source->listeners > source->peak_listeners) { source->peak_listeners = source->listeners; stats_event_args (source->mount, "listener_peak", "%lu", source->peak_listeners); } stats_event_args (source->mount, "listeners", "%lu", source->listeners); if (source->listeners == 0 && source->on_demand) source->running = 0; } /* lets reduce the queue, any lagging clients should of been * terminated by now */ if (source->stream_data) { /* normal unreferenced queue data will have a refcount 1, but * burst queue data will be at least 2, active clients will also * increase refcount */ while (source->stream_data->_count == 1) { refbuf_t *to_go = source->stream_data; if (to_go->next == NULL || source->burst_point == to_go) { /* this should not happen */ ERROR0 ("queue state is unexpected"); source->running = 0; break; } source->stream_data = to_go->next; source->queue_size -= to_go->len; to_go->next = NULL; refbuf_release (to_go); } } /* release write lock on client_tree */ avl_tree_unlock(source->client_tree); } source_shutdown (source); }
void fserve_scan (time_t now) { avl_node *node; avl_tree_wlock (fh_cache); node = avl_get_first (fh_cache); while (node) { fh_node *fh = node->key; node = avl_get_next (node); thread_mutex_lock (&fh->lock); if (global.running != ICE_RUNNING) fh->expire = 0; if (now == (time_t)0) { fh->expire = 0; thread_mutex_unlock (&fh->lock); continue; } if (fh->finfo.limit) { fbinfo *finfo = &fh->finfo; if (fh->stats == 0) { int len = strlen (finfo->mount) + 10; char *str = alloca (len); char buf[20]; snprintf (str, len, "%s-%s", (finfo->flags & FS_FALLBACK) ? "fallback" : "file", finfo->mount); fh->stats = stats_handle (str); stats_set_flags (fh->stats, "fallback", "file", STATS_COUNTERS|STATS_HIDDEN); stats_set_flags (fh->stats, "outgoing_kbitrate", "0", STATS_COUNTERS|STATS_HIDDEN); snprintf (buf, sizeof (buf), "%d", fh->refcount); stats_set_flags (fh->stats, "listeners", buf, STATS_GENERAL|STATS_HIDDEN); snprintf (buf, sizeof (buf), "%d", fh->peak); stats_set_flags (fh->stats, "listener_peak", buf, STATS_GENERAL|STATS_HIDDEN); fh->prev_count = fh->refcount; } else { stats_lock (fh->stats, NULL); if (fh->prev_count != fh->refcount) { fh->prev_count = fh->refcount; stats_set_args (fh->stats, "listeners", "%ld", fh->refcount); stats_set_args (fh->stats, "listener_peak", "%ld", fh->peak); } } if (fh->stats_update <= now) { fh->stats_update = now + 5; stats_set_args (fh->stats, "outgoing_kbitrate", "%ld", (long)((8 * rate_avg (fh->out_bitrate))/1024)); } stats_release (fh->stats); } if (fh->refcount == 0 && fh->expire >= 0 && now >= fh->expire) { DEBUG1 ("timeout of %s", fh->finfo.mount); if (fh->stats) { stats_lock (fh->stats, NULL); stats_set (fh->stats, NULL, NULL); } remove_fh_from_cache (fh); thread_mutex_unlock (&fh->lock); _delete_fh (fh); continue; } thread_mutex_unlock (&fh->lock); } avl_tree_unlock (fh_cache); }
int fserve_setup_client_fb (client_t *client, fbinfo *finfo) { fh_node *fh = &no_file; int ret = 0; refbuf_t *refbuf; ssize_t bytes; if (finfo) { mount_proxy *minfo; if (finfo->flags & FS_FALLBACK && finfo->limit == 0) return -1; avl_tree_wlock (fh_cache); fh = find_fh (finfo); minfo = config_find_mount (config_get_config(), finfo->mount); if (fh) { thread_mutex_lock (&fh->lock); avl_tree_unlock (fh_cache); client->shared_data = NULL; if (minfo) { if (minfo->max_listeners >= 0 && fh->refcount > minfo->max_listeners) { thread_mutex_unlock (&fh->lock); config_release_config(); return client_send_403redirect (client, finfo->mount, "max listeners reached"); } if (check_duplicate_logins (finfo->mount, fh->clients, client, minfo->auth) == 0) { thread_mutex_unlock (&fh->lock); config_release_config(); return client_send_403 (client, "Account already in use"); } } config_release_config(); } else { if (minfo && minfo->max_listeners == 0) { avl_tree_unlock (fh_cache); config_release_config(); client->shared_data = NULL; return client_send_403redirect (client, finfo->mount, "max listeners reached"); } config_release_config(); fh = open_fh (finfo); if (fh == NULL) return client_send_404 (client, NULL); if (fh->finfo.limit) DEBUG2 ("request for throttled file %s (bitrate %d)", fh->finfo.mount, fh->finfo.limit*8); } if (fh->finfo.limit) { client->timer_start = client->worker->current_time.tv_sec; if (client->connection.sent_bytes == 0) client->timer_start -= 2; client->counter = 0; client->intro_offset = 0; global_reduce_bitrate_sampling (global.out_bitrate); } } else { if (client->mount && (client->flags & CLIENT_AUTHENTICATED) && (client->respcode >= 300 || client->respcode < 200)) { fh = calloc (1, sizeof (no_file)); fh->finfo.mount = strdup (client->mount); fh->finfo.flags |= FS_DELETE; fh->refcount = 1; fh->f = SOCK_ERROR; thread_mutex_create (&fh->lock); } thread_mutex_lock (&fh->lock); } client->mount = fh->finfo.mount; if (fh->finfo.type == FORMAT_TYPE_UNDEFINED) { if (client->respcode == 0) { client->refbuf->len = 0; ret = format_general_headers (fh->format, client); } } else { if (fh->format->create_client_data && client->format_data == NULL) ret = fh->format->create_client_data (fh->format, client); if (fh->format->write_buf_to_client) client->check_buffer = fh->format->write_buf_to_client; } if (ret < 0) { thread_mutex_unlock (&fh->lock); return client_send_416 (client); } fh_add_client (fh, client); thread_mutex_unlock (&fh->lock); client->shared_data = fh; if (client->check_buffer == NULL) client->check_buffer = format_generic_write_to_client; // workaround for #134: fill the preallocated, but empty, chained buffer in case a range request was made if (client->flags & CLIENT_RANGE_END) { if (client->refbuf && client->refbuf->next) { refbuf = client->refbuf->next; bytes = pread (fh->f, refbuf->data, refbuf->len, client->intro_offset); if (bytes < 0) return -1; } } client->ops = &buffer_content_ops; client->flags &= ~CLIENT_HAS_INTRO_CONTENT; client->flags |= CLIENT_IN_FSERVE; if (client->flags & CLIENT_ACTIVE) { client->schedule_ms = client->worker->time_ms; if (finfo && finfo->flags & FS_FALLBACK) return 0; // prevent a recursive loop return client->ops->process (client); } else { worker_t *worker = client->worker; ret = (fh->finfo.limit) ? 0 : -1; client->flags |= CLIENT_ACTIVE; worker_wakeup (worker); /* worker may of already processed client but make sure */ } return ret; }
static void _handle_get_request(connection_t *con, http_parser_t *parser, char *passed_uri) { char *fullpath; client_t *client; int bytes; struct stat statbuf; source_t *source; int fileserve; char *host; int port; int i; char *serverhost = NULL; int serverport = 0; aliases *alias; ice_config_t *config; int client_limit; int ret; char *uri = passed_uri; config = config_get_config(); fileserve = config->fileserve; host = config->hostname; port = config->port; for(i = 0; i < MAX_LISTEN_SOCKETS; i++) { if(global.serversock[i] == con->serversock) { serverhost = config->listeners[i].bind_address; serverport = config->listeners[i].port; break; } } alias = config->aliases; client_limit = config->client_limit; /* there are several types of HTTP GET clients ** media clients, which are looking for a source (eg, URI = /stream.ogg) ** stats clients, which are looking for /admin/stats.xml ** and directory server authorizers, which are looking for /GUID-xxxxxxxx ** (where xxxxxx is the GUID in question) - this isn't implemented yet. ** we need to handle the latter two before the former, as the latter two ** aren't subject to the limits. */ /* TODO: add GUID-xxxxxx */ /* Handle aliases */ while(alias) { if(strcmp(uri, alias->source) == 0 && (alias->port == -1 || alias->port == serverport) && (alias->bind_address == NULL || (serverhost != NULL && strcmp(alias->bind_address, serverhost) == 0))) { uri = strdup (alias->destination); DEBUG2 ("alias has made %s into %s", passed_uri, uri); break; } alias = alias->next; } config_release_config(); /* make a client */ client = client_create(con, parser); stats_event_inc(NULL, "client_connections"); /* Dispatch all admin requests */ if (strncmp(uri, "/admin/", 7) == 0) { admin_handle_request(client, uri); if (uri != passed_uri) free (uri); return; } /* Here we are parsing the URI request to see ** if the extension is .xsl, if so, then process ** this request as an XSLT request */ fullpath = util_get_path_from_normalised_uri(uri); if (util_check_valid_extension(fullpath) == XSLT_CONTENT) { /* If the file exists, then transform it, otherwise, write a 404 */ if (stat(fullpath, &statbuf) == 0) { DEBUG0("Stats request, sending XSL transformed stats"); client->respcode = 200; bytes = sock_write(client->con->sock, "HTTP/1.0 200 OK\r\nContent-Type: text/html\r\n\r\n"); if(bytes > 0) client->con->sent_bytes = bytes; stats_transform_xslt(client, fullpath); client_destroy(client); } else { client_send_404(client, "The file you requested could not be found"); } free(fullpath); if (uri != passed_uri) free (uri); return; } else if(fileserve && stat(fullpath, &statbuf) == 0 && #ifdef _WIN32 ((statbuf.st_mode) & _S_IFREG)) #else S_ISREG(statbuf.st_mode)) #endif { fserve_client_create(client, fullpath); free(fullpath); if (uri != passed_uri) free (uri); return; } free(fullpath); if(strcmp(util_get_extension(uri), "m3u") == 0) { char *sourceuri = strdup(uri); char *dot = strrchr(sourceuri, '.'); *dot = 0; client->respcode = 200; bytes = sock_write(client->con->sock, "HTTP/1.0 200 OK\r\n" "Content-Type: audio/x-mpegurl\r\n\r\n" "http://%s:%d%s\r\n", host, port, sourceuri ); if(bytes > 0) client->con->sent_bytes = bytes; client_destroy(client); free(sourceuri); if (uri != passed_uri) free (uri); return; } global_lock(); if (global.clients >= client_limit) { global_unlock(); client_send_404(client, "The server is already full. Try again later."); if (uri != passed_uri) free (uri); return; } global_unlock(); avl_tree_rlock(global.source_tree); source = source_find_mount(uri); if (source) { DEBUG0("Source found for client"); /* The source may not be the requested source - it might have gone * via one or more fallbacks. We only reject it for no-mount if it's * the originally requested source */ if(strcmp(uri, source->mount) == 0 && source->no_mount) { avl_tree_unlock(global.source_tree); client_send_404(client, "This mount is unavailable."); if (uri != passed_uri) free (uri); return; } if (source->running == 0) { avl_tree_unlock(global.source_tree); DEBUG0("inactive source, client dropped"); client_send_404(client, "This mount is unavailable."); if (uri != passed_uri) free (uri); return; } /* Check for any required authentication first */ if(source->authenticator != NULL) { ret = auth_check_client(source, client); if(ret != AUTH_OK) { avl_tree_unlock(global.source_tree); if (ret == AUTH_FORBIDDEN) { INFO1("Client attempted to log multiple times to source " "(\"%s\")", uri); client_send_403(client); } else { /* If not FORBIDDEN, default to 401 */ INFO1("Client attempted to log in to source (\"%s\")with " "incorrect or missing password", uri); client_send_401(client); } if (uri != passed_uri) free (uri); return; } } /* And then check that there's actually room in the server... */ global_lock(); if (global.clients >= client_limit) { global_unlock(); avl_tree_unlock(global.source_tree); client_send_404(client, "The server is already full. Try again later."); if (uri != passed_uri) free (uri); return; } /* Early-out for per-source max listeners. This gets checked again * by the source itself, later. This route gives a useful message to * the client, also. */ else if(source->max_listeners != -1 && source->listeners >= source->max_listeners) { global_unlock(); avl_tree_unlock(global.source_tree); client_send_404(client, "Too many clients on this mountpoint. Try again later."); if (uri != passed_uri) free (uri); return; } global.clients++; global_unlock(); source->format->create_client_data (source, client); source->format->client_send_headers(source->format, source, client); bytes = sock_write(client->con->sock, "\r\n"); if(bytes > 0) client->con->sent_bytes += bytes; sock_set_blocking(client->con->sock, SOCK_NONBLOCK); sock_set_nodelay(client->con->sock); avl_tree_wlock(source->pending_tree); avl_insert(source->pending_tree, (void *)client); avl_tree_unlock(source->pending_tree); } avl_tree_unlock(global.source_tree); if (!source) { DEBUG0("Source not found for client"); client_send_404(client, "The source you requested could not be found."); } if (uri != passed_uri) free (uri); }