1 /* search.c -- functions for the search system */
2 
3 /*
4  * This file is part of CliFM
5  *
6  * Copyright (C) 2016-2021, L. Abramovich <johndoe.arch@outlook.com>
7  * All rights reserved.
8 
9  * CliFM is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 2 of the License, or
12  * (at your option) any later version.
13  *
14  * CliFM is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program; if not, write to the Free Software
21  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
22  * MA 02110-1301, USA.
23 */
24 
25 #include "helpers.h"
26 
27 #include <dirent.h>
28 #include <errno.h>
29 #include <fcntl.h>
30 #include <regex.h>
31 #include <stdio.h>
32 #include <string.h>
33 #include <sys/ioctl.h>
34 #include <sys/stat.h>
35 #include <unistd.h>
36 #include <glob.h>
37 
38 #include "aux.h"
39 #include "checks.h"
40 #include "colors.h"
41 #include "exec.h"
42 #include "navigation.h"
43 #include "sort.h"
44 
45 static int
check_glob_char(char * str)46 check_glob_char(char *str)
47 {
48 	size_t i;
49 	for (i = 1; str[i]; i++) {
50 		if (str[i] == '*' || str[i] == '?' || str[i] == '[' || str[i] == '{'
51 		    /* Consider regex chars as well: we don't want this "r$"
52 		    * to become this "*r$*" */
53 		|| str[i] == '|' || str[i] == '^' || str[i] == '+' || str[i] == '$'
54 		|| str[i] == '.')
55 			return 1;
56 	}
57 
58 	return 0;
59 }
60 
61 /* List matching file names in the specified directory */
62 int
search_glob(char ** comm,int invert)63 search_glob(char **comm, int invert)
64 {
65 	if (!comm || !comm[0])
66 		return EXIT_FAILURE;
67 
68 	char *search_str = (char *)NULL,
69 		 *search_path = (char *)NULL;
70 
71 	mode_t file_type = 0;
72 	struct stat file_attrib;
73 
74 	/* If there are two arguments, the one starting with '-' is the
75 	 * file type and the other is the path */
76 	if (comm[1] && comm[2]) {
77 		if (*comm[1] == '-') {
78 			file_type = (mode_t)comm[1][1];
79 			search_path = comm[2];
80 		} else if (*comm[2] == '-') {
81 			file_type = (mode_t)comm[2][1];
82 			search_path = comm[1];
83 		} else {
84 			search_path = comm[1];
85 		}
86 	}
87 
88 	/* If just one argument, '-' indicates file type. Else, we have a
89 	 * path */
90 	else if (comm[1]) {
91 		if (*comm[1] == '-')
92 			file_type = (mode_t)comm[1][1];
93 		else
94 			search_path = comm[1];
95 	}
96 
97 	/* If no arguments, search_path will be NULL and file_type zero */
98 	int recursive = 0;
99 
100 	if (file_type) {
101 		/* Convert file type into a macro that can be decoded by stat().
102 		 * If file type is specified, matches will be checked against
103 		 * this value */
104 		switch (file_type) {
105 		case 'd': file_type = invert ? DT_DIR : S_IFDIR; break;
106 		case 'r': file_type = invert ? DT_REG : S_IFREG; break;
107 		case 'l': file_type = invert ? DT_LNK : S_IFLNK; break;
108 		case 's': file_type = invert ? DT_SOCK : S_IFSOCK; break;
109 		case 'f': file_type = invert ? DT_FIFO : S_IFIFO; break;
110 		case 'b': file_type = invert ? DT_BLK : S_IFBLK; break;
111 		case 'c': file_type = invert ? DT_CHR : S_IFCHR; break;
112 		case 'x': recursive = 1; break;
113 		default:
114 			fprintf(stderr, _("%s: '%c': Unrecognized file type\n"),
115 			    PROGRAM_NAME, (char)file_type);
116 			return EXIT_FAILURE;
117 		}
118 	}
119 
120 	if (recursive) {
121 		int glob_char = check_glob_char(comm[0] + 1);
122 		if (glob_char) {
123 			char *cmd[] = {"find", (search_path && *search_path) ? search_path
124 						: ".", "-name", comm[0] + 1, NULL};
125 			launch_execve(cmd, FOREGROUND, E_NOSTDERR);
126 		} else {
127 			char *ss = (char *)xnmalloc(strlen(comm[0] + 1) + 3, sizeof(char));
128 			sprintf(ss, "*%s*", comm[0] + 1);
129 			char *cmd[] = {"find", (search_path && *search_path) ? search_path
130 						: ".", "-name", ss, NULL};
131 			launch_execve(cmd, FOREGROUND, E_NOSTDERR);
132 			free(ss);
133 		}
134 		return EXIT_SUCCESS;
135 	}
136 
137 	/* If we have a path ("/str /path"), chdir into it, since
138 	 * glob() works on CWD */
139 	if (search_path && *search_path) {
140 		/* Deescape the search path, if necessary */
141 		if (strchr(search_path, '\\')) {
142 			char *deq_dir = dequote_str(search_path, 0);
143 
144 			if (!deq_dir) {
145 				fprintf(stderr, _("%s: %s: Error dequoting file name\n"),
146 				    PROGRAM_NAME, comm[1]);
147 				return EXIT_FAILURE;
148 			}
149 
150 			strcpy(search_path, deq_dir);
151 			free(deq_dir);
152 		}
153 
154 		size_t path_len = strlen(search_path);
155 		if (search_path[path_len - 1] == '/')
156 			search_path[path_len - 1] = '\0';
157 
158 		/* If search is current directory */
159 		if ((*search_path == '.' && !search_path[1]) ||
160 		    (search_path[1] == ws[cur_ws].path[1]
161 		    && strcmp(search_path, ws[cur_ws].path) == 0)) {
162 			search_path = (char *)NULL;
163 		} else if (xchdir(search_path, NO_TITLE) == -1) {
164 			fprintf(stderr, "%s: %s: %s\n", PROGRAM_NAME, search_path,
165 			    strerror(errno));
166 			return EXIT_FAILURE;
167 		}
168 	}
169 
170 	int i;
171 	char *tmp = comm[0];
172 
173 	if (invert)
174 		tmp++;
175 
176 	/* Search for globbing char */
177 	int glob_char_found = check_glob_char(tmp);
178 /*	for (i = 1; tmp[i]; i++) {
179 		if (tmp[i] == '*' || tmp[i] == '?' || tmp[i] == '[' || tmp[i] == '{'
180 		    // Consider regex chars as well: we don't want this "r$"
181 		    // to become this "*r$*"
182 		    || tmp[i] == '|' || tmp[i] == '^' || tmp[i] == '+' || tmp[i] == '$'
183 		    || tmp[i] == '.') {
184 			glob_char_found = 1;
185 			break;
186 		}
187 	} */
188 
189 	/* If search string is just "STR" (no glob chars), change it
190 	 * to ".*STR.*" */
191 	if (!glob_char_found) {
192 		size_t search_str_len = strlen(comm[0]);
193 
194 //#ifndef __TEST
195 //		comm[0] = (char *)xrealloc(comm[0], (search_str_len + 2) * sizeof(char));
196 //#else
197 		comm[0] = (char *)xrealloc(comm[0], (search_str_len + 5) * sizeof(char));
198 //#endif
199 
200 		tmp = comm[0];
201 		if (invert) {
202 			++tmp;
203 //			search_str_len = strlen(tmp);
204 		}
205 /*
206 #ifndef __TEST
207 		tmp[0] = '*';
208 		tmp[search_str_len] = '*';
209 		tmp[search_str_len + 1] = '\0';
210 		search_str = tmp;
211 #else */
212 		char *s = xnmalloc(strlen(tmp) + 1, sizeof(char));
213 		strcpy(s, tmp);
214 
215 		*(tmp + 1) = '.';
216 		*(tmp + 2) = '*';
217 		strcpy(tmp + 3, s + 1);
218 		size_t slen = strlen(tmp);
219 		tmp[slen] = '.';
220 		tmp[slen + 1] = '*';
221 		tmp[slen + 2] = '\0';
222 
223 		free(s);
224 		return EXIT_FAILURE;
225 //#endif
226 	} else {
227 		search_str = tmp + 1;
228 	}
229 
230 	/* Get matches, if any */
231 	glob_t globbed_files;
232 	int ret = glob(search_str, GLOB_BRACE, NULL, &globbed_files);
233 
234 	if (ret != 0) {
235 		puts(_("Glob: No matches found. Trying regex..."));
236 
237 		globfree(&globbed_files);
238 
239 		if (search_path) {
240 			/* Go back to the directory we came from */
241 			if (xchdir(ws[cur_ws].path, NO_TITLE) == -1)
242 				fprintf(stderr, "%s: %s: %s\n", PROGRAM_NAME,
243 				    ws[cur_ws].path, strerror(errno));
244 		}
245 
246 		return EXIT_FAILURE;
247 	}
248 
249 	/* We have matches */
250 	int scandir_files = 0,
251 		found = 0;
252 
253 	size_t flongest = 0;
254 
255 	/* We need to store pointers to matching file names in array of
256 	 * pointers, just as the file name length (to construct the
257 	 * columned output), and, if searching in CWD, its index (ELN)
258 	 * in the dirlist array as well */
259 	char **pfiles = (char **)NULL;
260 	int *eln = (int *)0;
261 	size_t *files_len = (size_t *)0;
262 	struct dirent **ent = (struct dirent **)NULL;
263 
264 	if (invert) {
265 		if (!search_path) {
266 			int k;
267 			pfiles = (char **)xnmalloc(files + 1, sizeof(char *));
268 			eln = (int *)xnmalloc(files + 1, sizeof(int));
269 			files_len = (size_t *)xnmalloc(files + 1, sizeof(size_t));
270 
271 			for (k = 0; file_info[k].name; k++) {
272 				int l, f = 0;
273 
274 				for (l = 0; globbed_files.gl_pathv[l]; l++) {
275 					if (*globbed_files.gl_pathv[l] == *file_info[k].name
276 					&& strcmp(globbed_files.gl_pathv[l], file_info[k].name) == 0) {
277 						f = 1;
278 						break;
279 					}
280 				}
281 
282 				if (!f) {
283 					if (file_type && file_info[k].type != file_type)
284 						continue;
285 
286 					eln[found] = (int)(k + 1);
287 					files_len[found] = file_info[k].len
288 								+ (size_t)file_info[k].eln_n + 1;
289 					if (files_len[found] > flongest)
290 						flongest = files_len[found];
291 
292 					pfiles[found++] = file_info[k].name;
293 				}
294 			}
295 		} else {
296 			scandir_files = scandir(search_path, &ent, skip_files,
297 			    xalphasort);
298 
299 			if (scandir_files != -1) {
300 				pfiles = (char **)xnmalloc((size_t)scandir_files + 1,
301 				    sizeof(char *));
302 				eln = (int *)xnmalloc((size_t)scandir_files + 1,
303 				    sizeof(int));
304 				files_len = (size_t *)xnmalloc((size_t)scandir_files + 1,
305 				    sizeof(size_t));
306 
307 				int k, l;
308 
309 				for (k = 0; k < scandir_files; k++) {
310 					int f = 0;
311 
312 					for (l = 0; globbed_files.gl_pathv[l]; l++) {
313 						if (*ent[k]->d_name == *globbed_files.gl_pathv[l]
314 						&& strcmp(ent[k]->d_name, globbed_files.gl_pathv[l]) == 0) {
315 							f = 1;
316 							break;
317 						}
318 					}
319 
320 					if (!f) {
321 #if !defined(_DIRENT_HAVE_D_TYPE)
322 						struct stat attr;
323 						mode_t type;
324 						if (lstat(ent[k]->d_name, &attr) == -1)
325 							continue;
326 						type = get_dt(attr.st_mode);
327 						if (file_type && type != file_type)
328 #else
329 						if (file_type && ent[k]->d_type != file_type)
330 #endif
331 							continue;
332 
333 						eln[found] = -1;
334 						files_len[found] = unicode
335 								       ? wc_xstrlen(ent[k]->d_name)
336 								       : strlen(ent[k]->d_name);
337 
338 						if (files_len[found] > flongest)
339 							flongest = files_len[found];
340 
341 						pfiles[found++] = ent[k]->d_name;
342 					}
343 				}
344 			}
345 		}
346 	}
347 
348 	else { /* No invert search */
349 
350 		pfiles = (char **)xnmalloc(globbed_files.gl_pathc + 1,
351 		    sizeof(char *));
352 		eln = (int *)xnmalloc(globbed_files.gl_pathc + 1, sizeof(int));
353 		files_len = (size_t *)xnmalloc(globbed_files.gl_pathc + 1, sizeof(size_t));
354 
355 		for (i = 0; globbed_files.gl_pathv[i]; i++) {
356 			if (*globbed_files.gl_pathv[i] == '.'
357 			&& (!globbed_files.gl_pathv[i][1]
358 			|| (globbed_files.gl_pathv[i][1] == '.'
359 			&& !globbed_files.gl_pathv[i][2])))
360 				continue;
361 
362 			if (file_type) {
363 				/* Simply skip all files not matching file_type */
364 				if (lstat(globbed_files.gl_pathv[i], &file_attrib) == -1)
365 					continue;
366 				if ((file_attrib.st_mode & S_IFMT) != file_type)
367 					continue;
368 			}
369 
370 			pfiles[found] = globbed_files.gl_pathv[i];
371 
372 			/* Get the longest file name in the list */
373 			/* If not searching in CWD, we only need to know the file's
374 			 * length (no ELN) */
375 			if (search_path) {
376 				/* This will be passed to colors_list(): -1 means no ELN */
377 				eln[found] = -1;
378 				files_len[found] = unicode ? wc_xstrlen(pfiles[found])
379 							   : strlen(pfiles[found]);
380 
381 				if (files_len[found] > flongest)
382 					flongest = files_len[found];
383 
384 				found++;
385 			} else {
386 				/* If searching in CWD, take into account the file's ELN
387 				 * when calculating its legnth */
388 				size_t j;
389 				for (j = 0; file_info[j].name; j++) {
390 
391 					if (*pfiles[found] != *file_info[j].name
392 					|| strcmp(pfiles[found], file_info[j].name) != 0)
393 						continue;
394 
395 					eln[found] = (int)(j + 1);
396 					files_len[found] = file_info[j].len
397 							+ (size_t)file_info[j].eln_n + 1;
398 
399 					if (files_len[found] > flongest)
400 						flongest = files_len[found];
401 				}
402 
403 				found++;
404 			}
405 		}
406 	}
407 
408 	/* Print the results using colors and columns */
409 	if (found) {
410 		int columns_n = 0,
411 			last_column = 0;
412 
413 		struct winsize w;
414 		ioctl(STDOUT_FILENO, TIOCGWINSZ, &w);
415 		unsigned short tcols = w.ws_col;
416 
417 		if (flongest == 0 || flongest > tcols)
418 			columns_n = 1;
419 		else
420 			columns_n = (int)(tcols / (flongest + 1));
421 
422 		if (columns_n > found)
423 			columns_n = found;
424 
425 		size_t t = tab_offset;
426 		tab_offset = 0;
427 		for (i = 0; i < found; i++) {
428 			if (!pfiles[i])
429 				continue;
430 
431 			if ((i + 1) % columns_n == 0)
432 				last_column = 1;
433 			else
434 				last_column = 0;
435 
436 			colors_list(pfiles[i], (eln[i] && eln[i] != -1) ? eln[i] : 0,
437 			    (last_column || i == (found - 1)) ? 0 :
438 			    (int)(flongest - files_len[i]) + 1,
439 			    (last_column || i == found - 1) ? 1 : 0);
440 			/* Second argument to colors_list() is:
441 			 * 0: Do not print any ELN
442 			 * Positive number: Print positive number as ELN
443 			 * -1: Print "?" instead of an ELN */
444 		}
445 		tab_offset = t;
446 
447 		printf(_("Matches found: %d\n"), found);
448 	}
449 
450 	/*  else
451 		printf(_("%s: No matches found\n"), PROGRAM_NAME); */
452 
453 	/* Free stuff */
454 	if (invert && search_path) {
455 		i = scandir_files;
456 		while (--i >= 0)
457 			free(ent[i]);
458 		free(ent);
459 	}
460 
461 	free(eln);
462 	free(files_len);
463 	free(pfiles);
464 	globfree(&globbed_files);
465 
466 	/* If needed, go back to the directory we came from */
467 	if (search_path) {
468 		if (xchdir(ws[cur_ws].path, NO_TITLE) == -1) {
469 			fprintf(stderr, "%s: %s: %s\n", PROGRAM_NAME,
470 			    ws[cur_ws].path, strerror(errno));
471 			return EXIT_FAILURE;
472 		}
473 	}
474 
475 	if (!found)
476 		return EXIT_FAILURE;
477 	return EXIT_SUCCESS;
478 }
479 
480 /* List matching (or not marching, if inverse is set to 1) file names
481  * in the specified directory */
482 int
search_regex(char ** comm,int invert,int case_sens)483 search_regex(char **comm, int invert, int case_sens)
484 {
485 	if (!comm || !comm[0])
486 		return EXIT_FAILURE;
487 
488 	char *search_str = (char *)NULL, *search_path = (char *)NULL;
489 	mode_t file_type = 0;
490 
491 	/* If there are two arguments, the one starting with '-' is the
492 	 * file type and the other is the path */
493 	if (comm[1] && comm[2]) {
494 
495 		if (*comm[1] == '-') {
496 			file_type = (mode_t) * (comm[1] + 1);
497 			search_path = comm[2];
498 		}
499 
500 		else if (*comm[2] == '-') {
501 			file_type = (mode_t) * (comm[2] + 1);
502 			search_path = comm[1];
503 		}
504 
505 		else
506 			search_path = comm[1];
507 	}
508 
509 	/* If just one argument, '-' indicates file type. Else, we have a
510 	 * path */
511 	else if (comm[1]) {
512 		if (*comm[1] == '-')
513 			file_type = (mode_t) * (comm[1] + 1);
514 		else
515 			search_path = comm[1];
516 	}
517 
518 	/* If no arguments, search_path will be NULL and file_type zero */
519 
520 	if (file_type) {
521 		/* If file type is specified, matches will be checked against
522 		 * this value */
523 		switch (file_type) {
524 		case 'd': file_type = DT_DIR; break;
525 		case 'r': file_type = DT_REG; break;
526 		case 'l': file_type = DT_LNK; break;
527 		case 's': file_type = DT_SOCK; break;
528 		case 'f': file_type = DT_FIFO; break;
529 		case 'b': file_type = DT_BLK; break;
530 		case 'c': file_type = DT_CHR; break;
531 		default:
532 			fprintf(stderr, _("%s: '%c': Unrecognized file type\n"),
533 			    PROGRAM_NAME, (char)file_type);
534 			return EXIT_FAILURE;
535 		}
536 	}
537 
538 	struct dirent **reg_dirlist = (struct dirent **)NULL;
539 	int tmp_files = 0;
540 
541 	/* If we have a path ("/str /path"), chdir into it, since
542 	 * regex() works on CWD */
543 	if (search_path && *search_path) {
544 		/* Deescape the search path, if necessary */
545 		if (strchr(search_path, '\\')) {
546 			char *deq_dir = dequote_str(search_path, 0);
547 
548 			if (!deq_dir) {
549 				fprintf(stderr, _("%s: %s: Error dequoting file name\n"),
550 				    PROGRAM_NAME, comm[1]);
551 				return EXIT_FAILURE;
552 			}
553 
554 			strcpy(search_path, deq_dir);
555 			free(deq_dir);
556 		}
557 
558 		size_t path_len = strlen(search_path);
559 		if (search_path[path_len - 1] == '/')
560 			search_path[path_len - 1] = '\0';
561 
562 		if ((*search_path == '.' && !search_path[1])
563 		|| (search_path[1] == ws[cur_ws].path[1]
564 		&& strcmp(search_path, ws[cur_ws].path) == 0))
565 			search_path = (char *)NULL;
566 
567 		if (search_path && *search_path) {
568 			if (xchdir(search_path, NO_TITLE) == -1) {
569 				fprintf(stderr, "%s: %s: %s\n", PROGRAM_NAME,
570 				    search_path, strerror(errno));
571 				return EXIT_FAILURE;
572 			}
573 
574 			tmp_files = scandir(".", &reg_dirlist, skip_files, xalphasort);
575 			/*      tmp_files = scandir(".", &reg_dirlist, skip_files,
576 							sort == 0 ? NULL : sort == 1 ? m_alphasort
577 							: sort == 2 ? size_sort : sort == 3
578 							? atime_sort : sort == 4 ? btime_sort
579 							: sort == 5 ? ctime_sort : sort == 6
580 							? mtime_sort : sort == 7 ? m_versionsort
581 							: sort == 8 ? ext_sort : inode_sort); */
582 
583 			if (tmp_files == -1) {
584 				fprintf(stderr, "scandir: %s: %s\n", search_path,
585 				    strerror(errno));
586 
587 				if (xchdir(ws[cur_ws].path, NO_TITLE) == -1)
588 					fprintf(stderr, "%s: %s: %s\n", PROGRAM_NAME,
589 					    ws[cur_ws].path, strerror(errno));
590 
591 				return EXIT_FAILURE;
592 			}
593 		}
594 	}
595 
596 	size_t i;
597 
598 	/* Search for regex expression */
599 	int regex_found = check_regex(comm[0] + 1);
600 
601 	/* If search string is just "STR" (no regex chars), change it
602 	 * to ".*STR.*" */
603 	if (regex_found == EXIT_FAILURE) {
604 		size_t search_str_len = strlen(comm[0]);
605 
606 		comm[0] = (char *)xrealloc(comm[0], (search_str_len + 5) *
607 							sizeof(char));
608 
609 		char *tmp_str = (char *)xnmalloc(search_str_len + 1, sizeof(char));
610 
611 		strcpy(tmp_str, comm[0] + (invert ? 2 : 1));
612 
613 		*comm[0] = '.';
614 		*(comm[0] + 1) = '*';
615 		*(comm[0] + 2) = '\0';
616 		strcat(comm[0], tmp_str);
617 		free(tmp_str);
618 		*(comm[0] + search_str_len + 1) = '.';
619 		*(comm[0] + search_str_len + 2) = '*';
620 		*(comm[0] + search_str_len + 3) = '\0';
621 		search_str = comm[0];
622 	}
623 
624 	else
625 		search_str = comm[0] + (invert ? 2 : 1);
626 
627 	/* Get matches, if any, using regular expressions */
628 	regex_t regex_files;
629 	int reg_flags = case_sens ? (REG_NOSUB | REG_EXTENDED)
630 				: (REG_NOSUB | REG_EXTENDED | REG_ICASE);
631 	int ret = regcomp(&regex_files, search_str, reg_flags);
632 
633 	if (ret != EXIT_SUCCESS) {
634 		fprintf(stderr, _("'%s': Invalid regular expression\n"), search_str);
635 
636 		regfree(&regex_files);
637 
638 		if (search_path) {
639 			for (i = 0; i < (size_t)tmp_files; i++)
640 				free(reg_dirlist[i]);
641 
642 			free(reg_dirlist);
643 
644 			if (xchdir(ws[cur_ws].path, NO_TITLE) == -1)
645 				fprintf(stderr, "%s: %s: %s\n", PROGRAM_NAME,
646 				    ws[cur_ws].path, strerror(errno));
647 		}
648 
649 		return EXIT_FAILURE;
650 	}
651 
652 	size_t found = 0;
653 	int *regex_index = (int *)xnmalloc((search_path ? (size_t)tmp_files
654 						: files) + 1, sizeof(int));
655 
656 	for (i = 0; i < (search_path ? (size_t)tmp_files : files); i++) {
657 		if (regexec(&regex_files, (search_path ? reg_dirlist[i]->d_name
658 		: file_info[i].name), 0, NULL, 0) == EXIT_SUCCESS) {
659 			if (!invert)
660 				regex_index[found++] = (int)i;
661 		} else if (invert)
662 			regex_index[found++] = (int)i;
663 	}
664 
665 	regfree(&regex_files);
666 
667 	if (!found) {
668 		fprintf(stderr, _("No matches found\n"));
669 		free(regex_index);
670 
671 		if (search_path) {
672 			int j = tmp_files;
673 			while (--j >= 0)
674 				free(reg_dirlist[j]);
675 			free(reg_dirlist);
676 
677 			if (xchdir(ws[cur_ws].path, NO_TITLE) == -1)
678 				fprintf(stderr, "%s: %s: %s\n", PROGRAM_NAME,
679 				    ws[cur_ws].path, strerror(errno));
680 		}
681 
682 		return EXIT_FAILURE;
683 	}
684 
685 	/* We have matches */
686 	size_t flongest = 0,
687 		   type_ok = 0;
688 
689 	size_t *files_len = (size_t *)xnmalloc(found + 1, sizeof(size_t));
690 	int *match_type = (int *)xnmalloc(found + 1, sizeof(int));
691 
692 	/* Get the longest file name in the list */
693 	int j = (int)found;
694 	while (--j >= 0) {
695 		/* Simply skip all files not matching file_type */
696 		if (file_type) {
697 			match_type[j] = 0;
698 
699 			if (search_path) {
700 #if !defined(_DIRENT_HAVE_D_TYPE)
701 				mode_t type;
702 				struct stat attr;
703 				if (lstat(reg_dirlist[regex_index[j]]->d_name, &attr) == -1)
704 					continue;
705 				switch (attr.st_mode & S_IFMT) {
706 				case S_IFBLK: type = DT_BLK; break;
707 				case S_IFCHR: type = DT_CHR; break;
708 				case S_IFDIR: type = DT_DIR; break;
709 				case S_IFIFO: type = DT_FIFO; break;
710 				case S_IFLNK: type = DT_LNK; break;
711 				case S_IFREG: type = DT_REG; break;
712 				case S_IFSOCK: type = DT_SOCK; break;
713 				default: type = DT_UNKNOWN; break;
714 				}
715 				if (type != file_type)
716 #else
717 				if (reg_dirlist[regex_index[j]]->d_type != file_type)
718 #endif
719 					continue;
720 			} else if (file_info[regex_index[j]].type != file_type) {
721 				continue;
722 			}
723 		}
724 
725 		/* Amount of non-filtered files */
726 		type_ok++;
727 		/* Index of each non-filtered files */
728 		match_type[j] = 1;
729 
730 		/* If not searching in CWD, we only need to know the file's
731 		 * length (no ELN) */
732 		if (search_path) {
733 			files_len[j] = unicode ? wc_xstrlen(
734 					reg_dirlist[regex_index[j]]->d_name)
735 					: strlen(reg_dirlist[regex_index[j]]->d_name);
736 
737 			if (files_len[j] > flongest)
738 				flongest = files_len[j];
739 		}
740 
741 		/* If searching in CWD, take into account the file's ELN
742 		 * when calculating its legnth */
743 		else {
744 			files_len[j] = file_info[regex_index[j]].len
745 							+ (size_t)DIGINUM(regex_index[j] + 1) + 1;
746 
747 			if (files_len[j] > flongest)
748 				flongest = files_len[j];
749 		}
750 	}
751 
752 	if (type_ok) {
753 		int last_column = 0;
754 		size_t total_cols = 0;
755 
756 		struct winsize w;
757 		ioctl(STDOUT_FILENO, TIOCGWINSZ, &w);
758 		unsigned short terminal_cols = w.ws_col;
759 
760 		if (flongest == 0 || flongest > terminal_cols)
761 			total_cols = 1;
762 		else
763 			total_cols = (size_t)terminal_cols / (flongest + 1);
764 
765 		if (total_cols > type_ok)
766 			total_cols = type_ok;
767 
768 		/* cur_col: Current columns number */
769 		size_t cur_col = 0,
770 			   counter = 0;
771 
772 		size_t t = tab_offset;
773 		tab_offset = 0;
774 		for (i = 0; i < found; i++) {
775 			if (match_type[i] == 0)
776 				continue;
777 
778 			/* Print the results using colors and columns */
779 			cur_col++;
780 
781 			/* If the current file is in the last column or is the last
782 			 * listed file, we need to print no pad and a newline char.
783 			 * Else, print the corresponding pad, to equate the longest
784 			 * file length, and no newline char */
785 			if (cur_col == total_cols) {
786 				last_column = 1;
787 				cur_col = 0;
788 			} else {
789 				last_column = 0;
790 			}
791 
792 			/* Counter: Current amount of non-filtered files: if
793 			 * COUNTER equals TYPE_OK (total amount of non-filtered
794 			 * files), we have the last file to be printed */
795 			counter++;
796 
797 			colors_list(search_path ? reg_dirlist[regex_index[i]]->d_name
798 					: file_info[regex_index[i]].name, search_path ? NO_ELN
799 					: regex_index[i] + 1, (last_column || counter == type_ok)
800 					? NO_PAD : (int)(flongest - files_len[i]) + 1,
801 					(last_column || counter == type_ok) ? PRINT_NEWLINE
802 					: NO_NEWLINE);
803 		}
804 		tab_offset = t;
805 
806 		printf(_("Matches found: %zu\n"), counter);
807 	}
808 
809 	else
810 		fputs(_("No matches found\n"), stderr);
811 
812 	/* Free stuff */
813 	free(files_len);
814 	free(match_type);
815 	free(regex_index);
816 //	regfree(&regex_files);
817 
818 	/* If needed, go back to the directory we came from */
819 	if (search_path) {
820 		j = tmp_files;
821 		while (--j >= 0)
822 			free(reg_dirlist[j]);
823 		free(reg_dirlist);
824 
825 		if (xchdir(ws[cur_ws].path, NO_TITLE) == -1) {
826 			fprintf(stderr, "%s: %s: %s\n", PROGRAM_NAME,
827 			    ws[cur_ws].path, strerror(errno));
828 			return EXIT_FAILURE;
829 		}
830 	}
831 
832 	if (type_ok)
833 		return EXIT_SUCCESS;
834 
835 	return EXIT_FAILURE;
836 }
837