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