1 /* sort.c -- functions used to sort files */
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 <fcntl.h>
29 #include <stdio.h>
30 #include <string.h>
31 #include <unistd.h>
32 #ifdef __OpenBSD__
33 #include <strings.h>
34 #endif
35 //#include <sys/stat.h>
36
37 #include "checks.h"
38 //#include "aux.h" /* For xatoi */
39 #include "listing.h"
40 //#include "messages.h"
41
42 int
skip_nonexec(const struct dirent * ent)43 skip_nonexec(const struct dirent *ent)
44 {
45 if (access(ent->d_name, X_OK) == -1)
46 return 0;
47 return 1;
48
49 /* int f = 0; // Hold file ownership flags
50
51 struct stat a;
52 if (stat(ent->d_name, &a) == -1)
53 return 0;
54
55 mode_t val = (a.st_mode & (mode_t)~S_IFMT);
56 if (val & S_IXUSR) f |= X_USR;
57 if (val & S_IXGRP) f |= X_GRP;
58 if (val & S_IXOTH) f |= X_OTH;
59
60 if ((f & X_USR) && a.st_uid == user.uid)
61 return 1;
62 if ((f & X_GRP) && a.st_gid == user.gid)
63 return 1;
64 if (f & X_OTH)
65 return 1;
66
67 return 0; */
68 }
69
70 int
skip_files(const struct dirent * ent)71 skip_files(const struct dirent *ent)
72 {
73 /* In case a directory isn't reacheable, like a failed
74 * mountpoint... */
75 /* struct stat file_attrib;
76
77 if (lstat(entry->d_name, &file_attrib) == -1) {
78 fprintf(stderr, _("stat: cannot access '%s': %s\n"),
79 entry->d_name, strerror(errno));
80 return 0;
81 } */
82
83 /* Skip "." and ".." */
84 if (*ent->d_name == '.' && (!ent->d_name[1] || (ent->d_name[1] == '.' && !ent->d_name[2])))
85 return 0;
86
87 /* Skip files matching FILTER */
88 if (_filter && regexec(®ex_exp, ent->d_name, 0, NULL, 0) == EXIT_SUCCESS)
89 return 0;
90
91 /* If not hidden files */
92 if (!show_hidden && *ent->d_name == '.')
93 return 0;
94
95 return 1;
96 }
97
98 int
namecmp(const char * s1,const char * s2)99 namecmp(const char *s1, const char *s2)
100 {
101 /* Do not take initial dot into account */
102 if (*s1 == '.')
103 s1++;
104
105 if (*s2 == '.')
106 s2++;
107
108 /* If both string starts with number, sort them as numbers, not as strings */
109 if (_ISDIGIT(*s1) && _ISDIGIT(*s2)) {
110 char *p1, *p2;
111 long long n1 = strtoll(s1, &p1, 10);
112 long long n2 = strtoll(s2, &p2, 10);
113 if (n2 > n1)
114 return -1;
115 if (n2 < n1)
116 return 1;
117 }
118
119 char ac = *s1, bc = *s2;
120
121 if ((*s1 & 0xc0) != 0xc0 && (*s2 & 0xc0) != 0xc0) {
122 /* None of the strings starts with a unicode char: compare the first
123 * byte of both strings */
124 if (!case_sensitive) {
125 ac = (char)TOUPPER(*s1);
126 bc = (char)TOUPPER(*s2);
127 }
128
129 if (bc > ac)
130 return -1;
131
132 if (bc < ac)
133 return 1;
134 }
135
136 if (!case_sensitive || (*s1 & 0xc0) == 0xc0 || (*s2 & 0xc0) == 0xc0)
137 return strcoll(s1, s2);
138
139 return strcmp(s1, s2);
140 }
141
142 int
entrycmp(const void * a,const void * b)143 entrycmp(const void *a, const void *b)
144 {
145 const struct fileinfo *pa = (struct fileinfo *)a;
146 const struct fileinfo *pb = (struct fileinfo *)b;
147
148 if (list_folders_first) {
149 if (pb->dir != pa->dir) {
150 if (pb->dir)
151 return 1;
152
153 return -1;
154 }
155 }
156
157 int ret = 0, st = sort;
158
159 //#ifndef _GNU_SOURCE
160 // if (st == SVER)
161 // st = SNAME;
162 //#endif
163
164 if (light_mode && (st == SOWN || st == SGRP))
165 st = SNAME;
166
167 switch (st) {
168
169 case SSIZE:
170 if (pa->size > pb->size)
171 ret = 1;
172 else if (pa->size < pb->size)
173 ret = -1;
174 break;
175
176 case SATIME: /* fallthrough */
177 case SBTIME: /* fallthrough */
178 case SCTIME: /* fallthrough */
179 case SMTIME:
180 if (pa->time > pb->time)
181 ret = 1;
182 else if (pa->time < pb->time)
183 ret = -1;
184 break;
185
186 //#ifdef _GNU_SOURCE
187 case SVER:
188 ret = xstrverscmp(pa->name, pb->name);
189 break;
190 //#endif
191
192 case SEXT: {
193 char *aext = (char *)NULL, *bext = (char *)NULL, *val;
194 val = strrchr(pa->name, '.');
195 if (val && val != pa->name)
196 aext = val + 1;
197
198 val = strrchr(pb->name, '.');
199 if (val && val != pb->name)
200 bext = val + 1;
201
202 if (aext || bext) {
203 if (!aext)
204 ret = -1;
205 else if (!bext)
206 ret = 1;
207
208 else
209 ret = strcasecmp(aext, bext);
210 }
211 } break;
212
213 case SINO:
214 if (pa->inode > pb->inode)
215 ret = 1;
216 else if (pa->inode < pb->inode)
217 ret = -1;
218 break;
219
220 case SOWN:
221 if (pa->uid > pb->uid)
222 ret = 1;
223 else if (pa->uid < pb->uid)
224 ret = -1;
225 break;
226
227 case SGRP:
228 if (pa->gid > pb->gid)
229 ret = 1;
230 else if (pa->gid < pb->gid)
231 ret = -1;
232 break;
233 }
234
235 if (!ret)
236 ret = namecmp(pa->name, pb->name);
237
238 if (!sort_reverse)
239 return ret;
240
241 return (ret - (ret * 2));
242 }
243
244 /* Same as alphasort, but is uses strcmp instead of sctroll, which is
245 * slower. However, bear in mind that, unlike strcmp(), strcoll() is locale
246 * aware. Use only with C and english locales */
247 int
xalphasort(const struct dirent ** a,const struct dirent ** b)248 xalphasort(const struct dirent **a, const struct dirent **b)
249 {
250 int ret = 0;
251
252 /* The if statements prevent strcmp from running in every
253 * call to the function (it will be called only if the first
254 * character of the two strings is the same), which makes the
255 * function faster */
256 if ((*a)->d_name[0] > (*b)->d_name[0])
257 ret = 1;
258 else if ((*a)->d_name[0] < (*b)->d_name[0])
259 ret = -1;
260 else
261 ret = strcmp((*a)->d_name, (*b)->d_name);
262
263 if (!sort_reverse)
264 return ret;
265
266 /* If sort_reverse, return the opposite value */
267 return (ret - (ret * 2));
268 }
269
270 void
print_sort_method(void)271 print_sort_method(void)
272 {
273 switch (sort) {
274 case SNONE: puts(_("none")); break;
275
276 case SNAME:
277 printf(_("name %s\n"), (sort_reverse) ? "[rev]" : "");
278 break;
279
280 case SSIZE:
281 printf(_("size %s\n"), (sort_reverse) ? "[rev]" : "");
282 break;
283
284 case SATIME:
285 printf(_("atime %s\n"), (sort_reverse) ? "[rev]" : "");
286 break;
287
288 case SBTIME:
289 #if defined(HAVE_ST_BIRTHTIME) || defined(__BSD_VISIBLE) || defined(_STATX)
290 printf(_("btime %s\n"), (sort_reverse) ? "[rev]" : "");
291 #else
292 printf(_("btime (not available: using 'ctime') %s\n"),
293 (sort_reverse) ? "[rev]" : "");
294 #endif
295 break;
296
297 case SCTIME:
298 printf(_("ctime %s\n"), (sort_reverse) ? "[rev]" : "");
299 break;
300
301 case SMTIME:
302 printf(_("mtime %s\n"), (sort_reverse) ? "[rev]" : "");
303 break;
304
305 case SVER:
306 printf(_("version %s\n"), (sort_reverse) ? "[rev]" : "");
307 break;
308
309 case SEXT:
310 printf(_("extension %s\n"), (sort_reverse) ? "[rev]" : "");
311 break;
312
313 case SINO:
314 printf(_("inode %s\n"), (sort_reverse) ? "[rev]" : "");
315 break;
316
317 case SOWN:
318 if (light_mode) {
319 printf(_("owner (not available: using 'name') %s\n"),
320 (sort_reverse) ? "[rev]" : "");
321 } else {
322 printf(_("owner %s\n"), (sort_reverse) ? "[rev]" : "");
323 }
324 break;
325
326 case SGRP:
327 if (light_mode) {
328 printf(_("group (not available: using 'name') %s\n"),
329 (sort_reverse) ? "[rev]" : "");
330 } else {
331 printf(_("group %s\n"), (sort_reverse) ? "[rev]" : "");
332 }
333 break;
334
335 default: fputs("unknown sorting method\n", stdout);
336 }
337 }
338
339 int
sort_function(char ** arg)340 sort_function(char **arg)
341 {
342 int exit_status = EXIT_FAILURE;
343
344 /* No argument: Just print current sorting method */
345 if (!arg[1]) {
346 fputs(_("Sorting method: "), stdout);
347 print_sort_method();
348 return EXIT_SUCCESS;
349 }
350
351 /* Argument is alphanumerical string */
352 if (!is_number(arg[1])) {
353 struct sort_t {
354 const char *name;
355 int num;
356 int pad; /* Used only to properly align the struct */
357 };
358
359 static struct sort_t sorts[] = {
360 {"none", 0, 0},
361 {"name", 1, 0},
362 {"size", 2, 0},
363 {"atime", 3, 0},
364 {"btime", 4, 0},
365 {"ctime", 5, 0},
366 {"mtime", 6, 0},
367 {"version", 7, 0},
368 {"extension", 8, 0},
369 {"inode", 9, 0},
370 {"owner", 10, 0},
371 {"group", 11, 0},
372 };
373
374 size_t i;
375 for (i = 0; i < sizeof(sorts) / sizeof(struct sort_t); i++) {
376 if (strcmp(arg[1], sorts[i].name) == 0) {
377 sprintf(arg[1], "%d", sorts[i].num);
378 break;
379 }
380 }
381
382 if (strcmp(arg[1], "rev") == 0) {
383 if (sort_reverse)
384 sort_reverse = 0;
385 else
386 sort_reverse = 1;
387
388 if (autols) {
389 /* sort_switch just tells list_dir() to print a line
390 * with the current sorting method at the end of the
391 * files list */
392 sort_switch = 1;
393 free_dirlist();
394 exit_status = list_dir();
395 sort_switch = 0;
396 }
397
398 return exit_status;
399 }
400
401 /* If arg1 is not a number and is not "rev", the fputs()
402 * above is executed */
403 }
404
405 /* Argument is a number */
406 int int_arg = atoi(arg[1]);
407
408 if (int_arg >= 0 && int_arg <= SORT_TYPES) {
409 sort = int_arg;
410
411 if (arg[2] && strcmp(arg[2], "rev") == 0) {
412 if (sort_reverse)
413 sort_reverse = 0;
414 else
415 sort_reverse = 1;
416 }
417
418 if (autols) {
419 sort_switch = 1;
420 free_dirlist();
421 exit_status = list_dir();
422 sort_switch = 0;
423 }
424
425 return exit_status;
426 }
427
428 /* If arg1 is a number but is not in the range 0-SORT_TYPES,
429 * error */
430 fprintf(stderr, "%s\n", _(SORT_USAGE));
431 return EXIT_FAILURE;
432 }
433
434 /* This is a modification of the alphasort function that makes it case
435 * insensitive. It also sorts without taking the initial dot of hidden
436 * files into account. Note that strcasecmp() isn't locale aware. Use
437 * only with C and english locales */
438 int
alphasort_insensitive(const struct dirent ** a,const struct dirent ** b)439 alphasort_insensitive(const struct dirent **a, const struct dirent **b)
440 {
441 int ret = strcasecmp(((*a)->d_name[0] == '.') ? (*a)->d_name + 1
442 : (*a)->d_name, ((*b)->d_name[0] == '.') ? (*b)->d_name + 1 : (*b)->d_name);
443
444 if (!sort_reverse)
445 return ret;
446
447 return (ret - (ret * 2));
448 }
449