X-Git-Url: http://git.samba.org/?p=metze%2Fwireshark%2Fwip.git;a=blobdiff_plain;f=extcap.c;h=cdd8e0f70e02cb6792f90e8baa5f2d6077fe504f;hp=b8a1fd7d2a2f906dcacf3c7c355c32fcb860ed77;hb=6648e34c061a14d4f76e22ed891aae8ceede520a;hpb=fc17bfc4f13f09d58c0c7dbf9b116f30ae9c4c50 diff --git a/extcap.c b/extcap.c index b8a1fd7d2a..cdd8e0f70e 100644 --- a/extcap.c +++ b/extcap.c @@ -90,8 +90,67 @@ typedef struct _extcap_callback_info_t /* Callback definition for extcap_foreach */ typedef gboolean(*extcap_cb_t)(extcap_callback_info_t info_structure); +/** GThreadPool does not support pushing new work from a thread while waiting + * for the thread pool to finish. This data structure tracks ongoing work. + * See https://gitlab.gnome.org/GNOME/glib/issues/1598 */ +typedef struct thread_pool { + GThreadPool *pool; + gint count; /**< Number of tasks that have not finished. */ + GCond cond; + GMutex data_mutex; +} thread_pool_t; + +/** + * Callback definition for extcap_run_all, invoked with a thread pool (to + * schedule new tasks), an opaque data parameter, and the output from last task + * (or NULL if it failed). The output must be freed by the callback function. + * The implementation MUST be thread-safe. + */ +typedef void (*extcap_run_cb_t)(thread_pool_t *pool, void *data, char *output); + +typedef struct extcap_run_task { + const char *extcap_path; + char **argv; /**< NULL-terminated arguments list, freed when the task is completed. */ + extcap_run_cb_t output_cb; + void *data; /** Parameter to be passed to output_cb. */ +} extcap_run_task_t; + +typedef struct extcap_iface_info { + char *ifname; /**< Interface name. */ + char *output; /**< Output of --extcap-config. */ +} extcap_iface_info_t; + +typedef struct extcap_run_extcaps_info { + char *extcap_path; /**< Extcap program path, MUST be the first member. */ + char *output; /**< Output of --extcap-interfaces. */ + guint num_interfaces; /**< Number of discovered interfaces. */ + extcap_iface_info_t *iface_infos; /**< Per-interface information. */ +} extcap_run_extcaps_info_t; + + static void extcap_load_interface_list(void); +static gboolean +thread_pool_push(thread_pool_t *pool, gpointer data, GError **error) +{ + g_mutex_lock(&pool->data_mutex); + ++pool->count; + g_mutex_unlock(&pool->data_mutex); + return g_thread_pool_push(pool->pool, data, error); +} + +static void +thread_pool_wait(thread_pool_t *pool) +{ + g_mutex_lock(&pool->data_mutex); + while (pool->count != 0) { + g_cond_wait(&pool->cond, &pool->data_mutex); + } + g_cond_clear(&pool->cond); + g_mutex_unlock(&pool->data_mutex); + g_mutex_clear(&pool->data_mutex); +} + GHashTable * extcap_loaded_interfaces(void) { @@ -116,52 +175,35 @@ extcap_clear_interfaces(void) _tool_for_ifname = NULL; } -guint extcap_count(void) +/** + * Obtains a list of extcap program paths. Use g_slist_free_full(paths, g_free) + * to destroy the list. + */ +static GSList * +extcap_get_extcap_paths(void) { - const char *dirname = get_extcap_dir(); GDir *dir; - guint count; - - count = 0; - - if ((dir = g_dir_open(dirname, 0, NULL)) != NULL) - { - GString *extcap_path = NULL; - const gchar *file; + const char *dirname = get_extcap_dir(); + const gchar *file; + GSList *paths = NULL; - extcap_path = g_string_new(""); - while ((file = g_dir_read_name(dir)) != NULL) - { + if ((dir = g_dir_open(dirname, 0, NULL)) != NULL) { + while ((file = g_dir_read_name(dir)) != NULL) { /* full path to extcap binary */ - g_string_printf(extcap_path, "%s" G_DIR_SEPARATOR_S "%s", dirname, file); + gchar *extcap_path = g_strdup_printf("%s" G_DIR_SEPARATOR_S "%s", dirname, file); /* treat anything executable as an extcap binary */ - if (g_file_test(extcap_path->str, G_FILE_TEST_IS_REGULAR) && - g_file_test(extcap_path->str, G_FILE_TEST_IS_EXECUTABLE)) - { - count++; + if (g_file_test(extcap_path, G_FILE_TEST_IS_REGULAR) && + g_file_test(extcap_path, G_FILE_TEST_IS_EXECUTABLE)) { + paths = g_slist_append(paths, extcap_path); + } else { + g_free(extcap_path); } - } + } g_dir_close(dir); - g_string_free(extcap_path, TRUE); - } - return count; -} - -static gboolean -extcap_if_exists(const gchar *ifname) -{ - if (!ifname || !_tool_for_ifname) - { - return FALSE; - } - - if (g_hash_table_lookup(_tool_for_ifname, ifname)) - { - return TRUE; } - return FALSE; + return paths; } static extcap_interface * @@ -244,19 +286,6 @@ extcap_free_toolbar(gpointer data) g_free(toolbar); } -static gboolean -extcap_if_exists_for_extcap(const gchar *ifname, const gchar *extcap) -{ - extcap_interface *entry = extcap_find_interface_for_ifname(ifname); - - if (entry && strcmp(entry->extcap_path, extcap) == 0) - { - return TRUE; - } - - return FALSE; -} - static gchar * extcap_if_executable(const gchar *ifname) { @@ -314,70 +343,132 @@ static void extcap_free_array(gchar ** args, int argc) g_free(args); } -/* Note: args does not need to be NULL-terminated. */ -static gboolean extcap_foreach(GList * arguments, - extcap_cb_t cb, extcap_callback_info_t cb_info, GList * fallback_arguments) +static void +extcap_free_extcaps_info_array(extcap_run_extcaps_info_t *infos, guint count) +{ + for (guint i = 0; i < count; i++) { + g_free(infos[i].extcap_path); + g_free(infos[i].output); + for (guint j = 0; j < infos[i].num_interfaces; j++) { + extcap_iface_info_t *iface_info = &infos[i].iface_infos[j]; + g_free(iface_info->ifname); + g_free(iface_info->output); + } + g_free(infos[i].iface_infos); + } + g_free(infos); +} + +static void +extcap_run_one(const extcap_interface *interface, GList *arguments, extcap_cb_t cb, void *user_data, char **err_str) { - GDir *dir; - gboolean keep_going; const char *dirname = get_extcap_dir(); + gchar **args = extcap_convert_arguments_to_array(arguments); + int cnt = g_list_length(arguments); + gchar *command_output; + if (ws_pipe_spawn_sync(dirname, interface->extcap_path, cnt, args, &command_output)) { + extcap_callback_info_t cb_info = { + .ifname = interface->call, + .extcap = interface->extcap_path, + .output = command_output, + .data = user_data, + .err_str = err_str, + }; + cb(cb_info); + g_free(command_output); + } + extcap_free_array(args, cnt); +} - keep_going = TRUE; +/** Thread callback to run an extcap program and pass its output. */ +static void +extcap_thread_callback(gpointer data, gpointer user_data) +{ + extcap_run_task_t *task = (extcap_run_task_t *)data; + thread_pool_t *pool = (thread_pool_t *)user_data; + const char *dirname = get_extcap_dir(); - if (arguments && (dir = g_dir_open(dirname, 0, NULL)) != NULL) - { - GString *extcap_path = NULL; - const gchar *file; - gchar ** args = extcap_convert_arguments_to_array(arguments); - int cnt = g_list_length(arguments); + char *command_output; + if (ws_pipe_spawn_sync(dirname, task->extcap_path, g_strv_length(task->argv), task->argv, &command_output)) { + task->output_cb(pool, task->data, command_output); + } else { + task->output_cb(pool, task->data, NULL); + } + g_strfreev(task->argv); + g_free(task); - extcap_path = g_string_new(""); - while (keep_going && (file = g_dir_read_name(dir)) != NULL) - { - gchar *command_output = NULL; + // Notify when all tasks are completed and no new subtasks were created. + g_mutex_lock(&pool->data_mutex); + if (--pool->count == 0) { + g_cond_signal(&pool->cond); + } + g_mutex_unlock(&pool->data_mutex); +} - /* full path to extcap binary */ - g_string_printf(extcap_path, "%s" G_DIR_SEPARATOR_S "%s", dirname, file); - /* treat anything executable as an extcap binary */ - if (g_file_test(extcap_path->str, G_FILE_TEST_IS_REGULAR) && - g_file_test(extcap_path->str, G_FILE_TEST_IS_EXECUTABLE)) - { - if (extcap_if_exists(cb_info.ifname) && !extcap_if_exists_for_extcap(cb_info.ifname, extcap_path->str)) - { - continue; - } +/* + * Run all extcap programs with the given arguments list, invoke the callback to + * do some processing and return the results. + * + * @param [IN] argv NULL-terminated arguments list. + * @param [IN] output_cb Thread callback function that receives the output. + * @param [IN] data_size Size of the per-program information that will be returned. + * @param [OUT] count Size of the returned array. + * @return Array of information or NULL if there are none. The first member of + * each element (char *extcap_path) must be freed. + */ +static gpointer +extcap_run_all(const char *argv[], extcap_run_cb_t output_cb, gsize data_size, guint *count) +{ + /* Need enough space for at least 'extcap_path'. */ + g_assert(data_size >= sizeof(char *)); - if (ws_pipe_spawn_sync((gchar *) dirname, extcap_path->str, cnt, args, &command_output)) - { - cb_info.output = command_output; - cb_info.extcap = extcap_path->str; + GSList *paths = extcap_get_extcap_paths(); + int i = 0; +#if GLIB_CHECK_VERSION(2,36,0) + int max_threads = (int)g_get_num_processors(); +#else + // If the number of processors is unavailable, just use some sane maximum. + // extcap should not be CPU bound, so -1 could also be used for unlimited. + int max_threads = 8; +#endif - keep_going = cb(cb_info); - } - else if ( fallback_arguments ) - { - gchar ** fb_args = extcap_convert_arguments_to_array(fallback_arguments); + if (!paths) { + *count = 0; + return NULL; + } - if (ws_pipe_spawn_sync((gchar *) dirname, extcap_path->str, g_list_length(fallback_arguments), fb_args, &command_output)) - { - cb_info.output = command_output; - cb_info.extcap = extcap_path->str; + guint64 start_time = g_get_monotonic_time(); + guint paths_count = g_slist_length(paths); + /* GSList is not thread-safe, so pre-allocate an array instead. */ + gpointer infos = g_malloc0_n(paths_count, data_size); - keep_going = cb(cb_info); - } - } + thread_pool_t pool; + pool.pool = g_thread_pool_new(extcap_thread_callback, &pool, max_threads, FALSE, NULL); + pool.count = 0; + g_cond_init(&pool.cond); + g_mutex_init(&pool.data_mutex); - g_free(command_output); - } + for (GSList *path = paths; path; path = g_slist_next(path), i++) { + extcap_run_task_t *task = g_new0(extcap_run_task_t, 1); - } - extcap_free_array(args, cnt); + task->extcap_path = (char *)path->data; + task->argv = g_strdupv((char **)argv); + task->output_cb = output_cb; + task->data = ((char *)infos) + (i * data_size); + *((char **)task->data) = (char *)path->data; - g_dir_close(dir); - g_string_free(extcap_path, TRUE); + thread_pool_push(&pool, task, NULL); } + g_slist_free(paths); /* Note: the contents are transferred to 'infos'. */ - return keep_going; + /* Wait for all (sub)tasks to complete. */ + thread_pool_wait(&pool); + g_thread_pool_free(pool.pool, FALSE, TRUE); + + g_log(LOG_DOMAIN_CAPTURE, G_LOG_LEVEL_DEBUG, "extcap: completed discovery of %d tools in %.3fms", + paths_count, (g_get_monotonic_time() - start_time) / 1000.0); + *count = paths_count; + return infos; } static void extcap_free_dlt(gpointer d, gpointer user_data _U_) @@ -470,18 +561,14 @@ extcap_get_if_dlts(const gchar *ifname, char **err_str) *err_str = NULL; } - if (extcap_if_exists(ifname)) + extcap_interface *interface = extcap_find_interface_for_ifname(ifname); + if (interface) { arguments = g_list_append(arguments, g_strdup(EXTCAP_ARGUMENT_LIST_DLTS)); arguments = g_list_append(arguments, g_strdup(EXTCAP_ARGUMENT_INTERFACE)); arguments = g_list_append(arguments, g_strdup(ifname)); - extcap_callback_info_t cb_info; - cb_info.data = ∩︀ - cb_info.err_str = err_str; - cb_info.ifname = ifname; - - extcap_foreach(arguments, cb_dlt, cb_info, NULL); + extcap_run_one(interface, arguments, cb_dlt, &caps, err_str); g_list_free_full(arguments, g_free); } @@ -594,17 +681,6 @@ append_extcap_interface_list(GList *list, char **err_str _U_) return list; } -static void -extcap_register_preferences_callback(gpointer key, gpointer value _U_, gpointer user_data _U_) -{ - GList *arguments; - - arguments = extcap_get_if_configuration((gchar *)key); - /* Memory for prefs are external to an interface, they are part of - * extcap core, so the parsed arguments can be freed. */ - extcap_free_if_configuration(arguments, TRUE); -} - void extcap_register_preferences(void) { if (prefs.capture_no_extcap) @@ -617,11 +693,8 @@ void extcap_register_preferences(void) return; } - if ( !_loaded_interfaces || g_hash_table_size(_loaded_interfaces) == 0 ) - extcap_load_interface_list(); - - - g_hash_table_foreach(_tool_for_ifname, extcap_register_preferences_callback, NULL); + // Will load information about extcaps and their supported config. + extcap_load_interface_list(); } /** @@ -698,7 +771,7 @@ extcap_pref_for_argument(const gchar *ifname, struct _extcap_arg *arg) struct preference *pref = NULL; GRegex *regex_name = g_regex_new("[-]+", (GRegexCompileFlags) 0, (GRegexMatchFlags) 0, NULL); - GRegex *regex_ifname = g_regex_new("(?![a-zA-Z1-9_]).", (GRegexCompileFlags) 0, (GRegexMatchFlags) 0, NULL); + GRegex *regex_ifname = g_regex_new("(?![a-zA-Z0-9_]).", (GRegexCompileFlags) 0, (GRegexMatchFlags) 0, NULL); if (regex_name && regex_ifname) { if (prefs_find_module("extcap")) @@ -743,7 +816,7 @@ static gboolean cb_preference(extcap_callback_info_t cb_info) GList *walker = arguments; GRegex *regex_name = g_regex_new("[-]+", (GRegexCompileFlags) 0, (GRegexMatchFlags) 0, NULL); - GRegex *regex_ifname = g_regex_new("(?![a-zA-Z1-9_]).", (GRegexCompileFlags) 0, (GRegexMatchFlags) 0, NULL); + GRegex *regex_ifname = g_regex_new("(?![a-zA-Z0-9_]).", (GRegexCompileFlags) 0, (GRegexMatchFlags) 0, NULL); if (regex_name && regex_ifname) { while (walker != NULL) @@ -814,9 +887,9 @@ extcap_get_if_configuration(const char *ifname) { GList * arguments = NULL; GList *ret = NULL; - gchar **err_str = NULL; - if (extcap_if_exists(ifname)) + extcap_interface *interface = extcap_find_interface_for_ifname(ifname); + if (interface) { g_log(LOG_DOMAIN_CAPTURE, G_LOG_LEVEL_DEBUG, "Extcap path %s", get_extcap_dir()); @@ -825,12 +898,7 @@ extcap_get_if_configuration(const char *ifname) arguments = g_list_append(arguments, g_strdup(EXTCAP_ARGUMENT_INTERFACE)); arguments = g_list_append(arguments, g_strdup(ifname)); - extcap_callback_info_t cb_info; - cb_info.data = &ret; - cb_info.err_str = err_str; - cb_info.ifname = ifname; - - extcap_foreach(arguments, cb_preference, cb_info, NULL); + extcap_run_one(interface, arguments, cb_preference, &ret, NULL); g_list_free_full(arguments, g_free); } @@ -845,7 +913,7 @@ static gboolean cb_reload_preference(extcap_callback_info_t cb_info) arguments = extcap_parse_values(cb_info.output); - walker = g_list_first((GList *)(arguments)); + walker = g_list_first(arguments); while (walker != NULL) { extcap_value * val = (extcap_value *)walker->data; @@ -863,9 +931,9 @@ extcap_get_if_configuration_values(const char * ifname, const char * argname, GH { GList * args = NULL; GList *ret = NULL; - gchar **err_str = NULL; - if (extcap_if_exists(ifname)) + extcap_interface *interface = extcap_find_interface_for_ifname(ifname); + if (interface) { g_log(LOG_DOMAIN_CAPTURE, G_LOG_LEVEL_DEBUG, "Extcap path %s", get_extcap_dir()); @@ -888,12 +956,7 @@ extcap_get_if_configuration_values(const char * ifname, const char * argname, GH } } - extcap_callback_info_t cb_info; - cb_info.data = &ret; - cb_info.err_str = err_str; - cb_info.ifname = ifname; - - extcap_foreach(args, cb_reload_preference, cb_info, NULL); + extcap_run_one(interface, args, cb_reload_preference, &ret, NULL); g_list_free_full(args, g_free); } @@ -916,7 +979,7 @@ extcap_has_configuration(const char *ifname, gboolean is_required) gboolean found = FALSE; - arguments = extcap_get_if_configuration((const char *)(ifname)); + arguments = extcap_get_if_configuration(ifname); walker = g_list_first(arguments); while (walker != NULL && !found) @@ -1008,7 +1071,8 @@ extcap_verify_capture_filter(const char *ifname, const char *filter, gchar **err GList * arguments = NULL; extcap_filter_status status = EXTCAP_FILTER_UNKNOWN; - if (extcap_if_exists(ifname)) + extcap_interface *interface = extcap_find_interface_for_ifname(ifname); + if (interface) { g_log(LOG_DOMAIN_CAPTURE, G_LOG_LEVEL_DEBUG, "Extcap path %s", get_extcap_dir()); @@ -1018,12 +1082,7 @@ extcap_verify_capture_filter(const char *ifname, const char *filter, gchar **err arguments = g_list_append(arguments, g_strdup(EXTCAP_ARGUMENT_INTERFACE)); arguments = g_list_append(arguments, g_strdup(ifname)); - extcap_callback_info_t cb_info; - cb_info.data = &status; - cb_info.err_str = err_str; - cb_info.ifname = ifname; - - extcap_foreach(arguments, cb_verify_filter, cb_info, NULL); + extcap_run_one(interface, arguments, cb_verify_filter, &status, err_str); g_list_free_full(arguments, g_free); } @@ -1145,7 +1204,7 @@ void extcap_if_cleanup(capture_options *capture_opts, gchar **errormsg) #ifndef _WIN32 /* Final child watch may not have been called */ - if (interface_opts->extcap_child_watch != 0) + if (interface_opts->extcap_child_watch > 0) { extcap_child_watch_cb(pipedata->pid, 0, capture_opts); /* it will have changed in extcap_child_watch_cb */ @@ -1161,7 +1220,7 @@ void extcap_if_cleanup(capture_options *capture_opts, gchar **errormsg) if (overwrite_exitcode || pipedata->exitcode != 0) { - if (pipedata->stderr_msg != 0) + if (pipedata->stderr_msg != NULL) { if (*errormsg == NULL) { @@ -1644,43 +1703,41 @@ static void remove_extcap_entry(gpointer entry, gpointer data _U_) extcap_free_interface(entry); } -static gboolean cb_load_interfaces(extcap_callback_info_t cb_info) +static void +process_new_extcap(const char *extcap, char *output) { GList * interfaces = NULL, * control_items = NULL, * walker = NULL; extcap_interface * int_iter = NULL; extcap_info * element = NULL; iface_toolbar * toolbar_entry = NULL; - gchar * toolname = g_path_get_basename(cb_info.extcap); + gchar * toolname = g_path_get_basename(extcap); GList * interface_keys = g_hash_table_get_keys(_loaded_interfaces); /* Load interfaces from utility */ - interfaces = extcap_parse_interfaces(cb_info.output, &control_items); + interfaces = extcap_parse_interfaces(output, &control_items); - g_log(LOG_DOMAIN_CAPTURE, G_LOG_LEVEL_DEBUG, "Loading interface list for %s ", cb_info.extcap); + g_log(LOG_DOMAIN_CAPTURE, G_LOG_LEVEL_DEBUG, "Loading interface list for %s ", extcap); /* Seems, that there where no interfaces to be loaded */ if ( ! interfaces || g_list_length(interfaces) == 0 ) { - g_log(LOG_DOMAIN_CAPTURE, G_LOG_LEVEL_DEBUG, "Cannot load interfaces for %s", cb_info.extcap ); - /* Some utilities, androiddump for example, may actually don't present any interfaces, even - * if the utility itself is present. In such a case, we return here, but do not return - * FALSE, or otherwise further loading of other utilities will be stopped */ + g_log(LOG_DOMAIN_CAPTURE, G_LOG_LEVEL_DEBUG, "Cannot load interfaces for %s", extcap ); g_list_free(interface_keys); g_free(toolname); - return TRUE; + return; } /* Load or create the storage element for the tool */ element = extcap_ensure_interface(toolname, TRUE); if ( element == NULL ) { - g_log(LOG_DOMAIN_CAPTURE, G_LOG_LEVEL_ERROR, "Cannot store interface %s, maybe duplicate?", cb_info.extcap ); + g_log(LOG_DOMAIN_CAPTURE, G_LOG_LEVEL_ERROR, "Cannot store interface %s, maybe duplicate?", extcap ); g_list_foreach(interfaces, remove_extcap_entry, NULL); g_list_free(interfaces); g_list_free(interface_keys); g_free(toolname); - return FALSE; + return; } if (control_items) @@ -1711,7 +1768,7 @@ static gboolean cb_load_interfaces(extcap_callback_info_t cb_info) { element->version = g_strdup(int_iter->version); element->basename = g_strdup(toolname); - element->full_path = g_strdup(cb_info.extcap); + element->full_path = g_strdup(extcap); element->help = g_strdup(int_iter->help); } @@ -1734,7 +1791,7 @@ static gboolean cb_load_interfaces(extcap_callback_info_t cb_info) if ( g_list_find(interface_keys, int_iter->call) ) { g_log(LOG_DOMAIN_CAPTURE, G_LOG_LEVEL_WARNING, "Extcap interface \"%s\" is already provided by \"%s\" ", - int_iter->call, (gchar *)extcap_if_executable(int_iter->call)); + int_iter->call, extcap_if_executable(int_iter->call)); walker = g_list_next(walker); continue; } @@ -1742,7 +1799,7 @@ static gboolean cb_load_interfaces(extcap_callback_info_t cb_info) if ((int_iter->call != NULL) && (int_iter->display)) g_log(LOG_DOMAIN_CAPTURE, G_LOG_LEVEL_DEBUG, " Interface [%s] \"%s\" ", int_iter->call, int_iter->display); - int_iter->extcap_path = g_strdup(cb_info.extcap); + int_iter->extcap_path = g_strdup(extcap); /* Only set the help, if it exists and no parsed help information is present */ if ( ! int_iter->help && help ) @@ -1767,14 +1824,116 @@ static gboolean cb_load_interfaces(extcap_callback_info_t cb_info) if (toolbar_entry && toolbar_entry->menu_title) { iface_toolbar_add(toolbar_entry); - extcap_iface_toolbar_add(cb_info.extcap, toolbar_entry); + extcap_iface_toolbar_add(extcap, toolbar_entry); } g_list_foreach(interfaces, remove_extcap_entry, NULL); g_list_free(interfaces); g_list_free(interface_keys); g_free(toolname); - return TRUE; +} + + +/** Thread callback to save the output of a --extcap-config call. */ +static void +extcap_process_config_cb(thread_pool_t *pool _U_, void *data, char *output) +{ + extcap_iface_info_t *iface_info = (extcap_iface_info_t *)data; + iface_info->output = output; +} + +/** + * Thread callback to process discovered interfaces, scheduling more tasks to + * retrieve the configuration for each interface. Called once for every extcap + * program. + */ +static void +extcap_process_interfaces_cb(thread_pool_t *pool, void *data, char *output) +{ + extcap_run_extcaps_info_t *info = (extcap_run_extcaps_info_t *)data; + guint i = 0; + guint num_interfaces = 0; + + if (!output) { + // No interfaces available, nothing to do. + return; + } + + // Save output for process_new_extcap. + info->output = output; + + // Are there any interfaces to query information from? + GList *interfaces = extcap_parse_interfaces(output, NULL); + for (GList *iface = interfaces; iface; iface = g_list_next(iface)) { + extcap_interface *intf = (extcap_interface *)iface->data; + if (intf->if_type == EXTCAP_SENTENCE_INTERFACE) { + ++num_interfaces; + } + } + if (num_interfaces == 0) { + // nothing to do. + g_list_free_full(interfaces, extcap_free_interface); + return; + } + + /* GSList is not thread-safe, so pre-allocate an array instead. */ + info->iface_infos = g_new0(extcap_iface_info_t, num_interfaces); + info->num_interfaces = num_interfaces; + + // Schedule new commands to retrieve the configuration. + for (GList *iface = interfaces; iface; iface = g_list_next(iface)) { + extcap_interface *intf = (extcap_interface *)iface->data; + if (intf->if_type != EXTCAP_SENTENCE_INTERFACE) { + continue; + } + + const char *argv[] = { + EXTCAP_ARGUMENT_CONFIG, + EXTCAP_ARGUMENT_INTERFACE, + intf->call, + NULL + }; + extcap_run_task_t *task = g_new0(extcap_run_task_t, 1); + extcap_iface_info_t *iface_info = &info->iface_infos[i++]; + + task->extcap_path = info->extcap_path; + task->argv = g_strdupv((char **)argv); + task->output_cb = extcap_process_config_cb; + task->data = iface_info; + iface_info->ifname = g_strdup(intf->call); + + thread_pool_push(pool, task, NULL); + } + g_list_free_full(interfaces, extcap_free_interface); +} + +/** + * Thread callback to check whether the new-style --list-interfaces call with an + * explicit function succeeded. If not, schedule a call without the new version + * argument. + */ +static void +extcap_list_interfaces_cb(thread_pool_t *pool, void *data, char *output) +{ + extcap_run_extcaps_info_t *info = (extcap_run_extcaps_info_t *)data; + + if (!output) { + /* No output available, schedule a fallback query. */ + const char *argv[] = { + EXTCAP_ARGUMENT_LIST_INTERFACES, + NULL + }; + extcap_run_task_t *task = g_new0(extcap_run_task_t, 1); + + task->extcap_path = info->extcap_path; + task->argv = g_strdupv((char **)argv); + task->output_cb = extcap_process_interfaces_cb; + task->data = info; + + thread_pool_push(pool, task, NULL); + } else { + extcap_process_interfaces_cb(pool, info, output); + } } @@ -1785,8 +1944,6 @@ static gboolean cb_load_interfaces(extcap_callback_info_t cb_info) static void extcap_load_interface_list(void) { - gchar *error = NULL; - if (prefs.capture_no_extcap) return; @@ -1807,10 +1964,11 @@ extcap_load_interface_list(void) if (_loaded_interfaces == NULL) { - GList * arguments = NULL; - GList * fallback = NULL; int major = 0; int minor = 0; + guint count = 0; + extcap_run_extcaps_info_t *infos; + GList *unused_arguments = NULL; _loaded_interfaces = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, extcap_free_interface_info); /* Cleanup lookup table */ @@ -1822,21 +1980,42 @@ extcap_load_interface_list(void) _tool_for_ifname = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); } - arguments = g_list_append(arguments, g_strdup(EXTCAP_ARGUMENT_LIST_INTERFACES)); - fallback = g_list_append(fallback, g_strdup(EXTCAP_ARGUMENT_LIST_INTERFACES)); - get_ws_version_number(&major, &minor, NULL); - arguments = g_list_append(arguments, g_strdup_printf("%s=%d.%d", EXTCAP_ARGUMENT_VERSION, major, minor)); + char *arg_version = g_strdup_printf("%s=%d.%d", EXTCAP_ARGUMENT_VERSION, major, minor); + const char *argv[] = { + EXTCAP_ARGUMENT_LIST_INTERFACES, + arg_version, + NULL + }; + infos = (extcap_run_extcaps_info_t *)extcap_run_all(argv, + extcap_list_interfaces_cb, sizeof(extcap_run_extcaps_info_t), + &count); + for (guint i = 0; i < count; i++) { + if (!infos[i].output) { + continue; + } - extcap_callback_info_t cb_info; - cb_info.data = NULL; - cb_info.ifname = NULL; - cb_info.err_str = &error; + // Save new extcap and each discovered interface. + process_new_extcap(infos[i].extcap_path, infos[i].output); + for (guint j = 0; j < infos[i].num_interfaces; j++) { + extcap_iface_info_t *iface_info = &infos[i].iface_infos[j]; - extcap_foreach(arguments, cb_load_interfaces, cb_info, fallback); + if (!iface_info->output) { + continue; + } - g_list_free_full(fallback, g_free); - g_list_free_full(arguments, g_free); + extcap_callback_info_t cb_info = { + .ifname = iface_info->ifname, + .output = iface_info->output, + .data = &unused_arguments, + }; + cb_preference(cb_info); + } + } + /* XXX rework cb_preference such that this unused list can be removed. */ + extcap_free_if_configuration(unused_arguments, TRUE); + extcap_free_extcaps_info_array(infos, count); + g_free(arg_version); } }