root/lib/utilunix.c

/* [previous][next][first][last][top][bottom][index][help]  */

DEFINITIONS

This source file includes following definitions.
  1. i_cache_match
  2. i_cache_add
  3. my_fork
  4. my_system__save_sigaction_handlers
  5. my_system__restore_sigaction_handlers
  6. my_system_make_arg_array
  7. mc_pread_stream
  8. get_owner
  9. get_group
  10. save_stop_handler
  11. my_exit
  12. my_system
  13. my_systeml
  14. my_systemv
  15. my_systemv_flags
  16. mc_popen
  17. mc_pread
  18. mc_pstream_get_string
  19. mc_pclose
  20. tilde_expand
  21. custom_canonicalize_pathname
  22. canonicalize_pathname
  23. mc_realpath
  24. get_user_permissions
  25. mc_build_filenamev
  26. mc_build_filename

   1 /*
   2    Various utilities - Unix variants
   3 
   4    Copyright (C) 1994-2022
   5    Free Software Foundation, Inc.
   6 
   7    Written by:
   8    Miguel de Icaza, 1994, 1995, 1996
   9    Janne Kukonlehto, 1994, 1995, 1996
  10    Dugan Porter, 1994, 1995, 1996
  11    Jakub Jelinek, 1994, 1995, 1996
  12    Mauricio Plaza, 1994, 1995, 1996
  13 
  14    The mc_realpath routine is mostly from uClibc package, written
  15    by Rick Sladkey <jrs@world.std.com>
  16 
  17    This file is part of the Midnight Commander.
  18 
  19    The Midnight Commander is free software: you can redistribute it
  20    and/or modify it under the terms of the GNU General Public License as
  21    published by the Free Software Foundation, either version 3 of the License,
  22    or (at your option) any later version.
  23 
  24    The Midnight Commander is distributed in the hope that it will be useful,
  25    but WITHOUT ANY WARRANTY; without even the implied warranty of
  26    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  27    GNU General Public License for more details.
  28 
  29    You should have received a copy of the GNU General Public License
  30    along with this program.  If not, see <http://www.gnu.org/licenses/>.
  31  */
  32 
  33 /** \file utilunix.c
  34  *  \brief Source: various utilities - Unix variant
  35  */
  36 
  37 #include <config.h>
  38 
  39 #include <ctype.h>
  40 #include <errno.h>
  41 #include <limits.h>
  42 #include <signal.h>
  43 #include <stdarg.h>
  44 #include <stdio.h>
  45 #include <stdlib.h>
  46 #include <string.h>
  47 #ifdef HAVE_SYS_PARAM_H
  48 #include <sys/param.h>
  49 #endif
  50 #include <sys/types.h>
  51 #include <sys/stat.h>
  52 #ifdef HAVE_SYS_SELECT_H
  53 #include <sys/select.h>
  54 #endif
  55 #include <sys/wait.h>
  56 #include <pwd.h>
  57 #include <grp.h>
  58 
  59 #include "lib/global.h"
  60 
  61 #include "lib/unixcompat.h"
  62 #include "lib/vfs/vfs.h"        /* VFS_ENCODING_PREFIX */
  63 #include "lib/strutil.h"        /* str_move() */
  64 #include "lib/util.h"
  65 #include "lib/widget.h"         /* message() */
  66 #include "lib/vfs/xdirentry.h"
  67 
  68 #ifdef HAVE_CHARSET
  69 #include "lib/charsets.h"
  70 #endif
  71 
  72 #include "utilunix.h"
  73 
  74 /*** global variables ****************************************************************************/
  75 
  76 struct sigaction startup_handler;
  77 
  78 /*** file scope macro definitions ****************************************************************/
  79 
  80 #define UID_CACHE_SIZE 200
  81 #define GID_CACHE_SIZE 30
  82 
  83 /*** file scope type declarations ****************************************************************/
  84 
  85 typedef struct
  86 {
  87     int index;
  88     char *string;
  89 } int_cache;
  90 
  91 typedef enum
  92 {
  93     FORK_ERROR = -1,
  94     FORK_CHILD,
  95     FORK_PARENT,
  96 } my_fork_state_t;
  97 
  98 typedef struct
  99 {
 100     struct sigaction intr;
 101     struct sigaction quit;
 102     struct sigaction stop;
 103 } my_system_sigactions_t;
 104 
 105 /*** file scope variables ************************************************************************/
 106 
 107 static int_cache uid_cache[UID_CACHE_SIZE];
 108 static int_cache gid_cache[GID_CACHE_SIZE];
 109 
 110 /* --------------------------------------------------------------------------------------------- */
 111 /*** file scope functions ************************************************************************/
 112 /* --------------------------------------------------------------------------------------------- */
 113 
 114 static char *
 115 i_cache_match (int id, int_cache * cache, int size)
     /* [previous][next][first][last][top][bottom][index][help]  */
 116 {
 117     int i;
 118 
 119     for (i = 0; i < size; i++)
 120         if (cache[i].index == id)
 121             return cache[i].string;
 122     return 0;
 123 }
 124 
 125 /* --------------------------------------------------------------------------------------------- */
 126 
 127 static void
 128 i_cache_add (int id, int_cache * cache, int size, char *text, int *last)
     /* [previous][next][first][last][top][bottom][index][help]  */
 129 {
 130     g_free (cache[*last].string);
 131     cache[*last].string = g_strdup (text);
 132     cache[*last].index = id;
 133     *last = ((*last) + 1) % size;
 134 }
 135 
 136 /* --------------------------------------------------------------------------------------------- */
 137 
 138 static my_fork_state_t
 139 my_fork (void)
     /* [previous][next][first][last][top][bottom][index][help]  */
 140 {
 141     pid_t pid;
 142 
 143     pid = fork ();
 144 
 145     if (pid < 0)
 146     {
 147         fprintf (stderr, "\n\nfork () = -1\n");
 148         return FORK_ERROR;
 149     }
 150 
 151     if (pid == 0)
 152         return FORK_CHILD;
 153 
 154     while (TRUE)
 155     {
 156         int status = 0;
 157 
 158         if (waitpid (pid, &status, 0) > 0)
 159             return WEXITSTATUS (status) == 0 ? FORK_PARENT : FORK_ERROR;
 160 
 161         if (errno != EINTR)
 162             return FORK_ERROR;
 163     }
 164 }
 165 
 166 /* --------------------------------------------------------------------------------------------- */
 167 
 168 static void
 169 my_system__save_sigaction_handlers (my_system_sigactions_t * sigactions)
     /* [previous][next][first][last][top][bottom][index][help]  */
 170 {
 171     struct sigaction ignore;
 172 
 173     memset (&ignore, 0, sizeof (ignore));
 174     ignore.sa_handler = SIG_IGN;
 175     sigemptyset (&ignore.sa_mask);
 176 
 177     sigaction (SIGINT, &ignore, &sigactions->intr);
 178     sigaction (SIGQUIT, &ignore, &sigactions->quit);
 179 
 180     /* Restore the original SIGTSTP handler, we don't want ncurses' */
 181     /* handler messing the screen after the SIGCONT */
 182     sigaction (SIGTSTP, &startup_handler, &sigactions->stop);
 183 }
 184 
 185 /* --------------------------------------------------------------------------------------------- */
 186 
 187 static void
 188 my_system__restore_sigaction_handlers (my_system_sigactions_t * sigactions)
     /* [previous][next][first][last][top][bottom][index][help]  */
 189 {
 190     sigaction (SIGINT, &sigactions->intr, NULL);
 191     sigaction (SIGQUIT, &sigactions->quit, NULL);
 192     sigaction (SIGTSTP, &sigactions->stop, NULL);
 193 }
 194 
 195 /* --------------------------------------------------------------------------------------------- */
 196 
 197 static GPtrArray *
 198 my_system_make_arg_array (int flags, const char *shell, char **execute_name)
     /* [previous][next][first][last][top][bottom][index][help]  */
 199 {
 200     GPtrArray *args_array;
 201 
 202     args_array = g_ptr_array_new ();
 203 
 204     if ((flags & EXECUTE_AS_SHELL) != 0)
 205     {
 206         g_ptr_array_add (args_array, (gpointer) shell);
 207         g_ptr_array_add (args_array, (gpointer) "-c");
 208         *execute_name = g_strdup (shell);
 209     }
 210     else
 211     {
 212         char *shell_token;
 213 
 214         shell_token = shell != NULL ? strchr (shell, ' ') : NULL;
 215         if (shell_token == NULL)
 216             *execute_name = g_strdup (shell);
 217         else
 218             *execute_name = g_strndup (shell, (gsize) (shell_token - shell));
 219 
 220         g_ptr_array_add (args_array, (gpointer) shell);
 221     }
 222     return args_array;
 223 }
 224 
 225 /* --------------------------------------------------------------------------------------------- */
 226 
 227 static void
 228 mc_pread_stream (mc_pipe_stream_t * ps, const fd_set * fds)
     /* [previous][next][first][last][top][bottom][index][help]  */
 229 {
 230     size_t buf_len;
 231     ssize_t read_len;
 232 
 233     if (!FD_ISSET (ps->fd, fds))
 234     {
 235         ps->len = MC_PIPE_STREAM_UNREAD;
 236         return;
 237     }
 238 
 239     buf_len = (size_t) ps->len;
 240 
 241     if (buf_len >= MC_PIPE_BUFSIZE)
 242         buf_len = ps->null_term ? MC_PIPE_BUFSIZE - 1 : MC_PIPE_BUFSIZE;
 243 
 244     do
 245     {
 246         read_len = read (ps->fd, ps->buf, buf_len);
 247     }
 248     while (read_len < 0 && errno == EINTR);
 249 
 250     if (read_len < 0)
 251     {
 252         /* reading error */
 253         ps->len = MC_PIPE_ERROR_READ;
 254         ps->error = errno;
 255     }
 256     else if (read_len == 0)
 257         /* EOF */
 258         ps->len = MC_PIPE_STREAM_EOF;
 259     else
 260     {
 261         /* success */
 262         ps->len = read_len;
 263 
 264         if (ps->null_term)
 265             ps->buf[(size_t) ps->len] = '\0';
 266     }
 267 
 268     ps->pos = 0;
 269 }
 270 
 271 /* --------------------------------------------------------------------------------------------- */
 272 /*** public functions ****************************************************************************/
 273 /* --------------------------------------------------------------------------------------------- */
 274 
 275 const char *
 276 get_owner (uid_t uid)
     /* [previous][next][first][last][top][bottom][index][help]  */
 277 {
 278     struct passwd *pwd;
 279     char *name;
 280     static uid_t uid_last;
 281 
 282     name = i_cache_match ((int) uid, uid_cache, UID_CACHE_SIZE);
 283     if (name != NULL)
 284         return name;
 285 
 286     pwd = getpwuid (uid);
 287     if (pwd != NULL)
 288     {
 289         i_cache_add ((int) uid, uid_cache, UID_CACHE_SIZE, pwd->pw_name, (int *) &uid_last);
 290         return pwd->pw_name;
 291     }
 292     else
 293     {
 294         static char ibuf[10];
 295 
 296         g_snprintf (ibuf, sizeof (ibuf), "%d", (int) uid);
 297         return ibuf;
 298     }
 299 }
 300 
 301 /* --------------------------------------------------------------------------------------------- */
 302 
 303 const char *
 304 get_group (gid_t gid)
     /* [previous][next][first][last][top][bottom][index][help]  */
 305 {
 306     struct group *grp;
 307     char *name;
 308     static gid_t gid_last;
 309 
 310     name = i_cache_match ((int) gid, gid_cache, GID_CACHE_SIZE);
 311     if (name != NULL)
 312         return name;
 313 
 314     grp = getgrgid (gid);
 315     if (grp != NULL)
 316     {
 317         i_cache_add ((int) gid, gid_cache, GID_CACHE_SIZE, grp->gr_name, (int *) &gid_last);
 318         return grp->gr_name;
 319     }
 320     else
 321     {
 322         static char gbuf[10];
 323 
 324         g_snprintf (gbuf, sizeof (gbuf), "%d", (int) gid);
 325         return gbuf;
 326     }
 327 }
 328 
 329 /* --------------------------------------------------------------------------------------------- */
 330 /* Since ncurses uses a handler that automatically refreshes the */
 331 /* screen after a SIGCONT, and we don't want this behavior when */
 332 /* spawning a child, we save the original handler here */
 333 
 334 void
 335 save_stop_handler (void)
     /* [previous][next][first][last][top][bottom][index][help]  */
 336 {
 337     sigaction (SIGTSTP, NULL, &startup_handler);
 338 }
 339 
 340 /* --------------------------------------------------------------------------------------------- */
 341 /**
 342  * Wrapper for _exit() system call.
 343  * The _exit() function has gcc's attribute 'noreturn', and this is reason why we can't
 344  * mock the call.
 345  *
 346  * @param status exit code
 347  */
 348 
 349 void
 350 /* __attribute__ ((noreturn)) */
 351 my_exit (int status)
     /* [previous][next][first][last][top][bottom][index][help]  */
 352 {
 353     _exit (status);
 354 }
 355 
 356 /* --------------------------------------------------------------------------------------------- */
 357 /**
 358  * Call external programs.
 359  *
 360  * @parameter flags   addition conditions for running external programs.
 361  * @parameter shell   shell (if flags contain EXECUTE_AS_SHELL), command to run otherwise.
 362  *                    Shell (or command) will be found in paths described in PATH variable
 363  *                    (if shell parameter doesn't begin from path delimiter)
 364  * @parameter command Command for shell (or first parameter for command, if flags contain EXECUTE_AS_SHELL)
 365  * @return 0 if successfull, -1 otherwise
 366  */
 367 
 368 int
 369 my_system (int flags, const char *shell, const char *command)
     /* [previous][next][first][last][top][bottom][index][help]  */
 370 {
 371     return my_systeml (flags, shell, command, NULL);
 372 }
 373 
 374 /* --------------------------------------------------------------------------------------------- */
 375 /**
 376  * Call external programs with various parameters number.
 377  *
 378  * @parameter flags addition conditions for running external programs.
 379  * @parameter shell shell (if flags contain EXECUTE_AS_SHELL), command to run otherwise.
 380  *                  Shell (or command) will be found in pathes described in PATH variable
 381  *                  (if shell parameter doesn't begin from path delimiter)
 382  * @parameter ...   Command for shell with addition parameters for shell
 383  *                  (or parameters for command, if flags contain EXECUTE_AS_SHELL).
 384  *                  Should be NULL terminated.
 385  * @return 0 if successfull, -1 otherwise
 386  */
 387 
 388 int
 389 my_systeml (int flags, const char *shell, ...)
     /* [previous][next][first][last][top][bottom][index][help]  */
 390 {
 391     GPtrArray *args_array;
 392     int status = 0;
 393     va_list vargs;
 394     char *one_arg;
 395 
 396     args_array = g_ptr_array_new ();
 397 
 398     va_start (vargs, shell);
 399     while ((one_arg = va_arg (vargs, char *)) != NULL)
 400           g_ptr_array_add (args_array, one_arg);
 401     va_end (vargs);
 402 
 403     g_ptr_array_add (args_array, NULL);
 404     status = my_systemv_flags (flags, shell, (char *const *) args_array->pdata);
 405 
 406     g_ptr_array_free (args_array, TRUE);
 407 
 408     return status;
 409 }
 410 
 411 /* --------------------------------------------------------------------------------------------- */
 412 /**
 413  * Call external programs with array of strings as parameters.
 414  *
 415  * @parameter command command to run. Command will be found in paths described in PATH variable
 416  *                    (if command parameter doesn't begin from path delimiter)
 417  * @parameter argv    Array of strings (NULL-terminated) with parameters for command
 418  * @return 0 if successfull, -1 otherwise
 419  */
 420 
 421 int
 422 my_systemv (const char *command, char *const argv[])
     /* [previous][next][first][last][top][bottom][index][help]  */
 423 {
 424     my_fork_state_t fork_state;
 425     int status = 0;
 426     my_system_sigactions_t sigactions;
 427 
 428     my_system__save_sigaction_handlers (&sigactions);
 429 
 430     fork_state = my_fork ();
 431     switch (fork_state)
 432     {
 433     case FORK_ERROR:
 434         status = -1;
 435         break;
 436     case FORK_CHILD:
 437         {
 438             signal (SIGINT, SIG_DFL);
 439             signal (SIGQUIT, SIG_DFL);
 440             signal (SIGTSTP, SIG_DFL);
 441             signal (SIGCHLD, SIG_DFL);
 442 
 443             execvp (command, argv);
 444             my_exit (127);      /* Exec error */
 445         }
 446         MC_FALLTHROUGH;
 447         /* no break here, or unreachable-code warning by no returning my_exit() */
 448     default:
 449         status = 0;
 450         break;
 451     }
 452     my_system__restore_sigaction_handlers (&sigactions);
 453 
 454     return status;
 455 }
 456 
 457 /* --------------------------------------------------------------------------------------------- */
 458 /**
 459  * Call external programs with flags and with array of strings as parameters.
 460  *
 461  * @parameter flags   addition conditions for running external programs.
 462  * @parameter command shell (if flags contain EXECUTE_AS_SHELL), command to run otherwise.
 463  *                    Shell (or command) will be found in paths described in PATH variable
 464  *                    (if shell parameter doesn't begin from path delimiter)
 465  * @parameter argv    Array of strings (NULL-terminated) with parameters for command
 466  * @return 0 if successfull, -1 otherwise
 467  */
 468 
 469 int
 470 my_systemv_flags (int flags, const char *command, char *const argv[])
     /* [previous][next][first][last][top][bottom][index][help]  */
 471 {
 472     char *execute_name = NULL;
 473     GPtrArray *args_array;
 474     int status = 0;
 475 
 476     args_array = my_system_make_arg_array (flags, command, &execute_name);
 477 
 478     for (; argv != NULL && *argv != NULL; argv++)
 479         g_ptr_array_add (args_array, *argv);
 480 
 481     g_ptr_array_add (args_array, NULL);
 482     status = my_systemv (execute_name, (char *const *) args_array->pdata);
 483 
 484     g_free (execute_name);
 485     g_ptr_array_free (args_array, TRUE);
 486 
 487     return status;
 488 }
 489 
 490 /* --------------------------------------------------------------------------------------------- */
 491 /**
 492  * Create pipe and run child process.
 493  *
 494  * @parameter command command line of child process
 495  * @parameter read_out do or don't read the stdout of child process
 496  * @parameter read_err do or don't read the stderr of child process
 497  * @paremeter error contains pointer to object to handle error code and message
 498  *
 499  * @return newly created object of mc_pipe_t class in success, NULL otherwise
 500  */
 501 
 502 mc_pipe_t *
 503 mc_popen (const char *command, gboolean read_out, gboolean read_err, GError ** error)
     /* [previous][next][first][last][top][bottom][index][help]  */
 504 {
 505     mc_pipe_t *p;
 506     const char *const argv[] = { "/bin/sh", "sh", "-c", command, NULL };
 507 
 508     p = g_try_new (mc_pipe_t, 1);
 509     if (p == NULL)
 510     {
 511         mc_replace_error (error, MC_PIPE_ERROR_CREATE_PIPE, "%s",
 512                           _("Cannot create pipe descriptor"));
 513         goto ret_err;
 514     }
 515 
 516     p->out.fd = -1;
 517     p->err.fd = -1;
 518 
 519     if (!g_spawn_async_with_pipes
 520         (NULL, (gchar **) argv, NULL, G_SPAWN_DO_NOT_REAP_CHILD | G_SPAWN_FILE_AND_ARGV_ZERO, NULL,
 521          NULL, &p->child_pid, NULL, read_out ? &p->out.fd : NULL, read_err ? &p->err.fd : NULL,
 522          error))
 523     {
 524         mc_replace_error (error, MC_PIPE_ERROR_CREATE_PIPE_STREAM, "%s",
 525                           _("Cannot create pipe streams"));
 526         goto ret_err;
 527     }
 528 
 529     p->out.buf[0] = '\0';
 530     p->out.len = MC_PIPE_BUFSIZE;
 531     p->out.null_term = FALSE;
 532 
 533     p->err.buf[0] = '\0';
 534     p->err.len = MC_PIPE_BUFSIZE;
 535     p->err.null_term = FALSE;
 536 
 537     return p;
 538 
 539   ret_err:
 540     g_free (p);
 541     return NULL;
 542 }
 543 
 544 /* --------------------------------------------------------------------------------------------- */
 545 /**
 546  * Read stdout and stderr of pipe asynchronously.
 547  *
 548  * @parameter p pipe descriptor
 549  *
 550  * The lengths of read data contain in p->out.len and p->err.len.
 551  *
 552  * Before read, p->xxx.len is an input. It defines the number of data to read.
 553  * Should not be greater than MC_PIPE_BUFSIZE.
 554  *
 555  * After read, p->xxx.len is an output and contains the following:
 556  *   p->xxx.len > 0: an actual length of read data stored in p->xxx.buf;
 557  *   p->xxx.len == MC_PIPE_STREAM_EOF: EOF of stream p->xxx;
 558  *   p->xxx.len == MC_PIPE_STREAM_UNREAD: stream p->xxx was not read;
 559  *   p->xxx.len == MC_PIPE_ERROR_READ: reading error, and p->xxx.errno is set appropriately.
 560  *
 561  * @paremeter error contains pointer to object to handle error code and message
 562  */
 563 
 564 void
 565 mc_pread (mc_pipe_t * p, GError ** error)
     /* [previous][next][first][last][top][bottom][index][help]  */
 566 {
 567     gboolean read_out, read_err;
 568     fd_set fds;
 569     int maxfd = 0;
 570     int res;
 571 
 572     if (error != NULL)
 573         *error = NULL;
 574 
 575     read_out = p->out.fd >= 0;
 576     read_err = p->err.fd >= 0;
 577 
 578     if (!read_out && !read_err)
 579     {
 580         p->out.len = MC_PIPE_STREAM_UNREAD;
 581         p->err.len = MC_PIPE_STREAM_UNREAD;
 582         return;
 583     }
 584 
 585     FD_ZERO (&fds);
 586     if (read_out)
 587     {
 588         FD_SET (p->out.fd, &fds);
 589         maxfd = p->out.fd;
 590     }
 591 
 592     if (read_err)
 593     {
 594         FD_SET (p->err.fd, &fds);
 595         maxfd = MAX (maxfd, p->err.fd);
 596     }
 597 
 598     /* no timeout */
 599     res = select (maxfd + 1, &fds, NULL, NULL, NULL);
 600     if (res < 0 && errno != EINTR)
 601     {
 602         mc_propagate_error (error, MC_PIPE_ERROR_READ,
 603                             _
 604                             ("Unexpected error in select() reading data from a child process:\n%s"),
 605                             unix_error_string (errno));
 606         return;
 607     }
 608 
 609     if (read_out)
 610         mc_pread_stream (&p->out, &fds);
 611     else
 612         p->out.len = MC_PIPE_STREAM_UNREAD;
 613 
 614     if (read_err)
 615         mc_pread_stream (&p->err, &fds);
 616     else
 617         p->err.len = MC_PIPE_STREAM_UNREAD;
 618 }
 619 
 620 /* --------------------------------------------------------------------------------------------- */
 621 /**
 622  * Reads a line from @stream. Reading stops after an EOL or a newline. If a newline is read,
 623  * it is appended to the line.
 624  *
 625  * @stream mc_pipe_stream_t object
 626  *
 627  * @return newly created GString or NULL in case of EOL;
 628  */
 629 
 630 GString *
 631 mc_pstream_get_string (mc_pipe_stream_t * ps)
     /* [previous][next][first][last][top][bottom][index][help]  */
 632 {
 633     char *s;
 634     size_t size, i;
 635     gboolean escape = FALSE;
 636 
 637     g_return_val_if_fail (ps != NULL, NULL);
 638 
 639     if (ps->len < 0)
 640         return NULL;
 641 
 642     size = ps->len - ps->pos;
 643 
 644     if (size == 0)
 645         return NULL;
 646 
 647     s = ps->buf + ps->pos;
 648 
 649     if (s[0] == '\0')
 650         return NULL;
 651 
 652     /* find '\0' or unescaped '\n' */
 653     for (i = 0; i < size && !(s[i] == '\0' || (s[i] == '\n' && !escape)); i++)
 654         escape = s[i] == '\\' ? !escape : FALSE;
 655 
 656     if (i != size && s[i] == '\n')
 657         i++;
 658 
 659     ps->pos += i;
 660 
 661     return g_string_new_len (s, i);
 662 }
 663 
 664 /* --------------------------------------------------------------------------------------------- */
 665 /**
 666  * Close pipe and destroy pipe descriptor.
 667  *
 668  * @paremeter p pipe descriptor
 669  * @paremeter error contains pointer to object to handle error code and message
 670  */
 671 
 672 void
 673 mc_pclose (mc_pipe_t * p, GError ** error)
     /* [previous][next][first][last][top][bottom][index][help]  */
 674 {
 675     int res;
 676 
 677     if (p->out.fd >= 0)
 678         res = close (p->out.fd);
 679     if (p->err.fd >= 0)
 680         res = close (p->err.fd);
 681 
 682     do
 683     {
 684         int status;
 685 
 686         res = waitpid (p->child_pid, &status, 0);
 687     }
 688     while (res < 0 && errno == EINTR);
 689 
 690     if (res < 0)
 691         mc_replace_error (error, MC_PIPE_ERROR_READ, _("Unexpected error in waitpid():\n%s"),
 692                           unix_error_string (errno));
 693 
 694     g_free (p);
 695 }
 696 
 697 /* --------------------------------------------------------------------------------------------- */
 698 
 699 /**
 700  * Perform tilde expansion if possible.
 701  *
 702  * @param directory pointer to the path
 703  *
 704  * @return newly allocated string, even if it's unchanged.
 705  */
 706 
 707 char *
 708 tilde_expand (const char *directory)
     /* [previous][next][first][last][top][bottom][index][help]  */
 709 {
 710     struct passwd *passwd;
 711     const char *p, *q;
 712 
 713     if (*directory != '~')
 714         return g_strdup (directory);
 715 
 716     p = directory + 1;
 717 
 718     /* d = "~" or d = "~/" */
 719     if (*p == '\0' || IS_PATH_SEP (*p))
 720     {
 721         passwd = getpwuid (geteuid ());
 722         q = IS_PATH_SEP (*p) ? p + 1 : "";
 723     }
 724     else
 725     {
 726         q = strchr (p, PATH_SEP);
 727         if (q == NULL)
 728             passwd = getpwnam (p);
 729         else
 730         {
 731             char *name;
 732 
 733             name = g_strndup (p, q - p);
 734             passwd = getpwnam (name);
 735             q++;
 736             g_free (name);
 737         }
 738     }
 739 
 740     /* If we can't figure the user name, leave tilde unexpanded */
 741     if (passwd == NULL)
 742         return g_strdup (directory);
 743 
 744     return g_strconcat (passwd->pw_dir, PATH_SEP_STR, q, (char *) NULL);
 745 }
 746 
 747 /* --------------------------------------------------------------------------------------------- */
 748 /**
 749  * Canonicalize path, and return a new path.  Do everything in place.
 750  * The new path differs from path in:
 751  *      Multiple '/'s are collapsed to a single '/'.
 752  *      Leading './'s and trailing '/.'s are removed.
 753  *      Trailing '/'s are removed.
 754  *      Non-leading '../'s and trailing '..'s are handled by removing
 755  *      portions of the path.
 756  * Well formed UNC paths are modified only in the local part.
 757  */
 758 
 759 void
 760 custom_canonicalize_pathname (char *path, CANON_PATH_FLAGS flags)
     /* [previous][next][first][last][top][bottom][index][help]  */
 761 {
 762     char *p, *s;
 763     char *lpath = path;         /* path without leading UNC part */
 764     const size_t url_delim_len = strlen (VFS_PATH_URL_DELIMITER);
 765 
 766     /* Detect and preserve UNC paths: //server/... */
 767     if ((flags & CANON_PATH_GUARDUNC) != 0 && IS_PATH_SEP (path[0]) && IS_PATH_SEP (path[1]))
 768     {
 769         for (p = path + 2; p[0] != '\0' && !IS_PATH_SEP (p[0]); p++)
 770             ;
 771         if (IS_PATH_SEP (p[0]) && p > path + 2)
 772             lpath = p;
 773     }
 774 
 775     if (lpath[0] == '\0' || lpath[1] == '\0')
 776         return;
 777 
 778     if ((flags & CANON_PATH_JOINSLASHES) != 0)
 779     {
 780         /* Collapse multiple slashes */
 781         for (p = lpath; *p != '\0'; p++)
 782             if (IS_PATH_SEP (p[0]) && IS_PATH_SEP (p[1]) && (p == lpath || *(p - 1) != ':'))
 783             {
 784                 s = p + 1;
 785                 while (IS_PATH_SEP (*(++s)))
 786                     ;
 787                 str_move (p + 1, s);
 788             }
 789 
 790         /* Collapse "/./" -> "/" */
 791         for (p = lpath; *p != '\0';)
 792             if (IS_PATH_SEP (p[0]) && p[1] == '.' && IS_PATH_SEP (p[2]))
 793                 str_move (p, p + 2);
 794             else
 795                 p++;
 796     }
 797 
 798     if ((flags & CANON_PATH_REMSLASHDOTS) != 0)
 799     {
 800         size_t len;
 801 
 802         /* Remove trailing slashes */
 803         for (p = lpath + strlen (lpath) - 1; p > lpath && IS_PATH_SEP (*p); p--)
 804         {
 805             if (p >= lpath + url_delim_len - 1
 806                 && strncmp (p - url_delim_len + 1, VFS_PATH_URL_DELIMITER, url_delim_len) == 0)
 807                 break;
 808             *p = '\0';
 809         }
 810 
 811         /* Remove leading "./" */
 812         if (lpath[0] == '.' && IS_PATH_SEP (lpath[1]))
 813         {
 814             if (lpath[2] == '\0')
 815             {
 816                 lpath[1] = '\0';
 817                 return;
 818             }
 819 
 820             str_move (lpath, lpath + 2);
 821         }
 822 
 823         /* Remove trailing "/" or "/." */
 824         len = strlen (lpath);
 825         if (len < 2)
 826             return;
 827 
 828         if (IS_PATH_SEP (lpath[len - 1])
 829             && (len < url_delim_len
 830                 || strncmp (lpath + len - url_delim_len, VFS_PATH_URL_DELIMITER,
 831                             url_delim_len) != 0))
 832             lpath[len - 1] = '\0';
 833         else if (lpath[len - 1] == '.' && IS_PATH_SEP (lpath[len - 2]))
 834         {
 835             if (len == 2)
 836             {
 837                 lpath[1] = '\0';
 838                 return;
 839             }
 840 
 841             lpath[len - 2] = '\0';
 842         }
 843     }
 844 
 845     /* Collapse "/.." with the previous part of path */
 846     if ((flags & CANON_PATH_REMDOUBLEDOTS) != 0)
 847     {
 848 #ifdef HAVE_CHARSET
 849         const size_t enc_prefix_len = strlen (VFS_ENCODING_PREFIX);
 850 #endif /* HAVE_CHARSET */
 851 
 852         for (p = lpath; p[0] != '\0' && p[1] != '\0' && p[2] != '\0';)
 853         {
 854             if (!IS_PATH_SEP (p[0]) || p[1] != '.' || p[2] != '.'
 855                 || (!IS_PATH_SEP (p[3]) && p[3] != '\0'))
 856             {
 857                 p++;
 858                 continue;
 859             }
 860 
 861             /* search for the previous token */
 862             s = p - 1;
 863             if (s >= lpath + url_delim_len - 2
 864                 && strncmp (s - url_delim_len + 2, VFS_PATH_URL_DELIMITER, url_delim_len) == 0)
 865             {
 866                 s -= (url_delim_len - 2);
 867                 while (s >= lpath && !IS_PATH_SEP (*s--))
 868                     ;
 869             }
 870 
 871             while (s >= lpath)
 872             {
 873                 if (s - url_delim_len > lpath
 874                     && strncmp (s - url_delim_len, VFS_PATH_URL_DELIMITER, url_delim_len) == 0)
 875                 {
 876                     char *vfs_prefix = s - url_delim_len;
 877                     vfs_class *vclass;
 878 
 879                     while (vfs_prefix > lpath && !IS_PATH_SEP (*--vfs_prefix))
 880                         ;
 881                     if (IS_PATH_SEP (*vfs_prefix))
 882                         vfs_prefix++;
 883                     *(s - url_delim_len) = '\0';
 884 
 885                     vclass = vfs_prefix_to_class (vfs_prefix);
 886                     *(s - url_delim_len) = *VFS_PATH_URL_DELIMITER;
 887 
 888                     if (vclass != NULL && (vclass->flags & VFSF_REMOTE) != 0)
 889                     {
 890                         s = vfs_prefix;
 891                         continue;
 892                     }
 893                 }
 894 
 895                 if (IS_PATH_SEP (*s))
 896                     break;
 897 
 898                 s--;
 899             }
 900 
 901             s++;
 902 
 903             /* If the previous token is "..", we cannot collapse it */
 904             if (s[0] == '.' && s[1] == '.' && s + 2 == p)
 905             {
 906                 p += 3;
 907                 continue;
 908             }
 909 
 910             if (p[3] != '\0')
 911             {
 912                 if (s == lpath && IS_PATH_SEP (*s))
 913                 {
 914                     /* "/../foo" -> "/foo" */
 915                     str_move (s + 1, p + 4);
 916                 }
 917                 else
 918                 {
 919                     /* "token/../foo" -> "foo" */
 920 #ifdef HAVE_CHARSET
 921                     if ((strncmp (s, VFS_ENCODING_PREFIX, enc_prefix_len) == 0)
 922                         && (is_supported_encoding (s + enc_prefix_len)))
 923                         /* special case: remove encoding */
 924                         str_move (s, p + 1);
 925                     else
 926 #endif /* HAVE_CHARSET */
 927                         str_move (s, p + 4);
 928                 }
 929 
 930                 p = s > lpath ? s - 1 : s;
 931                 continue;
 932             }
 933 
 934             /* trailing ".." */
 935             if (s == lpath)
 936             {
 937                 /* "token/.." -> "." */
 938                 if (!IS_PATH_SEP (lpath[0]))
 939                     lpath[0] = '.';
 940                 lpath[1] = '\0';
 941             }
 942             else
 943             {
 944                 /* "foo/token/.." -> "foo" */
 945                 if (s == lpath + 1)
 946                     s[0] = '\0';
 947 #ifdef HAVE_CHARSET
 948                 else if ((strncmp (s, VFS_ENCODING_PREFIX, enc_prefix_len) == 0)
 949                          && (is_supported_encoding (s + enc_prefix_len)))
 950                 {
 951                     /* special case: remove encoding */
 952                     s[0] = '.';
 953                     s[1] = '.';
 954                     s[2] = '\0';
 955 
 956                     /* search for the previous token */
 957                     /* IS_PATH_SEP (s[-1]) */
 958                     for (p = s - 1; p >= lpath && !IS_PATH_SEP (*p); p--)
 959                         ;
 960 
 961                     if (p >= lpath)
 962                         continue;
 963                 }
 964 #endif /* HAVE_CHARSET */
 965                 else
 966                 {
 967                     if (s >= lpath + url_delim_len
 968                         && strncmp (s - url_delim_len, VFS_PATH_URL_DELIMITER, url_delim_len) == 0)
 969                         *s = '\0';
 970                     else
 971                         s[-1] = '\0';
 972                 }
 973             }
 974 
 975             break;
 976         }
 977     }
 978 }
 979 
 980 /* --------------------------------------------------------------------------------------------- */
 981 
 982 void
 983 canonicalize_pathname (char *path)
     /* [previous][next][first][last][top][bottom][index][help]  */
 984 {
 985     custom_canonicalize_pathname (path, CANON_PATH_ALL);
 986 }
 987 
 988 /* --------------------------------------------------------------------------------------------- */
 989 
 990 char *
 991 mc_realpath (const char *path, char *resolved_path)
     /* [previous][next][first][last][top][bottom][index][help]  */
 992 {
 993 #ifdef HAVE_CHARSET
 994     const char *p = path;
 995     gboolean absolute_path = FALSE;
 996 
 997     if (IS_PATH_SEP (*p))
 998     {
 999         absolute_path = TRUE;
1000         p++;
1001     }
1002 
1003     /* ignore encoding: skip "#enc:" */
1004     if (g_str_has_prefix (p, VFS_ENCODING_PREFIX))
1005     {
1006         p += strlen (VFS_ENCODING_PREFIX);
1007         p = strchr (p, PATH_SEP);
1008         if (p != NULL)
1009         {
1010             if (!absolute_path && p[1] != '\0')
1011                 p++;
1012 
1013             path = p;
1014         }
1015     }
1016 #endif /* HAVE_CHARSET */
1017 
1018 #ifdef HAVE_REALPATH
1019     return realpath (path, resolved_path);
1020 #else
1021     {
1022         char copy_path[PATH_MAX];
1023         char got_path[PATH_MAX];
1024         char *new_path = got_path;
1025         char *max_path;
1026 #ifdef S_IFLNK
1027         char link_path[PATH_MAX];
1028         int readlinks = 0;
1029         int n;
1030 #endif /* S_IFLNK */
1031 
1032         /* Make a copy of the source path since we may need to modify it. */
1033         if (strlen (path) >= PATH_MAX - 2)
1034         {
1035             errno = ENAMETOOLONG;
1036             return NULL;
1037         }
1038 
1039         strcpy (copy_path, path);
1040         path = copy_path;
1041         max_path = copy_path + PATH_MAX - 2;
1042         /* If it's a relative pathname use getwd for starters. */
1043         if (!IS_PATH_SEP (*path))
1044         {
1045             new_path = g_get_current_dir ();
1046             if (new_path == NULL)
1047                 strcpy (got_path, "");
1048             else
1049             {
1050                 g_snprintf (got_path, sizeof (got_path), "%s", new_path);
1051                 g_free (new_path);
1052                 new_path = got_path;
1053             }
1054 
1055             new_path += strlen (got_path);
1056             if (!IS_PATH_SEP (new_path[-1]))
1057                 *new_path++ = PATH_SEP;
1058         }
1059         else
1060         {
1061             *new_path++ = PATH_SEP;
1062             path++;
1063         }
1064         /* Expand each slash-separated pathname component. */
1065         while (*path != '\0')
1066         {
1067             /* Ignore stray "/". */
1068             if (IS_PATH_SEP (*path))
1069             {
1070                 path++;
1071                 continue;
1072             }
1073             if (*path == '.')
1074             {
1075                 /* Ignore ".". */
1076                 if (path[1] == '\0' || IS_PATH_SEP (path[1]))
1077                 {
1078                     path++;
1079                     continue;
1080                 }
1081                 if (path[1] == '.')
1082                 {
1083                     if (path[2] == '\0' || IS_PATH_SEP (path[2]))
1084                     {
1085                         path += 2;
1086                         /* Ignore ".." at root. */
1087                         if (new_path == got_path + 1)
1088                             continue;
1089                         /* Handle ".." by backing up. */
1090                         while (!IS_PATH_SEP ((--new_path)[-1]))
1091                             ;
1092                         continue;
1093                     }
1094                 }
1095             }
1096             /* Safely copy the next pathname component. */
1097             while (*path != '\0' && !IS_PATH_SEP (*path))
1098             {
1099                 if (path > max_path)
1100                 {
1101                     errno = ENAMETOOLONG;
1102                     return NULL;
1103                 }
1104                 *new_path++ = *path++;
1105             }
1106 #ifdef S_IFLNK
1107             /* Protect against infinite loops. */
1108             if (readlinks++ > MAXSYMLINKS)
1109             {
1110                 errno = ELOOP;
1111                 return NULL;
1112             }
1113             /* See if latest pathname component is a symlink. */
1114             *new_path = '\0';
1115             n = readlink (got_path, link_path, PATH_MAX - 1);
1116             if (n < 0)
1117             {
1118                 /* EINVAL means the file exists but isn't a symlink. */
1119                 if (errno != EINVAL)
1120                 {
1121                     /* Make sure it's null terminated. */
1122                     *new_path = '\0';
1123                     strcpy (resolved_path, got_path);
1124                     return NULL;
1125                 }
1126             }
1127             else
1128             {
1129                 /* Note: readlink doesn't add the null byte. */
1130                 link_path[n] = '\0';
1131                 if (IS_PATH_SEP (*link_path))
1132                     /* Start over for an absolute symlink. */
1133                     new_path = got_path;
1134                 else
1135                     /* Otherwise back up over this component. */
1136                     while (!IS_PATH_SEP (*(--new_path)))
1137                         ;
1138                 /* Safe sex check. */
1139                 if (strlen (path) + n >= PATH_MAX - 2)
1140                 {
1141                     errno = ENAMETOOLONG;
1142                     return NULL;
1143                 }
1144                 /* Insert symlink contents into path. */
1145                 strcat (link_path, path);
1146                 strcpy (copy_path, link_path);
1147                 path = copy_path;
1148             }
1149 #endif /* S_IFLNK */
1150             *new_path++ = PATH_SEP;
1151         }
1152         /* Delete trailing slash but don't whomp a lone slash. */
1153         if (new_path != got_path + 1 && IS_PATH_SEP (new_path[-1]))
1154             new_path--;
1155         /* Make sure it's null terminated. */
1156         *new_path = '\0';
1157         strcpy (resolved_path, got_path);
1158         return resolved_path;
1159     }
1160 #endif /* HAVE_REALPATH */
1161 }
1162 
1163 /* --------------------------------------------------------------------------------------------- */
1164 /**
1165  * Return the index of the permissions triplet
1166  *
1167  */
1168 
1169 int
1170 get_user_permissions (struct stat *st)
     /* [previous][next][first][last][top][bottom][index][help]  */
1171 {
1172     static gboolean initialized = FALSE;
1173     static gid_t *groups;
1174     static int ngroups;
1175     static uid_t uid;
1176     int i;
1177 
1178     if (!initialized)
1179     {
1180         uid = geteuid ();
1181 
1182         ngroups = getgroups (0, NULL);
1183         if (ngroups == -1)
1184             ngroups = 0;        /* ignore errors */
1185 
1186         /* allocate space for one element in addition to what
1187          * will be filled by getgroups(). */
1188         groups = g_new (gid_t, ngroups + 1);
1189 
1190         if (ngroups != 0)
1191         {
1192             ngroups = getgroups (ngroups, groups);
1193             if (ngroups == -1)
1194                 ngroups = 0;    /* ignore errors */
1195         }
1196 
1197         /* getgroups() may or may not return the effective group ID,
1198          * so we always include it at the end of the list. */
1199         groups[ngroups++] = getegid ();
1200 
1201         initialized = TRUE;
1202     }
1203 
1204     if (st->st_uid == uid || uid == 0)
1205         return 0;
1206 
1207     for (i = 0; i < ngroups; i++)
1208         if (st->st_gid == groups[i])
1209             return 1;
1210 
1211     return 2;
1212 }
1213 
1214 /* --------------------------------------------------------------------------------------------- */
1215 /**
1216  * Build filename from arguments.
1217  * Like to g_build_filename(), but respect VFS_PATH_URL_DELIMITER
1218  */
1219 
1220 char *
1221 mc_build_filenamev (const char *first_element, va_list args)
     /* [previous][next][first][last][top][bottom][index][help]  */
1222 {
1223     gboolean absolute;
1224     const char *element = first_element;
1225     GString *path;
1226     char *ret;
1227 
1228     if (element == NULL)
1229         return NULL;
1230 
1231     path = g_string_new ("");
1232 
1233     absolute = IS_PATH_SEP (*first_element);
1234 
1235     do
1236     {
1237         if (*element == '\0')
1238             element = va_arg (args, char *);
1239         else
1240         {
1241             char *tmp_element;
1242             size_t len;
1243             const char *start;
1244 
1245             tmp_element = g_strdup (element);
1246 
1247             element = va_arg (args, char *);
1248 
1249             canonicalize_pathname (tmp_element);
1250             len = strlen (tmp_element);
1251             start = IS_PATH_SEP (tmp_element[0]) ? tmp_element + 1 : tmp_element;
1252 
1253             g_string_append (path, start);
1254             if (!IS_PATH_SEP (tmp_element[len - 1]) && element != NULL)
1255                 g_string_append_c (path, PATH_SEP);
1256 
1257             g_free (tmp_element);
1258         }
1259     }
1260     while (element != NULL);
1261 
1262     if (absolute)
1263         g_string_prepend_c (path, PATH_SEP);
1264 
1265     ret = g_string_free (path, FALSE);
1266     canonicalize_pathname (ret);
1267 
1268     return ret;
1269 }
1270 
1271 /* --------------------------------------------------------------------------------------------- */
1272 /**
1273  * Build filename from arguments.
1274  * Like to g_build_filename(), but respect VFS_PATH_URL_DELIMITER
1275  */
1276 
1277 char *
1278 mc_build_filename (const char *first_element, ...)
     /* [previous][next][first][last][top][bottom][index][help]  */
1279 {
1280     va_list args;
1281     char *ret;
1282 
1283     if (first_element == NULL)
1284         return NULL;
1285 
1286     va_start (args, first_element);
1287     ret = mc_build_filenamev (first_element, args);
1288     va_end (args);
1289     return ret;
1290 }
1291 
1292 /* --------------------------------------------------------------------------------------------- */

/* [previous][next][first][last][top][bottom][index][help]  */