Update translations from Transifex
[midnight-commander.git] / src / filemanager / dir.c
blob6474aa2f299079eaa8ff82da4837766cd97a25cc
1 /*
2 Directory routines
4 Copyright (C) 1994-2019
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 "file.h" /* file_is_symlink_to_dir() */
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) || link_isdir (x)) && exec_first) \
60 ? 1 \
61 : ( (S_ISDIR (x->st.st_mode) || link_isdir (x)) ? 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 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 },
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;
154 gboolean stale;
156 if (DIR_IS_DOT (dp->d_name) || DIR_IS_DOTDOT (dp->d_name))
157 return FALSE;
158 if (!panels_options.show_dot_files && (dp->d_name[0] == '.'))
159 return FALSE;
160 if (!panels_options.show_backups && dp->d_name[strlen (dp->d_name) - 1] == '~')
161 return FALSE;
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 ".." */
190 static gboolean
191 dir_get_dotdot_stat (const vfs_path_t * vpath, struct stat *st)
193 gboolean ret = FALSE;
195 if ((vpath != NULL) && (st != NULL))
197 const char *path;
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);
210 return ret;
213 /* --------------------------------------------------------------------------------------------- */
215 static void
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;
225 dir_copy.len = 0;
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
241 gboolean
242 dir_list_grow (dir_list * list, int delta)
244 int size;
245 gboolean clear_flag = FALSE;
247 if (list == NULL)
248 return FALSE;
250 if (delta == 0)
251 return TRUE;
253 size = list->size + delta;
254 if (size <= 0)
256 size = DIR_LIST_MIN_SIZE;
257 clear_flag = TRUE;
260 if (size != list->size)
262 file_entry_t *fe;
264 fe = g_try_renew (file_entry_t, list->list, size);
265 if (fe == NULL)
266 return FALSE;
268 list->list = fe;
269 list->size = size;
272 list->len = clear_flag ? 0 : MIN (list->len, size);
274 return TRUE;
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
290 gboolean
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))
298 return FALSE;
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;
307 fentry->st = *st;
308 fentry->sort_key = NULL;
309 fentry->second_sort_key = NULL;
311 list->len++;
313 return TRUE;
316 /* --------------------------------------------------------------------------------------------- */
319 unsorted (file_entry_t * a, file_entry_t * b)
321 (void) a;
322 (void) b;
323 return 0;
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);
344 return bd - ad;
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;
359 else
361 return bd - ad;
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)
375 int r;
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);
383 if (r != 0)
384 return r * reverse;
385 else
386 return sort_name (a, b);
388 else
389 return bd - ad;
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;
403 if (result != 0)
404 return result * reverse;
405 else
406 return sort_name (a, b);
408 else
409 return bd - ad;
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;
423 if (result != 0)
424 return result * reverse;
425 else
426 return sort_name (a, b);
428 else
429 return bd - ad;
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;
443 if (result != 0)
444 return result * reverse;
445 else
446 return sort_name (a, b);
448 else
449 return bd - ad;
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;
462 else
463 return bd - ad;
466 /* --------------------------------------------------------------------------------------------- */
469 sort_size (file_entry_t * a, file_entry_t * b)
471 int ad = MY_ISDIR (a);
472 int bd = MY_ISDIR (b);
473 int result = 0;
475 if (ad != bd && !panels_options.mix_all_files)
476 return bd - ad;
478 result = a->st.st_size < b->st.st_size ? -1 : a->st.st_size > b->st.st_size;
479 if (result != 0)
480 return result * reverse;
481 else
482 return sort_name (a, b);
485 /* --------------------------------------------------------------------------------------------- */
487 void
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)
494 return;
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))
500 dot_dot_found = 1;
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 /* --------------------------------------------------------------------------------------------- */
512 void
513 dir_list_clean (dir_list * list)
515 int i;
517 for (i = 0; i < list->len; i++)
519 file_entry_t *fentry;
521 fentry = &list->list[i];
522 MC_PTR_FREE (fentry->fname);
525 list->len = 0;
526 /* reduce memory usage */
527 dir_list_grow (list, DIR_LIST_MIN_SIZE - list->size);
530 /* --------------------------------------------------------------------------------------------- */
532 void
533 dir_list_free_list (dir_list * list)
535 int i;
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);
546 list->len = 0;
547 list->size = 0;
550 /* --------------------------------------------------------------------------------------------- */
551 /** Used to set up a directory list when there is no access to a directory */
553 gboolean
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))
561 list->len = 0;
562 return FALSE;
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;
574 list->len = 1;
575 return TRUE;
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 */
587 gboolean
588 handle_path (const char *path, struct stat * buf1, int *link_to_dir, int *stale_link)
590 vfs_path_t *vpath;
592 if (DIR_IS_DOT (path) || DIR_IS_DOTDOT (path))
593 return FALSE;
595 vpath = vfs_path_from_str (path);
596 if (mc_lstat (vpath, buf1) == -1)
598 vfs_path_free (vpath);
599 return FALSE;
602 if (S_ISDIR (buf1->st_mode))
603 tree_store_mark_checked (path);
605 /* A link to a file or a directory? */
606 *link_to_dir = 0;
607 *stale_link = 0;
608 if (S_ISLNK (buf1->st_mode))
610 struct stat buf2;
612 if (mc_stat (vpath, &buf2) == 0)
613 *link_to_dir = S_ISDIR (buf2.st_mode) != 0;
614 else
615 *stale_link = 1;
618 vfs_path_free (vpath);
620 return TRUE;
623 /* --------------------------------------------------------------------------------------------- */
625 void
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)
629 DIR *dirp;
630 struct dirent *dp;
631 int link_to_dir, stale_link;
632 struct stat st;
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))
638 return;
640 fentry = &list->list[0];
641 if (dir_get_dotdot_stat (vpath, &st))
642 fentry->st = st;
644 dirp = mc_opendir (vpath);
645 if (dirp == NULL)
647 message (D_ERROR, MSG_ERROR, _("Cannot read directory contents"));
648 return;
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))
661 continue;
663 if (!dir_list_append (list, dp->d_name, &st, link_to_dir != 0, stale_link != 0))
664 goto ret;
666 if ((list->len & 31) == 0)
667 rotate_dash (TRUE);
670 dir_list_sort (list, sort, sort_op);
672 ret:
673 mc_closedir (dirp);
674 tree_store_end_check ();
675 rotate_dash (FALSE);
678 /* --------------------------------------------------------------------------------------------- */
680 gboolean
681 if_link_is_exe (const vfs_path_t * full_name_vpath, const file_entry_t * file)
683 struct stat b;
685 if (S_ISLNK (file->st.st_mode) && mc_stat (full_name_vpath, &b) == 0)
686 return is_exe (b.st_mode);
687 return TRUE;
690 /* --------------------------------------------------------------------------------------------- */
691 /** If fltr is null, then it is a match */
693 void
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)
697 DIR *dirp;
698 struct dirent *dp;
699 int i, link_to_dir, stale_link;
700 struct stat st;
701 int marked_cnt;
702 GHashTable *marked_files;
703 const char *tmp_path;
705 dirp = mc_opendir (vpath);
706 if (dirp == NULL)
708 message (D_ERROR, MSG_ERROR, _("Cannot read directory contents"));
709 dir_list_clean (list);
710 dir_list_init (list);
711 return;
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);
736 marked_cnt++;
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')
748 /* root directory */
749 dir_list_clean (list);
751 else
753 dir_list_clean (list);
754 if (!dir_list_init (list))
756 dir_list_free_list (&dir_copy);
757 return;
760 if (dir_get_dotdot_stat (vpath, &st))
762 file_entry_t *fentry;
764 fentry = &list->list[0];
765 fentry->st = st;
769 while ((dp = mc_readdir (dirp)) != NULL)
771 file_entry_t *fentry;
773 if (!handle_dirent (dp, fltr, &st, &link_to_dir, &stale_link))
774 continue;
776 if (!dir_list_append (list, dp->d_name, &st, link_to_dir != 0, stale_link != 0))
778 mc_closedir (dirp);
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);
791 return;
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
800 * we copied one.
802 if (marked_cnt > 0 && g_hash_table_lookup (marked_files, dp->d_name) != NULL)
804 fentry->f.marked = 1;
805 marked_cnt--;
808 if ((list->len & 15) == 0)
809 rotate_dash (TRUE);
811 mc_closedir (dirp);
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);
818 rotate_dash (FALSE);
821 /* --------------------------------------------------------------------------------------------- */