Ticket #4536: skins: add root variant of julia256 skin.
[midnight-commander.git] / src / filemanager / dir.c
blobb405131b1442bedf307275e3f337e8cbdbf9bd60
1 /*
2 Directory routines
4 Copyright (C) 1994-2024
5 Free Software Foundation, Inc.
7 Written by:
8 Slava Zanko <slavazanko@gmail.com>, 2013
9 Andrew Borodin <aborodin@vmail.ru>, 2013-2022
11 This file is part of the Midnight Commander.
13 The Midnight Commander is free software: you can redistribute it
14 and/or modify it under the terms of the GNU General Public License as
15 published by the Free Software Foundation, either version 3 of the License,
16 or (at your option) any later version.
18 The Midnight Commander is distributed in the hope that it will be useful,
19 but WITHOUT ANY WARRANTY; without even the implied warranty of
20 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 GNU General Public License for more details.
23 You should have received a copy of the GNU General Public License
24 along with this program. If not, see <http://www.gnu.org/licenses/>.
27 /** \file src/filemanager/dir.c
28 * \brief Source: directory routines
31 #include <config.h>
33 #include <stdio.h>
34 #include <stdlib.h>
35 #include <string.h>
36 #include <sys/stat.h>
38 #include "lib/global.h"
39 #include "lib/tty/tty.h"
40 #include "lib/search.h"
41 #include "lib/vfs/vfs.h"
42 #include "lib/fs.h"
43 #include "lib/strutil.h"
44 #include "lib/util.h"
46 #include "src/setup.h" /* panels_options */
48 #include "treestore.h"
49 #include "file.h" /* file_is_symlink_to_dir() */
50 #include "dir.h"
52 /*** global variables ****************************************************************************/
54 /*** file scope macro definitions ****************************************************************/
56 #define MY_ISDIR(x) (\
57 (is_exe (x->st.st_mode) && !(S_ISDIR (x->st.st_mode) || link_isdir (x)) && exec_first) \
58 ? 1 \
59 : ( (S_ISDIR (x->st.st_mode) || link_isdir (x)) ? 2 : 0) )
61 /*** file scope type declarations ****************************************************************/
63 /*** forward declarations (file scope functions) *************************************************/
65 /*** file scope variables ************************************************************************/
67 /* Reverse flag */
68 static int reverse = 1;
70 /* Are the files sorted case sensitively? */
71 static gboolean case_sensitive = OS_SORT_CASE_SENSITIVE_DEFAULT;
73 /* Are the exec_bit files top in list */
74 static gboolean exec_first = TRUE;
76 static dir_list dir_copy = { NULL, 0, 0, NULL };
78 /* --------------------------------------------------------------------------------------------- */
79 /*** file scope functions ************************************************************************/
80 /* --------------------------------------------------------------------------------------------- */
82 static inline int
83 key_collate (const char *t1, const char *t2)
85 int dotdot = 0;
86 int ret;
88 dotdot = (t1[0] == '.' ? 1 : 0) | ((t2[0] == '.' ? 1 : 0) << 1);
90 switch (dotdot)
92 case 0:
93 case 3:
94 ret = str_key_collate (t1, t2, case_sensitive) * reverse;
95 break;
96 case 1:
97 ret = -1; /* t1 < t2 */
98 break;
99 case 2:
100 ret = 1; /* t1 > t2 */
101 break;
102 default:
103 ret = 0; /* it must not happen */
106 return ret;
109 /* --------------------------------------------------------------------------------------------- */
111 static inline int
112 compare_by_names (file_entry_t * a, file_entry_t * b)
114 /* create key if does not exist, key will be freed after sorting */
115 if (a->name_sort_key == NULL)
116 a->name_sort_key = str_create_key_for_filename (a->fname->str, case_sensitive);
117 if (b->name_sort_key == NULL)
118 b->name_sort_key = str_create_key_for_filename (b->fname->str, case_sensitive);
120 return key_collate (a->name_sort_key, b->name_sort_key);
123 /* --------------------------------------------------------------------------------------------- */
125 * clear keys, should be call after sorting is finished.
128 static void
129 clean_sort_keys (dir_list * list, int start, int count)
131 int i;
133 for (i = 0; i < count; i++)
135 file_entry_t *fentry;
137 fentry = &list->list[i + start];
138 str_release_key (fentry->name_sort_key, case_sensitive);
139 fentry->name_sort_key = NULL;
140 str_release_key (fentry->extension_sort_key, case_sensitive);
141 fentry->extension_sort_key = NULL;
145 /* --------------------------------------------------------------------------------------------- */
147 * If you change handle_dirent then check also handle_path.
148 * @return FALSE = don't add, TRUE = add to the list
151 static gboolean
152 handle_dirent (struct vfs_dirent *dp, const file_filter_t * filter, struct stat *buf1,
153 gboolean * link_to_dir, gboolean * stale_link)
155 vfs_path_t *vpath;
156 gboolean ok = TRUE;
158 if (DIR_IS_DOT (dp->d_name) || DIR_IS_DOTDOT (dp->d_name))
159 return FALSE;
160 if (!panels_options.show_dot_files && (dp->d_name[0] == '.'))
161 return FALSE;
162 if (!panels_options.show_backups && dp->d_name[strlen (dp->d_name) - 1] == '~')
163 return FALSE;
165 vpath = vfs_path_from_str (dp->d_name);
166 if (mc_lstat (vpath, buf1) == -1)
169 * lstat() fails - such entries should be identified by
170 * buf1->st_mode being 0.
171 * It happens on QNX Neutrino for /fs/cd0 if no CD is inserted.
173 memset (buf1, 0, sizeof (*buf1));
176 if (S_ISDIR (buf1->st_mode))
177 tree_store_mark_checked (dp->d_name);
179 /* A link to a file or a directory? */
180 *link_to_dir = file_is_symlink_to_dir (vpath, buf1, stale_link);
182 vfs_path_free (vpath, TRUE);
184 if (filter != NULL && filter->handler != NULL)
186 gboolean files_only = (filter->flags & SELECT_FILES_ONLY) != 0;
188 ok = ((S_ISDIR (buf1->st_mode) || *link_to_dir) && files_only)
189 || mc_search_run (filter->handler, dp->d_name, 0, strlen (dp->d_name), NULL);
192 return ok;
195 /* --------------------------------------------------------------------------------------------- */
196 /** get info about ".." */
198 static gboolean
199 dir_get_dotdot_stat (const vfs_path_t * vpath, struct stat *st)
201 gboolean ret = FALSE;
203 if ((vpath != NULL) && (st != NULL))
205 const char *path;
207 path = vfs_path_get_by_index (vpath, 0)->path;
208 if (path != NULL && *path != '\0')
210 vfs_path_t *tmp_vpath;
212 tmp_vpath = vfs_path_append_new (vpath, "..", (char *) NULL);
213 ret = mc_stat (tmp_vpath, st) == 0;
214 vfs_path_free (tmp_vpath, TRUE);
218 return ret;
221 /* --------------------------------------------------------------------------------------------- */
223 static void
224 alloc_dir_copy (int size)
226 if (dir_copy.size < size)
228 if (dir_copy.list != NULL)
229 dir_list_free_list (&dir_copy);
231 dir_copy.list = g_new0 (file_entry_t, size);
232 dir_copy.size = size;
233 dir_copy.len = 0;
237 /* --------------------------------------------------------------------------------------------- */
238 /*** public functions ****************************************************************************/
239 /* --------------------------------------------------------------------------------------------- */
241 * Increase or decrease directory list size.
243 * @param list directory list
244 * @param delta value by increase (if positive) or decrease (if negative) list size
246 * @return FALSE on failure, TRUE on success
249 gboolean
250 dir_list_grow (dir_list * list, int delta)
252 int size;
253 gboolean clear_flag = FALSE;
255 if (list == NULL)
256 return FALSE;
258 if (delta == 0)
259 return TRUE;
261 size = list->size + delta;
262 if (size <= 0)
264 size = DIR_LIST_MIN_SIZE;
265 clear_flag = TRUE;
268 if (size != list->size)
270 file_entry_t *fe;
272 fe = g_try_renew (file_entry_t, list->list, size);
273 if (fe == NULL)
274 return FALSE;
276 list->list = fe;
277 list->size = size;
280 list->len = clear_flag ? 0 : MIN (list->len, size);
282 return TRUE;
285 /* --------------------------------------------------------------------------------------------- */
287 * Append file info to the directory list.
289 * @param list directory list
290 * @param fname file name
291 * @param st file stat info
292 * @param link_to_dir is file link to directory
293 * @param stale_link is file stale elink
295 * @return FALSE on failure, TRUE on success
298 gboolean
299 dir_list_append (dir_list * list, const char *fname, const struct stat * st,
300 gboolean link_to_dir, gboolean stale_link)
302 file_entry_t *fentry;
304 /* Need to grow the *list? */
305 if (list->len == list->size && !dir_list_grow (list, DIR_LIST_RESIZE_STEP))
306 return FALSE;
308 fentry = &list->list[list->len];
309 fentry->fname = g_string_new (fname);
310 fentry->f.marked = 0;
311 fentry->f.link_to_dir = link_to_dir ? 1 : 0;
312 fentry->f.stale_link = stale_link ? 1 : 0;
313 fentry->f.dir_size_computed = 0;
314 fentry->st = *st;
315 fentry->name_sort_key = NULL;
316 fentry->extension_sort_key = NULL;
318 list->len++;
320 return TRUE;
323 /* --------------------------------------------------------------------------------------------- */
326 unsorted (file_entry_t * a, file_entry_t * b)
328 (void) a;
329 (void) b;
331 return 0;
334 /* --------------------------------------------------------------------------------------------- */
337 sort_name (file_entry_t * a, file_entry_t * b)
339 int ad = MY_ISDIR (a);
340 int bd = MY_ISDIR (b);
342 if (ad == bd || panels_options.mix_all_files)
343 return compare_by_names (a, b);
345 return bd - ad;
348 /* --------------------------------------------------------------------------------------------- */
351 sort_vers (file_entry_t * a, file_entry_t * b)
353 int ad = MY_ISDIR (a);
354 int bd = MY_ISDIR (b);
356 if (ad == bd || panels_options.mix_all_files)
358 int result;
360 result = filevercmp (a->fname->str, b->fname->str);
361 if (result != 0)
362 return result * reverse;
364 return compare_by_names (a, b);
367 return bd - ad;
370 /* --------------------------------------------------------------------------------------------- */
373 sort_ext (file_entry_t * a, file_entry_t * b)
375 int ad = MY_ISDIR (a);
376 int bd = MY_ISDIR (b);
378 if (ad == bd || panels_options.mix_all_files)
380 int r;
382 if (a->extension_sort_key == NULL)
383 a->extension_sort_key = str_create_key (extension (a->fname->str), case_sensitive);
384 if (b->extension_sort_key == NULL)
385 b->extension_sort_key = str_create_key (extension (b->fname->str), case_sensitive);
387 r = str_key_collate (a->extension_sort_key, b->extension_sort_key, case_sensitive);
388 if (r != 0)
389 return r * reverse;
391 return compare_by_names (a, b);
394 return bd - ad;
397 /* --------------------------------------------------------------------------------------------- */
400 sort_time (file_entry_t * a, file_entry_t * b)
402 int ad = MY_ISDIR (a);
403 int bd = MY_ISDIR (b);
405 if (ad == bd || panels_options.mix_all_files)
407 int result = _GL_CMP (a->st.st_mtime, b->st.st_mtime);
409 if (result != 0)
410 return result * reverse;
412 return compare_by_names (a, b);
415 return bd - ad;
418 /* --------------------------------------------------------------------------------------------- */
421 sort_ctime (file_entry_t * a, file_entry_t * b)
423 int ad = MY_ISDIR (a);
424 int bd = MY_ISDIR (b);
426 if (ad == bd || panels_options.mix_all_files)
428 int result = _GL_CMP (a->st.st_ctime, b->st.st_ctime);
430 if (result != 0)
431 return result * reverse;
433 return compare_by_names (a, b);
436 return bd - ad;
439 /* --------------------------------------------------------------------------------------------- */
442 sort_atime (file_entry_t * a, file_entry_t * b)
444 int ad = MY_ISDIR (a);
445 int bd = MY_ISDIR (b);
447 if (ad == bd || panels_options.mix_all_files)
449 int result = _GL_CMP (a->st.st_atime, b->st.st_atime);
451 if (result != 0)
452 return result * reverse;
454 return compare_by_names (a, b);
457 return bd - ad;
460 /* --------------------------------------------------------------------------------------------- */
463 sort_inode (file_entry_t * a, file_entry_t * b)
465 int ad = MY_ISDIR (a);
466 int bd = MY_ISDIR (b);
468 if (ad == bd || panels_options.mix_all_files)
469 return (a->st.st_ino - b->st.st_ino) * reverse;
471 return bd - ad;
474 /* --------------------------------------------------------------------------------------------- */
477 sort_size (file_entry_t * a, file_entry_t * b)
479 int ad = MY_ISDIR (a);
480 int bd = MY_ISDIR (b);
482 if (ad == bd || panels_options.mix_all_files)
484 int result = _GL_CMP (a->st.st_size, b->st.st_size);
486 if (result != 0)
487 return result * reverse;
489 return compare_by_names (a, b);
492 return bd - ad;
495 /* --------------------------------------------------------------------------------------------- */
497 void
498 dir_list_sort (dir_list * list, GCompareFunc sort, const dir_sort_options_t * sort_op)
500 if (list->len > 1 && sort != (GCompareFunc) unsorted)
502 file_entry_t *fentry = &list->list[0];
503 int dot_dot_found;
505 /* If there is an ".." entry the caller must take care to
506 ensure that it occupies the first list element. */
507 dot_dot_found = DIR_IS_DOTDOT (fentry->fname->str) ? 1 : 0;
508 reverse = sort_op->reverse ? -1 : 1;
509 case_sensitive = sort_op->case_sensitive ? 1 : 0;
510 exec_first = sort_op->exec_first;
511 qsort (&(list->list)[dot_dot_found], list->len - dot_dot_found, sizeof (file_entry_t),
512 sort);
514 clean_sort_keys (list, dot_dot_found, list->len - dot_dot_found);
518 /* --------------------------------------------------------------------------------------------- */
520 void
521 dir_list_clean (dir_list * list)
523 int i;
525 for (i = 0; i < list->len; i++)
527 file_entry_t *fentry;
529 fentry = &list->list[i];
530 g_string_free (fentry->fname, TRUE);
531 fentry->fname = NULL;
534 list->len = 0;
535 /* reduce memory usage */
536 dir_list_grow (list, DIR_LIST_MIN_SIZE - list->size);
539 /* --------------------------------------------------------------------------------------------- */
541 void
542 dir_list_free_list (dir_list * list)
544 int i;
546 for (i = 0; i < list->len; i++)
548 file_entry_t *fentry;
550 fentry = &list->list[i];
551 g_string_free (fentry->fname, TRUE);
554 MC_PTR_FREE (list->list);
555 list->len = 0;
556 list->size = 0;
559 /* --------------------------------------------------------------------------------------------- */
560 /** Used to set up a directory list when there is no access to a directory */
562 gboolean
563 dir_list_init (dir_list * list)
565 file_entry_t *fentry;
567 /* Need to grow the *list? */
568 if (list->size == 0 && !dir_list_grow (list, DIR_LIST_RESIZE_STEP))
570 list->len = 0;
571 return FALSE;
574 fentry = &list->list[0];
575 memset (fentry, 0, sizeof (*fentry));
576 fentry->fname = g_string_new ("..");
577 fentry->f.link_to_dir = 0;
578 fentry->f.stale_link = 0;
579 fentry->f.dir_size_computed = 0;
580 fentry->f.marked = 0;
581 fentry->st.st_mode = 040755;
582 list->len = 1;
583 return TRUE;
586 /* --------------------------------------------------------------------------------------------- */
588 handle_path is a simplified handle_dirent. The difference is that
589 handle_path doesn't pay attention to panels_options.show_dot_files
590 and panels_options.show_backups.
591 Moreover handle_path can't be used with a filemask.
592 If you change handle_path then check also handle_dirent. */
593 /* Return values: FALSE = don't add, TRUE = add to the list */
595 gboolean
596 handle_path (const char *path, struct stat * buf1, gboolean * link_to_dir, gboolean * stale_link)
598 vfs_path_t *vpath;
600 if (DIR_IS_DOT (path) || DIR_IS_DOTDOT (path))
601 return FALSE;
603 vpath = vfs_path_from_str (path);
604 if (mc_lstat (vpath, buf1) == -1)
606 vfs_path_free (vpath, TRUE);
607 return FALSE;
610 if (S_ISDIR (buf1->st_mode))
611 tree_store_mark_checked (path);
613 /* A link to a file or a directory? */
614 *link_to_dir = FALSE;
615 *stale_link = FALSE;
616 if (S_ISLNK (buf1->st_mode))
618 struct stat buf2;
620 if (mc_stat (vpath, &buf2) == 0)
621 *link_to_dir = S_ISDIR (buf2.st_mode) != 0;
622 else
623 *stale_link = TRUE;
626 vfs_path_free (vpath, TRUE);
628 return TRUE;
631 /* --------------------------------------------------------------------------------------------- */
633 gboolean
634 dir_list_load (dir_list * list, const vfs_path_t * vpath, GCompareFunc sort,
635 const dir_sort_options_t * sort_op, const file_filter_t * filter)
637 DIR *dirp;
638 struct vfs_dirent *dp;
639 struct stat st;
640 file_entry_t *fentry;
641 const char *vpath_str;
642 gboolean ret = TRUE;
644 /* ".." (if any) must be the first entry in the list */
645 if (!dir_list_init (list))
646 return FALSE;
648 fentry = &list->list[0];
649 if (dir_get_dotdot_stat (vpath, &st))
650 fentry->st = st;
652 if (list->callback != NULL)
653 list->callback (DIR_OPEN, (void *) vpath);
654 dirp = mc_opendir (vpath);
655 if (dirp == NULL)
656 return FALSE;
658 tree_store_start_check (vpath);
660 vpath_str = vfs_path_as_str (vpath);
661 /* Do not add a ".." entry to the root directory */
662 if (IS_PATH_SEP (vpath_str[0]) && vpath_str[1] == '\0')
663 dir_list_clean (list);
665 while (ret && (dp = mc_readdir (dirp)) != NULL)
667 gboolean link_to_dir, stale_link;
669 if (list->callback != NULL)
670 list->callback (DIR_READ, dp);
672 if (!handle_dirent (dp, filter, &st, &link_to_dir, &stale_link))
673 continue;
675 if (!dir_list_append (list, dp->d_name, &st, link_to_dir, stale_link))
676 ret = FALSE;
679 if (ret)
680 dir_list_sort (list, sort, sort_op);
682 if (list->callback != NULL)
683 list->callback (DIR_CLOSE, NULL);
684 mc_closedir (dirp);
685 tree_store_end_check ();
687 return ret;
690 /* --------------------------------------------------------------------------------------------- */
692 gboolean
693 if_link_is_exe (const vfs_path_t * full_name_vpath, const file_entry_t * file)
695 struct stat b;
697 if (S_ISLNK (file->st.st_mode) && mc_stat (full_name_vpath, &b) == 0)
698 return is_exe (b.st_mode);
700 return TRUE;
703 /* --------------------------------------------------------------------------------------------- */
704 /** If filter is null, then it is a match */
706 gboolean
707 dir_list_reload (dir_list * list, const vfs_path_t * vpath, GCompareFunc sort,
708 const dir_sort_options_t * sort_op, const file_filter_t * filter)
710 DIR *dirp;
711 struct vfs_dirent *dp;
712 int i;
713 struct stat st;
714 int marked_cnt;
715 GHashTable *marked_files;
716 const char *tmp_path;
717 gboolean ret = TRUE;
719 if (list->callback != NULL)
720 list->callback (DIR_OPEN, (void *) vpath);
721 dirp = mc_opendir (vpath);
722 if (dirp == NULL)
724 dir_list_clean (list);
725 dir_list_init (list);
726 return FALSE;
729 tree_store_start_check (vpath);
731 marked_files = g_hash_table_new (g_str_hash, g_str_equal);
732 alloc_dir_copy (list->len);
733 for (marked_cnt = i = 0; i < list->len; i++)
735 file_entry_t *fentry, *dfentry;
737 fentry = &list->list[i];
738 dfentry = &dir_copy.list[i];
740 dfentry->fname = mc_g_string_dup (fentry->fname);
741 dfentry->f.marked = fentry->f.marked;
742 dfentry->f.dir_size_computed = fentry->f.dir_size_computed;
743 dfentry->f.link_to_dir = fentry->f.link_to_dir;
744 dfentry->f.stale_link = fentry->f.stale_link;
745 dfentry->name_sort_key = NULL;
746 dfentry->extension_sort_key = NULL;
747 if (fentry->f.marked != 0)
749 g_hash_table_insert (marked_files, dfentry->fname->str, dfentry);
750 marked_cnt++;
754 /* save len for later dir_list_clean() */
755 dir_copy.len = list->len;
757 /* Add ".." except to the root directory. The ".." entry
758 (if any) must be the first in the list. */
759 tmp_path = vfs_path_get_by_index (vpath, 0)->path;
760 if (vfs_path_elements_count (vpath) == 1 && IS_PATH_SEP (tmp_path[0]) && tmp_path[1] == '\0')
762 /* root directory */
763 dir_list_clean (list);
765 else
767 dir_list_clean (list);
768 if (!dir_list_init (list))
770 dir_list_free_list (&dir_copy);
771 mc_closedir (dirp);
772 return FALSE;
775 if (dir_get_dotdot_stat (vpath, &st))
777 file_entry_t *fentry;
779 fentry = &list->list[0];
780 fentry->st = st;
784 while (ret && (dp = mc_readdir (dirp)) != NULL)
786 gboolean link_to_dir, stale_link;
788 if (list->callback != NULL)
789 list->callback (DIR_READ, dp);
791 if (!handle_dirent (dp, filter, &st, &link_to_dir, &stale_link))
792 continue;
794 if (!dir_list_append (list, dp->d_name, &st, link_to_dir, stale_link))
795 ret = FALSE;
796 else
798 file_entry_t *fentry;
800 fentry = &list->list[list->len - 1];
803 * If we have marked files in the copy, scan through the copy
804 * to find matching file. Decrease number of remaining marks if
805 * we copied one.
807 fentry->f.marked = (marked_cnt > 0
808 && g_hash_table_lookup (marked_files, dp->d_name) != NULL) ? 1 : 0;
809 if (fentry->f.marked != 0)
810 marked_cnt--;
814 if (ret)
815 dir_list_sort (list, sort, sort_op);
817 if (list->callback != NULL)
818 list->callback (DIR_CLOSE, NULL);
819 mc_closedir (dirp);
820 tree_store_end_check ();
822 g_hash_table_destroy (marked_files);
823 dir_list_free_list (&dir_copy);
825 return ret;
828 /* --------------------------------------------------------------------------------------------- */
830 void
831 file_filter_clear (file_filter_t * filter)
833 MC_PTR_FREE (filter->value);
834 mc_search_free (filter->handler);
835 filter->handler = NULL;
836 /* keep filter->flags */
839 /* --------------------------------------------------------------------------------------------- */