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