Simplify constructing of file operation progress dialog.
[midnight-commander.git] / lib / utilunix.c
blob74baca469674e3237ac1183a03e617f52a628337
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;
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)
504 title = MSG_ERROR;
505 else
506 title = _("Warning");
507 if (old_error >= 0)
509 if (dup2 (old_error, 2) == -1)
511 message (error, MSG_ERROR, _("Error dup'ing old error pipe"));
512 return 1;
514 close (old_error);
515 len = read (error_pipe[0], msg, MAX_PIPE_SIZE - 1);
517 if (len >= 0)
518 msg[len] = 0;
519 close (error_pipe[0]);
520 error_pipe[0] = -1;
522 if (error < 0)
523 return 0; /* Just ignore error message */
524 if (text == NULL)
526 if (len <= 0)
527 return 0; /* Nothing to show */
529 /* Show message from pipe */
530 message (error, title, "%s", msg);
532 else
534 /* Show given text and possible message from pipe */
535 message (error, title, "%s\n%s", text, msg);
537 return 1;
540 /* --------------------------------------------------------------------------------------------- */
542 * Canonicalize path, and return a new path. Do everything in place.
543 * The new path differs from path in:
544 * Multiple `/'s are collapsed to a single `/'.
545 * Leading `./'s and trailing `/.'s are removed.
546 * Trailing `/'s are removed.
547 * Non-leading `../'s and trailing `..'s are handled by removing
548 * portions of the path.
549 * Well formed UNC paths are modified only in the local part.
552 void
553 custom_canonicalize_pathname (char *path, CANON_PATH_FLAGS flags)
555 char *p, *s;
556 size_t len;
557 char *lpath = path; /* path without leading UNC part */
558 const size_t url_delim_len = strlen (VFS_PATH_URL_DELIMITER);
560 /* Detect and preserve UNC paths: //server/... */
561 if ((flags & CANON_PATH_GUARDUNC) && path[0] == PATH_SEP && path[1] == PATH_SEP)
563 p = path + 2;
564 while (p[0] && p[0] != '/')
565 p++;
566 if (p[0] == '/' && p > path + 2)
567 lpath = p;
570 if (!lpath[0] || !lpath[1])
571 return;
573 if (flags & CANON_PATH_JOINSLASHES)
575 /* Collapse multiple slashes */
576 p = lpath;
577 while (*p)
579 if (p[0] == PATH_SEP && p[1] == PATH_SEP && (p == lpath || *(p - 1) != ':'))
581 s = p + 1;
582 while (*(++s) == PATH_SEP);
583 str_move (p + 1, s);
585 p++;
589 if (flags & CANON_PATH_JOINSLASHES)
591 /* Collapse "/./" -> "/" */
592 p = lpath;
593 while (*p)
595 if (p[0] == PATH_SEP && p[1] == '.' && p[2] == PATH_SEP)
596 str_move (p, p + 2);
597 else
598 p++;
602 if (flags & CANON_PATH_REMSLASHDOTS)
604 /* Remove trailing slashes */
605 p = lpath + strlen (lpath) - 1;
606 while (p > lpath && *p == PATH_SEP)
608 if (p >= lpath - (url_delim_len + 1)
609 && strncmp (p - url_delim_len + 1, VFS_PATH_URL_DELIMITER, url_delim_len) == 0)
610 break;
611 *p-- = 0;
614 /* Remove leading "./" */
615 if (lpath[0] == '.' && lpath[1] == PATH_SEP)
617 if (lpath[2] == 0)
619 lpath[1] = 0;
620 return;
622 else
624 str_move (lpath, lpath + 2);
628 /* Remove trailing "/" or "/." */
629 len = strlen (lpath);
630 if (len < 2)
631 return;
632 if (lpath[len - 1] == PATH_SEP
633 && (len < url_delim_len
634 || strncmp (lpath + len - url_delim_len, VFS_PATH_URL_DELIMITER,
635 url_delim_len) != 0))
637 lpath[len - 1] = '\0';
639 else
641 if (lpath[len - 1] == '.' && lpath[len - 2] == PATH_SEP)
643 if (len == 2)
645 lpath[1] = '\0';
646 return;
648 else
650 lpath[len - 2] = '\0';
656 if (flags & CANON_PATH_REMDOUBLEDOTS)
658 const size_t enc_prefix_len = strlen (VFS_ENCODING_PREFIX);
660 /* Collapse "/.." with the previous part of path */
661 p = lpath;
662 while (p[0] && p[1] && p[2])
664 if ((p[0] != PATH_SEP || p[1] != '.' || p[2] != '.') || (p[3] != PATH_SEP && p[3] != 0))
666 p++;
667 continue;
670 /* search for the previous token */
671 s = p - 1;
672 if (s >= lpath + url_delim_len - 2
673 && strncmp (s - url_delim_len + 2, VFS_PATH_URL_DELIMITER, url_delim_len) == 0)
675 s -= (url_delim_len - 2);
676 while (s >= lpath && *s-- != PATH_SEP);
679 while (s >= lpath)
681 if (s - url_delim_len > lpath
682 && strncmp (s - url_delim_len, VFS_PATH_URL_DELIMITER, url_delim_len) == 0)
684 char *vfs_prefix = s - url_delim_len;
685 struct vfs_class *vclass;
687 while (vfs_prefix > lpath && *--vfs_prefix != PATH_SEP);
688 if (*vfs_prefix == PATH_SEP)
689 vfs_prefix++;
690 *(s - url_delim_len) = '\0';
692 vclass = vfs_prefix_to_class (vfs_prefix);
693 *(s - url_delim_len) = *VFS_PATH_URL_DELIMITER;
695 if (vclass != NULL)
697 struct vfs_s_subclass *sub = (struct vfs_s_subclass *) vclass->data;
698 if (sub != NULL && sub->flags & VFS_S_REMOTE)
700 s = vfs_prefix;
701 continue;
706 if (*s == PATH_SEP)
707 break;
709 s--;
712 s++;
714 /* If the previous token is "..", we cannot collapse it */
715 if (s[0] == '.' && s[1] == '.' && s + 2 == p)
717 p += 3;
718 continue;
721 if (p[3] != 0)
723 if (s == lpath && *s == PATH_SEP)
725 /* "/../foo" -> "/foo" */
726 str_move (s + 1, p + 4);
728 else
730 /* "token/../foo" -> "foo" */
731 #if HAVE_CHARSET
732 if ((strncmp (s, VFS_ENCODING_PREFIX, enc_prefix_len) == 0)
733 && (is_supported_encoding (s + enc_prefix_len)))
734 /* special case: remove encoding */
735 str_move (s, p + 1);
736 else
737 #endif /* HAVE_CHARSET */
738 str_move (s, p + 4);
740 p = (s > lpath) ? s - 1 : s;
741 continue;
744 /* trailing ".." */
745 if (s == lpath)
747 /* "token/.." -> "." */
748 if (lpath[0] != PATH_SEP)
750 lpath[0] = '.';
752 lpath[1] = 0;
754 else
756 /* "foo/token/.." -> "foo" */
757 if (s == lpath + 1)
758 s[0] = 0;
759 #if HAVE_CHARSET
760 else if ((strncmp (s, VFS_ENCODING_PREFIX, enc_prefix_len) == 0)
761 && (is_supported_encoding (s + enc_prefix_len)))
763 /* special case: remove encoding */
764 s[0] = '.';
765 s[1] = '.';
766 s[2] = '\0';
768 /* search for the previous token */
769 /* s[-1] == PATH_SEP */
770 p = s - 1;
771 while (p >= lpath && *p != PATH_SEP)
772 p--;
774 if (p != NULL)
775 continue;
777 #endif /* HAVE_CHARSET */
778 else
780 if (s >= lpath + url_delim_len
781 && strncmp (s - url_delim_len, VFS_PATH_URL_DELIMITER, url_delim_len) == 0)
782 *s = '\0';
783 else
784 s[-1] = '\0';
786 break;
789 break;
794 /* --------------------------------------------------------------------------------------------- */
796 void
797 canonicalize_pathname (char *path)
799 custom_canonicalize_pathname (path, CANON_PATH_ALL);
802 /* --------------------------------------------------------------------------------------------- */
804 #ifdef HAVE_GET_PROCESS_STATS
806 gettimeofday (struct timeval *tp, void *tzp)
808 return get_process_stats (tp, PS_SELF, 0, 0);
810 #endif /* HAVE_GET_PROCESS_STATS */
812 /* --------------------------------------------------------------------------------------------- */
814 #ifndef HAVE_REALPATH
815 char *
816 mc_realpath (const char *path, char *resolved_path)
818 char copy_path[PATH_MAX];
819 char link_path[PATH_MAX];
820 char got_path[PATH_MAX];
821 char *new_path = got_path;
822 char *max_path;
823 int readlinks = 0;
824 int n;
826 /* Make a copy of the source path since we may need to modify it. */
827 if (strlen (path) >= PATH_MAX - 2)
829 errno = ENAMETOOLONG;
830 return NULL;
832 strcpy (copy_path, path);
833 path = copy_path;
834 max_path = copy_path + PATH_MAX - 2;
835 /* If it's a relative pathname use getwd for starters. */
836 if (*path != '/')
839 new_path = g_get_current_dir ();
840 if (new_path == NULL)
842 strcpy (got_path, "");
844 else
846 g_snprintf (got_path, PATH_MAX, "%s", new_path);
847 g_free (new_path);
848 new_path = got_path;
851 new_path += strlen (got_path);
852 if (new_path[-1] != '/')
853 *new_path++ = '/';
855 else
857 *new_path++ = '/';
858 path++;
860 /* Expand each slash-separated pathname component. */
861 while (*path != '\0')
863 /* Ignore stray "/". */
864 if (*path == '/')
866 path++;
867 continue;
869 if (*path == '.')
871 /* Ignore ".". */
872 if (path[1] == '\0' || path[1] == '/')
874 path++;
875 continue;
877 if (path[1] == '.')
879 if (path[2] == '\0' || path[2] == '/')
881 path += 2;
882 /* Ignore ".." at root. */
883 if (new_path == got_path + 1)
884 continue;
885 /* Handle ".." by backing up. */
886 while ((--new_path)[-1] != '/');
887 continue;
891 /* Safely copy the next pathname component. */
892 while (*path != '\0' && *path != '/')
894 if (path > max_path)
896 errno = ENAMETOOLONG;
897 return NULL;
899 *new_path++ = *path++;
901 #ifdef S_IFLNK
902 /* Protect against infinite loops. */
903 if (readlinks++ > MAXSYMLINKS)
905 errno = ELOOP;
906 return NULL;
908 /* See if latest pathname component is a symlink. */
909 *new_path = '\0';
910 n = readlink (got_path, link_path, PATH_MAX - 1);
911 if (n < 0)
913 /* EINVAL means the file exists but isn't a symlink. */
914 if (errno != EINVAL)
916 /* Make sure it's null terminated. */
917 *new_path = '\0';
918 strcpy (resolved_path, got_path);
919 return NULL;
922 else
924 /* Note: readlink doesn't add the null byte. */
925 link_path[n] = '\0';
926 if (*link_path == '/')
927 /* Start over for an absolute symlink. */
928 new_path = got_path;
929 else
930 /* Otherwise back up over this component. */
931 while (*(--new_path) != '/');
932 /* Safe sex check. */
933 if (strlen (path) + n >= PATH_MAX - 2)
935 errno = ENAMETOOLONG;
936 return NULL;
938 /* Insert symlink contents into path. */
939 strcat (link_path, path);
940 strcpy (copy_path, link_path);
941 path = copy_path;
943 #endif /* S_IFLNK */
944 *new_path++ = '/';
946 /* Delete trailing slash but don't whomp a lone slash. */
947 if (new_path != got_path + 1 && new_path[-1] == '/')
948 new_path--;
949 /* Make sure it's null terminated. */
950 *new_path = '\0';
951 strcpy (resolved_path, got_path);
952 return resolved_path;
954 #endif /* HAVE_REALPATH */
956 /* --------------------------------------------------------------------------------------------- */
958 * Return the index of the permissions triplet
963 get_user_permissions (struct stat *st)
965 static gboolean initialized = FALSE;
966 static gid_t *groups;
967 static int ngroups;
968 static uid_t uid;
969 int i;
971 if (!initialized)
973 uid = geteuid ();
975 ngroups = getgroups (0, NULL);
976 if (ngroups == -1)
977 ngroups = 0; /* ignore errors */
979 /* allocate space for one element in addition to what
980 * will be filled by getgroups(). */
981 groups = g_new (gid_t, ngroups + 1);
983 if (ngroups != 0)
985 ngroups = getgroups (ngroups, groups);
986 if (ngroups == -1)
987 ngroups = 0; /* ignore errors */
990 /* getgroups() may or may not return the effective group ID,
991 * so we always include it at the end of the list. */
992 groups[ngroups++] = getegid ();
994 initialized = TRUE;
997 if (st->st_uid == uid || uid == 0)
998 return 0;
1000 for (i = 0; i < ngroups; i++)
1002 if (st->st_gid == groups[i])
1003 return 1;
1006 return 2;
1009 /* --------------------------------------------------------------------------------------------- */
1011 * Build filename from arguments.
1012 * Like to g_build_filename(), but respect VFS_PATH_URL_DELIMITER
1015 char *
1016 mc_build_filename (const char *first_element, ...)
1018 gboolean absolute;
1019 va_list args;
1020 const char *element = first_element;
1021 GString *path;
1022 char *ret;
1024 if (element == NULL)
1025 return NULL;
1027 path = g_string_new ("");
1028 va_start (args, first_element);
1030 absolute = (*first_element != '\0' && *first_element == PATH_SEP);
1034 if (*element == '\0')
1035 element = va_arg (args, char *);
1036 else
1038 char *tmp_element;
1039 size_t len;
1040 const char *start;
1042 tmp_element = g_strdup (element);
1044 element = va_arg (args, char *);
1046 canonicalize_pathname (tmp_element);
1047 len = strlen (tmp_element);
1048 start = (tmp_element[0] == PATH_SEP) ? tmp_element + 1 : tmp_element;
1050 g_string_append (path, start);
1051 if (tmp_element[len - 1] != PATH_SEP && element != NULL)
1052 g_string_append_c (path, PATH_SEP);
1054 g_free (tmp_element);
1057 while (element != NULL);
1059 va_end (args);
1061 if (absolute)
1062 g_string_prepend_c (path, PATH_SEP);
1064 ret = g_string_free (path, FALSE);
1065 canonicalize_pathname (ret);
1067 return ret;
1070 /* --------------------------------------------------------------------------------------------- */