4 Copyright (C) 1994-2019
5 Free Software Foundation, Inc.
8 Slava Zanko <slavazanko@gmail.com>, 2013
9 Andrew Borodin <aborodin@vmail.ru>, 2013
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"
45 #include "lib/widget.h" /* message() */
47 #include "src/setup.h" /* panels_options */
49 #include "treestore.h"
50 #include "file.h" /* file_is_symlink_to_dir() */
52 #include "layout.h" /* rotate_dash() */
54 /*** global variables ****************************************************************************/
56 /*** file scope macro definitions ****************************************************************/
58 #define MY_ISDIR(x) (\
59 (is_exe (x->st.st_mode) && !(S_ISDIR (x->st.st_mode) || link_isdir (x)) && exec_first) \
61 : ( (S_ISDIR (x->st.st_mode) || link_isdir (x)) ? 2 : 0) )
63 /*** file scope type declarations ****************************************************************/
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 };
78 /*** file scope functions ************************************************************************/
79 /* --------------------------------------------------------------------------------------------- */
82 sort_orders_t sort_orders [SORT_TYPES_TOTAL] = {
83 { N_("&Unsorted"), unsorted },
84 { N_("&Name"), sort_name },
85 { N_("&Extension"), sort_ext },
86 { N_("&Modify time"), sort_time },
87 { N_("&Access time"), sort_atime },
88 { N_("C&Hange time"), sort_ctime },
89 { N_("&Size"), sort_size },
90 { N_("&Inode"), sort_inode },
95 key_collate (const char *t1
, const char *t2
)
100 dotdot
= (t1
[0] == '.' ? 1 : 0) | ((t2
[0] == '.' ? 1 : 0) << 1);
106 ret
= str_key_collate (t1
, t2
, case_sensitive
) * reverse
;
109 ret
= -1; /* t1 < t2 */
112 ret
= 1; /* t1 > t2 */
115 ret
= 0; /* it must not happen */
121 /* --------------------------------------------------------------------------------------------- */
123 * clear keys, should be call after sorting is finished.
127 clean_sort_keys (dir_list
* list
, int start
, int count
)
131 for (i
= 0; i
< count
; i
++)
133 file_entry_t
*fentry
;
135 fentry
= &list
->list
[i
+ start
];
136 str_release_key (fentry
->sort_key
, case_sensitive
);
137 fentry
->sort_key
= NULL
;
138 str_release_key (fentry
->second_sort_key
, case_sensitive
);
139 fentry
->second_sort_key
= NULL
;
143 /* --------------------------------------------------------------------------------------------- */
145 * If you change handle_dirent then check also handle_path.
146 * @return FALSE = don't add, TRUE = add to the list
150 handle_dirent (struct dirent
*dp
, const char *fltr
, struct stat
*buf1
, int *link_to_dir
,
156 if (DIR_IS_DOT (dp
->d_name
) || DIR_IS_DOTDOT (dp
->d_name
))
158 if (!panels_options
.show_dot_files
&& (dp
->d_name
[0] == '.'))
160 if (!panels_options
.show_backups
&& dp
->d_name
[strlen (dp
->d_name
) - 1] == '~')
163 vpath
= vfs_path_from_str (dp
->d_name
);
164 if (mc_lstat (vpath
, buf1
) == -1)
167 * lstat() fails - such entries should be identified by
168 * buf1->st_mode being 0.
169 * It happens on QNX Neutrino for /fs/cd0 if no CD is inserted.
171 memset (buf1
, 0, sizeof (*buf1
));
174 if (S_ISDIR (buf1
->st_mode
))
175 tree_store_mark_checked (dp
->d_name
);
177 /* A link to a file or a directory? */
178 *link_to_dir
= file_is_symlink_to_dir (vpath
, buf1
, &stale
) ? 1 : 0;
179 *stale_link
= stale
? 1 : 0;
181 vfs_path_free (vpath
);
183 return (S_ISDIR (buf1
->st_mode
) || *link_to_dir
!= 0 || fltr
== NULL
184 || mc_search (fltr
, NULL
, dp
->d_name
, MC_SEARCH_T_GLOB
));
187 /* --------------------------------------------------------------------------------------------- */
188 /** get info about ".." */
191 dir_get_dotdot_stat (const vfs_path_t
* vpath
, struct stat
*st
)
193 gboolean ret
= FALSE
;
195 if ((vpath
!= NULL
) && (st
!= NULL
))
199 path
= vfs_path_get_by_index (vpath
, 0)->path
;
200 if (path
!= NULL
&& *path
!= '\0')
202 vfs_path_t
*tmp_vpath
;
204 tmp_vpath
= vfs_path_append_new (vpath
, "..", (char *) NULL
);
205 ret
= mc_stat (tmp_vpath
, st
) == 0;
206 vfs_path_free (tmp_vpath
);
213 /* --------------------------------------------------------------------------------------------- */
216 alloc_dir_copy (int size
)
218 if (dir_copy
.size
< size
)
220 if (dir_copy
.list
!= NULL
)
221 dir_list_free_list (&dir_copy
);
223 dir_copy
.list
= g_new0 (file_entry_t
, size
);
224 dir_copy
.size
= size
;
229 /* --------------------------------------------------------------------------------------------- */
230 /*** public functions ****************************************************************************/
231 /* --------------------------------------------------------------------------------------------- */
233 * Increase or decrease directory list size.
235 * @param list directory list
236 * @param delta value by increase (if positive) or decrease (if negative) list size
238 * @return FALSE on failure, TRUE on success
242 dir_list_grow (dir_list
* list
, int delta
)
245 gboolean clear_flag
= FALSE
;
253 size
= list
->size
+ delta
;
256 size
= DIR_LIST_MIN_SIZE
;
260 if (size
!= list
->size
)
264 fe
= g_try_renew (file_entry_t
, list
->list
, size
);
272 list
->len
= clear_flag
? 0 : MIN (list
->len
, size
);
277 /* --------------------------------------------------------------------------------------------- */
279 * Append file info to the directory list.
281 * @param list directory list
282 * @param fname file name
283 * @param st file stat info
284 * @param link_to_dir is file link to directory
285 * @param stale_link is file stale elink
287 * @return FALSE on failure, TRUE on success
291 dir_list_append (dir_list
* list
, const char *fname
, const struct stat
* st
,
292 gboolean link_to_dir
, gboolean stale_link
)
294 file_entry_t
*fentry
;
296 /* Need to grow the *list? */
297 if (list
->len
== list
->size
&& !dir_list_grow (list
, DIR_LIST_RESIZE_STEP
))
300 fentry
= &list
->list
[list
->len
];
301 fentry
->fnamelen
= strlen (fname
);
302 fentry
->fname
= g_strndup (fname
, fentry
->fnamelen
);
303 fentry
->f
.marked
= 0;
304 fentry
->f
.link_to_dir
= link_to_dir
? 1 : 0;
305 fentry
->f
.stale_link
= stale_link
? 1 : 0;
306 fentry
->f
.dir_size_computed
= 0;
308 fentry
->sort_key
= NULL
;
309 fentry
->second_sort_key
= NULL
;
316 /* --------------------------------------------------------------------------------------------- */
319 unsorted (file_entry_t
* a
, file_entry_t
* b
)
326 /* --------------------------------------------------------------------------------------------- */
329 sort_name (file_entry_t
* a
, file_entry_t
* b
)
331 int ad
= MY_ISDIR (a
);
332 int bd
= MY_ISDIR (b
);
334 if (ad
== bd
|| panels_options
.mix_all_files
)
336 /* create key if does not exist, key will be freed after sorting */
337 if (a
->sort_key
== NULL
)
338 a
->sort_key
= str_create_key_for_filename (a
->fname
, case_sensitive
);
339 if (b
->sort_key
== NULL
)
340 b
->sort_key
= str_create_key_for_filename (b
->fname
, case_sensitive
);
342 return key_collate (a
->sort_key
, b
->sort_key
);
347 /* --------------------------------------------------------------------------------------------- */
350 sort_vers (file_entry_t
* a
, file_entry_t
* b
)
352 int ad
= MY_ISDIR (a
);
353 int bd
= MY_ISDIR (b
);
355 if (ad
== bd
|| panels_options
.mix_all_files
)
357 return filevercmp (a
->fname
, b
->fname
) * reverse
;
365 /* --------------------------------------------------------------------------------------------- */
368 sort_ext (file_entry_t
* a
, file_entry_t
* b
)
370 int ad
= MY_ISDIR (a
);
371 int bd
= MY_ISDIR (b
);
373 if (ad
== bd
|| panels_options
.mix_all_files
)
377 if (a
->second_sort_key
== NULL
)
378 a
->second_sort_key
= str_create_key (extension (a
->fname
), case_sensitive
);
379 if (b
->second_sort_key
== NULL
)
380 b
->second_sort_key
= str_create_key (extension (b
->fname
), case_sensitive
);
382 r
= str_key_collate (a
->second_sort_key
, b
->second_sort_key
, case_sensitive
);
386 return sort_name (a
, b
);
392 /* --------------------------------------------------------------------------------------------- */
395 sort_time (file_entry_t
* a
, file_entry_t
* b
)
397 int ad
= MY_ISDIR (a
);
398 int bd
= MY_ISDIR (b
);
400 if (ad
== bd
|| panels_options
.mix_all_files
)
402 int result
= a
->st
.st_mtime
< b
->st
.st_mtime
? -1 : a
->st
.st_mtime
> b
->st
.st_mtime
;
404 return result
* reverse
;
406 return sort_name (a
, b
);
412 /* --------------------------------------------------------------------------------------------- */
415 sort_ctime (file_entry_t
* a
, file_entry_t
* b
)
417 int ad
= MY_ISDIR (a
);
418 int bd
= MY_ISDIR (b
);
420 if (ad
== bd
|| panels_options
.mix_all_files
)
422 int result
= a
->st
.st_ctime
< b
->st
.st_ctime
? -1 : a
->st
.st_ctime
> b
->st
.st_ctime
;
424 return result
* reverse
;
426 return sort_name (a
, b
);
432 /* --------------------------------------------------------------------------------------------- */
435 sort_atime (file_entry_t
* a
, file_entry_t
* b
)
437 int ad
= MY_ISDIR (a
);
438 int bd
= MY_ISDIR (b
);
440 if (ad
== bd
|| panels_options
.mix_all_files
)
442 int result
= a
->st
.st_atime
< b
->st
.st_atime
? -1 : a
->st
.st_atime
> b
->st
.st_atime
;
444 return result
* reverse
;
446 return sort_name (a
, b
);
452 /* --------------------------------------------------------------------------------------------- */
455 sort_inode (file_entry_t
* a
, file_entry_t
* b
)
457 int ad
= MY_ISDIR (a
);
458 int bd
= MY_ISDIR (b
);
460 if (ad
== bd
|| panels_options
.mix_all_files
)
461 return (a
->st
.st_ino
- b
->st
.st_ino
) * reverse
;
466 /* --------------------------------------------------------------------------------------------- */
469 sort_size (file_entry_t
* a
, file_entry_t
* b
)
471 int ad
= MY_ISDIR (a
);
472 int bd
= MY_ISDIR (b
);
475 if (ad
!= bd
&& !panels_options
.mix_all_files
)
478 result
= a
->st
.st_size
< b
->st
.st_size
? -1 : a
->st
.st_size
> b
->st
.st_size
;
480 return result
* reverse
;
482 return sort_name (a
, b
);
485 /* --------------------------------------------------------------------------------------------- */
488 dir_list_sort (dir_list
* list
, GCompareFunc sort
, const dir_sort_options_t
* sort_op
)
490 file_entry_t
*fentry
;
491 int dot_dot_found
= 0;
493 if (list
->len
< 2 || sort
== (GCompareFunc
) unsorted
)
496 /* If there is an ".." entry the caller must take care to
497 ensure that it occupies the first list element. */
498 fentry
= &list
->list
[0];
499 if (DIR_IS_DOTDOT (fentry
->fname
))
502 reverse
= sort_op
->reverse
? -1 : 1;
503 case_sensitive
= sort_op
->case_sensitive
? 1 : 0;
504 exec_first
= sort_op
->exec_first
;
505 qsort (&(list
->list
)[dot_dot_found
], list
->len
- dot_dot_found
, sizeof (file_entry_t
), sort
);
507 clean_sort_keys (list
, dot_dot_found
, list
->len
- dot_dot_found
);
510 /* --------------------------------------------------------------------------------------------- */
513 dir_list_clean (dir_list
* list
)
517 for (i
= 0; i
< list
->len
; i
++)
519 file_entry_t
*fentry
;
521 fentry
= &list
->list
[i
];
522 MC_PTR_FREE (fentry
->fname
);
526 /* reduce memory usage */
527 dir_list_grow (list
, DIR_LIST_MIN_SIZE
- list
->size
);
530 /* --------------------------------------------------------------------------------------------- */
533 dir_list_free_list (dir_list
* list
)
537 for (i
= 0; i
< list
->len
; i
++)
539 file_entry_t
*fentry
;
541 fentry
= &list
->list
[i
];
542 g_free (fentry
->fname
);
545 MC_PTR_FREE (list
->list
);
550 /* --------------------------------------------------------------------------------------------- */
551 /** Used to set up a directory list when there is no access to a directory */
554 dir_list_init (dir_list
* list
)
556 file_entry_t
*fentry
;
558 /* Need to grow the *list? */
559 if (list
->size
== 0 && !dir_list_grow (list
, DIR_LIST_RESIZE_STEP
))
565 fentry
= &list
->list
[0];
566 memset (fentry
, 0, sizeof (*fentry
));
567 fentry
->fnamelen
= 2;
568 fentry
->fname
= g_strndup ("..", fentry
->fnamelen
);
569 fentry
->f
.link_to_dir
= 0;
570 fentry
->f
.stale_link
= 0;
571 fentry
->f
.dir_size_computed
= 0;
572 fentry
->f
.marked
= 0;
573 fentry
->st
.st_mode
= 040755;
578 /* --------------------------------------------------------------------------------------------- */
580 handle_path is a simplified handle_dirent. The difference is that
581 handle_path doesn't pay attention to panels_options.show_dot_files
582 and panels_options.show_backups.
583 Moreover handle_path can't be used with a filemask.
584 If you change handle_path then check also handle_dirent. */
585 /* Return values: FALSE = don't add, TRUE = add to the list */
588 handle_path (const char *path
, struct stat
* buf1
, int *link_to_dir
, int *stale_link
)
592 if (DIR_IS_DOT (path
) || DIR_IS_DOTDOT (path
))
595 vpath
= vfs_path_from_str (path
);
596 if (mc_lstat (vpath
, buf1
) == -1)
598 vfs_path_free (vpath
);
602 if (S_ISDIR (buf1
->st_mode
))
603 tree_store_mark_checked (path
);
605 /* A link to a file or a directory? */
608 if (S_ISLNK (buf1
->st_mode
))
612 if (mc_stat (vpath
, &buf2
) == 0)
613 *link_to_dir
= S_ISDIR (buf2
.st_mode
) != 0;
618 vfs_path_free (vpath
);
623 /* --------------------------------------------------------------------------------------------- */
626 dir_list_load (dir_list
* list
, const vfs_path_t
* vpath
, GCompareFunc sort
,
627 const dir_sort_options_t
* sort_op
, const char *fltr
)
631 int link_to_dir
, stale_link
;
633 file_entry_t
*fentry
;
634 const char *vpath_str
;
636 /* ".." (if any) must be the first entry in the list */
637 if (!dir_list_init (list
))
640 fentry
= &list
->list
[0];
641 if (dir_get_dotdot_stat (vpath
, &st
))
644 dirp
= mc_opendir (vpath
);
647 message (D_ERROR
, MSG_ERROR
, _("Cannot read directory contents"));
651 tree_store_start_check (vpath
);
653 vpath_str
= vfs_path_as_str (vpath
);
654 /* Do not add a ".." entry to the root directory */
655 if (IS_PATH_SEP (vpath_str
[0]) && vpath_str
[1] == '\0')
656 dir_list_clean (list
);
658 while ((dp
= mc_readdir (dirp
)) != NULL
)
660 if (!handle_dirent (dp
, fltr
, &st
, &link_to_dir
, &stale_link
))
663 if (!dir_list_append (list
, dp
->d_name
, &st
, link_to_dir
!= 0, stale_link
!= 0))
666 if ((list
->len
& 31) == 0)
670 dir_list_sort (list
, sort
, sort_op
);
674 tree_store_end_check ();
678 /* --------------------------------------------------------------------------------------------- */
681 if_link_is_exe (const vfs_path_t
* full_name_vpath
, const file_entry_t
* file
)
685 if (S_ISLNK (file
->st
.st_mode
) && mc_stat (full_name_vpath
, &b
) == 0)
686 return is_exe (b
.st_mode
);
690 /* --------------------------------------------------------------------------------------------- */
691 /** If fltr is null, then it is a match */
694 dir_list_reload (dir_list
* list
, const vfs_path_t
* vpath
, GCompareFunc sort
,
695 const dir_sort_options_t
* sort_op
, const char *fltr
)
699 int i
, link_to_dir
, stale_link
;
702 GHashTable
*marked_files
;
703 const char *tmp_path
;
705 dirp
= mc_opendir (vpath
);
708 message (D_ERROR
, MSG_ERROR
, _("Cannot read directory contents"));
709 dir_list_clean (list
);
710 dir_list_init (list
);
714 tree_store_start_check (vpath
);
716 marked_files
= g_hash_table_new (g_str_hash
, g_str_equal
);
717 alloc_dir_copy (list
->len
);
718 for (marked_cnt
= i
= 0; i
< list
->len
; i
++)
720 file_entry_t
*fentry
, *dfentry
;
722 fentry
= &list
->list
[i
];
723 dfentry
= &dir_copy
.list
[i
];
725 dfentry
->fnamelen
= fentry
->fnamelen
;
726 dfentry
->fname
= g_strndup (fentry
->fname
, fentry
->fnamelen
);
727 dfentry
->f
.marked
= fentry
->f
.marked
;
728 dfentry
->f
.dir_size_computed
= fentry
->f
.dir_size_computed
;
729 dfentry
->f
.link_to_dir
= fentry
->f
.link_to_dir
;
730 dfentry
->f
.stale_link
= fentry
->f
.stale_link
;
731 dfentry
->sort_key
= NULL
;
732 dfentry
->second_sort_key
= NULL
;
733 if (fentry
->f
.marked
)
735 g_hash_table_insert (marked_files
, dfentry
->fname
, dfentry
);
740 /* save len for later dir_list_clean() */
741 dir_copy
.len
= list
->len
;
743 /* Add ".." except to the root directory. The ".." entry
744 (if any) must be the first in the list. */
745 tmp_path
= vfs_path_get_by_index (vpath
, 0)->path
;
746 if (vfs_path_elements_count (vpath
) == 1 && IS_PATH_SEP (tmp_path
[0]) && tmp_path
[1] == '\0')
749 dir_list_clean (list
);
753 dir_list_clean (list
);
754 if (!dir_list_init (list
))
756 dir_list_free_list (&dir_copy
);
760 if (dir_get_dotdot_stat (vpath
, &st
))
762 file_entry_t
*fentry
;
764 fentry
= &list
->list
[0];
769 while ((dp
= mc_readdir (dirp
)) != NULL
)
771 file_entry_t
*fentry
;
773 if (!handle_dirent (dp
, fltr
, &st
, &link_to_dir
, &stale_link
))
776 if (!dir_list_append (list
, dp
->d_name
, &st
, link_to_dir
!= 0, stale_link
!= 0))
779 /* Norbert (Feb 12, 1997):
780 Just in case someone finds this memory leak:
781 -1 means big trouble (at the moment no memory left),
782 I don't bother with further cleanup because if one gets to
783 this point he will have more problems than a few memory
784 leaks and because one 'dir_list_clean' would not be enough (and
785 because I don't want to spent the time to make it working,
786 IMHO it's not worthwhile).
787 dir_list_clean (&dir_copy);
789 tree_store_end_check ();
790 g_hash_table_destroy (marked_files
);
793 fentry
= &list
->list
[list
->len
- 1];
795 fentry
->f
.marked
= 0;
798 * If we have marked files in the copy, scan through the copy
799 * to find matching file. Decrease number of remaining marks if
802 if (marked_cnt
> 0 && g_hash_table_lookup (marked_files
, dp
->d_name
) != NULL
)
804 fentry
->f
.marked
= 1;
808 if ((list
->len
& 15) == 0)
812 tree_store_end_check ();
813 g_hash_table_destroy (marked_files
);
815 dir_list_sort (list
, sort
, sort_op
);
817 dir_list_free_list (&dir_copy
);
821 /* --------------------------------------------------------------------------------------------- */