2 Various utilities - Unix variants
4 Copyright (C) 1994, 1995, 1996, 1998, 1999, 2000, 2001, 2002, 2003,
6 The Free Software Foundation, Inc.
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/>.
35 * \brief Source: various utilities - Unix variant
49 #include <sys/param.h>
50 #include <sys/types.h>
53 #ifdef HAVE_SYS_IOCTL_H
54 #include <sys/ioctl.h>
56 #ifdef HAVE_GET_PROCESS_STATS
57 #include <sys/procstats.h>
63 #include "lib/global.h"
64 #include "lib/vfs/vfs.h" /* VFS_ENCODING_PREFIX */
65 #include "lib/strutil.h" /* str_move() */
67 #include "lib/widget.h" /* message() */
68 #include "lib/vfs/xdirentry.h"
71 #include "lib/charsets.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 ****************************************************************/
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 /* --------------------------------------------------------------------------------------------- */
109 i_cache_match (int id
, int_cache
* cache
, int size
)
113 for (i
= 0; i
< size
; i
++)
114 if (cache
[i
].index
== id
)
115 return cache
[i
].string
;
119 /* --------------------------------------------------------------------------------------------- */
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 /* --------------------------------------------------------------------------------------------- */
138 static char ibuf
[10];
142 name
= i_cache_match (uid
, uid_cache
, UID_CACHE_SIZE
);
146 pwd
= getpwuid (uid
);
149 i_cache_add (uid
, uid_cache
, UID_CACHE_SIZE
, pwd
->pw_name
, &uid_last
);
154 g_snprintf (ibuf
, sizeof (ibuf
), "%d", uid
);
159 /* --------------------------------------------------------------------------------------------- */
165 static char gbuf
[10];
169 name
= i_cache_match (gid
, gid_cache
, GID_CACHE_SIZE
);
173 grp
= getgrgid (gid
);
176 i_cache_add (gid
, gid_cache
, GID_CACHE_SIZE
, grp
->gr_name
, &gid_last
);
181 g_snprintf (gbuf
, sizeof (gbuf
), "%d", gid
);
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 */
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
;
206 ignore
.sa_handler
= SIG_IGN
;
207 sigemptyset (&ignore
.sa_mask
);
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
);
220 fprintf (stderr
, "\n\nfork () = -1\n");
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
);
234 gchar
**shell_tokens
;
235 const gchar
*only_cmd
;
237 shell_tokens
= g_strsplit (shell
, " ", 2);
238 if (shell_tokens
== NULL
)
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 */
260 if (waitpid (pid
, &status
, 0) > 0)
262 status
= WEXITSTATUS (status
);
272 sigaction (SIGINT
, &save_intr
, NULL
);
273 sigaction (SIGQUIT
, &save_quit
, NULL
);
274 sigaction (SIGTSTP
, &save_stop
, NULL
);
280 /* --------------------------------------------------------------------------------------------- */
282 * Perform tilde expansion if possible.
283 * Always return a newly allocated string, even if it's unchanged.
287 tilde_expand (const char *directory
)
289 struct passwd
*passwd
;
293 if (*directory
!= '~')
294 return g_strdup (directory
);
298 /* d = "~" or d = "~/" */
299 if (!(*p
) || (*p
== PATH_SEP
))
301 passwd
= getpwuid (geteuid ());
302 q
= (*p
== PATH_SEP
) ? p
+ 1 : "";
306 q
= strchr (p
, PATH_SEP
);
309 passwd
= getpwnam (p
);
313 name
= g_strndup (p
, q
- p
);
314 passwd
= getpwnam (name
);
320 /* If we can't figure the user name, leave tilde unexpanded */
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.
339 static char buffer
[64];
340 static const char *tmpdir
= NULL
;
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)
351 sys_tmp
= getenv ("TMPDIR");
352 if (!sys_tmp
|| sys_tmp
[0] != '/')
354 sys_tmp
= TMPDIR_DEFAULT
;
357 pwd
= getpwuid (getuid ());
360 g_snprintf (buffer
, sizeof (buffer
), "%s/mc-%s", sys_tmp
, pwd
->pw_name
);
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");
378 /* Need to create directory */
379 if (mkdir (buffer
, S_IRWXU
) != 0)
382 _("Cannot create temporary directory %s: %s\n"),
383 buffer
, unix_error_string (errno
));
391 char *test_fn
, *fallback_prefix
;
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
);
404 test_fd
= open (test_fn
, O_RDONLY
);
415 fprintf (stderr
, _("Temporary files will be created in %s\n"), sys_tmp
);
416 g_snprintf (buffer
, sizeof (buffer
), "%s", sys_tmp
);
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..."));
432 g_setenv ("MC_TMPDIR", tmpdir
, TRUE
);
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.
445 open_error_pipe (void)
447 if (pipe (error_pipe
) < 0)
449 message (D_NORMAL
, _("Warning"), _("Pipe failed"));
452 if (old_error
< 0 || close (2) || dup (error_pipe
[1]) != 2)
454 message (D_NORMAL
, _("Warning"), _("Dup failed"));
456 close (error_pipe
[0]);
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
470 fd_flags
= fcntl (error_pipe
[0], F_GETFL
, NULL
);
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]);
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
)
496 char msg
[MAX_PIPE_SIZE
];
500 if (error_pipe
[0] == -1)
503 if (error
< 0 || (error
> 0 && (error
& D_ERROR
) != 0))
506 title
= _("Warning");
509 if (dup2 (old_error
, 2) == -1)
514 message (error
, MSG_ERROR
, _("Error dup'ing old error pipe"));
518 len
= read (error_pipe
[0], msg
, MAX_PIPE_SIZE
- 1);
522 close (error_pipe
[0]);
526 return 0; /* Just ignore error message */
530 return 0; /* Nothing to show */
532 /* Show message from pipe */
533 message (error
, title
, "%s", msg
);
537 /* Show given text and possible message from pipe */
538 message (error
, title
, "%s\n%s", text
, msg
);
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.
556 custom_canonicalize_pathname (char *path
, CANON_PATH_FLAGS flags
)
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
)
567 while (p
[0] && p
[0] != '/')
569 if (p
[0] == '/' && p
> path
+ 2)
573 if (!lpath
[0] || !lpath
[1])
576 if (flags
& CANON_PATH_JOINSLASHES
)
578 /* Collapse multiple slashes */
582 if (p
[0] == PATH_SEP
&& p
[1] == PATH_SEP
&& (p
== lpath
|| *(p
- 1) != ':'))
585 while (*(++s
) == PATH_SEP
);
592 if (flags
& CANON_PATH_JOINSLASHES
)
594 /* Collapse "/./" -> "/" */
598 if (p
[0] == PATH_SEP
&& p
[1] == '.' && p
[2] == PATH_SEP
)
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)
617 /* Remove leading "./" */
618 if (lpath
[0] == '.' && lpath
[1] == PATH_SEP
)
627 str_move (lpath
, lpath
+ 2);
631 /* Remove trailing "/" or "/." */
632 len
= strlen (lpath
);
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';
644 if (lpath
[len
- 1] == '.' && lpath
[len
- 2] == PATH_SEP
)
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 */
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))
673 /* search for the previous token */
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
);
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
)
693 *(s
- url_delim_len
) = '\0';
695 vclass
= vfs_prefix_to_class (vfs_prefix
);
696 *(s
- url_delim_len
) = *VFS_PATH_URL_DELIMITER
;
700 struct vfs_s_subclass
*sub
= (struct vfs_s_subclass
*) vclass
->data
;
701 if (sub
!= NULL
&& sub
->flags
& VFS_S_REMOTE
)
717 /* If the previous token is "..", we cannot collapse it */
718 if (s
[0] == '.' && s
[1] == '.' && s
+ 2 == p
)
726 if (s
== lpath
&& *s
== PATH_SEP
)
728 /* "/../foo" -> "/foo" */
729 str_move (s
+ 1, p
+ 4);
733 /* "token/../foo" -> "foo" */
735 if ((strncmp (s
, VFS_ENCODING_PREFIX
, enc_prefix_len
) == 0)
736 && (is_supported_encoding (s
+ enc_prefix_len
)))
737 /* special case: remove encoding */
740 #endif /* HAVE_CHARSET */
743 p
= (s
> lpath
) ? s
- 1 : s
;
750 /* "token/.." -> "." */
751 if (lpath
[0] != PATH_SEP
)
759 /* "foo/token/.." -> "foo" */
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 */
771 /* search for the previous token */
772 /* s[-1] == PATH_SEP */
774 while (p
>= lpath
&& *p
!= PATH_SEP
)
780 #endif /* HAVE_CHARSET */
783 if (s
>= lpath
+ url_delim_len
784 && strncmp (s
- url_delim_len
, VFS_PATH_URL_DELIMITER
, url_delim_len
) == 0)
797 /* --------------------------------------------------------------------------------------------- */
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
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
;
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
;
835 strcpy (copy_path
, path
);
837 max_path
= copy_path
+ PATH_MAX
- 2;
838 /* If it's a relative pathname use getwd for starters. */
842 new_path
= g_get_current_dir ();
843 if (new_path
== NULL
)
845 strcpy (got_path
, "");
849 g_snprintf (got_path
, PATH_MAX
, "%s", new_path
);
854 new_path
+= strlen (got_path
);
855 if (new_path
[-1] != '/')
863 /* Expand each slash-separated pathname component. */
864 while (*path
!= '\0')
866 /* Ignore stray "/". */
875 if (path
[1] == '\0' || path
[1] == '/')
882 if (path
[2] == '\0' || path
[2] == '/')
885 /* Ignore ".." at root. */
886 if (new_path
== got_path
+ 1)
888 /* Handle ".." by backing up. */
889 while ((--new_path
)[-1] != '/');
894 /* Safely copy the next pathname component. */
895 while (*path
!= '\0' && *path
!= '/')
899 errno
= ENAMETOOLONG
;
902 *new_path
++ = *path
++;
905 /* Protect against infinite loops. */
906 if (readlinks
++ > MAXSYMLINKS
)
911 /* See if latest pathname component is a symlink. */
913 n
= readlink (got_path
, link_path
, PATH_MAX
- 1);
916 /* EINVAL means the file exists but isn't a symlink. */
919 /* Make sure it's null terminated. */
921 strcpy (resolved_path
, got_path
);
927 /* Note: readlink doesn't add the null byte. */
929 if (*link_path
== '/')
930 /* Start over for an absolute symlink. */
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
;
941 /* Insert symlink contents into path. */
942 strcat (link_path
, path
);
943 strcpy (copy_path
, link_path
);
949 /* Delete trailing slash but don't whomp a lone slash. */
950 if (new_path
!= got_path
+ 1 && new_path
[-1] == '/')
952 /* Make sure it's null terminated. */
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
;
978 ngroups
= getgroups (0, NULL
);
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);
988 ngroups
= getgroups (ngroups
, groups
);
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 ();
1000 if (st
->st_uid
== uid
|| uid
== 0)
1003 for (i
= 0; i
< ngroups
; i
++)
1005 if (st
->st_gid
== groups
[i
])
1012 /* --------------------------------------------------------------------------------------------- */
1014 * Build filename from arguments.
1015 * Like to g_build_filename(), but respect VFS_PATH_URL_DELIMITER
1019 mc_build_filename (const char *first_element
, ...)
1023 const char *element
= first_element
;
1027 if (element
== 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 *);
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
);
1065 g_string_prepend_c (path
, PATH_SEP
);
1067 ret
= g_string_free (path
, FALSE
);
1068 canonicalize_pathname (ret
);
1073 /* --------------------------------------------------------------------------------------------- */