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 "status.h"
21 
22 #ifdef HAVE_LIBGTK
23 #include <gio/gio.h>
24 #include <gtk/gtk.h>
25 #undef MAX
26 #undef MIN
27 #endif
28 
29 #include <sys/types.h> /* ino_t */
30 
31 #include <assert.h> /* assert() */
32 #include <limits.h> /* INT_MIN */
33 #include <stddef.h> /* NULL */
34 #include <string.h>
35 #include <time.h> /* time_t time() */
36 
37 #include "cfg/config.h"
38 #include "compat/fs_limits.h"
39 #include "compat/pthread.h"
40 #include "compat/reallocarray.h"
41 #include "modes/modes.h"
42 #include "ui/colors.h"
43 #include "ui/ui.h"
44 #include "utils/env.h"
45 #include "utils/fsdata.h"
46 #include "utils/log.h"
47 #include "utils/macros.h"
48 #include "utils/path.h"
49 #include "utils/str.h"
50 #include "utils/utils.h"
51 #include "cmd_completion.h"
52 #include "cmd_core.h"
53 #include "filelist.h"
54 #include "filetype.h"
55 #include "opt_handlers.h"
56 
57 /* Environment variables by which application hosted by terminal multiplexer can
58  * identify the host. */
59 #define SCREEN_ENVVAR "STY"
60 #define TMUX_ENVVAR "TMUX"
61 
62 /* dcache entry. */
63 typedef struct
64 {
65 	uint64_t value;   /* Stored value. */
66 #ifndef _WIN32
67 	ino_t inode;      /* Inode number. */
68 #endif
69 	time_t timestamp; /* When the value was set. */
70 }
71 dcache_data_t;
72 
73 static void load_def_values(status_t *stats, config_t *config);
74 static void determine_fuse_umount_cmd(status_t *stats);
75 static void set_gtk_available(status_t *stats);
76 static int reset_dircache(void);
77 static void set_last_cmdline_command(const char cmd[]);
78 static void dcache_get(const char path[], time_t mtime, uint64_t inode,
79 		dcache_result_t *size, dcache_result_t *nitems);
80 static void size_updater(void *data, void *arg);
81 
82 status_t curr_stats;
83 
84 /* Whether redraw operation is scheduled. */
85 static int pending_redraw;
86 /* Whether reload operation is scheduled.  Redrawing is then assumed to be
87  * scheduled too, as it's part of reloading. */
88 static int pending_refresh;
89 static int inside_screen;
90 static int inside_tmux;
91 
92 /* Thread-safety guard for dcache_size variable. */
93 static pthread_mutex_t dcache_size_mutex = PTHREAD_MUTEX_INITIALIZER;
94 /* Thread-safety guard for dcache_nitems variable. */
95 static pthread_mutex_t dcache_nitems_mutex = PTHREAD_MUTEX_INITIALIZER;
96 /* Cache for directory sizes. */
97 static fsdata_t *dcache_size;
98 /* Cache for directory item count. */
99 static fsdata_t *dcache_nitems;
100 
101 /* Whether UI updates should be "paused" (a counter, not a flag). */
102 static int silent_ui;
103 /* Whether silencing UI led to skipping of screen updates. */
104 static int silence_skipped_updates;
105 
106 int
stats_init(config_t * config)107 stats_init(config_t *config)
108 {
109 	inside_screen = !is_null_or_empty(env_get(SCREEN_ENVVAR));
110 	inside_tmux = !is_null_or_empty(env_get(TMUX_ENVVAR));
111 
112 	load_def_values(&curr_stats, config);
113 	determine_fuse_umount_cmd(&curr_stats);
114 	set_gtk_available(&curr_stats);
115 	curr_stats.exec_env_type = get_exec_env_type();
116 	stats_update_shell_type(config->shell);
117 
118 	(void)hist_init(&curr_stats.cmd_hist, config->history_len);
119 	(void)hist_init(&curr_stats.search_hist, config->history_len);
120 	(void)hist_init(&curr_stats.prompt_hist, config->history_len);
121 	(void)hist_init(&curr_stats.filter_hist, config->history_len);
122 
123 	hists_resize(config->history_len);
124 
125 	return stats_reset(config);
126 }
127 
128 /* Initializes most fields of the status structure, some are left to be
129  * initialized by the stats_reset() function. */
130 static void
load_def_values(status_t * stats,config_t * config)131 load_def_values(status_t *stats, config_t *config)
132 {
133 	pending_redraw = 0;
134 	pending_refresh = 0;
135 
136 	stats->last_char = 0;
137 	stats->save_msg = 0;
138 	stats->use_register = 0;
139 	stats->curr_register = -1;
140 	stats->register_saved = 0;
141 	stats->number_of_windows = 2;
142 	stats->use_input_bar = 1;
143 	stats->drop_new_dir_hist = 0;
144 	stats->load_stage = 0;
145 	stats->term_state = TS_NORMAL;
146 	stats->ch_pos = 1;
147 	stats->confirmed = 0;
148 	stats->skip_shellout_redraw = 0;
149 	stats->cs = &config->cs;
150 	strcpy(stats->color_scheme, "");
151 
152 	stats->preview.on = 0;
153 	stats->preview.kind = VK_TEXTUAL;
154 	update_string(&stats->preview.cleanup_cmd, NULL);
155 	stats->preview.clearing = 0;
156 
157 	stats->msg_head = 0;
158 	stats->msg_tail = 0;
159 	stats->save_msg_in_list = 1;
160 	stats->allow_sb_msg_truncation = 1;
161 	size_t i;
162 	for(i = 0U; i < ARRAY_LEN(stats->msgs); ++i)
163 	{
164 		update_string(&stats->msgs[i], NULL);
165 	}
166 
167 	stats->scroll_bind_off = 0;
168 	stats->split = VSPLIT;
169 	stats->splitter_pos = -1;
170 	stats->splitter_ratio = 0.5;
171 
172 	stats->sourcing_state = SOURCING_NONE;
173 
174 	stats->restart_in_progress = 0;
175 
176 	stats->exec_env_type = EET_EMULATOR;
177 
178 	stats->term_multiplexer = TM_NONE;
179 
180 	stats->initial_lines = INT_MIN;
181 	stats->initial_columns = INT_MIN;
182 
183 	stats->ellipsis = "...";
184 
185 	stats->shell_type = ST_NORMAL;
186 
187 	stats->fuse_umount_cmd = "";
188 
189 	stats->original_stdout = NULL;
190 
191 	update_string(&stats->chosen_files_out, NULL);
192 	update_string(&stats->chosen_dir_out , NULL);
193 	(void)replace_string(&stats->output_delimiter, "\n");
194 
195 	update_string(&stats->on_choose, NULL);
196 
197 	stats->preview_hint = NULL;
198 
199 	stats->global_local_settings = 0;
200 
201 	stats->history_size = 0;
202 
203 	stats->ipc = NULL;
204 
205 #ifdef HAVE_LIBGTK
206 	stats->gtk_available = 0;
207 #endif
208 }
209 
210 /* Initializes stats->fuse_umount_cmd field of the stats. */
211 static void
determine_fuse_umount_cmd(status_t * stats)212 determine_fuse_umount_cmd(status_t *stats)
213 {
214 	if(external_command_exists("fusermount"))
215 	{
216 		stats->fuse_umount_cmd = "fusermount -u";
217 	}
218 	else if(external_command_exists("umount"))
219 	{
220 		/* Some systems use regular umount command for FUSE. */
221 		stats->fuse_umount_cmd = "umount";
222 	}
223 	else
224 	{
225 		/* Leave default value. */
226 	}
227 }
228 
229 static void
set_gtk_available(status_t * stats)230 set_gtk_available(status_t *stats)
231 {
232 #ifdef HAVE_LIBGTK
233 	char *argv[] = { "vifm", NULL };
234 	int argc = ARRAY_LEN(argv) - 1;
235 	char **ptr = argv;
236 	stats->gtk_available = gtk_init_check(&argc, &ptr);
237 #endif
238 }
239 
240 int
stats_reset(const config_t * config)241 stats_reset(const config_t *config)
242 {
243 	set_last_cmdline_command("");
244 
245 	curr_stats.initial_lines = config->lines;
246 	curr_stats.initial_columns = config->columns;
247 
248 	return reset_dircache();
249 }
250 
251 /* Returns non-zero on error. */
252 static int
reset_dircache(void)253 reset_dircache(void)
254 {
255 	fsdata_free(dcache_size);
256 	dcache_size = fsdata_create(0, 1);
257 
258 	fsdata_free(dcache_nitems);
259 	dcache_nitems = fsdata_create(0, 1);
260 
261 	return (dcache_size == NULL || dcache_nitems == NULL);
262 }
263 
264 void
stats_set_use_multiplexer(int use_term_multiplexer)265 stats_set_use_multiplexer(int use_term_multiplexer)
266 {
267 	if(!use_term_multiplexer)
268 	{
269 		curr_stats.term_multiplexer = TM_NONE;
270 	}
271 	else if(inside_screen)
272 	{
273 		curr_stats.term_multiplexer = TM_SCREEN;
274 	}
275 	else if(inside_tmux)
276 	{
277 		curr_stats.term_multiplexer = TM_TMUX;
278 	}
279 	else
280 	{
281 		curr_stats.term_multiplexer = TM_NONE;
282 	}
283 }
284 
285 void
stats_update_shell_type(const char shell_cmd[])286 stats_update_shell_type(const char shell_cmd[])
287 {
288 	curr_stats.shell_type = get_shell_type(shell_cmd);
289 }
290 
291 TermState
stats_update_term_state(int screen_x,int screen_y)292 stats_update_term_state(int screen_x, int screen_y)
293 {
294 	if(screen_x < MIN_TERM_WIDTH || screen_y < MIN_TERM_HEIGHT)
295 	{
296 		curr_stats.term_state = TS_TOO_SMALL;
297 	}
298 	else if(curr_stats.term_state != TS_NORMAL)
299 	{
300 		curr_stats.term_state = TS_BACK_TO_NORMAL;
301 	}
302 
303 	return curr_stats.term_state;
304 }
305 
306 void
stats_set_chosen_files_out(const char output[])307 stats_set_chosen_files_out(const char output[])
308 {
309 	(void)replace_string(&curr_stats.chosen_files_out, output);
310 }
311 
312 void
stats_set_chosen_dir_out(const char output[])313 stats_set_chosen_dir_out(const char output[])
314 {
315 	(void)replace_string(&curr_stats.chosen_dir_out, output);
316 }
317 
318 void
stats_set_output_delimiter(const char delimiter[])319 stats_set_output_delimiter(const char delimiter[])
320 {
321 	(void)replace_string(&curr_stats.output_delimiter, delimiter);
322 }
323 
324 void
stats_set_on_choose(const char command[])325 stats_set_on_choose(const char command[])
326 {
327 	(void)replace_string(&curr_stats.on_choose, command);
328 }
329 
330 int
stats_file_choose_action_set(void)331 stats_file_choose_action_set(void)
332 {
333 	return !is_null_or_empty(curr_stats.chosen_files_out)
334 	    || !is_null_or_empty(curr_stats.on_choose);
335 }
336 
337 void
stats_save_msg(const char msg[])338 stats_save_msg(const char msg[])
339 {
340 	if(!curr_stats.save_msg_in_list || msg[0] == '\0')
341 	{
342 		return;
343 	}
344 
345 	if(curr_stats.msg_tail != curr_stats.msg_head &&
346 			strcmp(curr_stats.msgs[curr_stats.msg_tail], msg) == 0)
347 	{
348 		return;
349 	}
350 
351 	curr_stats.msg_tail = (curr_stats.msg_tail + 1)%ARRAY_LEN(curr_stats.msgs);
352 	if(curr_stats.msg_tail == curr_stats.msg_head)
353 	{
354 		free(curr_stats.msgs[curr_stats.msg_head]);
355 		curr_stats.msg_head = (curr_stats.msg_head + 1)%ARRAY_LEN(curr_stats.msgs);
356 	}
357 	curr_stats.msgs[curr_stats.msg_tail] = strdup(msg);
358 }
359 
360 void
stats_set_quickview(int on)361 stats_set_quickview(int on)
362 {
363 	curr_stats.preview.on = on;
364 	load_quickview_option();
365 }
366 
367 void
stats_set_splitter_pos(int position)368 stats_set_splitter_pos(int position)
369 {
370 	double max = (curr_stats.split == HSPLIT ? cfg.lines : cfg.columns);
371 	double ratio = (position < 0 ? 0.5 : (max == INT_MIN ? -1 : position/max));
372 
373 	curr_stats.splitter_ratio = ratio;
374 	if(curr_stats.splitter_pos != position)
375 	{
376 		curr_stats.splitter_pos = position;
377 		stats_redraw_later();
378 	}
379 }
380 
381 void
stats_set_splitter_ratio(double ratio)382 stats_set_splitter_ratio(double ratio)
383 {
384 	if(ratio == -1)
385 	{
386 		stats_set_splitter_pos(curr_stats.splitter_pos);
387 		return;
388 	}
389 
390 	curr_stats.splitter_ratio = ratio;
391 
392 	int max = (curr_stats.split == HSPLIT ? cfg.lines : cfg.columns);
393 	if(max == INT_MIN)
394 	{
395 		/* Can't compute position from ratio, so leave it as is. */
396 		return;
397 	}
398 
399 	int position = (ratio == 0.5 ? -1 : max*ratio + 0.5);
400 	if(curr_stats.splitter_pos != position)
401 	{
402 		curr_stats.splitter_pos = position;
403 		stats_redraw_later();
404 	}
405 }
406 
407 void
stats_redraw_later(void)408 stats_redraw_later(void)
409 {
410 	pending_redraw = 1;
411 }
412 
413 void
stats_refresh_later(void)414 stats_refresh_later(void)
415 {
416 	pending_refresh = 1;
417 }
418 
419 UpdateType
stats_update_fetch(void)420 stats_update_fetch(void)
421 {
422 	if(pending_refresh)
423 	{
424 		pending_refresh = 0;
425 		pending_redraw = 0;
426 		return UT_FULL;
427 	}
428 	if(pending_redraw)
429 	{
430 		pending_redraw = 0;
431 		return UT_REDRAW;
432 	}
433 	return UT_NONE;
434 }
435 
436 int
stats_redraw_planned(void)437 stats_redraw_planned(void)
438 {
439 	return pending_refresh != 0
440 	    || pending_redraw != 0;
441 }
442 
443 void
stats_silence_ui(int more)444 stats_silence_ui(int more)
445 {
446 	if(more)
447 	{
448 		++silent_ui;
449 	}
450 	else if(--silent_ui == 0)
451 	{
452 		stats_unsilence_ui();
453 	}
454 	else if(silent_ui < 0)
455 	{
456 		silent_ui = 0;
457 	}
458 }
459 
460 int
stats_silenced_ui(void)461 stats_silenced_ui(void)
462 {
463 	if(silent_ui)
464 	{
465 		silence_skipped_updates = 1;
466 		return 1;
467 	}
468 	return 0;
469 }
470 
471 void
stats_unsilence_ui(void)472 stats_unsilence_ui(void)
473 {
474 	silent_ui = 0;
475 	if(silence_skipped_updates)
476 	{
477 		silence_skipped_updates = 0;
478 		pending_redraw = 0;
479 		modes_redraw();
480 	}
481 }
482 
483 void
hists_resize(int new_size)484 hists_resize(int new_size)
485 {
486 	curr_stats.history_size = new_size;
487 
488 	hist_resize(&curr_stats.search_hist, new_size);
489 	hist_resize(&curr_stats.cmd_hist, new_size);
490 	hist_resize(&curr_stats.prompt_hist, new_size);
491 	hist_resize(&curr_stats.filter_hist, new_size);
492 }
493 
494 void
hists_commands_save(const char command[])495 hists_commands_save(const char command[])
496 {
497 	if(is_history_command(command))
498 	{
499 		if(!curr_stats.restart_in_progress && curr_stats.load_stage == 3)
500 		{
501 			set_last_cmdline_command(command);
502 		}
503 		hist_add(&curr_stats.cmd_hist, command, -1);
504 	}
505 }
506 
507 /* Sets last_cmdline_command field of the status structure. */
508 static void
set_last_cmdline_command(const char cmd[])509 set_last_cmdline_command(const char cmd[])
510 {
511 	const int err = replace_string(&curr_stats.last_cmdline_command, cmd);
512 	if(err != 0)
513 	{
514 		LOG_ERROR_MSG("replace_string() failed on duplicating: %s", cmd);
515 	}
516 	assert(curr_stats.last_cmdline_command != NULL &&
517 			"The field was not initialized properly");
518 }
519 
520 void
hists_search_save(const char pattern[])521 hists_search_save(const char pattern[])
522 {
523 	hist_add(&curr_stats.search_hist, pattern, -1);
524 }
525 
526 void
hists_prompt_save(const char input[])527 hists_prompt_save(const char input[])
528 {
529 	hist_add(&curr_stats.prompt_hist, input, -1);
530 }
531 
532 void
hists_filter_save(const char input[])533 hists_filter_save(const char input[])
534 {
535 	hist_add(&curr_stats.filter_hist, input, -1);
536 }
537 
538 const char *
hists_search_last(void)539 hists_search_last(void)
540 {
541 	return hist_is_empty(&curr_stats.search_hist)
542 	     ? ""
543 	     : curr_stats.search_hist.items[0].text;
544 }
545 
546 void
dcache_get_at(const char path[],time_t mtime,uint64_t inode,uint64_t * size,uint64_t * nitems)547 dcache_get_at(const char path[], time_t mtime, uint64_t inode, uint64_t *size,
548 		uint64_t *nitems)
549 {
550 	dcache_result_t size_res, nitems_res;
551 	dcache_get(path, mtime, inode, (size == NULL ? NULL : &size_res),
552 			(nitems == NULL ? NULL : &nitems_res));
553 
554 	if(size != NULL)
555 	{
556 		*size = (size_res.is_valid ? size_res.value : DCACHE_UNKNOWN);
557 	}
558 	if(nitems != NULL)
559 	{
560 		*nitems = (nitems_res.is_valid ? nitems_res.value : DCACHE_UNKNOWN);
561 	}
562 }
563 
564 void
dcache_get_of(const dir_entry_t * entry,dcache_result_t * size,dcache_result_t * nitems)565 dcache_get_of(const dir_entry_t *entry, dcache_result_t *size,
566 		dcache_result_t *nitems)
567 {
568 	if(size == NULL && nitems == NULL)
569 	{
570 		return;
571 	}
572 
573 	char full_path[PATH_MAX + 1];
574 	get_full_path_of(entry, sizeof(full_path), full_path);
575 
576 	uint64_t inode = get_true_inode(entry);
577 	dcache_get(full_path, entry->mtime, inode, size, nitems);
578 }
579 
580 /* Retrieves information about the path checking whether it's outdated.  size
581  * and/or nitems can be NULL. */
582 static void
dcache_get(const char path[],time_t mtime,uint64_t inode,dcache_result_t * size,dcache_result_t * nitems)583 dcache_get(const char path[], time_t mtime, uint64_t inode,
584 		dcache_result_t *size, dcache_result_t *nitems)
585 {
586 	if(size != NULL)
587 	{
588 		size->value = DCACHE_UNKNOWN;
589 		size->is_valid = 0;
590 
591 		pthread_mutex_lock(&dcache_size_mutex);
592 		dcache_data_t size_data;
593 		if(fsdata_get(dcache_size, path, &size_data, sizeof(size_data)) == 0)
594 		{
595 			size->value = size_data.value;
596 			/* We check strictly for less than to handle scenario when multiple
597 			 * changes occurred during the same second. */
598 			size->is_valid = (mtime < size_data.timestamp);
599 #ifndef _WIN32
600 			size->is_valid &= (inode == size_data.inode);
601 #endif
602 		}
603 		pthread_mutex_unlock(&dcache_size_mutex);
604 	}
605 
606 	if(nitems != NULL)
607 	{
608 		nitems->value = DCACHE_UNKNOWN;
609 		nitems->is_valid = 0;
610 
611 		pthread_mutex_lock(&dcache_nitems_mutex);
612 		dcache_data_t nitems_data;
613 		if(fsdata_get(dcache_nitems, path, &nitems_data, sizeof(nitems_data)) == 0)
614 		{
615 			nitems->value = nitems_data.value;
616 			/* We check strictly for less than to handle scenario when multiple
617 			 * changes occurred during the same second. */
618 			nitems->is_valid = (mtime < nitems_data.timestamp);
619 #ifndef _WIN32
620 			nitems->is_valid &= (inode == nitems_data.inode);
621 #endif
622 		}
623 		pthread_mutex_unlock(&dcache_nitems_mutex);
624 	}
625 }
626 
627 void
dcache_update_parent_sizes(const char path[],uint64_t by)628 dcache_update_parent_sizes(const char path[], uint64_t by)
629 {
630 	pthread_mutex_lock(&dcache_size_mutex);
631 	(void)fsdata_map_parents(dcache_size, path, &size_updater, &by);
632 	pthread_mutex_unlock(&dcache_size_mutex);
633 }
634 
635 /* Updates cached value by a fixed amount. */
636 static void
size_updater(void * data,void * arg)637 size_updater(void *data, void *arg)
638 {
639 	const uint64_t *const by = arg;
640 	dcache_data_t *const what = data;
641 
642 	what->value += *by;
643 }
644 
645 int
dcache_set_at(const char path[],uint64_t inode,uint64_t size,uint64_t nitems)646 dcache_set_at(const char path[], uint64_t inode, uint64_t size, uint64_t nitems)
647 {
648 	int ret = 0;
649 	const time_t ts = time(NULL);
650 
651 	if(size != DCACHE_UNKNOWN)
652 	{
653 		dcache_data_t data = { .value = size, .timestamp = ts };
654 #ifndef _WIN32
655 		data.inode = (ino_t)inode;
656 #endif
657 
658 		pthread_mutex_lock(&dcache_size_mutex);
659 		ret |= fsdata_set(dcache_size, path, &data, sizeof(data));
660 		pthread_mutex_unlock(&dcache_size_mutex);
661 	}
662 
663 	if(nitems != DCACHE_UNKNOWN)
664 	{
665 		dcache_data_t data = { .value = nitems, .timestamp = ts };
666 #ifndef _WIN32
667 		data.inode = (ino_t)inode;
668 #endif
669 
670 		pthread_mutex_lock(&dcache_nitems_mutex);
671 		ret |= fsdata_set(dcache_nitems, path, &data, sizeof(data));
672 		pthread_mutex_unlock(&dcache_nitems_mutex);
673 	}
674 
675 	return ret;
676 }
677 
678 /* vim: set tabstop=2 softtabstop=2 shiftwidth=2 noexpandtab cinoptions-=(0 : */
679 /* vim: set cinoptions+=t0 filetype=c : */
680