1 /* vifm
2 * Copyright (C) 2001 Ken Steen.
3 * Copyright (C) 2011 xaizek.
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
18 */
19
20 #include "fuse.h"
21
22 #include <curses.h> /* werase() */
23
24 #include <sys/types.h> /* pid_t ssize_t */
25 #include <sys/stat.h> /* S_IRWXU */
26 #ifndef _WIN32
27 #include <sys/wait.h> /* WEXITSTATUS() WIFEXITED() waitpid() */
28 #endif
29 #include <unistd.h> /* execve() fork() rmdir() unlink() */
30
31 #include <errno.h> /* errno ENOTDIR */
32 #include <stddef.h> /* NULL size_t */
33 #include <stdio.h> /* snprintf() fclose() */
34 #include <stdlib.h> /* EXIT_SUCCESS free() malloc() */
35 #include <string.h> /* memmove() strcpy() strlen() strcmp() strcat() */
36
37 #include "../cfg/config.h"
38 #include "../compat/fs_limits.h"
39 #include "../compat/os.h"
40 #include "../menus/menus.h"
41 #include "../modes/dialogs/msg_dialog.h"
42 #include "../ui/cancellation.h"
43 #include "../ui/statusbar.h"
44 #include "../ui/ui.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/test_helpers.h"
52 #include "../utils/utils.h"
53 #include "../background.h"
54 #include "../filelist.h"
55 #include "../flist_pos.h"
56 #include "../status.h"
57
58 /* Description of existing FUSE mounts. */
59 typedef struct fuse_mount_t
60 {
61 char source_file_path[PATH_MAX + 1]; /* Full path to source file. */
62 char source_file_dir[PATH_MAX + 1]; /* Full path to dir of source file. */
63 char mount_point[PATH_MAX + 1]; /* Full path to mount point. */
64 int mount_point_id; /* ID of mounts for unique dirs. */
65 int needs_unmounting; /* Whether unmount call is required. */
66 struct fuse_mount_t *next; /* Pointer to the next mount in chain. */
67 }
68 fuse_mount_t;
69
70 static int fuse_mount(view_t *view, char file_full_path[], const char param[],
71 const char program[], char mount_point[]);
72 static int get_last_mount_point_id(const fuse_mount_t *mounts);
73 static void register_mount(fuse_mount_t **mounts, const char file_full_path[],
74 const char mount_point[], int id, int needs_unmounting);
75 TSTATIC void format_mount_command(const char mount_point[],
76 const char file_name[], const char param[], const char format[],
77 size_t buf_size, char buf[], int *foreground);
78 static fuse_mount_t * get_mount_by_source(const char source[]);
79 static fuse_mount_t * get_mount_by_mount_point(const char dir[]);
80 static fuse_mount_t * get_mount_by_path(const char path[]);
81 static int run_fuse_command(char cmd[], const cancellation_t *cancellation,
82 int *cancelled);
83 static void kill_mount_point(const char mount_point[]);
84 static void updir_from_mount(view_t *view, fuse_mount_t *runner);
85
86 /* List of active mounts. */
87 static fuse_mount_t *fuse_mounts;
88
89 void
fuse_try_mount(view_t * view,const char program[])90 fuse_try_mount(view_t *view, const char program[])
91 {
92 /* TODO: refactor this function fuse_try_mount() */
93
94 fuse_mount_t *runner;
95 char file_full_path[PATH_MAX + 1];
96 char mount_point[PATH_MAX + 1];
97
98 if(make_path(cfg.fuse_home, S_IRWXU) != 0)
99 {
100 show_error_msg("Unable to create FUSE mount home directory",
101 cfg.fuse_home);
102 return;
103 }
104
105 get_current_full_path(view, sizeof(file_full_path), file_full_path);
106
107 /* Check if already mounted. */
108 runner = get_mount_by_source(file_full_path);
109
110 if(runner != NULL)
111 {
112 strcpy(mount_point, runner->mount_point);
113 }
114 else
115 {
116 char param[PATH_MAX + 1];
117 param[0] = '\0';
118
119 /* New file to be mounted. */
120 if(starts_with(program, "FUSE_MOUNT2|"))
121 {
122 FILE *f;
123 if((f = os_fopen(file_full_path, "r")) == NULL)
124 {
125 show_error_msg("SSH mount failed", "Can't open file for reading");
126 curr_stats.save_msg = 1;
127 return;
128 }
129
130 if(fgets(param, sizeof(param), f) == NULL)
131 {
132 show_error_msg("SSH mount failed", "Can't read file content");
133 curr_stats.save_msg = 1;
134 fclose(f);
135 return;
136 }
137 fclose(f);
138
139 chomp(param);
140 if(param[0] == '\0')
141 {
142 show_error_msg("SSH mount failed", "File is empty");
143 curr_stats.save_msg = 1;
144 return;
145 }
146 }
147
148 if(fuse_mount(view, file_full_path, param, program, mount_point) != 0)
149 {
150 return;
151 }
152 }
153
154 navigate_to(view, mount_point);
155 }
156
157 /* Searchers for mount record by source file path. */
158 static fuse_mount_t *
get_mount_by_source(const char source[])159 get_mount_by_source(const char source[])
160 {
161 fuse_mount_t *runner = fuse_mounts;
162 while(runner != NULL)
163 {
164 if(paths_are_equal(runner->source_file_path, source))
165 break;
166 runner = runner->next;
167 }
168 return runner;
169 }
170
171 /* mount_point should be an array of at least PATH_MAX characters
172 * Returns non-zero on error. */
173 static int
fuse_mount(view_t * view,char file_full_path[],const char param[],const char program[],char mount_point[])174 fuse_mount(view_t *view, char file_full_path[], const char param[],
175 const char program[], char mount_point[])
176 {
177 /* TODO: refactor this function fuse_mount(). */
178
179 int id;
180 int mount_point_id;
181 char buf[2*PATH_MAX];
182 int foreground;
183 char errors_file[PATH_MAX + 1];
184 int status;
185 int cancelled;
186
187 id = get_last_mount_point_id(fuse_mounts);
188 mount_point_id = id;
189 do
190 {
191 snprintf(mount_point, PATH_MAX, "%s/%03d_%s", cfg.fuse_home,
192 ++mount_point_id, get_current_file_name(view));
193
194 /* Make sure this is not an infinite loop, although practically this
195 * condition will always be false. */
196 if(mount_point_id == id)
197 {
198 show_error_msg("Unable to create FUSE mount directory", mount_point);
199 return -1;
200 }
201
202 errno = 0;
203 }
204 while(os_mkdir(mount_point, S_IRWXU) != 0 && errno == EEXIST);
205
206 if(errno != 0)
207 {
208 show_error_msg("Unable to create FUSE mount directory", mount_point);
209 return -1;
210 }
211
212 /* Just before running the mount,
213 I need to chdir out temporarily from any FUSE mounted
214 paths, Otherwise the fuse-zip command fails with
215 "fusermount: failed to open current directory: permission denied"
216 (this happens when mounting JARs from mounted JARs) */
217 if(vifm_chdir(cfg.fuse_home) != 0)
218 {
219 show_error_msg("FUSE MOUNT ERROR", "Can't chdir() to FUSE home");
220 return -1;
221 }
222
223 format_mount_command(mount_point, file_full_path, param, program, sizeof(buf),
224 buf, &foreground);
225
226 ui_sb_msg("FUSE mounting selected file, please stand by..");
227
228 if(foreground)
229 {
230 ui_shutdown();
231 }
232
233 generate_tmp_file_name("vifm.errors", errors_file, sizeof(errors_file));
234
235 strcat(buf, " 2> ");
236 strcat(buf, errors_file);
237 LOG_INFO_MSG("FUSE mount command: `%s`", buf);
238 if(foreground)
239 {
240 cancelled = 0;
241 status = run_fuse_command(buf, &no_cancellation, NULL);
242 }
243 else
244 {
245 ui_cancellation_push_on();
246 status = run_fuse_command(buf, &ui_cancellation_info, &cancelled);
247 ui_cancellation_pop();
248 }
249
250 ui_sb_clear();
251
252 /* Check child process exit status. */
253 if(!WIFEXITED(status) || WEXITSTATUS(status) != EXIT_SUCCESS)
254 {
255 FILE *ef;
256
257 if(!WIFEXITED(status))
258 {
259 LOG_ERROR_MSG("FUSE mounter didn't exit!");
260 }
261 else
262 {
263 LOG_ERROR_MSG("FUSE mount command exit status: %d", WEXITSTATUS(status));
264 }
265
266 ef = os_fopen(errors_file, "r");
267 if(ef == NULL)
268 {
269 LOG_SERROR_MSG(errno, "Failed to open temporary stderr file: %s",
270 errors_file);
271 }
272 show_errors_from_file(ef, "FUSE mounter error");
273
274 werase(status_bar);
275
276 if(cancelled)
277 {
278 ui_sb_msg("FUSE mount cancelled");
279 curr_stats.save_msg = 1;
280 }
281 else
282 {
283 show_error_msg("FUSE MOUNT ERROR", file_full_path);
284 }
285
286 if(unlink(errors_file) != 0)
287 {
288 LOG_SERROR_MSG(errno, "Error file deletion failure: %s", errors_file);
289 }
290
291 /* Remove the directory we created for the mount. */
292 kill_mount_point(mount_point);
293
294 (void)vifm_chdir(flist_get_dir(view));
295 return -1;
296 }
297 unlink(errors_file);
298 ui_sb_msg("FUSE mount success");
299
300 register_mount(&fuse_mounts, file_full_path, mount_point, mount_point_id,
301 !starts_with(program, "FUSE_MOUNT3|"));
302
303 return 0;
304 }
305
306 /* Gets last mount point id used. Returns the id or 0 if list of mounts is
307 * empty. */
308 static int
get_last_mount_point_id(const fuse_mount_t * mounts)309 get_last_mount_point_id(const fuse_mount_t *mounts)
310 {
311 /* As new entries are added at the front, first entry must have the largest
312 * value of the id. */
313 return (mounts == NULL) ? 0 : mounts->mount_point_id;
314 }
315
316 /* Adds new entry to the list of *mounts. */
317 static void
register_mount(fuse_mount_t ** mounts,const char file_full_path[],const char mount_point[],int id,int needs_unmounting)318 register_mount(fuse_mount_t **mounts, const char file_full_path[],
319 const char mount_point[], int id, int needs_unmounting)
320 {
321 fuse_mount_t *fuse_mount = malloc(sizeof(*fuse_mount));
322
323 copy_str(fuse_mount->source_file_path, sizeof(fuse_mount->source_file_path),
324 file_full_path);
325
326 copy_str(fuse_mount->source_file_dir, sizeof(fuse_mount->source_file_dir),
327 file_full_path);
328 remove_last_path_component(fuse_mount->source_file_dir);
329
330 canonicalize_path(mount_point, fuse_mount->mount_point,
331 sizeof(fuse_mount->mount_point));
332
333 fuse_mount->mount_point_id = id;
334 fuse_mount->needs_unmounting = needs_unmounting;
335
336 fuse_mount->next = *mounts;
337 *mounts = fuse_mount;
338 }
339
340 /* Builds the mount command based on the file type program.
341 * Accepted formats are:
342 * FUSE_MOUNT|some_mount_command %SOURCE_FILE %DESTINATION_DIR [%FOREGROUND]
343 * FUSE_MOUNT2|some_mount_command %PARAM %DESTINATION_DIR [%FOREGROUND]
344 * FUSE_MOUNT3|some_mount_command %SOURCE_FILE %DESTINATION_DIR [%FOREGROUND]
345 * %CLEAR is an obsolete name of %FOREGROUND.
346 * Always sets value of *foreground. */
347 TSTATIC void
format_mount_command(const char mount_point[],const char file_name[],const char param[],const char format[],size_t buf_size,char buf[],int * foreground)348 format_mount_command(const char mount_point[], const char file_name[],
349 const char param[], const char format[], size_t buf_size, char buf[],
350 int *foreground)
351 {
352 char *buf_pos;
353 const char *prog_pos;
354 char *escaped_path;
355 char *escaped_mount_point;
356
357 *foreground = 0;
358
359 escaped_path = shell_like_escape(file_name, 0);
360 escaped_mount_point = shell_like_escape(mount_point, 0);
361
362 buf_pos = buf;
363 buf_pos[0] = '\0';
364
365 prog_pos = after_first(format, '|');
366 while(*prog_pos != '\0')
367 {
368 if(*prog_pos == '%')
369 {
370 char cmd_buf[96];
371 char *cmd_pos;
372
373 cmd_pos = cmd_buf;
374 while(*prog_pos != '\0' && *prog_pos != ' ')
375 {
376 *cmd_pos = *prog_pos;
377 if((size_t)(cmd_pos - cmd_buf) < sizeof(cmd_buf))
378 {
379 ++cmd_pos;
380 }
381 ++prog_pos;
382 }
383 *cmd_pos = '\0';
384
385 if(!strcmp(cmd_buf, "%SOURCE_FILE"))
386 {
387 copy_str(buf_pos, buf_size - (buf_pos - buf), escaped_path);
388 buf_pos += strlen(buf_pos);
389 }
390 else if(!strcmp(cmd_buf, "%PARAM"))
391 {
392 copy_str(buf_pos, buf_size - (buf_pos - buf), param);
393 buf_pos += strlen(buf_pos);
394 }
395 else if(!strcmp(cmd_buf, "%DESTINATION_DIR"))
396 {
397 copy_str(buf_pos, buf_size - (buf_pos - buf), escaped_mount_point);
398 buf_pos += strlen(buf_pos);
399 }
400 else if(!strcmp(cmd_buf, "%FOREGROUND") || !strcmp(cmd_buf, "%CLEAR"))
401 {
402 *foreground = 1;
403 }
404 }
405 else
406 {
407 *buf_pos = *prog_pos;
408 if((size_t)(buf_pos - buf) < buf_size - 1)
409 {
410 ++buf_pos;
411 }
412 ++prog_pos;
413 }
414 }
415
416 *buf_pos = '\0';
417 free(escaped_mount_point);
418 free(escaped_path);
419 }
420
421 void
fuse_unmount_all(void)422 fuse_unmount_all(void)
423 {
424 if(fuse_mounts == NULL)
425 {
426 return;
427 }
428
429 if(vifm_chdir("/") != 0)
430 {
431 return;
432 }
433
434 fuse_mount_t *runner = fuse_mounts;
435 fuse_mounts = NULL;
436
437 while(runner != NULL)
438 {
439 if(runner->needs_unmounting)
440 {
441 char *escaped_filename = shell_like_escape(runner->mount_point, 0);
442 char buf[14 + PATH_MAX + 1];
443 snprintf(buf, sizeof(buf), "%s %s", curr_stats.fuse_umount_cmd,
444 escaped_filename);
445 free(escaped_filename);
446
447 (void)vifm_system(buf, SHELL_BY_APP);
448 }
449
450 kill_mount_point(runner->mount_point);
451
452 fuse_mount_t *next = runner->next;
453 free(runner);
454 runner = next;
455 }
456
457 leave_invalid_dir(&lwin);
458 leave_invalid_dir(&rwin);
459 }
460
461 int
fuse_try_updir_from_a_mount(const char path[],view_t * view)462 fuse_try_updir_from_a_mount(const char path[], view_t *view)
463 {
464 fuse_mount_t *const mount = get_mount_by_mount_point(path);
465 if(mount == NULL)
466 {
467 return 0;
468 }
469
470 updir_from_mount(view, mount);
471 return 1;
472 }
473
474 int
fuse_is_mount_point(const char path[])475 fuse_is_mount_point(const char path[])
476 {
477 return get_mount_by_mount_point(path) != NULL;
478 }
479
480 /* Searches for mount record by path to mount point. Returns mount point or
481 * NULL on failure. */
482 static fuse_mount_t *
get_mount_by_mount_point(const char dir[])483 get_mount_by_mount_point(const char dir[])
484 {
485 fuse_mount_t *runner = fuse_mounts;
486 while(runner != NULL)
487 {
488 if(paths_are_equal(runner->mount_point, dir))
489 {
490 return runner;
491 }
492 runner = runner->next;
493 }
494 return NULL;
495 }
496
497 const char *
fuse_get_mount_file(const char path[])498 fuse_get_mount_file(const char path[])
499 {
500 const fuse_mount_t *const mount = get_mount_by_path(path);
501 return (mount == NULL) ? NULL : mount->source_file_path;
502 }
503
504 /* Searches for mount record by path inside one of mount points. Picks the
505 * longest match so that even nested mount points work. Returns mount point or
506 * NULL on failure. */
507 static fuse_mount_t *
get_mount_by_path(const char path[])508 get_mount_by_path(const char path[])
509 {
510 size_t max_len = 0U;
511 fuse_mount_t *mount = NULL;
512 fuse_mount_t *runner = fuse_mounts;
513 while(runner != NULL)
514 {
515 if(path_starts_with(path, runner->mount_point))
516 {
517 const size_t len = strlen(runner->mount_point);
518 if(len > max_len)
519 {
520 max_len = len;
521 mount = runner;
522 }
523 }
524 runner = runner->next;
525 }
526 return mount;
527 }
528
529 int
fuse_try_unmount(view_t * view)530 fuse_try_unmount(view_t *view)
531 {
532 fuse_mount_t *runner = fuse_mounts;
533 fuse_mount_t *trailer = NULL;
534 while(runner)
535 {
536 if(paths_are_equal(runner->mount_point, view->curr_dir))
537 {
538 break;
539 }
540
541 trailer = runner;
542 runner = runner->next;
543 }
544
545 if(runner == NULL)
546 {
547 return 0;
548 }
549
550 /* We are exiting a top level dir. */
551
552 if(runner->needs_unmounting)
553 {
554 char *escaped_mount_point = shell_like_escape(runner->mount_point, 0);
555
556 char buf[14 + PATH_MAX + 1];
557 snprintf(buf, sizeof(buf), "%s %s 2> /dev/null", curr_stats.fuse_umount_cmd,
558 escaped_mount_point);
559 LOG_INFO_MSG("FUSE unmount command: `%s`", buf);
560 free(escaped_mount_point);
561
562 /* Have to chdir to parent temporarily, so that this DIR can be
563 * unmounted. */
564 if(vifm_chdir(cfg.fuse_home) != 0)
565 {
566 show_error_msg("FUSE UMOUNT ERROR", "Can't chdir to FUSE home");
567 return -1;
568 }
569
570 ui_sb_msg("FUSE unmounting selected file, please stand by..");
571 int status = run_fuse_command(buf, &no_cancellation, NULL);
572 ui_sb_clear();
573 /* Check child status. */
574 if(!WIFEXITED(status) || WEXITSTATUS(status))
575 {
576 werase(status_bar);
577 show_error_msgf("FUSE UMOUNT ERROR", "Can't unmount %s. It may be busy.",
578 runner->source_file_path);
579 (void)vifm_chdir(flist_get_dir(view));
580 return -1;
581 }
582 }
583
584 /* Remove the directory we created for the mount. */
585 kill_mount_point(runner->mount_point);
586
587 /* Remove mount point from fuse_mount_t. */
588 fuse_mount_t *sniffer = runner->next;
589 if(trailer)
590 trailer->next = sniffer ? sniffer : NULL;
591 else
592 fuse_mounts = sniffer;
593
594 updir_from_mount(view, runner);
595 free(runner);
596 return 1;
597 }
598
599 /* Runs command in background not redirecting its streams. To determine an
600 * error uses exit status only. cancelled can be NULL when operations is not
601 * cancellable. Returns status on success, otherwise -1 is returned. Sets
602 * correct value of *cancelled even on error. */
603 static int
run_fuse_command(char cmd[],const cancellation_t * cancellation,int * cancelled)604 run_fuse_command(char cmd[], const cancellation_t *cancellation, int *cancelled)
605 {
606 #ifndef _WIN32
607 pid_t pid;
608 int status;
609
610 if(cancellation_possible(cancellation))
611 {
612 *cancelled = 0;
613 }
614
615 if(cmd == NULL)
616 {
617 return 1;
618 }
619
620 (void)set_sigchld(1);
621
622 pid = fork();
623 if(pid == (pid_t)-1)
624 {
625 (void)set_sigchld(0);
626 LOG_SERROR_MSG(errno, "Forking has failed.");
627 return -1;
628 }
629
630 if(pid == (pid_t)0)
631 {
632 extern char **environ;
633
634 (void)set_sigchld(0);
635
636 prepare_for_exec();
637 (void)execve(get_execv_path(cfg.shell),
638 make_execv_array(cfg.shell, "-c", cmd), environ);
639 _Exit(127);
640 }
641
642 while(waitpid(pid, &status, 0) == -1)
643 {
644 if(errno != EINTR)
645 {
646 LOG_SERROR_MSG(errno, "Failed waiting for process: %" PRINTF_ULL,
647 (unsigned long long)pid);
648 status = -1;
649 break;
650 }
651 process_cancel_request(pid, cancellation);
652 }
653
654 if(cancellation_requested(cancellation))
655 {
656 *cancelled = 1;
657 }
658
659 (void)set_sigchld(0);
660
661 return status;
662 #else
663 return -1;
664 #endif
665 }
666
667 /* Deletes mount point by its path. */
668 static void
kill_mount_point(const char mount_point[])669 kill_mount_point(const char mount_point[])
670 {
671 /* rmdir() on some systems (FreeBSD at least) can resolve symbolic links if
672 * path ends with a slash and unlink() will also fail if there is a trailing
673 * slash. */
674 char no_slash[strlen(mount_point) + 1];
675 strcpy(no_slash, mount_point);
676 chosp(no_slash);
677
678 if(rmdir(no_slash) != 0 && errno == ENOTDIR)
679 {
680 /* FUSE mounter might replace directory with a symbolic link, account for
681 * this possibility. */
682 (void)unlink(no_slash);
683 }
684 }
685
686 static void
updir_from_mount(view_t * view,fuse_mount_t * runner)687 updir_from_mount(view_t *view, fuse_mount_t *runner)
688 {
689 char *file;
690 int pos;
691
692 if(change_directory(view, runner->source_file_dir) < 0)
693 return;
694
695 load_dir_list(view, 0);
696
697 file = runner->source_file_path;
698 file += strlen(runner->source_file_dir) + 1;
699 pos = fpos_find_by_name(view, file);
700 fpos_set_pos(view, pos);
701 }
702
703 int
fuse_is_mount_string(const char string[])704 fuse_is_mount_string(const char string[])
705 {
706 return starts_with(string, "FUSE_MOUNT|")
707 || starts_with(string, "FUSE_MOUNT2|")
708 || starts_with(string, "FUSE_MOUNT3|");
709 }
710
711 void
fuse_strip_mount_metadata(char string[])712 fuse_strip_mount_metadata(char string[])
713 {
714 size_t prefix_len;
715 if(starts_with(string, "FUSE_MOUNT|"))
716 {
717 prefix_len = ARRAY_LEN("FUSE_MOUNT|") - 1;
718 }
719 else if(starts_with(string, "FUSE_MOUNT2|"))
720 {
721 prefix_len = ARRAY_LEN("FUSE_MOUNT2|") - 1;
722 }
723 else if(starts_with(string, "FUSE_MOUNT3|"))
724 {
725 prefix_len = ARRAY_LEN("FUSE_MOUNT3|") - 1;
726 }
727 else
728 {
729 prefix_len = 0;
730 }
731
732 if(prefix_len != 0)
733 {
734 size_t new_len = strlen(string) - prefix_len;
735 memmove(string, string + prefix_len, new_len + 1);
736 }
737 }
738
739 /* vim: set tabstop=2 softtabstop=2 shiftwidth=2 noexpandtab cinoptions-=(0 : */
740 /* vim: set cinoptions+=t0 filetype=c : */
741