Code refactoring: split src/execute.c:execute_with_vfs_arg() function.
[midnight-commander.git] / lib / utilunix.c
blob932d80614b3a5f0e36a73ad40145a34316df1598
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 typedef enum
101 FORK_ERROR = -1,
102 FORK_CHILD,
103 FORK_PARENT,
104 } my_fork_state_t;
106 typedef struct
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 /* --------------------------------------------------------------------------------------------- */
124 static char *
125 i_cache_match (int id, int_cache * cache, int size)
127 int i;
129 for (i = 0; i < size; i++)
130 if (cache[i].index == id)
131 return cache[i].string;
132 return 0;
135 /* --------------------------------------------------------------------------------------------- */
137 static void
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
149 my_fork (void)
151 pid_t pid;
153 pid = fork ();
155 if (pid < 0)
157 fprintf (stderr, "\n\nfork () = -1\n");
158 return FORK_ERROR;
161 if (pid == 0)
162 return FORK_CHILD;
164 while (TRUE)
166 int status = 0;
168 if (waitpid (pid, &status, 0) > 0)
169 return WEXITSTATUS (status) == 0 ? FORK_PARENT : FORK_ERROR;
171 if (errno != EINTR)
172 return FORK_ERROR;
176 /* --------------------------------------------------------------------------------------------- */
178 static void
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);
185 ignore.sa_flags = 0;
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 /* --------------------------------------------------------------------------------------------- */
197 static void
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 /* --------------------------------------------------------------------------------------------- */
206 /*** public functions ****************************************************************************/
207 /* --------------------------------------------------------------------------------------------- */
209 char *
210 get_owner (int uid)
212 struct passwd *pwd;
213 static char ibuf[10];
214 char *name;
215 static int uid_last;
217 name = i_cache_match (uid, uid_cache, UID_CACHE_SIZE);
218 if (name != NULL)
219 return name;
221 pwd = getpwuid (uid);
222 if (pwd != NULL)
224 i_cache_add (uid, uid_cache, UID_CACHE_SIZE, pwd->pw_name, &uid_last);
225 return pwd->pw_name;
227 else
229 g_snprintf (ibuf, sizeof (ibuf), "%d", uid);
230 return ibuf;
234 /* --------------------------------------------------------------------------------------------- */
236 char *
237 get_group (int gid)
239 struct group *grp;
240 static char gbuf[10];
241 char *name;
242 static int gid_last;
244 name = i_cache_match (gid, gid_cache, GID_CACHE_SIZE);
245 if (name != NULL)
246 return name;
248 grp = getgrgid (gid);
249 if (grp != NULL)
251 i_cache_add (gid, gid_cache, GID_CACHE_SIZE, grp->gr_name, &gid_last);
252 return grp->gr_name;
254 else
256 g_snprintf (gbuf, sizeof (gbuf), "%d", gid);
257 return gbuf;
261 /* --------------------------------------------------------------------------------------------- */
262 /* Since ncurses uses a handler that automatically refreshes the */
263 /* screen after a SIGCONT, and we don't want this behavior when */
264 /* spawning a child, we save the original handler here */
266 void
267 save_stop_handler (void)
269 sigaction (SIGTSTP, NULL, &startup_handler);
272 /* --------------------------------------------------------------------------------------------- */
274 * Wrapper for _exit() system call.
275 * The _exit() function has gcc's attribute 'noreturn', and this is reason why we can't
276 * mock the call.
278 * @param status exit code
281 void
282 my_exit (int status)
284 _exit (status);
287 /* --------------------------------------------------------------------------------------------- */
289 * Call external programs.
291 * @parameter flags addition conditions for running external programs.
292 * @parameter shell shell (if flags contain EXECUTE_AS_SHELL), command to run otherwise.
293 * Shell (or command) will be found in paths described in PATH variable
294 * (if shell parameter doesn't begin from path delimiter)
295 * @parameter command Command for shell (or first parameter for command, if flags contain EXECUTE_AS_SHELL)
296 * @return 0 if successfull, -1 otherwise
300 my_system (int flags, const char *shell, const char *command)
302 return my_systeml (flags, shell, command, NULL);
305 /* --------------------------------------------------------------------------------------------- */
307 * Call external programs with various parameters number.
309 * @parameter flags addition conditions for running external programs.
310 * @parameter shell shell (if flags contain EXECUTE_AS_SHELL), command to run otherwise.
311 * Shell (or command) will be found in pathes described in PATH variable
312 * (if shell parameter doesn't begin from path delimiter)
313 * @parameter ... Command for shell with addition parameters for shell
314 * (or parameters for command, if flags contain EXECUTE_AS_SHELL).
315 * Should be NULL terminated.
316 * @return 0 if successfull, -1 otherwise
320 my_systeml (int flags, const char *shell, ...)
322 char *execute_name;
323 GPtrArray *args_array;
324 int status = 0;
325 va_list vargs;
326 char *one_arg;
328 args_array = g_ptr_array_new ();
330 if ((flags & EXECUTE_AS_SHELL) != 0)
332 g_ptr_array_add (args_array, g_strdup (shell));
333 g_ptr_array_add (args_array, g_strdup ("-c"));
334 execute_name = g_strdup (shell);
336 else
338 gchar *shell_token;
340 shell_token = shell != NULL ? strchr (shell, ' ') : NULL;
341 if (shell_token == NULL)
342 *execute_name = g_strdup (shell);
343 else
344 *execute_name = g_strndup (shell, (gsize) (shell_token - shell));
345 g_ptr_array_add (args_array, g_strdup (shell));
348 va_start (vargs, shell);
349 while ((one_arg = va_arg (vargs, char *)) != NULL)
350 g_ptr_array_add (args_array, g_strdup (one_arg));
351 va_end (vargs);
353 g_ptr_array_add (args_array, NULL);
354 status = my_systemv (execute_name, (char *const *) args_array->pdata);
356 g_free (execute_name);
357 g_ptr_array_free (args_array, TRUE);
359 return status;
362 /* --------------------------------------------------------------------------------------------- */
364 * Call external programs with array of strings as parameters.
366 * @parameter command command to run. Command will be found in paths described in PATH variable
367 * (if command parameter doesn't begin from path delimiter)
368 * @parameter argv Array of strings (NULL-terminated) with parameters for command
369 * @return 0 if successfull, -1 otherwise
373 my_systemv (const char *command, char *const argv[])
375 my_fork_state_t fork_state;
376 int status = 0;
377 my_system_sigactions_t sigactions;
379 my_system__save_sigaction_handlers (&sigactions);
381 fork_state = my_fork ();
382 switch (fork_state)
384 case FORK_ERROR:
385 status = -1;
386 break;
387 case FORK_CHILD:
389 signal (SIGINT, SIG_DFL);
390 signal (SIGQUIT, SIG_DFL);
391 signal (SIGTSTP, SIG_DFL);
392 signal (SIGCHLD, SIG_DFL);
394 execvp (command, argv);
395 my_exit (127); /* Exec error */
397 break;
398 default:
399 status = 0;
400 break;
402 my_system__restore_sigaction_handlers (&sigactions);
404 return status;
407 /* --------------------------------------------------------------------------------------------- */
410 * Perform tilde expansion if possible.
412 * @param directory pointer to the path
414 * @return newly allocated string, even if it's unchanged.
417 char *
418 tilde_expand (const char *directory)
420 struct passwd *passwd;
421 const char *p, *q;
422 char *name;
424 if (*directory != '~')
425 return g_strdup (directory);
427 p = directory + 1;
429 /* d = "~" or d = "~/" */
430 if (!(*p) || (*p == PATH_SEP))
432 passwd = getpwuid (geteuid ());
433 q = (*p == PATH_SEP) ? p + 1 : "";
435 else
437 q = strchr (p, PATH_SEP);
438 if (!q)
440 passwd = getpwnam (p);
442 else
444 name = g_strndup (p, q - p);
445 passwd = getpwnam (name);
446 q++;
447 g_free (name);
451 /* If we can't figure the user name, leave tilde unexpanded */
452 if (!passwd)
453 return g_strdup (directory);
455 return g_strconcat (passwd->pw_dir, PATH_SEP_STR, q, (char *) NULL);
458 /* --------------------------------------------------------------------------------------------- */
460 * Creates a pipe to hold standard error for a later analysis.
461 * The pipe can hold 4096 bytes. Make sure no more is written
462 * or a deadlock might occur.
465 void
466 open_error_pipe (void)
468 if (pipe (error_pipe) < 0)
470 message (D_NORMAL, _("Warning"), _("Pipe failed"));
472 old_error = dup (2);
473 if (old_error < 0 || close (2) || dup (error_pipe[1]) != 2)
475 message (D_NORMAL, _("Warning"), _("Dup failed"));
477 close (error_pipe[0]);
478 error_pipe[0] = -1;
480 else
483 * Settng stderr in nonblocking mode as we close it earlier, than
484 * program stops. We try to read some error at program startup,
485 * but we should not block on it.
487 * TODO: make piped stdin/stderr poll()/select()able to get rid
488 * of following hack.
490 int fd_flags;
491 fd_flags = fcntl (error_pipe[0], F_GETFL, NULL);
492 if (fd_flags != -1)
494 fd_flags |= O_NONBLOCK;
495 if (fcntl (error_pipe[0], F_SETFL, fd_flags) == -1)
497 /* TODO: handle it somehow */
501 /* we never write there */
502 close (error_pipe[1]);
503 error_pipe[1] = -1;
506 /* --------------------------------------------------------------------------------------------- */
508 * Close a pipe
510 * @param error '-1' - ignore errors, '0' - display warning, '1' - display error
511 * @param text is prepended to the error message from the pipe
513 * @return not 0 if an error was displayed
517 close_error_pipe (int error, const char *text)
519 const char *title;
520 char msg[MAX_PIPE_SIZE];
521 int len = 0;
523 /* already closed */
524 if (error_pipe[0] == -1)
525 return 0;
527 if (error < 0 || (error > 0 && (error & D_ERROR) != 0))
528 title = MSG_ERROR;
529 else
530 title = _("Warning");
531 if (old_error >= 0)
533 if (dup2 (old_error, 2) == -1)
535 if (error < 0)
536 error = D_ERROR;
538 message (error, MSG_ERROR, _("Error dup'ing old error pipe"));
539 return 1;
541 close (old_error);
542 len = read (error_pipe[0], msg, MAX_PIPE_SIZE - 1);
544 if (len >= 0)
545 msg[len] = 0;
546 close (error_pipe[0]);
547 error_pipe[0] = -1;
549 if (error < 0)
550 return 0; /* Just ignore error message */
551 if (text == NULL)
553 if (len <= 0)
554 return 0; /* Nothing to show */
556 /* Show message from pipe */
557 message (error, title, "%s", msg);
559 else
561 /* Show given text and possible message from pipe */
562 message (error, title, "%s\n%s", text, msg);
564 return 1;
567 /* --------------------------------------------------------------------------------------------- */
569 * Canonicalize path, and return a new path. Do everything in place.
570 * The new path differs from path in:
571 * Multiple `/'s are collapsed to a single `/'.
572 * Leading `./'s and trailing `/.'s are removed.
573 * Trailing `/'s are removed.
574 * Non-leading `../'s and trailing `..'s are handled by removing
575 * portions of the path.
576 * Well formed UNC paths are modified only in the local part.
579 void
580 custom_canonicalize_pathname (char *path, CANON_PATH_FLAGS flags)
582 char *p, *s;
583 size_t len;
584 char *lpath = path; /* path without leading UNC part */
585 const size_t url_delim_len = strlen (VFS_PATH_URL_DELIMITER);
587 /* Detect and preserve UNC paths: //server/... */
588 if ((flags & CANON_PATH_GUARDUNC) && path[0] == PATH_SEP && path[1] == PATH_SEP)
590 p = path + 2;
591 while (p[0] && p[0] != '/')
592 p++;
593 if (p[0] == '/' && p > path + 2)
594 lpath = p;
597 if (!lpath[0] || !lpath[1])
598 return;
600 if (flags & CANON_PATH_JOINSLASHES)
602 /* Collapse multiple slashes */
603 p = lpath;
604 while (*p)
606 if (p[0] == PATH_SEP && p[1] == PATH_SEP && (p == lpath || *(p - 1) != ':'))
608 s = p + 1;
609 while (*(++s) == PATH_SEP);
610 str_move (p + 1, s);
612 p++;
616 if (flags & CANON_PATH_JOINSLASHES)
618 /* Collapse "/./" -> "/" */
619 p = lpath;
620 while (*p)
622 if (p[0] == PATH_SEP && p[1] == '.' && p[2] == PATH_SEP)
623 str_move (p, p + 2);
624 else
625 p++;
629 if (flags & CANON_PATH_REMSLASHDOTS)
631 /* Remove trailing slashes */
632 p = lpath + strlen (lpath) - 1;
633 while (p > lpath && *p == PATH_SEP)
635 if (p >= lpath - (url_delim_len + 1)
636 && strncmp (p - url_delim_len + 1, VFS_PATH_URL_DELIMITER, url_delim_len) == 0)
637 break;
638 *p-- = 0;
641 /* Remove leading "./" */
642 if (lpath[0] == '.' && lpath[1] == PATH_SEP)
644 if (lpath[2] == 0)
646 lpath[1] = 0;
647 return;
649 else
651 str_move (lpath, lpath + 2);
655 /* Remove trailing "/" or "/." */
656 len = strlen (lpath);
657 if (len < 2)
658 return;
659 if (lpath[len - 1] == PATH_SEP
660 && (len < url_delim_len
661 || strncmp (lpath + len - url_delim_len, VFS_PATH_URL_DELIMITER,
662 url_delim_len) != 0))
664 lpath[len - 1] = '\0';
666 else
668 if (lpath[len - 1] == '.' && lpath[len - 2] == PATH_SEP)
670 if (len == 2)
672 lpath[1] = '\0';
673 return;
675 else
677 lpath[len - 2] = '\0';
683 if (flags & CANON_PATH_REMDOUBLEDOTS)
685 #ifdef HAVE_CHARSET
686 const size_t enc_prefix_len = strlen (VFS_ENCODING_PREFIX);
687 #endif /* HAVE_CHARSET */
689 /* Collapse "/.." with the previous part of path */
690 p = lpath;
691 while (p[0] && p[1] && p[2])
693 if ((p[0] != PATH_SEP || p[1] != '.' || p[2] != '.') || (p[3] != PATH_SEP && p[3] != 0))
695 p++;
696 continue;
699 /* search for the previous token */
700 s = p - 1;
701 if (s >= lpath + url_delim_len - 2
702 && strncmp (s - url_delim_len + 2, VFS_PATH_URL_DELIMITER, url_delim_len) == 0)
704 s -= (url_delim_len - 2);
705 while (s >= lpath && *s-- != PATH_SEP);
708 while (s >= lpath)
710 if (s - url_delim_len > lpath
711 && strncmp (s - url_delim_len, VFS_PATH_URL_DELIMITER, url_delim_len) == 0)
713 char *vfs_prefix = s - url_delim_len;
714 struct vfs_class *vclass;
716 while (vfs_prefix > lpath && *--vfs_prefix != PATH_SEP);
717 if (*vfs_prefix == PATH_SEP)
718 vfs_prefix++;
719 *(s - url_delim_len) = '\0';
721 vclass = vfs_prefix_to_class (vfs_prefix);
722 *(s - url_delim_len) = *VFS_PATH_URL_DELIMITER;
724 if (vclass != NULL)
726 struct vfs_s_subclass *sub = (struct vfs_s_subclass *) vclass->data;
727 if (sub != NULL && sub->flags & VFS_S_REMOTE)
729 s = vfs_prefix;
730 continue;
735 if (*s == PATH_SEP)
736 break;
738 s--;
741 s++;
743 /* If the previous token is "..", we cannot collapse it */
744 if (s[0] == '.' && s[1] == '.' && s + 2 == p)
746 p += 3;
747 continue;
750 if (p[3] != 0)
752 if (s == lpath && *s == PATH_SEP)
754 /* "/../foo" -> "/foo" */
755 str_move (s + 1, p + 4);
757 else
759 /* "token/../foo" -> "foo" */
760 #ifdef HAVE_CHARSET
761 if ((strncmp (s, VFS_ENCODING_PREFIX, enc_prefix_len) == 0)
762 && (is_supported_encoding (s + enc_prefix_len)))
763 /* special case: remove encoding */
764 str_move (s, p + 1);
765 else
766 #endif /* HAVE_CHARSET */
767 str_move (s, p + 4);
769 p = (s > lpath) ? s - 1 : s;
770 continue;
773 /* trailing ".." */
774 if (s == lpath)
776 /* "token/.." -> "." */
777 if (lpath[0] != PATH_SEP)
779 lpath[0] = '.';
781 lpath[1] = 0;
783 else
785 /* "foo/token/.." -> "foo" */
786 if (s == lpath + 1)
787 s[0] = 0;
788 #ifdef HAVE_CHARSET
789 else if ((strncmp (s, VFS_ENCODING_PREFIX, enc_prefix_len) == 0)
790 && (is_supported_encoding (s + enc_prefix_len)))
792 /* special case: remove encoding */
793 s[0] = '.';
794 s[1] = '.';
795 s[2] = '\0';
797 /* search for the previous token */
798 /* s[-1] == PATH_SEP */
799 p = s - 1;
800 while (p >= lpath && *p != PATH_SEP)
801 p--;
803 if (p != NULL)
804 continue;
806 #endif /* HAVE_CHARSET */
807 else
809 if (s >= lpath + url_delim_len
810 && strncmp (s - url_delim_len, VFS_PATH_URL_DELIMITER, url_delim_len) == 0)
811 *s = '\0';
812 else
813 s[-1] = '\0';
815 break;
818 break;
823 /* --------------------------------------------------------------------------------------------- */
825 void
826 canonicalize_pathname (char *path)
828 custom_canonicalize_pathname (path, CANON_PATH_ALL);
831 /* --------------------------------------------------------------------------------------------- */
833 #ifdef HAVE_GET_PROCESS_STATS
835 gettimeofday (struct timeval *tp, void *tzp)
837 return get_process_stats (tp, PS_SELF, 0, 0);
839 #endif /* HAVE_GET_PROCESS_STATS */
841 /* --------------------------------------------------------------------------------------------- */
843 #ifndef HAVE_REALPATH
844 char *
845 mc_realpath (const char *path, char *resolved_path)
847 char copy_path[PATH_MAX];
848 char link_path[PATH_MAX];
849 char got_path[PATH_MAX];
850 char *new_path = got_path;
851 char *max_path;
852 int readlinks = 0;
853 int n;
855 /* Make a copy of the source path since we may need to modify it. */
856 if (strlen (path) >= PATH_MAX - 2)
858 errno = ENAMETOOLONG;
859 return NULL;
861 strcpy (copy_path, path);
862 path = copy_path;
863 max_path = copy_path + PATH_MAX - 2;
864 /* If it's a relative pathname use getwd for starters. */
865 if (*path != '/')
868 new_path = g_get_current_dir ();
869 if (new_path == NULL)
871 strcpy (got_path, "");
873 else
875 g_snprintf (got_path, PATH_MAX, "%s", new_path);
876 g_free (new_path);
877 new_path = got_path;
880 new_path += strlen (got_path);
881 if (new_path[-1] != '/')
882 *new_path++ = '/';
884 else
886 *new_path++ = '/';
887 path++;
889 /* Expand each slash-separated pathname component. */
890 while (*path != '\0')
892 /* Ignore stray "/". */
893 if (*path == '/')
895 path++;
896 continue;
898 if (*path == '.')
900 /* Ignore ".". */
901 if (path[1] == '\0' || path[1] == '/')
903 path++;
904 continue;
906 if (path[1] == '.')
908 if (path[2] == '\0' || path[2] == '/')
910 path += 2;
911 /* Ignore ".." at root. */
912 if (new_path == got_path + 1)
913 continue;
914 /* Handle ".." by backing up. */
915 while ((--new_path)[-1] != '/');
916 continue;
920 /* Safely copy the next pathname component. */
921 while (*path != '\0' && *path != '/')
923 if (path > max_path)
925 errno = ENAMETOOLONG;
926 return NULL;
928 *new_path++ = *path++;
930 #ifdef S_IFLNK
931 /* Protect against infinite loops. */
932 if (readlinks++ > MAXSYMLINKS)
934 errno = ELOOP;
935 return NULL;
937 /* See if latest pathname component is a symlink. */
938 *new_path = '\0';
939 n = readlink (got_path, link_path, PATH_MAX - 1);
940 if (n < 0)
942 /* EINVAL means the file exists but isn't a symlink. */
943 if (errno != EINVAL)
945 /* Make sure it's null terminated. */
946 *new_path = '\0';
947 strcpy (resolved_path, got_path);
948 return NULL;
951 else
953 /* Note: readlink doesn't add the null byte. */
954 link_path[n] = '\0';
955 if (*link_path == '/')
956 /* Start over for an absolute symlink. */
957 new_path = got_path;
958 else
959 /* Otherwise back up over this component. */
960 while (*(--new_path) != '/');
961 /* Safe sex check. */
962 if (strlen (path) + n >= PATH_MAX - 2)
964 errno = ENAMETOOLONG;
965 return NULL;
967 /* Insert symlink contents into path. */
968 strcat (link_path, path);
969 strcpy (copy_path, link_path);
970 path = copy_path;
972 #endif /* S_IFLNK */
973 *new_path++ = '/';
975 /* Delete trailing slash but don't whomp a lone slash. */
976 if (new_path != got_path + 1 && new_path[-1] == '/')
977 new_path--;
978 /* Make sure it's null terminated. */
979 *new_path = '\0';
980 strcpy (resolved_path, got_path);
981 return resolved_path;
983 #endif /* HAVE_REALPATH */
985 /* --------------------------------------------------------------------------------------------- */
987 * Return the index of the permissions triplet
992 get_user_permissions (struct stat *st)
994 static gboolean initialized = FALSE;
995 static gid_t *groups;
996 static int ngroups;
997 static uid_t uid;
998 int i;
1000 if (!initialized)
1002 uid = geteuid ();
1004 ngroups = getgroups (0, NULL);
1005 if (ngroups == -1)
1006 ngroups = 0; /* ignore errors */
1008 /* allocate space for one element in addition to what
1009 * will be filled by getgroups(). */
1010 groups = g_new (gid_t, ngroups + 1);
1012 if (ngroups != 0)
1014 ngroups = getgroups (ngroups, groups);
1015 if (ngroups == -1)
1016 ngroups = 0; /* ignore errors */
1019 /* getgroups() may or may not return the effective group ID,
1020 * so we always include it at the end of the list. */
1021 groups[ngroups++] = getegid ();
1023 initialized = TRUE;
1026 if (st->st_uid == uid || uid == 0)
1027 return 0;
1029 for (i = 0; i < ngroups; i++)
1031 if (st->st_gid == groups[i])
1032 return 1;
1035 return 2;
1038 /* --------------------------------------------------------------------------------------------- */
1040 * Build filename from arguments.
1041 * Like to g_build_filename(), but respect VFS_PATH_URL_DELIMITER
1044 char *
1045 mc_build_filenamev (const char *first_element, va_list args)
1047 gboolean absolute;
1048 const char *element = first_element;
1049 GString *path;
1050 char *ret;
1052 if (element == NULL)
1053 return NULL;
1055 path = g_string_new ("");
1057 absolute = (*first_element != '\0' && *first_element == PATH_SEP);
1061 if (*element == '\0')
1062 element = va_arg (args, char *);
1063 else
1065 char *tmp_element;
1066 size_t len;
1067 const char *start;
1069 tmp_element = g_strdup (element);
1071 element = va_arg (args, char *);
1073 canonicalize_pathname (tmp_element);
1074 len = strlen (tmp_element);
1075 start = (tmp_element[0] == PATH_SEP) ? tmp_element + 1 : tmp_element;
1077 g_string_append (path, start);
1078 if (tmp_element[len - 1] != PATH_SEP && element != NULL)
1079 g_string_append_c (path, PATH_SEP);
1081 g_free (tmp_element);
1084 while (element != NULL);
1086 if (absolute)
1087 g_string_prepend_c (path, PATH_SEP);
1089 ret = g_string_free (path, FALSE);
1090 canonicalize_pathname (ret);
1092 return ret;
1095 /* --------------------------------------------------------------------------------------------- */
1097 * Build filename from arguments.
1098 * Like to g_build_filename(), but respect VFS_PATH_URL_DELIMITER
1101 char *
1102 mc_build_filename (const char *first_element, ...)
1104 va_list args;
1105 char *ret;
1107 if (first_element == NULL)
1108 return NULL;
1110 va_start (args, first_element);
1111 ret = mc_build_filenamev (first_element, args);
1112 va_end (args);
1113 return ret;
1116 /* --------------------------------------------------------------------------------------------- */