1 /* bookmarks.c -- bookmarking functions */
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 
33 #include "aux.h"
34 #include "bookmarks.h"
35 #include "checks.h"
36 #include "exec.h"
37 #include "file_operations.h"
38 #include "init.h"
39 #include "mime.h"
40 #include "readline.h"
41 #include "messages.h"
42 
43 void
free_bookmarks(void)44 free_bookmarks(void)
45 {
46 	if (!bm_n)
47 		return;
48 
49 	size_t i;
50 	for (i = 0; i < bm_n; i++) {
51 		if (bookmarks[i].shortcut)
52 			free(bookmarks[i].shortcut);
53 
54 		if (bookmarks[i].name)
55 			free(bookmarks[i].name);
56 
57 		if (bookmarks[i].path)
58 			free(bookmarks[i].path);
59 	}
60 
61 	free(bookmarks);
62 	bookmarks = (struct bookmarks_t *)NULL;
63 
64 	for (i = 0; bookmark_names[i]; i++)
65 		free(bookmark_names[i]);
66 	free(bookmark_names);
67 	bookmark_names = (char **)NULL;
68 	bm_n = 0;
69 	return;
70 }
71 
72 static char **
bm_prompt(void)73 bm_prompt(void)
74 {
75 	char *bm_sel = (char *)NULL;
76 	printf(_("%s%s\nEnter '%c' to edit your bookmarks or '%c' to quit.\n"),
77 	    NC, df_c, 'e', 'q');
78 
79 	while (!bm_sel)
80 		bm_sel = rl_no_hist(_("Choose a bookmark: "));
81 
82 	char **comm_bm = get_substr(bm_sel, ' ');
83 	free(bm_sel);
84 	return comm_bm;
85 }
86 
87 static void
free_del_elements(char ** elements)88 free_del_elements(char **elements)
89 {
90 	size_t i;
91 	for (i = 0; elements[i]; i++)
92 		free(elements[i]);
93 	free(elements);
94 }
95 
96 static void
free_bms(char ** _bms,const size_t _bmn)97 free_bms(char **_bms, const size_t _bmn)
98 {
99 	size_t i;
100 	for (i = 0; i < _bmn; i++)
101 		free(_bms[i]);
102 	free(_bms);
103 }
104 
105 static int
bookmark_del(char * name)106 bookmark_del(char *name)
107 {
108 	FILE *bm_fp = NULL;
109 	bm_fp = fopen(bm_file, "r");
110 	if (!bm_fp)
111 		return EXIT_FAILURE;
112 
113 	size_t i = 0;
114 
115 	/* Get bookmarks from file */
116 	size_t line_size = 0;
117 	char *line = (char *)NULL, **bms = (char **)NULL;
118 	size_t bmn = 0;
119 	ssize_t line_len = 0;
120 
121 	while ((line_len = getline(&line, &line_size, bm_fp)) > 0) {
122 		if (!line || !*line || *line == '#' || *line == '\n')
123 			continue;
124 
125 		int slash = 0;
126 		for (i = 0; i < (size_t)line_len; i++) {
127 			if (line[i] == '/') {
128 				slash = 1;
129 				break;
130 			}
131 		}
132 
133 		if (!slash)
134 			continue;
135 
136 		if (line[line_len - 1] == '\n')
137 			line[line_len - 1] = '\0';
138 
139 		bms = (char **)xrealloc(bms, (bmn + 1) * sizeof(char *));
140 		bms[bmn++] = savestring(line, (size_t)line_len);
141 	}
142 
143 	free(line);
144 	line = (char *)NULL;
145 
146 	if (!bmn) {
147 		puts(_("bookmarks: There are no bookmarks"));
148 		fclose(bm_fp);
149 		return EXIT_SUCCESS;
150 	}
151 
152 	char **del_elements = (char **)NULL;
153 	int cmd_line = -1;
154 	/* This variable let us know two things: a) bookmark name was
155 	 * specified in command line; b) the index of this name in the
156 	 * bookmarks array. It is initialized as -1 since the index name
157 	 * could be zero */
158 
159 	if (name) {
160 		for (i = 0; i < bmn; i++) {
161 			char *bm_name = strbtw(bms[i], ']', ':');
162 			if (!bm_name)
163 				continue;
164 			if (strcmp(name, bm_name) == 0) {
165 				free(bm_name);
166 				cmd_line = (int)i;
167 				break;
168 			}
169 			free(bm_name);
170 		}
171 	}
172 
173 	/* If a valid bookmark name was passed in command line, copy the
174 	 * corresponding bookmark index (plus 1, as if it were typed in the
175 	 * bookmarks screen) to the del_elements array */
176 	if (cmd_line != -1) {
177 		del_elements = (char **)xnmalloc(2, sizeof(char *));
178 		del_elements[0] = (char *)xnmalloc((size_t)DIGINUM(cmd_line + 1) + 1, sizeof(char));
179 		sprintf(del_elements[0], "%d", cmd_line + 1);
180 		del_elements[1] = (char *)NULL;
181 	}
182 
183 	/* If bookmark name was passed but it is not a valid bookmark */
184 	else if (name) {
185 		fprintf(stderr, _("bookmarks: %s: No such bookmark\n"), name);
186 		free_bms(bms, bmn);
187 		fclose(bm_fp);
188 		return EXIT_FAILURE;
189 	}
190 
191 	/* If not name, list bookmarks and get user input */
192 	else {
193 		printf(_("%sBookmarks%s\n\n"), BOLD, df_c);
194 
195 		for (i = 0; i < bmn; i++)
196 			printf("%s%zu %s%s%s\n", el_c, i + 1, bm_c, bms[i], df_c);
197 
198 		/* Get user input */
199 		printf(_("\n%sEnter '%c' to quit.\n"), df_c, 'q');
200 		char *input = (char *)NULL;
201 		while (!input)
202 			input = rl_no_hist(_("Bookmark(s) to be deleted "
203 								"(ex: 1 2-6, or *): "));
204 		del_elements = get_substr(input, ' ');
205 		free(input);
206 		input = (char *)NULL;
207 
208 		if (!del_elements) {
209 			free_bms(bms, bmn);
210 			fclose(bm_fp);
211 			fprintf(stderr, _("bookmarks: Error parsing input\n"));
212 			return EXIT_FAILURE;
213 		}
214 	}
215 
216 	/* We have input */
217 	/* If quit */
218 	/* I inspect all substrings entered by the user for "q" before any
219 	 * other value to prevent some accidental deletion, like "1 q", or
220 	 * worst, "* q" */
221 	for (i = 0; del_elements[i]; i++) {
222 		int quit = 0;
223 
224 		if (strcmp(del_elements[i], "q") == 0) {
225 			quit = 1;
226 		} else if (is_number(del_elements[i]) && (atoi(del_elements[i]) <= 0
227 		|| atoi(del_elements[i]) > (int)bmn)) {
228 			fprintf(stderr, _("bookmarks: %s: No such bookmark\n"),
229 			    del_elements[i]);
230 			quit = 1;
231 		}
232 
233 		if (quit) {
234 			free_bms(bms, bmn);
235 			free_del_elements(del_elements);
236 			fclose(bm_fp);
237 			return EXIT_SUCCESS;
238 		}
239 	}
240 
241 	/* If "*", simply remove the bookmarks file */
242 	/* If there is some "*" in the input line (like "1 5 6-9 *"), it
243 	 * makes no sense to remove singles bookmarks: Just delete all of
244 	 * them at once */
245 	for (i = 0; del_elements[i]; i++) {
246 		if (strcmp(del_elements[i], "*") == 0) {
247 			/* Create a backup copy of the bookmarks file, just in case */
248 			char *bk_file = (char *)NULL;
249 			bk_file = (char *)xcalloc(strlen(config_dir) + 14,
250 			    sizeof(char));
251 			sprintf(bk_file, "%s/bookmarks.bk", config_dir);
252 			char *tmp_cmd[] = {"cp", bm_file, bk_file, NULL};
253 
254 			int ret = launch_execve(tmp_cmd, FOREGROUND, E_NOFLAG);
255 			/* Remove the bookmarks file, free stuff, and exit */
256 			if (ret == EXIT_SUCCESS) {
257 				unlink(bm_file);
258 				printf(_("bookmarks: All bookmarks were deleted\n "
259 					 "However, a backup copy was created (%s)\n"), bk_file);
260 				free(bk_file);
261 				bk_file = (char *)NULL;
262 			} else {
263 				printf(_("bookmarks: Error creating backup file. No "
264 					 "bookmark was deleted\n"));
265 			}
266 
267 			free_bms(bms, bmn);
268 			free_del_elements(del_elements);
269 			fclose(bm_fp);
270 			/* Update bookmark names for TAB completion */
271 			/*          get_bm_names(); */
272 			free_bookmarks();
273 			load_bookmarks();
274 
275 			/* If the argument "*" was specified in command line */
276 			if (cmd_line != -1)
277 				fputs(_("All bookmarks succesfully removed\n"), stdout);
278 
279 			return EXIT_SUCCESS;
280 		}
281 	}
282 
283 	/* Remove single bookmarks */
284 	/* Open a temporary file */
285 	char *tmp_file = (char *)NULL;
286 	tmp_file = (char *)xnmalloc(strlen(config_dir) + 8, sizeof(char));
287 	sprintf(tmp_file, "%s/bm_tmp", config_dir);
288 
289 	FILE *tmp_fp = fopen(tmp_file, "w+");
290 	if (!tmp_fp) {
291 		free_bms(bms, bmn);
292 		free_del_elements(del_elements);
293 		fclose(bm_fp);
294 		fprintf(stderr, _("bookmarks: Error creating temporary file\n"));
295 
296 		return EXIT_FAILURE;
297 	}
298 
299 	/* Go back to the beginning of the bookmarks file */
300 	fseek(bm_fp, 0, SEEK_SET);
301 
302 	/* Dump into the tmp file everything except bookmarks marked for
303 	 * deletion */
304 
305 	char *lineb = (char *)NULL;
306 	while ((line_len = getline(&lineb, &line_size, bm_fp)) > 0) {
307 		if (lineb[line_len - 1] == '\n')
308 			lineb[line_len - 1] = '\0';
309 
310 		int bm_found = 0;
311 		size_t j;
312 
313 		for (j = 0; del_elements[j]; j++) {
314 			if (!is_number(del_elements[j]))
315 				continue;
316 			if (strcmp(bms[atoi(del_elements[j]) - 1], lineb) == 0)
317 				bm_found = 1;
318 		}
319 
320 		if (bm_found)
321 			continue;
322 
323 		fprintf(tmp_fp, "%s\n", lineb);
324 	}
325 
326 	free(lineb);
327 
328 	/* Free stuff */
329 	free_del_elements(del_elements);
330 	free_bms(bms, bmn);
331 
332 	fclose(bm_fp);
333 	fclose(tmp_fp);
334 
335 	/* Remove the old bookmarks file and make the tmp file the new
336 	 * bookmarks file*/
337 	unlink(bm_file);
338 	rename(tmp_file, bm_file);
339 	free(tmp_file);
340 
341 	/* Update bookmark names for TAB completion */
342 	/*  get_bm_names(); */
343 	free_bookmarks();
344 	load_bookmarks();
345 
346 	/* If the bookmark to be removed was specified in command line */
347 	if (cmd_line != -1)
348 		printf(_("Successfully removed '%s'\n"), name);
349 
350 	return EXIT_SUCCESS;
351 }
352 
353 static int
bookmark_add(char * file)354 bookmark_add(char *file)
355 {
356 	if (!file)
357 		return EXIT_FAILURE;
358 
359 	int mod_file = 0;
360 	/* If not absolute path, prepend current path to file */
361 	if (*file != '/') {
362 		char *tmp_file = (char *)NULL;
363 		tmp_file = (char *)xnmalloc((strlen(ws[cur_ws].path) + strlen(file) + 2), sizeof(char));
364 		sprintf(tmp_file, "%s/%s", ws[cur_ws].path, file);
365 		file = tmp_file;
366 		tmp_file = (char *)NULL;
367 		mod_file = 1;
368 	}
369 
370 	/* Check if FILE is an available path */
371 
372 	FILE *bm_fp = fopen(bm_file, "r");
373 	if (!bm_fp) {
374 		fprintf(stderr, _("bookmarks: Error opening the bookmarks file\n"));
375 		if (mod_file)
376 			free(file);
377 
378 		return EXIT_FAILURE;
379 	}
380 
381 	int dup = 0;
382 	char **bms = (char **)NULL;
383 	size_t line_size = 0, i, bmn = 0;
384 	char *line = (char *)NULL;
385 
386 	while (getline(&line, &line_size, bm_fp) > 0) {
387 		if (!line || !*line || *line == '#' || *line == '\n')
388 			continue;
389 
390 		char *tmp_line = (char *)NULL;
391 		tmp_line = strchr(line, '/');
392 		if (tmp_line) {
393 			size_t tmp_line_len = strlen(tmp_line);
394 
395 			if (tmp_line_len && tmp_line[tmp_line_len - 1] == '\n')
396 				tmp_line[tmp_line_len - 1] = '\0';
397 
398 			if (strcmp(tmp_line, file) == 0) {
399 				fprintf(stderr, _("bookmarks: %s: Path already "
400 						  "bookmarked\n"),
401 				    file);
402 				dup = 1;
403 				break;
404 			}
405 
406 			tmp_line = (char *)NULL;
407 		}
408 
409 		/* Store lines: used later to check hotkeys */
410 		bms = (char **)xrealloc(bms, (bmn + 1) * sizeof(char *));
411 		bms[bmn++] = savestring(line, strlen(line));
412 	}
413 
414 	free(line);
415 	line = (char *)NULL;
416 	fclose(bm_fp);
417 
418 	if (dup) {
419 		for (i = 0; i < bmn; i++)
420 			free(bms[i]);
421 		free(bms);
422 		if (mod_file)
423 			free(file);
424 		return EXIT_FAILURE;
425 	}
426 
427 	/* If path is available */
428 
429 	char *name = (char *)NULL, *hk = (char *)NULL, *tmp = (char *)NULL;
430 
431 	/* Ask for data to construct the bookmark line. Both values could be
432 	 * NULL */
433 	puts(_("Bookmark line example: [sc]name:path"));
434 	hk = rl_no_hist("Shortcut: ");
435 
436 	/* Check if hotkey is available */
437 	if (hk) {
438 		char *tmp_line = (char *)NULL;
439 
440 		for (i = 0; i < bmn; i++) {
441 			tmp_line = strbtw(bms[i], '[', ']');
442 			if (tmp_line) {
443 				if (strcmp(hk, tmp_line) == 0) {
444 					fprintf(stderr, _("bookmarks: %s: This shortcut is "
445 							  "already in use\n"), hk);
446 
447 					dup = 1;
448 					free(tmp_line);
449 					break;
450 				}
451 
452 				free(tmp_line);
453 			}
454 		}
455 	}
456 
457 	if (dup) {
458 		if (hk)
459 			free(hk);
460 		for (i = 0; i < bmn; i++)
461 			free(bms[i]);
462 		free(bms);
463 		if (mod_file)
464 			free(file);
465 		return EXIT_FAILURE;
466 	}
467 
468 	name = rl_no_hist("Name: ");
469 
470 	if (name) {
471 		/* Check name is not duplicated */
472 		char *tmp_line = (char *)NULL;
473 		for (i = 0; i < bmn; i++) {
474 			tmp_line = strbtw(bms[i], ']', ':');
475 			if (tmp_line) {
476 				if (strcmp(name, tmp_line) == 0) {
477 					fprintf(stderr, _("bookmarks: %s: This name is "
478 							  "already in use\n"), name);
479 					dup = 1;
480 					free(tmp_line);
481 					break;
482 				}
483 				free(tmp_line);
484 			}
485 		}
486 
487 		if (dup) {
488 			free(name);
489 			if (hk)
490 				free(hk);
491 			for (i = 0; i < bmn; i++)
492 				free(bms[i]);
493 			free(bms);
494 			if (mod_file)
495 				free(file);
496 			return EXIT_FAILURE;
497 		}
498 
499 		/* Generate the bookmark line */
500 		if (hk) { /* name AND hk */
501 			tmp = (char *)xcalloc(strlen(hk) + strlen(name) + strlen(file) + 5, sizeof(char));
502 			sprintf(tmp, "[%s]%s:%s\n", hk, name, file);
503 			free(hk);
504 		} else { /* Only name */
505 			tmp = (char *)xnmalloc(strlen(name) + strlen(file) + 3,
506 			    sizeof(char));
507 			sprintf(tmp, "%s:%s\n", name, file);
508 		}
509 
510 		free(name);
511 		name = (char *)NULL;
512 	}
513 
514 	else if (hk) { /* Only hk */
515 		tmp = (char *)xnmalloc(strlen(hk) + strlen(file) + 4,
516 		    sizeof(char));
517 		sprintf(tmp, "[%s]%s\n", hk, file);
518 		free(hk);
519 		hk = (char *)NULL;
520 	} else { /* Neither shortcut nor name: only path */
521 		tmp = (char *)xnmalloc(strlen(file) + 2, sizeof(char));
522 		sprintf(tmp, "%s\n", file);
523 	}
524 
525 	for (i = 0; i < bmn; i++)
526 		free(bms[i]);
527 	free(bms);
528 	bms = (char **)NULL;
529 
530 	if (!tmp) {
531 		fprintf(stderr, _("bookmarks: Error generating the bookmark line\n"));
532 		return EXIT_FAILURE;
533 	}
534 
535 	/* Once we have the bookmark line, write it to the bookmarks file */
536 
537 	bm_fp = fopen(bm_file, "a+");
538 	if (!bm_fp) {
539 		fprintf(stderr, _("bookmarks: Error opening the bookmarks file\n"));
540 		free(tmp);
541 		return EXIT_FAILURE;
542 	}
543 
544 	if (mod_file)
545 		free(file);
546 
547 	if (fseek(bm_fp, 0L, SEEK_END) == -1) {
548 		fprintf(stderr, _("bookmarks: Error opening the bookmarks file\n"));
549 		free(tmp);
550 		fclose(bm_fp);
551 		return EXIT_FAILURE;
552 	}
553 
554 	/* Everything is fine: add the new bookmark to the bookmarks file */
555 	fprintf(bm_fp, "%s", tmp);
556 	fclose(bm_fp);
557 	printf(_("File succesfully bookmarked\n"));
558 	free(tmp);
559 	/* Update bookmark names for TAB completion */
560 	/*  get_bm_names();  */
561 	free_bookmarks();
562 	load_bookmarks();
563 	return EXIT_SUCCESS;
564 }
565 
566 int
edit_bookmarks(char * cmd)567 edit_bookmarks(char *cmd)
568 {
569 	int exit_status = EXIT_SUCCESS;
570 
571 	if (!cmd) {
572 		open_in_foreground = 1;
573 		exit_status = open_file(bm_file);
574 		open_in_foreground = 0;
575 	} else {
576 		char *tmp_cmd[] = {cmd, bm_file, NULL};
577 		if (launch_execve(tmp_cmd, FOREGROUND, E_NOSTDERR) != EXIT_SUCCESS)
578 			exit_status = EXIT_FAILURE;
579 	}
580 
581 	if (exit_status == EXIT_FAILURE)
582 		fprintf(stderr, _("%s: Cannot open the bookmarks file"), PROGRAM_NAME);
583 
584 	return exit_status;
585 }
586 
587 int
open_bookmark(void)588 open_bookmark(void)
589 {
590 	/* If no bookmarks */
591 	if (bm_n == 0) {
592 		printf(_("Bookmarks: There are no bookmarks\nEnter 'bm edit' "
593 			 "or press F11 to edit the bookmarks file. You can "
594 			 "also enter 'bm add PATH' to add a new bookmark\n"));
595 		return EXIT_SUCCESS;
596 	}
597 
598 	/* We have bookmarks... */
599 	struct stat file_attrib;
600 
601 	if (clear_screen)
602 		CLEAR;
603 
604 	printf(_("%sBookmarks Manager%s\n\n"), BOLD, df_c);
605 
606 	/* Print bookmarks taking into account the existence of shortcut,
607 	 * name, and path for each bookmark */
608 	size_t i, eln = 0;
609 
610 	for (i = 0; i < bm_n; i++) {
611 		if (!bookmarks[i].path || !*bookmarks[i].path)
612 			continue;
613 		eln++;
614 		int is_dir = 0, sc_ok = 0, name_ok = 0, non_existent = 0;
615 		int path_ok = stat(bookmarks[i].path, &file_attrib);
616 
617 		if (bookmarks[i].shortcut)
618 			sc_ok = 1;
619 
620 		if (bookmarks[i].name)
621 			name_ok = 1;
622 
623 		if (path_ok == -1) {
624 			non_existent = 1;
625 		} else {
626 			switch ((file_attrib.st_mode & S_IFMT)) {
627 			case S_IFDIR: is_dir = 1; break;
628 			case S_IFREG: break;
629 			default: non_existent = 1; break;
630 			}
631 		}
632 
633 		printf("%s%zu%s %s%c%s%c%s %s%s%s\n", el_c, eln, df_c,
634 		    BOLD, sc_ok ? '[' : 0, sc_ok ? bookmarks[i].shortcut : "",
635 		    sc_ok ? ']' : 0, df_c, non_existent ? GRAY : (is_dir ? bm_c : fi_c),
636 		    name_ok ? bookmarks[i].name : bookmarks[i].path, df_c);
637 	}
638 
639 	/* User selection. Display the prompt */
640 	char **arg = bm_prompt();
641 	if (!arg || !*arg)
642 		return EXIT_FAILURE;
643 
644 	int exit_status = EXIT_SUCCESS;
645 
646 	/* Case "edit" */
647 	if (*arg[0] == 'e' && (!arg[0][1] || strcmp(arg[0], "edit") == 0)) {
648 		stat(bm_file, &file_attrib);
649 		time_t mtime_bfr = (time_t)file_attrib.st_mtime;
650 
651 		edit_bookmarks(arg[1] ? arg[1] : NULL);
652 
653 		stat(bm_file, &file_attrib);
654 		if (mtime_bfr != (time_t)file_attrib.st_mtime) {
655 			free_bookmarks();
656 			load_bookmarks();
657 		}
658 
659 		for (i = 0; arg[i]; i++)
660 			free(arg[i]);
661 		free(arg);
662 
663 		arg = (char **)NULL;
664 
665 		char *tmp_cmd[] = {"bm", NULL};
666 		bookmarks_function(tmp_cmd);
667 		return EXIT_SUCCESS;
668 	}
669 
670 	/* Case "quit" */
671 	if (*arg[0] == 'q' && (!arg[0][1] || strcmp(arg[0], "quit") == 0))
672 		goto FREE_AND_EXIT;
673 
674 	char *tmp_path = (char *)NULL;
675 
676 	/* Get the corresponding bookmark path */
677 	/* If an ELN */
678 	if (is_number(arg[0])) {
679 		int num = atoi(arg[0]);
680 		if (num <= 0 || (size_t)num > bm_n) {
681 			fprintf(stderr, _("Bookmarks: %d: No such ELN\n"), num);
682 			exit_status = EXIT_FAILURE;
683 			goto FREE_AND_EXIT;
684 		} else {
685 			tmp_path = bookmarks[num - 1].path;
686 		}
687 	} else {
688 	/* If string, check shortcuts and names */
689 		for (i = 0; i < bm_n; i++) {
690 			if ((bookmarks[i].shortcut && *arg[0] == *bookmarks[i].shortcut
691 			&& strcmp(arg[0], bookmarks[i].shortcut) == 0)
692 			|| (bookmarks[i].name && *arg[0] == *bookmarks[i].name
693 			&& strcmp(arg[0], bookmarks[i].name) == 0)) {
694 
695 				if (bookmarks[i].path) {
696 					char *tmp_cmd[] = {"o", bookmarks[i].path,
697 					    arg[1] ? arg[1] : NULL,
698 					    NULL};
699 
700 					exit_status = open_function(tmp_cmd);
701 					goto FREE_AND_EXIT;
702 				}
703 
704 				fprintf(stderr, _("%s: %s: Invalid bookmark\n"),
705 				    PROGRAM_NAME, arg[0]);
706 				exit_status = EXIT_FAILURE;
707 				goto FREE_AND_EXIT;
708 			}
709 		}
710 	}
711 
712 	if (!tmp_path) {
713 		fprintf(stderr, _("Bookmarks: %s: No such bookmark\n"),
714 		    arg[0]);
715 		exit_status = EXIT_FAILURE;
716 		goto FREE_AND_EXIT;
717 	}
718 
719 	char *tmp_cmd[] = {"o", tmp_path, arg[1] ? arg[1] : NULL, NULL};
720 	exit_status = open_function(tmp_cmd);
721 	goto FREE_AND_EXIT;
722 
723 FREE_AND_EXIT : {
724 	for (i = 0; arg[i]; i++)
725 		free(arg[i]);
726 	free(arg);
727 	arg = (char **)NULL;
728 	return exit_status;
729 }
730 }
731 
732 int
bookmarks_function(char ** cmd)733 bookmarks_function(char **cmd)
734 {
735 	if (xargs.stealth_mode == 1) {
736 		printf(_("%s: Access to configuration files is not allowed in "
737 			 "stealth mode\n"), PROGRAM_NAME);
738 		return EXIT_SUCCESS;
739 	}
740 
741 	if (!config_ok) {
742 		fprintf(stderr, _("Bookmarks function disabled\n"));
743 		return EXIT_FAILURE;
744 	}
745 
746 	/* If the bookmarks file doesn't exist, create it. NOTE: This file
747 	 * should be created at startup (by get_bm_names()), but we check
748 	 * it again here just in case it was meanwhile deleted for some
749 	 * reason */
750 
751 	/* If no arguments */
752 	if (!cmd[1])
753 		return open_bookmark();
754 
755 	/* Check arguments */
756 
757 	/* Add a bookmark */
758 	if (*cmd[1] == 'a' && (!cmd[1][1] || strcmp(cmd[1], "add") == 0)) {
759 		if (!cmd[2]) {
760 			puts(_(BOOKMARKS_USAGE));
761 			return EXIT_SUCCESS;
762 		}
763 
764 		if (access(cmd[2], F_OK) != 0) {
765 			fprintf(stderr, _("Bookmarks: %s: %s\n"), cmd[2],
766 			    strerror(errno));
767 			return EXIT_FAILURE;
768 		}
769 
770 		return bookmark_add(cmd[2]);
771 	}
772 
773 	/* Delete bookmarks */
774 	if (*cmd[1] == 'd' && (!cmd[1][1] || strcmp(cmd[1], "del") == 0))
775 		return bookmark_del(cmd[2] ? cmd[2] : NULL);
776 
777 	/* Edit */
778 	if (*cmd[1] == 'e' && (!cmd[1][1] || strcmp(cmd[1], "edit") == 0))
779 		return edit_bookmarks(cmd[2] ? cmd[2] : NULL);
780 
781 	/* Shortcut, bm name, or (if expand_bookmarks) bm path */
782 	size_t i;
783 	for (i = 0; i < bm_n; i++) {
784 		if ((bookmarks[i].shortcut && *cmd[1] == *bookmarks[i].shortcut
785 			&& strcmp(cmd[1], bookmarks[i].shortcut) == 0)
786 
787 			|| (bookmarks[i].name && *cmd[1] == *bookmarks[i].name
788 			&& strcmp(cmd[1], bookmarks[i].name) == 0)
789 
790 			|| (expand_bookmarks && bookmarks[i].path
791 			&& *cmd[1] == *bookmarks[i].path
792 			&& strcmp(cmd[1], bookmarks[i].path) == 0)) {
793 
794 			if (bookmarks[i].path) {
795 				char *tmp_cmd[] = {"o", bookmarks[i].path,
796 				    cmd[2] ? cmd[2] : NULL, NULL};
797 				return open_function(tmp_cmd);
798 			}
799 
800 			fprintf(stderr, _("Bookmarks: %s: Invalid bookmark\n"),
801 			    cmd[1]);
802 			return EXIT_FAILURE;
803 		}
804 	}
805 
806 	fprintf(stderr, _("Bookmarks: %s: No such bookmark\n"), cmd[1]);
807 	return EXIT_FAILURE;
808 }
809