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(&regex_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