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 "fops_common.h"
21 
22 #include <regex.h>
23 
24 #include <sys/stat.h> /* stat umask() */
25 #include <sys/types.h> /* mode_t */
26 #include <fcntl.h>
27 #include <unistd.h> /* unlink() */
28 
29 #ifdef _WIN32
30 #include <windows.h>
31 #include <shellapi.h>
32 #endif
33 
34 #include <assert.h> /* assert() */
35 #include <errno.h> /* errno */
36 #include <stddef.h> /* NULL size_t */
37 #include <stdint.h> /* uint64_t */
38 #include <stdio.h> /* snprintf() */
39 #include <stdlib.h> /* calloc() free() malloc() realloc() strtol() */
40 #include <string.h> /* memcmp() memset() strcat() strcmp() strdup() strlen() */
41 #include <time.h> /* clock_gettime() */
42 
43 #include "cfg/config.h"
44 #include "compat/dtype.h"
45 #include "compat/fs_limits.h"
46 #include "compat/os.h"
47 #include "int/vim.h"
48 #include "io/ioeta.h"
49 #include "io/ionotif.h"
50 #include "modes/dialogs/msg_dialog.h"
51 #include "modes/modes.h"
52 #include "ui/cancellation.h"
53 #include "ui/fileview.h"
54 #include "ui/statusbar.h"
55 #include "ui/ui.h"
56 #include "utils/cancellation.h"
57 #ifdef _WIN32
58 #include "utils/env.h"
59 #endif
60 #include "utils/fs.h"
61 #include "utils/fsdata.h"
62 #include "utils/macros.h"
63 #include "utils/path.h"
64 #include "utils/str.h"
65 #include "utils/string_array.h"
66 #include "utils/utils.h"
67 #include "background.h"
68 #include "filelist.h"
69 #include "flist_pos.h"
70 #include "flist_sel.h"
71 #include "ops.h"
72 #include "running.h"
73 #include "status.h"
74 #include "trash.h"
75 #include "types.h"
76 #include "undo.h"
77 
78 /* 10 to the power of number of digits after decimal point to take into account
79  * on progress percentage counting. */
80 #define IO_PRECISION 10
81 
82 /* Maximum value of progress_data_t::last_progress. */
83 #define IO_MAX_PROGRESS (100*(IO_PRECISION))
84 
85 /* Key used to switch to progress dialog. */
86 #define IO_DETAILS_KEY 'i'
87 
88 /* Object for auxiliary information related to progress of operations in
89  * io_progress_changed() handler. */
90 typedef struct
91 {
92 	int bg; /* Whether this is background operation. */
93 	union
94 	{
95 		ops_t *ops;     /* Information for foreground operation. */
96 		bg_op_t *bg_op; /* Information for background operation. */
97 	};
98 
99 	int last_progress; /* Progress of the operation during previous call.
100 	                      Range is [-1; IO_MAX_PROGRESS]. */
101 	IoPs last_stage;   /* Stage of the operation during previous call. */
102 
103 	char *progress_bar;     /* String of progress bar. */
104 	int progress_bar_value; /* Value of progress bar during previous call. */
105 	int progress_bar_max;   /* Width of progress bar during previous call. */
106 
107 	/* State of rate calculation. */
108 	long long last_calc_time; /* Time of last rate calculation. */
109 	uint64_t last_seen_byte;  /* Position at the time of last call. */
110 	uint64_t rate;            /* Rate in bytes per millisecond. */
111 	char *rate_str;           /* Rate formatted as a string. */
112 
113 	/* Whether progress is displayed in a dialog, rather than on status bar. */
114 	int dialog;
115 
116 	int width; /* Maximum reached width of the dialog. */
117 }
118 progress_data_t;
119 
120 static void io_progress_changed(const io_progress_t *state);
121 static int calc_io_progress(const io_progress_t *state, int *skip);
122 static void update_io_rate(progress_data_t *pdata, const ioeta_estim_t *estim);
123 static void update_progress_bar(progress_data_t *pdata, const ioeta_estim_t *estim);
124 static void io_progress_fg(const io_progress_t *state, int progress);
125 static void io_progress_fg_sb(const io_progress_t *state, int progress);
126 static void io_progress_bg(const io_progress_t *state, int progress);
127 static char * format_file_progress(const ioeta_estim_t *estim, int precision);
128 static void format_pretty_path(const char base_dir[], const char path[],
129 		char pretty[], size_t pretty_size);
130 static int is_file_name_changed(const char old[], const char new[]);
131 static int ui_cancellation_hook(void *arg);
132 static progress_data_t * alloc_progress_data(int bg, void *info);
133 static long long time_in_ms(void);
134 
135 line_prompt_func fops_line_prompt;
136 options_prompt_func fops_options_prompt;
137 
138 void
fops_init(line_prompt_func line_func,options_prompt_func options_func)139 fops_init(line_prompt_func line_func, options_prompt_func options_func)
140 {
141 	fops_line_prompt = line_func;
142 	fops_options_prompt = options_func;
143 	ionotif_register(&io_progress_changed);
144 }
145 
146 /* I/O operation update callback. */
147 static void
io_progress_changed(const io_progress_t * state)148 io_progress_changed(const io_progress_t *state)
149 {
150 	const ioeta_estim_t *const estim = state->estim;
151 	progress_data_t *const pdata = estim->param;
152 
153 	int redraw = 0;
154 	int progress, skip;
155 
156 	progress = calc_io_progress(state, &skip);
157 	if(skip)
158 	{
159 		return;
160 	}
161 
162 	/* Don't query for scheduled redraw or input for background operations. */
163 	if(!pdata->bg)
164 	{
165 		redraw = (stats_update_fetch() != UT_NONE);
166 
167 		if(!pdata->dialog)
168 		{
169 			if(ui_char_pressed(IO_DETAILS_KEY))
170 			{
171 				pdata->dialog = 1;
172 				ui_sb_clear();
173 			}
174 		}
175 	}
176 
177 	/* Do nothing if progress change is small, but force update on stage
178 	 * change or redraw request. */
179 	if(progress == pdata->last_progress &&
180 			state->stage == pdata->last_stage && !redraw)
181 	{
182 		return;
183 	}
184 
185 	pdata->last_stage = state->stage;
186 
187 	if(progress >= 0)
188 	{
189 		pdata->last_progress = progress;
190 	}
191 
192 	if(redraw)
193 	{
194 		modes_redraw();
195 	}
196 
197 	if(pdata->bg)
198 	{
199 		io_progress_bg(state, progress);
200 	}
201 	else
202 	{
203 		io_progress_fg(state, progress);
204 	}
205 }
206 
207 /* Calculates current IO operation progress.  *skip will be set to non-zero
208  * value to indicate that progress change is irrelevant.  Returns progress in
209  * the range [-1; IO_MAX_PROGRESS], where -1 means "unknown". */
210 static int
calc_io_progress(const io_progress_t * state,int * skip)211 calc_io_progress(const io_progress_t *state, int *skip)
212 {
213 	const ioeta_estim_t *const estim = state->estim;
214 	progress_data_t *const pdata = estim->param;
215 
216 	*skip = 0;
217 	if(state->stage == IO_PS_ESTIMATING)
218 	{
219 		return estim->total_items/IO_PRECISION;
220 	}
221 	else if(estim->total_bytes == 0)
222 	{
223 		if(estim->total_items == 0)
224 		{
225 			return 0;
226 		}
227 		/* When files are empty, use their number for progress counting. */
228 		return (estim->current_item*IO_MAX_PROGRESS)/estim->total_items;
229 	}
230 	else if(pdata->last_progress >= IO_MAX_PROGRESS &&
231 			estim->current_byte == estim->total_bytes)
232 	{
233 		/* Special handling for unknown total size. */
234 		++pdata->last_progress;
235 		if(pdata->last_progress%IO_PRECISION != 0)
236 		{
237 			*skip = 1;
238 		}
239 		return -1;
240 	}
241 	else
242 	{
243 		return (estim->current_byte*IO_MAX_PROGRESS)/estim->total_bytes;
244 	}
245 }
246 
247 /* Updates rate of operation. */
248 static void
update_io_rate(progress_data_t * pdata,const ioeta_estim_t * estim)249 update_io_rate(progress_data_t *pdata, const ioeta_estim_t *estim)
250 {
251 	long long current_time_ms = time_in_ms();
252 	long long elapsed_time_ms = current_time_ms - pdata->last_calc_time;
253 
254 	if(elapsed_time_ms == 0 ||
255 			(pdata->last_seen_byte != 0 && elapsed_time_ms < 3000))
256 	{
257 		/* Rate is updated initially and then once in 3000 milliseconds. */
258 		return;
259 	}
260 
261 	uint64_t bytes_difference = estim->current_byte - pdata->last_seen_byte;
262 	pdata->rate = bytes_difference/elapsed_time_ms;
263 	pdata->last_calc_time = current_time_ms;
264 	pdata->last_seen_byte = estim->current_byte;
265 
266 	char rate_str[64];
267 	(void)friendly_size_notation(pdata->rate*1000, sizeof(rate_str) - 8,
268 			rate_str);
269 	strcat(rate_str, "/s");
270 	replace_string(&pdata->rate_str, rate_str);
271 }
272 
273 /* Updates progress bar of operation. */
274 static void
update_progress_bar(progress_data_t * pdata,const ioeta_estim_t * estim)275 update_progress_bar(progress_data_t *pdata, const ioeta_estim_t *estim)
276 {
277 	int max_width = pdata->width - 6;
278 	if(max_width <= 0)
279 	{
280 		return;
281 	}
282 
283 	int value = (pdata->last_progress*max_width)/IO_MAX_PROGRESS;
284 	if(value == pdata->progress_bar_value && max_width == pdata->progress_bar_max)
285 	{
286 		return;
287 	}
288 
289 	pdata->progress_bar_value = value;
290 	pdata->progress_bar_max = max_width;
291 
292 	pdata->progress_bar = malloc(max_width + 3);
293 
294 	pdata->progress_bar[0] = '[';
295 	memset(pdata->progress_bar + 1, '=', value);
296 	memset(pdata->progress_bar + 1 + value, ' ', max_width - value);
297 	pdata->progress_bar[1 + max_width] = ']';
298 	pdata->progress_bar[1 + max_width + 1] = '\0';
299 }
300 
301 /* Takes care of progress for foreground operations. */
302 static void
io_progress_fg(const io_progress_t * state,int progress)303 io_progress_fg(const io_progress_t *state, int progress)
304 {
305 	char current_size_str[64];
306 	char total_size_str[64];
307 	char src_path[PATH_MAX + 1];
308 	const char *title, *ctrl_msg;
309 	const char *target_name;
310 	char *as_part;
311 	const char *item_name;
312 	int item_num;
313 
314 	const ioeta_estim_t *const estim = state->estim;
315 	progress_data_t *const pdata = estim->param;
316 	ops_t *const ops = pdata->ops;
317 
318 	if(!pdata->dialog)
319 	{
320 		io_progress_fg_sb(state, progress);
321 		return;
322 	}
323 
324 	(void)friendly_size_notation(estim->total_bytes, sizeof(total_size_str),
325 			total_size_str);
326 
327 	copy_str(src_path, sizeof(src_path), replace_home_part(estim->item));
328 	remove_last_path_component(src_path);
329 
330 	title = ops_describe(ops);
331 	ctrl_msg = "Press Ctrl-C to cancel";
332 	if(state->stage == IO_PS_ESTIMATING)
333 	{
334 		char pretty_path[PATH_MAX + 1];
335 		format_pretty_path(ops->base_dir, estim->item, pretty_path,
336 				sizeof(pretty_path));
337 		draw_msgf(title, ctrl_msg, pdata->width,
338 				"In %s\nestimating...\nItems: %" PRINTF_ULL "\n"
339 				"Overall: %s\nCurrent: %s",
340 				ops->target_dir, (unsigned long long)estim->total_items, total_size_str,
341 				pretty_path);
342 		pdata->width = getmaxx(error_win);
343 		return;
344 	}
345 
346 	(void)friendly_size_notation(estim->current_byte,
347 			sizeof(current_size_str), current_size_str);
348 
349 	item_name = get_last_path_component(estim->item);
350 
351 	target_name = get_last_path_component(estim->target);
352 	if(stroscmp(target_name, item_name) == 0)
353 	{
354 		as_part = strdup("");
355 	}
356 	else
357 	{
358 		as_part = format_str("\nas   %s", target_name);
359 	}
360 
361 	item_num = MIN(estim->current_item + 1, estim->total_items);
362 
363 	update_io_rate(pdata, estim);
364 
365 	if(progress < 0)
366 	{
367 		/* Simplified message for unknown total size. */
368 		draw_msgf(title, ctrl_msg, pdata->width,
369 				"Location: %s\nItem:     %d of %" PRINTF_ULL "\n"
370 				"Overall:  %s %s\n"
371 				" \n" /* Space is on purpose to preserve empty line. */
372 				"file %s\nfrom %s%s",
373 				replace_home_part(ops->target_dir), item_num,
374 				(unsigned long long)estim->total_items, total_size_str, pdata->rate_str,
375 				item_name, src_path, as_part);
376 	}
377 	else
378 	{
379 		char *const file_progress = format_file_progress(estim, IO_PRECISION);
380 		update_progress_bar(pdata, estim);
381 
382 		draw_msgf(title, ctrl_msg, pdata->width,
383 				"Location: %s\nItem:     %d of %" PRINTF_ULL "\n"
384 				"Overall:  %s/%s (%2d%%) %s\n"
385 				"%s\n"
386 				" \n" /* Space is on purpose to preserve empty line. */
387 				"file %s\nfrom %s%s%s",
388 				replace_home_part(ops->target_dir), item_num,
389 				(unsigned long long)estim->total_items, current_size_str,
390 				total_size_str, progress/IO_PRECISION, pdata->rate_str,
391 				pdata->progress_bar, item_name, src_path, as_part, file_progress);
392 
393 		free(file_progress);
394 	}
395 
396 	pdata->width = getmaxx(error_win);
397 
398 	free(as_part);
399 }
400 
401 /* Takes care of progress for foreground operations displayed on status line. */
402 static void
io_progress_fg_sb(const io_progress_t * state,int progress)403 io_progress_fg_sb(const io_progress_t *state, int progress)
404 {
405 	const ioeta_estim_t *const estim = state->estim;
406 	progress_data_t *const pdata = estim->param;
407 	ops_t *const ops = pdata->ops;
408 
409 	char current_size_str[64];
410 	char total_size_str[64];
411 	char pretty_path[PATH_MAX + 1];
412 	char *suffix;
413 
414 	(void)friendly_size_notation(estim->total_bytes, sizeof(total_size_str),
415 			total_size_str);
416 
417 	format_pretty_path(ops->base_dir, estim->item, pretty_path,
418 			sizeof(pretty_path));
419 
420 	switch(state->stage)
421 	{
422 		case IO_PS_ESTIMATING:
423 			suffix = format_str("estimating... %" PRINTF_ULL "; %s %s",
424 					(unsigned long long)estim->total_items, total_size_str, pretty_path);
425 			break;
426 		case IO_PS_IN_PROGRESS:
427 			(void)friendly_size_notation(estim->current_byte,
428 					sizeof(current_size_str), current_size_str);
429 
430 			if(progress < 0)
431 			{
432 				/* Simplified message for unknown total size. */
433 				suffix = format_str("%" PRINTF_ULL " of %" PRINTF_ULL "; %s %s",
434 						(unsigned long long)estim->current_item + 1U,
435 						(unsigned long long)estim->total_items, total_size_str,
436 						pretty_path);
437 			}
438 			else
439 			{
440 				suffix = format_str("%" PRINTF_ULL " of %" PRINTF_ULL "; "
441 						"%s/%s (%2d%%) %s",
442 						(unsigned long long)estim->current_item + 1,
443 						(unsigned long long)estim->total_items, current_size_str,
444 						total_size_str, progress/IO_PRECISION, pretty_path);
445 			}
446 			break;
447 
448 		default:
449 			assert(0 && "Unhandled progress stage");
450 			suffix = strdup("");
451 			break;
452 	}
453 
454 	ui_sb_quick_msgf("(hit %c for details) %s: %s", IO_DETAILS_KEY,
455 			ops_describe(ops), suffix);
456 	free(suffix);
457 }
458 
459 /* Takes care of progress for background operations. */
460 static void
io_progress_bg(const io_progress_t * state,int progress)461 io_progress_bg(const io_progress_t *state, int progress)
462 {
463 	const ioeta_estim_t *const estim = state->estim;
464 	progress_data_t *const pdata = estim->param;
465 	bg_op_t *const bg_op = pdata->bg_op;
466 
467 	bg_op->progress = progress/IO_PRECISION;
468 	bg_op_changed(bg_op);
469 }
470 
471 /* Formats file progress part of the progress message.  Returns pointer to newly
472  * allocated memory. */
473 static char *
format_file_progress(const ioeta_estim_t * estim,int precision)474 format_file_progress(const ioeta_estim_t *estim, int precision)
475 {
476 	char current_size[64];
477 	char total_size[64];
478 
479 	const int file_progress = (estim->total_file_bytes == 0U) ? 0 :
480 		(estim->current_file_byte*100*precision)/estim->total_file_bytes;
481 
482 	if(estim->total_items == 1)
483 	{
484 		return strdup("");
485 	}
486 
487 	(void)friendly_size_notation(estim->current_file_byte, sizeof(current_size),
488 			current_size);
489 	(void)friendly_size_notation(estim->total_file_bytes, sizeof(total_size),
490 			total_size);
491 
492 	return format_str("\nprogress %s/%s (%2d%%)", current_size, total_size,
493 			file_progress/precision);
494 }
495 
496 /* Pretty prints path shortening it by skipping base directory path if
497  * possible, otherwise fallbacks to the full path. */
498 static void
format_pretty_path(const char base_dir[],const char path[],char pretty[],size_t pretty_size)499 format_pretty_path(const char base_dir[], const char path[], char pretty[],
500 		size_t pretty_size)
501 {
502 	if(!path_starts_with(path, base_dir))
503 	{
504 		copy_str(pretty, pretty_size, path);
505 		return;
506 	}
507 
508 	copy_str(pretty, pretty_size, skip_char(path + strlen(base_dir), '/'));
509 }
510 
511 int
fops_is_name_list_ok(int count,int nlines,char * list[],char * files[])512 fops_is_name_list_ok(int count, int nlines, char *list[], char *files[])
513 {
514 	int i;
515 
516 	if(nlines < count)
517 	{
518 		ui_sb_errf("Not enough file names (%d/%d)", nlines, count);
519 		curr_stats.save_msg = 1;
520 		return 0;
521 	}
522 
523 	if(nlines > count)
524 	{
525 		ui_sb_errf("Too many file names (%d/%d)", nlines, count);
526 		curr_stats.save_msg = 1;
527 		return 0;
528 	}
529 
530 	for(i = 0; i < count; ++i)
531 	{
532 		chomp(list[i]);
533 
534 		if(files != NULL)
535 		{
536 			char *file_s = find_slashr(files[i]);
537 			char *list_s = find_slashr(list[i]);
538 			if(list_s != NULL || file_s != NULL)
539 			{
540 				if(list_s == NULL || file_s == NULL ||
541 						list_s - list[i] != file_s - files[i] ||
542 						strnoscmp(files[i], list[i], list_s - list[i]) != 0)
543 				{
544 					if(file_s == NULL)
545 						ui_sb_errf("Name \"%s\" contains slash", list[i]);
546 					else
547 						ui_sb_errf("Won't move \"%s\" file", files[i]);
548 					curr_stats.save_msg = 1;
549 					return 0;
550 				}
551 			}
552 		}
553 
554 		if(list[i][0] != '\0' && is_in_string_array(list, i, list[i]))
555 		{
556 			ui_sb_errf("Name \"%s\" duplicates", list[i]);
557 			curr_stats.save_msg = 1;
558 			return 0;
559 		}
560 	}
561 
562 	return 1;
563 }
564 
565 int
fops_is_rename_list_ok(char * files[],char is_dup[],int len,char * list[])566 fops_is_rename_list_ok(char *files[], char is_dup[], int len, char *list[])
567 {
568 	int i;
569 	const char *const work_dir = flist_get_dir(curr_view);
570 	for(i = 0; i < len; ++i)
571 	{
572 		int j;
573 
574 		const int check_result =
575 			fops_check_file_rename(work_dir, files[i], list[i], ST_NONE);
576 		if(check_result < 0)
577 		{
578 			continue;
579 		}
580 
581 		for(j = 0; j < len; ++j)
582 		{
583 			if(strcmp(list[i], files[j]) == 0 && !is_dup[j])
584 			{
585 				is_dup[j] = 1;
586 				break;
587 			}
588 		}
589 		if(j >= len && check_result == 0)
590 		{
591 			/* Invoke fops_check_file_rename() again, but this time to produce error
592 			 * message. */
593 			(void)fops_check_file_rename(work_dir, files[i], list[i], ST_STATUS_BAR);
594 			break;
595 		}
596 	}
597 	return i >= len;
598 }
599 
600 int
fops_check_file_rename(const char dir[],const char old[],const char new[],SignalType signal_type)601 fops_check_file_rename(const char dir[], const char old[], const char new[],
602 		SignalType signal_type)
603 {
604 	if(!is_file_name_changed(old, new))
605 	{
606 		return -1;
607 	}
608 
609 	if(path_exists_at(dir, new, NODEREF) && stroscmp(old, new) != 0 &&
610 			!is_case_change(old, new))
611 	{
612 		switch(signal_type)
613 		{
614 			case ST_STATUS_BAR:
615 				ui_sb_errf("File \"%s\" already exists", new);
616 				curr_stats.save_msg = 1;
617 				break;
618 			case ST_DIALOG:
619 				show_error_msg("File exists",
620 						"That file already exists. Will not overwrite.");
621 				break;
622 
623 			default:
624 				assert(signal_type == ST_NONE && "Unhandled signaling type");
625 				break;
626 		}
627 		return 0;
628 	}
629 
630 	return 1;
631 }
632 
633 /* Checks whether file name change was performed.  Returns non-zero if change is
634  * detected, otherwise zero is returned. */
635 static int
is_file_name_changed(const char old[],const char new[])636 is_file_name_changed(const char old[], const char new[])
637 {
638 	/* Empty new name means reuse of the old name (rename cancellation).  Names
639 	 * are always compared in a case sensitive way, so that changes in case of
640 	 * letters triggers rename operation even for systems where paths are case
641 	 * insensitive. */
642 	return (new[0] != '\0' && strcmp(old, new) != 0);
643 }
644 
645 char **
fops_grab_marked_files(view_t * view,size_t * nmarked)646 fops_grab_marked_files(view_t *view, size_t *nmarked)
647 {
648 	char **marked = NULL;
649 	dir_entry_t *entry = NULL;
650 	*nmarked = 0;
651 	while(iter_marked_entries(view, &entry))
652 	{
653 		*nmarked = add_to_string_array(&marked, *nmarked, entry->name);
654 	}
655 	return marked;
656 }
657 
658 int
fops_is_dir_entry(const char full_path[],const struct dirent * dentry)659 fops_is_dir_entry(const char full_path[], const struct dirent *dentry)
660 {
661 #ifndef _WIN32
662 	struct stat s;
663 	const int type = get_dirent_type(dentry, full_path);
664 	if(type != DT_UNKNOWN)
665 	{
666 		return type == DT_DIR;
667 	}
668 	if(os_lstat(full_path, &s) == 0 && s.st_ino != 0)
669 	{
670 		return (s.st_mode&S_IFMT) == S_IFDIR;
671 	}
672 	return 0;
673 #else
674 	return is_dir(full_path);
675 #endif
676 }
677 
678 int
fops_enqueue_marked_files(ops_t * ops,view_t * view,const char dst_hint[],int to_trash)679 fops_enqueue_marked_files(ops_t *ops, view_t *view, const char dst_hint[],
680 		int to_trash)
681 {
682 	int nmarked_files = 0;
683 	dir_entry_t *entry = NULL;
684 
685 	ui_cancellation_enable();
686 
687 	while(iter_marked_entries(view, &entry) && !ui_cancellation_requested())
688 	{
689 		char full_path[PATH_MAX + 1];
690 
691 		get_full_path_of(entry, sizeof(full_path), full_path);
692 
693 		if(to_trash)
694 		{
695 			char *const trash_dir = trash_pick_dir(entry->origin);
696 			ops_enqueue(ops, full_path, trash_dir);
697 			free(trash_dir);
698 		}
699 		else
700 		{
701 			ops_enqueue(ops, full_path, dst_hint);
702 		}
703 
704 		++nmarked_files;
705 	}
706 
707 	ui_cancellation_disable();
708 
709 	return nmarked_files;
710 }
711 
712 ops_t *
fops_get_ops(OPS main_op,const char descr[],const char base_dir[],const char target_dir[])713 fops_get_ops(OPS main_op, const char descr[], const char base_dir[],
714 		const char target_dir[])
715 {
716 	ops_t *const ops = ops_alloc(main_op, 0, descr, base_dir, target_dir);
717 	if(ops->use_system_calls)
718 	{
719 		progress_data_t *const pdata = alloc_progress_data(ops->bg, ops);
720 		const io_cancellation_t cancellation = { .hook = &ui_cancellation_hook };
721 		ops->estim = ioeta_alloc(pdata, cancellation);
722 	}
723 	ui_cancellation_push_off();
724 	return ops;
725 }
726 
727 /* Implementation of cancellation hook for I/O unit. */
728 static int
ui_cancellation_hook(void * arg)729 ui_cancellation_hook(void *arg)
730 {
731 	return ui_cancellation_requested();
732 }
733 
734 void
fops_progress_msg(const char text[],int ready,int total)735 fops_progress_msg(const char text[], int ready, int total)
736 {
737 	if(!cfg.use_system_calls)
738 	{
739 		char msg[strlen(text) + 32];
740 
741 		sprintf(msg, "%s %d/%d", text, ready + 1, total);
742 		show_progress(msg, 1);
743 		curr_stats.save_msg = 2;
744 	}
745 }
746 
747 const char *
fops_get_dst_name(const char src_path[],int from_trash)748 fops_get_dst_name(const char src_path[], int from_trash)
749 {
750 	if(from_trash)
751 	{
752 		return trash_get_real_name_of(src_path);
753 	}
754 	return get_last_path_component(src_path);
755 }
756 
757 int
fops_can_read_marked_files(view_t * view)758 fops_can_read_marked_files(view_t *view)
759 {
760 	dir_entry_t *entry;
761 
762 	if(is_unc_path(view->curr_dir))
763 	{
764 		return 1;
765 	}
766 
767 	entry = NULL;
768 	while(iter_marked_entries(view, &entry))
769 	{
770 		char full_path[PATH_MAX + 1];
771 		get_full_path_of(entry, sizeof(full_path), full_path);
772 
773 		/* We can copy links even when they are broken, so it's OK to don't check
774 		 * them (otherwise access() fails for broken links). */
775 		if(entry->type == FT_LINK || os_access(full_path, R_OK) == 0)
776 		{
777 			continue;
778 		}
779 
780 		show_error_msgf("Access denied",
781 				"You don't have read permissions on \"%s\"", full_path);
782 		flist_sel_stash(view);
783 		redraw_view(view);
784 		return 0;
785 	}
786 	return 1;
787 }
788 
789 int
fops_check_dir_path(const view_t * view,const char path[],char buf[],size_t buf_len)790 fops_check_dir_path(const view_t *view, const char path[], char buf[],
791 		size_t buf_len)
792 {
793 	if(path[0] == '/' || path[0] == '~')
794 	{
795 		char *const expanded_path = expand_tilde(path);
796 		copy_str(buf, buf_len, expanded_path);
797 		free(expanded_path);
798 	}
799 	else
800 	{
801 		snprintf(buf, buf_len, "%s/%s", fops_get_dst_dir(view, -1), path);
802 	}
803 
804 	if(is_dir(buf))
805 	{
806 		return 1;
807 	}
808 
809 	copy_str(buf, buf_len, fops_get_dst_dir(view, -1));
810 	return 0;
811 }
812 
813 char **
fops_edit_list(size_t count,char * orig[],int * nlines,int load_always)814 fops_edit_list(size_t count, char *orig[], int *nlines, int load_always)
815 {
816 	char rename_file[PATH_MAX + 1];
817 	char **list = NULL;
818 	mode_t saved_umask;
819 
820 	*nlines = 0;
821 
822 	generate_tmp_file_name("vifm.rename", rename_file, sizeof(rename_file));
823 
824 	/* Allow temporary file to be only readable and writable by current user. */
825 	saved_umask = umask(~0600);
826 	if(write_file_of_lines(rename_file, orig, count) != 0)
827 	{
828 		(void)umask(saved_umask);
829 		show_error_msgf("Error Getting List Of Renames",
830 				"Can't create temporary file \"%s\": %s", rename_file, strerror(errno));
831 		return NULL;
832 	}
833 	(void)umask(saved_umask);
834 
835 	if(vim_view_file(rename_file, -1, -1, 0) != 0)
836 	{
837 		show_error_msgf("Error Editing File", "Editing of file \"%s\" failed.",
838 				rename_file);
839 	}
840 	else
841 	{
842 		list = read_file_of_lines(rename_file, nlines);
843 		if(list == NULL)
844 		{
845 			show_error_msgf("Error Getting List Of Renames",
846 					"Can't open temporary file \"%s\": %s", rename_file, strerror(errno));
847 		}
848 
849 		if(!load_always)
850 		{
851 			size_t i = count - 1U;
852 			if((size_t)*nlines == count)
853 			{
854 				for(i = 0U; i < count; ++i)
855 				{
856 					if(strcmp(list[i], orig[i]) != 0)
857 					{
858 						break;
859 					}
860 				}
861 			}
862 
863 			if(i == count)
864 			{
865 				free_string_array(list, *nlines);
866 				list = NULL;
867 				*nlines = 0;
868 			}
869 		}
870 	}
871 
872 	unlink(rename_file);
873 	return list;
874 }
875 
876 void
fops_bg_ops_init(ops_t * ops,bg_op_t * bg_op)877 fops_bg_ops_init(ops_t *ops, bg_op_t *bg_op)
878 {
879 	ops->bg_op = bg_op;
880 	if(ops->estim != NULL)
881 	{
882 		progress_data_t *const pdata = ops->estim->param;
883 		pdata->bg_op = bg_op;
884 	}
885 }
886 
887 ops_t *
fops_get_bg_ops(OPS main_op,const char descr[],const char dir[])888 fops_get_bg_ops(OPS main_op, const char descr[], const char dir[])
889 {
890 	ops_t *const ops = ops_alloc(main_op, 1, descr, dir, dir);
891 	if(ops->use_system_calls)
892 	{
893 		progress_data_t *const pdata = alloc_progress_data(ops->bg, NULL);
894 		const io_cancellation_t no_cancellation = {};
895 		ops->estim = ioeta_alloc(pdata, no_cancellation);
896 	}
897 	return ops;
898 }
899 
900 /* Allocates progress data with specified parameters and initializes all the
901  * rest of structure fields with default values. */
902 static progress_data_t *
alloc_progress_data(int bg,void * info)903 alloc_progress_data(int bg, void *info)
904 {
905 	progress_data_t *const pdata = malloc(sizeof(*pdata));
906 
907 	pdata->bg = bg;
908 	pdata->ops = info;
909 
910 	pdata->last_progress = -1;
911 	pdata->last_stage = (IoPs)-1;
912 
913 	pdata->progress_bar = strdup("");
914 	pdata->progress_bar_value = 0;
915 	pdata->progress_bar_max = 0;
916 
917 	/* Time of starting the operation to have meaningful first rate. */
918 	pdata->last_calc_time = time_in_ms();
919 	pdata->last_seen_byte = 0;
920 	pdata->rate = 0;
921 	pdata->rate_str = strdup("? B/s");
922 
923 	pdata->dialog = 0;
924 	pdata->width = 0;
925 
926 	return pdata;
927 }
928 
929 /* Retrieves current time in milliseconds. */
930 static long long
time_in_ms(void)931 time_in_ms(void)
932 {
933 	struct timespec current_time;
934 	if(clock_gettime(CLOCK_MONOTONIC, &current_time) != 0)
935 	{
936 		return 0;
937 	}
938 
939 	return current_time.tv_sec*1000 + current_time.tv_nsec/1000000;
940 }
941 
942 void
fops_free_ops(ops_t * ops)943 fops_free_ops(ops_t *ops)
944 {
945 	if(ops == NULL)
946 	{
947 		return;
948 	}
949 
950 	if(!ops->bg)
951 	{
952 		ui_cancellation_pop();
953 		ui_drain_input();
954 	}
955 
956 	if(ops->use_system_calls)
957 	{
958 		if(!ops->bg && ops->errors != NULL)
959 		{
960 			char *const title = format_str("Encountered errors on %s",
961 					ops_describe(ops));
962 			show_error_msg(title, ops->errors);
963 			free(title);
964 		}
965 
966 		progress_data_t *pdata = ops->estim->param;
967 		free(pdata->progress_bar);
968 		free(pdata->rate_str);
969 		free(pdata);
970 	}
971 	ops_free(ops);
972 }
973 
974 int
fops_mv_file(const char src[],const char src_dir[],const char dst[],const char dst_dir[],OPS op,int cancellable,ops_t * ops)975 fops_mv_file(const char src[], const char src_dir[], const char dst[],
976 		const char dst_dir[], OPS op, int cancellable, ops_t *ops)
977 {
978 	char full_src[PATH_MAX + 1], full_dst[PATH_MAX + 1];
979 
980 	to_canonic_path(src, src_dir, full_src, sizeof(full_src));
981 	to_canonic_path(dst, dst_dir, full_dst, sizeof(full_dst));
982 
983 	return fops_mv_file_f(full_src, full_dst, op, 0, cancellable, ops);
984 }
985 
986 int
fops_mv_file_f(const char src[],const char dst[],OPS op,int bg,int cancellable,ops_t * ops)987 fops_mv_file_f(const char src[], const char dst[], OPS op, int bg,
988 		int cancellable, ops_t *ops)
989 {
990 	int result;
991 
992 	/* Compare case sensitive strings even on Windows to let user rename file
993 	 * changing only case of some characters. */
994 	if(strcmp(src, dst) == 0)
995 	{
996 		return 0;
997 	}
998 
999 	result = perform_operation(op, ops, cancellable ? NULL : (void *)1, src, dst);
1000 	if(result == 0 && !bg)
1001 	{
1002 		un_group_add_op(op, NULL, NULL, src, dst);
1003 	}
1004 	return result;
1005 }
1006 
1007 void
fops_free_bg_args(bg_args_t * args)1008 fops_free_bg_args(bg_args_t *args)
1009 {
1010 	free_string_array(args->list, args->nlines);
1011 	free_string_array(args->sel_list, args->sel_list_len);
1012 	free(args->is_in_trash);
1013 	fops_free_ops(args->ops);
1014 	free(args);
1015 }
1016 
1017 void
fops_prepare_for_bg_task(view_t * view,bg_args_t * args)1018 fops_prepare_for_bg_task(view_t *view, bg_args_t *args)
1019 {
1020 	dir_entry_t *entry;
1021 
1022 	entry = NULL;
1023 	while(iter_marked_entries(view, &entry))
1024 	{
1025 		char full_path[PATH_MAX + 1];
1026 
1027 		get_full_path_of(entry, sizeof(full_path), full_path);
1028 		args->sel_list_len = add_to_string_array(&args->sel_list,
1029 				args->sel_list_len, full_path);
1030 	}
1031 
1032 	ui_view_reset_selection_and_reload(view);
1033 }
1034 
1035 void
fops_append_marked_files(view_t * view,char buf[],char ** fnames)1036 fops_append_marked_files(view_t *view, char buf[], char **fnames)
1037 {
1038 	const int custom_fnames = (fnames != NULL);
1039 	size_t len = strlen(buf);
1040 	dir_entry_t *entry = NULL;
1041 	while(iter_marked_entries(view, &entry) && len < COMMAND_GROUP_INFO_LEN)
1042 	{
1043 		fops_append_fname(buf, len, entry->name);
1044 		len = strlen(buf);
1045 
1046 		if(custom_fnames)
1047 		{
1048 			const char *const custom_fname = *fnames++;
1049 
1050 			strncat(buf, " to ", COMMAND_GROUP_INFO_LEN - len - 1);
1051 			len = strlen(buf);
1052 			strncat(buf, custom_fname, COMMAND_GROUP_INFO_LEN - len - 1);
1053 			len = strlen(buf);
1054 		}
1055 	}
1056 }
1057 
1058 void
fops_append_fname(char buf[],size_t len,const char fname[])1059 fops_append_fname(char buf[], size_t len, const char fname[])
1060 {
1061 	if(buf[len - 2] != ':')
1062 	{
1063 		strncat(buf, ", ", COMMAND_GROUP_INFO_LEN - len - 1);
1064 		len = strlen(buf);
1065 	}
1066 	strncat(buf, fname, COMMAND_GROUP_INFO_LEN - len - 1);
1067 }
1068 
1069 const char *
fops_get_cancellation_suffix(void)1070 fops_get_cancellation_suffix(void)
1071 {
1072 	return ui_cancellation_requested() ? " (cancelled)" : "";
1073 }
1074 
1075 int
fops_view_can_be_changed(const view_t * view)1076 fops_view_can_be_changed(const view_t *view)
1077 {
1078 	/* TODO: maybe add check whether directory of specific entry is writable for
1079 	 *       custom views. */
1080 	return flist_custom_active(view)
1081 	    || fops_is_dir_writable(DR_CURRENT, view->curr_dir);
1082 }
1083 
1084 int
fops_view_can_be_extended(const view_t * view,int at)1085 fops_view_can_be_extended(const view_t *view, int at)
1086 {
1087 	if(flist_custom_active(view) && !cv_tree(view->custom.type))
1088 	{
1089 		show_error_msg("Operation error",
1090 				"Custom view can't handle this operation.");
1091 		return 0;
1092 	}
1093 
1094 	return fops_is_dir_writable(DR_DESTINATION, fops_get_dst_dir(view, at));
1095 }
1096 
1097 const char *
fops_get_dst_dir(const view_t * view,int at)1098 fops_get_dst_dir(const view_t *view, int at)
1099 {
1100 	if(flist_custom_active(view) && cv_tree(view->custom.type))
1101 	{
1102 		if(at < 0)
1103 		{
1104 			at = view->list_pos;
1105 		}
1106 		else if(at >= view->list_rows)
1107 		{
1108 			at = view->list_rows - 1;
1109 		}
1110 		return view->dir_entry[at].origin;
1111 	}
1112 	return flist_get_dir(view);
1113 }
1114 
1115 int
fops_is_dir_writable(DirRole dir_role,const char path[])1116 fops_is_dir_writable(DirRole dir_role, const char path[])
1117 {
1118 	if(is_dir_writable(path))
1119 	{
1120 		return 1;
1121 	}
1122 
1123 	if(dir_role == DR_DESTINATION)
1124 	{
1125 		show_error_msg("Operation error", "Destination directory is not writable");
1126 	}
1127 	else
1128 	{
1129 		show_error_msg("Operation error", "Current directory is not writable");
1130 	}
1131 	return 0;
1132 }
1133 
1134 /* vim: set tabstop=2 softtabstop=2 shiftwidth=2 noexpandtab cinoptions-=(0 : */
1135 /* vim: set cinoptions+=t0 filetype=c : */
1136