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 /* --------------------------------------------------------------------------------------------- */
246 name
= i_cache_match (uid
, uid_cache
, UID_CACHE_SIZE
);
250 pwd
= getpwuid (uid
);
253 i_cache_add (uid
, uid_cache
, UID_CACHE_SIZE
, pwd
->pw_name
, &uid_last
);
258 static char ibuf
[10];
260 g_snprintf (ibuf
, sizeof (ibuf
), "%d", uid
);
265 /* --------------------------------------------------------------------------------------------- */
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 static char gbuf
[10];
288 g_snprintf (gbuf
, sizeof (gbuf
), "%d", gid
);
293 /* --------------------------------------------------------------------------------------------- */
294 /* Since ncurses uses a handler that automatically refreshes the */
295 /* screen after a SIGCONT, and we don't want this behavior when */
296 /* spawning a child, we save the original handler here */
299 save_stop_handler (void)
301 sigaction (SIGTSTP
, NULL
, &startup_handler
);
304 /* --------------------------------------------------------------------------------------------- */
306 * Wrapper for _exit() system call.
307 * The _exit() function has gcc's attribute 'noreturn', and this is reason why we can't
310 * @param status exit code
319 /* --------------------------------------------------------------------------------------------- */
321 * Call external programs.
323 * @parameter flags addition conditions for running external programs.
324 * @parameter shell shell (if flags contain EXECUTE_AS_SHELL), command to run otherwise.
325 * Shell (or command) will be found in paths described in PATH variable
326 * (if shell parameter doesn't begin from path delimiter)
327 * @parameter command Command for shell (or first parameter for command, if flags contain EXECUTE_AS_SHELL)
328 * @return 0 if successfull, -1 otherwise
332 my_system (int flags
, const char *shell
, const char *command
)
334 return my_systeml (flags
, shell
, command
, NULL
);
337 /* --------------------------------------------------------------------------------------------- */
339 * Call external programs with various parameters number.
341 * @parameter flags addition conditions for running external programs.
342 * @parameter shell shell (if flags contain EXECUTE_AS_SHELL), command to run otherwise.
343 * Shell (or command) will be found in pathes described in PATH variable
344 * (if shell parameter doesn't begin from path delimiter)
345 * @parameter ... Command for shell with addition parameters for shell
346 * (or parameters for command, if flags contain EXECUTE_AS_SHELL).
347 * Should be NULL terminated.
348 * @return 0 if successfull, -1 otherwise
353 my_systeml (int flags
, const char *shell
, ...)
355 GPtrArray
*args_array
;
360 args_array
= g_ptr_array_new ();
362 va_start (vargs
, shell
);
363 while ((one_arg
= va_arg (vargs
, char *)) != NULL
)
364 g_ptr_array_add (args_array
, one_arg
);
367 g_ptr_array_add (args_array
, NULL
);
368 status
= my_systemv_flags (flags
, shell
, (char *const *) args_array
->pdata
);
370 g_ptr_array_free (args_array
, TRUE
);
375 /* --------------------------------------------------------------------------------------------- */
377 * Call external programs with array of strings as parameters.
379 * @parameter command command to run. Command will be found in paths described in PATH variable
380 * (if command parameter doesn't begin from path delimiter)
381 * @parameter argv Array of strings (NULL-terminated) with parameters for command
382 * @return 0 if successfull, -1 otherwise
386 my_systemv (const char *command
, char *const argv
[])
388 my_fork_state_t fork_state
;
390 my_system_sigactions_t sigactions
;
392 my_system__save_sigaction_handlers (&sigactions
);
394 fork_state
= my_fork ();
402 signal (SIGINT
, SIG_DFL
);
403 signal (SIGQUIT
, SIG_DFL
);
404 signal (SIGTSTP
, SIG_DFL
);
405 signal (SIGCHLD
, SIG_DFL
);
407 execvp (command
, argv
);
408 my_exit (127); /* Exec error */
415 my_system__restore_sigaction_handlers (&sigactions
);
420 /* --------------------------------------------------------------------------------------------- */
422 * Call external programs with flags and with array of strings as parameters.
424 * @parameter flags addition conditions for running external programs.
425 * @parameter command shell (if flags contain EXECUTE_AS_SHELL), command to run otherwise.
426 * Shell (or command) will be found in paths described in PATH variable
427 * (if shell parameter doesn't begin from path delimiter)
428 * @parameter argv Array of strings (NULL-terminated) with parameters for command
429 * @return 0 if successfull, -1 otherwise
433 my_systemv_flags (int flags
, const char *command
, char *const argv
[])
435 char *execute_name
= NULL
;
436 GPtrArray
*args_array
;
439 args_array
= my_system_make_arg_array (flags
, command
, &execute_name
);
441 for (; argv
!= NULL
&& *argv
!= NULL
; argv
++)
442 g_ptr_array_add (args_array
, *argv
);
444 g_ptr_array_add (args_array
, NULL
);
445 status
= my_systemv (execute_name
, (char *const *) args_array
->pdata
);
447 g_free (execute_name
);
448 g_ptr_array_free (args_array
, TRUE
);
453 /* --------------------------------------------------------------------------------------------- */
456 * Perform tilde expansion if possible.
458 * @param directory pointer to the path
460 * @return newly allocated string, even if it's unchanged.
464 tilde_expand (const char *directory
)
466 struct passwd
*passwd
;
469 if (*directory
!= '~')
470 return g_strdup (directory
);
474 /* d = "~" or d = "~/" */
475 if (!(*p
) || (*p
== PATH_SEP
))
477 passwd
= getpwuid (geteuid ());
478 q
= (*p
== PATH_SEP
) ? p
+ 1 : "";
482 q
= strchr (p
, PATH_SEP
);
485 passwd
= getpwnam (p
);
491 name
= g_strndup (p
, q
- p
);
492 passwd
= getpwnam (name
);
498 /* If we can't figure the user name, leave tilde unexpanded */
500 return g_strdup (directory
);
502 return g_strconcat (passwd
->pw_dir
, PATH_SEP_STR
, q
, (char *) NULL
);
505 /* --------------------------------------------------------------------------------------------- */
507 * Creates a pipe to hold standard error for a later analysis.
508 * The pipe can hold 4096 bytes. Make sure no more is written
509 * or a deadlock might occur.
513 open_error_pipe (void)
515 if (pipe (error_pipe
) < 0)
517 message (D_NORMAL
, _("Warning"), _("Pipe failed"));
520 if (old_error
< 0 || close (2) || dup (error_pipe
[1]) != 2)
522 message (D_NORMAL
, _("Warning"), _("Dup failed"));
524 close (error_pipe
[0]);
530 * Settng stderr in nonblocking mode as we close it earlier, than
531 * program stops. We try to read some error at program startup,
532 * but we should not block on it.
534 * TODO: make piped stdin/stderr poll()/select()able to get rid
538 fd_flags
= fcntl (error_pipe
[0], F_GETFL
, NULL
);
541 fd_flags
|= O_NONBLOCK
;
542 if (fcntl (error_pipe
[0], F_SETFL
, fd_flags
) == -1)
544 /* TODO: handle it somehow */
548 /* we never write there */
549 close (error_pipe
[1]);
553 /* --------------------------------------------------------------------------------------------- */
557 * @param error '-1' - ignore errors, '0' - display warning, '1' - display error
558 * @param text is prepended to the error message from the pipe
560 * @return not 0 if an error was displayed
564 close_error_pipe (int error
, const char *text
)
567 char msg
[MAX_PIPE_SIZE
];
571 if (error_pipe
[0] == -1)
574 if (error
< 0 || (error
> 0 && (error
& D_ERROR
) != 0))
577 title
= _("Warning");
580 if (dup2 (old_error
, 2) == -1)
585 message (error
, MSG_ERROR
, _("Error dup'ing old error pipe"));
589 len
= read (error_pipe
[0], msg
, MAX_PIPE_SIZE
- 1);
593 close (error_pipe
[0]);
597 return 0; /* Just ignore error message */
601 return 0; /* Nothing to show */
603 /* Show message from pipe */
604 message (error
, title
, "%s", msg
);
608 /* Show given text and possible message from pipe */
609 message (error
, title
, "%s\n%s", text
, msg
);
614 /* --------------------------------------------------------------------------------------------- */
616 * Canonicalize path, and return a new path. Do everything in place.
617 * The new path differs from path in:
618 * Multiple '/'s are collapsed to a single '/'.
619 * Leading './'s and trailing '/.'s are removed.
620 * Trailing '/'s are removed.
621 * Non-leading '../'s and trailing '..'s are handled by removing
622 * portions of the path.
623 * Well formed UNC paths are modified only in the local part.
627 custom_canonicalize_pathname (char *path
, CANON_PATH_FLAGS flags
)
630 char *lpath
= path
; /* path without leading UNC part */
631 const size_t url_delim_len
= strlen (VFS_PATH_URL_DELIMITER
);
633 /* Detect and preserve UNC paths: //server/... */
634 if ((flags
& CANON_PATH_GUARDUNC
) && path
[0] == PATH_SEP
&& path
[1] == PATH_SEP
)
637 while (p
[0] && p
[0] != '/')
639 if (p
[0] == '/' && p
> path
+ 2)
643 if (!lpath
[0] || !lpath
[1])
646 if (flags
& CANON_PATH_JOINSLASHES
)
648 /* Collapse multiple slashes */
652 if (p
[0] == PATH_SEP
&& p
[1] == PATH_SEP
&& (p
== lpath
|| *(p
- 1) != ':'))
655 while (*(++s
) == PATH_SEP
);
662 if (flags
& CANON_PATH_JOINSLASHES
)
664 /* Collapse "/./" -> "/" */
668 if (p
[0] == PATH_SEP
&& p
[1] == '.' && p
[2] == PATH_SEP
)
675 if (flags
& CANON_PATH_REMSLASHDOTS
)
679 /* Remove trailing slashes */
680 p
= lpath
+ strlen (lpath
) - 1;
681 while (p
> lpath
&& *p
== PATH_SEP
)
683 if (p
>= lpath
- (url_delim_len
+ 1)
684 && strncmp (p
- url_delim_len
+ 1, VFS_PATH_URL_DELIMITER
, url_delim_len
) == 0)
689 /* Remove leading "./" */
690 if (lpath
[0] == '.' && lpath
[1] == PATH_SEP
)
699 str_move (lpath
, lpath
+ 2);
703 /* Remove trailing "/" or "/." */
704 len
= strlen (lpath
);
707 if (lpath
[len
- 1] == PATH_SEP
708 && (len
< url_delim_len
709 || strncmp (lpath
+ len
- url_delim_len
, VFS_PATH_URL_DELIMITER
,
710 url_delim_len
) != 0))
712 lpath
[len
- 1] = '\0';
716 if (lpath
[len
- 1] == '.' && lpath
[len
- 2] == PATH_SEP
)
725 lpath
[len
- 2] = '\0';
731 if (flags
& CANON_PATH_REMDOUBLEDOTS
)
734 const size_t enc_prefix_len
= strlen (VFS_ENCODING_PREFIX
);
735 #endif /* HAVE_CHARSET */
737 /* Collapse "/.." with the previous part of path */
739 while (p
[0] && p
[1] && p
[2])
741 if ((p
[0] != PATH_SEP
|| p
[1] != '.' || p
[2] != '.') || (p
[3] != PATH_SEP
&& p
[3] != 0))
747 /* search for the previous token */
749 if (s
>= lpath
+ url_delim_len
- 2
750 && strncmp (s
- url_delim_len
+ 2, VFS_PATH_URL_DELIMITER
, url_delim_len
) == 0)
752 s
-= (url_delim_len
- 2);
753 while (s
>= lpath
&& *s
-- != PATH_SEP
);
758 if (s
- url_delim_len
> lpath
759 && strncmp (s
- url_delim_len
, VFS_PATH_URL_DELIMITER
, url_delim_len
) == 0)
761 char *vfs_prefix
= s
- url_delim_len
;
762 struct vfs_class
*vclass
;
764 while (vfs_prefix
> lpath
&& *--vfs_prefix
!= PATH_SEP
);
765 if (*vfs_prefix
== PATH_SEP
)
767 *(s
- url_delim_len
) = '\0';
769 vclass
= vfs_prefix_to_class (vfs_prefix
);
770 *(s
- url_delim_len
) = *VFS_PATH_URL_DELIMITER
;
774 struct vfs_s_subclass
*sub
= (struct vfs_s_subclass
*) vclass
->data
;
775 if (sub
!= NULL
&& sub
->flags
& VFS_S_REMOTE
)
791 /* If the previous token is "..", we cannot collapse it */
792 if (s
[0] == '.' && s
[1] == '.' && s
+ 2 == p
)
800 if (s
== lpath
&& *s
== PATH_SEP
)
802 /* "/../foo" -> "/foo" */
803 str_move (s
+ 1, p
+ 4);
807 /* "token/../foo" -> "foo" */
809 if ((strncmp (s
, VFS_ENCODING_PREFIX
, enc_prefix_len
) == 0)
810 && (is_supported_encoding (s
+ enc_prefix_len
)))
811 /* special case: remove encoding */
814 #endif /* HAVE_CHARSET */
817 p
= (s
> lpath
) ? s
- 1 : s
;
824 /* "token/.." -> "." */
825 if (lpath
[0] != PATH_SEP
)
833 /* "foo/token/.." -> "foo" */
837 else if ((strncmp (s
, VFS_ENCODING_PREFIX
, enc_prefix_len
) == 0)
838 && (is_supported_encoding (s
+ enc_prefix_len
)))
840 /* special case: remove encoding */
845 /* search for the previous token */
846 /* s[-1] == PATH_SEP */
848 while (p
>= lpath
&& *p
!= PATH_SEP
)
854 #endif /* HAVE_CHARSET */
857 if (s
>= lpath
+ url_delim_len
858 && strncmp (s
- url_delim_len
, VFS_PATH_URL_DELIMITER
, url_delim_len
) == 0)
870 /* --------------------------------------------------------------------------------------------- */
873 canonicalize_pathname (char *path
)
875 custom_canonicalize_pathname (path
, CANON_PATH_ALL
);
878 /* --------------------------------------------------------------------------------------------- */
880 #ifdef HAVE_GET_PROCESS_STATS
882 gettimeofday (struct timeval
*tp
, void *tzp
)
884 return get_process_stats (tp
, PS_SELF
, 0, 0);
886 #endif /* HAVE_GET_PROCESS_STATS */
888 /* --------------------------------------------------------------------------------------------- */
890 #ifndef HAVE_REALPATH
892 mc_realpath (const char *path
, char *resolved_path
)
894 char copy_path
[PATH_MAX
];
895 char link_path
[PATH_MAX
];
896 char got_path
[PATH_MAX
];
897 char *new_path
= got_path
;
902 /* Make a copy of the source path since we may need to modify it. */
903 if (strlen (path
) >= PATH_MAX
- 2)
905 errno
= ENAMETOOLONG
;
908 strcpy (copy_path
, path
);
910 max_path
= copy_path
+ PATH_MAX
- 2;
911 /* If it's a relative pathname use getwd for starters. */
915 new_path
= g_get_current_dir ();
916 if (new_path
== NULL
)
918 strcpy (got_path
, "");
922 g_snprintf (got_path
, PATH_MAX
, "%s", new_path
);
927 new_path
+= strlen (got_path
);
928 if (new_path
[-1] != '/')
936 /* Expand each slash-separated pathname component. */
937 while (*path
!= '\0')
939 /* Ignore stray "/". */
948 if (path
[1] == '\0' || path
[1] == '/')
955 if (path
[2] == '\0' || path
[2] == '/')
958 /* Ignore ".." at root. */
959 if (new_path
== got_path
+ 1)
961 /* Handle ".." by backing up. */
962 while ((--new_path
)[-1] != '/');
967 /* Safely copy the next pathname component. */
968 while (*path
!= '\0' && *path
!= '/')
972 errno
= ENAMETOOLONG
;
975 *new_path
++ = *path
++;
978 /* Protect against infinite loops. */
979 if (readlinks
++ > MAXSYMLINKS
)
984 /* See if latest pathname component is a symlink. */
986 n
= readlink (got_path
, link_path
, PATH_MAX
- 1);
989 /* EINVAL means the file exists but isn't a symlink. */
992 /* Make sure it's null terminated. */
994 strcpy (resolved_path
, got_path
);
1000 /* Note: readlink doesn't add the null byte. */
1001 link_path
[n
] = '\0';
1002 if (*link_path
== '/')
1003 /* Start over for an absolute symlink. */
1004 new_path
= got_path
;
1006 /* Otherwise back up over this component. */
1007 while (*(--new_path
) != '/');
1008 /* Safe sex check. */
1009 if (strlen (path
) + n
>= PATH_MAX
- 2)
1011 errno
= ENAMETOOLONG
;
1014 /* Insert symlink contents into path. */
1015 strcat (link_path
, path
);
1016 strcpy (copy_path
, link_path
);
1019 #endif /* S_IFLNK */
1022 /* Delete trailing slash but don't whomp a lone slash. */
1023 if (new_path
!= got_path
+ 1 && new_path
[-1] == '/')
1025 /* Make sure it's null terminated. */
1027 strcpy (resolved_path
, got_path
);
1028 return resolved_path
;
1030 #endif /* HAVE_REALPATH */
1032 /* --------------------------------------------------------------------------------------------- */
1034 * Return the index of the permissions triplet
1039 get_user_permissions (struct stat
*st
)
1041 static gboolean initialized
= FALSE
;
1042 static gid_t
*groups
;
1051 ngroups
= getgroups (0, NULL
);
1053 ngroups
= 0; /* ignore errors */
1055 /* allocate space for one element in addition to what
1056 * will be filled by getgroups(). */
1057 groups
= g_new (gid_t
, ngroups
+ 1);
1061 ngroups
= getgroups (ngroups
, groups
);
1063 ngroups
= 0; /* ignore errors */
1066 /* getgroups() may or may not return the effective group ID,
1067 * so we always include it at the end of the list. */
1068 groups
[ngroups
++] = getegid ();
1073 if (st
->st_uid
== uid
|| uid
== 0)
1076 for (i
= 0; i
< ngroups
; i
++)
1078 if (st
->st_gid
== groups
[i
])
1085 /* --------------------------------------------------------------------------------------------- */
1087 * Build filename from arguments.
1088 * Like to g_build_filename(), but respect VFS_PATH_URL_DELIMITER
1092 mc_build_filenamev (const char *first_element
, va_list args
)
1095 const char *element
= first_element
;
1099 if (element
== NULL
)
1102 path
= g_string_new ("");
1104 absolute
= (*first_element
!= '\0' && *first_element
== PATH_SEP
);
1108 if (*element
== '\0')
1109 element
= va_arg (args
, char *);
1116 tmp_element
= g_strdup (element
);
1118 element
= va_arg (args
, char *);
1120 canonicalize_pathname (tmp_element
);
1121 len
= strlen (tmp_element
);
1122 start
= (tmp_element
[0] == PATH_SEP
) ? tmp_element
+ 1 : tmp_element
;
1124 g_string_append (path
, start
);
1125 if (tmp_element
[len
- 1] != PATH_SEP
&& element
!= NULL
)
1126 g_string_append_c (path
, PATH_SEP
);
1128 g_free (tmp_element
);
1131 while (element
!= NULL
);
1134 g_string_prepend_c (path
, PATH_SEP
);
1136 ret
= g_string_free (path
, FALSE
);
1137 canonicalize_pathname (ret
);
1142 /* --------------------------------------------------------------------------------------------- */
1144 * Build filename from arguments.
1145 * Like to g_build_filename(), but respect VFS_PATH_URL_DELIMITER
1149 mc_build_filename (const char *first_element
, ...)
1154 if (first_element
== NULL
)
1157 va_start (args
, first_element
);
1158 ret
= mc_build_filenamev (first_element
, args
);
1163 /* --------------------------------------------------------------------------------------------- */