Updated italian translation
[midnight-commander.git] / src / dir.c
blob18fd1995cfc20e4a4cd9c7797f5f7795f7da70dd
1 /* Directory routines
2 Copyright (C) 1994 Miguel de Icaza.
4 This program is free software; you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation; either version 2 of the License, or
7 (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
14 You should have received a copy of the GNU General Public License
15 along with this program; if not, write to the Free Software
16 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */
18 #include <config.h>
19 #include <string.h>
20 #include <stdio.h>
21 #include <stdlib.h>
22 #include <errno.h>
23 #include <sys/stat.h>
25 #include "global.h"
26 #include "tty.h"
27 #include "dir.h"
28 #include "wtools.h"
29 #include "treestore.h"
31 /* If true show files starting with a dot */
32 int show_dot_files = 1;
34 /* If true show files ending in ~ */
35 int show_backups = 1;
37 /* If false then directories are shown separately from files */
38 int mix_all_files = 0;
40 /* Reverse flag */
41 static int reverse = 1;
43 /* Are the files sorted case sensitively? */
44 static int case_sensitive = OS_SORT_CASE_SENSITIVE_DEFAULT;
46 #define MY_ISDIR(x) ( (S_ISDIR (x->st.st_mode) || x->f.link_to_dir) ? 1 : 0)
48 sort_orders_t sort_orders [SORT_TYPES_TOTAL] = {
49 { N_("&Unsorted"), unsorted },
50 { N_("&Name"), sort_name },
51 { N_("&Extension"), sort_ext },
52 { N_("&Modify time"), sort_time },
53 { N_("&Access time"), sort_atime },
54 { N_("&Change time"), sort_ctime },
55 { N_("&Size"), sort_size },
56 { N_("&Inode"), sort_inode },
58 /* New sort orders */
59 { N_("&Type"), sort_type },
60 { N_("&Links"), sort_links },
61 { N_("N&GID"), sort_ngid },
62 { N_("N&UID"), sort_nuid },
63 { N_("&Owner"), sort_owner },
64 { N_("&Group"), sort_group }
67 #ifdef HAVE_STRCOLL
69 * g_strcasecmp() doesn't work well in some locales because it relies on
70 * the locale-specific toupper(). On the other hand, strcoll() is case
71 * sensitive in the "C" and "POSIX" locales, unlike other locales.
72 * Solution: always use strcmp() for case sensitive sort. For case
73 * insensitive sort use strcoll() if it's case insensitive for ASCII and
74 * g_strcasecmp() otherwise.
76 typedef enum {
77 STRCOLL_NO,
78 STRCOLL_YES,
79 STRCOLL_TEST
80 } strcoll_status;
82 static int string_sortcomp (char *str1, char *str2)
84 static strcoll_status use_strcoll = STRCOLL_TEST;
86 if (case_sensitive) {
87 return strcmp (str1, str2);
90 /* Initialize use_strcoll once. */
91 if (use_strcoll == STRCOLL_TEST) {
92 /* Only use strcoll() if it considers "B" between "a" and "c". */
93 if (strcoll ("a", "B") * strcoll ("B", "c") > 0) {
94 use_strcoll = STRCOLL_YES;
95 } else {
96 use_strcoll = STRCOLL_NO;
100 if (use_strcoll == STRCOLL_NO)
101 return g_strcasecmp (str1, str2);
102 else
103 return strcoll (str1, str2);
105 #else
106 #define string_sortcomp(a,b) (case_sensitive ? strcmp (a,b) : g_strcasecmp (a,b))
107 #endif
110 unsorted (const file_entry *a, const file_entry *b)
112 return 0;
116 sort_name (const file_entry *a, const file_entry *b)
118 int ad = MY_ISDIR (a);
119 int bd = MY_ISDIR (b);
121 if (ad == bd || mix_all_files)
122 return string_sortcomp (a->fname, b->fname) * reverse;
123 return bd-ad;
127 sort_ext (const file_entry *a, const file_entry *b)
129 char *exta, *extb;
130 int r;
131 int ad = MY_ISDIR (a);
132 int bd = MY_ISDIR (b);
134 if (ad == bd || mix_all_files){
135 exta = extension (a->fname);
136 extb = extension (b->fname);
137 r = string_sortcomp (exta, extb);
138 if (r)
139 return r * reverse;
140 else
141 return sort_name (a, b);
142 } else
143 return bd-ad;
147 sort_owner (const file_entry *a, const file_entry *b)
149 int ad = MY_ISDIR (a);
150 int bd = MY_ISDIR (b);
152 if (ad == bd || mix_all_files)
153 return string_sortcomp (get_owner (a->st.st_uid), get_owner (a->st.st_uid)) * reverse;
154 return bd-ad;
158 sort_group (const file_entry *a, const file_entry *b)
160 int ad = MY_ISDIR (a);
161 int bd = MY_ISDIR (b);
163 if (ad == bd || mix_all_files)
164 return string_sortcomp (get_group (a->st.st_gid), get_group (a->st.st_gid)) * reverse;
165 return bd-ad;
169 sort_time (const file_entry *a, const file_entry *b)
171 int ad = MY_ISDIR (a);
172 int bd = MY_ISDIR (b);
174 if (ad == bd || mix_all_files)
175 return (a->st.st_mtime - b->st.st_mtime) * reverse;
176 else
177 return bd-ad;
181 sort_ctime (const file_entry *a, const file_entry *b)
183 int ad = MY_ISDIR (a);
184 int bd = MY_ISDIR (b);
186 if (ad == bd || mix_all_files)
187 return (a->st.st_ctime - b->st.st_ctime) * reverse;
188 else
189 return bd-ad;
193 sort_atime (const file_entry *a, const file_entry *b)
195 int ad = MY_ISDIR (a);
196 int bd = MY_ISDIR (b);
198 if (ad == bd || mix_all_files)
199 return (a->st.st_atime - b->st.st_atime) * reverse;
200 else
201 return bd-ad;
205 sort_inode (const file_entry *a, const file_entry *b)
207 int ad = MY_ISDIR (a);
208 int bd = MY_ISDIR (b);
210 if (ad == bd || mix_all_files)
211 return (a->st.st_ino - b->st.st_ino) * reverse;
212 else
213 return bd-ad;
217 sort_size (const file_entry *a, const file_entry *b)
219 int ad = MY_ISDIR (a);
220 int bd = MY_ISDIR (b);
222 if (ad != bd && !mix_all_files)
223 return bd - ad;
225 return (2 * (b->st.st_size > a->st.st_size) - 1) * reverse;
229 sort_links (const file_entry *a, const file_entry *b)
231 int ad = MY_ISDIR (a);
232 int bd = MY_ISDIR (b);
234 if (ad == bd || mix_all_files)
235 return (b->st.st_nlink - a->st.st_nlink) * reverse;
236 else
237 return bd-ad;
241 sort_ngid (const file_entry *a, const file_entry *b)
243 int ad = MY_ISDIR (a);
244 int bd = MY_ISDIR (b);
246 if (ad == bd || mix_all_files)
247 return (b->st.st_gid - a->st.st_gid) * reverse;
248 else
249 return bd-ad;
253 sort_nuid (const file_entry *a, const file_entry *b)
255 int ad = MY_ISDIR (a);
256 int bd = MY_ISDIR (b);
258 if (ad == bd || mix_all_files)
259 return (b->st.st_uid - a->st.st_uid) * reverse;
260 else
261 return bd-ad;
264 inline static int
265 file_type_to_num (const file_entry *fe)
267 const struct stat *s = &fe->st;
269 if (S_ISDIR (s->st_mode))
270 return 0;
271 if (S_ISLNK (s->st_mode)){
272 if (fe->f.link_to_dir)
273 return 1;
274 if (fe->f.stale_link)
275 return 2;
276 else
277 return 3;
279 if (S_ISSOCK (s->st_mode))
280 return 4;
281 if (S_ISCHR (s->st_mode))
282 return 5;
283 if (S_ISBLK (s->st_mode))
284 return 6;
285 if (S_ISFIFO (s->st_mode))
286 return 7;
287 if (is_exe (s->st_mode))
288 return 8;
289 return 9;
293 sort_type (const file_entry *a, const file_entry *b)
295 int aa = file_type_to_num (a);
296 int bb = file_type_to_num (b);
298 return bb-aa;
302 void
303 do_sort (dir_list *list, sortfn *sort, int top, int reverse_f, int case_sensitive_f)
305 int i;
306 int dot_dot_found = 0;
307 file_entry tmp_fe;
309 for (i = 0; i < top + 1; i++) { /* put ".." first in list */
310 if (!strcmp (list->list [i].fname, "..")) {
311 dot_dot_found = 1;
312 if (i > 0) { /* swap [i] and [0] */
313 memcpy (&tmp_fe, &(list->list [0]), sizeof (file_entry));
314 memcpy (&(list->list [0]), &(list->list [i]), sizeof (file_entry));
315 memcpy (&(list->list [i]), &tmp_fe, sizeof (file_entry));
317 break;
321 reverse = reverse_f ? -1 : 1;
322 case_sensitive = case_sensitive_f;
323 qsort (&(list->list) [dot_dot_found],
324 top + 1 - dot_dot_found, sizeof (file_entry), sort);
327 void
328 clean_dir (dir_list *list, int count)
330 int i;
332 for (i = 0; i < count; i++){
333 g_free (list->list [i].fname);
334 list->list [i].fname = 0;
338 static int
339 add_dotdot_to_list (dir_list *list, int index)
341 /* Need to grow the *list? */
342 if (index == list->size) {
343 list->list = g_realloc (list->list, sizeof (file_entry) *
344 (list->size + RESIZE_STEPS));
345 if (!list->list)
346 return 0;
347 list->size += RESIZE_STEPS;
350 memset (&(list->list) [index], 0, sizeof(file_entry));
351 (list->list) [index].fnamelen = 2;
352 (list->list) [index].fname = g_strdup ("..");
353 (list->list) [index].f.link_to_dir = 0;
354 (list->list) [index].f.stale_link = 0;
355 (list->list) [index].f.dir_size_computed = 0;
356 (list->list) [index].f.marked = 0;
357 (list->list) [index].st.st_mode = 040755;
358 return 1;
361 /* Used to set up a directory list when there is no access to a directory */
363 set_zero_dir (dir_list *list)
365 return (add_dotdot_to_list (list, 0));
368 /* If you change handle_dirent then check also handle_path. */
369 /* Return values: -1 = failure, 0 = don't add, 1 = add to the list */
370 static int
371 handle_dirent (dir_list *list, char *filter, struct dirent *dp,
372 struct stat *buf1, int next_free, int *link_to_dir,
373 int *stale_link)
375 if (dp->d_name[0] == '.' && dp->d_name[1] == 0)
376 return 0;
377 if (dp->d_name[0] == '.' && dp->d_name[1] == '.' && dp->d_name[2] == 0)
378 return 0;
379 if (!show_dot_files && (dp->d_name[0] == '.'))
380 return 0;
381 if (!show_backups && dp->d_name[NLENGTH (dp) - 1] == '~')
382 return 0;
383 if (mc_lstat (dp->d_name, buf1) == -1) {
385 * lstat() fails - such entries should be identified by
386 * buf1->st_mode being 0.
387 * It happens on QNX Neutrino for /fs/cd0 if no CD is inserted.
389 memset (buf1, 0, sizeof (*buf1));
392 if (S_ISDIR (buf1->st_mode))
393 tree_store_mark_checked (dp->d_name);
395 /* A link to a file or a directory? */
396 *link_to_dir = 0;
397 *stale_link = 0;
398 if (S_ISLNK (buf1->st_mode)) {
399 struct stat buf2;
400 if (!mc_stat (dp->d_name, &buf2))
401 *link_to_dir = S_ISDIR (buf2.st_mode) != 0;
402 else
403 *stale_link = 1;
405 if (!(S_ISDIR (buf1->st_mode) || *link_to_dir) && filter
406 && !regexp_match (filter, dp->d_name, match_file))
407 return 0;
409 /* Need to grow the *list? */
410 if (next_free == list->size) {
411 list->list =
412 g_realloc (list->list,
413 sizeof (file_entry) * (list->size + RESIZE_STEPS));
414 if (!list->list)
415 return -1;
416 list->size += RESIZE_STEPS;
418 return 1;
421 /* handle_path is a simplified handle_dirent. The difference is that
422 handle_path doesn't pay attention to show_dot_files and show_backups.
423 Moreover handle_path can't be used with a filemask.
424 If you change handle_path then check also handle_dirent. */
425 /* Return values: -1 = failure, 0 = don't add, 1 = add to the list */
427 handle_path (dir_list *list, char *path,
428 struct stat *buf1, int next_free, int *link_to_dir,
429 int *stale_link)
431 if (path [0] == '.' && path [1] == 0)
432 return 0;
433 if (path [0] == '.' && path [1] == '.' && path [2] == 0)
434 return 0;
435 if (mc_lstat (path, buf1) == -1)
436 return 0;
438 if (S_ISDIR (buf1->st_mode))
439 tree_store_mark_checked (path);
441 /* A link to a file or a directory? */
442 *link_to_dir = 0;
443 *stale_link = 0;
444 if (S_ISLNK(buf1->st_mode)){
445 struct stat buf2;
446 if (!mc_stat (path, &buf2))
447 *link_to_dir = S_ISDIR(buf2.st_mode) != 0;
448 else
449 *stale_link = 1;
452 /* Need to grow the *list? */
453 if (next_free == list->size){
454 list->list = g_realloc (list->list, sizeof (file_entry) *
455 (list->size + RESIZE_STEPS));
456 if (!list->list)
457 return -1;
458 list->size += RESIZE_STEPS;
460 return 1;
464 do_load_dir (char *path, dir_list *list, sortfn *sort, int reverse,
465 int case_sensitive, char *filter)
467 DIR *dirp;
468 struct dirent *dp;
469 int status, link_to_dir, stale_link;
470 int next_free = 0;
471 struct stat st;
473 dirp = mc_opendir (path);
474 if (!dirp) {
475 message (1, MSG_ERROR, _("Cannot read directory contents"));
476 return set_zero_dir (list);
478 tree_store_start_check (path);
479 while ((dp = mc_readdir (dirp))) {
480 status =
481 handle_dirent (list, filter, dp, &st, next_free, &link_to_dir,
482 &stale_link);
483 if (status == 0)
484 continue;
485 if (status == -1) {
486 tree_store_end_check ();
487 mc_closedir (dirp);
488 return next_free;
490 list->list[next_free].fnamelen = NLENGTH (dp);
491 list->list[next_free].fname = g_strdup (dp->d_name);
492 list->list[next_free].f.marked = 0;
493 list->list[next_free].f.link_to_dir = link_to_dir;
494 list->list[next_free].f.stale_link = stale_link;
495 list->list[next_free].f.dir_size_computed = 0;
496 list->list[next_free].st = st;
497 next_free++;
498 if (!(next_free % 32))
499 rotate_dash ();
502 if (next_free) {
503 /* Add ".." except the root directory */
504 if (strcmp (path, "/") != 0)
505 add_dotdot_to_list (list, next_free++);
506 do_sort (list, sort, next_free - 1, reverse, case_sensitive);
507 } else {
508 next_free = set_zero_dir (list);
511 mc_closedir (dirp);
512 tree_store_end_check ();
513 return next_free;
517 link_isdir (const file_entry *file)
519 if (file->f.link_to_dir)
520 return 1;
521 else
522 return 0;
526 if_link_is_exe (const char *full_name, const file_entry *file)
528 struct stat b;
530 if (S_ISLNK (file->st.st_mode) && !mc_stat (full_name, &b)) {
531 return is_exe (b.st_mode);
532 } else
533 return 1;
536 static dir_list dir_copy = { 0, 0 };
538 static void
539 alloc_dir_copy (int size)
541 int i;
543 if (dir_copy.size < size){
544 if (dir_copy.list){
546 for (i = 0; i < dir_copy.size; i++) {
547 if (dir_copy.list [i].fname)
548 g_free (dir_copy.list [i].fname);
550 g_free (dir_copy.list);
551 dir_copy.list = 0;
554 dir_copy.list = g_new (file_entry, size);
555 for (i = 0; i < size; i++)
556 dir_copy.list [i].fname = 0;
558 dir_copy.size = size;
562 /* If filter is null, then it is a match */
564 do_reload_dir (char *path, dir_list *list, sortfn *sort, int count,
565 int rev, int case_sensitive, char *filter)
567 DIR *dirp;
568 struct dirent *dp;
569 int next_free = 0;
570 int i, status, link_to_dir, stale_link;
571 struct stat st;
572 int marked_cnt;
573 GHashTable *marked_files;
575 dirp = mc_opendir (path);
576 if (!dirp) {
577 message (1, MSG_ERROR, _("Cannot read directory contents"));
578 clean_dir (list, count);
579 return set_zero_dir (list);
582 tree_store_start_check (path);
583 marked_files = g_hash_table_new (g_str_hash, g_str_equal);
584 alloc_dir_copy (list->size);
585 for (marked_cnt = i = 0; i < count; i++) {
586 dir_copy.list[i].fnamelen = list->list[i].fnamelen;
587 dir_copy.list[i].fname = list->list[i].fname;
588 dir_copy.list[i].f.marked = list->list[i].f.marked;
589 dir_copy.list[i].f.dir_size_computed =
590 list->list[i].f.dir_size_computed;
591 dir_copy.list[i].f.link_to_dir = list->list[i].f.link_to_dir;
592 dir_copy.list[i].f.stale_link = list->list[i].f.stale_link;
593 if (list->list[i].f.marked) {
594 g_hash_table_insert (marked_files, dir_copy.list[i].fname,
595 &dir_copy.list[i]);
596 marked_cnt++;
600 while ((dp = mc_readdir (dirp))) {
601 status =
602 handle_dirent (list, filter, dp, &st, next_free, &link_to_dir,
603 &stale_link);
604 if (status == 0)
605 continue;
606 if (status == -1) {
607 mc_closedir (dirp);
608 /* Norbert (Feb 12, 1997):
609 Just in case someone finds this memory leak:
610 -1 means big trouble (at the moment no memory left),
611 I don't bother with further cleanup because if one gets to
612 this point he will have more problems than a few memory
613 leaks and because one 'clean_dir' would not be enough (and
614 because I don't want to spent the time to make it working,
615 IMHO it's not worthwhile).
616 clean_dir (&dir_copy, count);
618 tree_store_end_check ();
619 g_hash_table_destroy (marked_files);
620 return next_free;
623 list->list[next_free].f.marked = 0;
626 * If we have marked files in the copy, scan through the copy
627 * to find matching file. Decrease number of remaining marks if
628 * we copied one.
630 if (marked_cnt > 0) {
631 if ((g_hash_table_lookup (marked_files, dp->d_name))) {
632 list->list[next_free].f.marked = 1;
633 marked_cnt--;
637 list->list[next_free].fnamelen = NLENGTH (dp);
638 list->list[next_free].fname = g_strdup (dp->d_name);
639 list->list[next_free].f.link_to_dir = link_to_dir;
640 list->list[next_free].f.stale_link = stale_link;
641 list->list[next_free].f.dir_size_computed = 0;
642 list->list[next_free].st = st;
643 next_free++;
644 if (!(next_free % 16))
645 rotate_dash ();
647 mc_closedir (dirp);
648 tree_store_end_check ();
649 g_hash_table_destroy (marked_files);
650 if (next_free) {
651 /* Add ".." except the root directory */
652 if (strcmp (path, "/") != 0)
653 add_dotdot_to_list (list, next_free++);
654 do_sort (list, sort, next_free - 1, rev, case_sensitive);
655 } else
656 next_free = set_zero_dir (list);
657 clean_dir (&dir_copy, count);
658 return next_free;