Manual pages: mc • mcdiff • mcedit • mcview

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_state
  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_signal
  13. my_sigaction
  14. my_fork
  15. my_execvp
  16. my_clonefile
  17. my_get_current_dir
  18. my_system
  19. my_systeml
  20. my_systemv
  21. my_systemv_flags
  22. mc_popen
  23. mc_pread
  24. mc_pstream_get_string
  25. mc_pclose
  26. tilde_expand
  27. canonicalize_pathname_custom
  28. mc_realpath
  29. get_user_permissions
  30. mc_build_filenamev
  31. mc_build_filename

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

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