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 #ifdef HAVE_SYS_PARAM_H
50 #include <sys/param.h>
52 #include <sys/types.h>
55 #ifdef HAVE_SYS_IOCTL_H
56 #include <sys/ioctl.h>
58 #ifdef HAVE_GET_PROCESS_STATS
59 #include <sys/procstats.h>
65 #include "lib/global.h"
66 #include "lib/vfs/vfs.h" /* VFS_ENCODING_PREFIX */
67 #include "lib/strutil.h" /* str_move() */
69 #include "lib/widget.h" /* message() */
70 #include "lib/vfs/xdirentry.h"
73 #include "lib/charsets.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 ****************************************************************/
99 /*** file scope variables ************************************************************************/
101 static int_cache uid_cache
[UID_CACHE_SIZE
];
102 static int_cache gid_cache
[GID_CACHE_SIZE
];
104 static int error_pipe
[2]; /* File descriptors of error pipe */
105 static int old_error
; /* File descriptor of old standard error */
107 /*** file scope functions ************************************************************************/
108 /* --------------------------------------------------------------------------------------------- */
111 i_cache_match (int id
, int_cache
* cache
, int size
)
115 for (i
= 0; i
< size
; i
++)
116 if (cache
[i
].index
== id
)
117 return cache
[i
].string
;
121 /* --------------------------------------------------------------------------------------------- */
124 i_cache_add (int id
, int_cache
* cache
, int size
, char *text
, int *last
)
126 g_free (cache
[*last
].string
);
127 cache
[*last
].string
= g_strdup (text
);
128 cache
[*last
].index
= id
;
129 *last
= ((*last
) + 1) % size
;
132 /* --------------------------------------------------------------------------------------------- */
133 /*** public functions ****************************************************************************/
134 /* --------------------------------------------------------------------------------------------- */
140 static char ibuf
[10];
144 name
= i_cache_match (uid
, uid_cache
, UID_CACHE_SIZE
);
148 pwd
= getpwuid (uid
);
151 i_cache_add (uid
, uid_cache
, UID_CACHE_SIZE
, pwd
->pw_name
, &uid_last
);
156 g_snprintf (ibuf
, sizeof (ibuf
), "%d", uid
);
161 /* --------------------------------------------------------------------------------------------- */
167 static char gbuf
[10];
171 name
= i_cache_match (gid
, gid_cache
, GID_CACHE_SIZE
);
175 grp
= getgrgid (gid
);
178 i_cache_add (gid
, gid_cache
, GID_CACHE_SIZE
, grp
->gr_name
, &gid_last
);
183 g_snprintf (gbuf
, sizeof (gbuf
), "%d", gid
);
188 /* --------------------------------------------------------------------------------------------- */
189 /* Since ncurses uses a handler that automatically refreshes the */
190 /* screen after a SIGCONT, and we don't want this behavior when */
191 /* spawning a child, we save the original handler here */
194 save_stop_handler (void)
196 sigaction (SIGTSTP
, NULL
, &startup_handler
);
199 /* --------------------------------------------------------------------------------------------- */
202 my_system (int flags
, const char *shell
, const char *command
)
204 struct sigaction ignore
, save_intr
, save_quit
, save_stop
;
208 ignore
.sa_handler
= SIG_IGN
;
209 sigemptyset (&ignore
.sa_mask
);
212 sigaction (SIGINT
, &ignore
, &save_intr
);
213 sigaction (SIGQUIT
, &ignore
, &save_quit
);
215 /* Restore the original SIGTSTP handler, we don't want ncurses' */
216 /* handler messing the screen after the SIGCONT */
217 sigaction (SIGTSTP
, &startup_handler
, &save_stop
);
222 fprintf (stderr
, "\n\nfork () = -1\n");
227 signal (SIGINT
, SIG_DFL
);
228 signal (SIGQUIT
, SIG_DFL
);
229 signal (SIGTSTP
, SIG_DFL
);
230 signal (SIGCHLD
, SIG_DFL
);
232 if (flags
& EXECUTE_AS_SHELL
)
233 execl (shell
, shell
, "-c", command
, (char *) NULL
);
236 gchar
**shell_tokens
;
237 const gchar
*only_cmd
;
239 shell_tokens
= g_strsplit (shell
, " ", 2);
240 if (shell_tokens
== NULL
)
243 only_cmd
= (*shell_tokens
!= NULL
) ? *shell_tokens
: shell
;
245 execlp (only_cmd
, shell
, command
, (char *) NULL
);
248 execlp will replace current process,
249 therefore no sence in call of g_strfreev().
250 But this keeped for estetic reason :)
252 g_strfreev (shell_tokens
);
256 _exit (127); /* Exec error */
262 if (waitpid (pid
, &status
, 0) > 0)
264 status
= WEXITSTATUS (status
);
274 sigaction (SIGINT
, &save_intr
, NULL
);
275 sigaction (SIGQUIT
, &save_quit
, NULL
);
276 sigaction (SIGTSTP
, &save_stop
, NULL
);
282 /* --------------------------------------------------------------------------------------------- */
284 * Perform tilde expansion if possible.
285 * Always return a newly allocated string, even if it's unchanged.
289 tilde_expand (const char *directory
)
291 struct passwd
*passwd
;
295 if (*directory
!= '~')
296 return g_strdup (directory
);
300 /* d = "~" or d = "~/" */
301 if (!(*p
) || (*p
== PATH_SEP
))
303 passwd
= getpwuid (geteuid ());
304 q
= (*p
== PATH_SEP
) ? p
+ 1 : "";
308 q
= strchr (p
, PATH_SEP
);
311 passwd
= getpwnam (p
);
315 name
= g_strndup (p
, q
- p
);
316 passwd
= getpwnam (name
);
322 /* If we can't figure the user name, leave tilde unexpanded */
324 return g_strdup (directory
);
326 return g_strconcat (passwd
->pw_dir
, PATH_SEP_STR
, q
, (char *) NULL
);
329 /* --------------------------------------------------------------------------------------------- */
331 * Return the directory where mc should keep its temporary files.
332 * This directory is (in Bourne shell terms) "${TMPDIR=/tmp}/mc-$USER"
333 * When called the first time, the directory is created if needed.
334 * The first call should be done early, since we are using fprintf()
335 * and not message() to report possible problems.
341 static char buffer
[64];
342 static const char *tmpdir
= NULL
;
346 const char *error
= NULL
;
348 /* Check if already correctly initialized */
349 if (tmpdir
&& lstat (tmpdir
, &st
) == 0 && S_ISDIR (st
.st_mode
) &&
350 st
.st_uid
== getuid () && (st
.st_mode
& 0777) == 0700)
353 sys_tmp
= getenv ("TMPDIR");
354 if (!sys_tmp
|| sys_tmp
[0] != '/')
356 sys_tmp
= TMPDIR_DEFAULT
;
359 pwd
= getpwuid (getuid ());
362 g_snprintf (buffer
, sizeof (buffer
), "%s/mc-%s", sys_tmp
, pwd
->pw_name
);
364 g_snprintf (buffer
, sizeof (buffer
), "%s/mc-%lu", sys_tmp
, (unsigned long) getuid ());
366 canonicalize_pathname (buffer
);
368 if (lstat (buffer
, &st
) == 0)
370 /* Sanity check for existing directory */
371 if (!S_ISDIR (st
.st_mode
))
372 error
= _("%s is not a directory\n");
373 else if (st
.st_uid
!= getuid ())
374 error
= _("Directory %s is not owned by you\n");
375 else if (((st
.st_mode
& 0777) != 0700) && (chmod (buffer
, 0700) != 0))
376 error
= _("Cannot set correct permissions for directory %s\n");
380 /* Need to create directory */
381 if (mkdir (buffer
, S_IRWXU
) != 0)
384 _("Cannot create temporary directory %s: %s\n"),
385 buffer
, unix_error_string (errno
));
393 char *test_fn
, *fallback_prefix
;
397 fprintf (stderr
, error
, buffer
);
399 /* Test if sys_tmp is suitable for temporary files */
400 fallback_prefix
= g_strdup_printf ("%s/mctest", sys_tmp
);
401 test_fd
= mc_mkstemps (&test_fn
, fallback_prefix
, NULL
);
402 g_free (fallback_prefix
);
406 test_fd
= open (test_fn
, O_RDONLY
);
417 fprintf (stderr
, _("Temporary files will be created in %s\n"), sys_tmp
);
418 g_snprintf (buffer
, sizeof (buffer
), "%s", sys_tmp
);
423 fprintf (stderr
, _("Temporary files will not be created\n"));
424 g_snprintf (buffer
, sizeof (buffer
), "%s", "/dev/null/");
427 fprintf (stderr
, "%s\n", _("Press any key to continue..."));
434 g_setenv ("MC_TMPDIR", tmpdir
, TRUE
);
439 /* --------------------------------------------------------------------------------------------- */
441 * Creates a pipe to hold standard error for a later analysis.
442 * The pipe can hold 4096 bytes. Make sure no more is written
443 * or a deadlock might occur.
447 open_error_pipe (void)
449 if (pipe (error_pipe
) < 0)
451 message (D_NORMAL
, _("Warning"), _("Pipe failed"));
454 if (old_error
< 0 || close (2) || dup (error_pipe
[1]) != 2)
456 message (D_NORMAL
, _("Warning"), _("Dup failed"));
458 close (error_pipe
[0]);
464 * Settng stderr in nonblocking mode as we close it earlier, than
465 * program stops. We try to read some error at program startup,
466 * but we should not block on it.
468 * TODO: make piped stdin/stderr poll()/select()able to get rid
472 fd_flags
= fcntl (error_pipe
[0], F_GETFL
, NULL
);
475 fd_flags
|= O_NONBLOCK
;
476 if (fcntl (error_pipe
[0], F_SETFL
, fd_flags
) == -1)
478 /* TODO: handle it somehow */
482 /* we never write there */
483 close (error_pipe
[1]);
487 /* --------------------------------------------------------------------------------------------- */
489 * Returns true if an error was displayed
490 * error: -1 - ignore errors, 0 - display warning, 1 - display error
491 * text is prepended to the error message from the pipe
495 close_error_pipe (int error
, const char *text
)
498 char msg
[MAX_PIPE_SIZE
];
502 if (error_pipe
[0] == -1)
505 if (error
< 0 || (error
> 0 && (error
& D_ERROR
) != 0))
508 title
= _("Warning");
511 if (dup2 (old_error
, 2) == -1)
516 message (error
, MSG_ERROR
, _("Error dup'ing old error pipe"));
520 len
= read (error_pipe
[0], msg
, MAX_PIPE_SIZE
- 1);
524 close (error_pipe
[0]);
528 return 0; /* Just ignore error message */
532 return 0; /* Nothing to show */
534 /* Show message from pipe */
535 message (error
, title
, "%s", msg
);
539 /* Show given text and possible message from pipe */
540 message (error
, title
, "%s\n%s", text
, msg
);
545 /* --------------------------------------------------------------------------------------------- */
547 * Canonicalize path, and return a new path. Do everything in place.
548 * The new path differs from path in:
549 * Multiple `/'s are collapsed to a single `/'.
550 * Leading `./'s and trailing `/.'s are removed.
551 * Trailing `/'s are removed.
552 * Non-leading `../'s and trailing `..'s are handled by removing
553 * portions of the path.
554 * Well formed UNC paths are modified only in the local part.
558 custom_canonicalize_pathname (char *path
, CANON_PATH_FLAGS flags
)
562 char *lpath
= path
; /* path without leading UNC part */
563 const size_t url_delim_len
= strlen (VFS_PATH_URL_DELIMITER
);
565 /* Detect and preserve UNC paths: //server/... */
566 if ((flags
& CANON_PATH_GUARDUNC
) && path
[0] == PATH_SEP
&& path
[1] == PATH_SEP
)
569 while (p
[0] && p
[0] != '/')
571 if (p
[0] == '/' && p
> path
+ 2)
575 if (!lpath
[0] || !lpath
[1])
578 if (flags
& CANON_PATH_JOINSLASHES
)
580 /* Collapse multiple slashes */
584 if (p
[0] == PATH_SEP
&& p
[1] == PATH_SEP
&& (p
== lpath
|| *(p
- 1) != ':'))
587 while (*(++s
) == PATH_SEP
);
594 if (flags
& CANON_PATH_JOINSLASHES
)
596 /* Collapse "/./" -> "/" */
600 if (p
[0] == PATH_SEP
&& p
[1] == '.' && p
[2] == PATH_SEP
)
607 if (flags
& CANON_PATH_REMSLASHDOTS
)
609 /* Remove trailing slashes */
610 p
= lpath
+ strlen (lpath
) - 1;
611 while (p
> lpath
&& *p
== PATH_SEP
)
613 if (p
>= lpath
- (url_delim_len
+ 1)
614 && strncmp (p
- url_delim_len
+ 1, VFS_PATH_URL_DELIMITER
, url_delim_len
) == 0)
619 /* Remove leading "./" */
620 if (lpath
[0] == '.' && lpath
[1] == PATH_SEP
)
629 str_move (lpath
, lpath
+ 2);
633 /* Remove trailing "/" or "/." */
634 len
= strlen (lpath
);
637 if (lpath
[len
- 1] == PATH_SEP
638 && (len
< url_delim_len
639 || strncmp (lpath
+ len
- url_delim_len
, VFS_PATH_URL_DELIMITER
,
640 url_delim_len
) != 0))
642 lpath
[len
- 1] = '\0';
646 if (lpath
[len
- 1] == '.' && lpath
[len
- 2] == PATH_SEP
)
655 lpath
[len
- 2] = '\0';
661 if (flags
& CANON_PATH_REMDOUBLEDOTS
)
663 const size_t enc_prefix_len
= strlen (VFS_ENCODING_PREFIX
);
665 /* Collapse "/.." with the previous part of path */
667 while (p
[0] && p
[1] && p
[2])
669 if ((p
[0] != PATH_SEP
|| p
[1] != '.' || p
[2] != '.') || (p
[3] != PATH_SEP
&& p
[3] != 0))
675 /* search for the previous token */
677 if (s
>= lpath
+ url_delim_len
- 2
678 && strncmp (s
- url_delim_len
+ 2, VFS_PATH_URL_DELIMITER
, url_delim_len
) == 0)
680 s
-= (url_delim_len
- 2);
681 while (s
>= lpath
&& *s
-- != PATH_SEP
);
686 if (s
- url_delim_len
> lpath
687 && strncmp (s
- url_delim_len
, VFS_PATH_URL_DELIMITER
, url_delim_len
) == 0)
689 char *vfs_prefix
= s
- url_delim_len
;
690 struct vfs_class
*vclass
;
692 while (vfs_prefix
> lpath
&& *--vfs_prefix
!= PATH_SEP
);
693 if (*vfs_prefix
== PATH_SEP
)
695 *(s
- url_delim_len
) = '\0';
697 vclass
= vfs_prefix_to_class (vfs_prefix
);
698 *(s
- url_delim_len
) = *VFS_PATH_URL_DELIMITER
;
702 struct vfs_s_subclass
*sub
= (struct vfs_s_subclass
*) vclass
->data
;
703 if (sub
!= NULL
&& sub
->flags
& VFS_S_REMOTE
)
719 /* If the previous token is "..", we cannot collapse it */
720 if (s
[0] == '.' && s
[1] == '.' && s
+ 2 == p
)
728 if (s
== lpath
&& *s
== PATH_SEP
)
730 /* "/../foo" -> "/foo" */
731 str_move (s
+ 1, p
+ 4);
735 /* "token/../foo" -> "foo" */
737 if ((strncmp (s
, VFS_ENCODING_PREFIX
, enc_prefix_len
) == 0)
738 && (is_supported_encoding (s
+ enc_prefix_len
)))
739 /* special case: remove encoding */
742 #endif /* HAVE_CHARSET */
745 p
= (s
> lpath
) ? s
- 1 : s
;
752 /* "token/.." -> "." */
753 if (lpath
[0] != PATH_SEP
)
761 /* "foo/token/.." -> "foo" */
765 else if ((strncmp (s
, VFS_ENCODING_PREFIX
, enc_prefix_len
) == 0)
766 && (is_supported_encoding (s
+ enc_prefix_len
)))
768 /* special case: remove encoding */
773 /* search for the previous token */
774 /* s[-1] == PATH_SEP */
776 while (p
>= lpath
&& *p
!= PATH_SEP
)
782 #endif /* HAVE_CHARSET */
785 if (s
>= lpath
+ url_delim_len
786 && strncmp (s
- url_delim_len
, VFS_PATH_URL_DELIMITER
, url_delim_len
) == 0)
799 /* --------------------------------------------------------------------------------------------- */
802 canonicalize_pathname (char *path
)
804 custom_canonicalize_pathname (path
, CANON_PATH_ALL
);
807 /* --------------------------------------------------------------------------------------------- */
809 #ifdef HAVE_GET_PROCESS_STATS
811 gettimeofday (struct timeval
*tp
, void *tzp
)
813 return get_process_stats (tp
, PS_SELF
, 0, 0);
815 #endif /* HAVE_GET_PROCESS_STATS */
817 /* --------------------------------------------------------------------------------------------- */
819 #ifndef HAVE_REALPATH
821 mc_realpath (const char *path
, char *resolved_path
)
823 char copy_path
[PATH_MAX
];
824 char link_path
[PATH_MAX
];
825 char got_path
[PATH_MAX
];
826 char *new_path
= got_path
;
831 /* Make a copy of the source path since we may need to modify it. */
832 if (strlen (path
) >= PATH_MAX
- 2)
834 errno
= ENAMETOOLONG
;
837 strcpy (copy_path
, path
);
839 max_path
= copy_path
+ PATH_MAX
- 2;
840 /* If it's a relative pathname use getwd for starters. */
844 new_path
= g_get_current_dir ();
845 if (new_path
== NULL
)
847 strcpy (got_path
, "");
851 g_snprintf (got_path
, PATH_MAX
, "%s", new_path
);
856 new_path
+= strlen (got_path
);
857 if (new_path
[-1] != '/')
865 /* Expand each slash-separated pathname component. */
866 while (*path
!= '\0')
868 /* Ignore stray "/". */
877 if (path
[1] == '\0' || path
[1] == '/')
884 if (path
[2] == '\0' || path
[2] == '/')
887 /* Ignore ".." at root. */
888 if (new_path
== got_path
+ 1)
890 /* Handle ".." by backing up. */
891 while ((--new_path
)[-1] != '/');
896 /* Safely copy the next pathname component. */
897 while (*path
!= '\0' && *path
!= '/')
901 errno
= ENAMETOOLONG
;
904 *new_path
++ = *path
++;
907 /* Protect against infinite loops. */
908 if (readlinks
++ > MAXSYMLINKS
)
913 /* See if latest pathname component is a symlink. */
915 n
= readlink (got_path
, link_path
, PATH_MAX
- 1);
918 /* EINVAL means the file exists but isn't a symlink. */
921 /* Make sure it's null terminated. */
923 strcpy (resolved_path
, got_path
);
929 /* Note: readlink doesn't add the null byte. */
931 if (*link_path
== '/')
932 /* Start over for an absolute symlink. */
935 /* Otherwise back up over this component. */
936 while (*(--new_path
) != '/');
937 /* Safe sex check. */
938 if (strlen (path
) + n
>= PATH_MAX
- 2)
940 errno
= ENAMETOOLONG
;
943 /* Insert symlink contents into path. */
944 strcat (link_path
, path
);
945 strcpy (copy_path
, link_path
);
951 /* Delete trailing slash but don't whomp a lone slash. */
952 if (new_path
!= got_path
+ 1 && new_path
[-1] == '/')
954 /* Make sure it's null terminated. */
956 strcpy (resolved_path
, got_path
);
957 return resolved_path
;
959 #endif /* HAVE_REALPATH */
961 /* --------------------------------------------------------------------------------------------- */
963 * Return the index of the permissions triplet
968 get_user_permissions (struct stat
*st
)
970 static gboolean initialized
= FALSE
;
971 static gid_t
*groups
;
980 ngroups
= getgroups (0, NULL
);
982 ngroups
= 0; /* ignore errors */
984 /* allocate space for one element in addition to what
985 * will be filled by getgroups(). */
986 groups
= g_new (gid_t
, ngroups
+ 1);
990 ngroups
= getgroups (ngroups
, groups
);
992 ngroups
= 0; /* ignore errors */
995 /* getgroups() may or may not return the effective group ID,
996 * so we always include it at the end of the list. */
997 groups
[ngroups
++] = getegid ();
1002 if (st
->st_uid
== uid
|| uid
== 0)
1005 for (i
= 0; i
< ngroups
; i
++)
1007 if (st
->st_gid
== groups
[i
])
1014 /* --------------------------------------------------------------------------------------------- */
1016 * Build filename from arguments.
1017 * Like to g_build_filename(), but respect VFS_PATH_URL_DELIMITER
1021 mc_build_filename (const char *first_element
, ...)
1025 const char *element
= first_element
;
1029 if (element
== NULL
)
1032 path
= g_string_new ("");
1033 va_start (args
, first_element
);
1035 absolute
= (*first_element
!= '\0' && *first_element
== PATH_SEP
);
1039 if (*element
== '\0')
1040 element
= va_arg (args
, char *);
1047 tmp_element
= g_strdup (element
);
1049 element
= va_arg (args
, char *);
1051 canonicalize_pathname (tmp_element
);
1052 len
= strlen (tmp_element
);
1053 start
= (tmp_element
[0] == PATH_SEP
) ? tmp_element
+ 1 : tmp_element
;
1055 g_string_append (path
, start
);
1056 if (tmp_element
[len
- 1] != PATH_SEP
&& element
!= NULL
)
1057 g_string_append_c (path
, PATH_SEP
);
1059 g_free (tmp_element
);
1062 while (element
!= NULL
);
1067 g_string_prepend_c (path
, PATH_SEP
);
1069 ret
= g_string_free (path
, FALSE
);
1070 canonicalize_pathname (ret
);
1075 /* --------------------------------------------------------------------------------------------- */