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

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