4 Copyright (C) 1994-2024
5 Free Software Foundation, Inc.
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
38 #include "lib/global.h"
39 #include "lib/tty/tty.h"
40 #include "lib/search.h"
41 #include "lib/vfs/vfs.h"
43 #include "lib/strutil.h"
46 #include "src/setup.h" /* panels_options */
48 #include "treestore.h"
49 #include "file.h" /* file_is_symlink_to_dir() */
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) \
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 ************************************************************************/
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 /* --------------------------------------------------------------------------------------------- */
83 key_collate (const char *t1
, const char *t2
)
88 dotdot
= (t1
[0] == '.' ? 1 : 0) | ((t2
[0] == '.' ? 1 : 0) << 1);
94 ret
= str_key_collate (t1
, t2
, case_sensitive
) * reverse
;
97 ret
= -1; /* t1 < t2 */
100 ret
= 1; /* t1 > t2 */
103 ret
= 0; /* it must not happen */
109 /* --------------------------------------------------------------------------------------------- */
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.
129 clean_sort_keys (dir_list
* list
, int start
, int count
)
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
152 handle_dirent (struct vfs_dirent
*dp
, const file_filter_t
* filter
, struct stat
*buf1
,
153 gboolean
* link_to_dir
, gboolean
* stale_link
)
158 if (DIR_IS_DOT (dp
->d_name
) || DIR_IS_DOTDOT (dp
->d_name
))
160 if (!panels_options
.show_dot_files
&& (dp
->d_name
[0] == '.'))
162 if (!panels_options
.show_backups
&& dp
->d_name
[strlen (dp
->d_name
) - 1] == '~')
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
);
195 /* --------------------------------------------------------------------------------------------- */
196 /** get info about ".." */
199 dir_get_dotdot_stat (const vfs_path_t
* vpath
, struct stat
*st
)
201 gboolean ret
= FALSE
;
203 if ((vpath
!= NULL
) && (st
!= NULL
))
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
);
221 /* --------------------------------------------------------------------------------------------- */
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
;
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
250 dir_list_grow (dir_list
* list
, int delta
)
253 gboolean clear_flag
= FALSE
;
261 size
= list
->size
+ delta
;
264 size
= DIR_LIST_MIN_SIZE
;
268 if (size
!= list
->size
)
272 fe
= g_try_renew (file_entry_t
, list
->list
, size
);
280 list
->len
= clear_flag
? 0 : MIN (list
->len
, size
);
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
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
))
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;
315 fentry
->name_sort_key
= NULL
;
316 fentry
->extension_sort_key
= NULL
;
323 /* --------------------------------------------------------------------------------------------- */
326 unsorted (file_entry_t
* a
, file_entry_t
* b
)
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
);
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
)
360 result
= filevercmp (a
->fname
->str
, b
->fname
->str
);
362 return result
* reverse
;
364 return compare_by_names (a
, b
);
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
)
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
);
391 return compare_by_names (a
, b
);
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
);
410 return result
* reverse
;
412 return compare_by_names (a
, b
);
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
);
431 return result
* reverse
;
433 return compare_by_names (a
, b
);
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
);
452 return result
* reverse
;
454 return compare_by_names (a
, b
);
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
;
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
);
487 return result
* reverse
;
489 return compare_by_names (a
, b
);
495 /* --------------------------------------------------------------------------------------------- */
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];
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
),
514 clean_sort_keys (list
, dot_dot_found
, list
->len
- dot_dot_found
);
518 /* --------------------------------------------------------------------------------------------- */
521 dir_list_clean (dir_list
* list
)
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
;
535 /* reduce memory usage */
536 dir_list_grow (list
, DIR_LIST_MIN_SIZE
- list
->size
);
539 /* --------------------------------------------------------------------------------------------- */
542 dir_list_free_list (dir_list
* list
)
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
);
559 /* --------------------------------------------------------------------------------------------- */
560 /** Used to set up a directory list when there is no access to a directory */
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
))
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;
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 */
596 handle_path (const char *path
, struct stat
* buf1
, gboolean
* link_to_dir
, gboolean
* stale_link
)
600 if (DIR_IS_DOT (path
) || DIR_IS_DOTDOT (path
))
603 vpath
= vfs_path_from_str (path
);
604 if (mc_lstat (vpath
, buf1
) == -1)
606 vfs_path_free (vpath
, TRUE
);
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
;
616 if (S_ISLNK (buf1
->st_mode
))
620 if (mc_stat (vpath
, &buf2
) == 0)
621 *link_to_dir
= S_ISDIR (buf2
.st_mode
) != 0;
626 vfs_path_free (vpath
, TRUE
);
631 /* --------------------------------------------------------------------------------------------- */
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
)
638 struct vfs_dirent
*dp
;
640 file_entry_t
*fentry
;
641 const char *vpath_str
;
644 /* ".." (if any) must be the first entry in the list */
645 if (!dir_list_init (list
))
648 fentry
= &list
->list
[0];
649 if (dir_get_dotdot_stat (vpath
, &st
))
652 if (list
->callback
!= NULL
)
653 list
->callback (DIR_OPEN
, (void *) vpath
);
654 dirp
= mc_opendir (vpath
);
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
))
675 if (!dir_list_append (list
, dp
->d_name
, &st
, link_to_dir
, stale_link
))
680 dir_list_sort (list
, sort
, sort_op
);
682 if (list
->callback
!= NULL
)
683 list
->callback (DIR_CLOSE
, NULL
);
685 tree_store_end_check ();
690 /* --------------------------------------------------------------------------------------------- */
693 if_link_is_exe (const vfs_path_t
* full_name_vpath
, const file_entry_t
* file
)
697 if (S_ISLNK (file
->st
.st_mode
) && mc_stat (full_name_vpath
, &b
) == 0)
698 return is_exe (b
.st_mode
);
703 /* --------------------------------------------------------------------------------------------- */
704 /** If filter is null, then it is a match */
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
)
711 struct vfs_dirent
*dp
;
715 GHashTable
*marked_files
;
716 const char *tmp_path
;
719 if (list
->callback
!= NULL
)
720 list
->callback (DIR_OPEN
, (void *) vpath
);
721 dirp
= mc_opendir (vpath
);
724 dir_list_clean (list
);
725 dir_list_init (list
);
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
);
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')
763 dir_list_clean (list
);
767 dir_list_clean (list
);
768 if (!dir_list_init (list
))
770 dir_list_free_list (&dir_copy
);
775 if (dir_get_dotdot_stat (vpath
, &st
))
777 file_entry_t
*fentry
;
779 fentry
= &list
->list
[0];
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
))
794 if (!dir_list_append (list
, dp
->d_name
, &st
, link_to_dir
, stale_link
))
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
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)
815 dir_list_sort (list
, sort
, sort_op
);
817 if (list
->callback
!= NULL
)
818 list
->callback (DIR_CLOSE
, NULL
);
820 tree_store_end_check ();
822 g_hash_table_destroy (marked_files
);
823 dir_list_free_list (&dir_copy
);
828 /* --------------------------------------------------------------------------------------------- */
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 /* --------------------------------------------------------------------------------------------- */