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