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_get_current_dir
  17. my_system
  18. my_systeml
  19. my_systemv
  20. my_systemv_flags
  21. mc_popen
  22. mc_pread
  23. mc_pstream_get_string
  24. mc_pclose
  25. tilde_expand
  26. canonicalize_pathname_custom
  27. mc_realpath
  28. get_user_permissions
  29. mc_build_filenamev
  30. mc_build_filename

   1 /*
   2    Various utilities - Unix variants
   3 
   4    Copyright (C) 1994-2024
   5    Free Software Foundation, Inc.
   6 
   7    Written by:
   8    Miguel de Icaza, 1994, 1995, 1996
   9    Janne Kukonlehto, 1994, 1995, 1996
  10    Dugan Porter, 1994, 1995, 1996
  11    Jakub Jelinek, 1994, 1995, 1996
  12    Mauricio Plaza, 1994, 1995, 1996
  13    Andrew Borodin <aborodin@vmail.ru> 2010-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 <http://www.gnu.org/licenses/>.
  32  */
  33 
  34 /** \file utilunix.c
  35  *  \brief Source: various utilities - Unix variant
  36  */
  37 
  38 #include <config.h>
  39 
  40 #include <ctype.h>
  41 #include <errno.h>
  42 #include <limits.h>
  43 #include <signal.h>
  44 #include <stdarg.h>
  45 #include <stdio.h>
  46 #include <stdlib.h>
  47 #include <string.h>
  48 #ifdef HAVE_SYS_PARAM_H
  49 #include <sys/param.h>
  50 #endif
  51 #include <sys/types.h>
  52 #include <sys/stat.h>
  53 #ifdef HAVE_SYS_SELECT_H
  54 #include <sys/select.h>
  55 #endif
  56 #include <sys/wait.h>
  57 #include <pwd.h>
  58 #include <grp.h>
  59 
  60 #include "lib/global.h"
  61 
  62 #include "lib/unixcompat.h"
  63 #include "lib/vfs/vfs.h"        /* VFS_ENCODING_PREFIX */
  64 #include "lib/strutil.h"        /* str_move(), str_tokenize() */
  65 #include "lib/util.h"
  66 #include "lib/widget.h"         /* message() */
  67 #include "lib/vfs/xdirentry.h"
  68 
  69 #ifdef HAVE_CHARSET
  70 #include "lib/charsets.h"
  71 #endif
  72 
  73 /*** 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 /* --------------------------------------------------------------------------------------------- */
 396 /**
 397  * Wrapper for g_get_current_dir() library function.
 398  */
 399 
 400 char *
 401 my_get_current_dir (void)
     /* [previous][next][first][last][top][bottom][index][help]  */
 402 {
 403     return g_get_current_dir ();
 404 }
 405 
 406 /* --------------------------------------------------------------------------------------------- */
 407 /**
 408  * Call external programs.
 409  *
 410  * @parameter flags   addition conditions for running external programs.
 411  * @parameter shell   shell (if flags contain EXECUTE_AS_SHELL), command to run otherwise.
 412  *                    Shell (or command) will be found in paths described in PATH variable
 413  *                    (if shell parameter doesn't begin from path delimiter)
 414  * @parameter command Command for shell (or first parameter for command, if flags contain EXECUTE_AS_SHELL)
 415  * @return 0 if successful, -1 otherwise
 416  */
 417 
 418 int
 419 my_system (int flags, const char *shell, const char *command)
     /* [previous][next][first][last][top][bottom][index][help]  */
 420 {
 421     return my_systeml (flags, shell, command, NULL);
 422 }
 423 
 424 /* --------------------------------------------------------------------------------------------- */
 425 /**
 426  * Call external programs with various parameters number.
 427  *
 428  * @parameter flags addition conditions for running external programs.
 429  * @parameter shell shell (if flags contain EXECUTE_AS_SHELL), command to run otherwise.
 430  *                  Shell (or command) will be found in paths described in PATH variable
 431  *                  (if shell parameter doesn't begin from path delimiter)
 432  * @parameter ...   Command for shell with addition parameters for shell
 433  *                  (or parameters for command, if flags contain EXECUTE_AS_SHELL).
 434  *                  Should be NULL terminated.
 435  * @return 0 if successful, -1 otherwise
 436  */
 437 
 438 int
 439 my_systeml (int flags, const char *shell, ...)
     /* [previous][next][first][last][top][bottom][index][help]  */
 440 {
 441     GPtrArray *args_array;
 442     int status = 0;
 443     va_list vargs;
 444     char *one_arg;
 445 
 446     args_array = g_ptr_array_new ();
 447 
 448     va_start (vargs, shell);
 449     while ((one_arg = va_arg (vargs, char *)) != NULL)
 450           g_ptr_array_add (args_array, one_arg);
 451     va_end (vargs);
 452 
 453     g_ptr_array_add (args_array, NULL);
 454     status = my_systemv_flags (flags, shell, (char *const *) args_array->pdata);
 455 
 456     g_ptr_array_free (args_array, TRUE);
 457 
 458     return status;
 459 }
 460 
 461 /* --------------------------------------------------------------------------------------------- */
 462 /**
 463  * Call external programs with array of strings as parameters.
 464  *
 465  * @parameter command command to run. Command will be found in paths described in PATH variable
 466  *                    (if command parameter doesn't begin from path delimiter)
 467  * @parameter argv    Array of strings (NULL-terminated) with parameters for command
 468  * @return 0 if successful, -1 otherwise
 469  */
 470 
 471 int
 472 my_systemv (const char *command, char *const argv[])
     /* [previous][next][first][last][top][bottom][index][help]  */
 473 {
 474     my_fork_state_t fork_state;
 475     int status = 0;
 476     my_system_sigactions_t sigactions;
 477 
 478     my_system__save_sigaction_handlers (&sigactions);
 479 
 480     fork_state = my_fork_state ();
 481     switch (fork_state)
 482     {
 483     case FORK_ERROR:
 484         status = -1;
 485         break;
 486     case FORK_CHILD:
 487         {
 488             my_signal (SIGINT, SIG_DFL);
 489             my_signal (SIGQUIT, SIG_DFL);
 490             my_signal (SIGTSTP, SIG_DFL);
 491             my_signal (SIGCHLD, SIG_DFL);
 492 
 493             my_execvp (command, argv);
 494             my_exit (127);      /* Exec error */
 495         }
 496         MC_FALLTHROUGH;
 497         /* no break here, or unreachable-code warning by no returning my_exit() */
 498     default:
 499         status = 0;
 500         break;
 501     }
 502     my_system__restore_sigaction_handlers (&sigactions);
 503 
 504     return status;
 505 }
 506 
 507 /* --------------------------------------------------------------------------------------------- */
 508 /**
 509  * Call external programs with flags and with array of strings as parameters.
 510  *
 511  * @parameter flags   addition conditions for running external programs.
 512  * @parameter command shell (if flags contain EXECUTE_AS_SHELL), command to run otherwise.
 513  *                    Shell (or command) will be found in paths described in PATH variable
 514  *                    (if shell parameter doesn't begin from path delimiter)
 515  * @parameter argv    Array of strings (NULL-terminated) with parameters for command
 516  * @return 0 if successful, -1 otherwise
 517  */
 518 
 519 int
 520 my_systemv_flags (int flags, const char *command, char *const argv[])
     /* [previous][next][first][last][top][bottom][index][help]  */
 521 {
 522     const char *execute_name;
 523     GPtrArray *args_array;
 524     int status = 0;
 525 
 526     args_array = my_system_make_arg_array (flags, command);
 527 
 528     execute_name = g_ptr_array_index (args_array, 0);
 529 
 530     for (; argv != NULL && *argv != NULL; argv++)
 531         g_ptr_array_add (args_array, *argv);
 532 
 533     g_ptr_array_add (args_array, NULL);
 534     status = my_systemv (execute_name, (char *const *) args_array->pdata);
 535 
 536     g_ptr_array_free (args_array, TRUE);
 537 
 538     return status;
 539 }
 540 
 541 /* --------------------------------------------------------------------------------------------- */
 542 /**
 543  * Create pipe and run child process.
 544  *
 545  * @parameter command command line of child process
 546  * @parameter read_out do or don't read the stdout of child process
 547  * @parameter read_err do or don't read the stderr of child process
 548  * @parameter error contains pointer to object to handle error code and message
 549  *
 550  * @return newly created object of mc_pipe_t class in success, NULL otherwise
 551  */
 552 
 553 mc_pipe_t *
 554 mc_popen (const char *command, gboolean read_out, gboolean read_err, GError **error)
     /* [previous][next][first][last][top][bottom][index][help]  */
 555 {
 556     mc_pipe_t *p;
 557     const char *const argv[] = { "/bin/sh", "sh", "-c", command, NULL };
 558 
 559     p = g_try_new (mc_pipe_t, 1);
 560     if (p == NULL)
 561     {
 562         mc_replace_error (error, MC_PIPE_ERROR_CREATE_PIPE, "%s",
 563                           _("Cannot create pipe descriptor"));
 564         goto ret_err;
 565     }
 566 
 567     p->out.fd = -1;
 568     p->err.fd = -1;
 569 
 570     if (!g_spawn_async_with_pipes
 571         (NULL, (gchar **) argv, NULL, G_SPAWN_DO_NOT_REAP_CHILD | G_SPAWN_FILE_AND_ARGV_ZERO, NULL,
 572          NULL, &p->child_pid, NULL, read_out ? &p->out.fd : NULL, read_err ? &p->err.fd : NULL,
 573          error))
 574     {
 575         mc_replace_error (error, MC_PIPE_ERROR_CREATE_PIPE_STREAM, "%s",
 576                           _("Cannot create pipe streams"));
 577         goto ret_err;
 578     }
 579 
 580     p->out.buf[0] = '\0';
 581     p->out.len = MC_PIPE_BUFSIZE;
 582     p->out.null_term = FALSE;
 583 
 584     p->err.buf[0] = '\0';
 585     p->err.len = MC_PIPE_BUFSIZE;
 586     p->err.null_term = FALSE;
 587 
 588     return p;
 589 
 590   ret_err:
 591     g_free (p);
 592     return NULL;
 593 }
 594 
 595 /* --------------------------------------------------------------------------------------------- */
 596 /**
 597  * Read stdout and stderr of pipe asynchronously.
 598  *
 599  * @parameter p pipe descriptor
 600  *
 601  * The lengths of read data contain in p->out.len and p->err.len.
 602  *
 603  * Before read, p->xxx.len is an input. It defines the number of data to read.
 604  * Should not be greater than MC_PIPE_BUFSIZE.
 605  *
 606  * After read, p->xxx.len is an output and contains the following:
 607  *   p->xxx.len > 0: an actual length of read data stored in p->xxx.buf;
 608  *   p->xxx.len == MC_PIPE_STREAM_EOF: EOF of stream p->xxx;
 609  *   p->xxx.len == MC_PIPE_STREAM_UNREAD: stream p->xxx was not read;
 610  *   p->xxx.len == MC_PIPE_ERROR_READ: reading error, and p->xxx.errno is set appropriately.
 611  *
 612  * @parameter error contains pointer to object to handle error code and message
 613  */
 614 
 615 void
 616 mc_pread (mc_pipe_t *p, GError **error)
     /* [previous][next][first][last][top][bottom][index][help]  */
 617 {
 618     gboolean read_out, read_err;
 619     fd_set fds;
 620     int maxfd = 0;
 621     int res;
 622 
 623     if (error != NULL)
 624         *error = NULL;
 625 
 626     read_out = p->out.fd >= 0;
 627     read_err = p->err.fd >= 0;
 628 
 629     if (!read_out && !read_err)
 630     {
 631         p->out.len = MC_PIPE_STREAM_UNREAD;
 632         p->err.len = MC_PIPE_STREAM_UNREAD;
 633         return;
 634     }
 635 
 636     FD_ZERO (&fds);
 637     if (read_out)
 638     {
 639         FD_SET (p->out.fd, &fds);
 640         maxfd = p->out.fd;
 641     }
 642 
 643     if (read_err)
 644     {
 645         FD_SET (p->err.fd, &fds);
 646         maxfd = MAX (maxfd, p->err.fd);
 647     }
 648 
 649     /* no timeout */
 650     res = select (maxfd + 1, &fds, NULL, NULL, NULL);
 651     if (res < 0 && errno != EINTR)
 652     {
 653         mc_propagate_error (error, MC_PIPE_ERROR_READ,
 654                             _
 655                             ("Unexpected error in select() reading data from a child process:\n%s"),
 656                             unix_error_string (errno));
 657         return;
 658     }
 659 
 660     if (read_out)
 661         mc_pread_stream (&p->out, &fds);
 662     else
 663         p->out.len = MC_PIPE_STREAM_UNREAD;
 664 
 665     if (read_err)
 666         mc_pread_stream (&p->err, &fds);
 667     else
 668         p->err.len = MC_PIPE_STREAM_UNREAD;
 669 }
 670 
 671 /* --------------------------------------------------------------------------------------------- */
 672 /**
 673  * Reads a line from @stream. Reading stops after an EOL or a newline. If a newline is read,
 674  * it is appended to the line.
 675  *
 676  * @stream mc_pipe_stream_t object
 677  *
 678  * @return newly created GString or NULL in case of EOL;
 679  */
 680 
 681 GString *
 682 mc_pstream_get_string (mc_pipe_stream_t *ps)
     /* [previous][next][first][last][top][bottom][index][help]  */
 683 {
 684     char *s;
 685     size_t size, i;
 686     gboolean escape = FALSE;
 687 
 688     g_return_val_if_fail (ps != NULL, NULL);
 689 
 690     if (ps->len < 0)
 691         return NULL;
 692 
 693     size = ps->len - ps->pos;
 694 
 695     if (size == 0)
 696         return NULL;
 697 
 698     s = ps->buf + ps->pos;
 699 
 700     if (s[0] == '\0')
 701         return NULL;
 702 
 703     /* find '\0' or unescaped '\n' */
 704     for (i = 0; i < size && !(s[i] == '\0' || (s[i] == '\n' && !escape)); i++)
 705         escape = s[i] == '\\' ? !escape : FALSE;
 706 
 707     if (i != size && s[i] == '\n')
 708         i++;
 709 
 710     ps->pos += i;
 711 
 712     return g_string_new_len (s, i);
 713 }
 714 
 715 /* --------------------------------------------------------------------------------------------- */
 716 /**
 717  * Close pipe and destroy pipe descriptor.
 718  *
 719  * @parameter p pipe descriptor
 720  * @parameter error contains pointer to object to handle error code and message
 721  */
 722 
 723 void
 724 mc_pclose (mc_pipe_t *p, GError **error)
     /* [previous][next][first][last][top][bottom][index][help]  */
 725 {
 726     int res;
 727 
 728     if (p == NULL)
 729     {
 730         mc_replace_error (error, MC_PIPE_ERROR_READ, "%s",
 731                           _("Cannot close pipe descriptor (p == NULL)"));
 732         return;
 733     }
 734 
 735     if (p->out.fd >= 0)
 736         res = close (p->out.fd);
 737     if (p->err.fd >= 0)
 738         res = close (p->err.fd);
 739 
 740     do
 741     {
 742         int status;
 743 
 744         res = waitpid (p->child_pid, &status, 0);
 745     }
 746     while (res < 0 && errno == EINTR);
 747 
 748     if (res < 0)
 749         mc_replace_error (error, MC_PIPE_ERROR_READ, _("Unexpected error in waitpid():\n%s"),
 750                           unix_error_string (errno));
 751 
 752     g_free (p);
 753 }
 754 
 755 /* --------------------------------------------------------------------------------------------- */
 756 
 757 /**
 758  * Perform tilde expansion if possible.
 759  *
 760  * @param directory pointer to the path
 761  *
 762  * @return newly allocated string, even if it's unchanged.
 763  */
 764 
 765 char *
 766 tilde_expand (const char *directory)
     /* [previous][next][first][last][top][bottom][index][help]  */
 767 {
 768     struct passwd *passwd;
 769     const char *p, *q;
 770 
 771     if (*directory != '~')
 772         return g_strdup (directory);
 773 
 774     p = directory + 1;
 775 
 776     /* d = "~" or d = "~/" */
 777     if (*p == '\0' || IS_PATH_SEP (*p))
 778     {
 779         passwd = getpwuid (geteuid ());
 780         q = IS_PATH_SEP (*p) ? p + 1 : "";
 781     }
 782     else
 783     {
 784         q = strchr (p, PATH_SEP);
 785         if (q == NULL)
 786             passwd = getpwnam (p);
 787         else
 788         {
 789             char *name;
 790 
 791             name = g_strndup (p, q - p);
 792             passwd = getpwnam (name);
 793             q++;
 794             g_free (name);
 795         }
 796     }
 797 
 798     /* If we can't figure the user name, leave tilde unexpanded */
 799     if (passwd == NULL)
 800         return g_strdup (directory);
 801 
 802     return g_strconcat (passwd->pw_dir, PATH_SEP_STR, q, (char *) NULL);
 803 }
 804 
 805 /* --------------------------------------------------------------------------------------------- */
 806 /**
 807  * Canonicalize path.
 808  *
 809  * @param path path to file
 810  * @param flags canonicalization flags
 811  *
 812  * All modifications of @path are made in place.
 813  * Well formed UNC paths are modified only in the local part.
 814  */
 815 
 816 void
 817 canonicalize_pathname_custom (char *path, canon_path_flags_t flags)
     /* [previous][next][first][last][top][bottom][index][help]  */
 818 {
 819     char *p, *s;
 820     char *lpath = path;         /* path without leading UNC part */
 821     const size_t url_delim_len = strlen (VFS_PATH_URL_DELIMITER);
 822 
 823     /* Detect and preserve UNC paths: //server/... */
 824     if ((flags & CANON_PATH_GUARDUNC) != 0 && IS_PATH_SEP (path[0]) && IS_PATH_SEP (path[1]))
 825     {
 826         for (p = path + 2; p[0] != '\0' && !IS_PATH_SEP (p[0]); p++)
 827             ;
 828         if (IS_PATH_SEP (p[0]) && p > path + 2)
 829             lpath = p;
 830     }
 831 
 832     if (lpath[0] == '\0' || lpath[1] == '\0')
 833         return;
 834 
 835     if ((flags & CANON_PATH_JOINSLASHES) != 0)
 836     {
 837         /* Collapse multiple slashes */
 838         for (p = lpath; *p != '\0'; p++)
 839             if (IS_PATH_SEP (p[0]) && IS_PATH_SEP (p[1]) && (p == lpath || *(p - 1) != ':'))
 840             {
 841                 s = p + 1;
 842                 while (IS_PATH_SEP (*(++s)))
 843                     ;
 844                 str_move (p + 1, s);
 845             }
 846 
 847         /* Collapse "/./" -> "/" */
 848         for (p = lpath; *p != '\0';)
 849             if (IS_PATH_SEP (p[0]) && p[1] == '.' && IS_PATH_SEP (p[2]))
 850                 str_move (p, p + 2);
 851             else
 852                 p++;
 853     }
 854 
 855     if ((flags & CANON_PATH_REMSLASHDOTS) != 0)
 856     {
 857         size_t len;
 858 
 859         /* Remove trailing slashes */
 860         for (p = lpath + strlen (lpath) - 1; p > lpath && IS_PATH_SEP (*p); p--)
 861         {
 862             if (p >= lpath + url_delim_len - 1
 863                 && strncmp (p - url_delim_len + 1, VFS_PATH_URL_DELIMITER, url_delim_len) == 0)
 864                 break;
 865             *p = '\0';
 866         }
 867 
 868         /* Remove leading "./" */
 869         if (lpath[0] == '.' && IS_PATH_SEP (lpath[1]))
 870         {
 871             if (lpath[2] == '\0')
 872             {
 873                 lpath[1] = '\0';
 874                 return;
 875             }
 876 
 877             str_move (lpath, lpath + 2);
 878         }
 879 
 880         /* Remove trailing "/" or "/." */
 881         len = strlen (lpath);
 882         if (len < 2)
 883             return;
 884 
 885         if (IS_PATH_SEP (lpath[len - 1])
 886             && (len < url_delim_len
 887                 || strncmp (lpath + len - url_delim_len, VFS_PATH_URL_DELIMITER,
 888                             url_delim_len) != 0))
 889             lpath[len - 1] = '\0';
 890         else if (lpath[len - 1] == '.' && IS_PATH_SEP (lpath[len - 2]))
 891         {
 892             if (len == 2)
 893             {
 894                 lpath[1] = '\0';
 895                 return;
 896             }
 897 
 898             lpath[len - 2] = '\0';
 899         }
 900     }
 901 
 902     /* Collapse "/.." with the previous part of path */
 903     if ((flags & CANON_PATH_REMDOUBLEDOTS) != 0)
 904     {
 905 #ifdef HAVE_CHARSET
 906         const size_t enc_prefix_len = strlen (VFS_ENCODING_PREFIX);
 907 #endif /* HAVE_CHARSET */
 908 
 909         for (p = lpath; p[0] != '\0' && p[1] != '\0' && p[2] != '\0';)
 910         {
 911             if (!IS_PATH_SEP (p[0]) || p[1] != '.' || p[2] != '.'
 912                 || (!IS_PATH_SEP (p[3]) && p[3] != '\0'))
 913             {
 914                 p++;
 915                 continue;
 916             }
 917 
 918             /* search for the previous token */
 919             s = p - 1;
 920             if (s >= lpath + url_delim_len - 2
 921                 && strncmp (s - url_delim_len + 2, VFS_PATH_URL_DELIMITER, url_delim_len) == 0)
 922             {
 923                 s -= (url_delim_len - 2);
 924                 while (s >= lpath && !IS_PATH_SEP (*s--))
 925                     ;
 926             }
 927 
 928             while (s >= lpath)
 929             {
 930                 if (s - url_delim_len > lpath
 931                     && strncmp (s - url_delim_len, VFS_PATH_URL_DELIMITER, url_delim_len) == 0)
 932                 {
 933                     char *vfs_prefix = s - url_delim_len;
 934                     vfs_class *vclass;
 935 
 936                     while (vfs_prefix > lpath && !IS_PATH_SEP (*--vfs_prefix))
 937                         ;
 938                     if (IS_PATH_SEP (*vfs_prefix))
 939                         vfs_prefix++;
 940                     *(s - url_delim_len) = '\0';
 941 
 942                     vclass = vfs_prefix_to_class (vfs_prefix);
 943                     *(s - url_delim_len) = *VFS_PATH_URL_DELIMITER;
 944 
 945                     if (vclass != NULL && (vclass->flags & VFSF_REMOTE) != 0)
 946                     {
 947                         s = vfs_prefix;
 948                         continue;
 949                     }
 950                 }
 951 
 952                 if (IS_PATH_SEP (*s))
 953                     break;
 954 
 955                 s--;
 956             }
 957 
 958             s++;
 959 
 960             /* If the previous token is "..", we cannot collapse it */
 961             if (s[0] == '.' && s[1] == '.' && s + 2 == p)
 962             {
 963                 p += 3;
 964                 continue;
 965             }
 966 
 967             if (p[3] != '\0')
 968             {
 969                 if (s == lpath && IS_PATH_SEP (*s))
 970                 {
 971                     /* "/../foo" -> "/foo" */
 972                     str_move (s + 1, p + 4);
 973                 }
 974                 else
 975                 {
 976                     /* "token/../foo" -> "foo" */
 977 #ifdef HAVE_CHARSET
 978                     if (strncmp (s, VFS_ENCODING_PREFIX, enc_prefix_len) == 0)
 979                     {
 980                         char *enc;
 981 
 982                         enc = vfs_get_encoding (s, -1);
 983 
 984                         if (is_supported_encoding (enc))
 985                             /* special case: remove encoding */
 986                             str_move (s, p + 1);
 987                         else
 988                             str_move (s, p + 4);
 989 
 990                         g_free (enc);
 991                     }
 992                     else
 993 #endif /* HAVE_CHARSET */
 994                         str_move (s, p + 4);
 995                 }
 996 
 997                 p = s > lpath ? s - 1 : s;
 998                 continue;
 999             }
1000 
1001             /* trailing ".." */
1002             if (s == lpath)
1003             {
1004                 /* "token/.." -> "." */
1005                 if (!IS_PATH_SEP (lpath[0]))
1006                     lpath[0] = '.';
1007                 lpath[1] = '\0';
1008             }
1009             else
1010             {
1011                 /* "foo/token/.." -> "foo" */
1012                 if (s == lpath + 1)
1013                     s[0] = '\0';
1014 #ifdef HAVE_CHARSET
1015                 else if (strncmp (s, VFS_ENCODING_PREFIX, enc_prefix_len) == 0)
1016                 {
1017                     char *enc;
1018                     gboolean ok;
1019 
1020                     enc = vfs_get_encoding (s, -1);
1021                     ok = is_supported_encoding (enc);
1022                     g_free (enc);
1023 
1024                     if (!ok)
1025                         goto last;
1026 
1027                     /* special case: remove encoding */
1028                     s[0] = '.';
1029                     s[1] = '.';
1030                     s[2] = '\0';
1031 
1032                     /* search for the previous token */
1033                     /* IS_PATH_SEP (s[-1]) */
1034                     for (p = s - 1; p >= lpath && !IS_PATH_SEP (*p); p--)
1035                         ;
1036 
1037                     if (p >= lpath)
1038                         continue;
1039                 }
1040 #endif /* HAVE_CHARSET */
1041                 else
1042                 {
1043 #ifdef HAVE_CHARSET
1044                   last:
1045 #endif /* HAVE_CHARSET */
1046                     if (s >= lpath + url_delim_len
1047                         && strncmp (s - url_delim_len, VFS_PATH_URL_DELIMITER, url_delim_len) == 0)
1048                         *s = '\0';
1049                     else
1050                         s[-1] = '\0';
1051                 }
1052             }
1053 
1054             break;
1055         }
1056     }
1057 }
1058 
1059 /* --------------------------------------------------------------------------------------------- */
1060 
1061 char *
1062 mc_realpath (const char *path, char *resolved_path)
     /* [previous][next][first][last][top][bottom][index][help]  */
1063 {
1064 #ifdef HAVE_CHARSET
1065     const char *p = path;
1066     gboolean absolute_path = FALSE;
1067 
1068     if (IS_PATH_SEP (*p))
1069     {
1070         absolute_path = TRUE;
1071         p++;
1072     }
1073 
1074     /* ignore encoding: skip "#enc:" */
1075     if (g_str_has_prefix (p, VFS_ENCODING_PREFIX))
1076     {
1077         p += strlen (VFS_ENCODING_PREFIX);
1078         p = strchr (p, PATH_SEP);
1079         if (p != NULL)
1080         {
1081             if (!absolute_path && p[1] != '\0')
1082                 p++;
1083 
1084             path = p;
1085         }
1086     }
1087 #endif /* HAVE_CHARSET */
1088 
1089 #ifdef HAVE_REALPATH
1090     return realpath (path, resolved_path);
1091 #else
1092     {
1093         char copy_path[PATH_MAX];
1094         char got_path[PATH_MAX];
1095         char *new_path = got_path;
1096         char *max_path;
1097 #ifdef S_IFLNK
1098         char link_path[PATH_MAX];
1099         int readlinks = 0;
1100         int n;
1101 #endif /* S_IFLNK */
1102 
1103         /* Make a copy of the source path since we may need to modify it. */
1104         if (strlen (path) >= PATH_MAX - 2)
1105         {
1106             errno = ENAMETOOLONG;
1107             return NULL;
1108         }
1109 
1110         strcpy (copy_path, path);
1111         path = copy_path;
1112         max_path = copy_path + PATH_MAX - 2;
1113         /* If it's a relative pathname use getwd for starters. */
1114         if (!IS_PATH_SEP (*path))
1115         {
1116             new_path = my_get_current_dir ();
1117             if (new_path == NULL)
1118                 strcpy (got_path, "");
1119             else
1120             {
1121                 g_snprintf (got_path, sizeof (got_path), "%s", new_path);
1122                 g_free (new_path);
1123                 new_path = got_path;
1124             }
1125 
1126             new_path += strlen (got_path);
1127             if (!IS_PATH_SEP (new_path[-1]))
1128                 *new_path++ = PATH_SEP;
1129         }
1130         else
1131         {
1132             *new_path++ = PATH_SEP;
1133             path++;
1134         }
1135         /* Expand each slash-separated pathname component. */
1136         while (*path != '\0')
1137         {
1138             /* Ignore stray "/". */
1139             if (IS_PATH_SEP (*path))
1140             {
1141                 path++;
1142                 continue;
1143             }
1144             if (*path == '.')
1145             {
1146                 /* Ignore ".". */
1147                 if (path[1] == '\0' || IS_PATH_SEP (path[1]))
1148                 {
1149                     path++;
1150                     continue;
1151                 }
1152                 if (path[1] == '.')
1153                 {
1154                     if (path[2] == '\0' || IS_PATH_SEP (path[2]))
1155                     {
1156                         path += 2;
1157                         /* Ignore ".." at root. */
1158                         if (new_path == got_path + 1)
1159                             continue;
1160                         /* Handle ".." by backing up. */
1161                         while (!IS_PATH_SEP ((--new_path)[-1]))
1162                             ;
1163                         continue;
1164                     }
1165                 }
1166             }
1167             /* Safely copy the next pathname component. */
1168             while (*path != '\0' && !IS_PATH_SEP (*path))
1169             {
1170                 if (path > max_path)
1171                 {
1172                     errno = ENAMETOOLONG;
1173                     return NULL;
1174                 }
1175                 *new_path++ = *path++;
1176             }
1177 #ifdef S_IFLNK
1178             /* Protect against infinite loops. */
1179             if (readlinks++ > MAXSYMLINKS)
1180             {
1181                 errno = ELOOP;
1182                 return NULL;
1183             }
1184             /* See if latest pathname component is a symlink. */
1185             *new_path = '\0';
1186             n = readlink (got_path, link_path, PATH_MAX - 1);
1187             if (n < 0)
1188             {
1189                 /* EINVAL means the file exists but isn't a symlink. */
1190                 if (errno != EINVAL)
1191                 {
1192                     /* Make sure it's null terminated. */
1193                     *new_path = '\0';
1194                     strcpy (resolved_path, got_path);
1195                     return NULL;
1196                 }
1197             }
1198             else
1199             {
1200                 /* Note: readlink doesn't add the null byte. */
1201                 link_path[n] = '\0';
1202                 if (IS_PATH_SEP (*link_path))
1203                     /* Start over for an absolute symlink. */
1204                     new_path = got_path;
1205                 else
1206                     /* Otherwise back up over this component. */
1207                     while (!IS_PATH_SEP (*(--new_path)))
1208                         ;
1209                 /* Safe sex check. */
1210                 if (strlen (path) + n >= PATH_MAX - 2)
1211                 {
1212                     errno = ENAMETOOLONG;
1213                     return NULL;
1214                 }
1215                 /* Insert symlink contents into path. */
1216                 strcat (link_path, path);
1217                 strcpy (copy_path, link_path);
1218                 path = copy_path;
1219             }
1220 #endif /* S_IFLNK */
1221             *new_path++ = PATH_SEP;
1222         }
1223         /* Delete trailing slash but don't whomp a lone slash. */
1224         if (new_path != got_path + 1 && IS_PATH_SEP (new_path[-1]))
1225             new_path--;
1226         /* Make sure it's null terminated. */
1227         *new_path = '\0';
1228         strcpy (resolved_path, got_path);
1229         return resolved_path;
1230     }
1231 #endif /* HAVE_REALPATH */
1232 }
1233 
1234 /* --------------------------------------------------------------------------------------------- */
1235 /**
1236  * Return the index of the permissions triplet
1237  *
1238  */
1239 
1240 int
1241 get_user_permissions (struct stat *st)
     /* [previous][next][first][last][top][bottom][index][help]  */
1242 {
1243     static gboolean initialized = FALSE;
1244     static gid_t *groups;
1245     static int ngroups;
1246     static uid_t uid;
1247     int i;
1248 
1249     if (!initialized)
1250     {
1251         uid = geteuid ();
1252 
1253         ngroups = getgroups (0, NULL);
1254         if (ngroups == -1)
1255             ngroups = 0;        /* ignore errors */
1256 
1257         /* allocate space for one element in addition to what
1258          * will be filled by getgroups(). */
1259         groups = g_new (gid_t, ngroups + 1);
1260 
1261         if (ngroups != 0)
1262         {
1263             ngroups = getgroups (ngroups, groups);
1264             if (ngroups == -1)
1265                 ngroups = 0;    /* ignore errors */
1266         }
1267 
1268         /* getgroups() may or may not return the effective group ID,
1269          * so we always include it at the end of the list. */
1270         groups[ngroups++] = getegid ();
1271 
1272         initialized = TRUE;
1273     }
1274 
1275     if (st->st_uid == uid || uid == 0)
1276         return 0;
1277 
1278     for (i = 0; i < ngroups; i++)
1279         if (st->st_gid == groups[i])
1280             return 1;
1281 
1282     return 2;
1283 }
1284 
1285 /* --------------------------------------------------------------------------------------------- */
1286 /**
1287  * Build filename from arguments.
1288  * Like to g_build_filename(), but respect VFS_PATH_URL_DELIMITER
1289  */
1290 
1291 char *
1292 mc_build_filenamev (const char *first_element, va_list args)
     /* [previous][next][first][last][top][bottom][index][help]  */
1293 {
1294     gboolean absolute;
1295     const char *element = first_element;
1296     GString *path;
1297     char *ret;
1298 
1299     if (first_element == NULL)
1300         return NULL;
1301 
1302     absolute = IS_PATH_SEP (*first_element);
1303 
1304     path = g_string_new (absolute ? PATH_SEP_STR : "");
1305 
1306     do
1307     {
1308         if (*element == '\0')
1309             element = va_arg (args, char *);
1310         else
1311         {
1312             char *tmp_element;
1313             const char *start;
1314 
1315             tmp_element = g_strdup (element);
1316 
1317             element = va_arg (args, char *);
1318 
1319             canonicalize_pathname (tmp_element);
1320             start = IS_PATH_SEP (tmp_element[0]) ? tmp_element + 1 : tmp_element;
1321 
1322             g_string_append (path, start);
1323             if (!IS_PATH_SEP (path->str[path->len - 1]) && element != NULL)
1324                 g_string_append_c (path, PATH_SEP);
1325 
1326             g_free (tmp_element);
1327         }
1328     }
1329     while (element != NULL);
1330 
1331     ret = g_string_free (path, FALSE);
1332     canonicalize_pathname (ret);
1333 
1334     return ret;
1335 }
1336 
1337 /* --------------------------------------------------------------------------------------------- */
1338 /**
1339  * Build filename from arguments.
1340  * Like to g_build_filename(), but respect VFS_PATH_URL_DELIMITER
1341  */
1342 
1343 char *
1344 mc_build_filename (const char *first_element, ...)
     /* [previous][next][first][last][top][bottom][index][help]  */
1345 {
1346     va_list args;
1347     char *ret;
1348 
1349     if (first_element == NULL)
1350         return NULL;
1351 
1352     va_start (args, first_element);
1353     ret = mc_build_filenamev (first_element, args);
1354     va_end (args);
1355     return ret;
1356 }
1357 
1358 /* --------------------------------------------------------------------------------------------- */

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