Rename type file_entry to the file_entry_t.
[midnight-commander.git] / src / filemanager / dir.c
blob79a99198b255091ee0c611f95eecb998fd05e82c
1 /*
2 Directory routines
4 Copyright (C) 1994, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005,
5 2006, 2007, 2011, 2013
6 The Free Software Foundation, Inc.
8 Written by:
9 Slava Zanko <slavazanko@gmail.com>, 2013
10 Andrew Borodin <aborodin@vmail.ru>, 2013
12 This file is part of the Midnight Commander.
14 The Midnight Commander is free software: you can redistribute it
15 and/or modify it under the terms of the GNU General Public License as
16 published by the Free Software Foundation, either version 3 of the License,
17 or (at your option) any later version.
19 The Midnight Commander is distributed in the hope that it will be useful,
20 but WITHOUT ANY WARRANTY; without even the implied warranty of
21 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 GNU General Public License for more details.
24 You should have received a copy of the GNU General Public License
25 along with this program. If not, see <http://www.gnu.org/licenses/>.
28 /** \file src/filemanager/dir.c
29 * \brief Source: directory routines
32 #include <config.h>
34 #include <stdio.h>
35 #include <stdlib.h>
36 #include <string.h>
37 #include <sys/stat.h>
39 #include "lib/global.h"
40 #include "lib/tty/tty.h"
41 #include "lib/search.h"
42 #include "lib/vfs/vfs.h"
43 #include "lib/fs.h"
44 #include "lib/strutil.h"
45 #include "lib/util.h"
46 #include "lib/widget.h" /* message() */
48 #include "src/setup.h" /* panels_options */
50 #include "treestore.h"
51 #include "dir.h"
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) || x->f.link_to_dir) && exec_first) \
60 ? 1 \
61 : ( (S_ISDIR (x->st.st_mode) || x->f.link_to_dir) ? 2 : 0) )
63 /*** file scope type declarations ****************************************************************/
65 /*** file scope variables ************************************************************************/
67 /* Reverse flag */
68 static int reverse = 1;
70 /* Are the files sorted case sensitively? */
71 static int 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 },
94 static inline int
95 key_collate (const char *t1, const char *t2)
97 int dotdot = 0;
98 int ret;
100 dotdot = (t1[0] == '.' ? 1 : 0) | ((t2[0] == '.' ? 1 : 0) << 1);
102 switch (dotdot)
104 case 0:
105 case 3:
106 ret = str_key_collate (t1, t2, case_sensitive) * reverse;
107 break;
108 case 1:
109 ret = -1; /* t1 < t2 */
110 break;
111 case 2:
112 ret = 1; /* t1 > t2 */
113 break;
114 default:
115 ret = 0; /* it must not happen */
118 return ret;
121 /* --------------------------------------------------------------------------------------------- */
123 * clear keys, should be call after sorting is finished.
126 static void
127 clean_sort_keys (dir_list * list, int start, int count)
129 int i;
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
149 static gboolean
150 handle_dirent (struct dirent *dp, const char *fltr, struct stat *buf1, int *link_to_dir,
151 int *stale_link)
153 vfs_path_t *vpath;
155 if (DIR_IS_DOT (dp->d_name) || DIR_IS_DOTDOT (dp->d_name))
156 return FALSE;
157 if (!panels_options.show_dot_files && (dp->d_name[0] == '.'))
158 return FALSE;
159 if (!panels_options.show_backups && dp->d_name[strlen (dp->d_name) - 1] == '~')
160 return FALSE;
162 vpath = vfs_path_from_str (dp->d_name);
163 if (mc_lstat (vpath, buf1) == -1)
166 * lstat() fails - such entries should be identified by
167 * buf1->st_mode being 0.
168 * It happens on QNX Neutrino for /fs/cd0 if no CD is inserted.
170 memset (buf1, 0, sizeof (*buf1));
173 if (S_ISDIR (buf1->st_mode))
174 tree_store_mark_checked (dp->d_name);
176 /* A link to a file or a directory? */
177 *link_to_dir = 0;
178 *stale_link = 0;
179 if (S_ISLNK (buf1->st_mode))
181 struct stat buf2;
183 if (mc_stat (vpath, &buf2) == 0)
184 *link_to_dir = S_ISDIR (buf2.st_mode) != 0;
185 else
186 *stale_link = 1;
189 vfs_path_free (vpath);
191 return (S_ISDIR (buf1->st_mode) || *link_to_dir != 0 || fltr == NULL
192 || mc_search (fltr, dp->d_name, MC_SEARCH_T_GLOB));
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, "..", NULL);
213 ret = mc_stat (tmp_vpath, st) == 0;
214 vfs_path_free (tmp_vpath);
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)
230 int i;
232 for (i = 0; i < dir_copy.len; i++)
234 file_entry_t *fentry;
236 fentry = &(dir_copy.list)[i];
237 g_free (fentry->fname);
239 g_free (dir_copy.list);
242 dir_copy.list = g_new0 (file_entry_t, size);
243 dir_copy.size = size;
244 dir_copy.len = 0;
248 /* --------------------------------------------------------------------------------------------- */
249 /*** public functions ****************************************************************************/
250 /* --------------------------------------------------------------------------------------------- */
252 * Increase or decrease directory list size.
254 * @param list directory list
255 * @param delta value by increase (if positive) or decrease (if negative) list size
257 * @return FALSE on failure, TRUE on success
260 gboolean
261 dir_list_grow (dir_list * list, int delta)
263 int size;
264 gboolean clear = FALSE;
266 if (list == NULL)
267 return FALSE;
269 if (delta == 0)
270 return TRUE;
272 size = list->size + delta;
273 if (size <= 0)
275 size = DIR_LIST_MIN_SIZE;
276 clear = TRUE;
279 if (size != list->size)
281 file_entry_t *fe;
283 fe = g_try_renew (file_entry_t, list->list, size);
284 if (fe == NULL)
285 return FALSE;
287 list->list = fe;
288 list->size = size;
291 list->len = clear ? 0 : min (list->len, size);
293 return TRUE;
296 /* --------------------------------------------------------------------------------------------- */
298 * Append file info to the directory list.
300 * @param list directory list
301 * @param fname file name
302 * @param st file stat info
303 * @param link_to_dir is file link to directory
304 * @param stale_link is file stale elink
306 * @return FALSE on failure, TRUE on success
309 gboolean
310 dir_list_append (dir_list * list, const char *fname, const struct stat * st,
311 gboolean link_to_dir, gboolean stale_link)
313 file_entry_t *fentry;
315 /* Need to grow the *list? */
316 if (list->len == list->size && !dir_list_grow (list, DIR_LIST_RESIZE_STEP))
317 return FALSE;
319 fentry = &list->list[list->len];
320 fentry->fnamelen = strlen (fname);
321 fentry->fname = g_strndup (fname, fentry->fnamelen);
322 fentry->f.marked = 0;
323 fentry->f.link_to_dir = link_to_dir ? 1 : 0;
324 fentry->f.stale_link = stale_link ? 1 : 0;
325 fentry->f.dir_size_computed = 0;
326 fentry->st = *st;
327 fentry->sort_key = NULL;
328 fentry->second_sort_key = NULL;
330 list->len++;
332 return TRUE;
335 /* --------------------------------------------------------------------------------------------- */
338 unsorted (file_entry_t * a, file_entry_t * b)
340 (void) a;
341 (void) b;
342 return 0;
345 /* --------------------------------------------------------------------------------------------- */
348 sort_name (file_entry_t * a, file_entry_t * b)
350 int ad = MY_ISDIR (a);
351 int bd = MY_ISDIR (b);
353 if (ad == bd || panels_options.mix_all_files)
355 /* create key if does not exist, key will be freed after sorting */
356 if (a->sort_key == NULL)
357 a->sort_key = str_create_key_for_filename (a->fname, case_sensitive);
358 if (b->sort_key == NULL)
359 b->sort_key = str_create_key_for_filename (b->fname, case_sensitive);
361 return key_collate (a->sort_key, b->sort_key);
363 return bd - ad;
366 /* --------------------------------------------------------------------------------------------- */
369 sort_vers (file_entry_t * a, file_entry_t * b)
371 int ad = MY_ISDIR (a);
372 int bd = MY_ISDIR (b);
374 if (ad == bd || panels_options.mix_all_files)
376 return str_verscmp (a->fname, b->fname) * reverse;
378 else
380 return bd - ad;
384 /* --------------------------------------------------------------------------------------------- */
387 sort_ext (file_entry_t * a, file_entry_t * b)
389 int r;
390 int ad = MY_ISDIR (a);
391 int bd = MY_ISDIR (b);
393 if (ad == bd || panels_options.mix_all_files)
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)
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 g_free (fentry->fname);
541 fentry->fname = NULL;
544 list->len = 0;
545 /* reduce memory usage */
546 dir_list_grow (list, DIR_LIST_MIN_SIZE - list->size);
549 /* --------------------------------------------------------------------------------------------- */
550 /** Used to set up a directory list when there is no access to a directory */
552 gboolean
553 dir_list_init (dir_list * list)
555 file_entry_t *fentry;
557 /* Need to grow the *list? */
558 if (list->size == 0 && !dir_list_grow (list, DIR_LIST_RESIZE_STEP))
560 list->len = 0;
561 return FALSE;
564 fentry = &list->list[0];
565 memset (fentry, 0, sizeof (file_entry_t));
566 fentry->fnamelen = 2;
567 fentry->fname = g_strndup ("..", fentry->fnamelen);
568 fentry->f.link_to_dir = 0;
569 fentry->f.stale_link = 0;
570 fentry->f.dir_size_computed = 0;
571 fentry->f.marked = 0;
572 fentry->st.st_mode = 040755;
573 list->len = 1;
574 return TRUE;
577 /* --------------------------------------------------------------------------------------------- */
579 handle_path is a simplified handle_dirent. The difference is that
580 handle_path doesn't pay attention to panels_options.show_dot_files
581 and panels_options.show_backups.
582 Moreover handle_path can't be used with a filemask.
583 If you change handle_path then check also handle_dirent. */
584 /* Return values: FALSE = don't add, TRUE = add to the list */
586 gboolean
587 handle_path (const char *path, struct stat * buf1, int *link_to_dir, int *stale_link)
589 vfs_path_t *vpath;
591 if (DIR_IS_DOT (path) || DIR_IS_DOTDOT (path))
592 return FALSE;
594 vpath = vfs_path_from_str (path);
595 if (mc_lstat (vpath, buf1) == -1)
597 vfs_path_free (vpath);
598 return FALSE;
601 if (S_ISDIR (buf1->st_mode))
602 tree_store_mark_checked (path);
604 /* A link to a file or a directory? */
605 *link_to_dir = 0;
606 *stale_link = 0;
607 if (S_ISLNK (buf1->st_mode))
609 struct stat buf2;
611 if (mc_stat (vpath, &buf2) == 0)
612 *link_to_dir = S_ISDIR (buf2.st_mode) != 0;
613 else
614 *stale_link = 1;
617 vfs_path_free (vpath);
619 return TRUE;
622 /* --------------------------------------------------------------------------------------------- */
624 void
625 dir_list_load (dir_list * list, const vfs_path_t * vpath, GCompareFunc sort,
626 const dir_sort_options_t * sort_op, const char *fltr)
628 DIR *dirp;
629 struct dirent *dp;
630 int link_to_dir, stale_link;
631 struct stat st;
632 file_entry_t *fentry;
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);
652 const char *vpath_str;
654 vpath_str = vfs_path_as_str (vpath);
655 /* Do not add a ".." entry to the root directory */
656 if ((vpath_str[0] == PATH_SEP) && (vpath_str[1] == '\0'))
657 list->len--;
660 while ((dp = mc_readdir (dirp)) != NULL)
662 if (!handle_dirent (dp, fltr, &st, &link_to_dir, &stale_link))
663 continue;
665 if (!dir_list_append (list, dp->d_name, &st, link_to_dir != 0, stale_link != 0))
666 goto ret;
668 if ((list->len & 31) == 0)
669 rotate_dash (TRUE);
672 dir_list_sort (list, sort, sort_op);
674 ret:
675 mc_closedir (dirp);
676 tree_store_end_check ();
677 rotate_dash (FALSE);
680 /* --------------------------------------------------------------------------------------------- */
682 gboolean
683 if_link_is_exe (const vfs_path_t * full_name_vpath, const file_entry_t * file)
685 struct stat b;
687 if (S_ISLNK (file->st.st_mode) && mc_stat (full_name_vpath, &b) == 0)
688 return is_exe (b.st_mode);
689 return TRUE;
692 /* --------------------------------------------------------------------------------------------- */
693 /** If fltr is null, then it is a match */
695 void
696 dir_list_reload (dir_list * list, const vfs_path_t * vpath, GCompareFunc sort,
697 const dir_sort_options_t * sort_op, const char *fltr)
699 DIR *dirp;
700 struct dirent *dp;
701 int i, link_to_dir, stale_link;
702 struct stat st;
703 int marked_cnt;
704 GHashTable *marked_files;
705 const char *tmp_path;
707 dirp = mc_opendir (vpath);
708 if (dirp == NULL)
710 message (D_ERROR, MSG_ERROR, _("Cannot read directory contents"));
711 dir_list_clean (list);
712 dir_list_init (list);
713 return;
716 tree_store_start_check (vpath);
718 marked_files = g_hash_table_new (g_str_hash, g_str_equal);
719 alloc_dir_copy (list->len);
720 for (marked_cnt = i = 0; i < list->len; i++)
722 file_entry_t *fentry, *dfentry;
724 fentry = &list->list[i];
725 dfentry = &dir_copy.list[i];
727 dfentry->fnamelen = fentry->fnamelen;
728 dfentry->fname = fentry->fname;
729 dfentry->f.marked = fentry->f.marked;
730 dfentry->f.dir_size_computed = fentry->f.dir_size_computed;
731 dfentry->f.link_to_dir = fentry->f.link_to_dir;
732 dfentry->f.stale_link = fentry->f.stale_link;
733 dfentry->sort_key = NULL;
734 dfentry->second_sort_key = NULL;
735 if (fentry->f.marked)
737 g_hash_table_insert (marked_files, dfentry->fname, dfentry);
738 marked_cnt++;
742 /* Add ".." except to the root directory. The ".." entry
743 (if any) must be the first in the list. */
744 tmp_path = vfs_path_get_by_index (vpath, 0)->path;
745 if (!
746 (vfs_path_elements_count (vpath) == 1 && (tmp_path[0] == PATH_SEP)
747 && (tmp_path[1] == '\0')))
749 if (!dir_list_init (list))
751 dir_list_clean (&dir_copy);
752 return;
755 if (dir_get_dotdot_stat (vpath, &st))
757 file_entry_t *fentry;
759 fentry = &list->list[0];
760 fentry->st = st;
764 while ((dp = mc_readdir (dirp)) != NULL)
766 file_entry_t *fentry;
767 if (!handle_dirent (dp, fltr, &st, &link_to_dir, &stale_link))
768 continue;
770 if (!dir_list_append (list, dp->d_name, &st, link_to_dir != 0, stale_link != 0))
772 mc_closedir (dirp);
773 /* Norbert (Feb 12, 1997):
774 Just in case someone finds this memory leak:
775 -1 means big trouble (at the moment no memory left),
776 I don't bother with further cleanup because if one gets to
777 this point he will have more problems than a few memory
778 leaks and because one 'dir_list_clean' would not be enough (and
779 because I don't want to spent the time to make it working,
780 IMHO it's not worthwhile).
781 dir_list_clean (&dir_copy);
783 tree_store_end_check ();
784 g_hash_table_destroy (marked_files);
785 return;
787 fentry = &list->list[list->len - 1];
789 fentry->f.marked = 0;
792 * If we have marked files in the copy, scan through the copy
793 * to find matching file. Decrease number of remaining marks if
794 * we copied one.
796 if (marked_cnt > 0 && g_hash_table_lookup (marked_files, dp->d_name) != NULL)
798 fentry->f.marked = 1;
799 marked_cnt--;
802 if ((list->len & 15) == 0)
803 rotate_dash (TRUE);
805 mc_closedir (dirp);
806 tree_store_end_check ();
807 g_hash_table_destroy (marked_files);
809 dir_list_sort (list, sort, sort_op);
811 dir_list_clean (&dir_copy);
812 rotate_dash (FALSE);
815 /* --------------------------------------------------------------------------------------------- */