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(".", ®_dirlist, skip_files, xalphasort);
575 /* tmp_files = scandir(".", ®_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(®ex_files, search_str, reg_flags);
632
633 if (ret != EXIT_SUCCESS) {
634 fprintf(stderr, _("'%s': Invalid regular expression\n"), search_str);
635
636 regfree(®ex_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(®ex_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(®ex_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(®ex_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