dab48f297f91a178edc820de8ba54114a5412c92
[midnight-commander.git] / lib / utilunix.c
blobdab48f297f91a178edc820de8ba54114a5412c92
1 /*
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.
8 Written by:
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/>.
34 /** \file utilunix.c
35 * \brief Source: various utilities - Unix variant
38 #include <config.h>
40 #include <ctype.h>
41 #include <errno.h>
42 #include <limits.h>
43 #include <signal.h>
44 #include <stdarg.h>
45 #include <stdio.h>
46 #include <stdlib.h>
47 #include <string.h>
48 #include <fcntl.h>
49 #ifdef HAVE_SYS_PARAM_H
50 #include <sys/param.h>
51 #endif
52 #include <sys/types.h>
53 #include <sys/stat.h>
54 #include <sys/wait.h>
55 #ifdef HAVE_SYS_IOCTL_H
56 #include <sys/ioctl.h>
57 #endif
58 #ifdef HAVE_GET_PROCESS_STATS
59 #include <sys/procstats.h>
60 #endif
61 #include <unistd.h>
62 #include <pwd.h>
63 #include <grp.h>
65 #include "lib/global.h"
66 #include "lib/vfs/vfs.h" /* VFS_ENCODING_PREFIX */
67 #include "lib/strutil.h" /* str_move() */
68 #include "lib/util.h"
69 #include "lib/widget.h" /* message() */
70 #include "lib/vfs/xdirentry.h"
72 #ifdef HAVE_CHARSET
73 #include "lib/charsets.h"
74 #endif
76 #include "utilunix.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 ****************************************************************/
93 typedef struct
95 int index;
96 char *string;
97 } int_cache;
99 /*** file scope variables ************************************************************************/
101 static int_cache uid_cache[UID_CACHE_SIZE];
102 static int_cache gid_cache[GID_CACHE_SIZE];
104 static int error_pipe[2]; /* File descriptors of error pipe */
105 static int old_error; /* File descriptor of old standard error */
107 /*** file scope functions ************************************************************************/
108 /* --------------------------------------------------------------------------------------------- */
110 static char *
111 i_cache_match (int id, int_cache * cache, int size)
113 int i;
115 for (i = 0; i < size; i++)
116 if (cache[i].index == id)
117 return cache[i].string;
118 return 0;
121 /* --------------------------------------------------------------------------------------------- */
123 static void
124 i_cache_add (int id, int_cache * cache, int size, char *text, int *last)
126 g_free (cache[*last].string);
127 cache[*last].string = g_strdup (text);
128 cache[*last].index = id;
129 *last = ((*last) + 1) % size;
132 /* --------------------------------------------------------------------------------------------- */
133 /*** public functions ****************************************************************************/
134 /* --------------------------------------------------------------------------------------------- */
136 char *
137 get_owner (int uid)
139 struct passwd *pwd;
140 static char ibuf[10];
141 char *name;
142 static int uid_last;
144 name = i_cache_match (uid, uid_cache, UID_CACHE_SIZE);
145 if (name != NULL)
146 return name;
148 pwd = getpwuid (uid);
149 if (pwd != NULL)
151 i_cache_add (uid, uid_cache, UID_CACHE_SIZE, pwd->pw_name, &uid_last);
152 return pwd->pw_name;
154 else
156 g_snprintf (ibuf, sizeof (ibuf), "%d", uid);
157 return ibuf;
161 /* --------------------------------------------------------------------------------------------- */
163 char *
164 get_group (int gid)
166 struct group *grp;
167 static char gbuf[10];
168 char *name;
169 static int gid_last;
171 name = i_cache_match (gid, gid_cache, GID_CACHE_SIZE);
172 if (name != NULL)
173 return name;
175 grp = getgrgid (gid);
176 if (grp != NULL)
178 i_cache_add (gid, gid_cache, GID_CACHE_SIZE, grp->gr_name, &gid_last);
179 return grp->gr_name;
181 else
183 g_snprintf (gbuf, sizeof (gbuf), "%d", gid);
184 return gbuf;
188 /* --------------------------------------------------------------------------------------------- */
189 /* Since ncurses uses a handler that automatically refreshes the */
190 /* screen after a SIGCONT, and we don't want this behavior when */
191 /* spawning a child, we save the original handler here */
193 void
194 save_stop_handler (void)
196 sigaction (SIGTSTP, NULL, &startup_handler);
199 /* --------------------------------------------------------------------------------------------- */
201 * Wrapper for _exit() system call.
202 * The _exit() function has gcc's attribute 'noreturn', and this is reason why we can't
203 * mock the call.
205 * @param status exit code
208 void
209 my_exit (int status)
211 _exit (status);
214 /* --------------------------------------------------------------------------------------------- */
217 my_system (int flags, const char *shell, const char *command)
219 struct sigaction ignore, save_intr, save_quit, save_stop;
220 pid_t pid;
221 int status = 0;
223 ignore.sa_handler = SIG_IGN;
224 sigemptyset (&ignore.sa_mask);
225 ignore.sa_flags = 0;
227 sigaction (SIGINT, &ignore, &save_intr);
228 sigaction (SIGQUIT, &ignore, &save_quit);
230 /* Restore the original SIGTSTP handler, we don't want ncurses' */
231 /* handler messing the screen after the SIGCONT */
232 sigaction (SIGTSTP, &startup_handler, &save_stop);
234 pid = fork ();
235 if (pid < 0)
237 fprintf (stderr, "\n\nfork () = -1\n");
238 status = -1;
240 else if (pid == 0)
242 signal (SIGINT, SIG_DFL);
243 signal (SIGQUIT, SIG_DFL);
244 signal (SIGTSTP, SIG_DFL);
245 signal (SIGCHLD, SIG_DFL);
247 if (flags & EXECUTE_AS_SHELL)
248 execl (shell, shell, "-c", command, (char *) NULL);
249 else
251 gchar **shell_tokens;
252 const gchar *only_cmd;
254 shell_tokens = g_strsplit (shell, " ", 2);
255 if (shell_tokens == NULL)
256 only_cmd = shell;
257 else
258 only_cmd = (*shell_tokens != NULL) ? *shell_tokens : shell;
260 execlp (only_cmd, shell, command, (char *) NULL);
263 execlp will replace current process,
264 therefore no sence in call of g_strfreev().
265 But this keeped for estetic reason :)
267 g_strfreev (shell_tokens);
271 my_exit (127); /* Exec error */
273 else
275 while (TRUE)
277 if (waitpid (pid, &status, 0) > 0)
279 status = WEXITSTATUS (status);
280 break;
282 if (errno != EINTR)
284 status = -1;
285 break;
289 sigaction (SIGINT, &save_intr, NULL);
290 sigaction (SIGQUIT, &save_quit, NULL);
291 sigaction (SIGTSTP, &save_stop, NULL);
293 return status;
297 /* --------------------------------------------------------------------------------------------- */
300 * Perform tilde expansion if possible.
302 * @param directory pointer to the path
304 * @return newly allocated string, even if it's unchanged.
307 char *
308 tilde_expand (const char *directory)
310 struct passwd *passwd;
311 const char *p, *q;
312 char *name;
314 if (*directory != '~')
315 return g_strdup (directory);
317 p = directory + 1;
319 /* d = "~" or d = "~/" */
320 if (!(*p) || (*p == PATH_SEP))
322 passwd = getpwuid (geteuid ());
323 q = (*p == PATH_SEP) ? p + 1 : "";
325 else
327 q = strchr (p, PATH_SEP);
328 if (!q)
330 passwd = getpwnam (p);
332 else
334 name = g_strndup (p, q - p);
335 passwd = getpwnam (name);
336 q++;
337 g_free (name);
341 /* If we can't figure the user name, leave tilde unexpanded */
342 if (!passwd)
343 return g_strdup (directory);
345 return g_strconcat (passwd->pw_dir, PATH_SEP_STR, q, (char *) NULL);
348 /* --------------------------------------------------------------------------------------------- */
350 * Creates a pipe to hold standard error for a later analysis.
351 * The pipe can hold 4096 bytes. Make sure no more is written
352 * or a deadlock might occur.
355 void
356 open_error_pipe (void)
358 if (pipe (error_pipe) < 0)
360 message (D_NORMAL, _("Warning"), _("Pipe failed"));
362 old_error = dup (2);
363 if (old_error < 0 || close (2) || dup (error_pipe[1]) != 2)
365 message (D_NORMAL, _("Warning"), _("Dup failed"));
367 close (error_pipe[0]);
368 error_pipe[0] = -1;
370 else
373 * Settng stderr in nonblocking mode as we close it earlier, than
374 * program stops. We try to read some error at program startup,
375 * but we should not block on it.
377 * TODO: make piped stdin/stderr poll()/select()able to get rid
378 * of following hack.
380 int fd_flags;
381 fd_flags = fcntl (error_pipe[0], F_GETFL, NULL);
382 if (fd_flags != -1)
384 fd_flags |= O_NONBLOCK;
385 if (fcntl (error_pipe[0], F_SETFL, fd_flags) == -1)
387 /* TODO: handle it somehow */
391 /* we never write there */
392 close (error_pipe[1]);
393 error_pipe[1] = -1;
396 /* --------------------------------------------------------------------------------------------- */
398 * Close a pipe
400 * @param error '-1' - ignore errors, '0' - display warning, '1' - display error
401 * @param text is prepended to the error message from the pipe
403 * @return not 0 if an error was displayed
407 close_error_pipe (int error, const char *text)
409 const char *title;
410 char msg[MAX_PIPE_SIZE];
411 int len = 0;
413 /* already closed */
414 if (error_pipe[0] == -1)
415 return 0;
417 if (error < 0 || (error > 0 && (error & D_ERROR) != 0))
418 title = MSG_ERROR;
419 else
420 title = _("Warning");
421 if (old_error >= 0)
423 if (dup2 (old_error, 2) == -1)
425 if (error < 0)
426 error = D_ERROR;
428 message (error, MSG_ERROR, _("Error dup'ing old error pipe"));
429 return 1;
431 close (old_error);
432 len = read (error_pipe[0], msg, MAX_PIPE_SIZE - 1);
434 if (len >= 0)
435 msg[len] = 0;
436 close (error_pipe[0]);
437 error_pipe[0] = -1;
439 if (error < 0)
440 return 0; /* Just ignore error message */
441 if (text == NULL)
443 if (len <= 0)
444 return 0; /* Nothing to show */
446 /* Show message from pipe */
447 message (error, title, "%s", msg);
449 else
451 /* Show given text and possible message from pipe */
452 message (error, title, "%s\n%s", text, msg);
454 return 1;
457 /* --------------------------------------------------------------------------------------------- */
459 * Canonicalize path, and return a new path. Do everything in place.
460 * The new path differs from path in:
461 * Multiple `/'s are collapsed to a single `/'.
462 * Leading `./'s and trailing `/.'s are removed.
463 * Trailing `/'s are removed.
464 * Non-leading `../'s and trailing `..'s are handled by removing
465 * portions of the path.
466 * Well formed UNC paths are modified only in the local part.
469 void
470 custom_canonicalize_pathname (char *path, CANON_PATH_FLAGS flags)
472 char *p, *s;
473 size_t len;
474 char *lpath = path; /* path without leading UNC part */
475 const size_t url_delim_len = strlen (VFS_PATH_URL_DELIMITER);
477 /* Detect and preserve UNC paths: //server/... */
478 if ((flags & CANON_PATH_GUARDUNC) && path[0] == PATH_SEP && path[1] == PATH_SEP)
480 p = path + 2;
481 while (p[0] && p[0] != '/')
482 p++;
483 if (p[0] == '/' && p > path + 2)
484 lpath = p;
487 if (!lpath[0] || !lpath[1])
488 return;
490 if (flags & CANON_PATH_JOINSLASHES)
492 /* Collapse multiple slashes */
493 p = lpath;
494 while (*p)
496 if (p[0] == PATH_SEP && p[1] == PATH_SEP && (p == lpath || *(p - 1) != ':'))
498 s = p + 1;
499 while (*(++s) == PATH_SEP);
500 str_move (p + 1, s);
502 p++;
506 if (flags & CANON_PATH_JOINSLASHES)
508 /* Collapse "/./" -> "/" */
509 p = lpath;
510 while (*p)
512 if (p[0] == PATH_SEP && p[1] == '.' && p[2] == PATH_SEP)
513 str_move (p, p + 2);
514 else
515 p++;
519 if (flags & CANON_PATH_REMSLASHDOTS)
521 /* Remove trailing slashes */
522 p = lpath + strlen (lpath) - 1;
523 while (p > lpath && *p == PATH_SEP)
525 if (p >= lpath - (url_delim_len + 1)
526 && strncmp (p - url_delim_len + 1, VFS_PATH_URL_DELIMITER, url_delim_len) == 0)
527 break;
528 *p-- = 0;
531 /* Remove leading "./" */
532 if (lpath[0] == '.' && lpath[1] == PATH_SEP)
534 if (lpath[2] == 0)
536 lpath[1] = 0;
537 return;
539 else
541 str_move (lpath, lpath + 2);
545 /* Remove trailing "/" or "/." */
546 len = strlen (lpath);
547 if (len < 2)
548 return;
549 if (lpath[len - 1] == PATH_SEP
550 && (len < url_delim_len
551 || strncmp (lpath + len - url_delim_len, VFS_PATH_URL_DELIMITER,
552 url_delim_len) != 0))
554 lpath[len - 1] = '\0';
556 else
558 if (lpath[len - 1] == '.' && lpath[len - 2] == PATH_SEP)
560 if (len == 2)
562 lpath[1] = '\0';
563 return;
565 else
567 lpath[len - 2] = '\0';
573 if (flags & CANON_PATH_REMDOUBLEDOTS)
575 #ifdef HAVE_CHARSET
576 const size_t enc_prefix_len = strlen (VFS_ENCODING_PREFIX);
577 #endif /* HAVE_CHARSET */
579 /* Collapse "/.." with the previous part of path */
580 p = lpath;
581 while (p[0] && p[1] && p[2])
583 if ((p[0] != PATH_SEP || p[1] != '.' || p[2] != '.') || (p[3] != PATH_SEP && p[3] != 0))
585 p++;
586 continue;
589 /* search for the previous token */
590 s = p - 1;
591 if (s >= lpath + url_delim_len - 2
592 && strncmp (s - url_delim_len + 2, VFS_PATH_URL_DELIMITER, url_delim_len) == 0)
594 s -= (url_delim_len - 2);
595 while (s >= lpath && *s-- != PATH_SEP);
598 while (s >= lpath)
600 if (s - url_delim_len > lpath
601 && strncmp (s - url_delim_len, VFS_PATH_URL_DELIMITER, url_delim_len) == 0)
603 char *vfs_prefix = s - url_delim_len;
604 struct vfs_class *vclass;
606 while (vfs_prefix > lpath && *--vfs_prefix != PATH_SEP);
607 if (*vfs_prefix == PATH_SEP)
608 vfs_prefix++;
609 *(s - url_delim_len) = '\0';
611 vclass = vfs_prefix_to_class (vfs_prefix);
612 *(s - url_delim_len) = *VFS_PATH_URL_DELIMITER;
614 if (vclass != NULL)
616 struct vfs_s_subclass *sub = (struct vfs_s_subclass *) vclass->data;
617 if (sub != NULL && sub->flags & VFS_S_REMOTE)
619 s = vfs_prefix;
620 continue;
625 if (*s == PATH_SEP)
626 break;
628 s--;
631 s++;
633 /* If the previous token is "..", we cannot collapse it */
634 if (s[0] == '.' && s[1] == '.' && s + 2 == p)
636 p += 3;
637 continue;
640 if (p[3] != 0)
642 if (s == lpath && *s == PATH_SEP)
644 /* "/../foo" -> "/foo" */
645 str_move (s + 1, p + 4);
647 else
649 /* "token/../foo" -> "foo" */
650 #ifdef HAVE_CHARSET
651 if ((strncmp (s, VFS_ENCODING_PREFIX, enc_prefix_len) == 0)
652 && (is_supported_encoding (s + enc_prefix_len)))
653 /* special case: remove encoding */
654 str_move (s, p + 1);
655 else
656 #endif /* HAVE_CHARSET */
657 str_move (s, p + 4);
659 p = (s > lpath) ? s - 1 : s;
660 continue;
663 /* trailing ".." */
664 if (s == lpath)
666 /* "token/.." -> "." */
667 if (lpath[0] != PATH_SEP)
669 lpath[0] = '.';
671 lpath[1] = 0;
673 else
675 /* "foo/token/.." -> "foo" */
676 if (s == lpath + 1)
677 s[0] = 0;
678 #ifdef HAVE_CHARSET
679 else if ((strncmp (s, VFS_ENCODING_PREFIX, enc_prefix_len) == 0)
680 && (is_supported_encoding (s + enc_prefix_len)))
682 /* special case: remove encoding */
683 s[0] = '.';
684 s[1] = '.';
685 s[2] = '\0';
687 /* search for the previous token */
688 /* s[-1] == PATH_SEP */
689 p = s - 1;
690 while (p >= lpath && *p != PATH_SEP)
691 p--;
693 if (p != NULL)
694 continue;
696 #endif /* HAVE_CHARSET */
697 else
699 if (s >= lpath + url_delim_len
700 && strncmp (s - url_delim_len, VFS_PATH_URL_DELIMITER, url_delim_len) == 0)
701 *s = '\0';
702 else
703 s[-1] = '\0';
705 break;
708 break;
713 /* --------------------------------------------------------------------------------------------- */
715 void
716 canonicalize_pathname (char *path)
718 custom_canonicalize_pathname (path, CANON_PATH_ALL);
721 /* --------------------------------------------------------------------------------------------- */
723 #ifdef HAVE_GET_PROCESS_STATS
725 gettimeofday (struct timeval *tp, void *tzp)
727 return get_process_stats (tp, PS_SELF, 0, 0);
729 #endif /* HAVE_GET_PROCESS_STATS */
731 /* --------------------------------------------------------------------------------------------- */
733 #ifndef HAVE_REALPATH
734 char *
735 mc_realpath (const char *path, char *resolved_path)
737 char copy_path[PATH_MAX];
738 char link_path[PATH_MAX];
739 char got_path[PATH_MAX];
740 char *new_path = got_path;
741 char *max_path;
742 int readlinks = 0;
743 int n;
745 /* Make a copy of the source path since we may need to modify it. */
746 if (strlen (path) >= PATH_MAX - 2)
748 errno = ENAMETOOLONG;
749 return NULL;
751 strcpy (copy_path, path);
752 path = copy_path;
753 max_path = copy_path + PATH_MAX - 2;
754 /* If it's a relative pathname use getwd for starters. */
755 if (*path != '/')
758 new_path = g_get_current_dir ();
759 if (new_path == NULL)
761 strcpy (got_path, "");
763 else
765 g_snprintf (got_path, PATH_MAX, "%s", new_path);
766 g_free (new_path);
767 new_path = got_path;
770 new_path += strlen (got_path);
771 if (new_path[-1] != '/')
772 *new_path++ = '/';
774 else
776 *new_path++ = '/';
777 path++;
779 /* Expand each slash-separated pathname component. */
780 while (*path != '\0')
782 /* Ignore stray "/". */
783 if (*path == '/')
785 path++;
786 continue;
788 if (*path == '.')
790 /* Ignore ".". */
791 if (path[1] == '\0' || path[1] == '/')
793 path++;
794 continue;
796 if (path[1] == '.')
798 if (path[2] == '\0' || path[2] == '/')
800 path += 2;
801 /* Ignore ".." at root. */
802 if (new_path == got_path + 1)
803 continue;
804 /* Handle ".." by backing up. */
805 while ((--new_path)[-1] != '/');
806 continue;
810 /* Safely copy the next pathname component. */
811 while (*path != '\0' && *path != '/')
813 if (path > max_path)
815 errno = ENAMETOOLONG;
816 return NULL;
818 *new_path++ = *path++;
820 #ifdef S_IFLNK
821 /* Protect against infinite loops. */
822 if (readlinks++ > MAXSYMLINKS)
824 errno = ELOOP;
825 return NULL;
827 /* See if latest pathname component is a symlink. */
828 *new_path = '\0';
829 n = readlink (got_path, link_path, PATH_MAX - 1);
830 if (n < 0)
832 /* EINVAL means the file exists but isn't a symlink. */
833 if (errno != EINVAL)
835 /* Make sure it's null terminated. */
836 *new_path = '\0';
837 strcpy (resolved_path, got_path);
838 return NULL;
841 else
843 /* Note: readlink doesn't add the null byte. */
844 link_path[n] = '\0';
845 if (*link_path == '/')
846 /* Start over for an absolute symlink. */
847 new_path = got_path;
848 else
849 /* Otherwise back up over this component. */
850 while (*(--new_path) != '/');
851 /* Safe sex check. */
852 if (strlen (path) + n >= PATH_MAX - 2)
854 errno = ENAMETOOLONG;
855 return NULL;
857 /* Insert symlink contents into path. */
858 strcat (link_path, path);
859 strcpy (copy_path, link_path);
860 path = copy_path;
862 #endif /* S_IFLNK */
863 *new_path++ = '/';
865 /* Delete trailing slash but don't whomp a lone slash. */
866 if (new_path != got_path + 1 && new_path[-1] == '/')
867 new_path--;
868 /* Make sure it's null terminated. */
869 *new_path = '\0';
870 strcpy (resolved_path, got_path);
871 return resolved_path;
873 #endif /* HAVE_REALPATH */
875 /* --------------------------------------------------------------------------------------------- */
877 * Return the index of the permissions triplet
882 get_user_permissions (struct stat *st)
884 static gboolean initialized = FALSE;
885 static gid_t *groups;
886 static int ngroups;
887 static uid_t uid;
888 int i;
890 if (!initialized)
892 uid = geteuid ();
894 ngroups = getgroups (0, NULL);
895 if (ngroups == -1)
896 ngroups = 0; /* ignore errors */
898 /* allocate space for one element in addition to what
899 * will be filled by getgroups(). */
900 groups = g_new (gid_t, ngroups + 1);
902 if (ngroups != 0)
904 ngroups = getgroups (ngroups, groups);
905 if (ngroups == -1)
906 ngroups = 0; /* ignore errors */
909 /* getgroups() may or may not return the effective group ID,
910 * so we always include it at the end of the list. */
911 groups[ngroups++] = getegid ();
913 initialized = TRUE;
916 if (st->st_uid == uid || uid == 0)
917 return 0;
919 for (i = 0; i < ngroups; i++)
921 if (st->st_gid == groups[i])
922 return 1;
925 return 2;
928 /* --------------------------------------------------------------------------------------------- */
930 * Build filename from arguments.
931 * Like to g_build_filename(), but respect VFS_PATH_URL_DELIMITER
934 char *
935 mc_build_filenamev (const char *first_element, va_list args)
937 gboolean absolute;
938 const char *element = first_element;
939 GString *path;
940 char *ret;
942 if (element == NULL)
943 return NULL;
945 path = g_string_new ("");
947 absolute = (*first_element != '\0' && *first_element == PATH_SEP);
951 if (*element == '\0')
952 element = va_arg (args, char *);
953 else
955 char *tmp_element;
956 size_t len;
957 const char *start;
959 tmp_element = g_strdup (element);
961 element = va_arg (args, char *);
963 canonicalize_pathname (tmp_element);
964 len = strlen (tmp_element);
965 start = (tmp_element[0] == PATH_SEP) ? tmp_element + 1 : tmp_element;
967 g_string_append (path, start);
968 if (tmp_element[len - 1] != PATH_SEP && element != NULL)
969 g_string_append_c (path, PATH_SEP);
971 g_free (tmp_element);
974 while (element != NULL);
976 if (absolute)
977 g_string_prepend_c (path, PATH_SEP);
979 ret = g_string_free (path, FALSE);
980 canonicalize_pathname (ret);
982 return ret;
985 /* --------------------------------------------------------------------------------------------- */
987 * Build filename from arguments.
988 * Like to g_build_filename(), but respect VFS_PATH_URL_DELIMITER
991 char *
992 mc_build_filename (const char *first_element, ...)
994 va_list args;
995 char *ret;
997 if (first_element == NULL)
998 return NULL;
1000 va_start (args, first_element);
1001 ret = mc_build_filenamev (first_element, args);
1002 va_end (args);
1003 return ret;
1006 /* --------------------------------------------------------------------------------------------- */