(examine_cd): split in two functions to be unit test friendly.
[midnight-commander.git] / lib / utilunix.c
blobafc801bbea708b9347632b7f0e67b9dfa84ff09c
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 * Return the directory where mc should keep its temporary files.
330 * This directory is (in Bourne shell terms) "${TMPDIR=/tmp}/mc-$USER"
331 * When called the first time, the directory is created if needed.
332 * The first call should be done early, since we are using fprintf()
333 * and not message() to report possible problems.
336 const char *
337 mc_tmpdir (void)
339 static char buffer[64];
340 static const char *tmpdir = NULL;
341 const char *sys_tmp;
342 struct passwd *pwd;
343 struct stat st;
344 const char *error = NULL;
346 /* Check if already correctly initialized */
347 if (tmpdir && lstat (tmpdir, &st) == 0 && S_ISDIR (st.st_mode) &&
348 st.st_uid == getuid () && (st.st_mode & 0777) == 0700)
349 return tmpdir;
351 sys_tmp = getenv ("TMPDIR");
352 if (!sys_tmp || sys_tmp[0] != '/')
354 sys_tmp = TMPDIR_DEFAULT;
357 pwd = getpwuid (getuid ());
359 if (pwd)
360 g_snprintf (buffer, sizeof (buffer), "%s/mc-%s", sys_tmp, pwd->pw_name);
361 else
362 g_snprintf (buffer, sizeof (buffer), "%s/mc-%lu", sys_tmp, (unsigned long) getuid ());
364 canonicalize_pathname (buffer);
366 if (lstat (buffer, &st) == 0)
368 /* Sanity check for existing directory */
369 if (!S_ISDIR (st.st_mode))
370 error = _("%s is not a directory\n");
371 else if (st.st_uid != getuid ())
372 error = _("Directory %s is not owned by you\n");
373 else if (((st.st_mode & 0777) != 0700) && (chmod (buffer, 0700) != 0))
374 error = _("Cannot set correct permissions for directory %s\n");
376 else
378 /* Need to create directory */
379 if (mkdir (buffer, S_IRWXU) != 0)
381 fprintf (stderr,
382 _("Cannot create temporary directory %s: %s\n"),
383 buffer, unix_error_string (errno));
384 error = "";
388 if (error != NULL)
390 int test_fd;
391 char *test_fn, *fallback_prefix;
392 int fallback_ok = 0;
394 if (*error)
395 fprintf (stderr, error, buffer);
397 /* Test if sys_tmp is suitable for temporary files */
398 fallback_prefix = g_strdup_printf ("%s/mctest", sys_tmp);
399 test_fd = mc_mkstemps (&test_fn, fallback_prefix, NULL);
400 g_free (fallback_prefix);
401 if (test_fd != -1)
403 close (test_fd);
404 test_fd = open (test_fn, O_RDONLY);
405 if (test_fd != -1)
407 close (test_fd);
408 unlink (test_fn);
409 fallback_ok = 1;
413 if (fallback_ok)
415 fprintf (stderr, _("Temporary files will be created in %s\n"), sys_tmp);
416 g_snprintf (buffer, sizeof (buffer), "%s", sys_tmp);
417 error = NULL;
419 else
421 fprintf (stderr, _("Temporary files will not be created\n"));
422 g_snprintf (buffer, sizeof (buffer), "%s", "/dev/null/");
425 fprintf (stderr, "%s\n", _("Press any key to continue..."));
426 getc (stdin);
429 tmpdir = buffer;
431 if (!error)
432 g_setenv ("MC_TMPDIR", tmpdir, TRUE);
434 return tmpdir;
437 /* --------------------------------------------------------------------------------------------- */
439 * Creates a pipe to hold standard error for a later analysis.
440 * The pipe can hold 4096 bytes. Make sure no more is written
441 * or a deadlock might occur.
444 void
445 open_error_pipe (void)
447 if (pipe (error_pipe) < 0)
449 message (D_NORMAL, _("Warning"), _("Pipe failed"));
451 old_error = dup (2);
452 if (old_error < 0 || close (2) || dup (error_pipe[1]) != 2)
454 message (D_NORMAL, _("Warning"), _("Dup failed"));
456 close (error_pipe[0]);
457 error_pipe[0] = -1;
459 else
462 * Settng stderr in nonblocking mode as we close it earlier, than
463 * program stops. We try to read some error at program startup,
464 * but we should not block on it.
466 * TODO: make piped stdin/stderr poll()/select()able to get rid
467 * of following hack.
469 int fd_flags;
470 fd_flags = fcntl (error_pipe[0], F_GETFL, NULL);
471 if (fd_flags != -1)
473 fd_flags |= O_NONBLOCK;
474 if (fcntl (error_pipe[0], F_SETFL, fd_flags) == -1)
476 /* TODO: handle it somehow */
480 /* we never write there */
481 close (error_pipe[1]);
482 error_pipe[1] = -1;
485 /* --------------------------------------------------------------------------------------------- */
487 * Returns true if an error was displayed
488 * error: -1 - ignore errors, 0 - display warning, 1 - display error
489 * text is prepended to the error message from the pipe
493 close_error_pipe (int error, const char *text)
495 const char *title;
496 char msg[MAX_PIPE_SIZE];
497 int len = 0;
499 /* already closed */
500 if (error_pipe[0] == -1)
501 return 0;
503 if (error < 0 || (error > 0 && (error & D_ERROR) != 0))
504 title = MSG_ERROR;
505 else
506 title = _("Warning");
507 if (old_error >= 0)
509 if (dup2 (old_error, 2) == -1)
511 if (error < 0)
512 error = D_ERROR;
514 message (error, MSG_ERROR, _("Error dup'ing old error pipe"));
515 return 1;
517 close (old_error);
518 len = read (error_pipe[0], msg, MAX_PIPE_SIZE - 1);
520 if (len >= 0)
521 msg[len] = 0;
522 close (error_pipe[0]);
523 error_pipe[0] = -1;
525 if (error < 0)
526 return 0; /* Just ignore error message */
527 if (text == NULL)
529 if (len <= 0)
530 return 0; /* Nothing to show */
532 /* Show message from pipe */
533 message (error, title, "%s", msg);
535 else
537 /* Show given text and possible message from pipe */
538 message (error, title, "%s\n%s", text, msg);
540 return 1;
543 /* --------------------------------------------------------------------------------------------- */
545 * Canonicalize path, and return a new path. Do everything in place.
546 * The new path differs from path in:
547 * Multiple `/'s are collapsed to a single `/'.
548 * Leading `./'s and trailing `/.'s are removed.
549 * Trailing `/'s are removed.
550 * Non-leading `../'s and trailing `..'s are handled by removing
551 * portions of the path.
552 * Well formed UNC paths are modified only in the local part.
555 void
556 custom_canonicalize_pathname (char *path, CANON_PATH_FLAGS flags)
558 char *p, *s;
559 size_t len;
560 char *lpath = path; /* path without leading UNC part */
561 const size_t url_delim_len = strlen (VFS_PATH_URL_DELIMITER);
563 /* Detect and preserve UNC paths: //server/... */
564 if ((flags & CANON_PATH_GUARDUNC) && path[0] == PATH_SEP && path[1] == PATH_SEP)
566 p = path + 2;
567 while (p[0] && p[0] != '/')
568 p++;
569 if (p[0] == '/' && p > path + 2)
570 lpath = p;
573 if (!lpath[0] || !lpath[1])
574 return;
576 if (flags & CANON_PATH_JOINSLASHES)
578 /* Collapse multiple slashes */
579 p = lpath;
580 while (*p)
582 if (p[0] == PATH_SEP && p[1] == PATH_SEP && (p == lpath || *(p - 1) != ':'))
584 s = p + 1;
585 while (*(++s) == PATH_SEP);
586 str_move (p + 1, s);
588 p++;
592 if (flags & CANON_PATH_JOINSLASHES)
594 /* Collapse "/./" -> "/" */
595 p = lpath;
596 while (*p)
598 if (p[0] == PATH_SEP && p[1] == '.' && p[2] == PATH_SEP)
599 str_move (p, p + 2);
600 else
601 p++;
605 if (flags & CANON_PATH_REMSLASHDOTS)
607 /* Remove trailing slashes */
608 p = lpath + strlen (lpath) - 1;
609 while (p > lpath && *p == PATH_SEP)
611 if (p >= lpath - (url_delim_len + 1)
612 && strncmp (p - url_delim_len + 1, VFS_PATH_URL_DELIMITER, url_delim_len) == 0)
613 break;
614 *p-- = 0;
617 /* Remove leading "./" */
618 if (lpath[0] == '.' && lpath[1] == PATH_SEP)
620 if (lpath[2] == 0)
622 lpath[1] = 0;
623 return;
625 else
627 str_move (lpath, lpath + 2);
631 /* Remove trailing "/" or "/." */
632 len = strlen (lpath);
633 if (len < 2)
634 return;
635 if (lpath[len - 1] == PATH_SEP
636 && (len < url_delim_len
637 || strncmp (lpath + len - url_delim_len, VFS_PATH_URL_DELIMITER,
638 url_delim_len) != 0))
640 lpath[len - 1] = '\0';
642 else
644 if (lpath[len - 1] == '.' && lpath[len - 2] == PATH_SEP)
646 if (len == 2)
648 lpath[1] = '\0';
649 return;
651 else
653 lpath[len - 2] = '\0';
659 if (flags & CANON_PATH_REMDOUBLEDOTS)
661 const size_t enc_prefix_len = strlen (VFS_ENCODING_PREFIX);
663 /* Collapse "/.." with the previous part of path */
664 p = lpath;
665 while (p[0] && p[1] && p[2])
667 if ((p[0] != PATH_SEP || p[1] != '.' || p[2] != '.') || (p[3] != PATH_SEP && p[3] != 0))
669 p++;
670 continue;
673 /* search for the previous token */
674 s = p - 1;
675 if (s >= lpath + url_delim_len - 2
676 && strncmp (s - url_delim_len + 2, VFS_PATH_URL_DELIMITER, url_delim_len) == 0)
678 s -= (url_delim_len - 2);
679 while (s >= lpath && *s-- != PATH_SEP);
682 while (s >= lpath)
684 if (s - url_delim_len > lpath
685 && strncmp (s - url_delim_len, VFS_PATH_URL_DELIMITER, url_delim_len) == 0)
687 char *vfs_prefix = s - url_delim_len;
688 struct vfs_class *vclass;
690 while (vfs_prefix > lpath && *--vfs_prefix != PATH_SEP);
691 if (*vfs_prefix == PATH_SEP)
692 vfs_prefix++;
693 *(s - url_delim_len) = '\0';
695 vclass = vfs_prefix_to_class (vfs_prefix);
696 *(s - url_delim_len) = *VFS_PATH_URL_DELIMITER;
698 if (vclass != NULL)
700 struct vfs_s_subclass *sub = (struct vfs_s_subclass *) vclass->data;
701 if (sub != NULL && sub->flags & VFS_S_REMOTE)
703 s = vfs_prefix;
704 continue;
709 if (*s == PATH_SEP)
710 break;
712 s--;
715 s++;
717 /* If the previous token is "..", we cannot collapse it */
718 if (s[0] == '.' && s[1] == '.' && s + 2 == p)
720 p += 3;
721 continue;
724 if (p[3] != 0)
726 if (s == lpath && *s == PATH_SEP)
728 /* "/../foo" -> "/foo" */
729 str_move (s + 1, p + 4);
731 else
733 /* "token/../foo" -> "foo" */
734 #if HAVE_CHARSET
735 if ((strncmp (s, VFS_ENCODING_PREFIX, enc_prefix_len) == 0)
736 && (is_supported_encoding (s + enc_prefix_len)))
737 /* special case: remove encoding */
738 str_move (s, p + 1);
739 else
740 #endif /* HAVE_CHARSET */
741 str_move (s, p + 4);
743 p = (s > lpath) ? s - 1 : s;
744 continue;
747 /* trailing ".." */
748 if (s == lpath)
750 /* "token/.." -> "." */
751 if (lpath[0] != PATH_SEP)
753 lpath[0] = '.';
755 lpath[1] = 0;
757 else
759 /* "foo/token/.." -> "foo" */
760 if (s == lpath + 1)
761 s[0] = 0;
762 #if HAVE_CHARSET
763 else if ((strncmp (s, VFS_ENCODING_PREFIX, enc_prefix_len) == 0)
764 && (is_supported_encoding (s + enc_prefix_len)))
766 /* special case: remove encoding */
767 s[0] = '.';
768 s[1] = '.';
769 s[2] = '\0';
771 /* search for the previous token */
772 /* s[-1] == PATH_SEP */
773 p = s - 1;
774 while (p >= lpath && *p != PATH_SEP)
775 p--;
777 if (p != NULL)
778 continue;
780 #endif /* HAVE_CHARSET */
781 else
783 if (s >= lpath + url_delim_len
784 && strncmp (s - url_delim_len, VFS_PATH_URL_DELIMITER, url_delim_len) == 0)
785 *s = '\0';
786 else
787 s[-1] = '\0';
789 break;
792 break;
797 /* --------------------------------------------------------------------------------------------- */
799 void
800 canonicalize_pathname (char *path)
802 custom_canonicalize_pathname (path, CANON_PATH_ALL);
805 /* --------------------------------------------------------------------------------------------- */
807 #ifdef HAVE_GET_PROCESS_STATS
809 gettimeofday (struct timeval *tp, void *tzp)
811 return get_process_stats (tp, PS_SELF, 0, 0);
813 #endif /* HAVE_GET_PROCESS_STATS */
815 /* --------------------------------------------------------------------------------------------- */
817 #ifndef HAVE_REALPATH
818 char *
819 mc_realpath (const char *path, char *resolved_path)
821 char copy_path[PATH_MAX];
822 char link_path[PATH_MAX];
823 char got_path[PATH_MAX];
824 char *new_path = got_path;
825 char *max_path;
826 int readlinks = 0;
827 int n;
829 /* Make a copy of the source path since we may need to modify it. */
830 if (strlen (path) >= PATH_MAX - 2)
832 errno = ENAMETOOLONG;
833 return NULL;
835 strcpy (copy_path, path);
836 path = copy_path;
837 max_path = copy_path + PATH_MAX - 2;
838 /* If it's a relative pathname use getwd for starters. */
839 if (*path != '/')
842 new_path = g_get_current_dir ();
843 if (new_path == NULL)
845 strcpy (got_path, "");
847 else
849 g_snprintf (got_path, PATH_MAX, "%s", new_path);
850 g_free (new_path);
851 new_path = got_path;
854 new_path += strlen (got_path);
855 if (new_path[-1] != '/')
856 *new_path++ = '/';
858 else
860 *new_path++ = '/';
861 path++;
863 /* Expand each slash-separated pathname component. */
864 while (*path != '\0')
866 /* Ignore stray "/". */
867 if (*path == '/')
869 path++;
870 continue;
872 if (*path == '.')
874 /* Ignore ".". */
875 if (path[1] == '\0' || path[1] == '/')
877 path++;
878 continue;
880 if (path[1] == '.')
882 if (path[2] == '\0' || path[2] == '/')
884 path += 2;
885 /* Ignore ".." at root. */
886 if (new_path == got_path + 1)
887 continue;
888 /* Handle ".." by backing up. */
889 while ((--new_path)[-1] != '/');
890 continue;
894 /* Safely copy the next pathname component. */
895 while (*path != '\0' && *path != '/')
897 if (path > max_path)
899 errno = ENAMETOOLONG;
900 return NULL;
902 *new_path++ = *path++;
904 #ifdef S_IFLNK
905 /* Protect against infinite loops. */
906 if (readlinks++ > MAXSYMLINKS)
908 errno = ELOOP;
909 return NULL;
911 /* See if latest pathname component is a symlink. */
912 *new_path = '\0';
913 n = readlink (got_path, link_path, PATH_MAX - 1);
914 if (n < 0)
916 /* EINVAL means the file exists but isn't a symlink. */
917 if (errno != EINVAL)
919 /* Make sure it's null terminated. */
920 *new_path = '\0';
921 strcpy (resolved_path, got_path);
922 return NULL;
925 else
927 /* Note: readlink doesn't add the null byte. */
928 link_path[n] = '\0';
929 if (*link_path == '/')
930 /* Start over for an absolute symlink. */
931 new_path = got_path;
932 else
933 /* Otherwise back up over this component. */
934 while (*(--new_path) != '/');
935 /* Safe sex check. */
936 if (strlen (path) + n >= PATH_MAX - 2)
938 errno = ENAMETOOLONG;
939 return NULL;
941 /* Insert symlink contents into path. */
942 strcat (link_path, path);
943 strcpy (copy_path, link_path);
944 path = copy_path;
946 #endif /* S_IFLNK */
947 *new_path++ = '/';
949 /* Delete trailing slash but don't whomp a lone slash. */
950 if (new_path != got_path + 1 && new_path[-1] == '/')
951 new_path--;
952 /* Make sure it's null terminated. */
953 *new_path = '\0';
954 strcpy (resolved_path, got_path);
955 return resolved_path;
957 #endif /* HAVE_REALPATH */
959 /* --------------------------------------------------------------------------------------------- */
961 * Return the index of the permissions triplet
966 get_user_permissions (struct stat *st)
968 static gboolean initialized = FALSE;
969 static gid_t *groups;
970 static int ngroups;
971 static uid_t uid;
972 int i;
974 if (!initialized)
976 uid = geteuid ();
978 ngroups = getgroups (0, NULL);
979 if (ngroups == -1)
980 ngroups = 0; /* ignore errors */
982 /* allocate space for one element in addition to what
983 * will be filled by getgroups(). */
984 groups = g_new (gid_t, ngroups + 1);
986 if (ngroups != 0)
988 ngroups = getgroups (ngroups, groups);
989 if (ngroups == -1)
990 ngroups = 0; /* ignore errors */
993 /* getgroups() may or may not return the effective group ID,
994 * so we always include it at the end of the list. */
995 groups[ngroups++] = getegid ();
997 initialized = TRUE;
1000 if (st->st_uid == uid || uid == 0)
1001 return 0;
1003 for (i = 0; i < ngroups; i++)
1005 if (st->st_gid == groups[i])
1006 return 1;
1009 return 2;
1012 /* --------------------------------------------------------------------------------------------- */
1014 * Build filename from arguments.
1015 * Like to g_build_filename(), but respect VFS_PATH_URL_DELIMITER
1018 char *
1019 mc_build_filename (const char *first_element, ...)
1021 gboolean absolute;
1022 va_list args;
1023 const char *element = first_element;
1024 GString *path;
1025 char *ret;
1027 if (element == NULL)
1028 return NULL;
1030 path = g_string_new ("");
1031 va_start (args, first_element);
1033 absolute = (*first_element != '\0' && *first_element == PATH_SEP);
1037 if (*element == '\0')
1038 element = va_arg (args, char *);
1039 else
1041 char *tmp_element;
1042 size_t len;
1043 const char *start;
1045 tmp_element = g_strdup (element);
1047 element = va_arg (args, char *);
1049 canonicalize_pathname (tmp_element);
1050 len = strlen (tmp_element);
1051 start = (tmp_element[0] == PATH_SEP) ? tmp_element + 1 : tmp_element;
1053 g_string_append (path, start);
1054 if (tmp_element[len - 1] != PATH_SEP && element != NULL)
1055 g_string_append_c (path, PATH_SEP);
1057 g_free (tmp_element);
1060 while (element != NULL);
1062 va_end (args);
1064 if (absolute)
1065 g_string_prepend_c (path, PATH_SEP);
1067 ret = g_string_free (path, FALSE);
1068 canonicalize_pathname (ret);
1070 return ret;
1073 /* --------------------------------------------------------------------------------------------- */