2 Various utilities - Unix variants
4 Copyright (C) 1994, 1995, 1996, 1998, 1999, 2000, 2001, 2002, 2003,
5 2004, 2005, 2007, 2011, 2012, 2013
6 The Free Software Foundation, Inc.
9 Miguel de Icaza, 1994, 1995, 1996
10 Janne Kukonlehto, 1994, 1995, 1996
11 Dugan Porter, 1994, 1995, 1996
12 Jakub Jelinek, 1994, 1995, 1996
13 Mauricio Plaza, 1994, 1995, 1996
15 The mc_realpath routine is mostly from uClibc package, written
16 by Rick Sladkey <jrs@world.std.com>
18 This file is part of the Midnight Commander.
20 The Midnight Commander is free software: you can redistribute it
21 and/or modify it under the terms of the GNU General Public License as
22 published by the Free Software Foundation, either version 3 of the License,
23 or (at your option) any later version.
25 The Midnight Commander is distributed in the hope that it will be useful,
26 but WITHOUT ANY WARRANTY; without even the implied warranty of
27 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
28 GNU General Public License for more details.
30 You should have received a copy of the GNU General Public License
31 along with this program. If not, see <http://www.gnu.org/licenses/>.
35 * \brief Source: various utilities - Unix variant
49 #ifdef HAVE_SYS_PARAM_H
50 #include <sys/param.h>
52 #include <sys/types.h>
55 #ifdef HAVE_SYS_IOCTL_H
56 #include <sys/ioctl.h>
58 #ifdef HAVE_GET_PROCESS_STATS
59 #include <sys/procstats.h>
65 #include "lib/global.h"
66 #include "lib/vfs/vfs.h" /* VFS_ENCODING_PREFIX */
67 #include "lib/strutil.h" /* str_move() */
69 #include "lib/widget.h" /* message() */
70 #include "lib/vfs/xdirentry.h"
73 #include "lib/charsets.h"
78 /*** global variables ****************************************************************************/
80 struct sigaction startup_handler
;
82 /*** file scope macro definitions ****************************************************************/
84 #define UID_CACHE_SIZE 200
85 #define GID_CACHE_SIZE 30
87 /* Pipes are guaranteed to be able to hold at least 4096 bytes */
88 /* More than that would be unportable */
89 #define MAX_PIPE_SIZE 4096
91 /*** file scope type declarations ****************************************************************/
108 struct sigaction intr
;
109 struct sigaction quit
;
110 struct sigaction stop
;
111 } my_system_sigactions_t
;
113 /*** file scope variables ************************************************************************/
115 static int_cache uid_cache
[UID_CACHE_SIZE
];
116 static int_cache gid_cache
[GID_CACHE_SIZE
];
118 static int error_pipe
[2]; /* File descriptors of error pipe */
119 static int old_error
; /* File descriptor of old standard error */
121 /*** file scope functions ************************************************************************/
122 /* --------------------------------------------------------------------------------------------- */
125 i_cache_match (int id
, int_cache
* cache
, int size
)
129 for (i
= 0; i
< size
; i
++)
130 if (cache
[i
].index
== id
)
131 return cache
[i
].string
;
135 /* --------------------------------------------------------------------------------------------- */
138 i_cache_add (int id
, int_cache
* cache
, int size
, char *text
, int *last
)
140 g_free (cache
[*last
].string
);
141 cache
[*last
].string
= g_strdup (text
);
142 cache
[*last
].index
= id
;
143 *last
= ((*last
) + 1) % size
;
146 /* --------------------------------------------------------------------------------------------- */
148 static my_fork_state_t
157 fprintf (stderr
, "\n\nfork () = -1\n");
168 if (waitpid (pid
, &status
, 0) > 0)
169 return WEXITSTATUS (status
) == 0 ? FORK_PARENT
: FORK_ERROR
;
176 /* --------------------------------------------------------------------------------------------- */
179 my_system__save_sigaction_handlers (my_system_sigactions_t
* sigactions
)
181 struct sigaction ignore
;
183 ignore
.sa_handler
= SIG_IGN
;
184 sigemptyset (&ignore
.sa_mask
);
187 sigaction (SIGINT
, &ignore
, &sigactions
->intr
);
188 sigaction (SIGQUIT
, &ignore
, &sigactions
->quit
);
190 /* Restore the original SIGTSTP handler, we don't want ncurses' */
191 /* handler messing the screen after the SIGCONT */
192 sigaction (SIGTSTP
, &startup_handler
, &sigactions
->stop
);
195 /* --------------------------------------------------------------------------------------------- */
198 my_system__restore_sigaction_handlers (my_system_sigactions_t
* sigactions
)
200 sigaction (SIGINT
, &sigactions
->intr
, NULL
);
201 sigaction (SIGQUIT
, &sigactions
->quit
, NULL
);
202 sigaction (SIGTSTP
, &sigactions
->stop
, NULL
);
205 /* --------------------------------------------------------------------------------------------- */
208 my_system_make_arg_array (int flags
, const char *shell
, char **execute_name
)
210 GPtrArray
*args_array
;
212 args_array
= g_ptr_array_new ();
214 if ((flags
& EXECUTE_AS_SHELL
) != 0)
216 g_ptr_array_add (args_array
, g_strdup (shell
));
217 g_ptr_array_add (args_array
, g_strdup ("-c"));
218 *execute_name
= g_strdup (shell
);
224 shell_token
= shell
!= NULL
? strchr (shell
, ' ') : NULL
;
225 if (shell_token
== NULL
)
226 *execute_name
= g_strdup (shell
);
228 *execute_name
= g_strndup (shell
, (gsize
) (shell_token
- shell
));
230 g_ptr_array_add (args_array
, g_strdup (shell
));
235 /* --------------------------------------------------------------------------------------------- */
236 /*** public functions ****************************************************************************/
237 /* --------------------------------------------------------------------------------------------- */
243 static char ibuf
[10];
247 name
= i_cache_match (uid
, uid_cache
, UID_CACHE_SIZE
);
251 pwd
= getpwuid (uid
);
254 i_cache_add (uid
, uid_cache
, UID_CACHE_SIZE
, pwd
->pw_name
, &uid_last
);
259 g_snprintf (ibuf
, sizeof (ibuf
), "%d", uid
);
264 /* --------------------------------------------------------------------------------------------- */
270 static char gbuf
[10];
274 name
= i_cache_match (gid
, gid_cache
, GID_CACHE_SIZE
);
278 grp
= getgrgid (gid
);
281 i_cache_add (gid
, gid_cache
, GID_CACHE_SIZE
, grp
->gr_name
, &gid_last
);
286 g_snprintf (gbuf
, sizeof (gbuf
), "%d", gid
);
291 /* --------------------------------------------------------------------------------------------- */
292 /* Since ncurses uses a handler that automatically refreshes the */
293 /* screen after a SIGCONT, and we don't want this behavior when */
294 /* spawning a child, we save the original handler here */
297 save_stop_handler (void)
299 sigaction (SIGTSTP
, NULL
, &startup_handler
);
302 /* --------------------------------------------------------------------------------------------- */
304 * Wrapper for _exit() system call.
305 * The _exit() function has gcc's attribute 'noreturn', and this is reason why we can't
308 * @param status exit code
317 /* --------------------------------------------------------------------------------------------- */
319 * Call external programs.
321 * @parameter flags addition conditions for running external programs.
322 * @parameter shell shell (if flags contain EXECUTE_AS_SHELL), command to run otherwise.
323 * Shell (or command) will be found in paths described in PATH variable
324 * (if shell parameter doesn't begin from path delimiter)
325 * @parameter command Command for shell (or first parameter for command, if flags contain EXECUTE_AS_SHELL)
326 * @return 0 if successfull, -1 otherwise
330 my_system (int flags
, const char *shell
, const char *command
)
332 return my_systeml (flags
, shell
, command
, NULL
);
335 /* --------------------------------------------------------------------------------------------- */
337 * Call external programs with various parameters number.
339 * @parameter flags addition conditions for running external programs.
340 * @parameter shell shell (if flags contain EXECUTE_AS_SHELL), command to run otherwise.
341 * Shell (or command) will be found in pathes described in PATH variable
342 * (if shell parameter doesn't begin from path delimiter)
343 * @parameter ... Command for shell with addition parameters for shell
344 * (or parameters for command, if flags contain EXECUTE_AS_SHELL).
345 * Should be NULL terminated.
346 * @return 0 if successfull, -1 otherwise
351 my_systeml (int flags
, const char *shell
, ...)
353 GPtrArray
*args_array
;
358 args_array
= g_ptr_array_new ();
360 va_start (vargs
, shell
);
361 while ((one_arg
= va_arg (vargs
, char *)) != NULL
)
362 g_ptr_array_add (args_array
, one_arg
);
365 g_ptr_array_add (args_array
, NULL
);
366 status
= my_systemv_flags (flags
, shell
, (char *const *) args_array
->pdata
);
368 g_ptr_array_free (args_array
, TRUE
);
373 /* --------------------------------------------------------------------------------------------- */
375 * Call external programs with array of strings as parameters.
377 * @parameter command command to run. Command will be found in paths described in PATH variable
378 * (if command parameter doesn't begin from path delimiter)
379 * @parameter argv Array of strings (NULL-terminated) with parameters for command
380 * @return 0 if successfull, -1 otherwise
384 my_systemv (const char *command
, char *const argv
[])
386 my_fork_state_t fork_state
;
388 my_system_sigactions_t sigactions
;
390 my_system__save_sigaction_handlers (&sigactions
);
392 fork_state
= my_fork ();
400 signal (SIGINT
, SIG_DFL
);
401 signal (SIGQUIT
, SIG_DFL
);
402 signal (SIGTSTP
, SIG_DFL
);
403 signal (SIGCHLD
, SIG_DFL
);
405 execvp (command
, argv
);
406 my_exit (127); /* Exec error */
413 my_system__restore_sigaction_handlers (&sigactions
);
418 /* --------------------------------------------------------------------------------------------- */
420 * Call external programs with flags and with array of strings as parameters.
422 * @parameter flags addition conditions for running external programs.
423 * @parameter command shell (if flags contain EXECUTE_AS_SHELL), command to run otherwise.
424 * Shell (or command) will be found in paths described in PATH variable
425 * (if shell parameter doesn't begin from path delimiter)
426 * @parameter argv Array of strings (NULL-terminated) with parameters for command
427 * @return 0 if successfull, -1 otherwise
431 my_systemv_flags (int flags
, const char *command
, char *const argv
[])
433 char *execute_name
= NULL
;
434 GPtrArray
*args_array
;
437 args_array
= my_system_make_arg_array (flags
, command
, &execute_name
);
439 for (; argv
!= NULL
&& *argv
!= NULL
; argv
++)
440 g_ptr_array_add (args_array
, *argv
);
442 g_ptr_array_add (args_array
, NULL
);
443 status
= my_systemv (execute_name
, (char *const *) args_array
->pdata
);
445 g_free (execute_name
);
446 g_ptr_array_free (args_array
, TRUE
);
451 /* --------------------------------------------------------------------------------------------- */
454 * Perform tilde expansion if possible.
456 * @param directory pointer to the path
458 * @return newly allocated string, even if it's unchanged.
462 tilde_expand (const char *directory
)
464 struct passwd
*passwd
;
468 if (*directory
!= '~')
469 return g_strdup (directory
);
473 /* d = "~" or d = "~/" */
474 if (!(*p
) || (*p
== PATH_SEP
))
476 passwd
= getpwuid (geteuid ());
477 q
= (*p
== PATH_SEP
) ? p
+ 1 : "";
481 q
= strchr (p
, PATH_SEP
);
484 passwd
= getpwnam (p
);
488 name
= g_strndup (p
, q
- p
);
489 passwd
= getpwnam (name
);
495 /* If we can't figure the user name, leave tilde unexpanded */
497 return g_strdup (directory
);
499 return g_strconcat (passwd
->pw_dir
, PATH_SEP_STR
, q
, (char *) NULL
);
502 /* --------------------------------------------------------------------------------------------- */
504 * Creates a pipe to hold standard error for a later analysis.
505 * The pipe can hold 4096 bytes. Make sure no more is written
506 * or a deadlock might occur.
510 open_error_pipe (void)
512 if (pipe (error_pipe
) < 0)
514 message (D_NORMAL
, _("Warning"), _("Pipe failed"));
517 if (old_error
< 0 || close (2) || dup (error_pipe
[1]) != 2)
519 message (D_NORMAL
, _("Warning"), _("Dup failed"));
521 close (error_pipe
[0]);
527 * Settng stderr in nonblocking mode as we close it earlier, than
528 * program stops. We try to read some error at program startup,
529 * but we should not block on it.
531 * TODO: make piped stdin/stderr poll()/select()able to get rid
535 fd_flags
= fcntl (error_pipe
[0], F_GETFL
, NULL
);
538 fd_flags
|= O_NONBLOCK
;
539 if (fcntl (error_pipe
[0], F_SETFL
, fd_flags
) == -1)
541 /* TODO: handle it somehow */
545 /* we never write there */
546 close (error_pipe
[1]);
550 /* --------------------------------------------------------------------------------------------- */
554 * @param error '-1' - ignore errors, '0' - display warning, '1' - display error
555 * @param text is prepended to the error message from the pipe
557 * @return not 0 if an error was displayed
561 close_error_pipe (int error
, const char *text
)
564 char msg
[MAX_PIPE_SIZE
];
568 if (error_pipe
[0] == -1)
571 if (error
< 0 || (error
> 0 && (error
& D_ERROR
) != 0))
574 title
= _("Warning");
577 if (dup2 (old_error
, 2) == -1)
582 message (error
, MSG_ERROR
, _("Error dup'ing old error pipe"));
586 len
= read (error_pipe
[0], msg
, MAX_PIPE_SIZE
- 1);
590 close (error_pipe
[0]);
594 return 0; /* Just ignore error message */
598 return 0; /* Nothing to show */
600 /* Show message from pipe */
601 message (error
, title
, "%s", msg
);
605 /* Show given text and possible message from pipe */
606 message (error
, title
, "%s\n%s", text
, msg
);
611 /* --------------------------------------------------------------------------------------------- */
613 * Canonicalize path, and return a new path. Do everything in place.
614 * The new path differs from path in:
615 * Multiple `/'s are collapsed to a single `/'.
616 * Leading `./'s and trailing `/.'s are removed.
617 * Trailing `/'s are removed.
618 * Non-leading `../'s and trailing `..'s are handled by removing
619 * portions of the path.
620 * Well formed UNC paths are modified only in the local part.
624 custom_canonicalize_pathname (char *path
, CANON_PATH_FLAGS flags
)
628 char *lpath
= path
; /* path without leading UNC part */
629 const size_t url_delim_len
= strlen (VFS_PATH_URL_DELIMITER
);
631 /* Detect and preserve UNC paths: //server/... */
632 if ((flags
& CANON_PATH_GUARDUNC
) && path
[0] == PATH_SEP
&& path
[1] == PATH_SEP
)
635 while (p
[0] && p
[0] != '/')
637 if (p
[0] == '/' && p
> path
+ 2)
641 if (!lpath
[0] || !lpath
[1])
644 if (flags
& CANON_PATH_JOINSLASHES
)
646 /* Collapse multiple slashes */
650 if (p
[0] == PATH_SEP
&& p
[1] == PATH_SEP
&& (p
== lpath
|| *(p
- 1) != ':'))
653 while (*(++s
) == PATH_SEP
);
660 if (flags
& CANON_PATH_JOINSLASHES
)
662 /* Collapse "/./" -> "/" */
666 if (p
[0] == PATH_SEP
&& p
[1] == '.' && p
[2] == PATH_SEP
)
673 if (flags
& CANON_PATH_REMSLASHDOTS
)
675 /* Remove trailing slashes */
676 p
= lpath
+ strlen (lpath
) - 1;
677 while (p
> lpath
&& *p
== PATH_SEP
)
679 if (p
>= lpath
- (url_delim_len
+ 1)
680 && strncmp (p
- url_delim_len
+ 1, VFS_PATH_URL_DELIMITER
, url_delim_len
) == 0)
685 /* Remove leading "./" */
686 if (lpath
[0] == '.' && lpath
[1] == PATH_SEP
)
695 str_move (lpath
, lpath
+ 2);
699 /* Remove trailing "/" or "/." */
700 len
= strlen (lpath
);
703 if (lpath
[len
- 1] == PATH_SEP
704 && (len
< url_delim_len
705 || strncmp (lpath
+ len
- url_delim_len
, VFS_PATH_URL_DELIMITER
,
706 url_delim_len
) != 0))
708 lpath
[len
- 1] = '\0';
712 if (lpath
[len
- 1] == '.' && lpath
[len
- 2] == PATH_SEP
)
721 lpath
[len
- 2] = '\0';
727 if (flags
& CANON_PATH_REMDOUBLEDOTS
)
730 const size_t enc_prefix_len
= strlen (VFS_ENCODING_PREFIX
);
731 #endif /* HAVE_CHARSET */
733 /* Collapse "/.." with the previous part of path */
735 while (p
[0] && p
[1] && p
[2])
737 if ((p
[0] != PATH_SEP
|| p
[1] != '.' || p
[2] != '.') || (p
[3] != PATH_SEP
&& p
[3] != 0))
743 /* search for the previous token */
745 if (s
>= lpath
+ url_delim_len
- 2
746 && strncmp (s
- url_delim_len
+ 2, VFS_PATH_URL_DELIMITER
, url_delim_len
) == 0)
748 s
-= (url_delim_len
- 2);
749 while (s
>= lpath
&& *s
-- != PATH_SEP
);
754 if (s
- url_delim_len
> lpath
755 && strncmp (s
- url_delim_len
, VFS_PATH_URL_DELIMITER
, url_delim_len
) == 0)
757 char *vfs_prefix
= s
- url_delim_len
;
758 struct vfs_class
*vclass
;
760 while (vfs_prefix
> lpath
&& *--vfs_prefix
!= PATH_SEP
);
761 if (*vfs_prefix
== PATH_SEP
)
763 *(s
- url_delim_len
) = '\0';
765 vclass
= vfs_prefix_to_class (vfs_prefix
);
766 *(s
- url_delim_len
) = *VFS_PATH_URL_DELIMITER
;
770 struct vfs_s_subclass
*sub
= (struct vfs_s_subclass
*) vclass
->data
;
771 if (sub
!= NULL
&& sub
->flags
& VFS_S_REMOTE
)
787 /* If the previous token is "..", we cannot collapse it */
788 if (s
[0] == '.' && s
[1] == '.' && s
+ 2 == p
)
796 if (s
== lpath
&& *s
== PATH_SEP
)
798 /* "/../foo" -> "/foo" */
799 str_move (s
+ 1, p
+ 4);
803 /* "token/../foo" -> "foo" */
805 if ((strncmp (s
, VFS_ENCODING_PREFIX
, enc_prefix_len
) == 0)
806 && (is_supported_encoding (s
+ enc_prefix_len
)))
807 /* special case: remove encoding */
810 #endif /* HAVE_CHARSET */
813 p
= (s
> lpath
) ? s
- 1 : s
;
820 /* "token/.." -> "." */
821 if (lpath
[0] != PATH_SEP
)
829 /* "foo/token/.." -> "foo" */
833 else if ((strncmp (s
, VFS_ENCODING_PREFIX
, enc_prefix_len
) == 0)
834 && (is_supported_encoding (s
+ enc_prefix_len
)))
836 /* special case: remove encoding */
841 /* search for the previous token */
842 /* s[-1] == PATH_SEP */
844 while (p
>= lpath
&& *p
!= PATH_SEP
)
850 #endif /* HAVE_CHARSET */
853 if (s
>= lpath
+ url_delim_len
854 && strncmp (s
- url_delim_len
, VFS_PATH_URL_DELIMITER
, url_delim_len
) == 0)
867 /* --------------------------------------------------------------------------------------------- */
870 canonicalize_pathname (char *path
)
872 custom_canonicalize_pathname (path
, CANON_PATH_ALL
);
875 /* --------------------------------------------------------------------------------------------- */
877 #ifdef HAVE_GET_PROCESS_STATS
879 gettimeofday (struct timeval
*tp
, void *tzp
)
881 return get_process_stats (tp
, PS_SELF
, 0, 0);
883 #endif /* HAVE_GET_PROCESS_STATS */
885 /* --------------------------------------------------------------------------------------------- */
887 #ifndef HAVE_REALPATH
889 mc_realpath (const char *path
, char *resolved_path
)
891 char copy_path
[PATH_MAX
];
892 char link_path
[PATH_MAX
];
893 char got_path
[PATH_MAX
];
894 char *new_path
= got_path
;
899 /* Make a copy of the source path since we may need to modify it. */
900 if (strlen (path
) >= PATH_MAX
- 2)
902 errno
= ENAMETOOLONG
;
905 strcpy (copy_path
, path
);
907 max_path
= copy_path
+ PATH_MAX
- 2;
908 /* If it's a relative pathname use getwd for starters. */
912 new_path
= g_get_current_dir ();
913 if (new_path
== NULL
)
915 strcpy (got_path
, "");
919 g_snprintf (got_path
, PATH_MAX
, "%s", new_path
);
924 new_path
+= strlen (got_path
);
925 if (new_path
[-1] != '/')
933 /* Expand each slash-separated pathname component. */
934 while (*path
!= '\0')
936 /* Ignore stray "/". */
945 if (path
[1] == '\0' || path
[1] == '/')
952 if (path
[2] == '\0' || path
[2] == '/')
955 /* Ignore ".." at root. */
956 if (new_path
== got_path
+ 1)
958 /* Handle ".." by backing up. */
959 while ((--new_path
)[-1] != '/');
964 /* Safely copy the next pathname component. */
965 while (*path
!= '\0' && *path
!= '/')
969 errno
= ENAMETOOLONG
;
972 *new_path
++ = *path
++;
975 /* Protect against infinite loops. */
976 if (readlinks
++ > MAXSYMLINKS
)
981 /* See if latest pathname component is a symlink. */
983 n
= readlink (got_path
, link_path
, PATH_MAX
- 1);
986 /* EINVAL means the file exists but isn't a symlink. */
989 /* Make sure it's null terminated. */
991 strcpy (resolved_path
, got_path
);
997 /* Note: readlink doesn't add the null byte. */
999 if (*link_path
== '/')
1000 /* Start over for an absolute symlink. */
1001 new_path
= got_path
;
1003 /* Otherwise back up over this component. */
1004 while (*(--new_path
) != '/');
1005 /* Safe sex check. */
1006 if (strlen (path
) + n
>= PATH_MAX
- 2)
1008 errno
= ENAMETOOLONG
;
1011 /* Insert symlink contents into path. */
1012 strcat (link_path
, path
);
1013 strcpy (copy_path
, link_path
);
1016 #endif /* S_IFLNK */
1019 /* Delete trailing slash but don't whomp a lone slash. */
1020 if (new_path
!= got_path
+ 1 && new_path
[-1] == '/')
1022 /* Make sure it's null terminated. */
1024 strcpy (resolved_path
, got_path
);
1025 return resolved_path
;
1027 #endif /* HAVE_REALPATH */
1029 /* --------------------------------------------------------------------------------------------- */
1031 * Return the index of the permissions triplet
1036 get_user_permissions (struct stat
*st
)
1038 static gboolean initialized
= FALSE
;
1039 static gid_t
*groups
;
1048 ngroups
= getgroups (0, NULL
);
1050 ngroups
= 0; /* ignore errors */
1052 /* allocate space for one element in addition to what
1053 * will be filled by getgroups(). */
1054 groups
= g_new (gid_t
, ngroups
+ 1);
1058 ngroups
= getgroups (ngroups
, groups
);
1060 ngroups
= 0; /* ignore errors */
1063 /* getgroups() may or may not return the effective group ID,
1064 * so we always include it at the end of the list. */
1065 groups
[ngroups
++] = getegid ();
1070 if (st
->st_uid
== uid
|| uid
== 0)
1073 for (i
= 0; i
< ngroups
; i
++)
1075 if (st
->st_gid
== groups
[i
])
1082 /* --------------------------------------------------------------------------------------------- */
1084 * Build filename from arguments.
1085 * Like to g_build_filename(), but respect VFS_PATH_URL_DELIMITER
1089 mc_build_filenamev (const char *first_element
, va_list args
)
1092 const char *element
= first_element
;
1096 if (element
== NULL
)
1099 path
= g_string_new ("");
1101 absolute
= (*first_element
!= '\0' && *first_element
== PATH_SEP
);
1105 if (*element
== '\0')
1106 element
= va_arg (args
, char *);
1113 tmp_element
= g_strdup (element
);
1115 element
= va_arg (args
, char *);
1117 canonicalize_pathname (tmp_element
);
1118 len
= strlen (tmp_element
);
1119 start
= (tmp_element
[0] == PATH_SEP
) ? tmp_element
+ 1 : tmp_element
;
1121 g_string_append (path
, start
);
1122 if (tmp_element
[len
- 1] != PATH_SEP
&& element
!= NULL
)
1123 g_string_append_c (path
, PATH_SEP
);
1125 g_free (tmp_element
);
1128 while (element
!= NULL
);
1131 g_string_prepend_c (path
, PATH_SEP
);
1133 ret
= g_string_free (path
, FALSE
);
1134 canonicalize_pathname (ret
);
1139 /* --------------------------------------------------------------------------------------------- */
1141 * Build filename from arguments.
1142 * Like to g_build_filename(), but respect VFS_PATH_URL_DELIMITER
1146 mc_build_filename (const char *first_element
, ...)
1151 if (first_element
== NULL
)
1154 va_start (args
, first_element
);
1155 ret
= mc_build_filenamev (first_element
, args
);
1160 /* --------------------------------------------------------------------------------------------- */