First bunch of mhl_mem_free removal patches
[midnight-commander.git] / src / utilunix.c
blobc322ef09a8dc9a665167d41e1450dd92ce3f9c49
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. */
25 #include <config.h>
27 #include <ctype.h>
28 #include <errno.h>
29 #include <limits.h>
30 #include <signal.h>
31 #include <stdarg.h>
32 #include <stdio.h>
33 #include <stdlib.h>
34 #include <string.h>
36 #include <sys/param.h>
37 #include <sys/types.h>
38 #include <sys/stat.h>
39 #ifdef HAVE_SYS_IOCTL_H
40 # include <sys/ioctl.h>
41 #endif
42 #include <unistd.h>
44 #include <mhl/types.h>
45 #include <mhl/memory.h>
46 #include <mhl/string.h>
48 #include "global.h"
49 #include "execute.h"
50 #include "wtools.h" /* message() */
52 struct sigaction startup_handler;
54 #define UID_CACHE_SIZE 200
55 #define GID_CACHE_SIZE 30
57 typedef struct {
58 int index;
59 char *string;
60 } int_cache;
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)
67 int i;
69 for (i = 0; i < size; i++)
70 if (cache [i].index == id)
71 return cache [i].string;
72 return 0;
75 static void i_cache_add (int id, int_cache *cache, int size, char *text,
76 int *last)
78 g_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)
86 struct passwd *pwd;
87 static char ibuf [10];
88 char *name;
89 static int uid_last;
91 if ((name = i_cache_match (uid, uid_cache, UID_CACHE_SIZE)) != NULL)
92 return name;
94 pwd = getpwuid (uid);
95 if (pwd){
96 i_cache_add (uid, uid_cache, UID_CACHE_SIZE, pwd->pw_name, &uid_last);
97 return pwd->pw_name;
99 else {
100 snprintf (ibuf, sizeof (ibuf), "%d", uid);
101 return ibuf;
105 char *get_group (int gid)
107 struct group *grp;
108 static char gbuf [10];
109 char *name;
110 static int gid_last;
112 if ((name = i_cache_match (gid, gid_cache, GID_CACHE_SIZE)) != NULL)
113 return name;
115 grp = getgrgid (gid);
116 if (grp){
117 i_cache_add (gid, gid_cache, GID_CACHE_SIZE, grp->gr_name, &gid_last);
118 return grp->gr_name;
119 } else {
120 snprintf (gbuf, sizeof (gbuf), "%d", gid);
121 return gbuf;
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;
136 pid_t pid;
137 int status = 0;
139 ignore.sa_handler = SIG_IGN;
140 sigemptyset (&ignore.sa_mask);
141 ignore.sa_flags = 0;
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");
152 return -1;
154 if (pid == 0){
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);
162 else
163 execlp (shell, shell, command, (char *) NULL);
165 _exit (127); /* Exec error */
166 } else {
167 while (waitpid (pid, &status, 0) < 0)
168 if (errno != EINTR){
169 status = -1;
170 break;
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.
185 char *
186 tilde_expand (const char *directory)
188 struct passwd *passwd;
189 const char *p, *q;
190 char *name;
192 if (*directory != '~')
193 return mhl_str_dup (directory);
195 p = directory + 1;
197 /* d = "~" or d = "~/" */
198 if (!(*p) || (*p == PATH_SEP)) {
199 passwd = getpwuid (geteuid ());
200 q = (*p == PATH_SEP) ? p + 1 : "";
201 } else {
202 q = strchr (p, PATH_SEP);
203 if (!q) {
204 passwd = getpwnam (p);
205 } else {
206 name = g_strndup (p, q - p);
207 passwd = getpwnam (name);
208 q++;
209 g_free (name);
213 /* If we can't figure the user name, leave tilde unexpanded */
214 if (!passwd)
215 return mhl_str_dup (directory);
217 return g_strconcat (passwd->pw_dir, PATH_SEP_STR, q, (char *) NULL);
220 static void
221 mc_setenv (const char *name, const char *value, int overwrite_flag)
223 #if defined(HAVE_SETENV)
224 setenv (name, value, overwrite_flag);
225 #else
226 if (overwrite_flag || getenv (name) == NULL)
227 putenv (g_strconcat (name, "=", value, (char *) NULL));
228 #endif
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.
238 const char *
239 mc_tmpdir (void)
241 static char buffer[64];
242 static const char *tmpdir;
243 const char *sys_tmp;
244 struct passwd *pwd;
245 struct stat st;
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)
251 return tmpdir;
253 sys_tmp = getenv ("TMPDIR");
254 if (!sys_tmp || sys_tmp[0] != '/') {
255 sys_tmp = TMPDIR_DEFAULT;
258 pwd = getpwuid (getuid ());
260 if (pwd)
261 snprintf (buffer, sizeof (buffer), "%s/mc-%s", sys_tmp,
262 pwd->pw_name);
263 else
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");
278 } else {
279 /* Need to create directory */
280 if (mkdir (buffer, S_IRWXU) != 0) {
281 fprintf (stderr,
282 _("Cannot create temporary directory %s: %s\n"),
283 buffer, unix_error_string (errno));
284 error = "";
288 if (error != NULL) {
289 int test_fd;
290 char *test_fn, *fallback_prefix;
291 int fallback_ok = 0;
293 if (*error)
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 g_free (fallback_prefix);
300 if (test_fd != -1) {
301 close (test_fd);
302 test_fd = open (test_fn, O_RDONLY);
303 if (test_fd != -1) {
304 close (test_fd);
305 unlink (test_fn);
306 fallback_ok = 1;
310 if (fallback_ok) {
311 fprintf (stderr, _("Temporary files will be created in %s\n"),
312 sys_tmp);
313 snprintf (buffer, sizeof (buffer), "%s", sys_tmp);
314 error = NULL;
315 } else {
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..."));
321 getc (stdin);
324 tmpdir = buffer;
326 if (!error)
327 mc_setenv ("MC_TMPDIR", tmpdir, 1);
329 return tmpdir;
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 "));
348 old_error = dup (2);
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)
365 const char *title;
366 char msg[MAX_PIPE_SIZE];
367 int len = 0;
369 if (error)
370 title = MSG_ERROR;
371 else
372 title = _("Warning");
373 if (old_error >= 0){
374 close (2);
375 dup (old_error);
376 close (old_error);
377 len = read (error_pipe[0], msg, MAX_PIPE_SIZE - 1);
379 if (len >= 0)
380 msg[len] = 0;
381 close (error_pipe[0]);
383 if (error < 0)
384 return 0; /* Just ignore error message */
385 if (text == NULL){
386 if (len <= 0)
387 return 0; /* Nothing to show */
389 /* Show message from pipe */
390 message (error, title, "%s", msg);
391 } else {
392 /* Show given text and possible message from pipe */
393 message (error, title, " %s \n %s ", text, msg);
395 return 1;
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.
408 void
409 canonicalize_pathname (char *path)
411 char *p, *s;
412 int len;
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) {
417 p = path + 2;
418 while (p[0] && p[0] != '/')
419 p++;
420 if (p[0] == '/' && p > path + 2)
421 lpath = p;
424 if (!lpath[0] || !lpath[1])
425 return;
427 /* Collapse multiple slashes */
428 p = lpath;
429 while (*p) {
430 if (p[0] == PATH_SEP && p[1] == PATH_SEP) {
431 s = p + 1;
432 while (*(++s) == PATH_SEP);
433 mhl_strmove (p + 1, s);
435 p++;
438 /* Collapse "/./" -> "/" */
439 p = lpath;
440 while (*p) {
441 if (p[0] == PATH_SEP && p[1] == '.' && p[2] == PATH_SEP)
442 mhl_strmove (p, p + 2);
443 else
444 p++;
447 /* Remove trailing slashes */
448 p = lpath + strlen (lpath) - 1;
449 while (p > lpath && *p == PATH_SEP)
450 *p-- = 0;
452 /* Remove leading "./" */
453 if (lpath[0] == '.' && lpath[1] == PATH_SEP) {
454 if (lpath[2] == 0) {
455 lpath[1] = 0;
456 return;
457 } else {
458 mhl_strmove (lpath, lpath + 2);
462 /* Remove trailing "/" or "/." */
463 len = strlen (lpath);
464 if (len < 2)
465 return;
466 if (lpath[len - 1] == PATH_SEP) {
467 lpath[len - 1] = 0;
468 } else {
469 if (lpath[len - 1] == '.' && lpath[len - 2] == PATH_SEP) {
470 if (len == 2) {
471 lpath[1] = 0;
472 return;
473 } else {
474 lpath[len - 2] = 0;
479 /* Collapse "/.." with the previous part of path */
480 p = lpath;
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)) {
484 p++;
485 continue;
488 /* search for the previous token */
489 s = p - 1;
490 while (s >= lpath && *s != PATH_SEP)
491 s--;
493 s++;
495 /* If the previous token is "..", we cannot collapse it */
496 if (s[0] == '.' && s[1] == '.' && s + 2 == p) {
497 p += 3;
498 continue;
501 if (p[3] != 0) {
502 if (s == lpath && *s == PATH_SEP) {
503 /* "/../foo" -> "/foo" */
504 mhl_strmove (s + 1, p + 4);
505 } else {
506 /* "token/../foo" -> "foo" */
507 mhl_strmove (s, p + 4);
509 p = (s > lpath) ? s - 1 : s;
510 continue;
513 /* trailing ".." */
514 if (s == lpath) {
515 /* "token/.." -> "." */
516 if (lpath[0] != PATH_SEP) {
517 lpath[0] = '.';
519 lpath[1] = 0;
520 } else {
521 /* "foo/token/.." -> "foo" */
522 if (s == lpath + 1)
523 s[0] = 0;
524 else
525 s[-1] = 0;
526 break;
529 break;
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 */
542 #ifndef HAVE_PUTENV
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;
549 #ifndef HAVE_GNU_LD
550 #define __environ environ
551 #endif
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;
560 register char **ep;
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){
568 ep[0] = ep[1];
569 ++ep;
571 *ep = NULL;
572 return 0;
576 size = 0;
577 for (ep = __environ; *ep != NULL; ++ep)
578 if (!strncmp (*ep, string, name_end - string) &&
579 (*ep)[name_end - string] == '=')
580 break;
581 else
582 ++size;
584 if (*ep == NULL){
585 static char **last_environ = NULL;
586 char **new_environ = g_new (char *, size + 2);
587 if (new_environ == NULL)
588 return -1;
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 g_free ((void *) last_environ);
594 last_environ = new_environ;
595 __environ = new_environ;
597 else
598 *ep = (char *) string;
600 return 0;
602 #endif /* !HAVE_PUTENV */
604 char *
605 mc_realpath (const char *path, char resolved_path[])
607 #ifdef USE_SYSTEM_REALPATH
608 return realpath (path, resolved_path);
609 #else
610 char copy_path[PATH_MAX];
611 char link_path[PATH_MAX];
612 char got_path[PATH_MAX];
613 char *new_path = got_path;
614 char *max_path;
615 int readlinks = 0;
616 int n;
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;
621 return NULL;
623 strcpy (copy_path, path);
624 path = copy_path;
625 max_path = copy_path + PATH_MAX - 2;
626 /* If it's a relative pathname use getwd for starters. */
627 if (*path != '/') {
628 /* Ohoo... */
629 #ifdef HAVE_GETCWD
630 getcwd (new_path, PATH_MAX - 1);
631 #else
632 getwd (new_path);
633 #endif
634 new_path += strlen (new_path);
635 if (new_path[-1] != '/')
636 *new_path++ = '/';
637 } else {
638 *new_path++ = '/';
639 path++;
641 /* Expand each slash-separated pathname component. */
642 while (*path != '\0') {
643 /* Ignore stray "/". */
644 if (*path == '/') {
645 path++;
646 continue;
648 if (*path == '.') {
649 /* Ignore ".". */
650 if (path[1] == '\0' || path[1] == '/') {
651 path++;
652 continue;
654 if (path[1] == '.') {
655 if (path[2] == '\0' || path[2] == '/') {
656 path += 2;
657 /* Ignore ".." at root. */
658 if (new_path == got_path + 1)
659 continue;
660 /* Handle ".." by backing up. */
661 while ((--new_path)[-1] != '/');
662 continue;
666 /* Safely copy the next pathname component. */
667 while (*path != '\0' && *path != '/') {
668 if (path > max_path) {
669 errno = ENAMETOOLONG;
670 return NULL;
672 *new_path++ = *path++;
674 #ifdef S_IFLNK
675 /* Protect against infinite loops. */
676 if (readlinks++ > MAXSYMLINKS) {
677 errno = ELOOP;
678 return NULL;
680 /* See if latest pathname component is a symlink. */
681 *new_path = '\0';
682 n = readlink (got_path, link_path, PATH_MAX - 1);
683 if (n < 0) {
684 /* EINVAL means the file exists but isn't a symlink. */
685 if (errno != EINVAL) {
686 /* Make sure it's null terminated. */
687 *new_path = '\0';
688 strcpy (resolved_path, got_path);
689 return NULL;
691 } else {
692 /* Note: readlink doesn't add the null byte. */
693 link_path[n] = '\0';
694 if (*link_path == '/')
695 /* Start over for an absolute symlink. */
696 new_path = got_path;
697 else
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;
703 return NULL;
705 /* Insert symlink contents into path. */
706 strcat (link_path, path);
707 strcpy (copy_path, link_path);
708 path = copy_path;
710 #endif /* S_IFLNK */
711 *new_path++ = '/';
713 /* Delete trailing slash but don't whomp a lone slash. */
714 if (new_path != got_path + 1 && new_path[-1] == '/')
715 new_path--;
716 /* Make sure it's null terminated. */
717 *new_path = '\0';
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;
728 static int ngroups;
729 static uid_t uid;
730 int i;
732 if (!initialized) {
733 uid = geteuid ();
735 ngroups = getgroups (0, NULL);
736 if (ngroups == -1)
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);
743 if (ngroups != 0) {
744 ngroups = getgroups (ngroups, groups);
745 if (ngroups == -1)
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 ();
753 initialized = TRUE;
756 if (st->st_uid == uid || uid == 0)
757 return 0;
759 for (i = 0; i < ngroups; i++) {
760 if (st->st_gid == groups[i])
761 return 1;
764 return 2;