1 /* vifm
2  * Copyright (C) 2001 Ken Steen.
3  * Copyright (C) 2011 xaizek.
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
18  */
19 
20 #include "filelist.h"
21 
22 #ifdef _WIN32
23 #include <windows.h>
24 #include <fcntl.h>
25 #include <lm.h>
26 #include <winioctl.h>
27 #endif
28 
29 #include <curses.h>
30 
31 #include <sys/stat.h> /* stat */
32 
33 #include <assert.h> /* assert() */
34 #include <errno.h> /* errno */
35 #include <limits.h> /* INT_MIN */
36 #include <stddef.h> /* NULL size_t */
37 #include <stdint.h> /* intptr_t uint64_t */
38 #include <stdio.h> /* snprintf() */
39 #include <stdlib.h> /* calloc() free() */
40 #include <string.h> /* memcmp() memcpy() memset() strcat() strcmp() strcpy()
41                        strdup() strlen() */
42 
43 #include "cfg/config.h"
44 #include "compat/fs_limits.h"
45 #include "compat/os.h"
46 #include "engine/autocmds.h"
47 #include "engine/mode.h"
48 #include "int/fuse.h"
49 #include "modes/dialogs/msg_dialog.h"
50 #include "modes/modes.h"
51 #include "modes/view.h"
52 #include "ui/cancellation.h"
53 #include "ui/column_view.h"
54 #include "ui/fileview.h"
55 #include "ui/statusbar.h"
56 #include "ui/statusline.h"
57 #include "ui/tabs.h"
58 #include "ui/ui.h"
59 #include "utils/dynarray.h"
60 #include "utils/env.h"
61 #include "utils/fs.h"
62 #include "utils/fsdata.h"
63 #include "utils/fswatch.h"
64 #include "utils/log.h"
65 #include "utils/macros.h"
66 #include "utils/matcher.h"
67 #include "utils/path.h"
68 #include "utils/regexp.h"
69 #include "utils/str.h"
70 #include "utils/string_array.h"
71 #include "utils/test_helpers.h"
72 #include "utils/trie.h"
73 #include "utils/utf8.h"
74 #include "utils/utils.h"
75 #include "filtering.h"
76 #include "flist_hist.h"
77 #include "flist_pos.h"
78 #include "flist_sel.h"
79 #include "fops_misc.h"
80 #include "macros.h"
81 #include "marks.h"
82 #include "opt_handlers.h"
83 #include "registers.h"
84 #include "running.h"
85 #include "sort.h"
86 #include "status.h"
87 #include "types.h"
88 
89 static void init_flist(view_t *view);
90 static void reset_view(view_t *view);
91 static void init_view_history(view_t *view);
92 static int navigate_to_file_in_custom_view(view_t *view, const char dir[],
93 		const char file[]);
94 static int fill_dir_entry_by_path(dir_entry_t *entry, const char path[]);
95 #ifndef _WIN32
96 static int fill_dir_entry(dir_entry_t *entry, const char path[],
97 		const struct dirent *d);
98 static int data_is_dir_entry(const struct dirent *d, const char path[]);
99 #else
100 static int fill_dir_entry(dir_entry_t *entry, const char path[],
101 		const WIN32_FIND_DATAW *ffd);
102 static int data_is_dir_entry(const WIN32_FIND_DATAW *ffd, const char path[]);
103 #endif
104 static int flist_custom_finish_internal(view_t *view, CVType type, int reload,
105 		const char dir[], int allow_empty);
106 static void on_location_change(view_t *view, int force);
107 static void disable_view_sorting(view_t *view);
108 static void enable_view_sorting(view_t *view);
109 static void exclude_in_compare(view_t *view, int selection_only);
110 static void mark_group(view_t *view, view_t *other, int idx);
111 static int got_excluded(view_t *view, const dir_entry_t *entry, void *arg);
112 static int exclude_temporary_entries(view_t *view);
113 static int is_temporary(view_t *view, const dir_entry_t *entry, void *arg);
114 static uint64_t recalc_entry_size(const dir_entry_t *entry, uint64_t old_size);
115 static uint64_t entry_calc_nitems(const dir_entry_t *entry);
116 static void load_dir_list_internal(view_t *view, int reload, int draw_only);
117 static int populate_dir_list_internal(view_t *view, int reload);
118 static int populate_custom_view(view_t *view, int reload);
119 static int entry_exists(view_t *view, const dir_entry_t *entry, void *arg);
120 static void zap_compare_view(view_t *view, view_t *other, zap_filter filter,
121 		void *arg);
122 static int find_separator(view_t *view, int idx);
123 static void update_dir_watcher(view_t *view);
124 static int custom_list_is_incomplete(const view_t *view);
125 static int is_dead_or_filtered(view_t *view, const dir_entry_t *entry,
126 		void *arg);
127 static void update_entries_data(view_t *view);
128 static int is_dir_big(const char path[]);
129 static void free_view_entries(view_t *view);
130 static int update_dir_list(view_t *view, int reload);
131 static void start_dir_list_change(view_t *view, dir_entry_t **entries, int *len,
132 		int reload);
133 static void finish_dir_list_change(view_t *view, dir_entry_t *entries, int len);
134 static int add_file_entry_to_view(const char name[], const void *data,
135 		void *param);
136 static void sort_dir_list(int msg, view_t *view);
137 static void merge_lists(view_t *view, dir_entry_t *entries, int len);
138 TSTATIC void check_file_uniqueness(view_t *view);
139 static void add_to_trie(trie_t *trie, view_t *view, dir_entry_t *entry);
140 static int is_in_trie(trie_t *trie, view_t *view, dir_entry_t *entry,
141 		void **data);
142 static void merge_entries(dir_entry_t *new, const dir_entry_t *prev);
143 static int correct_pos(view_t *view, int pos, int dist, int closest);
144 static int rescue_from_empty_filelist(view_t *view);
145 static void add_parent_entry(view_t *view, dir_entry_t **entries, int *count);
146 static void init_dir_entry(view_t *view, dir_entry_t *entry, const char name[]);
147 static dir_entry_t * alloc_dir_entry(dir_entry_t **list, int list_size);
148 static int tree_has_changed(const dir_entry_t *entries, size_t nchildren);
149 static void find_dir_in_cdpath(const char base_dir[], const char dst[],
150 		char buf[], size_t buf_size);
151 static entries_t list_sibling_dirs(view_t *view);
152 static entries_t flist_list_in(view_t *view, const char path[], int only_dirs,
153 		int can_include_parent);
154 static dir_entry_t * pick_sibling(view_t *view, entries_t parent_dirs,
155 		int offset, int wrap, int *wrapped);
156 static int iter_entries(view_t *view, dir_entry_t **entry,
157 		entry_predicate pred);
158 static int mark_selected(view_t *view);
159 static int set_position_by_path(view_t *view, const char path[]);
160 static int flist_load_tree_internal(view_t *view, const char path[],
161 		int reload);
162 static int make_tree(view_t *view, const char path[], int reload,
163 		trie_t *excluded_paths);
164 static void tree_from_cv(view_t *view);
165 static int complete_tree(const char name[], int valid, const void *parent_data,
166 		void *data, void *arg);
167 static void reset_entry_list(view_t *view, dir_entry_t **entries, int *count);
168 static void drop_tops(view_t *view, dir_entry_t *entries, int *nentries,
169 		int extra);
170 static int add_files_recursively(view_t *view, const char path[],
171 		trie_t *excluded_paths, int parent_pos, int no_direct_parent);
172 static int file_is_visible(view_t *view, const char name[], int is_dir,
173 		const void *data, int apply_local_filter);
174 static int add_directory_leaf(view_t *view, const char path[], int parent_pos);
175 static int init_parent_entry(view_t *view, dir_entry_t *entry,
176 		const char path[]);
177 
178 void
init_filelists(void)179 init_filelists(void)
180 {
181 	fview_setup();
182 
183 	flist_init_view(&rwin);
184 	flist_init_view(&lwin);
185 
186 	curr_view = &lwin;
187 	other_view = &rwin;
188 }
189 
190 void
flist_init_view(view_t * view)191 flist_init_view(view_t *view)
192 {
193 	init_flist(view);
194 	fview_init(view);
195 
196 	reset_view(view);
197 
198 	init_view_history(view);
199 	fview_sorting_updated(view);
200 }
201 
202 /* Initializes file list part of the view. */
203 static void
init_flist(view_t * view)204 init_flist(view_t *view)
205 {
206 	view->list_pos = 0;
207 	view->last_seen_pos = -1;
208 	view->history_num = 0;
209 	view->history_pos = 0;
210 	view->on_slow_fs = 0;
211 	view->has_dups = 0;
212 
213 	view->watched_dir = NULL;
214 	view->last_dir = NULL;
215 
216 	view->matches = 0;
217 
218 	view->custom.entries = NULL;
219 	view->custom.entry_count = 0;
220 	view->custom.orig_dir = NULL;
221 	view->custom.title = NULL;
222 
223 	/* Load fake empty element to make dir_entry valid. */
224 	view->dir_entry = dynarray_extend(NULL, sizeof(dir_entry_t));
225 	memset(view->dir_entry, 0, sizeof(*view->dir_entry));
226 	view->dir_entry[0].name = strdup("");
227 	view->dir_entry[0].type = FT_DIR;
228 	view->dir_entry[0].hi_num = -1;
229 	view->dir_entry[0].name_dec_num = -1;
230 	view->dir_entry[0].origin = &view->curr_dir[0];
231 	view->list_rows = 1;
232 }
233 
234 void
flist_free_view(view_t * view)235 flist_free_view(view_t *view)
236 {
237 	/* For the application, we don't need to zero out fields after freeing them,
238 	 * but doing so allows reusing this function in tests. */
239 
240 	int i;
241 
242 	for(i = 0; i < view->list_rows; ++i)
243 	{
244 		fentry_free(view, &view->dir_entry[i]);
245 	}
246 	dynarray_free(view->dir_entry);
247 	view->dir_entry = NULL;
248 	view->list_rows = 0;
249 
250 	for(i = 0; i < view->custom.entry_count; ++i)
251 	{
252 		fentry_free(view, &view->custom.entries[i]);
253 	}
254 	dynarray_free(view->custom.entries);
255 	view->custom.entries = NULL;
256 	view->custom.entry_count = 0;
257 
258 	update_string(&view->custom.next_title, NULL);
259 	update_string(&view->custom.orig_dir, NULL);
260 	update_string(&view->custom.title, NULL);
261 	trie_free(view->custom.excluded_paths);
262 	trie_free(view->custom.paths_cache);
263 	view->custom.excluded_paths = NULL;
264 	view->custom.paths_cache = NULL;
265 
266 	for(i = 0; i < view->local_filter.entry_count; ++i)
267 	{
268 		fentry_free(view, &view->local_filter.entries[i]);
269 	}
270 	dynarray_free(view->local_filter.entries);
271 	view->local_filter.entries = NULL;
272 	view->local_filter.entry_count = 0;
273 
274 	/* Two pointer fields below don't contain valid data that needs to be freed,
275 	 * zeroing them for tests and to at least mention them to signal that they
276 	 * weren't forgotten. */
277 	view->local_filter.unfiltered = NULL;
278 	view->local_filter.saved = NULL;
279 	view->local_filter.unfiltered_count = 0;
280 
281 	update_string(&view->local_filter.prev, NULL);
282 	free(view->local_filter.poshist);
283 	view->local_filter.poshist = NULL;
284 
285 	filter_dispose(&view->local_filter.filter);
286 	filter_dispose(&view->auto_filter);
287 	matcher_free(view->manual_filter);
288 	view->manual_filter = NULL;
289 	update_string(&view->prev_manual_filter, NULL);
290 	update_string(&view->prev_auto_filter, NULL);
291 
292 	view->custom.type = CV_REGULAR;
293 
294 	fswatch_free(view->watch);
295 	view->watch = NULL;
296 	update_string(&view->watched_dir, NULL);
297 
298 	update_string(&view->last_dir, NULL);
299 
300 	flist_free_cache(view, &view->left_column);
301 	flist_free_cache(view, &view->right_column);
302 
303 	update_string(&view->last_curr_file, NULL);
304 
305 	free_string_array(view->saved_selection, view->nsaved_selection);
306 	view->nsaved_selection = 0;
307 	view->saved_selection = NULL;
308 
309 	flist_hist_resize(view, 0);
310 
311 	cs_reset(&view->cs);
312 
313 	columns_free(view->columns);
314 	view->columns = NULL;
315 
316 	update_string(&view->view_columns, NULL);
317 	update_string(&view->view_columns_g, NULL);
318 	update_string(&view->sort_groups, NULL);
319 	update_string(&view->sort_groups_g, NULL);
320 	update_string(&view->preview_prg, NULL);
321 	update_string(&view->preview_prg_g, NULL);
322 
323 	modview_info_free(view->vi);
324 	view->vi = NULL;
325 
326 	regfree(&view->primary_group);
327 
328 	marks_clear_view(view);
329 }
330 
331 void
reset_views(void)332 reset_views(void)
333 {
334 	reset_view(&lwin);
335 	reset_view(&rwin);
336 }
337 
338 /* Loads some of view parameters that should be restored on configuration
339  * reloading (e.g. on :restart command). */
340 static void
reset_view(view_t * view)341 reset_view(view_t *view)
342 {
343 	fview_reset(view);
344 	filters_view_reset(view);
345 
346 	view->hide_dot_g = view->hide_dot = 1;
347 
348 	memset(&view->sort[0], SK_NONE, sizeof(view->sort));
349 	ui_view_sort_list_ensure_well_formed(view, view->sort);
350 	memcpy(&view->sort_g[0], &view->sort[0], sizeof(view->sort_g));
351 }
352 
353 /* Allocates memory for view history smartly (handles huge values). */
354 static void
init_view_history(view_t * view)355 init_view_history(view_t *view)
356 {
357 	if(cfg.history_len == 0)
358 		return;
359 
360 	view->history = calloc(cfg.history_len, sizeof(history_t));
361 	while(view->history == NULL)
362 	{
363 		cfg.history_len /= 2;
364 		view->history = calloc(cfg.history_len, sizeof(history_t));
365 	}
366 }
367 
368 void
load_initial_directory(view_t * view,const char dir[])369 load_initial_directory(view_t *view, const char dir[])
370 {
371 	/* Use current working directory as original location for custom views loaded
372 	 * from command-line via "-". */
373 	if(view->curr_dir[0] == '\0' || strcmp(view->curr_dir, "-") == 0)
374 	{
375 		copy_str(view->curr_dir, sizeof(view->curr_dir), dir);
376 	}
377 	else
378 	{
379 		dir = view->curr_dir;
380 	}
381 
382 	if(!is_root_dir(view->curr_dir))
383 	{
384 		chosp(view->curr_dir);
385 	}
386 	if(change_directory(view, dir) < 0)
387 	{
388 		leave_invalid_dir(view);
389 		(void)change_directory(view, view->curr_dir);
390 	}
391 }
392 
393 dir_entry_t *
get_current_entry(const view_t * view)394 get_current_entry(const view_t *view)
395 {
396 	if(view->list_pos < 0 || view->list_pos >= view->list_rows)
397 	{
398 		return NULL;
399 	}
400 
401 	return &view->dir_entry[view->list_pos];
402 }
403 
404 char *
get_current_file_name(view_t * view)405 get_current_file_name(view_t *view)
406 {
407 	if(view->list_pos == -1)
408 	{
409 		static char empty_string[1];
410 		return empty_string;
411 	}
412 	return get_current_entry(view)->name;
413 }
414 
415 void
invert_sorting_order(view_t * view)416 invert_sorting_order(view_t *view)
417 {
418 	if(memcmp(&view->sort_g[0], &view->sort[0], sizeof(view->sort_g)) == 0)
419 	{
420 		view->sort_g[0] = -view->sort_g[0];
421 	}
422 	view->sort[0] = -view->sort[0];
423 }
424 
425 void
leave_invalid_dir(view_t * view)426 leave_invalid_dir(view_t *view)
427 {
428 	struct stat s;
429 	char *const path = view->curr_dir;
430 
431 	if(fuse_try_updir_from_a_mount(path, view))
432 	{
433 		return;
434 	}
435 
436 	/* Use stat() directly to skip all possible optimizations.  It might be a bit
437 	 * heavier, but this is quite rare error-recovery procedure, we can afford it
438 	 * here.  Even in the worst case of going to the root, this isn't that much
439 	 * calls. */
440 	while((os_stat(path, &s) != 0 || !(s.st_mode & S_IFDIR) ||
441 			!directory_accessible(path)) && is_path_well_formed(path))
442 	{
443 		if(fuse_try_updir_from_a_mount(path, view))
444 		{
445 			break;
446 		}
447 
448 		remove_last_path_component(path);
449 	}
450 
451 	ensure_path_well_formed(path);
452 }
453 
454 void
navigate_to(view_t * view,const char path[])455 navigate_to(view_t *view, const char path[])
456 {
457 	if(change_directory(view, path) >= 0)
458 	{
459 		load_dir_list(view, 0);
460 		fview_cursor_redraw(view);
461 	}
462 }
463 
464 void
navigate_back(view_t * view)465 navigate_back(view_t *view)
466 {
467 	const char *const dest = flist_custom_active(view)
468 	                       ? view->custom.orig_dir
469 	                       : view->last_dir;
470 	if(dest != NULL)
471 	{
472 		navigate_to(view, dest);
473 	}
474 }
475 
476 void
navigate_to_file(view_t * view,const char dir[],const char file[],int preserve_cv)477 navigate_to_file(view_t *view, const char dir[], const char file[],
478 		int preserve_cv)
479 {
480 	if(flist_custom_active(view) && preserve_cv)
481 	{
482 		/* Try to find requested element in custom list of files. */
483 		if(navigate_to_file_in_custom_view(view, dir, file) == 0)
484 		{
485 			return;
486 		}
487 	}
488 
489 	/* Do not change directory if we already there. */
490 	if(!paths_are_equal(view->curr_dir, dir))
491 	{
492 		if(change_directory(view, dir) >= 0)
493 		{
494 			load_dir_list(view, 0);
495 		}
496 	}
497 
498 	if(paths_are_equal(view->curr_dir, dir))
499 	{
500 		(void)fpos_ensure_selected(view, file);
501 	}
502 }
503 
504 /* navigate_to_file() helper that tries to find requested file in custom view.
505  * Returns non-zero on failure to find such file, otherwise zero is returned. */
506 static int
navigate_to_file_in_custom_view(view_t * view,const char dir[],const char file[])507 navigate_to_file_in_custom_view(view_t *view, const char dir[],
508 		const char file[])
509 {
510 	char full_path[PATH_MAX + 1];
511 
512 	snprintf(full_path, sizeof(full_path), "%s/%s", dir, file);
513 
514 	if(custom_list_is_incomplete(view))
515 	{
516 		const dir_entry_t *entry;
517 		entry = entry_from_path(view, view->local_filter.entries,
518 				view->local_filter.entry_count, full_path);
519 		if(entry == NULL)
520 		{
521 			/* No such entry in the view at all. */
522 			return 1;
523 		}
524 
525 		if(!local_filter_matches(view, entry))
526 		{
527 			/* The item is filtered-out by current settings, undo this filtering. */
528 			local_filter_remove(view);
529 			load_dir_list(view, 1);
530 		}
531 	}
532 
533 	if(set_position_by_path(view, full_path) != 0)
534 	{
535 		/* File might not exist anymore at that location. */
536 		return 1;
537 	}
538 
539 	ui_view_schedule_redraw(view);
540 	return 0;
541 }
542 
543 int
change_directory(view_t * view,const char directory[])544 change_directory(view_t *view, const char directory[])
545 {
546 	/* TODO: refactor this big function change_directory(). */
547 
548 	char dir_dup[PATH_MAX + 1];
549 	const int was_in_custom_view = flist_custom_active(view);
550 	int location_changed;
551 
552 	if(is_dir_list_loaded(view))
553 	{
554 		flist_hist_save(view);
555 	}
556 
557 	if(is_path_absolute(directory))
558 	{
559 		canonicalize_path(directory, dir_dup, sizeof(dir_dup));
560 	}
561 	else
562 	{
563 		char newdir[PATH_MAX + 1];
564 		const char *const dir = flist_get_dir(view);
565 #ifdef _WIN32
566 		if(directory[0] == '/')
567 		{
568 			snprintf(newdir, sizeof(newdir), "%c:%s", dir[0], directory);
569 		}
570 		else
571 #endif
572 		{
573 			snprintf(newdir, sizeof(newdir), "%s/%s", dir, directory);
574 		}
575 		canonicalize_path(newdir, dir_dup, sizeof(dir_dup));
576 	}
577 
578 	system_to_internal_slashes(dir_dup);
579 
580 	if(cfg.chase_links)
581 	{
582 		char real_path[PATH_MAX + 1];
583 		if(os_realpath(dir_dup, real_path) == real_path)
584 		{
585 			/* Do this on success only, if realpath() fails, just go with the original
586 			 * path. */
587 			canonicalize_path(real_path, dir_dup, sizeof(dir_dup));
588 			system_to_internal_slashes(dir_dup);
589 		}
590 	}
591 
592 	if(!is_root_dir(dir_dup))
593 		chosp(dir_dup);
594 
595 	if(!is_valid_dir(dir_dup))
596 	{
597 		show_error_msgf("Directory Access Error", "Cannot open %s", dir_dup);
598 		copy_str(view->curr_dir, sizeof(view->curr_dir), dir_dup);
599 		leave_invalid_dir(view);
600 		copy_str(dir_dup, sizeof(dir_dup), view->curr_dir);
601 	}
602 
603 	location_changed = (stroscmp(dir_dup, flist_get_dir(view)) != 0);
604 
605 	/* Check if we're exiting from a FUSE mounted top level directory. */
606 	if(is_parent_dir(directory) && fuse_is_mount_point(view->curr_dir))
607 	{
608 		/* No other pane in any tab should be inside subtree that might be
609 		 * unmounted. */
610 		if(tabs_visitor_count(view->curr_dir) == 1)
611 		{
612 			const int r = fuse_try_unmount(view);
613 			if(r != 0)
614 			{
615 				return r;
616 			}
617 		}
618 		else if(fuse_try_updir_from_a_mount(view->curr_dir, view))
619 		{
620 			/* On success fuse_try_updir_from_a_mount() calls change_directory()
621 			 * recursively to change directory, so we can just leave. */
622 			return 1;
623 		}
624 	}
625 
626 	/* Clean up any excess separators */
627 	if(!is_root_dir(view->curr_dir))
628 		chosp(view->curr_dir);
629 	if(view->last_dir != NULL && !is_root_dir(view->last_dir))
630 		chosp(view->last_dir);
631 
632 #ifndef _WIN32
633 	if(!path_exists(dir_dup, DEREF))
634 #else
635 	if(!is_valid_dir(dir_dup))
636 #endif
637 	{
638 		LOG_SERROR_MSG(errno, "Can't access \"%s\"", dir_dup);
639 		log_cwd();
640 
641 		show_error_msgf("Directory Access Error", "Cannot open %s", dir_dup);
642 
643 		flist_sel_stash(view);
644 		return -1;
645 	}
646 
647 	if(os_access(dir_dup, X_OK) != 0 && !is_unc_root(dir_dup))
648 	{
649 		LOG_SERROR_MSG(errno, "Can't access(, X_OK) \"%s\"", dir_dup);
650 		log_cwd();
651 
652 		show_error_msgf("Directory Access Error",
653 				"You do not have execute access on %s", dir_dup);
654 
655 		flist_sel_stash(view);
656 		return -1;
657 	}
658 
659 	if(os_access(dir_dup, R_OK) != 0 && !is_unc_root(dir_dup))
660 	{
661 		LOG_SERROR_MSG(errno, "Can't access(, R_OK) \"%s\"", dir_dup);
662 		log_cwd();
663 
664 		if(location_changed)
665 		{
666 			show_error_msgf("Directory Access Error",
667 					"You do not have read access on %s", dir_dup);
668 		}
669 	}
670 
671 	if(vifm_chdir(dir_dup) != 0 && !is_unc_root(dir_dup))
672 	{
673 		LOG_SERROR_MSG(errno, "Can't chdir() \"%s\"", dir_dup);
674 		log_cwd();
675 
676 		show_error_msgf("Change Directory Error", "Couldn't open %s", dir_dup);
677 		return -1;
678 	}
679 
680 	if(!is_root_dir(dir_dup))
681 	{
682 		chosp(dir_dup);
683 	}
684 
685 	flist_sel_view_reloaded(view, location_changed);
686 
687 	/* Need to use dir_dup instead of calling get_cwd() to avoid resolving
688 	 * symbolic links in path. */
689 	env_set("PWD", dir_dup);
690 
691 	if(location_changed)
692 	{
693 		replace_string(&view->last_dir, flist_get_dir(view));
694 		view->on_slow_fs = is_on_slow_fs(dir_dup, cfg.slow_fs_list);
695 	}
696 
697 	copy_str(view->curr_dir, sizeof(view->curr_dir), dir_dup);
698 
699 	if(is_dir_list_loaded(view))
700 	{
701 		flist_hist_setup(view, NULL, "", -1, -1);
702 	}
703 
704 	/* Perform additional actions on leaving custom view. */
705 	if(was_in_custom_view)
706 	{
707 		if(ui_view_unsorted(view))
708 		{
709 			enable_view_sorting(view);
710 		}
711 		if(cv_compare(view->custom.type))
712 		{
713 			view_t *const other = (view == curr_view) ? other_view : curr_view;
714 
715 			/* Indicate that this is not a compare view anymore. */
716 			view->custom.type = CV_REGULAR;
717 
718 			/* Leave compare mode in both views at the same time. */
719 			if(other->custom.type == CV_DIFF)
720 			{
721 				rn_leave(other, 1);
722 			}
723 		}
724 	}
725 
726 	if(location_changed || was_in_custom_view)
727 	{
728 		on_location_change(view, location_changed);
729 	}
730 	if(location_changed)
731 	{
732 		view->location_changed = 1;
733 	}
734 	return 0;
735 }
736 
737 int
is_dir_list_loaded(view_t * view)738 is_dir_list_loaded(view_t *view)
739 {
740 	dir_entry_t *const entry = (view->list_rows < 1) ? NULL : &view->dir_entry[0];
741 	return entry != NULL && entry->name[0] != '\0';
742 }
743 
744 #ifdef _WIN32
745 /* Appends list of shares to filelist of the view.  Returns zero on success,
746  * otherwise non-zero is returned. */
747 static int
fill_with_shared(view_t * view)748 fill_with_shared(view_t *view)
749 {
750 	NET_API_STATUS res;
751 	wchar_t *wserver;
752 
753 	wserver = to_wide(view->curr_dir + 2);
754 	if(wserver == NULL)
755 	{
756 		show_error_msg("Memory Error", "Unable to allocate enough memory");
757 		return 1;
758 	}
759 
760 	do
761 	{
762 		PSHARE_INFO_0 buf_ptr;
763 		DWORD er = 0, tr = 0, resume = 0;
764 
765 		res = NetShareEnum(wserver, 0, (LPBYTE *)&buf_ptr, -1, &er, &tr, &resume);
766 		if(res == ERROR_SUCCESS || res == ERROR_MORE_DATA)
767 		{
768 			PSHARE_INFO_0 p;
769 			DWORD i;
770 
771 			for(i = 0, p = buf_ptr; i < er; ++i, ++p)
772 			{
773 				dir_entry_t *dir_entry;
774 				char *utf8_name;
775 
776 				dir_entry = alloc_dir_entry(&view->dir_entry, view->list_rows);
777 				if(dir_entry == NULL)
778 				{
779 					show_error_msg("Memory Error", "Unable to allocate enough memory");
780 					continue;
781 				}
782 
783 				utf8_name = utf8_from_utf16((wchar_t *)p->shi0_netname);
784 
785 				init_dir_entry(view, dir_entry, utf8_name);
786 				dir_entry->type = FT_DIR;
787 
788 				free(utf8_name);
789 
790 				++view->list_rows;
791 			}
792 			NetApiBufferFree(buf_ptr);
793 		}
794 	}
795 	while(res == ERROR_MORE_DATA);
796 
797 	free(wserver);
798 
799 	return (res != ERROR_SUCCESS);
800 }
801 #endif
802 
803 char *
get_typed_entry_fpath(const dir_entry_t * entry)804 get_typed_entry_fpath(const dir_entry_t *entry)
805 {
806 	const char *type_suffix;
807 	char full_path[PATH_MAX + 1];
808 
809 	type_suffix = fentry_is_dir(entry) ? "/" : "";
810 
811 	get_full_path_of(entry, sizeof(full_path), full_path);
812 	return format_str("%s%s", full_path, type_suffix);
813 }
814 
815 int
flist_custom_active(const view_t * view)816 flist_custom_active(const view_t *view)
817 {
818 	/* First check isn't enough on startup, which leads to false positives.  Yet
819 	 * this implicit condition seems to be preferable to omit introducing function
820 	 * that would terminate custom view mode. */
821 	return view->curr_dir[0] == '\0' && !is_null_or_empty(view->custom.orig_dir);
822 }
823 
824 void
flist_custom_start(view_t * view,const char title[])825 flist_custom_start(view_t *view, const char title[])
826 {
827 	free_dir_entries(view, &view->custom.entries, &view->custom.entry_count);
828 	(void)replace_string(&view->custom.next_title, title);
829 
830 	trie_free(view->custom.paths_cache);
831 	view->custom.paths_cache = trie_create();
832 }
833 
834 dir_entry_t *
flist_custom_add(view_t * view,const char path[])835 flist_custom_add(view_t *view, const char path[])
836 {
837 	char canonic_path[PATH_MAX + 1];
838 	to_canonic_path(path, flist_get_dir(view), canonic_path,
839 			sizeof(canonic_path));
840 
841 	/* Don't add duplicates. */
842 	if(trie_put(view->custom.paths_cache, canonic_path) != 0)
843 	{
844 		return NULL;
845 	}
846 
847 	return entry_list_add(view, &view->custom.entries, &view->custom.entry_count,
848 			canonic_path);
849 }
850 
851 dir_entry_t *
flist_custom_put(view_t * view,dir_entry_t * entry)852 flist_custom_put(view_t *view, dir_entry_t *entry)
853 {
854 	char full_path[PATH_MAX + 1];
855 	dir_entry_t *dir_entry;
856 	size_t list_size = view->custom.entry_count;
857 
858 	get_full_path_of(entry, sizeof(full_path), full_path);
859 
860 	/* Don't add duplicates. */
861 	if(trie_put(view->custom.paths_cache, full_path) != 0)
862 	{
863 		return NULL;
864 	}
865 
866 	dir_entry = add_dir_entry(&view->custom.entries, &list_size, entry);
867 	view->custom.entry_count = list_size;
868 	return dir_entry;
869 }
870 
871 void
flist_custom_add_separator(view_t * view,int id)872 flist_custom_add_separator(view_t *view, int id)
873 {
874 	dir_entry_t *const dir_entry = alloc_dir_entry(&view->custom.entries,
875 			view->custom.entry_count);
876 	if(dir_entry != NULL)
877 	{
878 		init_dir_entry(view, dir_entry, "");
879 		dir_entry->origin = strdup(flist_get_dir(view));
880 		dir_entry->id = id;
881 		++view->custom.entry_count;
882 	}
883 }
884 
885 #ifndef _WIN32
886 
887 /* Fills directory entry with information about file specified by the path.
888  * Returns non-zero on error, otherwise zero is returned. */
889 static int
fill_dir_entry_by_path(dir_entry_t * entry,const char path[])890 fill_dir_entry_by_path(dir_entry_t *entry, const char path[])
891 {
892 	return fill_dir_entry(entry, path, NULL);
893 }
894 
895 /* Fills fields of the entry from stat information of the file specified by its
896  * path.  d is optional source of file type.  Returns zero on success, otherwise
897  * non-zero is returned. */
898 static int
fill_dir_entry(dir_entry_t * entry,const char path[],const struct dirent * d)899 fill_dir_entry(dir_entry_t *entry, const char path[], const struct dirent *d)
900 {
901 	struct stat s;
902 
903 	/* Load the inode information or leave blank values in the entry. */
904 	if(os_lstat(path, &s) != 0)
905 	{
906 		LOG_SERROR_MSG(errno, "Can't lstat() \"%s\"", path);
907 		return 1;
908 	}
909 
910 	entry->type = get_type_from_mode(s.st_mode);
911 	if(entry->type == FT_UNK)
912 	{
913 		entry->type = (d == NULL) ? FT_UNK : type_from_dir_entry(d, path);
914 	}
915 	if(entry->type == FT_UNK)
916 	{
917 		LOG_ERROR_MSG("Can't determine type of \"%s\"", path);
918 		return 1;
919 	}
920 
921 	entry->size = (uintmax_t)s.st_size;
922 	entry->uid = s.st_uid;
923 	entry->gid = s.st_gid;
924 	entry->mode = s.st_mode;
925 	entry->inode = s.st_ino;
926 	entry->mtime = s.st_mtime;
927 	entry->atime = s.st_atime;
928 	entry->ctime = s.st_ctime;
929 	entry->nlinks = s.st_nlink;
930 
931 	if(entry->type == FT_LINK)
932 	{
933 		struct stat s;
934 
935 		const SymLinkType symlink_type = get_symlink_type(path);
936 		entry->dir_link = (symlink_type != SLT_UNKNOWN);
937 
938 		/* Query mode of symbolic link target. */
939 		if(symlink_type != SLT_SLOW && os_stat(entry->name, &s) == 0)
940 		{
941 			entry->mode = s.st_mode;
942 		}
943 	}
944 
945 	return 0;
946 }
947 
948 /* Checks whether file is a directory.  Returns non-zero if so, otherwise zero
949  * is returned. */
950 static int
data_is_dir_entry(const struct dirent * d,const char path[])951 data_is_dir_entry(const struct dirent *d, const char path[])
952 {
953 	return is_dirent_targets_dir(path, d);
954 }
955 
956 #else
957 
958 /* Fills directory entry with information about file specified by the path.
959  * Returns non-zero on error, otherwise zero is returned. */
960 static int
fill_dir_entry_by_path(dir_entry_t * entry,const char path[])961 fill_dir_entry_by_path(dir_entry_t *entry, const char path[])
962 {
963 	wchar_t *utf16_path;
964 	HANDLE hfind;
965 	WIN32_FIND_DATAW ffd;
966 
967 	utf16_path = utf8_to_utf16(path);
968 	hfind = FindFirstFileW(utf16_path, &ffd);
969 	free(utf16_path);
970 
971 	if(hfind == INVALID_HANDLE_VALUE)
972 	{
973 		return 1;
974 	}
975 
976 	fill_dir_entry(entry, path, &ffd);
977 
978 	FindClose(hfind);
979 
980 	return 0;
981 }
982 
983 /* Fills fields of the entry from *ffd fields for the file specified by its
984  * path.  type_hint is additional source of file type.  Returns zero on success,
985  * Returns zero on success, otherwise non-zero is returned. */
986 static int
fill_dir_entry(dir_entry_t * entry,const char path[],const WIN32_FIND_DATAW * ffd)987 fill_dir_entry(dir_entry_t *entry, const char path[],
988 		const WIN32_FIND_DATAW *ffd)
989 {
990 	entry->size = (ffd->nFileSizeHigh*((uint64_t)MAXDWORD + 1))
991 	            + ffd->nFileSizeLow;
992 	entry->attrs = ffd->dwFileAttributes;
993 	entry->mtime = win_to_unix_time(ffd->ftLastWriteTime);
994 	entry->atime = win_to_unix_time(ffd->ftLastAccessTime);
995 	entry->ctime = win_to_unix_time(ffd->ftCreationTime);
996 
997 	if(is_win_symlink(ffd->dwFileAttributes, ffd->dwReserved0))
998 	{
999 		const SymLinkType symlink_type = get_symlink_type(path);
1000 		entry->dir_link = (symlink_type != SLT_UNKNOWN);
1001 
1002 		entry->type = FT_LINK;
1003 	}
1004 	else if(ffd->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
1005 	{
1006 		/* Windows doesn't like returning size of directories when it can. */
1007 		entry->size = get_file_size(entry->name);
1008 		entry->type = FT_DIR;
1009 	}
1010 	else if(is_win_executable(path))
1011 	{
1012 		entry->type = FT_EXEC;
1013 	}
1014 	else
1015 	{
1016 		entry->type = FT_REG;
1017 	}
1018 
1019 	return 0;
1020 }
1021 
1022 /* Checks whether file is a directory.  Returns non-zero if so, otherwise zero
1023  * is returned. */
1024 static int
data_is_dir_entry(const WIN32_FIND_DATAW * ffd,const char path[])1025 data_is_dir_entry(const WIN32_FIND_DATAW *ffd, const char path[])
1026 {
1027 	return (ffd->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0;
1028 }
1029 
1030 #endif
1031 
1032 int
flist_custom_finish(view_t * view,CVType type,int allow_empty)1033 flist_custom_finish(view_t *view, CVType type, int allow_empty)
1034 {
1035 	return flist_custom_finish_internal(view, type, 0, flist_get_dir(view),
1036 			allow_empty);
1037 }
1038 
1039 /* Finishes file list population, handles empty resulting list corner case.
1040  * reload flag suppresses actions taken on location change.  dir is current
1041  * directory of the view.  Returns zero on success, otherwise (on empty list)
1042  * non-zero is returned. */
1043 static int
flist_custom_finish_internal(view_t * view,CVType type,int reload,const char dir[],int allow_empty)1044 flist_custom_finish_internal(view_t *view, CVType type, int reload,
1045 		const char dir[], int allow_empty)
1046 {
1047 	enum { NORMAL, CUSTOM, UNSORTED } previous;
1048 	const int empty_view = (view->custom.entry_count == 0);
1049 
1050 	trie_free(view->custom.paths_cache);
1051 	view->custom.paths_cache = NULL;
1052 
1053 	if(empty_view && !allow_empty)
1054 	{
1055 		free_dir_entries(view, &view->custom.entries, &view->custom.entry_count);
1056 		update_string(&view->custom.next_title, NULL);
1057 		return 1;
1058 	}
1059 
1060 	free(view->custom.title);
1061 	view->custom.title = view->custom.next_title;
1062 	view->custom.next_title = NULL;
1063 
1064 	/* If there are no files and we are allowed to add ".." directory, do it. */
1065 	if(empty_view || (!cv_unsorted(type) && cfg_parent_dir_is_visible(0)))
1066 	{
1067 		dir_entry_t *const dir_entry = alloc_dir_entry(&view->custom.entries,
1068 				view->custom.entry_count);
1069 		if(dir_entry != NULL)
1070 		{
1071 			init_dir_entry(view, dir_entry, "..");
1072 			dir_entry->type = FT_DIR;
1073 			dir_entry->origin = strdup(dir);
1074 			++view->custom.entry_count;
1075 		}
1076 	}
1077 
1078 	previous = (view->curr_dir[0] != '\0')
1079 	         ? NORMAL
1080 	         : (ui_view_unsorted(view) ? UNSORTED : CUSTOM);
1081 
1082 	if(previous == NORMAL)
1083 	{
1084 		/* Save current location in the directory before replacing it with custom
1085 		 * view. */
1086 		if(is_dir_list_loaded(view))
1087 		{
1088 			flist_hist_save(view);
1089 		}
1090 
1091 		(void)replace_string(&view->custom.orig_dir, dir);
1092 		view->curr_dir[0] = '\0';
1093 	}
1094 
1095 	/* Replace view file list with custom list. */
1096 	free_dir_entries(view, &view->dir_entry, &view->list_rows);
1097 	view->dir_entry = view->custom.entries;
1098 	view->list_rows = view->custom.entry_count;
1099 	view->custom.entries = NULL;
1100 	view->custom.entry_count = 0;
1101 	view->dir_entry = dynarray_shrink(view->dir_entry);
1102 	view->filtered = 0;
1103 	view->matches = 0;
1104 
1105 	/* Kind of custom view must be set to correct value before option loading and
1106 	 * sorting. */
1107 	view->custom.type = type;
1108 
1109 	if(cv_unsorted(type))
1110 	{
1111 		/* Disabling sorting twice in a row erases sorting completely. */
1112 		if(previous != UNSORTED)
1113 		{
1114 			disable_view_sorting(view);
1115 		}
1116 	}
1117 	else if(previous == UNSORTED)
1118 	{
1119 		enable_view_sorting(view);
1120 	}
1121 
1122 	if(!reload)
1123 	{
1124 		on_location_change(view, 0);
1125 		if(cfg.cvoptions & CVO_AUTOCMDS)
1126 		{
1127 			vle_aucmd_execute("DirEnter", flist_get_dir(view), view);
1128 		}
1129 	}
1130 
1131 	sort_dir_list(0, view);
1132 
1133 	ui_view_schedule_redraw(view);
1134 	fpos_ensure_valid_pos(view);
1135 
1136 	return 0;
1137 }
1138 
1139 /* Perform actions on view location change.  Force activates all actions
1140  * unconditionally otherwise they are checked against cvoptions. */
1141 static void
on_location_change(view_t * view,int force)1142 on_location_change(view_t *view, int force)
1143 {
1144 	view->has_dups = 0;
1145 
1146 	free_dir_entries(view, &view->local_filter.entries,
1147 			&view->local_filter.entry_count);
1148 
1149 	if(force || (cfg.cvoptions & CVO_LOCALFILTER))
1150 	{
1151 		filters_dir_updated(view);
1152 	}
1153 	/* Stage check is to skip body of the if in tests. */
1154 	if(curr_stats.load_stage > 0 && (force || (cfg.cvoptions & CVO_LOCALOPTS)))
1155 	{
1156 		reset_local_options(view);
1157 	}
1158 }
1159 
1160 /* Disables view sorting saving its state for the future. */
1161 static void
disable_view_sorting(view_t * view)1162 disable_view_sorting(view_t *view)
1163 {
1164 	memcpy(&view->custom.sort[0], &view->sort[0], sizeof(view->custom.sort));
1165 	memset(&view->sort[0], SK_NONE, sizeof(view->sort));
1166 	load_sort_option(view);
1167 }
1168 
1169 /* Undoes was was done by disable_view_sorting(). */
1170 static void
enable_view_sorting(view_t * view)1171 enable_view_sorting(view_t *view)
1172 {
1173 	memcpy(&view->sort[0], &view->custom.sort[0], sizeof(view->sort));
1174 	load_sort_option(view);
1175 }
1176 
1177 void
flist_custom_exclude(view_t * view,int selection_only)1178 flist_custom_exclude(view_t *view, int selection_only)
1179 {
1180 	flist_set_marking(view, 0);
1181 
1182 	if(!flist_custom_active(view))
1183 	{
1184 		return;
1185 	}
1186 
1187 	if(cv_compare(view->custom.type))
1188 	{
1189 		exclude_in_compare(view, selection_only);
1190 		return;
1191 	}
1192 
1193 	dir_entry_t *entry = NULL;
1194 	trie_t *excluded = trie_create();
1195 	while(iter_marked_entries(view, &entry))
1196 	{
1197 		char full_path[PATH_MAX + 1];
1198 
1199 		entry->temporary = 1;
1200 
1201 		get_full_path_of(entry, sizeof(full_path), full_path);
1202 		(void)trie_put(excluded, full_path);
1203 
1204 		if(cv_tree(view->custom.type))
1205 		{
1206 			(void)trie_put(view->custom.excluded_paths, full_path);
1207 		}
1208 	}
1209 
1210 	/* Update copy of list of entries made by local filter (it might be empty,
1211 	 * which is OK). */
1212 	(void)zap_entries(view, view->local_filter.entries,
1213 			&view->local_filter.entry_count, &got_excluded, excluded, 1, 1);
1214 	trie_free(excluded);
1215 
1216 	(void)exclude_temporary_entries(view);
1217 }
1218 
1219 /* Removes marked files from compare view.  Zero selection_only enables
1220  * excluding files that share ids with selected items. */
1221 static void
exclude_in_compare(view_t * view,int selection_only)1222 exclude_in_compare(view_t *view, int selection_only)
1223 {
1224 	view_t *const other = (view == curr_view) ? other_view : curr_view;
1225 	const int double_compare = (view->custom.type == CV_DIFF);
1226 	const int n = other->list_rows;
1227 	dir_entry_t *entry = NULL;
1228 	while(iter_marked_entries(view, &entry))
1229 	{
1230 		if(selection_only)
1231 		{
1232 			entry->temporary = 1;
1233 			if(double_compare)
1234 			{
1235 				other->dir_entry[entry - view->dir_entry].temporary = 1;
1236 			}
1237 		}
1238 		else
1239 		{
1240 			mark_group(view, other, entry - view->dir_entry);
1241 		}
1242 	}
1243 
1244 	(void)exclude_temporary_entries(view);
1245 	if(double_compare && exclude_temporary_entries(other) == n)
1246 	{
1247 		/* Leave compare mode if we excluded all files. */
1248 		rn_leave(view, 1);
1249 	}
1250 }
1251 
1252 /* Selects all neighbours of the idx-th element that share its id. */
1253 static void
mark_group(view_t * view,view_t * other,int idx)1254 mark_group(view_t *view, view_t *other, int idx)
1255 {
1256 	int i;
1257 	int id;
1258 
1259 	if(view->dir_entry[idx].temporary)
1260 	{
1261 		return;
1262 	}
1263 
1264 	id = view->dir_entry[idx].id;
1265 
1266 	for(i = idx - 1; i >= 0 && view->dir_entry[i].id == id; --i)
1267 	{
1268 		view->dir_entry[i].temporary = 1;
1269 		if(view->custom.type == CV_DIFF)
1270 		{
1271 			other->dir_entry[i].temporary = 1;
1272 		}
1273 	}
1274 	for(i = idx; i < view->list_rows && view->dir_entry[i].id == id; ++i)
1275 	{
1276 		view->dir_entry[i].temporary = 1;
1277 		if(view->custom.type == CV_DIFF)
1278 		{
1279 			other->dir_entry[i].temporary = 1;
1280 		}
1281 	}
1282 }
1283 
1284 /* zap_entries() filter to filter-out files, which were just excluded from the
1285  * view.  Returns non-zero if entry is to be kept and zero otherwise. */
1286 static int
got_excluded(view_t * view,const dir_entry_t * entry,void * arg)1287 got_excluded(view_t *view, const dir_entry_t *entry, void *arg)
1288 {
1289 	void *data;
1290 	trie_t *const excluded = arg;
1291 	int excluded_entry;
1292 
1293 	char full_path[PATH_MAX + 1];
1294 	get_full_path_of(entry, sizeof(full_path), full_path);
1295 
1296 	excluded_entry = (trie_get(excluded, full_path, &data) == 0);
1297 	return !excluded_entry;
1298 }
1299 
1300 /* Excludes view entries that are marked as "temporary".  Returns number of
1301  * items that were visible before. */
1302 static int
exclude_temporary_entries(view_t * view)1303 exclude_temporary_entries(view_t *view)
1304 {
1305 	const int n = zap_entries(view, view->dir_entry, &view->list_rows,
1306 			&is_temporary, NULL, 0, 1);
1307 	(void)zap_entries(view, view->custom.entries, &view->custom.entry_count,
1308 			&is_temporary, NULL, 1, 1);
1309 
1310 	fpos_ensure_valid_pos(view);
1311 	ui_view_schedule_redraw(view);
1312 
1313 	return n;
1314 }
1315 
1316 /* zap_entries() filter to filter-out files, which were marked for removal.
1317  * Returns non-zero if entry is to be kept and zero otherwise.*/
1318 static int
is_temporary(view_t * view,const dir_entry_t * entry,void * arg)1319 is_temporary(view_t *view, const dir_entry_t *entry, void *arg)
1320 {
1321 	return !entry->temporary;
1322 }
1323 
1324 void
flist_custom_clone(view_t * to,const view_t * from,int as_tree)1325 flist_custom_clone(view_t *to, const view_t *from, int as_tree)
1326 {
1327 	dir_entry_t *dst, *src;
1328 	int nentries;
1329 	int i, j;
1330 	const int from_tree = cv_tree(from->custom.type);
1331 
1332 	assert(flist_custom_active(from) && to->custom.paths_cache == NULL &&
1333 			"Wrong state of destination view.");
1334 
1335 	replace_string(&to->custom.orig_dir, from->custom.orig_dir);
1336 	to->curr_dir[0] = '\0';
1337 
1338 	replace_string(&to->custom.title,
1339 			(from_tree && !as_tree) ? "from tree" : from->custom.title);
1340 	to->custom.type = (ui_view_unsorted(from) || from_tree)
1341 	                ? (as_tree ? CV_CUSTOM_TREE : CV_VERY)
1342 	                : CV_REGULAR;
1343 
1344 	if(custom_list_is_incomplete(from))
1345 	{
1346 		src = from->local_filter.entries;
1347 		nentries = from->local_filter.entry_count;
1348 	}
1349 	else
1350 	{
1351 		src = from->dir_entry;
1352 		nentries = from->list_rows;
1353 	}
1354 
1355 	dst = dynarray_extend(NULL, nentries*sizeof(*dst));
1356 
1357 	j = 0;
1358 	for(i = 0; i < nentries; ++i)
1359 	{
1360 		if(!cv_tree(to->custom.type) && src[i].child_pos != 0 &&
1361 				is_parent_dir(src[i].name))
1362 		{
1363 			continue;
1364 		}
1365 
1366 		dst[j] = src[i];
1367 		dst[j].name = strdup(dst[j].name);
1368 		if(dst[j].origin == from->curr_dir)
1369 		{
1370 			dst[j].origin = to->curr_dir;
1371 		}
1372 		else
1373 		{
1374 			dst[j].origin = strdup(dst[j].origin);
1375 		}
1376 
1377 		if(!as_tree)
1378 		{
1379 			/* As destination pane won't be a tree, erase tree-specific data, because
1380 			 * some tree-specific code is driven directly by these fields. */
1381 			dst[j].child_count = 0;
1382 			dst[j].child_pos = 0;
1383 		}
1384 
1385 		++j;
1386 	}
1387 
1388 	free_dir_entries(to, &to->custom.entries, &to->custom.entry_count);
1389 	free_dir_entries(to, &to->dir_entry, &to->list_rows);
1390 	to->dir_entry = dst;
1391 	to->list_rows = j;
1392 
1393 	to->filtered = 0;
1394 
1395 	if(ui_view_unsorted(to))
1396 	{
1397 		disable_view_sorting(to);
1398 	}
1399 }
1400 
1401 void
flist_custom_uncompress_tree(view_t * view)1402 flist_custom_uncompress_tree(view_t *view)
1403 {
1404 	unsigned int i;
1405 
1406 	assert(cv_tree(view->custom.type) && "This function is for tree-view only!");
1407 
1408 	dir_entry_t *entries = view->dir_entry;
1409 	size_t nentries = view->list_rows;
1410 	int restore_parent = 0;
1411 
1412 	fsdata_t *const tree = fsdata_create(0, 0);
1413 
1414 	view->dir_entry = NULL;
1415 	view->list_rows = 0;
1416 
1417 	for(i = 0U; i < nentries; ++i)
1418 	{
1419 		char full_path[PATH_MAX + 1];
1420 		void *data = &entries[i];
1421 
1422 		if(entries[i].child_pos == 0 && is_parent_dir(entries[i].name))
1423 		{
1424 			fentry_free(view, &entries[i]);
1425 			restore_parent = 1;
1426 			continue;
1427 		}
1428 
1429 		get_full_path_of(&entries[i], sizeof(full_path), full_path);
1430 		fsdata_set(tree, full_path, &data, sizeof(data));
1431 	}
1432 
1433 	if(fsdata_traverse(tree, &complete_tree, view) != 0)
1434 	{
1435 		reset_entry_list(view, &view->dir_entry, &view->list_rows);
1436 		restore_parent = 0;
1437 	}
1438 
1439 	fsdata_free(tree);
1440 	dynarray_free(entries);
1441 
1442 	drop_tops(view, view->dir_entry, &view->list_rows, 0);
1443 
1444 	if(restore_parent)
1445 	{
1446 		add_parent_dir(view);
1447 	}
1448 }
1449 
1450 const char *
flist_get_dir(const view_t * view)1451 flist_get_dir(const view_t *view)
1452 {
1453 	if(flist_custom_active(view))
1454 	{
1455 		assert(view->custom.orig_dir != NULL && "Wrong view dir state.");
1456 		return view->custom.orig_dir;
1457 	}
1458 
1459 	return view->curr_dir;
1460 }
1461 
1462 void
flist_goto_by_path(view_t * view,const char path[])1463 flist_goto_by_path(view_t *view, const char path[])
1464 {
1465 	char full_path[PATH_MAX + 1];
1466 	const char *const name = get_last_path_component(path);
1467 
1468 	get_current_full_path(view, sizeof(full_path), full_path);
1469 	if(stroscmp(full_path, path) == 0)
1470 	{
1471 		return;
1472 	}
1473 
1474 	if(flist_custom_active(view) && cv_tree(view->custom.type) &&
1475 			strcmp(name, "..") == 0)
1476 	{
1477 		int pos;
1478 		char dir_only[PATH_MAX + 1];
1479 
1480 		snprintf(dir_only, sizeof(dir_only), "%.*s", (int)(name - path), path);
1481 		pos = fpos_find_entry(view, name, dir_only);
1482 		if(pos != -1)
1483 		{
1484 			view->list_pos = pos;
1485 		}
1486 		return;
1487 	}
1488 
1489 	(void)set_position_by_path(view, path);
1490 }
1491 
1492 dir_entry_t *
entry_from_path(view_t * view,dir_entry_t * entries,int count,const char path[])1493 entry_from_path(view_t *view, dir_entry_t *entries, int count,
1494 		const char path[])
1495 {
1496 	char canonic_path[PATH_MAX + 1];
1497 	const char *fname;
1498 	int i;
1499 
1500 	to_canonic_path(path, flist_get_dir(view), canonic_path,
1501 			sizeof(canonic_path));
1502 
1503 	fname = get_last_path_component(canonic_path);
1504 	for(i = 0; i < count; ++i)
1505 	{
1506 		char full_path[PATH_MAX + 1];
1507 		dir_entry_t *const entry = &entries[i];
1508 
1509 		if(stroscmp(entry->name, fname) != 0)
1510 		{
1511 			continue;
1512 		}
1513 
1514 		get_full_path_of(entry, sizeof(full_path), full_path);
1515 		if(stroscmp(full_path, canonic_path) == 0)
1516 		{
1517 			return entry;
1518 		}
1519 	}
1520 
1521 	return NULL;
1522 }
1523 
1524 uint64_t
fentry_get_nitems(const view_t * view,const dir_entry_t * entry)1525 fentry_get_nitems(const view_t *view, const dir_entry_t *entry)
1526 {
1527 	uint64_t nitems;
1528 	fentry_get_dir_info(view, entry, NULL, &nitems);
1529 	return nitems;
1530 }
1531 
1532 void
fentry_get_dir_info(const view_t * view,const dir_entry_t * entry,uint64_t * size,uint64_t * nitems)1533 fentry_get_dir_info(const view_t *view, const dir_entry_t *entry,
1534 		uint64_t *size, uint64_t *nitems)
1535 {
1536 	const int is_slow_fs = view->on_slow_fs;
1537 	dcache_result_t size_res, nitems_res;
1538 
1539 	assert((size != NULL || nitems != NULL) &&
1540 			"At least one of out parameters has to be non-NULL.");
1541 
1542 	dcache_get_of(entry, (size == NULL ? NULL : &size_res),
1543 			(nitems == NULL ? NULL : &nitems_res));
1544 
1545 	if(size != NULL)
1546 	{
1547 		*size = size_res.value;
1548 		if(size_res.value != DCACHE_UNKNOWN && !size_res.is_valid && !is_slow_fs)
1549 		{
1550 			*size = recalc_entry_size(entry, size_res.value);
1551 		}
1552 	}
1553 
1554 	if(nitems != NULL)
1555 	{
1556 		if(!nitems_res.is_valid && !is_slow_fs)
1557 		{
1558 			nitems_res.value = entry_calc_nitems(entry);
1559 		}
1560 
1561 		*nitems = (nitems_res.value == DCACHE_UNKNOWN ? 0 : nitems_res.value);
1562 	}
1563 }
1564 
1565 /* Updates cached size of a directory also updating its relevant parents.
1566  * Returns current size of the directory entry. */
1567 static uint64_t
recalc_entry_size(const dir_entry_t * entry,uint64_t old_size)1568 recalc_entry_size(const dir_entry_t *entry, uint64_t old_size)
1569 {
1570 	uint64_t size;
1571 
1572 	char full_path[PATH_MAX + 1];
1573 	get_full_path_of(entry, sizeof(full_path), full_path);
1574 
1575 	size = fops_dir_size(full_path, 0, &ui_cancellation_info);
1576 	dcache_update_parent_sizes(full_path, size - old_size);
1577 
1578 	return size;
1579 }
1580 
1581 /* Calculates number of items at path specified by the entry.  No check for file
1582  * type is performed.  Returns the number, which is zero for files. */
1583 static uint64_t
entry_calc_nitems(const dir_entry_t * entry)1584 entry_calc_nitems(const dir_entry_t *entry)
1585 {
1586 	char full_path[PATH_MAX + 1];
1587 	get_full_path_of(entry, sizeof(full_path), full_path);
1588 
1589 	uint64_t ret = count_dir_items(full_path);
1590 
1591 	uint64_t inode = get_true_inode(entry);
1592 	dcache_set_at(full_path, inode, DCACHE_UNKNOWN, ret);
1593 
1594 	return ret;
1595 }
1596 
1597 int
populate_dir_list(view_t * view,int reload)1598 populate_dir_list(view_t *view, int reload)
1599 {
1600 	const int result = populate_dir_list_internal(view, reload);
1601 	if(view->list_pos > view->list_rows - 1)
1602 	{
1603 		view->list_pos = view->list_rows - 1;
1604 	}
1605 	return result;
1606 }
1607 
1608 void
load_dir_list(view_t * view,int reload)1609 load_dir_list(view_t *view, int reload)
1610 {
1611 	load_dir_list_internal(view, reload, 0);
1612 }
1613 
1614 /* Loads file list for the view and redraws the view.  The reload parameter
1615  * should be set in case of view refresh operation.  The draw_only parameter
1616  * prevents extra actions that might be taken care of outside this function as
1617  * well. */
1618 static void
load_dir_list_internal(view_t * view,int reload,int draw_only)1619 load_dir_list_internal(view_t *view, int reload, int draw_only)
1620 {
1621 	if(populate_dir_list(view, reload) != 0)
1622 	{
1623 		return;
1624 	}
1625 
1626 	if(draw_only)
1627 	{
1628 		draw_dir_list_only(view);
1629 	}
1630 	else
1631 	{
1632 		draw_dir_list(view);
1633 	}
1634 
1635 	if(view == curr_view)
1636 	{
1637 		if(strnoscmp(view->curr_dir, cfg.fuse_home, strlen(cfg.fuse_home)) == 0 &&
1638 				stroscmp(other_view->curr_dir, view->curr_dir) == 0)
1639 		{
1640 			load_dir_list(other_view, 1);
1641 		}
1642 	}
1643 }
1644 
1645 /* Loads filelist for the view.  The reload parameter should be set in case of
1646  * view refresh operation.  Returns non-zero on error. */
1647 static int
populate_dir_list_internal(view_t * view,int reload)1648 populate_dir_list_internal(view_t *view, int reload)
1649 {
1650 	char *saved_cwd;
1651 
1652 	view->filtered = 0;
1653 
1654 	/* List reload usually implies that something related to file list has
1655 	 * changed, like an option.  Reset cached lists to make sure they are up to
1656 	 * date with main column. */
1657 	if(reload)
1658 	{
1659 		flist_free_cache(view, &view->left_column);
1660 		flist_free_cache(view, &view->right_column);
1661 	}
1662 
1663 	if(flist_custom_active(view))
1664 	{
1665 		return populate_custom_view(view, reload);
1666 	}
1667 
1668 	if(!reload && is_dir_big(view->curr_dir))
1669 	{
1670 		if(!vle_mode_is(CMDLINE_MODE))
1671 		{
1672 			ui_sb_quick_msgf("%s", "Reading directory...");
1673 		}
1674 	}
1675 
1676 	if(curr_stats.load_stage < 2)
1677 	{
1678 		update_all_windows();
1679 	}
1680 
1681 	saved_cwd = save_cwd();
1682 	/* this is needed for lstat() below */
1683 	if(vifm_chdir(view->curr_dir) != 0 && !is_unc_root(view->curr_dir))
1684 	{
1685 		LOG_SERROR_MSG(errno, "Can't chdir() into \"%s\"", view->curr_dir);
1686 		restore_cwd(saved_cwd);
1687 		return 1;
1688 	}
1689 
1690 	/* If directory didn't change. */
1691 	if(view->watch != NULL && view->watched_dir != NULL &&
1692 			stroscmp(view->watched_dir, view->curr_dir) == 0)
1693 	{
1694 		int failed;
1695 		/* Drain all events that happened before this point. */
1696 		(void)fswatch_changed(view->watch, &failed);
1697 	}
1698 
1699 	if(is_unc_root(view->curr_dir))
1700 	{
1701 #ifdef _WIN32
1702 		free_view_entries(view);
1703 		if(fill_with_shared(view) == 0)
1704 		{
1705 			if(view->list_rows == 0)
1706 			{
1707 				add_parent_dir(view);
1708 			}
1709 		}
1710 		else
1711 		{
1712 			show_error_msgf("Share enumeration error",
1713 					"Can't load list of shares of %s", view->curr_dir);
1714 
1715 			leave_invalid_dir(view);
1716 			if(update_dir_list(view, reload) != 0)
1717 			{
1718 				/* We don't have read access, only execute, or there were other
1719 				 * problems. */
1720 				free_view_entries(view);
1721 				add_parent_dir(view);
1722 			}
1723 		}
1724 #endif
1725 	}
1726 	else if(update_dir_list(view, reload) != 0)
1727 	{
1728 		/* We don't have read access, only execute, or there were other problems. */
1729 		free_view_entries(view);
1730 		add_parent_dir(view);
1731 	}
1732 
1733 	if(!reload && !vle_mode_is(CMDLINE_MODE))
1734 	{
1735 		ui_sb_clear();
1736 	}
1737 
1738 	fview_update_geometry(view);
1739 
1740 	/* If reloading the same directory don't jump to history position.  Stay at
1741 	 * the current line. */
1742 	if(!reload)
1743 	{
1744 		/* XXX: why cursor is positioned in code that loads the list? */
1745 		flist_hist_lookup(view, view);
1746 	}
1747 
1748 	if(view->location_changed)
1749 	{
1750 		fview_dir_updated(view);
1751 	}
1752 
1753 	if(view->list_rows < 1)
1754 	{
1755 		if(rescue_from_empty_filelist(view))
1756 		{
1757 			restore_cwd(saved_cwd);
1758 			return 0;
1759 		}
1760 
1761 		add_parent_dir(view);
1762 	}
1763 
1764 	fview_list_updated(view);
1765 
1766 	/* Because we reset directory watcher if directory didn't change before
1767 	 * loading file list, it's possible that we did load everything, but one more
1768 	 * update will happen anyway afterwards.  We shouldn't drain change event
1769 	 * here, because it makes it possible to skip an update (when directory was
1770 	 * changed while we were reading from it). */
1771 	update_dir_watcher(view);
1772 
1773 	if(view->location_changed)
1774 	{
1775 		view->location_changed = 0;
1776 		vle_aucmd_execute("DirEnter", view->curr_dir, view);
1777 	}
1778 
1779 	restore_cwd(saved_cwd);
1780 	return 0;
1781 }
1782 
1783 /* (Re)loads custom view file list.  Returns non-zero on error. */
1784 static int
populate_custom_view(view_t * view,int reload)1785 populate_custom_view(view_t *view, int reload)
1786 {
1787 	if(view->custom.type == CV_TREE)
1788 	{
1789 		dir_entry_t *prev_dir_entries;
1790 		int prev_list_rows, result;
1791 
1792 		start_dir_list_change(view, &prev_dir_entries, &prev_list_rows, reload);
1793 		result = flist_load_tree_internal(view, flist_get_dir(view), 1);
1794 
1795 		if(view->dir_entry == NULL)
1796 		{
1797 			/* Restore original list in case of failure. */
1798 			view->dir_entry = prev_dir_entries;
1799 			view->list_rows = result;
1800 		}
1801 		else
1802 		{
1803 			finish_dir_list_change(view, prev_dir_entries, prev_list_rows);
1804 		}
1805 
1806 		if(result != 0)
1807 		{
1808 			show_error_msg("Tree View", "Reload failed");
1809 		}
1810 
1811 		return result;
1812 	}
1813 
1814 	if(view->custom.type == CV_DIFF)
1815 	{
1816 		if(filter_in_compare(view, NULL, &entry_exists))
1817 		{
1818 			return 0;
1819 		}
1820 	}
1821 	else
1822 	{
1823 		if(custom_list_is_incomplete(view))
1824 		{
1825 			dir_entry_t *prev_dir_entries;
1826 			int prev_list_rows;
1827 
1828 			start_dir_list_change(view, &prev_dir_entries, &prev_list_rows, reload);
1829 
1830 			replace_dir_entries(view, &view->dir_entry, &view->list_rows,
1831 					view->local_filter.entries, view->local_filter.entry_count);
1832 			/* Selection of the original list shouldn't be restored. */
1833 			flist_sel_drop(view);
1834 
1835 			/* We're merging instead of simply replacing entries to account for
1836 			 * selection and possibly other attributes of entries.  Merging also takes
1837 			 * care of cursor position. */
1838 			finish_dir_list_change(view, prev_dir_entries, prev_list_rows);
1839 		}
1840 		else if(view->local_filter.entry_count == 0)
1841 		{
1842 			/* Save unfiltered (by local filter) list for further use. */
1843 			replace_dir_entries(view, &view->local_filter.entries,
1844 					&view->local_filter.entry_count, view->dir_entry, view->list_rows);
1845 		}
1846 
1847 		(void)zap_entries(view, view->dir_entry, &view->list_rows,
1848 				&is_dead_or_filtered, NULL, 0, 0);
1849 	}
1850 
1851 	update_entries_data(view);
1852 	sort_dir_list(!reload, view);
1853 	fview_list_updated(view);
1854 	return 0;
1855 }
1856 
1857 int
filter_in_compare(view_t * view,void * arg,zap_filter filter)1858 filter_in_compare(view_t *view, void *arg, zap_filter filter)
1859 {
1860 	view_t *const other = (view == curr_view) ? other_view : curr_view;
1861 
1862 	zap_compare_view(view, other, filter, arg);
1863 	if(view->list_rows == 0)
1864 	{
1865 		/* Load views before showing the message as event loop in message dialog can
1866 		 * try to reload views. */
1867 		rn_leave(view, 1);
1868 		show_error_msg("Comparison", "No files left in the views, left the mode.");
1869 		return 1;
1870 	}
1871 
1872 	exclude_temporary_entries(other);
1873 	return 0;
1874 }
1875 
1876 /* Checks whether entry refers to non-existing file.  Returns non-zero if so,
1877  * otherwise zero is returned. */
1878 static int
entry_exists(view_t * view,const dir_entry_t * entry,void * arg)1879 entry_exists(view_t *view, const dir_entry_t *entry, void *arg)
1880 {
1881 	return path_exists_at(entry->origin, entry->name, NODEREF);
1882 }
1883 
1884 /* Removes entries that refer to non-existing files from compare view marking
1885  * corresponding entries of the other view as temporary.  This is a
1886  * zap_entries() tailored for needs of compare, because that function is already
1887  * too complex. */
1888 static void
zap_compare_view(view_t * view,view_t * other,zap_filter filter,void * arg)1889 zap_compare_view(view_t *view, view_t *other, zap_filter filter, void *arg)
1890 {
1891 	int i, j = 0;
1892 
1893 	for(i = 0; i < view->list_rows; ++i)
1894 	{
1895 		dir_entry_t *const entry = &view->dir_entry[i];
1896 
1897 		if(!fentry_is_fake(entry) && !filter(view, entry, arg))
1898 		{
1899 			if(entry->selected)
1900 			{
1901 				--view->selected_files;
1902 			}
1903 
1904 			const int separator = find_separator(other, i);
1905 			if(separator >= 0)
1906 			{
1907 				fentry_free(view, entry);
1908 				other->dir_entry[separator].temporary = 1;
1909 
1910 				if(view->list_pos == i)
1911 				{
1912 					view->list_pos = j;
1913 				}
1914 				continue;
1915 			}
1916 			replace_string(&entry->name, "");
1917 			entry->type = FT_UNK;
1918 			entry->id = other->dir_entry[i].id;
1919 		}
1920 
1921 		if(i != j)
1922 		{
1923 			view->dir_entry[j] = view->dir_entry[i];
1924 		}
1925 
1926 		++j;
1927 	}
1928 
1929 	view->list_rows = j;
1930 }
1931 
1932 /* Finds separator among the group of equivalent files of the view specified by
1933  * its position.  Returns index of the separator or -1. */
1934 static int
find_separator(view_t * view,int idx)1935 find_separator(view_t *view, int idx)
1936 {
1937 	int i;
1938 	const int id = view->dir_entry[idx].id;
1939 
1940 	for(i = idx; i >= 0 && view->dir_entry[i].id == id; --i)
1941 	{
1942 		if(!view->dir_entry[i].temporary && fentry_is_fake(&view->dir_entry[i]))
1943 		{
1944 			return i;
1945 		}
1946 	}
1947 	for(i = idx + 1; i < view->list_rows && view->dir_entry[i].id == id; ++i)
1948 	{
1949 		if(!view->dir_entry[i].temporary && fentry_is_fake(&view->dir_entry[i]))
1950 		{
1951 			return i;
1952 		}
1953 	}
1954 	return -1;
1955 }
1956 
1957 /* Updates directory watcher of the view. */
1958 static void
update_dir_watcher(view_t * view)1959 update_dir_watcher(view_t *view)
1960 {
1961 	const char *const curr_dir = flist_get_dir(view);
1962 
1963 	if(view->watch == NULL || view->watched_dir == NULL ||
1964 			stroscmp(view->watched_dir, curr_dir) != 0)
1965 	{
1966 		fswatch_free(view->watch);
1967 		view->watch = fswatch_create(curr_dir);
1968 
1969 		/* Failure to create a watch is bad, but there isn't much we can do here and
1970 		 * this doesn't feel like a reason to block anything else. */
1971 		if(view->watch != NULL)
1972 		{
1973 			replace_string(&view->watched_dir, curr_dir);
1974 		}
1975 	}
1976 }
1977 
1978 /* Checks whether currently loaded custom list of files is missing some files
1979  * compared to the original custom list.  Returns non-zero if so, otherwise zero
1980  * is returned. */
1981 static int
custom_list_is_incomplete(const view_t * view)1982 custom_list_is_incomplete(const view_t *view)
1983 {
1984 	if(view->local_filter.entry_count == 0)
1985 	{
1986 		return 0;
1987 	}
1988 
1989 	if(view->list_rows == 1 && is_parent_dir(view->dir_entry[0].name) &&
1990 			!(view->local_filter.entry_count == 1 &&
1991 				is_parent_dir(view->local_filter.entries[0].name)))
1992 	{
1993 		return 1;
1994 	}
1995 
1996 	return view->list_rows != view->local_filter.entry_count;
1997 }
1998 
1999 /* zap_entries() filter to filter-out inexistent files or files which names
2000  * match local filter. */
2001 static int
is_dead_or_filtered(view_t * view,const dir_entry_t * entry,void * arg)2002 is_dead_or_filtered(view_t *view, const dir_entry_t *entry, void *arg)
2003 {
2004 	if(!path_exists_at(entry->origin, entry->name, NODEREF))
2005 	{
2006 		return 0;
2007 	}
2008 
2009 	if(local_filter_matches(view, entry))
2010 	{
2011 		return 1;
2012 	}
2013 
2014 	++view->filtered;
2015 	return 0;
2016 }
2017 
2018 /* Re-read meta-data for each entry (does nothing for entries on which querying
2019  * fails). */
2020 static void
update_entries_data(view_t * view)2021 update_entries_data(view_t *view)
2022 {
2023 	int i;
2024 	for(i = 0; i < view->list_rows; ++i)
2025 	{
2026 		char full_path[PATH_MAX + 1];
2027 		dir_entry_t *const entry = &view->dir_entry[i];
2028 
2029 		/* Fake entries do not map onto files in file system. */
2030 		if(fentry_is_fake(entry))
2031 		{
2032 			continue;
2033 		}
2034 
2035 		get_full_path_of(entry, sizeof(full_path), full_path);
2036 
2037 		/* Do not care about possible failure, just use previous meta-data. */
2038 		(void)fill_dir_entry_by_path(entry, full_path);
2039 	}
2040 }
2041 
2042 int
zap_entries(view_t * view,dir_entry_t * entries,int * count,zap_filter filter,void * arg,int allow_empty_list,int remove_subtrees)2043 zap_entries(view_t *view, dir_entry_t *entries, int *count, zap_filter filter,
2044 		void *arg, int allow_empty_list, int remove_subtrees)
2045 {
2046 	int i, j;
2047 
2048 	j = 0;
2049 	for(i = 0; i < *count; ++i)
2050 	{
2051 		int k, pos, parent;
2052 		dir_entry_t *const entry = &entries[i];
2053 		const int nremoved = remove_subtrees ? (entry->child_count + 1) : 1;
2054 
2055 		if(filter(view, entry, arg))
2056 		{
2057 			/* We're keeping this entry. */
2058 			if(i != j)
2059 			{
2060 				entries[j] = entries[i];
2061 			}
2062 
2063 			++j;
2064 			continue;
2065 		}
2066 
2067 		if(entry->selected && view->dir_entry == entries)
2068 		{
2069 			--view->selected_files;
2070 		}
2071 
2072 		/* Reassign children of node about to be deleted to its parent.  Child count
2073 		 * don't need an update here because these nodes are already counted. */
2074 		pos = i + 1;
2075 		while(pos < i + 1 + entry->child_count)
2076 		{
2077 			if(entry->child_pos == 0)
2078 			{
2079 				entries[pos].child_pos = 0;
2080 			}
2081 			else
2082 			{
2083 				entries[pos].child_pos += entry->child_pos - 1;
2084 			}
2085 			pos += entries[pos].child_count + 1;
2086 		}
2087 
2088 		if(entry->child_pos != 0)
2089 		{
2090 			/* Visit all parent nodes to update number of their children and also
2091 			 * all sibling nodes which require their parent links updated. */
2092 			int pos = i + (entry->child_count + 1);
2093 			int parent = j - entry->child_pos;
2094 			while(1)
2095 			{
2096 				while(pos <= parent + (i - j) + entries[parent].child_count)
2097 				{
2098 					entries[pos].child_pos -= nremoved;
2099 					pos += entries[pos].child_count + 1;
2100 				}
2101 				entries[parent].child_count -= nremoved;
2102 				if(entries[parent].child_pos == 0)
2103 				{
2104 					break;
2105 				}
2106 				parent -= entries[parent].child_pos;
2107 			}
2108 		}
2109 
2110 		for(k = 0; k < nremoved; ++k)
2111 		{
2112 			fentry_free(view, &entry[k]);
2113 		}
2114 
2115 		/* If we're removing file from main list of entries and cursor is right on
2116 		 * this file, move cursor at position this file would take in resulting
2117 		 * list. */
2118 		if(entries == view->dir_entry && view->list_pos >= i &&
2119 				view->list_pos < i + nremoved)
2120 		{
2121 			view->list_pos = j;
2122 		}
2123 
2124 		/* Add directory leaf if we just removed last child of the last of nodes
2125 		 * that wasn't filtered.  We can use one entry because if something was
2126 		 * filtered out, there is space for at least one extra entry. */
2127 		parent = i - entry->child_pos - (i - j);
2128 		int show_empty_dir_leafs = (cfg.dot_dirs & DD_TREE_LEAFS_PARENT);
2129 		if(show_empty_dir_leafs && remove_subtrees && parent == j - 1 &&
2130 				entries[parent].child_count == 0)
2131 		{
2132 			char full_path[PATH_MAX + 1];
2133 			char *path;
2134 
2135 			int pos = i + (entry->child_count + 1);
2136 
2137 			get_full_path_of(&entries[j - 1], sizeof(full_path), full_path);
2138 			path = format_str("%s/..", full_path);
2139 			init_parent_entry(view, &entries[j], path);
2140 			remove_last_path_component(path);
2141 			entries[j].origin = path;
2142 			entries[j].child_pos = 1;
2143 
2144 			/* Since we now adding back one entry, correct increase parent counts and
2145 			 * child positions back by one. */
2146 			while(1)
2147 			{
2148 				while(pos <= parent + (i - j + nremoved) + entries[parent].child_count)
2149 				{
2150 					++entries[pos].child_pos;
2151 					pos += entries[pos].child_count + 1;
2152 				}
2153 				++entries[parent].child_count;
2154 				if(entries[parent].child_pos == 0)
2155 				{
2156 					break;
2157 				}
2158 				parent -= entries[parent].child_pos;
2159 			}
2160 
2161 			++j;
2162 		}
2163 
2164 		i += nremoved - 1;
2165 	}
2166 
2167 	*count = j;
2168 
2169 	if(*count == 0 && !allow_empty_list)
2170 	{
2171 		add_parent_dir(view);
2172 	}
2173 
2174 	return i - j;
2175 }
2176 
2177 /* Checks for subjectively relative size of a directory specified by the path
2178  * parameter.  Returns non-zero if size of the directory in question is
2179  * considered to be big. */
2180 static int
is_dir_big(const char path[])2181 is_dir_big(const char path[])
2182 {
2183 #ifndef _WIN32
2184 	struct stat s;
2185 	if(os_stat(path, &s) != 0)
2186 	{
2187 		LOG_SERROR_MSG(errno, "Can't stat() \"%s\"", path);
2188 		return 1;
2189 	}
2190 	return s.st_size > s.st_blksize;
2191 #else
2192 	return 1;
2193 #endif
2194 }
2195 
2196 /* Frees list of directory entries of the view. */
2197 static void
free_view_entries(view_t * view)2198 free_view_entries(view_t *view)
2199 {
2200 	free_dir_entries(view, &view->dir_entry, &view->list_rows);
2201 }
2202 
2203 /* Updates file list with files from current directory.  Returns zero on
2204  * success, otherwise non-zero is returned. */
2205 static int
update_dir_list(view_t * view,int reload)2206 update_dir_list(view_t *view, int reload)
2207 {
2208 	dir_entry_t *prev_dir_entries;
2209 	int prev_list_rows;
2210 
2211 	start_dir_list_change(view, &prev_dir_entries, &prev_list_rows, reload);
2212 
2213 	if(enum_dir_content(view->curr_dir, &add_file_entry_to_view, view) != 0)
2214 	{
2215 		LOG_SERROR_MSG(errno, "Can't opendir() \"%s\"", view->curr_dir);
2216 		free_dir_entries(view, &prev_dir_entries, &prev_list_rows);
2217 		return 1;
2218 	}
2219 
2220 	if(cfg_parent_dir_is_visible(is_root_dir(view->curr_dir)) ||
2221 			view->list_rows == 0)
2222 	{
2223 		add_parent_dir(view);
2224 	}
2225 
2226 	sort_dir_list(!reload, view);
2227 
2228 	/* Merging must be performed after sorting so that list position remains fixed
2229 	 * (sorting doesn't preserve it). */
2230 	finish_dir_list_change(view, prev_dir_entries, prev_list_rows);
2231 
2232 	return 0;
2233 }
2234 
2235 /* Starts file list update, saving previous list for future reference if
2236  * necessary. */
2237 static void
start_dir_list_change(view_t * view,dir_entry_t ** entries,int * len,int reload)2238 start_dir_list_change(view_t *view, dir_entry_t **entries, int *len, int reload)
2239 {
2240 	if(reload)
2241 	{
2242 		*entries = view->dir_entry;
2243 		*len = view->list_rows;
2244 		view->dir_entry = NULL;
2245 		view->list_rows = 0;
2246 	}
2247 	else
2248 	{
2249 		*entries = NULL;
2250 		*len = 0;
2251 		free_view_entries(view);
2252 	}
2253 
2254 	view->matches = 0;
2255 	view->selected_files = 0;
2256 }
2257 
2258 /* Finishes file list update, possibly merging information from old entries into
2259  * new ones. */
2260 static void
finish_dir_list_change(view_t * view,dir_entry_t * entries,int len)2261 finish_dir_list_change(view_t *view, dir_entry_t *entries, int len)
2262 {
2263 	check_file_uniqueness(view);
2264 
2265 	if(entries != NULL)
2266 	{
2267 		merge_lists(view, entries, len);
2268 		free_dir_entries(view, &entries, &len);
2269 	}
2270 
2271 	view->dir_entry = dynarray_shrink(view->dir_entry);
2272 }
2273 
2274 /* enum_dir_content() callback that appends files to file list.  Returns zero on
2275  * success or non-zero to indicate failure and stop enumeration. */
2276 static int
add_file_entry_to_view(const char name[],const void * data,void * param)2277 add_file_entry_to_view(const char name[], const void *data, void *param)
2278 {
2279 	view_t *const view = param;
2280 	dir_entry_t *entry;
2281 
2282 	/* Always ignore the "." and ".." directories. */
2283 	if(strcmp(name, ".") == 0 || strcmp(name, "..") == 0)
2284 	{
2285 		return 0;
2286 	}
2287 
2288 	if(!file_is_visible(view, name, 0, data, 1))
2289 	{
2290 		++view->filtered;
2291 		return 0;
2292 	}
2293 
2294 	entry = alloc_dir_entry(&view->dir_entry, view->list_rows);
2295 	if(entry == NULL)
2296 	{
2297 		show_error_msg("Memory Error", "Unable to allocate enough memory");
2298 		return 1;
2299 	}
2300 
2301 	init_dir_entry(view, entry, name);
2302 
2303 	if(fill_dir_entry(entry, entry->name, data) == 0)
2304 	{
2305 		++view->list_rows;
2306 	}
2307 	else
2308 	{
2309 		fentry_free(view, entry);
2310 	}
2311 
2312 	return 0;
2313 }
2314 
2315 void
resort_dir_list(int msg,view_t * view)2316 resort_dir_list(int msg, view_t *view)
2317 {
2318 	char full_path[PATH_MAX + 1];
2319 	const int top_delta = view->list_pos - view->top_line;
2320 	if(view->list_pos < view->list_rows)
2321 	{
2322 		get_current_full_path(view, sizeof(full_path), full_path);
2323 	}
2324 
2325 	sort_dir_list(msg, view);
2326 
2327 	if(view->list_pos < view->list_rows)
2328 	{
2329 		flist_goto_by_path(view, full_path);
2330 		view->top_line = view->list_pos - top_delta;
2331 	}
2332 }
2333 
2334 /* Resorts view without reloading it.  msg parameter controls whether to show
2335  * "Sorting..." statusbar message. */
2336 static void
sort_dir_list(int msg,view_t * view)2337 sort_dir_list(int msg, view_t *view)
2338 {
2339 	if(msg && view->list_rows > 2048 && !vle_mode_is(CMDLINE_MODE))
2340 	{
2341 		ui_sb_quick_msgf("%s", "Sorting directory...");
2342 	}
2343 
2344 	sort_view(view);
2345 
2346 	if(msg && !vle_mode_is(CMDLINE_MODE))
2347 	{
2348 		ui_sb_clear();
2349 	}
2350 }
2351 
2352 /* Merges elements from previous list into the new one. */
2353 static void
merge_lists(view_t * view,dir_entry_t * entries,int len)2354 merge_lists(view_t *view, dir_entry_t *entries, int len)
2355 {
2356 	int i;
2357 	int closest_dist;
2358 	const int prev_pos = view->list_pos;
2359 	trie_t *prev_names = trie_create();
2360 
2361 	for(i = 0; i < len; ++i)
2362 	{
2363 		add_to_trie(prev_names, view, &entries[i]);
2364 
2365 		/* We won't use the name later, so free some memory. */
2366 		update_string(&entries[i].name, NULL);
2367 	}
2368 
2369 	closest_dist = INT_MIN;
2370 	for(i = 0; i < view->list_rows; ++i)
2371 	{
2372 		int dist;
2373 		void *data;
2374 		dir_entry_t *const entry = &view->dir_entry[i];
2375 		if(!is_in_trie(prev_names, view, entry, &data))
2376 		{
2377 			continue;
2378 		}
2379 
2380 		/* Transfer information from previous entry to the new one. */
2381 		merge_entries(entry, data);
2382 
2383 		/* Update number of selected files (should have been zeroed beforehand). */
2384 		view->selected_files += (entry->selected != 0);
2385 
2386 		/* Update cursor position in a smart way. */
2387 		dist = (dir_entry_t *)data - entries - prev_pos;
2388 		closest_dist = correct_pos(view, i, dist, closest_dist);
2389 	}
2390 
2391 	trie_free(prev_names);
2392 }
2393 
2394 /* Checks that entries don't have the same name (for non-cv).  And if there are
2395  * duplicates shows a warning to the user (shown each time broken directory is
2396  * entered) and drops duplicates. */
2397 TSTATIC void
check_file_uniqueness(view_t * view)2398 check_file_uniqueness(view_t *view)
2399 {
2400 	trie_t *file_names = trie_create();
2401 
2402 	int had_dups = view->has_dups;
2403 
2404 	int i;
2405 	for(i = 0; i < view->list_rows; ++i)
2406 	{
2407 		add_to_trie(file_names, view, &view->dir_entry[i]);
2408 	}
2409 
2410 	trie_free(file_names);
2411 
2412 	if(view->has_dups)
2413 	{
2414 		(void)exclude_temporary_entries(view);
2415 		if(!had_dups)
2416 		{
2417 			show_error_msg("Broken File System", "Underlying file system seems to "
2418 					"report duplicated file names.  Only one entry will be shown.");
2419 		}
2420 	}
2421 }
2422 
2423 /* Adds view entry into the trie mapping its name to entry structure.
2424  * Duplicated entries of a non-custom view are marked as temporary. */
2425 static void
add_to_trie(trie_t * trie,view_t * view,dir_entry_t * entry)2426 add_to_trie(trie_t *trie, view_t *view, dir_entry_t *entry)
2427 {
2428 	if(flist_custom_active(view))
2429 	{
2430 		char full_path[PATH_MAX + 1];
2431 		get_full_path_of(entry, sizeof(full_path), full_path);
2432 
2433 		int error = trie_set(trie, full_path, entry);
2434 		assert(error == 0 && "Duplicated file names in the list?");
2435 		(void)error;
2436 		return;
2437 	}
2438 
2439 	if(trie_set(trie, entry->name, entry) != 0)
2440 	{
2441 		LOG_INFO_MSG("Duplicated entry is `%s` in `%s`", entry->name,
2442 				entry->origin);
2443 		entry->temporary = 1;
2444 		view->has_dups = 1;
2445 	}
2446 }
2447 
2448 /* Looks up entry in the trie by its name.  Retrieves directory entry stored by
2449  * add_to_trie() into *data (unchanged on lookup failure).  Returns non-zero if
2450  * item was successfully retrieved and zero otherwise. */
2451 static int
is_in_trie(trie_t * trie,view_t * view,dir_entry_t * entry,void ** data)2452 is_in_trie(trie_t *trie, view_t *view, dir_entry_t *entry, void **data)
2453 {
2454 	int error;
2455 
2456 	if(flist_custom_active(view))
2457 	{
2458 		char full_path[PATH_MAX + 1];
2459 		get_full_path_of(entry, sizeof(full_path), full_path);
2460 		error = trie_get(trie, full_path, data);
2461 	}
2462 	else
2463 	{
2464 		error = trie_get(trie, entry->name, data);
2465 	}
2466 
2467 	return (error == 0);
2468 }
2469 
2470 /* Merges data from previous entry into the new one.  Both entries should
2471  * correspond to the same file (their names must match). */
2472 static void
merge_entries(dir_entry_t * new,const dir_entry_t * prev)2473 merge_entries(dir_entry_t *new, const dir_entry_t *prev)
2474 {
2475 	new->id = prev->id;
2476 
2477 	new->selected = prev->selected;
2478 	new->was_selected = prev->was_selected;
2479 
2480 	/* No need to check for name here, because only entries with exactly the same
2481 	 * names are merged. */
2482 	if(new->type == prev->type)
2483 	{
2484 		new->hi_num = prev->hi_num;
2485 		new->name_dec_num = prev->name_dec_num;
2486 	}
2487 }
2488 
2489 /* Corrects selected item position in the list.  Returns updated value of the
2490  * closest variable, which initially should be INT_MIN. */
2491 static int
correct_pos(view_t * view,int pos,int dist,int closest)2492 correct_pos(view_t *view, int pos, int dist, int closest)
2493 {
2494 	if(dist == 0)
2495 	{
2496 		closest = 0;
2497 		view->list_pos = pos;
2498 	}
2499 	else if((closest < 0 && dist > closest) || (closest > 0 && dist < closest))
2500 	{
2501 		closest = dist;
2502 		view->list_pos = pos;
2503 	}
2504 	return closest;
2505 }
2506 
2507 /* Performs actions needed to rescue from abnormal situation with empty
2508  * filelist.  Returns non-zero if file list was reloaded. */
2509 static int
rescue_from_empty_filelist(view_t * view)2510 rescue_from_empty_filelist(view_t *view)
2511 {
2512 	/* It is possible to set the file name filter so that no files are showing
2513 	 * in the / directory.  All other directories will always show at least the
2514 	 * ../ file.  This resets the filter and reloads the directory. */
2515 	if(name_filters_empty(view))
2516 	{
2517 		return 0;
2518 	}
2519 
2520 	show_error_msgf("Filter error",
2521 			"The %s\"%s\" pattern did not match any files. It was reset.",
2522 			view->invert ? "" : "inverted ", matcher_get_expr(view->manual_filter));
2523 
2524 	name_filters_drop(view);
2525 
2526 	load_dir_list(view, 1);
2527 	if(view->list_rows < 1)
2528 	{
2529 		leave_invalid_dir(view);
2530 		(void)change_directory(view, view->curr_dir);
2531 		load_dir_list(view, 0);
2532 	}
2533 
2534 	return 1;
2535 }
2536 
2537 void
add_parent_dir(view_t * view)2538 add_parent_dir(view_t *view)
2539 {
2540 	add_parent_entry(view, &view->dir_entry, &view->list_rows);
2541 }
2542 
2543 /* Adds parent directory entry (..) to specified list of entries. */
2544 static void
add_parent_entry(view_t * view,dir_entry_t ** entries,int * count)2545 add_parent_entry(view_t *view, dir_entry_t **entries, int *count)
2546 {
2547 	dir_entry_t *const dir_entry = alloc_dir_entry(entries, *count);
2548 	if(dir_entry == NULL)
2549 	{
2550 		show_error_msg("Memory Error", "Unable to allocate enough memory");
2551 		return;
2552 	}
2553 
2554 	if(init_parent_entry(view, dir_entry, "..") == 0)
2555 	{
2556 		++*count;
2557 	}
2558 }
2559 
2560 /* Initializes dir_entry_t with name and all other fields with default
2561  * values. */
2562 static void
init_dir_entry(view_t * view,dir_entry_t * entry,const char name[])2563 init_dir_entry(view_t *view, dir_entry_t *entry, const char name[])
2564 {
2565 	entry->name = strdup(name);
2566 	entry->origin = &view->curr_dir[0];
2567 
2568 	entry->size = 0ULL;
2569 #ifndef _WIN32
2570 	entry->uid = (uid_t)-1;
2571 	entry->gid = (gid_t)-1;
2572 	entry->mode = (mode_t)0;
2573 	entry->inode = (ino_t)0;
2574 #else
2575 	entry->attrs = 0;
2576 #endif
2577 
2578 	entry->mtime = (time_t)0;
2579 	entry->atime = (time_t)0;
2580 	entry->ctime = (time_t)0;
2581 
2582 	entry->type = FT_UNK;
2583 	entry->nlinks = 0;
2584 	entry->dir_link = 0;
2585 	entry->hi_num = -1;
2586 	entry->name_dec_num = -1;
2587 
2588 	entry->child_count = 0;
2589 	entry->child_pos = 0;
2590 
2591 	/* All files start as unselected, unmatched and unmarked. */
2592 	entry->selected = 0;
2593 	entry->was_selected = 0;
2594 	entry->search_match = 0;
2595 	entry->marked = 0;
2596 	entry->temporary = 0;
2597 
2598 	entry->tag = -1;
2599 	entry->id = -1;
2600 }
2601 
2602 void
replace_dir_entries(view_t * view,dir_entry_t ** entries,int * count,const dir_entry_t * with_entries,int with_count)2603 replace_dir_entries(view_t *view, dir_entry_t **entries, int *count,
2604 		const dir_entry_t *with_entries, int with_count)
2605 {
2606 	dir_entry_t *new;
2607 	int i;
2608 
2609 	new = dynarray_extend(NULL, with_count*sizeof(*new));
2610 	if(new == NULL)
2611 	{
2612 		return;
2613 	}
2614 
2615 	memcpy(new, with_entries, sizeof(*new)*with_count);
2616 
2617 	for(i = 0; i < with_count; ++i)
2618 	{
2619 		dir_entry_t *const entry = &new[i];
2620 
2621 		entry->name = strdup(entry->name);
2622 		entry->origin = strdup(entry->origin);
2623 
2624 		if(entry->name == NULL || entry->origin == NULL)
2625 		{
2626 			int count_so_far = i + 1;
2627 			free_dir_entries(view, &new, &count_so_far);
2628 			return;
2629 		}
2630 	}
2631 
2632 	free_dir_entries(view, entries, count);
2633 	*entries = new;
2634 	*count = with_count;
2635 }
2636 
2637 void
free_dir_entries(view_t * view,dir_entry_t ** entries,int * count)2638 free_dir_entries(view_t *view, dir_entry_t **entries, int *count)
2639 {
2640 	int i;
2641 	for(i = 0; i < *count; ++i)
2642 	{
2643 		fentry_free(view, &(*entries)[i]);
2644 	}
2645 
2646 	dynarray_free(*entries);
2647 	*entries = NULL;
2648 	*count = 0;
2649 }
2650 
2651 void
fentry_free(const view_t * view,dir_entry_t * entry)2652 fentry_free(const view_t *view, dir_entry_t *entry)
2653 {
2654 	free(entry->name);
2655 	entry->name = NULL;
2656 
2657 	if(entry->origin != &lwin.curr_dir[0] && entry->origin != &rwin.curr_dir[0])
2658 	{
2659 		free(entry->origin);
2660 		entry->origin = NULL;
2661 	}
2662 }
2663 
2664 dir_entry_t *
add_dir_entry(dir_entry_t ** list,size_t * list_size,const dir_entry_t * entry)2665 add_dir_entry(dir_entry_t **list, size_t *list_size, const dir_entry_t *entry)
2666 {
2667 	dir_entry_t *const new_entry = alloc_dir_entry(list, *list_size);
2668 	if(new_entry == NULL)
2669 	{
2670 		return NULL;
2671 	}
2672 
2673 	*new_entry = *entry;
2674 	++*list_size;
2675 	return new_entry;
2676 }
2677 
2678 dir_entry_t *
entry_list_add(view_t * view,dir_entry_t ** list,int * list_size,const char path[])2679 entry_list_add(view_t *view, dir_entry_t **list, int *list_size,
2680 		const char path[])
2681 {
2682 	dir_entry_t *const dir_entry = alloc_dir_entry(list, *list_size);
2683 	if(dir_entry == NULL)
2684 	{
2685 		return NULL;
2686 	}
2687 
2688 	init_dir_entry(view, dir_entry, get_last_path_component(path));
2689 
2690 	dir_entry->origin = strdup(path);
2691 	remove_last_path_component(dir_entry->origin);
2692 
2693 	if(fill_dir_entry_by_path(dir_entry, path) != 0)
2694 	{
2695 		fentry_free(view, dir_entry);
2696 		return NULL;
2697 	}
2698 
2699 	++*list_size;
2700 	return dir_entry;
2701 }
2702 
2703 /* Allocates one more directory entry for the *list of size list_size by
2704  * extending it.  Returns pointer to new entry or NULL on failure. */
2705 static dir_entry_t *
alloc_dir_entry(dir_entry_t ** list,int list_size)2706 alloc_dir_entry(dir_entry_t **list, int list_size)
2707 {
2708 	dir_entry_t *new_entry_list = dynarray_extend(*list, sizeof(dir_entry_t));
2709 	if(new_entry_list == NULL)
2710 	{
2711 		return NULL;
2712 	}
2713 
2714 	*list = new_entry_list;
2715 	return &new_entry_list[list_size];
2716 }
2717 
2718 void
check_if_filelist_has_changed(view_t * view)2719 check_if_filelist_has_changed(view_t *view)
2720 {
2721 	int failed, changed;
2722 	const char *const curr_dir = flist_get_dir(view);
2723 
2724 	if(view->on_slow_fs ||
2725 			(flist_custom_active(view) && !cv_tree(view->custom.type)) ||
2726 			is_unc_root(curr_dir))
2727 	{
2728 		return;
2729 	}
2730 
2731 	if(view->watch == NULL)
2732 	{
2733 		/* If watch is not initialized, try to do this, but don't fail on error. */
2734 
2735 		update_dir_watcher(view);
2736 		failed = 0;
2737 		changed = (view->watch != NULL);
2738 	}
2739 	else
2740 	{
2741 		changed = fswatch_changed(view->watch, &failed);
2742 	}
2743 
2744 	/* Check if we still have permission to visit this directory. */
2745 	failed |= (os_access(curr_dir, X_OK) != 0);
2746 
2747 	if(failed)
2748 	{
2749 		LOG_SERROR_MSG(errno, "Can't stat() \"%s\"", curr_dir);
2750 		log_cwd();
2751 
2752 		show_error_msgf("Directory Change Check", "Cannot open %s", curr_dir);
2753 
2754 		leave_invalid_dir(view);
2755 		(void)change_directory(view, curr_dir);
2756 		flist_sel_stash(view);
2757 		ui_view_schedule_reload(view);
2758 		return;
2759 	}
2760 
2761 	if(changed)
2762 	{
2763 		ui_view_schedule_reload(view);
2764 	}
2765 	else if(flist_custom_active(view) && cv_tree(view->custom.type))
2766 	{
2767 		/* Custom trees don't track file-system changes. */
2768 		if(view->custom.type == CV_TREE &&
2769 				tree_has_changed(view->dir_entry, view->list_rows))
2770 		{
2771 			ui_view_schedule_reload(view);
2772 		}
2773 	}
2774 	else
2775 	{
2776 		if(flist_update_cache(view, &view->left_column, view->left_column.dir) ||
2777 				flist_update_cache(view, &view->right_column, view->right_column.dir))
2778 		{
2779 			ui_view_schedule_redraw(view);
2780 		}
2781 	}
2782 }
2783 
2784 /* Checks whether tree-view needs a reload (any of subdirectories were changed).
2785  * Returns non-zero if so, otherwise zero is returned. */
2786 static int
tree_has_changed(const dir_entry_t * entries,size_t nchildren)2787 tree_has_changed(const dir_entry_t *entries, size_t nchildren)
2788 {
2789 	size_t pos = 0U;
2790 	while(pos < nchildren)
2791 	{
2792 		const dir_entry_t *const entry = &entries[pos];
2793 		if(entry->type == FT_DIR && !is_parent_dir(entry->name))
2794 		{
2795 			char full_path[PATH_MAX + 1];
2796 			struct stat s;
2797 
2798 			get_full_path_of(entry, sizeof(full_path), full_path);
2799 			if(os_stat(full_path, &s) != 0 || entry->mtime != s.st_mtime)
2800 			{
2801 				return 1;
2802 			}
2803 
2804 			if(tree_has_changed(entry + 1, entry->child_count))
2805 			{
2806 				return 1;
2807 			}
2808 		}
2809 
2810 		pos += entry->child_count + 1;
2811 	}
2812 	return 0;
2813 }
2814 
2815 int
flist_update_cache(view_t * view,cached_entries_t * cache,const char path[])2816 flist_update_cache(view_t *view, cached_entries_t *cache, const char path[])
2817 {
2818 	int update = 0;
2819 	int error;
2820 
2821 	if(path == NULL)
2822 	{
2823 		return 0;
2824 	}
2825 
2826 	if(cache->watch == NULL || stroscmp(cache->dir, path) != 0)
2827 	{
2828 		fswatch_free(cache->watch);
2829 
2830 		cache->watch = fswatch_create(path);
2831 		if(cache->watch == NULL)
2832 		{
2833 			/* Reset the cache on failure to create a watcher to do not accidentally
2834 			 * provide incorrect data. */
2835 			flist_free_cache(view, cache);
2836 			return 0;
2837 		}
2838 
2839 		replace_string(&cache->dir, path);
2840 
2841 		update = 1;
2842 	}
2843 
2844 	if(update || fswatch_changed(cache->watch, &error) || error)
2845 	{
2846 		free_dir_entries(view, &cache->entries.entries, &cache->entries.nentries);
2847 		cache->entries = flist_list_in(view, path, 0, 1);
2848 		return 1;
2849 	}
2850 
2851 	return 0;
2852 }
2853 
2854 void
flist_free_cache(view_t * view,cached_entries_t * cache)2855 flist_free_cache(view_t *view, cached_entries_t *cache)
2856 {
2857 	free_dir_entries(view, &cache->entries.entries, &cache->entries.nentries);
2858 	update_string(&cache->dir, NULL);
2859 	fswatch_free(cache->watch);
2860 	cache->watch = NULL;
2861 }
2862 
2863 void
flist_update_origins(view_t * view,const char from[],char to[])2864 flist_update_origins(view_t *view, const char from[], char to[])
2865 {
2866 	int i;
2867 	for(i = 0; i < view->list_rows; ++i)
2868 	{
2869 		dir_entry_t *const entry = &view->dir_entry[i];
2870 		if(entry->origin == from)
2871 		{
2872 			entry->origin = to;
2873 		}
2874 	}
2875 }
2876 
2877 int
cd_is_possible(const char path[])2878 cd_is_possible(const char path[])
2879 {
2880 	if(!is_valid_dir(path))
2881 	{
2882 		LOG_SERROR_MSG(errno, "Can't access \"%s\"", path);
2883 
2884 		show_error_msgf("Destination doesn't exist or isn't a directory", "\"%s\"",
2885 				path);
2886 		return 0;
2887 	}
2888 	else if(!directory_accessible(path))
2889 	{
2890 		LOG_SERROR_MSG(errno, "Can't access(, X_OK) \"%s\"", path);
2891 
2892 		show_error_msgf("Permission denied", "\"%s\"", path);
2893 		return 0;
2894 	}
2895 	else
2896 	{
2897 		return 1;
2898 	}
2899 }
2900 
2901 void
load_saving_pos(view_t * view)2902 load_saving_pos(view_t *view)
2903 {
2904 	char full_path[PATH_MAX + 1];
2905 
2906 	if(curr_stats.load_stage < 2)
2907 		return;
2908 
2909 	if(!window_shows_dirlist(view))
2910 		return;
2911 
2912 	if(view->local_filter.in_progress)
2913 	{
2914 		return;
2915 	}
2916 
2917 	get_current_full_path(view, sizeof(full_path), full_path);
2918 
2919 	load_dir_list_internal(view, 1, 1);
2920 
2921 	flist_goto_by_path(view, full_path);
2922 
2923 	fview_cursor_redraw(view);
2924 
2925 	if(curr_stats.number_of_windows != 1 || view == curr_view)
2926 	{
2927 		refresh_view_win(view);
2928 	}
2929 }
2930 
2931 int
window_shows_dirlist(const view_t * view)2932 window_shows_dirlist(const view_t *view)
2933 {
2934 	if(curr_stats.number_of_windows == 1 && view == other_view)
2935 	{
2936 		return 0;
2937 	}
2938 
2939 	if(view->explore_mode)
2940 	{
2941 		return 0;
2942 	}
2943 
2944 	if(view == other_view && vle_mode_is(VIEW_MODE))
2945 	{
2946 		return 0;
2947 	}
2948 
2949 	if(view == other_view && curr_stats.preview.on)
2950 	{
2951 		return 0;
2952 	}
2953 
2954 	if(NONE(vle_primary_mode_is, NORMAL_MODE, VISUAL_MODE, VIEW_MODE,
2955 				CMDLINE_MODE))
2956 	{
2957 		return 0;
2958 	}
2959 
2960 	return 1;
2961 }
2962 
2963 void
change_sort_type(view_t * view,char type,char descending)2964 change_sort_type(view_t *view, char type, char descending)
2965 {
2966 	if(cv_compare(view->custom.type))
2967 	{
2968 		return;
2969 	}
2970 
2971 	view->sort[0] = descending ? -type : type;
2972 	memset(&view->sort[1], SK_NONE, sizeof(view->sort) - 1);
2973 	memcpy(&view->sort_g[0], &view->sort[0], sizeof(view->sort_g));
2974 
2975 	fview_sorting_updated(view);
2976 
2977 	load_sort_option(view);
2978 
2979 	ui_view_schedule_reload(view);
2980 }
2981 
2982 int
pane_in_dir(const view_t * view,const char path[])2983 pane_in_dir(const view_t *view, const char path[])
2984 {
2985 	return paths_are_same(view->curr_dir, path);
2986 }
2987 
2988 int
cd(view_t * view,const char base_dir[],const char path[])2989 cd(view_t *view, const char base_dir[], const char path[])
2990 {
2991 	char dir[PATH_MAX + 1];
2992 	char canonic_dir[PATH_MAX + 1];
2993 	int updir;
2994 
2995 	flist_pick_cd_path(view, base_dir, path, &updir, dir, sizeof(dir));
2996 	to_canonic_path(dir, base_dir, canonic_dir, sizeof(canonic_dir));
2997 
2998 	if(updir)
2999 	{
3000 		rn_leave(view, 1);
3001 	}
3002 	else if(!cd_is_possible(canonic_dir) ||
3003 			change_directory(view, canonic_dir) < 0)
3004 	{
3005 		return 0;
3006 	}
3007 
3008 	load_dir_list(view, 0);
3009 	if(view == curr_view)
3010 	{
3011 		fview_cursor_redraw(view);
3012 	}
3013 	else
3014 	{
3015 		draw_dir_list(other_view);
3016 		refresh_view_win(other_view);
3017 	}
3018 	return 0;
3019 }
3020 
3021 void
flist_pick_cd_path(view_t * view,const char base_dir[],const char path[],int * updir,char buf[],size_t buf_size)3022 flist_pick_cd_path(view_t *view, const char base_dir[], const char path[],
3023 		int *updir, char buf[], size_t buf_size)
3024 {
3025 	char *arg;
3026 
3027 	*updir = 0;
3028 
3029 	if(is_null_or_empty(path))
3030 	{
3031 		copy_str(buf, buf_size, cfg.home_dir);
3032 		return;
3033 	}
3034 
3035 	arg = expand_tilde(path);
3036 
3037 #ifndef _WIN32
3038 	if(is_path_absolute(arg))
3039 		copy_str(buf, buf_size, arg);
3040 #else
3041 	if(is_path_absolute(arg) && *arg != '/')
3042 		copy_str(buf, buf_size, arg);
3043 	else if(*arg == '/' && is_unc_root(arg))
3044 		copy_str(buf, buf_size, arg);
3045 	else if(*arg == '/' && is_unc_path(arg))
3046 		copy_str(buf, buf_size, arg);
3047 	else if(*arg == '/' && is_unc_path(base_dir))
3048 		sprintf(buf + strlen(buf), "/%s", arg + 1);
3049 	else if(strcmp(arg, "/") == 0 && is_unc_path(base_dir))
3050 		copy_str(buf, strchr(base_dir + 2, '/') - base_dir + 1, base_dir);
3051 	else if(*arg == '/')
3052 		snprintf(buf, buf_size, "%c:%s", base_dir[0], arg);
3053 #endif
3054 	else if(strcmp(arg, "-") == 0)
3055 		copy_str(buf, buf_size, view->last_dir == NULL ? "." : view->last_dir);
3056 	else if(is_parent_dir(arg) && stroscmp(base_dir, flist_get_dir(view)) == 0)
3057 		*updir = 1;
3058 	else
3059 		find_dir_in_cdpath(base_dir, arg, buf, buf_size);
3060 	free(arg);
3061 }
3062 
3063 /* Searches for an existing directory in cdpath and fills the buf with final
3064  * absolute path. */
3065 static void
find_dir_in_cdpath(const char base_dir[],const char dst[],char buf[],size_t buf_size)3066 find_dir_in_cdpath(const char base_dir[], const char dst[], char buf[],
3067 		size_t buf_size)
3068 {
3069 	char *free_this;
3070 	char *part, *state;
3071 
3072 	if(is_builtin_dir(dst) || starts_with_lit(dst, "./") ||
3073 			starts_with_lit(dst, "../"))
3074 	{
3075 		snprintf(buf, buf_size, "%s/%s", base_dir, dst);
3076 		return;
3077 	}
3078 
3079 	part = strdup(cfg.cd_path);
3080 	free_this = part;
3081 
3082 	state = NULL;
3083 	while((part = split_and_get(part, ',', &state)) != NULL)
3084 	{
3085 		snprintf(buf, buf_size, "%s/%s", expand_tilde(part), dst);
3086 
3087 		if(is_dir(buf))
3088 		{
3089 			free(free_this);
3090 			return;
3091 		}
3092 	}
3093 	free(free_this);
3094 
3095 	snprintf(buf, buf_size, "%s/%s", base_dir, dst);
3096 }
3097 
3098 int
go_to_sibling_dir(view_t * view,int offset,int wrap)3099 go_to_sibling_dir(view_t *view, int offset, int wrap)
3100 {
3101 	entries_t parent_dirs;
3102 	dir_entry_t *entry;
3103 	int save_msg = 0;
3104 
3105 	if(is_root_dir(flist_get_dir(view)) || flist_custom_active(view))
3106 	{
3107 		return 0;
3108 	}
3109 
3110 	parent_dirs = list_sibling_dirs(view);
3111 	if(parent_dirs.nentries < 0)
3112 	{
3113 		show_error_msg("Error", "Can't list parent directory");
3114 		return 0;
3115 	}
3116 
3117 	sort_entries(view, parent_dirs);
3118 
3119 	entry = pick_sibling(view, parent_dirs, offset, wrap, &save_msg);
3120 	if(entry != NULL)
3121 	{
3122 		char full_path[PATH_MAX + 1];
3123 		get_full_path_of(entry, sizeof(full_path), full_path);
3124 		if(change_directory(view, full_path) >= 0)
3125 		{
3126 			load_dir_list(view, 0);
3127 			fview_position_updated(view);
3128 		}
3129 	}
3130 
3131 	free_dir_entries(view, &parent_dirs.entries, &parent_dirs.nentries);
3132 	return save_msg;
3133 }
3134 
3135 /* Lists siblings of current directory of the view (includes current directory
3136  * as well).  Returns the list, which is of length -1 on error. */
3137 static entries_t
list_sibling_dirs(view_t * view)3138 list_sibling_dirs(view_t *view)
3139 {
3140 	entries_t parent_dirs = {};
3141 	char *path;
3142 
3143 	if(is_root_dir(flist_get_dir(view)))
3144 	{
3145 		parent_dirs.nentries = -1;
3146 		return parent_dirs;
3147 	}
3148 
3149 	path = strdup(flist_get_dir(view));
3150 	remove_last_path_component(path);
3151 
3152 	parent_dirs = flist_list_in(view, path, 1, 0);
3153 
3154 	free(path);
3155 
3156 	if(parent_dirs.nentries < 0)
3157 	{
3158 		return parent_dirs;
3159 	}
3160 
3161 	if(entry_from_path(view, parent_dirs.entries, parent_dirs.nentries,
3162 				flist_get_dir(view)) == NULL)
3163 	{
3164 		/* If we couldn't find our current directory in the list (because it got
3165 		 * filtered-out), add it to be able to determine where it would go if it
3166 		 * were visible. */
3167 		entry_list_add(view, &parent_dirs.entries, &parent_dirs.nentries,
3168 				flist_get_dir(view));
3169 	}
3170 
3171 	return parent_dirs;
3172 }
3173 
3174 /* Lists files of specified directory.  Returns the list, which is of length -1
3175  * on error. */
3176 static entries_t
flist_list_in(view_t * view,const char path[],int only_dirs,int can_include_parent)3177 flist_list_in(view_t *view, const char path[], int only_dirs,
3178 		int can_include_parent)
3179 {
3180 	entries_t siblings = {};
3181 	int len, i;
3182 	char **list;
3183 
3184 	list = list_all_files(path, &len);
3185 	if(len < 0)
3186 	{
3187 		siblings.nentries = -1;
3188 		return siblings;
3189 	}
3190 
3191 	for(i = 0; i < len; ++i)
3192 	{
3193 		dir_entry_t *entry;
3194 		int is_dir;
3195 
3196 		if(view->hide_dot && list[i][0] == '.')
3197 		{
3198 			continue;
3199 		}
3200 
3201 		char *full_path = format_str("%s/%s", path, list[i]);
3202 		entry = entry_list_add(view, &siblings.entries, &siblings.nentries,
3203 				full_path);
3204 		free(full_path);
3205 
3206 		if(entry == NULL)
3207 		{
3208 			continue;
3209 		}
3210 
3211 		is_dir = fentry_is_dir(entry);
3212 		if((only_dirs && !is_dir) ||
3213 				!filters_file_is_visible(view, path, list[i], is_dir, 0))
3214 		{
3215 			fentry_free(view, entry);
3216 			--siblings.nentries;
3217 		}
3218 	}
3219 	free_string_array(list, len);
3220 
3221 	if(can_include_parent && cfg_parent_dir_is_visible(is_root_dir(path)))
3222 	{
3223 		char *const full_path = format_str("%s/..", path);
3224 		entry_list_add(view, &siblings.entries, &siblings.nentries, full_path);
3225 		free(full_path);
3226 	}
3227 
3228 	return siblings;
3229 }
3230 
3231 /* Picks next or previous sibling from the list with optional wrapping.
3232  * *wrapped is set to non-zero if wrapping happened.  Returns pointer to picked
3233  * sibling or NULL on error or if can't pick. */
3234 static dir_entry_t *
pick_sibling(view_t * view,entries_t parent_dirs,int offset,int wrap,int * wrapped)3235 pick_sibling(view_t *view, entries_t parent_dirs, int offset, int wrap,
3236 		int *wrapped)
3237 {
3238 	dir_entry_t *entry;
3239 	int i, initial;
3240 
3241 	/* Find parent entry. */
3242 	entry = entry_from_path(view, parent_dirs.entries, parent_dirs.nentries,
3243 			flist_get_dir(view));
3244 	if(entry == NULL)
3245 	{
3246 		show_error_msg("Error", "Can't find current directory position");
3247 		return NULL;
3248 	}
3249 
3250 	*wrapped = 0;
3251 	initial = entry - parent_dirs.entries;
3252 	i = initial;
3253 
3254 	while(offset != 0)
3255 	{
3256 		if(offset > 0 && i < parent_dirs.nentries - 1)
3257 		{
3258 			++i;
3259 		}
3260 		else if(offset < 0 && i > 0)
3261 		{
3262 			--i;
3263 		}
3264 		else if(wrap)
3265 		{
3266 			if(offset > 0)
3267 			{
3268 				i = 0;
3269 				ui_sb_err("Hit BOTTOM, continuing at TOP");
3270 			}
3271 			else
3272 			{
3273 				i = parent_dirs.nentries - 1;
3274 				ui_sb_err("Hit TOP, continuing at BOTTOM");
3275 			}
3276 			*wrapped = 1;
3277 		}
3278 		else
3279 		{
3280 			return (i == initial ? NULL : entry);
3281 		}
3282 
3283 		entry = &parent_dirs.entries[i];
3284 		offset = (offset > 0 ? offset - 1 : offset + 1);
3285 	}
3286 
3287 	return entry;
3288 }
3289 
3290 int
view_needs_cd(const view_t * view,const char path[])3291 view_needs_cd(const view_t *view, const char path[])
3292 {
3293 	if(path[0] == '\0' || stroscmp(view->curr_dir, path) == 0)
3294 	{
3295 		return 0;
3296 	}
3297 	return 1;
3298 }
3299 
3300 void
set_view_path(view_t * view,const char path[])3301 set_view_path(view_t *view, const char path[])
3302 {
3303 	if(view_needs_cd(view, path))
3304 	{
3305 		copy_str(view->curr_dir, sizeof(view->curr_dir), path);
3306 		exclude_file_name(view->curr_dir);
3307 	}
3308 }
3309 
3310 uint64_t
fentry_get_size(const view_t * view,const dir_entry_t * entry)3311 fentry_get_size(const view_t *view, const dir_entry_t *entry)
3312 {
3313 	uint64_t size = DCACHE_UNKNOWN;
3314 
3315 	if(fentry_is_dir(entry))
3316 	{
3317 		fentry_get_dir_info(view, entry, &size, NULL);
3318 	}
3319 
3320 	return (size == DCACHE_UNKNOWN ? entry->size : size);
3321 }
3322 
3323 int
iter_selected_entries(view_t * view,dir_entry_t ** entry)3324 iter_selected_entries(view_t *view, dir_entry_t **entry)
3325 {
3326 	return iter_entries(view, entry, &is_entry_selected);
3327 }
3328 
3329 int
iter_marked_entries(view_t * view,dir_entry_t ** entry)3330 iter_marked_entries(view_t *view, dir_entry_t **entry)
3331 {
3332 	return iter_entries(view, entry, &is_entry_marked);
3333 }
3334 
3335 /* Implements iteration over the entries which match specific predicate.
3336  * Returns non-zero if matching entry is found and is loaded to *entry,
3337  * otherwise it's set to NULL and zero is returned. */
3338 static int
iter_entries(view_t * view,dir_entry_t ** entry,entry_predicate pred)3339 iter_entries(view_t *view, dir_entry_t **entry, entry_predicate pred)
3340 {
3341 	int next = (*entry == NULL) ? 0 : (*entry - view->dir_entry + 1);
3342 
3343 	while(next < view->list_rows)
3344 	{
3345 		dir_entry_t *const e = &view->dir_entry[next];
3346 		if(fentry_is_valid(e) && pred(e))
3347 		{
3348 			*entry = e;
3349 			return 1;
3350 		}
3351 		++next;
3352 	}
3353 
3354 	*entry = NULL;
3355 	return 0;
3356 }
3357 
3358 int
is_entry_selected(const dir_entry_t * entry)3359 is_entry_selected(const dir_entry_t *entry)
3360 {
3361 	return entry->selected;
3362 }
3363 
3364 int
is_entry_marked(const dir_entry_t * entry)3365 is_entry_marked(const dir_entry_t *entry)
3366 {
3367 	return entry->marked;
3368 }
3369 
3370 int
iter_selection_or_current(view_t * view,dir_entry_t ** entry)3371 iter_selection_or_current(view_t *view, dir_entry_t **entry)
3372 {
3373 	if(view->selected_files == 0)
3374 	{
3375 		dir_entry_t *const curr = get_current_entry(view);
3376 		*entry = (*entry == NULL && fentry_is_valid(curr)) ? curr : NULL;
3377 		return *entry != NULL;
3378 	}
3379 	return iter_selected_entries(view, entry);
3380 }
3381 
3382 int
entry_to_pos(const view_t * view,const dir_entry_t * entry)3383 entry_to_pos(const view_t *view, const dir_entry_t *entry)
3384 {
3385 	const int pos = entry - view->dir_entry;
3386 	return (pos >= 0 && pos < view->list_rows) ? pos : -1;
3387 }
3388 
3389 void
get_current_full_path(const view_t * view,size_t buf_len,char buf[])3390 get_current_full_path(const view_t *view, size_t buf_len, char buf[])
3391 {
3392 	get_full_path_at(view, view->list_pos, buf_len, buf);
3393 }
3394 
3395 void
get_full_path_at(const view_t * view,int pos,size_t buf_len,char buf[])3396 get_full_path_at(const view_t *view, int pos, size_t buf_len, char buf[])
3397 {
3398 	if(pos < 0 || pos >= view->list_rows)
3399 	{
3400 		copy_str(buf, buf_len, "");
3401 		return;
3402 	}
3403 	get_full_path_of(&view->dir_entry[pos], buf_len, buf);
3404 }
3405 
3406 void
get_full_path_of(const dir_entry_t * entry,size_t buf_len,char buf[])3407 get_full_path_of(const dir_entry_t *entry, size_t buf_len, char buf[])
3408 {
3409 	build_path(buf, buf_len, entry->origin, entry->name);
3410 }
3411 
3412 void
get_short_path_of(const view_t * view,const dir_entry_t * entry,NameFormat fmt,int drop_prefix,size_t buf_len,char buf[])3413 get_short_path_of(const view_t *view, const dir_entry_t *entry, NameFormat fmt,
3414 		int drop_prefix, size_t buf_len, char buf[])
3415 {
3416 	char name[NAME_MAX + 1];
3417 	const char *path = entry->origin;
3418 
3419 	char parent_path[PATH_MAX + 1];
3420 	const char *root_path = flist_get_dir(view);
3421 
3422 	if(fentry_is_fake(entry))
3423 	{
3424 		copy_str(buf, buf_len, "");
3425 		return;
3426 	}
3427 
3428 	if(drop_prefix && entry->child_pos != 0)
3429 	{
3430 		/* Replace root to force obtaining of file name only. */
3431 		const dir_entry_t *const parent_entry = entry - entry->child_pos;
3432 		get_full_path_of(parent_entry, sizeof(parent_path), parent_path);
3433 		root_path = parent_path;
3434 	}
3435 
3436 	format_entry_name(entry, fmt, sizeof(name), name);
3437 
3438 	if(is_parent_dir(entry->name) || is_root_dir(entry->name))
3439 	{
3440 		copy_str(buf, buf_len, name);
3441 		return;
3442 	}
3443 
3444 	if(!path_starts_with(path, root_path))
3445 	{
3446 		build_path(buf, buf_len, path, name);
3447 		return;
3448 	}
3449 
3450 	assert(strlen(path) >= strlen(root_path) && "Path is too short.");
3451 
3452 	path += strlen(root_path);
3453 	path = skip_char(path, '/');
3454 	if(path[0] == '\0')
3455 	{
3456 		copy_str(buf, buf_len, name);
3457 	}
3458 	else
3459 	{
3460 		snprintf(buf, buf_len, "%s/%s", path, name);
3461 	}
3462 }
3463 
3464 void
check_marking(view_t * view,int count,const int indexes[])3465 check_marking(view_t *view, int count, const int indexes[])
3466 {
3467 	if(count != 0)
3468 	{
3469 		mark_files_at(view, count, indexes);
3470 	}
3471 	else
3472 	{
3473 		flist_set_marking(view, 0);
3474 	}
3475 }
3476 
3477 void
flist_set_marking(view_t * view,int prefer_current)3478 flist_set_marking(view_t *view, int prefer_current)
3479 {
3480 	if(view->pending_marking)
3481 	{
3482 		view->pending_marking = 0;
3483 		return;
3484 	}
3485 
3486 	dir_entry_t *curr = get_current_entry(view);
3487 	if(view->selected_files != 0 &&
3488 			(!prefer_current || (curr != NULL && curr->selected)))
3489 	{
3490 		mark_selected(view);
3491 	}
3492 	else
3493 	{
3494 		clear_marking(view);
3495 
3496 		if(curr != NULL && fentry_is_valid(curr))
3497 		{
3498 			curr->marked = 1;
3499 		}
3500 	}
3501 }
3502 
3503 void
mark_files_at(view_t * view,int count,const int indexes[])3504 mark_files_at(view_t *view, int count, const int indexes[])
3505 {
3506 	int i;
3507 
3508 	clear_marking(view);
3509 
3510 	for(i = 0; i < count; ++i)
3511 	{
3512 		view->dir_entry[indexes[i]].marked = 1;
3513 	}
3514 }
3515 
3516 /* Marks selected files of the view.  Returns number of marked files. */
3517 static int
mark_selected(view_t * view)3518 mark_selected(view_t *view)
3519 {
3520 	int i;
3521 	int nmarked = 0;
3522 	for(i = 0; i < view->list_rows; ++i)
3523 	{
3524 		view->dir_entry[i].marked = view->dir_entry[i].selected;
3525 		if(view->dir_entry[i].marked)
3526 		{
3527 			++nmarked;
3528 		}
3529 	}
3530 	return nmarked;
3531 }
3532 
3533 int
mark_selection_or_current(view_t * view)3534 mark_selection_or_current(view_t *view)
3535 {
3536 	dir_entry_t *const curr = get_current_entry(view);
3537 	if(view->selected_files == 0 && fentry_is_valid(curr))
3538 	{
3539 		clear_marking(view);
3540 		curr->marked = 1;
3541 		return 1;
3542 	}
3543 	return mark_selected(view);
3544 }
3545 
3546 int
flist_count_marked(const view_t * view)3547 flist_count_marked(const view_t *view)
3548 {
3549 	int i, n = 0;
3550 	for(i = 0; i < view->list_rows; ++i)
3551 	{
3552 		n += (view->dir_entry[i].marked != 0);
3553 	}
3554 	return n;
3555 }
3556 
3557 void
clear_marking(view_t * view)3558 clear_marking(view_t *view)
3559 {
3560 	int i;
3561 	for(i = 0; i < view->list_rows; ++i)
3562 	{
3563 		view->dir_entry[i].marked = 0;
3564 	}
3565 }
3566 
3567 void
flist_custom_set(view_t * view,const char title[],const char path[],char * lines[],int nlines)3568 flist_custom_set(view_t *view, const char title[], const char path[],
3569 		char *lines[], int nlines)
3570 {
3571 	int i;
3572 
3573 	if(vifm_chdir(path) != 0)
3574 	{
3575 		show_error_msgf("Custom view", "Can't change directory: %s", path);
3576 		return;
3577 	}
3578 
3579 	flist_custom_start(view, "-");
3580 
3581 	for(i = 0; i < nlines; ++i)
3582 	{
3583 		flist_custom_add_spec(view, lines[i]);
3584 	}
3585 
3586 	flist_custom_end(view, 1);
3587 }
3588 
3589 void
flist_custom_add_spec(view_t * view,const char line[])3590 flist_custom_add_spec(view_t *view, const char line[])
3591 {
3592 	char *const path = parse_line_for_path(line, flist_get_dir(view));
3593 	if(path != NULL)
3594 	{
3595 		flist_custom_add(view, path);
3596 		free(path);
3597 	}
3598 }
3599 
3600 void
flist_custom_end(view_t * view,int very)3601 flist_custom_end(view_t *view, int very)
3602 {
3603 	if(flist_custom_finish(view, very ? CV_VERY : CV_REGULAR, 0) != 0)
3604 	{
3605 		show_error_msg("Custom view", "Ignoring empty list of files");
3606 		return;
3607 	}
3608 
3609 	fpos_set_pos(view, 0);
3610 }
3611 
3612 void
fentry_rename(view_t * view,dir_entry_t * entry,const char to[])3613 fentry_rename(view_t *view, dir_entry_t *entry, const char to[])
3614 {
3615 	char *const old_name = entry->name;
3616 
3617 	/* Rename file in internal structures for correct positioning of cursor
3618 	 * after reloading, as cursor will be positioned on the file with the same
3619 	 * name. */
3620 	entry->name = strdup(to);
3621 	if(entry->name == NULL)
3622 	{
3623 		entry->name = old_name;
3624 		return;
3625 	}
3626 
3627 	/* Name change can affect name specific highlight and decorations, so reset
3628 	 * the caches. */
3629 	entry->hi_num = -1;
3630 	entry->name_dec_num = -1;
3631 
3632 	/* Update origins of entries which include the one we're renaming. */
3633 	if(flist_custom_active(view) && fentry_is_dir(entry))
3634 	{
3635 		int i;
3636 		char *const root = format_str("%s/%s", entry->origin, old_name);
3637 		const size_t root_len = strlen(root);
3638 
3639 		/* TODO: this doesn't handle circular renames like a/ -> b/, needs more
3640 		 *       bookkeeping. */
3641 		for(i = 0; i < view->list_rows; ++i)
3642 		{
3643 			dir_entry_t *const e = &view->dir_entry[i];
3644 			if(path_starts_with(e->origin, root))
3645 			{
3646 				char *const new_origin = format_str("%s/%s%s", entry->origin, to,
3647 						e->origin + root_len);
3648 				chosp(new_origin);
3649 				if(e->origin != view->curr_dir)
3650 				{
3651 					free(e->origin);
3652 				}
3653 				e->origin = new_origin;
3654 			}
3655 		}
3656 
3657 		free(root);
3658 	}
3659 
3660 	free(old_name);
3661 }
3662 
3663 int
fentry_is_fake(const dir_entry_t * entry)3664 fentry_is_fake(const dir_entry_t *entry)
3665 {
3666 	return entry->name[0] == '\0';
3667 }
3668 
3669 int
fentry_is_valid(const dir_entry_t * entry)3670 fentry_is_valid(const dir_entry_t *entry)
3671 {
3672 	return !fentry_is_fake(entry) && !is_parent_dir(entry->name);
3673 }
3674 
3675 int
fentry_is_dir(const dir_entry_t * entry)3676 fentry_is_dir(const dir_entry_t *entry)
3677 {
3678 	return (entry->type == FT_LINK) ? entry->dir_link : (entry->type == FT_DIR);
3679 }
3680 
3681 int
flist_load_tree(view_t * view,const char path[])3682 flist_load_tree(view_t *view, const char path[])
3683 {
3684 	char full_path[PATH_MAX + 1];
3685 	get_current_full_path(view, sizeof(full_path), full_path);
3686 
3687 	if(flist_load_tree_internal(view, path, 0) != 0)
3688 	{
3689 		return 1;
3690 	}
3691 
3692 	if(full_path[0] != '\0')
3693 	{
3694 		(void)set_position_by_path(view, full_path);
3695 	}
3696 
3697 	ui_view_schedule_redraw(view);
3698 	return 0;
3699 }
3700 
3701 /* Looks up entry by its path in main entry list of the view and updates cursor
3702  * position when such entry is found.  Returns zero if position was updated,
3703  * otherwise non-zero is returned. */
3704 static int
set_position_by_path(view_t * view,const char path[])3705 set_position_by_path(view_t *view, const char path[])
3706 {
3707 	const dir_entry_t *entry;
3708 	entry = entry_from_path(view, view->dir_entry, view->list_rows, path);
3709 	if(entry != NULL)
3710 	{
3711 		view->list_pos = entry_to_pos(view, entry);
3712 		return 0;
3713 	}
3714 	return 1;
3715 }
3716 
3717 int
flist_clone_tree(view_t * to,const view_t * from)3718 flist_clone_tree(view_t *to, const view_t *from)
3719 {
3720 	if(from->custom.type == CV_CUSTOM_TREE)
3721 	{
3722 		const int as_tree = (from->custom.type == CV_CUSTOM_TREE);
3723 		flist_custom_clone(to, from, as_tree);
3724 	}
3725 	else
3726 	{
3727 		if(make_tree(to, flist_get_dir(from), 0, from->custom.excluded_paths) != 0)
3728 		{
3729 			return 1;
3730 		}
3731 	}
3732 
3733 	trie_free(to->custom.excluded_paths);
3734 	to->custom.excluded_paths = trie_clone(from->custom.excluded_paths);
3735 	return 0;
3736 }
3737 
3738 /* Implements tree view (re)loading.  Returns zero on success, otherwise
3739  * non-zero is returned. */
3740 static int
flist_load_tree_internal(view_t * view,const char path[],int reload)3741 flist_load_tree_internal(view_t *view, const char path[], int reload)
3742 {
3743 	trie_t *excluded_paths = reload ? view->custom.excluded_paths : NULL;
3744 
3745 	if(make_tree(view, path, reload, excluded_paths) != 0)
3746 	{
3747 		return 1;
3748 	}
3749 
3750 	if(!reload)
3751 	{
3752 		trie_free(view->custom.excluded_paths);
3753 		view->custom.excluded_paths = trie_create();
3754 	}
3755 	return 0;
3756 }
3757 
3758 /* (Re)loads tree at path into the view using specified list of excluded files.
3759  * Returns zero on success, otherwise non-zero is returned. */
3760 static int
make_tree(view_t * view,const char path[],int reload,trie_t * excluded_paths)3761 make_tree(view_t *view, const char path[], int reload, trie_t *excluded_paths)
3762 {
3763 	char canonic_path[PATH_MAX + 1];
3764 	int nfiltered;
3765 	CVType type;
3766 	const int from_custom = flist_custom_active(view)
3767 	                     && ONE_OF(view->custom.type, CV_REGULAR, CV_VERY);
3768 
3769 	flist_custom_start(view, from_custom ? view->custom.title : "");
3770 
3771 	show_progress("Building tree...", 0);
3772 
3773 	ui_cancellation_push_on();
3774 	if(from_custom)
3775 	{
3776 		nfiltered = 0;
3777 		tree_from_cv(view);
3778 		type = CV_CUSTOM_TREE;
3779 	}
3780 	else
3781 	{
3782 		nfiltered = add_files_recursively(view, path, excluded_paths, -1, 0);
3783 		type = CV_TREE;
3784 	}
3785 	ui_cancellation_pop();
3786 
3787 	ui_sb_quick_msg_clear();
3788 
3789 	if(ui_cancellation_requested())
3790 	{
3791 		return 1;
3792 	}
3793 
3794 	if(nfiltered < 0)
3795 	{
3796 		show_error_msg("Tree View", "Failed to list directory");
3797 		return 1;
3798 	}
3799 
3800 	to_canonic_path(path, flist_get_dir(view), canonic_path,
3801 			sizeof(canonic_path));
3802 
3803 	if(flist_custom_finish_internal(view, type, reload, canonic_path, 1) != 0)
3804 	{
3805 		return 1;
3806 	}
3807 	view->filtered = nfiltered;
3808 
3809 	replace_string(&view->custom.orig_dir, canonic_path);
3810 
3811 	return 0;
3812 }
3813 
3814 /* Turns custom list into custom tree. */
3815 static void
tree_from_cv(view_t * view)3816 tree_from_cv(view_t *view)
3817 {
3818 	int i;
3819 
3820 	dir_entry_t *entries = view->dir_entry;
3821 	int nentries = view->list_rows;
3822 
3823 	fsdata_t *const tree = fsdata_create(0, 0);
3824 
3825 	view->dir_entry = NULL;
3826 	view->list_rows = 0;
3827 
3828 	for(i = 0U; i < nentries; ++i)
3829 	{
3830 		if(!is_parent_dir(entries[i].name))
3831 		{
3832 			char full_path[PATH_MAX + 1];
3833 			void *data = &entries[i];
3834 			get_full_path_of(&entries[i], sizeof(full_path), full_path);
3835 			fsdata_set(tree, full_path, &data, sizeof(data));
3836 
3837 			/* Mark entries that came from original list. */
3838 			entries[i].tag = 1;
3839 		}
3840 		else
3841 		{
3842 			fentry_free(view, &entries[i]);
3843 		}
3844 	}
3845 
3846 	if(fsdata_traverse(tree, &complete_tree, view) != 0)
3847 	{
3848 		reset_entry_list(view, &view->custom.entries, &view->custom.entry_count);
3849 	}
3850 
3851 	fsdata_free(tree);
3852 	dynarray_free(entries);
3853 
3854 	drop_tops(view, view->custom.entries, &view->custom.entry_count, 1);
3855 }
3856 
3857 /* fsdata_traverse() callback that flattens the tree into array of entries.
3858  * Should return non-zero to stop traverser. */
3859 static int
complete_tree(const char name[],int valid,const void * parent_data,void * data,void * arg)3860 complete_tree(const char name[], int valid, const void *parent_data, void *data,
3861 		void *arg)
3862 {
3863 	/* Initially data associated with existing entries points to original entries.
3864 	 * Once that entry is copied, the data is replaced with its index in the new
3865 	 * list. */
3866 
3867 	view_t *const view = arg;
3868 	const int in_place = cv_tree(view->custom.type);
3869 
3870 	dir_entry_t **entries = (in_place ? &view->dir_entry : &view->custom.entries);
3871 	int *nentries = (in_place ? &view->list_rows : &view->custom.entry_count);
3872 
3873 	dir_entry_t *dir_entry = alloc_dir_entry(entries, *nentries);
3874 	if(dir_entry == NULL)
3875 	{
3876 		return 1;
3877 	}
3878 
3879 	++*nentries;
3880 
3881 	if(valid)
3882 	{
3883 		*dir_entry = **(dir_entry_t **)data;
3884 		dir_entry->child_count = 0;
3885 		(*(dir_entry_t **)data)->name = NULL;
3886 		(*(dir_entry_t **)data)->origin = NULL;
3887 	}
3888 	else
3889 	{
3890 		char full_path[PATH_MAX + 1];
3891 
3892 		if(parent_data == NULL)
3893 		{
3894 			char *const typed_path = format_str("%s/", name);
3895 			if(is_root_dir(typed_path))
3896 			{
3897 				/* Entry for a root is added temporarily (this happens only on Windows)
3898 				 * as a storage of path prefix and is removed afterwards in
3899 				 * drop_tops(). */
3900 				init_dir_entry(view, dir_entry, "");
3901 				dir_entry->origin = strdup(name);
3902 			}
3903 			else
3904 			{
3905 				init_dir_entry(view, dir_entry, name);
3906 				dir_entry->origin = strdup("/");
3907 			}
3908 			free(typed_path);
3909 		}
3910 		else
3911 		{
3912 			char parent_path[PATH_MAX + 1];
3913 			const intptr_t *parent_idx = parent_data;
3914 			init_dir_entry(view, dir_entry, name);
3915 			get_full_path_of(&(*entries)[*parent_idx], sizeof(parent_path),
3916 					parent_path);
3917 			dir_entry->origin = strdup(parent_path);
3918 		}
3919 
3920 		get_full_path_of(dir_entry, sizeof(full_path), full_path);
3921 		fill_dir_entry_by_path(dir_entry, full_path);
3922 
3923 		dir_entry->temporary = in_place;
3924 	}
3925 
3926 	*(intptr_t *)data = dir_entry - *entries;
3927 
3928 	if(parent_data != NULL)
3929 	{
3930 		const intptr_t *const parent_idx = parent_data;
3931 		dir_entry->child_pos = (dir_entry - *entries) - *parent_idx;
3932 
3933 		do
3934 		{
3935 			dir_entry -= dir_entry->child_pos;
3936 			++dir_entry->child_count;
3937 		}
3938 		while(dir_entry->child_pos != 0);
3939 	}
3940 	return 0;
3941 }
3942 
3943 /* Replaces list of entries with a single parent directory entry.  Might leave
3944  * the list empty on memory error. */
3945 static void
reset_entry_list(view_t * view,dir_entry_t ** entries,int * count)3946 reset_entry_list(view_t *view, dir_entry_t **entries, int *count)
3947 {
3948 	free_dir_entries(view, entries, count);
3949 	add_parent_entry(view, entries, count);
3950 }
3951 
3952 /* Traverses root children and drops fake root nodes and optionally extra tops
3953  * of subtrees. */
3954 static void
drop_tops(view_t * view,dir_entry_t * entries,int * nentries,int extra)3955 drop_tops(view_t *view, dir_entry_t *entries, int *nentries, int extra)
3956 {
3957 	int i;
3958 	for(i = 0; i < *nentries - 1; i += entries[i].child_count + 1)
3959 	{
3960 		int j = i;
3961 		while(j < *nentries - 1 && entries[j].child_count > 0 &&
3962 				((extra && entries[j].child_count == entries[j + 1].child_count + 1) ||
3963 				 entries[j].name[0] == '\0') && entries[j].tag != 1)
3964 		{
3965 			fentry_free(view, &entries[j++]);
3966 		}
3967 
3968 		memmove(&entries[i], &entries[j], sizeof(*entries)*(*nentries - j));
3969 		*nentries -= j - i;
3970 		entries[i].child_pos = 0;
3971 	}
3972 }
3973 
3974 /* Adds custom view entries corresponding to file system tree.  parent_pos is
3975  * expected to be negative for the outermost invocation.  Returns number of
3976  * filtered out files on success or partial success and negative value on
3977  * serious error. */
3978 static int
add_files_recursively(view_t * view,const char path[],trie_t * excluded_paths,int parent_pos,int no_direct_parent)3979 add_files_recursively(view_t *view, const char path[], trie_t *excluded_paths,
3980 		int parent_pos, int no_direct_parent)
3981 {
3982 	int i;
3983 	const int prev_count = view->custom.entry_count;
3984 	int nfiltered = 0;
3985 
3986 	int len;
3987 	char **lst = list_all_files(path, &len);
3988 	if(len < 0)
3989 	{
3990 		return -1;
3991 	}
3992 
3993 	for(i = 0; i < len && !ui_cancellation_requested(); ++i)
3994 	{
3995 		int dir;
3996 		void *dummy;
3997 		dir_entry_t *entry;
3998 		char *const full_path = format_str("%s/%s", path, lst[i]);
3999 
4000 		if(trie_get(excluded_paths, full_path, &dummy) == 0)
4001 		{
4002 			free(full_path);
4003 			continue;
4004 		}
4005 
4006 		dir = is_dir(full_path);
4007 		if(!file_is_visible(view, lst[i], dir, NULL, 1))
4008 		{
4009 			/* Traverse directory (but not symlink to it) even if we're skipping it,
4010 			 * because we might need files that are inside of it. */
4011 			if(dir && !is_symlink(full_path) &&
4012 					file_is_visible(view, lst[i], dir, NULL, 0))
4013 			{
4014 				nfiltered += add_files_recursively(view, full_path, excluded_paths,
4015 						parent_pos, 1);
4016 			}
4017 
4018 			free(full_path);
4019 			++nfiltered;
4020 			continue;
4021 		}
4022 
4023 		entry = flist_custom_add(view, full_path);
4024 		if(entry == NULL)
4025 		{
4026 			free(full_path);
4027 			free_string_array(lst, len);
4028 			return -1;
4029 		}
4030 
4031 		if(parent_pos >= 0)
4032 		{
4033 			entry->child_pos = (view->custom.entry_count - 1) - parent_pos;
4034 		}
4035 
4036 		/* Not using dir variable here, because it is set for symlinks to
4037 		 * directories as well. */
4038 		if(entry->type == FT_DIR)
4039 		{
4040 			const int idx = view->custom.entry_count - 1;
4041 			const int filtered = add_files_recursively(view, full_path,
4042 					excluded_paths, idx, 0);
4043 			/* Keep going in case of error and load partial list. */
4044 			if(filtered >= 0)
4045 			{
4046 				/* If one of recursive calls returned error, keep going and build
4047 				 * partial tree. */
4048 				view->custom.entries[idx].child_count = (view->custom.entry_count - 1)
4049 				                                      - idx;
4050 				nfiltered += filtered;
4051 			}
4052 		}
4053 
4054 		free(full_path);
4055 
4056 		show_progress("Building tree...", 1000);
4057 	}
4058 
4059 	free_string_array(lst, len);
4060 
4061 	/* The prev_count != 0 check is to make sure that we won't create leaf instead
4062 	 * of the whole tree (this is handled in flist_custom_finish()). */
4063 	int show_empty_dir_leafs = (cfg.dot_dirs & DD_TREE_LEAFS_PARENT);
4064 	if(show_empty_dir_leafs && !no_direct_parent && prev_count != 0 &&
4065 			view->custom.entry_count == prev_count)
4066 	{
4067 		/* To be able to perform operations inside directory (e.g., create files),
4068 		 * we need at least one element there. */
4069 		if(add_directory_leaf(view, path, parent_pos) != 0)
4070 		{
4071 			return -1;
4072 		}
4073 	}
4074 
4075 	return nfiltered;
4076 }
4077 
4078 /* Checks whether file is visible according to dot and filename filters.  is_dir
4079  * is used when data is NULL, otherwise data_is_dir_entry() called (this is an
4080  * optimization).  Returns non-zero if so, otherwise zero is returned. */
4081 static int
file_is_visible(view_t * view,const char name[],int is_dir,const void * data,int apply_local_filter)4082 file_is_visible(view_t *view, const char name[], int is_dir, const void *data,
4083 		int apply_local_filter)
4084 {
4085 	if(view->hide_dot && name[0] == '.')
4086 	{
4087 		return 0;
4088 	}
4089 
4090 	if(data != NULL)
4091 	{
4092 		char full_path[PATH_MAX + 1];
4093 		snprintf(full_path, sizeof(full_path), "%s/%s", flist_get_dir(view), name);
4094 
4095 		is_dir = data_is_dir_entry(data, full_path);
4096 	}
4097 
4098 	return filters_file_is_visible(view, flist_get_dir(view), name, is_dir,
4099 			apply_local_filter);
4100 }
4101 
4102 /* Adds ".." directory leaf of an empty directory to the tree which is being
4103  * built.  Returns non-zero on error, otherwise zero is returned. */
4104 static int
add_directory_leaf(view_t * view,const char path[],int parent_pos)4105 add_directory_leaf(view_t *view, const char path[], int parent_pos)
4106 {
4107 	char *full_path;
4108 	dir_entry_t *entry;
4109 
4110 	/* If local filter isn't empty, assume that user is looking for something and
4111 	 * leafs will get in his way. */
4112 	if(!filter_is_empty(&view->local_filter.filter))
4113 	{
4114 		return 0;
4115 	}
4116 
4117 	entry = alloc_dir_entry(&view->custom.entries, view->custom.entry_count);
4118 	if(entry == NULL)
4119 	{
4120 		show_error_msg("Memory Error", "Unable to allocate enough memory");
4121 		return 1;
4122 	}
4123 
4124 	full_path = format_str("%s/..", path);
4125 	if(init_parent_entry(view, entry, full_path) != 0)
4126 	{
4127 		free(full_path);
4128 		/* Keep going in case of error and load partial list. */
4129 		return 0;
4130 	}
4131 
4132 	remove_last_path_component(full_path);
4133 	entry->origin = full_path;
4134 
4135 	if(parent_pos >= 0)
4136 	{
4137 		entry->child_pos = (view->custom.entry_count - 1) - parent_pos;
4138 	}
4139 
4140 	++view->custom.entry_count;
4141 	return 0;
4142 }
4143 
4144 /* Fills given entry of the view at specified path (can be relative, i.e. "..").
4145  * Returns zero on success, otherwise non-zero is returned. */
4146 static int
init_parent_entry(view_t * view,dir_entry_t * entry,const char path[])4147 init_parent_entry(view_t *view, dir_entry_t *entry, const char path[])
4148 {
4149 	struct stat s;
4150 
4151 	init_dir_entry(view, entry, get_last_path_component(path));
4152 	entry->type = FT_DIR;
4153 
4154 	/* Load the inode info or leave blank values in entry. */
4155 	if(os_lstat(path, &s) != 0)
4156 	{
4157 		fentry_free(view, entry);
4158 		LOG_SERROR_MSG(errno, "Can't lstat() \"%s\"", path);
4159 		log_cwd();
4160 		return 1;
4161 	}
4162 
4163 #ifndef _WIN32
4164 	entry->size = (uintmax_t)s.st_size;
4165 	entry->uid = s.st_uid;
4166 	entry->gid = s.st_gid;
4167 	entry->mode = s.st_mode;
4168 	entry->inode = s.st_ino;
4169 #else
4170 	/* Windows doesn't like returning size of directories even if it can. */
4171 	entry->size = get_file_size(entry->name);
4172 #endif
4173 	entry->mtime = s.st_mtime;
4174 	entry->atime = s.st_atime;
4175 	entry->ctime = s.st_ctime;
4176 	entry->nlinks = s.st_nlink;
4177 
4178 	return 0;
4179 }
4180 
4181 /* vim: set tabstop=2 softtabstop=2 shiftwidth=2 noexpandtab cinoptions-=(0 : */
4182 /* vim: set cinoptions+=t0 filetype=c : */
4183