* extfs/trpm: Fix quoting issues.
[midnight-commander.git] / src / utilunix.c
blob3d8a2134b964f3e3b4fe245aa59f231f2a0e2c74
1 /* Various utilities - Unix variants
2 Copyright (C) 1994, 1995, 1996 the Free Software Foundation.
3 Written 1994, 1995, 1996 by:
4 Miguel de Icaza, Janne Kukonlehto, Dugan Porter,
5 Jakub Jelinek, Mauricio Plaza.
7 This program is free software; you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation; either version 2 of the License, or
10 (at your option) any later version.
12 This program is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
17 You should have received a copy of the GNU General Public License
18 along with this program; if not, write to the Free Software
19 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */
21 #include <config.h>
22 #include <stdio.h>
23 #include <sys/types.h>
24 #ifdef HAVE_UNISTD_H
25 # include <unistd.h>
26 #endif
27 #include <signal.h> /* struct sigaction */
28 #include <limits.h> /* INT_MAX */
29 #include <sys/stat.h>
30 #include <stdarg.h>
31 #include <errno.h> /* errno */
32 #include <string.h>
33 #include <ctype.h>
34 #ifdef HAVE_SYS_IOCTL_H
35 # include <sys/ioctl.h>
36 #endif
38 #include "global.h"
39 #include "execute.h"
40 #include "wtools.h" /* message() */
42 struct sigaction startup_handler;
44 /* uid of the MC user */
45 static uid_t current_user_uid = -1;
46 /* List of the gids of the user */
47 static GTree *current_user_gid = NULL;
49 /* Helper function to compare 2 gids */
50 static gint
51 mc_gid_compare (gconstpointer v, gconstpointer v2)
53 return ((GPOINTER_TO_UINT(v) > GPOINTER_TO_UINT(v2)) ? 1 :
54 (GPOINTER_TO_UINT(v) < GPOINTER_TO_UINT(v2)) ? -1 : 0);
57 /* Helper function to delete keys of the gids tree */
58 static gint
59 mc_gid_destroy (gpointer key, gpointer value, gpointer data)
61 g_free (value);
63 return FALSE;
66 /* This function initialize global GTree with the gids of groups,
67 to which user belongs. Tree also store corresponding string
68 with the name of the group.
69 FIXME: Do we need this names at all? If not, we can simplify
70 initialization by eliminating g_strdup's.
72 void init_groups (void)
74 int i;
75 struct passwd *pwd;
76 struct group *grp;
78 current_user_uid = getuid ();
79 pwd = getpwuid (current_user_uid);
80 g_return_if_fail (pwd != NULL);
82 current_user_gid = g_tree_new (mc_gid_compare);
84 /* Put user's primary group first. */
85 if ((grp = getgrgid (pwd->pw_gid)) != NULL) {
86 g_tree_insert (current_user_gid,
87 GUINT_TO_POINTER ((int) grp->gr_gid),
88 g_strdup (grp->gr_name));
91 setgrent ();
92 while ((grp = getgrent ()) != NULL) {
93 for (i = 0; grp->gr_mem[i]; i++) {
94 if (!strcmp (pwd->pw_name, grp->gr_mem[i]) &&
95 !g_tree_lookup (current_user_gid,
96 GUINT_TO_POINTER ((int) grp->gr_gid))) {
97 g_tree_insert (current_user_gid,
98 GUINT_TO_POINTER ((int) grp->gr_gid),
99 g_strdup (grp->gr_name));
100 break;
104 endgrent ();
107 /* Return the index of the permissions triplet */
109 get_user_permissions (struct stat *buf) {
111 if (buf->st_uid == current_user_uid || current_user_uid == 0)
112 return 0;
114 if (current_user_gid && g_tree_lookup (current_user_gid,
115 GUINT_TO_POINTER((int) buf->st_gid)))
116 return 1;
118 return 2;
121 /* Completely destroys the gids tree */
122 void
123 destroy_groups (void)
125 g_tree_traverse (current_user_gid, mc_gid_destroy, G_POST_ORDER, NULL);
126 g_tree_destroy (current_user_gid);
129 #define UID_CACHE_SIZE 200
130 #define GID_CACHE_SIZE 30
132 typedef struct {
133 int index;
134 char *string;
135 } int_cache;
137 static int_cache uid_cache [UID_CACHE_SIZE];
138 static int_cache gid_cache [GID_CACHE_SIZE];
140 static char *i_cache_match (int id, int_cache *cache, int size)
142 int i;
144 for (i = 0; i < size; i++)
145 if (cache [i].index == id)
146 return cache [i].string;
147 return 0;
150 static void i_cache_add (int id, int_cache *cache, int size, char *text,
151 int *last)
153 g_free (cache [*last].string);
154 cache [*last].string = g_strdup (text);
155 cache [*last].index = id;
156 *last = ((*last)+1) % size;
159 char *get_owner (int uid)
161 struct passwd *pwd;
162 static char ibuf [10];
163 char *name;
164 static int uid_last;
166 if ((name = i_cache_match (uid, uid_cache, UID_CACHE_SIZE)) != NULL)
167 return name;
169 pwd = getpwuid (uid);
170 if (pwd){
171 i_cache_add (uid, uid_cache, UID_CACHE_SIZE, pwd->pw_name, &uid_last);
172 return pwd->pw_name;
174 else {
175 g_snprintf (ibuf, sizeof (ibuf), "%d", uid);
176 return ibuf;
180 char *get_group (int gid)
182 struct group *grp;
183 static char gbuf [10];
184 char *name;
185 static int gid_last;
187 if ((name = i_cache_match (gid, gid_cache, GID_CACHE_SIZE)) != NULL)
188 return name;
190 grp = getgrgid (gid);
191 if (grp){
192 i_cache_add (gid, gid_cache, GID_CACHE_SIZE, grp->gr_name, &gid_last);
193 return grp->gr_name;
194 } else {
195 g_snprintf (gbuf, sizeof (gbuf), "%d", gid);
196 return gbuf;
200 /* Since ncurses uses a handler that automatically refreshes the */
201 /* screen after a SIGCONT, and we don't want this behavior when */
202 /* spawning a child, we save the original handler here */
203 void save_stop_handler (void)
205 sigaction (SIGTSTP, NULL, &startup_handler);
208 int my_system (int flags, const char *shell, const char *command)
210 struct sigaction ignore, save_intr, save_quit, save_stop;
211 pid_t pid;
212 int status = 0;
214 ignore.sa_handler = SIG_IGN;
215 sigemptyset (&ignore.sa_mask);
216 ignore.sa_flags = 0;
218 sigaction (SIGINT, &ignore, &save_intr);
219 sigaction (SIGQUIT, &ignore, &save_quit);
221 /* Restore the original SIGTSTP handler, we don't want ncurses' */
222 /* handler messing the screen after the SIGCONT */
223 sigaction (SIGTSTP, &startup_handler, &save_stop);
225 if ((pid = fork ()) < 0){
226 fprintf (stderr, "\n\nfork () = -1\n");
227 return -1;
229 if (pid == 0){
230 signal (SIGINT, SIG_DFL);
231 signal (SIGQUIT, SIG_DFL);
232 signal (SIGTSTP, SIG_DFL);
233 signal (SIGCHLD, SIG_DFL);
235 if (flags & EXECUTE_AS_SHELL)
236 execl (shell, shell, "-c", command, (char *) NULL);
237 else
238 execlp (shell, shell, command, (char *) NULL);
240 _exit (127); /* Exec error */
241 } else {
242 while (waitpid (pid, &status, 0) < 0)
243 if (errno != EINTR){
244 status = -1;
245 break;
248 sigaction (SIGINT, &save_intr, NULL);
249 sigaction (SIGQUIT, &save_quit, NULL);
250 sigaction (SIGTSTP, &save_stop, NULL);
252 return WEXITSTATUS(status);
257 * Perform tilde expansion if possible.
258 * Always return a newly allocated string, even if it's unchanged.
260 char *
261 tilde_expand (const char *directory)
263 struct passwd *passwd;
264 const char *p, *q;
265 char *name;
267 if (*directory != '~')
268 return g_strdup (directory);
270 p = directory + 1;
272 /* d = "~" or d = "~/" */
273 if (!(*p) || (*p == PATH_SEP)) {
274 passwd = getpwuid (geteuid ());
275 q = (*p == PATH_SEP) ? p + 1 : "";
276 } else {
277 q = strchr (p, PATH_SEP);
278 if (!q) {
279 passwd = getpwnam (p);
280 } else {
281 name = g_strndup (p, q - p);
282 passwd = getpwnam (name);
283 q++;
284 g_free (name);
288 /* If we can't figure the user name, leave tilde unexpanded */
289 if (!passwd)
290 return g_strdup (directory);
292 return g_strconcat (passwd->pw_dir, PATH_SEP_STR, q, (char *) NULL);
297 * Return the directory where mc should keep its temporary files.
298 * This directory is (in Bourne shell terms) "${TMPDIR=/tmp}/mc-$USER"
299 * When called the first time, the directory is created if needed.
300 * The first call should be done early, since we are using fprintf()
301 * and not message() to report possible problems.
303 const char *
304 mc_tmpdir (void)
306 static char buffer[64];
307 static const char *tmpdir;
308 const char *sys_tmp;
309 struct passwd *pwd;
310 struct stat st;
311 const char *error = NULL;
313 /* Check if already initialized */
314 if (tmpdir)
315 return tmpdir;
317 sys_tmp = getenv ("TMPDIR");
318 if (!sys_tmp) {
319 sys_tmp = TMPDIR_DEFAULT;
322 pwd = getpwuid (getuid ());
324 if (pwd)
325 g_snprintf (buffer, sizeof (buffer), "%s/mc-%s", sys_tmp,
326 pwd->pw_name);
327 else
328 g_snprintf (buffer, sizeof (buffer), "%s/mc-%lu", sys_tmp,
329 (unsigned long) getuid ());
331 canonicalize_pathname (buffer);
333 if (lstat (buffer, &st) == 0) {
334 /* Sanity check for existing directory */
335 if (!S_ISDIR (st.st_mode))
336 error = _("%s is not a directory\n");
337 else if (st.st_uid != getuid ())
338 error = _("Directory %s is not owned by you\n");
339 else if (((st.st_mode & 0777) != 0700)
340 && (chmod (buffer, 0700) != 0))
341 error = _("Cannot set correct permissions for directory %s\n");
342 } else {
343 /* Need to create directory */
344 if (mkdir (buffer, S_IRWXU) != 0) {
345 fprintf (stderr,
346 _("Cannot create temporary directory %s: %s\n"),
347 buffer, unix_error_string (errno));
348 error = "";
352 if (!error) {
353 tmpdir = buffer;
354 } else {
355 int test_fd;
356 char *test_fn;
357 int fallback_ok = 0;
359 if (*error)
360 fprintf (stderr, error, buffer);
362 /* Test if sys_tmp is suitable for temporary files */
363 tmpdir = sys_tmp;
364 test_fd = mc_mkstemps (&test_fn, "mctest", NULL);
365 if (test_fd != -1) {
366 close (test_fd);
367 test_fd = open (test_fn, O_RDONLY);
368 if (test_fd != -1) {
369 close (test_fd);
370 unlink (test_fn);
371 fallback_ok = 1;
375 if (fallback_ok) {
376 fprintf (stderr, _("Temporary files will be created in %s\n"),
377 sys_tmp);
378 error = NULL;
379 } else {
380 fprintf (stderr, _("Temporary files will not be created\n"));
381 tmpdir = "/dev/null/";
384 fprintf (stderr, "%s\n", _("Press any key to continue..."));
385 getc (stdin);
388 if (!error)
389 setenv ("MC_TMPDIR", tmpdir, 1);
391 return tmpdir;
395 /* Pipes are guaranteed to be able to hold at least 4096 bytes */
396 /* More than that would be unportable */
397 #define MAX_PIPE_SIZE 4096
399 static int error_pipe[2]; /* File descriptors of error pipe */
400 static int old_error; /* File descriptor of old standard error */
402 /* Creates a pipe to hold standard error for a later analysis. */
403 /* The pipe can hold 4096 bytes. Make sure no more is written */
404 /* or a deadlock might occur. */
405 void open_error_pipe (void)
407 if (pipe (error_pipe) < 0){
408 message (0, _("Warning"), _(" Pipe failed "));
410 old_error = dup (2);
411 if(old_error < 0 || close(2) || dup (error_pipe[1]) != 2){
412 message (0, _("Warning"), _(" Dup failed "));
413 close (error_pipe[0]);
414 close (error_pipe[1]);
416 close (error_pipe[1]);
420 * Returns true if an error was displayed
421 * error: -1 - ignore errors, 0 - display warning, 1 - display error
422 * text is prepended to the error message from the pipe
425 close_error_pipe (int error, const char *text)
427 const char *title;
428 char msg[MAX_PIPE_SIZE];
429 int len = 0;
431 if (error)
432 title = MSG_ERROR;
433 else
434 title = _("Warning");
435 if (old_error >= 0){
436 close (2);
437 dup (old_error);
438 close (old_error);
439 len = read (error_pipe[0], msg, MAX_PIPE_SIZE - 1);
441 if (len >= 0)
442 msg[len] = 0;
443 close (error_pipe[0]);
445 if (error < 0)
446 return 0; /* Just ignore error message */
447 if (text == NULL){
448 if (len <= 0)
449 return 0; /* Nothing to show */
451 /* Show message from pipe */
452 message (error, title, "%s", msg);
453 } else {
454 /* Show given text and possible message from pipe */
455 message (error, title, " %s \n %s ", text, msg);
457 return 1;
461 * Canonicalize path, and return a new path. Do everything in place.
462 * The new path differs from path in:
463 * Multiple `/'s are collapsed to a single `/'.
464 * Leading `./'s and trailing `/.'s are removed.
465 * Trailing `/'s are removed.
466 * Non-leading `../'s and trailing `..'s are handled by removing
467 * portions of the path.
468 * Well formed UNC paths are modified only in the local part.
470 void
471 canonicalize_pathname (char *path)
473 char *p, *s;
474 int len;
475 char *lpath = path; /* path without leading UNC part */
477 /* Detect and preserve UNC paths: //server/... */
478 if (path[0] == PATH_SEP && path[1] == PATH_SEP) {
479 p = path + 2;
480 while (p[0] && p[0] != '/')
481 p++;
482 if (p[0] == '/' && p > path + 2)
483 lpath = p;
486 if (!lpath[0] || !lpath[1])
487 return;
489 /* Collapse multiple slashes */
490 p = lpath;
491 while (*p) {
492 if (p[0] == PATH_SEP && p[1] == PATH_SEP) {
493 s = p + 1;
494 while (*(++s) == PATH_SEP);
495 strcpy (p + 1, s);
497 p++;
500 /* Collapse "/./" -> "/" */
501 p = lpath;
502 while (*p) {
503 if (p[0] == PATH_SEP && p[1] == '.' && p[2] == PATH_SEP)
504 strcpy (p, p + 2);
505 else
506 p++;
509 /* Remove trailing slashes */
510 p = lpath + strlen (lpath) - 1;
511 while (p > lpath && *p == PATH_SEP)
512 *p-- = 0;
514 /* Remove leading "./" */
515 if (lpath[0] == '.' && lpath[1] == PATH_SEP) {
516 if (lpath[2] == 0) {
517 lpath[1] = 0;
518 return;
519 } else {
520 strcpy (lpath, lpath + 2);
524 /* Remove trailing "/" or "/." */
525 len = strlen (lpath);
526 if (len < 2)
527 return;
528 if (lpath[len - 1] == PATH_SEP) {
529 lpath[len - 1] = 0;
530 } else {
531 if (lpath[len - 1] == '.' && lpath[len - 2] == PATH_SEP) {
532 if (len == 2) {
533 lpath[1] = 0;
534 return;
535 } else {
536 lpath[len - 2] = 0;
541 /* Collapse "/.." with the previous part of path */
542 p = lpath;
543 while (p[0] && p[1] && p[2]) {
544 if ((p[0] != PATH_SEP || p[1] != '.' || p[2] != '.')
545 || (p[3] != PATH_SEP && p[3] != 0)) {
546 p++;
547 continue;
550 /* search for the previous token */
551 s = p - 1;
552 while (s >= lpath && *s != PATH_SEP)
553 s--;
555 s++;
557 /* If the previous token is "..", we cannot collapse it */
558 if (s[0] == '.' && s[1] == '.' && s + 2 == p) {
559 p += 3;
560 continue;
563 if (p[3] != 0) {
564 if (s == lpath && *s == PATH_SEP) {
565 /* "/../foo" -> "/foo" */
566 strcpy (s + 1, p + 4);
567 } else {
568 /* "token/../foo" -> "foo" */
569 strcpy (s, p + 4);
571 p = (s > lpath) ? s - 1 : s;
572 continue;
575 /* trailing ".." */
576 if (s == lpath) {
577 /* "token/.." -> "." */
578 if (lpath[0] != PATH_SEP) {
579 lpath[0] = '.';
581 lpath[1] = 0;
582 } else {
583 /* "foo/token/.." -> "foo" */
584 if (s == lpath + 1)
585 s[0] = 0;
586 else
587 s[-1] = 0;
588 break;
591 break;
595 #ifdef HAVE_GET_PROCESS_STATS
596 # include <sys/procstats.h>
598 int gettimeofday (struct timeval *tp, void *tzp)
600 return get_process_stats(tp, PS_SELF, 0, 0);
602 #endif /* HAVE_GET_PROCESS_STATS */
604 #ifndef HAVE_PUTENV
606 /* The following piece of code was copied from the GNU C Library */
607 /* And is provided here for nextstep who lacks putenv */
609 extern char **environ;
611 #ifndef HAVE_GNU_LD
612 #define __environ environ
613 #endif
616 /* Put STRING, which is of the form "NAME=VALUE", in the environment. */
618 putenv (char *string)
620 const char *const name_end = strchr (string, '=');
621 register size_t size;
622 register char **ep;
624 if (name_end == NULL){
625 /* Remove the variable from the environment. */
626 size = strlen (string);
627 for (ep = __environ; *ep != NULL; ++ep)
628 if (!strncmp (*ep, string, size) && (*ep)[size] == '='){
629 while (ep[1] != NULL){
630 ep[0] = ep[1];
631 ++ep;
633 *ep = NULL;
634 return 0;
638 size = 0;
639 for (ep = __environ; *ep != NULL; ++ep)
640 if (!strncmp (*ep, string, name_end - string) &&
641 (*ep)[name_end - string] == '=')
642 break;
643 else
644 ++size;
646 if (*ep == NULL){
647 static char **last_environ = NULL;
648 char **new_environ = g_new (char *, size + 2);
649 if (new_environ == NULL)
650 return -1;
651 (void) memcpy ((void *) new_environ, (void *) __environ,
652 size * sizeof (char *));
653 new_environ[size] = (char *) string;
654 new_environ[size + 1] = NULL;
655 g_free ((void *) last_environ);
656 last_environ = new_environ;
657 __environ = new_environ;
659 else
660 *ep = (char *) string;
662 return 0;
664 #endif /* !HAVE_PUTENV */