updated the .TP cleanup for coherency in the key description pages.
[midnight-commander.git] / src / dir.c
blob14f8c54dbfd6e13590584ac0a4410bbb99424e3d
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 /* If true show files starting with a dot */
34 int show_dot_files = 1;
36 /* If true show files ending in ~ */
37 int show_backups = 1;
39 /* If false then directories are shown separately from files */
40 int mix_all_files = 0;
42 /* Reverse flag */
43 static int reverse = 1;
45 /* Are the files sorted case sensitively? */
46 static int case_sensitive = OS_SORT_CASE_SENSITIVE_DEFAULT;
48 #define MY_ISDIR(x) ( (S_ISDIR (x->buf.st_mode) || x->f.link_to_dir) ? 1 : 0)
50 sort_orders_t sort_orders [SORT_TYPES_TOTAL] = {
51 { N_("&Unsorted"), unsorted },
52 { N_("&Name"), sort_name },
53 { N_("&Extension"), sort_ext },
54 { N_("&Modify time"), sort_time },
55 { N_("&Access time"), sort_atime },
56 { N_("&Change time"), sort_ctime },
57 { N_("&Size"), sort_size },
58 { N_("&Inode"), sort_inode },
60 /* New sort orders */
61 { N_("&Type"), sort_type },
62 { N_("&Links"), sort_links },
63 { N_("N&GID"), sort_ngid },
64 { N_("N&UID"), sort_nuid },
65 { N_("&Owner"), sort_owner },
66 { N_("&Group"), sort_group }
69 #ifdef HAVE_STRCOLL
71 * g_strcasecmp() doesn't work well in some locales because it relies on
72 * the locale-specific toupper(). On the other hand, strcoll() is case
73 * sensitive in the "C" and "POSIX" locales, unlike other locales.
74 * Solution: always use strcmp() for case sensitive sort. For case
75 * insensitive sort use strcoll() if it's case insensitive for ASCII and
76 * g_strcasecmp() otherwise.
78 typedef enum {
79 STRCOLL_NO,
80 STRCOLL_YES,
81 STRCOLL_TEST
82 } strcoll_status;
84 static int string_sortcomp (char *str1, char *str2)
86 static strcoll_status use_strcoll = STRCOLL_TEST;
88 if (case_sensitive) {
89 return strcmp (str1, str2);
92 /* Initialize use_strcoll once. */
93 if (use_strcoll == STRCOLL_TEST) {
94 /* Only use strcoll() if it considers "B" between "a" and "c". */
95 if (strcoll ("a", "B") * strcoll ("B", "c") > 0) {
96 use_strcoll = STRCOLL_YES;
97 } else {
98 use_strcoll = STRCOLL_NO;
102 if (use_strcoll == STRCOLL_NO)
103 return g_strcasecmp (str1, str2);
104 else
105 return strcoll (str1, str2);
107 #else
108 #define string_sortcomp(a,b) (case_sensitive ? strcmp (a,b) : g_strcasecmp (a,b))
109 #endif
112 unsorted (const file_entry *a, const file_entry *b)
114 return 0;
118 sort_name (const file_entry *a, const file_entry *b)
120 int ad = MY_ISDIR (a);
121 int bd = MY_ISDIR (b);
123 if (ad == bd || mix_all_files)
124 return string_sortcomp (a->fname, b->fname) * reverse;
125 return bd-ad;
129 sort_ext (const file_entry *a, const file_entry *b)
131 char *exta, *extb;
132 int r;
133 int ad = MY_ISDIR (a);
134 int bd = MY_ISDIR (b);
136 if (ad == bd || mix_all_files){
137 exta = extension (a->fname);
138 extb = extension (b->fname);
139 r = string_sortcomp (exta, extb);
140 if (r)
141 return r * reverse;
142 else
143 return sort_name (a, b);
144 } else
145 return bd-ad;
149 sort_owner (const file_entry *a, const file_entry *b)
151 int ad = MY_ISDIR (a);
152 int bd = MY_ISDIR (b);
154 if (ad == bd || mix_all_files)
155 return string_sortcomp (get_owner (a->buf.st_uid), get_owner (a->buf.st_uid)) * reverse;
156 return bd-ad;
160 sort_group (const file_entry *a, const file_entry *b)
162 int ad = MY_ISDIR (a);
163 int bd = MY_ISDIR (b);
165 if (ad == bd || mix_all_files)
166 return string_sortcomp (get_group (a->buf.st_gid), get_group (a->buf.st_gid)) * reverse;
167 return bd-ad;
171 sort_time (const file_entry *a, const file_entry *b)
173 int ad = MY_ISDIR (a);
174 int bd = MY_ISDIR (b);
176 if (ad == bd || mix_all_files)
177 return (a->buf.st_mtime - b->buf.st_mtime) * reverse;
178 else
179 return bd-ad;
183 sort_ctime (const file_entry *a, const file_entry *b)
185 int ad = MY_ISDIR (a);
186 int bd = MY_ISDIR (b);
188 if (ad == bd || mix_all_files)
189 return (a->buf.st_ctime - b->buf.st_ctime) * reverse;
190 else
191 return bd-ad;
195 sort_atime (const file_entry *a, const file_entry *b)
197 int ad = MY_ISDIR (a);
198 int bd = MY_ISDIR (b);
200 if (ad == bd || mix_all_files)
201 return (a->buf.st_atime - b->buf.st_atime) * reverse;
202 else
203 return bd-ad;
207 sort_inode (const file_entry *a, const file_entry *b)
209 int ad = MY_ISDIR (a);
210 int bd = MY_ISDIR (b);
212 if (ad == bd || mix_all_files)
213 return (a->buf.st_ino - b->buf.st_ino) * reverse;
214 else
215 return bd-ad;
219 sort_size (const file_entry *a, const file_entry *b)
221 int ad = MY_ISDIR (a);
222 int bd = MY_ISDIR (b);
224 if (ad == bd || mix_all_files)
225 return (b->buf.st_size - a->buf.st_size) * reverse;
226 else
227 return bd-ad;
231 sort_links (const file_entry *a, const file_entry *b)
233 int ad = MY_ISDIR (a);
234 int bd = MY_ISDIR (b);
236 if (ad == bd || mix_all_files)
237 return (b->buf.st_nlink - a->buf.st_nlink) * reverse;
238 else
239 return bd-ad;
243 sort_ngid (const file_entry *a, const file_entry *b)
245 int ad = MY_ISDIR (a);
246 int bd = MY_ISDIR (b);
248 if (ad == bd || mix_all_files)
249 return (b->buf.st_gid - a->buf.st_gid) * reverse;
250 else
251 return bd-ad;
255 sort_nuid (const file_entry *a, const file_entry *b)
257 int ad = MY_ISDIR (a);
258 int bd = MY_ISDIR (b);
260 if (ad == bd || mix_all_files)
261 return (b->buf.st_uid - a->buf.st_uid) * reverse;
262 else
263 return bd-ad;
266 inline static int
267 file_type_to_num (const file_entry *fe)
269 const struct stat *s = &fe->buf;
271 if (S_ISDIR (s->st_mode))
272 return 0;
273 if (S_ISLNK (s->st_mode)){
274 if (fe->f.link_to_dir)
275 return 1;
276 if (fe->f.stale_link)
277 return 2;
278 else
279 return 3;
281 if (S_ISSOCK (s->st_mode))
282 return 4;
283 if (S_ISCHR (s->st_mode))
284 return 5;
285 if (S_ISBLK (s->st_mode))
286 return 6;
287 if (S_ISFIFO (s->st_mode))
288 return 7;
289 if (is_exe (s->st_mode))
290 return 8;
291 return 9;
295 sort_type (const file_entry *a, const file_entry *b)
297 int aa = file_type_to_num (a);
298 int bb = file_type_to_num (b);
300 return bb-aa;
304 void
305 do_sort (dir_list *list, sortfn *sort, int top, int reverse_f, int case_sensitive_f)
307 int i;
308 int dot_dot_found = 0;
309 file_entry tmp_fe;
311 for (i = 0; i < top + 1; i++) { /* put ".." first in list */
312 if (!strcmp (list->list [i].fname, "..")) {
313 dot_dot_found = 1;
314 if (i > 0) { /* swap [i] and [0] */
315 memcpy (&tmp_fe, &(list->list [0]), sizeof (file_entry));
316 memcpy (&(list->list [0]), &(list->list [i]), sizeof (file_entry));
317 memcpy (&(list->list [i]), &tmp_fe, sizeof (file_entry));
319 break;
323 reverse = reverse_f ? -1 : 1;
324 case_sensitive = case_sensitive_f;
325 qsort (&(list->list) [dot_dot_found],
326 top + 1 - dot_dot_found, sizeof (file_entry), sort);
329 void
330 clean_dir (dir_list *list, int count)
332 int i;
334 for (i = 0; i < count; i++){
335 g_free (list->list [i].fname);
336 list->list [i].fname = 0;
340 static int
341 add_dotdot_to_list (dir_list *list, int index)
343 /* Need to grow the *list? */
344 if (index == list->size) {
345 list->list = g_realloc (list->list, sizeof (file_entry) *
346 (list->size + RESIZE_STEPS));
347 if (!list->list)
348 return 0;
349 list->size += RESIZE_STEPS;
352 memset (&(list->list) [index], 0, sizeof(file_entry));
353 (list->list) [index].fnamelen = 2;
354 (list->list) [index].fname = g_strdup ("..");
355 (list->list) [index].f.link_to_dir = 0;
356 (list->list) [index].f.stale_link = 0;
357 (list->list) [index].f.dir_size_computed = 0;
358 (list->list) [index].f.marked = 0;
359 (list->list) [index].buf.st_mode = 040755;
360 return 1;
363 /* Used to set up a directory list when there is no access to a directory */
365 set_zero_dir (dir_list *list)
367 return (add_dotdot_to_list (list, 0));
370 /* If you change handle_dirent then check also handle_path. */
371 /* Return values: -1 = failure, 0 = don't add, 1 = add to the list */
373 handle_dirent (dir_list *list, char *filter, struct dirent *dp,
374 struct stat *buf1, int next_free, int *link_to_dir,
375 int *stale_link)
377 if (dp->d_name [0] == '.' && dp->d_name [1] == 0)
378 return 0;
379 if (dp->d_name [0] == '.' && dp->d_name [1] == '.' && dp->d_name [2] == 0)
380 return 0;
381 if (!show_dot_files && (dp->d_name [0] == '.'))
382 return 0;
383 if (!show_backups && dp->d_name [NLENGTH (dp)-1] == '~')
384 return 0;
385 if (mc_lstat (dp->d_name, buf1) == -1) {
386 message(1, MSG_ERROR, _("File '%s' exists but can not be stat-ed: %s"), dp->d_name, strerror(errno));
387 return 0;
390 if (S_ISDIR (buf1->st_mode))
391 tree_store_mark_checked (dp->d_name);
393 /* A link to a file or a directory? */
394 *link_to_dir = 0;
395 *stale_link = 0;
396 if (S_ISLNK(buf1->st_mode)){
397 struct stat buf2;
398 if (!mc_stat (dp->d_name, &buf2))
399 *link_to_dir = S_ISDIR(buf2.st_mode) != 0;
400 else
401 *stale_link = 1;
403 if (!(S_ISDIR(buf1->st_mode) || *link_to_dir) && filter &&
404 !regexp_match (filter, dp->d_name, match_file))
405 return 0;
407 /* Need to grow the *list? */
408 if (next_free == list->size){
409 list->list = g_realloc (list->list, sizeof (file_entry) *
410 (list->size + RESIZE_STEPS));
411 if (!list->list)
412 return -1;
413 list->size += RESIZE_STEPS;
415 return 1;
418 /* handle_path is a simplified handle_dirent. The difference is that
419 handle_path doesn't pay attention to show_dot_files and show_backups.
420 Moreover handle_path can't be used with a filemask.
421 If you change handle_path then check also handle_dirent. */
422 /* Return values: -1 = failure, 0 = don't add, 1 = add to the list */
424 handle_path (dir_list *list, char *path,
425 struct stat *buf1, int next_free, int *link_to_dir,
426 int *stale_link)
428 if (path [0] == '.' && path [1] == 0)
429 return 0;
430 if (path [0] == '.' && path [1] == '.' && path [2] == 0)
431 return 0;
432 if (mc_lstat (path, buf1) == -1)
433 return 0;
435 if (S_ISDIR (buf1->st_mode))
436 tree_store_mark_checked (path);
438 /* A link to a file or a directory? */
439 *link_to_dir = 0;
440 *stale_link = 0;
441 if (S_ISLNK(buf1->st_mode)){
442 struct stat buf2;
443 if (!mc_stat (path, &buf2))
444 *link_to_dir = S_ISDIR(buf2.st_mode) != 0;
445 else
446 *stale_link = 1;
449 /* Need to grow the *list? */
450 if (next_free == list->size){
451 list->list = g_realloc (list->list, sizeof (file_entry) *
452 (list->size + RESIZE_STEPS));
453 if (!list->list)
454 return -1;
455 list->size += RESIZE_STEPS;
457 return 1;
461 do_load_dir (dir_list *list, sortfn *sort, int reverse, int case_sensitive, char *filter)
463 DIR *dirp;
464 struct dirent *dp;
465 int status, link_to_dir, stale_link;
466 int next_free = 0;
467 struct stat buf;
468 int dotdot_found = 0;
470 tree_store_start_check_cwd ();
472 dirp = mc_opendir (".");
473 if (!dirp){
474 tree_store_end_check ();
475 return set_zero_dir (list);
477 for (dp = mc_readdir (dirp); dp; dp = mc_readdir (dirp)){
478 status = handle_dirent (list, filter, dp, &buf, next_free, &link_to_dir,
479 &stale_link);
480 if (status == 0)
481 continue;
482 if (status == -1){
483 tree_store_end_check ();
484 mc_closedir (dirp);
485 return next_free;
487 list->list [next_free].fnamelen = NLENGTH (dp);
488 list->list [next_free].fname = g_strdup (dp->d_name);
489 list->list [next_free].f.marked = 0;
490 list->list [next_free].f.link_to_dir = link_to_dir;
491 list->list [next_free].f.stale_link = stale_link;
492 list->list [next_free].f.dir_size_computed = 0;
493 list->list [next_free].buf = buf;
494 if (strcmp (dp->d_name, ".." ) == 0)
495 dotdot_found = 1;
496 next_free++;
497 if (!(next_free % 32))
498 rotate_dash ();
501 if (next_free) {
502 if (!dotdot_found)
503 add_dotdot_to_list (list, next_free++);
504 do_sort (list, sort, next_free-1, reverse, case_sensitive);
505 } else {
506 tree_store_end_check ();
507 mc_closedir (dirp);
508 return set_zero_dir (list);
511 mc_closedir (dirp);
512 tree_store_end_check ();
513 return next_free;
517 link_isdir (file_entry *file)
519 if (file->f.link_to_dir)
520 return 1;
521 else
522 return 0;
526 if_link_is_exe (char *full_name, file_entry *file)
528 struct stat b;
530 if (S_ISLNK (file->buf.st_mode)) {
531 mc_stat (full_name, &b);
532 return is_exe (b.st_mode);
533 } else
534 return 1;
537 static dir_list dir_copy = { 0, 0 };
539 static void
540 alloc_dir_copy (int size)
542 int i;
544 if (dir_copy.size < size){
545 if (dir_copy.list){
547 for (i = 0; i < dir_copy.size; i++) {
548 if (dir_copy.list [i].fname)
549 g_free (dir_copy.list [i].fname);
551 g_free (dir_copy.list);
552 dir_copy.list = 0;
555 dir_copy.list = g_new (file_entry, size);
556 for (i = 0; i < size; i++)
557 dir_copy.list [i].fname = 0;
559 dir_copy.size = size;
563 /* If filter is null, then it is a match */
565 do_reload_dir (dir_list * list, sortfn * sort, int count, int rev,
566 int case_sensitive, char *filter)
568 DIR *dirp;
569 struct dirent *dp;
570 int next_free = 0;
571 int i, status, link_to_dir, stale_link;
572 struct stat buf;
573 int dotdot_found = 0;
574 int marked_cnt;
575 GHashTable *marked_files = g_hash_table_new (g_str_hash, g_str_equal);
577 tree_store_start_check_cwd ();
578 dirp = mc_opendir (".");
579 if (!dirp) {
580 clean_dir (list, count);
581 tree_store_end_check ();
582 return set_zero_dir (list);
585 alloc_dir_copy (list->size);
586 for (marked_cnt = i = 0; i < count; i++) {
587 dir_copy.list[i].fnamelen = list->list[i].fnamelen;
588 dir_copy.list[i].fname = list->list[i].fname;
589 dir_copy.list[i].f.marked = list->list[i].f.marked;
590 dir_copy.list[i].f.dir_size_computed =
591 list->list[i].f.dir_size_computed;
592 dir_copy.list[i].f.link_to_dir = list->list[i].f.link_to_dir;
593 dir_copy.list[i].f.stale_link = list->list[i].f.stale_link;
594 if (list->list[i].f.marked) {
595 g_hash_table_insert (marked_files, dir_copy.list[i].fname,
596 &dir_copy.list[i]);
597 marked_cnt++;
601 for (dp = mc_readdir (dirp); dp; dp = mc_readdir (dirp)) {
602 status =
603 handle_dirent (list, filter, dp, &buf, next_free, &link_to_dir,
604 &stale_link);
605 if (status == 0)
606 continue;
607 if (status == -1) {
608 mc_closedir (dirp);
609 /* Norbert (Feb 12, 1997):
610 Just in case someone finds this memory leak:
611 -1 means big trouble (at the moment no memory left),
612 I don't bother with further cleanup because if one gets to
613 this point he will have more problems than a few memory
614 leaks and because one 'clean_dir' would not be enough (and
615 because I don't want to spent the time to make it working,
616 IMHO it's not worthwhile).
617 clean_dir (&dir_copy, count);
619 tree_store_end_check ();
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 file_entry *p;
632 if (NULL !=
633 (p = g_hash_table_lookup (marked_files, dp->d_name))) {
634 list->list[next_free].f.marked = 1;
635 marked_cnt--;
639 list->list[next_free].fnamelen = NLENGTH (dp);
640 list->list[next_free].fname = g_strdup (dp->d_name);
641 list->list[next_free].f.link_to_dir = link_to_dir;
642 list->list[next_free].f.stale_link = stale_link;
643 list->list[next_free].f.dir_size_computed = 0;
644 list->list[next_free].buf = buf;
645 if (strcmp (dp->d_name, "..") == 0)
646 dotdot_found = 1;
647 next_free++;
648 if (!(next_free % 16))
649 rotate_dash ();
651 mc_closedir (dirp);
652 tree_store_end_check ();
653 g_hash_table_destroy (marked_files);
654 if (next_free) {
655 if (!dotdot_found)
656 add_dotdot_to_list (list, next_free++);
657 do_sort (list, sort, next_free - 1, rev, case_sensitive);
658 } else
659 next_free = set_zero_dir (list);
660 clean_dir (&dir_copy, count);
661 return next_free;