1 /* vifm
2  * Copyright (C) 2011 xaizek.
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 2 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
17  */
18 
19 #include "ops.h"
20 
21 #ifdef _WIN32
22 #include <windows.h>
23 #include <shellapi.h>
24 
25 #include "utils/utf8.h"
26 #endif
27 
28 #include <sys/stat.h> /* gid_t uid_t */
29 
30 #include <assert.h> /* assert() */
31 #include <stddef.h> /* NULL size_t */
32 #include <stdio.h> /* snprintf() */
33 #include <stdlib.h> /* calloc() free() */
34 #include <string.h> /* strdup() */
35 
36 #include "cfg/config.h"
37 #include "compat/fs_limits.h"
38 #include "compat/os.h"
39 #include "compat/reallocarray.h"
40 #include "io/ioeta.h"
41 #include "io/iop.h"
42 #include "io/ior.h"
43 #include "modes/dialogs/msg_dialog.h"
44 #include "ui/cancellation.h"
45 #include "utils/cancellation.h"
46 #include "utils/fs.h"
47 #include "utils/log.h"
48 #include "utils/macros.h"
49 #include "utils/path.h"
50 #include "utils/str.h"
51 #include "utils/utils.h"
52 #include "background.h"
53 #include "bmarks.h"
54 #include "status.h"
55 #include "trash.h"
56 #include "undo.h"
57 
58 #ifdef SUPPORT_NO_CLOBBER
59 #define NO_CLOBBER "-n"
60 #else
61 #define NO_CLOBBER ""
62 #endif
63 
64 /* Enable O(1) file cloning if it's available in installed version of
65  * coreutils. */
66 #ifdef SUPPORT_REFLINK_AUTO
67 #define REFLINK_AUTO "--reflink=auto"
68 #else
69 #define REFLINK_AUTO ""
70 #endif
71 
72 #define PRESERVE_FLAGS "-p"
73 
74 /* Types of conflict resolution actions to perform. */
75 typedef enum
76 {
77 	CA_FAIL,      /* Fail with an error. */
78 	CA_OVERWRITE, /* Overwrite existing files. */
79 	CA_APPEND,    /* Append the rest of source file to destination file. */
80 }
81 ConflictAction;
82 
83 /* Type of function that implements single operation. */
84 typedef int (*op_func)(ops_t *ops, void *data, const char *src, const char *dst);
85 
86 static int op_none(ops_t *ops, void *data, const char *src, const char *dst);
87 static int op_remove(ops_t *ops, void *data, const char *src, const char *dst);
88 static int op_removesl(ops_t *ops, void *data, const char *src,
89 		const char *dst);
90 static int op_copy(ops_t *ops, void *data, const char src[], const char dst[]);
91 static int op_copyf(ops_t *ops, void *data, const char src[], const char dst[]);
92 static int op_copya(ops_t *ops, void *data, const char src[], const char dst[]);
93 static int op_cp(ops_t *ops, void *data, const char src[], const char dst[],
94 		ConflictAction conflict_action);
95 static int op_move(ops_t *ops, void *data, const char src[], const char dst[]);
96 static int op_movef(ops_t *ops, void *data, const char src[], const char dst[]);
97 static int op_movea(ops_t *ops, void *data, const char src[], const char dst[]);
98 static int op_mv(ops_t *ops, void *data, const char src[], const char dst[],
99 		ConflictAction conflict_action);
100 static IoCrs ca_to_crs(ConflictAction conflict_action);
101 static int op_chown(ops_t *ops, void *data, const char *src, const char *dst);
102 static int op_chgrp(ops_t *ops, void *data, const char *src, const char *dst);
103 #ifndef _WIN32
104 static int op_chmod(ops_t *ops, void *data, const char *src, const char *dst);
105 static int op_chmodr(ops_t *ops, void *data, const char *src, const char *dst);
106 #else
107 static int op_addattr(ops_t *ops, void *data, const char *src, const char *dst);
108 static int op_subattr(ops_t *ops, void *data, const char *src, const char *dst);
109 #endif
110 static int op_symlink(ops_t *ops, void *data, const char *src, const char *dst);
111 static int op_mkdir(ops_t *ops, void *data, const char *src, const char *dst);
112 static int op_rmdir(ops_t *ops, void *data, const char *src, const char *dst);
113 static int op_mkfile(ops_t *ops, void *data, const char *src, const char *dst);
114 static int ops_uses_syscalls(const ops_t *ops);
115 static int exec_io_op(ops_t *ops, int (*func)(io_args_t *), io_args_t *args,
116 		int cancellable);
117 static int confirm_overwrite(io_args_t *args, const char src[],
118 		const char dst[]);
119 static char * pretty_dir_path(const char path[]);
120 static IoErrCbResult dispatch_error(io_args_t *args, const ioe_err_t *err);
121 static char prompt_user(const io_args_t *args, const char title[],
122 		const char msg[], const response_variant variants[]);
123 static int ui_cancellation_hook(void *arg);
124 #ifndef _WIN32
125 static int run_operation_command(ops_t *ops, char cmd[], int cancellable);
126 #endif
127 static int bg_cancellation_hook(void *arg);
128 
129 /* List of functions that implement operations. */
130 static op_func op_funcs[] = {
131 	[OP_NONE]     = &op_none,
132 	[OP_USR]      = &op_none,
133 	[OP_REMOVE]   = &op_remove,
134 	[OP_REMOVESL] = &op_removesl,
135 	[OP_COPY]     = &op_copy,
136 	[OP_COPYF]    = &op_copyf,
137 	[OP_COPYA]    = &op_copya,
138 	[OP_MOVE]     = &op_move,
139 	[OP_MOVEF]    = &op_movef,
140 	[OP_MOVEA]    = &op_movea,
141 	[OP_MOVETMP1] = &op_move,
142 	[OP_MOVETMP2] = &op_move,
143 	[OP_MOVETMP3] = &op_move,
144 	[OP_MOVETMP4] = &op_move,
145 	[OP_CHOWN]    = &op_chown,
146 	[OP_CHGRP]    = &op_chgrp,
147 #ifndef _WIN32
148 	[OP_CHMOD]    = &op_chmod,
149 	[OP_CHMODR]   = &op_chmodr,
150 #else
151 	[OP_ADDATTR]  = &op_addattr,
152 	[OP_SUBATTR]  = &op_subattr,
153 #endif
154 	[OP_SYMLINK]  = &op_symlink,
155 	[OP_SYMLINK2] = &op_symlink,
156 	[OP_MKDIR]    = &op_mkdir,
157 	[OP_RMDIR]    = &op_rmdir,
158 	[OP_MKFILE]   = &op_mkfile,
159 };
160 ARRAY_GUARD(op_funcs, OP_COUNT);
161 
162 /* Operation that is processed at the moment. */
163 static ops_t *curr_ops;
164 
165 ops_t *
ops_alloc(OPS main_op,int bg,const char descr[],const char base_dir[],const char target_dir[])166 ops_alloc(OPS main_op, int bg, const char descr[], const char base_dir[],
167 		const char target_dir[])
168 {
169 	ops_t *const ops = calloc(1, sizeof(*ops));
170 	ops->main_op = main_op;
171 	ops->descr = descr;
172 	update_string(&ops->slow_fs_list, cfg.slow_fs_list);
173 	update_string(&ops->delete_prg, cfg.delete_prg);
174 	ops->use_system_calls = cfg.use_system_calls;
175 	ops->fast_file_cloning = cfg.fast_file_cloning;
176 	ops->base_dir = strdup(base_dir);
177 	ops->target_dir = strdup(target_dir);
178 	ops->bg = bg;
179 	return ops;
180 }
181 
182 const char *
ops_describe(const ops_t * ops)183 ops_describe(const ops_t *ops)
184 {
185 	return ops->descr;
186 }
187 
188 void
ops_enqueue(ops_t * ops,const char src[],const char dst[])189 ops_enqueue(ops_t *ops, const char src[], const char dst[])
190 {
191 	++ops->total;
192 
193 	if(ops->estim == NULL)
194 	{
195 		return;
196 	}
197 
198 	/* Check once and cache result, it should be the same for each invocation. */
199 	if(ops->estim->total_items == 0)
200 	{
201 		switch(ops->main_op)
202 		{
203 			case OP_MOVE:
204 			case OP_MOVEF:
205 			case OP_MOVETMP1:
206 			case OP_MOVETMP2:
207 			case OP_MOVETMP3:
208 			case OP_MOVETMP4:
209 				if(dst != NULL && are_on_the_same_fs(src, dst))
210 				{
211 					/* Moving files/directories inside file system is cheap operation on
212 					 * top level items, no need to recur below. */
213 					ops->shallow_eta = 1;
214 				}
215 				break;
216 
217 			case OP_SYMLINK:
218 			case OP_SYMLINK2:
219 				/* No need for recursive traversal if we're going to create symbolic
220 				 * links. */
221 				ops->shallow_eta = 1;
222 				break;
223 
224 			default:
225 				/* No optimizations for other operations. */
226 				break;
227 		}
228 
229 		if(is_on_slow_fs(src, ops->slow_fs_list))
230 		{
231 			ops->shallow_eta = 1;
232 		}
233 	}
234 
235 	ioeta_calculate(ops->estim, src, ops->shallow_eta);
236 }
237 
238 void
ops_advance(ops_t * ops,int succeeded)239 ops_advance(ops_t *ops, int succeeded)
240 {
241 	++ops->current;
242 	assert(ops->current <= ops->total && "Current and total are out of sync.");
243 
244 	if(succeeded)
245 	{
246 		++ops->succeeded;
247 	}
248 }
249 
250 void
ops_free(ops_t * ops)251 ops_free(ops_t *ops)
252 {
253 	if(ops == NULL)
254 	{
255 		return;
256 	}
257 
258 	ioeta_free(ops->estim);
259 	free(ops->errors);
260 	free(ops->slow_fs_list);
261 	free(ops->delete_prg);
262 	free(ops->base_dir);
263 	free(ops->target_dir);
264 	free(ops);
265 }
266 
267 int
perform_operation(OPS op,ops_t * ops,void * data,const char src[],const char dst[])268 perform_operation(OPS op, ops_t *ops, void *data, const char src[],
269 		const char dst[])
270 {
271 	return op_funcs[op](ops, data, src, dst);
272 }
273 
274 static int
op_none(ops_t * ops,void * data,const char * src,const char * dst)275 op_none(ops_t *ops, void *data, const char *src, const char *dst)
276 {
277 	return 0;
278 }
279 
280 static int
op_remove(ops_t * ops,void * data,const char * src,const char * dst)281 op_remove(ops_t *ops, void *data, const char *src, const char *dst)
282 {
283 	if((ops == NULL || !ops->bg) && cfg_confirm_delete(0) &&
284 			!curr_stats.confirmed)
285 	{
286 		char *msg = format_str("Are you sure?  "
287 				"At least the following file is about to be deleted:\n \n%s\n \n"
288 				"If you're undoing a command and want to see file names, use "
289 				":undolist! command.",
290 				replace_home_part(src));
291 		curr_stats.confirmed = prompt_msg("Permanent deletion", msg);
292 		free(msg);
293 		if(!curr_stats.confirmed)
294 			return SKIP_UNDO_REDO_OPERATION;
295 	}
296 
297 	return op_removesl(ops, data, src, dst);
298 }
299 
300 static int
op_removesl(ops_t * ops,void * data,const char * src,const char * dst)301 op_removesl(ops_t *ops, void *data, const char *src, const char *dst)
302 {
303 	const char *const delete_prg = (ops == NULL)
304 	                             ? cfg.delete_prg
305 	                             : ops->delete_prg;
306 	if(delete_prg[0] != '\0')
307 	{
308 #ifndef _WIN32
309 		char *escaped;
310 		char cmd[2*PATH_MAX + 1];
311 		const int cancellable = (data == NULL);
312 
313 		escaped = shell_like_escape(src, 0);
314 		if(escaped == NULL)
315 		{
316 			return -1;
317 		}
318 
319 		snprintf(cmd, sizeof(cmd), "%s %s", delete_prg, escaped);
320 		free(escaped);
321 
322 		LOG_INFO_MSG("Running trash command: \"%s\"", cmd);
323 		return run_operation_command(ops, cmd, cancellable);
324 #else
325 		char cmd[PATH_MAX*2 + 1];
326 		snprintf(cmd, sizeof(cmd), "%s \"%s\"", delete_prg, src);
327 		internal_to_system_slashes(cmd);
328 
329 		return os_system(cmd);
330 #endif
331 	}
332 
333 	if(!ops_uses_syscalls(ops))
334 	{
335 #ifndef _WIN32
336 		char *escaped;
337 		char cmd[16 + PATH_MAX];
338 		int result;
339 		const int cancellable = data == NULL;
340 
341 		escaped = shell_like_escape(src, 0);
342 		if(escaped == NULL)
343 			return -1;
344 
345 		snprintf(cmd, sizeof(cmd), "rm -rf %s", escaped);
346 		LOG_INFO_MSG("Running rm command: \"%s\"", cmd);
347 		result = run_operation_command(ops, cmd, cancellable);
348 
349 		free(escaped);
350 		return result;
351 #else
352 		if(is_dir(src))
353 		{
354 			char path[PATH_MAX + 1];
355 			int err;
356 
357 			copy_str(path, sizeof(path), src);
358 			internal_to_system_slashes(path);
359 
360 			wchar_t *utf16_path = utf8_to_utf16(path);
361 
362 			/* SHFileOperationW requires pFrom to be double-nul terminated. */
363 			const size_t len = wcslen(utf16_path);
364 			wchar_t *new_utf16_path = reallocarray(utf16_path, len + 1U + 1U,
365 					sizeof(*utf16_path));
366 			if(new_utf16_path == NULL)
367 			{
368 				free(utf16_path);
369 				return -1;
370 			}
371 
372 			utf16_path = new_utf16_path;
373 			utf16_path[len + 1U] = L'\0';
374 
375 			SHFILEOPSTRUCTW fo = {
376 				.hwnd = NULL,
377 				.wFunc = FO_DELETE,
378 				.pFrom = utf16_path,
379 				.pTo = NULL,
380 				.fFlags = FOF_SILENT | FOF_NOCONFIRMATION | FOF_NOERRORUI,
381 			};
382 			err = SHFileOperationW(&fo);
383 
384 			log_msg("Error: %d", err);
385 			free(utf16_path);
386 
387 			return err;
388 		}
389 		else
390 		{
391 			int ok;
392 			wchar_t *const utf16_path = utf8_to_utf16(src);
393 			DWORD attributes = GetFileAttributesW(utf16_path);
394 			if(attributes & FILE_ATTRIBUTE_READONLY)
395 			{
396 				SetFileAttributesW(utf16_path, attributes & ~FILE_ATTRIBUTE_READONLY);
397 			}
398 
399 			ok = DeleteFileW(utf16_path);
400 			if(!ok)
401 			{
402 				LOG_WERROR(GetLastError());
403 			}
404 
405 			free(utf16_path);
406 			return !ok;
407 		}
408 #endif
409 	}
410 
411 	io_args_t args = {
412 		.arg1.path = src,
413 	};
414 	return exec_io_op(ops, &ior_rm, &args, data == NULL);
415 }
416 
417 /* OP_COPY operation handler.  Copies file/directory without overwriting
418  * destination files (when it's supported by the system).  Returns non-zero on
419  * error, otherwise zero is returned. */
420 static int
op_copy(ops_t * ops,void * data,const char src[],const char dst[])421 op_copy(ops_t *ops, void *data, const char src[], const char dst[])
422 {
423 	return op_cp(ops, data, src, dst, CA_FAIL);
424 }
425 
426 /* OP_COPYF operation handler.  Copies file/directory overwriting destination
427  * files.  Returns non-zero on error, otherwise zero is returned. */
428 static int
op_copyf(ops_t * ops,void * data,const char src[],const char dst[])429 op_copyf(ops_t *ops, void *data, const char src[], const char dst[])
430 {
431 	return op_cp(ops, data, src, dst, CA_OVERWRITE);
432 }
433 
434 /* OP_COPYA operation handler.  Copies file appending rest of the source file to
435  * the destination.  Returns non-zero on error, otherwise zero is returned. */
436 static int
op_copya(ops_t * ops,void * data,const char src[],const char dst[])437 op_copya(ops_t *ops, void *data, const char src[], const char dst[])
438 {
439 	return op_cp(ops, data, src, dst, CA_APPEND);
440 }
441 
442 /* Copies file/directory overwriting/appending destination files if requested.
443  * Returns non-zero on error, otherwise zero is returned. */
444 static int
op_cp(ops_t * ops,void * data,const char src[],const char dst[],ConflictAction conflict_action)445 op_cp(ops_t *ops, void *data, const char src[], const char dst[],
446 		ConflictAction conflict_action)
447 {
448 	const int fast_file_cloning = (ops == NULL)
449 	                             ? cfg.fast_file_cloning
450 	                             : ops->fast_file_cloning;
451 
452 	if(!ops_uses_syscalls(ops))
453 	{
454 #ifndef _WIN32
455 		char *escaped_src, *escaped_dst;
456 		char cmd[6 + PATH_MAX*2 + 1];
457 		int result;
458 		const int cancellable = (data == NULL);
459 
460 		escaped_src = shell_like_escape(src, 0);
461 		escaped_dst = shell_like_escape(dst, 0);
462 		if(escaped_src == NULL || escaped_dst == NULL)
463 		{
464 			free(escaped_dst);
465 			free(escaped_src);
466 			return -1;
467 		}
468 
469 		snprintf(cmd, sizeof(cmd),
470 				"cp %s %s -R " PRESERVE_FLAGS " %s %s",
471 				(conflict_action == CA_FAIL) ? NO_CLOBBER : "",
472 				fast_file_cloning ? REFLINK_AUTO : "",
473 				escaped_src, escaped_dst);
474 		LOG_INFO_MSG("Running cp command: \"%s\"", cmd);
475 		result = run_operation_command(ops, cmd, cancellable);
476 
477 		free(escaped_dst);
478 		free(escaped_src);
479 		return result;
480 #else
481 		int ret;
482 
483 		if(is_dir(src))
484 		{
485 			char cmd[6 + PATH_MAX*2 + 1];
486 			snprintf(cmd, sizeof(cmd), "xcopy \"%s\" \"%s\" ", src, dst);
487 			internal_to_system_slashes(cmd);
488 
489 			if(is_vista_and_above())
490 				strcat(cmd, "/B ");
491 			if(conflict_action != CA_FAIL)
492 			{
493 				strcat(cmd, "/Y ");
494 			}
495 			strcat(cmd, "/E /I /H /R > NUL");
496 			ret = os_system(cmd);
497 		}
498 		else
499 		{
500 			wchar_t *const utf16_src = utf8_to_utf16(src);
501 			wchar_t *const utf16_dst = utf8_to_utf16(dst);
502 			ret = (CopyFileW(utf16_src, utf16_dst, 0) == 0);
503 			free(utf16_dst);
504 			free(utf16_src);
505 		}
506 
507 		return ret;
508 #endif
509 	}
510 
511 	if(conflict_action == CA_OVERWRITE)
512 	{
513 		ops->crp = CRP_OVERWRITE_ALL;
514 	}
515 
516 	io_args_t args = {
517 		.arg1.src = src,
518 		.arg2.dst = dst,
519 		.arg3.crs = ca_to_crs(conflict_action),
520 		.arg4.fast_file_cloning = fast_file_cloning,
521 	};
522 	return exec_io_op(ops, &ior_cp, &args, data == NULL);
523 }
524 
525 /* OP_MOVE operation handler.  Moves file/directory without overwriting
526  * destination files (when it's supported by the system).  Returns non-zero on
527  * error, otherwise zero is returned. */
528 static int
op_move(ops_t * ops,void * data,const char src[],const char dst[])529 op_move(ops_t *ops, void *data, const char src[], const char dst[])
530 {
531 	return op_mv(ops, data, src, dst, CA_FAIL);
532 }
533 
534 /* OP_MOVEF operation handler.  Moves file/directory overwriting destination
535  * files.  Returns non-zero on error, otherwise zero is returned. */
536 static int
op_movef(ops_t * ops,void * data,const char src[],const char dst[])537 op_movef(ops_t *ops, void *data, const char src[], const char dst[])
538 {
539 	return op_mv(ops, data, src, dst, CA_OVERWRITE);
540 }
541 
542 /* OP_MOVEA operation handler.  Moves file appending rest of the source file to
543  * the destination.  Returns non-zero on error, otherwise zero is returned. */
544 static int
op_movea(ops_t * ops,void * data,const char src[],const char dst[])545 op_movea(ops_t *ops, void *data, const char src[], const char dst[])
546 {
547 	return op_mv(ops, data, src, dst, CA_APPEND);
548 }
549 
550 /* Moves file/directory overwriting/appending destination files if requested.
551  * Returns non-zero on error, otherwise zero is returned. */
552 static int
op_mv(ops_t * ops,void * data,const char src[],const char dst[],ConflictAction conflict_action)553 op_mv(ops_t *ops, void *data, const char src[], const char dst[],
554 		ConflictAction conflict_action)
555 {
556 	int result;
557 
558 	if(!ops_uses_syscalls(ops))
559 	{
560 #ifndef _WIN32
561 		struct stat st;
562 		char *escaped_src, *escaped_dst;
563 		char cmd[6 + PATH_MAX*2 + 1];
564 		const int cancellable = data == NULL;
565 
566 		if(conflict_action == CA_FAIL && os_lstat(dst, &st) == 0 &&
567 				!is_case_change(src, dst))
568 		{
569 			return -1;
570 		}
571 
572 		escaped_src = shell_like_escape(src, 0);
573 		escaped_dst = shell_like_escape(dst, 0);
574 		if(escaped_src == NULL || escaped_dst == NULL)
575 		{
576 			free(escaped_dst);
577 			free(escaped_src);
578 			return -1;
579 		}
580 
581 		snprintf(cmd, sizeof(cmd), "mv %s %s %s",
582 				(conflict_action == CA_FAIL) ? NO_CLOBBER : "",
583 				escaped_src, escaped_dst);
584 		free(escaped_dst);
585 		free(escaped_src);
586 
587 		LOG_INFO_MSG("Running mv command: \"%s\"", cmd);
588 		result = run_operation_command(ops, cmd, cancellable);
589 		if(result != 0)
590 		{
591 			return result;
592 		}
593 #else
594 		wchar_t *const utf16_src = utf8_to_utf16(src);
595 		wchar_t *const utf16_dst = utf8_to_utf16(dst);
596 
597 		BOOL ret = MoveFileW(utf16_src, utf16_dst);
598 
599 		free(utf16_src);
600 		free(utf16_dst);
601 
602 		if(!ret && GetLastError() == 5)
603 		{
604 			const int r = op_cp(ops, data, src, dst, conflict_action);
605 			if(r != 0)
606 			{
607 				return r;
608 			}
609 			return op_removesl(ops, data, src, NULL);
610 		}
611 		result = (ret == 0);
612 #endif
613 	}
614 	else
615 	{
616 		io_args_t args = {
617 			.arg1.src = src,
618 			.arg2.dst = dst,
619 			.arg3.crs = ca_to_crs(conflict_action),
620 			/* It's safe to always use fast file cloning on moving files. */
621 			.arg4.fast_file_cloning = 1,
622 		};
623 		result = exec_io_op(ops, &ior_mv, &args, data == NULL);
624 	}
625 
626 	if(result == 0)
627 	{
628 		trash_file_moved(src, dst);
629 		bmarks_file_moved(src, dst);
630 	}
631 
632 	return result;
633 }
634 
635 /* Maps conflict action to conflict resolution strategy of i/o modules.  Returns
636  * conflict resolution strategy type. */
637 static IoCrs
ca_to_crs(ConflictAction conflict_action)638 ca_to_crs(ConflictAction conflict_action)
639 {
640 	switch(conflict_action)
641 	{
642 		case CA_FAIL:      return IO_CRS_FAIL;
643 		case CA_OVERWRITE: return IO_CRS_REPLACE_FILES;
644 		case CA_APPEND:    return IO_CRS_APPEND_TO_FILES;
645 	}
646 	assert(0 && "Unhandled conflict action.");
647 	return IO_CRS_FAIL;
648 }
649 
650 static int
op_chown(ops_t * ops,void * data,const char * src,const char * dst)651 op_chown(ops_t *ops, void *data, const char *src, const char *dst)
652 {
653 #ifndef _WIN32
654 	char cmd[10 + 32 + PATH_MAX];
655 	char *escaped;
656 	uid_t uid = (uid_t)(long)data;
657 
658 	escaped = shell_like_escape(src, 0);
659 	snprintf(cmd, sizeof(cmd), "chown -fR %u %s", uid, escaped);
660 	free(escaped);
661 
662 	LOG_INFO_MSG("Running chown command: \"%s\"", cmd);
663 	return run_operation_command(ops, cmd, 1);
664 #else
665 	return -1;
666 #endif
667 }
668 
669 static int
op_chgrp(ops_t * ops,void * data,const char * src,const char * dst)670 op_chgrp(ops_t *ops, void *data, const char *src, const char *dst)
671 {
672 #ifndef _WIN32
673 	char cmd[10 + 32 + PATH_MAX];
674 	char *escaped;
675 	gid_t gid = (gid_t)(long)data;
676 
677 	escaped = shell_like_escape(src, 0);
678 	snprintf(cmd, sizeof(cmd), "chown -fR :%u %s", gid, escaped);
679 	free(escaped);
680 
681 	LOG_INFO_MSG("Running chgrp command: \"%s\"", cmd);
682 	return run_operation_command(ops, cmd, 1);
683 #else
684 	return -1;
685 #endif
686 }
687 
688 #ifndef _WIN32
689 static int
op_chmod(ops_t * ops,void * data,const char * src,const char * dst)690 op_chmod(ops_t *ops, void *data, const char *src, const char *dst)
691 {
692 	char cmd[128 + PATH_MAX];
693 	char *escaped;
694 
695 	escaped = shell_like_escape(src, 0);
696 	snprintf(cmd, sizeof(cmd), "chmod %s %s", (char *)data, escaped);
697 	free(escaped);
698 
699 	LOG_INFO_MSG("Running chmod command: \"%s\"", cmd);
700 	return run_operation_command(ops, cmd, 1);
701 }
702 
703 static int
op_chmodr(ops_t * ops,void * data,const char * src,const char * dst)704 op_chmodr(ops_t *ops, void *data, const char *src, const char *dst)
705 {
706 	char cmd[128 + PATH_MAX];
707 	char *escaped;
708 
709 	escaped = shell_like_escape(src, 0);
710 	snprintf(cmd, sizeof(cmd), "chmod -R %s %s", (char *)data, escaped);
711 	free(escaped);
712 
713 	LOG_INFO_MSG("Running chmodr command: \"%s\"", cmd);
714 	return run_operation_command(ops, cmd, 1);
715 }
716 #else
717 static int
op_addattr(ops_t * ops,void * data,const char * src,const char * dst)718 op_addattr(ops_t *ops, void *data, const char *src, const char *dst)
719 {
720 	const DWORD add_mask = (size_t)data;
721 	wchar_t *const utf16_path = utf8_to_utf16(src);
722 	const DWORD attrs = GetFileAttributesW(utf16_path);
723 	if(attrs == INVALID_FILE_ATTRIBUTES)
724 	{
725 		free(utf16_path);
726 		LOG_WERROR(GetLastError());
727 		return -1;
728 	}
729 	if(!SetFileAttributesW(utf16_path, attrs | add_mask))
730 	{
731 		free(utf16_path);
732 		LOG_WERROR(GetLastError());
733 		return -1;
734 	}
735 	free(utf16_path);
736 	return 0;
737 }
738 
739 static int
op_subattr(ops_t * ops,void * data,const char * src,const char * dst)740 op_subattr(ops_t *ops, void *data, const char *src, const char *dst)
741 {
742 	const DWORD sub_mask = (size_t)data;
743 	wchar_t *const utf16_path = utf8_to_utf16(src);
744 	const DWORD attrs = GetFileAttributesW(utf16_path);
745 	if(attrs == INVALID_FILE_ATTRIBUTES)
746 	{
747 		free(utf16_path);
748 		LOG_WERROR(GetLastError());
749 		return -1;
750 	}
751 	if(!SetFileAttributesW(utf16_path, attrs & ~sub_mask))
752 	{
753 		free(utf16_path);
754 		LOG_WERROR(GetLastError());
755 		return -1;
756 	}
757 	free(utf16_path);
758 	return 0;
759 }
760 #endif
761 
762 static int
op_symlink(ops_t * ops,void * data,const char * src,const char * dst)763 op_symlink(ops_t *ops, void *data, const char *src, const char *dst)
764 {
765 	if(!ops_uses_syscalls(ops))
766 	{
767 		char *escaped_src, *escaped_dst;
768 		char cmd[6 + PATH_MAX*2 + 1];
769 		int result;
770 
771 #ifndef _WIN32
772 		escaped_src = shell_like_escape(src, 0);
773 		escaped_dst = shell_like_escape(dst, 0);
774 #else
775 		escaped_src = strdup(enclose_in_dquotes(src));
776 		escaped_dst = strdup(enclose_in_dquotes(dst));
777 #endif
778 
779 		if(escaped_src == NULL || escaped_dst == NULL)
780 		{
781 			free(escaped_dst);
782 			free(escaped_src);
783 			return -1;
784 		}
785 
786 #ifndef _WIN32
787 		snprintf(cmd, sizeof(cmd), "ln -s %s %s", escaped_src, escaped_dst);
788 		LOG_INFO_MSG("Running ln command: \"%s\"", cmd);
789 		result = run_operation_command(ops, cmd, 1);
790 #else
791 		char exe_dir[PATH_MAX + 2];
792 		if(get_exe_dir(exe_dir, ARRAY_LEN(exe_dir)) != 0)
793 		{
794 			free(escaped_dst);
795 			free(escaped_src);
796 			return -1;
797 		}
798 
799 		snprintf(cmd, sizeof(cmd), "%s\\win_helper -s %s %s", exe_dir, escaped_src,
800 				escaped_dst);
801 		result = os_system(cmd);
802 #endif
803 
804 		free(escaped_dst);
805 		free(escaped_src);
806 		return result;
807 	}
808 
809 	io_args_t args = {
810 		.arg1.path = src,
811 		.arg2.target = dst,
812 		.arg3.crs = IO_CRS_REPLACE_FILES,
813 	};
814 	return exec_io_op(ops, &iop_ln, &args, 0);
815 }
816 
817 static int
op_mkdir(ops_t * ops,void * data,const char * src,const char * dst)818 op_mkdir(ops_t *ops, void *data, const char *src, const char *dst)
819 {
820 	if(!ops_uses_syscalls(ops))
821 	{
822 #ifndef _WIN32
823 		char cmd[128 + PATH_MAX];
824 		char *escaped;
825 
826 		escaped = shell_like_escape(src, 0);
827 		snprintf(cmd, sizeof(cmd), "mkdir %s %s", (data == NULL) ? "" : "-p",
828 				escaped);
829 		free(escaped);
830 		LOG_INFO_MSG("Running mkdir command: \"%s\"", cmd);
831 		return run_operation_command(ops, cmd, 1);
832 #else
833 		if(data == NULL)
834 		{
835 			wchar_t *const utf16_path = utf8_to_utf16(src);
836 			int r = CreateDirectoryW(utf16_path, NULL) == 0;
837 			free(utf16_path);
838 			return r;
839 		}
840 		else
841 		{
842 			char *const partial_path = strdup(src);
843 			char *part = partial_path + (is_path_absolute(src) ? 2 : 0);
844 			char *state = NULL;
845 
846 			while((part = split_and_get(part, '/', &state)) != NULL)
847 			{
848 				if(!is_dir(partial_path))
849 				{
850 					wchar_t *const utf16_path = utf8_to_utf16(partial_path);
851 					if(!CreateDirectoryW(utf16_path, NULL))
852 					{
853 						free(utf16_path);
854 						free(partial_path);
855 						return -1;
856 					}
857 					free(utf16_path);
858 				}
859 			}
860 			free(partial_path);
861 			return 0;
862 		}
863 #endif
864 	}
865 
866 	io_args_t args = {
867 		.arg1.path = src,
868 		.arg2.process_parents = data != NULL,
869 		.arg3.mode = 0755,
870 	};
871 	return exec_io_op(ops, &iop_mkdir, &args, 0);
872 }
873 
874 static int
op_rmdir(ops_t * ops,void * data,const char * src,const char * dst)875 op_rmdir(ops_t *ops, void *data, const char *src, const char *dst)
876 {
877 	if(!ops_uses_syscalls(ops))
878 	{
879 #ifndef _WIN32
880 		char cmd[128 + PATH_MAX];
881 		char *escaped;
882 
883 		escaped = shell_like_escape(src, 0);
884 		snprintf(cmd, sizeof(cmd), "rmdir %s", escaped);
885 		free(escaped);
886 		LOG_INFO_MSG("Running rmdir command: \"%s\"", cmd);
887 		return run_operation_command(ops, cmd, 1);
888 #else
889 		wchar_t *const utf16_path = utf8_to_utf16(src);
890 		const BOOL r = RemoveDirectoryW(utf16_path);
891 		free(utf16_path);
892 		return r == FALSE;
893 #endif
894 	}
895 
896 	io_args_t args = {
897 		.arg1.path = src,
898 	};
899 	return exec_io_op(ops, &iop_rmdir, &args, 0);
900 }
901 
902 static int
op_mkfile(ops_t * ops,void * data,const char * src,const char * dst)903 op_mkfile(ops_t *ops, void *data, const char *src, const char *dst)
904 {
905 	if(!ops_uses_syscalls(ops))
906 	{
907 #ifndef _WIN32
908 		char cmd[128 + PATH_MAX];
909 		char *escaped;
910 
911 		escaped = shell_like_escape(src, 0);
912 		snprintf(cmd, sizeof(cmd), "touch %s", escaped);
913 		free(escaped);
914 		LOG_INFO_MSG("Running touch command: \"%s\"", cmd);
915 		return run_operation_command(ops, cmd, 1);
916 #else
917 		HANDLE hfile;
918 
919 		wchar_t *const utf16_path = utf8_to_utf16(src);
920 		hfile = CreateFileW(utf16_path, 0, 0, NULL, CREATE_NEW,
921 				FILE_ATTRIBUTE_NORMAL, NULL);
922 		free(utf16_path);
923 		if(hfile == INVALID_HANDLE_VALUE)
924 		{
925 			return -1;
926 		}
927 
928 		CloseHandle(hfile);
929 		return 0;
930 #endif
931 	}
932 
933 	io_args_t args = {
934 		.arg1.path = src,
935 	};
936 	return exec_io_op(ops, &iop_mkfile, &args, 0);
937 }
938 
939 /* Checks whether specific operation should use system calls.  Returns non-zero
940  * if so, otherwise zero is returned. */
941 static int
ops_uses_syscalls(const ops_t * ops)942 ops_uses_syscalls(const ops_t *ops)
943 {
944 	return ops == NULL ? cfg.use_system_calls : ops->use_system_calls;
945 }
946 
947 /* Executes i/o operation with some predefined pre/post actions.  Returns exit
948  * code of i/o operation. */
949 static int
exec_io_op(ops_t * ops,int (* func)(io_args_t *),io_args_t * args,int cancellable)950 exec_io_op(ops_t *ops, int (*func)(io_args_t *), io_args_t *args,
951 		int cancellable)
952 {
953 	int result;
954 
955 	args->estim = (ops == NULL) ? NULL : ops->estim;
956 
957 	if(ops != NULL)
958 	{
959 		if(!ops->bg)
960 		{
961 			args->confirm = &confirm_overwrite;
962 			args->result.errors_cb = &dispatch_error;
963 		}
964 
965 		ioe_errlst_init(&args->result.errors);
966 	}
967 
968 	if(cancellable)
969 	{
970 		if(ops != NULL && ops->bg)
971 		{
972 			args->cancellation.arg = ops->bg_op;
973 			args->cancellation.hook = &bg_cancellation_hook;
974 		}
975 		else
976 		{
977 			/* ui_cancellation_push_off() should be called outside of this unit to
978 			 * allow bulking several operations together. */
979 			ui_cancellation_enable();
980 			args->cancellation.hook = &ui_cancellation_hook;
981 		}
982 	}
983 
984 	curr_ops = ops;
985 	result = func(args);
986 	curr_ops = NULL;
987 
988 	if(cancellable && (ops == NULL || !ops->bg))
989 	{
990 		ui_cancellation_disable();
991 	}
992 
993 	if(ops != NULL)
994 	{
995 		size_t len = (ops->errors == NULL) ? 0U : strlen(ops->errors);
996 		char *const suffix = ioe_errlst_to_str(&args->result.errors);
997 
998 		if(len != 0U)
999 		{
1000 			(void)strappend(&ops->errors, &len, "\n");
1001 		}
1002 		(void)strappend(&ops->errors, &len, suffix);
1003 
1004 		free(suffix);
1005 		ioe_errlst_free(&args->result.errors);
1006 	}
1007 
1008 	return result;
1009 }
1010 
1011 /* Asks user to confirm file overwrite.  Returns non-zero on positive user
1012  * answer, otherwise zero is returned. */
1013 static int
confirm_overwrite(io_args_t * args,const char src[],const char dst[])1014 confirm_overwrite(io_args_t *args, const char src[], const char dst[])
1015 {
1016 	/* TODO: think about adding "append" and "rename" options here. */
1017 	static const response_variant responses[] = {
1018 		{ .key = 'y', .descr = "[y]es", },
1019 		{ .key = 'Y', .descr = "[Y]es for all", },
1020 		{ .key = 'n', .descr = "[n]o", },
1021 		{ .key = 'N', .descr = "[N]o for all", },
1022 		{ },
1023 	};
1024 
1025 	char *title;
1026 	char *msg;
1027 	char response;
1028 	char *src_dir, *dst_dir;
1029 	const char *fname = get_last_path_component(dst);
1030 
1031 	if(curr_ops->crp != CRP_ASK)
1032 	{
1033 		return (curr_ops->crp == CRP_OVERWRITE_ALL) ? 1 : 0;
1034 	}
1035 
1036 	src_dir = pretty_dir_path(src);
1037 	dst_dir = pretty_dir_path(dst);
1038 
1039 	title = format_str("File overwrite while %s", curr_ops->descr);
1040 	msg = format_str("Overwrite \"%s\" in\n%s\nwith \"%s\" from\n%s\n?", fname,
1041 			dst_dir, fname, src_dir);
1042 
1043 	free(dst_dir);
1044 	free(src_dir);
1045 
1046 	response = prompt_user(args, title, msg, responses);
1047 
1048 	free(msg);
1049 	free(title);
1050 
1051 	switch(response)
1052 	{
1053 		case 'Y': curr_ops->crp = CRP_OVERWRITE_ALL; /* Fall through. */
1054 		case 'y': return 1;
1055 
1056 		case 'N': curr_ops->crp = CRP_SKIP_ALL; /* Fall through. */
1057 		case 'n': return 0;
1058 
1059 		default:
1060 			assert(0 && "Unexpected response.");
1061 			return 0;
1062 	}
1063 }
1064 
1065 /* Prepares path to presenting to the user.  Returns newly allocated string,
1066  * which should be freed by the caller, or NULL if there is not enough
1067  * memory. */
1068 static char *
pretty_dir_path(const char path[])1069 pretty_dir_path(const char path[])
1070 {
1071 	char dir_only[strlen(path) + 1];
1072 	char canonic[PATH_MAX + 1];
1073 
1074 	copy_str(dir_only, sizeof(dir_only), path);
1075 	remove_last_path_component(dir_only);
1076 	canonicalize_path(dir_only, canonic, sizeof(canonic));
1077 
1078 	return strdup(canonic);
1079 }
1080 
1081 /* Asks user what to do with encountered error.  Returns the response. */
1082 static IoErrCbResult
dispatch_error(io_args_t * args,const ioe_err_t * err)1083 dispatch_error(io_args_t *args, const ioe_err_t *err)
1084 {
1085 	static const response_variant responses[] = {
1086 		{ .key = 'r', .descr = "[r]etry", },
1087 		{ .key = 'i', .descr = "[i]gnore", },
1088 		{ .key = 'I', .descr = "[I]gnore for all", },
1089 		{ .key = 'a', .descr = "[a]bort", },
1090 		{ },
1091 	};
1092 
1093 	char *title;
1094 	char *msg;
1095 	char response;
1096 
1097 	/* For tests. */
1098 	if(curr_stats.load_stage == 0)
1099 	{
1100 		return IO_ECR_BREAK;
1101 	}
1102 
1103 	if(curr_ops->erp == ERP_IGNORE_ALL)
1104 	{
1105 		return IO_ECR_IGNORE;
1106 	}
1107 
1108 	title = format_str("Error while %s", curr_ops->descr);
1109 	msg = format_str("%s: %s", replace_home_part(err->path), err->msg);
1110 
1111 	response = prompt_user(args, title, msg, responses);
1112 
1113 	free(msg);
1114 	free(title);
1115 
1116 	switch(response)
1117 	{
1118 		case 'r': return IO_ECR_RETRY;
1119 
1120 		case 'I': curr_ops->erp = ERP_IGNORE_ALL; /* Fall through. */
1121 		case 'i': return IO_ECR_IGNORE;
1122 
1123 		case 'a': return IO_ECR_BREAK;
1124 
1125 		default:
1126 			assert(0 && "Unexpected response.");
1127 			return 0;
1128 	}
1129 }
1130 
1131 /* prompt_msg_custom() wrapper that takes care of interaction if cancellation is
1132  * active. */
1133 static char
prompt_user(const io_args_t * args,const char title[],const char msg[],const response_variant variants[])1134 prompt_user(const io_args_t *args, const char title[], const char msg[],
1135 		const response_variant variants[])
1136 {
1137 	/* Active cancellation conflicts with input processing by putting terminal in
1138 	 * a cooked mode. */
1139 	ui_cancellation_push_off();
1140 	const char response = prompt_msg_custom(title, msg, variants);
1141 	ui_cancellation_pop();
1142 
1143 	return response;
1144 }
1145 
1146 /* Implementation of cancellation hook for I/O unit. */
1147 static int
ui_cancellation_hook(void * arg)1148 ui_cancellation_hook(void *arg)
1149 {
1150 	return ui_cancellation_requested();
1151 }
1152 
1153 #ifndef _WIN32
1154 
1155 /* Runs command in background and displays its errors to a user.  To determine
1156  * an error uses both stderr stream and exit status.  Returns zero on success,
1157  * otherwise non-zero is returned. */
1158 static int
run_operation_command(ops_t * ops,char cmd[],int cancellable)1159 run_operation_command(ops_t *ops, char cmd[], int cancellable)
1160 {
1161 	if(!cancellable)
1162 	{
1163 		return bg_and_wait_for_errors(cmd, &no_cancellation);
1164 	}
1165 
1166 	if(ops != NULL && ops->bg)
1167 	{
1168 		const cancellation_t bg_cancellation_info = {
1169 			.arg = ops->bg_op,
1170 			.hook = &bg_cancellation_hook,
1171 		};
1172 		return bg_and_wait_for_errors(cmd, &bg_cancellation_info);
1173 	}
1174 	else
1175 	{
1176 		int result;
1177 		/* ui_cancellation_push_off() should be called outside this unit to allow
1178 		 * bulking several operations together. */
1179 		ui_cancellation_enable();
1180 		result = bg_and_wait_for_errors(cmd, &ui_cancellation_info);
1181 		ui_cancellation_disable();
1182 		return result;
1183 	}
1184 }
1185 
1186 #endif
1187 
1188 /* Implementation of cancellation hook for background tasks. */
1189 static int
bg_cancellation_hook(void * arg)1190 bg_cancellation_hook(void *arg)
1191 {
1192 	return bg_op_cancelled(arg);
1193 }
1194 
1195 /* vim: set tabstop=2 softtabstop=2 shiftwidth=2 noexpandtab cinoptions-=(0 : */
1196 /* vim: set cinoptions+=t0 filetype=c : */
1197