Ticket #2877: code cleanup before 4.8.1.6 release.
[midnight-commander.git] / lib / utilunix.c
blob38216105721913a6daa88bb36487acd19054342b
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 /* --------------------------------------------------------------------------------------------- */
284 * Perform tilde expansion if possible.
285 * Always return a newly allocated string, even if it's unchanged.
288 char *
289 tilde_expand (const char *directory)
291 struct passwd *passwd;
292 const char *p, *q;
293 char *name;
295 if (*directory != '~')
296 return g_strdup (directory);
298 p = directory + 1;
300 /* d = "~" or d = "~/" */
301 if (!(*p) || (*p == PATH_SEP))
303 passwd = getpwuid (geteuid ());
304 q = (*p == PATH_SEP) ? p + 1 : "";
306 else
308 q = strchr (p, PATH_SEP);
309 if (!q)
311 passwd = getpwnam (p);
313 else
315 name = g_strndup (p, q - p);
316 passwd = getpwnam (name);
317 q++;
318 g_free (name);
322 /* If we can't figure the user name, leave tilde unexpanded */
323 if (!passwd)
324 return g_strdup (directory);
326 return g_strconcat (passwd->pw_dir, PATH_SEP_STR, q, (char *) NULL);
329 /* --------------------------------------------------------------------------------------------- */
331 * Creates a pipe to hold standard error for a later analysis.
332 * The pipe can hold 4096 bytes. Make sure no more is written
333 * or a deadlock might occur.
336 void
337 open_error_pipe (void)
339 if (pipe (error_pipe) < 0)
341 message (D_NORMAL, _("Warning"), _("Pipe failed"));
343 old_error = dup (2);
344 if (old_error < 0 || close (2) || dup (error_pipe[1]) != 2)
346 message (D_NORMAL, _("Warning"), _("Dup failed"));
348 close (error_pipe[0]);
349 error_pipe[0] = -1;
351 else
354 * Settng stderr in nonblocking mode as we close it earlier, than
355 * program stops. We try to read some error at program startup,
356 * but we should not block on it.
358 * TODO: make piped stdin/stderr poll()/select()able to get rid
359 * of following hack.
361 int fd_flags;
362 fd_flags = fcntl (error_pipe[0], F_GETFL, NULL);
363 if (fd_flags != -1)
365 fd_flags |= O_NONBLOCK;
366 if (fcntl (error_pipe[0], F_SETFL, fd_flags) == -1)
368 /* TODO: handle it somehow */
372 /* we never write there */
373 close (error_pipe[1]);
374 error_pipe[1] = -1;
377 /* --------------------------------------------------------------------------------------------- */
379 * Returns true if an error was displayed
380 * error: -1 - ignore errors, 0 - display warning, 1 - display error
381 * text is prepended to the error message from the pipe
385 close_error_pipe (int error, const char *text)
387 const char *title;
388 char msg[MAX_PIPE_SIZE];
389 int len = 0;
391 /* already closed */
392 if (error_pipe[0] == -1)
393 return 0;
395 if (error < 0 || (error > 0 && (error & D_ERROR) != 0))
396 title = MSG_ERROR;
397 else
398 title = _("Warning");
399 if (old_error >= 0)
401 if (dup2 (old_error, 2) == -1)
403 if (error < 0)
404 error = D_ERROR;
406 message (error, MSG_ERROR, _("Error dup'ing old error pipe"));
407 return 1;
409 close (old_error);
410 len = read (error_pipe[0], msg, MAX_PIPE_SIZE - 1);
412 if (len >= 0)
413 msg[len] = 0;
414 close (error_pipe[0]);
415 error_pipe[0] = -1;
417 if (error < 0)
418 return 0; /* Just ignore error message */
419 if (text == NULL)
421 if (len <= 0)
422 return 0; /* Nothing to show */
424 /* Show message from pipe */
425 message (error, title, "%s", msg);
427 else
429 /* Show given text and possible message from pipe */
430 message (error, title, "%s\n%s", text, msg);
432 return 1;
435 /* --------------------------------------------------------------------------------------------- */
437 * Canonicalize path, and return a new path. Do everything in place.
438 * The new path differs from path in:
439 * Multiple `/'s are collapsed to a single `/'.
440 * Leading `./'s and trailing `/.'s are removed.
441 * Trailing `/'s are removed.
442 * Non-leading `../'s and trailing `..'s are handled by removing
443 * portions of the path.
444 * Well formed UNC paths are modified only in the local part.
447 void
448 custom_canonicalize_pathname (char *path, CANON_PATH_FLAGS flags)
450 char *p, *s;
451 size_t len;
452 char *lpath = path; /* path without leading UNC part */
453 const size_t url_delim_len = strlen (VFS_PATH_URL_DELIMITER);
455 /* Detect and preserve UNC paths: //server/... */
456 if ((flags & CANON_PATH_GUARDUNC) && path[0] == PATH_SEP && path[1] == PATH_SEP)
458 p = path + 2;
459 while (p[0] && p[0] != '/')
460 p++;
461 if (p[0] == '/' && p > path + 2)
462 lpath = p;
465 if (!lpath[0] || !lpath[1])
466 return;
468 if (flags & CANON_PATH_JOINSLASHES)
470 /* Collapse multiple slashes */
471 p = lpath;
472 while (*p)
474 if (p[0] == PATH_SEP && p[1] == PATH_SEP && (p == lpath || *(p - 1) != ':'))
476 s = p + 1;
477 while (*(++s) == PATH_SEP);
478 str_move (p + 1, s);
480 p++;
484 if (flags & CANON_PATH_JOINSLASHES)
486 /* Collapse "/./" -> "/" */
487 p = lpath;
488 while (*p)
490 if (p[0] == PATH_SEP && p[1] == '.' && p[2] == PATH_SEP)
491 str_move (p, p + 2);
492 else
493 p++;
497 if (flags & CANON_PATH_REMSLASHDOTS)
499 /* Remove trailing slashes */
500 p = lpath + strlen (lpath) - 1;
501 while (p > lpath && *p == PATH_SEP)
503 if (p >= lpath - (url_delim_len + 1)
504 && strncmp (p - url_delim_len + 1, VFS_PATH_URL_DELIMITER, url_delim_len) == 0)
505 break;
506 *p-- = 0;
509 /* Remove leading "./" */
510 if (lpath[0] == '.' && lpath[1] == PATH_SEP)
512 if (lpath[2] == 0)
514 lpath[1] = 0;
515 return;
517 else
519 str_move (lpath, lpath + 2);
523 /* Remove trailing "/" or "/." */
524 len = strlen (lpath);
525 if (len < 2)
526 return;
527 if (lpath[len - 1] == PATH_SEP
528 && (len < url_delim_len
529 || strncmp (lpath + len - url_delim_len, VFS_PATH_URL_DELIMITER,
530 url_delim_len) != 0))
532 lpath[len - 1] = '\0';
534 else
536 if (lpath[len - 1] == '.' && lpath[len - 2] == PATH_SEP)
538 if (len == 2)
540 lpath[1] = '\0';
541 return;
543 else
545 lpath[len - 2] = '\0';
551 if (flags & CANON_PATH_REMDOUBLEDOTS)
553 #ifdef HAVE_CHARSET
554 const size_t enc_prefix_len = strlen (VFS_ENCODING_PREFIX);
555 #endif /* HAVE_CHARSET */
557 /* Collapse "/.." with the previous part of path */
558 p = lpath;
559 while (p[0] && p[1] && p[2])
561 if ((p[0] != PATH_SEP || p[1] != '.' || p[2] != '.') || (p[3] != PATH_SEP && p[3] != 0))
563 p++;
564 continue;
567 /* search for the previous token */
568 s = p - 1;
569 if (s >= lpath + url_delim_len - 2
570 && strncmp (s - url_delim_len + 2, VFS_PATH_URL_DELIMITER, url_delim_len) == 0)
572 s -= (url_delim_len - 2);
573 while (s >= lpath && *s-- != PATH_SEP);
576 while (s >= lpath)
578 if (s - url_delim_len > lpath
579 && strncmp (s - url_delim_len, VFS_PATH_URL_DELIMITER, url_delim_len) == 0)
581 char *vfs_prefix = s - url_delim_len;
582 struct vfs_class *vclass;
584 while (vfs_prefix > lpath && *--vfs_prefix != PATH_SEP);
585 if (*vfs_prefix == PATH_SEP)
586 vfs_prefix++;
587 *(s - url_delim_len) = '\0';
589 vclass = vfs_prefix_to_class (vfs_prefix);
590 *(s - url_delim_len) = *VFS_PATH_URL_DELIMITER;
592 if (vclass != NULL)
594 struct vfs_s_subclass *sub = (struct vfs_s_subclass *) vclass->data;
595 if (sub != NULL && sub->flags & VFS_S_REMOTE)
597 s = vfs_prefix;
598 continue;
603 if (*s == PATH_SEP)
604 break;
606 s--;
609 s++;
611 /* If the previous token is "..", we cannot collapse it */
612 if (s[0] == '.' && s[1] == '.' && s + 2 == p)
614 p += 3;
615 continue;
618 if (p[3] != 0)
620 if (s == lpath && *s == PATH_SEP)
622 /* "/../foo" -> "/foo" */
623 str_move (s + 1, p + 4);
625 else
627 /* "token/../foo" -> "foo" */
628 #ifdef HAVE_CHARSET
629 if ((strncmp (s, VFS_ENCODING_PREFIX, enc_prefix_len) == 0)
630 && (is_supported_encoding (s + enc_prefix_len)))
631 /* special case: remove encoding */
632 str_move (s, p + 1);
633 else
634 #endif /* HAVE_CHARSET */
635 str_move (s, p + 4);
637 p = (s > lpath) ? s - 1 : s;
638 continue;
641 /* trailing ".." */
642 if (s == lpath)
644 /* "token/.." -> "." */
645 if (lpath[0] != PATH_SEP)
647 lpath[0] = '.';
649 lpath[1] = 0;
651 else
653 /* "foo/token/.." -> "foo" */
654 if (s == lpath + 1)
655 s[0] = 0;
656 #ifdef HAVE_CHARSET
657 else if ((strncmp (s, VFS_ENCODING_PREFIX, enc_prefix_len) == 0)
658 && (is_supported_encoding (s + enc_prefix_len)))
660 /* special case: remove encoding */
661 s[0] = '.';
662 s[1] = '.';
663 s[2] = '\0';
665 /* search for the previous token */
666 /* s[-1] == PATH_SEP */
667 p = s - 1;
668 while (p >= lpath && *p != PATH_SEP)
669 p--;
671 if (p != NULL)
672 continue;
674 #endif /* HAVE_CHARSET */
675 else
677 if (s >= lpath + url_delim_len
678 && strncmp (s - url_delim_len, VFS_PATH_URL_DELIMITER, url_delim_len) == 0)
679 *s = '\0';
680 else
681 s[-1] = '\0';
683 break;
686 break;
691 /* --------------------------------------------------------------------------------------------- */
693 void
694 canonicalize_pathname (char *path)
696 custom_canonicalize_pathname (path, CANON_PATH_ALL);
699 /* --------------------------------------------------------------------------------------------- */
701 #ifdef HAVE_GET_PROCESS_STATS
703 gettimeofday (struct timeval *tp, void *tzp)
705 return get_process_stats (tp, PS_SELF, 0, 0);
707 #endif /* HAVE_GET_PROCESS_STATS */
709 /* --------------------------------------------------------------------------------------------- */
711 #ifndef HAVE_REALPATH
712 char *
713 mc_realpath (const char *path, char *resolved_path)
715 char copy_path[PATH_MAX];
716 char link_path[PATH_MAX];
717 char got_path[PATH_MAX];
718 char *new_path = got_path;
719 char *max_path;
720 int readlinks = 0;
721 int n;
723 /* Make a copy of the source path since we may need to modify it. */
724 if (strlen (path) >= PATH_MAX - 2)
726 errno = ENAMETOOLONG;
727 return NULL;
729 strcpy (copy_path, path);
730 path = copy_path;
731 max_path = copy_path + PATH_MAX - 2;
732 /* If it's a relative pathname use getwd for starters. */
733 if (*path != '/')
736 new_path = g_get_current_dir ();
737 if (new_path == NULL)
739 strcpy (got_path, "");
741 else
743 g_snprintf (got_path, PATH_MAX, "%s", new_path);
744 g_free (new_path);
745 new_path = got_path;
748 new_path += strlen (got_path);
749 if (new_path[-1] != '/')
750 *new_path++ = '/';
752 else
754 *new_path++ = '/';
755 path++;
757 /* Expand each slash-separated pathname component. */
758 while (*path != '\0')
760 /* Ignore stray "/". */
761 if (*path == '/')
763 path++;
764 continue;
766 if (*path == '.')
768 /* Ignore ".". */
769 if (path[1] == '\0' || path[1] == '/')
771 path++;
772 continue;
774 if (path[1] == '.')
776 if (path[2] == '\0' || path[2] == '/')
778 path += 2;
779 /* Ignore ".." at root. */
780 if (new_path == got_path + 1)
781 continue;
782 /* Handle ".." by backing up. */
783 while ((--new_path)[-1] != '/');
784 continue;
788 /* Safely copy the next pathname component. */
789 while (*path != '\0' && *path != '/')
791 if (path > max_path)
793 errno = ENAMETOOLONG;
794 return NULL;
796 *new_path++ = *path++;
798 #ifdef S_IFLNK
799 /* Protect against infinite loops. */
800 if (readlinks++ > MAXSYMLINKS)
802 errno = ELOOP;
803 return NULL;
805 /* See if latest pathname component is a symlink. */
806 *new_path = '\0';
807 n = readlink (got_path, link_path, PATH_MAX - 1);
808 if (n < 0)
810 /* EINVAL means the file exists but isn't a symlink. */
811 if (errno != EINVAL)
813 /* Make sure it's null terminated. */
814 *new_path = '\0';
815 strcpy (resolved_path, got_path);
816 return NULL;
819 else
821 /* Note: readlink doesn't add the null byte. */
822 link_path[n] = '\0';
823 if (*link_path == '/')
824 /* Start over for an absolute symlink. */
825 new_path = got_path;
826 else
827 /* Otherwise back up over this component. */
828 while (*(--new_path) != '/');
829 /* Safe sex check. */
830 if (strlen (path) + n >= PATH_MAX - 2)
832 errno = ENAMETOOLONG;
833 return NULL;
835 /* Insert symlink contents into path. */
836 strcat (link_path, path);
837 strcpy (copy_path, link_path);
838 path = copy_path;
840 #endif /* S_IFLNK */
841 *new_path++ = '/';
843 /* Delete trailing slash but don't whomp a lone slash. */
844 if (new_path != got_path + 1 && new_path[-1] == '/')
845 new_path--;
846 /* Make sure it's null terminated. */
847 *new_path = '\0';
848 strcpy (resolved_path, got_path);
849 return resolved_path;
851 #endif /* HAVE_REALPATH */
853 /* --------------------------------------------------------------------------------------------- */
855 * Return the index of the permissions triplet
860 get_user_permissions (struct stat *st)
862 static gboolean initialized = FALSE;
863 static gid_t *groups;
864 static int ngroups;
865 static uid_t uid;
866 int i;
868 if (!initialized)
870 uid = geteuid ();
872 ngroups = getgroups (0, NULL);
873 if (ngroups == -1)
874 ngroups = 0; /* ignore errors */
876 /* allocate space for one element in addition to what
877 * will be filled by getgroups(). */
878 groups = g_new (gid_t, ngroups + 1);
880 if (ngroups != 0)
882 ngroups = getgroups (ngroups, groups);
883 if (ngroups == -1)
884 ngroups = 0; /* ignore errors */
887 /* getgroups() may or may not return the effective group ID,
888 * so we always include it at the end of the list. */
889 groups[ngroups++] = getegid ();
891 initialized = TRUE;
894 if (st->st_uid == uid || uid == 0)
895 return 0;
897 for (i = 0; i < ngroups; i++)
899 if (st->st_gid == groups[i])
900 return 1;
903 return 2;
906 /* --------------------------------------------------------------------------------------------- */
908 * Build filename from arguments.
909 * Like to g_build_filename(), but respect VFS_PATH_URL_DELIMITER
912 char *
913 mc_build_filenamev (const char *first_element, va_list args)
915 gboolean absolute;
916 const char *element = first_element;
917 GString *path;
918 char *ret;
920 if (element == NULL)
921 return NULL;
923 path = g_string_new ("");
925 absolute = (*first_element != '\0' && *first_element == PATH_SEP);
929 if (*element == '\0')
930 element = va_arg (args, char *);
931 else
933 char *tmp_element;
934 size_t len;
935 const char *start;
937 tmp_element = g_strdup (element);
939 element = va_arg (args, char *);
941 canonicalize_pathname (tmp_element);
942 len = strlen (tmp_element);
943 start = (tmp_element[0] == PATH_SEP) ? tmp_element + 1 : tmp_element;
945 g_string_append (path, start);
946 if (tmp_element[len - 1] != PATH_SEP && element != NULL)
947 g_string_append_c (path, PATH_SEP);
949 g_free (tmp_element);
952 while (element != NULL);
954 if (absolute)
955 g_string_prepend_c (path, PATH_SEP);
957 ret = g_string_free (path, FALSE);
958 canonicalize_pathname (ret);
960 return ret;
963 /* --------------------------------------------------------------------------------------------- */
965 * Build filename from arguments.
966 * Like to g_build_filename(), but respect VFS_PATH_URL_DELIMITER
969 char *
970 mc_build_filename (const char *first_element, ...)
972 va_list args;
973 char *ret;
975 if (first_element == NULL)
976 return NULL;
978 va_start (args, first_element);
979 ret = mc_build_filenamev (first_element, args);
980 va_end (args);
981 return ret;
984 /* --------------------------------------------------------------------------------------------- */