+/** 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();
+
+ 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);
+
+ // 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);
+}
+
+/*
+ * 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 *));
+
+ 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
+
+ if (!paths) {
+ *count = 0;
+ return NULL;
+ }
+
+ 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);
+
+ 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);
+
+ for (GSList *path = paths; path; path = g_slist_next(path), i++) {
+ extcap_run_task_t *task = g_new0(extcap_run_task_t, 1);
+
+ 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;
+
+ thread_pool_push(&pool, task, NULL);
+ }
+ g_slist_free(paths); /* Note: the contents are transferred to 'infos'. */
+
+ /* 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;