Ticket #3730: extfs: don't run tester on "make check" if disabled.
[midnight-commander.git] / src / filemanager / treestore.c
blobd64ec5966957c86301692b1515d400d01e14455a
1 /*
2 Tree Store
3 Contains a storage of the file system tree representation
5 This module has been converted to be a widget.
7 The program load and saves the tree each time the tree widget is
8 created and destroyed. This is required for the future vfs layer,
9 it will be possible to have tree views over virtual file systems.
11 Copyright (C) 1999-2016
12 Free Software Foundation, Inc.
14 Written by:
15 Janne Kukonlehto, 1994, 1996
16 Norbert Warmuth, 1997
17 Miguel de Icaza, 1996, 1999
18 Slava Zanko <slavazanko@gmail.com>, 2013
19 Andrew Borodin <aborodin@vmail.ru>, 2013
21 This file is part of the Midnight Commander.
23 The Midnight Commander is free software: you can redistribute it
24 and/or modify it under the terms of the GNU General Public License as
25 published by the Free Software Foundation, either version 3 of the License,
26 or (at your option) any later version.
28 The Midnight Commander is distributed in the hope that it will be useful,
29 but WITHOUT ANY WARRANTY; without even the implied warranty of
30 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
31 GNU General Public License for more details.
33 You should have received a copy of the GNU General Public License
34 along with this program. If not, see <http://www.gnu.org/licenses/>.
37 /** \file treestore.c
38 * \brief Source: tree store
40 * Contains a storage of the file system tree representation.
43 #include <config.h>
45 #include <errno.h>
46 #include <stdio.h>
47 #include <stdlib.h>
48 #include <string.h>
49 #include <sys/types.h>
50 #include <sys/stat.h>
51 #include <unistd.h>
53 #include "lib/global.h"
54 #include "lib/mcconfig.h"
55 #include "lib/vfs/vfs.h"
56 #include "lib/fileloc.h"
57 #include "lib/strescape.h"
58 #include "lib/hook.h"
59 #include "lib/util.h"
61 #include "src/setup.h" /* setup_init() */
63 #include "treestore.h"
65 /*** global variables ****************************************************************************/
67 /*** file scope macro definitions ****************************************************************/
69 #define TREE_SIGNATURE "Midnight Commander TreeStore v 2.0"
71 /*** file scope type declarations ****************************************************************/
73 /*** file scope variables ************************************************************************/
75 static struct TreeStore ts;
77 static hook_t *remove_entry_hooks;
79 /*** file scope functions ************************************************************************/
80 /* --------------------------------------------------------------------------------------------- */
82 static tree_entry *tree_store_add_entry (const vfs_path_t * name);
84 /* --------------------------------------------------------------------------------------------- */
86 static inline void
87 tree_store_dirty (gboolean dirty)
89 ts.dirty = dirty;
92 /* --------------------------------------------------------------------------------------------- */
93 /**
95 * @return the number of common bytes in the strings.
98 static size_t
99 str_common (const vfs_path_t * s1_vpath, const vfs_path_t * s2_vpath)
101 size_t result = 0;
102 const char *s1, *s2;
104 s1 = vfs_path_as_str (s1_vpath);
105 s2 = vfs_path_as_str (s2_vpath);
107 while (*s1 != '\0' && *s2 != '\0' && *s1++ == *s2++)
108 result++;
110 return result;
113 /* --------------------------------------------------------------------------------------------- */
114 /** The directory names are arranged in a single linked list in the same
115 * order as they are displayed. When the tree is displayed the expected
116 * order is like this:
118 * /bin
119 * /etc
120 * /etc/X11
121 * /etc/rc.d
122 * /etc.old/X11
123 * /etc.old/rc.d
124 * /usr
126 * i.e. the required collating sequence when comparing two directory names is
127 * '\0' < PATH_SEP < all-other-characters-in-encoding-order
129 * Since strcmp doesn't fulfil this requirement we use pathcmp when
130 * inserting directory names into the list. The meaning of the return value
131 * of pathcmp and strcmp are the same (an integer less than, equal to, or
132 * greater than zero if p1 is found to be less than, to match, or be greater
133 * than p2.
136 static int
137 pathcmp (const vfs_path_t * p1_vpath, const vfs_path_t * p2_vpath)
139 int ret_val;
140 const char *p1, *p2;
142 p1 = vfs_path_as_str (p1_vpath);
143 p2 = vfs_path_as_str (p2_vpath);
145 for (; *p1 == *p2; p1++, p2++)
146 if (*p1 == '\0')
147 return 0;
149 if (*p1 == '\0')
150 ret_val = -1;
151 else if (*p2 == '\0')
152 ret_val = 1;
153 else if (IS_PATH_SEP (*p1))
154 ret_val = -1;
155 else if (IS_PATH_SEP (*p2))
156 ret_val = 1;
157 else
158 ret_val = (*p1 - *p2);
160 return ret_val;
163 /* --------------------------------------------------------------------------------------------- */
165 static char *
166 decode (char *buffer)
168 char *res = g_strdup (buffer);
169 char *p, *q;
171 for (p = q = res; *p; p++, q++)
173 if (*p == '\n')
175 *q = 0;
176 return res;
179 if (*p != '\\')
181 *q = *p;
182 continue;
185 p++;
187 switch (*p)
189 case 'n':
190 *q = '\n';
191 break;
192 case '\\':
193 *q = '\\';
194 break;
195 default:
196 break;
199 *q = *p;
201 return res;
204 /* --------------------------------------------------------------------------------------------- */
205 /** Loads the tree store from the specified filename */
207 static int
208 tree_store_load_from (char *name)
210 FILE *file = NULL;
211 char buffer[MC_MAXPATHLEN + 20];
213 g_return_val_if_fail (name != NULL, 0);
215 if (ts.loaded)
216 return 1;
218 file = fopen (name, "r");
220 if (file != NULL
221 && (fgets (buffer, sizeof (buffer), file) == NULL
222 || strncmp (buffer, TREE_SIGNATURE, strlen (TREE_SIGNATURE)) != 0))
224 fclose (file);
225 file = NULL;
228 if (file != NULL)
230 char oldname[MC_MAXPATHLEN] = "\0";
232 ts.loaded = TRUE;
234 /* File open -> read contents */
235 while (fgets (buffer, MC_MAXPATHLEN, file))
237 tree_entry *e;
238 gboolean scanned;
239 char *lc_name;
241 /* Skip invalid records */
242 if ((buffer[0] != '0' && buffer[0] != '1'))
243 continue;
245 if (buffer[1] != ':')
246 continue;
248 scanned = buffer[0] == '1';
250 lc_name = decode (buffer + 2);
251 if (!IS_PATH_SEP (lc_name[0]))
253 /* Clear-text decompression */
254 char *s = strtok (lc_name, " ");
256 if (s != NULL)
258 char *different;
259 int common;
261 common = atoi (s);
262 different = strtok (NULL, "");
263 if (different)
265 vfs_path_t *vpath;
267 vpath = vfs_path_from_str (oldname);
268 strcpy (oldname + common, different);
269 if (vfs_file_is_local (vpath))
271 vfs_path_t *tmp_vpath;
273 tmp_vpath = vfs_path_from_str (oldname);
274 e = tree_store_add_entry (tmp_vpath);
275 vfs_path_free (tmp_vpath);
276 e->scanned = scanned;
278 vfs_path_free (vpath);
282 else
284 vfs_path_t *vpath;
286 vpath = vfs_path_from_str (lc_name);
287 if (vfs_file_is_local (vpath))
289 e = tree_store_add_entry (vpath);
290 e->scanned = scanned;
292 vfs_path_free (vpath);
293 strcpy (oldname, lc_name);
295 g_free (lc_name);
298 fclose (file);
301 /* Nothing loaded, we add some standard directories */
302 if (!ts.tree_first)
304 vfs_path_t *tmp_vpath = vfs_path_from_str (PATH_SEP_STR);
305 tree_store_add_entry (tmp_vpath);
306 tree_store_rescan (tmp_vpath);
307 vfs_path_free (tmp_vpath);
308 ts.loaded = TRUE;
311 return 1;
314 /* --------------------------------------------------------------------------------------------- */
316 static char *
317 encode (const vfs_path_t * vpath, size_t offset)
319 return strutils_escape (vfs_path_as_str (vpath) + offset, -1, "\n\\", FALSE);
322 /* --------------------------------------------------------------------------------------------- */
323 /** Saves the tree to the specified filename */
325 static int
326 tree_store_save_to (char *name)
328 tree_entry *current;
329 FILE *file;
331 file = fopen (name, "w");
332 if (!file)
333 return errno;
335 fprintf (file, "%s\n", TREE_SIGNATURE);
337 current = ts.tree_first;
338 while (current)
340 if (vfs_file_is_local (current->name))
342 int i, common;
344 /* Clear-text compression */
345 if (current->prev && (common = str_common (current->prev->name, current->name)) > 2)
347 char *encoded = encode (current->name, common);
349 i = fprintf (file, "%d:%d %s\n", current->scanned ? 1 : 0, common, encoded);
350 g_free (encoded);
352 else
354 char *encoded = encode (current->name, 0);
356 i = fprintf (file, "%d:%s\n", current->scanned ? 1 : 0, encoded);
357 g_free (encoded);
360 if (i == EOF)
362 fprintf (stderr, _("Cannot write to the %s file:\n%s\n"),
363 name, unix_error_string (errno));
364 break;
367 current = current->next;
369 tree_store_dirty (FALSE);
370 fclose (file);
372 return 0;
375 /* --------------------------------------------------------------------------------------------- */
377 static tree_entry *
378 tree_store_add_entry (const vfs_path_t * name)
380 int flag = -1;
381 tree_entry *current = ts.tree_first;
382 tree_entry *old = NULL;
383 tree_entry *new;
384 int submask = 0;
386 if (ts.tree_last && ts.tree_last->next)
387 abort ();
389 /* Search for the correct place */
390 while (current != NULL && (flag = pathcmp (current->name, name)) < 0)
392 old = current;
393 current = current->next;
396 if (flag == 0)
397 return current; /* Already in the list */
399 /* Not in the list -> add it */
400 new = g_new0 (tree_entry, 1);
401 if (!current)
403 /* Append to the end of the list */
404 if (!ts.tree_first)
406 /* Empty list */
407 ts.tree_first = new;
408 new->prev = NULL;
410 else
412 if (old != NULL)
413 old->next = new;
414 new->prev = old;
416 new->next = NULL;
417 ts.tree_last = new;
419 else
421 /* Insert in to the middle of the list */
422 new->prev = old;
423 if (old != NULL)
425 /* Yes, in the middle */
426 new->next = old->next;
427 old->next = new;
429 else
431 /* Nope, in the beginning of the list */
432 new->next = ts.tree_first;
433 ts.tree_first = new;
435 new->next->prev = new;
438 /* Calculate attributes */
439 new->name = vfs_path_clone (name);
440 new->sublevel = vfs_path_tokens_count (new->name);
442 const char *new_name;
444 new_name = vfs_path_get_last_path_str (new->name);
445 new->subname = strrchr (new_name, PATH_SEP);
446 if (new->subname == NULL)
447 new->subname = new_name;
448 else
449 new->subname++;
451 if (new->next)
452 submask = new->next->submask;
453 else
454 submask = 0;
455 submask |= 1 << new->sublevel;
456 submask &= (2 << new->sublevel) - 1;
457 new->submask = submask;
458 new->mark = FALSE;
460 /* Correct the submasks of the previous entries */
461 current = new->prev;
462 while (current && current->sublevel > new->sublevel)
464 current->submask |= 1 << new->sublevel;
465 current = current->prev;
468 tree_store_dirty (TRUE);
469 return new;
472 /* --------------------------------------------------------------------------------------------- */
474 static void
475 tree_store_notify_remove (tree_entry * entry)
477 hook_t *p = remove_entry_hooks;
479 while (p != NULL)
481 tree_store_remove_fn r = (tree_store_remove_fn) p->hook_fn;
483 r (entry, p->hook_data);
484 p = p->next;
488 /* --------------------------------------------------------------------------------------------- */
490 static tree_entry *
491 remove_entry (tree_entry * entry)
493 tree_entry *current = entry->prev;
494 long submask = 0;
495 tree_entry *ret = NULL;
497 tree_store_notify_remove (entry);
499 /* Correct the submasks of the previous entries */
500 if (entry->next)
501 submask = entry->next->submask;
502 while (current && current->sublevel > entry->sublevel)
504 submask |= 1 << current->sublevel;
505 submask &= (2 << current->sublevel) - 1;
506 current->submask = submask;
507 current = current->prev;
510 /* Unlink the entry from the list */
511 if (entry->prev)
512 entry->prev->next = entry->next;
513 else
514 ts.tree_first = entry->next;
516 if (entry->next)
517 entry->next->prev = entry->prev;
518 else
519 ts.tree_last = entry->prev;
521 /* Free the memory used by the entry */
522 vfs_path_free (entry->name);
523 g_free (entry);
525 return ret;
528 /* --------------------------------------------------------------------------------------------- */
530 static void
531 process_special_dirs (GList ** special_dirs, const char *file)
533 gchar **start_buff;
534 mc_config_t *cfg;
536 cfg = mc_config_init (file, TRUE);
537 if (cfg == NULL)
538 return;
540 start_buff = mc_config_get_string_list (cfg, "Special dirs", "list", NULL);
541 if (start_buff != NULL)
543 gchar **buffers;
545 for (buffers = start_buff; *buffers != NULL; buffers++)
547 *special_dirs = g_list_prepend (*special_dirs, *buffers);
548 *buffers = NULL;
551 g_strfreev (start_buff);
553 mc_config_deinit (cfg);
556 /* --------------------------------------------------------------------------------------------- */
558 static gboolean
559 should_skip_directory (const vfs_path_t * vpath)
561 static GList *special_dirs = NULL;
562 GList *l;
563 static gboolean loaded = FALSE;
564 gboolean ret = FALSE;
566 if (!loaded)
568 const char *profile_name;
570 profile_name = setup_init ();
571 process_special_dirs (&special_dirs, profile_name);
572 process_special_dirs (&special_dirs, global_profile_name);
574 loaded = TRUE;
577 for (l = special_dirs; l != NULL; l = g_list_next (l))
578 if (strncmp (vfs_path_as_str (vpath), l->data, strlen (l->data)) == 0)
580 ret = TRUE;
581 break;
584 return ret;
587 /* --------------------------------------------------------------------------------------------- */
588 /*** public functions ****************************************************************************/
589 /* --------------------------------------------------------------------------------------------- */
591 /* Searches for specified directory */
592 tree_entry *
593 tree_store_whereis (const vfs_path_t * name)
595 tree_entry *current = ts.tree_first;
596 int flag = -1;
598 while (current && (flag = pathcmp (current->name, name)) < 0)
599 current = current->next;
601 if (flag == 0)
602 return current;
603 else
604 return NULL;
607 /* --------------------------------------------------------------------------------------------- */
609 struct TreeStore *
610 tree_store_get (void)
612 return &ts;
615 /* --------------------------------------------------------------------------------------------- */
617 * \fn int tree_store_load(void)
618 * \brief Loads the tree from the default location
619 * \return 1 if success (true), 0 otherwise (false)
623 tree_store_load (void)
625 char *name;
626 int retval;
628 name = mc_config_get_full_path (MC_TREESTORE_FILE);
629 retval = tree_store_load_from (name);
630 g_free (name);
632 return retval;
635 /* --------------------------------------------------------------------------------------------- */
637 * \fn int tree_store_save(void)
638 * \brief Saves the tree to the default file in an atomic fashion
639 * \return 0 if success, errno on error
643 tree_store_save (void)
645 char *name;
646 int retval;
648 name = mc_config_get_full_path (MC_TREESTORE_FILE);
649 mc_util_make_backup_if_possible (name, ".tmp");
651 retval = tree_store_save_to (name);
652 if (retval != 0)
653 mc_util_restore_from_backup_if_possible (name, ".tmp");
654 else
655 mc_util_unlink_backup_if_possible (name, ".tmp");
657 g_free (name);
658 return retval;
661 /* --------------------------------------------------------------------------------------------- */
663 void
664 tree_store_add_entry_remove_hook (tree_store_remove_fn callback, void *data)
666 add_hook (&remove_entry_hooks, (void (*)(void *)) callback, data);
669 /* --------------------------------------------------------------------------------------------- */
671 void
672 tree_store_remove_entry_remove_hook (tree_store_remove_fn callback)
674 delete_hook (&remove_entry_hooks, (void (*)(void *)) callback);
678 /* --------------------------------------------------------------------------------------------- */
680 void
681 tree_store_remove_entry (const vfs_path_t * name_vpath)
683 tree_entry *current, *base;
684 size_t len;
686 g_return_if_fail (name_vpath != NULL);
688 /* Miguel Ugly hack */
690 gboolean is_root;
691 const char *name_vpath_str;
693 name_vpath_str = vfs_path_as_str (name_vpath);
694 is_root = (IS_PATH_SEP (name_vpath_str[0]) && name_vpath_str[1] == '\0');
695 if (is_root)
696 return;
698 /* Miguel Ugly hack end */
700 base = tree_store_whereis (name_vpath);
701 if (!base)
702 return; /* Doesn't exist */
704 len = vfs_path_len (base->name);
705 current = base->next;
706 while (current != NULL && vfs_path_equal_len (current->name, base->name, len))
708 gboolean ok;
709 tree_entry *old;
710 const char *cname;
712 cname = vfs_path_as_str (current->name);
713 ok = (cname[len] == '\0' || IS_PATH_SEP (cname[len]));
714 if (!ok)
715 break;
717 old = current;
718 current = current->next;
719 remove_entry (old);
721 remove_entry (base);
722 tree_store_dirty (TRUE);
725 /* --------------------------------------------------------------------------------------------- */
726 /** This subdirectory exists -> clear deletion mark */
728 void
729 tree_store_mark_checked (const char *subname)
731 vfs_path_t *name;
732 tree_entry *current, *base;
733 int flag = 1;
734 const char *cname;
736 if (!ts.loaded)
737 return;
739 if (ts.check_name == NULL)
740 return;
742 /* Calculate the full name of the subdirectory */
743 if (DIR_IS_DOT (subname) || DIR_IS_DOTDOT (subname))
744 return;
746 cname = vfs_path_as_str (ts.check_name);
747 if (IS_PATH_SEP (cname[0]) && cname[1] == '\0')
748 name = vfs_path_build_filename (PATH_SEP_STR, subname, (char *) NULL);
749 else
750 name = vfs_path_append_new (ts.check_name, subname, (char *) NULL);
752 /* Search for the subdirectory */
753 current = ts.check_start;
754 while (current && (flag = pathcmp (current->name, name)) < 0)
755 current = current->next;
757 if (flag != 0)
759 /* Doesn't exist -> add it */
760 current = tree_store_add_entry (name);
761 ts.add_queue_vpath = g_list_prepend (ts.add_queue_vpath, name);
763 else
764 vfs_path_free (name);
766 /* Clear the deletion mark from the subdirectory and its children */
767 base = current;
768 if (base)
770 size_t len;
772 len = vfs_path_len (base->name);
773 base->mark = FALSE;
774 current = base->next;
775 while (current != NULL && vfs_path_equal_len (current->name, base->name, len))
777 gboolean ok;
779 cname = vfs_path_as_str (current->name);
780 ok = (cname[len] == '\0' || IS_PATH_SEP (cname[len]) || len == 1);
781 if (!ok)
782 break;
784 current->mark = FALSE;
785 current = current->next;
790 /* --------------------------------------------------------------------------------------------- */
791 /** Mark the subdirectories of the current directory for delete */
793 tree_entry *
794 tree_store_start_check (const vfs_path_t * vpath)
796 tree_entry *current, *retval;
797 size_t len;
799 if (!ts.loaded)
800 return NULL;
802 g_return_val_if_fail (ts.check_name == NULL, NULL);
803 ts.check_start = NULL;
805 /* Search for the start of subdirectories */
806 current = tree_store_whereis (vpath);
807 if (!current)
809 struct stat s;
811 if (mc_stat (vpath, &s) == -1)
812 return NULL;
814 if (!S_ISDIR (s.st_mode))
815 return NULL;
817 current = tree_store_add_entry (vpath);
818 ts.check_name = vfs_path_clone (vpath);
820 return current;
823 ts.check_name = vfs_path_clone (vpath);
825 retval = current;
827 /* Mark old subdirectories for delete */
828 ts.check_start = current->next;
829 len = vfs_path_len (ts.check_name);
831 current = ts.check_start;
832 while (current != NULL && vfs_path_equal_len (current->name, ts.check_name, len))
834 gboolean ok;
835 const char *cname;
837 cname = vfs_path_as_str (current->name);
838 ok = (cname[len] == '\0' || IS_PATH_SEP (cname[len]) || len == 1);
839 if (!ok)
840 break;
842 current->mark = TRUE;
843 current = current->next;
846 return retval;
849 /* --------------------------------------------------------------------------------------------- */
850 /** Delete subdirectories which still have the deletion mark */
852 void
853 tree_store_end_check (void)
855 tree_entry *current;
856 size_t len;
857 GList *the_queue;
859 if (!ts.loaded)
860 return;
862 g_return_if_fail (ts.check_name != NULL);
864 /* Check delete marks and delete if found */
865 len = vfs_path_len (ts.check_name);
867 current = ts.check_start;
868 while (current != NULL && vfs_path_equal_len (current->name, ts.check_name, len))
870 gboolean ok;
871 tree_entry *old;
872 const char *cname;
874 cname = vfs_path_as_str (current->name);
875 ok = (cname[len] == '\0' || IS_PATH_SEP (cname[len]) || len == 1);
876 if (!ok)
877 break;
879 old = current;
880 current = current->next;
881 if (old->mark)
882 remove_entry (old);
885 /* get the stuff in the scan order */
886 the_queue = g_list_reverse (ts.add_queue_vpath);
887 ts.add_queue_vpath = NULL;
888 vfs_path_free (ts.check_name);
889 ts.check_name = NULL;
891 g_list_free_full (the_queue, (GDestroyNotify) vfs_path_free);
894 /* --------------------------------------------------------------------------------------------- */
896 tree_entry *
897 tree_store_rescan (const vfs_path_t * vpath)
899 DIR *dirp;
900 struct stat buf;
901 tree_entry *entry;
903 if (should_skip_directory (vpath))
905 entry = tree_store_add_entry (vpath);
906 entry->scanned = TRUE;
907 return entry;
910 entry = tree_store_start_check (vpath);
911 if (entry == NULL)
912 return NULL;
914 dirp = mc_opendir (vpath);
915 if (dirp)
917 struct dirent *dp;
919 for (dp = mc_readdir (dirp); dp; dp = mc_readdir (dirp))
921 vfs_path_t *tmp_vpath;
923 if (DIR_IS_DOT (dp->d_name) || DIR_IS_DOTDOT (dp->d_name))
924 continue;
926 tmp_vpath = vfs_path_append_new (vpath, dp->d_name, (char *) NULL);
927 if (mc_lstat (tmp_vpath, &buf) != -1 && S_ISDIR (buf.st_mode))
928 tree_store_mark_checked (dp->d_name);
929 vfs_path_free (tmp_vpath);
931 mc_closedir (dirp);
933 tree_store_end_check ();
934 entry->scanned = TRUE;
936 return entry;
939 /* --------------------------------------------------------------------------------------------- */