Fix of DOXYGEN docs: @return instead of @returns
[midnight-commander.git] / lib / utilunix.c
blobc3c670442ce30490da7ad754f54873ff08d4a8a8
1 /*
2 Various utilities - Unix variants
4 Copyright (C) 1994, 1995, 1996, 1998, 1999, 2000, 2001, 2002, 2003,
5 2004, 2005, 2007, 2011
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 /* --------------------------------------------------------------------------------------------- */
202 my_system (int flags, const char *shell, const char *command)
204 struct sigaction ignore, save_intr, save_quit, save_stop;
205 pid_t pid;
206 int status = 0;
208 ignore.sa_handler = SIG_IGN;
209 sigemptyset (&ignore.sa_mask);
210 ignore.sa_flags = 0;
212 sigaction (SIGINT, &ignore, &save_intr);
213 sigaction (SIGQUIT, &ignore, &save_quit);
215 /* Restore the original SIGTSTP handler, we don't want ncurses' */
216 /* handler messing the screen after the SIGCONT */
217 sigaction (SIGTSTP, &startup_handler, &save_stop);
219 pid = fork ();
220 if (pid < 0)
222 fprintf (stderr, "\n\nfork () = -1\n");
223 status = -1;
225 else if (pid == 0)
227 signal (SIGINT, SIG_DFL);
228 signal (SIGQUIT, SIG_DFL);
229 signal (SIGTSTP, SIG_DFL);
230 signal (SIGCHLD, SIG_DFL);
232 if (flags & EXECUTE_AS_SHELL)
233 execl (shell, shell, "-c", command, (char *) NULL);
234 else
236 gchar **shell_tokens;
237 const gchar *only_cmd;
239 shell_tokens = g_strsplit (shell, " ", 2);
240 if (shell_tokens == NULL)
241 only_cmd = shell;
242 else
243 only_cmd = (*shell_tokens != NULL) ? *shell_tokens : shell;
245 execlp (only_cmd, shell, command, (char *) NULL);
248 execlp will replace current process,
249 therefore no sence in call of g_strfreev().
250 But this keeped for estetic reason :)
252 g_strfreev (shell_tokens);
256 _exit (127); /* Exec error */
258 else
260 while (TRUE)
262 if (waitpid (pid, &status, 0) > 0)
264 status = WEXITSTATUS (status);
265 break;
267 if (errno != EINTR)
269 status = -1;
270 break;
274 sigaction (SIGINT, &save_intr, NULL);
275 sigaction (SIGQUIT, &save_quit, NULL);
276 sigaction (SIGTSTP, &save_stop, NULL);
278 return status;
282 /* --------------------------------------------------------------------------------------------- */
285 * Perform tilde expansion if possible.
287 * @param directory pointer to the path
289 * @return newly allocated string, even if it's unchanged.
292 char *
293 tilde_expand (const char *directory)
295 struct passwd *passwd;
296 const char *p, *q;
297 char *name;
299 if (*directory != '~')
300 return g_strdup (directory);
302 p = directory + 1;
304 /* d = "~" or d = "~/" */
305 if (!(*p) || (*p == PATH_SEP))
307 passwd = getpwuid (geteuid ());
308 q = (*p == PATH_SEP) ? p + 1 : "";
310 else
312 q = strchr (p, PATH_SEP);
313 if (!q)
315 passwd = getpwnam (p);
317 else
319 name = g_strndup (p, q - p);
320 passwd = getpwnam (name);
321 q++;
322 g_free (name);
326 /* If we can't figure the user name, leave tilde unexpanded */
327 if (!passwd)
328 return g_strdup (directory);
330 return g_strconcat (passwd->pw_dir, PATH_SEP_STR, q, (char *) NULL);
333 /* --------------------------------------------------------------------------------------------- */
335 * Creates a pipe to hold standard error for a later analysis.
336 * The pipe can hold 4096 bytes. Make sure no more is written
337 * or a deadlock might occur.
340 void
341 open_error_pipe (void)
343 if (pipe (error_pipe) < 0)
345 message (D_NORMAL, _("Warning"), _("Pipe failed"));
347 old_error = dup (2);
348 if (old_error < 0 || close (2) || dup (error_pipe[1]) != 2)
350 message (D_NORMAL, _("Warning"), _("Dup failed"));
352 close (error_pipe[0]);
353 error_pipe[0] = -1;
355 else
358 * Settng stderr in nonblocking mode as we close it earlier, than
359 * program stops. We try to read some error at program startup,
360 * but we should not block on it.
362 * TODO: make piped stdin/stderr poll()/select()able to get rid
363 * of following hack.
365 int fd_flags;
366 fd_flags = fcntl (error_pipe[0], F_GETFL, NULL);
367 if (fd_flags != -1)
369 fd_flags |= O_NONBLOCK;
370 if (fcntl (error_pipe[0], F_SETFL, fd_flags) == -1)
372 /* TODO: handle it somehow */
376 /* we never write there */
377 close (error_pipe[1]);
378 error_pipe[1] = -1;
381 /* --------------------------------------------------------------------------------------------- */
383 * Close a pipe
385 * @param error '-1' - ignore errors, '0' - display warning, '1' - display error
386 * @param text is prepended to the error message from the pipe
388 * @return not 0 if an error was displayed
392 close_error_pipe (int error, const char *text)
394 const char *title;
395 char msg[MAX_PIPE_SIZE];
396 int len = 0;
398 /* already closed */
399 if (error_pipe[0] == -1)
400 return 0;
402 if (error < 0 || (error > 0 && (error & D_ERROR) != 0))
403 title = MSG_ERROR;
404 else
405 title = _("Warning");
406 if (old_error >= 0)
408 if (dup2 (old_error, 2) == -1)
410 if (error < 0)
411 error = D_ERROR;
413 message (error, MSG_ERROR, _("Error dup'ing old error pipe"));
414 return 1;
416 close (old_error);
417 len = read (error_pipe[0], msg, MAX_PIPE_SIZE - 1);
419 if (len >= 0)
420 msg[len] = 0;
421 close (error_pipe[0]);
422 error_pipe[0] = -1;
424 if (error < 0)
425 return 0; /* Just ignore error message */
426 if (text == NULL)
428 if (len <= 0)
429 return 0; /* Nothing to show */
431 /* Show message from pipe */
432 message (error, title, "%s", msg);
434 else
436 /* Show given text and possible message from pipe */
437 message (error, title, "%s\n%s", text, msg);
439 return 1;
442 /* --------------------------------------------------------------------------------------------- */
444 * Canonicalize path, and return a new path. Do everything in place.
445 * The new path differs from path in:
446 * Multiple `/'s are collapsed to a single `/'.
447 * Leading `./'s and trailing `/.'s are removed.
448 * Trailing `/'s are removed.
449 * Non-leading `../'s and trailing `..'s are handled by removing
450 * portions of the path.
451 * Well formed UNC paths are modified only in the local part.
454 void
455 custom_canonicalize_pathname (char *path, CANON_PATH_FLAGS flags)
457 char *p, *s;
458 size_t len;
459 char *lpath = path; /* path without leading UNC part */
460 const size_t url_delim_len = strlen (VFS_PATH_URL_DELIMITER);
462 /* Detect and preserve UNC paths: //server/... */
463 if ((flags & CANON_PATH_GUARDUNC) && path[0] == PATH_SEP && path[1] == PATH_SEP)
465 p = path + 2;
466 while (p[0] && p[0] != '/')
467 p++;
468 if (p[0] == '/' && p > path + 2)
469 lpath = p;
472 if (!lpath[0] || !lpath[1])
473 return;
475 if (flags & CANON_PATH_JOINSLASHES)
477 /* Collapse multiple slashes */
478 p = lpath;
479 while (*p)
481 if (p[0] == PATH_SEP && p[1] == PATH_SEP && (p == lpath || *(p - 1) != ':'))
483 s = p + 1;
484 while (*(++s) == PATH_SEP);
485 str_move (p + 1, s);
487 p++;
491 if (flags & CANON_PATH_JOINSLASHES)
493 /* Collapse "/./" -> "/" */
494 p = lpath;
495 while (*p)
497 if (p[0] == PATH_SEP && p[1] == '.' && p[2] == PATH_SEP)
498 str_move (p, p + 2);
499 else
500 p++;
504 if (flags & CANON_PATH_REMSLASHDOTS)
506 /* Remove trailing slashes */
507 p = lpath + strlen (lpath) - 1;
508 while (p > lpath && *p == PATH_SEP)
510 if (p >= lpath - (url_delim_len + 1)
511 && strncmp (p - url_delim_len + 1, VFS_PATH_URL_DELIMITER, url_delim_len) == 0)
512 break;
513 *p-- = 0;
516 /* Remove leading "./" */
517 if (lpath[0] == '.' && lpath[1] == PATH_SEP)
519 if (lpath[2] == 0)
521 lpath[1] = 0;
522 return;
524 else
526 str_move (lpath, lpath + 2);
530 /* Remove trailing "/" or "/." */
531 len = strlen (lpath);
532 if (len < 2)
533 return;
534 if (lpath[len - 1] == PATH_SEP
535 && (len < url_delim_len
536 || strncmp (lpath + len - url_delim_len, VFS_PATH_URL_DELIMITER,
537 url_delim_len) != 0))
539 lpath[len - 1] = '\0';
541 else
543 if (lpath[len - 1] == '.' && lpath[len - 2] == PATH_SEP)
545 if (len == 2)
547 lpath[1] = '\0';
548 return;
550 else
552 lpath[len - 2] = '\0';
558 if (flags & CANON_PATH_REMDOUBLEDOTS)
560 #ifdef HAVE_CHARSET
561 const size_t enc_prefix_len = strlen (VFS_ENCODING_PREFIX);
562 #endif /* HAVE_CHARSET */
564 /* Collapse "/.." with the previous part of path */
565 p = lpath;
566 while (p[0] && p[1] && p[2])
568 if ((p[0] != PATH_SEP || p[1] != '.' || p[2] != '.') || (p[3] != PATH_SEP && p[3] != 0))
570 p++;
571 continue;
574 /* search for the previous token */
575 s = p - 1;
576 if (s >= lpath + url_delim_len - 2
577 && strncmp (s - url_delim_len + 2, VFS_PATH_URL_DELIMITER, url_delim_len) == 0)
579 s -= (url_delim_len - 2);
580 while (s >= lpath && *s-- != PATH_SEP);
583 while (s >= lpath)
585 if (s - url_delim_len > lpath
586 && strncmp (s - url_delim_len, VFS_PATH_URL_DELIMITER, url_delim_len) == 0)
588 char *vfs_prefix = s - url_delim_len;
589 struct vfs_class *vclass;
591 while (vfs_prefix > lpath && *--vfs_prefix != PATH_SEP);
592 if (*vfs_prefix == PATH_SEP)
593 vfs_prefix++;
594 *(s - url_delim_len) = '\0';
596 vclass = vfs_prefix_to_class (vfs_prefix);
597 *(s - url_delim_len) = *VFS_PATH_URL_DELIMITER;
599 if (vclass != NULL)
601 struct vfs_s_subclass *sub = (struct vfs_s_subclass *) vclass->data;
602 if (sub != NULL && sub->flags & VFS_S_REMOTE)
604 s = vfs_prefix;
605 continue;
610 if (*s == PATH_SEP)
611 break;
613 s--;
616 s++;
618 /* If the previous token is "..", we cannot collapse it */
619 if (s[0] == '.' && s[1] == '.' && s + 2 == p)
621 p += 3;
622 continue;
625 if (p[3] != 0)
627 if (s == lpath && *s == PATH_SEP)
629 /* "/../foo" -> "/foo" */
630 str_move (s + 1, p + 4);
632 else
634 /* "token/../foo" -> "foo" */
635 #ifdef HAVE_CHARSET
636 if ((strncmp (s, VFS_ENCODING_PREFIX, enc_prefix_len) == 0)
637 && (is_supported_encoding (s + enc_prefix_len)))
638 /* special case: remove encoding */
639 str_move (s, p + 1);
640 else
641 #endif /* HAVE_CHARSET */
642 str_move (s, p + 4);
644 p = (s > lpath) ? s - 1 : s;
645 continue;
648 /* trailing ".." */
649 if (s == lpath)
651 /* "token/.." -> "." */
652 if (lpath[0] != PATH_SEP)
654 lpath[0] = '.';
656 lpath[1] = 0;
658 else
660 /* "foo/token/.." -> "foo" */
661 if (s == lpath + 1)
662 s[0] = 0;
663 #ifdef HAVE_CHARSET
664 else if ((strncmp (s, VFS_ENCODING_PREFIX, enc_prefix_len) == 0)
665 && (is_supported_encoding (s + enc_prefix_len)))
667 /* special case: remove encoding */
668 s[0] = '.';
669 s[1] = '.';
670 s[2] = '\0';
672 /* search for the previous token */
673 /* s[-1] == PATH_SEP */
674 p = s - 1;
675 while (p >= lpath && *p != PATH_SEP)
676 p--;
678 if (p != NULL)
679 continue;
681 #endif /* HAVE_CHARSET */
682 else
684 if (s >= lpath + url_delim_len
685 && strncmp (s - url_delim_len, VFS_PATH_URL_DELIMITER, url_delim_len) == 0)
686 *s = '\0';
687 else
688 s[-1] = '\0';
690 break;
693 break;
698 /* --------------------------------------------------------------------------------------------- */
700 void
701 canonicalize_pathname (char *path)
703 custom_canonicalize_pathname (path, CANON_PATH_ALL);
706 /* --------------------------------------------------------------------------------------------- */
708 #ifdef HAVE_GET_PROCESS_STATS
710 gettimeofday (struct timeval *tp, void *tzp)
712 return get_process_stats (tp, PS_SELF, 0, 0);
714 #endif /* HAVE_GET_PROCESS_STATS */
716 /* --------------------------------------------------------------------------------------------- */
718 #ifndef HAVE_REALPATH
719 char *
720 mc_realpath (const char *path, char *resolved_path)
722 char copy_path[PATH_MAX];
723 char link_path[PATH_MAX];
724 char got_path[PATH_MAX];
725 char *new_path = got_path;
726 char *max_path;
727 int readlinks = 0;
728 int n;
730 /* Make a copy of the source path since we may need to modify it. */
731 if (strlen (path) >= PATH_MAX - 2)
733 errno = ENAMETOOLONG;
734 return NULL;
736 strcpy (copy_path, path);
737 path = copy_path;
738 max_path = copy_path + PATH_MAX - 2;
739 /* If it's a relative pathname use getwd for starters. */
740 if (*path != '/')
743 new_path = g_get_current_dir ();
744 if (new_path == NULL)
746 strcpy (got_path, "");
748 else
750 g_snprintf (got_path, PATH_MAX, "%s", new_path);
751 g_free (new_path);
752 new_path = got_path;
755 new_path += strlen (got_path);
756 if (new_path[-1] != '/')
757 *new_path++ = '/';
759 else
761 *new_path++ = '/';
762 path++;
764 /* Expand each slash-separated pathname component. */
765 while (*path != '\0')
767 /* Ignore stray "/". */
768 if (*path == '/')
770 path++;
771 continue;
773 if (*path == '.')
775 /* Ignore ".". */
776 if (path[1] == '\0' || path[1] == '/')
778 path++;
779 continue;
781 if (path[1] == '.')
783 if (path[2] == '\0' || path[2] == '/')
785 path += 2;
786 /* Ignore ".." at root. */
787 if (new_path == got_path + 1)
788 continue;
789 /* Handle ".." by backing up. */
790 while ((--new_path)[-1] != '/');
791 continue;
795 /* Safely copy the next pathname component. */
796 while (*path != '\0' && *path != '/')
798 if (path > max_path)
800 errno = ENAMETOOLONG;
801 return NULL;
803 *new_path++ = *path++;
805 #ifdef S_IFLNK
806 /* Protect against infinite loops. */
807 if (readlinks++ > MAXSYMLINKS)
809 errno = ELOOP;
810 return NULL;
812 /* See if latest pathname component is a symlink. */
813 *new_path = '\0';
814 n = readlink (got_path, link_path, PATH_MAX - 1);
815 if (n < 0)
817 /* EINVAL means the file exists but isn't a symlink. */
818 if (errno != EINVAL)
820 /* Make sure it's null terminated. */
821 *new_path = '\0';
822 strcpy (resolved_path, got_path);
823 return NULL;
826 else
828 /* Note: readlink doesn't add the null byte. */
829 link_path[n] = '\0';
830 if (*link_path == '/')
831 /* Start over for an absolute symlink. */
832 new_path = got_path;
833 else
834 /* Otherwise back up over this component. */
835 while (*(--new_path) != '/');
836 /* Safe sex check. */
837 if (strlen (path) + n >= PATH_MAX - 2)
839 errno = ENAMETOOLONG;
840 return NULL;
842 /* Insert symlink contents into path. */
843 strcat (link_path, path);
844 strcpy (copy_path, link_path);
845 path = copy_path;
847 #endif /* S_IFLNK */
848 *new_path++ = '/';
850 /* Delete trailing slash but don't whomp a lone slash. */
851 if (new_path != got_path + 1 && new_path[-1] == '/')
852 new_path--;
853 /* Make sure it's null terminated. */
854 *new_path = '\0';
855 strcpy (resolved_path, got_path);
856 return resolved_path;
858 #endif /* HAVE_REALPATH */
860 /* --------------------------------------------------------------------------------------------- */
862 * Return the index of the permissions triplet
867 get_user_permissions (struct stat *st)
869 static gboolean initialized = FALSE;
870 static gid_t *groups;
871 static int ngroups;
872 static uid_t uid;
873 int i;
875 if (!initialized)
877 uid = geteuid ();
879 ngroups = getgroups (0, NULL);
880 if (ngroups == -1)
881 ngroups = 0; /* ignore errors */
883 /* allocate space for one element in addition to what
884 * will be filled by getgroups(). */
885 groups = g_new (gid_t, ngroups + 1);
887 if (ngroups != 0)
889 ngroups = getgroups (ngroups, groups);
890 if (ngroups == -1)
891 ngroups = 0; /* ignore errors */
894 /* getgroups() may or may not return the effective group ID,
895 * so we always include it at the end of the list. */
896 groups[ngroups++] = getegid ();
898 initialized = TRUE;
901 if (st->st_uid == uid || uid == 0)
902 return 0;
904 for (i = 0; i < ngroups; i++)
906 if (st->st_gid == groups[i])
907 return 1;
910 return 2;
913 /* --------------------------------------------------------------------------------------------- */
915 * Build filename from arguments.
916 * Like to g_build_filename(), but respect VFS_PATH_URL_DELIMITER
919 char *
920 mc_build_filenamev (const char *first_element, va_list args)
922 gboolean absolute;
923 const char *element = first_element;
924 GString *path;
925 char *ret;
927 if (element == NULL)
928 return NULL;
930 path = g_string_new ("");
932 absolute = (*first_element != '\0' && *first_element == PATH_SEP);
936 if (*element == '\0')
937 element = va_arg (args, char *);
938 else
940 char *tmp_element;
941 size_t len;
942 const char *start;
944 tmp_element = g_strdup (element);
946 element = va_arg (args, char *);
948 canonicalize_pathname (tmp_element);
949 len = strlen (tmp_element);
950 start = (tmp_element[0] == PATH_SEP) ? tmp_element + 1 : tmp_element;
952 g_string_append (path, start);
953 if (tmp_element[len - 1] != PATH_SEP && element != NULL)
954 g_string_append_c (path, PATH_SEP);
956 g_free (tmp_element);
959 while (element != NULL);
961 if (absolute)
962 g_string_prepend_c (path, PATH_SEP);
964 ret = g_string_free (path, FALSE);
965 canonicalize_pathname (ret);
967 return ret;
970 /* --------------------------------------------------------------------------------------------- */
972 * Build filename from arguments.
973 * Like to g_build_filename(), but respect VFS_PATH_URL_DELIMITER
976 char *
977 mc_build_filename (const char *first_element, ...)
979 va_list args;
980 char *ret;
982 if (first_element == NULL)
983 return NULL;
985 va_start (args, first_element);
986 ret = mc_build_filenamev (first_element, args);
987 va_end (args);
988 return ret;
991 /* --------------------------------------------------------------------------------------------- */