1 /* Various utilities - Unix variants
2 Copyright (C) 1994, 1995, 1996, 1998, 1999, 2000, 2001, 2002, 2003,
3 2004, 2005, 2007 Free Software Foundation, Inc.
4 Written 1994, 1995, 1996 by:
5 Miguel de Icaza, Janne Kukonlehto, Dugan Porter,
6 Jakub Jelinek, Mauricio Plaza.
8 The mc_realpath routine is mostly from uClibc package, written
9 by Rick Sladkey <jrs@world.std.com>
11 This program is free software; you can redistribute it and/or modify
12 it under the terms of the GNU General Public License as published by
13 the Free Software Foundation; either version 2 of the License, or
14 (at your option) any later version.
16 This program is distributed in the hope that it will be useful,
17 but WITHOUT ANY WARRANTY; without even the implied warranty of
18 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 GNU General Public License for more details.
21 You should have received a copy of the GNU General Public License
22 along with this program; if not, write to the Free Software
23 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */
36 #include <sys/param.h>
37 #include <sys/types.h>
39 #ifdef HAVE_SYS_IOCTL_H
40 # include <sys/ioctl.h>
44 #include <mhl/types.h>
45 #include <mhl/memory.h>
46 #include <mhl/string.h>
50 #include "wtools.h" /* message() */
52 struct sigaction startup_handler
;
54 #define UID_CACHE_SIZE 200
55 #define GID_CACHE_SIZE 30
62 static int_cache uid_cache
[UID_CACHE_SIZE
];
63 static int_cache gid_cache
[GID_CACHE_SIZE
];
65 static char *i_cache_match (int id
, int_cache
*cache
, int size
)
69 for (i
= 0; i
< size
; i
++)
70 if (cache
[i
].index
== id
)
71 return cache
[i
].string
;
75 static void i_cache_add (int id
, int_cache
*cache
, int size
, char *text
,
78 mhl_mem_free (cache
[*last
].string
);
79 cache
[*last
].string
= mhl_str_dup (text
);
80 cache
[*last
].index
= id
;
81 *last
= ((*last
)+1) % size
;
84 char *get_owner (int uid
)
87 static char ibuf
[10];
91 if ((name
= i_cache_match (uid
, uid_cache
, UID_CACHE_SIZE
)) != NULL
)
96 i_cache_add (uid
, uid_cache
, UID_CACHE_SIZE
, pwd
->pw_name
, &uid_last
);
100 snprintf (ibuf
, sizeof (ibuf
), "%d", uid
);
105 char *get_group (int gid
)
108 static char gbuf
[10];
112 if ((name
= i_cache_match (gid
, gid_cache
, GID_CACHE_SIZE
)) != NULL
)
115 grp
= getgrgid (gid
);
117 i_cache_add (gid
, gid_cache
, GID_CACHE_SIZE
, grp
->gr_name
, &gid_last
);
120 snprintf (gbuf
, sizeof (gbuf
), "%d", gid
);
125 /* Since ncurses uses a handler that automatically refreshes the */
126 /* screen after a SIGCONT, and we don't want this behavior when */
127 /* spawning a child, we save the original handler here */
128 void save_stop_handler (void)
130 sigaction (SIGTSTP
, NULL
, &startup_handler
);
133 int my_system (int flags
, const char *shell
, const char *command
)
135 struct sigaction ignore
, save_intr
, save_quit
, save_stop
;
139 ignore
.sa_handler
= SIG_IGN
;
140 sigemptyset (&ignore
.sa_mask
);
143 sigaction (SIGINT
, &ignore
, &save_intr
);
144 sigaction (SIGQUIT
, &ignore
, &save_quit
);
146 /* Restore the original SIGTSTP handler, we don't want ncurses' */
147 /* handler messing the screen after the SIGCONT */
148 sigaction (SIGTSTP
, &startup_handler
, &save_stop
);
150 if ((pid
= fork ()) < 0){
151 fprintf (stderr
, "\n\nfork () = -1\n");
155 signal (SIGINT
, SIG_DFL
);
156 signal (SIGQUIT
, SIG_DFL
);
157 signal (SIGTSTP
, SIG_DFL
);
158 signal (SIGCHLD
, SIG_DFL
);
160 if (flags
& EXECUTE_AS_SHELL
)
161 execl (shell
, shell
, "-c", command
, (char *) NULL
);
163 execlp (shell
, shell
, command
, (char *) NULL
);
165 _exit (127); /* Exec error */
167 while (waitpid (pid
, &status
, 0) < 0)
173 sigaction (SIGINT
, &save_intr
, NULL
);
174 sigaction (SIGQUIT
, &save_quit
, NULL
);
175 sigaction (SIGTSTP
, &save_stop
, NULL
);
177 return WEXITSTATUS(status
);
182 * Perform tilde expansion if possible.
183 * Always return a newly allocated string, even if it's unchanged.
186 tilde_expand (const char *directory
)
188 struct passwd
*passwd
;
192 if (*directory
!= '~')
193 return mhl_str_dup (directory
);
197 /* d = "~" or d = "~/" */
198 if (!(*p
) || (*p
== PATH_SEP
)) {
199 passwd
= getpwuid (geteuid ());
200 q
= (*p
== PATH_SEP
) ? p
+ 1 : "";
202 q
= strchr (p
, PATH_SEP
);
204 passwd
= getpwnam (p
);
206 name
= g_strndup (p
, q
- p
);
207 passwd
= getpwnam (name
);
213 /* If we can't figure the user name, leave tilde unexpanded */
215 return mhl_str_dup (directory
);
217 return g_strconcat (passwd
->pw_dir
, PATH_SEP_STR
, q
, (char *) NULL
);
221 mc_setenv (const char *name
, const char *value
, int overwrite_flag
)
223 #if defined(HAVE_SETENV)
224 setenv (name
, value
, overwrite_flag
);
226 if (overwrite_flag
|| getenv (name
) == NULL
)
227 putenv (g_strconcat (name
, "=", value
, (char *) NULL
));
232 * Return the directory where mc should keep its temporary files.
233 * This directory is (in Bourne shell terms) "${TMPDIR=/tmp}/mc-$USER"
234 * When called the first time, the directory is created if needed.
235 * The first call should be done early, since we are using fprintf()
236 * and not message() to report possible problems.
241 static char buffer
[64];
242 static const char *tmpdir
;
246 const char *error
= NULL
;
248 /* Check if already correctly initialized */
249 if (tmpdir
&& lstat (tmpdir
, &st
) == 0 && S_ISDIR (st
.st_mode
) &&
250 st
.st_uid
== getuid () && (st
.st_mode
& 0777) == 0700)
253 sys_tmp
= getenv ("TMPDIR");
254 if (!sys_tmp
|| sys_tmp
[0] != '/') {
255 sys_tmp
= TMPDIR_DEFAULT
;
258 pwd
= getpwuid (getuid ());
261 snprintf (buffer
, sizeof (buffer
), "%s/mc-%s", sys_tmp
,
264 snprintf (buffer
, sizeof (buffer
), "%s/mc-%lu", sys_tmp
,
265 (unsigned long) getuid ());
267 canonicalize_pathname (buffer
);
269 if (lstat (buffer
, &st
) == 0) {
270 /* Sanity check for existing directory */
271 if (!S_ISDIR (st
.st_mode
))
272 error
= _("%s is not a directory\n");
273 else if (st
.st_uid
!= getuid ())
274 error
= _("Directory %s is not owned by you\n");
275 else if (((st
.st_mode
& 0777) != 0700)
276 && (chmod (buffer
, 0700) != 0))
277 error
= _("Cannot set correct permissions for directory %s\n");
279 /* Need to create directory */
280 if (mkdir (buffer
, S_IRWXU
) != 0) {
282 _("Cannot create temporary directory %s: %s\n"),
283 buffer
, unix_error_string (errno
));
290 char *test_fn
, *fallback_prefix
;
294 fprintf (stderr
, error
, buffer
);
296 /* Test if sys_tmp is suitable for temporary files */
297 fallback_prefix
= g_strdup_printf ("%s/mctest", sys_tmp
);
298 test_fd
= mc_mkstemps (&test_fn
, fallback_prefix
, NULL
);
299 mhl_mem_free (fallback_prefix
);
302 test_fd
= open (test_fn
, O_RDONLY
);
311 fprintf (stderr
, _("Temporary files will be created in %s\n"),
313 snprintf (buffer
, sizeof (buffer
), "%s", sys_tmp
);
316 fprintf (stderr
, _("Temporary files will not be created\n"));
317 snprintf (buffer
, sizeof (buffer
), "%s", "/dev/null/");
320 fprintf (stderr
, "%s\n", _("Press any key to continue..."));
327 mc_setenv ("MC_TMPDIR", tmpdir
, 1);
333 /* Pipes are guaranteed to be able to hold at least 4096 bytes */
334 /* More than that would be unportable */
335 #define MAX_PIPE_SIZE 4096
337 static int error_pipe
[2]; /* File descriptors of error pipe */
338 static int old_error
; /* File descriptor of old standard error */
340 /* Creates a pipe to hold standard error for a later analysis. */
341 /* The pipe can hold 4096 bytes. Make sure no more is written */
342 /* or a deadlock might occur. */
343 void open_error_pipe (void)
345 if (pipe (error_pipe
) < 0){
346 message (D_NORMAL
, _("Warning"), _(" Pipe failed "));
349 if(old_error
< 0 || close(2) || dup (error_pipe
[1]) != 2){
350 message (D_NORMAL
, _("Warning"), _(" Dup failed "));
351 close (error_pipe
[0]);
352 close (error_pipe
[1]);
354 close (error_pipe
[1]);
358 * Returns true if an error was displayed
359 * error: -1 - ignore errors, 0 - display warning, 1 - display error
360 * text is prepended to the error message from the pipe
363 close_error_pipe (int error
, const char *text
)
366 char msg
[MAX_PIPE_SIZE
];
372 title
= _("Warning");
377 len
= read (error_pipe
[0], msg
, MAX_PIPE_SIZE
- 1);
381 close (error_pipe
[0]);
384 return 0; /* Just ignore error message */
387 return 0; /* Nothing to show */
389 /* Show message from pipe */
390 message (error
, title
, "%s", msg
);
392 /* Show given text and possible message from pipe */
393 message (error
, title
, " %s \n %s ", text
, msg
);
399 * Canonicalize path, and return a new path. Do everything in place.
400 * The new path differs from path in:
401 * Multiple `/'s are collapsed to a single `/'.
402 * Leading `./'s and trailing `/.'s are removed.
403 * Trailing `/'s are removed.
404 * Non-leading `../'s and trailing `..'s are handled by removing
405 * portions of the path.
406 * Well formed UNC paths are modified only in the local part.
409 canonicalize_pathname (char *path
)
413 char *lpath
= path
; /* path without leading UNC part */
415 /* Detect and preserve UNC paths: //server/... */
416 if (path
[0] == PATH_SEP
&& path
[1] == PATH_SEP
) {
418 while (p
[0] && p
[0] != '/')
420 if (p
[0] == '/' && p
> path
+ 2)
424 if (!lpath
[0] || !lpath
[1])
427 /* Collapse multiple slashes */
430 if (p
[0] == PATH_SEP
&& p
[1] == PATH_SEP
) {
432 while (*(++s
) == PATH_SEP
);
433 mhl_strmove (p
+ 1, s
);
438 /* Collapse "/./" -> "/" */
441 if (p
[0] == PATH_SEP
&& p
[1] == '.' && p
[2] == PATH_SEP
)
442 mhl_strmove (p
, p
+ 2);
447 /* Remove trailing slashes */
448 p
= lpath
+ strlen (lpath
) - 1;
449 while (p
> lpath
&& *p
== PATH_SEP
)
452 /* Remove leading "./" */
453 if (lpath
[0] == '.' && lpath
[1] == PATH_SEP
) {
458 mhl_strmove (lpath
, lpath
+ 2);
462 /* Remove trailing "/" or "/." */
463 len
= strlen (lpath
);
466 if (lpath
[len
- 1] == PATH_SEP
) {
469 if (lpath
[len
- 1] == '.' && lpath
[len
- 2] == PATH_SEP
) {
479 /* Collapse "/.." with the previous part of path */
481 while (p
[0] && p
[1] && p
[2]) {
482 if ((p
[0] != PATH_SEP
|| p
[1] != '.' || p
[2] != '.')
483 || (p
[3] != PATH_SEP
&& p
[3] != 0)) {
488 /* search for the previous token */
490 while (s
>= lpath
&& *s
!= PATH_SEP
)
495 /* If the previous token is "..", we cannot collapse it */
496 if (s
[0] == '.' && s
[1] == '.' && s
+ 2 == p
) {
502 if (s
== lpath
&& *s
== PATH_SEP
) {
503 /* "/../foo" -> "/foo" */
504 mhl_strmove (s
+ 1, p
+ 4);
506 /* "token/../foo" -> "foo" */
507 mhl_strmove (s
, p
+ 4);
509 p
= (s
> lpath
) ? s
- 1 : s
;
515 /* "token/.." -> "." */
516 if (lpath
[0] != PATH_SEP
) {
521 /* "foo/token/.." -> "foo" */
533 #ifdef HAVE_GET_PROCESS_STATS
534 # include <sys/procstats.h>
536 int gettimeofday (struct timeval
*tp
, void *tzp
)
538 return get_process_stats(tp
, PS_SELF
, 0, 0);
540 #endif /* HAVE_GET_PROCESS_STATS */
544 /* The following piece of code was copied from the GNU C Library */
545 /* And is provided here for nextstep who lacks putenv */
547 extern char **environ
;
550 #define __environ environ
554 /* Put STRING, which is of the form "NAME=VALUE", in the environment. */
556 putenv (char *string
)
558 const char *const name_end
= strchr (string
, '=');
559 register size_t size
;
562 if (name_end
== NULL
){
563 /* Remove the variable from the environment. */
564 size
= strlen (string
);
565 for (ep
= __environ
; *ep
!= NULL
; ++ep
)
566 if (!strncmp (*ep
, string
, size
) && (*ep
)[size
] == '='){
567 while (ep
[1] != NULL
){
577 for (ep
= __environ
; *ep
!= NULL
; ++ep
)
578 if (!strncmp (*ep
, string
, name_end
- string
) &&
579 (*ep
)[name_end
- string
] == '=')
585 static char **last_environ
= NULL
;
586 char **new_environ
= g_new (char *, size
+ 2);
587 if (new_environ
== NULL
)
589 (void) memcpy ((void *) new_environ
, (void *) __environ
,
590 size
* sizeof (char *));
591 new_environ
[size
] = (char *) string
;
592 new_environ
[size
+ 1] = NULL
;
593 mhl_mem_free ((void *) last_environ
);
594 last_environ
= new_environ
;
595 __environ
= new_environ
;
598 *ep
= (char *) string
;
602 #endif /* !HAVE_PUTENV */
605 mc_realpath (const char *path
, char resolved_path
[])
607 #ifdef USE_SYSTEM_REALPATH
608 return realpath (path
, resolved_path
);
610 char copy_path
[PATH_MAX
];
611 char link_path
[PATH_MAX
];
612 char got_path
[PATH_MAX
];
613 char *new_path
= got_path
;
618 /* Make a copy of the source path since we may need to modify it. */
619 if (strlen (path
) >= PATH_MAX
- 2) {
620 errno
= ENAMETOOLONG
;
623 strcpy (copy_path
, path
);
625 max_path
= copy_path
+ PATH_MAX
- 2;
626 /* If it's a relative pathname use getwd for starters. */
630 getcwd (new_path
, PATH_MAX
- 1);
634 new_path
+= strlen (new_path
);
635 if (new_path
[-1] != '/')
641 /* Expand each slash-separated pathname component. */
642 while (*path
!= '\0') {
643 /* Ignore stray "/". */
650 if (path
[1] == '\0' || path
[1] == '/') {
654 if (path
[1] == '.') {
655 if (path
[2] == '\0' || path
[2] == '/') {
657 /* Ignore ".." at root. */
658 if (new_path
== got_path
+ 1)
660 /* Handle ".." by backing up. */
661 while ((--new_path
)[-1] != '/');
666 /* Safely copy the next pathname component. */
667 while (*path
!= '\0' && *path
!= '/') {
668 if (path
> max_path
) {
669 errno
= ENAMETOOLONG
;
672 *new_path
++ = *path
++;
675 /* Protect against infinite loops. */
676 if (readlinks
++ > MAXSYMLINKS
) {
680 /* See if latest pathname component is a symlink. */
682 n
= readlink (got_path
, link_path
, PATH_MAX
- 1);
684 /* EINVAL means the file exists but isn't a symlink. */
685 if (errno
!= EINVAL
) {
686 /* Make sure it's null terminated. */
688 strcpy (resolved_path
, got_path
);
692 /* Note: readlink doesn't add the null byte. */
694 if (*link_path
== '/')
695 /* Start over for an absolute symlink. */
698 /* Otherwise back up over this component. */
699 while (*(--new_path
) != '/');
700 /* Safe sex check. */
701 if (strlen (path
) + n
>= PATH_MAX
- 2) {
702 errno
= ENAMETOOLONG
;
705 /* Insert symlink contents into path. */
706 strcat (link_path
, path
);
707 strcpy (copy_path
, link_path
);
713 /* Delete trailing slash but don't whomp a lone slash. */
714 if (new_path
!= got_path
+ 1 && new_path
[-1] == '/')
716 /* Make sure it's null terminated. */
718 strcpy (resolved_path
, got_path
);
719 return resolved_path
;
720 #endif /* USE_SYSTEM_REALPATH */
723 /* Return the index of the permissions triplet */
725 get_user_permissions (struct stat
*st
) {
726 static bool initialized
= FALSE
;
727 static gid_t
*groups
;
735 ngroups
= getgroups (0, NULL
);
737 ngroups
= 0; /* ignore errors */
739 /* allocate space for one element in addition to what
740 * will be filled by getgroups(). */
741 groups
= g_new (gid_t
, ngroups
+ 1);
744 ngroups
= getgroups (ngroups
, groups
);
746 ngroups
= 0; /* ignore errors */
749 /* getgroups() may or may not return the effective group ID,
750 * so we always include it at the end of the list. */
751 groups
[ngroups
++] = getegid ();
756 if (st
->st_uid
== uid
|| uid
== 0)
759 for (i
= 0; i
< ngroups
; i
++) {
760 if (st
->st_gid
== groups
[i
])