More use link_isdir().
[midnight-commander.git] / src / filemanager / dir.c
blobcfa14e706e8e32062b68708c4162e296a2834aa0
1 /*
2 Directory routines
4 Copyright (C) 1994-2016
5 Free Software Foundation, Inc.
7 Written by:
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
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"
45 #include "lib/widget.h" /* message() */
47 #include "src/setup.h" /* panels_options */
49 #include "treestore.h"
50 #include "dir.h"
51 #include "layout.h" /* rotate_dash() */
53 /*** global variables ****************************************************************************/
55 /*** file scope macro definitions ****************************************************************/
57 #define MY_ISDIR(x) (\
58 (is_exe (x->st.st_mode) && !(S_ISDIR (x->st.st_mode) || link_isdir (x)) && exec_first) \
59 ? 1 \
60 : ( (S_ISDIR (x->st.st_mode) || link_isdir (x)) ? 2 : 0) )
62 /*** file scope type declarations ****************************************************************/
64 /*** file scope variables ************************************************************************/
66 /* Reverse flag */
67 static int reverse = 1;
69 /* Are the files sorted case sensitively? */
70 static int case_sensitive = OS_SORT_CASE_SENSITIVE_DEFAULT;
72 /* Are the exec_bit files top in list */
73 static gboolean exec_first = TRUE;
75 static dir_list dir_copy = { NULL, 0, 0 };
77 /*** file scope functions ************************************************************************/
78 /* --------------------------------------------------------------------------------------------- */
81 sort_orders_t sort_orders [SORT_TYPES_TOTAL] = {
82 { N_("&Unsorted"), unsorted },
83 { N_("&Name"), sort_name },
84 { N_("&Extension"), sort_ext },
85 { N_("&Modify time"), sort_time },
86 { N_("&Access time"), sort_atime },
87 { N_("C&Hange time"), sort_ctime },
88 { N_("&Size"), sort_size },
89 { N_("&Inode"), sort_inode },
93 static inline int
94 key_collate (const char *t1, const char *t2)
96 int dotdot = 0;
97 int ret;
99 dotdot = (t1[0] == '.' ? 1 : 0) | ((t2[0] == '.' ? 1 : 0) << 1);
101 switch (dotdot)
103 case 0:
104 case 3:
105 ret = str_key_collate (t1, t2, case_sensitive) * reverse;
106 break;
107 case 1:
108 ret = -1; /* t1 < t2 */
109 break;
110 case 2:
111 ret = 1; /* t1 > t2 */
112 break;
113 default:
114 ret = 0; /* it must not happen */
117 return ret;
120 /* --------------------------------------------------------------------------------------------- */
122 * clear keys, should be call after sorting is finished.
125 static void
126 clean_sort_keys (dir_list * list, int start, int count)
128 int i;
130 for (i = 0; i < count; i++)
132 file_entry_t *fentry;
134 fentry = &list->list[i + start];
135 str_release_key (fentry->sort_key, case_sensitive);
136 fentry->sort_key = NULL;
137 str_release_key (fentry->second_sort_key, case_sensitive);
138 fentry->second_sort_key = NULL;
142 /* --------------------------------------------------------------------------------------------- */
144 * If you change handle_dirent then check also handle_path.
145 * @return FALSE = don't add, TRUE = add to the list
148 static gboolean
149 handle_dirent (struct dirent *dp, const char *fltr, struct stat *buf1, int *link_to_dir,
150 int *stale_link)
152 vfs_path_t *vpath;
154 if (DIR_IS_DOT (dp->d_name) || DIR_IS_DOTDOT (dp->d_name))
155 return FALSE;
156 if (!panels_options.show_dot_files && (dp->d_name[0] == '.'))
157 return FALSE;
158 if (!panels_options.show_backups && dp->d_name[strlen (dp->d_name) - 1] == '~')
159 return FALSE;
161 vpath = vfs_path_from_str (dp->d_name);
162 if (mc_lstat (vpath, buf1) == -1)
165 * lstat() fails - such entries should be identified by
166 * buf1->st_mode being 0.
167 * It happens on QNX Neutrino for /fs/cd0 if no CD is inserted.
169 memset (buf1, 0, sizeof (*buf1));
172 if (S_ISDIR (buf1->st_mode))
173 tree_store_mark_checked (dp->d_name);
175 /* A link to a file or a directory? */
176 *link_to_dir = 0;
177 *stale_link = 0;
178 if (S_ISLNK (buf1->st_mode))
180 struct stat buf2;
182 if (mc_stat (vpath, &buf2) == 0)
183 *link_to_dir = S_ISDIR (buf2.st_mode) != 0;
184 else
185 *stale_link = 1;
188 vfs_path_free (vpath);
190 return (S_ISDIR (buf1->st_mode) || *link_to_dir != 0 || fltr == NULL
191 || mc_search (fltr, NULL, dp->d_name, MC_SEARCH_T_GLOB));
194 /* --------------------------------------------------------------------------------------------- */
195 /** get info about ".." */
197 static gboolean
198 dir_get_dotdot_stat (const vfs_path_t * vpath, struct stat *st)
200 gboolean ret = FALSE;
202 if ((vpath != NULL) && (st != NULL))
204 const char *path;
206 path = vfs_path_get_by_index (vpath, 0)->path;
207 if (path != NULL && *path != '\0')
209 vfs_path_t *tmp_vpath;
211 tmp_vpath = vfs_path_append_new (vpath, "..", (char *) NULL);
212 ret = mc_stat (tmp_vpath, st) == 0;
213 vfs_path_free (tmp_vpath);
217 return ret;
220 /* --------------------------------------------------------------------------------------------- */
222 static void
223 alloc_dir_copy (int size)
225 if (dir_copy.size < size)
227 if (dir_copy.list != NULL)
229 int i;
231 for (i = 0; i < dir_copy.len; i++)
233 file_entry_t *fentry;
235 fentry = &(dir_copy.list)[i];
236 g_free (fentry->fname);
238 g_free (dir_copy.list);
241 dir_copy.list = g_new0 (file_entry_t, size);
242 dir_copy.size = size;
243 dir_copy.len = 0;
247 /* --------------------------------------------------------------------------------------------- */
248 /*** public functions ****************************************************************************/
249 /* --------------------------------------------------------------------------------------------- */
251 * Increase or decrease directory list size.
253 * @param list directory list
254 * @param delta value by increase (if positive) or decrease (if negative) list size
256 * @return FALSE on failure, TRUE on success
259 gboolean
260 dir_list_grow (dir_list * list, int delta)
262 int size;
263 gboolean clear_flag = FALSE;
265 if (list == NULL)
266 return FALSE;
268 if (delta == 0)
269 return TRUE;
271 size = list->size + delta;
272 if (size <= 0)
274 size = DIR_LIST_MIN_SIZE;
275 clear_flag = TRUE;
278 if (size != list->size)
280 file_entry_t *fe;
282 fe = g_try_renew (file_entry_t, list->list, size);
283 if (fe == NULL)
284 return FALSE;
286 list->list = fe;
287 list->size = size;
290 list->len = clear_flag ? 0 : MIN (list->len, size);
292 return TRUE;
295 /* --------------------------------------------------------------------------------------------- */
297 * Append file info to the directory list.
299 * @param list directory list
300 * @param fname file name
301 * @param st file stat info
302 * @param link_to_dir is file link to directory
303 * @param stale_link is file stale elink
305 * @return FALSE on failure, TRUE on success
308 gboolean
309 dir_list_append (dir_list * list, const char *fname, const struct stat * st,
310 gboolean link_to_dir, gboolean stale_link)
312 file_entry_t *fentry;
314 /* Need to grow the *list? */
315 if (list->len == list->size && !dir_list_grow (list, DIR_LIST_RESIZE_STEP))
316 return FALSE;
318 fentry = &list->list[list->len];
319 fentry->fnamelen = strlen (fname);
320 fentry->fname = g_strndup (fname, fentry->fnamelen);
321 fentry->f.marked = 0;
322 fentry->f.link_to_dir = link_to_dir ? 1 : 0;
323 fentry->f.stale_link = stale_link ? 1 : 0;
324 fentry->f.dir_size_computed = 0;
325 fentry->st = *st;
326 fentry->sort_key = NULL;
327 fentry->second_sort_key = NULL;
329 list->len++;
331 return TRUE;
334 /* --------------------------------------------------------------------------------------------- */
337 unsorted (file_entry_t * a, file_entry_t * b)
339 (void) a;
340 (void) b;
341 return 0;
344 /* --------------------------------------------------------------------------------------------- */
347 sort_name (file_entry_t * a, file_entry_t * b)
349 int ad = MY_ISDIR (a);
350 int bd = MY_ISDIR (b);
352 if (ad == bd || panels_options.mix_all_files)
354 /* create key if does not exist, key will be freed after sorting */
355 if (a->sort_key == NULL)
356 a->sort_key = str_create_key_for_filename (a->fname, case_sensitive);
357 if (b->sort_key == NULL)
358 b->sort_key = str_create_key_for_filename (b->fname, case_sensitive);
360 return key_collate (a->sort_key, b->sort_key);
362 return bd - ad;
365 /* --------------------------------------------------------------------------------------------- */
368 sort_vers (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)
375 return str_verscmp (a->fname, b->fname) * reverse;
377 else
379 return bd - ad;
383 /* --------------------------------------------------------------------------------------------- */
386 sort_ext (file_entry_t * a, file_entry_t * b)
388 int ad = MY_ISDIR (a);
389 int bd = MY_ISDIR (b);
391 if (ad == bd || panels_options.mix_all_files)
393 int r;
395 if (a->second_sort_key == NULL)
396 a->second_sort_key = str_create_key (extension (a->fname), case_sensitive);
397 if (b->second_sort_key == NULL)
398 b->second_sort_key = str_create_key (extension (b->fname), case_sensitive);
400 r = str_key_collate (a->second_sort_key, b->second_sort_key, case_sensitive);
401 if (r)
402 return r * reverse;
403 else
404 return sort_name (a, b);
406 else
407 return bd - ad;
410 /* --------------------------------------------------------------------------------------------- */
413 sort_time (file_entry_t * a, file_entry_t * b)
415 int ad = MY_ISDIR (a);
416 int bd = MY_ISDIR (b);
418 if (ad == bd || panels_options.mix_all_files)
420 int result = a->st.st_mtime < b->st.st_mtime ? -1 : a->st.st_mtime > b->st.st_mtime;
421 if (result != 0)
422 return result * reverse;
423 else
424 return sort_name (a, b);
426 else
427 return bd - ad;
430 /* --------------------------------------------------------------------------------------------- */
433 sort_ctime (file_entry_t * a, file_entry_t * b)
435 int ad = MY_ISDIR (a);
436 int bd = MY_ISDIR (b);
438 if (ad == bd || panels_options.mix_all_files)
440 int result = a->st.st_ctime < b->st.st_ctime ? -1 : a->st.st_ctime > b->st.st_ctime;
441 if (result != 0)
442 return result * reverse;
443 else
444 return sort_name (a, b);
446 else
447 return bd - ad;
450 /* --------------------------------------------------------------------------------------------- */
453 sort_atime (file_entry_t * a, file_entry_t * b)
455 int ad = MY_ISDIR (a);
456 int bd = MY_ISDIR (b);
458 if (ad == bd || panels_options.mix_all_files)
460 int result = a->st.st_atime < b->st.st_atime ? -1 : a->st.st_atime > b->st.st_atime;
461 if (result != 0)
462 return result * reverse;
463 else
464 return sort_name (a, b);
466 else
467 return bd - ad;
470 /* --------------------------------------------------------------------------------------------- */
473 sort_inode (file_entry_t * a, file_entry_t * b)
475 int ad = MY_ISDIR (a);
476 int bd = MY_ISDIR (b);
478 if (ad == bd || panels_options.mix_all_files)
479 return (a->st.st_ino - b->st.st_ino) * reverse;
480 else
481 return bd - ad;
484 /* --------------------------------------------------------------------------------------------- */
487 sort_size (file_entry_t * a, file_entry_t * b)
489 int ad = MY_ISDIR (a);
490 int bd = MY_ISDIR (b);
491 int result = 0;
493 if (ad != bd && !panels_options.mix_all_files)
494 return bd - ad;
496 result = a->st.st_size < b->st.st_size ? -1 : a->st.st_size > b->st.st_size;
497 if (result != 0)
498 return result * reverse;
499 else
500 return sort_name (a, b);
503 /* --------------------------------------------------------------------------------------------- */
505 void
506 dir_list_sort (dir_list * list, GCompareFunc sort, const dir_sort_options_t * sort_op)
508 file_entry_t *fentry;
509 int dot_dot_found = 0;
511 if (list->len < 2 || sort == (GCompareFunc) unsorted)
512 return;
514 /* If there is an ".." entry the caller must take care to
515 ensure that it occupies the first list element. */
516 fentry = &list->list[0];
517 if (DIR_IS_DOTDOT (fentry->fname))
518 dot_dot_found = 1;
520 reverse = sort_op->reverse ? -1 : 1;
521 case_sensitive = sort_op->case_sensitive ? 1 : 0;
522 exec_first = sort_op->exec_first;
523 qsort (&(list->list)[dot_dot_found], list->len - dot_dot_found, sizeof (file_entry_t), sort);
525 clean_sort_keys (list, dot_dot_found, list->len - dot_dot_found);
528 /* --------------------------------------------------------------------------------------------- */
530 void
531 dir_list_clean (dir_list * list)
533 int i;
535 for (i = 0; i < list->len; i++)
537 file_entry_t *fentry;
539 fentry = &list->list[i];
540 MC_PTR_FREE (fentry->fname);
543 list->len = 0;
544 /* reduce memory usage */
545 dir_list_grow (list, DIR_LIST_MIN_SIZE - list->size);
548 /* --------------------------------------------------------------------------------------------- */
549 /** Used to set up a directory list when there is no access to a directory */
551 gboolean
552 dir_list_init (dir_list * list)
554 file_entry_t *fentry;
556 /* Need to grow the *list? */
557 if (list->size == 0 && !dir_list_grow (list, DIR_LIST_RESIZE_STEP))
559 list->len = 0;
560 return FALSE;
563 fentry = &list->list[0];
564 memset (fentry, 0, sizeof (*fentry));
565 fentry->fnamelen = 2;
566 fentry->fname = g_strndup ("..", fentry->fnamelen);
567 fentry->f.link_to_dir = 0;
568 fentry->f.stale_link = 0;
569 fentry->f.dir_size_computed = 0;
570 fentry->f.marked = 0;
571 fentry->st.st_mode = 040755;
572 list->len = 1;
573 return TRUE;
576 /* --------------------------------------------------------------------------------------------- */
578 handle_path is a simplified handle_dirent. The difference is that
579 handle_path doesn't pay attention to panels_options.show_dot_files
580 and panels_options.show_backups.
581 Moreover handle_path can't be used with a filemask.
582 If you change handle_path then check also handle_dirent. */
583 /* Return values: FALSE = don't add, TRUE = add to the list */
585 gboolean
586 handle_path (const char *path, struct stat * buf1, int *link_to_dir, int *stale_link)
588 vfs_path_t *vpath;
590 if (DIR_IS_DOT (path) || DIR_IS_DOTDOT (path))
591 return FALSE;
593 vpath = vfs_path_from_str (path);
594 if (mc_lstat (vpath, buf1) == -1)
596 vfs_path_free (vpath);
597 return FALSE;
600 if (S_ISDIR (buf1->st_mode))
601 tree_store_mark_checked (path);
603 /* A link to a file or a directory? */
604 *link_to_dir = 0;
605 *stale_link = 0;
606 if (S_ISLNK (buf1->st_mode))
608 struct stat buf2;
610 if (mc_stat (vpath, &buf2) == 0)
611 *link_to_dir = S_ISDIR (buf2.st_mode) != 0;
612 else
613 *stale_link = 1;
616 vfs_path_free (vpath);
618 return TRUE;
621 /* --------------------------------------------------------------------------------------------- */
623 void
624 dir_list_load (dir_list * list, const vfs_path_t * vpath, GCompareFunc sort,
625 const dir_sort_options_t * sort_op, const char *fltr)
627 DIR *dirp;
628 struct dirent *dp;
629 int link_to_dir, stale_link;
630 struct stat st;
631 file_entry_t *fentry;
632 const char *vpath_str;
634 /* ".." (if any) must be the first entry in the list */
635 if (!dir_list_init (list))
636 return;
638 fentry = &list->list[0];
639 if (dir_get_dotdot_stat (vpath, &st))
640 fentry->st = st;
642 dirp = mc_opendir (vpath);
643 if (dirp == NULL)
645 message (D_ERROR, MSG_ERROR, _("Cannot read directory contents"));
646 return;
649 tree_store_start_check (vpath);
651 vpath_str = vfs_path_as_str (vpath);
652 /* Do not add a ".." entry to the root directory */
653 if (IS_PATH_SEP (vpath_str[0]) && vpath_str[1] == '\0')
654 dir_list_clean (list);
656 while ((dp = mc_readdir (dirp)) != NULL)
658 if (!handle_dirent (dp, fltr, &st, &link_to_dir, &stale_link))
659 continue;
661 if (!dir_list_append (list, dp->d_name, &st, link_to_dir != 0, stale_link != 0))
662 goto ret;
664 if ((list->len & 31) == 0)
665 rotate_dash (TRUE);
668 dir_list_sort (list, sort, sort_op);
670 ret:
671 mc_closedir (dirp);
672 tree_store_end_check ();
673 rotate_dash (FALSE);
676 /* --------------------------------------------------------------------------------------------- */
678 gboolean
679 if_link_is_exe (const vfs_path_t * full_name_vpath, const file_entry_t * file)
681 struct stat b;
683 if (S_ISLNK (file->st.st_mode) && mc_stat (full_name_vpath, &b) == 0)
684 return is_exe (b.st_mode);
685 return TRUE;
688 /* --------------------------------------------------------------------------------------------- */
689 /** If fltr is null, then it is a match */
691 void
692 dir_list_reload (dir_list * list, const vfs_path_t * vpath, GCompareFunc sort,
693 const dir_sort_options_t * sort_op, const char *fltr)
695 DIR *dirp;
696 struct dirent *dp;
697 int i, link_to_dir, stale_link;
698 struct stat st;
699 int marked_cnt;
700 GHashTable *marked_files;
701 const char *tmp_path;
703 dirp = mc_opendir (vpath);
704 if (dirp == NULL)
706 message (D_ERROR, MSG_ERROR, _("Cannot read directory contents"));
707 dir_list_clean (list);
708 dir_list_init (list);
709 return;
712 tree_store_start_check (vpath);
714 marked_files = g_hash_table_new (g_str_hash, g_str_equal);
715 alloc_dir_copy (list->len);
716 for (marked_cnt = i = 0; i < list->len; i++)
718 file_entry_t *fentry, *dfentry;
720 fentry = &list->list[i];
721 dfentry = &dir_copy.list[i];
723 dfentry->fnamelen = fentry->fnamelen;
724 dfentry->fname = g_strndup (fentry->fname, fentry->fnamelen);
725 dfentry->f.marked = fentry->f.marked;
726 dfentry->f.dir_size_computed = fentry->f.dir_size_computed;
727 dfentry->f.link_to_dir = fentry->f.link_to_dir;
728 dfentry->f.stale_link = fentry->f.stale_link;
729 dfentry->sort_key = NULL;
730 dfentry->second_sort_key = NULL;
731 if (fentry->f.marked)
733 g_hash_table_insert (marked_files, dfentry->fname, dfentry);
734 marked_cnt++;
738 /* save len for later dir_list_clean() */
739 dir_copy.len = list->len;
741 /* Add ".." except to the root directory. The ".." entry
742 (if any) must be the first in the list. */
743 tmp_path = vfs_path_get_by_index (vpath, 0)->path;
744 if (vfs_path_elements_count (vpath) == 1 && IS_PATH_SEP (tmp_path[0]) && tmp_path[1] == '\0')
746 /* root directory */
747 dir_list_clean (list);
749 else
751 dir_list_clean (list);
752 if (!dir_list_init (list))
754 dir_list_clean (&dir_copy);
755 return;
758 if (dir_get_dotdot_stat (vpath, &st))
760 file_entry_t *fentry;
762 fentry = &list->list[0];
763 fentry->st = st;
767 while ((dp = mc_readdir (dirp)) != NULL)
769 file_entry_t *fentry;
771 if (!handle_dirent (dp, fltr, &st, &link_to_dir, &stale_link))
772 continue;
774 if (!dir_list_append (list, dp->d_name, &st, link_to_dir != 0, stale_link != 0))
776 mc_closedir (dirp);
777 /* Norbert (Feb 12, 1997):
778 Just in case someone finds this memory leak:
779 -1 means big trouble (at the moment no memory left),
780 I don't bother with further cleanup because if one gets to
781 this point he will have more problems than a few memory
782 leaks and because one 'dir_list_clean' would not be enough (and
783 because I don't want to spent the time to make it working,
784 IMHO it's not worthwhile).
785 dir_list_clean (&dir_copy);
787 tree_store_end_check ();
788 g_hash_table_destroy (marked_files);
789 return;
791 fentry = &list->list[list->len - 1];
793 fentry->f.marked = 0;
796 * If we have marked files in the copy, scan through the copy
797 * to find matching file. Decrease number of remaining marks if
798 * we copied one.
800 if (marked_cnt > 0 && g_hash_table_lookup (marked_files, dp->d_name) != NULL)
802 fentry->f.marked = 1;
803 marked_cnt--;
806 if ((list->len & 15) == 0)
807 rotate_dash (TRUE);
809 mc_closedir (dirp);
810 tree_store_end_check ();
811 g_hash_table_destroy (marked_files);
813 dir_list_sort (list, sort, sort_op);
815 dir_list_clean (&dir_copy);
816 rotate_dash (FALSE);
819 /* --------------------------------------------------------------------------------------------- */