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 * Creates a pipe to hold standard error for a later analysis.
332 * The pipe can hold 4096 bytes. Make sure no more is written
333 * or a deadlock might occur.
337 open_error_pipe (void)
339 if (pipe (error_pipe
) < 0)
341 message (D_NORMAL
, _("Warning"), _("Pipe failed"));
344 if (old_error
< 0 || close (2) || dup (error_pipe
[1]) != 2)
346 message (D_NORMAL
, _("Warning"), _("Dup failed"));
348 close (error_pipe
[0]);
354 * Settng stderr in nonblocking mode as we close it earlier, than
355 * program stops. We try to read some error at program startup,
356 * but we should not block on it.
358 * TODO: make piped stdin/stderr poll()/select()able to get rid
362 fd_flags
= fcntl (error_pipe
[0], F_GETFL
, NULL
);
365 fd_flags
|= O_NONBLOCK
;
366 if (fcntl (error_pipe
[0], F_SETFL
, fd_flags
) == -1)
368 /* TODO: handle it somehow */
372 /* we never write there */
373 close (error_pipe
[1]);
377 /* --------------------------------------------------------------------------------------------- */
379 * Returns true if an error was displayed
380 * error: -1 - ignore errors, 0 - display warning, 1 - display error
381 * text is prepended to the error message from the pipe
385 close_error_pipe (int error
, const char *text
)
388 char msg
[MAX_PIPE_SIZE
];
392 if (error_pipe
[0] == -1)
395 if (error
< 0 || (error
> 0 && (error
& D_ERROR
) != 0))
398 title
= _("Warning");
401 if (dup2 (old_error
, 2) == -1)
406 message (error
, MSG_ERROR
, _("Error dup'ing old error pipe"));
410 len
= read (error_pipe
[0], msg
, MAX_PIPE_SIZE
- 1);
414 close (error_pipe
[0]);
418 return 0; /* Just ignore error message */
422 return 0; /* Nothing to show */
424 /* Show message from pipe */
425 message (error
, title
, "%s", msg
);
429 /* Show given text and possible message from pipe */
430 message (error
, title
, "%s\n%s", text
, msg
);
435 /* --------------------------------------------------------------------------------------------- */
437 * Canonicalize path, and return a new path. Do everything in place.
438 * The new path differs from path in:
439 * Multiple `/'s are collapsed to a single `/'.
440 * Leading `./'s and trailing `/.'s are removed.
441 * Trailing `/'s are removed.
442 * Non-leading `../'s and trailing `..'s are handled by removing
443 * portions of the path.
444 * Well formed UNC paths are modified only in the local part.
448 custom_canonicalize_pathname (char *path
, CANON_PATH_FLAGS flags
)
452 char *lpath
= path
; /* path without leading UNC part */
453 const size_t url_delim_len
= strlen (VFS_PATH_URL_DELIMITER
);
455 /* Detect and preserve UNC paths: //server/... */
456 if ((flags
& CANON_PATH_GUARDUNC
) && path
[0] == PATH_SEP
&& path
[1] == PATH_SEP
)
459 while (p
[0] && p
[0] != '/')
461 if (p
[0] == '/' && p
> path
+ 2)
465 if (!lpath
[0] || !lpath
[1])
468 if (flags
& CANON_PATH_JOINSLASHES
)
470 /* Collapse multiple slashes */
474 if (p
[0] == PATH_SEP
&& p
[1] == PATH_SEP
&& (p
== lpath
|| *(p
- 1) != ':'))
477 while (*(++s
) == PATH_SEP
);
484 if (flags
& CANON_PATH_JOINSLASHES
)
486 /* Collapse "/./" -> "/" */
490 if (p
[0] == PATH_SEP
&& p
[1] == '.' && p
[2] == PATH_SEP
)
497 if (flags
& CANON_PATH_REMSLASHDOTS
)
499 /* Remove trailing slashes */
500 p
= lpath
+ strlen (lpath
) - 1;
501 while (p
> lpath
&& *p
== PATH_SEP
)
503 if (p
>= lpath
- (url_delim_len
+ 1)
504 && strncmp (p
- url_delim_len
+ 1, VFS_PATH_URL_DELIMITER
, url_delim_len
) == 0)
509 /* Remove leading "./" */
510 if (lpath
[0] == '.' && lpath
[1] == PATH_SEP
)
519 str_move (lpath
, lpath
+ 2);
523 /* Remove trailing "/" or "/." */
524 len
= strlen (lpath
);
527 if (lpath
[len
- 1] == PATH_SEP
528 && (len
< url_delim_len
529 || strncmp (lpath
+ len
- url_delim_len
, VFS_PATH_URL_DELIMITER
,
530 url_delim_len
) != 0))
532 lpath
[len
- 1] = '\0';
536 if (lpath
[len
- 1] == '.' && lpath
[len
- 2] == PATH_SEP
)
545 lpath
[len
- 2] = '\0';
551 if (flags
& CANON_PATH_REMDOUBLEDOTS
)
553 const size_t enc_prefix_len
= strlen (VFS_ENCODING_PREFIX
);
555 /* Collapse "/.." with the previous part of path */
557 while (p
[0] && p
[1] && p
[2])
559 if ((p
[0] != PATH_SEP
|| p
[1] != '.' || p
[2] != '.') || (p
[3] != PATH_SEP
&& p
[3] != 0))
565 /* search for the previous token */
567 if (s
>= lpath
+ url_delim_len
- 2
568 && strncmp (s
- url_delim_len
+ 2, VFS_PATH_URL_DELIMITER
, url_delim_len
) == 0)
570 s
-= (url_delim_len
- 2);
571 while (s
>= lpath
&& *s
-- != PATH_SEP
);
576 if (s
- url_delim_len
> lpath
577 && strncmp (s
- url_delim_len
, VFS_PATH_URL_DELIMITER
, url_delim_len
) == 0)
579 char *vfs_prefix
= s
- url_delim_len
;
580 struct vfs_class
*vclass
;
582 while (vfs_prefix
> lpath
&& *--vfs_prefix
!= PATH_SEP
);
583 if (*vfs_prefix
== PATH_SEP
)
585 *(s
- url_delim_len
) = '\0';
587 vclass
= vfs_prefix_to_class (vfs_prefix
);
588 *(s
- url_delim_len
) = *VFS_PATH_URL_DELIMITER
;
592 struct vfs_s_subclass
*sub
= (struct vfs_s_subclass
*) vclass
->data
;
593 if (sub
!= NULL
&& sub
->flags
& VFS_S_REMOTE
)
609 /* If the previous token is "..", we cannot collapse it */
610 if (s
[0] == '.' && s
[1] == '.' && s
+ 2 == p
)
618 if (s
== lpath
&& *s
== PATH_SEP
)
620 /* "/../foo" -> "/foo" */
621 str_move (s
+ 1, p
+ 4);
625 /* "token/../foo" -> "foo" */
627 if ((strncmp (s
, VFS_ENCODING_PREFIX
, enc_prefix_len
) == 0)
628 && (is_supported_encoding (s
+ enc_prefix_len
)))
629 /* special case: remove encoding */
632 #endif /* HAVE_CHARSET */
635 p
= (s
> lpath
) ? s
- 1 : s
;
642 /* "token/.." -> "." */
643 if (lpath
[0] != PATH_SEP
)
651 /* "foo/token/.." -> "foo" */
655 else if ((strncmp (s
, VFS_ENCODING_PREFIX
, enc_prefix_len
) == 0)
656 && (is_supported_encoding (s
+ enc_prefix_len
)))
658 /* special case: remove encoding */
663 /* search for the previous token */
664 /* s[-1] == PATH_SEP */
666 while (p
>= lpath
&& *p
!= PATH_SEP
)
672 #endif /* HAVE_CHARSET */
675 if (s
>= lpath
+ url_delim_len
676 && strncmp (s
- url_delim_len
, VFS_PATH_URL_DELIMITER
, url_delim_len
) == 0)
689 /* --------------------------------------------------------------------------------------------- */
692 canonicalize_pathname (char *path
)
694 custom_canonicalize_pathname (path
, CANON_PATH_ALL
);
697 /* --------------------------------------------------------------------------------------------- */
699 #ifdef HAVE_GET_PROCESS_STATS
701 gettimeofday (struct timeval
*tp
, void *tzp
)
703 return get_process_stats (tp
, PS_SELF
, 0, 0);
705 #endif /* HAVE_GET_PROCESS_STATS */
707 /* --------------------------------------------------------------------------------------------- */
709 #ifndef HAVE_REALPATH
711 mc_realpath (const char *path
, char *resolved_path
)
713 char copy_path
[PATH_MAX
];
714 char link_path
[PATH_MAX
];
715 char got_path
[PATH_MAX
];
716 char *new_path
= got_path
;
721 /* Make a copy of the source path since we may need to modify it. */
722 if (strlen (path
) >= PATH_MAX
- 2)
724 errno
= ENAMETOOLONG
;
727 strcpy (copy_path
, path
);
729 max_path
= copy_path
+ PATH_MAX
- 2;
730 /* If it's a relative pathname use getwd for starters. */
734 new_path
= g_get_current_dir ();
735 if (new_path
== NULL
)
737 strcpy (got_path
, "");
741 g_snprintf (got_path
, PATH_MAX
, "%s", new_path
);
746 new_path
+= strlen (got_path
);
747 if (new_path
[-1] != '/')
755 /* Expand each slash-separated pathname component. */
756 while (*path
!= '\0')
758 /* Ignore stray "/". */
767 if (path
[1] == '\0' || path
[1] == '/')
774 if (path
[2] == '\0' || path
[2] == '/')
777 /* Ignore ".." at root. */
778 if (new_path
== got_path
+ 1)
780 /* Handle ".." by backing up. */
781 while ((--new_path
)[-1] != '/');
786 /* Safely copy the next pathname component. */
787 while (*path
!= '\0' && *path
!= '/')
791 errno
= ENAMETOOLONG
;
794 *new_path
++ = *path
++;
797 /* Protect against infinite loops. */
798 if (readlinks
++ > MAXSYMLINKS
)
803 /* See if latest pathname component is a symlink. */
805 n
= readlink (got_path
, link_path
, PATH_MAX
- 1);
808 /* EINVAL means the file exists but isn't a symlink. */
811 /* Make sure it's null terminated. */
813 strcpy (resolved_path
, got_path
);
819 /* Note: readlink doesn't add the null byte. */
821 if (*link_path
== '/')
822 /* Start over for an absolute symlink. */
825 /* Otherwise back up over this component. */
826 while (*(--new_path
) != '/');
827 /* Safe sex check. */
828 if (strlen (path
) + n
>= PATH_MAX
- 2)
830 errno
= ENAMETOOLONG
;
833 /* Insert symlink contents into path. */
834 strcat (link_path
, path
);
835 strcpy (copy_path
, link_path
);
841 /* Delete trailing slash but don't whomp a lone slash. */
842 if (new_path
!= got_path
+ 1 && new_path
[-1] == '/')
844 /* Make sure it's null terminated. */
846 strcpy (resolved_path
, got_path
);
847 return resolved_path
;
849 #endif /* HAVE_REALPATH */
851 /* --------------------------------------------------------------------------------------------- */
853 * Return the index of the permissions triplet
858 get_user_permissions (struct stat
*st
)
860 static gboolean initialized
= FALSE
;
861 static gid_t
*groups
;
870 ngroups
= getgroups (0, NULL
);
872 ngroups
= 0; /* ignore errors */
874 /* allocate space for one element in addition to what
875 * will be filled by getgroups(). */
876 groups
= g_new (gid_t
, ngroups
+ 1);
880 ngroups
= getgroups (ngroups
, groups
);
882 ngroups
= 0; /* ignore errors */
885 /* getgroups() may or may not return the effective group ID,
886 * so we always include it at the end of the list. */
887 groups
[ngroups
++] = getegid ();
892 if (st
->st_uid
== uid
|| uid
== 0)
895 for (i
= 0; i
< ngroups
; i
++)
897 if (st
->st_gid
== groups
[i
])
904 /* --------------------------------------------------------------------------------------------- */
906 * Build filename from arguments.
907 * Like to g_build_filename(), but respect VFS_PATH_URL_DELIMITER
911 mc_build_filenamev (const char *first_element
, va_list args
)
914 const char *element
= first_element
;
921 path
= g_string_new ("");
923 absolute
= (*first_element
!= '\0' && *first_element
== PATH_SEP
);
927 if (*element
== '\0')
928 element
= va_arg (args
, char *);
935 tmp_element
= g_strdup (element
);
937 element
= va_arg (args
, char *);
939 canonicalize_pathname (tmp_element
);
940 len
= strlen (tmp_element
);
941 start
= (tmp_element
[0] == PATH_SEP
) ? tmp_element
+ 1 : tmp_element
;
943 g_string_append (path
, start
);
944 if (tmp_element
[len
- 1] != PATH_SEP
&& element
!= NULL
)
945 g_string_append_c (path
, PATH_SEP
);
947 g_free (tmp_element
);
950 while (element
!= NULL
);
953 g_string_prepend_c (path
, PATH_SEP
);
955 ret
= g_string_free (path
, FALSE
);
956 canonicalize_pathname (ret
);
961 /* --------------------------------------------------------------------------------------------- */
963 * Build filename from arguments.
964 * Like to g_build_filename(), but respect VFS_PATH_URL_DELIMITER
968 mc_build_filename (const char *first_element
, ...)
973 if (first_element
== NULL
)
976 va_start (args
, first_element
);
977 ret
= mc_build_filenamev (first_element
, args
);
982 /* --------------------------------------------------------------------------------------------- */