Ticket #2695: fixed segfault on newly created files in editor
[midnight-commander.git] / lib / utilunix.c
blob9cb720187fa8d94e1c0cf52f57f5927f71366853
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 #include <sys/param.h>
50 #include <sys/types.h>
51 #include <sys/stat.h>
52 #include <sys/wait.h>
53 #ifdef HAVE_SYS_IOCTL_H
54 #include <sys/ioctl.h>
55 #endif
56 #ifdef HAVE_GET_PROCESS_STATS
57 #include <sys/procstats.h>
58 #endif
59 #include <unistd.h>
60 #include <pwd.h>
61 #include <grp.h>
63 #include "lib/global.h"
64 #include "lib/vfs/vfs.h" /* VFS_ENCODING_PREFIX */
65 #include "lib/strutil.h" /* str_move() */
66 #include "lib/util.h"
67 #include "lib/widget.h" /* message() */
68 #include "lib/vfs/xdirentry.h"
70 #ifdef HAVE_CHARSET
71 #include "lib/charsets.h"
72 #endif
74 #include "utilunix.h"
76 /*** global variables ****************************************************************************/
78 struct sigaction startup_handler;
80 /*** file scope macro definitions ****************************************************************/
82 #define UID_CACHE_SIZE 200
83 #define GID_CACHE_SIZE 30
85 /* Pipes are guaranteed to be able to hold at least 4096 bytes */
86 /* More than that would be unportable */
87 #define MAX_PIPE_SIZE 4096
89 /*** file scope type declarations ****************************************************************/
91 typedef struct
93 int index;
94 char *string;
95 } int_cache;
97 /*** file scope variables ************************************************************************/
99 static int_cache uid_cache[UID_CACHE_SIZE];
100 static int_cache gid_cache[GID_CACHE_SIZE];
102 static int error_pipe[2]; /* File descriptors of error pipe */
103 static int old_error; /* File descriptor of old standard error */
105 /*** file scope functions ************************************************************************/
106 /* --------------------------------------------------------------------------------------------- */
108 static char *
109 i_cache_match (int id, int_cache * cache, int size)
111 int i;
113 for (i = 0; i < size; i++)
114 if (cache[i].index == id)
115 return cache[i].string;
116 return 0;
119 /* --------------------------------------------------------------------------------------------- */
121 static void
122 i_cache_add (int id, int_cache * cache, int size, char *text, int *last)
124 g_free (cache[*last].string);
125 cache[*last].string = g_strdup (text);
126 cache[*last].index = id;
127 *last = ((*last) + 1) % size;
130 /* --------------------------------------------------------------------------------------------- */
131 /*** public functions ****************************************************************************/
132 /* --------------------------------------------------------------------------------------------- */
134 char *
135 get_owner (int uid)
137 struct passwd *pwd;
138 static char ibuf[10];
139 char *name;
140 static int uid_last;
142 name = i_cache_match (uid, uid_cache, UID_CACHE_SIZE);
143 if (name != NULL)
144 return name;
146 pwd = getpwuid (uid);
147 if (pwd != NULL)
149 i_cache_add (uid, uid_cache, UID_CACHE_SIZE, pwd->pw_name, &uid_last);
150 return pwd->pw_name;
152 else
154 g_snprintf (ibuf, sizeof (ibuf), "%d", uid);
155 return ibuf;
159 /* --------------------------------------------------------------------------------------------- */
161 char *
162 get_group (int gid)
164 struct group *grp;
165 static char gbuf[10];
166 char *name;
167 static int gid_last;
169 name = i_cache_match (gid, gid_cache, GID_CACHE_SIZE);
170 if (name != NULL)
171 return name;
173 grp = getgrgid (gid);
174 if (grp != NULL)
176 i_cache_add (gid, gid_cache, GID_CACHE_SIZE, grp->gr_name, &gid_last);
177 return grp->gr_name;
179 else
181 g_snprintf (gbuf, sizeof (gbuf), "%d", gid);
182 return gbuf;
186 /* --------------------------------------------------------------------------------------------- */
187 /* Since ncurses uses a handler that automatically refreshes the */
188 /* screen after a SIGCONT, and we don't want this behavior when */
189 /* spawning a child, we save the original handler here */
191 void
192 save_stop_handler (void)
194 sigaction (SIGTSTP, NULL, &startup_handler);
197 /* --------------------------------------------------------------------------------------------- */
200 my_system (int flags, const char *shell, const char *command)
202 struct sigaction ignore, save_intr, save_quit, save_stop;
203 pid_t pid;
204 int status = 0;
206 ignore.sa_handler = SIG_IGN;
207 sigemptyset (&ignore.sa_mask);
208 ignore.sa_flags = 0;
210 sigaction (SIGINT, &ignore, &save_intr);
211 sigaction (SIGQUIT, &ignore, &save_quit);
213 /* Restore the original SIGTSTP handler, we don't want ncurses' */
214 /* handler messing the screen after the SIGCONT */
215 sigaction (SIGTSTP, &startup_handler, &save_stop);
217 pid = fork ();
218 if (pid < 0)
220 fprintf (stderr, "\n\nfork () = -1\n");
221 status = -1;
223 else if (pid == 0)
225 signal (SIGINT, SIG_DFL);
226 signal (SIGQUIT, SIG_DFL);
227 signal (SIGTSTP, SIG_DFL);
228 signal (SIGCHLD, SIG_DFL);
230 if (flags & EXECUTE_AS_SHELL)
231 execl (shell, shell, "-c", command, (char *) NULL);
232 else
234 gchar **shell_tokens;
235 const gchar *only_cmd;
237 shell_tokens = g_strsplit (shell, " ", 2);
238 if (shell_tokens == NULL)
239 only_cmd = shell;
240 else
241 only_cmd = (*shell_tokens != NULL) ? *shell_tokens : shell;
243 execlp (only_cmd, shell, command, (char *) NULL);
246 execlp will replace current process,
247 therefore no sence in call of g_strfreev().
248 But this keeped for estetic reason :)
250 g_strfreev (shell_tokens);
254 _exit (127); /* Exec error */
256 else
258 while (TRUE)
260 if (waitpid (pid, &status, 0) > 0)
262 status = WEXITSTATUS (status);
263 break;
265 if (errno != EINTR)
267 status = -1;
268 break;
272 sigaction (SIGINT, &save_intr, NULL);
273 sigaction (SIGQUIT, &save_quit, NULL);
274 sigaction (SIGTSTP, &save_stop, NULL);
276 return status;
280 /* --------------------------------------------------------------------------------------------- */
282 * Perform tilde expansion if possible.
283 * Always return a newly allocated string, even if it's unchanged.
286 char *
287 tilde_expand (const char *directory)
289 struct passwd *passwd;
290 const char *p, *q;
291 char *name;
293 if (*directory != '~')
294 return g_strdup (directory);
296 p = directory + 1;
298 /* d = "~" or d = "~/" */
299 if (!(*p) || (*p == PATH_SEP))
301 passwd = getpwuid (geteuid ());
302 q = (*p == PATH_SEP) ? p + 1 : "";
304 else
306 q = strchr (p, PATH_SEP);
307 if (!q)
309 passwd = getpwnam (p);
311 else
313 name = g_strndup (p, q - p);
314 passwd = getpwnam (name);
315 q++;
316 g_free (name);
320 /* If we can't figure the user name, leave tilde unexpanded */
321 if (!passwd)
322 return g_strdup (directory);
324 return g_strconcat (passwd->pw_dir, PATH_SEP_STR, q, (char *) NULL);
327 /* --------------------------------------------------------------------------------------------- */
329 * Creates a pipe to hold standard error for a later analysis.
330 * The pipe can hold 4096 bytes. Make sure no more is written
331 * or a deadlock might occur.
334 void
335 open_error_pipe (void)
337 if (pipe (error_pipe) < 0)
339 message (D_NORMAL, _("Warning"), _("Pipe failed"));
341 old_error = dup (2);
342 if (old_error < 0 || close (2) || dup (error_pipe[1]) != 2)
344 message (D_NORMAL, _("Warning"), _("Dup failed"));
346 close (error_pipe[0]);
347 error_pipe[0] = -1;
349 else
352 * Settng stderr in nonblocking mode as we close it earlier, than
353 * program stops. We try to read some error at program startup,
354 * but we should not block on it.
356 * TODO: make piped stdin/stderr poll()/select()able to get rid
357 * of following hack.
359 int fd_flags;
360 fd_flags = fcntl (error_pipe[0], F_GETFL, NULL);
361 if (fd_flags != -1)
363 fd_flags |= O_NONBLOCK;
364 if (fcntl (error_pipe[0], F_SETFL, fd_flags) == -1)
366 /* TODO: handle it somehow */
370 /* we never write there */
371 close (error_pipe[1]);
372 error_pipe[1] = -1;
375 /* --------------------------------------------------------------------------------------------- */
377 * Returns true if an error was displayed
378 * error: -1 - ignore errors, 0 - display warning, 1 - display error
379 * text is prepended to the error message from the pipe
383 close_error_pipe (int error, const char *text)
385 const char *title;
386 char msg[MAX_PIPE_SIZE];
387 int len = 0;
389 /* already closed */
390 if (error_pipe[0] == -1)
391 return 0;
393 if (error < 0 || (error > 0 && (error & D_ERROR) != 0))
394 title = MSG_ERROR;
395 else
396 title = _("Warning");
397 if (old_error >= 0)
399 if (dup2 (old_error, 2) == -1)
401 if (error < 0)
402 error = D_ERROR;
404 message (error, MSG_ERROR, _("Error dup'ing old error pipe"));
405 return 1;
407 close (old_error);
408 len = read (error_pipe[0], msg, MAX_PIPE_SIZE - 1);
410 if (len >= 0)
411 msg[len] = 0;
412 close (error_pipe[0]);
413 error_pipe[0] = -1;
415 if (error < 0)
416 return 0; /* Just ignore error message */
417 if (text == NULL)
419 if (len <= 0)
420 return 0; /* Nothing to show */
422 /* Show message from pipe */
423 message (error, title, "%s", msg);
425 else
427 /* Show given text and possible message from pipe */
428 message (error, title, "%s\n%s", text, msg);
430 return 1;
433 /* --------------------------------------------------------------------------------------------- */
435 * Canonicalize path, and return a new path. Do everything in place.
436 * The new path differs from path in:
437 * Multiple `/'s are collapsed to a single `/'.
438 * Leading `./'s and trailing `/.'s are removed.
439 * Trailing `/'s are removed.
440 * Non-leading `../'s and trailing `..'s are handled by removing
441 * portions of the path.
442 * Well formed UNC paths are modified only in the local part.
445 void
446 custom_canonicalize_pathname (char *path, CANON_PATH_FLAGS flags)
448 char *p, *s;
449 size_t len;
450 char *lpath = path; /* path without leading UNC part */
451 const size_t url_delim_len = strlen (VFS_PATH_URL_DELIMITER);
453 /* Detect and preserve UNC paths: //server/... */
454 if ((flags & CANON_PATH_GUARDUNC) && path[0] == PATH_SEP && path[1] == PATH_SEP)
456 p = path + 2;
457 while (p[0] && p[0] != '/')
458 p++;
459 if (p[0] == '/' && p > path + 2)
460 lpath = p;
463 if (!lpath[0] || !lpath[1])
464 return;
466 if (flags & CANON_PATH_JOINSLASHES)
468 /* Collapse multiple slashes */
469 p = lpath;
470 while (*p)
472 if (p[0] == PATH_SEP && p[1] == PATH_SEP && (p == lpath || *(p - 1) != ':'))
474 s = p + 1;
475 while (*(++s) == PATH_SEP);
476 str_move (p + 1, s);
478 p++;
482 if (flags & CANON_PATH_JOINSLASHES)
484 /* Collapse "/./" -> "/" */
485 p = lpath;
486 while (*p)
488 if (p[0] == PATH_SEP && p[1] == '.' && p[2] == PATH_SEP)
489 str_move (p, p + 2);
490 else
491 p++;
495 if (flags & CANON_PATH_REMSLASHDOTS)
497 /* Remove trailing slashes */
498 p = lpath + strlen (lpath) - 1;
499 while (p > lpath && *p == PATH_SEP)
501 if (p >= lpath - (url_delim_len + 1)
502 && strncmp (p - url_delim_len + 1, VFS_PATH_URL_DELIMITER, url_delim_len) == 0)
503 break;
504 *p-- = 0;
507 /* Remove leading "./" */
508 if (lpath[0] == '.' && lpath[1] == PATH_SEP)
510 if (lpath[2] == 0)
512 lpath[1] = 0;
513 return;
515 else
517 str_move (lpath, lpath + 2);
521 /* Remove trailing "/" or "/." */
522 len = strlen (lpath);
523 if (len < 2)
524 return;
525 if (lpath[len - 1] == PATH_SEP
526 && (len < url_delim_len
527 || strncmp (lpath + len - url_delim_len, VFS_PATH_URL_DELIMITER,
528 url_delim_len) != 0))
530 lpath[len - 1] = '\0';
532 else
534 if (lpath[len - 1] == '.' && lpath[len - 2] == PATH_SEP)
536 if (len == 2)
538 lpath[1] = '\0';
539 return;
541 else
543 lpath[len - 2] = '\0';
549 if (flags & CANON_PATH_REMDOUBLEDOTS)
551 const size_t enc_prefix_len = strlen (VFS_ENCODING_PREFIX);
553 /* Collapse "/.." with the previous part of path */
554 p = lpath;
555 while (p[0] && p[1] && p[2])
557 if ((p[0] != PATH_SEP || p[1] != '.' || p[2] != '.') || (p[3] != PATH_SEP && p[3] != 0))
559 p++;
560 continue;
563 /* search for the previous token */
564 s = p - 1;
565 if (s >= lpath + url_delim_len - 2
566 && strncmp (s - url_delim_len + 2, VFS_PATH_URL_DELIMITER, url_delim_len) == 0)
568 s -= (url_delim_len - 2);
569 while (s >= lpath && *s-- != PATH_SEP);
572 while (s >= lpath)
574 if (s - url_delim_len > lpath
575 && strncmp (s - url_delim_len, VFS_PATH_URL_DELIMITER, url_delim_len) == 0)
577 char *vfs_prefix = s - url_delim_len;
578 struct vfs_class *vclass;
580 while (vfs_prefix > lpath && *--vfs_prefix != PATH_SEP);
581 if (*vfs_prefix == PATH_SEP)
582 vfs_prefix++;
583 *(s - url_delim_len) = '\0';
585 vclass = vfs_prefix_to_class (vfs_prefix);
586 *(s - url_delim_len) = *VFS_PATH_URL_DELIMITER;
588 if (vclass != NULL)
590 struct vfs_s_subclass *sub = (struct vfs_s_subclass *) vclass->data;
591 if (sub != NULL && sub->flags & VFS_S_REMOTE)
593 s = vfs_prefix;
594 continue;
599 if (*s == PATH_SEP)
600 break;
602 s--;
605 s++;
607 /* If the previous token is "..", we cannot collapse it */
608 if (s[0] == '.' && s[1] == '.' && s + 2 == p)
610 p += 3;
611 continue;
614 if (p[3] != 0)
616 if (s == lpath && *s == PATH_SEP)
618 /* "/../foo" -> "/foo" */
619 str_move (s + 1, p + 4);
621 else
623 /* "token/../foo" -> "foo" */
624 #if HAVE_CHARSET
625 if ((strncmp (s, VFS_ENCODING_PREFIX, enc_prefix_len) == 0)
626 && (is_supported_encoding (s + enc_prefix_len)))
627 /* special case: remove encoding */
628 str_move (s, p + 1);
629 else
630 #endif /* HAVE_CHARSET */
631 str_move (s, p + 4);
633 p = (s > lpath) ? s - 1 : s;
634 continue;
637 /* trailing ".." */
638 if (s == lpath)
640 /* "token/.." -> "." */
641 if (lpath[0] != PATH_SEP)
643 lpath[0] = '.';
645 lpath[1] = 0;
647 else
649 /* "foo/token/.." -> "foo" */
650 if (s == lpath + 1)
651 s[0] = 0;
652 #if HAVE_CHARSET
653 else if ((strncmp (s, VFS_ENCODING_PREFIX, enc_prefix_len) == 0)
654 && (is_supported_encoding (s + enc_prefix_len)))
656 /* special case: remove encoding */
657 s[0] = '.';
658 s[1] = '.';
659 s[2] = '\0';
661 /* search for the previous token */
662 /* s[-1] == PATH_SEP */
663 p = s - 1;
664 while (p >= lpath && *p != PATH_SEP)
665 p--;
667 if (p != NULL)
668 continue;
670 #endif /* HAVE_CHARSET */
671 else
673 if (s >= lpath + url_delim_len
674 && strncmp (s - url_delim_len, VFS_PATH_URL_DELIMITER, url_delim_len) == 0)
675 *s = '\0';
676 else
677 s[-1] = '\0';
679 break;
682 break;
687 /* --------------------------------------------------------------------------------------------- */
689 void
690 canonicalize_pathname (char *path)
692 custom_canonicalize_pathname (path, CANON_PATH_ALL);
695 /* --------------------------------------------------------------------------------------------- */
697 #ifdef HAVE_GET_PROCESS_STATS
699 gettimeofday (struct timeval *tp, void *tzp)
701 return get_process_stats (tp, PS_SELF, 0, 0);
703 #endif /* HAVE_GET_PROCESS_STATS */
705 /* --------------------------------------------------------------------------------------------- */
707 #ifndef HAVE_REALPATH
708 char *
709 mc_realpath (const char *path, char *resolved_path)
711 char copy_path[PATH_MAX];
712 char link_path[PATH_MAX];
713 char got_path[PATH_MAX];
714 char *new_path = got_path;
715 char *max_path;
716 int readlinks = 0;
717 int n;
719 /* Make a copy of the source path since we may need to modify it. */
720 if (strlen (path) >= PATH_MAX - 2)
722 errno = ENAMETOOLONG;
723 return NULL;
725 strcpy (copy_path, path);
726 path = copy_path;
727 max_path = copy_path + PATH_MAX - 2;
728 /* If it's a relative pathname use getwd for starters. */
729 if (*path != '/')
732 new_path = g_get_current_dir ();
733 if (new_path == NULL)
735 strcpy (got_path, "");
737 else
739 g_snprintf (got_path, PATH_MAX, "%s", new_path);
740 g_free (new_path);
741 new_path = got_path;
744 new_path += strlen (got_path);
745 if (new_path[-1] != '/')
746 *new_path++ = '/';
748 else
750 *new_path++ = '/';
751 path++;
753 /* Expand each slash-separated pathname component. */
754 while (*path != '\0')
756 /* Ignore stray "/". */
757 if (*path == '/')
759 path++;
760 continue;
762 if (*path == '.')
764 /* Ignore ".". */
765 if (path[1] == '\0' || path[1] == '/')
767 path++;
768 continue;
770 if (path[1] == '.')
772 if (path[2] == '\0' || path[2] == '/')
774 path += 2;
775 /* Ignore ".." at root. */
776 if (new_path == got_path + 1)
777 continue;
778 /* Handle ".." by backing up. */
779 while ((--new_path)[-1] != '/');
780 continue;
784 /* Safely copy the next pathname component. */
785 while (*path != '\0' && *path != '/')
787 if (path > max_path)
789 errno = ENAMETOOLONG;
790 return NULL;
792 *new_path++ = *path++;
794 #ifdef S_IFLNK
795 /* Protect against infinite loops. */
796 if (readlinks++ > MAXSYMLINKS)
798 errno = ELOOP;
799 return NULL;
801 /* See if latest pathname component is a symlink. */
802 *new_path = '\0';
803 n = readlink (got_path, link_path, PATH_MAX - 1);
804 if (n < 0)
806 /* EINVAL means the file exists but isn't a symlink. */
807 if (errno != EINVAL)
809 /* Make sure it's null terminated. */
810 *new_path = '\0';
811 strcpy (resolved_path, got_path);
812 return NULL;
815 else
817 /* Note: readlink doesn't add the null byte. */
818 link_path[n] = '\0';
819 if (*link_path == '/')
820 /* Start over for an absolute symlink. */
821 new_path = got_path;
822 else
823 /* Otherwise back up over this component. */
824 while (*(--new_path) != '/');
825 /* Safe sex check. */
826 if (strlen (path) + n >= PATH_MAX - 2)
828 errno = ENAMETOOLONG;
829 return NULL;
831 /* Insert symlink contents into path. */
832 strcat (link_path, path);
833 strcpy (copy_path, link_path);
834 path = copy_path;
836 #endif /* S_IFLNK */
837 *new_path++ = '/';
839 /* Delete trailing slash but don't whomp a lone slash. */
840 if (new_path != got_path + 1 && new_path[-1] == '/')
841 new_path--;
842 /* Make sure it's null terminated. */
843 *new_path = '\0';
844 strcpy (resolved_path, got_path);
845 return resolved_path;
847 #endif /* HAVE_REALPATH */
849 /* --------------------------------------------------------------------------------------------- */
851 * Return the index of the permissions triplet
856 get_user_permissions (struct stat *st)
858 static gboolean initialized = FALSE;
859 static gid_t *groups;
860 static int ngroups;
861 static uid_t uid;
862 int i;
864 if (!initialized)
866 uid = geteuid ();
868 ngroups = getgroups (0, NULL);
869 if (ngroups == -1)
870 ngroups = 0; /* ignore errors */
872 /* allocate space for one element in addition to what
873 * will be filled by getgroups(). */
874 groups = g_new (gid_t, ngroups + 1);
876 if (ngroups != 0)
878 ngroups = getgroups (ngroups, groups);
879 if (ngroups == -1)
880 ngroups = 0; /* ignore errors */
883 /* getgroups() may or may not return the effective group ID,
884 * so we always include it at the end of the list. */
885 groups[ngroups++] = getegid ();
887 initialized = TRUE;
890 if (st->st_uid == uid || uid == 0)
891 return 0;
893 for (i = 0; i < ngroups; i++)
895 if (st->st_gid == groups[i])
896 return 1;
899 return 2;
902 /* --------------------------------------------------------------------------------------------- */
904 * Build filename from arguments.
905 * Like to g_build_filename(), but respect VFS_PATH_URL_DELIMITER
908 char *
909 mc_build_filenamev (const char *first_element, va_list args)
911 gboolean absolute;
912 const char *element = first_element;
913 GString *path;
914 char *ret;
916 if (element == NULL)
917 return NULL;
919 path = g_string_new ("");
921 absolute = (*first_element != '\0' && *first_element == PATH_SEP);
925 if (*element == '\0')
926 element = va_arg (args, char *);
927 else
929 char *tmp_element;
930 size_t len;
931 const char *start;
933 tmp_element = g_strdup (element);
935 element = va_arg (args, char *);
937 canonicalize_pathname (tmp_element);
938 len = strlen (tmp_element);
939 start = (tmp_element[0] == PATH_SEP) ? tmp_element + 1 : tmp_element;
941 g_string_append (path, start);
942 if (tmp_element[len - 1] != PATH_SEP && element != NULL)
943 g_string_append_c (path, PATH_SEP);
945 g_free (tmp_element);
948 while (element != NULL);
950 if (absolute)
951 g_string_prepend_c (path, PATH_SEP);
953 ret = g_string_free (path, FALSE);
954 canonicalize_pathname (ret);
956 return ret;
959 /* --------------------------------------------------------------------------------------------- */
961 * Build filename from arguments.
962 * Like to g_build_filename(), but respect VFS_PATH_URL_DELIMITER
965 char *
966 mc_build_filename (const char *first_element, ...)
968 va_list args;
969 char *ret;
971 if (first_element == NULL)
972 return NULL;
974 va_start (args, first_element);
975 ret = mc_build_filenamev (first_element, args);
976 va_end (args);
977 return ret;
980 /* --------------------------------------------------------------------------------------------- */