1 /* file_operations.c -- control multiple file operations */
2
3 /*
4 * This file is part of CliFM
5 *
6 * Copyright (C) 2016-2021, L. Abramovich <johndoe.arch@outlook.com>
7 * All rights reserved.
8
9 * CliFM is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
13 *
14 * CliFM is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, write to the Free Software
21 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
22 * MA 02110-1301, USA.
23 */
24
25 #include "helpers.h"
26
27 #include <errno.h>
28 #include <stdio.h>
29 #include <string.h>
30 #include <sys/stat.h>
31 #include <unistd.h>
32 #include <time.h>
33 #include <readline/readline.h>
34
35 #include <fcntl.h>
36
37 #ifndef _NO_ARCHIVING
38 #include "archives.h"
39 #endif
40 #include "aux.h"
41 #include "checks.h"
42 #include "colors.h"
43 #include "exec.h"
44 #include "file_operations.h"
45 #include "history.h"
46 #include "listing.h"
47 #include "mime.h"
48 #include "misc.h"
49 #include "navigation.h"
50 #include "readline.h"
51 #include "selection.h"
52 #include "messages.h"
53
54 void
clear_selbox(void)55 clear_selbox(void)
56 {
57 size_t i;
58 for (i = 0; i < sel_n; i++)
59 free(sel_elements[i]);
60 sel_n = 0;
61 save_sel();
62 }
63
64 /* Open a file via OPENER, if set, or via LIRA. If not compiled with
65 * Lira support, fallback to open (Haiku), or xdg-open. Returns zero
66 * on success and one on failure */
67 int
open_file(char * file)68 open_file(char *file)
69 {
70 if (!file || !*file)
71 return EXIT_FAILURE;
72
73 int exit_status = EXIT_SUCCESS;
74
75 if (opener) {
76 char *cmd[] = {opener, file, NULL};
77 if (launch_execve(cmd, FOREGROUND, E_NOSTDERR) != EXIT_SUCCESS)
78 exit_status = EXIT_FAILURE;
79 } else {
80 #ifndef _NO_LIRA
81 char *cmd[] = {"mm", file, NULL};
82 exit_status = mime_open(cmd);
83 #else
84 /* Fallback to (xdg-)open */
85 #ifdef __HAIKU__
86 char *cmd[] = {"open", file, NULL};
87 #else
88 char *cmd[] = {"xdg-open", file, NULL};
89 #endif /* __HAIKU__ */
90 if (launch_execve(cmd, FOREGROUND, E_NOSTDERR) != EXIT_SUCCESS)
91 exit_status = EXIT_FAILURE;
92 #endif /* _NO_LIRA */
93 }
94
95 return exit_status;
96 }
97
98 /* Toggle executable bit on file */
99 int
xchmod(const char * file,mode_t mode)100 xchmod(const char *file, mode_t mode)
101 {
102 /* Set or unset S_IXUSR, S_IXGRP, and S_IXOTH */
103 (0100 & mode) ? (mode &= (mode_t)~0111) : (mode |= 0111);
104
105 log_function(NULL);
106
107 int fd = open(file, O_WRONLY);
108 if (fd == -1) {
109 fprintf(stderr, "%s: %s: %s\n", PROGRAM_NAME, file, strerror(errno));
110 return EXIT_FAILURE;
111 }
112
113 if (fchmod(fd, mode) == -1) {
114 close(fd);
115 fprintf(stderr, "%s: %s: %s\n", PROGRAM_NAME, file, strerror(errno));
116 return EXIT_FAILURE;
117 }
118
119 close(fd);
120
121 return EXIT_SUCCESS;
122 }
123
124 /* Create a duplicate of a file/dir using rsync or cp */
125 int
dup_file(char * source,char * dest)126 dup_file(char *source, char *dest)
127 {
128 if (!source || !*source)
129 return EXIT_FAILURE;
130
131 log_function(NULL);
132
133 if (strchr(source, '\\')) {
134 char *deq_str = dequote_str(source, 0);
135 if (!deq_str) {
136 fprintf(stderr, "%s: %s: Error dequoting file name\n",
137 PROGRAM_NAME, source);
138 return EXIT_FAILURE;
139 }
140 strcpy(source, deq_str);
141 free(deq_str);
142 }
143
144 if (dest) {
145 if (strchr(dest, '\\')) {
146 char *deq_str = dequote_str(dest, 0);
147 if (!deq_str) {
148 fprintf(stderr, "%s: %s: Error dequoting file name\n",
149 PROGRAM_NAME, source);
150 return EXIT_FAILURE;
151 }
152 strcpy(dest, deq_str);
153 free(deq_str);
154 }
155 }
156
157 int exit_status = EXIT_SUCCESS;
158 int free_dest = 0;
159
160 /* If no dest, use source as file name: source.copy, and, if already
161 * exists, source.copy.YYYYMMDDHHMMSS */
162 if (!dest) {
163 size_t source_len = strlen(source);
164 if (strcmp(source, "/") != 0 && source[source_len - 1] == '/')
165 source[source_len - 1] = '\0';
166
167 char *tmp = strrchr(source, '/');
168 char *source_name;
169
170 if (tmp && *(tmp + 1))
171 source_name = tmp + 1;
172 else
173 source_name = source;
174
175 free_dest = 1;
176 dest = (char *)xnmalloc(strlen(source_name) + 6, sizeof(char));
177 sprintf(dest, "%s.copy", source_name);
178
179 struct stat attr;
180 if (stat(dest, &attr) == EXIT_SUCCESS) {
181 time_t rawtime = time(NULL);
182 struct tm tm;
183 localtime_r(&rawtime, &tm);
184 char *suffix = gen_date_suffix(tm);
185 if (!suffix) {
186 free(dest);
187 return EXIT_FAILURE;
188 }
189
190 char tmp_dest[PATH_MAX];
191 xstrsncpy(tmp_dest, dest, PATH_MAX);
192 dest = (char *)xrealloc(dest, (strlen(tmp_dest) + strlen(suffix) + 2)
193 * sizeof(char));
194 sprintf(dest, "%s.%s", tmp_dest, suffix);
195 free(suffix);
196 }
197 }
198
199 char *rsync_path = get_cmd_path("rsync");
200 if (rsync_path) {
201 char *cmd[] = {"rsync", "-aczvAXHS", "--progress", source, dest, NULL};
202 if (launch_execve(cmd, FOREGROUND, E_NOFLAG) != EXIT_SUCCESS)
203 exit_status = EXIT_FAILURE;
204 free(rsync_path);
205 } else {
206 char *cmd[] = {"cp", "-a", source, dest, NULL};
207 if (launch_execve(cmd, FOREGROUND, E_NOFLAG) != EXIT_SUCCESS)
208 exit_status = EXIT_FAILURE;
209 }
210
211 if (free_dest)
212 free(dest);
213 return exit_status;
214 }
215
216 int
create_file(char ** cmd)217 create_file(char **cmd)
218 {
219 if (cmd[1] && *cmd[1] == '-' && strcmp(cmd[1], "--help") == 0) {
220 puts(_(NEW_USAGE));
221 return EXIT_FAILURE;
222 }
223
224 log_function(NULL);
225
226 int exit_status = EXIT_SUCCESS;
227 #ifdef __HAIKU__
228 int file_in_cwd = 0;
229 #endif
230 int free_cmd = 0;
231
232 /* If no argument provided, ask the user for a filename */
233 if (!cmd[1]) {
234 char *filename = (char *)NULL;
235 while (!filename) {
236 puts(_("End filename with a slash to create a directory"));
237 filename = rl_no_hist(_("Filename ('q' to quit): "));
238
239 if (!filename)
240 continue;
241
242 if (!*filename) {
243 free(filename);
244 filename = (char *)NULL;
245 continue;
246 }
247 }
248
249 if (*filename == 'q' && !filename[1]) {
250 free(filename);
251 return EXIT_SUCCESS;
252 }
253
254 /* Once we have the filename, reconstruct the cmd array */
255 char **tmp_cmd = (char **)xnmalloc(args_n + 3, sizeof(char *));
256 tmp_cmd[0] = (char *)xnmalloc(2, sizeof(char));
257 *tmp_cmd[0] = 'n';
258 tmp_cmd[0][1] = '\0';
259 tmp_cmd[1] = (char *)xnmalloc(strlen(filename) + 1, sizeof(char));
260 strcpy(tmp_cmd[1], filename);
261 tmp_cmd[2] = (char *)NULL;
262 cmd = tmp_cmd;
263 free_cmd = 1;
264 free(filename);
265 }
266
267 /* Properly format filenames */
268 size_t i;
269 for (i = 1; cmd[i]; i++) {
270 if (strchr(cmd[i], '\\')) {
271 char *deq_str = dequote_str(cmd[i], 0);
272 if (!deq_str) {
273 _err('w', PRINT_PROMPT, _("%s: %s: Error dequoting filename\n"),
274 PROGRAM_NAME, cmd[i]);
275 continue;
276 }
277
278 strcpy(cmd[i], deq_str);
279 free(deq_str);
280 }
281
282 if (*cmd[i] == '~') {
283 char *exp_path = tilde_expand(cmd[i]);
284 if (exp_path) {
285 cmd[i] = (char *)xrealloc(cmd[i], (strlen(exp_path) + 1)
286 * sizeof(char));
287 strcpy(cmd[i], exp_path);
288 free(exp_path);
289 }
290 }
291
292 /* If the file already exists, create it as file.new */
293 struct stat a;
294 if (lstat(cmd[i], &a) == 0) {
295 int dir = 0;
296 char old_name[PATH_MAX];
297 strcpy(old_name, cmd[i]);
298
299 size_t len = strlen(cmd[i]);
300 if (cmd[i][len - 1] == '/') {
301 cmd[i][len - 1] = '\0';
302 dir = 1;
303 }
304
305 cmd[i] = (char *)xrealloc(cmd[i], (len + 5) * sizeof(char));
306 if (dir)
307 strcat(cmd[i], ".new/");
308 else
309 strcat(cmd[i], ".new");
310
311 _err(0, PRINT_PROMPT, _("%s: %s: File already exists. "
312 "Trying with '%s' instead\n"), PROGRAM_NAME, old_name, cmd[i]);
313 }
314
315 #ifdef __HAIKU__
316 /* If at least one filename lacks a slash (or it is the only and
317 * last char, in which case we have a directory in CWD), we are
318 * creating a file in CWD, and thereby we need to update the screen */
319 char *ret = strrchr(cmd[i], '/');
320 if (!ret || !*(ret + 1))
321 file_in_cwd = 1;
322 #endif
323 }
324
325 /* Construct commands */
326 size_t files_num = i - 1;
327
328 char **nfiles = (char **)xnmalloc(files_num + 2, sizeof(char *));
329 char **ndirs = (char **)xnmalloc(files_num + 3, sizeof(char *));
330
331 /* Let's use 'touch' for files and 'mkdir -p' for dirs */
332 nfiles[0] = (char *)xnmalloc(6, sizeof(char));
333 strcpy(nfiles[0], "touch");
334
335 ndirs[0] = (char *)xnmalloc(6, sizeof(char));
336 strcpy(ndirs[0], "mkdir");
337
338 ndirs[1] = (char *)xnmalloc(3, sizeof(char));
339 ndirs[1][0] = '-';
340 ndirs[1][1] = 'p';
341 ndirs[1][2] = '\0';
342
343 size_t cnfiles = 1, cndirs = 2;
344
345 for (i = 1; cmd[i]; i++) {
346 size_t cmd_len = strlen(cmd[i]);
347 /* Filenames ending with a slash are taken as dir names */
348 if (cmd[i][cmd_len - 1] == '/')
349 ndirs[cndirs++] = cmd[i];
350 else
351 nfiles[cnfiles++] = cmd[i];
352 }
353
354 ndirs[cndirs] = (char *)NULL;
355 nfiles[cnfiles] = (char *)NULL;
356
357 /* Execute commands */
358 if (cnfiles > 1) {
359 if (launch_execve(nfiles, FOREGROUND, 0) != EXIT_SUCCESS)
360 exit_status = EXIT_FAILURE;
361 }
362
363 if (cndirs > 2) {
364 if (launch_execve(ndirs, FOREGROUND, 0) != EXIT_SUCCESS)
365 exit_status = EXIT_FAILURE;
366 }
367
368 free(nfiles[0]);
369 free(ndirs[0]);
370 free(ndirs[1]);
371 free(nfiles);
372 free(ndirs);
373 if (free_cmd) {
374 for (i = 0; cmd[i]; i++)
375 free(cmd[i]);
376 free(cmd);
377 }
378
379 #ifdef __HAIKU__
380 if (exit_status == EXIT_SUCCESS && autols && file_in_cwd) {
381 free_dirlist();
382 if (list_dir() != EXIT_SUCCESS)
383 exit_status = EXIT_FAILURE;
384 }
385 #endif
386
387 return exit_status;
388 }
389
390 int
open_function(char ** cmd)391 open_function(char **cmd)
392 {
393 if (!cmd)
394 return EXIT_FAILURE;
395
396 if (*cmd[0] == 'o' && (!cmd[0][1] || strcmp(cmd[0], "open") == 0)) {
397 if (strchr(cmd[1], '\\')) {
398 char *deq_path = dequote_str(cmd[1], 0);
399 if (!deq_path) {
400 fprintf(stderr, _("%s: %s: Error dequoting filename\n"),
401 PROGRAM_NAME, cmd[1]);
402 return EXIT_FAILURE;
403 }
404
405 strcpy(cmd[1], deq_path);
406 free(deq_path);
407 }
408 }
409
410 char *file = cmd[1];
411
412 /* Check file existence */
413 struct stat attr;
414 if (lstat(file, &attr) == -1) {
415 fprintf(stderr, "%s: open: %s: %s\n", PROGRAM_NAME, cmd[1],
416 strerror(errno));
417 return EXIT_FAILURE;
418 }
419
420 /* Check file type: only directories, symlinks, and regular files
421 * will be opened */
422
423 char no_open_file = 1;
424 char *file_type = (char *)NULL;
425 char *types[] = {
426 "block device",
427 "character device",
428 "socket",
429 "FIFO/pipe",
430 "unknown file type",
431 NULL};
432
433 switch ((attr.st_mode & S_IFMT)) {
434 /* Store file type to compose and print the error message, if
435 * necessary */
436 case S_IFBLK: file_type = types[OPEN_BLK]; break;
437 case S_IFCHR: file_type = types[OPEN_CHR]; break;
438 case S_IFSOCK: file_type = types[OPEN_SOCK]; break;
439 case S_IFIFO: file_type = types[OPEN_FIFO]; break;
440 case S_IFDIR: return cd_function(file, CD_PRINT_ERROR);
441 case S_IFLNK: {
442 int ret = get_link_ref(file);
443 if (ret == -1) {
444 fprintf(stderr, _("%s: %s: Broken symbolic link\n"),
445 PROGRAM_NAME, file);
446 return EXIT_FAILURE;
447 } else if (ret == S_IFDIR) {
448 return cd_function(file, CD_PRINT_ERROR);
449 } else if (ret != S_IFREG) {
450 switch (ret) {
451 case S_IFBLK: file_type = types[OPEN_BLK]; break;
452 case S_IFCHR: file_type = types[OPEN_CHR]; break;
453 case S_IFSOCK: file_type = types[OPEN_SOCK]; break;
454 case S_IFIFO: file_type = types[OPEN_FIFO]; break;
455 default: file_type = types[OPEN_UNK]; break;
456 }
457 }
458 }
459 /* fallthrough */
460 case S_IFREG:
461 /*#ifndef _NO_ARCHIVING
462 // If an archive/compressed file, call archiver()
463 if (is_compressed(file, 1) == 0) {
464 char *tmp_cmd[] = {"ad", file, NULL};
465 return archiver(tmp_cmd, 'd');
466 }
467 #endif */
468 no_open_file = 0;
469 break;
470
471 default:
472 file_type = types[OPEN_UNK];
473 break;
474 }
475
476 /* If neither directory nor regular file nor symlink (to directory
477 * or regular file), print the corresponding error message and
478 * exit */
479 if (no_open_file) {
480 fprintf(stderr, _("%s: %s (%s): Cannot open file\nTry "
481 "'APPLICATION FILENAME'\n"), PROGRAM_NAME, cmd[1], file_type);
482 return EXIT_FAILURE;
483 }
484
485 /* At this point we know the file to be openend is either a regular
486 * file or a symlink to a regular file. So, just open the file */
487 if (!cmd[2] || (*cmd[2] == '&' && !cmd[2][1])) {
488 int ret = open_file(file);
489 if (!opener && ret == EXIT_FAILURE) {
490 fputs("Add a new entry to the mimelist file ('mime "
491 "edit' or F6) or run 'open FILE APPLICATION'\n", stderr);
492 return EXIT_FAILURE;
493 }
494 return ret;
495 }
496
497 /* If some application was specified to open the file */
498 char *tmp_cmd[] = {cmd[2], file, NULL};
499 int ret = launch_execve(tmp_cmd, bg_proc ? BACKGROUND : FOREGROUND, E_NOSTDERR);
500 if (ret != EXIT_SUCCESS)
501 return EXIT_FAILURE;
502
503 return EXIT_SUCCESS;
504 }
505
506 /* Relink symlink to new path */
507 int
edit_link(char * link)508 edit_link(char *link)
509 {
510 if (!link || !*link)
511 return EXIT_FAILURE;
512
513 log_function(NULL);
514
515 /* Dequote the file name, if necessary */
516 if (strchr(link, '\\')) {
517 char *tmp = dequote_str(link, 0);
518 if (!tmp) {
519 fprintf(stderr, _("%s: %s: Error dequoting file\n"),
520 PROGRAM_NAME, link);
521 return EXIT_FAILURE;
522 }
523
524 strcpy(link, tmp);
525 free(tmp);
526 }
527
528 /* Check we have a valid symbolic link */
529 struct stat file_attrib;
530 if (lstat(link, &file_attrib) == -1) {
531 fprintf(stderr, "%s: %s: %s\n", PROGRAM_NAME, link,
532 strerror(errno));
533 return EXIT_FAILURE;
534 }
535
536 if ((file_attrib.st_mode & S_IFMT) != S_IFLNK) {
537 fprintf(stderr, _("%s: %s: Not a symbolic link\n"),
538 PROGRAM_NAME, link);
539 return EXIT_FAILURE;
540 }
541
542 /* Get file pointed to by symlink and report to the user */
543 char *real_path = realpath(link, NULL);
544 if (!real_path) {
545 printf(_("%s%s%s currently pointing to nowhere (broken link)\n"),
546 or_c, link, df_c);
547 } else {
548 printf(_("%s%s%s currently pointing to "), ln_c, link, df_c);
549 colors_list(real_path, NO_ELN, NO_PAD, PRINT_NEWLINE);
550 free(real_path);
551 real_path = (char *)NULL;
552 }
553
554 char *new_path = (char *)NULL;
555 /* Enable autocd and auto-open (in case they are not already
556 * enabled) to allow TAB completion for ELN's */
557 int autocd_status = autocd, auto_open_status = auto_open;
558 autocd = auto_open = 1;
559
560 while (!new_path) {
561 new_path = rl_no_hist(_("New path ('q' to quit): "));
562 if (!new_path)
563 continue;
564 if (!*new_path) {
565 free(new_path);
566 new_path = (char *)NULL;
567 continue;
568 }
569
570 if (*new_path == 'q' && !new_path[1]) {
571 free(new_path);
572 return EXIT_SUCCESS;
573 }
574 }
575
576 /* Set autocd and auto-open to their original values */
577 autocd = autocd_status;
578 auto_open = auto_open_status;
579
580 /* If an ELN, replace by the corresponding file name */
581 if (is_number(new_path)) {
582 int i_new_path = atoi(new_path) - 1;
583 if (file_info[i_new_path].name) {
584 new_path = (char *)xrealloc(new_path,
585 (strlen(file_info[i_new_path].name) + 1) * sizeof(char));
586 strcpy(new_path, file_info[i_new_path].name);
587 }
588 }
589
590 /* Remove terminating space. TAB completion puts a final space
591 * after file names */
592 size_t path_len = strlen(new_path);
593 if (new_path[path_len - 1] == ' ')
594 new_path[path_len - 1] = '\0';
595
596 /* Dequote new path, if needed */
597 if (strchr(new_path, '\\')) {
598 char *tmp = dequote_str(new_path, 0);
599 if (!tmp) {
600 fprintf(stderr, _("%s: %s: Error dequoting file\n"),
601 PROGRAM_NAME, new_path);
602 free(new_path);
603 return EXIT_FAILURE;
604 }
605
606 strcpy(new_path, tmp);
607 free(tmp);
608 }
609
610 /* Check new_path existence and warn the user if it does not
611 * exist */
612 if (lstat(new_path, &file_attrib) == -1) {
613 printf("'%s': %s\n", new_path, strerror(errno));
614 char *answer = (char *)NULL;
615 while (!answer) {
616 answer = rl_no_hist(_("Relink as a broken symbolic link? [y/n] "));
617 if (!answer)
618 continue;
619 if (!*answer) {
620 free(answer);
621 answer = (char *)NULL;
622 continue;
623 }
624
625 if (*answer != 'y' && *answer != 'n') {
626 free(answer);
627 answer = (char *)NULL;
628 continue;
629 }
630
631 if (answer[1]) {
632 free(answer);
633 answer = (char *)NULL;
634 continue;
635 }
636
637 if (*answer == 'y') {
638 free(answer);
639 break;
640 } else {
641 free(answer);
642 free(new_path);
643 return EXIT_SUCCESS;
644 }
645 }
646 }
647
648 /* Finally, relink the symlink to new_path */
649 char *cmd[] = {"ln", "-sfn", new_path, link, NULL};
650 if (launch_execve(cmd, FOREGROUND, E_NOFLAG) != EXIT_SUCCESS) {
651 free(new_path);
652 return EXIT_FAILURE;
653 }
654
655 real_path = realpath(link, NULL);
656 printf(_("%s%s%s successfully relinked to "), real_path ? ln_c
657 : or_c, link, df_c);
658 colors_list(new_path, NO_ELN, NO_PAD, PRINT_NEWLINE);
659 free(new_path);
660 if (real_path)
661 free(real_path);
662
663 return EXIT_SUCCESS;
664 }
665
666 int
copy_function(char ** comm)667 copy_function(char **comm)
668 {
669 log_function(NULL);
670
671 if (*comm[0] == 'm' && comm[1]) {
672 size_t len = strlen(comm[1]);
673 if (comm[1][len - 1] == '/')
674 comm[1][len - 1] = '\0';
675 }
676
677 if (!is_sel)
678 return run_and_refresh(comm);
679
680 char *tmp_cmd = (char *)NULL;
681 size_t total_len = 0, i = 0;
682
683 for (i = 0; comm[i]; i++)
684 total_len += strlen(comm[i]);
685
686 tmp_cmd = (char *)xcalloc(total_len + (i + 1) + 2, sizeof(char));
687
688 for (i = 0; comm[i]; i++) {
689 strcat(tmp_cmd, comm[i]);
690 strcat(tmp_cmd, " ");
691 }
692
693 if (sel_is_last)
694 strcat(tmp_cmd, ".");
695
696 int ret = 0;
697 ret = launch_execle(tmp_cmd);
698 free(tmp_cmd);
699
700 if (ret != EXIT_SUCCESS)
701 return EXIT_FAILURE;
702
703 if (copy_n_rename) { /* vv */
704 char **tmp = (char **)xnmalloc(sel_n + 3, sizeof(char *));
705 tmp[0] = savestring("br", 2);
706
707 size_t j;
708 for (j = 0; j < sel_n; j++) {
709 size_t arg_len = strlen(sel_elements[j]);
710
711 if (sel_elements[j][arg_len - 1] == '/')
712 sel_elements[j][arg_len - 1] = '\0';
713
714 if (*comm[args_n] == '~') {
715 char *exp_dest = tilde_expand(comm[args_n]);
716 comm[args_n] = xrealloc(comm[args_n],
717 (strlen(exp_dest) + 1) * sizeof(char));
718 strcpy(comm[args_n], exp_dest);
719 free(exp_dest);
720 }
721
722 size_t dest_len = strlen(comm[args_n]);
723 if (comm[args_n][dest_len - 1] == '/') {
724 comm[args_n][dest_len - 1] = '\0';
725 }
726
727 char dest[PATH_MAX];
728 strcpy(dest, (sel_is_last || strcmp(comm[args_n], ".") == 0)
729 ? ws[cur_ws].path
730 : comm[args_n]);
731
732 char *ret_val = strrchr(sel_elements[j], '/');
733 char *tmp_str = (char *)xnmalloc(strlen(dest)
734 + strlen(ret_val + 1) + 2, sizeof(char));
735
736 sprintf(tmp_str, "%s/%s", dest, ret_val + 1);
737
738 tmp[j + 1] = savestring(tmp_str, strlen(tmp_str));
739 free(tmp_str);
740 }
741
742 tmp[j + 1] = (char *)NULL;
743 bulk_rename(tmp);
744
745 for (i = 0; tmp[i]; i++)
746 free(tmp[i]);
747 free(tmp);
748 copy_n_rename = 0;
749 return EXIT_SUCCESS;
750 }
751
752 /* If 'mv sel' and command is successful deselect everything,
753 * since sel files are note there anymore */
754 if (*comm[0] == 'm' && comm[0][1] == 'v'
755 && (!comm[0][2] || comm[0][2] == ' '))
756 clear_selbox();
757
758 #ifdef __HAIKU__
759 if (autols) {
760 free_dirlist();
761 list_dir();
762 }
763 #endif
764
765 return EXIT_SUCCESS;
766 }
767
768 int
remove_file(char ** args)769 remove_file(char **args)
770 {
771 int cwd = 0, exit_status = EXIT_SUCCESS;
772
773 log_function(NULL);
774
775 char **rm_cmd = (char **)xnmalloc(args_n + 4, sizeof(char *));
776 int i, j = 3, dirs = 0;
777
778 for (i = 1; args[i]; i++) {
779 /* Check if at least one file is in the current directory. If not,
780 * there is no need to refresh the screen */
781 if (!cwd) {
782 char *ret = strchr(args[i], '/');
783 /* If there's no slash, or if slash is the last char and
784 * the file is not root "/", we have a file in CWD */
785 if (!ret || (!*(ret + 1) && ret != args[i]))
786 cwd = 1;
787 }
788
789 char *tmp = (char *)NULL;
790 if (strchr(args[i], '\\')) {
791 tmp = dequote_str(args[i], 0);
792 if (tmp) {
793 /* Start storing file names in 3: 0 is for 'rm', and 1
794 * and 2 for parameters, including end of parameters (--) */
795 rm_cmd[j++] = savestring(tmp, strlen(tmp));
796 free(tmp);
797 } else {
798 fprintf(stderr, "%s: %s: Error dequoting file name\n",
799 PROGRAM_NAME, args[i]);
800 continue;
801 }
802 } else {
803 rm_cmd[j++] = savestring(args[i], strlen(args[i]));
804 }
805
806 struct stat attr;
807 if (!dirs && lstat(rm_cmd[j - 1], &attr) != -1
808 && (attr.st_mode & S_IFMT) == S_IFDIR)
809 dirs = 1;
810 }
811
812 rm_cmd[j] = (char *)NULL;
813
814 rm_cmd[0] = savestring("rm", 2);
815 if (dirs)
816 #if defined(__NetBSD__) || defined(__OpenBSD__)
817 rm_cmd[1] = savestring("-r", 2);
818 #else
819 rm_cmd[1] = savestring("-dIr", 4);
820 #endif
821 else
822 #if defined(__NetBSD__) || defined(__OpenBSD__)
823 rm_cmd[1] = savestring("-f", 2);
824 #else
825 rm_cmd[1] = savestring("-I", 2);
826 #endif
827 rm_cmd[2] = savestring("--", 2);
828
829 if (launch_execve(rm_cmd, FOREGROUND, E_NOFLAG) != EXIT_SUCCESS)
830 exit_status = EXIT_FAILURE;
831 #ifdef __HAIKU__
832 else {
833 if (cwd && autols && strcmp(args[1], "--help") != 0
834 && strcmp(args[1], "--version") != 0) {
835 free_dirlist();
836 exit_status = list_dir();
837 }
838 }
839 #endif
840
841 if (is_sel && exit_status == EXIT_SUCCESS)
842 clear_selbox();
843
844 for (i = 0; rm_cmd[i]; i++)
845 free(rm_cmd[i]);
846 free(rm_cmd);
847 return exit_status;
848 }
849
850 /* Rename a bulk of files (ARGS) at once. Takes files to be renamed
851 * as arguments, and returns zero on success and one on error. The
852 * procedude is quite simple: file names to be renamed are copied into
853 * a temporary file, which is opened via the mime function and shown
854 * to the user to modify it. Once the file names have been modified and
855 * saved, modifications are printed on the screen and the user is
856 * asked whether to perform the actual bulk renaming (via mv) or not.
857 * I took this bulk rename method, just because it is quite simple and
858 * KISS, from the fff filemanager. So, thanks fff! BTW, this method
859 * is also implemented by ranger and nnn */
860 int
bulk_rename(char ** args)861 bulk_rename(char **args)
862 {
863 if (!args[1])
864 return EXIT_FAILURE;
865
866 log_function(NULL);
867
868 int exit_status = EXIT_SUCCESS;
869
870 char bulk_file[PATH_MAX];
871 if (xargs.stealth_mode == 1)
872 snprintf(bulk_file, PATH_MAX - 1, "%s/%s", P_tmpdir, TMP_FILENAME);
873 else
874 snprintf(bulk_file, PATH_MAX - 1, "%s/%s", tmp_dir, TMP_FILENAME);
875
876 int fd = mkstemp(bulk_file);
877 if (fd == -1) {
878 _err('e', PRINT_PROMPT, "bulk: %s: %s\n", bulk_file, strerror(errno));
879 return EXIT_FAILURE;
880 }
881
882 size_t i, arg_total = 0;
883 FILE *fp = (FILE *)NULL;
884
885 #ifdef __HAIKU__
886 fp = fopen(bulk_file, "w");
887 if (!fp) {
888 _err('e', PRINT_PROMPT, "bulk: %s: %s\n", bulk_file, strerror(errno));
889 return EXIT_FAILURE;
890 }
891 #endif
892
893 /* Copy all files to be renamed to the bulk file */
894 for (i = 1; args[i]; i++) {
895 /* Dequote file name, if necessary */
896 if (strchr(args[i], '\\')) {
897 char *deq_file = dequote_str(args[i], 0);
898 if (!deq_file) {
899 fprintf(stderr, _("bulk: %s: Error dequoting "
900 "file name\n"), args[i]);
901 continue;
902 }
903 strcpy(args[i], deq_file);
904 free(deq_file);
905 }
906 #ifndef __HAIKU__
907 dprintf(fd, "%s\n", args[i]);
908 #else
909 fprintf(fp, "%s\n", args[i]);
910 #endif
911 }
912 #ifdef __HAIKU__
913 fclose(fp);
914 #endif
915 arg_total = i;
916 close(fd);
917
918 fp = open_fstream_r(bulk_file, &fd);
919 if (!fp) {
920 _err('e', PRINT_PROMPT, "bulk: '%s': %s\n", bulk_file, strerror(errno));
921 return EXIT_FAILURE;
922 }
923
924 /* Store the last modification time of the bulk file. This time
925 * will be later compared to the modification time of the same
926 * file after shown to the user */
927 struct stat attr;
928 fstat(fd, &attr);
929 time_t mtime_bfr = (time_t)attr.st_mtime;
930
931 /* Open the bulk file */
932 open_in_foreground = 1;
933 exit_status = open_file(bulk_file);
934 open_in_foreground = 0;
935 if (exit_status != EXIT_SUCCESS) {
936 fprintf(stderr, _("bulk: %s\n"), strerror(errno));
937 if (unlinkat(fd, bulk_file, 0) == -1) {
938 _err('e', PRINT_PROMPT, "%s: '%s': %s\n", PROGRAM_NAME,
939 bulk_file, strerror(errno));
940 }
941 close_fstream(fp, fd);
942 return EXIT_FAILURE;
943 }
944
945 close_fstream(fp, fd);
946 fp = open_fstream_r(bulk_file, &fd);
947 if (!fp) {
948 _err('e', PRINT_PROMPT, "bulk: '%s': %s\n", bulk_file, strerror(errno));
949 return EXIT_FAILURE;
950 }
951
952 /* Compare the new modification time to the stored one: if they
953 * match, nothing was modified */
954 fstat(fd, &attr);
955 if (mtime_bfr == (time_t)attr.st_mtime) {
956 puts(_("bulk: Nothing to do"));
957 if (unlinkat(fd, bulk_file, 0) == -1) {
958 _err('e', PRINT_PROMPT, "%s: '%s': %s\n", PROGRAM_NAME,
959 bulk_file, strerror(errno));
960 exit_status = EXIT_FAILURE;
961 }
962 close_fstream(fp, fd);
963 return exit_status;
964 }
965
966 /* Make sure there are as many lines in the bulk file as files
967 * to be renamed */
968 size_t file_total = 1;
969 char tmp_line[256];
970 while (fgets(tmp_line, (int)sizeof(tmp_line), fp))
971 file_total++;
972
973 if (arg_total != file_total) {
974 fputs(_("bulk: Line mismatch in rename file\n"), stderr);
975 if (unlinkat(fd, bulk_file, 0) == -1)
976 _err('e', PRINT_PROMPT, "%s: '%s': %s\n", PROGRAM_NAME,
977 bulk_file, strerror(errno));
978 close_fstream(fp, fd);
979 return EXIT_FAILURE;
980 }
981
982 /* Go back to the beginning of the bulk file, again */
983 fseek(fp, 0L, SEEK_SET);
984
985 size_t line_size = 0;
986 char *line = (char *)NULL;
987 ssize_t line_len = 0;
988 int modified = 0;
989
990 i = 1;
991 /* Print what would be done */
992 while ((line_len = getline(&line, &line_size, fp)) > 0) {
993 if (line[line_len - 1] == '\n')
994 line[line_len - 1] = '\0';
995
996 if (args[i] && strcmp(args[i], line) != 0) {
997 printf("%s %s->%s %s\n", args[i], mi_c, df_c, line);
998 modified++;
999 }
1000
1001 i++;
1002 }
1003
1004 /* If no file name was modified */
1005 if (!modified) {
1006 puts(_("bulk: Nothing to do"));
1007 if (unlinkat(fd, bulk_file, 0) == -1) {
1008 _err('e', PRINT_PROMPT, "%s: '%s': %s\n", PROGRAM_NAME,
1009 bulk_file, strerror(errno));
1010 exit_status = EXIT_FAILURE;
1011 }
1012 free(line);
1013 close_fstream(fp, fd);
1014 return exit_status;
1015 }
1016
1017 /* Ask the user for confirmation */
1018 char *answer = (char *)NULL;
1019 while (!answer) {
1020 answer = rl_no_hist(_("Continue? [y/N] "));
1021 if (answer && *answer && strlen(answer) > 1) {
1022 free(answer);
1023 answer = (char *)NULL;
1024 continue;
1025 }
1026
1027 if (!answer) {
1028 free(line);
1029 close_fstream(fp, fd);
1030 return EXIT_SUCCESS;
1031 }
1032
1033 switch (*answer) {
1034 case 'y': /* fallthrough */
1035 case 'Y': break;
1036
1037 case 'n': /* fallthrough */
1038 case 'N': /* fallthrough */
1039 case '\0':
1040 free(answer);
1041 free(line);
1042 close_fstream(fp, fd);
1043 return EXIT_SUCCESS;
1044
1045 default:
1046 free(answer);
1047 answer = (char *)NULL;
1048 break;
1049 }
1050 }
1051
1052 free(answer);
1053
1054 /* Once again */
1055 fseek(fp, 0L, SEEK_SET);
1056
1057 i = 1;
1058
1059 /* Rename each file */
1060 while ((line_len = getline(&line, &line_size, fp)) > 0) {
1061 if (!args[i]) {
1062 i++;
1063 continue;
1064 }
1065
1066 if (line[line_len - 1] == '\n')
1067 line[line_len - 1] = '\0';
1068 if (args[i] && strcmp(args[i], line) != 0) {
1069 if (renameat(AT_FDCWD, args[i], AT_FDCWD, line) == -1)
1070 exit_status = EXIT_FAILURE;
1071 }
1072
1073 i++;
1074 }
1075
1076 free(line);
1077
1078 if (unlinkat(fd, bulk_file, 0) == -1) {
1079 _err('e', PRINT_PROMPT, "%s: '%s': %s\n", PROGRAM_NAME,
1080 bulk_file, strerror(errno));
1081 exit_status = EXIT_FAILURE;
1082 }
1083 close_fstream(fp, fd);
1084
1085 #ifdef __HAIKU__
1086 if (autols) {
1087 free_dirlist();
1088 if (list_dir() != EXIT_SUCCESS)
1089 exit_status = EXIT_FAILURE;
1090 }
1091 #endif
1092
1093 return exit_status;
1094 }
1095
1096 /* Export files in CWD (if FILENAMES is NULL), or files in FILENAMES,
1097 * into a temporary file. Return the address of this empt file if
1098 * success (it must be freed) or NULL in case of error */
export(char ** filenames,int open)1099 char *export(char **filenames, int open)
1100 {
1101 char *tmp_file = (char *)xnmalloc(strlen(tmp_dir) + 14, sizeof(char));
1102 sprintf(tmp_file, "%s/%s", tmp_dir, TMP_FILENAME);
1103
1104 int fd = mkstemp(tmp_file);
1105 if (fd == -1) {
1106 fprintf(stderr, "%s: %s: %s\n", PROGRAM_NAME, tmp_file, strerror(errno));
1107 free(tmp_file);
1108 return (char *)NULL;
1109 }
1110
1111 size_t i;
1112 #ifdef __HAIKU__
1113 FILE *fp = fopen(tmp_file, "w");
1114 if (!fp) {
1115 fprintf(stderr, "%s: %s: %s\n", PROGRAM_NAME, tmp_file, strerror(errno));
1116 free(tmp_file);
1117 return (char *)NULL;
1118 }
1119 #endif
1120
1121 /* If no argument, export files in CWD */
1122 if (!filenames[1]) {
1123 for (i = 0; file_info[i].name; i++)
1124 #ifndef __HAIKU__
1125 dprintf(fd, "%s\n", file_info[i].name);
1126 #else
1127 fprintf(fp, "%s\n", file_info[i].name);
1128 #endif
1129 } else {
1130 for (i = 1; filenames[i]; i++) {
1131 if (*filenames[i] == '.' && (!filenames[i][1] || (filenames[i][1] == '.' && !filenames[i][2])))
1132 continue;
1133 #ifndef __HAIKU__
1134 dprintf(fd, "%s\n", filenames[i]);
1135 #else
1136 fprintf(fp, "%s\n", filenames[i]);
1137 #endif
1138 }
1139 }
1140 #ifdef __HAIKU__
1141 fclose(fp);
1142 #endif
1143 close(fd);
1144
1145 if (!open)
1146 return tmp_file;
1147
1148 int ret = open_file(tmp_file);
1149 if (ret == EXIT_SUCCESS) {
1150 return tmp_file;
1151 } else {
1152 free(tmp_file);
1153 return (char *)NULL;
1154 }
1155 }
1156
1157 int
batch_link(char ** args)1158 batch_link(char **args)
1159 {
1160 if (!args)
1161 return EXIT_FAILURE;
1162
1163 if (!args[1] || (*args[1] == '-' && strcmp(args[1], "--help") == 0)) {
1164 puts(_(BL_USAGE));
1165 return EXIT_SUCCESS;
1166 }
1167
1168 log_function(NULL);
1169
1170 char *suffix = (char *)NULL;
1171 while (!suffix) {
1172 suffix = rl_no_hist(_("Enter links suffix ('n' for none): "));
1173 if (!suffix)
1174 continue;
1175 if (!*suffix) {
1176 free(suffix);
1177 suffix = (char *)NULL;
1178 continue;
1179 }
1180 }
1181
1182 size_t i;
1183 int exit_status = EXIT_SUCCESS;
1184 char tmp[NAME_MAX];
1185
1186 for (i = 1; args[i]; i++) {
1187 char *linkname = (char *)NULL;
1188
1189 if (*suffix == 'n' && !suffix[1]) {
1190 linkname = args[i];
1191 } else {
1192 snprintf(tmp, NAME_MAX, "%s%s", args[i], suffix);
1193 linkname = tmp;
1194 }
1195
1196 char *ptr = strrchr(linkname, '/');
1197 if (symlinkat(args[i], AT_FDCWD, ptr ? ++ptr : linkname) == -1) {
1198 exit_status = EXIT_FAILURE;
1199 fprintf(stderr, _("%s: %s: Cannot create symlink: %s\n"),
1200 PROGRAM_NAME, ptr ? ptr : linkname, strerror(errno));
1201 }
1202 }
1203
1204 #ifdef __HAIKU__
1205 if (exit_status == EXIT_SUCCESS && autols) {
1206 free_dirlist();
1207
1208 if (list_dir() != EXIT_SUCCESS)
1209 exit_status = EXIT_FAILURE;
1210 }
1211 #endif
1212
1213 free(suffix);
1214 return exit_status;
1215 }
1216