small typo
[midnight-commander.git] / src / dir.c
blob3a54d32caaf7b87692ddf3fd257a209193025483
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 #define DIR_H_INCLUDE_HANDLE_DIRENT
20 #include <string.h>
21 #include <stdio.h>
22 #include <stdlib.h>
23 #include <errno.h>
24 #include <sys/stat.h>
26 #include "global.h"
27 #include "tty.h"
28 #include "dir.h"
29 #include "dialog.h"
30 #include "tree.h"
31 #include "../vfs/vfs.h"
33 /* "$Id$" */
35 /* If true show files starting with a dot */
36 int show_dot_files = 1;
38 /* If true show files ending in ~ */
39 int show_backups = 1;
41 /* If false then directories are shown separately from files */
42 int mix_all_files = 0;
44 /* Reverse flag */
45 static int reverse = 1;
47 /* Are the files sorted case sensitively? */
48 static int case_sensitive = OS_SORT_CASE_SENSITIVE_DEFAULT;
50 #define MY_ISDIR(x) ( (S_ISDIR (x->buf.st_mode) || x->f.link_to_dir) ? 1 : 0)
52 sort_orders_t sort_orders [SORT_TYPES_TOTAL] = {
53 { N_("&Unsorted"), unsorted },
54 { N_("&Name"), sort_name },
55 { N_("&Extension"), sort_ext },
56 { N_("&Modify time"), sort_time },
57 { N_("&Access time"), sort_atime },
58 { N_("&Change time"), sort_ctime },
59 { N_("&Size"), sort_size },
60 { N_("&Inode"), sort_inode },
62 /* New sort orders */
63 { N_("&Type"), sort_type },
64 { N_("&Links"), sort_links },
65 { N_("N&GID"), sort_ngid },
66 { N_("N&UID"), sort_nuid },
67 { N_("&Owner"), sort_owner },
68 { N_("&Group"), sort_group }
71 #ifdef HAVE_STRCOLL
73 * g_strcasecmp() doesn't work well in some locales because it relies on
74 * the locale-specific toupper(). On the other hand, strcoll() is case
75 * sensitive in the "C" and "POSIX" locales, unlike other locales.
76 * Solution: always use strcmp() for case sensitive sort. For case
77 * insensitive sort use strcoll() if it's case insensitive for ASCII and
78 * g_strcasecmp() otherwise.
80 typedef enum {
81 STRCOLL_NO,
82 STRCOLL_YES,
83 STRCOLL_TEST
84 } strcoll_status;
86 static int string_sortcomp (char *str1, char *str2)
88 static strcoll_status use_strcoll = STRCOLL_TEST;
90 if (case_sensitive) {
91 return strcmp (str1, str2);
94 /* Initialize use_strcoll once. */
95 if (use_strcoll == STRCOLL_TEST) {
96 /* Only use strcoll() if it considers "B" between "a" and "c". */
97 if (strcoll ("a", "B") * strcoll ("B", "c") > 0) {
98 use_strcoll = STRCOLL_YES;
99 } else {
100 use_strcoll = STRCOLL_NO;
104 if (use_strcoll == STRCOLL_NO)
105 return g_strcasecmp (str1, str2);
106 else
107 return strcoll (str1, str2);
109 #else
110 #define string_sortcomp(a,b) (case_sensitive ? strcmp (a,b) : g_strcasecmp (a,b))
111 #endif
114 unsorted (const file_entry *a, const file_entry *b)
116 return 0;
120 sort_name (const file_entry *a, const file_entry *b)
122 int ad = MY_ISDIR (a);
123 int bd = MY_ISDIR (b);
125 if (ad == bd || mix_all_files)
126 return string_sortcomp (a->fname, b->fname) * reverse;
127 return bd-ad;
131 sort_ext (const file_entry *a, const file_entry *b)
133 char *exta, *extb;
134 int r;
135 int ad = MY_ISDIR (a);
136 int bd = MY_ISDIR (b);
138 if (ad == bd || mix_all_files){
139 exta = extension (a->fname);
140 extb = extension (b->fname);
141 r = string_sortcomp (exta, extb);
142 if (r)
143 return r * reverse;
144 else
145 return sort_name (a, b);
146 } else
147 return bd-ad;
151 sort_owner (const file_entry *a, const file_entry *b)
153 int ad = MY_ISDIR (a);
154 int bd = MY_ISDIR (b);
156 if (ad == bd || mix_all_files)
157 return string_sortcomp (get_owner (a->buf.st_uid), get_owner (a->buf.st_uid)) * reverse;
158 return bd-ad;
162 sort_group (const file_entry *a, const file_entry *b)
164 int ad = MY_ISDIR (a);
165 int bd = MY_ISDIR (b);
167 if (ad == bd || mix_all_files)
168 return string_sortcomp (get_group (a->buf.st_gid), get_group (a->buf.st_gid)) * reverse;
169 return bd-ad;
173 sort_time (const file_entry *a, const file_entry *b)
175 int ad = MY_ISDIR (a);
176 int bd = MY_ISDIR (b);
178 if (ad == bd || mix_all_files)
179 return (a->buf.st_mtime - b->buf.st_mtime) * reverse;
180 else
181 return bd-ad;
185 sort_ctime (const file_entry *a, const file_entry *b)
187 int ad = MY_ISDIR (a);
188 int bd = MY_ISDIR (b);
190 if (ad == bd || mix_all_files)
191 return (a->buf.st_ctime - b->buf.st_ctime) * reverse;
192 else
193 return bd-ad;
197 sort_atime (const file_entry *a, const file_entry *b)
199 int ad = MY_ISDIR (a);
200 int bd = MY_ISDIR (b);
202 if (ad == bd || mix_all_files)
203 return (a->buf.st_atime - b->buf.st_atime) * reverse;
204 else
205 return bd-ad;
209 sort_inode (const file_entry *a, const file_entry *b)
211 int ad = MY_ISDIR (a);
212 int bd = MY_ISDIR (b);
214 if (ad == bd || mix_all_files)
215 return (a->buf.st_ino - b->buf.st_ino) * reverse;
216 else
217 return bd-ad;
221 sort_size (const file_entry *a, const file_entry *b)
223 int ad = MY_ISDIR (a);
224 int bd = MY_ISDIR (b);
226 if (ad == bd || mix_all_files)
227 return (b->buf.st_size - a->buf.st_size) * reverse;
228 else
229 return bd-ad;
233 sort_links (const file_entry *a, const file_entry *b)
235 int ad = MY_ISDIR (a);
236 int bd = MY_ISDIR (b);
238 if (ad == bd || mix_all_files)
239 return (b->buf.st_nlink - a->buf.st_nlink) * reverse;
240 else
241 return bd-ad;
245 sort_ngid (const file_entry *a, const file_entry *b)
247 int ad = MY_ISDIR (a);
248 int bd = MY_ISDIR (b);
250 if (ad == bd || mix_all_files)
251 return (b->buf.st_gid - a->buf.st_gid) * reverse;
252 else
253 return bd-ad;
257 sort_nuid (const file_entry *a, const file_entry *b)
259 int ad = MY_ISDIR (a);
260 int bd = MY_ISDIR (b);
262 if (ad == bd || mix_all_files)
263 return (b->buf.st_uid - a->buf.st_uid) * reverse;
264 else
265 return bd-ad;
268 inline static int
269 file_type_to_num (const file_entry *fe)
271 const struct stat *s = &fe->buf;
273 if (S_ISDIR (s->st_mode))
274 return 0;
275 if (S_ISLNK (s->st_mode)){
276 if (fe->f.link_to_dir)
277 return 1;
278 if (fe->f.stalled_link)
279 return 2;
280 else
281 return 3;
283 if (S_ISSOCK (s->st_mode))
284 return 4;
285 if (S_ISCHR (s->st_mode))
286 return 5;
287 if (S_ISBLK (s->st_mode))
288 return 6;
289 if (S_ISFIFO (s->st_mode))
290 return 7;
291 if (is_exe (s->st_mode))
292 return 8;
293 return 9;
297 sort_type (const file_entry *a, const file_entry *b)
299 int aa = file_type_to_num (a);
300 int bb = file_type_to_num (b);
302 return bb-aa;
306 void
307 do_sort (dir_list *list, sortfn *sort, int top, int reverse_f, int case_sensitive_f)
309 int i;
310 int dot_dot_found = 0;
311 file_entry tmp_fe;
313 for (i = 0; i < top + 1; i++) { /* put ".." first in list */
314 if (!strcmp (list->list [i].fname, "..")) {
315 dot_dot_found = 1;
316 if (i > 0) { /* swap [i] and [0] */
317 memcpy (&tmp_fe, &(list->list [0]), sizeof (file_entry));
318 memcpy (&(list->list [0]), &(list->list [i]), sizeof (file_entry));
319 memcpy (&(list->list [i]), &tmp_fe, sizeof (file_entry));
321 break;
325 reverse = reverse_f ? -1 : 1;
326 case_sensitive = case_sensitive_f;
327 qsort (&(list->list) [dot_dot_found],
328 top + 1 - dot_dot_found, sizeof (file_entry), sort);
331 void
332 clean_dir (dir_list *list, int count)
334 int i;
336 for (i = 0; i < count; i++){
337 g_free (list->list [i].fname);
338 list->list [i].fname = 0;
342 static int
343 add_dotdot_to_list (dir_list *list, int index)
345 /* Need to grow the *list? */
346 if (index == list->size) {
347 list->list = g_realloc (list->list, sizeof (file_entry) *
348 (list->size + RESIZE_STEPS));
349 if (!list->list)
350 return 0;
351 list->size += RESIZE_STEPS;
354 memset (&(list->list) [index], 0, sizeof(file_entry));
355 (list->list) [index].fnamelen = 2;
356 (list->list) [index].fname = g_strdup ("..");
357 (list->list) [index].f.link_to_dir = 0;
358 (list->list) [index].f.stalled_link = 0;
359 (list->list) [index].f.dir_size_computed = 0;
360 (list->list) [index].f.marked = 0;
361 (list->list) [index].buf.st_mode = 040755;
362 return 1;
365 /* Used to set up a directory list when there is no access to a directory */
367 set_zero_dir (dir_list *list)
369 return (add_dotdot_to_list (list, 0));
372 /* If you change handle_dirent then check also handle_path. */
373 /* Return values: -1 = failure, 0 = don't add, 1 = add to the list */
375 handle_dirent (dir_list *list, char *filter, struct dirent *dp,
376 struct stat *buf1, int next_free, int *link_to_dir,
377 int *stalled_link)
379 if (dp->d_name [0] == '.' && dp->d_name [1] == 0)
380 return 0;
381 if (dp->d_name [0] == '.' && dp->d_name [1] == '.' && dp->d_name [2] == 0)
382 return 0;
383 if (!show_dot_files && (dp->d_name [0] == '.'))
384 return 0;
385 if (!show_backups && dp->d_name [NLENGTH (dp)-1] == '~')
386 return 0;
387 if (mc_lstat (dp->d_name, buf1) == -1) {
388 message(1, MSG_ERROR, _("File '%s' exists but can not be stat-ed: %s"), dp->d_name, strerror(errno));
389 return 0;
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 *stalled_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 *stalled_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 = g_realloc (list->list, sizeof (file_entry) *
412 (list->size + RESIZE_STEPS));
413 if (!list->list)
414 return -1;
415 list->size += RESIZE_STEPS;
417 return 1;
420 /* handle_path is a simplified handle_dirent. The difference is that
421 handle_path doesn't pay attention to show_dot_files and show_backups.
422 Moreover handle_path can't be used with a filemask.
423 If you change handle_path then check also handle_dirent. */
424 /* Return values: -1 = failure, 0 = don't add, 1 = add to the list */
426 handle_path (dir_list *list, char *path,
427 struct stat *buf1, int next_free, int *link_to_dir,
428 int *stalled_link)
430 if (path [0] == '.' && path [1] == 0)
431 return 0;
432 if (path [0] == '.' && path [1] == '.' && path [2] == 0)
433 return 0;
434 if (mc_lstat (path, buf1) == -1)
435 return 0;
437 if (S_ISDIR (buf1->st_mode))
438 tree_store_mark_checked (path);
440 /* A link to a file or a directory? */
441 *link_to_dir = 0;
442 *stalled_link = 0;
443 if (S_ISLNK(buf1->st_mode)){
444 struct stat buf2;
445 if (!mc_stat (path, &buf2))
446 *link_to_dir = S_ISDIR(buf2.st_mode) != 0;
447 else
448 *stalled_link = 1;
451 /* Need to grow the *list? */
452 if (next_free == list->size){
453 list->list = g_realloc (list->list, sizeof (file_entry) *
454 (list->size + RESIZE_STEPS));
455 if (!list->list)
456 return -1;
457 list->size += RESIZE_STEPS;
459 return 1;
463 do_load_dir (dir_list *list, sortfn *sort, int reverse, int case_sensitive, char *filter)
465 DIR *dirp;
466 struct dirent *dp;
467 int status, link_to_dir, stalled_link;
468 int next_free = 0;
469 struct stat buf;
470 int dotdot_found = 0;
472 tree_store_start_check_cwd ();
474 dirp = mc_opendir (".");
475 if (!dirp){
476 tree_store_end_check ();
477 return set_zero_dir (list);
479 for (dp = mc_readdir (dirp); dp; dp = mc_readdir (dirp)){
480 status = handle_dirent (list, filter, dp, &buf, next_free, &link_to_dir,
481 &stalled_link);
482 if (status == 0)
483 continue;
484 if (status == -1){
485 tree_store_end_check ();
486 mc_closedir (dirp);
487 return next_free;
489 list->list [next_free].fnamelen = NLENGTH (dp);
490 list->list [next_free].fname = g_strdup (dp->d_name);
491 list->list [next_free].f.marked = 0;
492 list->list [next_free].f.link_to_dir = link_to_dir;
493 list->list [next_free].f.stalled_link = stalled_link;
494 list->list [next_free].f.dir_size_computed = 0;
495 list->list [next_free].buf = buf;
496 if (strcmp (dp->d_name, ".." ) == 0)
497 dotdot_found = 1;
498 next_free++;
499 if (!(next_free % 32))
500 rotate_dash ();
503 if (next_free) {
504 if (!dotdot_found)
505 add_dotdot_to_list (list, next_free++);
506 do_sort (list, sort, next_free-1, reverse, case_sensitive);
507 } else {
508 tree_store_end_check ();
509 mc_closedir (dirp);
510 return set_zero_dir (list);
513 mc_closedir (dirp);
514 tree_store_end_check ();
515 return next_free;
519 link_isdir (file_entry *file)
521 if (file->f.link_to_dir)
522 return 1;
523 else
524 return 0;
528 if_link_is_exe (char *full_name, file_entry *file)
530 struct stat b;
532 if (S_ISLNK (file->buf.st_mode)) {
533 mc_stat (full_name, &b);
534 return is_exe (b.st_mode);
535 } else
536 return 1;
539 static dir_list dir_copy = { 0, 0 };
541 static void
542 alloc_dir_copy (int size)
544 int i;
546 if (dir_copy.size < size){
547 if (dir_copy.list){
549 for (i = 0; i < dir_copy.size; i++) {
550 if (dir_copy.list [i].fname)
551 g_free (dir_copy.list [i].fname);
553 g_free (dir_copy.list);
554 dir_copy.list = 0;
557 dir_copy.list = g_new (file_entry, size);
558 for (i = 0; i < size; i++)
559 dir_copy.list [i].fname = 0;
561 dir_copy.size = size;
565 /* If filter is null, then it is a match */
567 do_reload_dir (dir_list * list, sortfn * sort, int count, int rev,
568 int case_sensitive, char *filter)
570 DIR *dirp;
571 struct dirent *dp;
572 int next_free = 0;
573 int i, status, link_to_dir, stalled_link;
574 struct stat buf;
575 int dotdot_found = 0;
576 int marked_cnt;
577 GHashTable *marked_files = g_hash_table_new (g_str_hash, g_str_equal);
579 tree_store_start_check_cwd ();
580 dirp = mc_opendir (".");
581 if (!dirp) {
582 clean_dir (list, count);
583 tree_store_end_check ();
584 return set_zero_dir (list);
587 alloc_dir_copy (list->size);
588 for (marked_cnt = i = 0; i < count; i++) {
589 dir_copy.list[i].fnamelen = list->list[i].fnamelen;
590 dir_copy.list[i].fname = list->list[i].fname;
591 dir_copy.list[i].f.marked = list->list[i].f.marked;
592 dir_copy.list[i].f.dir_size_computed =
593 list->list[i].f.dir_size_computed;
594 dir_copy.list[i].f.link_to_dir = list->list[i].f.link_to_dir;
595 dir_copy.list[i].f.stalled_link = list->list[i].f.stalled_link;
596 if (list->list[i].f.marked) {
597 g_hash_table_insert (marked_files, dir_copy.list[i].fname,
598 &dir_copy.list[i]);
599 marked_cnt++;
603 for (dp = mc_readdir (dirp); dp; dp = mc_readdir (dirp)) {
604 status =
605 handle_dirent (list, filter, dp, &buf, next_free, &link_to_dir,
606 &stalled_link);
607 if (status == 0)
608 continue;
609 if (status == -1) {
610 mc_closedir (dirp);
611 /* Norbert (Feb 12, 1997):
612 Just in case someone finds this memory leak:
613 -1 means big trouble (at the moment no memory left),
614 I don't bother with further cleanup because if one gets to
615 this point he will have more problems than a few memory
616 leaks and because one 'clean_dir' would not be enough (and
617 because I don't want to spent the time to make it working,
618 IMHO it's not worthwhile).
619 clean_dir (&dir_copy, count);
621 tree_store_end_check ();
622 return next_free;
625 list->list[next_free].f.marked = 0;
628 * If we have marked files in the copy, scan through the copy
629 * to find matching file. Decrease number of remaining marks if
630 * we copied one.
632 if (marked_cnt > 0) {
633 file_entry *p;
634 if (NULL !=
635 (p = g_hash_table_lookup (marked_files, dp->d_name))) {
636 list->list[next_free].f.marked = 1;
637 marked_cnt--;
641 list->list[next_free].fnamelen = NLENGTH (dp);
642 list->list[next_free].fname = g_strdup (dp->d_name);
643 list->list[next_free].f.link_to_dir = link_to_dir;
644 list->list[next_free].f.stalled_link = stalled_link;
645 list->list[next_free].f.dir_size_computed = 0;
646 list->list[next_free].buf = buf;
647 if (strcmp (dp->d_name, "..") == 0)
648 dotdot_found = 1;
649 next_free++;
650 if (!(next_free % 16))
651 rotate_dash ();
653 mc_closedir (dirp);
654 tree_store_end_check ();
655 g_hash_table_destroy (marked_files);
656 if (next_free) {
657 if (!dotdot_found)
658 add_dotdot_to_list (list, next_free++);
659 do_sort (list, sort, next_free - 1, rev, case_sensitive);
660 } else
661 next_free = set_zero_dir (list);
662 clean_dir (&dir_copy, count);
663 return next_free;