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. canonicalize_pathname_custom
  22. mc_realpath
  23. get_user_permissions
  24. mc_build_filenamev
  25. mc_build_filename

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

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