Repository: rizinorg/rizin
Pull request #4950: search.show_progress: Show search intervals
Author: login: kazarmy
Labels: name: rz-test
name: API
name: RzCore
name: RzSearch
Created: 2025-03-01T06:40:00Z | Merged: 2025-03-07T10:39:12Z

 <!-- Filling this template is mandatory -->

**Your checklist for this pull request**
- [X] I've read the [guidelines for contributing](https://github.com/rizinorg/rizin/blob/master/DEVELOPERS.md) to this repository.
- [X] I made sure to follow the project's [coding style](https://github.com/rizinorg/rizin/blob/master/DEVELOPERS.md#code-style).
- [ ] I've documented every `RZ_API` function and struct this PR changes.
- [X] I've added tests that prove my changes are effective (required for changes to `RZ_API`).
- [ ] I've updated the [Rizin book](https://github.com/rizinorg/book) with the relevant information (if needed).

**Detailed description**

<!-- Explain the **details** for making this change. Is a new feature implemented? What existing problem does the pull request solve? How does the pull request solve these issues? Please provide enough information so that others can review your pull request. -->

This pr makes `search.show_progress` show search intervals as can be seen in [this test output](https://github.com/rizinorg/rizin/compare/dev...kazarmy:rizin:search.show_progress-show-search-intervals?expand=1#diff-e85e3180c7387bbc70d11122adca6d61576a3a1ddce417ce1cd3e4a94b72275cR146) except that they will be unsorted. This is so one can confirm whether the search intervals are correct.

**Test plan**

<!-- What steps should the reviewer take to test your pull request? Demonstrate the code is solid. Example: The exact commands you ran and their output, screenshots/videos. This is your time to re-check that everything works and that you covered all the edge cases -->

The output makes sense. All builds are green.

**Closing issues**

<!-- put "closes #XXXX" in your comment to auto-close the issue that your PR fixes (if any). -->

...



Diff:
diff --git a/librz/core/cconfig.c b/librz/core/cconfig.c
index 36c5d0546ec..9ec275297f4 100644
--- a/librz/core/cconfig.c
+++ b/librz/core/cconfig.c
@@ -2651,6 +2651,18 @@ static bool cb_search_in(void *user, void *data) {
 	return false;
 }
 
+static bool cb_search_show_progress(void *user, void *data) {
+	RzConfigNode *node = (RzConfigNode *)data;
+	if (rz_str_is_false(node->value)) {
+		free(node->value);
+		node->value = rz_str_dup("false");
+	} else if (!rz_str_cmp_list("num_hits intervals", node->value, ' ')) {
+		RZ_LOG_ERROR("search.show_progress: invalid value (%s), supported only `false, num_hits, intervals`\n", node->value);
+		return false;
+	}
+	return true;
+}
+
 static bool cb_analysis_in(void *user, void *data) {
 	RzCore *core = (RzCore *)user;
 	RzConfigNode *node = (RzConfigNode *)data;
@@ -3747,7 +3759,9 @@ RZ_API int rz_core_config_init(RzCore *core) {
 		NULL);
 	SETPREF("search.prefix", "hit", "Prefix name in search hits label");
 	SETI("search.maxhits", 0, "Maximum number of hits ('0' means no limit)");
-	SETBPREF("search.show_progress", "true", "Show the search process.");
+	n = NODECB("search.show_progress", "num_hits", &cb_search_show_progress);
+	SETDESC(n, "Show the search process (false, num_hits, intervals)");
+	SETOPTIONS(n, "false", "num_hits", "intervals", NULL);
 	SETICB("search.str.min_length", RZ_BIN_STRING_SEARCH_MIN_STRING, &cb_search_str_min_length, "Smallest string length (in number of characters).");
 	SETICB("search.str.max_length", RZ_BIN_STRING_SEARCH_BUFFER_SIZE, &cb_search_str_max_length, "Maximum string length (in number of characters).");
 	SETICB("search.str.max_region_size", RZ_BIN_STRING_SEARCH_MAX_REGION_SIZE, &cb_search_str_max_region_size, "Maximum allowable size for the string search interval between two memory regions.");
diff --git a/librz/core/cmd/cmd_search.c b/librz/core/cmd/cmd_search.c
index c30c6f8b6a4..1d488479741 100644
--- a/librz/core/cmd/cmd_search.c
+++ b/librz/core/cmd/cmd_search.c
@@ -2423,7 +2423,7 @@ static RzCmdStatus cmd_core_handle_search_hits(RzCore *core, RzCmdStateOutput *s
 		return RZ_CMD_STATUS_ERROR;
 	}
 
-	if (rz_config_get_b(core->config, "search.show_progress")) {
+	if (!rz_str_is_false(rz_config_get(core->config, "search.show_progress"))) {
 		// clear last progress notification, if any
 		rz_cons_clear_line(1);
 	}
@@ -2475,8 +2475,12 @@ static RzCmdStatus cmd_core_handle_search_hits(RzCore *core, RzCmdStateOutput *s
 
 static RzSearchOpt *setup_search_options(RzCore *core) {
 	RzSearchOpt *search_opts = rz_search_opt_new();
-	if (!(rz_search_opt_set_max_hits(search_opts, rz_config_get_i(core->config, "search.maxhits")) &&
-		    rz_search_opt_set_max_threads(search_opts, rz_th_max_threads(rz_config_get_i(core->config, "search.max_threads"))))) {
+	ut32 max_threads = rz_th_max_threads(rz_config_get_i(core->config, "search.max_threads"));
+	ut32 max_hits = rz_config_get_i(core->config, "search.maxhits");
+	const char *show_progress = rz_config_get(core->config, "search.show_progress");
+	if (!(rz_search_opt_set_max_threads(search_opts, max_threads) &&
+		    rz_search_opt_set_max_hits(search_opts, max_hits) &&
+		    rz_search_opt_set_show_progress_from_str(search_opts, show_progress))) {
 		RZ_LOG_ERROR("Failed setup find options.\n");
 		return NULL;
 	}
@@ -2507,7 +2511,7 @@ static RzCmdStatus byte_pattern_search(RzCore *core, RZ_OWN RzSearchBytesPattern
 		goto error;
 	}
 
-	bool progress = rz_config_get_b(core->config, "search.show_progress");
+	bool progress = !rz_str_is_false(rz_config_get(core->config, "search.show_progress"));
 	if (!rz_search_opt_set_cancel_cb(search_opts, cmd_search_progress_cancel, progress ? state : NULL)) {
 		RZ_LOG_ERROR("code: Failed to setup default search options.\n");
 		goto error;
@@ -2890,7 +2894,7 @@ RZ_IPI RzCmdStatus rz_cmd_search_hex_regex_handler(RzCore *core, int argc, const
 		goto error;
 	}
 
-	bool progress = rz_config_get_b(core->config, "search.show_progress");
+	bool progress = !rz_str_is_false(rz_config_get(core->config, "search.show_progress"));
 	if (!rz_search_opt_set_cancel_cb(search_opts, cmd_search_progress_cancel, progress ? state : NULL)) {
 		RZ_LOG_ERROR("code: Failed to setup default search options.\n");
 		goto error;
@@ -2950,7 +2954,7 @@ static RzCmdStatus cmd_string_search_generic(RzCore *core, const char *string, c
 		// Else we are fine, since the encoding in the settings is set to 'guess'.
 	}
 
-	bool progress = rz_config_get_b(core->config, "search.show_progress");
+	bool progress = !rz_str_is_false(rz_config_get(core->config, "search.show_progress"));
 	if (!rz_search_opt_set_cancel_cb(search_opts, cmd_search_progress_cancel, progress ? state : NULL)) {
 		RZ_LOG_ERROR("code: Failed to setup default search options.\n");
 		free(search_str);
diff --git a/librz/core/csearch.c b/librz/core/csearch.c
index 9b1770b5a4c..bbd929485c9 100644
--- a/librz/core/csearch.c
+++ b/librz/core/csearch.c
@@ -65,8 +65,10 @@ RZ_API RZ_OWN RzList /*<RzIOMap *>*/ *rz_core_setup_io_search_parameters(RzCore
 		// Set search options known by core.
 		ut32 max_threads = rz_th_max_threads(rz_config_get_i(core->config, "search.max_threads"));
 		ut32 max_hits = rz_config_get_i(core->config, "search.maxhits");
+		const char *show_progress = rz_config_get(core->config, "search.show_progress");
 		if (!(rz_search_opt_set_max_threads(search_opts, max_threads) &&
-			    rz_search_opt_set_max_hits(search_opts, max_hits))) {
+			    rz_search_opt_set_max_hits(search_opts, max_hits) &&
+			    rz_search_opt_set_show_progress_from_str(search_opts, show_progress))) {
 			RZ_LOG_ERROR("core: Failed to setup search options.\n");
 			goto fail;
 		}
diff --git a/librz/include/rz_search.h b/librz/include/rz_search.h
index 838710845ee..a0bc0f28293 100644
--- a/librz/include/rz_search.h
+++ b/librz/include/rz_search.h
@@ -136,6 +136,8 @@ typedef struct rz_search_opt_t RzSearchOpt;
  */
 typedef struct rz_search_find_opt_t RzSearchFindOpt;
 
+typedef struct rz_search_interval_t RzSearchInterval;
+
 typedef struct rz_search_collection_t RzSearchCollection;
 
 typedef struct rz_search_hit_t {
@@ -177,6 +179,7 @@ RZ_API void rz_search_opt_free(RZ_NULLABLE RzSearchOpt *opt);
 RZ_API bool rz_search_opt_set_max_hits(RZ_NONNULL RzSearchOpt *opt, size_t max_hits);
 RZ_API bool rz_search_opt_set_elemet_size(RZ_NONNULL RzSearchOpt *opt, ut64 chunk_size);
 RZ_API bool rz_search_opt_set_max_threads(RZ_NONNULL RzSearchOpt *opt, RzThreadNCores max_threads);
+RZ_API bool rz_search_opt_set_show_progress_from_str(RZ_NONNULL RzSearchOpt *opt, const char *show_progress);
 RZ_API bool rz_search_opt_set_cancel_cb(RZ_NONNULL RzSearchOpt *opt, RzSearchCancelCallback callback, void *user);
 RZ_API bool rz_search_opt_set_find_options(RZ_NONNULL RzSearchOpt *opt, RZ_OWN RzSearchFindOpt *find_opts);
 
diff --git a/librz/search/bytes_search.c b/librz/search/bytes_search.c
index e77f3b29eef..954c0c99646 100644
--- a/librz/search/bytes_search.c
+++ b/librz/search/bytes_search.c
@@ -204,7 +204,8 @@ static inline bool bytes_pattern_compare_masked(RZ_BORROW RZ_NONNULL const ut8 *
 	return true;
 }
 
-static bool bytes_find(RzSearchFindOpt *fopts, void *user, ut64 address, const RzBuffer *buffer, RZ_OUT RzThreadQueue *hits) {
+static bool bytes_find(RzSearchFindOpt *fopts, void *user, ut64 address, const RzBuffer *buffer,
+	RZ_OUT RzThreadQueue *hits, RZ_OUT size_t *n_hits) {
 	if (!fopts) {
 		RZ_LOG_ERROR("bytes_find requires valid find options.\n");
 		return false;
@@ -215,6 +216,7 @@ static bool bytes_find(RzSearchFindOpt *fopts, void *user, ut64 address, const R
 	const ut8 *raw_buf = rz_buf_get_whole_hot_paths((RzBuffer *)buffer, &size);
 	void **it = NULL;
 	RzPVector /*<BytesPattern *>*/ *patterns = (RzPVector *)user;
+	*n_hits = 0;
 	rz_pvector_foreach (patterns, it) {
 		RzSearchBytesPattern *hp = (RzSearchBytesPattern *)*it;
 		if (hp->regex) {
@@ -234,6 +236,7 @@ static bool bytes_find(RzSearchFindOpt *fopts, void *user, ut64 address, const R
 					rz_pvector_free(matches);
 					return false;
 				}
+				(*n_hits)++;
 			}
 			rz_pvector_free(matches);
 			continue;
@@ -264,6 +267,7 @@ static bool bytes_find(RzSearchFindOpt *fopts, void *user, ut64 address, const R
 				rz_search_hit_free(hit);
 				return false;
 			}
+			(*n_hits)++;
 			offset += fopts->match_overlap ? 1 : hp->length;
 		}
 	}
diff --git a/librz/search/cryptographic_search.c b/librz/search/cryptographic_search.c
index 2b3b5a3f1e5..36f51ffbf49 100644
--- a/librz/search/cryptographic_search.c
+++ b/librz/search/cryptographic_search.c
@@ -50,7 +50,8 @@ RZ_API bool rz_search_collection_cryptographic_name_to_type(RZ_NONNULL const cha
 	return false;
 }
 
-static bool cryptographic_find(RzSearchFindOpt *fopts, void *user, ut64 address, const RzBuffer *buffer, RZ_OUT RzThreadQueue *hits) {
+static bool cryptographic_find(RzSearchFindOpt *fopts, void *user, ut64 address, const RzBuffer *buffer,
+	RZ_OUT RzThreadQueue *hits, RZ_OUT size_t *n_hits) {
 	RzPVector /*<CryptographicCallback *>*/ *pvec = (RzPVector *)user;
 
 	ut64 n_bytes = 0;
@@ -61,6 +62,7 @@ static bool cryptographic_find(RzSearchFindOpt *fopts, void *user, ut64 address,
 	CryptographicCallback find_cb = NULL;
 	void **it;
 
+	*n_hits = 0;
 	for (size_t offset = 0; offset < n_bytes;) {
 		if (fopts->alignment > 1 && rz_mem_align_padding(address + offset, fopts->alignment) != 0) {
 			// Match has not the correct alignment in memory.
@@ -78,6 +80,7 @@ static bool cryptographic_find(RzSearchFindOpt *fopts, void *user, ut64 address,
 				rz_search_hit_free(hit);
 				return false;
 			}
+			(*n_hits)++;
 			if (!fopts->match_overlap) {
 				match_len = RZ_MIN(match_len, hit->size);
 				break;
diff --git a/librz/search/options.c b/librz/search/options.c
index 11157588b9b..419020cf139 100644
--- a/librz/search/options.c
+++ b/librz/search/options.c
@@ -66,6 +66,18 @@ RZ_API bool rz_search_opt_set_max_threads(RZ_NONNULL RzSearchOpt *opt, RzThreadN
 	return true;
 }
 
+RZ_API bool rz_search_opt_set_show_progress_from_str(RZ_NONNULL RzSearchOpt *opt, const char *show_progress) {
+	rz_return_val_if_fail(opt, false);
+	if (rz_str_is_false(show_progress)) {
+		opt->show_progress = RZ_SEARCH_PROGRESS_DISABLED;
+	} else if (RZ_STR_EQ(show_progress, "intervals")) {
+		opt->show_progress = RZ_SEARCH_PROGRESS_INTERVALS;
+	} else {
+		opt->show_progress = RZ_SEARCH_PROGRESS_NUM_HITS;
+	}
+	return true;
+}
+
 RZ_API bool rz_search_opt_set_cancel_cb(RZ_NONNULL RzSearchOpt *opt, RzSearchCancelCallback callback, void *user) {
 	rz_return_val_if_fail(opt, false);
 	opt->cancel_cb = callback;
diff --git a/librz/search/search.c b/librz/search/search.c
index 3698dd161e9..8d98241ec91 100644
--- a/librz/search/search.c
+++ b/librz/search/search.c
@@ -556,8 +556,20 @@ typedef struct search_ctx {
 	RzSearchOpt *opt; ///< User options
 	RzThreadQueue /* RzSearchHits */ *hits; ///< Hits list
 	RzAtomicBool *loop; ///< If set, the execution will continue until it terminates. If unset, the execution cancels.
+	RzThreadQueue /* RzSearchInterval */ *finished_intervals; ///< Interval queue
 } search_ctx_t;
 
+static void print_intervals(RZ_NONNULL RzThreadQueue *intervals) {
+	rz_return_if_fail(intervals);
+
+	RzSearchInterval *search_interval = NULL;
+	while ((search_interval = rz_th_queue_pop(intervals, false))) {
+		RzInterval *itv = &search_interval->interval;
+		eprintf("[0x%" PFMT64x ", 0x%" PFMT64x "): %" PFMTSZu "\n", itv->addr, itv->addr + itv->size,
+			search_interval->n_hits);
+	}
+}
+
 static void *search_cancel_th(void *user) {
 	search_ctx_t *ctx = (search_ctx_t *)user;
 	RzSearchOpt *opt = ctx->opt;
@@ -567,12 +579,14 @@ static void *search_cancel_th(void *user) {
 		if (!rz_atomic_bool_get(ctx->loop)) {
 			break;
 		}
+		print_intervals(ctx->finished_intervals);
 		size_t n_hits = rz_th_queue_size(ctx->hits);
 		if (opt->cancel_cb(opt->cancel_usr, n_hits, RZ_SEARCH_CANCEL_REGULAR_CHECK)) {
 			rz_atomic_bool_set(ctx->loop, false);
 			break;
 		}
 	}
+	print_intervals(ctx->finished_intervals);
 
 	return NULL;
 }
@@ -602,10 +616,18 @@ static bool search_iterator_io_map_cb(void *element, void *user) {
 	}
 	rz_th_lock_leave(ctx->io_lock);
 
+	size_t n_hits = 0;
 	RzSearchFindBytesCallback find = col->find;
-	if (!find(ctx->opt->find_opts, col->user, at, buffer, ctx->hits)) {
+	if (!find(ctx->opt->find_opts, col->user, at, buffer, ctx->hits, &n_hits)) {
 		RZ_LOG_ERROR("search: failed search at 0x%08" PFMT64x "\n", at);
 		goto failure;
+	} else if (ctx->opt->show_progress == RZ_SEARCH_PROGRESS_INTERVALS) {
+		RzSearchInterval *interval = rz_search_interval_new(*window, n_hits);
+		if (!interval || !rz_th_queue_push(ctx->finished_intervals, interval, true)) {
+			RZ_LOG_ERROR("search: failed to push search interval to queue\n");
+			free(interval);
+			goto failure;
+		}
 	}
 
 	rz_buf_free(buffer);
@@ -663,6 +685,7 @@ RZ_API RZ_OWN RzList /*<RzSearchHit *>*/ *rz_search_on_io(
 	search_ctx_t ctx = { 0 };
 	RzList *results = NULL;
 	RzThreadQueue *hits = NULL;
+	RzThreadQueue *intervals = NULL;
 	RzList /* RzInterval */ *windows = NULL;
 	RzThread *cancel_th = NULL;
 
@@ -692,6 +715,12 @@ RZ_API RZ_OWN RzList /*<RzSearchHit *>*/ *rz_search_on_io(
 		return NULL;
 	}
 
+	intervals = rz_th_queue_new(RZ_THREAD_QUEUE_UNLIMITED, (RzListFree)rz_search_interval_free);
+	if (!intervals) {
+		RZ_LOG_ERROR("search: cannot allocate RzSearchInterval queue.\n");
+		return NULL;
+	}
+
 	windows = assemble_search_window_list(search_in, opt);
 	if (!windows) {
 		RZ_LOG_ERROR("search: Could not prepare search window queue.\n");
@@ -705,6 +734,7 @@ RZ_API RZ_OWN RzList /*<RzSearchHit *>*/ *rz_search_on_io(
 	ctx.io_lock = rz_th_lock_new(false);
 	ctx.loop = rz_atomic_bool_new(true);
 	ctx.hits = hits;
+	ctx.finished_intervals = intervals;
 
 	if (opt->cancel_cb) {
 		// create cancel thread
@@ -712,6 +742,7 @@ RZ_API RZ_OWN RzList /*<RzSearchHit *>*/ *rz_search_on_io(
 		if (!cancel_th) {
 			RZ_LOG_ERROR("search: cannot allocate cancel thread.\n");
 			rz_th_queue_free(hits);
+			rz_th_queue_free(intervals);
 			rz_atomic_bool_free(ctx.loop);
 			return NULL;
 		}
@@ -734,6 +765,7 @@ RZ_API RZ_OWN RzList /*<RzSearchHit *>*/ *rz_search_on_io(
 	rz_th_lock_free(ctx.io_lock);
 	rz_list_free(windows);
 	rz_th_queue_free(hits);
+	rz_th_queue_free(intervals);
 
 	rz_list_sort(results, (RzListComparator)rz_search_hit_cmp, NULL);
 	return results;
@@ -825,3 +857,20 @@ RZ_API RZ_OWN char *rz_search_hit_flag_name(RZ_NONNULL const RzSearchHit *hit,
 
 	return rz_strbuf_drain(buf);
 }
+
+RZ_IPI RZ_OWN RzSearchInterval *rz_search_interval_new(RzInterval interval, size_t n_hits) {
+	RzSearchInterval *search_interval = RZ_NEW0(RzSearchInterval);
+	if (!search_interval) {
+		return NULL;
+	}
+	search_interval->interval = interval;
+	search_interval->n_hits = n_hits;
+	return search_interval;
+}
+
+RZ_IPI void rz_search_interval_free(RZ_NULLABLE RzSearchInterval *search_interval) {
+	if (!search_interval) {
+		return;
+	}
+	free(search_interval);
+}
diff --git a/librz/search/search_internal.h b/librz/search/search_internal.h
index de02e974f38..98f14bffa5e 100644
--- a/librz/search/search_internal.h
+++ b/librz/search/search_internal.h
@@ -64,12 +64,14 @@ typedef bool (*RzSearchIsEmptyCallback)(void *user);
  * \param user The private user data.
  * \param address The address associated with the given bytes.
  * \param buffer The bytes buffer.
- * \param The queue to push new hits onto.
+ * \param hits The queue to push new hits onto.
+ * \param n_hits The variable to store the number of new hits.
  *
  * \return True, if a match was found.
  * \return False otherwise.
  */
-typedef bool (*RzSearchFindBytesCallback)(RzSearchFindOpt *fopt, void *user, ut64 address, const RzBuffer *buffer, RZ_OUT RzThreadQueue *hits);
+typedef bool (*RzSearchFindBytesCallback)(RzSearchFindOpt *fopt, void *user, ut64 address, const RzBuffer *buffer,
+	RZ_OUT RzThreadQueue *hits, RZ_OUT size_t *n_hits);
 
 /**
  * \brief A callback to search a graph for a pattern.
@@ -89,6 +91,12 @@ typedef enum {
 	RZ_SEARCH_SPACE_KB, ///< The search is performed on the knowledge base.
 } RzSearchSpace;
 
+typedef enum {
+	RZ_SEARCH_PROGRESS_DISABLED = 0, ///< Don't show any search progress.
+	RZ_SEARCH_PROGRESS_NUM_HITS, ///< Show running count of hits.
+	RZ_SEARCH_PROGRESS_INTERVALS, ///< Above + show hits per interval.
+} RzSearchProgress;
+
 struct rz_search_collection_t {
 	void *user; ///< Context defined by the various collections
 	RzSearchSpace space; ///< The search space of this collection.
@@ -111,6 +119,7 @@ struct rz_search_opt_t {
 	ut64 chunk_size;
 	ut64 element_size;
 	RzThreadNCores max_threads;
+	RzSearchProgress show_progress;
 
 	// cancel callback
 	void *cancel_usr;
@@ -123,10 +132,18 @@ struct rz_search_find_opt_t {
 	size_t alignment; ///< The address alignment to start the search from. If >1, only `buffer + (alignment * x)` is searched.
 };
 
+struct rz_search_interval_t {
+	RzInterval interval;
+	size_t n_hits;
+};
+
 RZ_IPI RZ_OWN RzSearchHit *rz_search_hit_new(const char *metadata, ut64 address, size_t size);
 RZ_IPI void rz_search_hit_free(RZ_NULLABLE RzSearchHit *hit);
 RZ_IPI int rz_search_hit_cmp(RZ_NULLABLE RzSearchHit *a, RZ_NULLABLE RzSearchHit *b, void *user);
 
+RZ_IPI RZ_OWN RzSearchInterval *rz_search_interval_new(RzInterval interval, size_t n_hits);
+RZ_IPI void rz_search_interval_free(RZ_NULLABLE RzSearchInterval *interval);
+
 RZ_IPI RZ_OWN RzSearchCollection *rz_search_collection_new_bytes_space(RZ_NONNULL RzSearchFindBytesCallback find, RZ_NONNULL RzSearchIsEmptyCallback is_empty, RZ_NULLABLE RzSearchFreeCallback free, RZ_NULLABLE void *user);
 RZ_IPI RZ_OWN RzSearchCollection *rz_search_collection_new_graph_space(RZ_NONNULL RzSearchFindGraphCallback find, RZ_NONNULL RzSearchIsEmptyCallback is_empty, RZ_NULLABLE RzSearchFreeCallback free, RZ_NULLABLE void *user);
 RZ_IPI bool rz_search_collection_has_find_callback(RZ_NONNULL RzSearchCollection *col, RZ_NONNULL void *expected);
diff --git a/librz/search/string_search.c b/librz/search/string_search.c
index 2dcf4c9144f..604de4bae36 100644
--- a/librz/search/string_search.c
+++ b/librz/search/string_search.c
@@ -49,7 +49,8 @@ static void align_offsets(RzUtilStrScanOptions options, RzStrEnc encoding, RzDet
 	}
 }
 
-static bool string_find(RzSearchFindOpt *fopt, void *user, ut64 offset, const RzBuffer *buffer, RZ_OUT RzThreadQueue *hits) {
+static bool string_find(RzSearchFindOpt *fopt, void *user, ut64 offset, const RzBuffer *buffer,
+	RZ_OUT RzThreadQueue *hits, RZ_OUT size_t *n_hits) {
 	rz_return_val_if_fail(fopt, false);
 
 	StringSearch *ss = (StringSearch *)user;
@@ -77,6 +78,7 @@ static bool string_find(RzSearchFindOpt *fopt, void *user, ut64 offset, const Rz
 	}
 
 	ut64 found_idx = 0;
+	*n_hits = 0;
 	rz_list_foreach (found, it_s, detected) {
 		void **it_m = NULL;
 		rz_pvector_foreach (ss->strings, it_m) {
@@ -108,6 +110,7 @@ static bool string_find(RzSearchFindOpt *fopt, void *user, ut64 offset, const Rz
 					rz_list_free(found);
 					return false;
 				}
+				(*n_hits)++;
 			}
 			rz_pvector_free(matches);
 		}
diff --git a/test/db/cmd/regexp b/test/db/cmd/regexp
index 7995d0e55d5..1a2d6c264c7 100644
--- a/test/db/cmd/regexp
+++ b/test/db/cmd/regexp
@@ -125,12 +125,16 @@ EOF
 EXPECT_ERR=
 RUN
 
-NAME=consistency btw /z
+NAME=consistency btw /z and /x
 FILE=bins/elf/analysis/ls2
 CMDS=<<EOF
-e scr.color=1
-e search.str.min_length=3
-/z lib
+!!rizin -qc "e search.show_progress=intervals; /z lib" -1 bins/elf/analysis/ls2 | grep "^.*[[:punct:]]0x.*" | sed "s/^.*[[:cntrl:]]//"~$0
+!!rizin -qc "/z lib" bins/elf/analysis/ls2
+echo ----
+!!rizin -qc "e search.show_progress=intervals; /x `%x lib`" -1 bins/elf/analysis/ls2 | grep "^.*[[:punct:]]0x.*" | sed "s/^.*[[:cntrl:]]//"~$0
+!!rizin -qc "/x `%x lib`" bins/elf/analysis/ls2
+echo ----
+/z lib > /dev/null
 ps ascii @ hit.string.ascii.0
 ps ascii @ hit.string.ascii.1
 ps ascii @ hit.string.ascii.2
@@ -141,6 +145,10 @@ ps ascii @ hit.string.ascii.6
 ps ascii @ hit.string.ascii.7
 EOF
 EXPECT=<<EOF
+[0x400000, 0x41bc2c): 8
+[0x61bdf0, 0x61c5f4): 0
+[0x61c5f4, 0x61d360): 0
+[0x61d368, 0x61d720): 0
 0x00400239 3 hit.string.ascii.0
 0x00400f19 3 hit.string.ascii.1
 0x00400fae 3 hit.string.ascii.2
@@ -149,6 +157,47 @@ EXPECT=<<EOF
 0x0041769a 3 hit.string.ascii.5
 0x004186e0 3 hit.string.ascii.6
 0x00418e58 3 hit.string.ascii.7
+----
+[0x400000, 0x401002): 4
+[0x401000, 0x402002): 1
+[0x402000, 0x403002): 0
+[0x403000, 0x404002): 0
+[0x404000, 0x405002): 0
+[0x405000, 0x406002): 0
+[0x406000, 0x407002): 0
+[0x407000, 0x408002): 0
+[0x408000, 0x409002): 0
+[0x409000, 0x40a002): 0
+[0x40a000, 0x40b002): 0
+[0x40b000, 0x40c002): 0
+[0x40c000, 0x40d002): 0
+[0x40d000, 0x40e002): 0
+[0x40e000, 0x40f002): 0
+[0x40f000, 0x410002): 0
+[0x410000, 0x411002): 0
+[0x411000, 0x412002): 0
+[0x412000, 0x413002): 0
+[0x413000, 0x414002): 0
+[0x414000, 0x415002): 0
+[0x415000, 0x416002): 0
+[0x416000, 0x417002): 0
+[0x417000, 0x418002): 1
+[0x418000, 0x419002): 2
+[0x419000, 0x41a002): 0
+[0x41a000, 0x41b002): 0
+[0x41b000, 0x41bc2c): 0
+[0x61bdf0, 0x61c5f4): 0
+[0x61c5f4, 0x61d360): 0
+[0x61d368, 0x61d720): 0
+0x00400239 3 hit.bytes.0
+0x00400f19 3 hit.bytes.1
+0x00400fae 3 hit.bytes.2
+0x00400feb 3 hit.bytes.3
+0x004013c3 3 hit.bytes.4
+0x0041769a 3 hit.bytes.5
+0x004186e0 3 hit.bytes.6
+0x00418e58 3 hit.bytes.7
+----
 lib64/ld-linux-x86-64.so.2
 libselinux.so.1
 libacl.so.1
@@ -158,5 +207,4 @@ libs/
 lib/xstrtol.c
 lib
 EOF
-EXPECT_ERR=
 RUN


Reviews:
user: login: wargio
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-01T09:47:55Z
user: login: kazarmy
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-01T09:52:39Z
user: login: kazarmy
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-01T10:11:12Z
user: login: wargio
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-01T10:49:35Z
user: login: wargio
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-01T10:51:59Z
user: login: kazarmy
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-01T11:00:13Z
user: login: wargio
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-01T11:11:58Z
user: login: kazarmy
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-01T11:20:00Z
user: login: Rot127
body: > Well if 2 threads have the same search range, they are doing duplicate work.

This should not happen btw. Did you experience it? If so, it is a bug.
state: CHANGES_REQUESTED
author_association: MEMBER
submitted_at: 2025-03-01T12:08:12Z
user: login: Rot127
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-01T12:10:48Z
user: login: kazarmy
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-01T22:37:52Z
user: login: kazarmy
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-02T12:40:10Z
user: login: kazarmy
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-02T12:44:14Z
user: login: kazarmy
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-03T12:40:47Z
user: login: wargio
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-03T16:32:10Z
user: login: kazarmy
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-04T11:23:33Z
user: login: Rot127
body: Just those nitpicks. Thanks, love this UX stuff!
state: CHANGES_REQUESTED
author_association: MEMBER
submitted_at: 2025-03-05T09:12:19Z
user: login: kazarmy
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-05T10:06:34Z
user: login: kazarmy
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-05T10:10:17Z
user: login: wargio
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-05T11:03:22Z
user: login: kazarmy
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-05T11:16:55Z
user: login: Rot127
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-07T10:23:46Z
user: login: Rot127
state: APPROVED
author_association: MEMBER
submitted_at: 2025-03-07T10:25:02Z

Review comments:
diff_hunk: @@ -604,10 +604,13 @@ static bool search_iterator_io_map_cb(void *element, void *user) {
 	}
 	rz_th_lock_leave(ctx->io_lock);
 
+	size_t num_hits = 0;
 	RzSearchFindBytesCallback find = col->find;
-	if (!find(ctx->opt->find_opts, col->user, at, buffer, ctx->hits)) {
+	if (!find(ctx->opt->find_opts, col->user, at, buffer, ctx->hits, &num_hits)) {
 		RZ_LOG_ERROR("search: failed search at 0x%08" PFMT64x "\n", at);
 		goto failure;
+	} else if (ctx->opt->show_progress) {
+		eprintf("Searching in [0x%" PFMT64x ", 0x%" PFMT64x ") ... hits: %zu\n", at, at + size, num_hits);
path: librz/search/search.c
user: login: wargio
body: This is wrong for me, since each thread has a different search range. if you want to do this, you should follow what Basefind does.
created_at: 2025-03-01T09:47:55Z
author_association: MEMBER
diff_hunk: @@ -604,10 +604,13 @@ static bool search_iterator_io_map_cb(void *element, void *user) {
 	}
 	rz_th_lock_leave(ctx->io_lock);
 
+	size_t num_hits = 0;
 	RzSearchFindBytesCallback find = col->find;
-	if (!find(ctx->opt->find_opts, col->user, at, buffer, ctx->hits)) {
+	if (!find(ctx->opt->find_opts, col->user, at, buffer, ctx->hits, &num_hits)) {
 		RZ_LOG_ERROR("search: failed search at 0x%08" PFMT64x "\n", at);
 		goto failure;
+	} else if (ctx->opt->show_progress) {
+		eprintf("Searching in [0x%" PFMT64x ", 0x%" PFMT64x ") ... hits: %zu\n", at, at + size, num_hits);
path: librz/search/search.c
user: login: kazarmy
body: > This is wrong for me, since each thread has a different search range.

Well if 2 threads have the same search range, they are doing duplicate work.

> if you want to do this, you should follow what Basefind does.

Why?
created_at: 2025-03-01T09:52:39Z
author_association: MEMBER
diff_hunk: @@ -604,10 +604,13 @@ static bool search_iterator_io_map_cb(void *element, void *user) {
 	}
 	rz_th_lock_leave(ctx->io_lock);
 
+	size_t num_hits = 0;
 	RzSearchFindBytesCallback find = col->find;
-	if (!find(ctx->opt->find_opts, col->user, at, buffer, ctx->hits)) {
+	if (!find(ctx->opt->find_opts, col->user, at, buffer, ctx->hits, &num_hits)) {
 		RZ_LOG_ERROR("search: failed search at 0x%08" PFMT64x "\n", at);
 		goto failure;
+	} else if (ctx->opt->show_progress) {
+		eprintf("Searching in [0x%" PFMT64x ", 0x%" PFMT64x ") ... hits: %zu\n", at, at + size, num_hits);
path: librz/search/search.c
user: login: kazarmy
body: I looked at https://github.com/LindLL/basefind/tree/main?tab=readme-ov-file#1sony and https://github.com/sgayou/rbasefind?tab=readme-ov-file#example, they have more compact output (I think) but they are definitely not sorted
created_at: 2025-03-01T10:11:12Z
author_association: MEMBER
diff_hunk: @@ -604,10 +604,13 @@ static bool search_iterator_io_map_cb(void *element, void *user) {
 	}
 	rz_th_lock_leave(ctx->io_lock);
 
+	size_t num_hits = 0;
 	RzSearchFindBytesCallback find = col->find;
-	if (!find(ctx->opt->find_opts, col->user, at, buffer, ctx->hits)) {
+	if (!find(ctx->opt->find_opts, col->user, at, buffer, ctx->hits, &num_hits)) {
 		RZ_LOG_ERROR("search: failed search at 0x%08" PFMT64x "\n", at);
 		goto failure;
+	} else if (ctx->opt->show_progress) {
+		eprintf("Searching in [0x%" PFMT64x ", 0x%" PFMT64x ") ... hits: %zu\n", at, at + size, num_hits);
path: librz/search/search.c
user: login: wargio
body: no, basefind output is the one that you shown, and we do the same, let me explain better what i meant.

`rz_th_iterate_*` iterates elements over threads, each thread will get **only one** element of the array/list.
In search.c we fill the list with RzInterval, so each thread will get only one interval, so if you want to print the number of hits for each RzInterval, then we can just add a mutex and print the nHits at the end of each `find` callback.

The issue i see here is that search and TUI prints should go in different threads, so when we bring this code to cutter, we wont have prints over terminal when one searches but on the GUI. so if you want to have nhits for each interval, then we should define a new structure and store the metadata there, like

```c
typedef struct rz_search_interval_t {
    RzInterval interval;
    size_t n_hits;
} RzSearchInterval;
```

This is what we do for basefind: https://github.com/rizinorg/rizin/blob/dev/librz/include/rz_basefind.h#L22
created_at: 2025-03-01T10:49:35Z
author_association: MEMBER
diff_hunk: @@ -604,10 +604,13 @@ static bool search_iterator_io_map_cb(void *element, void *user) {
 	}
 	rz_th_lock_leave(ctx->io_lock);
 
+	size_t num_hits = 0;
 	RzSearchFindBytesCallback find = col->find;
-	if (!find(ctx->opt->find_opts, col->user, at, buffer, ctx->hits)) {
+	if (!find(ctx->opt->find_opts, col->user, at, buffer, ctx->hits, &num_hits)) {
 		RZ_LOG_ERROR("search: failed search at 0x%08" PFMT64x "\n", at);
 		goto failure;
+	} else if (ctx->opt->show_progress) {
+		eprintf("Searching in [0x%" PFMT64x ", 0x%" PFMT64x ") ... hits: %zu\n", at, at + size, num_hits);
path: librz/search/search.c
user: login: wargio
body: So for printing these results, for me, we have 2 ways:

- Print them live via the `search_cancel_th` thread (we need to define a new RzThreadQueue to inform the `search_cancel_th` callback to print stuff when the queue is not empty).
- Print them at the end of the execution.
created_at: 2025-03-01T10:51:59Z
author_association: MEMBER
diff_hunk: @@ -604,10 +604,13 @@ static bool search_iterator_io_map_cb(void *element, void *user) {
 	}
 	rz_th_lock_leave(ctx->io_lock);
 
+	size_t num_hits = 0;
 	RzSearchFindBytesCallback find = col->find;
-	if (!find(ctx->opt->find_opts, col->user, at, buffer, ctx->hits)) {
+	if (!find(ctx->opt->find_opts, col->user, at, buffer, ctx->hits, &num_hits)) {
 		RZ_LOG_ERROR("search: failed search at 0x%08" PFMT64x "\n", at);
 		goto failure;
+	} else if (ctx->opt->show_progress) {
+		eprintf("Searching in [0x%" PFMT64x ", 0x%" PFMT64x ") ... hits: %zu\n", at, at + size, num_hits);
path: librz/search/search.c
user: login: kazarmy
body: > - Print them at the end of the execution.

Deferring printing until the end means not printing during the search (about search progress) so I think only the first way is viable.
created_at: 2025-03-01T11:00:13Z
author_association: MEMBER
diff_hunk: @@ -604,10 +604,13 @@ static bool search_iterator_io_map_cb(void *element, void *user) {
 	}
 	rz_th_lock_leave(ctx->io_lock);
 
+	size_t num_hits = 0;
 	RzSearchFindBytesCallback find = col->find;
-	if (!find(ctx->opt->find_opts, col->user, at, buffer, ctx->hits)) {
+	if (!find(ctx->opt->find_opts, col->user, at, buffer, ctx->hits, &num_hits)) {
 		RZ_LOG_ERROR("search: failed search at 0x%08" PFMT64x "\n", at);
 		goto failure;
+	} else if (ctx->opt->show_progress) {
+		eprintf("Searching in [0x%" PFMT64x ", 0x%" PFMT64x ") ... hits: %zu\n", at, at + size, num_hits);
path: librz/search/search.c
user: login: wargio
body: If we do live printing, then we can even have a percentage of progress.

created_at: 2025-03-01T11:11:58Z
author_association: MEMBER
diff_hunk: @@ -604,10 +604,13 @@ static bool search_iterator_io_map_cb(void *element, void *user) {
 	}
 	rz_th_lock_leave(ctx->io_lock);
 
+	size_t num_hits = 0;
 	RzSearchFindBytesCallback find = col->find;
-	if (!find(ctx->opt->find_opts, col->user, at, buffer, ctx->hits)) {
+	if (!find(ctx->opt->find_opts, col->user, at, buffer, ctx->hits, &num_hits)) {
 		RZ_LOG_ERROR("search: failed search at 0x%08" PFMT64x "\n", at);
 		goto failure;
+	} else if (ctx->opt->show_progress) {
+		eprintf("Searching in [0x%" PFMT64x ", 0x%" PFMT64x ") ... hits: %zu\n", at, at + size, num_hits);
path: librz/search/search.c
user: login: kazarmy
body: > ...  print the nHits at the end of each `find` callback.

Now I see I can (probably) get the search interval size from the buffer 🤦 
created_at: 2025-03-01T11:20:00Z
author_association: MEMBER
diff_hunk: @@ -604,10 +604,13 @@ static bool search_iterator_io_map_cb(void *element, void *user) {
 	}
 	rz_th_lock_leave(ctx->io_lock);
 
+	size_t num_hits = 0;
 	RzSearchFindBytesCallback find = col->find;
-	if (!find(ctx->opt->find_opts, col->user, at, buffer, ctx->hits)) {
+	if (!find(ctx->opt->find_opts, col->user, at, buffer, ctx->hits, &num_hits)) {
 		RZ_LOG_ERROR("search: failed search at 0x%08" PFMT64x "\n", at);
 		goto failure;
+	} else if (ctx->opt->show_progress) {
+		eprintf("[0x%" PFMT64x ", 0x%" PFMT64x "): %zu\n", at, at + size, num_hits);
path: librz/search/search.c
user: login: Rot127
body: ```suggestion
		eprintf("[0x%" PFMT64x ", 0x%" PFMT64x "): %" PFMTSZu "\n", at, at + size, num_hits);
```

~~Also I think it is an end inclusive range `[x, y]`. Could be wrong though.~~ Can't read. all good as it is.
created_at: 2025-03-01T12:04:17Z
author_association: MEMBER
diff_hunk: @@ -66,6 +66,12 @@ RZ_API bool rz_search_opt_set_max_threads(RZ_NONNULL RzSearchOpt *opt, RzThreadN
 	return true;
 }
 
+RZ_API bool rz_search_opt_set_show_progress(RZ_NONNULL RzSearchOpt *opt, bool show_progress) {
+	rz_return_val_if_fail(opt, false);
+	opt->show_progress = show_progress;
path: librz/search/options.c
user: login: Rot127
body: I think though this should be an extra option. Something like `show_progress_verbose`. Because if the file is big, there will be many intervals, which clutters the output. Maybe it could be also a choice:

`number_of_hits`, `percent`, `hits_per_interval/sub-graph/knowledge domain` etc. Might be also useful for later, when we have searches over graphs or the KB.
created_at: 2025-03-01T12:07:13Z
author_association: MEMBER
diff_hunk: @@ -604,10 +604,13 @@ static bool search_iterator_io_map_cb(void *element, void *user) {
 	}
 	rz_th_lock_leave(ctx->io_lock);
 
+	size_t num_hits = 0;
 	RzSearchFindBytesCallback find = col->find;
-	if (!find(ctx->opt->find_opts, col->user, at, buffer, ctx->hits)) {
+	if (!find(ctx->opt->find_opts, col->user, at, buffer, ctx->hits, &num_hits)) {
 		RZ_LOG_ERROR("search: failed search at 0x%08" PFMT64x "\n", at);
 		goto failure;
+	} else if (ctx->opt->show_progress) {
+		eprintf("Searching in [0x%" PFMT64x ", 0x%" PFMT64x ") ... hits: %zu\n", at, at + size, num_hits);
path: librz/search/search.c
user: login: Rot127
body: Agree with the design of the latest commit (https://github.com/rizinorg/rizin/pull/4950/commits/ac9f5ce5631981325437d2469ccb1efc829a75a2).

Passing `*num_hits` to the `find()` function is a little meh, because it clutters it, but can't come up with a simpler idea as well. Please document the parameter though!
created_at: 2025-03-01T12:10:48Z
author_association: MEMBER
diff_hunk: @@ -604,10 +604,13 @@ static bool search_iterator_io_map_cb(void *element, void *user) {
 	}
 	rz_th_lock_leave(ctx->io_lock);
 
+	size_t num_hits = 0;
 	RzSearchFindBytesCallback find = col->find;
-	if (!find(ctx->opt->find_opts, col->user, at, buffer, ctx->hits)) {
+	if (!find(ctx->opt->find_opts, col->user, at, buffer, ctx->hits, &num_hits)) {
 		RZ_LOG_ERROR("search: failed search at 0x%08" PFMT64x "\n", at);
 		goto failure;
+	} else if (ctx->opt->show_progress) {
+		eprintf("Searching in [0x%" PFMT64x ", 0x%" PFMT64x ") ... hits: %zu\n", at, at + size, num_hits);
path: librz/search/search.c
user: login: kazarmy
body: Documented the parameter in 3b78cb6aa8987a9f3f2e279666929a5aaf2ab0eb. I did think of discarding the parameter and doing final nHits processing at the end of each `find` callback, but I think it's better to reduce code duplication as much as possible.
created_at: 2025-03-01T22:37:52Z
author_association: MEMBER
diff_hunk: @@ -604,10 +604,13 @@ static bool search_iterator_io_map_cb(void *element, void *user) {
 	}
 	rz_th_lock_leave(ctx->io_lock);
 
+	size_t num_hits = 0;
 	RzSearchFindBytesCallback find = col->find;
-	if (!find(ctx->opt->find_opts, col->user, at, buffer, ctx->hits)) {
+	if (!find(ctx->opt->find_opts, col->user, at, buffer, ctx->hits, &num_hits)) {
 		RZ_LOG_ERROR("search: failed search at 0x%08" PFMT64x "\n", at);
 		goto failure;
+	} else if (ctx->opt->show_progress) {
+		eprintf("Searching in [0x%" PFMT64x ", 0x%" PFMT64x ") ... hits: %zu\n", at, at + size, num_hits);
path: librz/search/search.c
user: login: kazarmy
body: > - Print them live via the `search_cancel_th` thread ...

See 204643c809051ae7042f8a5be10f3ec708c2020c
created_at: 2025-03-02T12:40:10Z
author_association: MEMBER
diff_hunk: @@ -66,6 +66,12 @@ RZ_API bool rz_search_opt_set_max_threads(RZ_NONNULL RzSearchOpt *opt, RzThreadN
 	return true;
 }
 
+RZ_API bool rz_search_opt_set_show_progress(RZ_NONNULL RzSearchOpt *opt, bool show_progress) {
+	rz_return_val_if_fail(opt, false);
+	opt->show_progress = show_progress;
path: librz/search/options.c
user: login: kazarmy
body: I think that a hybrid approach with `show_progress` i.e. it accepts `false`, `true` and the special value `interval_hits` (shorter than `hits_per_interval`) is currently the best way to handle this, but it will take some time.
created_at: 2025-03-02T12:44:14Z
author_association: MEMBER
diff_hunk: @@ -66,6 +66,12 @@ RZ_API bool rz_search_opt_set_max_threads(RZ_NONNULL RzSearchOpt *opt, RzThreadN
 	return true;
 }
 
+RZ_API bool rz_search_opt_set_show_progress(RZ_NONNULL RzSearchOpt *opt, bool show_progress) {
+	rz_return_val_if_fail(opt, false);
+	opt->show_progress = show_progress;
path: librz/search/options.c
user: login: kazarmy
body: See 97ae8557d1c660f11744f8b81f1558b94fb9479f ... I'm using `intervals` since it's shorter than `interval_hits` yet basically has the same meaning (i think)
created_at: 2025-03-03T12:40:47Z
author_association: MEMBER
diff_hunk: @@ -3747,7 +3747,7 @@ RZ_API int rz_core_config_init(RzCore *core) {
 		NULL);
 	SETPREF("search.prefix", "hit", "Prefix name in search hits label");
 	SETI("search.maxhits", 0, "Maximum number of hits ('0' means no limit)");
-	SETBPREF("search.show_progress", "true", "Show the search process.");
+	SETPREF("search.show_progress", "1", "Show the search process (true, false, intervals)");
path: librz/core/cconfig.c
user: login: wargio
body: make this as a option see `str.encoding` for the example..
created_at: 2025-03-03T16:32:10Z
author_association: MEMBER
diff_hunk: @@ -3747,7 +3747,7 @@ RZ_API int rz_core_config_init(RzCore *core) {
 		NULL);
 	SETPREF("search.prefix", "hit", "Prefix name in search hits label");
 	SETI("search.maxhits", 0, "Maximum number of hits ('0' means no limit)");
-	SETBPREF("search.show_progress", "true", "Show the search process.");
+	SETPREF("search.show_progress", "1", "Show the search process (true, false, intervals)");
path: librz/core/cconfig.c
user: login: kazarmy
body: ok b9355bdcf7767ae0ddc442b7dea8fa47a87c742b ... the reason `"1"` is used instead of `"true"` as the initial value is because of what appears to be a config design flaw that limits config options to only boolean values if the initial value is either `"true"` or `"false"`
created_at: 2025-03-04T11:23:33Z
author_association: MEMBER
diff_hunk: @@ -2651,6 +2651,21 @@ static bool cb_search_in(void *user, void *data) {
 	return false;
 }
 
+static bool cb_search_show_progress(void *user, void *data) {
+	RzConfigNode *node = (RzConfigNode *)data;
+	if (rz_str_is_true(node->value)) {
+		free(node->value);
+		node->value = rz_str_dup("true");
+	} else if (rz_str_is_false(node->value)) {
+		free(node->value);
+		node->value = rz_str_dup("false");
+	} else if (strcmp(node->value, "intervals")) {
path: librz/core/cconfig.c
user: login: Rot127
body: ```suggestion
	} else if (!RZ_STR_EQ(node->value, "intervals")) {
```
created_at: 2025-03-05T09:04:48Z
author_association: MEMBER
diff_hunk: @@ -2651,6 +2651,21 @@ static bool cb_search_in(void *user, void *data) {
 	return false;
 }
 
+static bool cb_search_show_progress(void *user, void *data) {
+	RzConfigNode *node = (RzConfigNode *)data;
+	if (rz_str_is_true(node->value)) {
+		free(node->value);
+		node->value = rz_str_dup("true");
+	} else if (rz_str_is_false(node->value)) {
+		free(node->value);
+		node->value = rz_str_dup("false");
+	} else if (strcmp(node->value, "intervals")) {
path: librz/core/cconfig.c
user: login: Rot127
body: Also, the the `intervals` case is missing?
created_at: 2025-03-05T09:05:23Z
author_association: MEMBER
diff_hunk: @@ -66,6 +66,18 @@ RZ_API bool rz_search_opt_set_max_threads(RZ_NONNULL RzSearchOpt *opt, RzThreadN
 	return true;
 }
 
+RZ_API bool rz_search_opt_set_show_progress_from_str(RZ_NONNULL RzSearchOpt *opt, const char *show_progress) {
+	rz_return_val_if_fail(opt, false);
+	if (rz_str_is_false(show_progress)) {
+		opt->show_progress = RZ_SEARCH_PROGRESS_DISABLED;
+	} else if (!strcmp(show_progress, "intervals")) {
path: librz/search/options.c
user: login: Rot127
body: ```suggestion
	} else if (RZ_STR_EQ(show_progress, "intervals")) {
```
created_at: 2025-03-05T09:06:59Z
author_association: MEMBER
diff_hunk: @@ -556,8 +556,20 @@ typedef struct search_ctx {
 	RzSearchOpt *opt; ///< User options
 	RzThreadQueue /* RzSearchHits */ *hits; ///< Hits list
 	RzAtomicBool *loop; ///< If set, the execution will continue until it terminates. If unset, the execution cancels.
+	RzThreadQueue /* RzSearchInterval */ *intervals; ///< Interval list
path: librz/search/search.c
user: login: Rot127
body: ```suggestion
	RzThreadQueue /* RzSearchInterval */ *finished_intervals; ///< Interval queue
```

This is the correct interpretation, right?
created_at: 2025-03-05T09:07:36Z
author_association: MEMBER
diff_hunk: @@ -556,8 +556,20 @@ typedef struct search_ctx {
 	RzSearchOpt *opt; ///< User options
 	RzThreadQueue /* RzSearchHits */ *hits; ///< Hits list
 	RzAtomicBool *loop; ///< If set, the execution will continue until it terminates. If unset, the execution cancels.
+	RzThreadQueue /* RzSearchInterval */ *intervals; ///< Interval list
 } search_ctx_t;
 
+static void print_intervals(RZ_NONNULL RzThreadQueue *intervals) {
+	rz_return_if_fail(intervals);
+
+	RzSearchInterval *search_interval = NULL;
+	while ((search_interval = rz_th_queue_pop(intervals, false))) {
+		RzInterval *itv = &search_interval->interval;
+		eprintf("[0x%" PFMT64x ", 0x%" PFMT64x "): %zu\n", itv->addr, itv->addr + itv->size,
path: librz/search/search.c
user: login: Rot127
body: ```suggestion
		eprintf("[0x%" PFMT64x ", 0x%" PFMT64x "): %" PFMTSZu "\n", itv->addr, itv->addr + itv->size,
```
created_at: 2025-03-05T09:08:11Z
author_association: MEMBER
diff_hunk: @@ -556,8 +556,20 @@ typedef struct search_ctx {
 	RzSearchOpt *opt; ///< User options
 	RzThreadQueue /* RzSearchHits */ *hits; ///< Hits list
 	RzAtomicBool *loop; ///< If set, the execution will continue until it terminates. If unset, the execution cancels.
+	RzThreadQueue /* RzSearchInterval */ *intervals; ///< Interval list
path: librz/search/search.c
user: login: kazarmy
body: Yes. A complete commit is in 33b30d29a65e036bc9719cd455fc773bbb1a44c8.
created_at: 2025-03-05T10:06:34Z
author_association: MEMBER
diff_hunk: @@ -2651,6 +2651,21 @@ static bool cb_search_in(void *user, void *data) {
 	return false;
 }
 
+static bool cb_search_show_progress(void *user, void *data) {
+	RzConfigNode *node = (RzConfigNode *)data;
+	if (rz_str_is_true(node->value)) {
+		free(node->value);
+		node->value = rz_str_dup("true");
+	} else if (rz_str_is_false(node->value)) {
+		free(node->value);
+		node->value = rz_str_dup("false");
+	} else if (strcmp(node->value, "intervals")) {
path: librz/core/cconfig.c
user: login: kazarmy
body: Nothing needs to be done for the `intervals` case and the code can just return true.
created_at: 2025-03-05T10:10:16Z
author_association: MEMBER
diff_hunk: @@ -3747,7 +3762,9 @@ RZ_API int rz_core_config_init(RzCore *core) {
 		NULL);
 	SETPREF("search.prefix", "hit", "Prefix name in search hits label");
 	SETI("search.maxhits", 0, "Maximum number of hits ('0' means no limit)");
-	SETBPREF("search.show_progress", "true", "Show the search process.");
+	n = NODECB("search.show_progress", "1", &cb_search_show_progress);
+	SETDESC(n, "Show the search process (true, false, intervals)");
+	SETOPTIONS(n, "true", "false", "intervals", NULL);
path: librz/core/cconfig.c
user: login: wargio
body: tbh, i don't like this. is not really clear...
created_at: 2025-03-05T11:03:22Z
author_association: MEMBER
diff_hunk: @@ -3747,7 +3762,9 @@ RZ_API int rz_core_config_init(RzCore *core) {
 		NULL);
 	SETPREF("search.prefix", "hit", "Prefix name in search hits label");
 	SETI("search.maxhits", 0, "Maximum number of hits ('0' means no limit)");
-	SETBPREF("search.show_progress", "true", "Show the search process.");
+	n = NODECB("search.show_progress", "1", &cb_search_show_progress);
+	SETDESC(n, "Show the search process (true, false, intervals)");
+	SETOPTIONS(n, "true", "false", "intervals", NULL);
path: librz/core/cconfig.c
user: login: kazarmy
body: how about a12dccf5d976ad7ac852e9ee567ef1250dfe71e7?
created_at: 2025-03-05T11:16:55Z
author_association: MEMBER
diff_hunk: @@ -3747,7 +3762,9 @@ RZ_API int rz_core_config_init(RzCore *core) {
 		NULL);
 	SETPREF("search.prefix", "hit", "Prefix name in search hits label");
 	SETI("search.maxhits", 0, "Maximum number of hits ('0' means no limit)");
-	SETBPREF("search.show_progress", "true", "Show the search process.");
+	n = NODECB("search.show_progress", "1", &cb_search_show_progress);
+	SETDESC(n, "Show the search process (true, false, intervals)");
+	SETOPTIONS(n, "true", "false", "intervals", NULL);
path: librz/core/cconfig.c
user: login: Rot127
body: https://github.com/rizinorg/rizin/commit/a12dccf5d976ad7ac852e9ee567ef1250dfe71e7 is nice IMHO
created_at: 2025-03-07T10:23:46Z
author_association: MEMBER

PR comments:
user: login: wargio
created_at: 2025-03-01T15:41:44Z
body: > This should not happen btw. Did you experience it? If so, it is a bug.

i think it was just a misunderstanding of the API
author_association: MEMBER
user: login: kazarmy
created_at: 2025-03-01T15:43:04Z
body: > This should not happen btw. Did you experience it? If so, it is a bug.

No I didn't experience it
author_association: MEMBER