root/lib/utilunix.c

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

DEFINITIONS

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

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

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