Manual pages: mcmcdiffmceditmcview

root/lib/utilunix.c

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

DEFINITIONS

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

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