Manual pages: mcmcdiffmceditmcview

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

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